diff --git a/.gitignore b/.gitignore index 81650e309..2ab52d776 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,174 @@ *.sln *.sw? ошибки.txt +rustbook-ru/book/tomorrow-night.css +rustbook-ru/book/title-page.html +rustbook-ru/book/theme/2018-edition.css +rustbook-ru/book/searchindex.json +rustbook-ru/book/searchindex.js +rustbook-ru/book/searcher.js +rustbook-ru/book/print.html +rustbook-ru/book/mark.min.js +rustbook-ru/book/index.html +rustbook-ru/book/img/trpl20-01.png +rustbook-ru/book/img/trpl15-04.svg +rustbook-ru/book/img/trpl15-03.svg +rustbook-ru/book/img/trpl15-02.svg +rustbook-ru/book/img/trpl15-01.svg +rustbook-ru/book/img/trpl14-10.png +rustbook-ru/book/img/trpl14-07.png +rustbook-ru/book/img/trpl14-05.png +rustbook-ru/book/img/trpl14-04.png +rustbook-ru/book/img/trpl14-03.png +rustbook-ru/book/img/trpl14-02.png +rustbook-ru/book/img/trpl14-01.png +rustbook-ru/book/img/trpl04-06.svg +rustbook-ru/book/img/trpl04-05.svg +rustbook-ru/book/img/trpl04-04.svg +rustbook-ru/book/img/trpl04-03.svg +rustbook-ru/book/img/trpl04-02.svg +rustbook-ru/book/img/trpl04-01.svg +rustbook-ru/book/img/ferris/unsafe.svg +rustbook-ru/book/img/ferris/panics.svg +rustbook-ru/book/img/ferris/not_desired_behavior.svg +rustbook-ru/book/img/ferris/does_not_compile.svg +rustbook-ru/book/highlight.js +rustbook-ru/book/highlight.css +rustbook-ru/book/foreword.html +rustbook-ru/book/fonts/source-code-pro-v11-all-charsets-500.woff2 +rustbook-ru/book/fonts/SOURCE-CODE-PRO-LICENSE.txt +rustbook-ru/book/fonts/open-sans-v17-all-charsets-regular.woff2 +rustbook-ru/book/fonts/open-sans-v17-all-charsets-italic.woff2 +rustbook-ru/book/fonts/open-sans-v17-all-charsets-800italic.woff2 +rustbook-ru/book/fonts/open-sans-v17-all-charsets-800.woff2 +rustbook-ru/book/fonts/open-sans-v17-all-charsets-700italic.woff2 +rustbook-ru/book/fonts/open-sans-v17-all-charsets-700.woff2 +rustbook-ru/book/fonts/open-sans-v17-all-charsets-600italic.woff2 +rustbook-ru/book/fonts/open-sans-v17-all-charsets-600.woff2 +rustbook-ru/book/fonts/open-sans-v17-all-charsets-300italic.woff2 +rustbook-ru/book/fonts/open-sans-v17-all-charsets-300.woff2 +rustbook-ru/book/fonts/OPEN-SANS-LICENSE.txt +rustbook-ru/book/fonts/fonts.css +rustbook-ru/book/FontAwesome/fonts/FontAwesome.ttf +rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.woff2 +rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.woff +rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.ttf +rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.svg +rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.eot +rustbook-ru/book/FontAwesome/css/font-awesome.css +rustbook-ru/book/ferris.js +rustbook-ru/book/ferris.css +rustbook-ru/book/favicon.svg +rustbook-ru/book/favicon.png +rustbook-ru/book/elasticlunr.min.js +rustbook-ru/book/css/variables.css +rustbook-ru/book/css/print.css +rustbook-ru/book/css/general.css +rustbook-ru/book/css/chrome.css +rustbook-ru/book/clipboard.min.js +rustbook-ru/book/ch20-03-graceful-shutdown-and-cleanup.html +rustbook-ru/book/ch20-02-multithreaded.html +rustbook-ru/book/ch20-01-single-threaded.html +rustbook-ru/book/ch20-00-final-project-a-web-server.html +rustbook-ru/book/ch19-06-macros.html +rustbook-ru/book/ch19-05-advanced-functions-and-closures.html +rustbook-ru/book/ch19-04-advanced-types.html +rustbook-ru/book/ch19-03-advanced-traits.html +rustbook-ru/book/ch19-01-unsafe-rust.html +rustbook-ru/book/ch19-00-advanced-features.html +rustbook-ru/book/ch18-03-pattern-syntax.html +rustbook-ru/book/ch18-02-refutability.html +rustbook-ru/book/ch18-01-all-the-places-for-patterns.html +rustbook-ru/book/ch18-00-patterns.html +rustbook-ru/book/ch17-03-oo-design-patterns.html +rustbook-ru/book/ch17-02-trait-objects.html +rustbook-ru/book/ch17-01-what-is-oo.html +rustbook-ru/book/ch17-00-oop.html +rustbook-ru/book/ch16-04-extensible-concurrency-sync-and-send.html +rustbook-ru/book/ch16-03-shared-state.html +rustbook-ru/book/ch16-02-message-passing.html +rustbook-ru/book/ch16-01-threads.html +rustbook-ru/book/ch16-00-concurrency.html +rustbook-ru/book/ch15-06-reference-cycles.html +rustbook-ru/book/ch15-05-interior-mutability.html +rustbook-ru/book/ch15-04-rc.html +rustbook-ru/book/ch15-03-drop.html +rustbook-ru/book/ch15-02-deref.html +rustbook-ru/book/ch15-01-box.html +rustbook-ru/book/ch15-00-smart-pointers.html +rustbook-ru/book/ch14-05-extending-cargo.html +rustbook-ru/book/ch14-04-installing-binaries.html +rustbook-ru/book/ch14-03-cargo-workspaces.html +rustbook-ru/book/ch14-02-publishing-to-crates-io.html +rustbook-ru/book/ch14-01-release-profiles.html +rustbook-ru/book/ch14-00-more-about-cargo.html +rustbook-ru/book/ch13-04-performance.html +rustbook-ru/book/ch13-03-improving-our-io-project.html +rustbook-ru/book/ch13-02-iterators.html +rustbook-ru/book/ch13-01-closures.html +rustbook-ru/book/ch13-00-functional-features.html +rustbook-ru/book/ch12-06-writing-to-stderr-instead-of-stdout.html +rustbook-ru/book/ch12-05-working-with-environment-variables.html +rustbook-ru/book/ch12-04-testing-the-librarys-functionality.html +rustbook-ru/book/ch12-03-improving-error-handling-and-modularity.html +rustbook-ru/book/ch12-02-reading-a-file.html +rustbook-ru/book/ch12-01-accepting-command-line-arguments.html +rustbook-ru/book/ch12-00-an-io-project.html +rustbook-ru/book/ch11-03-test-organization.html +rustbook-ru/book/ch11-02-running-tests.html +rustbook-ru/book/ch11-01-writing-tests.html +rustbook-ru/book/ch11-00-testing.html +rustbook-ru/book/ch10-03-lifetime-syntax.html +rustbook-ru/book/ch10-02-traits.html +rustbook-ru/book/ch10-01-syntax.html +rustbook-ru/book/ch10-00-generics.html +rustbook-ru/book/ch09-03-to-panic-or-not-to-panic.html +rustbook-ru/book/ch09-02-recoverable-errors-with-result.html +rustbook-ru/book/ch09-01-unrecoverable-errors-with-panic.html +rustbook-ru/book/ch09-00-error-handling.html +rustbook-ru/book/ch08-03-hash-maps.html +rustbook-ru/book/ch08-02-strings.html +rustbook-ru/book/ch08-01-vectors.html +rustbook-ru/book/ch08-00-common-collections.html +rustbook-ru/book/ch07-05-separating-modules-into-different-files.html +rustbook-ru/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html +rustbook-ru/book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html +rustbook-ru/book/ch07-02-defining-modules-to-control-scope-and-privacy.html +rustbook-ru/book/ch07-01-packages-and-crates.html +rustbook-ru/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html +rustbook-ru/book/ch06-03-if-let.html +rustbook-ru/book/ch06-02-match.html +rustbook-ru/book/ch06-01-defining-an-enum.html +rustbook-ru/book/ch06-00-enums.html +rustbook-ru/book/ch05-03-method-syntax.html +rustbook-ru/book/ch05-02-example-structs.html +rustbook-ru/book/ch05-01-defining-structs.html +rustbook-ru/book/ch05-00-structs.html +rustbook-ru/book/ch04-03-slices.html +rustbook-ru/book/ch04-02-references-and-borrowing.html +rustbook-ru/book/ch04-01-what-is-ownership.html +rustbook-ru/book/ch04-00-understanding-ownership.html +rustbook-ru/book/ch03-05-control-flow.html +rustbook-ru/book/ch03-04-comments.html +rustbook-ru/book/ch03-03-how-functions-work.html +rustbook-ru/book/ch03-02-data-types.html +rustbook-ru/book/ch03-01-variables-and-mutability.html +rustbook-ru/book/ch03-00-common-programming-concepts.html +rustbook-ru/book/ch02-00-guessing-game-tutorial.html +rustbook-ru/book/ch01-03-hello-cargo.html +rustbook-ru/book/ch01-02-hello-world.html +rustbook-ru/book/ch01-01-installation.html +rustbook-ru/book/ch01-00-getting-started.html +rustbook-ru/book/ch00-00-introduction.html +rustbook-ru/book/book.js +rustbook-ru/book/ayu-highlight.css +rustbook-ru/book/appendix-07-nightly-rust.html +rustbook-ru/book/appendix-06-translation.html +rustbook-ru/book/appendix-05-editions.html +rustbook-ru/book/appendix-04-useful-development-tools.html +rustbook-ru/book/appendix-03-derivable-traits.html +rustbook-ru/book/appendix-02-operators.html +rustbook-ru/book/appendix-01-keywords.html +rustbook-ru/book/appendix-00.html +rustbook-ru/book/404.html +rustbook-ru/book/.nojekyll diff --git a/rustbook-ru/README.md b/rustbook-ru/README.md index e62988b09..ad4e6a864 100644 --- a/rustbook-ru/README.md +++ b/rustbook-ru/README.md @@ -1,14 +1,14 @@ -# Язык программирования Rust +# Язык программирования Ржавчина -Данный хранилище содержит перевод второго издания “Язык программирования Rust”. +Данный хранилище содержит перевод второго издания “Язык программирования Ржавчина”. Второе издание - это переработанная книга "The Ржавчина Programming Language", которая будет напечатана издательством "No Starch Press" примерно в мае 2018 года. Последнюю сведения о дате выхода книги и о способе ее заказа вы можете узнать на сайте самого издательства [No Starch Press][nostarch]. [nostarch]: https://nostarch.com/rust -Книгу можно [читать онлайн](https://rustycrate.ru/book). +Книгу можно [читать в сети](https://rustycrate.ru/book). -Подлинник книги вы можете прочесть [онлайн][html]; несколько последних глав еще не закончены, но готовая часть книги заметно улучшена по сравнению с первым изданием. Авторы изначальной книги советуют начать чтение со второго издания. +Подлинник книги вы можете прочесть [в сети][html]; несколько последних глав еще не закончены, но готовая часть книги заметно улучшена по сравнению с первым изданием. Составители изначальной книги советуют начать чтение со второго издания. [html]: http://rust-lang.github.io/book/ @@ -25,13 +25,13 @@ $ cargo install mdbook ## Сборка Для того, чтобы собрать книгу, перейдите в нужный папка с помощью приказы cd - first-edition для первого, либо second-edition для второго издания. -Далее введите следующую приказ: +Далее введите следующий приказ: ```bash $ mdbook build ``` -Итоги выполнения приказы появятся в подпапке `book`. Для проверки откройте книгу в браузере. +Итоги выполнения приказы появятся в подпапке `book`. Для проверки откройте книгу в обозревателе. _Firefox:_ ```bash @@ -67,7 +67,7 @@ $ mdbook test [![ruRust/rust_book_ru](http://issuestats.com/github/ruRust/rust_book_ru/badge/issue?style=flat)](http://issuestats.com/github/ruRust/rust_book_ru) -# Соавторам +# Сосоставителям ## С чего начать @@ -79,7 +79,7 @@ $ mdbook test [Правила перевода](https://github.com/ruRust/rust_book_ru/wiki/Правила). -## Ресурсы +## Источники * первое издание rustbook расположено [здесь][original] * перевод первого издания расположен [здесь][rustbook] diff --git a/rustbook-ru/book.toml b/rustbook-ru/book.toml index 2fd9c8267..cbc05b1c5 100644 --- a/rustbook-ru/book.toml +++ b/rustbook-ru/book.toml @@ -1,6 +1,6 @@ [book] -title = "Язык программирования Rust" -author = "Стив Клабник, Кэрол Николс и другие участники сообщества Rust" +title = "Язык программирования Ржавчина" +author = "Стив Клабник, Кэрол Николс и другие участники сообщества Ржавчины" language = "ru-RU" [output.html] diff --git a/rustbook-ru/book/.nojekyll b/rustbook-ru/book/.nojekyll deleted file mode 100644 index f17311098..000000000 --- a/rustbook-ru/book/.nojekyll +++ /dev/null @@ -1 +0,0 @@ -This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/rustbook-ru/book/404.html b/rustbook-ru/book/404.html deleted file mode 100644 index c9ac91689..000000000 --- a/rustbook-ru/book/404.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - Page not found - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Document not found (404)

-

This URL is invalid, sorry. Please use the navigation bar or search to continue.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/FontAwesome/css/font-awesome.css b/rustbook-ru/book/FontAwesome/css/font-awesome.css deleted file mode 100644 index 540440ce8..000000000 --- a/rustbook-ru/book/FontAwesome/css/font-awesome.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/rustbook-ru/book/FontAwesome/fonts/FontAwesome.ttf b/rustbook-ru/book/FontAwesome/fonts/FontAwesome.ttf deleted file mode 100644 index 35acda2fa..000000000 Binary files a/rustbook-ru/book/FontAwesome/fonts/FontAwesome.ttf and /dev/null differ diff --git a/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.eot b/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca95..000000000 Binary files a/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.svg b/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845e5..000000000 --- a/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,2671 +0,0 @@ - - - - -Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 - By ,,, -Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.ttf b/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2fa..000000000 Binary files a/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.woff b/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a4b..000000000 Binary files a/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.woff2 b/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc604..000000000 Binary files a/rustbook-ru/book/FontAwesome/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/rustbook-ru/book/appendix-00.html b/rustbook-ru/book/appendix-00.html deleted file mode 100644 index 98de70edb..000000000 --- a/rustbook-ru/book/appendix-00.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - Приложения - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Дополнительная сведения

-

Следующие разделы содержат справочные источники, которые могут оказаться полезными в вашем путешествии по Rust.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/appendix-01-keywords.html b/rustbook-ru/book/appendix-01-keywords.html deleted file mode 100644 index cb9098105..000000000 --- a/rustbook-ru/book/appendix-01-keywords.html +++ /dev/null @@ -1,317 +0,0 @@ - - - - - - А — Ключевые слова - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Приложение A: Ключевые слова

-

Следующий список содержит ключевые слова, зарезервированные для текущего или будущего использования в языке Rust. Как таковые их нельзя использовать в качестве определителей (за исключением сырых определителей, которые мы обсудим в разделе «Сырые определители»). определительы — это имена функций, переменных, свойств, полей устройств, звеньев, ящиков, постоянных значений, макросов, постоянных значений, свойств, видов, свойств или времён жизни.

-

Используемые в настоящее время ключевые слова

-

Ниже приведён список используемых в настоящее время ключевых слов с их описанием.

-
    -
  • as — выполнить простое преобразование, уточнить определенную свойство, которую содержит предмет, или переименовать элемент в выражении use
  • -
  • async — возврат Future вместо блокировки текущего потока
  • -
  • await — остановка выполнения до готовности итога Future
  • -
  • break — немедленный выход из цикла
  • -
  • const — определение постоянного элемента или неизменяемого сырого указателя
  • -
  • continue — досрочный переход к следующей повторения цикла
  • -
  • crate — ссылка на корень дополнения в пути к звену
  • -
  • dyn — изменяемая отсылка к особенности предмета
  • -
  • else — иные ветви для устройств управления потока if и if let
  • -
  • enum — определение перечислений
  • -
  • extern — связывание внешней функции или переменной
  • -
  • false — логический ложный запись
  • -
  • fn — определение функции или вида указателя на функцию
  • -
  • for — замкнуто перебирать элементы из повторителя, выполнить признак или указывать время жизни с более высоким рейтингом.
  • -
  • if — ветвление на основе итога условного выражения
  • -
  • impl — выполнение встроенной возможности или возможности особенности
  • -
  • in — часть правил написания цикла for
  • -
  • let — объявление (связывание) переменной
  • -
  • loop — безусловный цикл
  • -
  • match — сопоставление значения с образцами
  • -
  • mod — определение звена
  • -
  • move — перекладывание владения на замыкание всеми захваченными элементами
  • -
  • mut — обозначение изменчивости в ссылках, сырах указателей и привязках к образцу
  • -
  • pub — изменитель открытой доступность полей устройств, разделов impl и звеньев
  • -
  • ref — привязка по ссылке
  • -
  • return — возвращает итог из функции
  • -
  • Self — псевдоним для определяемого или исполняемого вида
  • -
  • self — предмет текущего способа или звена
  • -
  • static — вездесущая переменная или время жизни, продолжающееся на протяжении всего выполнения программы
  • -
  • struct — определение устройства
  • -
  • super — родительский звено текущего звена
  • -
  • trait — определение особенности
  • -
  • true — логический истинный запись
  • -
  • type — определение псевдонима вида или связанного вида
  • -
  • union - определить объединение; является ключевым словом только при использовании в объявлении объединения
  • -
  • unsafe — обозначение небезопасного кода, функций, особенностей и их выполнений
  • -
  • use — ввод имён в область видимости
  • -
  • where — ограничение вида
  • -
  • while — условный цикл, основанный на итоге выражения
  • -
-

Ключевые слова, зарезервированные для будущего использования

-

Следующие ключевые слова ещё не имеют никакой возможности, но зарезервированы Ржавчина для возможного использования в будущем.

-
    -
  • abstract
  • -
  • become
  • -
  • box
  • -
  • do
  • -
  • final
  • -
  • macro
  • -
  • override
  • -
  • priv
  • -
  • try
  • -
  • typeof
  • -
  • unsized
  • -
  • virtual
  • -
  • yield
  • -
-

Сырые определители

-

Сырые определители — это правила написания, позволяющий использовать ключевые слова там, где обычно они не могут быть. Для создания и использования сырого определителя к ключевому слову добавляется приставка r#.

-

Например, ключевое слово match. Если вы попытаетесь собрать следующую функцию, использующую в качестве имени match:

-

Файл: src/main.rs

-
fn match(needle: &str, haystack: &str) -> bool {
-    haystack.contains(needle)
-}
-

вы получите ошибку:

-
error: expected identifier, found keyword `match`
- --> src/main.rs:4:4
-  |
-4 | fn match(needle: &str, haystack: &str) -> bool {
-  |    ^^^^^ expected identifier, found keyword
-
-

Ошибка говорит о том, что вы не можете использовать ключевое слово match в качестве определителя функции. Чтобы получить возможность использования слова match в качестве имени функции, нужно использовать правила написания «сырых определителей», например так:

-

Файл: src/main.rs

-
fn r#match(needle: &str, haystack: &str) -> bool {
-    haystack.contains(needle)
-}
-
-fn main() {
-    assert!(r#match("foo", "foobar"));
-}
-

Этот код собирается без ошибок. Обратите внимание, что приставка r# в определении имени функции указан так же, как он указан в месте её вызова в main.

-

Сырые определители позволяют вам использовать любое слово, которое вы выберете, в качестве определителя, даже если это слово окажется зарезервированным ключевым словом. Это даёт нам больше свободы в выборе имён определителей, а также позволяет нам встраиваться с программами, написанными на языке, где эти слова не являются ключевыми. Кроме того, необработанные определители позволяют вам использовать библиотеки, написанные в исполнения Rust, отличной от используемой в вашем ящике. Например, try не является ключевым словом в выпуске 2015 года, но является в выпуске 2018 года. Если вы зависите от библиотеки, написанной с использованием исполнения 2015 года и имеющей функцию try, вам потребуется использовать правила написания сырого определителя, в данном случае r#try, для вызова этой функции из кода исполнения 2018 года. См. Приложение E для получения дополнительной сведений о изданиех Rust.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/appendix-02-operators.html b/rustbook-ru/book/appendix-02-operators.html deleted file mode 100644 index a9c8a274a..000000000 --- a/rustbook-ru/book/appendix-02-operators.html +++ /dev/null @@ -1,399 +0,0 @@ - - - - - - B — Операторы и символы - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Дополнение Б: Операторы и обозначения

-

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

-

Операторы

-

Таблица Б-1 содержит операторы языка Rust, пример появления оператора, короткое объяснение, возможность перегрузки оператора. Если оператор можно перегрузить, то показан особенность, с помощью которого его можно перегрузить.

-

Таблица Б-1: Операторы

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ОператорПримерОбъяснениеПерегружаемость
!ident!(...), ident!{...}, ident![...]Вызов макроса
!!exprПобитовое или логическое отрицаниеNot
!=expr != exprСравнение "не равно"PartialEq
%expr % exprОстаток от деленияRem
%=var %= exprОстаток от деления и присваиваниеRemAssign
&&expr, &mut exprЗаимствование
&&type, &mut type, &'a type, &'a mut typeУказывает что данный вид заимствуется
&expr & exprПобитовое ИBitAnd
&=var &= exprПобитовое И и присваиваниеBitAndAssign
&&expr && exprЛогическое И
*expr * exprАрифметическое умножениеMul
*=var *= exprАрифметическое умножение и присваиваниеMulAssign
**exprРазыменование ссылкиDeref
**const type, *mut typeУказывает, что данный вид является сырым указателем
+trait + trait, 'a + traitСоединение ограничений вида
+expr + exprАрифметическое сложениеAdd
+=var += exprАрифметическое сложение и присваиваниеAddAssign
,expr, exprРазделитель переменных и элементов
-- exprАрифметическое отрицаниеNeg
-expr - exprАрифметическое вычитаниеSub
-var -= exprАрифметическое вычитание и присваиваниеSubAssign
->fn(...) -> type, |...| -> type...
.expr.identДоступ к элементу
...., expr.., ..expr, expr..exprУказывает на рядчисел, исключая правыйPartialOrd
..=..=expr, expr..=exprУказывает на рядчисел, включая правыйPartialOrd
....exprправила написания обновления устройства
..variant(x, ..), struct_type { x, .. }Привязка «И все остальное»
...expr...expr(Устарело, используйте новый правила написания ..=) Используется при определении инклюзивного ряда
/expr / exprАрифметическое делениеDiv
/=var /= exprАрифметическое деление и присваиваниеDivAssign
:pat: type, ident: typeОграничения видов
:ident: exprОбъявление поля устройства
:'a: loop {...}Метка цикла
;expr;Признак конца указания и элемента
;[...; len]Часть правил написания массива конечного размера
<<expr << exprБитовый сдвиг влевоShl
<<=var <<= exprБитовый сдвиг влево и присваиваниеShlAssign
<expr < exprСравнение "меньше чем"PartialOrd
<=expr <= exprСравнение "меньше или равно"PartialOrd
=var = expr, ident = typeПрисваивание/эквивалентность
==expr == exprСравнение "равно"PartialEq
=>pat => exprЧасть правил написания устройства match
>expr > exprСравнение "больше чем"PartialOrd
>=expr >= exprСравнение "больше или равно"PartialOrd
>>expr >> exprБитовый сдвиг вправоShr
>>=var >>= exprБитовый сдвиг вправо и присваиваниеShrAssign
@ident @ patPattern binding
^expr ^ exprПобитовое исключающее ИЛИBitXor
^=var ^= exprПобитовое исключающее ИЛИ и присваиваниеBitXorAssign
&vert;pat &vert; patИные образцы
&vert;expr &vert; exprПобитовое ИЛИBitOr
&vert;=var &vert;= exprПобитовое ИЛИ и присваиваниеBitOrAssign
&vert;&vert;expr &vert;&vert; exprКороткое логическое ИЛИ
?expr?Возврат ошибки
-
-

Обозначения не-операторы

-

Следующий список содержит все символы, которые не работают как операторы; то есть они не ведут себя как вызов функции или способа.

-

Таблица Б-2 показывает символы, которые появляются сами по себе и допустимы в различных местах.

-

Таблица Б-2: Автономный правила написания

-
- - - - - - - - - - - -
ОбозначениеОбъяснение
'identИменованное время жизни или метка цикла
...u8, ...i32, ...f64, ...usize, etc.Числовой запись определённого вида
"..."Строковый запись
r"...", r#"..."#, r##"..."##, etc.Необработанный строковый запись, в котором не обрабатываются escape-символы
b"..."Строковый запись байтов; создаёт массив байтов вместо строки
br"...", br#"..."#, br##"..."##, etc.Необработанный строковый байтовый запись, сочетание необработанного и байтового записи
'...'Символьный запись
b'...'ASCII байтовый запись
&vert;...&vert; exprЗамыкание
!Всегда пустой вид для расходящихся функций
_«Пренебрегаемое» связывание образцов; также используется для читабельности целочисленных записей
-
-

Таблица Б-3 показывает обозначения которые появляются в среде путей упорядочевания звеньев

-

Таблица Б-3. Правила написания, связанный с путями

-
- - - - - - - - - -
ОбозначениеОбъяснение
ident::identПуть к пространству имён
::pathПуть относительно корня ящика (т. е. явный абсолютный путь)
self::pathПуть относительно текущего звена (т. е. явный относительный путь).
super::pathПуть относительно родительского звена текущего звена
type::ident, <type as trait>::identСопряженные постоянные значения, функции и виды
<type>::...Сопряженный элемент для вида, который не может быть назван прямо (например <&T>::..., <[T]>::..., etc.)
trait::method(...)Устранение неоднозначности вызова способа путём именования особенности, который определяет его
type::method(...)Устранение неоднозначности путём вызова способа через имя вида, для которого он определён
<type as trait>::method(...)Устранение неоднозначности вызова способа путём именования особенности и вида
-
-

Таблица Б-4 показывает обозначения которые появляются в среде использования обобщённых видов свойств

-

Таблица Б-4: Обобщения

-
- - - - - - - - -
ОбозначениеОбъяснение
path<...>Определяет свойства для обобщённых свойств в виде (e.g., Vec<u8>)
path::<...>, method::<...>Определяет свойства для обобщённых свойств, функций, или способов в выражении. Часто называют turbofish (например "42".parse::<i32>())
fn ident<...> ...Определение обобщённой функции
struct ident<...> ...Определение обобщённой устройства
enum ident<...> ...Объявление обобщённого перечисления
impl<...> ...Определение обобщённой выполнения
for<...> typeВысокоуровневое связывание времени жизни
type<ident=type>Обобщённый вид где один или более сопряженных видов имеют определённое присваивание (например Iterator<Item=T>)
-
-

Таблица Б-5 показывает обозначения которые появляются в среде использования обобщённых видов свойств с ограничениями видов

-

Таблица Б-5: Ограничения видов

-
- - - - - - -
ОбозначениеОбъяснение
T: UОбобщённый свойство T ограничивается до видов которые выполняют особенность U
T: 'aОбобщённый вид T должен существовать не меньше чем 'a (то есть вид не может иметь ссылки с временем жизни меньше чем 'a)
T: 'staticОбобщённый вид T не имеет заимствованных ссылок кроме имеющих время жизни 'static
'b: 'aОбобщённое время жизни 'b должно быть не меньше чем 'a
T: ?SizedПозволяет обобщённым видам свойства иметь изменяемый размер
'a + trait, trait + traitСоединение ограничений видов
-
-

Таблица Б-6 показывает обозначения, которые появляются в среде вызова или определения макросов и указания свойств элемента.

-

Таблица Б-6: Макросы и свойства

-
- - - - - - -
ОбозначениеОбъяснение
#[meta]Внешний свойство
#![meta]Внутренний свойство
$identПодстановка в макросе
$ident:kindЗахват макроса
$(…)…Повторение макроса
ident!(...), ident!{...}, ident![...]Вызов макроса
-
-

Таблица Б-7 показывает обозначения, которые создают примечания.

-

Таблица Б-7: Примечания

-
- - - - - - -
ОбозначениеОбъяснение
//Однострочный примечание
//!Внутренний однострочный примечание документации
///Внешний однострочный примечание документации
/*...*/Многострочный примечание
/*!...*/Внутренний многострочный примечание документации
/**...*/Внешний многострочный примечание документации
-
-

Таблица Б-8 показывает обозначения, которые появляются в среде использования упорядоченных рядов.

-

Таблица Б-8: Упорядоченные ряды

-
- - - - - - - - -
ОбозначениеОбъяснение
()Пустой упорядоченный ряд, он же пустой вид. И запись и вид.
(expr)Выражение в скобках
(expr,)Упорядоченный ряд с одним элементом выражения
(type,)Упорядоченный ряд с одним элементом вида
(expr, ...)Выражение упорядоченного ряда
(type, ...)Вид упорядоченного ряда
(type, ...)Выражение вызова функции; также используется для объявления устройств-упорядоченных рядов и исходов-упорядоченных рядов перечисления
expr.0, expr.1, etc.Взятие элемента по порядковому указателю в упорядоченном ряде
-
-

Таблица Б-9 показывает среды, в которых используются фигурные скобки.

-

Таблица Б-9: Фигурные скобки

-
- - -
СредаОбъяснение
{...}Выражение раздела
Type {...}struct запись
-
-

Таблица Б-10 показывает среды, в которых используются квадратные скобки.

-

Таблица Б-10: Квадратные скобки

-
- - - - - -
СредаОбъяснение
[...]Запись массива
[expr; len]Запись массива, содержащий len повторов expr
[type; len]Массив, содержащий len образцов вида type
expr[expr]Взятие по порядковому указателю в собрания. Возможна перегрузка (Index, IndexMut)
expr[..], expr[a..], expr[..b], expr[a..b]Взятие среза собрания по порядковому указателю, используется Range, RangeFrom, RangeTo, или RangeFull как "порядковый указатель"
-
-
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/appendix-03-derivable-traits.html b/rustbook-ru/book/appendix-03-derivable-traits.html deleted file mode 100644 index 33ad57a72..000000000 --- a/rustbook-ru/book/appendix-03-derivable-traits.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - C — Выводимые особенности - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Дополнение В: Выводимые особенности

-

Во многих частях книги мы обсуждали свойство derive, которые Вы могли применить к объявлению устройства или перечисления. Свойство derive порождает код по умолчанию для выполнения особенности, который вы указали в derive.

-

В этом дополнении, мы расскажем про все особенности, которые вы можете использовать в свойстве derive. Каждая раздел содержит:

-
    -
  • Действия и способы, добавляемые особенностью
  • -
  • Как представлена выполнение особенности через derive
  • -
  • Что выполнение особенности рассказывает про вид
  • -
  • Условия, в которых разрешено или запрещено выполнить особенность
  • -
  • Примеры случаев, которые требуют наличие особенности
  • -
-

Если Вам понадобилось поведение отличное от поведения при выполнения через derive, обратитесь к документации по встроенной библиотеке чтобы узнать как вручную выполнить особенность.

-

Перечисленные здесь особенности являются единственными, определёнными встроенной библиотекой, которые могут быть выполнены в ваших видах с помощью derive. Другие особенности, определённые в встроенной библиотеке, не имеют ощутимого поведения по умолчанию, поэтому вам решать, как выполнить их для достижения ваших целей.

-

Пример особенности, который нельзя выполнить через derive - Display, который обрабатывает изменение -для конечных пользователей. Вы всегда должны сами рассмотреть лучший способ для отображения вида конечному пользователю. Какие части вида должны быть разрешены для просмотра конечному пользователю? Какие части они найдут подходящими? Какой вид вывода для них будет самым подходящим? Сборщик Ржавчина не знает ответы на эти вопросы, поэтому он не может подобрать подходящее обычное поведение.

-

Список видов, выполняемых через derive, в этом дополнении не является исчерпывающим: библиотеки могут выполнить derive для их собственных особенностей, составляя свои списки особенностей, которые Вы можете использовать с помощью derive. Выполнение derive включает в себя использование процедурных макросов, которые были рассмотрены в разделе "Макросы" главы 19.

-

Debug для отладочного вывода

-

Особенность Debug включает отладочное изменение -в изменяемых строках, которые вы можете указать с помощью :? внутри {} фигурных скобок.

-

Особенность Debug позволяет Вам напечатать предметы вида с целью отладки, поэтому Вы и другие программисты, использующие Ваш вид, смогут проверить предмет в определённой точке выполнения программы.

-

Особенность Debug обязателен в некоторых случаях. Например, при использовании макроса assert_eq!. Этот макрос печатает значения входных переменных, если они не совпадают. Это позволяет программистам увидеть, почему эти предметы не равны.

-

PartialEq и Eq для сравнения равенства

-

Особенность PartialEq позволяет Вам сравнить предметы одного вида на эквивалентность, и включает для них использование операторов == и !=.

-

Использование PartialEq выполняет способ eq. Когда PartialEq используют для устройства, два предмета равны если равны все поля предметов, и предметы не равны, если хотя бы одно поле отлично. Когда используется для перечислений, каждый исход равен себе, и не равен другим исходам.

-

Особенность PartialEq обязателен в некоторых случаях. Например для макроса assert_eq!, где необходимо сравнивать два предмета одного вида на эквивалентность.

-

Особенность Eq не имеет способов. Он указывает что каждое значение определеного вида равно самому себе. Особенность Eq может быть применён только для видов выполняющих особенность PartialEq, хотя не все виды, которые выполняют PartialEq могут выполнить Eq. Примером являются числа с плавающей запятой: выполнение чисел с плавающей запятой говорит, что два образца со значениями не-число (NaN) не равны друг другу.

-

Особенность Eqнеобходим в некоторых случаях. Например, для ключей в HashMap<K, V>. Поэтому HashMap<K, V> может сказать, что два ключа являются одним и тем же.

-

PartialOrd и Ord для сравнения порядка

-

Особенность PartialOrd позволяет Вам сравнить предметы одного вида с помощью сортировки. Вид, выполняющий PartialOrd может использоваться с операторами <, >, <=, и >=. Вы можете выполнить особенность PartialOrd только для видов, выполняющих PartialEq.

-

Использование PartialOrd выполняет способ partial_cmp, который возвращает Option<Ordering> который является None когда значения не выстраивают порядок. Примером значения, которое не может быть упорядочено, не являются числом (NaN) значение с плавающей запятой. Вызов partial_cmp с любым числом с плавающей запятой и значением NaN вернёт None.

-

Когда используется для устройств, PartialOrd сравнивает два предмета путём сравнения значений каждого поля в порядке, в котором поля объявлены в устройстве. Когда используется для перечислений, то исходы перечисления объявленные ранее будут меньше чем исходы объявленные позже.

-

Например, особенность PartialOrd может потребоваться для способа gen_range из rand ящика который порождает случайные значения в заданном ряде (который определён выражением ряда).

-

Особенность Ord позволяет знать, для двух значений определеного вида всегда будет существовать валидный порядок. Особенность Ord выполняет способ cmp, который возвращает Ordering а не Option<Ordering> потому что валидный порядок всегда будет существовать. Вы можете применить особенность Ord только для видов, выполняющих особенность PartialOrd и Eq (Eq также требует PartialEq). При использовании на устройствах или перечислениях, cmp имеет такое же поведение, как и partial_cmp вPartialOrd.

-

Особенность Ord необходим в некоторых случаях. Например, сохранение значений в BTreeSet<T>, виде данных, который хранит сведения на основе порядка отсортированных данных.

-

Clone и Copy для повторения значений

-

Особенность Clone позволяет вам явно создать глубокую повтор значения, а также этап повторения может вызывать особый код и воспроизводить данные с кучи. Более подробно про Clone смотрите в разделы "Способы взаимодействия переменных и данных: клонирование" в разделе 4.

-

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

-

Особенность Clone необходим в некоторых случаях. Например, для вызова способа to_vec для среза. Срез не владеет данными, содержащимися в нем, но вектор значений, возвращённый из to_vec должен владеть этими предметами, поэтому to_vec вызывает clone для всех данных. Таким образом, вид хранящийся в срезе, должен выполнить Clone.

-

Особенность Copy позволяет повторять значения повторяя только данные, которые хранятся на обойме, произвольный код не требуется. Смотрите раздел "Из обоймы данные: Повторение" в разделе 4 для большей сведений о Copy.

-

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

-

Вы можете вывести Copy для любого вида все части которого выполняют Copy. Вид который выполняет Copy должен также выполнить Clone, потому что вид выполняющий Copy имеет обыкновенную выполнение Clone который выполняет ту же задачу, что и Copy.

-

Особенность Copy нужен очень редко; виды, выполняющие Copy имеют небольшую переработку, то есть для него не нужно вызывать способ clone, который делает код более кратким.

-

Все, что вы делаете с Copy можно также делать и с Clone, но код может быть медленнее и требовать вызов способа clone в некоторых местах.

-

Hash для превращения значения в значение конечного размера

-

Особенность Hash позволяет превратить значение произвольного размера в значение конечного размера с использованием хеш-функции. Использование Hash выполняет способ hash. При выполнения через derive, способ hash сочетает итоги вызова hash на каждой части данных вида, то есть все поля или значения должны выполнить Hash для использования Hash с помощью derive.

-

Особенность Hash необходим в некоторых случаях. Например, для хранения ключей в HashMap<K, V>, для их более эффективного хранения.

-

Default для значений по умолчанию

-

Особенность Default позволяет создавать значение по умолчанию для вида. Использование Default выполняет функцию default. Обычная выполнение способа default вызовет функцию default на каждой части данных вида, то есть для использования Default через derive, все поля и значения вида данных должны также выполнить Default.

-

Функция Default::defaultчасто используется в сочетания с правилами написания обновления устройства, который мы обсуждали в разделы "Создание образца устройства из образца другой устройства с помощью правил написания обновления устройства" главы 5. Вы можете настроить несколько полей для устройства, а для остальных полей установить значения с помощью ..Default::default().

-

Особенность Default необходим в некоторых случаях. Например, для способа unwrap_or_default у вида Option<T>. Если значение Option<T> будет None, способ unwrap_or_default вернёт итог вызова функции Default::default для вида T, хранящегося в Option<T>.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/appendix-04-useful-development-tools.html b/rustbook-ru/book/appendix-04-useful-development-tools.html deleted file mode 100644 index 3e65f111b..000000000 --- a/rustbook-ru/book/appendix-04-useful-development-tools.html +++ /dev/null @@ -1,320 +0,0 @@ - - - - - - D — Полезные средства разработки - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Дополнение Г - Средства разработки

-

В этом дополнении мы расскажем про часто используемые средства разработки, предоставляемые Rust. Мы рассмотрим самостоятельное изменение -, быстрый путь исправления предупреждений, линтер, и встраивание с IDE.

-

Самостоятельное изменение

-

с rustfmt

-

Средство rustfmt переделает ваш код в соответствии со исполнением кода сообщества. Многие совместные дела используют rustfmt, чтобы предотвратить споры о том, какой исполнение использовать при написании Rust: все изменяют свой код с помощью этого средства.

-

Для установки rustfmt, введите следующее:

-
$ rustup component add rustfmt
-
-

Этот приказ установит rustfmt и cargo-fmt, также как Ржавчина даёт Вам одновременно rustc и cargo. Для изменения дела, использующего Cargo, введите следующее:

-
$ cargo fmt
-
-

Этот приказ изменит весь код на языке Ржавчина в текущем ящике. Будет изменён только исполнение кода, смысл останется прежней. Для большей сведений о rustfmt, смотрите документацию.

-

Исправление кода с rustfix

-

Средство rustfix включён в установку Ржавчина и может самостоятельно исправлять предупреждения сборщика с очевидным способом исправления сбоев, скорее всего, подходящим вам. Вероятно, вы уже видели предупреждения сборщика. Например, рассмотрим этот код:

-

Файл: src/main.rs

-
fn do_something() {}
-
-fn main() {
-    for i in 0..100 {
-        do_something();
-    }
-}
-

Мы вызываем функцию do_something 100 раз, но никогда не используем переменную i в теле цикла for. Ржавчина предупреждает нас об этом:

-
$ cargo build
-   Compiling myprogram v0.1.0 (file:///projects/myprogram)
-warning: unused variable: `i`
- --> src/main.rs:4:9
-  |
-4 |     for i in 0..100 {
-  |         ^ help: consider using `_i` instead
-  |
-  = note: #[warn(unused_variables)] on by default
-
-    Finished dev [unoptimized + debuginfo] target(s) in 0.50s
-
-

Предупреждение предлагает нам использовать _i как имя переменной: нижнее подчёркивание в начале определителя предполагает, что мы его не используем. Мы можем самостоятельно применить это предположение с помощью rustfix, запустив приказ cargo fix:

-
$ cargo fix
-    Checking myprogram v0.1.0 (file:///projects/myprogram)
-      Fixing src/main.rs (1 fix)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.59s
-
-

Когда посмотрим в src/main.rs снова, мы увидим что cargo fix изменил наш код:

-

Файл: src/main.rs

-
fn do_something() {}
-
-fn main() {
-    for _i in 0..100 {
-        do_something();
-    }
-}
-

Переменная цикла for теперь носит имя _i, и предупреждение больше не появляется.

-

Также Вы можете использовать приказ cargo fix для перемещения вашего кода между различными изданиеми Rust. Издания будут рассмотрены в дополнении Д.

-

Больше проверок с Clippy

-

Средство Clippy является собранием проверок (lints) для анализа Вашего кода, поэтому Вы можете найти простые ошибки и улучшить ваш Ржавчина код.

-

Для установки Clippy, введите следующее:

-
$ rustup component add clippy
-
-

Для запуска проверок Clippy’s для дела Cargo, введите следующее:

-
$ cargo clippy
-
-

Например, скажем что Вы хотите написать программу, в которой будет использоваться приближенная математическая постоянное значение, такая как число Пи, как в следующей программе:

-

Файл: src/main.rs

-
fn main() {
-    let x = 3.1415;
-    let r = 8.0;
-    println!("the area of the circle is {}", x * r * r);
-}
-

Запуск cargo clippy для этого дела вызовет следующую ошибку:

-
error: approximate value of `f{32, 64}::consts::PI` found
- --> src/main.rs:2:13
-  |
-2 |     let x = 3.1415;
-  |             ^^^^^^
-  |
-  = note: `#[deny(clippy::approx_constant)]` on by default
-  = help: consider using the constant directly
-  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
-
-

Эта ошибка сообщает вам, что в Ржавчина уже определена более точная постоянное значение PI, и что ваша программа будет более правильной, если вы вместо неё будете использовать эту постоянное значение. Затем вы должны изменить свой код, чтобы использовать постоянное значение PI. Следующий код не приводит к ошибкам или предупреждениям от Clippy:

-

Файл: src/main.rs

-
fn main() {
-    let x = std::f64::consts::PI;
-    let r = 8.0;
-    println!("the area of the circle is {}", x * r * r);
-}
-

Для большей сведений о Clippy смотрите документацию.

-

Встраивание с IDE с помощью rust-analyzer

-

Чтобы облегчить встраивание с IDE, сообщество Ржавчина советует использовать rust-analyzer. Этот средство представляет собой набор направленных на сборщик утилит, которые используют Language Server Protocol, который является сводом требований для взаимодействия IDE и языков программирования друг с другом. Разные клиенты могут использовать rust-analyzer, например подключаемый звено анализатора Ржавчина для Visual Studio Code.

-

Посетите домашнюю страницу дела rust-analyzer для получения указаний по установке, затем установите поддержку языкового сервера в именно среде IDE. Ваша IDE получит такие возможности, как автозаполнение, переход к определению и встроенные ошибки.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/appendix-05-editions.html b/rustbook-ru/book/appendix-05-editions.html deleted file mode 100644 index 4229fdce8..000000000 --- a/rustbook-ru/book/appendix-05-editions.html +++ /dev/null @@ -1,246 +0,0 @@ - - - - - - E — Издания - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Приложение E - Издания языка

-

В главе 1, можно увидеть, что приказ cargo new добавляет некоторые мета-данные о издания языка в файл Cargo.toml. Данное приложение рассказывает, что они означают.

-

Язык Ржавчина и его сборщик имеют шестинедельный цикл выпуска, означающий, что пользователи постоянно получают новые функции. В других языках обычно выпускают большие обновления, но редко. Объединение Ржавчина выпускает меньшие обновления, но более часто. Через некоторое время все эти небольшие изменения накапливаются. Между исполнениями обычно сложно оглянуться назад и сказать "Ого, язык сильно изменился между исполнениями Ржавчина 1.10 и Ржавчина 1.31!"

-

Каждые два или три года, объединение Ржавчина выпускает новую издание языка (Rust edition). Каждая издание объединяет все новые особенности, которые попали в язык с новыми дополнениями, с полной, обновлённой документацией и набором средств. Новые издания поставляются как часть шестинедельного этапа исполнений.

-

Для разных людей издания служат разным целям:

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

На мгновение написания доступны две издания Rust: Ржавчина 2015 и Ржавчина 2018. Данная книга написана с использованием идиом издания Ржавчина 2018.

-

Ключ edition в настроечном файле Cargo.toml отображает, какую издание сборщик должен использовать для вашего кода. Если ключа нет, то для обратной совместимости сборщик Ржавчина использует издание 2015.

-

Любой дело может выбрать издание отличную от издания по умолчанию, которая равна 2015. Издания могут содержать несовместимые изменения, включая новые ключевые слова, которые могут враждовать с определителями в коде. Однако, пока вы не переключитесь на новую издание, ваш код будет продолжать собираться даже после обновления используемой исполнения сборщика.

-

Все исполнения сборщика Ржавчина поддерживают любую издание, которая предшествовала выпуску текущей, и они могут линковать дополнения любой поддерживаемой издания. Изменения изданий действуют только на способ начального разбора сборщиком исходного кода. Поэтому, если вы используете 2015 издание, а одна из ваших зависимостей использует 2018, ваш дело будет собран и сможет пользоваться этой зависимостью. Обратная случаей, когда ваш дело использует Ржавчина 2018, а зависимость использует Ржавчина 2015, работает таким же образом.

-

Внесём ясность: большая часть возможностей будет доступна во всех изданиях. Разработчики, использующие любую издание Rust, будут продолжать получать улучшения по мере выпуска новых исполнений. Однако в некоторых случаях, в основном, когда добавляются новые ключевые слова, некоторые новые возможности могут быть доступны только в последних изданиях. Нужно переключить издание, чтобы воспользоваться новыми возможностями.

-

Для получения больше подробностей, есть полная книга Edition Guide про издания, в которой перечисляются различия между изданиями и объясняется, как самостоятельно обновить свой код на новую издание с помощью приказы cargo fix.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/appendix-06-translation.html b/rustbook-ru/book/appendix-06-translation.html deleted file mode 100644 index ffb62b025..000000000 --- a/rustbook-ru/book/appendix-06-translation.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - F — Переводы книги - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Приложение Е: Переводы книги

-

Для ресурсов на языках, отличных от английского. Большинство из них все ещё в разработке; см. ярлык «Переводы», чтобы помочь или сообщить нам о новом переводе!

- - -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/appendix-07-nightly-rust.html b/rustbook-ru/book/appendix-07-nightly-rust.html deleted file mode 100644 index 978aa7fe2..000000000 --- a/rustbook-ru/book/appendix-07-nightly-rust.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - G — Как создаётся Ржавчина и «Nightly Rust» - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Дополнение Ё - Как создаётся Ржавчина и “Nightly Rust”

-

Это дополнение рассказывает как создаётся Rust, и как это влияет на Вас как на разработчика.

-

Безотказность без стагнации

-

Как язык, Ржавчина много заботиться о безотказности Вашего кода. Мы хотим чтобы Ржавчина был прочным фундаментом, вашей опорой, и если бы все постоянно менялось, это было бы невозможно. В то же время, если мы не можем экспериментировать с различными возможностями, мы не можем обнаружить важные сбоев до исполнения, когда мы не можем их изменить.

-

Нашим решением сбоев является “безотказность без стагнации”, и наш руководящий принцип: Вы никогда не должны бояться перехода на новую безотказную исполнение Rust. Каждое обновление должно быть безболезненным, но также должно добавлять новые функции, меньше дефектов и более быструю скорость сборки.

-

Ту-ту! потоки выпуска и поездка на поезде

-

Разработка языка Ржавчина работает по принципу расписания поездов. То есть, вся разработка совершается в ветке master Ржавчина хранилища. Выпуски следуют подходы последовательного выпуска продукта (software release train), которая была использована Cisco IOS и другими программными продуктами. Есть три потока выпуска Rust:

-
    -
  • Ночной (Nightly)
  • -
  • Бета (Beta)
  • -
  • Безотказный (Stable)
  • -
-

Большинство Ржавчина разработчиков используют безотказную исполнение, но те кто хотят попробовать экспериментальные новые функции, должны использовать Nightly или Beta.

-

Приведём пример, как работает этап разработки и выпуска новых исполнений. Давайте предположим, что объединение Ржавчина работает над исполнением Ржавчина 1.5. Его исполнение состоялся в декабре 2015 года, но это даст существующегостичность номера исполнения. Была добавлена новая возможность в Rust: новые изменения в ветку master. Каждую ночь выпускается новая ночная исполнение Rust. Каждый день является днём выпуска ночной исполнения и эти выпуски создаются нашей устройством самостоятельно . По мере того как идёт время, наши выпуски выглядят так:

-
nightly: * - - * - - *
-
-

Каждые шесть недель наступает время подготовки новой Beta исполнения! Ветка beta Ржавчина хранилища ответвляется от ветки master, используемой исполнением Nightly. Теперь мы имеем два выпуска:

-
nightly: * - - * - - *
-                     |
-beta:                *
-
-

Многие пользователи Ржавчина не используют активно бета-исполнение, но проверяют бета-исполнение в их системе CI для помощи Ржавчина обнаружить сбоев обратной совместимости. В это время каждую ночь выпускается новая исполнение Nightly:

-
nightly: * - - * - - * - - * - - *
-                     |
-beta:                *
-
-

Предположим, что была найдена отступление. Хорошо, что мы можем проверять бета-исполнение перед тем как отступление попала в безотказную исполнение! Исправление отправляется в ветку master, поэтому исполнение nightly исправлена и затем исправление также направляется в ветку beta, и происходит новый выпуск бета-исполнения:

-
nightly: * - - * - - * - - * - - * - - *
-                     |
-beta:                * - - - - - - - - *
-
-

Через шесть недель после выпуска бета-исполнения, наступает время для выпуска безотказной исполнения! Ветка stable создаётся из ветки beta:

-
nightly: * - - * - - * - - * - - * - - * - * - *
-                     |
-beta:                * - - - - - - - - *
-                                       |
-stable:                                *
-
-

Ура! Ржавчина 1.5 выпущена! Но мы также забыли про одну вещь: так как прошло шесть недель, мы должны выпустить бета-исполнение следующей исполнения Ржавчина 1.6. Поэтому после ответвления ветки stable из ветки beta, следующая исполнение beta ответвляется снова от nightly:

-
nightly: * - - * - - * - - * - - * - - * - * - *
-                     |                         |
-beta:                * - - - - - - - - *       *
-                                       |
-stable:                                *
-
-

Это называется “прообраз поезда” (train model), потому что каждые шесть недель выпуск “покидает станцию”, но ему все ещё нужно пройти поток beta, чтобы попасть в безотказную исполнение.

-

Rust выпускается каждые шесть недель, как часы. Если вы знаете дату одного выпуска Rust, вы знаете дату выпуска следующего: это шесть недель позднее. Хорошим особенностью выпуска исполнений каждые шесть недель является то, что следующий поезд прибывает скоро. Если какая-то функция не попадает в исполнение, не надо волноваться: ещё один выпуск произойдёт очень скоро! Это помогает снизить давление в случае если функция возможно не отполирована к дате выпуска.

-

Благодаря этому этапу, вы всегда можете посмотреть следующую исполнение Ржавчина и убедиться, что на неё легко будет перейти: если бета-выпуск будет работать не так как ожидалось, вы можете сообщить об этом разработчикам и он будет исправлен перед выпуском безотказной исполнения! Поломки в бета-исполнения случаются относительно редко, но rustc все ещё является частью программного обеспечения, поэтому дефекты все ещё существуют.

-

Ненадежные функции

-

У этой подходы выпуска есть ещё один плюс: ненадежные функции. Ржавчина использует технику называемую “флаги возможностей” (feature flags) для определения функций, которые были включены в выпуске. Если новая функция находится в активной разработке, она попадает в ветку master, и поэтому попадает в ночную исполнение, но с флагом функции (feature flag). Если как пользователь, вы хотите попробовать работу такой функции, находящейся в разработке, вы должны использовать ночную исполнение Ржавчина и указать в вашем исходном коде определённый флаг.

-

Если вы используете бета или безотказную исполнение Rust, Вы не можете использовать флаги функций. Этот ключевой мгновение позволяет использовать в действительностиновые возможности перед их отладкой. Это может использоваться желающими идти в ногу со временем, а другие могут использовать безотказную исполнение и быть уверенными что их код не сломается. Безотказность без стагнации.

-

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

-

Rustup и значение ночной исполнения Rust

-

Rustup делает лёгким изменение между различными потоками Rust, на вездесущем или местном для дела уровне. По умолчанию устанавливается безотказная исполнение Rust. Для установки ночной исполнения выполните приказ:

-
$ rustup toolchain install nightly
-
-

Вы можете также увидеть все установленные средства разработчика (toolchains) (исполнения Ржавчина и сопряженные составляющие) с помощью rustup. Это пример вывода у одного из авторов Ржавчина с компьютером на Windows:

-
> rustup toolchain list
-stable-x86_64-pc-windows-msvc (default)
-beta-x86_64-pc-windows-msvc
-nightly-x86_64-pc-windows-msvc
-
-

Как видите, включенный набор средств (toolchain) используется по умолчанию. Большинство пользователей Ржавчина используют безотказные исполнения большую часть времени. Возможно, вы захотите использовать безотказную большую часть времени, но использовать каждую ночную исполнение в определенном деле, потому что заботитесь о передовых возможностях. Для этого вы можете использовать приказ rustup override в папке этого дела, чтобы установить ночной набор средств, должна использоваться приказ rustup, когда вы находитесь в этом папке:

-
$ cd ~/projects/needs-nightly
-$ rustup override set nightly
-
-

Теперь каждый раз, когда вы вызываете rustc или cargo внутри ~/projects/needs-nightly, rustup будет следить за тем, чтобы вы используете ночную исполнение Rust, а не безотказную по умолчанию. Это очень удобно, когда у вас есть множество Ржавчина дел!

-

Этап RFC и приказы

-

Итак, как вы узнаете об этих новых возможностях? Прообраз разработки Ржавчина следует этапу запроса примечаниев (RFC - Request For Comments). Если хотите улучшить Rust, вы можете написать предложение, которое называется RFC.

-

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

-

Если новая возможность принята и кто-то может выполнить её, то задача открывается в хранилища Rust. Человек выполняющий её, вполне может не быть тем, кто предложил эту возможность! Когда выполнение готова, она попадает в master ветвь с флагом функции, как мы обсуждали в разделе "Небезотказных функциях".

-

Через некоторое время, разработчики Ржавчина использующие ночные выпуски, смогут опробовать новую возможность, члены приказы обсудят её, как она работает в ночной исполнения и решат, должна ли она попасть в безотказную исполнение Ржавчина или нет. Если принимается решение двигать её вперёд, ограничение функции с помощью флага убирается и функция теперь считается безотказной! Она едет в новую безотказную исполнение Rust.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ayu-highlight.css b/rustbook-ru/book/ayu-highlight.css deleted file mode 100644 index 32c943222..000000000 --- a/rustbook-ru/book/ayu-highlight.css +++ /dev/null @@ -1,78 +0,0 @@ -/* -Based off of the Ayu theme -Original by Dempfi (https://github.com/dempfi/ayu) -*/ - -.hljs { - display: block; - overflow-x: auto; - background: #191f26; - color: #e6e1cf; -} - -.hljs-comment, -.hljs-quote { - color: #5c6773; - font-style: italic; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-attr, -.hljs-regexp, -.hljs-link, -.hljs-selector-id, -.hljs-selector-class { - color: #ff7733; -} - -.hljs-number, -.hljs-meta, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #ffee99; -} - -.hljs-string, -.hljs-bullet { - color: #b8cc52; -} - -.hljs-title, -.hljs-built_in, -.hljs-section { - color: #ffb454; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-symbol { - color: #ff7733; -} - -.hljs-name { - color: #36a3d9; -} - -.hljs-tag { - color: #00568d; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-addition { - color: #91b362; -} - -.hljs-deletion { - color: #d96c75; -} diff --git a/rustbook-ru/book/book.js b/rustbook-ru/book/book.js deleted file mode 100644 index aa12e7ecc..000000000 --- a/rustbook-ru/book/book.js +++ /dev/null @@ -1,697 +0,0 @@ -"use strict"; - -// Fix back button cache problem -window.onunload = function () { }; - -// Global variable, shared between modules -function playground_text(playground, hidden = true) { - let code_block = playground.querySelector("code"); - - if (window.ace && code_block.classList.contains("editable")) { - let editor = window.ace.edit(code_block); - return editor.getValue(); - } else if (hidden) { - return code_block.textContent; - } else { - return code_block.innerText; - } -} - -(function codeSnippets() { - function fetch_with_timeout(url, options, timeout = 6000) { - return Promise.race([ - fetch(url, options), - new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)) - ]); - } - - var playgrounds = Array.from(document.querySelectorAll(".playground")); - if (playgrounds.length > 0) { - fetch_with_timeout("https://play.rust-lang.org/meta/crates", { - headers: { - 'Content-Type': "application/json", - }, - method: 'POST', - mode: 'cors', - }) - .then(response => response.json()) - .then(response => { - // get list of crates available in the rust playground - let playground_crates = response.crates.map(item => item["id"]); - playgrounds.forEach(block => handle_crate_list_update(block, playground_crates)); - }); - } - - function handle_crate_list_update(playground_block, playground_crates) { - // update the play buttons after receiving the response - update_play_button(playground_block, playground_crates); - - // and install on change listener to dynamically update ACE editors - if (window.ace) { - let code_block = playground_block.querySelector("code"); - if (code_block.classList.contains("editable")) { - let editor = window.ace.edit(code_block); - editor.addEventListener("change", function (e) { - update_play_button(playground_block, playground_crates); - }); - // add Ctrl-Enter command to execute rust code - editor.commands.addCommand({ - name: "run", - bindKey: { - win: "Ctrl-Enter", - mac: "Ctrl-Enter" - }, - exec: _editor => run_rust_code(playground_block) - }); - } - } - } - - // updates the visibility of play button based on `no_run` class and - // used crates vs ones available on https://play.rust-lang.org - function update_play_button(pre_block, playground_crates) { - var play_button = pre_block.querySelector(".play-button"); - - // skip if code is `no_run` - if (pre_block.querySelector('code').classList.contains("no_run")) { - play_button.classList.add("hidden"); - return; - } - - // get list of `extern crate`'s from snippet - var txt = playground_text(pre_block); - var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; - var snippet_crates = []; - var item; - while (item = re.exec(txt)) { - snippet_crates.push(item[1]); - } - - // check if all used crates are available on play.rust-lang.org - var all_available = snippet_crates.every(function (elem) { - return playground_crates.indexOf(elem) > -1; - }); - - if (all_available) { - play_button.classList.remove("hidden"); - } else { - play_button.classList.add("hidden"); - } - } - - function run_rust_code(code_block) { - var result_block = code_block.querySelector(".result"); - if (!result_block) { - result_block = document.createElement('code'); - result_block.className = 'result hljs language-bash'; - - code_block.append(result_block); - } - - let text = playground_text(code_block); - let classes = code_block.querySelector('code').classList; - let edition = "2015"; - if(classes.contains("edition2018")) { - edition = "2018"; - } else if(classes.contains("edition2021")) { - edition = "2021"; - } - var params = { - version: "stable", - optimize: "0", - code: text, - edition: edition - }; - - if (text.indexOf("#![feature") !== -1) { - params.version = "nightly"; - } - - result_block.innerText = "Running..."; - - fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { - headers: { - 'Content-Type': "application/json", - }, - method: 'POST', - mode: 'cors', - body: JSON.stringify(params) - }) - .then(response => response.json()) - .then(response => { - if (response.result.trim() === '') { - result_block.innerText = "No output"; - result_block.classList.add("result-no-output"); - } else { - result_block.innerText = response.result; - result_block.classList.remove("result-no-output"); - } - }) - .catch(error => result_block.innerText = "Playground Communication: " + error.message); - } - - // Syntax highlighting Configuration - hljs.configure({ - tabReplace: ' ', // 4 spaces - languages: [], // Languages used for auto-detection - }); - - let code_nodes = Array - .from(document.querySelectorAll('code')) - // Don't highlight `inline code` blocks in headers. - .filter(function (node) {return !node.parentElement.classList.contains("header"); }); - - if (window.ace) { - // language-rust class needs to be removed for editable - // blocks or highlightjs will capture events - code_nodes - .filter(function (node) {return node.classList.contains("editable"); }) - .forEach(function (block) { block.classList.remove('language-rust'); }); - - code_nodes - .filter(function (node) {return !node.classList.contains("editable"); }) - .forEach(function (block) { hljs.highlightBlock(block); }); - } else { - code_nodes.forEach(function (block) { hljs.highlightBlock(block); }); - } - - // Adding the hljs class gives code blocks the color css - // even if highlighting doesn't apply - code_nodes.forEach(function (block) { block.classList.add('hljs'); }); - - Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) { - - var lines = Array.from(block.querySelectorAll('.boring')); - // If no lines were hidden, return - if (!lines.length) { return; } - block.classList.add("hide-boring"); - - var buttons = document.createElement('div'); - buttons.className = 'buttons'; - buttons.innerHTML = ""; - - // add expand button - var pre_block = block.parentNode; - pre_block.insertBefore(buttons, pre_block.firstChild); - - pre_block.querySelector('.buttons').addEventListener('click', function (e) { - if (e.target.classList.contains('fa-eye')) { - e.target.classList.remove('fa-eye'); - e.target.classList.add('fa-eye-slash'); - e.target.title = 'Hide lines'; - e.target.setAttribute('aria-label', e.target.title); - - block.classList.remove('hide-boring'); - } else if (e.target.classList.contains('fa-eye-slash')) { - e.target.classList.remove('fa-eye-slash'); - e.target.classList.add('fa-eye'); - e.target.title = 'Show hidden lines'; - e.target.setAttribute('aria-label', e.target.title); - - block.classList.add('hide-boring'); - } - }); - }); - - if (window.playground_copyable) { - Array.from(document.querySelectorAll('pre code')).forEach(function (block) { - var pre_block = block.parentNode; - if (!pre_block.classList.contains('playground')) { - var buttons = pre_block.querySelector(".buttons"); - if (!buttons) { - buttons = document.createElement('div'); - buttons.className = 'buttons'; - pre_block.insertBefore(buttons, pre_block.firstChild); - } - - var clipButton = document.createElement('button'); - clipButton.className = 'fa fa-copy clip-button'; - clipButton.title = 'Copy to clipboard'; - clipButton.setAttribute('aria-label', clipButton.title); - clipButton.innerHTML = ''; - - buttons.insertBefore(clipButton, buttons.firstChild); - } - }); - } - - // Process playground code blocks - Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) { - // Add play button - var buttons = pre_block.querySelector(".buttons"); - if (!buttons) { - buttons = document.createElement('div'); - buttons.className = 'buttons'; - pre_block.insertBefore(buttons, pre_block.firstChild); - } - - var runCodeButton = document.createElement('button'); - runCodeButton.className = 'fa fa-play play-button'; - runCodeButton.hidden = true; - runCodeButton.title = 'Run this code'; - runCodeButton.setAttribute('aria-label', runCodeButton.title); - - buttons.insertBefore(runCodeButton, buttons.firstChild); - runCodeButton.addEventListener('click', function (e) { - run_rust_code(pre_block); - }); - - if (window.playground_copyable) { - var copyCodeClipboardButton = document.createElement('button'); - copyCodeClipboardButton.className = 'fa fa-copy clip-button'; - copyCodeClipboardButton.innerHTML = ''; - copyCodeClipboardButton.title = 'Copy to clipboard'; - copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title); - - buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); - } - - let code_block = pre_block.querySelector("code"); - if (window.ace && code_block.classList.contains("editable")) { - var undoChangesButton = document.createElement('button'); - undoChangesButton.className = 'fa fa-history reset-button'; - undoChangesButton.title = 'Undo changes'; - undoChangesButton.setAttribute('aria-label', undoChangesButton.title); - - buttons.insertBefore(undoChangesButton, buttons.firstChild); - - undoChangesButton.addEventListener('click', function () { - let editor = window.ace.edit(code_block); - editor.setValue(editor.originalCode); - editor.clearSelection(); - }); - } - }); -})(); - -(function themes() { - var html = document.querySelector('html'); - var themeToggleButton = document.getElementById('theme-toggle'); - var themePopup = document.getElementById('theme-list'); - var themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); - var stylesheets = { - ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"), - tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"), - highlight: document.querySelector("[href$='highlight.css']"), - }; - - function showThemes() { - themePopup.style.display = 'block'; - themeToggleButton.setAttribute('aria-expanded', true); - themePopup.querySelector("button#" + get_theme()).focus(); - } - - function updateThemeSelected() { - themePopup.querySelectorAll('.theme-selected').forEach(function (el) { - el.classList.remove('theme-selected'); - }); - themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected'); - } - - function hideThemes() { - themePopup.style.display = 'none'; - themeToggleButton.setAttribute('aria-expanded', false); - themeToggleButton.focus(); - } - - function get_theme() { - var theme; - try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { } - if (theme === null || theme === undefined) { - return default_theme; - } else { - return theme; - } - } - - function set_theme(theme, store = true) { - let ace_theme; - - if (theme == 'coal' || theme == 'navy') { - stylesheets.ayuHighlight.disabled = true; - stylesheets.tomorrowNight.disabled = false; - stylesheets.highlight.disabled = true; - - ace_theme = "ace/theme/tomorrow_night"; - } else if (theme == 'ayu') { - stylesheets.ayuHighlight.disabled = false; - stylesheets.tomorrowNight.disabled = true; - stylesheets.highlight.disabled = true; - ace_theme = "ace/theme/tomorrow_night"; - } else { - stylesheets.ayuHighlight.disabled = true; - stylesheets.tomorrowNight.disabled = true; - stylesheets.highlight.disabled = false; - ace_theme = "ace/theme/dawn"; - } - - setTimeout(function () { - themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor; - }, 1); - - if (window.ace && window.editors) { - window.editors.forEach(function (editor) { - editor.setTheme(ace_theme); - }); - } - - var previousTheme = get_theme(); - - if (store) { - try { localStorage.setItem('mdbook-theme', theme); } catch (e) { } - } - - html.classList.remove(previousTheme); - html.classList.add(theme); - updateThemeSelected(); - } - - // Set theme - var theme = get_theme(); - - set_theme(theme, false); - - themeToggleButton.addEventListener('click', function () { - if (themePopup.style.display === 'block') { - hideThemes(); - } else { - showThemes(); - } - }); - - themePopup.addEventListener('click', function (e) { - var theme; - if (e.target.className === "theme") { - theme = e.target.id; - } else if (e.target.parentElement.className === "theme") { - theme = e.target.parentElement.id; - } else { - return; - } - set_theme(theme); - }); - - themePopup.addEventListener('focusout', function(e) { - // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) - if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) { - hideThemes(); - } - }); - - // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628 - document.addEventListener('click', function(e) { - if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) { - hideThemes(); - } - }); - - document.addEventListener('keydown', function (e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } - if (!themePopup.contains(e.target)) { return; } - - switch (e.key) { - case 'Escape': - e.preventDefault(); - hideThemes(); - break; - case 'ArrowUp': - e.preventDefault(); - var li = document.activeElement.parentElement; - if (li && li.previousElementSibling) { - li.previousElementSibling.querySelector('button').focus(); - } - break; - case 'ArrowDown': - e.preventDefault(); - var li = document.activeElement.parentElement; - if (li && li.nextElementSibling) { - li.nextElementSibling.querySelector('button').focus(); - } - break; - case 'Home': - e.preventDefault(); - themePopup.querySelector('li:first-child button').focus(); - break; - case 'End': - e.preventDefault(); - themePopup.querySelector('li:last-child button').focus(); - break; - } - }); -})(); - -(function sidebar() { - var body = document.querySelector("body"); - var sidebar = document.getElementById("sidebar"); - var sidebarLinks = document.querySelectorAll('#sidebar a'); - var sidebarToggleButton = document.getElementById("sidebar-toggle"); - var sidebarResizeHandle = document.getElementById("sidebar-resize-handle"); - var firstContact = null; - - function showSidebar() { - body.classList.remove('sidebar-hidden') - body.classList.add('sidebar-visible'); - Array.from(sidebarLinks).forEach(function (link) { - link.setAttribute('tabIndex', 0); - }); - sidebarToggleButton.setAttribute('aria-expanded', true); - sidebar.setAttribute('aria-hidden', false); - try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { } - } - - - var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle'); - - function toggleSection(ev) { - ev.currentTarget.parentElement.classList.toggle('expanded'); - } - - Array.from(sidebarAnchorToggles).forEach(function (el) { - el.addEventListener('click', toggleSection); - }); - - function hideSidebar() { - body.classList.remove('sidebar-visible') - body.classList.add('sidebar-hidden'); - Array.from(sidebarLinks).forEach(function (link) { - link.setAttribute('tabIndex', -1); - }); - sidebarToggleButton.setAttribute('aria-expanded', false); - sidebar.setAttribute('aria-hidden', true); - try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { } - } - - // Toggle sidebar - sidebarToggleButton.addEventListener('click', function sidebarToggle() { - if (body.classList.contains("sidebar-hidden")) { - var current_width = parseInt( - document.documentElement.style.getPropertyValue('--sidebar-width'), 10); - if (current_width < 150) { - document.documentElement.style.setProperty('--sidebar-width', '150px'); - } - showSidebar(); - } else if (body.classList.contains("sidebar-visible")) { - hideSidebar(); - } else { - if (getComputedStyle(sidebar)['transform'] === 'none') { - hideSidebar(); - } else { - showSidebar(); - } - } - }); - - sidebarResizeHandle.addEventListener('mousedown', initResize, false); - - function initResize(e) { - window.addEventListener('mousemove', resize, false); - window.addEventListener('mouseup', stopResize, false); - body.classList.add('sidebar-resizing'); - } - function resize(e) { - var pos = (e.clientX - sidebar.offsetLeft); - if (pos < 20) { - hideSidebar(); - } else { - if (body.classList.contains("sidebar-hidden")) { - showSidebar(); - } - pos = Math.min(pos, window.innerWidth - 100); - document.documentElement.style.setProperty('--sidebar-width', pos + 'px'); - } - } - //on mouseup remove windows functions mousemove & mouseup - function stopResize(e) { - body.classList.remove('sidebar-resizing'); - window.removeEventListener('mousemove', resize, false); - window.removeEventListener('mouseup', stopResize, false); - } - - document.addEventListener('touchstart', function (e) { - firstContact = { - x: e.touches[0].clientX, - time: Date.now() - }; - }, { passive: true }); - - document.addEventListener('touchmove', function (e) { - if (!firstContact) - return; - - var curX = e.touches[0].clientX; - var xDiff = curX - firstContact.x, - tDiff = Date.now() - firstContact.time; - - if (tDiff < 250 && Math.abs(xDiff) >= 150) { - if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) - showSidebar(); - else if (xDiff < 0 && curX < 300) - hideSidebar(); - - firstContact = null; - } - }, { passive: true }); -})(); - -(function chapterNavigation() { - document.addEventListener('keydown', function (e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } - if (window.search && window.search.hasFocus()) { return; } - var html = document.querySelector('html'); - - function next() { - var nextButton = document.querySelector('.nav-chapters.next'); - if (nextButton) { - window.location.href = nextButton.href; - } - } - function prev() { - var previousButton = document.querySelector('.nav-chapters.previous'); - if (previousButton) { - window.location.href = previousButton.href; - } - } - switch (e.key) { - case 'ArrowRight': - e.preventDefault(); - if (html.dir == 'rtl') { - prev(); - } else { - next(); - } - break; - case 'ArrowLeft': - e.preventDefault(); - if (html.dir == 'rtl') { - next(); - } else { - prev(); - } - break; - } - }); -})(); - -(function clipboard() { - var clipButtons = document.querySelectorAll('.clip-button'); - - function hideTooltip(elem) { - elem.firstChild.innerText = ""; - elem.className = 'fa fa-copy clip-button'; - } - - function showTooltip(elem, msg) { - elem.firstChild.innerText = msg; - elem.className = 'fa fa-copy tooltipped'; - } - - var clipboardSnippets = new ClipboardJS('.clip-button', { - text: function (trigger) { - hideTooltip(trigger); - let playground = trigger.closest("pre"); - return playground_text(playground, false); - } - }); - - Array.from(clipButtons).forEach(function (clipButton) { - clipButton.addEventListener('mouseout', function (e) { - hideTooltip(e.currentTarget); - }); - }); - - clipboardSnippets.on('success', function (e) { - e.clearSelection(); - showTooltip(e.trigger, "Copied!"); - }); - - clipboardSnippets.on('error', function (e) { - showTooltip(e.trigger, "Clipboard error!"); - }); -})(); - -(function scrollToTop () { - var menuTitle = document.querySelector('.menu-title'); - - menuTitle.addEventListener('click', function () { - document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); - }); -})(); - -(function controllMenu() { - var menu = document.getElementById('menu-bar'); - - (function controllPosition() { - var scrollTop = document.scrollingElement.scrollTop; - var prevScrollTop = scrollTop; - var minMenuY = -menu.clientHeight - 50; - // When the script loads, the page can be at any scroll (e.g. if you reforesh it). - menu.style.top = scrollTop + 'px'; - // Same as parseInt(menu.style.top.slice(0, -2), but faster - var topCache = menu.style.top.slice(0, -2); - menu.classList.remove('sticky'); - var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster - document.addEventListener('scroll', function () { - scrollTop = Math.max(document.scrollingElement.scrollTop, 0); - // `null` means that it doesn't need to be updated - var nextSticky = null; - var nextTop = null; - var scrollDown = scrollTop > prevScrollTop; - var menuPosAbsoluteY = topCache - scrollTop; - if (scrollDown) { - nextSticky = false; - if (menuPosAbsoluteY > 0) { - nextTop = prevScrollTop; - } - } else { - if (menuPosAbsoluteY > 0) { - nextSticky = true; - } else if (menuPosAbsoluteY < minMenuY) { - nextTop = prevScrollTop + minMenuY; - } - } - if (nextSticky === true && stickyCache === false) { - menu.classList.add('sticky'); - stickyCache = true; - } else if (nextSticky === false && stickyCache === true) { - menu.classList.remove('sticky'); - stickyCache = false; - } - if (nextTop !== null) { - menu.style.top = nextTop + 'px'; - topCache = nextTop; - } - prevScrollTop = scrollTop; - }, { passive: true }); - })(); - (function controllBorder() { - function updateBorder() { - if (menu.offsetTop === 0) { - menu.classList.remove('bordered'); - } else { - menu.classList.add('bordered'); - } - } - updateBorder(); - document.addEventListener('scroll', updateBorder, { passive: true }); - })(); -})(); diff --git a/rustbook-ru/book/ch00-00-introduction.html b/rustbook-ru/book/ch00-00-introduction.html deleted file mode 100644 index d9bbba894..000000000 --- a/rustbook-ru/book/ch00-00-introduction.html +++ /dev/null @@ -1,281 +0,0 @@ - - - - - - Введение - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Введение

-
-

Примечание. Это издание книги такое же, как и Язык программирования Rust, доступное в печатном и электронном виде от No Starch Press.

-
-

Добро пожаловать в The Ржавчина Programming Language, вводную книгу о Rust. Язык программирования Ржавчина помогает создавать быстрые, более надёжные приложения. Хорошая удобство и низкоуровневый управление часто являются противоречивыми требованиями для внешнего видаязыков программирования; Ржавчина бросает вызов этому вражде. Благодаря уравновешенности мощных технических возможностей c большим удобством разработки, Ржавчина предоставляет возможности управления низкоуровневыми элементами (например, использование памяти) без трудностей, привычно связанных с таким управлением.

-

Кому подходит Rust

-

Rust наилучше подходит для многих людей по целому ряду причин. Давайте рассмотрим несколько наиболее важных объединений.

-

Объединения разработчиков

-

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

-

Rust также привносит современные средства разработчика в мир системного программирования:

-
    -
  • Cargo, входящий в состав управленец зависимостей и средство сборки, делает добавление, сборку и управление зависимостями безболезненным и согласованным в рамках всей внутреннего устройства Rust.
  • -
  • Средство изменения Rustfmt обеспечивает единый исполнение кодирования для всех разработчиков.
  • -
  • Ржавчина Language Server обеспечивает встраивание с встроенной средой разработки (IDE) для автодополнения кода и встроенных сообщений об ошибках.
  • -
-

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

-

Студенты

-

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

-

Предприятия

-

Сотни больших и малых предприятий используют Ржавчина в промышленных условиях для решения различных задач, включая средства приказной строки, веб-сервисы, средства DevOps, встраиваемые устройства, анализ и транскодирование аудио и видео, криптовалюты, биоинформатику, поисковые системы, приложения Интернета вещей, машинное обучение и даже основные части веб-браузера Firefox.

-

Разработчики Open Source

-

Rust предназначен для людей, которые хотят развивать язык программирования Rust, сообщество, средства для разработчиков и библиотеки. Мы будем рады, если вы внесёте свой вклад в развитие языка Rust.

-

Люди, ценящие скорость и безотказность

-

Rust предназначен для любителей скорости и безотказности в языке. Под скоростью мы подразумеваем как быстродействие программы на Rust, так и быстроту, с которой Ржавчина позволяет писать программы. Проверки сборщика Ржавчина обеспечивают безотказность за счёт полезных дополнений и переработки кода. Это выгодно отличается от хрупкого унаследованного кода в языках без таких проверок, который разработчики часто боятся изменять. Благодаря обеспечению абстракций с нулевой стоимостью, высокоуровневых возможностей, собираемых в низкоуровневый код такой же быстрый, как и написанный вручную, Ржавчина стремится сделать безопасный код ещё и быстрым.

-

Язык Ржавчина надеется поддержать и многих других пользователей; перечисленные здесь - лишь самые значимые увлеченные лица. В целом, главная цель Ржавчина - избавиться от соглашений, на которые программисты шли десятилетиями, обеспечив безопасность и производительность, скорость и удобство. Попробуйте Ржавчина и убедитесь, подойдут ли вам его решения.

-

Для кого эта книга

-

В этой книге предполагается, что вы писали код на другом языке программирования, но не оговаривается, на каком именно. Мы постарались сделать источник доступным для широкого круга людей с разным уровнем подготовки в области программирования. Мы не будем тратить время на обсуждение сути понятия программирования или как его понимать. Если вы совсем новичок в программировании, советуем прочитать книгу, посвящённую введению в программирование.

-

Как использовать эту книгу

-

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

-

В этой книге вы найдёте два вида глав: главы о подходах и главы с делом. В главах о подходах вы узнаете о каком-либо особенности Rust. В главах дела мы будем вместе создавать небольшие программы, применяя то, что вы уже узнали. Главы 2, 12 и 20 - это главы дела; остальные - главы о подходах.

-

Глава 1 объясняет, как установить Rust, как написать программу "Hello, world!" и как использовать Cargo, управленец дополнений и средство сборки Rust. Глава 2 - это опытное введение в написание программы на Rust, в которой вам предлагается создать игру для угадывания чисел. Здесь мы рассмотрим подходы на высоком уровне, а в последующих главах будет предоставлена дополнительная сведения. Если вы хотите сразу же приступить к работе, глава 2 - самое подходящее место для этого. В главе 3 рассматриваются возможности Rust, схожие с возможностями других языков программирования, а в главе 4 вы узнаете о системе владения Rust. Если вы особенно дотошный ученик и предпочитаете изучить каждую подробность, прежде чем переходить к следующей, возможно, вы захотите пропустить главу 2 и сразу перейти к главе 3, вернувшись к главе 2, когда захотите поработать над делом, применяя изученные подробности.

-

Глава 5 описывает устройства и способы, а глава 6 охватывает перечисления, выражения match и устройства управления потоком if let. Вы будете использовать устройства и перечисления для создания пользовательских видов в Rust.

-

В главе 7 вы узнаете о системе звеньев Rust, о правилах согласования закрытости вашего кода и его открытом внешней оболочке прикладного программирования (API). В главе 8 обсуждаются некоторые распространённые устройства данных - собрания, которые предоставляет обычная библиотека, такие как векторы, строки и HashMaps. В главе 9 рассматриваются философия и способы обработки ошибок в Rust.

-

В главе 10 рассматриваются образцовые виды данных, особенности и времена жизни, позволяющие написать код, который может использоваться разными видами. Глава 11 посвящена проверке, которое даже с заверениями безопасности в Ржавчина необходимо для обеспечения правильной логики вашей программы. В главе 12 мы создадим собственную выполнение подмножества возможности средства приказной строки grep, предназначенного для поиска текста в файлах. Для этого мы будем использовать многие подходы, которые обсуждались в предыдущих главах.

-

В главе 13 рассматриваются замыкания и повторители: особенности Rust, пришедшие из полезных языков программирования. В главе 14 мы более подробно рассмотрим Cargo и поговорим о лучших способах распространения ваших библиотек среди других разработчиков. В главе 15 обсуждаются умные указатели, которые предоставляет обычная библиотека, и особенности, обеспечивающие их возможность.

-

В главе 16 мы рассмотрим различные подходы одновременного программирования и поговорим о возможности Ржавчина для безбоязненного многопоточно программирования. В главе 17 рассматривается сравнение идиом Ржавчина с принципами предметно-направленного программирования, которые наверняка вам знакомы.

-

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

-

В главе 20 мы завершим дело, в котором выполняем низкоуровневый многопоточный веб-сервер!

-

Наконец, некоторые приложения содержат полезную сведения о языке в более справочном виде. В приложении A рассматриваются ключевые слова Rust, в приложении B — операторы и символы Rust, в приложении C — производные особенности, предоставляемые встроенной библиотекой, в приложении D — некоторые полезные средства разработки, а в приложении E — издания Rust. В приложении F вы найдёте переводы книги, а в приложении G мы расскажем о том, как создаётся Ржавчина и что такое nightly Rust.

-

Нет неправильного способа читать эту книгу: если вы хотите пропустить главу - сделайте это! Возможно, вам придётся вернуться к предыдущим главам, если возникнет недопонимание. Делайте все, как вам удобно.

-

-

Важной частью этапа обучения Ржавчина является изучение того, как читать сообщения об ошибках, которые отображает сборщик: они приведут вас к работающему коду. Мы изучим много примеров, которые не собираются и отображают ошибки в сообщениях сборщика в разных случаейх. Знайте, что если вы введёте и запустите случайный пример, он может не собраться! Убедитесь, что вы прочитали окружающий текст, чтобы понять, не предназначен ли пример, который вы пытаетесь запустить, для отображения ошибки. Ferris также поможет вам различить код, который не предназначен для работы:

-
- - - -
FerrisПояснения
Ferris with a question markЭтот код не собирается!
Феррис вскидывает рукиЭтот код вызывает панику!
Феррис с одним когтем вверх, пожимая плечамиЭтот код не приводит к желаемому поведению.
-
-

В большинстве случаев мы приведём вас к правильной исполнения любого кода, который не собирается.

-

Исходные коды

-

Файлы с исходным кодом, используемым в этой книге, можно найти на GitHub.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch01-00-getting-started.html b/rustbook-ru/book/ch01-00-getting-started.html deleted file mode 100644 index 44682704e..000000000 --- a/rustbook-ru/book/ch01-00-getting-started.html +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - С чего начать - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Начало работы

-

Начнём наше путешествие в Rust! Нужно много всего изучить, но каждое путешествие с чего-то начинается. В этой главе мы обсудим:

-
    -
  • установку Ржавчина на Linux, macOS и Windows,
  • -
  • написание программы, печатающей Hello, world!,
  • -
  • использование cargo, управленца дополнений и системы сборки в одном лице для Rust.
  • -
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch01-01-installation.html b/rustbook-ru/book/ch01-01-installation.html deleted file mode 100644 index f3b8ae3e6..000000000 --- a/rustbook-ru/book/ch01-01-installation.html +++ /dev/null @@ -1,289 +0,0 @@ - - - - - - Установка - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Установка

-

Первым шагом является установка Rust. Мы загрузим Rust, используя средство приказной строки rustup, предназначенный для управлениями исполнениями Ржавчина и другими связанными с ним средствами. Вам понадобится интернет-соединение для его загрузки.

-
-

Примечание: если вы по каким-то причинам предпочитаете не использовать rustup, пожалуйста, посетите страницу «Другие способы установки Rust» для получения дополнительных возможностей.

-
-

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

-
-

Условные обозначения приказной строки

-

В этой главе и во всей книге мы будем выполнять некоторые приказы, используемые в окне вызова. Строки, которые вы должны вводить в окне вызова, начинаются с $. Вам не нужно вводить символ $; это подсказка приказной строки, отображаемая для обозначения начала каждой приказы. Строки, которые не начинаются с $, обычно показывают вывод предыдущей приказы. Кроме того, в примерах, своеобразных для PowerShell, будет использоваться >, а не $.

-
-

Установка rustup на Linux или macOS

-

Если вы используете Linux или macOS, пожалуйста, выполните следующую приказ:

-
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
-
-

Приказ загружает сценарий и запускает установку средства rustup, который устанавливает последнюю безотказную исполнение Rust. Вам может быть предложено ввести пазначение. Если установка прошла успешно, появится следующая строка:

-
Rust is installed now. Great!
-
-

Вам также понадобится составитель (linker) — программа, которую Ржавчина использует для объединения своих собранных выходных данных в один файл. Скорее всего, он у вас уже есть. При возникновении ошибок объединения, вам следует установить сборщик C, который обычно будет включать в себя и составитель. Сборщик C также полезен, потому что некоторые распространённые дополнения Ржавчина зависят от кода C и нуждаются в сборщике C.

-

На macOS вы можете получить сборщик C, выполнив приказ:

-
$ xcode-select --install
-
-

Пользователи Linux, как правило, должны устанавливать GCC или Clang в соответствии с документацией их установочного набора. Например, при использовании Ubuntu можно установить дополнение build-essential.

-

Установка rustup на Windows

-

На Windows перейдите по адресу https://www.rust-lang.org/tools/install и следуйте указаниям по установке Rust. На определённом этапе установки вы получите сообщение, предупреждающее, что вам также понадобятся средства сборки MSVC для Visual Studio 2013 или более поздней исполнения.

-

Чтобы получить средства сборки, вам потребуется установить Visual Studio 2022. На вопрос о том, какие составляющие необходимо установить, выберите:

-
    -
  • “Desktop Development with C++”
  • -
  • The Windows 10 or 11 SDK
  • -
  • Английский языковой дополнение вместе с любым другим языковым дополнением по вашему выбору.
  • -
-

В остальной части этой книги используются приказы, которые работают как в cmd.exe, так и в PowerShell. При наличии отличительных различий мы объясним, что необходимо сделать в таких случаях.

-

Устранение возможных ошибок

-

Чтобы проверить, правильно ли у вас установлен Rust, откройте оболочку и введите эту строку:

-
$ rustc --version
-
-

Вы должны увидеть номер исполнения, хэш определения и дату определения для последней безотказной исполнения, которая была выпущена, в следующем виде:

-
rustc x.y.z (abcabcabc yyyy-mm-dd)
-
-

Если вы видите эту сведения, вы успешно установили Rust! Если вы не видите эту сведения, убедитесь, что Ржавчина находится в вашей системной переменной %PATH% следующим образом:

-

В Windows CMD:

-
> echo %PATH%
-
-

В PowerShell:

-
> echo $env:Path
-
-

В Linux и macOS:

-
$ echo $PATH
-
-

Если все было сделано правильно, но Ржавчина все ещё не работает, есть несколько мест, где вам могут помочь. Узнайте, как связаться с другими Rustaceans (так мы себя называем) на странице сообщества.

-

Обновление и удаление

-

После установки Ржавчина с помощью rustup обновление до новой исполнения не составит труда. В приказной оболочке запустите следующий скрипт обновления:

-
$ rustup update
-
-

Чтобы удалить Ржавчина и rustup, выполните следующую приказ:

-
$ rustup self uninstall
-
-

Местная документация

-

Установка Ржавчина также включает местную повтор документации, чтобы вы могли читать её в без доступа к мировой сети режиме. Выполните rustup doc, чтобы открыть местную документацию в браузере.

-

Если обычная библиотека предоставляет вид или функцию, а вы не знаете, что она делает или как её использовать, воспользуйтесь документацией внешней оболочки прикладного программирования (API), чтобы это узнать!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch01-02-hello-world.html b/rustbook-ru/book/ch01-02-hello-world.html deleted file mode 100644 index f831807c6..000000000 --- a/rustbook-ru/book/ch01-02-hello-world.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - Привет, Мир! - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Привет, мир!

-

Теперь, когда вы установили Rust, пришло время написать свою первую программу на Rust. Привычно при изучении нового языка принято писать небольшую программу, которая печатает на экране текст Привет, мир!, поэтому мы сделаем то же самое!

-
-

Примечание: Эта книга предполагает наличие достаточного навыка работы с приказной строкой. Ржавчина не предъявляет особых требований к тому, каким набором средств вы пользуетесь для изменения или хранения вашего кода, поэтому если вы предпочитаете использовать встроенную среду разработки (IDE) вместо приказной строки, смело используйте вашу любимую IDE. Многие IDE сейчас в той или иной степени поддерживают Rust; подробности можно узнать из документации к IDE. Объединение Ржавчина сосредоточилась на обеспечении отличной поддержки IDE с помощью rust-analyzer. Более подробную сведения смотрите в Приложении D.

-
-

Создание папки дела

-

Прежде всего начнём с создания папки, в которой будем сохранять наш код на языке Rust. На самом деле не важно, где сохранять наш код. Однако, для упражнений и дел, обсуждаемых в данной книге, мы советуем создать папку projects в вашем домашнем папке, там же и хранить в будущем код программ из книги.

-

Откройте окно вызова и введите следующие приказы для того, чтобы создать папку projects для хранения кода разных дел, и, внутри неё, папку hello_world для дела “Привет, мир!”.

-

Для Linux, macOS и PowerShell на Windows, введите:

-
$ mkdir ~/projects
-$ cd ~/projects
-$ mkdir hello_world
-$ cd hello_world
-
-

Для Windows в CMD, введите:

-
> mkdir "%USERPROFILE%\projects"
-> cd /d "%USERPROFILE%\projects"
-> mkdir hello_world
-> cd hello_world
-
-

Написание и запуск первой Ржавчина программы

-

Затем создайте новый исходный файл и назовите его main.rs. Файлы Ржавчина всегда заканчиваются расширением .rs. Если вы используете более одного слова в имени файла, принято разделять их символом подчёркивания. Например, используйте hello_world.rs вместо helloworld.rs.

-

Теперь откроем файл main.rs для изменения и введём следующие строки кода:

-

Название файла: main.rs

-
fn main() {
-    println!("Привет, мир!");
-}
-

Приложение 1-1: Программа, которая печатает Привет, мир!

-

Сохраните файл и вернитесь в окно окна вызова в папка ~/projects/hello_world. В Linux или macOS введите следующие приказы для сборки и запуска файла:

-
$ rustc main.rs
-$ ./main
-Привет, мир!
-
-

В Windows, введите приказ .\main.exe вместо ./main:

-
> rustc main.rs
-> .\main.exe
-Привет, мир!
-
-

Независимо от вашей операционной системы, строка Привет, мир! должна быть выведена на окно вызова. Если вы не видите такого вывода, обратитесь к разделу "Устранение неполадок ", чтобы узнать, как получить помощь.

-

Если напечаталось Привет, мир!, то примите наши поздравления! Вы написали программу на Rust, что делает вас Ржавчина программистом — добро пожаловать!

-

Анатомия программы на Rust

-

Давайте рассмотрим «Привет, мир!» программу в подробностях. Вот первая часть головоломки:

-
fn main() {
-
-}
-

Эти строки определяют функцию с именем main. Функция main особенная: это всегда первый код, который запускается в каждой исполняемой программе Rust. Первая строка объявляет функцию с именем main, которая не имеет свойств и ничего не возвращает. Если бы были свойства, они бы заключались в круглые скобки ().

-

Тело функции заключено в {}. Ржавчина требует фигурных скобок вокруг всех тел функций. Хороший исполнение — поместить открывающую фигурную скобку на ту же строку, что и объявление функции, добавив между ними один пробел.

-
-

Примечание: Если хотите придерживаться принятого исполнения во всех делах Rust, вы можете использовать средство самостоятельного изменения под названием rustfmt для изменения кода в определённом исполнении (подробнее о rustfmt в Приложении D. Объединение Ржавчина включила этот средство в обычный установочный набор Rust, как rustc, поэтому он уже должен быть установлен на вашем компьютере!

-
-

Тело функции main содержит следующий код:

-
#![allow(unused)]
-fn main() {
-    println!("Привет, мир!");
-}
-

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

-

Во-первых, исполнение Ржавчина предполагает отступ в четыре пробела, а не табуляцию.

-

Во-вторых, println! вызывается макрос Rust. Если бы вместо него была вызвана функция, она была бы набрана как println (без !). Более подробно мы обсудим макросы Ржавчина в главе 19. Пока достаточно знать, что использование ! подразумевает вызов макроса вместо обычной функции, и что макросы не всегда подчиняются тем же правилам как функции.

-

В-третьих, вы видите строку "Привет, мир!". Мы передаём её в качестве переменной макросу println!, и она выводится на экран.

-

В-четвёртых, мы завершаем строку точкой с запятой (;), которая указывает на окончание этого выражения и возможность начала следующего. Большинство строк кода Ржавчина заканчиваются точкой с запятой.

-

Сборка и запуск - это отдельные шаги

-

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

-

Перед запуском программы на Ржавчина вы должны собрать её с помощью сборщика Rust, введя приказ rustc и передав ей имя вашего исходного файла, например:

-
$ rustc main.rs
-
-

Если у вас есть опыт работы с C или C++, вы заметите, что это похоже на gcc или clang. После успешной сборки Ржавчина выводит двоичный исполняемый файл.

-

В Linux, macOS и PowerShell в Windows вы можете увидеть исполняемый файл, введя приказ ls в оболочке:

-
$ ls
-main  main.rs
-
-

В Linux и macOS вы увидите два файла. При использовании PowerShell в Windows вы увидите такие же три файла, как и при использовании CMD. Используя CMD в Windows, введите следующее:

-
> dir /B %= the /B option says to only show the file names =%
-main.exe
-main.pdb
-main.rs
-
-

Это показывает исходный код файла с расширением .rs, исполняемый файл (main.exe на Windows, но main на всех других площадках) и, при использовании Windows, файл, содержащий отладочную сведения с расширением .pdb. Отсюда вы запускаете файлы main или main.exe, например:

-
$ ./main # для Linux
-> .\main.exe # для Windows
-
-

Если ваш main.rs — это ваша программа «Привет, мир!», эта строка выведет в окно вызова Привет, мир!.

-

Если вы лучше знакомы с изменяемыми языками, такими как Ruby, Python или JavaScript, возможно, вы не привыкли собирать и запускать программу как отдельные шаги. Ржавчина — это предварительно собранный язык, то есть вы можете собрать программу и передать исполняемый файл кому-то другому, и он сможет запустить его даже без установленного Rust. Если вы даёте кому-то файл .rb , .py или .js, у него должна быть установлена выполнение Ruby, Python или JavaScript (соответственно). Но в этих языках вам нужна только одна приказ для сборки и запуска вашей программы. В внешнем виде языков программирования всё — соглашение.

-

Сборка с помощью rustc подходит для простых программ, но по мере роста вашего дела вы захотите управлять всеми свойствами и упростить передачу кода. Далее мы познакомим вас с средством Cargo, который поможет вам писать программы из существующего мира на Rust.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch01-03-hello-cargo.html b/rustbook-ru/book/ch01-03-hello-cargo.html deleted file mode 100644 index c322f1715..000000000 --- a/rustbook-ru/book/ch01-03-hello-cargo.html +++ /dev/null @@ -1,332 +0,0 @@ - - - - - - Hello, Cargo! - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Привет, Cargo!

-

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

-

Самые простые программы на Rust, подобные той, которую мы написали, не имеют никаких зависимостей. Если бы мы сделали дело «Hello, world!» с Cargo, он бы использовал только ту часть Cargo, которая отвечает за сборку вашего кода. По мере написания более сложных программ на Ржавчина вы будете добавлять зависимости, а если вы начнёте дело с использованием Cargo, добавлять зависимости станет намного проще.

-

Поскольку значительное число дел Ржавчина используют Cargo, оставшаяся часть книги подразумевает, что вы тоже используете Cargo. Cargo входит в состав поставки Rust, если вы использовали напрямую от разрабочиков программы установки, рассмотренные в разделе "Установка". Если вы установили Ржавчина другим способом, проверьте, установлен ли Cargo, введя в окне вызова следующее:

-
$ cargo --version
-
-

Если приказ выдал номер исполнения, то значит Cargo установлен. Если вы видите ошибку, вроде command not found ("приказ не найдена"), загляните в документацию для использованного вами способа установки, чтобы выполнить установку Cargo отдельно.

-

Создание дела с помощью Cargo

-

Давайте создадим новый дело с помощью Cargo и посмотрим, как он отличается от нашего начального дела "Hello, world!". Перейдите обратно в папку projects (или любую другую, где вы решили сохранять код). Затем, в любой операционной системе, запустите приказ:

-
$ cargo new hello_cargo
-$ cd hello_cargo
-
-

Первая приказ создаёт новый папка и дело с именем hello_cargo. Мы назвали наш дело hello_cargo, и Cargo создаёт свои файлы в папке с тем же именем.

-

Перейдём в папка hello_cargo и посмотрим файлы. Увидим, что Cargo создал два файла и одну папку: файл Cargo.toml и папка src с файлом main.rs внутри.

-

Кроме того, cargo объявлял новый хранилище Git вместе с файлом .gitignore. Файлы Git не будут созданы, если вы запустите cargo new в существующем хранилища Git; вы можете изменить это поведение, используя cargo new --vcs=git.

-
-

Примечание. Git — это распространённая система управления исполнений. Вы можете изменить cargo new, чтобы использовать другую систему управления исполнений или не использовать систему управления исполнений, используя флаг --vcs. Запустите cargo new --help, чтобы увидеть доступные свойства.

-
-

Откройте файл Cargo.toml в любом текстовом редакторе. Он должен выглядеть как код в приложении 1-2.

-

Файл: Cargo.toml

-
[package]
-name = "hello_cargo"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-
-

Приложение 1-2: Содержимое файла Cargo.toml, созданное приказом cargo new

-

Это файл в видеTOML (Tom’s Obvious, Minimal Language), который является видом настроек Cargo.

-

Первая строка, [package], является заголовочной разделом, которая указывает что следующие указания настраивают дополнение. По мере добавления больше сведений в данный файл, будет добавляться больше разделов и указаний (строк).

-

Следующие три строки задают сведения о настройке, необходимую Cargo для сборки вашей программы: имя, исполнение и издание Rust, который будет использоваться. Мы поговорим о ключе edition в Приложении E.

-

Последняя строка, [dependencies] является началом разделы для списка любых зависимостей вашего дела. В Rust, это внешние дополнения кода, на которые ссылаются ключевым словом crate. Нам не нужны никакие зависимости в данном деле, но мы будем использовать их в первом деле главы 2, так что нам пригодится данная раздел зависимостей потом.

-

Откройте файл src/main.rs и загляните в него:

-

Файл: src/main.rs

-
fn main() {
-    println!("Hello, world!");
-}
-

Cargo создал для вас программу "Hello, world!", подобную той, которую мы написали в Приложении 1-1! Пока что различия между нашим предыдущим делом и делом, созданным при помощи Cargo, заключаются в том, что Cargo поместил исходный код в папка src, и у нас есть настроечный файл Cargo.toml в верхнем папке дела.

-

Cargo ожидает, что ваши исходные файлы находятся внутри папки src. Папка верхнего уровня дела предназначен только для файлов README, сведений о лицензии, файлы настройке и чего то ещё не относящего к вашему коду. Использование Cargo помогает создавать дело. Есть место для всего и все находится на своём месте.

-

Если вы начали дело без использования Cargo, как мы делали для "Hello, world!" дела, то можно преобразовывать его в дело с использованием Cargo. Переместите код в подпапка src и создайте соответствующий файл Cargo.toml в папке.

-

Сборка и запуск Cargo дела

-

Посмотрим, в чем разница при сборке и запуске программы "Hello, world!" с помощью Cargo. В папке hello_cargo соберите дело следующей приказом:

-
$ cargo build
-   Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
-    Finished dev [unoptimized + debuginfo] target(s) in 2.85 secs
-
-

Этот приказ создаёт исполняемый файл в target/debug/hello_cargo (или target\debug\hello_cargo.exe в Windows), а не в вашем текущем папке. Поскольку обычная сборка является отладочной, Cargo помещает двоичный файл в папка с именем debug. Вы можете запустить исполняемый файл с помощью этой приказы:

-
$ ./target/debug/hello_cargo # or .\target\debug\hello_cargo.exe on Windows
-Hello, world!
-
-

Если все хорошо, то Hello, world! печатается в окне вызова. Запуск приказы cargo build в первый раз также приводит к созданию нового файла Cargo.lock в папке верхнего уровня. Данный файл хранит точные исполнения зависимостей вашего дела. Так как у нас нет зависимостей, то файл пустой. Вы никогда не должны менять этот файл вручную: Cargo сам управляет его содержимым для вас.

-

Только что мы собрали дело приказом cargo build и запустили его из ./target/debug/hello_cargo. Но мы также можем при помощи приказы cargo run сразу и собрать код, и затем запустить полученный исполняемый файл всего лишь одной приказом:

-
$ cargo run
-    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
-     Running `target/debug/hello_cargo`
-Hello, world!
-
-

Использование cargo run более удобно, чем необходимость помнить и запускать cargo build, а затем использовать весь путь к двоичному файлу, поэтому большинство разработчиков используют cargo run.

-

Обратите внимание, что на этот раз мы не видели вывода, указывающего на то, что Cargo собирает hello_cargo. Cargo выяснил, что файлы не изменились, поэтому не стал пересобирать, а просто запустил двоичный файл. Если бы вы изменили свой исходный код, Cargo пересобрал бы дело перед его запуском, и вы бы увидели этот вывод:

-
$ cargo run
-   Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.33 secs
-     Running `target/debug/hello_cargo`
-Hello, world!
-
-

Cargo также предоставляет приказ, называемую cargo check. Этот приказ быстро проверяет ваш код, чтобы убедиться, что он собирается, но не создаёт исполняемый файл:

-
$ cargo check
-   Checking hello_cargo v0.1.0 (file:///projects/hello_cargo)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs
-
-

Почему вам не нужен исполняемый файл? Часто cargo check выполняется намного быстрее, чем cargo build, поскольку пропускает этап создания исполняемого файла. Если вы постоянно проверяете свою работу во время написания кода, использование cargo check ускорит этап уведомления вас о том, что ваш дело всё ещё собирается! Таким образом, многие Rustacean периодически запускают cargo check, когда пишут свои программы, чтобы убедиться, что она собирается. Затем они запускают cargo build, когда готовы использовать исполняемый файл.

-

Давайте подытожим, что мы уже узнали о Cargo:

-
    -
  • Мы можем создать дело с помощью cargo new.
  • -
  • можно собирать дело, используя приказ cargo build,
  • -
  • можно одновременно собирать и запускать дело одной приказом cargo run,
  • -
  • можно собрать дело для проверки ошибок с помощью cargo check, не тратя время на кодосоздание исполняемого файла,
  • -
  • cargo сохраняет итоги сборки не в папку с исходным кодом, а в отдельный папка target/debug.
  • -
-

Дополнительным преимуществом использования Cargo является то, что его приказы одинаковы для разных операционных систем. С этой точки зрения, мы больше не будем предоставлять отдельные указания для Linux, macOS или Windows.

-

Сборка конечной исполнения (Release)

-

Когда дело, наконец, готов к исполнению, можно использовать приказ cargo build --release для его сборки с переработкой. Данная приказ создаёт исполняемый файл в папке target/release в отличии от папки target/debug. Переработки делают так, что Ржавчина код работает быстрее, но их включение увеличивает время сборки. По этой причине есть два отдельных профиля: один для разработки, когда нужно осуществлять сборку быстро и часто, и другой, для сборки конечной программы, которую будете отдавать пользователям, которая готова к работе и будет выполняться сверх быстро. Если вы замеряете время выполнения вашего кода, убедитесь, что собрали дело с переработкой cargo build --release и проверяете исполняемый файл из папки target/release.

-

Cargo как Условие

-

В простых делах Cargo не даёт больших преимуществ по сравнению с использованием rustc, но он проявит себя, когда ваши программы станут более сложными. Когда программы вырастают до нескольких файлов или нуждаются в зависимостях, гораздо проще позволить Cargo согласовывать сборку.

-

Не смотря на то, что дело hello_cargo простой, теперь он использует большую часть существующего набора средств, который вы будете повседневно использовать в вашей развитии, связанной с Rust. Когда потребуется работать над делами размещёнными в сети, вы сможете просто использовать следующую последовательность приказов для получения кода с помощью Git, перехода в папка дела, сборку дела:

-
$ git clone example.org/someproject
-$ cd someproject
-$ cargo build
-
-

Для получения дополнительной сведений о Cargo ознакомьтесь с его документацией .

-

Итоги

-

Теперь вы готовы начать своё Ржавчина путешествие! В данной главе вы изучили как:

-
    -
  • установить последнюю безотказную исполнение Rust, используя rustup,
  • -
  • обновить Ржавчина до последней исполнения,
  • -
  • открыть местно установленную документацию,
  • -
  • написать и запустить программу вида "Hello, world!", используя напрямую сборщик rustc,
  • -
  • создать и запустить новый дело, используя соглашения и приказы Cargo.
  • -
-

Это отличное время для создания более существенной программы, чтобы привыкнуть читать и писать код на языке Rust. Итак, в главе 2 мы построим программу для игры в угадай число. Если вы предпочитаете начать с изучения того, как работают общие подходы программирования в Rust, обратитесь к главе 3, а затем вернитесь к главе 2.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch02-00-guessing-game-tutorial.html b/rustbook-ru/book/ch02-00-guessing-game-tutorial.html deleted file mode 100644 index 57324c616..000000000 --- a/rustbook-ru/book/ch02-00-guessing-game-tutorial.html +++ /dev/null @@ -1,974 +0,0 @@ - - - - - - Программирование игры в загадки - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Программируем игру в загадки

-

Давайте окунёмся в Rust, вместе поработав над опытным делом! В этой главе вы познакомитесь с несколькими общими подходами Rust, показав, как использовать их в существующей программе. Вы узнаете о let , match, способах, сопряженных функциях, внешних дополнениях и многом другом! В следующих главах мы рассмотрим эти мысли более подробно. В этой главе вы просто примените в основах.

-

Мы выполняем привычную для начинающих программистов задачу — игру в загадки. Вот как это работает: программа порождает случайное целое число в ряде от 1 до 100. Затем она предлагает игроку его угадать. После ввода числа программа укажет, меньше или больше было загаданное число. Если догадка верна, игра напечатает поздравительное сообщение и завершится.

-

Настройка нового дела

-

Для настройки нового дела перейдите в папка projects, который вы создали в главе 1, и создайте новый дело с использованием Cargo, как показано ниже:

-
$ cargo new guessing_game
-$ cd guessing_game
-
-

Первая приказ, cargo new, принимает в качестве первого переменной имя дела (guessing_game). Вторая приказ изменяет папка на новый папка дела.

-

Загляните в созданный файл Cargo.toml:

- -

Файл: Cargo.toml

-
[package]
-name = "guessing_game"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-
-

Как вы уже видели в главе 1, cargo new создаёт программу «Hello, world!». Посмотрите файл src/main.rs:

-

Файл: src/main.rs

-
fn main() {
-    println!("Hello, world!");
-}
-

Теперь давайте соберем программу «Hello, world!» и сразу на этом же этапе запустим её с помощью приказы cargo run:

-
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.50s
-     Running `target/debug/guessing_game`
-Hello, world!
-
-

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

-

Снова откройте файл src/main.rs. Весь код вы будете писать в нем.

-

Обработка догадки

-

Первая часть программы запрашивает ввод данных пользователем, обрабатывает их и проверяет, что они в ожидаемой виде. Начнём с того, что позволим игроку ввести догадку. Вставьте код из приложения 2-1 в src/main.rs.

-

Файл: src/main.rs

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Приложение 2-1: код, который получает догадку от пользователя и выводит её на экран

-

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

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

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

-

Если вид, который требуется использовать, отсутствует в прелюдии, его нужно явно ввести в область видимости с помощью оператора use. Использование библиотеки std::io предоставляет ряд полезных полезных возможностей, включая способность принимать пользовательский ввод.

-

Как уже отмечалось в главе 1, функция main является точкой входа в программу:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Ключевое слово fn объявляет новую функцию, круглые скобки () показывают, что у функции нет входных свойств, фигурная скобка { - обозначение начала тела функции.

-

Также в главе 1 упоминалось, что println! — это макрос, который выводит строку на экран:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Этот код показывает сведения о ходе игры и запрашивает пользовательский ввод.

-

Хранение значений с помощью переменных

-

Далее мы создаём переменную для хранения пользовательского ввода, как показано ниже:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Вот теперь программа становится важнее! В этой маленькой строке на самом деле происходит очень многое. Для создания переменной мы используем оператор let. Вот ещё один пример:

-
let apples = 5;
-

Эта строка создаёт новую переменную с именем apples и привязывает её к значению 5. В Ржавчина переменные неизменяемы по умолчанию, то есть как только мы присвоим переменной значение, оно не изменится. Мы подробно обсудим эту подход в разделе "Переменные и изменчивость". в главе 3. Чтобы сделать переменную изменяемой, мы добавляем mut перед её именем:

-
let apples = 5; // неизменяемая
-let mut bananas = 5; // изменяемая
-
-

Примечание: сочетание знаков // начинает примечание, который продолжается до конца строки. Ржавчина пренебрегает всё, что находится в примечаниях. Мы обсудим примечания более подробно в Главе 3.

-
-

Возвращаясь к программе игры "Угадайка" — теперь вы знаете, что let mut guess предоставит изменяемую переменную с именем guess. Знак равенства (=) сообщает Rust, что сейчас нужно связать что-то с этой переменной. Справа от знака равенства находится значение, связанное с guess, которое является итогом вызова функции String::new, возвращающей новый образец String. String — это вид строки, предоставляемый встроенной библиотекой, который является расширяемым отрывком текста в кодировке UTF-8.

-

правила написания :: в строке ::new указывает, что new является сопряженной функцией вида String. Сопряженная функция — это функция, выполненная для вида, в данном случае String. Функция new создаёт новую пустую строку. Функцию new можно встретить во многих видах, это привычное название для функции, которая создаёт новое значение какого-либо вида.

-

В конечном итоге строка let mut guess = String::new(); создала изменяемую переменную, которая связывается с новым пустым образцом String. Фух!

-

Получение пользовательского ввода

-

Напомним: мы подключили возможность ввода/вывода из встроенной библиотеки с помощью use std::io; в первой строке программы. Теперь мы вызовем функцию stdin из звена io, которая позволит нам обрабатывать пользовательский ввод:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Если бы мы не подключили библиотеку io с помощью use std::io в начале программы, мы все равно могли бы использовать эту функцию, записав её вызов как std::io::stdin. Функция stdin возвращает образец std::io::Stdin, который является видом, представляющим указатель принятого ввода для вашего окна вызова.

-

Далее строка .read_line(&mut guess) вызывает способ read_line на указателе принятого ввода для получения ввода от пользователя. Мы также передаём &mut guess в качестве переменной read_line, сообщая ему, в какой строке хранить пользовательский ввод. Главная задача read_line — принять все, что пользователь вводит в обычный ввод, и сложить это в строку (не переписывая её содержимое), поэтому мы передаём эту строку в качестве переменной. Строковый переменная должен быть изменяемым, чтобы способ мог изменить содержимое строки.

-

Символ & указывает, что этот переменная является ссылкой, которая предоставляет возможность нескольким частям вашего кода получить доступ к одному отрывку данных без необходимости воспроизводить эти данные в память несколько раз. Ссылки — это сложная полезная возможность, а одним из главных преимуществ Ржавчина является безопасность и простота использования ссылок. Чтобы дописать эту программу, вам не понадобится знать много таких подробностей. Пока вам достаточно знать, что ссылки, как и переменные, по умолчанию неизменяемы. Соответственно, чтобы сделать её изменяемой, нужно написать &mut guess, а не &guess. (В главе 4 ссылки будут описаны более подробно).

- -

-

Обработка возможного сбоя с помощью вида Result

-

Мы всё ещё работаем над этой строкой кода. Сейчас мы обсуждаем третью строку, но обратите внимание, что она по-прежнему является частью одной логической строки. Следующая часть — способ:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Мы могли бы написать этот код так:

-
io::stdin().read_line(&mut guess).expect("Failed to read line");
-

Однако одну длинную строку трудно читать, поэтому лучше разделить её. При вызове способа с помощью правил написания .method_name() часто целесообразно вводить новую строку и другие пробельные символы, чтобы разбить длинные строки. Теперь давайте обсудим, что делает эта строка.

-

Как упоминалось ранее, read_line помещает всё, что вводит пользователь, в строку, которую мы ему передаём, но также возвращает значение Result. Result — это перечисление, часто называемое enum, то есть вид, который может находиться в одном из нескольких возможных состояний. Мы называем каждое такое состояние исходом.

-

В Главе 6 рассмотрим перечисления более подробно. Задачей видов Result является кодирование сведений для обработки ошибок.

-

Исходами Result являются Ok и Err. Исход Ok указывает, что действие завершилась успешно, а внутри Ok находится успешно созданное значение. Исход Err означает, что действие не удалась, а Err содержит сведения о причинах неудачи.

-

Значения вида Result, как и значения любого вида, имеют определённые для них способы. У образца Result есть способ expect, который можно вызвать. Если этот образец Result является значением Err, expect вызовет сбой программы и отобразит сообщение, которое вы передали в качестве переменной. Если способ read_line возвращает Err, то это, скорее всего, итог ошибки основной операционной системы. Если образец Result является значением Ok, expect возьмёт возвращаемое значение, которое удерживает Ok, и вернёт вам только это значение, чтобы вы могли его использовать далее. В данном случае это значение представляет собой количество байтов, введённых пользователем.

-

Если не вызвать expect, программа собирается, но будет получено предупреждение:

-
$ cargo build
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-warning: unused `Result` that must be used
-  --> src/main.rs:10:5
-   |
-10 |     io::stdin().read_line(&mut guess);
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: this `Result` may be an `Err` variant, which should be handled
-   = note: `#[warn(unused_must_use)]` on by default
-help: use `let _ = ...` to ignore the resulting value
-   |
-10 |     let _ = io::stdin().read_line(&mut guess);
-   |     +++++++
-
-warning: `guessing_game` (bin "guessing_game") generated 1 warning
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.59s
-
-

Rust предупреждает о неиспользованном значении Result, возвращаемого из read_line, показывая, что программа не учла возможность возникновения ошибки.

-

Правильный способ убрать предупреждение — это написать обработку ошибок, но в нашем случае мы просто хотим со сбоем завершить программу при возникновении сбоев, поэтому используем expect. О способах восстановления после ошибок вы узнаете в главе 9.

-

Вывод значений с помощью заполнителей println!

-

Кроме закрывающей фигурной скобки, в коде на данный мгновение есть ещё только одно место для обсуждения:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Этот код выводит строку, которая теперь содержит ввод пользователя. Набор фигурных скобок {} является заполнителем: думайте о {} как о маленьких клешнях краба, которые удерживают значение на месте. При печати значения переменной имя переменной может заключаться в фигурные скобки. При печати итога вычисления выражения поместите пустые фигурные скобки в строку вида, затем после строки вида укажите список выражений, разделённых запятыми, которые будут напечатаны в каждом заполнителе пустой фигурной скобки в том же порядке. Печать переменной и итога выражения одним вызовом println! будет выглядеть так:

-
#![allow(unused)]
-fn main() {
-let x = 5;
-let y = 10;
-
-println!("x = {x} and y + 2 = {}", y + 2);
-}
-

Этот код выведет x = 5 and y + 2 = 12.

-

Проверка первой части

-

Давайте проверим первую часть игры. Запустите её используя cargo run:

- -
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 6.44s
-     Running `target/debug/guessing_game`
-Guess the number!
-Please input your guess.
-6
-You guessed: 6
-
-

На данном этапе первая часть игры завершена: мы получаем ввод с клавиатуры и затем печатаем его.

-

Создание тайного числа

-

Далее нам нужно создать тайное число, которое пользователь попытается угадать. Тайное число должно быть каждый раз разным, чтобы в игру можно было играть несколько раз. Мы будем использовать случайное число в ряде от 1 до 100, чтобы игра не была слишком сложной. Ржавчина пока не включает возможность случайных чисел в свою обычную библиотеку. Однако приказ Ржавчина предоставляет [ящик rand] с подобной возможностью.

-

Использование ящика для получения дополнительного возможностей

-

Помните, что дополнение (crate) - это собрание файлов исходного кода Rust. Дело, создаваемый нами, представляет собой
двоичный дополнение (binary crate), который является исполняемым файлом. Дополнение rand - это библиотечный дополнение (library crate), содержащий код, который предназначен для использования в других программах и поэтому не может исполняться сам по себе.

-

Согласование работы внешних дополнений является тем местом, где Cargo на самом деле блистает. Чтобы начать писать код, использующий rand, необходимо изменить файл Cargo.toml, включив в него в качестве зависимости дополнение rand. Итак, откройте этот файл и добавьте следующую строку внизу под заголовком разделы [dependencies], созданным для вас Cargo. Обязательно укажите rand в точности так же, как здесь, с таким же номером исполнения, иначе примеры кода из этого урока могут не заработать.

- -

Имя файла: Cargo.toml

-
[dependencies]
-rand = "0.8.5"
-
-

В файле Cargo.toml всё, что следует за заголовком, является частью этой разделы, которая продолжается до тех пор, пока не начнётся следующая. В [dependencies] вы сообщаете Cargo, от каких внешних ящиков зависит ваш дело и какие исполнения этих ящиков вам нужны. В этом случае мы указываем ящик rand со определетелем смысловой исполнения 0.8.5. Cargo понимает смысловое управление исполнениями (иногда называемое SemVer), которое является исполнением для описания исполнений. Число 0.8.5 на самом деле является сокращением от ^0.8.5, что означает любую исполнение не ниже 0.8.5, но ниже 0.9.0.

-

Cargo рассчитывает, что эти исполнения имеют общедоступное API, совместимое с исполнением 0.8.5, и вы получите последние исполнения исправлений, которые по-прежнему будут собираться с кодом из этой главы. Не обеспечивается, что исполнение 0.9.0 или выше будет иметь тот же API, что и в следующих примерах.

-

Теперь, не меняя ничего в коде, давайте соберём дело, как показано в приложении 2-2.

- -
$ cargo build
-    Updating crates.io index
-  Downloaded rand v0.8.5
-  Downloaded libc v0.2.127
-  Downloaded getrandom v0.2.7
-  Downloaded cfg-if v1.0.0
-  Downloaded ppv-lite86 v0.2.16
-  Downloaded rand_chacha v0.3.1
-  Downloaded rand_core v0.6.3
-   Compiling libc v0.2.127
-   Compiling getrandom v0.2.7
-   Compiling cfg-if v1.0.0
-   Compiling ppv-lite86 v0.2.16
-   Compiling rand_core v0.6.3
-   Compiling rand_chacha v0.3.1
-   Compiling rand v0.8.5
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 2.53s
-
-

Приложение 2-2: итог выполнения cargo build после добавления ящика rand в качестве зависимости

-

Вы можете увидеть другие номера исполнений (но все они будут совместимы с кодом благодаря SemVer), другие строки (в зависимости от операционной системы), а также строки могут быть расположены в другом порядке.

-

Когда мы включаем внешнюю зависимость, Cargo берет последние исполнения всего, что нужно этой зависимости, из реестра (registry), который является повтором данных с Crates.io. Crates.io — это место, где участники внутреннего устройства Ржавчина размещают свои дела с открытым исходным кодом для использования другими.

-

После обновления реестра Cargo проверяет раздел [dependencies] и загружает все указанные в списке дополнения, которые ещё не были загружены. В нашем случае, хотя мы указали только rand в качестве зависимости, Cargo также захватил другие дополнения, от которых зависит работа rand. После загрузки дополнений Ржавчина собирает их, а затем собирает дело с имеющимися зависимостями.

-

Если сразу же запустить cargo build снова, не внося никаких изменений, то кроме строки Finished вы не получите никакого вывода. Cargo знает, что он уже загрузил и собрал зависимости, и вы не вносили никаких изменений в файл Cargo.toml. Cargo также знает, что вы ничего не изменили в своём коде, поэтому он не пересоберет и его. Если делать нечего, он просто завершает работу.

-

Если вы откроете файл src/main.rs, внесёте обыкновенное изменение, а затем сохраните его и снова соберёте, вы увидите только две строки вывода:

- -
$ cargo build
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
-
-

Эти строки показывают, что Cargo обновляет сборку только с вашим крошечным изменением в файле src/main.rs. Ваши зависимости не изменились, поэтому Cargo знает, что может повторно использовать то, что уже скачано и собрано для них.

-

Обеспечение воспроизводимых сборок с помощью файла Cargo.lock

-

В Cargo есть рычаг, обеспечивающий возможность пересобрать всё тот же артефакт каждый раз, когда вы или кто-либо другой собирает ваш код. Пока вы не укажете обратное, Cargo будет использовать только те исполнения зависимостей, которые были заданы ранее. Например, допустим, что на следующей неделе выходит исполнение 0.8.6 дополнения rand , и она содержит важное исправление ошибки, но также отступление, которая может сломать ваш код. Чтобы справиться с этим, Ржавчина создаёт файл Cargo.lock при первом запуске cargo build, поэтому теперь он есть в папке guessing_game.

-

Когда вы создаёте дело в первый раз, Cargo определяет все исполнения зависимостей, которые соответствуют условиям, а затем записывает их в файл Cargo.lock. Когда вы будете собирать свой дело в будущем, Cargo увидит, что файл Cargo.lock существует, и будет использовать указанные там исполнения, а не выполнять всю работу по выяснению исполнений заново. Это позволяет самостоятельно создавать воспроизводимую сборку. Другими словами, ваш дело останется на 0.8.5 до тех пор, пока вы явно не обновите его благодаря файлу Cargo.lock. Поскольку файл Cargo.lock важен для воспроизводимых сборок, он часто хранится в системе управления исполнениями вместе с остальным кодом дела.

-

Обновление дополнения для получения новой исполнения

-

Если вы захотите обновить дополнение, Cargo предоставляет приказ update, которая пренебрегает файл Cargo.lock и определяет последние исполнения, соответствующие вашим согласно принятых требованийм из файла Cargo.toml. После этого Cargo запишет эти исполнения в файл Cargo.lock. Иначе по умолчанию Cargo будет искать только исполнения больше 0.8.5, но при этом меньше 0.9.0. Если дополнение rand имеет две новые исполнения — 0.8.6 и 0.9.0 — то при запуске cargo update вы увидите следующее:

- -
$ cargo update
-    Updating crates.io index
-    Updating rand v0.8.5 -> v0.8.6
-
-

Cargo пренебрегает исполнение 0.9.0. В этот мгновение также появится изменение в файле Cargo.lock, указывающее на то, что исполнение rand, которая теперь используется, равна 0.8.6. Чтобы использовать rand исполнения 0.9.0 или любой другой исполнения из серии 0.9.x, необходимо обновить файл Cargo.toml следующим образом:

-
[dependencies]
-rand = "0.9.0"
-
-

В следующий раз, при запуске cargo build, Cargo обновит реестр доступных дополнений и пересмотрит ваши требования к rand в соответствии с новой исполнением, которую вы указали.

-

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

-

Создание случайного числа

-

Давайте начнём использовать rand, чтобы создать число для угадывания. Следующим шагом будет обновление src/main.rs, как показано в приложении 2-3.

-

Файл: src/main.rs

-
use std::io;
-use rand::Rng;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {guess}");
-}
-

Приложение 2-3: Добавление кода который порождает случайное число

-

Сначала мы добавляем строку use rand::Rng. Особенность Rng определяет способы, выполняющие породители случайных чисел, и этот особенность должен быть в области видимости, чтобы эти способы можно было использовать. В главе 10 мы рассмотрим особенности подробно.

-

Затем мы добавляем две строки посередине. В первой строке мы вызываем функцию rand::thread_rng, дающую нам породитель случайных чисел, который мы собираемся использовать: тот самый, который является местным для текущего потока выполнения и запускается операционной системой. Затем мы вызываем его способ gen_range. Этот способ определяется Rng, который мы включили в область видимости с помощью оператора use rand::Rng. Способ gen_range принимает в качестве переменной выражение ряда и порождает случайное число в этом ряде. Вид используемого выражения ряда принимает разновидность start..=end и включает нижнюю и верхнюю границы, поэтому, чтобы запросить число от 1 до 100, нам нужно указать 1..=100.

-
-

Примечание: непросто сразу разобраться, какие особенности использовать, какие способы и функции вызывать из дополнения, поэтому каждый дополнение имеет документацию с указаниями по его использованию. Ещё одной замечательной особенностью Cargo является выполнение приказы cargo doc --open, которая местно собирает документацию, предоставляемую всеми вашими зависимостями, и открывает её в браузере. К примеру, если важна другая возможность из дополнения rand, запустите cargo doc --open и нажмите rand в боковой панели слева.

-
-

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

-

Попробуйте запустить программу несколько раз:

- -
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 2.53s
-     Running `target/debug/guessing_game`
-Guess the number!
-The secret number is: 7
-Please input your guess.
-4
-You guessed: 4
-
-$ cargo run
-    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
-     Running `target/debug/guessing_game`
-Guess the number!
-The secret number is: 83
-Please input your guess.
-5
-You guessed: 5
-
-

Вы должны получить разные случайные числа, и все они должны быть числами в ряде от 1 до 100. Отличная работа!

-

Сравнение догадки с тайным числом

-

Теперь, когда у нас есть пользовательский ввод и случайное число, мы можем сравнить их. Этот шаг показан в приложении 2-4. Учтите, что этот код ещё не собирается, подробнее мы объясним дальше.

-

Имя файла: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    // --snip--
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {guess}");
-
-    match guess.cmp(&secret_number) {
-        Ordering::Less => println!("Too small!"),
-        Ordering::Greater => println!("Too big!"),
-        Ordering::Equal => println!("You win!"),
-    }
-}
-

Приложение 2-4: Обработка возможных возвращаемых значений при сравнении двух чисел

-

Сначала добавим ещё один оператор use, который вводит вид с именем std::cmp::Ordering в область видимости из встроенной библиотеки. Вид Ordering является ещё одним перечислением и имеет исходы Less, Greater и Equal. Это три возможных исхода при сравнении двух величин.

-

После чего ниже добавляем пять новых строк, использующих вид Ordering. Способ cmp сравнивает два значения и может вызываться для всего, что можно сравнить. Он принимает ссылку на все, что требуется сравнить: здесь сравнивается guess с secret_number. В итоге возвращается исход перечисления Ordering, которое мы ввели в область видимости с помощью оператора use. Для принятия решения о том, что делать дальше, мы используем выражение match, определяющее, какой исход Ordering был возвращён из вызова cmp со значениями guess и secret_number.

-

Выражение match состоит из веток (arms). Ветка состоит из образца для сопоставления и кода, который будет запущен, если значение, переданное в match, соответствует образцу этой ветки. Ржавчина принимает значение, заданное match, и по очереди просматривает образец каждой ветки. Образцы и устройство match — это мощные возможности Rust, позволяющие выразить множество случаев, с которыми может столкнуться ваш код, и обеспечить их обработку. Эти возможности будут подробно раскрыты в главе 6 и главе 18 соответственно.

-

Давайте рассмотрим пример с выражением match, которое мы здесь используем. Скажем, пользователь угадал 50, а случайно созданное тайное число на этот раз — 38.

-

Когда код сравнивает 50 с 38, способ cmp вернёт Ordering::Greater, поскольку 50 больше, чем 38. Выражение match получит значение Ordering::Greater и начнёт проверять образец в каждой ветке. Он просмотрит образец первой ветки, Ordering::Less, и увидит, что значение Ordering::Greater не соответствует Ordering::Less, поэтому пренебрегает код этой ветки и перейдёт к следующей. Образец следующей ветки — Ordering::Greater, который соответствует Ordering::Greater! Код этой ветки будет выполнен и напечатает Too big! на экран. Выражение match заканчивается после первого успешного совпадения, поэтому в этом сценарии оно не будет рассматривать последнюю ветку.

-

Однако код в приложении 2-4 всё ещё не собирается. Давайте попробуем:

- -
$ cargo build
-   Compiling libc v0.2.86
-   Compiling getrandom v0.2.2
-   Compiling cfg-if v1.0.0
-   Compiling ppv-lite86 v0.2.10
-   Compiling rand_core v0.6.2
-   Compiling rand_chacha v0.3.0
-   Compiling rand v0.8.5
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-error[E0308]: mismatched types
-  --> src/main.rs:22:21
-   |
-22 |     match guess.cmp(&secret_number) {
-   |                 --- ^^^^^^^^^^^^^^ expected `&String`, found `&{integer}`
-   |                 |
-   |                 arguments to this method are incorrect
-   |
-   = note: expected reference `&String`
-              found reference `&{integer}`
-note: method defined here
-  --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/cmp.rs:840:8
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `guessing_game` (bin "guessing_game") due to 1 previous error
-
-

Суть ошибки заключается в наличии несовпадающих видов. У Ржавчина строгая постоянная система видов. Однако в нем также есть рычаг вывода видов. Когда мы написали let mut guess = String::new(), Ржавчина смог сделать вывод, что guess должна быть String и не заставил указывать вид. С другой стороны, secret_number — это числовой вид. Несколько видов чисел в Ржавчина могут иметь значение от 1 до 100: i32, 32-битное число; u32, беззнаковое 32-битное число; i64, 64-битное число, и так далее. Если не указано иное, Ржавчина по умолчанию использует i32, который будет видом secret_number, если вы не добавите сведения о виде где-то ещё, чтобы заставить Ржавчина вывести другой числовой вид. Причина ошибки заключается в том, что Ржавчина не может сравнить строку и числовой вид.

-

В конечном итоге необходимо преобразовать String, считываемую программой в качестве входных данных, в существующий числовой вид, чтобы иметь возможность числового сравнения с загаданным числом. Для этого добавьте в тело функции main следующую строку:

-

Имя файла: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    // --snip--
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    let guess: u32 = guess.trim().parse().expect("Please type a number!");
-
-    println!("You guessed: {guess}");
-
-    match guess.cmp(&secret_number) {
-        Ordering::Less => println!("Too small!"),
-        Ordering::Greater => println!("Too big!"),
-        Ordering::Equal => println!("You win!"),
-    }
-}
-

Вот эта строка:

-
let guess: u32 = guess.trim().parse().expect("Please type a number!");
-

Мы создаём переменную с именем guess. Но подождите, разве в программе уже нет переменной с этим именем guess? Так и есть, но Ржавчина позволяет нам затенять предыдущее значение guess новым. Затенение позволяет нам повторно использовать имя переменной guess, чтобы избежать создания двух единственных переменных, таких как guess_str и guess, например. Мы рассмотрим это более подробно в главе 3, а пока знайте, что эта функция часто используется, когда необходимо преобразовать значение из одного вида в другой.

-

Мы связываем эту новую переменную с выражением guess.trim().parse(). Переменная guess в этом выражении относится к исходной переменной guess, которая содержала входные данные в виде строки. Способ trim на образце String удалит любые пробельные символы в начале и конце строки для того, чтобы мы могли сопоставить строку с u32, который содержит только числовые данные. Пользователь должен нажать enter, чтобы выполнить read_line и ввести свою догадку, при этом в строку добавится символ новой строки. Например, если пользователь набирает 5 и нажимает enter, guess будет выглядеть так: 5\n. Символ \n означает "новая строка". (В Windows нажатие enter сопровождается возвратом каретки и новой строкой, \r\n). Способ trim убирает \n или \r\n, оставляя только 5.

-

Способ parse строк преобразует строку в другой вид. Здесь мы используем его для преобразования строки в число. Нам нужно сообщить Ржавчина точный числовой вид, который мы хотим получить, используя let guess: u32. Двоеточие ( : ) после guess говорит Rust, что мы определяем вид переменной. В Ржавчина есть несколько встроенных числовых видов; u32, показанный здесь, представляет собой 32-битное целое число без знака. Это хороший выбор по умолчанию для небольшого положительного числа. Вы узнаете о других видах чисел в главе 3.

-

Кроме того, изложение u32 в этом примере программы и сравнение с secret_number означает, что Ржавчина сделает вывод, что secret_number должен быть u32. Итак, теперь сравнение будет между двумя значениями одного типа!

-

Способ parse будет работать только с символами, которые логически могут быть преобразованы в числа, и поэтому легко может вызвать ошибки. Если, например, строка содержит A👍%, преобразовать её в число невозможно. Так как способ parse может потерпеть неудачу, он возвращает вид Result — так же как и способ read_line (обсуждалось ранее в разделе «Обработка возможной ошибки с помощью вида Result»). Мы будем точно так же обрабатывать данный Result, вновь используя способ expect. Если parse вернёт исход Result Err, так как не смог создать число из строки, вызов expect со сбоем завершит игру и отобразит переданное ему сообщение. Если parse сможет успешно преобразовать строку в число, он вернёт исход Result Ok, а expect вернёт число, полученное из значения Ok.

-

Давайте запустим программу теперь:

- -
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.43s
-     Running `target/debug/guessing_game`
-Guess the number!
-The secret number is: 58
-Please input your guess.
-  76
-You guessed: 76
-Too big!
-
-

Хорошо! Несмотря на то, что были добавлены пробелы в строке ввода, программа всё равно поняла, что пользователь имел в виду число 76. Запустите программу несколько раз, чтобы проверить разное поведение при различных видах ввода: задайте число правильно, задайте слишком большое число и задайте слишком маленькое число.

-

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

-

Возможность нескольких догадок с помощью циклов

-

Ключевое слово loop создаёт бесконечный цикл. Мы добавляем цикл, чтобы дать пользователям больше шансов угадать число:

-

Имя файла: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    // --snip--
-
-    println!("The secret number is: {secret_number}");
-
-    loop {
-        println!("Please input your guess.");
-
-        // --snip--
-
-
-        let mut guess = String::new();
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: u32 = guess.trim().parse().expect("Please type a number!");
-
-        println!("You guessed: {guess}");
-
-        match guess.cmp(&secret_number) {
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => println!("You win!"),
-        }
-    }
-}
-

Как видите, мы перемеисполнения всё, начиная с подсказки ввода догадки, в цикл. Не забудьте добавить ещё по четыре пробела на отступы строк внутри цикла и запустите программу снова. Теперь программа будет бесконечно запрашивать ещё одну догадку, что в действительности создаёт новую неполадку. Похоже, пользователь не сможет выйти из игры!

-

Пользователь может прервать выполнение программы с помощью сочетания клавиш ctrl+c. Но есть и другой способ спастись от этого ненасытного монстра, о котором говорилось при обсуждении parse в «Сравнение догадки с тайным числом»: если пользователь введёт нечисловой ответ, программа завершится со сбоем. Мы можем воспользоваться этим, чтобы позволить пользователю выйти из игры, как показано здесь:

- -
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 1.50s
-     Running `target/debug/guessing_game`
-Guess the number!
-The secret number is: 59
-Please input your guess.
-45
-You guessed: 45
-Too small!
-Please input your guess.
-60
-You guessed: 60
-Too big!
-Please input your guess.
-59
-You guessed: 59
-You win!
-Please input your guess.
-quit
-thread 'main' panicked at 'Please type a number!: ParseIntError { kind: InvalidDigit }', src/main.rs:28:47
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Ввод quit приведёт к выходу из игры, но, как вы заметите, так же будет и при любом другом нечисловом вводе. Однако это, мягко говоря, не разумно. Мы хотим, чтобы игра самостоятельно остановилась, когда будет угадано правильное число.

-

Выход после правильной догадки

-

Давайте запрограммируем игру на выход при выигрыше пользователя, добавив оператор break:

-

Файл: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    loop {
-        println!("Please input your guess.");
-
-        let mut guess = String::new();
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: u32 = guess.trim().parse().expect("Please type a number!");
-
-        println!("You guessed: {guess}");
-
-        // --snip--
-
-        match guess.cmp(&secret_number) {
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => {
-                println!("You win!");
-                break;
-            }
-        }
-    }
-}
-

Добавление строки break после You win! заставляет программу выйти из цикла, когда пользователь правильно угадает тайное число. Выход из цикла также означает выход из программы, так как цикл является последней частью main.

-

Обработка недопустимого ввода

-

Чтобы улучшить поведение игры, вместо со сбоемго завершения программы, когда пользователь вводит не число, давайте заставим игру пренебрегать этотобстоятельство, позволяя пользователю продолжить угадывание. Для этого необходимо изменить строку, в которой guess преобразуется из String в u32, как показано в приложении 2-5.

-

Файл: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    loop {
-        println!("Please input your guess.");
-
-        let mut guess = String::new();
-
-        // --snip--
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: u32 = match guess.trim().parse() {
-            Ok(num) => num,
-            Err(_) => continue,
-        };
-
-        println!("You guessed: {guess}");
-
-        // --snip--
-
-        match guess.cmp(&secret_number) {
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => {
-                println!("You win!");
-                break;
-            }
-        }
-    }
-}
-

Приложение 2-5. Пренебрежение нечисловой догадки и запрос другой догадки вместо завершения программы

-

Мы заменяем вызов expect на выражение match, чтобы перейти от со сбоемго завершения при ошибке к обработке ошибки. Помните, что parse возвращает вид Result, а Result — это перечисление, которое имеет исходы Ok и Err. Здесь мы используем выражение match, как и в случае с итогом Ordering способа cmp.

-

Если parse успешно преобразует строку в число, он вернёт значение Ok, содержащее полученное число. Это значение Ok будет соответствовать образцу первой ветки, а выражение match просто вернёт значение num, которое parse произвёл и поместил внутрь значения Ok. Это число окажется в нужной нам переменной guess, которую мы создали.

-

Если способ parse не способен превратить строку в число, он вернёт значение Err, которое содержит более подробную сведения об ошибке. Значение Err не совпадает с образцом Ok(num) в первой ветке match, но совпадает с образцом Err(_) второй ветки. Подчёркивание _ является всеохватывающим выражением. В этой ветке мы говорим, что хотим обработать совпадение всех значений Err, независимо от того, какая сведения находится внутри. Поэтому программа выполнит код второй ветки, continue, который сообщает программе перейти к следующей повторения loop и запросить ещё одну догадку. В этом случае программа эффективно пренебрегает все ошибки, с которыми parse может столкнуться!

-

Всё в программе теперь должно работать как положено. Давайте попробуем:

- -
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 4.45s
-     Running `target/debug/guessing_game`
-Guess the number!
-The secret number is: 61
-Please input your guess.
-10
-You guessed: 10
-Too small!
-Please input your guess.
-99
-You guessed: 99
-Too big!
-Please input your guess.
-foo
-Please input your guess.
-61
-You guessed: 61
-You win!
-
-

Потрясающе! С помощью одной маленькой последней правки мы закончим игру в угадывание. Напомним, что программа все ещё печатает тайное число. Это хорошо подходило для проверки, но это портит игру. Давайте удалим println!, который выводит тайное число. В Приложении 2-6 показан окончательный исход кода.

-

Файл: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    loop {
-        println!("Please input your guess.");
-
-        let mut guess = String::new();
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: u32 = match guess.trim().parse() {
-            Ok(num) => num,
-            Err(_) => continue,
-        };
-
-        println!("You guessed: {guess}");
-
-        match guess.cmp(&secret_number) {
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => {
-                println!("You win!");
-                break;
-            }
-        }
-    }
-}
-

Приложение 2-6: полный код игры

-

На данный мгновение вы успешно создали игру в загадки. Поздравляем!

-

Заключение

-

Этот дело — опытный способ познакомить вас со многими новыми подходами Rust: let, match, функции, использование внешних ящиков и многое другое. В следующих нескольких главах вы изучите эти подходы более подробно. Глава 3 охватывает понятия, которые есть в большинстве языков программирования, такие как переменные, виды данных и функции, и показывает, как использовать их в Rust. В главе 4 рассматривается владение — особенность, которая отличает Ржавчина от других языков. В главе 5 обсуждаются устройства и правила написания способов, а в главе 6 объясняется, как работают перечисления.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch03-00-common-programming-concepts.html b/rustbook-ru/book/ch03-00-common-programming-concepts.html deleted file mode 100644 index c65efebc4..000000000 --- a/rustbook-ru/book/ch03-00-common-programming-concepts.html +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - Общие подходы программирования - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Общие подходы программирования

-

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

-

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

-
-

Ключевые слова

-

В языке Ржавчина как и в других языках есть набор ключевых слов, зарезервированных только для использования в языке. Помните, что нельзя использовать эти слова в качестве имён переменных или функций. Большинство этих ключевых слов имеют особые назначения, и вы будете использовать их для выполнения различных задач в своих программах на Rust. Некоторые из них сейчас не имеют функционального назначения, но зарезервированы для возможности, которая может быть добавлена в Ржавчина в будущем. Список ключевых слов вы можете найти в Приложении А.

-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch03-01-variables-and-mutability.html b/rustbook-ru/book/ch03-01-variables-and-mutability.html deleted file mode 100644 index ae0c5e152..000000000 --- a/rustbook-ru/book/ch03-01-variables-and-mutability.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - - - Переменные и изменяемость - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Переменные и изменяемость

-

Как упоминалось в разделе "Хранение значений с помощью переменных", по умолчанию переменные неизменяемы. Это один из многих стимулов Rust, позволяющий писать код с использованием преимущества безопасности и удобной состязательности (concurrency), предоставляемых Rust. Тем не менее, существует возможность сделать переменные изменяемыми. Давайте рассмотрим, как и почему Ржавчина побуждает предпочесть неизменяемость и почему иногда можно отказаться от этого.

-

Если переменная является неизменяемой, то после привязки значения к имени изменить его будет нельзя. Чтобы показать это, создайте новый дело под названием variables в папке projects с помощью приказы cargo new variables.

-

Далее, в новом папке variables откройте src/main.rs и замените в нем код на ниже приведённый, который пока не будет собираться:

-

Имя файла: src/main.rs

-
fn main() {
-let x = 5;
-println!("The value of x is: {}", x);
-x = 6;
-println!("The value of x is: {}", x);
-}
-

Сохраните и запустите программу, используя cargo run. Будет получено сообщение об ошибке относительно неизменяемости, как показано в этом выводе:

-
error[E0384]: cannot assign twice to immutable variable `x`  --> src/main.rs:4:5   | 2 |     let x = 5;   |         - first assignment to `x` 3 |     println!("The value of x is: {}", x); 4 |     x = 6;   |     ^^^^^ cannot assign twice to immutable variable
-
-

В этом примере показано, как сборщик помогает находить ошибки в ваших программах. Ошибки сборщика могут расстраивать, но в действительности они означают, что программа пока не делает правильно то, что вы ожидаете; это не значит, что вы плохой программист! Даже опытные Rustaceans иногда сталкиваются с ошибками сборщика.

-

Вы получили сообщение об ошибке cannot assign twice to immutable variable x``, потому что попытались присвоить новое значение неизменяемой переменной x.

-

Важно, чтобы при попытке изменить значение, объявленное неизменяемым, выдавались ошибки времени сборки, так как подобная случаей может привести к сбоям. Если одна часть нашего кода исполняется исходя из уверенности в неизменяемости значения, а другая часть изменяет это значение, то велика вероятность , что первая часть не выполнит своего предназначения. Причину такой ошибки бывает трудно отследить, особенно если вторая часть кода изменяет значение лишь изредка. Сборщик Ржавчина предоставляет заверение, что если объявить значение неизменяемым, то оно действительно не изменится, а значит, не нужно следить за этим самим. Таким образом, ваш код становится проще для понимания.

-

Однако изменяемость может быть очень полезной и может сделать код более удобным для написания. Хотя переменные по умолчанию неизменяемы, их можно сделать изменяемыми, добавив mut перед именем переменной, как это было сделано в Главе 2. Добавление mut также передаёт будущим читателям кода намерение, обозначая, что другие части кода будут изменять значение этой переменной.

-

Например, изменим src/main.rs на следующий код:

-

Имя файла: src/main.rs

-
fn main() {
-    let mut x = 5;
-    println!("The value of x is: {x}");
-    x = 6;
-    println!("The value of x is: {x}");
-}
-

Запустив программу, мы получим итог:

-
$ cargo run
-   Compiling variables v0.1.0 (file:///projects/variables)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
-     Running `target/debug/variables`
-The value of x is: 5
-The value of x is: 6
-
-

Нам разрешено изменить значение, связанное с x, с 5 на 6 при помощи mut. В конечном счёте, решение об использовании изменяемости остаётся за вами и зависит от вашего мнения о наилучшем исходе в данной именно случаи.

-

Постоянного значения

-

Подобно неизменяемым переменным, постоянные значения — это значения, которые связаны с именем и не могут изменяться, но между постоянными значениями и переменными есть несколько различий.

-

Во-первых, нельзя использовать mut с постоянными значениями. Постоянного значения не просто неизменяемы по умолчанию — они неизменяемы всегда. Для объявления постоянных значенийиспользуется ключевое слово const вместо let, а также вид значения должен быть указан в изложении. Мы рассмотрим виды и изложении видов в следующем разделе «Виды данных»., так что не беспокойтесь о подробностях прямо сейчас. Просто знайте, что вы всегда должны определять вид.

-

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

-

Последнее отличие в том, что постоянные значения могут быть заданы только постоянным выражением, но не итогом вычисленного во время выполнения значения.

-

Вот пример объявления постоянные значения:

-
#![allow(unused)]
-fn main() {
-const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
-}
-

Имя постоянные значения - THREE_HOURS_IN_SECONDS, а её значение устанавливается как итог умножения 60 (количество секунд в минуте) на 60 (количество минут в часе) на 3 (количество часов, которые нужно посчитать в этой программе). Соглашение Ржавчина для именования постоянных значенийтребует использования всех заглавных букв с подчёркиванием между словами. Сборщик может вычислять ограниченный набор действий во время сборки, позволяющий записать это значение более понятным и простым для проверки способом, чем установка этой постоянные значения в значение 10 800. Дополнительную сведения о том, какие действия можно использовать при объявлении постоянных значений, см. в разделе Раздел справки Ржавчина по вычислениям постоянных значений.

-

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

-

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

-

Затенение (переменных)

-

Как было показано в уроке по игре в Угадайка в главе 2, можно объявить новую переменную с тем же именем, как и у существующей переменной. Rustaceans говорят, что первая переменная затеняется второй, то есть вторая переменная - это то, что увидит сборщик, когда вы будете использовать имя переменной. По сути, вторая переменная затеняет первую, принимая любое использование имени переменной на себя до тех пор, пока либо она сама не станет тенью, либо не закончится область видимости. Мы можем затенять переменную, используя то же имя переменной и повторяя использование ключевого слова let следующим образом:

-

Имя файла: src/main.rs

-
fn main() {
-    let x = 5;
-
-    let x = x + 1;
-
-    {
-        let x = x * 2;
-        println!("The value of x in the inner scope is: {x}");
-    }
-
-    println!("The value of x is: {x}");
-}
-

Эта программа сначала привязывает x к значению 5. Затем она создаёт новую переменную x, повторяя let x =, беря исходное значение и добавляя 1, чтобы значение x стало равным 6. Затем во внутренней области видимости, созданной с помощью фигурных скобок, третий оператор let также затеняет x и создаёт новую переменную, умножая предыдущее значение на 2, чтобы дать x значение 12. Когда эта область заканчивается, внутреннее затенение заканчивается, и x возвращается к значению 6. Запустив эту программу, она выведет следующее:

-
$ cargo run
-   Compiling variables v0.1.0 (file:///projects/variables)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
-     Running `target/debug/variables`
-The value of x in the inner scope is: 12
-The value of x is: 6
-
-

Затенение отличается от объявления переменной с помощью mut, так как мы получим ошибку сборки, если случайно попробуем переназначить значение без использования ключевого слова let. Используя let, можно выполнить несколько превращений над значением, при этом оставляя переменную неизменяемой, после того как все эти превращения завершены.

-

Другой разницей между mut и затенением является то, что мы создаём совершенно новую переменную, когда снова используем слово let (ещё одну). Мы можем даже изменить вид значения, но снова использовать прежнее имя. К примеру, наша программа спрашивает пользователя, сколько пробелов он хочет разместить между некоторым текстом, запрашивая символы пробела, но мы на самом деле хотим сохранить данный ввод как число:

-
fn main() {
-    let spaces = "   ";
-    let spaces = spaces.len();
-}
-

Первая переменная spaces — является строковым видом, а вторая переменная spaces — числовым видом. Таким образом, затенение избавляет нас от необходимости придумывать разные имена, такие как spaces_str и spaces_num. Вместо этого мы можем повторно использовать более простое имя spaces. Однако, если мы попытаемся использовать для этого mut, как показано далее, то получим ошибку времени сборки:

-
fn main() {
-    let mut spaces = "   ";
-    spaces = spaces.len();
-}
-

Ошибка говорит, что не разрешается менять вид переменной:

-
$ cargo run
-   Compiling variables v0.1.0 (file:///projects/variables)
-error[E0308]: mismatched types
- --> src/main.rs:3:14
-  |
-2 |     let mut spaces = "   ";
-  |                      ----- expected due to this value
-3 |     spaces = spaces.len();
-  |              ^^^^^^^^^^^^ expected `&str`, found `usize`
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `variables` (bin "variables") due to 1 previous error
-
-

Теперь, когда мы изучили, как работают переменные, давайте рассмотрим различные виды данных, которые они могут иметь.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch03-02-data-types.html b/rustbook-ru/book/ch03-02-data-types.html deleted file mode 100644 index 3d0bb60fd..000000000 --- a/rustbook-ru/book/ch03-02-data-types.html +++ /dev/null @@ -1,446 +0,0 @@ - - - - - - Виды Данных - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Виды Данных

-

Каждое значение в Ржавчина относится к определённому виду данных, который указывает на вид данных, что позволяет Ржавчина знать, как работать с этими данными. Мы рассмотрим два подмножества видов данных: одиночные и составные.

-

Не забывайте, что Ржавчина является постоянно строго определенным (statically typed) языком. Это означает, что он должен знать виды всех переменных во время сборки. Обычно сборщик может предположить, какой вид используется (вывести его), основываясь на значении и на том, как мы с ним работаем. В случаях, когда может быть выведено несколько видов, необходимо добавлять изложение вида вручную. Например, когда мы преобразовали String в число с помощью вызова parse в разделе «Сравнение предположения с загаданным номером» главы 2, мы должны добавить такую изложение:

-
#![allow(unused)]
-fn main() {
-let guess: u32 = "42".parse().expect("Not a number!");
-}
-

Если мы не добавим изложение вида : u32, показанную в предыдущем коде, Ржавчина отобразит следующую ошибку, которая означает, что сборщику нужно от нас больше сведений, чтобы узнать, какой вид мы хотим использовать:

-
$ cargo build
-   Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)
-error[E0284]: type annotations needed
- --> src/main.rs:2:9
-  |
-2 |     let guess = "42".parse().expect("Not a number!");
-  |         ^^^^^        ----- type must be known at this point
-  |
-  = note: cannot satisfy `<_ as FromStr>::Err == _`
-help: consider giving `guess` an explicit type
-  |
-2 |     let guess: /* Type */ = "42".parse().expect("Not a number!");
-  |              ++++++++++++
-
-For more information about this error, try `rustc --explain E0284`.
-error: could not compile `no_type_annotations` (bin "no_type_annotations") due to 1 previous error
-
-

В будущем вы увидите различные изложении для разных видов данных.

-

Одиночные виды данных

-

Одиночный вид представляет собой единичное значение. В Ржавчина есть четыре основных одиночных вида: целочисленный, числа с плавающей точкой, логический и символы. Вы наверняка знакомы с этими видами по другим языкам программирования. Давайте разберёмся, как они работают в Rust.

-

Целочисленные виды

-

Целочисленный вид (integer) — это число без дробной части. В главе 2 мы использовали один целочисленный вид — вид u32. Такое объявление вида указывает, что значение, с которым оно связано, должно быть целым числом без знака (виды целых чисел со знаком начинаются с i вместо u), которое занимает 32 бита памяти. В Таблице 3-1 показаны встроенные целочисленные виды в Rust. Мы можем использовать любой из этих исходов для объявления вида целочисленного значения.

-

Таблица 3-1: целочисленные виды в Rust

-
- - - - - - -
ДлинаСо знакомБез знака
8 битi8u8
16 битi16u16
32 битаi32u32
64 битаi64u64
128 битi128u128
архитектурно-зависимаяisizeusize
-
-

Каждый исход может быть как со знаком, так и без знака и имеет явный размер. Такая свойство вида как знаковый и беззнаковый определяет возможность числа быть отрицательным. Другими словами, должно ли число иметь знак (знаковое) или оно всегда будет только положительным и, следовательно, может быть представлено без знака (беззнаковое). Это похоже на написание чисел на бумаге: когда знак имеет значение, число отображается со знаком плюс или со знаком -; однако, когда можно с уверенностью предположить, что число положительное, оно отображается без знака. Числа со знаком хранятся с использованием дополнительного кода.

-

Каждый исход со знаком может хранить числа от -(2 n - 1 ) до 2 n - 1 - 1 включительно, где n — количество битов, которые использует этот исход. Таким образом, i8 может хранить числа от -(2 7 ) до 2 7 - 1, что равно значениям от -128 до 127. Исходы без знака могут хранить числа от 0 до 2 n - 1, поэтому u8 может хранить числа от 0 до 2 8 - 1, что равно значениям от 0 до 255.

-

Кроме того, виды isize и usize зависят от архитектуры компьютера, на котором выполняется программа, и обозначаются в таблице как "arch": 64 бита, если используется 64-битная архитектура, и 32 бита, если используется 32-битная архитектура.

-

Вы можете записывать целочисленные записи в любой из разновидностей, показанных в таблице 3-2. Заметьте, что числовые записи, имеющие несколько числовых видов, допускают использование вставки вида, например 57u8, для обозначения вида. Числовые записи также могут использовать _ в качестве визуального разделителя для облегчения чтения числа, например 1_000, который будет иметь такое же значение, как если бы было задано 1000.

-

Таблица 3-2: Целочисленные записи в Rust

-
- - - - - -
Числовой записьПример
Десятичный98_222
Шестнадцатеричный0xff
восьмеричный0o77
Двоичный0b1111_0000
Байт (только u8)b'A'
-
-

Как же узнать, какой вид целого числа использовать? Если вы не уверены, значения по умолчанию в Rust, как правило, подходят для начала: целочисленные виды по умолчанию i32. Основной случай, в котором вы должны использовать isize или usize, — это упорядочевание какой-либо собрания.

-
-

Целочисленное переполнение Допустим, имеется переменная вида u8, которая может хранить значения от 0 до 255. Если попытаться изменить переменную на значение вне этого ряда, например, 256, произойдёт целочисленное переполнение, что может привести к одному из двух исходов поведения. Если выполняется сборка в режиме отладки, Ржавчина включает проверку на целочисленное переполнение, приводящую вашу программу к панике во время выполнения, когда возникает такое поведение. Ржавчина использует понятие паника(panicking), когда программа завершается с ошибкой. Мы обсудим панику более подробно в разделе "Неустранимые ошибки с panic!" в главе 9. . При сборки в режиме release с флагом --release, Ржавчина не включает проверки на целочисленное переполнение, которое вызывает панику. Вместо этого, в случае переполнения, Ржавчина выполняет обёртывание второго дополнения. Проще говоря, значения, превышающие наибольшее значение, которое может хранить вид, "оборачиваются" к наименьшему из значений, которые может хранить вид. В случае u8 значение 256 становится 0, значение 257 становится 1, и так далее. Программа не запаникует, но переменная будет иметь значение, которое, вероятно, не будет соответствовать вашим ожиданиям. Полагаться на поведение обёртывания целочисленного переполнения считается ошибкой. Для явной обработки возможности переполнения существует семейство способов, предоставляемых встроенной библиотекой для простых числовых видов:

-
    -
  • Обёртывание во всех режимах с помощью способов wrapping_*, таких как wrapping_add.
  • -
  • Возврат значения None при переполнении с помощью способов checked_*.
  • -
  • Возврат значения и логический индикатор, указывающий, произошло ли переполнение при использовании способов overflowing_*.
  • -
  • Насыщение наименьшим или наибольшим значением с помощью способов saturating_*.
  • -
-
-

Числа с плавающей запятой

-

Также в Ржавчина есть два простых вида для чисел с плавающей запятой, представляющих собой числа с десятичной точкой. Виды с плавающей точкой в Ржавчина - это f32 и f64, размер которых составляет 32 бита и 64 бита соответственно. По умолчанию используется вид f64, поскольку на современных процессорах он работает примерно с той же скоростью, как и f32, но обладает большей точностью. Все виды с плавающей запятой являются знаковыми.

-

Вот пример, отображающий числа с плавающей запятой в действии:

-

Файл: src/main.rs

-
fn main() {
-    let x = 2.0; // f64
-
-    let y: f32 = 3.0; // f32
-}
-

Числа с плавающей запятой представлены в соответствии со исполнением IEEE-754. Вид f32 является плавающей запятой одинарной точности, а f64 - двойной точности.

-

Числовые действия

-

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

-

Файл: src/main.rs

-
fn main() {
-    // addition
-    let sum = 5 + 10;
-
-    // subtraction
-    let difference = 95.5 - 4.3;
-
-    // multiplication
-    let product = 4 * 30;
-
-    // division
-    let quotient = 56.7 / 32.2;
-    let truncated = -5 / 3; // Results in -1
-
-    // remainder
-    let remainder = 43 % 5;
-}
-

Каждое выражение в этих указаниях использует математический оператор и вычисляется в одно значение, которое связывается с переменной. Приложении B содержит список всех операторов, которые предоставляет Rust.

-

Логический вид данных

-

Как и в большинстве других языков программирования, логический вид в Ржавчина имеет два возможных значения: true и false. Значения логических видов имеют размер в один байт. Логический вид в Ржавчина задаётся с помощью bool. Например:

-

Файл: src/main.rs

-
fn main() {
-    let t = true;
-
-    let f: bool = false; // with explicit type annotation
-}
-

Основной способ использования логических значений - это использование условий, таких как выражение if. Мы рассмотрим, как выражения if работают в Ржавчина в разделе "Поток управления".

-

Символьный вид данных

-

Вид char в Ржавчина является самым простым алфавитным видом языка. Вот несколько примеров объявления значений char:

-

Файл: src/main.rs

-
fn main() {
-    let c = 'z';
-    let z: char = 'ℤ'; // with explicit type annotation
-    let heart_eyed_cat = '😻';
-}
-

Заметьте, мы указываем записи char с одинарными кавычками, в отличие от строковых записей, для которых используются двойные кавычки. Вид char в Ржавчина имеет размер четыре байта и представляет собой одиночное значение Unicode, а значит, может представлять собой не только ASCII. Акцентированные буквы, китайские, японские и корейские символы, эмодзи и пробелы нулевой ширины - все это допустимые значения вида char в Rust. Одиночные значения Unicode находятся в ряде от U+0000 до U+D7FF и от U+E000 до U+10FFFF включительно. Однако "символ" не является понятием в Unicode, поэтому ваше человеческое представление о том, что такое "символ", может не совпадать с тем, что такое char в Rust. Мы подробно обсудим эту тему в главе 8 "Хранение текста в кодировке UTF-8 с помощью строк".

-

Составные виды данных

-

Составные виды могут объединять различные значения в один вид. В Ржавчина есть два простых составных вида: упорядоченные ряды и массивы.

-

Упорядоченные ряды

-

Упорядоченный ряд- это гибкий способ объединения нескольких значений с различными видами в один составной вид. Упорядоченные ряды имеют конечную длину: после объявления они не могут увеличиваться или уменьшаться в размерах.

-

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

-

Файл: src/main.rs

-
fn main() {
-    let tup: (i32, f64, u8) = (500, 6.4, 1);
-}
-

Переменная tup связана со всем упорядоченным рядом, поскольку упорядоченный ряд является одним составным элементом. Чтобы получить отдельные значения из упорядоченного ряда, можно использовать сопоставление с образцом для разъединения значения упорядоченного ряда, например, так:

-

Файл: src/main.rs

-
fn main() {
-    let tup = (500, 6.4, 1);
-
-    let (x, y, z) = tup;
-
-    println!("The value of y is: {y}");
-}
-

Эта программа сначала создаёт упорядоченный ряд и связывает его с переменной tup. Затем с помощью образца let берётся tup и превращается в три отдельные переменные, x, y и z. Это называется разъединением, поскольку разбивает единый упорядоченный ряд на три части. Наконец, программа печатает значение y, которое равно 6.4.

-

Мы также можем получить доступ к элементу упорядоченного ряда напрямую, используя точку (.), за которой следует порядковый указательзначения, требуемого для доступа. Например:

-

Файл: src/main.rs

-
fn main() {
-    let x: (i32, f64, u8) = (500, 6.4, 1);
-
-    let five_hundred = x.0;
-
-    let six_point_four = x.1;
-
-    let one = x.2;
-}
-

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

-

Упорядоченный ряд, не имеющий значений, имеет особое имя единичный вид (unit). Это значение и соответствующий ему вид записываются как () и представляет собой пустое значение или пустой возвращаемый вид. Выражения неявно возвращают значение единичного вида, если не возвращают никакого другого значения.

-

Массивы

-

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

-

Мы записываем значения в массиве в виде списка, разделённого запятыми, внутри квадратных скобок:

-

Файл: src/main.rs

-
fn main() {
-    let a = [1, 2, 3, 4, 5];
-}
-

Массивы удобно использовать, если данные необходимо разместить в обойме, а не в куче (мы подробнее обсудим обойма и кучу в Главе 4) или если требуется, чтобы количество элементов всегда было конечным. Однако массив не так гибок, как вектор. Вектор - это подобный вид собрания, предоставляемый встроенной библиотекой, который может увеличиваться или уменьшаться в размере. Если вы не уверены, что лучше использовать - массив или вектор, то, скорее всего, вам следует использовать вектор. Более подробно векторы рассматриваются в Главе 8.

-

Однако массивы более полезны, когда вы знаете, что количество элементов не нужно будет изменять. Например, если бы вы использовали названия месяцев в программе, вы, вероятно, использовали бы массив, а не вектор, потому что вы знаете, что он всегда будет содержать 12 элементов:

-
#![allow(unused)]
-fn main() {
-let months = ["January", "February", "March", "April", "May", "June", "July",
-              "August", "September", "October", "November", "December"];
-}
-

Вид массива записывается следующим образом: в квадратных скобках обозначается вид элементов массива, а затем, через точку с запятой, количество элементов. Например:

-
#![allow(unused)]
-fn main() {
-let a: [i32; 5] = [1, 2, 3, 4, 5];
-}
-

Здесь i32 является видом каждого элемента массива. После точки с запятой указано число 5, показывающее, что массив содержит 5 элементов.

-

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

-
#![allow(unused)]
-fn main() {
-let a = [3; 5];
-}
-

Массив в переменной a будет включать 5 элементов, значение которых будет равно 3. Данная запись подобна коду let a = [3, 3, 3, 3, 3];, но является более краткой.

-
Доступ к элементам массива
-

Массив — это единый отрывок памяти известного конечного размера, который может быть размещён в обойме. Вы можете получить доступ к элементам массива с помощью упорядочевания, например:

-

Файл: src/main.rs

-
fn main() {
-    let a = [1, 2, 3, 4, 5];
-
-    let first = a[0];
-    let second = a[1];
-}
-

В этом примере переменная с именем first получит значение 1, потому что это значение находится по порядковому указателю [0] в массиве. Переменная с именем second получит значение 2 по порядковому указателю [1] в массиве.

-
Неправильный доступ к элементу массива
-

Давайте посмотрим, что произойдёт, если попытаться получить доступ к элементу массива, находящемуся за его пределами. Допустим, вы запускаете данный код, похожий на игру в угадывание из Главы 2, чтобы получить от пользователя порядковый указательмассива:

-

Файл: src/main.rs

-
use std::io;
-
-fn main() {
-    let a = [1, 2, 3, 4, 5];
-
-    println!("Please enter an array index.");
-
-    let mut index = String::new();
-
-    io::stdin()
-        .read_line(&mut index)
-        .expect("Failed to read line");
-
-    let index: usize = index
-        .trim()
-        .parse()
-        .expect("Index entered was not a number");
-
-    let element = a[index];
-
-    println!("The value of the element at index {index} is: {element}");
-}
-

Этот код успешно собирается. Если запустить этот код с помощью cargo run и ввести 0, 1, 2, 3 или 4, программа напечатает соответствующее значение по данному порядковому указателю в массиве. Если вместо этого ввести число за пределами массива, например, 10, то программа выведет следующее:

- -
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Программа столкнулась с ошибкой во времени выполнения на этапе применения недопустимого значения в действия упорядочевания. Программа завершилась с сообщением об ошибке и не выполнила конечную указанию println!. При попытке доступа к элементу с помощью упорядочевания Ржавчина проверяет, что указанный порядковый указательменьше длины массива. Если порядковый указательбольше или равен длине, Ржавчина паникует. Эта проверка должна происходить во время выполнения, особенно в данном случае, потому что сборщик не может знать, какое значение введёт пользователь при последующем выполнении кода.

-

Это пример принципов безопасности памяти Ржавчина в действии. Во многих низкоуровневых языках такая проверка не выполняется, и когда вы указываете неправильный порядковый указатель, доступ к памяти может быть неправильным. Ржавчина защищает вас от такого рода ошибок, немедленно закрываясь вместо того, чтобы разрешать доступ к памяти и продолжать работу. В главе 9 подробнее обсуждается обработка ошибок в Ржавчина и то, как вы можете написать читаемый, безопасный код, который не вызывает панику и не разрешает неправильный доступ к памяти.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch03-03-how-functions-work.html b/rustbook-ru/book/ch03-03-how-functions-work.html deleted file mode 100644 index 4adb9c4ea..000000000 --- a/rustbook-ru/book/ch03-03-how-functions-work.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - Функции - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Функции

-

Функции широко распространены в коде Rust. Вы уже познакомились с одной из самых важных функций в языке: функцией main, которая является точкой входа большинства программ. Вы также видели ключевое слово fn, позволяющее объявлять новые функции.

-

Код Ржавчина использует змеиный регистр (snake case) как основной исполнение для имён функций и переменных, в котором все буквы строчные, а символ подчёркивания разделяет слова. Вот программа, содержащая пример определения функции:

-

Имя файла: src/main.rs

-
fn main() {
-    println!("Hello, world!");
-
-    another_function();
-}
-
-fn another_function() {
-    println!("Another function.");
-}
-

Для определения функции в Ржавчина необходимо указать fn, за которым следует имя функции и набор круглых скобок. Фигурные скобки указывают сборщику, где начинается и заканчивается тело функции.

-

Мы можем вызвать любую функцию, которую мы определили ранее, введя её имя и набор скобок следом. Поскольку в программе определена another_function, её можно вызвать из функции main. Обратите внимание, что another_function определена после функции main в исходном коде; мы могли бы определить её и раньше. Ржавчина не важно, где вы определяете свои функции, главное, чтобы они были определены где-то в той области видимости, которую может видеть вызывающий их код.

-

Создадим новый двоичный дело с названием functions для дальнейшего изучения функций. Поместите пример another_function в файл src/main.rs и запустите его. Вы должны увидеть следующий вывод:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
-     Running `target/debug/functions`
-Hello, world!
-Another function.
-
-

Строки выполняются в том порядке, в котором они расположены в функции main. Сначала печатается сообщение "Hello, world!", а затем вызывается another_function, которая также печатает сообщение.

-

Свойства функции

-

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

-

В этой исполнения another_function мы добавляем свойство:

-

Имя файла: src/main.rs

-
fn main() {
-    another_function(5);
-}
-
-fn another_function(x: i32) {
-    println!("The value of x is: {x}");
-}
-

Попробуйте запустить эту программу. Должны получить следующий итог:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.21s
-     Running `target/debug/functions`
-The value of x is: 5
-
-

Объявление another_function содержит один свойство с именем x. Вид x задан как i32. Когда мы передаём 5 в another_function, макрос println! помещает 5 на место пары фигурных скобок, содержащих x в строке вида.

-

В ярлыках функций вы обязаны указывать вид каждого свойства. Это намеренное решение в внешнем виде Rust: требование наставлений видов в определениях функций позволяет сборщику в дальнейшем избежать необходимости использовать их в других местах кода, чтобы определить, какой вид вы имеете в виду. Сборщик также может выдавать более полезные сообщения об ошибках, если он знает, какие виды ожидает функция.

-

При определении нескольких свойств, разделяйте объявления свойств запятыми, как показано ниже:

-

Имя файла: src/main.rs

-
fn main() {
-    print_labeled_measurement(5, 'h');
-}
-
-fn print_labeled_measurement(value: i32, unit_label: char) {
-    println!("The measurement is: {value}{unit_label}");
-}
-

Этот пример создаёт функцию под именем print_labeled_measurement с двумя свойствами. Первый свойство называется value с видом i32. Второй называется unit_label и имеет вид char. Затем функция печатает текст, содержащий value и unit_label.

-

Попробуем запустить этот код. Замените текущую программу дела functions в файле src/main.rs на предыдущий пример и запустите его с помощью cargo run:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
-     Running `target/debug/functions`
-The measurement is: 5h
-
-

Поскольку мы вызвали функцию с 5 в качестве значения для value и 'h' в качестве значения для unit_label, вывод программы содержит эти значения.

-

Указания и выражения

-

Тела функций состоят из ряда указаний, необязательно заканчивающихся выражением. До сих пор функции, которые мы рассматривали, не включали завершающее выражение, но вы видели выражение как часть указания. Поскольку Ржавчина является языком, основанным на выражениях, это важное различие необходимо понимать. В других языках таких различий нет, поэтому давайте рассмотрим, что такое указания и выражения, и как их различия влияют на тела функций.

-
    -
  • Указания выполняют какое-либо действие и не возвращают значения.
  • -
  • Выражения вычисляются до результирующего значения. Давайте рассмотрим несколько примеров.
  • -
-

На самом деле мы уже использовали указания и выражения. Создание переменной и присвоение ей значения с помощью ключевого слова let является оператором. В Приложении 3-1, let y = 6; — это указание.

-

Имя файла: src/main.rs

-
fn main() {
-    let y = 6;
-}
-

Приложение 3-1: Объявление функции main, содержащей одну указанию

-

Определения функций также являются указанием. Весь предыдущий пример сам по себе является указанием.

-

Указания не возвращают значения. Следовательно вы не можете присвоить let указанию другой переменной, как это пытается сделать следующий код. Вы получите ошибку:

-

Имя файла: src/main.rs

-
fn main() {
-    let x = (let y = 6);
-}
-

Если вы запустите эту программу, то ошибка будет выглядеть так:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-error: expected expression, found `let` statement
- --> src/main.rs:2:14
-  |
-2 |     let x = (let y = 6);
-  |              ^^^
-  |
-  = note: only supported directly in conditions of `if` and `while` expressions
-
-warning: unnecessary parentheses around assigned value
- --> src/main.rs:2:13
-  |
-2 |     let x = (let y = 6);
-  |             ^         ^
-  |
-  = note: `#[warn(unused_parens)]` on by default
-help: remove these parentheses
-  |
-2 -     let x = (let y = 6);
-2 +     let x = let y = 6;
-  |
-
-warning: `functions` (bin "functions") generated 1 warning
-error: could not compile `functions` (bin "functions") due to 1 previous error; 1 warning emitted
-
-

Указание let y = 6 не возвращает значение, поэтому не с чем связать переменную x. Это отличается от поведения в других языках, таких как C и Ruby, где присваивание возвращает присвоенное значение. В таких языках можно писать код x = y = 6 и обе переменные x и y будут иметь значение 6. Но в Ржавчина не так.

-

Выражения вычисляют значение и составляют большую часть остального кода, который вы напишете на Rust. Рассмотрим математическую действие, к примеру 5 + 6, которая является выражением, вычисляющим значение 11. Выражения могут быть частью указаний: в приложении 3-1 6 в указания let y = 6; является выражением, которое вычисляется в значение 6. Вызов функции — это выражение. Вызов макроса — это выражение. Новый разделобласти видимости, созданный с помощью фигурных скобок, представляет собой выражение, например:

-

Имя файла: src/main.rs

-
fn main() {
-    let y = {
-        let x = 3;
-        x + 1
-    };
-
-    println!("The value of y is: {y}");
-}
-

Это выражение:

-
{
-    let x = 3;
-    x + 1
-}
-

это блок, который в данном случае вычисляется в значение 4. Это значение связывается с y как часть указания let. Обратите внимание, что строка x + 1 не имеет точки с запятой в конце, что отличается от большинства строк, которые вы видели до сих пор. Выражения не содержат завершающих точек с запятой. Если вы добавите точку с запятой в конец выражения, вы превратите его в указанию, и тогда она не будет возвращать значение. Помните об этом, когда будете изучать возвращаемые значения функций и выражения.

-

Функции с возвращаемыми значениями

-

Функции могут возвращать значения коду, который их вызывает. Мы не называем возвращаемые значения, но мы должны объявить их вид после стрелки ( -> ). В Ржавчина возвращаемое значение функции является родственным значения конечного выражения в разделе тела функции. Вы можете раньше выйти из функции и вернуть значение, используя ключевое слово return и указав значение, но большинство функций неявно возвращают последнее выражение. Вот пример такой функции:

-

Имя файла: src/main.rs

-
fn five() -> i32 {
-    5
-}
-
-fn main() {
-    let x = five();
-
-    println!("The value of x is: {x}");
-}
-

В коде функции five нет вызовов функций, макросов или даже указаний let — есть только одно число 5. Это является абсолютно правильной функцией в Rust. Заметьте, что возвращаемый вид у данной функции определён как -> i32. Попробуйте запустить этот код. Вывод будет таким:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
-     Running `target/debug/functions`
-The value of x is: 5
-
-

Значение 5 в five является возвращаемым функцией значением, поэтому возвращаемый вид - i32. Рассмотрим пример более подробно. Здесь есть два важных особенности: во-первых, строка let x = five(); показывает использование возвращаемого функцией значения для объявления переменной. Так как функция five возвращает 5, то эта строка эквивалентна следующей:

-
#![allow(unused)]
-fn main() {
-let x = 5;
-}
-

Во-вторых, у функции five нет свойств и определён вид возвращаемого значения, но тело функции представляет собой одинокую 5 без точки с запятой, потому что это выражение, значение которого мы хотим вернуть.

-

Рассмотрим другой пример:

-

Имя файла: src/main.rs

-
fn main() {
-    let x = plus_one(5);
-
-    println!("The value of x is: {x}");
-}
-
-fn plus_one(x: i32) -> i32 {
-    x + 1
-}
-

Запуск кода напечатает The value of x is: 6. Но если поставить точку с запятой в конце строки, содержащей x + 1, превратив её из выражения в указанию, мы получим ошибку:

-

Имя файла: src/main.rs

-
fn main() {
-    let x = plus_one(5);
-
-    println!("The value of x is: {x}");
-}
-
-fn plus_one(x: i32) -> i32 {
-    x + 1;
-}
-

Сборка данного кода вызывает следующую ошибку:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-error[E0308]: mismatched types
- --> src/main.rs:7:24
-  |
-7 | fn plus_one(x: i32) -> i32 {
-  |    --------            ^^^ expected `i32`, found `()`
-  |    |
-  |    implicitly returns `()` as its body has no tail or `return` expression
-8 |     x + 1;
-  |          - help: remove this semicolon to return this value
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `functions` (bin "functions") due to 1 previous error
-
-

Основное сообщение об ошибке, несовпадение видов, раскрывает ключевую неполадку этого кода. Определение функции plus_one сообщает, что будет возвращено i32, но указания не вычисляются в значение, что и выражается единичным видом (). Следовательно, ничего не возвращается, что противоречит определению функции и приводит к ошибке. В этом выводе Ржавчина выдаёт сообщение, которое, возможно, поможет исправить эту неполадку: он предлагает удалить точку с запятой для устранения ошибки.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch03-04-comments.html b/rustbook-ru/book/ch03-04-comments.html deleted file mode 100644 index d1ac5825c..000000000 --- a/rustbook-ru/book/ch03-04-comments.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - Примечания - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Примечания

-

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

-

Пример простого примечания:

-
#![allow(unused)]
-fn main() {
-// Hello, world.
-}
-

В Ржавчина принят идиоматический исполнение примечаниев, который начинает примечание с двух косых черт, и примечание продолжается до конца строки. Для примечаниев, выходящих за пределы одной строки, необходимо включить // в каждую строку, как показано ниже:

-
#![allow(unused)]
-fn main() {
-// Итак, мы делаем что-то сложное, настолько длинное, что нам нужно
-// несколько строк примечаниев, чтобы сделать это! Ух! Надеюсь, этот примечание
-// объясняет, что происходит.
-}
-

Примечания также можно размещать в конце строк, содержащих код:

-

Имя файла: src/main.rs

-
fn main() {
-    let lucky_number = 7; // I’m feeling lucky today
-}
-

Но чаще всего они используются в таком виде: примечание располагается на отдельной строке над кодом, который он определяет:

-

Имя файла: src/main.rs

-
fn main() {
-    // I’m feeling lucky today
-    let lucky_number = 7;
-}
-

В Ржавчина есть ещё один вид примечаниев - документационные примечания, которые мы обсудим в разделе "Обнародование дополнения на Crates.io" главы 14.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch03-05-control-flow.html b/rustbook-ru/book/ch03-05-control-flow.html deleted file mode 100644 index fdc0c7ae6..000000000 --- a/rustbook-ru/book/ch03-05-control-flow.html +++ /dev/null @@ -1,526 +0,0 @@ - - - - - - Управляющие устройства - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Управляющие устройства

-

Возможности запуска некоторого кода в зависимости от некоторого условия, и замкнутого выполнения некоторого кода, являются основными элементами в большинстве языков программирования. Наиболее распространёнными устройствоми, позволяющими управлять потоком выполнения кода Rust, являются выражения if и циклы.

-

Выражения if

-

Выражение if позволяет выполнять части кода в зависимости от условий. Вы задаёте условие, а затем указываете: "Если это условие выполняется, выполните этот разделкода. Если условие не выполняется, не выполняйте этот разделкода".

-

Для изучения выражения if создайте новый дело под названием branches в папке projects. В файл src/main.rs поместите следующий код:

-

Имя файла: src/main.rs

-
fn main() {
-    let number = 3;
-
-    if number < 5 {
-        println!("condition was true");
-    } else {
-        println!("condition was false");
-    }
-}
-

Условие начинается с ключевого слова if, за которым следует условное выражение. В данном случае условное выражение проверяет, имеет ли переменная number значение меньше 5. Сразу после условного выражения внутри фигурных скобок мы помещаем разделкода, который будет выполняться, если итог равен true. Блоки кода, связанные с условными выражениями, иногда называют ветками, как и ветки в выражениях match, которые мы обсуждали в разделе "Сравнение догадки с тайным числом" главы 2.

-

Это необязательно, но мы также можем использовать ключевое слово else, которое мы используем в данном примере, чтобы предоставить программе иной разделвыполнения кода, выполняющийся если итог вычисления будет ложным. Если не указать выражение else и условие будет ложным, программа просто пропустит разделif и перейдёт к следующему отрывку кода.

-

Попробуйте запустить этот код. Появится следующий итог:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
-     Running `target/debug/branches`
-condition was true
-
-

Попробуйте изменить значение number на значение, которое делает условие false и посмотрите, что произойдёт:

-
fn main() {
-    let number = 7;
-
-    if number < 5 {
-        println!("condition was true");
-    } else {
-        println!("condition was false");
-    }
-}
-

Запустите программу снова и посмотрите на вывод:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
-     Running `target/debug/branches`
-condition was false
-
-

Также стоит отметить, что условие в этом коде должно быть логического вида bool. Если условие не является bool, возникнет ошибка. Например, попробуйте запустить следующий код:

-

Имя файла: src/main.rs

-
fn main() {
-    let number = 3;
-
-    if number {
-        println!("number was three");
-    }
-}
-

На этот раз условие if вычисляется в значение 3, и Ржавчина бросает ошибку:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-error[E0308]: mismatched types
- --> src/main.rs:4:8
-  |
-4 |     if number {
-  |        ^^^^^^ expected `bool`, found integer
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `branches` (bin "branches") due to 1 previous error
-
-

Ошибка говорит, что Ржавчина ожидал вид bool, но получил значение целочисленного вида. В отличии от других языков вроде Ruby и JavaScript, Ржавчина не будет пытаться самостоятельно преобразовывать нелогические виды в логические. Необходимо явно и всегда использовать if с логическим видом в качестве условия. Если нужно, чтобы разделкода if запускался только, когда число не равно 0, то, например, мы можем изменить выражение if на следующее:

-

Имя файла: src/main.rs

-
fn main() {
-    let number = 3;
-
-    if number != 0 {
-        println!("number was something other than zero");
-    }
-}
-

Будет напечатана следующая строка number was something other than zero.

-

Обработка нескольких условий с помощью else if

-

Можно использовать несколько условий, сочетая if и else в выражении else if. Например:

-

Имя файла: src/main.rs

-
fn main() {
-    let number = 6;
-
-    if number % 4 == 0 {
-        println!("number is divisible by 4");
-    } else if number % 3 == 0 {
-        println!("number is divisible by 3");
-    } else if number % 2 == 0 {
-        println!("number is divisible by 2");
-    } else {
-        println!("number is not divisible by 4, 3, or 2");
-    }
-}
-

У этой программы есть четыре возможных пути выполнения. После её запуска вы должны увидеть следующий итог:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
-     Running `target/debug/branches`
-number is divisible by 3
-
-

Во время выполнения этой программы по очереди проверяется каждое выражение if и выполняется первый блок, для которого условие true. Заметьте, что хотя 6 делится на 2, мы не видим ни вывода number is divisible by 2, ни текста number is not divisible by 4, 3, or 2 из раздела else. Так происходит потому, что Ржавчина выполняет разделтолько для первого истинного условия, а обнаружив его, даже не проверяет остальные.

-

Использование множества выражений else if приводит к загромождению кода, поэтому при наличии более чем одного выражения, возможно, стоит провести переработка кода кода. В главе 6 описана мощная устройство ветвления Ржавчина для таких случаев, называемая match.

-

Использование if в указания let

-

Поскольку if является выражением, его можно использовать в правой части указания let для присвоения итога переменной, как в приложении 3-2.

-

Имя файла: src/main.rs

-
fn main() {
-    let condition = true;
-    let number = if condition { 5 } else { 6 };
-
-    println!("The value of number is: {number}");
-}
-

Приложение 3-2: Присвоение итога выражения if переменной

-

Переменная number будет привязана к значению, которое является итогом выражения if. Запустим код и посмотрим, что происходит:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
-     Running `target/debug/branches`
-The value of number is: 5
-
-

Вспомните, что разделы кода вычисляются последним выражением в них, а числа сами по себе также являются выражениями. В данном случае, значение всего выражения if зависит от того, какой разделвыполняется. При этом значения, которые могут быть итогами каждого из ветвей if, должны быть одного вида. В Приложении 3-2, итогами обеих ветвей if и else являются целочисленный вид i32. Если виды не совпадают, как в следующем примере, мы получим ошибку:

-

Имя файла: src/main.rs

-
fn main() {
-    let condition = true;
-
-    let number = if condition { 5 } else { "six" };
-
-    println!("The value of number is: {number}");
-}
-

При попытке сборки этого кода, мы получим ошибку. Ветви if и else представляют несовместимые виды значений, и Ржавчина точно указывает, где искать неполадку в программе:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-error[E0308]: `if` and `else` have incompatible types
- --> src/main.rs:4:44
-  |
-4 |     let number = if condition { 5 } else { "six" };
-  |                                 -          ^^^^^ expected integer, found `&str`
-  |                                 |
-  |                                 expected because of this
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `branches` (bin "branches") due to 1 previous error
-
-

Выражение в разделе if вычисляется как целочисленное, а выражение в разделе else вычисляется как строка. Это не сработает, потому что переменные должны иметь один вид, а Ржавчина должен знать во время сборки, какого вида переменная number. Зная вид number, сборщик может убедиться, что вид действителен везде, где мы используем number. Ржавчина не смог бы этого сделать, если бы вид number определялся только во время выполнения. Сборщик усложнился бы и давал бы меньше заверений в отношении кода, если бы ему приходилось отслеживать несколько гипотетических видов для любой переменной.

-

Повторное выполнение кода с помощью циклов

-

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

-

В Ржавчина есть три вида циклов: loop, while и for. Давайте попробуем каждый из них.

-

Повторение выполнения кода с помощью loop

-

Ключевое слово loop говорит Ржавчина выполнять разделкода снова и снова до бесконечности или пока не будет явно приказано остановиться.

-

В качестве примера, измените код файла src/main.rs в папке дела loops на код ниже:

-

Имя файла: src/main.rs

-
fn main() {
-    loop {
-        println!("again!");
-    }
-}
-

Когда запустим эту программу, увидим, как again! печатается снова и снова, пока не остановить программу вручную. Большинство окно вызоваов поддерживают сочетание клавиш ctrl-c для прерывания программы, которая застряла в непрерывном цикле. Попробуйте:

- -
$ cargo run
-   Compiling loops v0.1.0 (file:///projects/loops)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.29s
-     Running `target/debug/loops`
-again!
-again!
-again!
-again!
-^Cagain!
-
-

Символ ^C обозначает место, где было нажато ctrl-c . В зависимости от того, где находился код в цикле в мгновение получения звонка отпрерывания, вы можете увидеть или не увидеть слово again!, напечатанное после ^C.

-

К счастью, Ржавчина также предоставляет способ выйти из цикла с помощью кода. Ключевое слово break нужно поместить в цикл, чтобы указать программе, когда следует прекратить выполнение цикла. Напоминаем, мы делали так в игре "Угадайка" в разделе "Выход после правильной догадки" Главы 2, чтобы выйти из программы, когда пользователь выиграл игру, угадав правильное число.

-

Мы также использовали continue в игре "Угадайка", которое указывает программе в цикле пропустить весь оставшийся код в данной повторения цикла и перейти к следующей повторения.

-

Возвращение значений из циклов

-

Одно из применений loop - это повторение действия, которая может закончиться неудачей, например, проверка успешности выполнения потоком своего задания. Также может понадобиться передать из цикла итог этой действия в остальную часть кода. Для этого можно добавить возвращаемое значение после выражения break, которое используется для остановки цикла. Это значение будет возвращено из цикла, и его можно будет использовать, как показано здесь:

-
fn main() {
-    let mut counter = 0;
-
-    let result = loop {
-        counter += 1;
-
-        if counter == 10 {
-            break counter * 2;
-        }
-    };
-
-    println!("The result is {result}");
-}
-

Перед циклом мы объявляем переменную с именем counter и объявим её значением 0. Затем мы объявляем переменную с именем result для хранения значения, возвращаемого из цикла. На каждой повторения цикла мы добавляем 1 к переменной counter, а затем проверяем, равняется ли 10 переменная counter. Когда это происходит, мы используем ключевое слово break со значением counter * 2. После цикла мы ставим точку с запятой для завершения указания, присваивающей значение result. Наконец, мы выводим значение в result, равное в данном случае 20.

-

Метки циклов для устранения неоднозначности между несколькими циклами

-

Если у вас есть циклы внутри циклов, break и continue применяются к самому внутреннему циклу в этой цепочке. При желании вы можете создать метку цикла, которую вы затем сможете использовать с break или continue для указания, что эти ключевые слова применяются к помеченному циклу, а не к самому внутреннему циклу. Метки цикла должны начинаться с одинарной кавычки. Вот пример с двумя вложенными циклами:

-
fn main() {
-    let mut count = 0;
-    'counting_up: loop {
-        println!("count = {count}");
-        let mut remaining = 10;
-
-        loop {
-            println!("remaining = {remaining}");
-            if remaining == 9 {
-                break;
-            }
-            if count == 2 {
-                break 'counting_up;
-            }
-            remaining -= 1;
-        }
-
-        count += 1;
-    }
-    println!("End count = {count}");
-}
-

Внешний цикл имеет метку 'counting_up, и он будет считать от 0 до 2. Внутренний цикл без метки ведёт обратный отсчёт от 10 до 9. Первый break, который не содержит метку, выйдет только из внутреннего цикла. Указание break 'counting_up; завершит внешний цикл. Этот код напечатает:

-
$ cargo run
-   Compiling loops v0.1.0 (file:///projects/loops)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s
-     Running `target/debug/loops`
-count = 0
-remaining = 10
-remaining = 9
-count = 1
-remaining = 10
-remaining = 9
-count = 2
-remaining = 10
-End count = 2
-
-

Циклы с условием while

-

В программе часто требуется проверить состояние условия в цикле. Пока условие истинно, цикл выполняется. Когда условие перестаёт быть истинным, программа вызывает break, останавливая цикл. Такое поведение можно выполнить с помощью сочетания loop, if, else и break. При желании попробуйте сделать это в программе. Это настолько распространённый образец, что в Ржавчина выполнена встроенная языковая устройство для него, называемая цикл while. В приложении 3-3 мы используем while, чтобы выполнить три цикла программы, производя каждый раз обратный отсчёт, а затем, после завершения цикла, печатаем сообщение и выходим.

-

Имя файла: src/main.rs

-
fn main() {
-    let mut number = 3;
-
-    while number != 0 {
-        println!("{number}!");
-
-        number -= 1;
-    }
-
-    println!("LIFTOFF!!!");
-}
-

Приложение 3-3: Использование цикла while для выполнения кода, пока условие истинно

-

Эта устройство устраняет множество вложений, которые потребовались бы при использовании loop, if, else и break, и она более понятна. Пока условие вычисляется в true, код выполняется; в противном случае происходит выход из цикла.

-

Цикл по элементам собрания с помощью for

-

Для перебора элементов собрания, например, массива, можно использовать устройство while. Например, цикл в приложении 3-4 печатает каждый элемент массива a.

-

Имя файла: src/main.rs

-
fn main() {
-    let a = [10, 20, 30, 40, 50];
-    let mut index = 0;
-
-    while index < 5 {
-        println!("the value is: {}", a[index]);
-
-        index += 1;
-    }
-}
-

Приложение 3-4: Перебор каждого элемента собрания с помощью цикла while

-

Этот код выполняет перебор элементов массива. Он начинается с порядкового указателя 0, а затем замкнуто выполняется, пока не достигнет последнего порядкового указателя в массиве (то есть, когда index < 5 уже не является истиной). Выполнение этого кода напечатает каждый элемент массива:

-
$ cargo run
-   Compiling loops v0.1.0 (file:///projects/loops)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s
-     Running `target/debug/loops`
-the value is: 10
-the value is: 20
-the value is: 30
-the value is: 40
-the value is: 50
-
-

Все пять значений массива появляются в окне вызова, как и ожидалось. Поскольку index в какой-то мгновение достигнет значения 5, цикл прекратит выполнение перед попыткой извлечь шестое значение из массива.

-

Однако такой подход чреват ошибками; мы можем вызвать панику в программе, если значение порядкового указателя или условие проверки неверны. Например, если изменить определение массива a на четыре элемента, но забыть обновить условие на while index < 4, код вызовет панику. Также это медленно, поскольку сборщик добавляет код времени выполнения для обеспечения проверки нахождения порядкового указателя в границах массива на каждой повторения цикла.

-

В качестве более краткой иного решения можно использовать цикл for и выполнять некоторый код для каждого элемента собрания. Цикл for может выглядеть как код в приложении 3-5.

-

Имя файла: src/main.rs

-
fn main() {
-    let a = [10, 20, 30, 40, 50];
-
-    for element in a {
-        println!("the value is: {element}");
-    }
-}
-

Приложение 3-5: Перебор каждого элемента собрания с помощью цикла for

-

При выполнении этого кода мы увидим тот же итог, что и в приложении 3-4. Что важнее, теперь мы повысили безопасность кода и устранили вероятность ошибок, которые могут возникнуть в итоге выхода за пределы массива или недостаточно далёкого перехода и пропуска некоторых элементов.

-

При использовании цикла for не нужно помнить о внесении изменений в другой код, в случае изменения количества значений в массиве, как это было бы с способом, использованным в приложении 3-4.

-

Безопасность и краткость циклов for делают их наиболее часто используемой устройством цикла в Rust. Даже в случаейх необходимости выполнения некоторого кода определённое количество раз, как в примере обратного отсчёта, в котором использовался цикл while из Приложения 3-3, большинство Rustaceans использовали бы цикл for. Для этого можно использовать Range, предоставляемый встроенной библиотекой, который порождает последовательность всех чисел, начиная с первого числа и заканчивая вторым числом, но не включая его (т.е. (1..4) эквивалентно [1, 2, 3] или в общем случае (start..end) эквивалентно [start, start+1, start+2, ... , end-2, end-1] - прим.переводчика).

-

Вот как будет выглядеть обратный отсчёт с использованием цикла for и другого способа, о котором мы ещё не говорили, rev, для разворота ряда:

-

Имя файла: src/main.rs

-
fn main() {
-    for number in (1..4).rev() {
-        println!("{number}!");
-    }
-    println!("LIFTOFF!!!");
-}
-

Данный код выглядит лучше, не так ли?

-

Итоги

-

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

-
    -
  • Преобразование температур между значениями по Фаренгейту к Цельсию.
  • -
  • Порождение n-го числа Фибоначчи.
  • -
  • Распечатайте текст рождественской песни "Двенадцать дней Рождества", воспользовавшись повторами в песне.
  • -
-

Когда вы будете готовы двигаться дальше, мы поговорим о подходы в Rust, которая не существует обычно в других языках программирования: владение.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch04-00-understanding-ownership.html b/rustbook-ru/book/ch04-00-understanding-ownership.html deleted file mode 100644 index 0d870ef28..000000000 --- a/rustbook-ru/book/ch04-00-understanding-ownership.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - Понимание владения - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Понимание Владения

-

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

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch04-01-what-is-ownership.html b/rustbook-ru/book/ch04-01-what-is-ownership.html deleted file mode 100644 index 9bf96fdb8..000000000 --- a/rustbook-ru/book/ch04-01-what-is-ownership.html +++ /dev/null @@ -1,492 +0,0 @@ - - - - - - Что такое "владение"? - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Что такое владение?

-

Владение — это набор правил, определяющих, как программа на языке Ржавчина управляет памятью. Все программы так или иначе должны управлять тем, как они используют память компьютера во время работы. Некоторые языки имеют сборщик мусора, постоянно отслеживающий неиспользуемую память во время работы программы; в других языках программист должен явно выделять и освобождать память. В Ржавчина используется третий подход: память управляется через систему владения с набором правил, которые проверяются сборщиком. При нарушении любого из правил программа не будет собрана. Ни одна из особенностей системы владения не замедлит работу вашей программы.

-

Поскольку владение является новой подходом для многих программистов, требуется некоторое время, чтобы привыкнуть к ней. Хорошая новость заключается в том, что чем больше у вас будет опыта с Ржавчина и с правилами системы владения, тем легче вам будет естественным образом разрабатывать безопасный и эффективный код. Держитесь! Не сдавайтесь!

-

Понимание подходы владения даст вам основу для понимания всех остальных особенностей, делающих Ржавчина единственным. В этой главе вы изучите владение на примерах, которые сосредоточены на наиболее часто используемой устройстве данных: строках.

-
-

Обойма и куча

-

Многие языки программирования не требуют, чтобы вы слишком часто думали о обойме и куче. Но в языках системного программирования, одним из которых является Rust, то, какое значение находится в обойме или в куче, влияет на поведение языка и на принятие вами определённых решений. Владение будет описано через призму обоймы и кучи позже в этой главе, а пока — краткое пояснение.

-

И обойма, и куча — это части памяти, доступные вашему коду для использования во время выполнения. Однако они внутренне выстроенны

-
-

по-разному. Обойма хранит значения в порядке их получения, а удаляет — в обратном. Это называется «последним пришёл — первым ушёл». Подумайте о стопке тарелок: когда вы добавляете тарелки, вы кладёте их сверху стопки — когда вам нужна тарелка, вы берёте одну так же сверху. Добавление или удаление тарелок посередине или снизу не сработает! Добавление данных называется помещением в обойма, а удаление — извлечением из обоймы. Все данные, хранящиеся в обойме, должны иметь известный определенный размер. Данные, размер которых во время сборки неизвестен или может измениться, должны храниться в куче.

-
-

Куча устроена менее согласованно: когда вы кладёте данные в кучу, вы запрашиваете определённый объём пространства. Операционная система находит в куче свободный участок памяти достаточного размера, помечает его как используемый и возвращает указатель, являющийся адресом этого участка памяти. Этот этап называется выделением памяти в куче и иногда сокращается до выделения памяти (помещение значений в обойма не считается выделением). Поскольку указатель на участок памяти в куче имеет определённый определенный размер, его можно расположить в обойме, однако когда вам понадобятся актуальные данные, вам придётся проследовать по указателю. Представьте, что вы сидите в ресторане. Когда вы входите, вы называете количество человек в вашей объединении, и человек находит свободный стол, которого хватит на всех, и ведёт вас туда. Если кто-то из вашей объединение опоздает, он может спросить, куда вас посадили, чтобы найти вас.

-

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

-

Доступ к данным в куче медленнее, чем доступ к данным в обойме, потому что вам нужно следовать по адресу указателя, чтобы добраться туда. Современные процессоры работают быстрее, если они меньше прыгают по памяти. Продолжая подобие, рассмотрим официанта в ресторане, принимающего заказы со многих столов. Наиболее эффективно будет получить все заказы за одним столом, прежде чем переходить к следующему столу. Получение заказа со стола А, затем со стола В, затем снова одного с А, а затем снова одного с В было бы гораздо более медленным делом. Точно так же процессор может выполнять свою работу лучше, если он работает с данными, которые находятся близко к другим данным (как в обойме), а не далеко (как это может быть в куче).

-

Когда ваш код вызывает функцию, значения, переданные в неё (возможно включающие указатели на данные в куче), и местные переменные помещаются в обойма. Когда функция завершается, эти значения извлекаются из обоймы.

-

Отслеживание того, какие части кода используют какие данные, уменьшение количества повторяющихся данных и очистка неиспользуемых данных в куче, чтобы не исчерпать пространство, — все эти сбоев решает владение. Как только вы поймёте, что такое владение, вам не нужно будет слишком часто думать о обойме и куче. Однако знание того, что основная цель владения — управление данными кучи, может помочь объяснить, почему оно работает именно так.

-
-

Правила владения

-

Во-первых, давайте взглянем на правила владения. Помните об этих правилах, пока мы работаем с примерами, которые их отображают:

-
    -
  • У каждого значения в Ржавчина есть владелец,
  • -
  • У значения может быть только один владелец в один мгновение времени,
  • -
  • Когда владелец покидает область видимости, значение удаляется.
  • -
-

Область видимости переменной

-

Теперь, когда мы прошли основной правила написания Rust, мы не будем включать весь код fn main() { в примеры. Поэтому, если вы будете следовать этому курсу, убедитесь, что следующие примеры помещены в функцию main вручную. В итоге наши примеры будут более краткими, что позволит нам сосредоточиться на существующих подробностях, а не на образцовом коде.

-

В качестве первого примера владения мы рассмотрим область видимости некоторых переменных. Область видимости — это рядвнутри программы, для которого допустим элемент. Возьмём следующую переменную:

-
#![allow(unused)]
-fn main() {
-let s = "hello";
-}
-

Переменная s относится к строковому записи, где значение строки жёстко прописано в тексте нашей программы. Переменная действительна с особенности её объявления до конца текущей области видимости. В приложении 4-1 показана программа с примечаниями, указывающими, где допустима переменная s .

-
fn main() {
-    {                      // s is not valid here, it’s not yet declared
-        let s = "hello";   // s is valid from this point forward
-
-        // do stuff with s
-    }                      // this scope is now over, and s is no longer valid
-}
-

Приложение 4-1: переменная и область действия, в которой она допустима

-

Другими словами, здесь есть два важных особенности:

-
    -
  • Когда переменная s появляется в области видимости, она считается действительной,
  • -
  • Она остаётся действительной до особенности выхода за границы этой области.
  • -
-

На этом этапе объяснения взаимосвязь между областями видимости и допустимостью переменных подобна той, что существует в других языках программирования. Теперь мы будем опираться на это понимание, введя вид String.

-

Вид данных String

-

Для отображения правил владения нам требуется более сложный вид данных чем те, что мы обсуждали в части "Виды данных" Главы 3. Виды, рассмотренные ранее, имеют определённый размер, а значит могут быть размещены на обойме и извлечены из него, когда их область видимости закончится, и могут быть быстро и обыкновенно воспроизведены для создания новой, независимой повторы, если другой части кода нужно использовать то же самое значение в другой области видимости. Но мы хотим посмотреть на данные, хранящиеся в куче, и выяснить, как Ржавчина узнаёт, когда нужно очистить эти данные, поэтому вид String — отличный пример.

-

Мы сосредоточимся на тех частях String, которые связаны с владением. Эти особенности также применимы к другим сложным видам данных, независимо от того, предоставлены они встроенной библиотекой или созданы вами. Более подробно мы обсудим String в главе 8.

-

Мы уже видели строковые записи, где строковое значение жёстко прописано в нашей программе. Строковые записи удобны, но они подходят не для каждой случаи, где мы можем хотеть использовать текст. Одна из причин заключается в том, что они неизменны. Кроме того, не каждое строковое значение может быть известно во время написания кода: что, если мы захотим принять и сохранить пользовательский ввод? Для таких случаев в Ржавчина есть ещё один строковый вид — String. Этот вид управляет данными, выделенными в куче, и поэтому может хранить объём текста, который во время сборки неизвестен. Также вы можете создать String из строкового записи, используя функцию from, например:

-
#![allow(unused)]
-fn main() {
-let s = String::from("hello");
-}
-

Оператор "Двойное двоеточие" :: позволяет использовать пространство имён данной именно функции from с видом String, а не какое-то иное имя, такое как string_from. Мы обсудим этот правила написания более подробно в разделе «Синтаксис способа». раздел Главы 5, и в ходе обсуждения пространств имён с звенами в «Пути для обращения к элементу в дереве звеньев» в главе 7.

-

Строка такого вида может быть изменяема:

-
fn main() {
-    let mut s = String::from("hello");
-
-    s.push_str(", world!"); // push_str() appends a literal to a String
-
-    println!("{s}"); // This will print `hello, world!`
-}
-

В чем же тут разница? Почему строку String можно изменить, а записи — нельзя? Разница заключается в том, как эти два вида работают с памятью.

-

Память и способы её выделения

-

В случае строкового записи мы знаем его содержимое во время сборки, и оно жёстко прописано в итоговом исполняемом файле. Причина того, что строковые записи более быстрые и эффективные, в их неизменяемости. К сожалению, нельзя поместить неопределённый кусок памяти в выполняемый файл для текста, размер которого неизвестен при сборки и может меняться во время выполнения программы.

-

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

-
    -
  • Память должна запрашиваться у операционной системы во время выполнения программы,
  • -
  • Необходим способ возврата этой памяти операционной системе, когда мы закончили в программе работу со String.
  • -
-

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

-

Однако вторая часть отличается. В языках со сборщиком мусора (GC), память, которая больше не используется, отслеживается и очищается с его помощью — нам не нужно об этом думать. В большинстве языков без сборщика мусора мы обязаны сами определять, когда память больше не используется, и вызывать код для явного её освобождения, точно так же, как мы делали это для её запроса. Правильное выполнение этого этапа исторически было сложной неполадкой программирования. Если мы забудем освободить память, она будет потеряна. Если мы сделаем это слишком рано, у нас будет недопустимая переменная. Сделать это дважды — тоже будет ошибкой. Нам нужно соединить ровно один allocate ровно с одним free.

-

Rust выбирает другой путь: память самостоятельно возвращается, как только владеющая памятью переменная выходит из области видимости. Вот исполнение примера с областью видимости из приложения 4-1, в котором используется вид String вместо строкового записи:

-
fn main() {
-    {
-        let s = String::from("hello"); // s is valid from this point forward
-
-        // do stuff with s
-    }                                  // this scope is now over, and s is no
-                                       // longer valid
-}
-

Существует естественный мгновение, когда мы можем вернуть память, необходимую нашему String, обратно распределителю — когда s выходит за пределы области видимости. Когда переменная выходит за пределы области видимости, Ржавчина вызывает для нас особую функцию. Эта функция называется drop, и именно здесь автор String может поместить код для возврата памяти. Ржавчина самостоятельно вызывает drop после закрывающей фигурной скобки.

-
-

Примечание: в C++ этот образец освобождения ресурсов в конце времени жизни элемента иногда называется «Получение ресурса есть объявление» (англ. Resource Acquisition Is Initialization (RAII)). Функция drop в Ржавчина покажется вам знакомой, если вы использовали образцы RAII.

-
-

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

- -

-

Взаимодействие переменных и данных с помощью перемещения

-

Несколько переменных могут по-разному взаимодействовать с одними и теми же данными в Rust. Давайте рассмотрим пример использования целого числа в приложении 4-2.

-
fn main() {
-    let x = 5;
-    let y = x;
-}
-

Приложение 4-2. Присвоение целочисленного значения переменной x переменной y

-

Мы можем догадаться, что делает этот код: «привязать значение 5 к x; затем сделать повтор значения в x и привязать его к y». Теперь у нас есть две переменные: x и y, и обе равны 5. Это то, что происходит на самом деле, потому что целые числа — это простые значения с известным конечным размером, и эти два значения 5 помещаются в обойма.

-

Теперь рассмотрим исполнение с видом String:

-
fn main() {
-    let s1 = String::from("hello");
-    let s2 = s1;
-}
-

Это выглядит очень похоже, поэтому мы можем предположить, что происходит то же самое: вторая строка сделает повтор значения в s1 и привяжет его к s2. Но это не совсем так.

-

Взгляните на рисунок 4-1, чтобы увидеть, что происходит со String под капотом. String состоит из трёх частей, показанных слева: указатель на память, в которой хранится содержимое строки, длина и ёмкость. Эта объединение данных хранится в обойме. Справа — память в куче, которая содержит содержимое.

-Two tables: the first table contains the representation of s1 on the<br>stack, consisting of its length (5), capacity (5), and a pointer to the first<br>value in the second table. The second table contains the representation of the<br>string data on the heap, byte by byte. -

Рисунок 4-1: представление в памяти String, содержащей значение "hello", привязанное к s1

-

Длина — это объём памяти в байтах, который в настоящее время использует содержимое String. Ёмкость — это общий объём памяти в байтах, который String получил от распределителя. Разница между длиной и ёмкостью имеет значение, но не в этом среде, поэтому на данный мгновение можно пренебрегать ёмкость.

-

Когда мы присваиваем s1 значению s2, данные String повторяются, то есть мы повторяем указатель, длину и ёмкость, которые находятся в обойме. Мы не повторяем данные в куче, на которые указывает указатель. Другими словами, представление данных в памяти выглядит так, как показано на рис. 4-2.

-Three tables: tables s1 and s2 representing those strings on the<br>stack, respectively, and both pointing to the same string data on the heap. -

Рисунок 4-2: представление в памяти переменной s2, имеющей повтор указателя, длины и ёмкости s1

-

Представление не похоже на рисунок 4-3, как выглядела бы память, если бы вместо этого Ржавчина также воспроизвел данные кучи. Если бы Ржавчина сделал это, действие s2 = s1 могла бы быть очень дорогой с точки зрения производительности во время выполнения, если бы данные в куче были большими.

-Two tables: the first table contains the representation of s1 on the<br>stack, consisting of its length (5), capacity (5), and a pointer to the first<br>value in the second table. The second table contains the representation of the<br>string data on the heap, byte by byte. -

Рисунок 4-3: другой исход того, что может сделать s2 = s1, если Ржавчина также воспроизведет данные кучи

-

Ранее мы сказали, что когда переменная выходит за пределы области видимости, Ржавчина самостоятельно вызывает функцию drop и очищает память в куче для данной переменной. Но на рис. 4.2 оба указателя данных указывают на одно и то же место. Это неполадка: когда переменные s2 и s1 выходят из области видимости, они обе будут пытаться освободить одну и ту же память в куче. Это известно как ошибка двойного освобождения (double free) и является одной из ошибок безопасности памяти, упоминаемых ранее. Освобождение памяти дважды может привести к повреждению памяти, что возможно может привести к уязвимостям безопасности.

-

Чтобы обеспечить безопасность памяти, после строки let s2 = s1; , Ржавчина считает s1 более недействительным. Следовательно, Ржавчина не нужно ничего освобождать, когда s1 выходит за пределы области видимости. Посмотрите, что происходит, когда вы пытаетесь использовать s1 после создания s2 ; это не сработает:

-
fn main() {
-    let s1 = String::from("hello");
-    let s2 = s1;
-
-    println!("{s1}, world!");
-}
-

Вы получите похожую ошибку, потому что Ржавчина не позволяет вам использовать недействительную ссылку:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0382]: borrow of moved value: `s1`
- --> src/main.rs:5:15
-  |
-2 |     let s1 = String::from("hello");
-  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
-3 |     let s2 = s1;
-  |              -- value moved here
-4 |
-5 |     println!("{s1}, world!");
-  |               ^^^^ value borrowed here after move
-  |
-  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
-help: consider cloning the value if the performance cost is acceptable
-  |
-3 |     let s2 = s1.clone();
-  |                ++++++++
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `ownership` (bin "ownership") due to 1 previous error
-
-

Если вы слышали понятия поверхностное повторение и глубокое повторение при работе с другими языками, подход повторения указателя, длины и ёмкости без повторения данных, вероятно, звучит как создание поверхностной повторы. Но поскольку Ржавчина также аннулирует первую переменную, вместо того, чтобы называть это поверхностным повторением, это называется перемещением. В этом примере мы бы сказали, что s1 был перемещён в s2. Итак, что на самом деле происходит, показано на рисунке 4-4.

-Три таблицы: таблицы s1 и s2, представляющие эти строки в обойме соответственно, и обе указывающие на одни и те же строковые данные в куче. Таблица s1 выделена серым цветом, потому что s1 больше недействительна; только s2 можно использовать для доступа к данным кучи. -

Рисунок 4-4: представление в памяти после того, как s1 был признан недействительным

-

Это решает нашу неполадку! Действительной остаётся только переменная s2. Когда она выходит из области видимости, то она одна будет освобождать память в куче.

-

Такой выбор внешнего вида языка даёт дополнительное преимущество: Ржавчина никогда не будет самостоятельно создавать «глубокие» повторы ваших данных. Следовательно любое такое самостоятельное повторение можно считать недорогим с точки зрения производительности во время выполнения.

- -

-

Взаимодействие переменных и данных с помощью клонирования

-

Если мы хотим глубоко воспроизвести данные кучи String, а не только данные обоймы, мы можем использовать общий способ, называемый clone. Мы обсудим правила написания способов в главе 5, но поскольку способы являются общей чертой многих языков программирования, вы, вероятно, уже встречались с ними.

-

Вот пример работы способа clone:

-
fn main() {
-    let s1 = String::from("hello");
-    let s2 = s1.clone();
-
-    println!("s1 = {s1}, s2 = {s2}");
-}
-

Это отлично работает и очевидно приводит к поведению, представленному на рисунке 4-3, где данные кучи были воспроизведены.

-

Когда вы видите вызов clone, вы знаете о выполнении некоторого кода, который может быть дорогим. В то же время использование clone является визуальным индикатором того, что тут происходит что-то необычное.

-

Из обоймы данные: повторение

-

Это ещё одна особенность о которой мы ранее не говорили. Этот код, часть которого была показа ранее в приложении 4-2, использует целые числа. Он работает без ошибок:

-
fn main() {
-    let x = 5;
-    let y = x;
-
-    println!("x = {x}, y = {y}");
-}
-

Но этот код, кажется, противоречит тому, что мы только что узнали: у нас нет вызова clone, но x всё ещё действителен и не был перемещён в y.

-

Причина в том, что такие виды, как целые числа, размер которых известен во время сборки, полностью хранятся в обойме, поэтому повторы действительных значений создаются быстро. Это означает, что нет причин, по которым мы хотели бы предотвратить доступность x после того, как создадим переменную y. Другими словами, здесь нет разницы между глубоким и поверхностным повторением, поэтому вызов clone ничем не отличается от обычного поверхностного повторения, и мы можем его опустить.

-

В Ржавчина есть особая изложение, называемая особенностью Copy, которую мы можем размещать на видах, хранящихся в обойме, как и целые числа (подробнее о видах мы поговорим в главе 10). Если вид выполняет особенность Copy, переменные, которые его используют, не перемещаются, а обыкновенно повторяются, что делает их действительными после присвоения другой переменной.

-

Rust не позволит нам определять вид с помощью Copy, если вид или любая из его частей выполняет Drop. Если для вида нужно, чтобы произошло что-то особенное, когда значение выходит за пределы области видимости, и мы добавляем изложение Copy к этому виду, мы получим ошибку времени сборки. Чтобы узнать, как добавить изложение Copy к вашему виду для выполнения особенности, смотрите раздел «Производные особенности» в приложении С.

-

Но какие же виды выполняют особенность Copy? Можно проверить документацию любого вида для уверенности, но как правило любая объединение простых одиночных значений может быть выполнить Copy, и никакие виды, которые требуют выделения памяти в куче или являются некоторой способом ресурсов, не выполняют особенности Copy. Вот некоторые виды, которые выполняют Copy:

-
    -
  • Все целочисленные виды, такие как u32,
  • -
  • Логический вид данных bool, возможные значения которого true и false,
  • -
  • Все виды с плавающей запятой, такие как f64.
  • -
  • Символьный вид char,
  • -
  • Упорядоченные ряды, но только если они содержат виды, которые также выполняют Copy. Например, (i32, i32) будет с Copy, но упорядоченный ряд (i32, String) уже нет.
  • -
-

Владение и функции

-

Механика передачи значения функции подобна тому, что происходит при присвоении значения переменной. Передача переменной в функцию приведёт к перемещению или воспроизведению, как и присваивание. В приложении 4-3 есть пример с некоторыми изложениями, показывающими, где переменные входят в область видимости и выходят из неё.

-

Файл: src/main.rs

-
fn main() {
-    let s = String::from("hello");  // s comes into scope
-
-    takes_ownership(s);             // s's value moves into the function...
-                                    // ... and so is no longer valid here
-
-    let x = 5;                      // x comes into scope
-
-    makes_copy(x);                  // x would move into the function,
-                                    // but i32 is Copy, so it's okay to still
-                                    // use x afterward
-
-} // Here, x goes out of scope, then s. But because s's value was moved, nothing
-  // special happens.
-
-fn takes_ownership(some_string: String) { // some_string comes into scope
-    println!("{some_string}");
-} // Here, some_string goes out of scope and `drop` is called. The backing
-  // memory is freed.
-
-fn makes_copy(some_integer: i32) { // some_integer comes into scope
-    println!("{some_integer}");
-} // Here, some_integer goes out of scope. Nothing special happens.
-

Приложение 4-3. Функции с определенными владельцами и областью действия

-

Если попытаться использовать s после вызова takes_ownership, Ржавчина выдаст ошибку времени сборки. Такие постоянные проверки защищают от ошибок. Попробуйте добавить код в main, который использует переменную s и x, чтобы увидеть где их можно использовать и где правила владения предотвращают их использование.

-

Возвращение значений и область видимости

-

Возвращаемые значения также могут передавать право владения. В приложении 4-4 показан пример функции, возвращающей некоторое значение, с такими же изложениями, как в приложении 4-3.

-

Файл: src/main.rs

-
fn main() {
-    let s1 = gives_ownership();         // gives_ownership moves its return
-                                        // value into s1
-
-    let s2 = String::from("hello");     // s2 comes into scope
-
-    let s3 = takes_and_gives_back(s2);  // s2 is moved into
-                                        // takes_and_gives_back, which also
-                                        // moves its return value into s3
-} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
-  // happens. s1 goes out of scope and is dropped.
-
-fn gives_ownership() -> String {             // gives_ownership will move its
-                                             // return value into the function
-                                             // that calls it
-
-    let some_string = String::from("yours"); // some_string comes into scope
-
-    some_string                              // some_string is returned and
-                                             // moves out to the calling
-                                             // function
-}
-
-// This function takes a String and returns one
-fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
-                                                      // scope
-
-    a_string  // a_string is returned and moves out to the calling function
-}
-

Приложение 4-4: передача права владения на возвращаемые значения

-

Владение переменной каждый раз следует одному и тому же образцу: присваивание значения другой переменной перемещает его. Когда переменная, содержащая данные в куче, выходит из области видимости, содержимое в куче будет очищено функцией drop, если только данные не были перемещены во владение другой переменной.

-

Хотя это работает, получение права владения, а затем возвращение владения каждой функцией немного утомительно. Что, если мы хотим, чтобы функция использовала значение, но не становилась владельцем? Очень раздражает, что всё, что мы передаём, также должно быть передано обратно, если мы хотим использовать это снова, в дополнение к любым данным, полученным из тела функции, которые мы также можем захотеть вернуть.

-

Rust позволяет нам возвращать несколько значений с помощью упорядоченного ряда, как показано в приложении 4-5.

-

Файл: src/main.rs

-
fn main() {
-    let s1 = String::from("hello");
-
-    let (s2, len) = calculate_length(s1);
-
-    println!("The length of '{s2}' is {len}.");
-}
-
-fn calculate_length(s: String) -> (String, usize) {
-    let length = s.len(); // len() returns the length of a String
-
-    (s, length)
-}
-

Приложение 4-5: возврат права владения на свойства

-

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

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch04-02-references-and-borrowing.html b/rustbook-ru/book/ch04-02-references-and-borrowing.html deleted file mode 100644 index e1e78e48c..000000000 --- a/rustbook-ru/book/ch04-02-references-and-borrowing.html +++ /dev/null @@ -1,490 +0,0 @@ - - - - - - Ссылки и заимствование - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Ссылки и заимствование

-

Неполадкас кодом упорядоченного ряда в приложении 4-5 заключается в том, что мы должны вернуть String из вызванной функции, чтобы использовать String после вызова calculate_length, потому что String была перемещена в calculate_length. Вместо этого мы можем предоставить ссылку на значение String. Ссылка похожа на указатель в том смысле, что это адрес, по которому мы можем проследовать, чтобы получить доступ к данным, хранящимся по этому адресу; эти данные принадлежат какой-то другой переменной. В отличие от указателя, ссылка обязательно указывает на допустимое значение определённого вида в течение всего срока существования этой ссылки.

-

Вот как вы могли бы определить и использовать функцию calculate_length, имеющую ссылку на предмет в качестве свойства, вместо того, чтобы брать на себя ответственность за значение:

-

Файл: src/main.rs

-
fn main() {
-    let s1 = String::from("hello");
-
-    let len = calculate_length(&s1);
-
-    println!("The length of '{s1}' is {len}.");
-}
-
-fn calculate_length(s: &String) -> usize {
-    s.len()
-}
-

Во-первых, обратите внимание, что весь код упорядоченного ряда в объявлении переменной и возвращаемое значение функции исчезли. Во-вторых, обратите внимание, что мы передаём &s1 в calculate_length и в его определении используем &String, а не String. Эти знаки представляют собой ссылки, и они позволяют вам ссылаться на некоторое значение, не принимая владение над ним. Рисунок 4-5 изображает эту подход.

-&String s pointing at String s1 -

Рисунок 4-5: диаграмма для &String s, указывающей на String s1

-
-

Примечание: противоположностью ссылки с использованием & является разыменование, выполняемое с помощью оператора разыменования *. Мы увидим некоторые исходы использования оператора разыменования в главе 8 и обсудим подробности разыменования в главе 15.

-
-

Давайте подробнее рассмотрим рычаг вызова функции:

-
fn main() {
-    let s1 = String::from("hello");
-
-    let len = calculate_length(&s1);
-
-    println!("The length of '{s1}' is {len}.");
-}
-
-fn calculate_length(s: &String) -> usize {
-    s.len()
-}
-

&s1 позволяет нам создать ссылку, которая ссылается на значение s1, но не владеет им. Поскольку она не владеет им, значение, на которое она указывает, не будет удалено, когда ссылка перестанет использоваться.

-

Ярлык функции использует & для индикации того, что вид свойства s является ссылкой. Добавим объясняющие примечания:

-
fn main() {
-    let s1 = String::from("hello");
-
-    let len = calculate_length(&s1);
-
-    println!("The length of '{s1}' is {len}.");
-}
-
-fn calculate_length(s: &String) -> usize { // s is a reference to a String
-    s.len()
-} // Here, s goes out of scope. But because it does not have ownership of what
-  // it refers to, it is not dropped.
-

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

-

Мы называем этап создания ссылки заимствованием. Как и в существующей жизни, если человек чем-то владеет, вы можете это у него позаимствовать. Когда вы закончите, вы должны вернуть это законному владельцу.

-

А что произойдёт, если попытаться изменить то, что было позаимствовано? Попробуйте код приложения 4-6 Спойлер: этот код не сработает!

-

Файл: src/main.rs

-
fn main() {
-    let s = String::from("hello");
-
-    change(&s);
-}
-
-fn change(some_string: &String) {
-    some_string.push_str(", world");
-}
-

Приложение 4-6: попытка изменения заимствованной переменной

-

Вот ошибка:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
- --> src/main.rs:8:5
-  |
-8 |     some_string.push_str(", world");
-  |     ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
-  |
-help: consider changing this to be a mutable reference
-  |
-7 | fn change(some_string: &mut String) {
-  |                         +++
-
-For more information about this error, try `rustc --explain E0596`.
-error: could not compile `ownership` (bin "ownership") due to 1 previous error
-
-

Как переменные неизменяемы по умолчанию, так и ссылки. Нам не разрешено изменять то, на что у нас есть ссылка.

-

Изменяемые ссылки

-

Мы можем исправить код из приложения 4-6, чтобы позволить себе изменять заимствованное значение, с помощью нескольких небольших настроек, которые используют изменяемую ссылку:

-

Файл: src/main.rs

-
fn main() {
-    let mut s = String::from("hello");
-
-    change(&mut s);
-}
-
-fn change(some_string: &mut String) {
-    some_string.push_str(", world");
-}
-

Сначала мы меняем s на mut. Затем мы создаём изменяемую ссылку с помощью &mut s, у которой вызываем change и обновляем ярлык функции, чтобы принять изменяемую ссылку с помощью some_string: &mut String. Это даёт понять, что change изменит значение, которое заимствует.

-

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

-

Файл: src/main.rs

-
fn main() {
-    let mut s = String::from("hello");
-
-    let r1 = &mut s;
-    let r2 = &mut s;
-
-    println!("{}, {}", r1, r2);
-}
-

Описание ошибки:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0499]: cannot borrow `s` as mutable more than once at a time
- --> src/main.rs:5:14
-  |
-4 |     let r1 = &mut s;
-  |              ------ first mutable borrow occurs here
-5 |     let r2 = &mut s;
-  |              ^^^^^^ second mutable borrow occurs here
-6 |
-7 |     println!("{}, {}", r1, r2);
-  |                        -- first borrow later used here
-
-For more information about this error, try `rustc --explain E0499`.
-error: could not compile `ownership` (bin "ownership") due to 1 previous error
-
-

Эта ошибка говорит о том, что код недействителен, потому что мы не можем заимствовать s как изменяемые более одного раза в один мгновение. Первое изменяемое заимствование находится в r1 и должно длиться до тех пор, пока оно не будет использовано в println!, но между созданием этой изменяемой ссылки и её использованием мы попытались создать другую изменяемую ссылку в r2, которая заимствует те же данные, что и r1.

-

Ограничение, предотвращающее одновременное использование нескольких изменяемых ссылок на одни и те же данные, допускает изменение, но очень управляющим образом. Это то, с чем борются новые Rustaceans, потому что большинство языков позволяют изменять значение в любой мгновение. Преимущество этого ограничения заключается в том, что Ржавчина может предотвратить гонку данных во время сборки. Гонка данных похожа на состояние гонки и происходит, когда возникают следующие три сценария:

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

Гонки данных вызывают неопределённое поведение, и их может быть сложно диагностировать и исправить, когда вы пытаетесь отследить их во время выполнения. Ржавчина предотвращает такую неполадку, отказываясь собирать код с гонками данных!

-

Как всегда, мы можем использовать фигурные скобки для создания новой области видимости, позволяющей использовать несколько изменяемых ссылок, но не одновременно:

-
fn main() {
-    let mut s = String::from("hello");
-
-    {
-        let r1 = &mut s;
-    } // r1 goes out of scope here, so we can make a new reference with no problems.
-
-    let r2 = &mut s;
-}
-

Rust применяет подобное правило для соединения изменяемых и неизменяемых ссылок. Этот код приводит к ошибке:

-
fn main() {
-    let mut s = String::from("hello");
-
-    let r1 = &s; // no problem
-    let r2 = &s; // no problem
-    let r3 = &mut s; // BIG PROBLEM
-
-    println!("{}, {}, and {}", r1, r2, r3);
-}
-

Ошибка:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
- --> src/main.rs:6:14
-  |
-4 |     let r1 = &s; // no problem
-  |              -- immutable borrow occurs here
-5 |     let r2 = &s; // no problem
-6 |     let r3 = &mut s; // BIG PROBLEM
-  |              ^^^^^^ mutable borrow occurs here
-7 |
-8 |     println!("{}, {}, and {}", r1, r2, r3);
-  |                                -- immutable borrow later used here
-
-For more information about this error, try `rustc --explain E0502`.
-error: could not compile `ownership` (bin "ownership") due to 1 previous error
-
-

Вау! У нас также не может быть изменяемой ссылки, пока у нас есть неизменяемая ссылка на то же значение.

-

Пользователи неизменяемой ссылки не ожидают, что значение внезапно изменится из-под них! Однако разрешены множественные неизменяемые ссылки, потому что никто, кто просто читает данные, не может повлиять на чтение данных кем-либо ещё.

-

Обратите внимание, что область действия ссылки начинается с того места, где она была введена, и продолжается до последнего использования этой ссылки. Например, этот код будет собираться, потому что последнее использование неизменяемых ссылок println!, происходит до того, как вводится изменяемая ссылка:

-
fn main() {
-    let mut s = String::from("hello");
-
-    let r1 = &s; // no problem
-    let r2 = &s; // no problem
-    println!("{r1} and {r2}");
-    // variables r1 and r2 will not be used after this point
-
-    let r3 = &mut s; // no problem
-    println!("{r3}");
-}
-

Области неизменяемых ссылок r1 и r2 заканчиваются после println! где они использовались в последний раз, то есть до создания изменяемой ссылки r3. Эти области не перекрываются, поэтому этот код разрешён: сборщик может сказать, что ссылка больше не используется в точке перед концом области.

-

Несмотря на то, что ошибки заимствования могут иногда вызывать разочарование, помните, что сборщик Ржавчина заранее указывает на вероятную ошибку (во время сборки, а не во время выполнения) и точно показывает, в чем неполадка. Тогда вам не придётся выяснять, почему ваши данные оказались не такими, как вы ожидали.

-

Висячие ссылки

-

В языках с указателями весьма легко ошибочно создать недействительную (висячую) (dangling) ссылку. Ссылку указывающую на участок памяти, который мог быть передан кому-то другому, путём освобождения некоторой памяти при сохранении указателя на эту память. Ржавчина сборщик заверяет, что ссылки никогда не станут недействительными: если у вас есть ссылка на какие-то данные, сборщик обеспечит что эти данные не выйдут из области видимости прежде, чем из области видимости исчезнет ссылка.

-

Давайте попробуем создать висячую ссылку, чтобы увидеть, как Ржавчина предотвращает их появление с помощью ошибки во время сборки:

-

Файл: src/main.rs

-
fn main() {
-    let reference_to_nothing = dangle();
-}
-
-fn dangle() -> &String {
-    let s = String::from("hello");
-
-    &s
-}
-

Здесь ошибка:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0106]: missing lifetime specifier
- --> src/main.rs:5:16
-  |
-5 | fn dangle() -> &String {
-  |                ^ expected named lifetime parameter
-  |
-  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
-help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
-  |
-5 | fn dangle() -> &'static String {
-  |                 +++++++
-help: instead, you are more likely to want to return an owned value
-  |
-5 - fn dangle() -> &String {
-5 + fn dangle() -> String {
-  |
-
-error[E0515]: cannot return reference to local variable `s`
- --> src/main.rs:8:5
-  |
-8 |     &s
-  |     ^^ returns a reference to data owned by the current function
-
-Some errors have detailed explanations: E0106, E0515.
-For more information about an error, try `rustc --explain E0106`.
-error: could not compile `ownership` (bin "ownership") due to 2 previous errors
-
-

Это сообщение об ошибке относится к особенности языка, которую мы ещё не рассмотрели: времени жизни. Мы подробно обсудим времена жизни в главе 10. Но если вы не обращаете внимания на части, касающиеся времени жизни, сообщение будет содержать ключ к тому, почему этот код является неполадкой:

-
this function's return type contains a borrowed value, but there is no value
-for it to be borrowed from
-
-

Давайте подробнее рассмотрим, что именно происходит на каждом этапе нашего кода dangle:

-

Файл: src/main.rs

-
fn main() {
-    let reference_to_nothing = dangle();
-}
-
-fn dangle() -> &String { // dangle returns a reference to a String
-
-    let s = String::from("hello"); // s is a new String
-
-    &s // we return a reference to the String, s
-} // Here, s goes out of scope, and is dropped. Its memory goes away.
-  // Danger!
-

Поскольку s создаётся внутри dangle, когда код dangle будет завершён, s будет освобождена. Но мы попытались вернуть ссылку на неё. Это означает, что эта ссылка будет указывать на недопустимую String. Это нехорошо! Ржавчина не позволит нам сделать это.

-

Решением будет вернуть непосредственно String:

-
fn main() {
-    let string = no_dangle();
-}
-
-fn no_dangle() -> String {
-    let s = String::from("hello");
-
-    s
-}
-

Это работает без неполадок. Владение перемещено, и ничего не освобождено.

-

Правила работы с ссылками

-

Давайте повторим все, что мы обсудили про ссылки:

-
    -
  • В любой мгновение времени у вас может быть одна (но не обе) изменяемая ссылка или любое количество неизменяемых ссылок.
  • -
  • Все ссылки должны быть действительными.
  • -
-

В следующей главе мы рассмотрим другой вид ссылок — срезы.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch04-03-slices.html b/rustbook-ru/book/ch04-03-slices.html deleted file mode 100644 index bfcaf3cda..000000000 --- a/rustbook-ru/book/ch04-03-slices.html +++ /dev/null @@ -1,529 +0,0 @@ - - - - - - Вид среза - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Вид срезы

-

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

-

Вот небольшая неполадка программирования: напишите функцию, которая принимает строку слов, разделённых пробелами, и возвращает первое слово, которое она находит в этой строке. Если функция не находит пробела в строке, вся строка должна состоять из одного слова, поэтому должна быть возвращена вся строка.

-

Давайте рассмотрим, как бы мы написали ярлык этой функции без использования срезов, чтобы понять неполадку, которую решат срезы:

-
fn first_word(s: &String) -> ?
-

Функция first_word имеет &String в качестве свойства. Мы не хотим владения, так что всё в порядке. Но что мы должны вернуть? На самом деле у нас нет способа говорить о части строки. Однако мы могли бы вернуть порядковый указательконца слова, обозначенного пробелом. Давайте попробуем, как показано в Приложении 4-7.

-

Файл: src/main.rs

-
fn first_word(s: &String) -> usize {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return i;
-        }
-    }
-
-    s.len()
-}
-
-fn main() {}
-

Приложение 4-7. Функция first_word, возвращающая значение порядкового указателя байта в свойство String

-

Поскольку нам нужно просмотреть String поэлементно и проверить, является ли значение пробелом, мы преобразуем нашу String в массив байтов с помощью способа as_bytes.

-
fn first_word(s: &String) -> usize {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return i;
-        }
-    }
-
-    s.len()
-}
-
-fn main() {}
-

Далее, мы создаём повторитель по массиву байт используя способ iter:

-
fn first_word(s: &String) -> usize {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return i;
-        }
-    }
-
-    s.len()
-}
-
-fn main() {}
-

Мы обсудим повторители более подробно в Главе 13. На данный мгновение знайте, что iter — это способ, который возвращает каждый элемент в собрания, а enumerate оборачивает итог iter и вместо этого возвращает каждый элемент как часть упорядоченного ряда. Первый элемент упорядоченного ряда, возвращаемый из enumerate, является порядковым указателем, а второй элемент — ссылкой на элемент. Это немного удобнее, чем вычислять порядковый указательсамостоятельно.

-

Поскольку способ enumerate возвращает упорядоченный ряд, мы можем использовать образцы для разъединения этого упорядоченного ряда. Мы подробнее обсудим образцы в Главе 6.. В цикле for мы указываем образец, имеющий i для порядкового указателя в упорядоченном ряде и &item для одного байта в упорядоченном ряде. Поскольку мы получаем ссылку на элемент из .iter().enumerate(), мы используем & в образце.

-

Внутри цикла for мы ищем байт, представляющий пробел, используя правила написания байтового записи. Если мы находим пробел, мы возвращаем положение. В противном случае мы возвращаем длину строки с помощью s.len().

-
fn first_word(s: &String) -> usize {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return i;
-        }
-    }
-
-    s.len()
-}
-
-fn main() {}
-

Теперь у нас есть способ узнать порядковый указательбайта указывающего на конец первого слова в строке, но есть неполадка. Мы возвращаем сам usize, но это число имеет значение только в среде &String. Другими словами, поскольку это значение отдельное от String, то нет заверения, что оно все ещё будет действительным в будущем. Рассмотрим программу из приложения 4-8, которая использует функцию first_word приложения 4-7.

-

Файл: src/main.rs

-
fn first_word(s: &String) -> usize {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return i;
-        }
-    }
-
-    s.len()
-}
-
-fn main() {
-    let mut s = String::from("hello world");
-
-    let word = first_word(&s); // word will get the value 5
-
-    s.clear(); // this empties the String, making it equal to ""
-
-    // word still has the value 5 here, but there's no more string that
-    // we could meaningfully use the value 5 with. word is now totally invalid!
-}
-

Приложение 4-8. Сохранение итога вызова функции first_word и последующего изменения содержимого String

-

Данная программа собирается без ошибок и будет успешно работать, даже после того как мы воспользуемся переменной word после вызова s.clear(). Так как значение word совсем не связано с состоянием переменной s, то word сохраняет своё значение 5 без изменений. Мы бы могли воспользоваться значением 5 чтобы получить первое слово из переменной s, но это приведёт к ошибке, потому что содержимое s изменилось после того как мы сохранили 5 в переменной word (стало пустой строкой в вызове s.clear()).

-

Необходимость беспокоиться о том, что порядковый указательв переменной word не согласуется с данными в переменной s является утомительной и подверженной ошибкам! Управление этими порядковыми указателями становится ещё более хрупким, если мы напишем функцию second_word. Её ярлык могла бы выглядеть так:

-
fn second_word(s: &String) -> (usize, usize) {
-

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

-

К счастью в Ржавчина есть решение данной сбоев: строковые срезы.

-

Строковые срезы

-

Строковый срез - это ссылка на часть строки String и он выглядит следующим образом:

-
fn main() {
-    let s = String::from("hello world");
-
-    let hello = &s[0..5];
-    let world = &s[6..11];
-}
-

Вместо ссылки на всю String hello является ссылкой на часть String, указанную в дополнительном куске кода [0..5]. Мы создаём срезы, используя рядв квадратных скобках, указав [starting_index..ending_index], где starting_index — это первая позиция, аending_index конечный_порядковый указатель— это на единицу больше, чем последняя позиция в срезе. Внутри устройства данных среза хранит начальную положение и длину среза, что соответствует ending_index - starting_index. Итак, в случае let world = &s[6..11];, world будет срезом, содержащим указатель на байт с порядковым указателем 6 s со значением длины 5.

-

Рисунок 4-6 отображает это на диаграмме.

- world containing a pointer to the 6th byte of String s and a length 5 -

Рисунок 4-6: Строковый срез ссылается на часть String

-

С правилами написания Ржавчина .., если вы хотите начать с порядкового указателя 0, вы можете отбросить значение перед двумя точками. Другими словами, они равны:

-
#![allow(unused)]
-fn main() {
-let s = String::from("hello");
-
-let slice = &s[0..2];
-let slice = &s[..2];
-}
-

Таким же образом, если ваш срез включает последний байт String, вы можете отбросить конечный номер. Это означает, что они равны:

-
#![allow(unused)]
-fn main() {
-let s = String::from("hello");
-
-let len = s.len();
-
-let slice = &s[3..len];
-let slice = &s[3..];
-}
-

Вы также можете отбросить оба значения, чтобы получить часть всей строки. Итак, они равны:

-
#![allow(unused)]
-fn main() {
-let s = String::from("hello");
-
-let len = s.len();
-
-let slice = &s[0..len];
-let slice = &s[..];
-}
-
-

Примечание. Порядковые указатели ряда срезов строк должны располагаться на допустимых границах символов UTF-8. Если вы попытаетесь создать отрывок строки нарушая границы символа в котором больше одного байта, ваша программа завершится с ошибкой. В целях введения срезов строк мы предполагаем, что в этом разделе используется только ASCII; более подробное обсуждение обработки UTF-8 находится в разделе «Сохранение закодированного текста UTF-8 со строками». раздел главы 8.

-
-

Давайте используем полученную сведения и перепишем способ first_word так, чтобы он возвращал срез. Для обозначения вида "срез строки" существует запись &str:

-

Файл: src/main.rs

-
fn first_word(s: &String) -> &str {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return &s[0..i];
-        }
-    }
-
-    &s[..]
-}
-
-fn main() {}
-

Мы получаем порядковый указательконца слова так же, как в приложении 4.7, ища первое вхождение пробела. Когда мы находим пробел, мы возвращаем отрывок строки, используя начало строки и порядковый указательпробела в качестве начального и конечного порядковых указателей.

-

Теперь, когда мы вызываем first_word, мы возвращаем одно значение, привязанное к основным данным. Значение состоит из ссылки на начальную точку среза и количества элементов в срезе.

-

Подобным образом можно переписать и второй способ second_word:

-
fn second_word(s: &String) -> &str {
-

Теперь у нас есть простой API, который гораздо сложнее испортить, потому что сборщик заверяет, что ссылки в String останутся действительными. Помните ошибку в программе в приложении 4-8, когда мы получили порядковый указательдо конца первого слова, но затем очиисполнения строку, так что наш порядковый указательстал недействительным? Этот код был логически неправильным, но не показывал немедленных ошибок. Неполадки проявятся позже, если мы попытаемся использовать порядковый указательпервого слова с пустой строкой. Срезы делают эту ошибку невозможной и сообщают нам о неполадке с нашим кодом гораздо раньше. Так, использование исполнения способа first_word со срезом вернёт ошибку сборки:

-

Файл: src/main.rs

-
fn first_word(s: &String) -> &str {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return &s[0..i];
-        }
-    }
-
-    &s[..]
-}
-
-fn main() {
-    let mut s = String::from("hello world");
-
-    let word = first_word(&s);
-
-    s.clear(); // error!
-
-    println!("the first word is: {word}");
-}
-

Ошибка сборки:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
-  --> src/main.rs:18:5
-   |
-16 |     let word = first_word(&s);
-   |                           -- immutable borrow occurs here
-17 |
-18 |     s.clear(); // error!
-   |     ^^^^^^^^^ mutable borrow occurs here
-19 |
-20 |     println!("the first word is: {word}");
-   |                                  ------ immutable borrow later used here
-
-For more information about this error, try `rustc --explain E0502`.
-error: could not compile `ownership` (bin "ownership") due to 1 previous error
-
-

Напомним из правил заимствования, что если у нас есть неизменяемая ссылка на что-то, мы не можем также взять изменяемую ссылку. Поскольку для clear необходимо обрезать String, необходимо получить изменяемую ссылку. println! после вызова clear использует ссылку в word, поэтому неизменяемая ссылка в этот мгновение всё ещё должна быть активной. Ржавчина запрещает одновременное существование изменяемой ссылки в видеclear и неизменяемой ссылки в word, и сборка завершается ошибкой. Ржавчина не только упростил использование нашего API, но и устранил целый класс ошибок во время сборки!

- -

-

Строковые записи - это срезы

-

Напомним, что мы говорили о строковых записях, хранящихся внутри двоичного файла. Теперь, когда мы знаем чем являются срезы, мы правильно понимаем что такое строковые записи:

-
#![allow(unused)]
-fn main() {
-let s = "Hello, world!";
-}
-

Вид s здесь &str: это срез, указывающий на эту определенную точку двоичного файла. Вот почему строковые записи неизменяемы; &str — неизменяемая ссылка.

-

Строковые срезы как свойства

-

Знание того, что вы можете брать срезы записей и String значений, приводит нас к ещё одному улучшению first_word, и это его ярлык:

-
fn first_word(s: &String) -> &str {
-

Более опытный пользователь Rustacean вместо этого написал бы ярлык, показанную в приложении 4.9, потому что это позволяет нам использовать одну и ту же функцию как для значений &String, так и для значений &str.

-
fn first_word(s: &str) -> &str {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return &s[0..i];
-        }
-    }
-
-    &s[..]
-}
-
-fn main() {
-    let my_string = String::from("hello world");
-
-    // `first_word` works on slices of `String`s, whether partial or whole
-    let word = first_word(&my_string[0..6]);
-    let word = first_word(&my_string[..]);
-    // `first_word` also works on references to `String`s, which are equivalent
-    // to whole slices of `String`s
-    let word = first_word(&my_string);
-
-    let my_string_literal = "hello world";
-
-    // `first_word` works on slices of string literals, whether partial or whole
-    let word = first_word(&my_string_literal[0..6]);
-    let word = first_word(&my_string_literal[..]);
-
-    // Because string literals *are* string slices already,
-    // this works too, without the slice syntax!
-    let word = first_word(my_string_literal);
-}
-

Приложение 4-9: Улучшение функции first_word используя вид строкового среза для свойства s

-

Если у нас есть отрывок строки, мы можем передать его напрямую. Если у нас есть String, мы можем передать часть String или ссылку на String. Эта гибкость использует преимущества приведения deref, функции, которую мы рассмотрим в разделе «Неявное приведение Deref с функциями и способами». раздел главы 15.

-

Определение функции для получения отрывка строки вместо ссылки на String делает наш API более общим и полезным без потери какой-либо возможности:

-

Файл: src/main.rs

-
fn first_word(s: &str) -> &str {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return &s[0..i];
-        }
-    }
-
-    &s[..]
-}
-
-fn main() {
-    let my_string = String::from("hello world");
-
-    // `first_word` works on slices of `String`s, whether partial or whole
-    let word = first_word(&my_string[0..6]);
-    let word = first_word(&my_string[..]);
-    // `first_word` also works on references to `String`s, which are equivalent
-    // to whole slices of `String`s
-    let word = first_word(&my_string);
-
-    let my_string_literal = "hello world";
-
-    // `first_word` works on slices of string literals, whether partial or whole
-    let word = first_word(&my_string_literal[0..6]);
-    let word = first_word(&my_string_literal[..]);
-
-    // Because string literals *are* string slices already,
-    // this works too, without the slice syntax!
-    let word = first_word(my_string_literal);
-}
-

Другие срезы

-

Срезы строк, как вы можете себе представить, отличительны для строк. Но есть и более общий вид среза. Рассмотрим этот массив:

-
#![allow(unused)]
-fn main() {
-let a = [1, 2, 3, 4, 5];
-}
-

Точно так же, как мы можем захотеть сослаться на часть строки, мы можем захотеть сослаться на часть массива. Мы бы сделали так:

-
#![allow(unused)]
-fn main() {
-let a = [1, 2, 3, 4, 5];
-
-let slice = &a[1..3];
-
-assert_eq!(slice, &[2, 3]);
-}
-

Этот срез имеет вид &[i32]. Он работает так же, как и срезы строк, сохраняя ссылку на первый элемент и его длину. Вы будете использовать этот вид отрывка для всех видов других собраний. Мы подробно обсудим эти собрания, когда будем говорить о векторах в главе 8.

-

Итоги

-

Подходы владения, заимствования и срезов обеспечивают безопасность памяти в программах на Ржавчина во время сборки. Язык Ржавчина даёт вам управление над использованием памяти так же, как и другие языки системного программирования, но то, что владелец данных самостоятельно очищает эти данные, когда владелец выходит за рамки, означает, что вам не нужно писать и отлаживать дополнительный код, чтобы получить этот управление.

-

Владение влияет на множество других частей и подходов языка Rust. Мы будем говорить об этих подходах на протяжении оставшихся частей книги. Давайте перейдём к Главе 5 и рассмотрим объединение частей данных в устройства struct.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch05-00-structs.html b/rustbook-ru/book/ch05-00-structs.html deleted file mode 100644 index 82168c934..000000000 --- a/rustbook-ru/book/ch05-00-structs.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - - Использование устройств для объединения связанных данных - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Использование устройств для внутреннего выстраивания

-

связанных данных

-

Устройства (struct) — это пользовательский вид данных, позволяющий назвать и упаковать вместе несколько связанных значений, составляющих значимую логическую объединение. Если вы знакомы с предметно-направленными языками, устройства похожа на свойства данных предмета. В этой главе мы сравним и сопоставим упорядоченные ряды со устройствами, чтобы опираться на то, что вы уже знаете, и отобразим, когда устройства являются лучшим способом объединения данных.

-

Мы отобразим, как определять устройства и создавать их образцы. Мы обсудим, как определить сопряженные функции, особенно сопряженные функции, называемые способами, для указания поведения, сопряженного с видом устройства. Устройства и перечисления (обсуждаемые в главе 6) являются строительными разделами для создания новых видов в предметной области вашей программы. Они дают возможность в полной мере воспользоваться преимуществами проверки видов во время сборки Rust.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch05-01-defining-structs.html b/rustbook-ru/book/ch05-01-defining-structs.html deleted file mode 100644 index cadba884c..000000000 --- a/rustbook-ru/book/ch05-01-defining-structs.html +++ /dev/null @@ -1,476 +0,0 @@ - - - - - - Определение и создание образцов устройств - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Определение и объявление устройств

-

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

-

Для определения устройства указывается ключевое слово struct и её название. Название должно описывать значение частей данных, объединенных вместе. Далее, в фигурных скобках для каждой новой части данных поочерёдно определяются имя части данных и её вид. Каждая пара имя: тип называется полем. Приложение 5-1 описывает устройство для хранения сведений об учётной записи пользователя:

-

Имя файла: src/main.rs

-
struct User {
-    active: bool,
-    username: String,
-    email: String,
-    sign_in_count: u64,
-}
-
-fn main() {}
-

Приложение 5-1: Определение устройства User

-

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

-

Файл: src/main.rs

-
struct User {
-    active: bool,
-    username: String,
-    email: String,
-    sign_in_count: u64,
-}
-
-fn main() {
-    let user1 = User {
-        active: true,
-        username: String::from("someusername123"),
-        email: String::from("someone@example.com"),
-        sign_in_count: 1,
-    };
-}
-

Приложение 5-2: Создание образца устройства User

-

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

-

Файл: src/main.rs

-
struct User {
-    active: bool,
-    username: String,
-    email: String,
-    sign_in_count: u64,
-}
-
-fn main() {
-    let mut user1 = User {
-        active: true,
-        username: String::from("someusername123"),
-        email: String::from("someone@example.com"),
-        sign_in_count: 1,
-    };
-
-    user1.email = String::from("anotheremail@example.com");
-}
-

Приложение 5-3: Изменение значения в поле email образца User

-

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

-

На приложении 5-4 функция build_user возвращает образец User с указанным адресом и именем. Поле active получает значение true, а поле sign_in_count получает значение 1.

-

Файл: src/main.rs

-
struct User {
-    active: bool,
-    username: String,
-    email: String,
-    sign_in_count: u64,
-}
-
-fn build_user(email: String, username: String) -> User {
-    User {
-        active: true,
-        username: username,
-        email: email,
-        sign_in_count: 1,
-    }
-}
-
-fn main() {
-    let user1 = build_user(
-        String::from("someone@example.com"),
-        String::from("someusername123"),
-    );
-}
-

Приложение 5-4: Функция build_user, которая принимает email и имя пользователя и возвращает образец User

-

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

- -

-

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

-

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

-

Файл: src/main.rs

-
struct User {
-    active: bool,
-    username: String,
-    email: String,
-    sign_in_count: u64,
-}
-
-fn build_user(email: String, username: String) -> User {
-    User {
-        active: true,
-        username,
-        email,
-        sign_in_count: 1,
-    }
-}
-
-fn main() {
-    let user1 = build_user(
-        String::from("someone@example.com"),
-        String::from("someusername123"),
-    );
-}
-

Приложение 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.

-

Файл: src/main.rs

-
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-6: Создание нового образца User с использованием некоторых значений из образца user1

-

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

-

Файл: src/main.rs

-
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: Использование правил написания обновления устройства для установки нового значения 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:

-

Файл: 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:

-

Файл: src/main.rs

-
struct AlwaysEqual;
-
-fn main() {
-    let subject = AlwaysEqual;
-}
-

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

-
-

Владение данными устройства

-

В определении устройства User в приложении 5-1 мы использовали владеющий вид String вместо вида строковый срез &str. Это осознанный выбор, поскольку мы хотим, чтобы каждый образец этой устройства владел всеми своими данными и чтобы эти данные были действительны до тех пор, пока действительна вся устройства.

-

Устройства также могут хранить ссылки на данные, принадлежащие кому-то другому, но для этого необходимо использовать возможность Ржавчина время жизни, которую мы обсудим в главе 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: "someone@example.com",
-        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
-
-

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

-
- - -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch05-02-example-structs.html b/rustbook-ru/book/ch05-02-example-structs.html deleted file mode 100644 index 40b17b1ff..000000000 --- a/rustbook-ru/book/ch05-02-example-structs.html +++ /dev/null @@ -1,422 +0,0 @@ - - - - - - Пример программы, использующей устройства - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

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

-

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

-

Давайте создадим новый дело программы при помощи Cargo и назовём его rectangles. Наша программа будет получать на вход длину и ширину прямоугольника в пикселях и затем рассчитывать площадь прямоугольника. Приложение 5-8 показывает один из коротких исходов кода, который позволит нам сделать именно то, что надо, в файле дела src/main.rs.

-

Файл: src/main.rs

-
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
-}
-

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

-

Теперь запустим программу, используя cargo run:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s
-     Running `target/debug/rectangles`
-The area of the rectangle is 1500 square pixels.
-
-

Этот код успешно вычисляет площадь прямоугольника, вызывая функцию 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
-}
-

Функция area должна вычислять площадь одного прямоугольника, но функция, которую мы написали, имеет два свойства, и нигде в нашей программе не ясно, что эти свойства взаимосвязаны. Было бы более читабельным и управляемым собъединять ширину и высоту вместе. В разделе «Упорядоченные ряды» главы 3 мы уже обсуждали один из способов сделать это — использовать упорядоченные ряды.

-

Переработка кода при помощи упорядоченных рядов

-

Приложение 5-9 — это другая исполнение программы, использующая упорядоченные ряды.

-

Файл: 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-9: определение ширины и высоты прямоугольника с помощью упорядоченного ряда

-

С одной стороны, эта программа лучше. Упорядоченные ряды позволяют добавить немного устройства, и теперь мы передаём только один переменная. Но с другой стороны, эта исполнение менее понятна: упорядоченные ряды не называют свои элементы, поэтому нам приходится упорядочивать части упорядоченного ряда, что делает наше вычисление менее очевидным.

-

Если мы перепутаем местами ширину с высотой при расчёте площади, то это не имеет значения. Но если мы хотим нарисовать прямоугольник на экране, то это уже будет важно! Мы должны помнить, что ширина width находится в упорядоченном ряде с порядковым указателем 0, а высота height — с порядковым указателем 1. Если кто-то другой поработал бы с кодом, ему бы пришлось разобраться в этом и также помнить про порядок. Легко забыть и перепутать эти значения — и это вызовет ошибки, потому что данный код не передаёт наши намерения.

-

Переработка кода при помощи устройств: добавим больше смысла

-

Мы используем устройства, чтобы добавить смысл данным при помощи назначения им осмысленных имён . Мы можем переделать используемый упорядоченный ряд в устройство с единым именем для сущности и частными названиями её частей, как показано в приложении 5-10.

-

Файл: src/main.rs

-
struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-
-    println!(
-        "The area of the rectangle is {} square pixels.",
-        area(&rect1)
-    );
-}
-
-fn area(rectangle: &Rectangle) -> u32 {
-    rectangle.width * rectangle.height
-}
-

Приложение 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. Это торжество ясности.

-

Добавление полезной возможности при помощи выводимых особенностей

-

Было бы полезно иметь возможность печатать образец Rectangle во время отладки программы и видеть значения всех полей. Приложение 5-11 использует макрос println!, который мы уже использовали в предыдущих главах. Тем не менее, это не работает.

-

Файл: src/main.rs

-
struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-
-    println!("rect1 is {}", rect1);
-}
-

Приложение 5-11: Попытка вывести значения образца Rectangle

-

При сборки этого кода мы получаем ошибку с сообщением:

-
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
-
-

Макрос println! умеет выполнять множество видов изменения, и по умолчанию фигурные скобки в println! означают использование изменение -, известное как особенность Display. Его вывод предназначен для непосредственного использования конечным пользователем. Простые виды, изученные ранее, по умолчанию выполняют особенность Display, потому что есть только один способ отобразить число 1 или любой другой простой вид. Но для устройств изменение -println! менее очевидно, потому что есть гораздо больше способов отображения: Вы хотите запятые или нет? Вы хотите печатать фигурные скобки? Должны ли отображаться все поля? Из-за этой неоднозначности Ржавчина не пытается угадать, что нам нужно, а устройства не имеют встроенной выполнения Display для использования в println! с заполнителем {}.

-

Продолжив чтение текста ошибки, мы найдём полезное замечание:

-
   = help: the trait `std::fmt::Display` is not implemented for `Rectangle`
-   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
-
-

Давайте попробуем! Вызов макроса println! теперь будет выглядеть так println!("rect1 is {:?}", rect1);. Ввод определетеля :? внутри фигурных скобок говорит макросу println!, что мы хотим использовать другой вид вывода, известный как Debug. Особенность Debug позволяет печатать устройство способом, удобным для разработчиков, чтобы видеть значение во время отладки кода.

-

Соберем код с этими изменениями. Упс! Мы всё ещё получаем ошибку:

-
error[E0277]: `Rectangle` doesn't implement `Debug`
-
-

Снова сборщик даёт нам полезное замечание:

-
   = help: the trait `Debug` is not implemented for `Rectangle`
-   = note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
-
-

Rust выполняет возможность для печати отладочной сведений, но не включает (не выводит) её по умолчанию. Мы должны явно включить эту возможность для нашей устройства. Чтобы это сделать, добавляем внешний свойство #[derive(Debug)] сразу перед определением устройства, как показано в приложении 5-12.

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-
-    println!("rect1 is {rect1:?}");
-}
-

Приложение 5-12: добавление свойства для вывода особенности Debug и печати образца Rectangle с отладочным изменением -

-

Теперь при запуске программы мы не получим ошибок и увидим следующий вывод:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
-     Running `target/debug/rectangles`
-rect1 is Rectangle { width: 30, height: 50 }
-
-

Отлично! Это не самый красивый вывод, но он показывает значения всех полей образца, которые определённо помогут при отладке. Когда у нас более крупные устройства, то полезно иметь более простой для чтения вывод; в таких случаях можно использовать код {:#?} вместо {:?} в строке макроса println!. В этом примере использование исполнения {:#?} приведёт к такому выводу:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
-     Running `target/debug/rectangles`
-rect1 is Rectangle {
-    width: 30,
-    height: 50,
-}
-
-

Другой способ распечатать значение в видеDebug — использовать макрос dbg!, который становится владельцем выражения (в отличие от println!, принимающего ссылку), печатает номер файла и строки, где происходит вызов макроса dbg!, вместе с результирующим значением этого выражения и возвращает владение на значение.

-
-

Примечание: при вызове макроса dbg! выполняется печать в обычный поток ошибок (stderr), в отличие от println!, который использует обычный поток вывода в окно вывода (stdout). Подробнее о stderr и stdout мы поговорим в разделе «Запись сообщений об ошибках в обычный вывод ошибок вместо принятого вывода» главы 12.

-
-

Вот пример, когда нас важно значение, которое присваивается полю width, а также значение всей устройства в rect1:

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let scale = 2;
-    let rect1 = Rectangle {
-        width: dbg!(30 * scale),
-        height: 50,
-    };
-
-    dbg!(&rect1);
-}
-

Можем написать макрос dbg! вокруг выражения 30 * scale, потому что dbg! возвращает владение значения выражения. Поле width получит то же значение, как если бы у нас не было вызова dbg!. Мы не хотим, чтобы макрос dbg! становился владельцем rect1, поэтому используем ссылку на rect1 в следующем вызове. Вот как выглядит вывод этого примера:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
-     Running `target/debug/rectangles`
-[src/main.rs:10:16] 30 * scale = 60
-[src/main.rs:14:5] &rect1 = Rectangle {
-    width: 60,
-    height: 50,
-}
-
-

Мы можем увидеть, что первый отладочный вывод поступил из строки 10 src/main.rs, там, где мы отлаживаем выражение 30 * scale, и его результирующее значение равно 60 (Debug изменение -, выполненное для целых чисел, заключается в печати только их значения). Вызов dbg! в строке 14 src/main.rs выводит значение &rect1, которое является устройством Rectangle. В этом выводе используется красивое изменение -Debug вида Rectangle. Макрос dbg! может быть очень полезен, когда вы пытаетесь понять, что делает ваш код!

-

В дополнение к Debug, Ржавчина предоставил нам ряд особенностей, которые мы можем использовать с свойством derive для добавления полезного поведения к нашим пользовательским видам. Эти особенности и их поведение перечислены в приложении C. Мы расскажем, как выполнить эти особенности с пользовательским поведением, а также как создать свои собственные особенности в главе 10. Кроме того, есть много других свойств помимо derive; для получения дополнительной сведений смотрите раздел “Свойства” справочника Rust.

-

Функция area является довольно отличительной: она считает только площадь прямоугольников. Было бы полезно привязать данное поведение как можно ближе к устройстве Rectangle, потому что наш отличительный код не будет работать с любым другим видом. Давайте рассмотрим, как можно улучшить наш код превращая функцию area в способ area, определённый для вида Rectangle.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch05-03-method-syntax.html b/rustbook-ru/book/ch05-03-method-syntax.html deleted file mode 100644 index 9f6d2aeca..000000000 --- a/rustbook-ru/book/ch05-03-method-syntax.html +++ /dev/null @@ -1,446 +0,0 @@ - - - - - - правила написания способа - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

правила написания способа

-

Способы похожи на функции: мы объявляем их с помощью ключевого слова fn и имени, они могут иметь свойства и возвращаемое значение, и они содержат код, запускающийся в случае вызова способа. В отличие от функций, способы определяются в среде устройства (или предмета перечисления или особенности, которые мы рассмотрим в главе 6) и главе 17 соответственно), а их первым свойствоом всегда является self, представляющий собой образец устройства, с которой вызывается этот способ.

-

Определение способов

-

Давайте изменим функцию area так, чтобы она имела образец Rectangle в качестве входного свойства и сделаем её способом area, определённым для устройства Rectangle, как показано в приложении 5-13:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn area(&self) -> u32 {
-        self.width * self.height
-    }
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-
-    println!(
-        "The area of the rectangle is {} square pixels.",
-        rect1.area()
-    );
-}
-

Приложение 5-13: Определение способа area для устройства Rectangle

-

Чтобы определить функцию в среде Rectangle, мы создаём разделimpl (implementation - выполнение) для Rectangle. Всё в impl будет связано с видом Rectangle. Затем мы перемещаем функцию area внутрь фигурных скобок impl и меняем первый (и в данном случае единственный) свойство на self в ярлыке и в теле. В main, где мы вызвали функцию area и передали rect1 в качестве переменной, теперь мы можем использовать правила написания способа для вызова способа area нашего образца Rectangle. правила написания способа идёт после образца: мы добавляем точку, за которой следует имя способа, круглые скобки и любые переменные.

-

В ярлыке area мы используем &self вместо rectangle: &Rectangle. &self на самом деле является сокращением от self: &Self. Внутри раздела impl вид Self является псевдонимом вида, для которого выполнен разделimpl. Способы обязаны иметь свойство с именем self вида Self, поэтому Ржавчина позволяет вам сокращать его, используя только имя self на месте первого свойства. Обратите внимание, что нам по-прежнему нужно использовать & перед сокращением self, чтобы указать на то, что этот способ заимствует образец Self, точно так же, как мы делали это в rectangle: &Rectangle. Как и любой другой свойство, способы могут брать во владение self, заимствовать неизменяемый self, как мы поступили в данном случае, или заимствовать изменяемый self.

-

Мы выбрали &self здесь по той же причине, по которой использовали &Rectangle в исполнения кода с функцией: мы не хотим брать устройство во владение, мы просто хотим прочитать данные в устройстве, а не писать в неё. Если бы мы хотели изменить образец, на котором мы вызывали способ силами самого способа, то мы бы использовали &mut self в качестве первого свойства. Наличие способа, который берёт образец во владение, используя только self в качестве первого свойства, является редким; эта техника обычно используется, когда способ превращает self во что-то ещё, и вы хотите запретить вызывающей стороне использовать исходный образец после превращения.

-

Основная причина использования способов вместо функций, помимо правил написания способа, где нет необходимости повторять вид self в ярлыке каждого способа, заключается в согласования кода. Мы помеисполнения все, что мы можем сделать с образцом вида, в один impl вместо того, чтобы заставлять будущих пользователей нашего кода искать доступный возможности Rectangle в разных местах предоставляемой нами библиотеки.

-

Обратите внимание, что мы можем дать способу то же имя, что и одному из полей устройства. Например, для Rectangle мы можем определить способ, также названный width:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn width(&self) -> bool {
-        self.width > 0
-    }
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-
-    if rect1.width() {
-        println!("The rectangle has a nonzero width; it is {}", rect1.width);
-    }
-}
-

Здесь мы определили, чтобы способ width возвращал значение true, если значение в поле width образца больше 0, и значение false, если значение равно 0, но мы можем использовать поле в способе с тем же именем для любых целей. В main, когда мы ставим после rect1.width круглые скобки, Ржавчина знает, что мы имеем в виду способ width. Когда мы не используем круглые скобки, Ржавчина понимает, что мы имеем в виду поле width.

-

Часто, но не всегда, когда мы создаём способы с тем же именем, что и у поля, мы хотим, чтобы он только возвращал значение одноимённого поля и больше ничего не делал. Подобные способы называются геттерами, и Ржавчина не выполняет их самостоятельно для полей устройства, как это делают некоторые другие языки. Геттеры полезны, поскольку вы можете сделать поле закрытым, а способ открытым и, таким образом, включить доступ только для чтения к этому полю как часть общедоступного API вида. Мы обсудим, что такое открытость и закрытость, и как обозначить поле или способ в качестве открытого или закрытого в главе 7.

-
-

Где используется оператор ->?

-

В языках C и C++, используются два различных оператора для вызова способов: используется ., если вызывается способ непосредственно у образца устройства и используется ->, если вызывается способ для указателя на предмет. Другими словами, если object является указателем, то вызовы способа object->something() и (*object).something() являются подобными.

-

Ржавчина не имеет эквивалента оператора ->, наоборот, в Ржавчина есть возможность называемая самостоятельное обращение по ссылке и разыменование (automatic referencing and dereferencing). Вызов способов является одним из немногих мест в Rust, в котором есть такое поведение.

-

Вот как это работает: когда вы вызываете способ object.something(), Ржавчина самостоятельно добавляет &, &mut или *, таким образом, чтобы object соответствовал ярлыке способа. Другими словами, это то же самое:

- -
#![allow(unused)]
-fn main() {
-#[derive(Debug,Copy,Clone)]
-struct Point {
-    x: f64,
-    y: f64,
-}
-
-impl Point {
-   fn distance(&self, other: &Point) -> f64 {
-       let x_squared = f64::powi(other.x - self.x, 2);
-       let y_squared = f64::powi(other.y - self.y, 2);
-
-       f64::sqrt(x_squared + y_squared)
-   }
-}
-let p1 = Point { x: 0.0, y: 0.0 };
-let p2 = Point { x: 5.0, y: 6.5 };
-p1.distance(&p2);
-(&p1).distance(&p2);
-}
-

Первый пример выглядит намного понятнее. Самостоятельный вывод ссылки работает потому, что способы имеют понятного получателя - вид self. Учитывая получателя и имя способа, Ржавчина может точно определить, что в данном случае делает код: читает ли способ (&self), делает ли изменение (&mut self) или поглощает (self). Тотобстоятельство, что Ржавчина делает заимствование неявным для принимающего способа, в значительной степени способствует тому, чтобы сделать владение удобным на опыте.

-
-

Способы с несколькими свойствами

-

Давайте применим в использовании способов, выполнив второй способ в устройстве Rectangle. На этот раз мы хотим, чтобы образец Rectangle брал другой образец Rectangle и возвращал true, если второй Rectangle может полностью поместиться внутри self (первый Rectangle); в противном случае он должен вернуть false. То есть, как только мы определим способ can_hold, мы хотим иметь возможность написать программу, показанную в Приложении 5-14.

-

Файл: src/main.rs

-
fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-    let rect2 = Rectangle {
-        width: 10,
-        height: 40,
-    };
-    let rect3 = Rectangle {
-        width: 60,
-        height: 45,
-    };
-
-    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
-    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
-}
-

Приложение 5-14: Использование ещё не написанного способа can_hold

-

Ожидаемый итог будет выглядеть следующим образом, т.к. оба размера в образце rect2 меньше, чем размеры в образце rect1, а rect3 шире, чем rect1:

-
Can rect1 hold rect2? true
-Can rect1 hold rect3? false
-
-

Мы знаем, что хотим определить способ, поэтому он будет находится в impl Rectangle разделе. Имя способа будет can_hold, и оно будет принимать неизменяемое заимствование на другой Rectangle в качестве свойства. Мы можем сказать, какой это будет вид свойства, посмотрев на код вызывающего способа: способ rect1.can_hold(&rect2) передаёт в него &rect2 , который является неизменяемым заимствованием образца rect2 вида Rectangle. В этом есть смысл, потому что нам нужно только читать rect2 (а не писать, что означало бы, что нужно изменяемое заимствование), и мы хотим, чтобы main сохранил право собственности на образец rect2, чтобы мы могли использовать его снова после вызов способа can_hold. Возвращаемое значение can_hold имеет булевый вид, а выполнение проверяет, являются ли ширина и высота self больше, чем ширина и высота другого Rectangle соответственно. Давайте добавим новый способ can_hold в impl разделиз приложения 5-13, как показано в приложении 5-15.

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn area(&self) -> u32 {
-        self.width * self.height
-    }
-
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width > other.width && self.height > other.height
-    }
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-    let rect2 = Rectangle {
-        width: 10,
-        height: 40,
-    };
-    let rect3 = Rectangle {
-        width: 60,
-        height: 45,
-    };
-
-    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
-    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
-}
-

Приложение 5-15: Выполнение способа can_hold для Rectangle, принимающего другой образец Rectangle в качестве свойства

-

Когда мы запустим код с функцией main приложения 5-14, мы получим желаемый вывод. Способы могут принимать несколько свойств, которые мы добавляем в ярлык после первого свойства self, и эти свойства работают так же, как свойства в функциях.

-

Сопряженные функции

-

Все функции, определённые в разделе impl, называются сопряженными функциями, потому что они сопряжены с видом, указанным после ключевого слова impl. Мы можем определить сопряженные функции, которые не имеют self в качестве первого свойства (и, следовательно, не являются способами), потому что им не нужен образец вида для работы. Мы уже использовали одну подобную функцию: функцию String::from, определённую для вида String.

-

Сопряженные функции, не являющиеся способами, часто используются для строителей, возвращающих новый образец устройства. Их часто называют new, но new не является особым именем и не встроена в язык. Например, мы можем предоставить сопряженную функцию с именем square, которая будет иметь один свойство размера и использовать его как ширину и высоту, что упростит создание квадратного Rectangle, вместо того, чтобы указывать одно и то же значение дважды:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn square(size: u32) -> Self {
-        Self {
-            width: size,
-            height: size,
-        }
-    }
-}
-
-fn main() {
-    let sq = Rectangle::square(3);
-}
-

Ключевые слова Self в возвращаемом виде и в теле функции являются псевдонимами для вида, указанного после ключевого слова impl, которым в данном случае является Rectangle.

-

Чтобы вызвать эту связанную функцию, используется правила написания :: с именем устройства; например let sq = Rectangle::square(3);. Эта функция находится в пространстве имён устройства. правила написания :: используется как для связанных функций, так и для пространств имён, созданных звенами. Мы обсудим звенья в главе 7.

-

Несколько разделов impl

-

Каждая устройства может иметь несколько impl. Например, Приложение 5-15 эквивалентен коду, показанному в приложении 5-16, в котором каждый способ находится в своём собственном разделе impl.

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn area(&self) -> u32 {
-        self.width * self.height
-    }
-}
-
-impl Rectangle {
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width > other.width && self.height > other.height
-    }
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-    let rect2 = Rectangle {
-        width: 10,
-        height: 40,
-    };
-    let rect3 = Rectangle {
-        width: 60,
-        height: 45,
-    };
-
-    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
-    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
-}
-

Приложение 5-16: Переписанный Приложения 5-15 с использованием нескольких impl

-

Здесь нет причин разделять способы на несколько impl, но это допустимый правила написания. Мы увидим случай, когда несколько impl могут оказаться полезными, в Главе 10, рассматривающей обобщённые виды и свойства.

-

Итоги

-

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

-

Но устройства — не единственный способ создавать собственные виды: давайте обратимся к перечислениям в Rust, чтобы добавить ещё один средство в свой арсенал.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch06-00-enums.html b/rustbook-ru/book/ch06-00-enums.html deleted file mode 100644 index 7d905eb11..000000000 --- a/rustbook-ru/book/ch06-00-enums.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - Перечисления и сопоставление с образцом - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Перечисления и сопоставление с образцом

-

В этой главе мы рассмотрим перечисления (enumerations), также называемые enums. Перечисления позволяют определить вид путём перечисления его возможных исходов . Сначала мы определим и используем перечисление, чтобы показать, как оно может объединить значения и данные. Далее мы рассмотрим особенно полезное перечисление под названием Option, которое выражает, что значение может быть либо чем-то, либо ничем. Затем мы рассмотрим, как сопоставление с образцом в выражении match позволяет легко запускать разный код для разных значений перечисления. Наконец, мы узнаем, насколько устройство if let удобна и кратка для обработки перечислений в вашем коде.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch06-01-defining-an-enum.html b/rustbook-ru/book/ch06-01-defining-an-enum.html deleted file mode 100644 index bd0774f43..000000000 --- a/rustbook-ru/book/ch06-01-defining-an-enum.html +++ /dev/null @@ -1,468 +0,0 @@ - - - - - - Определение Enum - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Определение перечисления

-

Там, где устройства дают вам возможность объединять связанные поля и данные, например Rectangle с его width и height, перечисления дают вам способ сказать, что значение является одним из возможных наборов значений. Например, мы можем захотеть сказать, что Rectangle — это одна из множества возможных фигур, в которую также входят Circle и Triangle. Для этого Ржавчина позволяет нам закодировать эти возможности в виде перечисления.

-

Давайте рассмотрим случай, которую мы могли бы захотеть отразить в коде, и поймём, почему перечисления полезны и более уместны, чем устройства в этом случае. Допустим, нам нужно работать с IP-адресами. В настоящее время для обозначения IP-адресов используются два основных исполнения: четвёртая и шестая исполнения. Поскольку это единственно возможные исходы IP-адресов, с которыми может столкнуться наша программа, мы можем перечислить все возможные исходы, откуда перечисление и получило своё название.

-

Любой IP-адрес может быть либо четвёртой, либо шестой исполнения, но не обеими одновременно. Эта особенность IP-адресов делает устройство данных enum подходящей, поскольку значение enum может представлять собой только один из его возможных исходов. Адреса как четвёртой, так и шестой исполнения по своей сути все равно являются IP-адресами, поэтому их следует рассматривать как один и тот же вид, когда в коде обрабатываются задачи, относящиеся к любому виду IP-адресов.

-

Можно выразить эту подход в коде, определив перечисление IpAddrKind и составив список возможных видов IP-адресов, V4 и V6. Вот исходы перечислений:

-
enum IpAddrKind {
-    V4,
-    V6,
-}
-
-fn main() {
-    let four = IpAddrKind::V4;
-    let six = IpAddrKind::V6;
-
-    route(IpAddrKind::V4);
-    route(IpAddrKind::V6);
-}
-
-fn route(ip_kind: IpAddrKind) {}
-

IpAddrKind теперь является пользовательским видом данных, который мы можем использовать в другом месте нашего кода.

-

Значения перечислений

-

Образцы каждого исхода перечисления IpAddrKind можно создать следующим образом:

-
enum IpAddrKind {
-    V4,
-    V6,
-}
-
-fn main() {
-    let four = IpAddrKind::V4;
-    let six = IpAddrKind::V6;
-
-    route(IpAddrKind::V4);
-    route(IpAddrKind::V6);
-}
-
-fn route(ip_kind: IpAddrKind) {}
-

Обратите внимание, что исходы перечисления находятся в пространстве имён вместе с его определителем, а для их обособления мы используем двойное двоеточие. Это удобно тем, что теперь оба значения IpAddrKind::V4 и IpAddrKind::V6 относятся к одному виду: IpAddrKind. Затем мы можем, например, определить функцию, которая принимает любой из исходов IpAddrKind:

-
enum IpAddrKind {
-    V4,
-    V6,
-}
-
-fn main() {
-    let four = IpAddrKind::V4;
-    let six = IpAddrKind::V6;
-
-    route(IpAddrKind::V4);
-    route(IpAddrKind::V6);
-}
-
-fn route(ip_kind: IpAddrKind) {}
-

Можно вызвать эту функцию с любым из исходов:

-
enum IpAddrKind {
-    V4,
-    V6,
-}
-
-fn main() {
-    let four = IpAddrKind::V4;
-    let six = IpAddrKind::V6;
-
-    route(IpAddrKind::V4);
-    route(IpAddrKind::V6);
-}
-
-fn route(ip_kind: IpAddrKind) {}
-

Использование перечислений позволяет получить ещё больше преимуществ. Если подумать о нашем виде для IP-адреса, то выяснится, что на данный мгновение у нас нет возможности хранить собственно сам IP-адрес; мы будем знать только его вид. Учитывая, что недавно в главе 5 вы узнали о устройствах, у вас может возникнуть соблазн решить эту неполадку с помощью устройств, как показано в приложении 6-1.

-
fn main() {
-    enum IpAddrKind {
-        V4,
-        V6,
-    }
-
-    struct IpAddr {
-        kind: IpAddrKind,
-        address: String,
-    }
-
-    let home = IpAddr {
-        kind: IpAddrKind::V4,
-        address: String::from("127.0.0.1"),
-    };
-
-    let loopback = IpAddr {
-        kind: IpAddrKind::V6,
-        address: String::from("::1"),
-    };
-}
-

Приложение 6-1. Сохранение данных и IpAddrKind IP-адреса с использованием struct

-

Здесь мы определили устройство IpAddr, у которой есть два поля: kind вида IpAddrKind (перечисление, которое мы определили ранее) и address вида String. У нас есть два образца этой устройства. Первый - home, который является IpAddrKind::V4 в качестве значения kind с соответствующим адресом 127.0.0.1. Второй образец - loopback. Он в качестве значения kind имеет другой исход IpAddrKind, V6, и с ним сопряжен адрес ::1. Мы использовали устройство для объединения значений kind и address вместе, таким образом вид вида адреса теперь сопряжен со значением.

-

Однако представление этой же подходы с помощью перечисления более кратко: вместо того, чтобы помещать перечисление в устройство, мы можем поместить данные непосредственно в любой из исходов перечисления. Это новое определение перечисления IpAddr гласит, что оба исхода V4 и V6 будут иметь соответствующие значения String:

-
fn main() {
-    enum IpAddr {
-        V4(String),
-        V6(String),
-    }
-
-    let home = IpAddr::V4(String::from("127.0.0.1"));
-
-    let loopback = IpAddr::V6(String::from("::1"));
-}
-

Мы прикрепляем данные к каждому исходу перечисления напрямую, поэтому нет необходимости в дополнительной устройстве. Здесь также легче увидеть ещё одну подробность того, как работают перечисления: имя каждого исхода перечисления, который мы определяем, также становится функцией, которая создаёт образец перечисления. То есть IpAddr::V4() - это вызов функции, который принимает String и возвращает образец вида IpAddr. Мы самостоятельно получаем эту функцию-строитель, определяемую в итоге определения перечисления.

-

Ещё одно преимущество использования перечисления вместо устройства заключается в том, что каждый исход перечисления может иметь разное количество сопряженных данных представленных в разных видах. Исполнение 4 для IP адресов всегда будет содержать четыре цифровых составляющих, которые будут иметь значения между 0 и 255. При необходимости сохранить адреса вида V4 как четыре значения вида u8, а также описать адреса вида V6 как единственное значение вида String, мы не смогли бы с помощью устройства. Перечисления решают эту задачу легко:

-
fn main() {
-    enum IpAddr {
-        V4(u8, u8, u8, u8),
-        V6(String),
-    }
-
-    let home = IpAddr::V4(127, 0, 0, 1);
-
-    let loopback = IpAddr::V6(String::from("::1"));
-}
-

Мы показали несколько различных способов определения устройств данных для хранения IP-адресов четвёртой и шестой исполнений. Однако, как оказалось, желание хранить IP-адреса и указывать их вид настолько распространено, что в встроенной библиотеке есть определение, которое мы можем использовать! Давайте посмотрим, как обычная библиотека определяет IpAddr: в ней есть точно такое же перечисление с исходами, которое мы определили и использовали, но она помещает данные об адресе внутрь этих исходов в виде двух различных устройств, которые имеют различные определения для каждого из исходов:

-
#![allow(unused)]
-fn main() {
-struct Ipv4Addr {
-    // --snip--
-}
-
-struct Ipv6Addr {
-    // --snip--
-}
-
-enum IpAddr {
-    V4(Ipv4Addr),
-    V6(Ipv6Addr),
-}
-}
-

Этот код отображает что мы можем добавлять любой вид данных в значение перечисления: строку, число, устройство и пр. Вы даже можете включить в перечисление другие перечисления! Обычные виды данных не очень сложны, хотя, возможно, могут быть очень сложными (вложенность данных может быть очень глубокой).

-

Обратите внимание, что хотя определение перечисления IpAddr есть в встроенной библиотеке, мы смогли объявлять и использовать свою собственную выполнение с подобным названием без каких-либо несоответствий, потому что мы не добавили определение встроенной библиотеки в область видимости кода. Подробнее об этом поговорим в Главе 7.

-

Рассмотрим другой пример перечисления в приложении 6-2: в этом примере каждый элемент перечисления имеет свой особый вид данных внутри:

-
enum Message {
-    Quit,
-    Move { x: i32, y: i32 },
-    Write(String),
-    ChangeColor(i32, i32, i32),
-}
-
-fn main() {}
-

Приложение 6-2. Перечисление Message, в каждом из исходов которого хранятся разные количества и виды значений.

-

Это перечисление имеет 4 элемента:

-
    -
  • Quit - пустой элемент без сопряженных данных,
  • -
  • Move имеет именованные поля, как и устройства.
  • -
  • Write - элемент с единственной строкой вида String,
  • -
  • ChangeColor - упорядоченный ряд из трёх значений вида i32.
  • -
-

Определение перечисления с исходами, такими как в приложении 6-2, похоже на определение значений различных видов внутри устройств, за исключением того, что перечисление не использует ключевое слово struct и все исходы объединены внутри вида Message. Следующие устройства могут содержать те же данные, что и предыдущие исходы перечислений:

-
struct QuitMessage; // unit struct
-struct MoveMessage {
-    x: i32,
-    y: i32,
-}
-struct WriteMessage(String); // tuple struct
-struct ChangeColorMessage(i32, i32, i32); // tuple struct
-
-fn main() {}
-

Но когда мы использовали различные устройства, каждая из которых имеет свои собственные виды, мы не могли легко определять функции, которые принимают любые виды сообщений, как это можно сделать с помощью перечисления вида Message, объявленного в приложении 6-2, который является единым видом.

-

Есть ещё одно сходство между перечислениями и устройствами: так же, как мы можем определять способы для устройств с помощью impl раздела, мы можем определять и способы для перечисления. Вот пример способа с именем call, который мы могли бы определить в нашем перечислении Message:

-
fn main() {
-    enum Message {
-        Quit,
-        Move { x: i32, y: i32 },
-        Write(String),
-        ChangeColor(i32, i32, i32),
-    }
-
-    impl Message {
-        fn call(&self) {
-            // method body would be defined here
-        }
-    }
-
-    let m = Message::Write(String::from("hello"));
-    m.call();
-}
-

В теле способа будет использоваться self для получения значение того предмета. у которого мы вызвали этот способ. В этом примере мы создали переменную m, содержащую значение Message::Write(String::from("hello")), и именно это значение будет представлять self в теле способа call при выполнении m.call().

-

Теперь посмотрим на другое наиболее часто используемое перечисление из встроенной библиотеки, которое является очень распространённым и полезным: Option.

-

Перечисление Option и его преимущества перед Null-значениями

-

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

-

Например, если вы запросите первый элемент из непустого списка, вы получите значение. Если вы запросите первый элемент пустого списка, вы ничего не получите. Выражение этой подходы в понятиях системы видов означает, что сборщик может проверить, обработали ли вы все случаи, которые должны были обработать; эта возможность может предотвратить ошибки, которые чрезвычайно распространены в других языках программирования.

-

Внешний вид языка программирования часто рассматривается с точки зрения того, какие функции вы включаете в него, но те функции, которые вы исключаете, также важны. Например в Ржавчина нет такого возможностей как null значения, однако он есть во многих других языках. Null значение - это значение, которое означает, что значения нет. В языках с null значением переменные всегда могут находиться в одном из двух состояний: нет значения (null) или есть значение (not-null).

-

В своей презентации 2009 года «Null ссылки: ошибка в миллиард долларов» Тони Хоар (Tony Hoare), изобретатель null, сказал следующее:

-
-

Я называю это своей ошибкой на миллиард долларов. В то время я разрабатывал первую комплексную систему видов для ссылок на предметно-направленном языке. Моя цель состояла в том, чтобы обеспечить, что любое использование ссылок должно быть абсолютно безопасным, с самостоятельной проверкой сборщиком. Но я не мог устоять перед соблазном вставить пустую ссылку просто потому, что это было так легко выполнить. Это привело к бесчисленным ошибкам, уязвимостям и системным сбоям, которые, вероятно, причинили боль и ущерб на миллиард долларов за последние сорок лет.

-
-

Неполадкас null значениями заключается в том, что если вы попытаетесь использовать null значение в качестве not-null значения, вы получите ошибку определённого рода. Поскольку свойство null или not-null распространено повсеместно, сделать такую ошибку очень просто.

-

Тем не менее, подход, которую null пытается выразить, является полезной: null - это значение, которое в настоящее время по какой-то причине недействительно или отсутствует.

-

Неполадкана самом деле не в подходы, а в именно выполнения. Таким образом, в Ржавчина нет значений null, но есть перечисление, которое может закодировать подход присутствия или отсутствия значения. Это перечисление Option<T> , и оно определено встроенной библиотекой следующим образом:

-
#![allow(unused)]
-fn main() {
-enum Option<T> {
-    None,
-    Some(T),
-}
-}
-

Перечисление Option<T> настолько полезно, что оно даже включено в прелюдию; вам не нужно явно вводить его в область видимости. Его исходы также включены в прелюдию: вы можете использовать Some и None напрямую, без приставки Option::. При всём при этом, Option<T> является обычным перечислением, а Some(T) и None представляют собой его исходы.

-

<T> - это особенность Rust, о которой мы ещё не говорили. Это свойство обобщённого вида, и мы рассмотрим его более подробно в главе 10. На данный мгновение всё, что вам нужно знать, это то, что <T> означает, что исход Some Option может содержать один отрывок данных любого вида, и что каждый определенный вид, который используется вместо T делает общий Option<T> другим видом. Вот несколько примеров использования Option для хранения числовых и строковых видов:

-
fn main() {
-    let some_number = Some(5);
-    let some_char = Some('e');
-
-    let absent_number: Option<i32> = None;
-}
-

Вид some_number - Option<i32>. Вид some_char - Option<char>, это другой вид. Ржавчина может вывести эти виды, потому что мы указали значение внутри исхода Some. Для absent_number Ржавчина требует, чтобы мы определяли общий вид для Option: сборщик не может вывести вид, который будет в Some, глядя только на значение None. Здесь мы сообщаем Rust, что absent_number должен иметь вид Option<i32>.

-

Когда есть значение Some, мы знаем, что значение присутствует и содержится внутри Some. Когда есть значение None, это означает то же самое, что и null в некотором смысле: у нас нет действительного значения. Так почему наличие Option<T> лучше, чем null?

-

Вкратце, поскольку Option<T> и T (где T может быть любым видом) относятся к разным видам, сборщик не позволит нам использовать значение Option<T> даже если бы оно было определённо допустимым значением. Например, этот код не будет собираться, потому что он пытается добавить i8 к значению вида Option<i8>:

-
fn main() {
-    let x: i8 = 5;
-    let y: Option<i8> = Some(5);
-
-    let sum = x + y;
-}
-

Если мы запустим этот код, то получим такое сообщение об ошибке:

-
$ cargo run
-   Compiling enums v0.1.0 (file:///projects/enums)
-error[E0277]: cannot add `Option<i8>` to `i8`
- --> src/main.rs:5:17
-  |
-5 |     let sum = x + y;
-  |                 ^ no implementation for `i8 + Option<i8>`
-  |
-  = help: the trait `Add<Option<i8>>` is not implemented for `i8`
-  = help: the following other types implement trait `Add<Rhs>`:
-            <&'a i8 as Add<i8>>
-            <&i8 as Add<&i8>>
-            <i8 as Add<&i8>>
-            <i8 as Add>
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `enums` (bin "enums") due to 1 previous error
-
-

Сильно! В действительности, это сообщение об ошибке означает, что Ржавчина не понимает, как сложить i8 и Option<i8>, потому что это разные виды. Когда у нас есть значение вида наподобие i8, сборщик заверяет, что у нас всегда есть допустимое значение вида. Мы можем уверенно продолжать работу, не проверяя его на null перед использованием. Однако, когда у нас есть значение вида Option<T> (где T - это любое значение любого вида T, упакованное в Option, например значение вида i8 или String), мы должны беспокоиться о том, что значение вида T возможно не имеет значения (является исходом None), и сборщик позаботится о том, чтобы мы обработали такой случай, прежде чем мы бы попытались использовать None значение.

-

Другими словами, вы должны преобразовать Option<T> в T прежде чем вы сможете выполнять действия с этим T. Как правило, это помогает выявить одну из наиболее распространённых неполадок с null: предполагая, что что-то не равно null, когда оно на самом деле равно null.

-

Устранение риска ошибочного предположения касательно не-null значения помогает вам быть более уверенным в своём коде. Чтобы иметь значение, которое может быть null, вы должны явно описать вид этого значения с помощью Option<T>. Затем, когда вы используете это значение, вы обязаны явно обрабатывать случай, когда значение равно null. Везде, где значение имеет вид, отличный от Option<T>, вы можете смело рассчитывать на то, что значение не равно null. Это продуманное расчетное решение в Rust, ограничивающее распространение null и увеличивающее безопасность кода на Rust.

-

Итак, как же получить значение T из исхода Some, если у вас на руках есть только предмет Option<T>, и как можно его, вообще, использовать? Перечисление Option<T> имеет большое количество способов, полезных в различных случаейх; вы можете ознакомиться с ними в его документации. Знакомство с способами перечисления Option<T> будет чрезвычайно полезным в вашем путешествии с Rust.

-

В общем случае, чтобы использовать значение Option<T>, нужен код, который будет обрабатывать все исходы перечисления Option<T>. Вам понадобится некоторый код, который будет работать только тогда, когда у вас есть значение Some(T), и этому коду разрешено использовать внутри T. Также вам понадобится другой код, который будет работать, если у вас есть значение None, и у этого кода не будет доступного значения T. Выражение match — это устройство управления потоком выполнения программы, которая делает именно это при работе с перечислениями: она запускает разный код в зависимости от того, какой исход перечисления имеется, и этот код может использовать данные, находящиеся внутри совпавшего исхода.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch06-02-match.html b/rustbook-ru/book/ch06-02-match.html deleted file mode 100644 index 308198251..000000000 --- a/rustbook-ru/book/ch06-02-match.html +++ /dev/null @@ -1,472 +0,0 @@ - - - - - - Устройство потока управления match - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- -

-

Управляющая устройство match

-

В Ржавчина есть чрезвычайно мощный рычаг управления потоком, именуемый match, который позволяет сравнивать значение с различными образцами и затем выполнять код в зависимости от того, какой из образцов совпал. Образцы могут состоять из записанных значений, имён переменных, подстановочных знаков и многого другого; в главе 18 рассматриваются все различные виды образцов и то, что они делают. Сила match заключается в выразительности образцов и в том, что сборщик проверяет, что все возможные случаи обработаны.

-

Думайте о выражении match как о машине для сортировки монет: монеты скользят по дорожке с различными по размеру отверстиями, и каждая монета падает через первое попавшееся отверстие, в которое она поместилась. Таким же образом значения проходят через каждый образец в match, и при первом же "подходящем" образце значение попадает в соответствующий раздел кода, который будет использоваться во время выполнения.

-

Говоря о монетах, давайте используем их в качестве примера, используя match! Для этого мы напишем функцию, которая будет получать на вход неизвестную монету Соединённых Штатов и, подобно счётной машине, определять, какая это монета, и возвращать её стоимость в центах, как показано в приложении 6-3.

-
enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter,
-}
-
-fn value_in_cents(coin: Coin) -> u8 {
-    match coin {
-        Coin::Penny => 1,
-        Coin::Nickel => 5,
-        Coin::Dime => 10,
-        Coin::Quarter => 25,
-    }
-}
-
-fn main() {}
-

Приложение 6-3: Перечисление и выражение match, использующее в качестве образцов его исходы

-

Давайте разберём match в функции value_in_cents. Сначала пишется ключевое слово match, затем следует выражение, которое в данном случае является значением coin. Это выглядит очень похоже на условное выражение, используемое в if, но есть большая разница: с if выражение должно возвращать булево значение, а здесь это может быть любой вид. Вид coin в этом примере — перечисление вида Coin, объявленное в строке 1.

-

Далее идут ветки match. Ветки состоят из двух частей: образец и некоторый код. Здесь первая ветка имеет образец, который является значением Coin::Penny, затем идёт оператор =>, который разделяет образец и код для выполнения. Код в этом случае - это просто значение 1. Каждая ветка отделяется от последующей при помощи запятой.

-

Когда выполняется выражение match, оно сравнивает полученное значение с образцом каждого ответвления по порядку. Если образец совпадает со значением, то выполняется код, связанный с этим образцом. Если этот образец не соответствует значению, то выполнение продолжается со следующей ветки, так же, как в автомате по сортировке монет. У нас может быть столько ответвлений, сколько нужно: в приложении 6-3 наш match состоит из четырёх ответвлений.

-

Код, связанный с каждым ответвлением, является выражением, а полученное значение выражения в соответствующем ответвлении — это значение, которое возвращается для всего выражения match.

-

Обычно фигурные скобки не используются, если код совпадающей ветви невелик, как в приложении 6-3, где каждая ветвь просто возвращает значение. Если вы хотите выполнить несколько строк кода в одной ветви, вы должны использовать фигурные скобки, а запятая после этой ветви необязательна. Например, следующий код печатает "Lucky penny!" каждый раз, когда способ вызывается с Coin::Penny, но при этом он возвращает последнее значение раздела - 1:

-
enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter,
-}
-
-fn value_in_cents(coin: Coin) -> u8 {
-    match coin {
-        Coin::Penny => {
-            println!("Lucky penny!");
-            1
-        }
-        Coin::Nickel => 5,
-        Coin::Dime => 10,
-        Coin::Quarter => 25,
-    }
-}
-
-fn main() {}
-

Образцы, привязывающие значения

-

Есть ещё одно полезное качество у веток в выражении match: они могут привязываться к частям тех значений, которые совпали с образцом. Благодаря этому можно извлекать значения из исходов перечисления.

-

В качестве примера, давайте изменим один из исходов перечисления так, чтобы он хранил в себе данные. С 1999 по 2008 год Соединённые Штаты чеканили 25 центов с различным внешнем видом на одной стороне для каждого из 50 штатов. Ни одна другая монета не получила внешнего видаштата, только четверть доллара имела эту дополнительную особенность. Мы можем добавить эту сведения в наш enum путём изменения исхода Quarter и включить в него значение UsState, как сделано в приложении 6-4.

-
#[derive(Debug)] // so we can inspect the state in a minute
-enum UsState {
-    Alabama,
-    Alaska,
-    // --snip--
-}
-
-enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter(UsState),
-}
-
-fn main() {}
-

Приложение 6-4: Перечисление Coin, в котором исход Quarter также сохраняет значение UsState

-

Представьте, что ваш друг пытается собрать четвертаки всех 50 штатов. Сортируя монеты по виду, мы также будем сообщать название штата, к которому относится каждый четвертак, чтобы, если у нашего друга нет такой монеты, он мог добавить её в свою собрание.

-

В выражении match для этого кода мы добавляем переменную с именем state в образец, который соответствует значениям исхода Coin::Quarter. Когда Coin::Quarter совпадёт с образцом, переменная state будет привязана к значению штата этого четвертака. Затем мы сможем использовать state в коде этой ветки, вот так:

-
#[derive(Debug)]
-enum UsState {
-    Alabama,
-    Alaska,
-    // --snip--
-}
-
-enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter(UsState),
-}
-
-fn value_in_cents(coin: Coin) -> u8 {
-    match coin {
-        Coin::Penny => 1,
-        Coin::Nickel => 5,
-        Coin::Dime => 10,
-        Coin::Quarter(state) => {
-            println!("State quarter from {state:?}!");
-            25
-        }
-    }
-}
-
-fn main() {
-    value_in_cents(Coin::Quarter(UsState::Alaska));
-}
-

Если мы сделаем вызов функции value_in_cents(Coin::Quarter(UsState::Alaska)), то coin будет иметь значение Coin::Quarter(UsState::Alaska). Когда мы будем сравнивать это значение с каждой из веток, ни одна из них не будет совпадать, пока мы не достигнем исхода Coin::Quarter(state). В этот мгновение state привяжется к значению UsState::Alaska. Затем мы сможем использовать эту привязку в выражении println!, получив таким образом внутреннее значение исхода Quarter перечисления Coin.

-

Сопоставление образца для Option<T>

-

В предыдущем разделе мы хотели получить внутреннее значение T для случая Some при использовании Option<T>; мы можем обработать вид Option<T> используя match, как уже делали с перечислением Coin! Вместо сравнения монет мы будем сравнивать исходы Option<T>, независимо от этого изменения рычаг работы выражения match останется прежним.

-

Допустим, мы хотим написать функцию, которая принимает Option<i32> и если есть значение внутри, то добавляет 1 к существующему значению. Если значения нет, то функция должна возвращать значение None и не пытаться выполнить какие-либо действия.

-

Такую функцию довольно легко написать благодаря выражению match, код будет выглядеть как в приложении 6-5.

-
fn main() {
-    fn plus_one(x: Option<i32>) -> Option<i32> {
-        match x {
-            None => None,
-            Some(i) => Some(i + 1),
-        }
-    }
-
-    let five = Some(5);
-    let six = plus_one(five);
-    let none = plus_one(None);
-}
-

Приложение 6-5: Функция, использующая выражение match для Option<i32>

-

Давайте более подробно рассмотрим первое выполнение plus_one. Когда мы вызываем plus_one(five), переменная x в теле plus_one будет иметь значение Some(5). Затем мы сравниваем это значение с каждой ветвью сопоставления:

-
fn main() {
-    fn plus_one(x: Option<i32>) -> Option<i32> {
-        match x {
-            None => None,
-            Some(i) => Some(i + 1),
-        }
-    }
-
-    let five = Some(5);
-    let six = plus_one(five);
-    let none = plus_one(None);
-}
-

Значение Some(5) не соответствует образцу None, поэтому мы продолжаем со следующим ответвлением:

-
fn main() {
-    fn plus_one(x: Option<i32>) -> Option<i32> {
-        match x {
-            None => None,
-            Some(i) => Some(i + 1),
-        }
-    }
-
-    let five = Some(5);
-    let six = plus_one(five);
-    let none = plus_one(None);
-}
-

Совпадает ли Some(5) с образцом Some(i)? Да, это так! У нас такой же исход. Тогда переменная i привязывается к значению, содержащемуся внутри Some, поэтому i получает значение 5. Затем выполняется код сопряженный для данного ответвления, поэтому мы добавляем 1 к значению i и создаём новое значение Some со значением 6 внутри.

-

Теперь давайте рассмотрим второй вызов plus_one в приложении 6-5, где x является None. Мы входим в выражение match и сравниваем значение с первым ответвлением:

-
fn main() {
-    fn plus_one(x: Option<i32>) -> Option<i32> {
-        match x {
-            None => None,
-            Some(i) => Some(i + 1),
-        }
-    }
-
-    let five = Some(5);
-    let six = plus_one(five);
-    let none = plus_one(None);
-}
-

Оно совпадает! Для данной ветки образец (None) не подразумевает наличие какого-то значения к которому можно было бы что-то добавить, поэтому программа останавливается и возвращает значение которое находится справа от => - т.е. None. Так как образец первой ветки совпал, то никакие другие образцы веток не сравниваются.

-

Соединение match и перечислений полезно во многих случаейх. Вы часто будете видеть подобную сочетание в коде на Rust: сделать сопоставление значений перечисления используя match, привязать переменную к данным внутри значения, выполнить код на основе привязанных данных. Сначала это может показаться немного сложным, но как только вы привыкнете, то захотите чтобы такая возможность была бы во всех языках. Это неизменно любимый пользователями приём.

-

Match охватывает все исходы значения

-

Есть ещё один особенность match, который мы должны обсудить: образцы должны покрывать все возможные исходы. Рассмотрим эту исполнение нашей функции plus_one, которая содержит ошибку и не собирается:

-
fn main() {
-    fn plus_one(x: Option<i32>) -> Option<i32> {
-        match x {
-            Some(i) => Some(i + 1),
-        }
-    }
-
-    let five = Some(5);
-    let six = plus_one(five);
-    let none = plus_one(None);
-}
-

Мы не обработали исход None, поэтому этот код вызовет изъян в программе. К счастью, Ржавчина знает и умеет ловить такой случай. Если мы попытаемся собрать такой код, мы получим ошибку сборки:

-
$ cargo run
-   Compiling enums v0.1.0 (file:///projects/enums)
-error[E0004]: non-exhaustive patterns: `None` not covered
- --> src/main.rs:3:15
-  |
-3 |         match x {
-  |               ^ pattern `None` not covered
-  |
-note: `Option<i32>` defined here
- --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/option.rs:571:1
- ::: /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/option.rs:575:5
-  |
-  = note: not covered
-  = note: the matched value is of type `Option<i32>`
-help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
-  |
-4 ~             Some(i) => Some(i + 1),
-5 ~             None => todo!(),
-  |
-
-For more information about this error, try `rustc --explain E0004`.
-error: could not compile `enums` (bin "enums") due to 1 previous error
-
-

Rust знает, что мы не описали все возможные случаи, и даже знает, какой именно из образцов мы упуисполнения! Сопоставления в Ржавчина являются исчерпывающими: мы должны покрыть все возможные исходы, чтобы код был правильным. Особенно в случае Option<T>, когда Ржавчина не даёт нам забыть обработать явным образом значение None, тем самым он защищает нас от предположения, что у нас есть значение, в то время как у нас может быть и null, что делает невозможным совершить ошибку на миллиард долларов, о которой говорилось ранее.

-

Гибкие образцы и заполнитель _

-

Используя перечисления, мы также можем выполнять особые действия для нескольких определённых значений, а для всех остальных значений выполнять одно действие по умолчанию. Представьте, что мы выполняем игру, в которой при выпадении 3 игрок не двигается, а получает новую нового образца шляпу. Если выпадает 7, игрок теряет шляпу. При всех остальных значениях ваш игрок перемещается на столько-то мест на игровом поле. Вот match, выполняющий эту логику, в котором итог броска костей жёстко закодирован, а не является случайным значением, а вся остальная логика представлена функциями без тел, поскольку их выполнение не входит в рамки данного примера:

-
fn main() {
-    let dice_roll = 9;
-    match dice_roll {
-        3 => add_fancy_hat(),
-        7 => remove_fancy_hat(),
-        other => move_player(other),
-    }
-
-    fn add_fancy_hat() {}
-    fn remove_fancy_hat() {}
-    fn move_player(num_spaces: u8) {}
-}
-

Для первых двух веток образцами являются записанные значения 3 и 7. Для последней ветки, которая охватывает все остальные возможные значения, образцом является переменная, которую мы решили назвать other. Код, выполняемый для ветки other, использует эту переменную, передавая её в функцию move_player.

-

Этот код собирается, даже если мы не перечислили все возможные значения u8, потому что последний образец будет соответствовать всем значениям, не указанным в определенном списке. Этот гибкий образец удовлетворяет требованию, что соответствие должно быть исчерпывающим. Обратите внимание, что мы должны поместить ветку с гибким образцом последней, потому что образцы оцениваются по порядку. Ржавчина предупредит нас, если мы добавим ветки после гибкого образца, потому что эти последующие ветки никогда не будут выполняться!

-

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

-

Давайте изменим правила игры так: если выпадает что-то, кроме 3 или 7, нужно бросить ещё раз. Нам не нужно использовать значение в этом случае, поэтому мы можем изменить наш код, чтобы использовать _ вместо переменной с именем other:

-
fn main() {
-    let dice_roll = 9;
-    match dice_roll {
-        3 => add_fancy_hat(),
-        7 => remove_fancy_hat(),
-        _ => reroll(),
-    }
-
-    fn add_fancy_hat() {}
-    fn remove_fancy_hat() {}
-    fn reroll() {}
-}
-

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

-

Если мы изменим правила игры ещё раз, чтобы в ваш ход не происходило ничего другого, если вы бросаете не 3 или 7, мы можем выразить это, используя единичное значение (пустой вид упорядоченного ряда, о котором мы упоминали в разделе "Упорядоченные ряды") в качестве кода, который идёт вместе с веткой _:

-
fn main() {
-    let dice_roll = 9;
-    match dice_roll {
-        3 => add_fancy_hat(),
-        7 => remove_fancy_hat(),
-        _ => (),
-    }
-
-    fn add_fancy_hat() {}
-    fn remove_fancy_hat() {}
-}
-

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

-

Подробнее о образцах и совпадениях мы поговорим в Главе 18. Пока же мы перейдём к правилам написания if let, который может быть полезен в случаейх, когда выражение match слишком многословно.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch06-03-if-let.html b/rustbook-ru/book/ch06-03-if-let.html deleted file mode 100644 index 273e8368d..000000000 --- a/rustbook-ru/book/ch06-03-if-let.html +++ /dev/null @@ -1,303 +0,0 @@ - - - - - - Краткий поток управления с if let - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Краткое управление потоком выполнения с if let

-

правила написания if let позволяет ссоединенять if и let в менее многословную устройство, и затем обработать значения соответствующе только одному образцу, одновременно пренебрегая все остальные. Рассмотрим программу в приложении 6-6, которая обрабатывает сопоставление значения Option<u8> в переменной config_max, но хочет выполнить код только в том случае, если значение является исходом Some.

-
fn main() {
-    let config_max = Some(3u8);
-    match config_max {
-        Some(max) => println!("The maximum is configured to be {max}"),
-        _ => (),
-    }
-}
-

Приложение 6-6. Выражение match, которое выполнит код только при значении равном Some

-

Если значение равно Some, мы распечатываем значение в исходе Some, привязывая значение к переменной max в образце. Мы не хотим ничего делать со значением None. Чтобы удовлетворить выражение match, мы должны добавить _ => () после обработки первой и единственной ветки, и добавление образцового кода раздражает.

-

Вместо этого, мы могли бы написать это более коротким способом, используя if let. Следующий код ведёт себя так же, как выражение match в приложении 6-6:

-
fn main() {
-    let config_max = Some(3u8);
-    if let Some(max) = config_max {
-        println!("The maximum is configured to be {max}");
-    }
-}
-

правила написания if let принимает образец и выражение, разделённые знаком равенства. Он работает так же, как match, когда в него на вход передадут выражение и подходящим образцом для этого выражения окажется первая ветка. В данном случае образцом является Some(max), где max привязывается к значению внутри Some. Затем мы можем использовать max в теле раздела if let так же, как мы использовали max в соответствующей ветке match. Код в разделе if let не запускается, если значение не соответствует образцу.

-

Используя if let мы меньше печатаем, меньше делаем отступов и меньше получаем образцового кода. Тем не менее, мы теряем полную проверку всех исходов, предоставляемую выражением match. Выбор между match и if let зависит от того, что вы делаете в вашем определенном случае и является ли получение краткости при потере полноты проверки подходящим соглашением.

-

Другими словами, вы можете думать о устройства if let как о синтаксическом сахаре для match, который выполнит код если входное значение будет соответствовать единственному образцу, и пренебрегает все остальные значения.

-

Можно добавлять else к if let. Разделкода, который находится внутри else подобен по смыслу блоку кода ветки связанной с образцом _ выражения match (которое эквивалентно сборной устройства if let и else). Вспомним объявление перечисления Coin в приложении 6-4, где исход Quarter также содержит внутри значение штата вида UsState. Если бы мы хотели посчитать все монеты не являющиеся четвертями, а для четвертей печатать название штата, то мы могли бы сделать это с помощью выражения match таким образом:

-
#[derive(Debug)]
-enum UsState {
-    Alabama,
-    Alaska,
-    // --snip--
-}
-
-enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter(UsState),
-}
-
-fn main() {
-    let coin = Coin::Penny;
-    let mut count = 0;
-    match coin {
-        Coin::Quarter(state) => println!("State quarter from {state:?}!"),
-        _ => count += 1,
-    }
-}
-

Или мы могли бы использовать выражение if let и else так:

-
#[derive(Debug)]
-enum UsState {
-    Alabama,
-    Alaska,
-    // --snip--
-}
-
-enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter(UsState),
-}
-
-fn main() {
-    let coin = Coin::Penny;
-    let mut count = 0;
-    if let Coin::Quarter(state) = coin {
-        println!("State quarter from {state:?}!");
-    } else {
-        count += 1;
-    }
-}
-

Если у вас есть случаей в которой ваша программа имеет логику которая слишком многословна для того чтобы её выражать используя match, помните, о том, что также в вашем наборе средств Ржавчина есть if let.

-

Итоги

-

Мы рассмотрели как использовать перечисления для создания пользовательских видов, которые могут быть одним из наборов перечисляемых значений. Мы показали, как вид Option<T> из встроенной библиотеки помогает использовать систему видов для предотвращения ошибок. А когда значения перечисления имеют данные внутри них, можно использовать match или if let, чтобы извлечь и пользоваться значением, в зависимости от того, сколько случаев нужно обработать.

-

Теперь ваши программы на Ржавчина могут выражать подходы вашей предметной области, используя устройства и перечисления. Создание и использование пользовательских видов в API обеспечивает типобезопасность: сборщик позаботится о том, чтобы функции получали значения только того вида, который они ожидают.

-

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

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html b/rustbook-ru/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html deleted file mode 100644 index da1651e53..000000000 --- a/rustbook-ru/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html +++ /dev/null @@ -1,243 +0,0 @@ - - - - - - Управление растущими делами с помощью дополнений, ящиков и звеньев - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Управление растущими делами с помощью дополнений, ящиков и звеньев

-

По мере роста кодовой хранилища ваших программ, создание дела будет иметь большое значение, ведь отслеживание всей программы в голове будет становиться всё более сложным. Объединенияя связанные функции и разделяя код по основным возможностям (фичам, feature), вы делаете более прозрачным понимание о том, где искать код выполняющий определённую функцию и где стоит вносить изменения для того чтобы изменить её поведение.

-

Программы, которые мы писали до сих пор, были в одном файле одного звена. По мере роста дела, мы можем создавать код иначе, разделив его на несколько звеньев и несколько файлов. Дополнение может содержать несколько двоичных ящиков и дополнительно один ящик библиотеки. Дополнение может включать в себя много двоичных ящиков и дополнительно один библиотечный ящик. По мере роста дополнения вы можете извлекать части программы в отдельные ящики, которые затем станут внешними зависимостями для основного кода нашей программы. Эта глава охватывает все эти техники. В свою очередь для очень крупных дел, состоящих из набора взаимосвязанных дополнений развивающихся вместе, Cargo предоставляет рабочие пространства, workspaces, их мы рассмотрим за пределами данной главы, в разделе "Рабочие пространства Cargo" Главы 14.

-

Мы также обсудим инкапсуляцию подробностей, которая позволяет использовать код снова на более высоком уровне: единожды выполнив какую-то действие, другой код может вызывать этот код через открытый внешняя оболочка, не зная как работает выполнение. То, как вы пишете код, определяет какие части общедоступны для использования другим кодом и какие части являются закрытыми деталями выполнения для которых вы оставляете право на изменения только за собой. Это ещё один способ ограничить количество подробностей, которые вы должны держать в голове.

-

Связанное понятие - это область видимости: вложенный среда в котором написан код имеющий набор имён, которые определены «в текущей области видимости». При чтении, письме и сборки кода, программистам и сборщикам необходимо знать, относится ли определенное имя в определённом месте к переменной, к функции, к устройстве, к перечислению, к звену, к постоянных значенийе или другому элементу и что означает этот элемент. Можно создавать области видимости и изменять какие имена входят или выходят за их рамки. Нельзя иметь два элемента с тем же именем в одной области; есть доступные средства для разрешения несоответствий имён.

-

Rust имеет ряд функций, которые позволяют управлять согласованием кода, в том числе управлять тем какие подробности открыты, какие подробности являются частными, какие имена есть в каждой области вашей программы. Эти функции иногда вместе именуемые состоящей из звеньев системой включают в себя:

-
    -
  • Дополнения: Возможности Cargo позволяющий собирать, проверять и делиться ящиками
  • -
  • Ящики: Дерево звеньев, которое создаёт библиотечный или исполняемый файл
  • -
  • Звенья и use: Позволяют вместе управлять устройство, область видимости и скрытие путей
  • -
  • Пути: способ именования элемента, такого как устройства, функция или звено
  • -
-

В этой главе мы рассмотрим все эти функции, обсудим как они взаимодействуют и объясним, как использовать их для управления областью видимости. К концу у вас должно появиться солидное понимание состоящей из звеньев системы и умение работать с областями видимости на уровне искуссника!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch07-01-packages-and-crates.html b/rustbook-ru/book/ch07-01-packages-and-crates.html deleted file mode 100644 index 45c999154..000000000 --- a/rustbook-ru/book/ch07-01-packages-and-crates.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - Дополнения и ящики - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Дополнения и ящики

-

Первые части состоящей из звеньев системы, которые мы рассмотрим — это дополнения и ящики.

-

Ящик — это наименьший размер кода, который сборщик Ржавчина рассматривает за раз. Даже если вы запустите rustc вместо cargo и передадите один файл с исходным кодом (как мы уже делали в разделе «Написание и запуск программы на Rust» Главы 1), сборщик считает этот файл ящиком. Ящики могут содержать звенья, и звенья могут быть определены в других файлах, которые собираются вместе с ящиком, как мы увидим в следующих разделах.

-

Ящик может быть одним из двух видов: двоичный ящик или библиотечный ящик. Бинарные ящики — это программы, которые вы можете собрать в исполняемые файлы, которые вы можете запускать, например программу приказной строки или сервер. У каждого двоичного ящика должна быть функция с именем main, которая определяет, что происходит при запуске исполняемого файла. Все ящики, которые мы создали до сих пор, были двоичными ящиками.

-

Библиотечные ящики не имеют функции main и не собираются в исполняемый файл. Вместо этого они определяют возможность, предназначенную для совместного использования другими делами. Например, ящик rand, который мы использовали в Главе 2 обеспечивает возможность, которая порождает случайные числа. В большинстве случаев, когда Rustaceans говорят «ящик», они имеют в виду библиотечный ящик, и они используют «ящик» взаимозаменяемо с общей подходом программирования «библиотека».

-

Корневой звено ящика — это исходный файл, из которого сборщик Ржавчина начинает собирать корневой звено вашего ящика (мы подробно объясним звенья в разделе «Определение звеньев для управления видимости и закрытости»).

-

Дополнение — это набор из одного или нескольких ящиков, предоставляющий набор возможности. Дополнение содержит файл Cargo.toml, в котором описывается, как собирать эти ящики. На самом деле Cargo — это дополнение, содержащий двоичный ящик для средства приказной строки, который вы использовали для создания своего кода. Дополнение Cargo также содержит библиотечный ящик, от которого зависит двоичный ящик. Другие дела тоже могут зависеть от библиотечного ящика Cargo, чтобы использовать ту же логику, что и средство приказной строки Cargo.

-

Дополнение может содержать сколько угодно двоичных ящиков, но не более одного библиотечного ящика. Дополнение должен содержать хотя бы один ящик, библиотечный или двоичный.

-

Давайте пройдёмся по тому, что происходит, когда мы создаём дополнение. Сначала введём приказ cargo new:

-
$ cargo new my-project
-     Created binary (application) `my-project` package
-$ ls my-project
-Cargo.toml
-src
-$ ls my-project/src
-main.rs
-
-

После того, как мы запустили 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: каждый файл будет отдельным двоичным ящиком.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch07-02-defining-modules-to-control-scope-and-privacy.html b/rustbook-ru/book/ch07-02-defining-modules-to-control-scope-and-privacy.html deleted file mode 100644 index 0e434ae68..000000000 --- a/rustbook-ru/book/ch07-02-defining-modules-to-control-scope-and-privacy.html +++ /dev/null @@ -1,322 +0,0 @@ - - - - - - Определение звеньев для управления областью действия и тайностью - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

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

-

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

-
backyard
-├── Cargo.lock
-├── Cargo.toml
-└── src
-    ├── garden
-    │   └── vegetables.rs
-    ├── garden.rs
-    └── main.rs
-
-

Файл корневого звена ящика в нашем случае src/main.rs, и его содержимое:

-

Файл: src/main.rs

-
use crate::garden::vegetables::Asparagus;
-
-pub mod garden;
-
-fn main() {
-    let plant = Asparagus {};
-    println!("I'm growing {plant:?}!");
-}
-

Строка pub mod garden; говорит сборщику о подключении кода, найденном в src/garden.rs:

-

Файл: src/garden.rs

-
pub mod vegetables;
-

А здесь pub mod vegetables; указывает на подключаемый код в src/garden/vegetables.rs. Этот код:

-
#[derive(Debug)]
-pub struct Asparagus {}
-

Теперь давайте рассмотрим подробности этих правил и отобразим их в действии!

-

Объединение связанного кода в звенах

-

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

-

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

-

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

-

Чтобы внутренне выстроить

-

ящик подобно тому, как работает настоящий ресторан, можно согласовать размещение функций во вложенных звенах. Создадим новую библиотеку (библиотечный ящик) с именем restaurant выполнив приказ cargo new restaurant --lib; затем вставим код из приложения 7-1 в 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.

-
crate
- └── front_of_house
-     ├── hosting
-     │   ├── add_to_waitlist
-     │   └── seat_at_table
-     └── serving
-         ├── take_order
-         ├── serve_order
-         └── take_payment
-
-

Приложение 7-2: Древо звеньев для программы из Приложения 7-1

-

Это дерево показывает, как некоторые из звеньев вкладываются друг в друга; например, hosting находится внутри front_of_house. Дерево также показывает, что некоторые звенья являются братьями (siblings) друг для друга, то есть они определены в одном звене; hosting и serving это братья которые определены внутри front_of_house. Если звено A содержится внутри звена B, мы говорим, что звено A является потомком (child) звена B, а звено B является родителем (parent) звена A. Обратите внимание, что родителем всего дерева звеньев является неявный звено с именем crate.

-

Дерево звеньев может напомнить вам дерево папок файловой системы на компьютере; это очень удачное сравнение! По подобию с папкими в файловой системе, мы используется звенья для согласования кода. И так же, как нам надо искать файлы в папких на компьютере, нам требуется способ поиска нужных звеньев.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html b/rustbook-ru/book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html deleted file mode 100644 index a3670d4ef..000000000 --- a/rustbook-ru/book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html +++ /dev/null @@ -1,439 +0,0 @@ - - - - - - Пути для ссылки на элемент в дереве звеньев - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Пути для ссылки на элемент в дереве звеньев

-

Чтобы показать 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

-
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();
-}
-

Приложение 7-3. Вызов функции 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.

-
$ cargo build
-   Compiling restaurant v0.1.0 (file:///projects/restaurant)
-error[E0603]: module `hosting` is private
- --> src/lib.rs:9:28
-  |
-9 |     crate::front_of_house::hosting::add_to_waitlist();
-  |                            ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported
-  |                            |
-  |                            private module
-  |
-note: the module `hosting` is defined here
- --> src/lib.rs:2:5
-  |
-2 |     mod hosting {
-  |     ^^^^^^^^^^^
-
-error[E0603]: module `hosting` is private
-  --> src/lib.rs:12:21
-   |
-12 |     front_of_house::hosting::add_to_waitlist();
-   |                     ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported
-   |                     |
-   |                     private module
-   |
-note: the module `hosting` is defined here
-  --> src/lib.rs:2:5
-   |
-2  |     mod hosting {
-   |     ^^^^^^^^^^^
-
-For more information about this error, try `rustc --explain E0603`.
-error: could not compile `restaurant` (lib) due to 2 previous errors
-
-

Приложение 7-4. Ошибки сборки при сборке кода из приложения 7-3

-

Сообщения об ошибках говорят о том, что звено hosting является закрытым. Другими словами, у нас есть правильные пути к звену hosting и функции add_to_waitlist, но Ржавчина не позволяет нам использовать их, потому что у него нет доступа к закрытым разделам. В Ржавчина все элементы (функции, способы, устройства, перечисления, звенья и постоянные значения) по умолчанию являются закрытыми для родительских звеньев. Если вы хотите сделать элемент, например функцию или устройство, закрытым, вы помещаете его в звено.

-

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

-

В Ржавчина решили, что система звеньев должна исполняться таким образом, чтобы по умолчанию скрывать подробности выполнения. Таким образом, вы знаете, какие части внутреннего кода вы можете изменять не нарушая работы внешнего кода. Тем не менее, Ржавчина даёт нам возможность открывать внутренние части кода дочерних звеньев для внешних звеньев-предков, используя ключевое слово pub, чтобы сделать элемент общедоступным.

-

Раскрываем закрытые пути с помощью ключевого слова pub

-

Давайте вернёмся к ошибке в приложении 7-4, которая говорит, что звено hosting является закрытым. Мы хотим, чтобы функция eat_at_restaurant из родительского звена имела доступ к функции add_to_waitlist в дочернем звене, поэтому мы помечаем звено hosting ключевым словом pub, как показано в приложении 7-5.

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub 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();
-}
-

Приложение 7-5. Объявление звена hosting как pub для его использования из eat_at_restaurant

-

К сожалению, код в приложении 7-5 всё ещё приводит к ошибке, как показано в приложении 7-6.

-
$ cargo build
-   Compiling restaurant v0.1.0 (file:///projects/restaurant)
-error[E0603]: function `add_to_waitlist` is private
- --> src/lib.rs:9:37
-  |
-9 |     crate::front_of_house::hosting::add_to_waitlist();
-  |                                     ^^^^^^^^^^^^^^^ private function
-  |
-note: the function `add_to_waitlist` is defined here
- --> src/lib.rs:3:9
-  |
-3 |         fn add_to_waitlist() {}
-  |         ^^^^^^^^^^^^^^^^^^^^
-
-error[E0603]: function `add_to_waitlist` is private
-  --> src/lib.rs:12:30
-   |
-12 |     front_of_house::hosting::add_to_waitlist();
-   |                              ^^^^^^^^^^^^^^^ private function
-   |
-note: the function `add_to_waitlist` is defined here
-  --> src/lib.rs:3:9
-   |
-3  |         fn add_to_waitlist() {}
-   |         ^^^^^^^^^^^^^^^^^^^^
-
-For more information about this error, try `rustc --explain E0603`.
-error: could not compile `restaurant` (lib) due to 2 previous errors
-
-

Приложение 7-6: Ошибки сборки при сборке кода в приложении 7-5

-

Что произошло? Добавление ключевого слова pub перед mod hosting сделало звено общедоступным. После этого изменения, если мы можем получить доступ к звену front_of_house, то мы можем получить доступ к звену hosting. Но содержимое звена hosting всё ещё является закрытым: превращение звена в общедоступный звено не делает его содержимое общедоступным. Ключевое слово pub позволяет внешнему коду в звенах-предках обращаться только к звену, без доступа ко внутреннему коду. Поскольку звенья являются дополнениями, мы мало что можем сделать, просто сделав звено общедоступным; нам нужно пойти дальше и сделать один или несколько элементов в звене общедоступными.

-

Ошибки в приложении 7-6 говорят, что функция add_to_waitlist является закрытой. Правила закрытости применяются к устройствам, перечислениям, функциям и способам, также как и к звенам.

-

Давайте также сделаем функцию add_to_waitlist общедоступной, добавив ключевое слово pub перед её определением, как показано в приложении 7-7.

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub mod hosting {
-        pub 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();
-}
-

Приложение 7-7. Добавление ключевого слова pub к mod hosting и к fn add_to_waitlist позволяет нам вызывать функцию из eat_at_restaurant

-

Теперь код собирается! Чтобы понять, почему добавление ключевого слова pub позволяет нам использовать эти пути для add_to_waitlist в соответствии с правилами закрытости, давайте рассмотрим абсолютный и относительный пути.

-

В случае абсолютного пути мы начинаем с crate, корня дерева звеньев нашего ящика. Звено front_of_house определён в корневом звене ящика. Хотя front_of_house не является общедоступным, но поскольку функция eat_at_restaurant определена в том же звене, что и front_of_house (то есть, eat_at_restaurant и front_of_house являются потомками одного родителя), мы можем ссылаться на front_of_house из eat_at_restaurant. Далее идёт звено hosting, помеченный как pub. Мы можем получить доступ к родительскому звену звена hosting, поэтому мы можем получить доступ и к hosting. Наконец, функция add_to_waitlist помечена как pub, и так как мы можем получить доступ к её родительскому звену, то вызов этой функции разрешён!

-

В случае относительного пути логика такая же как для абсолютного пути, за исключением первого шага: вместо того, чтобы начинать с корневого звена ящика, путь начинается с front_of_house. Звено front_of_house определён в том же звене, что и eat_at_restaurant, поэтому относительный путь, начинающийся с звена, в котором определена eat_at_restaurant тоже работает. Тогда, по причине того, что hosting и add_to_waitlist помечены как pub, остальная часть пути работает и вызов этой функции разрешён!

-

Если вы собираетесь предоставить общий доступ к своему библиотечному ящику, чтобы другие дела могли использовать ваш код, ваш общедоступный API — это ваш договор с пользователями вашего ящика, определяющий, как они могут взаимодействовать с вашим кодом. Есть много соображений по поводу управления изменениями в вашем общедоступном API, чтобы сделать необременительным для людей зависимость от вашего ящика. Эти соображения выходят за рамки этой книги; если вам важна эта тема, см. The Ржавчина API Guidelines.

-
-

Лучшие опытов для дополнений с двоичным и библиотечным ящиками

-

Мы упоминали, что дополнение может содержать как корневой звено двоичного ящика src/main.rs, так и корневой звено библиотечного ящика src/lib.rs, и оба ящика будут по умолчанию иметь имя дополнения. Как правило, дополнения с таким образцом, содержащим как библиотечный, так и двоичный ящик, будут иметь достаточно кода в двоичном ящике, чтобы запустить исполняемый файл, который вызывает код из библиотечного ящика. Это позволяет другим делам извлечь выгоду из большей части возможности, предоставляемой дополнением, поскольку код библиотечного ящика можно использовать совместно.

-

Дерево звеньев должно быть определено в src/lib.rs. Затем любые общедоступные элементы можно использовать в двоичном ящике, начав пути с имени дополнения. Двоичный ящик становится пользователем библиотечного ящика точно так же, как полностью внешний ящик использует библиотечный ящик: он может использовать только общедоступный API. Это поможет вам разработать хороший API; вы не только автор, но и пользователь!

-

В Главе 12 мы эту опыт согласования кода с помощью окно выводаной программы, которая будет содержать как двоичный, так и библиотечный ящики.

-
-

Начинаем относительный путь с помощью super

-

Также можно построить относительные пути, которые начинаются в родительском звене, используя ключевое слово super в начале пути. Это похоже на правила написания начала пути файловой системы ... Использование super позволяет нам сослаться на элемент, который, как мы знаем, находится в родительском звене, что может упростить переупорядочение дерева звеньев, чем когда звено тесно связан с родителем, но родитель может когда-нибудь быть перемещён в другое место в дереве звеньев.

-

Рассмотрим код в приложении 7-8, где расчитывается случаей, в которой повар исправляет неправильный заказ и лично приносит его клиенту. Функция fix_incorrect_order вызывает функцию deliver_order, определённую в родительском звене, указывая путь к deliver_order, начинающийся с super:

-

Файл: src/lib.rs

-
fn deliver_order() {}
-
-mod back_of_house {
-    fn fix_incorrect_order() {
-        cook_order();
-        super::deliver_order();
-    }
-
-    fn cook_order() {}
-}
-

Приложение 7-8: Вызов функции с использованием относительного пути, начинающегося с super

-

Функция fix_incorrect_order находится в звене back_of_house, поэтому мы можем использовать super для перехода к родительскому звену звена back_of_house, который в этом случае является crate, корневым звеном. В этом звене мы ищем deliver_order и находим его. Успех! Мы думаем, что звено back_of_house и функция deliver_order, скорее всего, останутся в тех же родственных отношениях друг с другом, и должны будут перемещены вместе, если мы решим ресогласовать дерево звеньев ящика. Поэтому мы использовали super, чтобы в будущем у нас было меньше мест для обновления кода, если этот код будет перемещён в другой звено.

-

Делаем общедоступными устройства и перечисления

-

Мы также можем использовать pub для обозначения устройств и перечислений как общедоступных, но есть несколько дополнительных подробностей использования pub со устройствами и перечислениями. Если мы используем pub перед определением устройства, мы делаем устройство общедоступной, но поля устройства по-прежнему остаются закрытыми. Мы можем сделать каждое поле общедоступным или нет в каждом определенном случае. В приложении 7-9 мы определили общедоступную устройство back_of_house::Breakfast с общедоступным полем toast и с закрытым полем seasonal_fruit. Это расчитывает случай в ресторане, когда клиент может выбрать вид хлеба, который подаётся с едой, а шеф-повар решает какие фрукты сопровождают еду, исходя из того, что сезонно и что есть в наличии. Доступные фрукты быстро меняются, поэтому клиенты не могут выбирать фрукты или даже увидеть, какие фрукты они получат.

-

Файл: src/lib.rs

-
mod back_of_house {
-    pub struct Breakfast {
-        pub toast: String,
-        seasonal_fruit: String,
-    }
-
-    impl Breakfast {
-        pub fn summer(toast: &str) -> Breakfast {
-            Breakfast {
-                toast: String::from(toast),
-                seasonal_fruit: String::from("peaches"),
-            }
-        }
-    }
-}
-
-pub fn eat_at_restaurant() {
-    // Order a breakfast in the summer with Rye toast
-    let mut meal = back_of_house::Breakfast::summer("Rye");
-    // Change our mind about what bread we'd like
-    meal.toast = String::from("Wheat");
-    println!("I'd like {} toast please", meal.toast);
-
-    // The next line won't compile if we uncomment it; we're not allowed
-    // to see or modify the seasonal fruit that comes with the meal
-    // meal.seasonal_fruit = String::from("blueberries");
-}
-

Приложение 7-9: Устройства с общедоступными и закрытыми полями

-

Поскольку поле toast в устройстве back_of_house::Breakfast является открытым, то в функции eat_at_restaurant можно писать и читать поле toast, используя точечную наставление. Обратите внимание, что мы не можем использовать поле seasonal_fruit в eat_at_restaurant, потому что seasonal_fruit является закрытым. Попробуйте убрать примечания с последней строки для значения поля seasonal_fruit, чтобы увидеть какую ошибку вы получите!

-

Также обратите внимание, что поскольку back_of_house::Breakfast имеет закрытое поле, то устройства должна предоставить открытую сопряженную функцию, которая создаёт образец Breakfast (мы назвали её summer). Если Breakfast не имел бы такой функции, мы бы не могли создать образец Breakfast внутри eat_at_restaurant, потому что мы не смогли бы установить значение закрытого поля seasonal_fruit в функции eat_at_restaurant.

-

В отличии от устройства, если мы сделаем общедоступным перечисление, то все его исходы будут общедоступными. Нужно только указать pub перед ключевым словом enum, как показано в приложении 7-10.

-

Файл: src/lib.rs

-
mod back_of_house {
-    pub enum Appetizer {
-        Soup,
-        Salad,
-    }
-}
-
-pub fn eat_at_restaurant() {
-    let order1 = back_of_house::Appetizer::Soup;
-    let order2 = back_of_house::Appetizer::Salad;
-}
-

Приложение 7-10. Определяя перечисление общедоступным мы делаем все его исходы общедоступными

-

Поскольку мы сделали общедоступным перечисление Appetizer, то можно использовать исходы Soup и Salad в функции eat_at_restaurant.

-

Перечисления не очень полезны, если их исходы не являются общедоступными: было бы досадно каждый раз определять все исходы перечисления как pub. По этой причине по умолчанию исходы перечислений являются общедоступными. Устройства часто полезны, если их поля не являются общедоступными, поэтому поля устройства следуют общему правилу, согласно которому, всё по умолчанию является закрытым, если не указано pub.

-

Есть ещё одна случаей с pub, которую мы не освещали, и это последняя особенность состоящей из звеньев системы: ключевое слово use. Мы сначала опишем use само по себе, а затем покажем как сочетать pub и use вместе.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html b/rustbook-ru/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html deleted file mode 100644 index fa3a4eae4..000000000 --- a/rustbook-ru/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html +++ /dev/null @@ -1,490 +0,0 @@ - - - - - - Введение путей в область видимости с помощью ключевого слова use - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Подключение путей в область видимости с помощью ключевого слова use

-

Необходимость записывать пути к функциям вызова может показаться неудобной и повторяющейся. В приложении 7-7 независимо от того, выбирали ли мы абсолютный или относительный путь к функции add_to_waitlist , каждый раз, когда мы хотели вызвать add_to_waitlist , нам приходилось также указывать front_of_house и hosting . К счастью, есть способ упростить этот этап: мы можем один раз создать псевдоним на путь при помощи ключевого слова use, а затем использовать более короткое имя везде в области видимости.

-

В приложении 7-11 мы подключили звено crate::front_of_house::hosting в область действия функции eat_at_restaurant, поэтому нам достаточно только указать hosting::add_to_waitlist для вызова функции add_to_waitlist внутри eat_at_restaurant.

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub mod hosting {
-        pub fn add_to_waitlist() {}
-    }
-}
-
-use crate::front_of_house::hosting;
-
-pub fn eat_at_restaurant() {
-    hosting::add_to_waitlist();
-}
-

Приложение 7-11. Добавление звена в область видимости при помощи use

-

Добавление use и пути в область видимости подобно созданию символической ссылки в файловой системе. С добавлением use crate::front_of_house::hosting в корневой звено ящика, hosting становится допустимым именем в этой области, как если бы звено hosting был определён в корневом звене ящика. Пути, подключённые в область видимости с помощью use, также проверяются на доступность, как и любые другие пути.

-

Обратите внимание, что use создаёт псевдоним только для той именно области, в которой это объявление use и находится. В приложении 7-12 функция eat_at_restaurant перемещается в новый дочерний звено с именем customer, область действия которого отличается от области действия указания use, поэтому тело функции не будет собираться:

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub mod hosting {
-        pub fn add_to_waitlist() {}
-    }
-}
-
-use crate::front_of_house::hosting;
-
-mod customer {
-    pub fn eat_at_restaurant() {
-        hosting::add_to_waitlist();
-    }
-}
-

Приложение 7-12. Указание use применяется только в её собственной области видимости

-

Ошибка сборщика показывает, что данный псевдоним не может использоваться в звене customer:

-
$ cargo build
-   Compiling restaurant v0.1.0 (file:///projects/restaurant)
-error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
-  --> src/lib.rs:11:9
-   |
-11 |         hosting::add_to_waitlist();
-   |         ^^^^^^^ use of undeclared crate or module `hosting`
-   |
-help: consider importing this module through its public re-export
-   |
-10 +     use crate::hosting;
-   |
-
-warning: unused import: `crate::front_of_house::hosting`
- --> src/lib.rs:7:5
-  |
-7 | use crate::front_of_house::hosting;
-  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  |
-  = note: `#[warn(unused_imports)]` on by default
-
-For more information about this error, try `rustc --explain E0433`.
-warning: `restaurant` (lib) generated 1 warning
-error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted
-
-

Обратите внимание, что есть также предупреждение о том, что use не используется в своей области! Чтобы решить эту неполадку, можно переместить use в звено customer, или же можно сослаться на псевдоним в родительском звене с помощью super::hosting в дочернем звене customer.

-

Создание идиоматических путей с use

-

В приложении 7-11 вы могли бы задаться вопросом, почему мы указали use crate::front_of_house::hosting, а затем вызвали hosting::add_to_waitlist внутри eat_at_restaurant вместо указания в use полного пути прямо до функции add_to_waitlist для получения того же итога, что в приложении 7-13.

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub mod hosting {
-        pub fn add_to_waitlist() {}
-    }
-}
-
-use crate::front_of_house::hosting::add_to_waitlist;
-
-pub fn eat_at_restaurant() {
-    add_to_waitlist();
-}
-

Приложение 7-13: Добавление функции add_to_waitlist в область видимости с use неидиоматическим способом

-

Хотя приложениеи 7-11 и 7-13 выполняют одну и ту же задачу, приложение 7-11 является идиоматическим способом подключения функции в область видимости с помощью use. Подключение родительского звена функции в область видимости при помощи use означает, что мы должны указывать родительский звено при вызове функции. Указание родительского звена при вызове функции даёт понять, что функция не определена местно, но в то же время сводя к уменьшению повторение полного пути. В коде приложения 7-13 не ясно, где именно определена add_to_waitlist.

-

С другой стороны, при подключении устройств, перечислений и других элементов используя use, идиоматически правильным будет указывать полный путь. Приложение 7-14 показывает идиоматический способ подключения устройства встроенной библиотеки HashMap в область видимости двоичного ящика.

-

Файл: src/main.rs

-
use std::collections::HashMap;
-
-fn main() {
-    let mut map = HashMap::new();
-    map.insert(1, 2);
-}
-

Приложение 7-14. Включение HashMap в область видимости идиоматическим способом

-

За этой идиомой нет веской причины: это просто соглашение, которое появилось само собой. Люди привыкли читать и писать код на Ржавчина таким образом.

-

Исключением из этой идиомы является случай, когда мы подключаем два элемента с одинаковыми именами в область видимости используя указанию use — Ржавчина просто не позволяет этого сделать. Приложение 7-15 показывает, как подключить в область действия два вида с одинаковыми именами Result, но из разных родительских звеньев и как на них ссылаться.

-

Файл: src/lib.rs

-
use std::fmt;
-use std::io;
-
-fn function1() -> fmt::Result {
-    // --snip--
-    Ok(())
-}
-
-fn function2() -> io::Result<()> {
-    // --snip--
-    Ok(())
-}
-

Приложение 7-15. Для включения двух видов с одинаковыми именами в одну область видимости необходимо использовать их родительские звенья.

-

Как видите, использование имени родительских звеньев позволяет различать два вида Result. Если бы вместо этого мы указали use std::fmt::Result и use std::io::Result, мы бы имели два вида Result в одной области видимости, и Ржавчина не смог бы понять какой из двух Result мы имели в виду, когда нашёл бы их употребление в коде.

-

Предоставление новых имён с помощью ключевого слова as

-

Есть другое решение сбоев добавления двух видов с одинаковыми именами в одну и ту же область видимости используя use: после пути можно указать as и новое местное имя (псевдоним) для вида. Приложение 7-16 показывает как по-другому написать код из приложения 7-15, путём переименования одного из двух видов Result используя as.

-

Файл: src/lib.rs

-
use std::fmt::Result;
-use std::io::Result as IoResult;
-
-fn function1() -> Result {
-    // --snip--
-    Ok(())
-}
-
-fn function2() -> IoResult<()> {
-    // --snip--
-    Ok(())
-}
-

Приложение 7-16: Переименование вида, когда он включён в область видимости с помощью ключевого слова as

-

Во второй указания use мы выбрали новое имя IoResult для вида std::io::Result, которое теперь не будет враждовать с видом Result из std::fmt, который также подключён в область видимости. Приложения 7-15 и 7-16 считаются идиоматичными, поэтому выбор за вами!

-

Реэкспорт имён с pub use

-

Когда мы подключаем имя в область видимости, используя ключевое слово use, то имя, доступное в новой области видимости, является закрытым. Чтобы позволить коду, который вызывает наш код, ссылаться на это имя, как если бы оно было определено в области видимости данного кода, можно объединить pub и use. Этот способ называется реэкспортом (re-exporting), потому что мы подключаем элемент в область видимости, но также делаем этот элемент доступным для подключения в других областях видимости.

-

Приложение 7-17 показывает код из приложения 7-11, где use в корневом звене заменено на pub use.

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub mod hosting {
-        pub fn add_to_waitlist() {}
-    }
-}
-
-pub use crate::front_of_house::hosting;
-
-pub fn eat_at_restaurant() {
-    hosting::add_to_waitlist();
-}
-

Приложение 7-17. Предоставление имени для использования любым кодом из новой области при помощи pub use

-

До этого изменения внешний код должен был вызывать функцию add_to_waitlist , используя путь restaurant::front_of_house::hosting::add_to_waitlist() . Теперь, когда это объявление pub use повторно экспортировало звено hosting из корневого звена, внешний код теперь может использовать вместо него путь restaurant::hosting::add_to_waitlist() .

-

Реэкспорт полезен, когда внутренняя устройства вашего кода отличается от того, как программисты, вызывающие ваш код, думают о предметной области. Например, по подобию с рестораном люди, управляющие им, думают о «передней части дома» и «задней части дома». Но клиенты, посещающие ресторан, вероятно, не будут думать о частях ресторана в таких понятиях. Используя pub use , мы можем написать наш код с одной устройством, но сделать общедоступной другую устройство. Благодаря этому наша библиотека хорошо согласована для программистов, работающих над библиотекой, и для программистов, вызывающих библиотеку. Мы рассмотрим ещё один пример pub use и его влияние на документацию вашего ящика в разделе «Экспорт удобного общедоступного API с pub use» Главы 14.

-

Использование внешних дополнений

-

В Главе 2 мы запрограммировали игру угадывания числа, где использовался внешний дополнение с именем rand для создания случайного числа. Чтобы использовать rand в нашем деле, мы добавили эту строку в Cargo.toml:

- -

Файл: Cargo.toml

-
rand = "0.8.5"
-
-

Добавление rand в качестве зависимости в Cargo.toml указывает Cargo загрузить дополнение rand и все его зависимости из crates.io и сделать rand доступным для нашего дела.

-

Затем, чтобы подключить определения rand в область видимости нашего дополнения, мы добавили строку use начинающуюся с названия дополнения rand и списка элементов, которые мы хотим подключить в область видимости. Напомним, что в разделе "Создание случайного числа" Главы 2, мы подключили особенность Rng в область видимости и вызвали функцию rand::thread_rng:

-
use std::io;
-use rand::Rng;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {guess}");
-}
-

Члены сообщества Ржавчина сделали много дополнений доступными на ресурсе crates.io, и добавление любого из них в ваш дополнение включает в себя одни и те же шаги: добавить внешние дополнения в файл Cargo.toml вашего дополнения, использовать use для подключения элементов внешних дополнений в область видимости.

-

Обратите внимание, что обычная библиотека std также является ящиком, внешним по отношению к нашему дополнению. Поскольку обычная библиотека поставляется с языком Rust, нам не нужно изменять Cargo.toml для подключения std. Но нам нужно ссылаться на неё при помощи use, чтобы добавить элементы оттуда в область видимости нашего дополнения. Например, с HashMap мы использовали бы эту строку:

-
#![allow(unused)]
-fn main() {
-use std::collections::HashMap;
-}
-

Это абсолютный путь, начинающийся с std, имени ящика встроенной библиотеки.

-

Использование вложенных путей для уменьшения длинных списков use

-

Если мы используем несколько элементов, определённых в одном ящике или в том же звене, то перечисление каждого элемента в отдельной строке может занимать много вертикального пространства в файле. Например, эти две указания use используются в программе угадывания числа (приложение 2-4) для подключения элементов из std в область видимости:

-

Файл: src/main.rs

-
use rand::Rng;
-// --snip--
-use std::cmp::Ordering;
-use std::io;
-// --snip--
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {guess}");
-
-    match guess.cmp(&secret_number) {
-        Ordering::Less => println!("Too small!"),
-        Ordering::Greater => println!("Too big!"),
-        Ordering::Equal => println!("You win!"),
-    }
-}
-

Вместо этого, мы можем использовать вложенные пути, чтобы добавить эти элементы в область видимости одной строкой. Мы делаем это, как показано в приложении 7-18, указывая общую часть пути, за которой следуют два двоеточия, а затем фигурные скобки вокруг списка тех частей продолжения пути, которые отличаются.

-

Файл: src/main.rs

-
use rand::Rng;
-// --snip--
-use std::{cmp::Ordering, io};
-// --snip--
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    let guess: u32 = guess.trim().parse().expect("Please type a number!");
-
-    println!("You guessed: {guess}");
-
-    match guess.cmp(&secret_number) {
-        Ordering::Less => println!("Too small!"),
-        Ordering::Greater => println!("Too big!"),
-        Ordering::Equal => println!("You win!"),
-    }
-}
-

Приложение 7-18. Указание вложенного пути для добавления нескольких элементов с одинаковым приставкой в область видимости

-

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

-

Можно использовать вложенный путь на любом уровне, что полезно при объединении двух указаний use, которые имеют общую часть пути. Например, в приложении 7-19 показаны две указания use: одна подключает std::io, а другая подключает std::io::Write в область видимости.

-

Файл: src/lib.rs

-
use std::io;
-use std::io::Write;
-

Приложение 7-19: Две указания use, в которых один путь является частью другого

-

Общей частью этих двух путей является std::io, и это полный первый путь. Чтобы объединить эти два пути в одной указания use, мы можем использовать ключевое слово self во вложенном пути, как показано в приложении 7-20.

-

Файл: src/lib.rs

-
use std::io::{self, Write};
-

Приложение 7-20: Объединение путей из Приложения 7-19 в одну указанию use

-

Эта строка подключает std::io и std::io::Write в область видимости.

-

Оператор * (glob)

-

Если мы хотим включить в область видимости все общедоступные элементы, определённые в пути, мы можем указать этот путь, за которым следует оператор *:

-
#![allow(unused)]
-fn main() {
-use std::collections::*;
-}
-

Эта указание use подключает все открытые элементы из звена std::collections в текущую область видимости. Будьте осторожны при использовании оператора *! Он может усложнить понимание, какие имена находятся в области видимости и где были определены имена, используемые в вашей программе.

-

Оператор * часто используется при проверке для подключения всего что есть в звене tests; мы поговорим об этом в разделе "Как писать проверки" Главы 11. Оператор * также иногда используется как часть образца самостоятельного подключения (prelude): смотрите документацию по встроенной библиотеке для получения дополнительной сведений об этом образце.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch07-05-separating-modules-into-different-files.html b/rustbook-ru/book/ch07-05-separating-modules-into-different-files.html deleted file mode 100644 index 88aebde8d..000000000 --- a/rustbook-ru/book/ch07-05-separating-modules-into-different-files.html +++ /dev/null @@ -1,278 +0,0 @@ - - - - - - Separating Modules into Different Files - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Разделение звеньев на разные файлы

-

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

-

Например, давайте начнём с кода из приложения 7-17, в котором было несколько звеньев ресторана. Мы будем извлекать звенья в файлы вместо того, чтобы определять все звенья в корневом звене ящика. В нашем случае корневой звено ящика - src/lib.rs, но это разделение также работает и с двоичными ящиками, у которых корневой звено ящика — src/main.rs.

-

Сначала мы извлечём звено front_of_house в свой собственный файл. Удалите код внутри фигурных скобок для звена front_of_house, оставив только объявление mod front_of_house;, так что теперь src/lib.rs содержит код, показанный в приложении 7-21. Обратите внимание, что этот исход не собирается, пока мы не создадим файл src/front_of_house.rs из приложении 7-22.

-

Файл: src/lib.rs

-
mod front_of_house;
-
-pub use crate::front_of_house::hosting;
-
-pub fn eat_at_restaurant() {
-    hosting::add_to_waitlist();
-}
-

Приложение 7-21. Объявление звена front_of_house, чьё содержимое будет в src/front_of_house.rs

-

Затем поместим код, который был в фигурных скобках, в новый файл с именем src/front_of_house.rs, как показано в приложении 7-22. Сборщик знает, что нужно искать в этом файле, потому что он наткнулся в корневом звене ящика на объявление звена с именем front_of_house.

-

Файл: src/front_of_house.rs

-
pub mod hosting {
-    pub fn add_to_waitlist() {}
-}
-

Приложение 7-22. Определение содержимого звена front_of_house в файле src/front_of_house.rs

-

Обратите внимание, что вам нужно только один раз загрузить файл с помощью объявления mod в вашем дереве звеньев. Как только сборщик узнает, что файл является частью дела (и узнает, где в дереве звеньев находится код из-за того, куда вы помеисполнения указанию mod), другие файлы в вашем деле должны ссылаться на код загруженного файла, используя путь к месту, где он был объявлен, как описано в разделе «Пути для ссылки на элемент в дереве звеньев». Другими словами, mod — это не действие «включения», которую вы могли видеть в других языках программирования.

-

Далее мы извлечём звено hosting в его собственный файл. Этап немного отличается, потому что hosting является дочерним звеном для front_of_house, а не корневого звена. Мы поместим файл для hosting в новый папка, который будет назван по имени его предка в дереве звеньев, в данном случае это src/front_of_house/.

-

Чтобы начать перенос hosting, мы меняем src/front_of_house.rs так, чтобы он содержал только объявление звена hosting:

-

Файл: src/front_of_house.rs

-
pub mod hosting;
-

Затем мы создаём папка src/front_of_house и файл hosting.rs, в котором будут определения, сделанные в звене hosting:

-

Файл: src/front_of_house/hosting.rs

-
pub fn add_to_waitlist() {}
-

Если вместо этого мы поместим hosting.rs в папка src, сборщик будет думать, что код в hosting.rs это звено hosting, объявленный в корне ящика, а не объявленный как дочерний звено front_of_house. Правила сборщика для проверки какие файлы содержат код каких звеньев предполагают, что папки и файлы точно соответствуют дереву звеньев.

-
-

Иные пути к файлам

-

До сих пор мы рассматривали наиболее идиоматические пути к файлам, используемые сборщиком Rust, но Ржавчина также поддерживает и старый исполнение пути к файлу. Для звена с именем front_of_house, объявленного в корневом звене ящика, сборщик будет искать код звена в:

-
    -
  • src/front_of_house.rs (что мы рассматривали)
  • -
  • src/front_of_house/mod.rs (старый исполнение, всё ещё поддерживаемый путь)
  • -
-

Для звена с именем hosting, который является подзвеном front_of_house, сборщик будет искать код звена в:

-
    -
  • src/front_of_house/hosting.rs (что мы рассматривали)
  • -
  • src/front_of_house/hosting/mod.rs (старый исполнение, всё ещё поддерживаемый путь)
  • -
-

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

-

Основным недостатком исполнения, в котором используются файлы с именами mod.rs, является то, что в вашем деле может оказаться много файлов с именами mod.rs, что может привести к путанице, если вы одновременно откроете их в редакторе.

-
-

Мы перенесли код каждого звена в отдельный файл, а дерево звеньев осталось прежним. Вызовы функций в eat_at_restaurant будут работать без каких-либо изменений, несмотря на то, что определения находятся в разных файлах. Этот способ позволяет перемещать звенья в новые файлы по мере увеличения их размеров.

-

Обратите внимание, что указание pub use crate::front_of_house::hosting в src/lib.rs также не изменилась, и use не влияет на то, какие файлы собираются как часть ящика. Ключевое слово mod объявляет звенья, и Ржавчина ищет в файле с тем же именем, что и у звена, код, который входит в этот звено.

-

Итог

-

Rust позволяет разбить дополнение на несколько ящиков и ящик - на звенья, так что вы можете ссылаться на элементы, определённые в одном звене, из другого звена. Это можно делать при помощи указания абсолютных или относительных путей. Эти пути можно добавить в область видимости указанием use, поэтому вы можете пользоваться более короткими путями для многократного использования элементов в этой области видимости. Код звена по умолчанию является закрытым, но можно сделать определения общедоступными, добавив ключевое слово pub.

-

В следующей главе мы рассмотрим некоторые собрания устройств данных из встроенной библиотеки, которые вы можете использовать в своём правильноно согласованном коде.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch08-00-common-collections.html b/rustbook-ru/book/ch08-00-common-collections.html deleted file mode 100644 index 0f047b70e..000000000 --- a/rustbook-ru/book/ch08-00-common-collections.html +++ /dev/null @@ -1,239 +0,0 @@ - - - - - - Общие собрания - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Общие собрания

-

Обычная библиотека содержит несколько полезных устройств данных, которые называются собраниями. Большая часть других видов данных представляют собой хранение определенного значения, но особенностью собраний является хранение множества однотипных значений. В отличии от массива или упорядоченного ряда данные собраний хранятся в куче, а это значит, что размер собрания может быть неизвестен в мгновение сборки программы. Он может изменяться (увеличиваться, уменьшаться) во время работы программы. Каждый вид собраний имеет свои возможности и отличается по производительности, так что выбор именно собрания зависит от случаи и является умением разработчика, вырабатываемым со временем. В этой главе будет рассмотрено несколько собраний:

-
    -
  • Вектор (vector) - позволяет нам сохранять различное количество последовательно хранящихся значений,
  • -
  • Строка (string) - это последовательность символов. Мы же упоминали вид String ранее, но в данной главе мы поговорим о нем подробнее.
  • -
  • Хеш-таблица (hash map) - собрание которая позволяет хранить перечень ассоциаций значения с ключом (перечень пар ключ:значение). Является именно выполнением более общей устройства данных называемой map.
  • -
-

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

-

Мы обсудим как создавать и обновлять векторы, строки и хеш-таблицы, а также объясним что делает каждую из них особенной.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch08-01-vectors.html b/rustbook-ru/book/ch08-01-vectors.html deleted file mode 100644 index 60f2439f9..000000000 --- a/rustbook-ru/book/ch08-01-vectors.html +++ /dev/null @@ -1,368 +0,0 @@ - - - - - - Хранение списков значений с векторами - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Хранение списков значений в векторах

-

Первым видом собрания, который мы разберём, будет Vec<T>, также известный как вектор (vector). Векторы позволяют хранить более одного значения в единой устройстве данных, хранящей элементы в памяти один за другим. Векторы могут хранить данные только одного вида. Их удобно использовать, когда нужно хранить список элементов, например, список текстовых строк из файла, или список цен товаров в корзине покупок.

-

Создание нового вектора

-

Чтобы создать новый пустой вектор, мы вызываем функцию Vec::new, как показано в приложении 8-1.

-
fn main() {
-    let v: Vec<i32> = Vec::new();
-}
-

Приложение 8-1: Создание нового пустого вектора для хранения значений вида i32

-

Обратите внимание, что здесь мы добавили изложение вида. Поскольку мы не вставляем никаких значений в этот вектор, Ржавчина не знает, какие элементы мы собираемся хранить. Это важный мгновение. Векторы выполнены с использованием обобщённых видов; мы рассмотрим, как использовать обобщённые виды с вашими собственными видами в Главе 10. А пока знайте, что вид Vec<T>, предоставляемый встроенной библиотекой, может хранить любой вид. Когда мы создаём новый вектор для хранения определенного вида, мы можем указать этот вид в угловых скобках. В приложении 8-1 мы сообщили Rust, что Vec<T> в v будет хранить элементы вида i32.

-

Чаще всего вы будете создавать Vec<T> с начальными значениями и Ржавчина может определить вид сохраняемых вами значений, но иногда вам всё же придётся указывать изложение вида. Для удобства Ржавчина предоставляет макрос vec!, который создаст новый вектор, содержащий заданные вами значения. В приложении 8-2 создаётся новый Vec<i32>, который будет хранить значения 1, 2 и 3. Числовым видом является i32, потому что это вид по умолчанию для целочисленных значений, о чём упоминалось в разделе “Виды данных” Главы 3.

-
fn main() {
-    let v = vec![1, 2, 3];
-}
-

Приложение 8-2: Создание нового вектора, содержащего значения

-

Поскольку мы указали начальные значения вида i32, Ржавчина может сделать вывод, что вид переменной v это Vec<i32> и изложение вида здесь не нужна. Далее мы посмотрим как изменять вектор.

-

Изменение вектора

-

Чтобы создать вектор и затем добавить к нему элементы, можно использовать способ push показанный в приложении 8-3.

-
fn main() {
-    let mut v = Vec::new();
-
-    v.push(5);
-    v.push(6);
-    v.push(7);
-    v.push(8);
-}
-

Приложение 8-3: Использование способа push для добавления значений в вектор

-

Как и с любой переменной, если мы хотим изменить её значение, нам нужно сделать её изменяемой с помощью ключевого слова mut, что обсуждалось в Главе 3. Все числа которые мы помещаем в вектор имеют вид i32 по этому Ржавчина с лёгкостью выводит вид вектора, по этой причине нам не нужна здесь изложение вида вектора Vec<i32>.

-

Чтение данных вектора

-

Есть два способа сослаться на значение, хранящееся в векторе: с помощью порядкового указателя или способа get . В следующих примерах для большей ясности мы указали виды значений, возвращаемых этими функциями.

-

В приложении 8-4 показаны оба способа доступа к значению в векторе: либо с помощью правил написания упорядочевания и с помощью способа get.

-
fn main() {
-    let v = vec![1, 2, 3, 4, 5];
-
-    let third: &i32 = &v[2];
-    println!("The third element is {third}");
-
-    let third: Option<&i32> = v.get(2);
-    match third {
-        Some(third) => println!("The third element is {third}"),
-        None => println!("There is no third element."),
-    }
-}
-

Приложение 8-4. Использование правил написания упорядочевания и способа get для доступа к элементу в векторе

-

Обратите внимание здесь на пару подробностей. Мы используем значение порядкового указателя 2 для получения третьего элемента: векторы упорядочеваются начиная с нуля. Указывая & и [] мы получаем ссылку на элемент по указанному порядковому указателю. Когда мы используем способ get содержащего порядковый указатель, переданный в качестве переменной, мы получаем вид Option<&T>, который мы можем проверить с помощью match.

-

Причина, по которой Ржавчина предоставляет два способа ссылки на элемент, заключается в том, что вы можете выбрать, как программа будет себя вести, когда вы попытаетесь использовать значение порядкового указателя за пределами ряда существующих элементов. В качестве примера давайте посмотрим, что происходит, когда у нас есть вектор из пяти элементов, а затем мы пытаемся получить доступ к элементу с порядковым указателем 100 с помощью каждого способа, как показано в приложении 8-5.

-
fn main() {
-    let v = vec![1, 2, 3, 4, 5];
-
-    let does_not_exist = &v[100];
-    let does_not_exist = v.get(100);
-}
-

Приложение 8-5. Попытка доступа к элементу с порядковым указателем 100 в векторе, содержащем пять элементов

-

Когда мы запускаем этот код, первая строка с &v[100] вызовет панику программы, потому что происходит попытка получить ссылку на несуществующий элемент. Такой подход лучше всего использовать, когда вы хотите, чтобы ваша программа со сбоем завершила работу при попытке доступа к элементу за пределами вектора.

-

Когда способу get передаётся порядковый указатель, который находится за пределами вектора, он без паники возвращает None. Вы могли бы использовать такой подход, если доступ к элементу за пределами рядавектора происходит время от времени при обычных обстоятельствах. Тогда ваш код будет иметь логику для обработки наличия Some(&element) или None, как обсуждалось в Главе 6. Например, порядковый указательможет исходить от человека, вводящего число. Если пользователь случайно введёт слишком большое число, то программа получит значение None и у вас будет возможность сообщить пользователю, сколько элементов находится в текущем векторе, и дать ему возможность ввести допустимое значение. Такое поведение было бы более дружелюбным для пользователя, чем внезапный сбой программы из-за опечатки!

-

Когда у программы есть действительная ссылка, borrow checker (средство проверки заимствований), обеспечивает соблюдение правил владения и заимствования (описанные в Главе 4), чтобы обеспечить, что эта ссылка и любые другие ссылки на содержимое вектора остаются действительными. Вспомните правило, которое гласит, что у вас не может быть изменяемых и неизменяемых ссылок в одной и той же области. Это правило применяется в приложении 8-6, где мы храним неизменяемую ссылку на первый элемент вектора и затем пытаемся добавить элемент в конец вектора. Данная программа не будет работать, если мы также попробуем сослаться на данный элемент позже в функции:

-
fn main() {
-    let mut v = vec![1, 2, 3, 4, 5];
-
-    let first = &v[0];
-
-    v.push(6);
-
-    println!("The first element is: {first}");
-}
-

Приложение 8-6. Попытка добавить некоторый элемент в вектор, в то время когда есть ссылка на элемент вектора

-

Сборка этого кода приведёт к ошибке:

-
$ cargo run
-   Compiling collections v0.1.0 (file:///projects/collections)
-error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
- --> src/main.rs:6:5
-  |
-4 |     let first = &v[0];
-  |                  - immutable borrow occurs here
-5 |
-6 |     v.push(6);
-  |     ^^^^^^^^^ mutable borrow occurs here
-7 |
-8 |     println!("The first element is: {first}");
-  |                                     ------- immutable borrow later used here
-
-For more information about this error, try `rustc --explain E0502`.
-error: could not compile `collections` (bin "collections") due to 1 previous error
-
-

Код в приложении 8-6 может выглядеть так, как будто он должен работать. Почему ссылка на первый элемент должна заботиться об изменениях в конце вектора? Эта ошибка возникает из-за особенности того, как работают векторы: поскольку векторы размещают значения в памяти друг за другом, добавление нового элемента в конец вектора может потребовать выделения новой памяти и повторения старых элементов в новое пространство, если нет достаточного места, чтобы разместить все элементы друг за другом там, где в данный мгновение хранится вектор. В этом случае ссылка на первый элемент будет указывать на освобождённую память. Правила заимствования предотвращают попадание программ в такую случай.

-
-

Примечание: Дополнительные сведения о выполнения вида Vec<T> смотрите в разделе "The Rustonomicon".

-
-

Перебор значений в векторе

-

Для доступа к каждому элементу вектора по очереди, мы повторяем все элементы, вместо использования порядковых указателей для доступа к одному за раз. В приложении 8-7 показано, как использовать цикл for для получения неизменяемых ссылок на каждый элемент в векторе значений вида i32 и их вывода.

-
fn main() {
-    let v = vec![100, 32, 57];
-    for i in &v {
-        println!("{i}");
-    }
-}
-

Приложение 8-7. Печать каждого элемента векторе, при помощи повторения по элементам вектора с помощью цикла for

-

Мы также можем повторять изменяемые ссылки на каждый элемент изменяемого вектора, чтобы вносить изменения во все элементы. Цикл for в приложении 8-8 добавит 50 к каждому элементу.

-
fn main() {
-    let mut v = vec![100, 32, 57];
-    for i in &mut v {
-        *i += 50;
-    }
-}
-

Приложение 8-8. Повторение и изменение элементов вектора по изменяемым ссылкам

-

Чтобы изменить значение на которое ссылается изменяемая ссылка, мы должны использовать оператор разыменования ссылки * для получения значения по ссылке в переменной i прежде чем использовать оператор +=. Мы поговорим подробнее об операторе разыменования в разделе “Следование по указателю к значению с помощью оператора разыменования” Главы 15.

-

Перебор вектора, будь то неизменяемый или изменяемый, безопасен из-за правил проверки заимствования. Если бы мы попытались вставить или удалить элементы в телах цикла for в приложениях 8-7 и 8-8, мы бы получили ошибку сборщика, подобную той, которую мы получили с кодом в приложении 8-6. Ссылка на вектор, содержащийся в цикле for, предотвращает одновременную изменение всего вектора.

-

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

-

Векторы могут хранить значения только одинакового вида. Это может быть неудобно; определённо могут быть случаи когда надо хранить список элементов разных видов. К счастью, исходы перечисления определены для одного и того же вида перечисления, поэтому, когда нам нужен один вид для представления элементов разных видов, мы можем определить и использовать перечисление!

-

Например, мы хотим получить значения из строки в электронной таблице где некоторые столбцы строки содержат целые числа, некоторые числа с плавающей точкой, а другие - строковые значения. Можно определить перечисление, исходы которого будут содержать разные виды значений и тогда все исходы перечисления будут считаться одним и тем же видом: видом самого перечисления. Затем мы можем создать вектор для хранения этого перечисления и, в конечном счёте, для хранения различных видов. Мы покажем это в приложении 8-9.

-
fn main() {
-    enum SpreadsheetCell {
-        Int(i32),
-        Float(f64),
-        Text(String),
-    }
-
-    let row = vec![
-        SpreadsheetCell::Int(3),
-        SpreadsheetCell::Text(String::from("blue")),
-        SpreadsheetCell::Float(10.12),
-    ];
-}
-

Приложение 8-9: Определение enum для хранения значений разных видов в одном векторе

-

Rust должен знать, какие виды будут в векторе во время сборки, чтобы точно знать сколько памяти в куче потребуется для хранения каждого элемента. Мы также должны чётко указать, какие виды разрешены в этом векторе. Если бы Ржавчина позволял вектору содержать любой вид, то был бы шанс что один или несколько видов вызовут ошибки при выполнении действий над элементами вектора. Использование перечисления вместе с выражением match означает, что во время сборки Ржавчина заверяет, что все возможные случаи будут обработаны, как обсуждалось в главе 6.

-

Если вы не знаете исчерпывающий набор видов, которые программа получит во время выполнения для хранения в векторе, то техника использования перечисления не сработает. Вместо этого вы можете использовать особенность-предмет, который мы рассмотрим в главе 17.

-

Теперь, когда мы обсудили некоторые из наиболее распространённых способов использования векторов, обязательно ознакомьтесь с документацией по API вектора, чтобы узнать о множестве полезных способов, определённых в Vec<T> встроенной библиотеки. Например, в дополнение к способу push, существует способ pop, который удаляет и возвращает последний элемент.

-

Удаление элементов из вектора

-

Подобно устройствам struct, вектор высвобождает свою память когда выходит из области видимости, что показано в приложении 8-10.

-
fn main() {
-    {
-        let v = vec![1, 2, 3, 4];
-
-        // do stuff with v
-    } // <- v goes out of scope and is freed here
-}
-

Приложение 8-10. Показано как удаляется вектор и его элементы

-

Когда вектор удаляется, всё его содержимое также удаляется: удаление вектора означает и удаление значений, которые он содержит. Средство проверки заимствования заверяет, что любые ссылки на содержимое вектора используются только тогда, когда сам вектор действителен.

-

Давайте перейдём к следующему виду собрания: String!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch08-02-strings.html b/rustbook-ru/book/ch08-02-strings.html deleted file mode 100644 index 9b4478341..000000000 --- a/rustbook-ru/book/ch08-02-strings.html +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - Хранение закодированного текста UTF-8 со строками - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Хранение закодированного текста UTF-8 в строках

-

Мы говорили о строках в главе 4, но сейчас мы рассмотрим их более подробно. Новички в Ржавчина обычно застревают на строках из-за сочетания трёх причин: склонность Ржавчина сборщика к выявлению возможных ошибок, более сложная устройства данных чем считают многие программисты и UTF-8. Эти обстоятельства объединяются таким образом, что направление может показаться сложной, если вы пришли из других языков программирования.

-

Полезно обсуждать строки в среде собраний, потому что строки выполнены в виде набора байтов, плюс некоторые способы для обеспечения полезной возможности, когда эти байты преобразуются как текст. В этом разделе мы поговорим об действиех над String таких как создание, обновление и чтение, которые есть у каждого вида собраний. Мы также обсудим какими особенностями String отличается от других собраний, а именно каким образом упорядочевание в String осложняется различием между тем как люди и компьютеры преобразуют данные заключённые в String.

-

Что же такое строка?

-

Сначала мы определим, что мы подразумеваем под понятием строка (string). В Ржавчина есть только один строковый вид в ядре языка - срез строки str, обычно используемый в заимствованном виде как &str. В Главе 4 мы говорили о срезах строк, string slices, которые являются ссылками на некоторые строковые данные в кодировке UTF-8. Например, строковые записи хранятся в двоичном файле программы и поэтому являются срезами строк.

-

Вид String предоставляемый встроенной библиотекой Rust, не встроен в ядро языка и является расширяемым, изменяемым, владеющим, строковым видом в UTF-8 кодировке. Когда Rustaceans говорят о "строках" то, они обычно имеют в виду виды String или строковые срезы &str, а не просто один из них. Хотя этот раздел в основном посвящён String, оба вида усиленно используются в встроенной библиотеке Rust, оба, и String и строковые срезы, кодируются в UTF-8.

-

Создание новых строк

-

Многие из тех же действий, которые доступны Vec<T> , доступны также в String, потому что String в действительности выполнен как обёртка вокруг вектора байтов с некоторыми дополнительными заверениями, ограничениями и возможностями. Примером функции, которая одинаково работает с Vec<T> и String, является функция new, создающая новый образец вида, и показана в Приложении 8-11.

-
fn main() {
-    let mut s = String::new();
-}
-

Приложение 8-11. Создание новой пустой String строки

-

Эта строка создаёт новую пустую строковую переменную с именем s, в которую мы можем затем загрузить данные. Часто у нас есть некоторые начальные данные, которые мы хотим назначить строке. Для этого мы используем способ to_string доступный для любого вида, который выполняет особенность Display, как у строковых записей. Приложение 8-12 показывает два примера.

-
fn main() {
-    let data = "initial contents";
-
-    let s = data.to_string();
-
-    // the method also works on a literal directly:
-    let s = "initial contents".to_string();
-}
-

Приложение 8-12: Использование способа to_string для создания образца вида String из строкового записи

-

Эти выражения создают строку с initial contents.

-

Мы также можем использовать функцию String::from для создания String из строкового записи. Код приложения 8-13 является эквивалентным коду из приложения 8-12, который использует функцию to_string:

-
fn main() {
-    let s = String::from("initial contents");
-}
-

Приложение 8-13: Использование функции String::from для создания образца вида String из строкового записи

-

Поскольку строки используются для очень многих вещей, можно использовать множество API для строк, предоставляющих множество возможностей. Некоторые из них могут показаться избыточными, но все они занимаются своим делом! В данном случае String::from и to_string делают одно и тоже, поэтому выбор зависит от исполнения который вам больше импонирует.

-

Запомните, что строки хранятся в кодировке UTF-8, поэтому можно использовать любые правильно кодированные данные в них, как показано в приложении 8-14:

-
fn main() {
-    let hello = String::from("السلام عليكم");
-    let hello = String::from("Dobrý den");
-    let hello = String::from("Hello");
-    let hello = String::from("שלום");
-    let hello = String::from("नमस्ते");
-    let hello = String::from("こんにちは");
-    let hello = String::from("안녕하세요");
-    let hello = String::from("你好");
-    let hello = String::from("Olá");
-    let hello = String::from("Здравствуйте");
-    let hello = String::from("Hola");
-}
-

Приложение 8-14: Хранение приветствий в строках на разных языках

-

Все это допустимые String значения.

-

Обновление строковых данных

-

Строка String может увеличиваться в размере, а её содержимое может меняться, по подобию как содержимое Vec<T> при вставке в него большего количества данных. Кроме того, можно использовать оператор + или макрос format! для объединения значений String.

-

Присоединение к строке с помощью push_str и push

-

Мы можем нарастить String используя способ push_str который добавит в исходное значение новый строковый срез, как показано в приложении 8-15.

-
fn main() {
-    let mut s = String::from("foo");
-    s.push_str("bar");
-}
-

Приложение 8-15. Добавление среза строки к String с помощью способа push_str

-

После этих двух строк кода s будет содержать foobar. Способ push_str принимает строковый срез, потому что мы не всегда хотим владеть входным свойствоом. Например, код в приложении 8-16 показывает исход, когда будет не желательно поведение, при котором мы не сможем использовать s2 после его добавления к содержимому значения переменной s1.

-
fn main() {
-    let mut s1 = String::from("foo");
-    let s2 = "bar";
-    s1.push_str(s2);
-    println!("s2 is {s2}");
-}
-

Приложение 8-16: Использование среза строки после добавления её содержимого к другой String

-

Если способ push_str стал бы владельцем переменнойs2, мы не смогли бы напечатать его значение в последней строке. Однако этот код работает так, как мы ожидали!

-

Способ push принимает один символ в качестве свойства и добавляет его к String. В приложении 8-17 показан код, добавляющий букву “l” к String используя способ push.

-
fn main() {
-    let mut s = String::from("lo");
-    s.push('l');
-}
-

Приложение 8-17: Добавление одного символа в String значение используя push

-

В итоге s будет содержать lol.

-

Объединение строк с помощью оператора + или макроса format!

-

Часто хочется объединять две существующие строки. Один из возможных способов — это использование оператора + из приложения 8-18:

-
fn main() {
-    let s1 = String::from("Hello, ");
-    let s2 = String::from("world!");
-    let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
-}
-

Приложение 8-18: Использование оператора + для объединения двух значений String в новое String значение

-

Строка s3 будет содержать Hello, world!. Причина того, что s1 после добавления больше недействительна и причина, по которой мы использовали ссылку на s2 имеют отношение к ярлыке вызываемого способа при использовании оператора +. Оператор + использует способ add, чья ярлык выглядит примерно так:

-
fn add(self, s: &str) -> String {
-

В встроенной библиотеке вы увидите способ add определённым с использованием обобщённых и связанных видов. Здесь мы видим ярлык с определенными видами, заменяющими обобщённый, что происходит когда вызывается данный способ со значениями String. Мы обсудим обобщённые виды в Главе 10. Эта ярлык даёт нам ключ для понимания особенностей оператора +.

-

Во-первых, перед s2 мы видим &, что означает что мы складываем ссылку на вторую строку с первой строкой. Это происходит из-за свойства s в функции add: мы можем добавить только &str к String; мы не можем сложить два значения String. Но подождите — вид &s2 это &String, а не &str, как определён второй свойство в add. Так почему код в приложении 8-18 собирается?

-

Причина, по которой мы можем использовать &s2 в вызове add заключается в том, что сборщик может принудительно привести (coerce) переменная вида &String к виду &str. Когда мы вызываем способ add в Ржавчина используется принудительное приведение (deref coercion), которое превращает &s2 в &s2[..]. Мы подробно обсудим принудительное приведение в Главе 15. Так как add не забирает во владение свойство s, s2 по прежнему будет действительной строкой String после применения действия.

-

Во-вторых, как можно видеть в ярлыке, add забирает во владение self, потому что self не имеет &. Это означает, что s1 в приложении 8-18 будет перемещён в вызов add и больше не будет действителен после этого вызова. Не смотря на то, что код let s3 = s1 + &s2; выглядит как будто он воспроизведет обе строки и создаёт новую, эта указание в действительности забирает во владение переменную s1, присоединяет к ней повтор содержимого s2, а затем возвращает владение итогом. Другими словами, это выглядит как будто код создаёт множество повторов, но это не так; данная выполнение более эффективна, чем повторение.

-

Если нужно объединить несколько строк, поведение оператора + становится громоздким:

-
fn main() {
-    let s1 = String::from("tic");
-    let s2 = String::from("tac");
-    let s3 = String::from("toe");
-
-    let s = s1 + "-" + &s2 + "-" + &s3;
-}
-

Здесь переменная s будет содержать tic-tac-toe. С множеством символов + и " становится трудно понять, что происходит. Для более сложного соединения строк можно использовать макрос format!:

-
fn main() {
-    let s1 = String::from("tic");
-    let s2 = String::from("tac");
-    let s3 = String::from("toe");
-
-    let s = format!("{s1}-{s2}-{s3}");
-}
-

Этот код также устанавливает переменную s в значение tic-tac-toe. Макрос format! работает тем же способом что макрос println!, но вместо вывода на экран возвращает вид String с содержимым. Исполнение кода с использованием format! значительно легче читается, а также код, созданный макросом format!, использует ссылки, а значит не забирает во владение ни один из его свойств.

-

Упорядочевание в строках

-

Доступ к отдельным символам в строке, при помощи ссылки на них по порядковому указателю, является допустимой и распространённой действием во многих других языках программирования. Тем не менее, если вы попытаетесь получить доступ к частям String, используя правила написания упорядочевания в Rust, то вы получите ошибку. Рассмотрим неверный код в приложении 8-19.

-
fn main() {
-    let s1 = String::from("hello");
-    let h = s1[0];
-}
-

Приложение 8-19: Попытка использовать правила написания порядкового указателя со строкой

-

Этот код приведёт к следующей ошибке:

-
$ cargo run
-   Compiling collections v0.1.0 (file:///projects/collections)
-error[E0277]: the type `str` cannot be indexed by `{integer}`
- --> src/main.rs:3:16
-  |
-3 |     let h = s1[0];
-  |                ^ string indices are ranges of `usize`
-  |
-  = help: the trait `SliceIndex<str>` is not implemented for `{integer}`, which is required by `String: Index<_>`
-  = note: you can use `.chars().nth()` or `.bytes().nth()`
-          for more information, see chapter 8 in The Book: <https://doc.rust-lang.org/book/ch08-02-strings.html#indexing-into-strings>
-  = help: the trait `SliceIndex<[_]>` is implemented for `usize`
-  = help: for that trait implementation, expected `[_]`, found `str`
-  = note: required for `String` to implement `Index<{integer}>`
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `collections` (bin "collections") due to 1 previous error
-
-

Ошибка и примечание говорит, что в Ржавчина строки не поддерживают упорядочевание. Но почему так? Чтобы ответить на этот вопрос, нужно обсудить то, как Ржавчина хранит строки в памяти.

-

Внутреннее представление

-

Вид String является оболочкой над видом Vec<u8>. Давайте посмотрим на несколько закодированных правильным образом в UTF-8 строк из примера приложения 8-14. Начнём с этой:

-
fn main() {
-    let hello = String::from("السلام عليكم");
-    let hello = String::from("Dobrý den");
-    let hello = String::from("Hello");
-    let hello = String::from("שלום");
-    let hello = String::from("नमस्ते");
-    let hello = String::from("こんにちは");
-    let hello = String::from("안녕하세요");
-    let hello = String::from("你好");
-    let hello = String::from("Olá");
-    let hello = String::from("Здравствуйте");
-    let hello = String::from("Hola");
-}
-

В этом случае len будет 4, что означает вектор, хранит строку "Hola" длиной 4 байта. Каждая из этих букв занимает 1 байт при кодировании в UTF-8. Но как насчёт следующей строки? (Обратите внимание, что эта строка начинается с заглавной кириллической "З", а не цифры 3.)

-
fn main() {
-    let hello = String::from("السلام عليكم");
-    let hello = String::from("Dobrý den");
-    let hello = String::from("Hello");
-    let hello = String::from("שלום");
-    let hello = String::from("नमस्ते");
-    let hello = String::from("こんにちは");
-    let hello = String::from("안녕하세요");
-    let hello = String::from("你好");
-    let hello = String::from("Olá");
-    let hello = String::from("Здравствуйте");
-    let hello = String::from("Hola");
-}
-

Отвечая на вопрос, какова длина строки, вы можете ответить 12. Однако ответ Ржавчина - 24, что равно числу байт, необходимых для кодирования «Здравствуйте» в UTF-8, так происходит, потому что каждое одиночное значение Unicode символа в этой строке занимает 2 байта памяти. Следовательно, порядковый указательпо байтам строки не всегда бы соответствовал действительному одиночному Unicode значению. Для отображения рассмотрим этот недопустимый код Rust:

-
let hello = "Здравствуйте";
-let answer = &hello[0];
-

Каким должно быть значение переменной answer? Должно ли оно быть значением первой буквы З? При кодировке в UTF-8, первый байт значения З равен 208, а второй - 151, поэтому значение в answer на самом деле должно быть 208, но само по себе 208 не является действительным символом. Возвращение 208, скорее всего не то, что хотел бы получить пользователь: ведь он ожидает первую букву этой строки; тем не менее, это единственный байт данных, который в Ржавчина доступен по порядковому указателю 0. Пользователи обычно не хотят получить значение байта, даже если строка содержит только латинские буквы: если &"hello"[0] было бы допустимым кодом, который вернул значение байта, то он вернул бы 104, а не h.

-

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

-

Байты, одиночные значения и кластеры графем! Боже мой!

-

Ещё один мгновение, касающийся UTF-8, заключается в том, что на самом деле существует три способа рассмотрения строк с точки зрения Rust: как байты, как одиночные значения и как кластеры графем (самая близкая вещь к тому, что мы назвали бы буквами).

-

Если посмотреть на слово языка хинди «नमस्ते», написанное в транскрипции Devanagari, то оно хранится как вектор значений u8 который выглядит следующим образом:

-
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
-224, 165, 135]
-
-

Эти 18 байт являются именно тем, как компьютеры в конечном итоге сохранят в памяти эту строку. Если мы посмотрим на 18 байт как на одиночные Unicode значения, которые являются Ржавчина видом char, то байты будут выглядеть так:

-
['न', 'म', 'स', '्', 'त', 'े']
-
-

Здесь есть шесть значений вида char, но четвёртый и шестой являются не буквами: они диакритики, особые обозначения которые не имеют смысла сами по себе. Наконец, если мы посмотрим на байты как на кластеры графем, то получим то, что человек назвал бы словом на хинди состоящем из четырёх букв:

-
["न", "म", "स्", "ते"]
-
-

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

-

Последняя причина, по которой Ржавчина не позволяет нам упорядочивать String для получения символов является то, что программисты ожидают, что действия упорядочевания всегда имеют постоянное время (O(1)) выполнения. Но невозможно обеспечить такую производительность для String, потому что Ржавчина понадобилось бы пройтись по содержимому от начала до порядкового указателя, чтобы определить, сколько было действительных символов.

-

Срезы строк

-

Упорядочевание строк часто является плохой мыслью, потому что не ясно каким должен быть возвращаемый вид такой действия: байтовым значением, символом, кластером графем или срезом строки. Поэтому Ржавчина просит вас быть более определенным, если действительно требуется использовать порядковые указатели для создания срезов строк.

-

Вместо упорядочевания с помощью числового порядкового указателя [], вы можете использовать оператор ряда[] при создании среза строки в котором содержится указание на то, срез каких байтов надо делать:

-
#![allow(unused)]
-fn main() {
-let hello = "Здравствуйте";
-
-let s = &hello[0..4];
-}
-

Здесь переменная s будет вида &str который содержит первые 4 байта строки. Ранее мы упоминали, что каждый из этих символов был по 2 байта, что означает, что s будет содержать "Зд".

-

Что бы произошло, если бы мы использовали &hello[0..1]? Ответ: Ржавчина бы запаниковал во время выполнения точно так же, как если бы обращались к недействительному порядковому указателю в векторе:

-
$ cargo run
-   Compiling collections v0.1.0 (file:///projects/collections)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s
-     Running `target/debug/collections`
-thread 'main' panicked at src/main.rs:4:19:
-byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

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

-

Способы для перебора строк

-

Лучший способ работать с отрывками строк — чётко указать, нужны ли вам символы или байты. Для отдельных одиночных значений в Юникоде используйте способ chars. Вызов chars у "Зд" выделяет и возвращает два значения вида char, и вы можете выполнить повторение по итогу для доступа к каждому элементу:

-
#![allow(unused)]
-fn main() {
-for c in "Зд".chars() {
-    println!("{c}");
-}
-}
-

Код напечатает следующее:

-
З
-д
-
-

Способ bytes возвращает каждый байт, который может быть подходящим в другой предметной области:

-
#![allow(unused)]
-fn main() {
-for b in "Зд".bytes() {
-    println!("{b}");
-}
-}
-

Этот код выведет четыре байта, составляющих эту строку:

-
208
-151
-208
-180
-
-

Но делая так, обязательно помните, что валидные одиночные Unicode значения могут состоять более чем из одного байта.

-

Извлечение кластеров графем из строк, как в случае с языком хинди, является сложным, поэтому эта возможность не предусмотрена встроенной библиотекой. На crates.io есть доступные библиотеки, если Вам нужен данный возможности.

-

Строки не так просты

-

Подводя итог, становится ясно, что строки сложны. Различные языки программирования выполняют различные исходы того, как представить эту сложность для программиста. В Ржавчина решили сделать правильную обработку данных String поведением по умолчанию для всех программ Rust, что означает, что программисты должны заранее продумать обработку UTF-8 данных. Этот соглашение раскрывает большую сложность строк, чем в других языках программирования, но это предотвращает от необходимости обрабатывать ошибки, связанные с не-ASCII символами которые могут появиться в ходе разработки позже.

-

Хорошая новость состоит в том что обычная библиотека предлагает множество полезных возможностей, построенных на основе видов String и &str, чтобы помочь правильно обрабатывать эти сложные случаи. Обязательно ознакомьтесь с документацией для полезных способов, таких как contains для поиска в строке и replace для замены частей строки другой строкой.

-

Давайте переключимся на что-то немного менее сложное: HashMap!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch08-03-hash-maps.html b/rustbook-ru/book/ch08-03-hash-maps.html deleted file mode 100644 index 8bdcd2ecc..000000000 --- a/rustbook-ru/book/ch08-03-hash-maps.html +++ /dev/null @@ -1,361 +0,0 @@ - - - - - - Хранение ключей со связанными значениями в HashMap - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Хранение ключей со связанными значениями в HashMap

-

Последняя собрание, которую мы рассмотрим, будет hash map (хеш-карта). Вид HashMap<K, V> хранит ключи вида K на значения вида V. Данная устройства согласует и хранит данные с помощью функции хеширования. Во множестве языков программирования выполнена данная устройства, но часто с разными наименованиями: такими как hash, map, object, hash table, dictionary или ассоциативный массив.

-

Хеш-карты полезны, когда нужно искать данные не используя порядковый указатель, как это например делается в векторах, а с помощью ключа, который может быть любого вида. Например, в игре вы можете отслеживать счёт каждой приказы в хеш-карте, в которой каждый ключ - это название приказы, а значение - счёт приказы. Имея имя приказы, вы можете получить её счёт из хеш-карты.

-

В этом разделе мы рассмотрим основной API хеш-карт. Остальной набор полезных функций скрывается в объявлении вида HashMap<K, V>. Как и прежде, советуем обратиться к документации по встроенной библиотеке для получения дополнительной сведений.

-

Создание новой хеш-карты

-

Создать пустую хеш-карту можно с помощью new, а добавить в неё элементы - с помощью insert. В приложении 8-20 мы отслеживаем счёт двух приказов, синей Blue и жёлтой Yellow. Синяя приказ набрала 10 очков, а жёлтая приказ - 50.

-
fn main() {
-    use std::collections::HashMap;
-
-    let mut scores = HashMap::new();
-
-    scores.insert(String::from("Blue"), 10);
-    scores.insert(String::from("Yellow"), 50);
-}
-

Приложение 8-20: Создание новой хеш-карты и вставка в неё пары ключей и значений

-

Обратите внимание, что нужно сначала указать строку use std::collections::HashMap; для её подключения из собраний встроенной библиотеки. Из трёх собраний данная является наименее используемой, поэтому она не подключается в область видимости функцией самостоятельного подключения (prelude). Хеш-карты также имеют меньшую поддержку со стороны встроенной библиотеки; например, нет встроенного макроса для их разработки.

-

Подобно векторам, хеш-карты хранят свои данные в куче. Здесь вид HashMap имеет в качестве вида ключей String, а в качестве вида значений вид i32. Как и векторы, HashMap однородны: все ключи должны иметь одинаковый вид и все значения должны иметь тоже одинаковый вид.

-

Доступ к данным в HashMap

-

Мы можем получить значение из HashMap по ключу, с помощью способа get, как показано в приложении 8-21.

-
fn main() {
-    use std::collections::HashMap;
-
-    let mut scores = HashMap::new();
-
-    scores.insert(String::from("Blue"), 10);
-    scores.insert(String::from("Yellow"), 50);
-
-    let team_name = String::from("Blue");
-    let score = scores.get(&team_name).copied().unwrap_or(0);
-}
-

Приложение 8-21: Доступ к очкам приказы "Blue", которые хранятся в хеш-карте

-

Здесь score будет иметь количество очков, связанное с приказом "Blue", итог будет 10. Способ get возвращает Option<&V>; если для какого-то ключа нет значения в HashMap, get вернёт None. Из-за такого подхода программе следует обрабатывать Option, вызывая copied для получения Option<i32> вместо Option<&i32>, затем unwrap_or для установки score в ноль, если scores не содержит данных по этому ключу.

-

Мы можем перебирать каждую пару ключ/значение в HashMap таким же образом, как мы делали с векторами, используя цикл for:

-
fn main() {
-    use std::collections::HashMap;
-
-    let mut scores = HashMap::new();
-
-    scores.insert(String::from("Blue"), 10);
-    scores.insert(String::from("Yellow"), 50);
-
-    for (key, value) in &scores {
-        println!("{key}: {value}");
-    }
-}
-

Этот код будет печатать каждую пару в произвольном порядке:

-
Yellow: 50
-Blue: 10
-
-

Хеш-карты и владение

-

Для видов, которые выполняют особенность Copy, например i32, значения повторяются в HashMap. Для значений со владением, таких как String, значения будут перемещены в хеш-карту и она станет владельцем этих значений, как показано в приложении 8-22.

-
fn main() {
-    use std::collections::HashMap;
-
-    let field_name = String::from("Favorite color");
-    let field_value = String::from("Blue");
-
-    let mut map = HashMap::new();
-    map.insert(field_name, field_value);
-    // field_name and field_value are invalid at this point, try using them and
-    // see what compiler error you get!
-}
-

Приложение 8-22: Показывает, что ключи и значения находятся во владении HashMap, как только они были вставлены

-

Мы не можем использовать переменные field_name и field_value после того, как их значения были перемещены в HashMap вызовом способа insert.

-

Если мы вставим в HashMap ссылки на значения, то они не будут перемещены в HashMap. Значения, на которые указывают ссылки, должны быть действительными хотя бы до тех пор, пока хеш-карта действительна. Мы поговорим подробнее об этих вопросах в разделе "Валидация ссылок при помощи времён жизни" главы 10.

-

Обновление данных в HashMap

-

Хотя количество ключей и значений может увеличиваться в HashMap, каждый ключ может иметь только одно значение, связанное с ним в один мгновение времени (обратное утверждение неверно: приказы "Blue" и "Yellow" могут хранить в хеш-карте scores одинаковое количество очков, например 10).

-

Когда вы хотите изменить данные в хеш-карте, необходимо решить, как обрабатывать случай, когда ключ уже имеет назначенное значение. Можно заменить старое значение новым, полностью пренебрегая старое. Можно сохранить старое значение и пренебрегать новое, или добавлять новое значение, если только ключ ещё не имел значения. Или можно было бы объединить старое значение и новое значение. Давайте посмотрим, как сделать каждый из исходов!

-

Перезапись старых значений

-

Если мы вставим ключ и значение в HashMap, а затем вставим тот же ключ с новым значением, то старое значение связанное с этим ключом, будет заменено на новое. Даже несмотря на то, что код в приложении 8-23 вызывает insert дважды, хеш-карта будет содержать только одну пару ключ/значение, потому что мы вставляем значения для одного и того же ключа - ключа приказы "Blue".

-
fn main() {
-    use std::collections::HashMap;
-
-    let mut scores = HashMap::new();
-
-    scores.insert(String::from("Blue"), 10);
-    scores.insert(String::from("Blue"), 25);
-
-    println!("{scores:?}");
-}
-

Приложение 8-23: Замена значения, хранимого в определенном ключе

-

Код напечатает {"Blue": 25}. Начальное значение 10 было перезаписано.

- -

-

Вставка значения только в том случае, когда ключ не имеет значения

-

Обычно проверяют, существует ли определенный ключ в хеш-карте со значением, а затем предпринимаются следующие действия: если ключ существует в хеш-карте, существующее значение должно оставаться таким, какое оно есть. Если ключ не существует, то вставляют его и значение для него.

-

Хеш-карты имеют для этого особый API, называемый entry , который принимает ключ для проверки в качестве входного свойства. Возвращаемое значение способа entry - это перечисление Entry, с двумя исходами: первый представляет значение, которое может существовать, а второй говорит о том, что значение отсутствует. Допустим, мы хотим проверить, имеется ли ключ и связанное с ним значение для приказы "Yellow". Если хеш-карта не имеет значения для такого ключа, то мы хотим вставить значение 50. То же самое мы хотим проделать и для приказы "Blue". Используем API entry в коде приложения 8-24.

-
fn main() {
-    use std::collections::HashMap;
-
-    let mut scores = HashMap::new();
-    scores.insert(String::from("Blue"), 10);
-
-    scores.entry(String::from("Yellow")).or_insert(50);
-    scores.entry(String::from("Blue")).or_insert(50);
-
-    println!("{scores:?}");
-}
-

Приложение 8-24: Использование способа entry для вставки значения только в том случае, когда ключ не имеет значения

-

Способ or_insert определён в Entry так, чтобы возвращать изменяемую ссылку на соответствующее значение ключа внутри исхода перечисления Entry, когда этот ключ существует, а если его нет, то вставлять свойство в качестве нового значения этого ключа и возвращать изменяемую ссылку на новое значение. Эта техника намного чище, чем самостоятельное написание логики и, кроме того, она более безопасна и согласуется с правилами заимствования.

-

При выполнении кода приложения 8-24 будет напечатано {"Yellow": 50, "Blue": 10}. Первый вызов способа entry вставит ключ для приказы "Yellow" со значением 50, потому что для жёлтой приказы ещё не имеется значения в HashMap. Второй вызов entry не изменит хеш-карту, потому что для ключа приказы "Blue" уже имеется значение 10.

-

Создание нового значения на основе старого значения

-

Другим распространённым исходом использования хеш-карт является поиск значения по ключу, а затем обновление этого значения на основе старого значения. Например, в приложении 8-25 показан код, который подсчитывает, сколько раз определённое слово встречается в некотором тексте. Мы используем HashMap со словами в качестве ключей и увеличиваем соответствующее слову значение, чтобы отслеживать, сколько раз мы встретили это слово. Если мы впервые встретили слово, то сначала вставляем значение 0.

-
fn main() {
-    use std::collections::HashMap;
-
-    let text = "hello world wonderful world";
-
-    let mut map = HashMap::new();
-
-    for word in text.split_whitespace() {
-        let count = map.entry(word).or_insert(0);
-        *count += 1;
-    }
-
-    println!("{map:?}");
-}
-

Приложение 8-25: Подсчёт количества вхождений слов с использованием хеш-карты, которая хранит слова и счётчики

-

Этот код напечатает {"world": 2, "hello": 1, "wonderful": 1}. Если вы увидите, что пары ключ/значение печатаются в другом порядке, то вспомните, что мы писали в разделы "Доступ к данным в HashMap", что повторение по хеш-карте происходит в произвольном порядке.

-

Способ split_whitespace возвращает повторитель по срезам строки, разделённых пробелам, для строки text. Способ or_insert возвращает изменяемую ссылку (&mut V) на значение ключа. Мы сохраняем изменяемую ссылку в переменной count, для этого, чтобы присвоить переменной значение, необходимо произвести разыменование с помощью звёздочки (*). Изменяемая ссылка удаляется сразу же после выхода из области видимости цикла for, поэтому все эти изменения безопасны и согласуются с правилами заимствования.

-

Функция хеширования

-

По умолчанию HashMap использует функцию хеширования SipHash, которая может противостоять атакам класса отказ в обслуживании, Denial of Service (DoS) с использованием хеш-таблиц siphash. Это не самый быстрый из возможных алгоритмов хеширования, в данном случае производительность идёт на соглашение с обеспечением лучшей безопасности. Если после профилирования вашего кода окажется, что хеш-функция, используемая по умолчанию, очень медленная, вы можете заменить её используя другой hasher. Hasher - это вид, выполняющий особенность BuildHasher. Подробнее о особенностях мы поговорим в Главе 10. Вам совсем не обязательно выполнить свою собственную функцию хеширования; crates.io имеет достаточное количество библиотек, предоставляющих разные выполнения hasher с множеством общих алгоритмов хеширования.

-

Итоги

-

Векторы, строки и хеш-карты предоставят большое количество возможностей для программ, когда необходимо сохранять, получать доступ и изменять данные. Теперь вы готовы решить следующие учебные задания:

-
    -
  • Есть список целых чисел. Создайте функцию, используйте вектор и верните из списка: среднее значение; медиану (значение элемента из середины списка после его сортировки); режиму списка (mode of list, то значение которое встречается в списке наибольшее количество раз; HashMap будет полезна в данном случае).
  • -
  • Преобразуйте строку в кодировку "поросячьей латыни" (Pig Latin). Первая согласная каждого слова перемещается в конец и к ней добавляется окончание "ay", так "first" станет "irst-fay". Слову, начинающемуся на гласную, в конец добавляется "hay" ("apple" становится "apple-hay"). Помните о подробностях работы с кодировкой UTF-8!
  • -
  • Используя хеш-карту и векторы, создайте текстовый внешняя оболочка позволяющий пользователю добавлять имена сотрудников к названию отдела предприятия. Например, "Add Sally to Engineering" или "Add Amir to Sales". Затем позвольте пользователю получить список всех людей из отдела или всех людей в предприятия, отсортированных по отделам в алфавитном порядке.
  • -
-

Документация API встроенной библиотеки описывает способы у векторов, строк и HashMap. Советуем воспользоваться ей при решении упражнений.

-

Потихоньку мы переходим к более сложным программам, в которых действия могут потерпеть неудачу. Наступило наилучшее время для обсуждения обработки ошибок.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch09-00-error-handling.html b/rustbook-ru/book/ch09-00-error-handling.html deleted file mode 100644 index 3e947c5bc..000000000 --- a/rustbook-ru/book/ch09-00-error-handling.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - - Обработка ошибок - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Обработка ошибок

-

Возникновение ошибок в ходе выполнения программ — это суровая действительность в жизни программного обеспечения, поэтому Ржавчина имеет ряд функций для обработки случаев, в которых что-то идёт не так. Во многих случаях Ржавчина требует, чтобы вы признали возможность ошибки и предприняли некоторые действия, прежде чем ваш код будет собран. Это требование делает вашу программу более надёжной, обеспечивая, что вы обнаружите ошибки и обработаете их надлежащим образом, прежде чем развернёте свой код в производственной среде!

-

В Ржавчина ошибки объединяются на две основные разряды: исправимые (recoverable) и неисправимые (unrecoverable). В случае исправимой ошибки, такой как файл не найден, мы, скорее всего, просто хотим сообщить о неполадке пользователю и повторить действие. Неисправимые ошибки всегда являются симптомами изъянов в коде, например, попытка доступа к ячейке за пределами границ массива, и поэтому мы хотим немедленно остановить программу.

-

Большинство языков не различают эти два вида ошибок и обрабатывают оба вида одинаково, используя такие рычаги, как исключения. В Ржавчина нет исключений. Вместо этого он имеет вид Result<T, E> для обрабатываемых (исправимых) ошибок и макрос panic!, который останавливает выполнение, когда программа встречает необрабатываемую (неисправимую) ошибку. Сначала эта глава расскажет про вызов panic!, а потом расскажет о возврате значений Result<T, E>. Кроме того, мы рассмотрим, что нужно учитывать при принятии решения о том, следует ли попытаться исправить ошибку или остановить выполнение.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch09-01-unrecoverable-errors-with-panic.html b/rustbook-ru/book/ch09-01-unrecoverable-errors-with-panic.html deleted file mode 100644 index b97e60097..000000000 --- a/rustbook-ru/book/ch09-01-unrecoverable-errors-with-panic.html +++ /dev/null @@ -1,309 +0,0 @@ - - - - - - Неустранимые ошибки с panic! - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Неустранимые ошибки с макросом panic!

-

Иногда в коде происходят плохие вещи, и вы ничего не можете с этим поделать. В этих случаях у Ржавчина есть макрос panic! В действительностисуществует два способа вызвать панику: путём выполнения действия, которое вызывает панику в нашем коде (например, обращение к массиву за пределами его размера) или путём явного вызова макроса panic!. В обоих случаях мы вызываем панику в нашей программе. По умолчанию паника выводит сообщение об ошибке, раскручивает и очищает обойма вызовов, и завершают работу. С помощью переменной окружения вы также можете заставить Ржавчина отображать обойма вызовов при возникновении паники, чтобы было легче отследить источник паники.

-
-

Раскручивать обойма или прерывать выполнение программы в ответ на панику?

-

По умолчанию, когда происходит паника, программа начинает этап раскрутки обоймы, означающий в Ржавчина проход обратно по обойме вызовов и очистку данных для каждой обнаруженной функции. Тем не менее, этот обратный проход по обойме и очистка порождают много работы. Ржавчина как иное решение предоставляет вам возможность немедленного прерывания (aborting), которое завершает работу программы без очистки.

-

Память, которую использовала программа, должна быть очищена операционной системой. Если в вашем деле нужно насколько это возможно сделать маленьким исполняемый файл, вы можете переключиться с исхода раскрутки обоймы на исход прерывания при панике, добавьте panic = 'abort' в раздел [profile] вашего Cargo.toml файла. Например, если вы хотите прервать панику в режиме исполнения, добавьте это:

-
[profile.release]
-panic = 'abort'
-
-
-

Давайте попробуем вызвать panic! в простой программе:

-

Файл: src/main.rs

-
fn main() {
-    panic!("crash and burn");
-}
-

При запуске программы, вы увидите что-то вроде этого:

-
$ cargo run
-   Compiling panic v0.1.0 (file:///projects/panic)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
-     Running `target/debug/panic`
-thread 'main' panicked at src/main.rs:2:5:
-crash and burn
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-

Выполнение макроса panic! вызывает сообщение об ошибке, содержащееся в двух последних строках. Первая строка показывает сообщение паники и место в исходном коде, где возникла паника: src/main.rs:2:5 указывает, что это вторая строка, пятый символ внутри нашего файла src/main.rs

-

В этом случае указанная строка является частью нашего кода, и если мы перейдём к этой строке, мы увидим вызов макроса panic!. В других случаях вызов panic! мог бы произойти в стороннем коде, который вызывает наш код, тогда имя файла и номер строки для сообщения об ошибке будет из чужого кода, где макрос panic! выполнен, а не из строк нашего кода, которые в конечном итоге привели к выполнению panic!. Мы можем использовать обратную трассировку вызовов функций которые вызвали panic! чтобы выяснить, какая часть нашего кода вызывает неполадку. Мы обсудим обратную трассировку более подробно далее.

-

Использование обратной трассировки panic!

-

Давайте посмотрим на другой пример, где, вызов panic! происходит в сторонней библиотеке из-за ошибки в нашем коде (а не как в примере ранее, из-за вызова макроса нашим кодом напрямую). В приложении 9-1 приведён код, который пытается получить доступ по порядковому указателю в векторе за пределами допустимого рядазначений порядкового указателя.

-

Файл: src/main.rs

-
fn main() {
-    let v = vec![1, 2, 3];
-
-    v[99];
-}
-

Приложение 9-1: Попытка доступа к элементу за пределами вектора, которая вызовет panic!

-

Здесь мы пытаемся получить доступ к 100-му элементу вектора (который находится по порядковому указателю 99, потому что упорядочевание начинается с нуля), но вектор имеет только 3 элемента. В этой случаи, Ржавчина будет вызывать панику. Использование [] должно возвращать элемент, но вы передаёте неверный порядковый указатель: не существует элемента, который Ржавчина мог бы вернуть.

-

В языке C, например, попытка прочесть за пределами конца устройства данных (в нашем случае векторе) приведёт к неопределённому поведению, undefined behavior, UB. Вы всё равно получите значение, которое находится в том месте памяти компьютера, которое соответствовало бы этому элементу в векторе, несмотря на то, что память по тому адресу совсем не принадлежит вектору (всё просто: C рассчитал бы место хранения элемента с порядковым указателем 99 и считал бы то, что там хранится, упс). Это называется чтением за пределом буфера, buffer overread, и может привести к уязвимостям безопасности. Если злоумышленник может управлять порядковым указателем таким образом, то у него появляется возможность читать данные, которые он не должен иметь возможности читать.

-

Чтобы защитить вашу программу от такого рода уязвимостей при попытке прочитать элемент с порядковым указателем, которого не существует, Ржавчина остановит выполнение и откажется продолжить работу программы. Давайте попробуем так сделать и посмотрим на поведение Rust:

-
$ cargo run
-   Compiling panic v0.1.0 (file:///projects/panic)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
-     Running `target/debug/panic`
-thread 'main' panicked at src/main.rs:4:6:
-index out of bounds: the len is 3 but the index is 99
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Следующая строка говорит, что мы можем установить переменную среды RUST_BACKTRACE, чтобы получить обратную трассировку того, что именно стало причиной ошибки. Обратная трассировка создаёт список всех функций, которые были вызваны до какой-то определённой точки выполнения программы. Обратная трассировка в Ржавчина работает так же, как и в других языках. По этому предлагаем вам читать данные обратной трассировки как и везде - читать сверху вниз, пока не увидите сведения о файлах написанных вами. Это место, где возникла неполадка. Другие строки, которые выше над строками с упоминанием наших файлов, - это код, который вызывается нашим кодом; строки ниже являются кодом, который вызывает наш код. Эти строки могут включать основной код Rust, код встроенной библиотеки или используемые ящики. Давайте попробуем получить обратную трассировку с помощью установки переменной среды RUST_BACKTRACE в любое значение, кроме 0. Приложение 9-2 показывает вывод, подобный тому, что вы увидите.

- -
$ RUST_BACKTRACE=1 cargo run
-thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
-stack backtrace:
-   0: rust_begin_unwind
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/panicking.rs:584:5
-   1: core::panicking::panic_fmt
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:142:14
-   2: core::panicking::panic_bounds_check
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:84:5
-   3: <usize as core::slice::index::SliceIndex<[T]>>::index
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:242:10
-   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:18:9
-   5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/alloc/src/vec/mod.rs:2591:9
-   6: panic::main
-             at ./src/main.rs:4:5
-   7: core::ops::function::FnOnce::call_once
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/ops/function.rs:248:5
-note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
-
-

Приложение 9-2: Обратная трассировка, созданная вызовом panic!, когда установлена переменная окружения RUST_BACKTRACE

-

Тут много вывода! Вывод, который вы увидите, может отличаться от представленного, в зависимости от вашей операционной системы и исполнения Rust. Для того, чтобы получить обратную трассировку с этой сведениями, должны быть включены символы отладки, debug symbols. Символы отладки включены по умолчанию при использовании cargo build или cargo run без флага --release, как у нас в примере.

-

В выводе обратной трассировки приложения 9-2, строка #6 указывает на строку в нашем деле, которая вызывала неполадку: строка 4 из файла src/main.rs. Если мы не хотим, чтобы наша программа запаниковала, мы должны начать исследование с места, на которое указывает первая строка с упоминанием нашего файла. В приложении 9-1, где мы для отображения обратной трассировки сознательно написали код, который паникует, способ исправления паники состоит в том, чтобы не запрашивать элемент за пределами ряда значений порядковых указателей вектора. Когда ваш код запаникует в будущем, вам нужно будет выяснить, какое выполняющееся кодом действие, с какими значениями вызывает панику и что этот код должен делать вместо этого.

-

Мы вернёмся к обсуждению макроса panic!, и того когда нам следует и не следует использовать panic! для обработки ошибок в разделе "panic! или НЕ panic!" этой главы. Далее мы рассмотрим, как восстановить выполнение программы после исправляемых ошибок, использующих вид Result.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch09-02-recoverable-errors-with-result.html b/rustbook-ru/book/ch09-02-recoverable-errors-with-result.html deleted file mode 100644 index 61e9e971c..000000000 --- a/rustbook-ru/book/ch09-02-recoverable-errors-with-result.html +++ /dev/null @@ -1,516 +0,0 @@ - - - - - - Устранимые ошибки с Result - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Исправимые ошибки с Result

-

Многие ошибки являются не настолько критичными, чтобы останавливать выполнение программы. Иногда, когда в функции происходит сбой, необходима просто правильная преобразование и обработка ошибки. К примеру, при попытке открыть файл может произойти ошибка из-за отсутствия файла. Вы, возможно, захотите исправить случай и создать новый файл вместо остановки программы.

-

Вспомните раздел ["Обработка возможного сбоя с помощью Result"] главы 2: мы использовали там перечисление Result, имеющее два исхода. Ok и Err для обработки сбоев. Само перечисление определено следующим образом:

-
#![allow(unused)]
-fn main() {
-enum Result<T, E> {
-    Ok(T),
-    Err(E),
-}
-}
-

Виды T и E являются свойствами обобщённого вида: мы обсудим обобщённые виды более подробно в Главе 10. Все что вам нужно знать прямо сейчас - это то, что T представляет вид значения, которое будет возвращено в случае успеха внутри исхода Ok, а E представляет вид ошибки, которая будет возвращена при сбое внутри исхода Err. Так как вид Result имеет эти обобщённые свойства (generic type parameters), мы можем использовать вид Result и функции, которые определены для него, в разных случаейх, когда вид успешного значение и значения ошибки, которые мы хотим вернуть, отличаются.

-

Давайте вызовем функцию, которая возвращает значение Result, потому что может потерпеть неудачу. В приложении 9-3 мы пытаемся открыть файл.

-

Файл: src/main.rs

-
use std::fs::File;
-
-fn main() {
-    let greeting_file_result = File::open("hello.txt");
-}
-

Приложение 9-3: Открытие файла

-

File::open возвращает значения вида Result<T, E>. Гибкий вид T в выполнения File::open соответствует виду успешно полученного значения, std::fs::File, а именно указателю файла. Вид E, используемый для значения в случае возникновения ошибки, - std::io::Error. Такой возвращаемый вид означает, что вызов File::open может быть успешным и вернуть указатель файла, из которого мы можем читать или в который можем писать. Также вызов функции может завершиться неудачей: например, файл может не существовать, или у нас может не быть разрешения на доступ к файлу. Функция File::open должна иметь способ сообщить нам об успехе или неудаче и в то же время дать нам либо указатель файла, либо сведения об ошибке. Эту возможность как раз и предоставляет перечисление Result.

-

В случае успеха File::open значением переменной greeting_file_result будет образец Ok, содержащий указатель файла. В случае неудачи значение в переменной greeting_file_result будет образцом Err, содержащим дополнительную сведения о том, какая именно ошибка произошла.

-

Необходимо дописать в код приложения 9-3 выполнение разных действий в зависимости от значения, которое вернёт вызов File::open. Приложение 9-4 показывает один из способов обработки Result - пользуясь основным средством языка, таким как выражение match, рассмотренным в Главе 6.

-

Файл: src/main.rs

-
use std::fs::File;
-
-fn main() {
-    let greeting_file_result = File::open("hello.txt");
-
-    let greeting_file = match greeting_file_result {
-        Ok(file) => file,
-        Err(error) => panic!("Problem opening the file: {error:?}"),
-    };
-}
-

Приложение 9-4: Использование выражения match для обработки возвращаемых исходов вида Result

-

Обратите внимание, что также как перечисление Option, перечисление Result и его исходы, входят в область видимости благодаря авто-подключения (prelude), поэтому не нужно указывать Result:: перед использованием исходов Ok и Err в ветках выражения match.

-

Если итогом будет Ok, этот код вернёт значение file из исхода Ok, а мы затем присвоим это значение файлового указателя переменной greeting_file. После match мы можем использовать указатель файла для чтения или записи.

-

Другая ветвь match обрабатывает случай, где мы получаем значение Err после вызова File::open. В этом примере мы решили вызвать макрос panic!. Если в нашей текущей папки нет файла с именем hello.txt и мы выполним этот код, то мы увидим следующее сообщение от макроса panic!:

-
$ cargo run
-   Compiling error-handling v0.1.0 (file:///projects/error-handling)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
-     Running `target/debug/error-handling`
-thread 'main' panicked at src/main.rs:8:23:
-Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Как обычно, данное сообщение точно говорит, что пошло не так.

-

Обработка различных ошибок с помощью match

-

Код в приложении 9-4 будет вызывать panic! независимо от того, почему вызов File::open не удался. Однако мы хотим предпринять различные действия для разных причин сбоя. Если открытие File::open не удалось из-за отсутствия файла, мы хотим создать файл и вернуть его указатель. Если вызов File::open не удался по любой другой причине - например, потому что у нас не было прав на открытие файла, то все равно мы хотим вызвать panic! как у нас сделано в приложении 9-4. Для этого мы добавляем выражение внутреннего match, показанное в приложении 9-5.

-

Файл: src/main.rs

- -
use std::fs::File;
-use std::io::ErrorKind;
-
-fn main() {
-    let greeting_file_result = File::open("hello.txt");
-
-    let greeting_file = match greeting_file_result {
-        Ok(file) => file,
-        Err(error) => match error.kind() {
-            ErrorKind::NotFound => match File::create("hello.txt") {
-                Ok(fc) => fc,
-                Err(e) => panic!("Problem creating the file: {e:?}"),
-            },
-            other_error => {
-                panic!("Problem opening the file: {other_error:?}");
-            }
-        },
-    };
-}
-

Приложение 9-5: Обработка различных ошибок разными способами

-

Видом значения возвращаемого функцией File::open внутри Err исхода является io::Error, устройства из встроенной библиотеки. Данная устройства имеет способ kind, который можно вызвать для получения значения io::ErrorKind. Перечисление io::ErrorKind из встроенной библиотеки имеет исходы, представляющие различные виды ошибок, которые могут появиться при выполнении действий в io. Исход, который мы хотим использовать, это ErrorKind::NotFound, который даёт сведения, о том, что файл который мы пытаемся открыть ещё не существует. Итак, во второй строке мы вызываем сопоставление образца с переменной greeting_file_result и попадаем в ветку с обработкой ошибки, но также у нас есть внутренняя проверка для сопоставления error.kind() ошибки.

-

Условие, которое мы хотим проверить во внутреннем match, заключается в том, является ли значение, возвращаемое error.kind(), исходом NotFound перечисления ErrorKind. Если это так, мы пытаемся создать файл с помощью функции File::create. Однако, поскольку вызов File::create тоже может завершиться ошибкой, нам нужна обработка ещё одной ошибки, теперь уже во внутреннем выражении match. Заметьте: если файл не может быть создан, выводится другое, особое сообщение об ошибке. Вторая же ветка внешнего match (который обрабатывает вызов error.kind()), остаётся той же самой - в итоге программа паникует при любой ошибке, кроме ошибки отсутствия файла.

-
-

Иные использованию match с Result<T, E>

-

Как много match! Выражение match является очень полезным, но в то же время довольно простым. В главе 13 вы узнаете о замыканиях (closures), которые используются во многих способах вида Result<T, E>. Эти способы помогают быть более кратким, чем использование match при работе со значениями Result<T, E> в вашем коде.

-

Например, вот другой способ написать ту же логику, что показана в Приложении 9-5, но с использованием замыканий и способа unwrap_or_else:

- -
use std::fs::File;
-use std::io::ErrorKind;
-
-fn main() {
-    let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
-        if error.kind() == ErrorKind::NotFound {
-            File::create("hello.txt").unwrap_or_else(|error| {
-                panic!("Problem creating the file: {:?}", error);
-            })
-        } else {
-            panic!("Problem opening the file: {:?}", error);
-        }
-    });
-}
-

Несмотря на то, что данный код имеет такое же поведение как в приложении 9-5, он не содержит ни одного выражения match и проще для чтения. Советуем вам вернуться к примеру этого раздела после того как вы прочитаете Главу 13 и изучите способ unwrap_or_else по документации встроенной библиотеки. Многие из способов о которых вы узнаете в документации и Главе 13 могут очистить код от больших, вложенных выражений match при обработке ошибок.

-
-

Краткие способы обработки ошибок - unwrap и expect

-

Использование match работает достаточно хорошо, но может быть довольно многословным и не всегда хорошо передаёт смысл. Вид Result<T, E> имеет множество вспомогательных способов для выполнения различных, более отличительных задач. Способ unwrap - это способ быстрого доступа к значениям, выполненный так же, как и выражение match, которое мы написали в Приложении 9-4. Если значение Result является исходом Ok, unwrap возвращает значение внутри Ok. Если Result - исход Err, то unwrap вызовет для нас макрос panic!. Вот пример unwrap в действии:

-

Файл: src/main.rs

-
use std::fs::File;
-
-fn main() {
-    let greeting_file = File::open("hello.txt").unwrap();
-}
-

Если мы запустим этот код при отсутствии файла hello.txt, то увидим сообщение об ошибке из вызова panic! способа unwrap:

- -
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os {
-code: 2, kind: NotFound, message: "No such file or directory" }',
-src/main.rs:4:49
-
-

Другой способ, похожий на unwrap, это expect, позволяющий указать сообщение об ошибке для макроса panic!. Использование expect вместо unwrap с предоставлением хорошего сообщения об ошибке выражает ваше намерение и делает более простым отслеживание источника паники. правила написания способа expect выглядит так:

-

Файл: src/main.rs

-
use std::fs::File;
-
-fn main() {
-    let greeting_file = File::open("hello.txt")
-        .expect("hello.txt should be included in this project");
-}
-

expect используется так же как и unwrap: либо возвращается указатель файла либо вызывается макрос panic!.
Наше сообщение об ошибке в expect будет передано в panic! и заменит обычное используемое сообщение.
Вот как это выглядит:

- -
thread 'main' panicked at 'hello.txt should be included in this project: Os {
-code: 2, kind: NotFound, message: "No such file or directory" }',
-src/main.rs:5:10
-
-

В рабочем коде, большинство выбирает expect в угоду unwrap и добавляет описание, почему действие должна закончиться успешно. Но даже если предположение оказалось неверным, сведений для отладки будет больше.

-

Проброс ошибок

-

Когда вы пишете функцию, выполнение которой вызывает что-то, что может завершиться ошибкой, вместо обработки ошибки в этой функции, вы можете вернуть ошибку в вызывающий код, чтобы он мог решить, что с ней делать. Такой приём известен как распространение ошибки (propagating the error). Благодаря нему мы даём больше управления вызывающему коду, где может быть больше сведений или логики, которая диктует, как ошибка должна обрабатываться, чем было бы в месте появления этой ошибки.

-

Например, код программы 9-6 читает имя пользователя из файла. Если файл не существует или не может быть прочтён, то функция возвращает ошибку в код, который вызвал данную функцию.

-

Файл: src/main.rs

- -
#![allow(unused)]
-fn main() {
-use std::fs::File;
-use std::io::{self, Read};
-
-fn read_username_from_file() -> Result<String, io::Error> {
-    let username_file_result = File::open("hello.txt");
-
-    let mut username_file = match username_file_result {
-        Ok(file) => file,
-        Err(e) => return Err(e),
-    };
-
-    let mut username = String::new();
-
-    match username_file.read_to_string(&mut username) {
-        Ok(_) => Ok(username),
-        Err(e) => Err(e),
-    }
-}
-}
-

Приложение 9-6: Функция, которая возвращает ошибки в вызывающий код, используя оператор match

-

Эта функция может быть написана гораздо более коротким способом, но мы начнём с того, что многое сделаем вручную, чтобы изучить обработку ошибок; а в конце покажем более короткий способ. Давайте сначала рассмотрим вид возвращаемого значения: Result<String, io::Error>. Здесь есть возвращаемое значение функции вида Result<T, E> где образцовый свойство T был заполнен определенным видом String и образцовый свойство E был заполнен определенным видом io::Error.

-

Если эта функция выполнится без неполадок. то код, вызывающий эту функцию, получит значение Ok, содержащее String - имя пользователя, которое эта функция прочитала из файла. Если функция столкнётся с какими-либо неполадками, вызывающий код получит значение Err, содержащее образец io::Error, который включает дополнительную сведения о том, какие сбоев возникли. Мы выбрали io::Error в качестве возвращаемого вида этой функции, потому что это вид значения ошибки, возвращаемого из обеих действий, которые мы вызываем в теле этой функции и которые могут завершиться неудачей: функция File::open и способ read_to_string.

-

Тело функции начинается с вызова File::open. Затем мы обрабатываем значение Result с помощью match, подобно match из приложения 9-4. Если File::open завершается успешно, то указатель файла в переменной образца file становится значением в изменяемой переменной username_file и функция продолжит свою работу. В случае Err, вместо вызова panic!, мы используем ключевое слово return для досрочного возврата из функции и передаём значение ошибки из File::open, которое теперь находится в переменной образца e, обратно в вызывающий код как значение ошибки этой функции.

-

Таким образом, если у нас есть файловый указатель в username_file, функция создаёт новую String в переменной username и вызывает способ read_to_string для файлового указателя в username_file, чтобы прочитать содержимое файла в username. Способ read_to_string также возвращает Result, потому что он может потерпеть неудачу, даже если File::open завершился успешно. Поэтому нам нужен ещё один match для обработки этого Result: если read_to_string завершится успешно, то наша функция сработала, и мы возвращаем имя пользователя из файла, которое теперь находится в username, обёрнутое в Ok. Если read_to_string потерпит неудачу, мы возвращаем значение ошибки таким же образом, как мы возвращали значение ошибки в match, который обрабатывал возвращаемое значение File::open. Однако нам не нужно явно указывать return, потому что это последнее выражение в функции.

-

Затем код, вызывающий этот, будет обрабатывать получение либо значения Ok, содержащего имя пользователя, либо значения Err, содержащего io::Error. Вызывающий код должен решить, что делать с этими значениями. Если вызывающий код получает значение Err, он может вызвать panic! и завершить работу программы, использовать имя пользователя по умолчанию или найти имя пользователя, например, не в файле. У нас недостаточно сведений о том, что на самом деле пытается сделать вызывающий код, поэтому мы распространяем всю сведения об успехах или ошибках вверх, чтобы она могла обрабатываться соответствующим образом.

-

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

-

Сокращение для проброса ошибок: оператор ?

-

В приложении 9-7 показана выполнение read_username_from_file, которая имеет ту же возможность, что и в приложении 9-6, но в этой выполнения используется оператор ?.

-

Файл: src/main.rs

- -
#![allow(unused)]
-fn main() {
-use std::fs::File;
-use std::io::{self, Read};
-
-fn read_username_from_file() -> Result<String, io::Error> {
-    let mut username_file = File::open("hello.txt")?;
-    let mut username = String::new();
-    username_file.read_to_string(&mut username)?;
-    Ok(username)
-}
-}
-

Приложение 9-7: Функция, возвращающая ошибки в вызывающий код с помощью оператора ?

-

Выражение ?, расположенное после Result, работает почти так же, как и те выражения match, которые мы использовали для обработки значений Result в приложении 9-6. Если в качестве значения Result будет Ok, то значение внутри Ok будет возвращено из этого выражения, и программа продолжит работу. Если же значение представляет собой Err, то Err будет возвращено из всей функции, как если бы мы использовали ключевое слово return, так что значение ошибки будет передано в вызывающий код.

-

Существует разница между тем, что делает выражение match из приложения 9-6 и тем, что делает оператор ?: значения ошибок, для которых вызван оператор ?, проходят через функцию from, определённую в особенности From встроенной библиотеки, которая используется для преобразования значений из одного вида в другой. Когда оператор ? вызывает функцию from, полученный вид ошибки преобразуется в вид ошибки, определённый в возвращаемом виде текущей функции. Это полезно, когда функция возвращает только один вид ошибки, для описания всех возможных исходов сбоев, даже если её отдельные составляющие могут выходить из строя по разным причинам.

-

Например, мы могли бы изменить функцию read_username_from_file в приложении 9-7, чтобы возвращать пользовательский вид ошибки с именем OurError, который мы определим. Если мы также определим impl From<io::Error> for OurError для создания образца OurError из io::Error, то оператор ?, вызываемый в теле read_username_from_file, вызовет from и преобразует виды ошибок без необходимости добавления дополнительного кода в функцию.

-

В случае приложения 9-7 оператор ? в конце вызова File::open вернёт значение внутри Ok в переменную username_file. Если произойдёт ошибка, оператор ? выполнит ранний возврат значения Err вызывающему коду. То же самое относится к оператору ? в конце вызова read_to_string.

-

Оператор ? позволяет избавиться от большого количества образцового кода и упростить выполнение этой функции. Мы могли бы даже ещё больше сократить этот код, если бы использовали цепочку вызовов способов сразу после ?, как показано в приложении 9-8.

-

Файл: src/main.rs

- -
#![allow(unused)]
-fn main() {
-use std::fs::File;
-use std::io::{self, Read};
-
-fn read_username_from_file() -> Result<String, io::Error> {
-    let mut username = String::new();
-
-    File::open("hello.txt")?.read_to_string(&mut username)?;
-
-    Ok(username)
-}
-}
-

Приложение 9-8: Цепочка вызовов способов после оператора ?

-

Мы перенесли создание новой String в username в начало функции; эта часть не изменилась. Вместо создания переменной username_file мы соединили вызов read_to_string непосредственно с итогом File::open("hello.txt")?. У нас по-прежнему есть ? в конце вызова read_to_string, и мы по-прежнему возвращаем значение Ok, содержащее username, когда и File::open и read_to_string завершаются успешно, а не возвращают ошибки. Возможность снова такая же, как в Приложении 9-6 и Приложении 9-7; это просто другой, более удобный способ её написания.

-

Продолжая рассматривать разные способы записи данной функции, приложение 9-9 отображает способ сделать её ещё короче с помощью fs::read_to_string.

-

Файл: src/main.rs

- -
#![allow(unused)]
-fn main() {
-use std::fs;
-use std::io;
-
-fn read_username_from_file() -> Result<String, io::Error> {
-    fs::read_to_string("hello.txt")
-}
-}
-

Приложение 9-9: Использование fs::read_to_string вместо открытия и последующего чтения файла

-

Чтение файла в строку довольно распространённая действие, так что обычная библиотека предоставляет удобную функцию fs::read_to_string, которая открывает файл, создаёт новую String, читает содержимое файла, размещает его в String и возвращает её. Конечно, использование функции fs::read_to_string не даёт возможности объяснить обработку всех ошибок, поэтому мы сначала изучили длинный способ.

-

Где можно использовать оператор ?

-

Оператор ? может использоваться только в функциях, вид возвращаемого значения которых совместим со значением, для которого используется ?. Это потому, что оператор ? определён для выполнения раннего возврата значения из функции таким же образом, как и выражение match, которое мы определили в приложении 9-6. В приложении 9-6 match использовало значение Result, а ответвление с ранним возвратом вернуло значение Err(e). Вид возвращаемого значения функции должен быть Result, чтобы он был совместим с этим return.

-

В приложении 9-10 давайте посмотрим на ошибку, которую мы получим, если воспользуемся оператором ? в функции main с видом возвращаемого значения, несовместимым с видом значения, для которого мы используем ?:

-

Файл: src/main.rs

-
use std::fs::File;
-
-fn main() {
-    let greeting_file = File::open("hello.txt")?;
-}
-

Приложение 9-10: Попытка использовать ? в main функции, которая возвращает () , не будет собираться

-

Этот код открывает файл, что может привести к сбою. ? оператор следует за значением Result , возвращаемым File::open , но эта main функция имеет возвращаемый вид () , а не Result . Когда мы собираем этот код, мы получаем следующее сообщение об ошибке:

-
$ cargo run
-   Compiling error-handling v0.1.0 (file:///projects/error-handling)
-error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
- --> src/main.rs:4:48
-  |
-3 | fn main() {
-  | --------- this function should return `Result` or `Option` to accept `?`
-4 |     let greeting_file = File::open("hello.txt")?;
-  |                                                ^ cannot use the `?` operator in a function that returns `()`
-  |
-  = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `error-handling` (bin "error-handling") due to 1 previous error
-
-

Эта ошибка указывает на то, что оператор ? разрешено использовать только в функции, которая возвращает Result, Option или другой вид, выполняющий FromResidual.

-

Для исправления ошибки есть два исхода. Первый - изменить возвращаемый вид вашей функции так, чтобы он был совместим со значением, для которого вы используете оператор ?, если у вас нет ограничений, препятствующих этому. Другой способ - использовать match или один из способов Result<T, E> для обработки Result<T, E> любым подходящим способом.

-

В сообщении об ошибке также упоминалось, что ? можно использовать и со значениями Option<T>. Как и при использовании ? для Result, вы можете использовать ? только для Option в функции, которая возвращает Option. Поведение оператора ? при вызове Option<T> похоже на его поведение при вызове Result<T, E>: если значение равно None, то None будет возвращено раньше из функции в этот мгновение. Если значение Some, значение внутри Some является результирующим значением выражения, и функция продолжает исполняться. В приложении 9-11 приведён пример функции, которая находит последний символ первой строки заданного текста:

-
fn last_char_of_first_line(text: &str) -> Option<char> {
-    text.lines().next()?.chars().last()
-}
-
-fn main() {
-    assert_eq!(
-        last_char_of_first_line("Hello, world\nHow are you today?"),
-        Some('d')
-    );
-
-    assert_eq!(last_char_of_first_line(""), None);
-    assert_eq!(last_char_of_first_line("\nhi"), None);
-}
-

Приложение 9-11: Использование оператора ? для значения Option<T>

-

Эта функция возвращает Option<char>, потому что возможно, что там есть символ, но также возможно, что его нет. Этот код принимает переменная среза text строки и вызывает для него способ lines, который возвращает повторитель для строк в строке. Поскольку эта функция хочет проверить первую строку, она вызывает next у повторителя, чтобы получить первое значение от повторителя. Если text является пустой строкой, этот вызов next вернёт None, и в этом случае мы используем ? чтобы остановить и вернуть None из last_char_of_first_line. Если text не является пустой строкой, next вернёт значение Some, содержащее отрывок строки первой строки в text.

-

Символ ? извлекает отрывок строки, и мы можем вызвать chars для этого отрывка строки. чтобы получить повторитель символов. Нас важно последний символ в первой строке, поэтому мы вызываем last, чтобы вернуть последний элемент в повторителе. Вернётся Option, потому что возможно, что первая строка пустая - например, если text начинается с пустой строки, но имеет символы в других строках, как в "\nhi". Однако, если в первой строке есть последний символ, он будет возвращён в исходе Some. Оператор ? в середине даёт нам краткий способ выразить эту логику, позволяя выполнить функцию в одной строке. Если бы мы не могли использовать оператор ? в Option, нам пришлось бы выполнить эту логику, используя больше вызовов способов или выражение match.

-

Обратите внимание, что вы можете использовать оператор ? Result в функции, которая возвращает Result , и вы можете использовать оператор ? для Option в функции, которая возвращает Option , но вы не можете смешивать и сопоставлять. Оператор ? не будет самостоятельно преобразовывать Result в Option или наоборот; в этих случаях вы можете использовать такие способы, как способ ok для Result или способ ok_or для Option, чтобы выполнить преобразование явно.

-

До сих пор все функции main, которые мы использовали, возвращали (). Функция main - особенная, потому что это точка входа и выхода исполняемых программ, и существуют ограничения на вид возвращаемого значения, чтобы программы вели себя так, как ожидается.

-

К счастью, main также может возвращать Result<(), E> . В приложении 9-12 используется код из приложения 9-10, но мы изменили возвращаемый вид main на Result<(), Box<dyn Error>> и добавили возвращаемое значение Ok(()) в конец. Теперь этот код будет собран:

-
use std::error::Error;
-use std::fs::File;
-
-fn main() -> Result<(), Box<dyn Error>> {
-    let greeting_file = File::open("hello.txt")?;
-
-    Ok(())
-}
-

Приложение 9-12: Замена main на return Result<(), E> позволяет использовать оператор ? оператор над значениями Result

-

Вид Box<dyn Error> является особенность-предметом, о котором мы поговорим в разделе "Использование особенность-предметов, допускающих значения разных видов" в главе 17. Пока что вы можете считать, что Box<dyn Error> означает "любой вид ошибки". Использование ? для значения Result в функции main с видом ошибки Box<dyn Error> разрешено, так как позволяет вернуть любое значение Err раньше времени. Даже если тело этой функции main будет возвращать только ошибки вида std::io::Error, указав Box<dyn Error>, эта ярлык останется правильной, даже если в тело main будет добавлен код, возвращающий другие ошибки.

-

Когда main функция возвращает Result<(), E>, исполняемый файл завершится со значением 0, если main вернёт Ok(()), и выйдет с ненулевым значением, если main вернёт значение Err. Исполняемые файлы, написанные на C, при выходе возвращают целые числа: успешно завершённые программы возвращают целое число 0, а программы с ошибкой возвращают целое число, отличное от 0. Ржавчина также возвращает целые числа из исполняемых файлов, чтобы быть совместимым с этим соглашением.

-

Функция main может возвращать любые виды, выполняющие особенность std::process::Termination, в которых имеется функция report, возвращающая ExitCode. Обратитесь к документации встроенной библиотеки за дополнительной сведениями о порядке выполнения особенности Termination для ваших собственных видов.

-

Теперь, когда мы обсудили подробности вызова panic! или возврата Result, давайте вернёмся к тому, как решить, какой из случаев подходит для какой случаи.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch09-03-to-panic-or-not-to-panic.html b/rustbook-ru/book/ch09-03-to-panic-or-not-to-panic.html deleted file mode 100644 index e819e859f..000000000 --- a/rustbook-ru/book/ch09-03-to-panic-or-not-to-panic.html +++ /dev/null @@ -1,321 +0,0 @@ - - - - - - panic! или Не panic! - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

panic! или не panic!

-

Итак, как принимается решение о том, когда следует вызывать panic!, а когда вернуть Result? При панике код не имеет возможности восстановить своё выполнение. Можно было бы вызывать panic! для любой ошибочной случаи, независимо от того, имеется ли способ восстановления или нет, но с другой стороны, вы принимаете решение от имени вызывающего вас кода, что случаей необратима. Когда вы возвращаете значение Result, вы делегируете принятие решения вызывающему коду. Вызывающий код может попытаться выполнить восстановление способом, который подходит в данной случаи, или же он может решить, что из ошибки в Err нельзя восстановиться и вызовет panic!, превратив вашу исправимую ошибку в неисправимую. Поэтому возвращение Result является хорошим выбором по умолчанию для функции, которая может дать сбой.

-

В таких случаей как примеры, протовиды и проверки, более уместно писать код, который паникует вместо возвращения Result. Давайте рассмотрим почему, а затем мы обсудим случаи, в которых сборщик не может доказать, что ошибка невозможна, но вы, как человек, можете это сделать. Глава будет заканчиваться некоторыми общими руководящими принципами о том, как решить, стоит ли паниковать в коде библиотеки.

-

Примеры, прототипирование и проверки

-

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

-

Точно так же способы unwrap и expect являются очень удобными при создании протовида, прежде чем вы будете готовы решить, как обрабатывать ошибки. Они оставляют чёткие отступыв коде до особенности, когда вы будете готовы сделать программу более надёжной.

-

Если в проверке происходит сбой при вызове способа, то вы бы хотели, чтобы весь проверка не прошёл, даже если этот способ не является проверяемой возможностью. Поскольку вызов panic! это способ, которым проверка помечается как провалившийся, использование unwrap или expect - именно то, что нужно.

-

Случаи, в которых у вас больше сведений, чем у сборщика

-

Также было бы целесообразно вызывать unwrap или expect когда у вас есть какая-то другая логика, которая заверяет, что Result будет иметь значение Ok, но вашу логику не понимает сборщик. У вас по-прежнему будет значение Result которое нужно обработать: любая действие, которую вы вызываете, все ещё имеет возможность неудачи в целом, хотя это логически невозможно в вашей именно случаи. Если, проверяя код вручную, вы можете убедиться, что никогда не будет исход с Err, то вполне допустимо вызывать unwrap, а ещё лучше задокументировать причину, по которой, по вашему мнению, у вас никогда не будет исхода Err в тексте expect. Вот пример:

-
fn main() {
-    use std::net::IpAddr;
-
-    let home: IpAddr = "127.0.0.1"
-        .parse()
-        .expect("Hardcoded IP address should be valid");
-}
-

Мы создаём образец IpAddr, анализируя жёстко закодированную строку. Можно увидеть, что 127.0.0.1 является действительным IP-адресом, поэтому здесь допустимо использование expect. Однако наличие жёстко закодированной допустимой строки не меняет вид возвращаемого значения способа parse: мы все ещё получаем значение Result и сборщик все также заставляет нас обращаться с Resultтак, будто возможен исход Err, потому что сборщик недостаточно умён, чтобы увидеть, что эта строка всегда действительный IP-адрес. Если строка IP-адреса пришла от пользователя, то она не является жёстко запрограммированной в программе и, следовательно, может привести к ошибке, мы определённо хотели бы обработать Result более надёжным способом. Упоминание предположения о том, что этот IP-адрес жёстко закодирован, побудит нас изменить expect для лучшей обработки ошибок, если в будущем нам потребуется вместо этого получить IP-адрес из какого-либо другого источника.

-

Руководство по обработке ошибок

-

Желательно, чтобы код паниковал, если он может оказаться в неправильном состоянии. В этом среде неправильное состояние это когда некоторое допущение, заверение, договор или неизменная величина были нарушены. Например, когда недопустимые, противоречивые или пропущенные значения передаются в ваш код - плюс один или несколько пунктов из следующего перечисленного в списке:

-
    -
  • Неправильное состояние — это что-то неожиданное, отличается от того, что может происходить время от времени, например, когда пользователь вводит данные в неправильном виде.
  • -
  • Ваш код после этой точки должен полагаться на то, что он не находится в неправильном состоянии, вместо проверок наличия сбоев на каждом этапе.
  • -
  • Нет хорошего способа закодировать данную сведения в видах, которые вы используете. Мы рассмотрим пример того, что мы имеем в виду в разделе “Кодирование состояний и поведения на основе видов” главы 17.
  • -
-

Если кто-то вызывает ваш код и передаёт значения, которые не имеют смысла, лучше всего вернуть ошибку, если вы это можете, чтобы пользователь библиотеки мог решить, что он хочет делать в этом случае. Однако в тех случаях, когда продолжение выполнения программы может быть небезопасным или вредным, лучшим выбором будет вызов panic! и оповещение пользователя, использующего вашу библиотеку, об ошибке в его коде, чтобы он мог исправить её во время разработки. Подобно panic! подходит, если вы вызываете внешний, неподуправлениеный вам код, и он возвращает недопустимое состояние, которое вы не можете исправить.

-

Однако, когда ожидается сбой, лучше вернуть Result, чем выполнить вызов panic!. В качестве примера можно привести синтаксический анализатор, которому передали неправильно созданные данные, или HTTP-запрос, возвращающий значение указывающий на то, что вы достигли ограничения на частоту запросов. В этих случаях возврат Result означает, что ошибка является ожидаемой и вызывающий код должен решить, как её обрабатывать.

-

Когда ваш код выполняет действие, которая может подвергнуть пользователя риску, если она вызывается с использованием недопустимых значений, ваш код должен сначала проверить допустимость значений и паниковать, если значения недопустимы. Так советуется делать в основном из соображений безопасности: попытка оперировать неправильными данными может привести к уязвимостям. Это основная причина, по которой обычная библиотека будет вызывать panic!, если попытаться получить доступ к памяти вне границ массива: доступ к памяти, не относящейся к текущей устройстве данных, является известной неполадкой безопасности. Функции часто имеют договоры: их поведение обеспечивается, только если входные данные отвечают определённым требованиям. Паника при нарушении договора имеет смысл, потому что это всегда указывает на изъян со стороны вызывающего кода, и это не ошибка, которую вы хотели бы, чтобы вызывающий код явно обрабатывал. На самом деле, нет разумного способа для восстановления вызывающего кода; программисты, вызывающие ваш код, должны исправить свой. Договоры для функции, особенно когда нарушение вызывает панику, следует описать в документации по API функции.

-

Тем не менее, наличие множества проверок ошибок во всех ваших функциях было бы многословным и раздражительным. К счастью, можно использовать систему видов Ржавчина (следовательно и проверку видов сборщиком), чтобы она сделала множество проверок вместо вас. Если ваша функция имеет определённый вид в качестве свойства, вы можете продолжить работу с логикой кода зная, что сборщик уже обеспечил правильное значение. Например, если используется обычный вид, а не вид Option, то ваша программа ожидает наличие чего-то вместо ничего. Ваш код не должен будет обрабатывать оба исхода Some и None: он будет иметь только один исход для определённого значения. Код, пытающийся ничего не передавать в функцию, не будет даже собираться, поэтому ваша функция не должна проверять такой случай во время выполнения. Другой пример - это использование целого вида без знака, такого как u32, который заверяет, что свойство никогда не будет отрицательным.

-

Создание пользовательских видов для проверки

-

Давайте разовьём мысль использования системы видов Ржавчина чтобы убедиться, что у нас есть правильное значение, и рассмотрим создание пользовательского вида для валидации. Вспомним игру угадывания числа из Главы 2, в которой наш код просил пользователя угадать число между 1 и 100. Мы никогда не проверяли, что предположение пользователя лежит между этими числами, перед сравнением предположения с загаданным нами числом; мы только проверяли, что оно положительно. В этом случае последствия были не очень страшными: наши сообщения «Слишком много» или «Слишком мало», выводимые в окно вывода, все равно были правильными. Но было бы лучше подталкивать пользователя к правильным догадкам и иметь различное поведение для случаев, когда пользователь предлагает число за пределами ряда, и когда пользователь вводит, например, буквы вместо цифр.

-

Один из способов добиться этого - пытаться разобрать введённое значение как i32, а не как u32, чтобы разрешить возможно отрицательные числа, а затем добавить проверку для нахождение числа в ряде, например, так:

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    loop {
-        // --snip--
-
-        println!("Please input your guess.");
-
-        let mut guess = String::new();
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: i32 = match guess.trim().parse() {
-            Ok(num) => num,
-            Err(_) => continue,
-        };
-
-        if guess < 1 || guess > 100 {
-            println!("The secret number will be between 1 and 100.");
-            continue;
-        }
-
-        match guess.cmp(&secret_number) {
-            // --snip--
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => {
-                println!("You win!");
-                break;
-            }
-        }
-    }
-}
-

Выражение if проверяет, находится ли наше значение вне ряда, сообщает пользователю о неполадке и вызывает continue, чтобы начать следующую повторение цикла и попросить ввести другое число. После выражения if мы можем продолжить сравнение значения guess с загаданным числом, зная, что guess лежит в ряде от 1 до 100.

-

Однако это не наилучшее решение: если бы было чрезвычайно важно, чтобы программа работала только со значениями от 1 до 100, существовало бы много функций, требующих этого, то такая проверка в каждой функции была бы утомительной (и могла бы отрицательно повлиять на производительность).

-

Вместо этого можно создать новый вид и поместить проверки в функцию создания образца этого вида, не повторяя их везде. Таким образом, функции могут использовать новый вид в своих ярлыках и быть уверены в значениях, которые им передают. Приложение 9-13 показывает один из способов, как определить вид Guess, чтобы образец Guess создавался только при условии, что функция new получает значение от 1 до 100.

- -
#![allow(unused)]
-
-fn main() {
-}
-

Приложение 9-13. Вид Guess, который будет создавать образцы только для значений от 1 до 100

-

Сначала мы определяем устройство с именем Guess, которая имеет поле с именем value вида i32, в котором будет храниться число.

-

Затем мы выполняем сопряженную функцию new, создающую образцы значений вида Guess. Функция new имеет один свойство value вида i32, и возвращает Guess. Код в теле функции new проверяет, что значение value находится между 1 и 100. Если value не проходит эту проверку, мы вызываем panic!, которая оповестит программиста, написавшего вызывающий код, что в его коде есть ошибка, которую необходимо исправить, поскольку попытка создания Guess со значением value вне заданного ряда нарушает договор, на который полагается Guess::new. Условия, в которых Guess::new паникует, должны быть описаны в документации к API; мы рассмотрим соглашения о документации, указывающие на возможность появления panic! в документации API, которую вы создадите в Главе 14. Если value проходит проверку, мы создаём новый образец Guess, у которого значение поля value равно значению свойства value, и возвращаем Guess.

-

Затем мы выполняем способ с названием value, который заимствует self, не имеет других свойств, и возвращает значение вида i32. Этот способ иногда называют извлекатель (getter), потому что его цель состоит в том, чтобы извлечь данные из полей устройства и вернуть их. Этот открытый способ является необходимым, поскольку поле value устройства Guess является закрытым. Важно, чтобы поле value было закрытым, чтобы код, использующий устройство Guess, не мог устанавливать value напрямую: код снаружи звена должен использовать функцию Guess::new для создания образца Guess, таким образом обеспечивая, что у Guess нет возможности получить value, не проверенное условиями в функции Guess::new.

-

Функция, которая принимает или возвращает только числа от 1 до 100, может объявить в своей ярлыке, что она принимает или возвращает Guess, вместо i32, таким образом не будет необходимости делать дополнительные проверки в теле такой функции.

-

Итоги

-

Функции обработки ошибок в Ржавчина призваны помочь написанию более надёжного кода. Макрос panic! указывает , что ваша программа находится в состоянии, которое она не может обработать, и позволяет сказать этапу чтобы он прекратил своё выполнение, вместо попытки продолжить выполнение с неправильными или неверными значениями. Перечисление Result использует систему видов Rust, чтобы сообщить, что действия могут завершиться неудачей, и ваш код мог восстановиться. Можно использовать Result, чтобы сообщить вызывающему коду, что он должен обрабатывать вероятный успех или вероятную неудачу. Использование panic! и Result правильным образом сделает ваш код более надёжным перед лицом неизбежных неполадок.

-

Теперь, когда вы увидели полезные способы использования обобщённых видов Option и Result в встроенной библиотеке, мы поговорим о том, как работают обобщённые виды и как вы можете использовать их в своём коде.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch10-00-generics.html b/rustbook-ru/book/ch10-00-generics.html deleted file mode 100644 index fbe9df578..000000000 --- a/rustbook-ru/book/ch10-00-generics.html +++ /dev/null @@ -1,323 +0,0 @@ - - - - - - Общие виды, особенности (свойства) и время жизни - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Обобщённые виды, особенности и время жизни

-

Каждый язык программирования имеет в своём арсенале эффективные средства борьбы с повторением кода. В Ржавчина одним из таких средств являются обобщённые виды данных - generics. Это абстрактные подставные виды на место которых возможно поставить какой-либо определенный вид или другое свойство. Когда мы пишем код, мы можем выразить поведение обобщённых видов или их связь с другими обобщёнными видами, не зная какой вид будет использован на их месте при сборки и запуске кода.

-

Функции могут принимать свойства некоторого "обобщённого" вида вместо привычных "определенных" видов, вроде i32 или String. Подобно, функция принимает свойства с неизвестными заранее значениями, чтобы выполнять одинаковые действия над несколькими определенными значениями. На самом деле мы уже использовали обобщённые виды данных в Главе 6 (Option<T>), в Главе 8 (Vec<T> и HashMap<K, V>) и в Главе 9 (Result<T, E>). В этой главе вы узнаете, как определить собственные виды данных, функции и способы, используя возможности обобщённых видов.

-

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

-

После этого мы изучим как использовать особенности (traits) для определения поведения в обобщённом виде. Можно соединенять особенности с обобщёнными видами, чтобы обобщённый вид мог принимать только такие виды, которые имеют определённое поведение, а не все подряд.

-

В конце мы обсудим времена жизни (lifetimes), вариации обобщённых видов, которые дают сборщику сведения о том, как сроки жизни ссылок относятся друг к другу. Времена жизни позволяют нам указать дополнительную сведения об "одолженных" (borrowed) значениях, которая позволит сборщику удостовериться в соблюдения правил используемых ссылок в тех случаейх, когда сборщик не может сделать это самостоятельно .

-

Удаление повторения кода с помощью выделения общей возможности

-

В обобщениях мы можем заменить определенный вид на "заполнитель" (placeholder), обозначающую несколько видов, что позволяет удалить повторяющийся код. Прежде чем углубляться в правила написания обобщённых видов, давайте сначала посмотрим, как удалить повторение, не задействуя гибкие виды, путём извлечения функции, которая заменяет определённые значения заполнителем, представляющим несколько значений. Затем мы применим ту же технику для извлечения гибкой функции! Изучив, как распознать повторяющийся код, который можно извлечь в функцию, вы начнёте распознавать повторяющийся код, который может использовать обобщённые виды.

-

Начнём с короткой программы в приложении 10-1, которая находит наибольшее число в списке.

-

Файл: src/main.rs

-
fn main() {
-    let number_list = vec![34, 50, 25, 100, 65];
-
-    let mut largest = &number_list[0];
-
-    for number in &number_list {
-        if number > largest {
-            largest = number;
-        }
-    }
-
-    println!("The largest number is {largest}");
-    assert_eq!(*largest, 100);
-}
-

Приложение 10-1: Поиск наибольшего числа в списке чисел

-

Сохраним список целых чисел в переменной number_list и поместим первое значение из списка в переменную largest. Далее, переберём все элементы списка, и, если текущий элемент больше числа сохранённого в переменной largest, заменим значение в этой переменной. Если текущий элемент меньше или равен "наибольшему", найденному ранее, значение переменной оставим прежним и перейдём к следующему элементу списка. После перебора всех элементов списка переменная largest должна содержать наибольшее значение, которое в нашем случае будет равно 100.

-

Теперь перед нами стоит задача найти наибольшее число в двух разных списках. Для этого мы можем повторять код из приложения 10-1 и использовать ту же логику в двух разных местах программы, как показано в приложении 10-2.

-

Файл: src/main.rs

-
fn main() {
-    let number_list = vec![34, 50, 25, 100, 65];
-
-    let mut largest = &number_list[0];
-
-    for number in &number_list {
-        if number > largest {
-            largest = number;
-        }
-    }
-
-    println!("The largest number is {largest}");
-
-    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
-
-    let mut largest = &number_list[0];
-
-    for number in &number_list {
-        if number > largest {
-            largest = number;
-        }
-    }
-
-    println!("The largest number is {largest}");
-}
-

Приложение 10-2: Код для поиска наибольшего числа в двух списках чисел

-

Несмотря на то, что код программы работает, повторение кода утомительно и подвержено ошибкам. При внесении изменений мы должны не забыть обновить каждое место, где код повторяется.

-

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

-

В приложении 10-3 мы извлекаем код, который находит наибольшее число, в функцию с именем largest. Затем мы вызываем функцию, чтобы найти наибольшее число в двух списках из приложения 10-2. Мы также можем использовать эту функцию для любого другого списка значений i32 , который может встретиться позже.

-

Файл: src/main.rs

-
fn largest(list: &[i32]) -> &i32 {
-    let mut largest = &list[0];
-
-    for item in list {
-        if item > largest {
-            largest = item;
-        }
-    }
-
-    largest
-}
-
-fn main() {
-    let number_list = vec![34, 50, 25, 100, 65];
-
-    let result = largest(&number_list);
-    println!("The largest number is {result}");
-    assert_eq!(*result, 100);
-
-    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
-
-    let result = largest(&number_list);
-    println!("The largest number is {result}");
-    assert_eq!(*result, 6000);
-}
-

Приложение 10-3: Абстрактный код для поиска наибольшего числа в двух списках

-

Функция largest имеет свойство с именем list, который представляет любой срез значений вида i32, которые мы можем передать в неё. В итоге вызова функции, код выполнится с определенными, переданными в неё значениями.

-

Итак, вот шаги выполненные для изменения кода из приложения 10-2 в приложение 10-3:

-
    -
  1. Определить повторяющийся код.
  2. -
  3. Извлечь повторяющийся код и поместить его в тело функции, определив входные и выходные значения этого кода в ярлыке функции.
  4. -
  5. Обновить и заменить два участка повторяющегося кода вызовом одной функции.
  6. -
-

Далее, чтобы уменьшить повторение кода, мы воспользуемся теми же шагами для обобщённых видов. Обобщённые виды позволяют работать над абстрактными видами таким же образом, как тело функции может работать над абстрактным списком list вместо определенных значений.

-

Например, у нас есть две функции: одна ищет наибольший элемент внутри среза значений вида i32, а другая внутри среза значений вида char. Как уменьшить такое повторение? Давайте выяснять!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch10-01-syntax.html b/rustbook-ru/book/ch10-01-syntax.html deleted file mode 100644 index 0ab8dd559..000000000 --- a/rustbook-ru/book/ch10-01-syntax.html +++ /dev/null @@ -1,498 +0,0 @@ - - - - - - Обобщённые виды данных - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Обобщённые виды данных

-

Мы используем обобщённые виды данных для объявления функций или устройств, которые затем можно использовать с различными определенными видами данных. Давайте сначала посмотрим, как объявлять функции, устройства, перечисления и способы, используя обобщённые виды данных. Затем мы обсудим, как обобщённые виды данных влияют на производительность кода.

-

В объявлении функций

-

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

-

Рассмотрим пример с функцией largest. Приложение 10-4 показывает две функции, каждая из которых находит самое большое значение в срезе своего вида. Позже мы объединим их в одну функцию, использующую обобщённые виды данных.

-

Файл: src/main.rs

-
fn largest_i32(list: &[i32]) -> &i32 {
-    let mut largest = &list[0];
-
-    for item in list {
-        if item > largest {
-            largest = item;
-        }
-    }
-
-    largest
-}
-
-fn largest_char(list: &[char]) -> &char {
-    let mut largest = &list[0];
-
-    for item in list {
-        if item > largest {
-            largest = item;
-        }
-    }
-
-    largest
-}
-
-fn main() {
-    let number_list = vec![34, 50, 25, 100, 65];
-
-    let result = largest_i32(&number_list);
-    println!("The largest number is {result}");
-    assert_eq!(*result, 100);
-
-    let char_list = vec!['y', 'm', 'a', 'q'];
-
-    let result = largest_char(&char_list);
-    println!("The largest char is {result}");
-    assert_eq!(*result, 'y');
-}
-

Приложение 10-4: две функции, отличающиеся только именем и видом обрабатываемых данных

-

Функция largest_i32 уже встречалась нам: мы извлекли её в приложении 10-3, когда боролись с повторением кода — она находит наибольшее значение вида i32 в срезе. Функция largest_char находит самое большое значение вида char в срезе. Тело у этих функций одинаковое, поэтому давайте избавимся от повторяемлшл кода, используя свойство обобщённого вида в одной функции.

-

Для свойствоизации видов данных в новой объявляемой функции нам нужно дать имя обобщённому виду — так же, как мы это делаем для переменных функций. Можно использовать любой определитель для имени свойства вида, но мы будем использовать T, потому что по соглашению имена свойств в Ржавчина должны быть короткими (обычно длиной в один символ), а именование видов в Ржавчина делается в наставлении UpperCamelCase. Сокращение слова «type» до одной буквы T является обычным выбором большинства программистов, использующих язык Rust.

-

Когда мы используем свойство в теле функции, мы должны объявить имя свойства в ярлыке, чтобы сборщик знал, что означает это имя. Подобно когда мы используем имя вида свойства в ярлыке функции, мы должны объявить это имя раньше, чем мы его используем. Чтобы определить обобщённую функцию largest, поместим объявление имён свойств в треугольные скобки <> между именем функции и списком свойств, как здесь:

-
fn largest<T>(list: &[T]) -> &T {
-

Объявление читается так: функция largest является обобщённой по виду T. Эта функция имеет один свойство с именем list, который является срезом значений с видом данных T. Функция largest возвращает значение этого же вида T.

-

Приложение 10-5 показывает определение функции largest с использованием обобщённых видов данных в её ярлыке. Приложение также показывает, как мы можем вызвать функцию со срезом данных вида i32 или char. Данный код пока не будет собираться, но мы исправим это к концу раздела.

-

Файл: src/main.rs

-
fn largest<T>(list: &[T]) -> &T {
-    let mut largest = &list[0];
-
-    for item in list {
-        if item > largest {
-            largest = item;
-        }
-    }
-
-    largest
-}
-
-fn main() {
-    let number_list = vec![34, 50, 25, 100, 65];
-
-    let result = largest(&number_list);
-    println!("The largest number is {result}");
-
-    let char_list = vec!['y', 'm', 'a', 'q'];
-
-    let result = largest(&char_list);
-    println!("The largest char is {result}");
-}
-

Приложение 10-5: функция largest, использующая свойства обобщённого типа; пока ещё не собирается

-

Если мы соберем программу сейчас, мы получим следующую ошибку:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0369]: binary operation `>` cannot be applied to type `&T`
- --> src/main.rs:5:17
-  |
-5 |         if item > largest {
-  |            ---- ^ ------- &T
-  |            |
-  |            &T
-  |
-help: consider restricting type parameter `T`
-  |
-1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
-  |             ++++++++++++++++++++++
-
-For more information about this error, try `rustc --explain E0369`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

В подсказке упоминается std::cmp::PartialOrd, который является особенностью. Мы поговорим про особенности в следующем разделе. Сейчас ошибка в функции largest указывает, что функция не будет работать для всех возможных видов T. Так как мы хотим сравнивать значения вида T в теле функции, мы можем использовать только те виды, данные которых можно упорядочить: можем упорядочить — значит, можем и сравнить. Чтобы можно было задействовать сравнения, обычная библиотека имеет особенность std::cmp::PartialOrd, который вы можете выполнить для видов (смотрите дополнение С для большей сведений про данный особенность). Следуя совету в сообщении сборщика, ограничим вид T теми исходами, которые поддерживают особенность PartialOrd, и тогда пример успешно собирается, так как обычная библиотека выполняет PartialOrd как для вида i32, так и для вида char.

-

В определении устройств

-

Мы также можем определить устройства, использующие обобщённые виды в одном или нескольких своих полях, с помощью правил написания <>. Приложение 10-6 показывает, как определить устройство Point<T>, чтобы хранить поля координат x и y любого вида данных.

-

Файл: src/main.rs

-
struct Point<T> {
-    x: T,
-    y: T,
-}
-
-fn main() {
-    let integer = Point { x: 5, y: 10 };
-    let float = Point { x: 1.0, y: 4.0 };
-}
-

Приложение 10-6: устройства Point, содержащая поля x и y вида T

-

правила написания использования обобщённых видов в определении устройства очень похож на правила написания в определении функции. Сначала мы объявляем имена видов свойств внутри треугольных скобок сразу после названия устройства. Затем мы можем использовать обобщённые виды в определении устройства в тех местах, где ранее мы указывали бы определенные виды.

-

Так как мы используем только один обобщённый вид данных для определения устройства Point<T>, это определение означает, что устройства Point<T> является обобщённой с видом T, и оба поля x и y имеют одинаковый вид, каким бы он не являлся. Если мы создадим образец устройства Point<T> со значениями разных видов, как показано в приложении 10-7, наш код не собирается.

-

Файл: src/main.rs

-
struct Point<T> {
-    x: T,
-    y: T,
-}
-
-fn main() {
-    let wont_work = Point { x: 5, y: 4.0 };
-}
-

Приложение 10-7: поля x и y должны быть одного вида, так как они имеют один и тот же обобщённый вид T

-

В этом примере, когда мы присваиваем целочисленное значение 5 переменной x , мы сообщаем сборщику, что обобщённый вид T будет целым числом для этого образца Point<T>. Затем, когда мы указываем значение 4.0 (имеющее вид, отличный от целого числа) для y, который по нашему определению должен иметь тот же вид, что и x, мы получим ошибку несоответствия видов:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0308]: mismatched types
- --> src/main.rs:7:38
-  |
-7 |     let wont_work = Point { x: 5, y: 4.0 };
-  |                                      ^^^ expected integer, found floating-point number
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

Чтобы определить устройство Point, где оба значения x и y являются обобщёнными, но различными видами, можно использовать несколько свойств обобщённого вида. Например, в приложении 10-8 мы изменим определение Point таким образом, чтобы оно использовало обобщённые виды T и U, где x имеет вид T а y имеет вид U.

-

Файл: src/main.rs

-
struct Point<T, U> {
-    x: T,
-    y: U,
-}
-
-fn main() {
-    let both_integer = Point { x: 5, y: 10 };
-    let both_float = Point { x: 1.0, y: 4.0 };
-    let integer_and_float = Point { x: 5, y: 4.0 };
-}
-

Приложение 10-8: устройства Point<T, U> обобщена для двух видов, так что x и y могут быть значениями разных видов

-

Теперь разрешены все показанные образцы вида Point! В объявлении можно использовать сколь угодно много свойств обобщённого вида, но если делать это в большом количестве, код будет тяжело читать. Если в вашем коде требуется много обобщённых видов, возможно, стоит разбить его на более мелкие части.

-

В определениях перечислений

-

Как и устройства, перечисления также могут хранить обобщённые виды в своих исхода.. Давайте ещё раз посмотрим на перечисление Option<T>, предоставленное встроенной библиотекой, которое мы использовали в главе 6:

-
#![allow(unused)]
-fn main() {
-enum Option<T> {
-    Some(T),
-    None,
-}
-}
-

Это определение теперь должно быть вам более понятно. Как видите, перечисление Option<T> является обобщённым по виду T и имеет два исхода: исход Some, который содержит одно значение вида T, и исход None, который не содержит никакого значения. Используя перечисление Option<T>, можно выразить абстрактную подход необязательного значения — и так как Option<T> является обобщённым, можно использовать эту абстракцию независимо от того, каким будет вид необязательного значения.

-

Перечисления также могут использовать несколько обобщённых видов. Определение перечисления Result, которое мы упоминали в главе 9, является примером такого использования:

-
#![allow(unused)]
-fn main() {
-enum Result<T, E> {
-    Ok(T),
-    Err(E),
-}
-}
-

Перечисление Result имеет два обобщённых вида: T и E — и два исхода: Ok, который содержит вид T, и Err, содержащий вид E. С таким определением удобно использовать перечисление Result везде, где действия могут быть выполнены успешно (возвращая значение вида T) или неуспешно (возвращая ошибку вида E). Это то, что мы делали при открытии файла в приложении 9-3, где T заполнялось видом std::fs::File, если файл был открыт успешно, либо E заполнялось видом std::io::Error, если при открытии файла возникали какие-либо сбоев.

-

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

-

В определении способов

-

Мы можем выполнить способы для устройств и перечислений (как мы делали в главе 5) и в определениях этих способов также использовать обобщённые виды. В приложении 10-9 показана устройства Point<T>, которую мы определили в приложении 10-6, с добавленным для неё способом x.

-

Файл: src/main.rs

-
struct Point<T> {
-    x: T,
-    y: T,
-}
-
-impl<T> Point<T> {
-    fn x(&self) -> &T {
-        &self.x
-    }
-}
-
-fn main() {
-    let p = Point { x: 5, y: 10 };
-
-    println!("p.x = {}", p.x());
-}
-

Приложение 10-9: Выполнение способа с именем x у устройства Point<T>, которая будет возвращать ссылку на поле x вида T

-

Здесь мы определили способ с именем x у устройства Point<T>, который возвращает ссылку на данные в поле x.

-

Обратите внимание, что мы должны объявить T сразу после impl . В этом случае мы можем использовать T для указания на то, что выполняем способ для вида Point<T>. Объявив T гибким видом сразу после impl , Ржавчина может определить, что вид в угловых скобках в Point является гибким, а не определенным видом. Мы могли бы выбрать другое имя для этого обобщённого свойства, отличное от имени, использованного в определении устройства, но обычно используют одно и то же имя. Способы, написанные внутри раздела impl , который использует обобщённый вид, будут определены для любого образца вида, независимо от того, какой определенный вид в конечном итоге будет подставлен вместо этого обобщённого.

-

Мы можем также указать ограничения, какие обобщённые виды разрешено использовать при определении способов. Например, мы могли бы выполнить способы только для образцов вида Point<f32>, а не для образцов Point<T>, в которых используется произвольный обобщённый вид. В приложении 10-10 мы используем определенный вид f32, что означает, что мы не определяем никакие виды после impl.

-

Файл: src/main.rs

-
struct Point<T> {
-    x: T,
-    y: T,
-}
-
-impl<T> Point<T> {
-    fn x(&self) -> &T {
-        &self.x
-    }
-}
-
-impl Point<f32> {
-    fn distance_from_origin(&self) -> f32 {
-        (self.x.powi(2) + self.y.powi(2)).sqrt()
-    }
-}
-
-fn main() {
-    let p = Point { x: 5, y: 10 };
-
-    println!("p.x = {}", p.x());
-}
-

Приложение 10-10: разделimpl, который применяется только к устройстве, имеющей определенный вид для свойства обобщённого вида T

-

Этот код означает, что вид Point<f32> будет иметь способ с именем distance_from_origin, а другие образцы Point<T>, где T имеет вид, отличный от f32, не будут иметь этого способа. Способ вычисляет, насколько далеко наша точка находится от точки с координатами (0.0, 0.0), и использует математические действия, доступные только для видов с плавающей точкой.

-

Свойства обобщённого вида, которые мы используем в определении устройства, не всегда совпадают с подобиями, использующимися в ярлыках способов этой устройства. Чтобы пример был более очевидным, в приложении 10-11 используются обобщённые виды X1 и Y1 для определения устройства Point и виды X2 Y2 для ярлыки способа mixup. Способ создаёт новый образец устройства Point, где значение x берётся из self Point (имеющей вид X1), а значение y - из переданной устройства Point (где эта переменная имеет вид Y2).

-

Файл: src/main.rs

-
struct Point<X1, Y1> {
-    x: X1,
-    y: Y1,
-}
-
-impl<X1, Y1> Point<X1, Y1> {
-    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
-        Point {
-            x: self.x,
-            y: other.y,
-        }
-    }
-}
-
-fn main() {
-    let p1 = Point { x: 5, y: 10.4 };
-    let p2 = Point { x: "Hello", y: 'c' };
-
-    let p3 = p1.mixup(p2);
-
-    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
-}
-

Приложение 10-11: способ, использующий обобщённые виды, отличающиеся от видов, используемых в определении устройства

-

В функции main мы определили вид Point, который имеет вид i32 для x (со значением 5 ) и вид f64 для y (со значением 10.4). Переменная p2 является устройством Point, которая имеет строковый срез для x (со значением «Hello») и char для y (со значением c). Вызов mixup на p1 с переменнаяом p2 создаст для нас образец устройства p3, который будет иметь вид i32 для x (потому что x взят из p1). Переменная p3 будет иметь вид char для y (потому что y взят из p2). Вызов макроса println! выведет p3.x = 5, p3.y = c.

-

Цель этого примера — отобразить случай, в которой некоторые обобщённые свойства объявлены с помощью impl, а некоторые объявлены в определении способа. Здесь обобщённые свойства X1 и Y1 объявляются после impl, потому что они относятся к определению устройства. Обобщённые свойства X2 и Y2 объявляются после fn mixup, так как они относятся только к способу.

-

Производительность кода, использующего обобщённые виды

-

Вы могли бы задаться вопросом, возникают ли какие-нибудь дополнительные издержки при использовании свойств обобщённого вида. Хорошая новость в том, что при использовании обобщённых видов ваша программа работает ничуть ни медленнее, чем если бы она работала с использованием определенных видов.

-

В Ржавчина это достигается во время сборки при помощи мономорфизации кода, использующего обобщённые виды. Мономорфизация — это этап превращения обобщённого кода в определенный код путём подстановки определенных видов, использующихся при сборки. В этом этапе сборщик выполняет шаги, противоположные тем, которые мы использовали для создания обобщённой функции в приложении 10-5: он просматривает все места, где вызывается обобщённый код, и порождает код для определенных видов, использовавшихся для вызова в обобщённом.

-

Давайте посмотрим, как это работает при использовании перечисления Option<T> из встроенной библиотеки:

-
#![allow(unused)]
-fn main() {
-let integer = Some(5);
-let float = Some(5.0);
-}
-

Когда Ржавчина собирает этот код, он выполняет мономорфизацию. Во время этого этапа сборщик считывает значения, которые были использованы в образцах Option<T>, и определяет два вида Option<T>: один для вида i32, а другой — для f64. Таким образом, он разворачивает обобщённое определение Option<T> в два определения, именно для i32 и f64, тем самым заменяя обобщённое определение определенными.

-

Мономорфизированная исполнение кода выглядит примерно так (сборщик использует имена, отличные от тех, которые мы используем здесь для отображения):

-

Файл: src/main.rs

-
enum Option_i32 {
-    Some(i32),
-    None,
-}
-
-enum Option_f64 {
-    Some(f64),
-    None,
-}
-
-fn main() {
-    let integer = Option_i32::Some(5);
-    let float = Option_f64::Some(5.0);
-}
-

Обобщённое Option<T> заменяется определенными определениями, созданными сборщиком. Поскольку Ржавчина собирает обобщённый код в код, определяющий вид в каждом образце, мы не платим за использование обобщённых видов во время выполнения. Когда код запускается, он работает точно так же, как если бы мы сделали повторение каждое определение вручную. Этап мономорфизации делает обобщённые виды Ржавчина чрезвычайно эффективными во время выполнения.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch10-02-traits.html b/rustbook-ru/book/ch10-02-traits.html deleted file mode 100644 index a8a4e0d02..000000000 --- a/rustbook-ru/book/ch10-02-traits.html +++ /dev/null @@ -1,614 +0,0 @@ - - - - - - Особенности (свойства): определение разделяемого поведения - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Особенности: определение общего поведения

-

Особенность сообщает сборщику Ржавчина о возможности, которой обладает определённый вид и которой он может поделиться с другими видами. Можно использовать особенности, чтобы определять общее поведение абстрактным способом. Мы можем использовать ограничение особенности (trait bounds) чтобы указать, что общим видом может быть любой вид, который имеет определённое поведение.

-
-

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

-
-

Определение особенности

-

Поведение вида определяется теми способами, которые мы можем вызвать у данного вида. Различные виды разделяют одинаковое поведение, если мы можем вызвать одни и те же способы у этих видов. Определение особенностей - это способ собъединять ярлыки способов вместе для того, чтобы описать общее поведение, необходимое для достижения определённой цели.

-

Например, пусть есть несколько устройств, которые имеют различный вид и различный размер текста: устройства NewsArticle, которая содержит новость, напечатанную в каком-то месте мира; устройства Tweet, которая содержит 280 символьную строку твита и мета-данные, обозначающие является ли твит новым или ответом на другой твит.

-

Мы хотим создать ящик библиотеки медиа-агрегатора aggregator, которая может отображать сводку данных сохранённых в образцах устройств NewsArticle или Tweet. Чтобы этого достичь, нам необходимо иметь возможность для каждой устройства получить короткую сводку на основе имеющихся данных, и для этого мы запросим сводку вызвав способ summarize. Приложение 10-12 показывает определение особенности Summary, который выражает это поведение.

-

Файл: src/lib.rs

-
pub trait Summary {
-    fn summarize(&self) -> String;
-}
-

Приложение 10-12: Определение особенности Summary, который содержит поведение предоставленное способом summarize

-

Здесь мы объявляем особенность с использованием ключевого слова trait, а затем его название, которым в нашем случае является Summary. Также мы объявляем ящик как pub что позволяет ящикам, зависящим от нашего ящика, тоже использовать наш ящик, что мы увидим в последующих примерах. Внутри фигурных скобок объявляются ярлыки способов, которые описывают поведения видов, выполняющих данный особенность, в данном случае поведение определяется только одной ярлыком способа fn summarize(&self) -> String.

-

После ярлыки способа, вместо предоставления выполнения в фигурных в скобках, мы используем точку с запятой. Каждый вид, выполняющий данный особенность, должен предоставить своё собственное поведение для данного способа. Сборщик обеспечит, что любой вид содержащий особенность Summary, будет также иметь и способ summarize объявленный с точно такой же ярлыком.

-

Особенность может иметь несколько способов в описании его тела: ярлыки способов перечисляются по одной на каждой строке и должны закачиваться символом ;.

-

Выполнение особенности у вида

-

Теперь, после того как мы определили желаемое поведение используя особенность Summary, можно выполнить его у видов в нашем медиа-агрегаторе. Приложение 10-13 показывает выполнение особенности Summary у устройства NewsArticle, которая использует для создания сводки в способе summarize заголовок, автора и место обнародования статьи. Для устройства Tweet мы определяем выполнение summarize используя имя пользователя и следующий за ним полный текст твита, полагая что содержание твита уже ограничено 280 символами.

-

Файл: src/lib.rs

-
pub trait Summary {
-    fn summarize(&self) -> String;
-}
-
-pub struct NewsArticle {
-    pub headline: String,
-    pub location: String,
-    pub author: String,
-    pub content: String,
-}
-
-impl Summary for NewsArticle {
-    fn summarize(&self) -> String {
-        format!("{}, by {} ({})", self.headline, self.author, self.location)
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize(&self) -> String {
-        format!("{}: {}", self.username, self.content)
-    }
-}
-

Приложение 10-13: Выполнение особенности Summary для устройств NewsArticle и Tweet

-

Выполнение особенности у вида подобна выполнения обычных способов. Разница в том что после impl мы ставим имя особенности, который мы хотим выполнить, затем используем ключевое слово for, а затем указываем имя вида, для которого мы хотим сделать выполнение особенности. Внутри раздела impl мы помещаем ярлык способа объявленную в особенности. Вместо добавления точки с запятой в конце, после каждой ярлыки используются фигурные скобки и тело способа заполняется определенным поведением, которое мы хотим получить у способов особенности для определенного вида.

-

Теперь когда библиотека выполнила особенность Summary для NewsArticle и Tweet, программисты использующие ящик могут вызывать способы особенности у образцов видов NewsArticle и Tweet точно так же как если бы это были обычные способы. Единственное отличие состоит в том, что программист должен ввести особенность в область видимости точно так же как и виды. Здесь пример того как двоичный ящик может использовать наш aggregator:

-
use aggregator::{Summary, Tweet};
-
-fn main() {
-    let tweet = Tweet {
-        username: String::from("horse_ebooks"),
-        content: String::from(
-            "of course, as you probably already know, people",
-        ),
-        reply: false,
-        retweet: false,
-    };
-
-    println!("1 new tweet: {}", tweet.summarize());
-}
-

Данный код напечатает: 1 new tweet: horse_ebooks: of course, as you probably already know, people.

-

Другие ящики, которые зависят от aggregator, тоже могу включить особенность Summary в область видимости для выполнения Summary в их собственных видах. Одно ограничение, на которое следует обратить внимание, заключается в том, что мы можем выполнить особенность для вида только в том случае, если хотя бы один из особенностей вида является местным для нашего ящика. Например, мы можем выполнить обычный библиотечный особенность Display на собственном виде Tweet как часть возможности нашего ящика aggregator потому что вид Tweet является местным для ящика aggregator. Также мы можем выполнить Summary для Vec<T> в нашем ящике aggregator, потому что особенность Summary является местным для нашего ящика aggregator.

-

Но мы не можем выполнить внешние особенности для внешних видов. Например, мы не можем выполнить особенность Display для Vec<T> внутри нашего ящика aggregator, потому что Display и Vec<T> оба определены в встроенной библиотеке а не местно в нашем ящике aggregator. Это ограничение является частью свойства называемого согласованность (coherence), а ещё точнее сиротское правило (orphan rule), которое называется так потому что не представлен родительский вид. Это правило заверяет, что код других людей не может сломать ваш код и наоборот. Без этого правила два ящика могли бы выполнить один особенность для одинакового вида и Ржавчина не сможет понять, какой выполнением нужно пользоваться.

-

Выполнение поведения по умолчанию

-

Иногда полезно иметь поведение по умолчанию для некоторых или всех способов в особенности вместо того, чтобы требовать выполнения всех способов в каждом виде, выполняющим данный особенность. Затем, когда мы выполняем особенность для определённого вида, можно сохранить или переопределить поведение каждого способа по умолчанию уже внутри видов.

-

В примере 10-14 показано, как указать строку по умолчанию для способа summarize из особенности Summary вместо определения только ярлыки способа, как мы сделали в примере 10-12.

-

Файл: src/lib.rs

-
pub trait Summary {
-    fn summarize(&self) -> String {
-        String::from("(Read more...)")
-    }
-}
-
-pub struct NewsArticle {
-    pub headline: String,
-    pub location: String,
-    pub author: String,
-    pub content: String,
-}
-
-impl Summary for NewsArticle {}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize(&self) -> String {
-        format!("{}: {}", self.username, self.content)
-    }
-}
-

Приложение 10-14: Определение особенности Summary с выполнением способа summarize по умолчанию

-

Для использования выполнения по умолчанию при создании сводки у образцов NewsArticle вместо определения пользовательской выполнения, мы указываем пустой разделimpl с impl Summary for NewsArticle {}.

-

Хотя мы больше не определяем способ summarize непосредственно в NewsArticle, мы предоставили выполнение по умолчанию и указали, что NewsArticle выполняет особенность Summary. В итоге мы всё ещё можем вызвать способ summarize у образца NewsArticle, например так:

-
use aggregator::{self, NewsArticle, Summary};
-
-fn main() {
-    let article = NewsArticle {
-        headline: String::from("Penguins win the Stanley Cup Championship!"),
-        location: String::from("Pittsburgh, PA, USA"),
-        author: String::from("Iceburgh"),
-        content: String::from(
-            "The Pittsburgh Penguins once again are the best \
-             hockey team in the NHL.",
-        ),
-    };
-
-    println!("New article available! {}", article.summarize());
-}
-

Этот код печатает New article available! (Read more...) .

-

Создание выполнения по умолчанию не требует от нас изменений чего-либо в выполнения Summary для Tweet в приложении 10-13. Причина заключается в том, что правила написания для переопределения выполнения по умолчанию является таким же, как правила написания для выполнения способа особенности, который не имеет выполнения по умолчанию.

-

Выполнения по умолчанию могут вызывать другие способы в том же особенности, даже если эти другие способы не имеют выполнения по умолчанию. Таким образом, особенность может предоставить много полезной возможности и только требует от разработчиков указывать небольшую его часть. Например, мы могли бы определить особенность Summary имеющий способ summarize_author, выполнение которого требуется, а затем определить способ summarize который имеет выполнение по умолчанию, которая внутри вызывает способ summarize_author:

-
pub trait Summary {
-    fn summarize_author(&self) -> String;
-
-    fn summarize(&self) -> String {
-        format!("(Read more from {}...)", self.summarize_author())
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize_author(&self) -> String {
-        format!("@{}", self.username)
-    }
-}
-

Чтобы использовать такую исполнение особенности Summary, нужно только определить способ summarize_author, при выполнения особенности для вида:

-
pub trait Summary {
-    fn summarize_author(&self) -> String;
-
-    fn summarize(&self) -> String {
-        format!("(Read more from {}...)", self.summarize_author())
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize_author(&self) -> String {
-        format!("@{}", self.username)
-    }
-}
-

После того, как мы определим summarize_author, можно вызвать summarize для образцов устройства Tweet и выполнение по умолчанию способа summarize будет вызывать определение summarize_author которое мы уже предоставили. Так как мы выполнили способ summarize_author особенности Summary, то особенность даёт нам поведение способа summarize без необходимости писать код.

-
use aggregator::{self, Summary, Tweet};
-
-fn main() {
-    let tweet = Tweet {
-        username: String::from("horse_ebooks"),
-        content: String::from(
-            "of course, as you probably already know, people",
-        ),
-        reply: false,
-        retweet: false,
-    };
-
-    println!("1 new tweet: {}", tweet.summarize());
-}
-

Этот код печатает 1 new tweet: (Read more from @horse_ebooks...) .

-

Обратите внимание, что невозможно вызвать выполнение по умолчанию из переопределённой выполнения того же способа.

-

Особенности как свойства

-

Теперь, когда вы знаете, как определять и выполнить особенности, можно изучить, как использовать особенности, чтобы определить функции, которые принимают много различных видов. Мы будем использовать особенность Summary, выполненный для видов NewsArticle и Tweet в приложении 10-13, чтобы определить функцию notify, которая вызывает способ summarize для его свойства item, который имеет некоторый вид, выполняющий особенность Summary. Для этого мы используем правила написания impl Trait примерно так:

-
pub trait Summary {
-    fn summarize(&self) -> String;
-}
-
-pub struct NewsArticle {
-    pub headline: String,
-    pub location: String,
-    pub author: String,
-    pub content: String,
-}
-
-impl Summary for NewsArticle {
-    fn summarize(&self) -> String {
-        format!("{}, by {} ({})", self.headline, self.author, self.location)
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize(&self) -> String {
-        format!("{}: {}", self.username, self.content)
-    }
-}
-
-pub fn notify(item: &impl Summary) {
-    println!("Breaking news! {}", item.summarize());
-}
-

Вместо определенного вида у свойства item указывается ключевое слово impl и имя особенности. Этот свойство принимает любой вид, который выполняет указанный особенность. В теле notify мы можем вызывать любые способы у образца item , которые приходят с особенностью Summary, такие как способ summarize. Мы можем вызвать notify и передать в него любой образец NewsArticle или Tweet. Код, который вызывает данную функцию с любым другим видом, таким как String или i32, не будет собираться, потому что эти виды не выполняют особенность Summary.

- -

-

правила написания ограничения особенности

-

правила написания impl Trait работает для простых случаев, но на самом деле является синтаксическим сахаром для более длинной видовы, которая называется ограничением особенности (trait bound); это выглядит так:

-
pub fn notify<T: Summary>(item: &T) {
-    println!("Breaking news! {}", item.summarize());
-}
-

Эта более длинная разновидность эквивалентна примеру в предыдущем разделе, но она более многословна. Мы помещаем объявление свойства обобщённого вида с ограничением особенности после двоеточия внутри угловых скобок.

-

правила написания impl Trait удобен и делает код более сжатым в простых случаях, в то время как более полный правила написания с ограничением особенности в других случаях может выразить большую сложность. Например, у нас может быть два свойства, которые выполняют особенность Summary. Использование правил написания impl Trait выглядит так:

-
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
-

Использовать impl Trait удобнее если мы хотим разрешить функции иметь разные виды для item1 и item2 (но оба вида должны выполнить Summary). Если же мы хотим заставить оба свойства иметь один и тот же вид, то мы должны использовать ограничение особенности так:

-
pub fn notify<T: Summary>(item1: &T, item2: &T) {
-

Обобщённый вид T указан для видов свойств item1 и item2 и ограничивает функцию так, что определенные значения видов переданные переменнойми для item1 и item2 должны быть одинаковыми.

-

Задание нескольких границ особенностей с помощью правил написания +

-

Также можно указать более одного ограничения особенности. Допустим, мы хотели бы чтобы notify использовал как изменение -вывода так и summarize для свойства item:
тогда мы указываем что в notify свойство item должен выполнить оба особенности Display и Summary. Мы можем сделать это используя правила написания +:

-
pub fn notify(item: &(impl Summary + Display)) {
-

правила написания + также допустим с ограничениями особенности для обобщённых видов:

-
pub fn notify<T: Summary + Display>(item: &T) {
-

При наличии двух ограничений особенности, тело способа notify может вызывать summarize и использовать {} для изменения item при его печати.

-

Более ясные границы особенности с помощью where

-

Использование слишком большого количества ограничений особенности имеет свои недостатки. Каждый обобщённый вид имеет свои границы особенности, поэтому функции с несколькими свойствами обобщённого вида могут содержать много сведений об ограничениях между названием функции и списком её свойств затрудняющих чтение ярлыки. По этой причине в Ржавчина есть иной правила написания для определения ограничений особенности внутри предложения where после ярлыки функции. Поэтому вместо того, чтобы писать так:

-
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
-

можно использовать where таким образом:

-
fn some_function<T, U>(t: &T, u: &U) -> i32
-where
-    T: Display + Clone,
-    U: Clone + Debug,
-{
-    unimplemented!()
-}
-

Ярлык этой функции менее загромождена: название функции, список свойств, и возвращаемый вид находятся рядом, а ярлык не содержит в себе множество ограничений особенности.

-

Возврат значений вида выполняющего определённый особенность

-

Также можно использовать правила написания impl Trait в возвращаемой позиции, чтобы вернуть значение некоторого вида выполняющего особенность, как показано здесь:

-
pub trait Summary {
-    fn summarize(&self) -> String;
-}
-
-pub struct NewsArticle {
-    pub headline: String,
-    pub location: String,
-    pub author: String,
-    pub content: String,
-}
-
-impl Summary for NewsArticle {
-    fn summarize(&self) -> String {
-        format!("{}, by {} ({})", self.headline, self.author, self.location)
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize(&self) -> String {
-        format!("{}: {}", self.username, self.content)
-    }
-}
-
-fn returns_summarizable() -> impl Summary {
-    Tweet {
-        username: String::from("horse_ebooks"),
-        content: String::from(
-            "of course, as you probably already know, people",
-        ),
-        reply: false,
-        retweet: false,
-    }
-}
-

Используя impl Summary для возвращаемого вида, мы указываем, что функция returns_summarizable возвращает некоторый вид, который выполняет особенность Summary без обозначения определенного вида. В этом случае returns_summarizable возвращает Tweet, но код, вызывающий эту функцию, этого не знает.

-

Возможность возвращать вид, который определяется только выполняемым им признаком, особенно полезна в среде замыканий и повторителей, которые мы рассмотрим в Главе 13. Замыкания и повторители создают виды, которые знает только сборщик или виды, которые очень долго указывать. правила написания impl Trait позволяет кратко указать, что функция возвращает некоторый вид, который выполняет особенность Iterator без необходимости писать очень длинный вид.

-

Однако, impl Trait возможно использовать, если возвращаете только один вид. Например, данный код, который возвращает значения или вида NewsArticle или вида Tweet, но в качестве возвращаемого вида объявляет impl Summary , не будет работать:

-
pub trait Summary {
-    fn summarize(&self) -> String;
-}
-
-pub struct NewsArticle {
-    pub headline: String,
-    pub location: String,
-    pub author: String,
-    pub content: String,
-}
-
-impl Summary for NewsArticle {
-    fn summarize(&self) -> String {
-        format!("{}, by {} ({})", self.headline, self.author, self.location)
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize(&self) -> String {
-        format!("{}: {}", self.username, self.content)
-    }
-}
-
-fn returns_summarizable(switch: bool) -> impl Summary {
-    if switch {
-        NewsArticle {
-            headline: String::from(
-                "Penguins win the Stanley Cup Championship!",
-            ),
-            location: String::from("Pittsburgh, PA, USA"),
-            author: String::from("Iceburgh"),
-            content: String::from(
-                "The Pittsburgh Penguins once again are the best \
-                 hockey team in the NHL.",
-            ),
-        }
-    } else {
-        Tweet {
-            username: String::from("horse_ebooks"),
-            content: String::from(
-                "of course, as you probably already know, people",
-            ),
-            reply: false,
-            retweet: false,
-        }
-    }
-}
-

Возврат либо NewsArticle либо Tweet не допускается из-за ограничений того, как выполнен правила написания impl Trait в сборщике. Мы рассмотрим, как написать функцию с таким поведением в разделе "Использование предметов особенностей, которые разрешены для значений или разных видов" Главы 17.

-

Использование ограничений особенности для условной выполнения способов

-

Используя ограничение особенности с разделом impl, который использует свойства обобщённого вида, можно выполнить способы условно, для тех видов, которые выполняют указанный особенность. Например, вид Pair<T> в приложении 10-15 всегда выполняет функцию new для возврата нового образца Pair<T> (вспомните раздел “Определение способов” Главы 5 где Self является псевдонимом вида для вида раздела impl, который в данном случае является Pair<T>). Но в следующем разделе impl вид Pair<T> выполняет способ cmp_display только если его внутренний вид T выполняет особенность PartialOrd (позволяющий сравнивать) и особенность Display (позволяющий выводить на печать).

-

Файл: src/lib.rs

-
use std::fmt::Display;
-
-struct Pair<T> {
-    x: T,
-    y: T,
-}
-
-impl<T> Pair<T> {
-    fn new(x: T, y: T) -> Self {
-        Self { x, y }
-    }
-}
-
-impl<T: Display + PartialOrd> Pair<T> {
-    fn cmp_display(&self) {
-        if self.x >= self.y {
-            println!("The largest member is x = {}", self.x);
-        } else {
-            println!("The largest member is y = {}", self.y);
-        }
-    }
-}
-

Приложение 10-15: Условная выполнение способов у обобщённых видов в зависимости от ограничений особенности

-

Мы также можем условно выполнить особенность для любого вида, который выполняет другой особенность. Выполнения особенности для любого вида, который удовлетворяет ограничениям особенности, называются общими выполнениеми и широко используются в встроенной библиотеке Rust. Например, обычная библиотека выполняет особенность ToString для любого вида, который выполняет особенность Display. Разделimpl в встроенной библиотеке выглядит примерно так:

-
impl<T: Display> ToString for T {
-    // --snip--
-}
-

Поскольку обычная библиотека имеет эту общую выполнение, то можно вызвать способ to_string определённый особенностью ToString для любого вида, который выполняет особенность Display. Например, мы можем превратить целые числа в их соответствующие String значения, потому что целые числа выполняют особенность Display:

-
#![allow(unused)]
-fn main() {
-let s = 3.to_string();
-}
-

Общие выполнения приведены в документации к особенности в разделе "Implementors".

-

Особенности и ограничения особенностей позволяют писать код, который использует свойства обобщённого вида для уменьшения повторения кода, а также указывая сборщику, что мы хотим обобщённый вид, чтобы иметь определённое поведение. Затем сборщик может использовать сведения про ограничения особенности, чтобы проверить, что все определенные виды, используемые с нашим кодом, обеспечивают правильное поведение. В изменяемых строго определенных языках мы получили бы ошибку во время выполнения, если бы вызвали способ для вида, который не выполняет вид определяемый способом. Но Ржавчина перемещает эти ошибки на время сборки, поэтому мы вынуждены исправить сбоев, прежде чем наш код начнёт работать. Кроме того, мы не должны писать код, который проверяет своё поведение во время выполнения, потому что это уже проверено во время сборки. Это повышает производительность без необходимости отказываться от гибкости обобщённых видов.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch10-03-lifetime-syntax.html b/rustbook-ru/book/ch10-03-lifetime-syntax.html deleted file mode 100644 index ca385cc7f..000000000 --- a/rustbook-ru/book/ch10-03-lifetime-syntax.html +++ /dev/null @@ -1,658 +0,0 @@ - - - - - - Валидация ссылок посредством сроков жизни - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Валидация ссылок при помощи времён жизни

-

Сроки (времена) жизни - ещё один вид обобщений, с которыми мы уже встречались. Если раньше мы использовали обобщения, чтобы убедиться, что вид обладает нужным нам поведением, теперь мы будем использовать сроки жизни для того, чтобы быть уверенными, что ссылки действительны как самое меньшее столько времени в этапе исполнения программы, сколько нам требуется.

-

В разделе "Ссылки и заимствование" главы 4, мы кое о чём умолчали: у каждой ссылки в Ржавчина есть своё время жизни — область кода, на протяжении которого данная ссылка действительна (valid). В большинстве случаев сроки жизни выводятся неявно — так же, как у видов (нам требуется явно объявлять виды лишь в тех случаях, когда при самостоятельном выведении вида возможны исходы). Точно так же мы должны явно объявлять сроки жизни тех ссылок, для которых времена жизни могут быть определены сборщиком по-разному. Ржавчина требует от нас объявлять взаимосвязи посредством обобщённых свойств сроков жизни, чтобы убедиться в том, что во время исполнения все действующие ссылки будут правильными.

-

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

-

Времена жизни предотвращают появление "повисших" ссылок

-

Основное предназначение сроков жизни — предотвращать появление так называемых "повисших ссылок" (dangling references), из-за которых программа обращается не к тем данным, к которым она собиралась обратиться. Рассмотрим программу из приложения 10-16, имеющую внешнюю и внутреннюю области видимости.

-
fn main() {
-    let r;
-
-    {
-        let x = 5;
-        r = &x;
-    }
-
-    println!("r: {r}");
-}
-

Приложение 10-16: Попытка использования ссылки, значение которой вышло из области видимости

-
-

Примечание: примеры в приложениях 10-16, 10-17 и 10-23 объявляют переменные без указания их начального значения, поэтому имя переменной существует во внешней области видимости. На первый взгляд может показаться, что это противоречит отсутствию в Ржавчина нулевых (null) значений. Однако, если мы попытаемся использовать переменную, прежде чем присвоить ей значение, мы получим ошибку сборки, которая показывает, что Ржавчина действительно не разрешает нулевые (null) значения.

-
-

Внешняя область видимости объявляет переменную с именем r без начального значения, а внутренняя область объявляет переменную с именем x с начальным значением 5. Во внутренней области мы пытаемся установить значение r как ссылку на x. Затем внутренняя область видимости заканчивается и мы пытаемся напечатать значение из r. Этот код не будет собран, потому что значение на которое ссылается r исчезает из области видимости, прежде чем мы попробуем использовать его. Вот сообщение об ошибке:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0597]: `x` does not live long enough
- --> src/main.rs:6:13
-  |
-5 |         let x = 5;
-  |             - binding `x` declared here
-6 |         r = &x;
-  |             ^^ borrowed value does not live long enough
-7 |     }
-  |     - `x` dropped here while still borrowed
-8 |
-9 |     println!("r: {r}");
-  |                  --- borrow later used here
-
-For more information about this error, try `rustc --explain E0597`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

Переменная x «не живёт достаточно долго». Причина в том, что x выйдет из области видимости, когда эта внутренняя область закончится в строке 7. Но r все ещё является действительной во внешней области видимости; поскольку её охват больше, мы говорим, что она «живёт дольше». Если бы Ржавчина позволил такому коду работать, то переменная r смогла бы ссылаться на память, которая уже была освобождена (в тот мгновение, когда x вышла из внутренней области видимости), и всё что мы попытались бы сделать с r работало бы неправильно. Как же Ржавчина определяет, что этот код неправилен? Он использует для этого анализатор заимствований (borrow checker).

-

Анализатор заимствований

-

Сборщик Ржавчина имеет в своём составе анализатор заимствований, который сравнивает области видимости для определения, являются ли все заимствования действительными. В приложении 10-17 показан тот же код, что и в приложении 10-16, но с изложениями, показывающими времена жизни переменных.

-
fn main() {
-    let r;                // ---------+-- 'a
-                          //          |
-    {                     //          |
-        let x = 5;        // -+-- 'b  |
-        r = &x;           //  |       |
-    }                     // -+       |
-                          //          |
-    println!("r: {r}");   //          |
-}                         // ---------+
-

Пример 10-17: Изложение времён жизни переменных r и x, с помощью определителей времени жизни 'a и 'b, соответственно

-

Здесь мы описали время жизни для r с помощью 'a и время жизни x с помощью 'b . Как видите, время жизни 'b внутреннего раздела гораздо меньше, чем время жизни 'a внешнего раздела. Во время сборки Ржавчина сравнивает продолжительность двух времён жизни и видит, что r имеет время жизни 'a, но ссылается на память со временем жизни 'b. Программа отклоняется, потому что 'b короче, чем 'a: предмет ссылки не живёт так же долго, как сама ссылка.

-

Приложение 10-18 исправляет код, чтобы в нём не было повисшей ссылки, и собирается без ошибок.

-
fn main() {
-    let x = 5;            // ----------+-- 'b
-                          //           |
-    let r = &x;           // --+-- 'a  |
-                          //   |       |
-    println!("r: {r}");   //   |       |
-                          // --+       |
-}                         // ----------+
-

Приложение 10-18: Ссылка правильна, так как данные имеют более продолжительное время жизни, чем ссылка на эти данные

-

Здесь переменная x имеет время жизни 'b, которое больше, чем время жизни 'a. Это означает, что переменная r может ссылаться на переменную x потому что Ржавчина знает, что ссылка в переменной r будет всегда действительной до тех пор, пока переменная x является валидной.

-

После того, как мы на примерах рассмотрели времена жизни ссылок и обсудили как Ржавчина их анализирует, давайте поговорим об обобщённых временах жизни входных свойств и возвращаемых значений функций.

-

Обобщённые времена жизни в функциях

-

Напишем функцию, которая возвращает более длинный из двух срезов строки. Эта функция принимает два среза строки и возвращает один срез строки. После того как мы выполнили функцию longest, код в приложении 10-19 должен вывести The longest string is abcd.

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "xyz";
-
-    let result = longest(string1.as_str(), string2);
-    println!("The longest string is {result}");
-}
-

Приложение 10-19: Функция main вызывает функцию longest для поиска наибольшего из двух срезов строки

-

Обратите внимание, что мы хотим чтобы функция принимала строковые срезы, которые являются ссылками, а не строки, потому что мы не хотим, чтобы функция longest забирала во владение свои свойства. Обратитесь к разделу "Строковые срезы как свойства" Главы 4 для более подробного обсуждения того, почему свойства используемые в приложении 10-19 выбраны именно таким образом.

-

Если мы попробуем выполнить функцию longest так, как это показано в приложении 10-20, программа не собирается:

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "xyz";
-
-    let result = longest(string1.as_str(), string2);
-    println!("The longest string is {result}");
-}
-
-fn longest(x: &str, y: &str) -> &str {
-    if x.len() > y.len() {
-        x
-    } else {
-        y
-    }
-}
-

Приложение 10-20: Выполнение функции longest, которая возвращает наибольший срез строки, но пока не собирается

-

Вместо этого мы получим следующую ошибку, говорящую о временах жизни:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0106]: missing lifetime specifier
- --> src/main.rs:9:33
-  |
-9 | fn longest(x: &str, y: &str) -> &str {
-  |               ----     ----     ^ expected named lifetime parameter
-  |
-  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
-help: consider introducing a named lifetime parameter
-  |
-9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
-  |           ++++     ++          ++          ++
-
-For more information about this error, try `rustc --explain E0106`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

Текст ошибки показывает, что возвращаемому виду нужен обобщённый свойство времени жизни, потому что Ржавчина не может определить, относится ли возвращаемая ссылка к x или к y. На самом деле, мы тоже не знаем, потому что разделif в теле функции возвращает ссылку на x, а разделelse возвращает ссылку на y!

-

Когда мы определяем эту функцию, мы не знаем определенных значений, которые будут в неё передаваться. Поэтому мы не знаем какая из ветвей оператора if или else будет выполнена. Мы также не знаем определенных времён жизни ссылок, которые будут переданы в функцию, поэтому мы не можем посмотреть на их области видимости, как мы делали в примерах 10-17 и 10-18, чтобы определить, будет ли возвращаемая нами ссылка правильной во всех случаях. Анализатор заимствований также не может этого определить, потому что он не знает как времена жизни переменных x и y соотносятся с временем жизни возвращаемого значения. Чтобы исправить эту ошибку, мы добавим обобщённый свойство времени жизни, который определит отношения между ссылками таким образом, чтобы анализатор заимствований мог провести свой анализ.

-

правила написания изложении времени жизни

-

Изложения времени жизни не меняют срок, как долго живёт та или иная ссылка. Они скорее описывают, как соотносятся между собой времена жизни нескольких ссылок, не влияя на само время жизни. Точно так же, как функции могут принимать любой вид, когда в ярлыке указан свойство обобщённого вида, функции могут принимать ссылки с любым временем жизни, указанным с помощью свойства обобщённого времени жизни.

-

Изложения времени жизни имеют немного необычный правила написания: имена свойств времени жизни должны начинаться с апострофа ('), пишутся маленькими буквами, и обычно очень короткие, как и имена обобщённых видов. Большинство людей использует имя 'a в качестве первой изложении времени жизни. Изложения свойств времени жизни следуют после символа & и отделяются пробелом от названия ссылочного вида.

-

Приведём несколько примеров: у нас есть ссылка на i32 без указания времени жизни, ссылка на i32, с временем жизни имеющим имя 'a и изменяемая ссылка на i32, которая также имеет время жизни 'a.

-
&i32        // a reference
-&'a i32     // a reference with an explicit lifetime
-&'a mut i32 // a mutable reference with an explicit lifetime
-

Одна изложение времени жизни сама по себе не имеет большого значения, поскольку изложении предназначены для того, чтобы уведомить Ржавчина о том, как времена жизни нескольких ссылок соотносятся между собой. Давайте рассмотрим, как изложении времени жизни связаны друг с другом в среде функции longest.

-

Изложения времени жизни в ярлыках функций

-

Чтобы использовать изложении времени жизни в ярлыках функций, нам нужно объявить свойства обобщённого времени жизни внутри угловых скобок между именем функции и списком свойств, как мы это делали с свойствами обобщённого вида .

-

Мы хотим, чтобы ярлык отражала следующее ограничение: возвращаемая ссылка будет действительна до тех пор, пока валидны оба свойства. Это связь между временами жизни свойств и возвращаемого значения. Мы назовём это время жизни 'a, а затем добавим его к каждой ссылке, как показано в приложении 10-21.

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "xyz";
-
-    let result = longest(string1.as_str(), string2);
-    println!("The longest string is {result}");
-}
-
-fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
-    if x.len() > y.len() {
-        x
-    } else {
-        y
-    }
-}
-

Приложение 10-21: В определении функции longest указано, что все ссылки должны иметь одинаковое время жизни, обозначенное как 'a

-

Этот код должен собираться и давать желаемый итог, когда мы вызовем его в функции main приложения 10-19.

-

Ярлык функции теперь сообщает Rust, что для некоторого времени жизни 'a функция принимает два свойства, оба из которых являются срезами строк, которые живут не меньше, чем время жизни 'a. Ярлык функции также сообщает Rust, что срез строки, возвращаемый функцией, будет жить как самое меньшее столько, сколько длится время жизни 'a. В действительностиэто означает, что время жизни ссылки, возвращаемой функцией longest, равно меньшему времени жизни передаваемых в неё ссылок. Мы хотим, чтобы Ржавчина использовал именно такие отношения при анализе этого кода.

-

Помните, когда мы указываем свойства времени жизни в этой ярлыке функции, мы не меняем время жизни каких-либо переданных или возвращённых значений. Скорее, мы указываем, что анализатор заимствований должен отклонять любые значения, которые не соответствуют этим ограничениям. Обратите внимание, что самой функции longest не нужно точно знать, как долго будут жить x и y, достаточно того, что некоторая область может быть заменена на 'a, которая будет удовлетворять этой ярлыке.

-

При определении времён жизни функций, изложении помещаются в ярлык функции, а не в тело функции. Изложения времени жизни становятся частью договора функции, как и виды в ярлыке. Наличие ярлыков функций, содержащих договор времени жизни, означает, что анализ который выполняет сборщик Rust, может быть проще. Если есть неполадка с тем, как функция определяется или как она вызывается, ошибки сборщика могут указать на часть нашего кода и ограничения более точно. Если бы вместо этого сборщик Ржавчина сделал больше предположений о том, какие отношения времён жизни мы хотели получить, сборщик смог бы указать только на использование нашего кода за много шагов от источника сбоев.

-

Когда мы передаём определенные ссылки в функцию longest, определенным временем жизни, которое будет заменено на 'a, является часть области видимости x, которая пересекается с областью видимости y. Другими словами, обобщённое время жизни 'a получит определенное время жизни, равное меньшему из времён жизни x и y. Так как мы определяли возвращаемую ссылку тем же свойствоом времени жизни 'a, то возвращённая ссылка также будет действительна на протяжении меньшего из времён жизни x и y.

-

Давайте посмотрим, как изложении времени жизни ограничивают функцию longest путём передачи в неё ссылок, которые имеют разные определенные времена жизни. Приложение 10-22 является очевидным примером.

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("long string is long");
-
-    {
-        let string2 = String::from("xyz");
-        let result = longest(string1.as_str(), string2.as_str());
-        println!("The longest string is {result}");
-    }
-}
-
-fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
-    if x.len() > y.len() {
-        x
-    } else {
-        y
-    }
-}
-

Приложение 10-22: Использование функции longest со ссылками на значения вида String, имеющими разное время жизни

-

В этом примере переменная string1 действительна до конца внешней области, string2 действует до конца внутренней области видимости и result ссылается на что-то, что является действительным до конца внутренней области видимости. Запустите этот код, и вы увидите что анализатор заимствований разрешает такой код; он собирает и напечатает The longest string is long string is long.

-

Теперь, давайте попробуем пример, который показывает, что время жизни ссылки result должно быть меньшим временем жизни одного из двух переменных. Мы переместим объявление переменной result за пределы внутренней области видимости, но оставим присвоение значения переменной result в области видимости string2. Затем мы переместим println!, который использует result за пределы внутренней области видимости, после того как внутренняя область видимости закончилась. Код в приложении 10-23 не собирается.

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("long string is long");
-    let result;
-    {
-        let string2 = String::from("xyz");
-        result = longest(string1.as_str(), string2.as_str());
-    }
-    println!("The longest string is {result}");
-}
-
-fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
-    if x.len() > y.len() {
-        x
-    } else {
-        y
-    }
-}
-

Приложение 10-23: Попытка использования result, после того как string2 вышла из области видимости

-

При попытке собрать этот код, мы получим такую ошибку:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0597]: `string2` does not live long enough
- --> src/main.rs:6:44
-  |
-5 |         let string2 = String::from("xyz");
-  |             ------- binding `string2` declared here
-6 |         result = longest(string1.as_str(), string2.as_str());
-  |                                            ^^^^^^^ borrowed value does not live long enough
-7 |     }
-  |     - `string2` dropped here while still borrowed
-8 |     println!("The longest string is {result}");
-  |                                     -------- borrow later used here
-
-For more information about this error, try `rustc --explain E0597`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

Эта ошибка говорит о том, что если мы хотим использовать result в указания println!, переменная string2 должна бы быть действительной до конца внешней области видимости. Ржавчина знает об этом, потому что мы определяли свойства функции и её возвращаемое значение одинаковым временем жизни 'a.

-

Будучи людьми, мы можем посмотреть на этот код и увидеть, что string1 длиннее, чем string2 и, следовательно, result будет содержать ссылку на string1. Поскольку string1 ещё не вышла из области видимости, ссылка на string1 будет все ещё действительной в указания println!. Однако сборщик не видит, что ссылка в этом случае валидна. Мы сказали Rust, что время жизни ссылки, возвращаемой из функции longest, равняется меньшему из времён жизни переданных в неё ссылок. Таким образом, анализатор заимствований запрещает код в приложении 10-23, как возможно имеющий недействительную ссылку.

-

Попробуйте провести больше экспериментов с различными значениями и временами жизни ссылок, передаваемых в функцию longest, а также с тем, как используется возвращаемое значение Перед сборкой делайте предположения о том, пройдёт ли ваш код анализ заимствований, а затем проверяйте, насколько вы были правы.

-

Мышление в понятиях времён жизни

-

В зависимости от того, что делает ваша функция, следует использовать разные способы указания свойств времени жизни. Например, если мы изменим выполнение функции longest таким образом, чтобы она всегда возвращала свой первый переменная вместо самого длинного среза строки, то время жизни для свойства y можно совсем не указывать. Этот код собирается:

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "efghijklmnopqrstuvwxyz";
-
-    let result = longest(string1.as_str(), string2);
-    println!("The longest string is {result}");
-}
-
-fn longest<'a>(x: &'a str, y: &str) -> &'a str {
-    x
-}
-

Мы указали свойство времени жизни 'a для свойства x и возвращаемого значения, но не для свойства y, поскольку время жизни свойства y никак не соотносится с временем жизни свойства x или возвращаемого значения.

-

При возврате ссылки из функции, свойство времени жизни для возвращаемого вида должен соответствовать свойству времени жизни одного из переменных. Если возвращаемая ссылка не ссылается на один из свойств, она должна ссылаться на значение, созданное внутри функции. Однако, это приведёт к недействительной ссылке, поскольку значение, на которое она ссылается, выйдет из области видимости в конце функции. Посмотрите на попытку выполнения функции longest, которая не собирается:

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "xyz";
-
-    let result = longest(string1.as_str(), string2);
-    println!("The longest string is {result}");
-}
-
-fn longest<'a>(x: &str, y: &str) -> &'a str {
-    let result = String::from("really long string");
-    result.as_str()
-}
-

Здесь, несмотря на то, что мы указали свойство времени жизни 'a для возвращаемого вида, выполнение не будет собрана, потому что время жизни возвращаемого значения никак не связано с временем жизни свойств. Получаем сообщение об ошибке:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0515]: cannot return value referencing local variable `result`
-  --> src/main.rs:11:5
-   |
-11 |     result.as_str()
-   |     ------^^^^^^^^^
-   |     |
-   |     returns a value referencing data owned by the current function
-   |     `result` is borrowed here
-
-For more information about this error, try `rustc --explain E0515`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

Неполадказаключается в том, что result выходит за область видимости и очищается в конце функции longest. Мы также пытаемся вернуть ссылку на result из функции. Мы не можем указать свойства времени жизни, которые могли бы изменить недействительную ссылку, а Ржавчина не позволит нам создать недействительную ссылку. В этом случае лучшим решением будет вернуть владеющий вид данных, а не ссылку: в этом случае вызывающая функция будет нести ответственность за очистку полученного ею значения.

-

В конечном итоге, правила написания времён жизни выполняет связывание времён жизни различных переменных и возвращаемых значений функций. Описывая времена жизни, мы даём Ржавчина достаточно сведений, чтобы разрешить безопасные действия с памятью и запретить действия, которые могли бы создать недействительные ссылки или иным способом нарушить безопасность памяти.

-

Определение времён жизни при объявлении устройств

-

До сих пор мы объявляли устройства, которые всегда содержали владеющие виды данных. Устройства могут содержать и ссылки, но при этом необходимо добавить изложение времени жизни для каждой ссылки в определении устройства. Приложение 10-24 описывает устройство ImportantExcerpt, содержащую срез строки:

-

Файл: src/main.rs

-
struct ImportantExcerpt<'a> {
-    part: &'a str,
-}
-
-fn main() {
-    let novel = String::from("Call me Ishmael. Some years ago...");
-    let first_sentence = novel.split('.').next().unwrap();
-    let i = ImportantExcerpt {
-        part: first_sentence,
-    };
-}
-

Приложение 10-25. Устройства, содержащая ссылку, требует изложении времени жизни

-

У устройства имеется одно поле part, хранящее срез строки, который сам по себе является ссылкой. Как и в случае с обобщёнными видами данных, мы объявляем имя обобщённого свойства времени жизни внутри угловых скобок после имени устройства, чтобы иметь возможность использовать его внутри определения устройства. Данная изложение означает, что образец ImportantExcerpt не может пережить ссылку, которую он содержит в своём поле part.

-

Функция main здесь создаёт образец устройства ImportantExcerpt, который содержит ссылку на первое предложение вида String принадлежащее переменной novel. Данные в novel существуют до создания образца ImportantExcerpt. Кроме того, novel не выходит из области видимости до тех пор, пока ImportantExcerpt не выйдет за область видимости, поэтому ссылка в внутри образца ImportantExcerpt является действительной.

-

Правила неявного выведения времени жизни

-

Вы изучили, что у каждой ссылки есть время жизни и что нужно указывать свойства времени жизни для функций или устройств, которые используют ссылки. Однако в Главе 4 у нас была функция в приложении 4-9, которая затем снова показана в приложении 10-25, в которой код собрался без наставлений времени жизни.

-

Файл: src/lib.rs

-
fn first_word(s: &str) -> &str {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return &s[0..i];
-        }
-    }
-
-    &s[..]
-}
-
-fn main() {
-    let my_string = String::from("hello world");
-
-    // first_word works on slices of `String`s
-    let word = first_word(&my_string[..]);
-
-    let my_string_literal = "hello world";
-
-    // first_word works on slices of string literals
-    let word = first_word(&my_string_literal[..]);
-
-    // Because string literals *are* string slices already,
-    // this works too, without the slice syntax!
-    let word = first_word(my_string_literal);
-}
-

Приложение 10-25: Функция, которую мы определили в приложении 4-9 собирается без наставлений времени жизни, несмотря на то, что входной и возвращаемый вид свойств являются ссылками

-

Причина, по которой этот код собирается — историческая. В ранних (до-1.0) исполнениях Ржавчина этот код не собрался бы, поскольку каждой ссылке нужно было явно назначать время жизни. В те времена, ярлык функции была бы написана примерно так:

-
fn first_word<'a>(s: &'a str) -> &'a str {
-

После написания большого количества кода на Ржавчина разработчики языка обнаружили, что в определённых случаейх программисты описывают одни и те же изложении времён жизни снова и снова. Эти случаи были предсказуемы и следовали нескольким определенным образцовым моделям. Объединение Ржавчина решила запрограммировать эти образцы в код сборщика Rust, чтобы анализатор заимствований мог вывести времена жизни в таких случаейх без необходимости явного указания наставлений программистами.

-

Мы упоминаем этот отрывок истории Rust, потому что возможно, что в будущем появится больше образцов для самостоятельного выведения времён жизни, которые будут добавлены в сборщик. Таким образом, в будущем может понадобится ещё меньшее количество наставлений.

-

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

-

Правила выведения не предоставляют полного заключения. Если Ржавчина определенно применяет правила, но некоторая неясность относительно времён жизни ссылок все ещё остаётся, сборщик не будет догадываться, какими должны быть времена жизни оставшихся ссылок. В этом случае, вместо угадывания сборщик выдаст ошибку, которую вы можете устранить, добавив изложении времени жизни.

-

Времена жизни свойств функции или способа называются временем жизни ввода, а времена жизни возвращаемых значений называются временем жизни вывода.

-

Сборщик использует три правила, чтобы выяснить времена жизни ссылок при отсутствии явных наставлений. Первое правило относится ко времени жизни ввода, второе и третье правила применяются ко временам жизни вывода. Если сборщик доходит до конца проверки трёх правил и всё ещё есть ссылки, для которых он не может выяснить время жизни, сборщик остановится с ошибкой. Эти правила применяются к объявлениям fn, а также к разделам impl.

-

Первое правило заключается в том, что каждый свойство являющийся ссылкой, получает свой собственный свойство времени жизни. Другими словами, функция с одним свойствоом получит один свойство времени жизни: fn foo<'a>(x: &'a i32); функция с двумя переменнойми получит два отдельных свойства времени жизни: fn foo<'a, 'b>(x: &'a i32, y: &'b i32), и так далее.

-

Второе правило говорит, что если есть ровно один входной свойство времени жизни, то его время жизни назначается всем выходным свойствам: fn foo<'a>(x: &'a i32) -> &'a i32.

-

Третье правило о том, что если есть множество входных свойств времени жизни, но один из них является ссылкой &self или &mut self, так как эта функция является способом, то время жизни self назначается временем жизни всем выходным свойствам. Это третье правило делает способы намного приятнее для чтения и записи, потому что требуется меньше символов.

-

Представим, что мы сборщик и применим эти правила, чтобы вывести времена жизни ссылок в ярлыке функции first_word приложения 10-25. Ярлык этой функции начинается без объявления времён жизни ссылок:

-
fn first_word(s: &str) -> &str {
-

Теперь мы (в качестве сборщика) применим первое правило, утверждающее, что каждый свойство функции получает своё собственное время жизни. Как обычно, назовём его 'a и теперь ярлык выглядит так:

-
fn first_word<'a>(s: &'a str) -> &str {
-

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

-
fn first_word<'a>(s: &'a str) -> &'a str {
-

Теперь все ссылки в этой функции имеют свойства времени жизни и сборщик может продолжить свой анализ без необходимости просить у программиста указать изложении времён жизни в ярлыке этой функции.

-

Давайте рассмотрим ещё один пример: на этот раз функцию longest, в которой не было свойств времени жизни, когда мы начали с ней работать в приложении 10-20:

-
fn longest(x: &str, y: &str) -> &str {
-

Применим первое правило: каждому свойству назначается собственное время жизни. На этот раз у функции есть два свойства, поэтому есть два времени жизни:

-
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
-

Можно заметить, что второе правило здесь не применимо, так как в ярлыке указано больше одного входного свойства времени жизни. Третье правило также не применимо, так как longest — функция, а не способ, следовательно, в ней нет свойства self. Итак, мы прошли все три правила, но так и не смогли вычислить время жизни выходного свойства. Поэтому мы и получили ошибку при попытке собрать код приложения 10-20: сборщик работал по правилам неявного выведения времён жизни, но не мог выяснить все времена жизни ссылок в ярлыке.

-

Так как третье правило применяется только к способам, далее мы рассмотрим времена жизни в этом среде, чтобы понять, почему нам часто не требуется определять времена жизни в ярлыках способов.

-

Изложение времён жизни в определении способов

-

Когда мы выполняем способы для устройств с временами жизни, мы используем тот же правила написания, который применялся для наставлений обобщённых видов данных на приложении 10-11. Место, где мы объявляем и используем времена жизни, зависит от того, с чем они связаны — с полями устройства, либо с переменнойми способов и возвращаемыми значениями.

-

Имена свойств времени жизни для полей устройств всегда описываются после ключевого слова impl и затем используются после имени устройства, поскольку эти времена жизни являются частью вида устройства.

-

В ярлыках способов внутри раздела impl ссылки могут быть привязаны ко времени жизни ссылок в полях устройства, либо могут быть независимыми. Вдобавок, правила неявного выведения времён жизни часто делают так, что изложении переменных времён жизни являются необязательными в ярлыках способов. Рассмотрим несколько примеров, использующих устройство с названием ImportantExcerpt, которую мы определили в приложении 10-24.

-

Сначала, воспользуемся способом level, чей единственный свойство является ссылкой на self, а возвращаемое значение i32, не является ссылкой ни на что:

-
struct ImportantExcerpt<'a> {
-    part: &'a str,
-}
-
-impl<'a> ImportantExcerpt<'a> {
-    fn level(&self) -> i32 {
-        3
-    }
-}
-
-impl<'a> ImportantExcerpt<'a> {
-    fn announce_and_return_part(&self, announcement: &str) -> &str {
-        println!("Attention please: {announcement}");
-        self.part
-    }
-}
-
-fn main() {
-    let novel = String::from("Call me Ishmael. Some years ago...");
-    let first_sentence = novel.split('.').next().unwrap();
-    let i = ImportantExcerpt {
-        part: first_sentence,
-    };
-}
-

Объявление свойства времени жизни после impl и его использование после имени вида является обязательным, но нам не нужно определять время жизни ссылки на self, благодаря первому правилу неявного выведения времён жизни.

-

Вот пример, где применяется третье правило неявного выведения времён жизни:

-
struct ImportantExcerpt<'a> {
-    part: &'a str,
-}
-
-impl<'a> ImportantExcerpt<'a> {
-    fn level(&self) -> i32 {
-        3
-    }
-}
-
-impl<'a> ImportantExcerpt<'a> {
-    fn announce_and_return_part(&self, announcement: &str) -> &str {
-        println!("Attention please: {announcement}");
-        self.part
-    }
-}
-
-fn main() {
-    let novel = String::from("Call me Ishmael. Some years ago...");
-    let first_sentence = novel.split('.').next().unwrap();
-    let i = ImportantExcerpt {
-        part: first_sentence,
-    };
-}
-

В этом способе имеется два входных свойства, поэтому Ржавчина применит первое правило и назначит обоим свойствам &self и announcement собственные времена жизни. Далее, поскольку один из свойств является &self, то возвращаемое значение получает время жизни переменой &self и все времена жизни теперь выведены.

-

Постоянное время жизни

-

Одно особенное время жизни, которое мы должны обсудить, называется 'static. Оно означает, что данная ссылка может жить всю продолжительность работы программы. Все строковые записи по умолчанию имеют время жизни 'static, но мы можем указать его явным образом:

-
#![allow(unused)]
-fn main() {
-let s: &'static str = "I have a static lifetime.";
-}
-

Содержание этой строки сохраняется внутри двоичного файл программы и всегда доступно для использования. Следовательно, время жизни всех строковых записей равно 'static.

-

Сообщения сборщика об ошибках в качестве решения сбоев могут предлагать вам использовать время жизни 'static. Но прежде чем указывать 'static как время жизни для ссылки, подумайте, на самом ли деле данная ссылка будет доступна во всё время работы программы. В большинстве случаев, сообщения об ошибках, предлагающие использовать время жизни 'static появляются при попытках создания недействительных ссылок или несовпадения имеющихся времён жизни. В таких случаях, решение заключается в исправлении таких неполадок. а не в указании постоянного времени жизни 'static.

-

Обобщённые виды свойств, ограничения особенностей и времена жизни вместе

-

Давайте кратко рассмотрим правила написания задания свойств обобщённых видов, ограничений особенности и времён жизни совместно в одной функции:

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "xyz";
-
-    let result = longest_with_an_announcement(
-        string1.as_str(),
-        string2,
-        "Today is someone's birthday!",
-    );
-    println!("The longest string is {result}");
-}
-
-use std::fmt::Display;
-
-fn longest_with_an_announcement<'a, T>(
-    x: &'a str,
-    y: &'a str,
-    ann: T,
-) -> &'a str
-where
-    T: Display,
-{
-    println!("Announcement! {ann}");
-    if x.len() > y.len() {
-        x
-    } else {
-        y
-    }
-}
-

Это функция longest из приложения 10-21, которая возвращает наибольший из двух срезов строки. Но теперь у неё есть дополнительный свойство с именем ann обобщённого вида T, который может быть представлен любым видом, выполняющим особенность Display, как указано в предложении where. Этот дополнительный свойство будет напечатан с использованием {} , поэтому ограничение особенности Display необходимо. Поскольку время жизни является обобщённым видом, то объявления свойства времени жизни 'a и свойства обобщённого вида T помещаются в один список внутри угловых скобок после имени функции.

-

Итоги

-

В этой главе мы рассмотрели много всего! Теперь вы знакомы с свойствами обобщённого вида, особенностями и ограничениями особенности, обобщёнными свойствами времени жизни, вы готовы писать код без повторений, который будет работать во множестве различных случаев. Свойства обобщённого вида позволяют использовать код для различных видов данных. Особенности и ограничения особенности помогают убедиться, что, хотя виды и обобщённые, они будут вести себя, как этого требует ваш код. Вы изучили, как использовать изложении времени жизни чтобы убедиться, что этот гибкий код не будет порождать никаких повисших ссылок. И весь этот анализ происходит в мгновение сборки и не влияет на производительность программы во время работы!

-

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

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch11-00-testing.html b/rustbook-ru/book/ch11-00-testing.html deleted file mode 100644 index 1f0621904..000000000 --- a/rustbook-ru/book/ch11-00-testing.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - Написание самостоятельно х проверок - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Написание автоматизированных проверок

-

В своём эссе 1972 года “The Humble Programmer,” Edsger W. Dijkstra сказал, что «Проверка программы может быть очень эффективным способом показать наличие ошибок, но это безнадёжно неадекватно для показа их отсутствия». Это не значит, что мы не должны пытаться проверять столько, сколько мы можем!

-

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

-

Допустим, мы пишем функцию add_two, которая прибавляет 2 к любому переданному ей числу. Ярлык этой функции принимает целое число в качестве свойства и возвращает целое число в качестве итога. Когда мы выполняем и собираем эту функцию, Ржавчина выполняет всю проверку видов и проверку заимствований, которую вы уже изучили, чтобы убедиться, что, например, мы не передаём значение String или недопустимую ссылку в эту функцию. Но Ржавчина не способен проверить, что эта функция сделает именно то, что мы задумали, то есть вернёт свойство плюс 2, а не, скажем, свойство плюс 10 или свойство - 50! Вот тут-то и приходят на помощь проверки.

-

Мы можем написать проверки, которые утверждают, например, что когда мы передаём 3 в функцию add_two, возвращаемое значение будет 5. Мы можем запускать эти проверки всякий раз, когда мы вносим изменения в наш код, чтобы убедиться, что любое существующее правильное поведение не изменилось.

-

Проверка - сложный навык: мы не сможем охватить все подробности написания хороших проверок в одной главе, но мы обсудим основные подходы к проверке в Rust. Мы поговорим об изложениех и макросах, доступных вам для написания проверок, о поведении по умолчанию и свойствах, предусмотренных для запуска проверок, а также о том, как согласовать проверки в состоящие из звеньев проверки и встроенные проверки.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch11-01-writing-tests.html b/rustbook-ru/book/ch11-01-writing-tests.html deleted file mode 100644 index c82e583ee..000000000 --- a/rustbook-ru/book/ch11-01-writing-tests.html +++ /dev/null @@ -1,990 +0,0 @@ - - - - - - Как писать проверки - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Как писать проверки

-

Проверки - это функции Rust, которые проверяют, что не проверочный код работает ожидаемым образом. Содержимое проверочных функций обычно выполняет следующие три действия:

-
    -
  1. Установка любых необходимых данных или состояния.
  2. -
  3. Запуск кода, который вы хотите проверить.
  4. -
  5. Утверждение, что итоги являются теми, которые вы ожидаете.
  6. -
-

Давайте рассмотрим функции предоставляемые в Ржавчина целенаправленно для написания проверок, которые выполнят все эти действия, включая свойство test, несколько макросов и свойство should_panic.

-

Устройства проверяющей функции

-

В простейшем случае в Ржавчина проверка - это функция, определенная свойством test. Свойства представляют собой метаданные о отрывках кода Rust; один из примеров свойство derive, который мы использовали со устройствами в главе 5. Чтобы превратить функцию в проверяющую функцию добавьте #[test] в строку перед fn. Когда вы запускаете проверки приказом cargo test, Ржавчина создаёт двоичный звено выполняющий функции определеные свойством test и сообщающий о том, успешно или нет прошла каждая проверяющая функция.

-

Когда мы создаём новый дело библиотеки с помощью Cargo, то в нём самостоятельно порождается проверочный звено с проверку-функцией для нас. Этот звено даст вам образец для написания ваших проверок, так что вам не нужно искать точную устройство и правила написания проверочных функций каждый раз, когда вы начинаете новый дело. Вы можете добавить столько дополнительных проверочных функций и столько проверочных звеньев, сколько захотите!

-

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

-

Давайте создадим новый библиотечный дело под названием adder, который складывает два числа:

-
$ cargo new adder --lib
-     Created library `adder` project
-$ cd adder
-
-

Содержимое файла src/lib.rs вашей библиотеки adder должно выглядеть как в приложении 11-1.

-

Файл: src/lib.rs

- -
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_works() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-}
-

Приложение 11-1: Проверочный звено и функция, созданные самостоятельно с помощью cargo new

-

Сейчас давайте пренебрегаем первые две строчки кода и сосредоточимся на функции. Обратите внимание на правила написания изложении #[test]: этот свойство указывает, что это проверочная функция, поэтому запускающий проверка знает, что эту функцию следует рассматривать как проверочную. У нас также могут быть не проверяемые функции в звене tests, которые помогут настроить общие сценарии или выполнить общие действия, поэтому нам всегда нужно указывать, какие функции являются проверкими.

-

В теле функции проверки используется макрос assert_eq!, чтобы утверждать, что result, который содержит итог сложения 2 и 2, равен 4. Это утверждение служит примером вида для типичного проверки. Давайте запустим его, чтобы убедиться, что этот проверка пройден.

-

Приказ cargo test выполнит все проверки в выбранном деле и сообщит о итогах как в приложении 11-2:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.57s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-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 adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Приложение 11-2: Вывод сведений о работе самостоятельно созданных проверок

-

Cargo собрал и выполнил проверку. Мы видим строку running 1 test. Следующая строка показывает имя созданной проверочной функции, называемой it_works, и итог запуска этого проверки равный ok. Текст test result: ok. означает, что все проверки пройдены успешно и часть вывода 1 passed; 0 failed сообщает общее количество проверок, которые прошли или были ошибочными.

-

Можно пометить проверка как пренебрегаемый, чтобы он не выполнялся в определенном случае; мы рассмотрим это в разделе “Пренебрежение некоторых проверок, если их целенаправленно не запрашивать” позже в этой главе. Поскольку в данный мгновение мы этого не сделали, в сводке показано, что 0 ignored. Мы также можем передать переменная приказу cargo test для запуска только тех проверок, имя которых соответствует строке; это называется выборкой, и мы рассмотрим это в разделе “Запуск подмножества проверок по имени”. Мы также не фильтровали выполняемые проверки, поэтому в конце сводки показано, что 0 filtered out.

-

Исчисление 0 measured предназначена для проверок производительности. На мгновение написания этой статьи такие проверки доступны только в ночной сборке Rust. Посмотрите документацию о проверках производительности, чтобы узнать больше.

-

Следующая часть вывода проверок начинается с Doc-tests adder - это сведения о проверках в документации. У нас пока нет проверок документации, но Ржавчина может собирать любые примеры кода, которые находятся в API документации. Такая возможность помогает поддерживать документацию и код в согласованном состоянии. Мы поговорим о написании проверок документации в разделы "Примечания документации как проверки" Главы 14. Пока просто пренебрегаем часть Doc-tests вывода.

-

Давайте начнём настраивать проверка в соответствии с нашими собственными потребностями. Сначала поменяем название нашего проверки it_works на exploration, вот так:

-

Файл: src/lib.rs

-
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn exploration() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-}
-

Снова выполним приказ cargo test. Вывод показывает наименование нашей проверку-функции - exploration вместо it_works:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.59s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test tests::exploration ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Добавим ещё один проверка, но в этот раз целенаправленно сделаем так, чтобы этот новый проверка не отработал! Проверка терпит неудачу, когда что-то паникует в проверяемой функции. Каждый проверка запускается в новом потоке и когда главный поток видит, что проверочный поток упал, то помечает проверка как завершившийся со сбоем. Мы говорили о простейшем способе вызвать панику в главе 9, используя для этого известный макрос panic!. Введём код проверку-функции another, как в файле src/lib.rs из приложения 11-3.

-

Файл: src/lib.rs

-
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn exploration() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-
-    #[test]
-    fn another() {
-        panic!("Make this test fail");
-    }
-}
-

Приложение 11-3: Добавление второго проверки, который завершится ошибкой, потому что мы вызываем panic! макрос

-

Запустим приказ cargo test. Вывод итогов показан в приложении 11-4, который сообщает, что проверка exploration пройден, а another нет:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.72s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 2 tests
-test tests::another ... FAILED
-test tests::exploration ... ok
-
-failures:
-
----- tests::another stdout ----
-thread 'tests::another' panicked at src/lib.rs:17:9:
-Make this test fail
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::another
-
-test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Приложение 11-4. Итоги проверки, когда один проверка пройден, а другой нет

-

Вместо ok, строка test tests::another сообщает FAILED. Две новые разделы появились между отдельными итогами и сводкой: в первом отображается подробная причина каждого сбоя проверки. В данном случае проверка another не сработал, потому что panicked at 'Make this test fail', произошло в строке 10 файла src/lib.rs. В следующем разделе перечисляют имена всех не пройденных проверок, что удобно, когда есть много проверок и много подробных итогов неудачных проверок. Мы можем использовать имя не пройденного проверки для его дальнейшей отладки; мы больше поговорим о способах запуска проверок в разделе "Управление хода выполнения проверок".

-

Итоговая строка отображается в конце: общий итог нашего проверки FAILED. У нас один проверка пройден и один проверка завершён со сбоем.

-

Теперь, когда вы увидели, как выглядят итоги проверки при разных сценариях, давайте рассмотрим другие макросы полезные в проверках, кроме panic!.

-

Проверка итогов с помощью макроса assert!

-

Макрос assert! доступен из встроенной библиотеки и является удобным, когда вы хотите проверить что некоторое условие в проверке вычисляется в значение true. Мы передаём в макрос assert! переменная, который вычисляется в логическое значение. Если оно true, то ничего не происходит и проверка считается пройденным. Если же значение вычисляется в false, то макрос assert! вызывает макрос panic!, чтобы вызвать сбой проверки. Использование макроса assert! помогает проверить, что код исполняется как ожидалось.

-

В главе 5, приложении 5-15, мы использовали устройство Rectangle и способ can_hold, который повторён в приложении 11-5. Давайте поместим этот код в файл src/lib.rs и напишем несколько проверок для него используя макрос assert!.

-

Файл: src/lib.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width > other.width && self.height > other.height
-    }
-}
-

Приложение 11-5: Использование устройства Rectangle и её способа can_hold из главы 5

-

Способ can_hold возвращает логическое значение, что означает, что он является наилучшим исходом использования в макросе assert!. В приложении 11-6 мы пишем проверка, который выполняет способ can_hold путём создания образца Rectangle шириной 8 и высотой 7 и убеждаемся, что он может содержать другой образец Rectangle имеющий ширину 5 и высоту 1.

-

Файл: src/lib.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width > other.width && self.height > other.height
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn larger_can_hold_smaller() {
-        let larger = Rectangle {
-            width: 8,
-            height: 7,
-        };
-        let smaller = Rectangle {
-            width: 5,
-            height: 1,
-        };
-
-        assert!(larger.can_hold(&smaller));
-    }
-}
-

Приложение 11-6: Проверка для способа can_hold, который проверяет что больший прямоугольник действительно может содержать меньший

-

Также, в звене tests обратите внимание на новую добавленную строку use super::*;. Звено tests является обычным и подчиняется тем же правилам видимости, которые мы обсуждали в главе 7 "Пути для ссылки на элементы внутри дерева звена". Так как этот звено tests является внутренним, нужно подключить проверяемый код из внешнего звена в область видимости внутреннего звена с проверкими. Для этого используется вездесущеее подключение, так что все что определено во внешнем звене становится доступным внутри tests звена.

-

Мы назвали наш проверка larger_can_hold_smaller и создали два нужных образца Rectangle. Затем вызвали макрос assert! и передали итог вызова larger.can_hold(&smaller) в него. Это выражение должно возвращать true, поэтому наш проверка должен пройти. Давайте выясним!

-
$ cargo test
-   Compiling rectangle v0.1.0 (file:///projects/rectangle)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
-     Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
-
-running 1 test
-test tests::larger_can_hold_smaller ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests rectangle
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

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

-

Файл: src/lib.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width > other.width && self.height > other.height
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn larger_can_hold_smaller() {
-        // --snip--
-        let larger = Rectangle {
-            width: 8,
-            height: 7,
-        };
-        let smaller = Rectangle {
-            width: 5,
-            height: 1,
-        };
-
-        assert!(larger.can_hold(&smaller));
-    }
-
-    #[test]
-    fn smaller_cannot_hold_larger() {
-        let larger = Rectangle {
-            width: 8,
-            height: 7,
-        };
-        let smaller = Rectangle {
-            width: 5,
-            height: 1,
-        };
-
-        assert!(!smaller.can_hold(&larger));
-    }
-}
-

Поскольку правильный итог функции can_hold в этом случае false, то мы должны инвертировать этот итог, прежде чем передадим его в assert! макро. Как итог, наш проверка пройдёт, если can_hold вернёт false:

-
$ cargo test
-   Compiling rectangle v0.1.0 (file:///projects/rectangle)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
-     Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
-
-running 2 tests
-test tests::larger_can_hold_smaller ... ok
-test tests::smaller_cannot_hold_larger ... ok
-
-test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests rectangle
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

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

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-// --snip--
-impl Rectangle {
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width < other.width && self.height > other.height
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn larger_can_hold_smaller() {
-        let larger = Rectangle {
-            width: 8,
-            height: 7,
-        };
-        let smaller = Rectangle {
-            width: 5,
-            height: 1,
-        };
-
-        assert!(larger.can_hold(&smaller));
-    }
-
-    #[test]
-    fn smaller_cannot_hold_larger() {
-        let larger = Rectangle {
-            width: 8,
-            height: 7,
-        };
-        let smaller = Rectangle {
-            width: 5,
-            height: 1,
-        };
-
-        assert!(!smaller.can_hold(&larger));
-    }
-}
-

Запуск проверок теперь производит следующее:

-
$ cargo test
-   Compiling rectangle v0.1.0 (file:///projects/rectangle)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
-     Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
-
-running 2 tests
-test tests::larger_can_hold_smaller ... FAILED
-test tests::smaller_cannot_hold_larger ... ok
-
-failures:
-
----- tests::larger_can_hold_smaller stdout ----
-thread 'tests::larger_can_hold_smaller' panicked at src/lib.rs:28:9:
-assertion failed: larger.can_hold(&smaller)
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::larger_can_hold_smaller
-
-test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Наши проверки нашли ошибку! Так как в проверке larger.width равно 8 и smaller.width равно 5 сравнение ширины в способе can_hold возвращает итог false, поскольку число 8 не меньше чем 5.

-

Проверка на равенство с помощью макросов assert_eq! и assert_ne!

-

Общим способом проверки возможности является использование сравнения итога проверяемого кода и ожидаемого значения, чтобы убедиться в их равенстве. Для этого можно использовать макрос assert!, передавая ему выражение с использованием оператора ==. Важно также знать, что кроме этого обычная библиотека предлагает пару макросов assert_eq! и assert_ne!, чтобы сделать проверка более удобным. Эти макросы сравнивают два переменной на равенство или неравенство соответственно. Макросы также печатают два значения входных свойств, если проверка завершился ошибкой, что позволяет легче увидеть почему проверка ошибочен. Противоположно этому, макрос assert! может только отобразить, что он вычислил значение false для выражения ==, но не значения, которые привели к итогу false.

-

В приложении 11-7, мы напишем функцию add_two, которая прибавляет к входному свойству 2 и возвращает значение. Затем, проверим эту функцию с помощью макроса assert_eq!:

-

Файл: src/lib.rs

-
pub fn add_two(a: usize) -> usize {
-    a + 2
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_adds_two() {
-        let result = add_two(2);
-        assert_eq!(result, 4);
-    }
-}
-

Приложение 11-7: Проверка функции add_two с помощью макроса assert_eq!

-

Проверим, что проверки проходят!

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test tests::it_adds_two ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Первый переменная, который мы передаём в макрос assert_eq! число 4 чей итог вызова равен add_two(2) . Строка для этого проверки - test tests::it_adds_two ... ok , а текст ok означает, что наш проверка пройден!

-

Давайте введём ошибку в код, чтобы увидеть, как она выглядит, когда проверка, который использует assert_eq! завершается ошибкой. Измените выполнение функции add_two, чтобы добавлять 3:

-
pub fn add_two(a: usize) -> usize {
-    a + 3
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_adds_two() {
-        let result = add_two(2);
-        assert_eq!(result, 4);
-    }
-}
-

Попробуем выполнить данный проверка ещё раз:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test tests::it_adds_two ... FAILED
-
-failures:
-
----- tests::it_adds_two stdout ----
-thread 'tests::it_adds_two' panicked at src/lib.rs:12:9:
-assertion `left == right` failed
-  left: 5
- right: 4
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::it_adds_two
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Наш проверка нашёл ошибку! Проверка it_adds_two не выполнился, отображается сообщение assertion failed: (left == right)`` и показывает, что left было 4, а right было 5. Это сообщение полезно и помогает начать отладку: это означает left переменная assert_eq! имел значение 4, но right переменная для вызова add_two(2) был со значением 5.

-

Обратите внимание, что в некоторых языках (таких как Java) в библиотеках кода для проверки принято именовать входные свойства проверочных функций как "ожидаемое" (expected) и "действительное" (actual). В Ржавчина приняты следующие обозначения left и right соответственно, а порядок в котором определяются ожидаемое значение и производимое проверяемым кодом значение не имеют значения. Мы могли бы написать выражение в проверке как assert_eq!(add_two(2), 4), что приведёт к отображаемому сообщению об ошибке assertion failed: (left == right)``, слева left было бы 5, а справа right было бы 4.

-

Макрос assert_ne! сработает успешно, если входные свойства не равны друг другу и завершится с ошибкой, если значения равны. Этот макрос наиболее полезен в тех случаях, когда мы не знаем заранее, каким значение будет, но знаем точно, каким оно не может быть. К примеру, если проверяется функция, которая обязательно изменяет входные данные определённым образом, но способ изменения входного свойства зависит от дня недели, в который запускаются проверки, что лучший способ проверить правильность работы такой функции - это сравнить и убедиться, что выходное значение функции не должно быть равным входному значению.

-

В своей работе макросы assert_eq! и assert_ne! неявным образом используют операторы == и != соответственно. Когда проверка не срабатывает, макросы печатают значения переменных с помощью отладочного изменения и это означает, что значения сравниваемых переменных должны выполнить особенности PartialEq и Debug. Все простые и большая часть видов встроенной библиотеки Ржавчина выполняют эти особенности. Для устройств и перечислений, которые вы выполняете сами будет необходимо выполнить особенность PartialEq для сравнения значений на равенство или неравенство. Для печати отладочной сведений в виде сообщений в строку вывода окне вывода необходимо выполнить особенность Debug. Так как оба особенности являются выводимыми особенностями, как упоминалось в приложении 5-12 главы 5, то эти особенности можно выполнить добавив изложение #[derive(PartialEq, Debug)] к определению устройства или перечисления. Смотрите больше подробностей в Appendix C "Выводимые особенности" про эти и другие выводимые особенности.

-

Создание сообщений об ошибках

-

Также можно добавить пользовательское сообщение как дополнительный переменная макросов для печати в сообщении об ошибке проверки assert!, assert_eq!, и assert_ne!. Любые переменные, указанные после обязательных переменных, далее передаются в макрос format! (он обсуждается в разделе "Сцепление с помощью оператора + или макроса format!"), так что вы можете передать измененную строку, которая содержит {} для заполнителей и значения, заменяющие эти заполнители. Пользовательские сообщения полезны для пояснения того, что означает утверждение (assertion); когда проверка завершается неудачей, у вас будет лучшее представление о том, в чем неполадка с кодом.

-

Например, есть функция, которая приветствует человека по имени и мы хотим проверять эту функцию. Мы хотим чтобы передаваемое ей имя выводилось в окно вывода:

-

Файл: src/lib.rs

-
pub fn greeting(name: &str) -> String {
-    format!("Hello {name}!")
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn greeting_contains_name() {
-        let result = greeting("Carol");
-        assert!(result.contains("Carol"));
-    }
-}
-

Требования к этой программе ещё не были согласованы и мы вполне уверены, что текст Hello в начале приветствия ещё изменится. Мы решили, что не хотим обновлять проверка при изменении требований, поэтому вместо проверки на точное равенство со значением возвращённым из greeting, мы просто будем проверять, что вывод содержит текст из входного свойства.

-

Давайте внесём ошибку в этот код, изменив greeting так, чтобы оно не включало name и увидим, как выглядит сбой этого проверки:

-
pub fn greeting(name: &str) -> String {
-    String::from("Hello!")
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn greeting_contains_name() {
-        let result = greeting("Carol");
-        assert!(result.contains("Carol"));
-    }
-}
-

Запуск этого проверки выводит следующее:

-
$ cargo test
-   Compiling greeter v0.1.0 (file:///projects/greeter)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s
-     Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
-
-running 1 test
-test tests::greeting_contains_name ... FAILED
-
-failures:
-
----- tests::greeting_contains_name stdout ----
-thread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:
-assertion failed: result.contains("Carol")
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::greeting_contains_name
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Сообщение содержит лишь сведения о том что сравнение не было успешным и в какой строке это произошло. В данном случае, более полезный текст сообщения был бы, если бы также выводилось значение из функции greeting. Изменим проверяющую функцию так, чтобы выводились пользовательское сообщение измененное строкой с заменителем и действительными данными из кода greeting:

-
pub fn greeting(name: &str) -> String {
-    String::from("Hello!")
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn greeting_contains_name() {
-        let result = greeting("Carol");
-        assert!(
-            result.contains("Carol"),
-            "Greeting did not contain name, value was `{result}`"
-        );
-    }
-}
-

После того, как выполним проверка ещё раз мы получим подробное сообщение об ошибке:

-
$ cargo test
-   Compiling greeter v0.1.0 (file:///projects/greeter)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.93s
-     Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
-
-running 1 test
-test tests::greeting_contains_name ... FAILED
-
-failures:
-
----- tests::greeting_contains_name stdout ----
-thread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:
-Greeting did not contain name, value was `Hello!`
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::greeting_contains_name
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Мы можем увидеть значение, которое мы на самом деле получили в проверочном выводе, что поможет нам отлаживать произошедшее, а не то, что мы ожидали.

-

Проверка с помощью макроса should_panic

-

В дополнение к проверке того, что наш код возвращает правильные, ожидаемые значения, важным также является проверить, что наш код обрабатывает ошибки, которые мы ожидаем. Например, рассмотрим вид Guess который мы создали в главе 9, приложения 9-10. Другой код, который использует Guess зависит от заверения того, что Guess образцы будут содержать значения только от 1 до 100. Мы можем написать проверка, который заверяет, что попытка создать образец Guess со значением вне этого ряда вызывает панику.

-

Выполняем это с помощью другого свойства проверку-функции #[should_panic]. Этот свойство сообщает системе проверки, что проверка проходит, когда способ порождает ошибку. Если ошибка не порождается - проверка считается не пройденным.

-

Приложение 11-8 показывает проверка, который проверяет, что условия ошибки Guess::new произойдут, когда мы их ожидаем их.

-

Файл: src/lib.rs

-
pub struct Guess {
-    value: i32,
-}
-
-impl Guess {
-    pub fn new(value: i32) -> Guess {
-        if value < 1 || value > 100 {
-            panic!("Guess value must be between 1 and 100, got {value}.");
-        }
-
-        Guess { value }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    #[should_panic]
-    fn greater_than_100() {
-        Guess::new(200);
-    }
-}
-

Приложение 11-8: Проверка того, что условие вызовет макрос panic!

-

Свойство #[should_panic] следует после #[test] и до объявления проверочной функции. Посмотрим на вывод итога, когда проверка проходит:

-
$ cargo test
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
-     Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
-
-running 1 test
-test tests::greater_than_100 - should panic ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests guessing_game
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Выглядит хорошо! Теперь давайте внесём ошибку в наш код, убрав условие о том, что функция new будет паниковать если значение больше 100:

-
pub struct Guess {
-    value: i32,
-}
-
-// --snip--
-impl Guess {
-    pub fn new(value: i32) -> Guess {
-        if value < 1 {
-            panic!("Guess value must be between 1 and 100, got {value}.");
-        }
-
-        Guess { value }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    #[should_panic]
-    fn greater_than_100() {
-        Guess::new(200);
-    }
-}
-

Когда мы запустим проверка в приложении 11-8, он потерпит неудачу:

-
$ cargo test
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s
-     Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
-
-running 1 test
-test tests::greater_than_100 - should panic ... FAILED
-
-failures:
-
----- tests::greater_than_100 stdout ----
-note: test did not panic as expected
-
-failures:
-    tests::greater_than_100
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Мы получаем не очень полезное сообщение в этом случае, но когда мы смотрим на проверяющую функцию, мы видим, что она #[should_panic]. Со сбоеме выполнение, которое мы получили означает, что код в проверяющей функции не вызвал паники.

-

Проверки, которые используют should_panic могут быть неточными, потому что они только указывают, что код вызвал панику. Проверка с свойством should_panic пройдёт, даже если проверка паникует по причине, отличной от той, которую мы ожидали. Чтобы сделать проверки с should_panic более точными, мы можем добавить необязательный свойство expected для свойства should_panic. Такая подробностизация проверки позволит удостовериться, что сообщение об ошибке содержит предоставленный текст. Например, рассмотрим измененный код для Guess в приложении 11-9, где new функция паникует с различными сообщениями в зависимости от того, является ли значение слишком маленьким или слишком большим.

-

Файл: src/lib.rs

-
pub struct Guess {
-    value: i32,
-}
-
-// --snip--
-
-impl Guess {
-    pub fn new(value: i32) -> Guess {
-        if value < 1 {
-            panic!(
-                "Guess value must be greater than or equal to 1, got {value}."
-            );
-        } else if value > 100 {
-            panic!(
-                "Guess value must be less than or equal to 100, got {value}."
-            );
-        }
-
-        Guess { value }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    #[should_panic(expected = "less than or equal to 100")]
-    fn greater_than_100() {
-        Guess::new(200);
-    }
-}
-

Приложение 11-9: Проверка panic! на наличие в его сообщении указанной подстроки

-

Этот проверка пройдёт, потому что значение, которое мы помеисполнения для should_panic в свойство свойства expected является подстрокой сообщения, с которым функция Guess::new вызывает панику. Мы могли бы указать полное, ожидаемое сообщение для паники, в этом случае это будет Guess value must be less than or equal to 100, got 200. То что вы выберите для указания как ожидаемого свойства у should_panic зависит от того, какая часть сообщения о панике неповторима или динамична, насколько вы хотите, чтобы ваш проверка был точным. В этом случае достаточно подстроки из сообщения паники, чтобы обеспечить выполнение кода в проверочной функции else if value > 100 .

-

Чтобы увидеть, что происходит, когда проверка should_panic неуспешно завершается с сообщением expected, давайте снова внесём ошибку в наш код, поменяв местами if value < 1 и else if value > 100 блоки:

-
pub struct Guess {
-    value: i32,
-}
-
-impl Guess {
-    pub fn new(value: i32) -> Guess {
-        if value < 1 {
-            panic!(
-                "Guess value must be less than or equal to 100, got {value}."
-            );
-        } else if value > 100 {
-            panic!(
-                "Guess value must be greater than or equal to 1, got {value}."
-            );
-        }
-
-        Guess { value }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    #[should_panic(expected = "less than or equal to 100")]
-    fn greater_than_100() {
-        Guess::new(200);
-    }
-}
-

На этот раз, когда мы выполним should_panic проверка, он потерпит неудачу:

-
$ cargo test
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
-     Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
-
-running 1 test
-test tests::greater_than_100 - should panic ... FAILED
-
-failures:
-
----- tests::greater_than_100 stdout ----
-thread 'tests::greater_than_100' panicked at src/lib.rs:12:13:
-Guess value must be greater than or equal to 1, got 200.
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-note: panic did not contain expected string
-      panic message: `"Guess value must be greater than or equal to 1, got 200."`,
- expected substring: `"less than or equal to 100"`
-
-failures:
-    tests::greater_than_100
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Сообщение об ошибке указывает, что этот проверка действительно вызвал панику, как мы и ожидали, но сообщение о панике не включено ожидаемую строку 'Guess value must be less than or equal to 100'. Сообщение о панике, которое мы получили в этом случае, было Guess value must be greater than or equal to 1, got 200. Теперь мы можем начать выяснение, где ошибка!

-

Использование Result<T, E> в проверках

-

Пока что мы написали проверки, которые паникуют, когда терпят неудачу. Мы также можем написать проверки которые используют Result<T, E>! Вот проверка из приложения 11-1, переписанный с использованием Result<T, E> и возвращающий Err вместо паники:

-
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    // ANCHOR: here
-    #[test]
-    fn it_works() -> Result<(), String> {
-        let result = add(2, 2);
-
-        if result == 4 {
-            Ok(())
-        } else {
-            Err(String::from("two plus two does not equal four"))
-        }
-    }
-    // ANCHOR_END: here
-}
-

Функция it_works теперь имеет возвращаемый вид Result<(), String>. В теле функции, вместо вызова макроса assert_eq!, мы возвращаем Ok(()) когда проверка успешно выполнен и Err со String внутри, когда проверка не проходит.

-

Написание проверок так, чтобы они возвращали Result<T, E> позволяет использовать оператор "вопросительный знак" в теле проверок, который может быть удобным способом писать проверки, которые должны выполниться не успешно, если какая-либо действие внутри них возвращает исход ошибки Err.

-

Вы не можете использовать изложение #[should_panic] в проверках, использующих Result<T, E>. Чтобы утверждать, что действие возвращает исход Err, не используйте оператор вопросительного знака для значения Result<T, E>. Вместо этого используйте assert!(value.is_err()).

-

Теперь, когда вы знаете несколько способов написания проверок, давайте взглянем на то, что происходит при запуске проверок и исследуем разные возможности используемые с приказом cargo test.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch11-02-running-tests.html b/rustbook-ru/book/ch11-02-running-tests.html deleted file mode 100644 index 1eff4909a..000000000 --- a/rustbook-ru/book/ch11-02-running-tests.html +++ /dev/null @@ -1,479 +0,0 @@ - - - - - - Управление выполнением проверок - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Управление хода выполнения проверок

-

Подобно тому, как cargo run выполняет сборку вашего кода, а затем запускает полученный двоичный файл, cargo test собирает ваш код в режиме проверки и запускает полученный двоичный файл с проверкими. Двоичный файл, создаваемый cargo test, по умолчанию запускает все проверки одновременно и перехватывает вывод, порождаемый во время выполнения проверок, предотвращая их вывод на экран для облегчения чтения вывода, относящегося к итогам проверки. Однако вы можете указать свойства приказной строки, чтобы изменить это поведение по умолчанию.

-

Часть свойств приказной строки передаётся в cargo test, а часть - в итоговый двоичный файл с проверкими. Чтобы разделить эти два вида переменных, нужно сначала указать переменные, которые идут в cargo test, затем использовать разделитель --, а потом те, которые попадут в двоичный файл проверки. Выполнение cargo test --help выводит возможности, которые вы можете использовать с cargo test, а выполнение cargo test -- --help выводит возможности, которые вы можете использовать за разделителем.

-

Выполнение проверок одновременно или последовательно

-

Когда вы запускаете несколько проверок, по умолчанию они выполняются одновременно с использованием потоков, что означает, что они завершатся быстрее, и вы быстрее получите итоги. Поскольку проверки выполняются одновременно, вы должны убедиться, что ваши проверки не зависят друг от друга или от какого-либо общего состояния, включая общее окружение, например, текущий рабочий папка или переменные окружения.

-

Например, допустим, каждый из ваших проверок запускает код, который создаёт файл на диске с именем test-output.txt и записывает некоторые данные в этот файл. Затем каждый проверка считывает данные из этого файла и утверждает, что файл содержит определённое значение, которое в каждом проверке разное. Поскольку все проверки выполняются одновременно, один из проверок может перезаписать файл в промежутке между записью и чтением файла другим проверкой. Тогда второй проверка потерпит неудачу, но не потому, что код неверен, а потому, что эти проверки мешали друг другу при одновременном выполнении. Одно из решений - убедиться, что каждый проверка пишет в свой отдельный файл; другое решение - запускать проверки по одному.

-

Если вы не хотите запускать проверки одновременно или хотите более подробный управление над количеством используемых потоков, можно установить флаг --test-threads и то количество потоков, которое вы хотите использовать для проверки. Взгляните на следующий пример:

-
$ cargo test -- --test-threads=1
-
-

Мы устанавливаем количество проверочных потоков равным 1 , указывая программе не использовать одновременность. Выполнение проверок с использованием одного потока займёт больше времени, чем их одновременное выполнение, но проверки не будут мешать друг другу, если они совместно используют состояние.

-

Отображение итогов работы функции

-

По умолчанию, если проверка пройден, система управления запуска проверок блокирует вывод на печать, т.е. если вы вызовете макрос println! внутри кода проверки и проверка будет пройден, вы не увидите вывода на окно вывода итогов вызова println!. Если же проверка не был пройден, все несущие сведения сообщения, а также описание ошибки будут выведены на окно вывода.

-

Например, в коде (11-10) функция выводит значение свойства с поясняющим текстовым сообщением, а также возвращает целочисленное постоянных значенийное значение 10. Далее следует проверка, который имеет правильный входной свойство и проверка, который имеет ошибочный входной свойство:

-

Файл: src/lib.rs

-
fn prints_and_returns_10(a: i32) -> i32 {
-    println!("I got the value {a}");
-    10
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn this_test_will_pass() {
-        let value = prints_and_returns_10(4);
-        assert_eq!(value, 10);
-    }
-
-    #[test]
-    fn this_test_will_fail() {
-        let value = prints_and_returns_10(8);
-        assert_eq!(value, 5);
-    }
-}
-

Приложение 11-10: Проверка функции, которая использует макрос println!

-

Итог вывода на окно вывода приказы cargo test:

-
$ cargo test
-   Compiling silly-function v0.1.0 (file:///projects/silly-function)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
-     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)
-
-running 2 tests
-test tests::this_test_will_fail ... FAILED
-test tests::this_test_will_pass ... ok
-
-failures:
-
----- tests::this_test_will_fail stdout ----
-I got the value 8
-thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
-assertion `left == right` failed
-  left: 10
- right: 5
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::this_test_will_fail
-
-test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Обратите внимание, что нигде в этом выводе мы не видим сообщения I got the value 4 , которое печатается при выполнении пройденного проверки. Этот вывод был записан. Итог неудачного проверки, I got the value 8 , появляется в разделе итоговых итогов проверки, который также показывает причину неудачного проверки.

-

Если мы хотим видеть напечатанные итоги прохождения проверок, мы можем сказать Rust, чтобы он также показывал итоги успешных проверок с помощью --show-output.

-
$ cargo test -- --show-output
-
-

Когда мы снова запускаем проверки из Приложения 11-10 с флагом --show-output , мы видим следующий итог:

-
$ cargo test -- --show-output
-   Compiling silly-function v0.1.0 (file:///projects/silly-function)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
-     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)
-
-running 2 tests
-test tests::this_test_will_fail ... FAILED
-test tests::this_test_will_pass ... ok
-
-successes:
-
----- tests::this_test_will_pass stdout ----
-I got the value 4
-
-
-successes:
-    tests::this_test_will_pass
-
-failures:
-
----- tests::this_test_will_fail stdout ----
-I got the value 8
-thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
-assertion `left == right` failed
-  left: 5
- right: 10
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::this_test_will_fail
-
-test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Запуск подмножества проверок по имени

-

Бывают случаи, когда в запуске всех проверок нет необходимости и нужно запустить только несколько проверок. Если вы работаете над функцией и хотите запустить проверки, которые исследуют её работу - это было бы удобно. Вы можете это сделать, используя приказ cargo test, передав в качестве переменной имена проверок.

-

Для отображения, как запустить объединение проверок, мы создадим объединение проверок для функции add_two function, как показано в Приложении 11-11, и постараемся выбрать какие из них запускать.

-

Файл: src/lib.rs

-
pub fn add_two(a: usize) -> usize {
-    a + 2
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn add_two_and_two() {
-        let result = add_two(2);
-        assert_eq!(result, 4);
-    }
-
-    #[test]
-    fn add_three_and_two() {
-        let result = add_two(3);
-        assert_eq!(result, 5);
-    }
-
-    #[test]
-    fn one_hundred() {
-        let result = add_two(100);
-        assert_eq!(result, 102);
-    }
-}
-

Приложение 11-11: Три проверки с различными именами

-

Если вы выполните приказ cargo test без уточняющих переменных, все проверки выполнятся одновременно:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 3 tests
-test tests::add_three_and_two ... ok
-test tests::add_two_and_two ... ok
-test tests::one_hundred ... ok
-
-test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Запуск одного проверки

-

Мы можем запустить один проверка с помощью указания его имени в приказу cargo test:

-
$ cargo test one_hundred
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.69s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test tests::one_hundred ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
-
-
-

Был запущен только проверка с названием one_hundred; два других проверки не соответствовали этому названию. Итоги проверки с помощью вывода 2 filtered out дают нам понять, что у нас было больше проверок, но они не были запущены.

-

Таким образом мы не можем указать имена нескольких проверок; будет использоваться только первое значение, указанное для cargo test . Но есть способ запустить несколько проверок.

-

Использование фильтров для запуска нескольких проверок

-

Мы можем указать часть имени проверки, и будет запущен любой проверка, имя которого соответствует этому значению. Например, поскольку имена двух наших проверок содержат add, мы можем запустить эти два, запустив cargo test add:

-
$ cargo test add
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 2 tests
-test tests::add_three_and_two ... ok
-test tests::add_two_and_two ... ok
-
-test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
-
-
-

Этот приказ запускала все проверки с add в имени и отфильтровывала проверка с именем one_hundred . Также обратите внимание, что звено, в котором появляется проверка, становится частью имени проверки, поэтому мы можем запускать все проверки в звене, фильтруя имя звена.

-

Пренебрежение проверок

-

Бывает, что некоторые проверки требуют продолжительного времени для своего исполнения, и вы хотите исключить их из исполнения при запуске cargo test. Вместо перечисления в приказной строке всех проверок, которые вы хотите запускать, вы можете определять проверки, требующие много времени для прогона, свойством ignore, чтобы исключить их, как показано здесь:

-

Файл: src/lib.rs

-
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-// ANCHOR: here
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_works() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-
-    #[test]
-    #[ignore]
-    fn expensive_test() {
-        // code that takes an hour to run
-    }
-}
-// ANCHOR_END: here
-

После #[test] мы добавляем строку #[ignore] в проверка, который хотим исключить. Теперь, когда мы запускаем наши проверки, it_works запускается, а expensive_test пренебрегается:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 2 tests
-test tests::expensive_test ... ignored
-test tests::it_works ... ok
-
-test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Функция expensive_test помечена как ignored. Если вы хотите выполнить только пренебреженные проверки, вы можете воспользоваться приказом cargo test -- --ignored:

-
$ cargo test -- --ignored
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test expensive_test ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Управляя тем, какие проверки запускать, вы можете быть уверены, что итоги вашего cargo test будут быстрыми. Когда вы дойдёте до особенности, где имеет смысл проверить итоги проверок ignored, и у вас есть время дождаться их итогов, вы можете запустить их с помощью cargo test -- --ignored. Если вы хотите запустить все проверки независимо от того, пренебрегаются они или нет, выполните cargo test -- --include-ignored.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch11-03-test-organization.html b/rustbook-ru/book/ch11-03-test-organization.html deleted file mode 100644 index 072fa2cfd..000000000 --- a/rustbook-ru/book/ch11-03-test-organization.html +++ /dev/null @@ -1,413 +0,0 @@ - - - - - - Создание проверок - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Создание проверок

-

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

-

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

-

Состоящие из звеньев проверки

-

Целью состоящих из звеньев проверок является проверка каждого раздела кода, изолированное от остального возможностей, чтобы можно было быстро понять, что работает неправильно или не так как ожидается. Мы разместим состоящие из звеньев проверки в папке src, в каждый проверяемый файл. Но в Ржавчина принято создавать проверяемый звено tests и код проверки сохранять в файлы с таким же именем, как составляющие которые предстоит проверять. Также необходимо добавить изложение cfg(test) к этому звену.

-

Звено проверок и изложение #[cfg(test)]

-

Изложение #[cfg(test)] у звена с проверкими указывает Ржавчина собирать и запускать только код проверок, когда выполняется приказ cargo test, а не когда запускается cargo build. Это уменьшает время сборки, если вы только хотите собрать библиотеку и уменьшить место для результирующих собранных артефактов, потому что проверки не будут включены. Вы увидите что, по причине того, что встроенные проверки помещаются в другой папка им не нужна изложение #[cfg(test)]. Тем не менее, так как состоящие из звеньев проверки идут в тех же файлах что и основной код, вы будете использовать #[cfg(test)] чтобы указать, что они не должны быть включены в собранный итог.

-

Напомним, что когда мы порождали новый дело adder в первом разделе этой главы, то Cargo создал для нас код ниже:

-

Файл: src/lib.rs

-
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_works() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-}
-

Этот код является самостоятельно созданным проверочным звеном. Свойство cfg предназначен для настройке и говорит Rust, что следующий элемент должен быть включён только учитывая определённую возможность настройке. В этом случае возможностью настройке является test, который предоставлен в Ржавчина для сборки и запуска текущих проверок. Используя свойство cfg, Cargo собирает только проверочный код при активном запуске проверок приказом cargo test. Это включает в себя любые вспомогательные функции, которые могут быть в этом звене в дополнение к функциям помеченным #[test].

-

Проверка закрытых функций (private)

-

Сообщество программистов не имеет однозначного мнения по поводу проверять или нет закрытые функции. В некоторых языках весьма сложно или даже невозможно проверять такие функции. Независимо от того, какой технологии проверки вы придерживаетесь, в Ржавчина закрытые функции можно проверять. Рассмотрим приложение 11-12 с закрытой функцией internal_adder.

-

Файл: src/lib.rs

-
pub fn add_two(a: usize) -> usize {
-    internal_adder(a, 2)
-}
-
-fn internal_adder(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn internal() {
-        let result = internal_adder(2, 2);
-        assert_eq!(result, 4);
-    }
-}
-

Приложение 11-12: Проверка закрытых функций

-

Обратите внимание, что функция internal_adder не помечена как pub. Проверки — это просто Ржавчина код, а звено tests — это ещё один звено. Как мы обсуждали в разделе “Пути для ссылки на элемент в дереве звеньев“, элементы в дочерних звенах могут использовать элементы из своих родительских звеньев. В этом проверке мы помещаем все элементы родительского звена test в область видимости с помощью use super::* и затем проверка может вызывать internal_adder. Если вы считаете, что закрытые функции не нужно проверять, то Ржавчина не заставит вас это сделать.

-

Встраиваемые проверки

-

В Ржавчина встроенные проверки являются полностью внешними по отношению к вашей библиотеке. Они используют вашу библиотеку так же, как любой другой код, что означает, что они могут вызывать только функции, которые являются частью открытого API библиотеки. Их целью является проверка, много ли частей вашей библиотеки работают вместе правильно. У звеньев кода правильно работающих самостоятельно, могут возникнуть сбоев при встраивани, поэтому проверочное покрытие встроенного кода также важно. Для создания встроенных проверок сначала нужен папка tests .

-

Папка tests

-

Мы создаём папку tests в корневой папке вашего дела, рядом с папкой src. Cargo знает, что искать файлы с встроенными проверкими нужно в этой папки. После этого мы можем создать столько проверочных файлов, сколько захотим, и Cargo собирает каждый из файлов в отдельный ящик.

-

Давайте создадим встроенный проверку. Рядом с кодом из приложения 11-12, который всё ещё в файле src/lib.rs, создайте папка tests, создайте новый файл с именем tests/integration_test.rs. Устройства папок должна выглядеть так:

-
adder
-├── Cargo.lock
-├── Cargo.toml
-├── src
-│   └── lib.rs
-└── tests
-    └── integration_test.rs
-
-

Введите код из приложения 11-13 в файл tests/integration_test.rs file:

-

Файл: tests/integration_test.rs

-
use adder::add_two;
-
-#[test]
-fn it_adds_two() {
-    let result = add_two(2);
-    assert_eq!(result, 4);
-}
-

Приложение 11-13: Встраиваемая проверка функция из ящика adder

-

Каждый файл в папке tests представляет собой отдельный ящик, поэтому нам нужно подключить нашу библиотеку в область видимости каждого проверочного ящика. По этой причине мы добавляем use adder в верхней части кода, что не нужно нам делать в состоящих из звеньев проверках.

-

Нам не нужно вносить примечания в код в tests/integration_test.rs с помощью #[cfg(test)]. Cargo особым образом обрабатывает папка tests и собирает файлы в этом папке только тогда, когда мы запускаем приказ cargo test. Запустите cargo test сейчас:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.31s
-     Running unittests src/lib.rs (target/debug/deps/adder-1082c4b063a8fbe6)
-
-running 1 test
-test tests::internal ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-     Running tests/integration_test.rs (target/debug/deps/integration_test-1082c4b063a8fbe6)
-
-running 1 test
-test it_adds_two ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Выходные данные представлены тремя разделами: состоящие из звеньев проверки, встроенные проверки и проверки документации. Обратите внимание, что если какой-нибудь проверка в одной из разделов не пройдёт, последующие разделы выполняться не будут. Например, если состоящий из звеньев проверка провалился, не будет выведено итогов встроенных и документационных проверок, потому что эти проверки будут выполняться только в том случае, если все состоящие из звеньев проверки завершатся успешно.

-

Первый раздел для состоящих из звеньев проверок такой же, как мы видели: одна строка для каждого состоящего из звеньев проверки (один с именем internal, который мы добавили в приложении 11-12), а затем сводная строка для состоящих из звеньев проверок.

-

Раздел встроенных проверок начинается со строки Running tests/integration_test.rs. Далее идёт строка для каждой проверочной функции в этом встроенном проверке и итоговая строка для итогов встроенного проверки непосредственно перед началом раздела Doc-tests adder.

-

Каждый файл встроенного проверки имеет свой собственный раздел, поэтому, если мы добавим больше файлов в папка tests, то здесь будет больше разделов встроенного проверки.

-

Мы всё ещё можем запустить определённую функцию в встроенных проверках, указав имя проверка функции в качестве переменной в cargo test. Чтобы запустить все проверки в определенном файле встроенных проверок, используйте переменная --test сопровождаемый именем файла у приказы cargo test:

-
$ cargo test --test integration_test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.64s
-     Running tests/integration_test.rs (target/debug/deps/integration_test-82e7799c1bc62298)
-
-running 1 test
-test it_adds_two ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Этот приказ запускает только проверки в файле tests/integration_test.rs.

-

Подзвенья в встроенных проверках

-

По мере добавления большего количества встроенных проверок, можно создать более одного файла в папке tests, чтобы легче создавать их; например, вы можете собъединять функции проверки по возможности, которую они проверяют. Как упоминалось ранее, каждый файл в папке tests собран как отдельный ящик, что полезно для создания отдельных областей видимости, чтобы более точно создавать видимость то, как конечные пользователи будут использовать ваш ящик. Однако это означает, что файлы в папке tests ведут себя не так, как файлы в src, как вы узнали в Главе 7 относительно того как разделить код на звенья и файлы.

-

Различное поведение файлов в папке tests наиболее заметно, когда у вас есть набор вспомогательных функций, которые будут полезны в нескольких встроенных проверочных файлах. Представим, что вы пытаетесь выполнить действия, описанные в разделе «Разделение звеньев в разные файлы» главы 7, чтобы извлечь их в общий звено. Например, вы создали файл tests/common.rs и помеисполнения в него функцию setup, содержащую некоторый код, который вы будете вызывать из разных проверочных функций в нескольких проверочных файлах

-

Файл: tests/common.rs

-
pub fn setup() {
-    // setup code specific to your library's tests would go here
-}
-

Когда мы снова запустим проверки, мы увидим новый раздел в итогах проверок для файла common.rs, хотя этот файл не содержит никаких проверочных функций, более того, мы даже не вызывали функцию setup откуда либо:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.89s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test tests::internal ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-     Running tests/common.rs (target/debug/deps/common-92948b65e88960b4)
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-     Running tests/integration_test.rs (target/debug/deps/integration_test-92948b65e88960b4)
-
-running 1 test
-test it_adds_two ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Упоминание файла common и появление в итогах выполнения проверок сообщения вида running 0 tests - это не то, чего мы хотели. Мы только хотели выделить некоторый общий код, который будет использоваться другими файлами встроенных проверок.

-

Чтобы звено common больше не появлялся в итогах выполнения проверок, вместо файла tests/common.rs мы создадим файл tests/common/mod.rs. Директория дела теперь выглядит следующим образом:

-
├── Cargo.lock
-├── Cargo.toml
-├── src
-│   └── lib.rs
-└── tests
-    ├── common
-    │   └── mod.rs
-    └── integration_test.rs
-
-

Здесь используется более раннее соглашение об именовании файлов, которое Ржавчина также понимает. Мы говорили об этом в разделе “Иные пути к файлам” главы 7. Именование файла таким образом говорит, что Ржавчина не должен рассматривать звено common как файл встроенных проверок. Когда мы перемещаем код функции setup в файл tests/common/mod.rs и удаляем файл tests/common.rs, дополнительный раздел больше не будет отображаться в итогах проверок. Файлы в подпапких папки tests не собираются как отдельные ящики или не появляются в итогах выполнения проверок.

-

После того, как мы создали файл tests/common/mod.rs, мы можем использовать его в любых файлах встроенных проверок как обычный звено. Вот пример вызова функции setup из проверки it_adds_two в файле tests/integration_test.rs:

-

Файл: tests/integration_test.rs

-
use adder::add_two;
-
-mod common;
-
-#[test]
-fn it_adds_two() {
-    common::setup();
-
-    let result = add_two(2);
-    assert_eq!(result, 4);
-}
-

Обратите внимание, что объявление mod common; совпадает с объявлением звена, которое отображено в приложении 7-21. Затем в проверочной функции мы можем вызвать функцию common::setup().

-

Встраиваемые проверки для двоичных ящиков

-

Если наш дело является двоичным ящиком, который содержит только src/main.rs и не содержит src/lib.rs, мы не сможем создать встроенные проверки в папке tests и подключить функции определённые в файле src/main.rs в область видимости с помощью указания use. Только библиотечные ящики могут предоставлять функции, которые можно использовать в других ящиках; двоичные ящики предназначены только для самостоятельного запуска.

-

Это одна из причин, почему дела на Rust, которые порождают исполняемые звенья, обычно имеют простой файл src/main.rs, который в свою очередь вызывает логику, которая находится в файле src/lib.rs. Используя такую устройство, встроенные проверки могут проверить библиотечный ящик, используя оператор use для подключения важного возможностей. Если этот важный возможности работает, то и небольшое количество кода в файле src/main.rs также будет работать, а значит этот небольшой объём кода не нуждается в проверке.

-

Итоги

-

Средства проверки языка Ржавчина предоставляют способ задать ожидаемое поведение кода, чтобы убедиться, что он всё ещё соответствует вашим ожиданиям даже после внесения изменений. Состоящие из звеньев проверки проверяют различные части библиотеки по отдельности и могут проверять закрытые подробности выполнения. Встраиваемые проверки проверяют, что части библиотеки работают правильно сообща. Эти проверки используют для проверки кода открытый API библиотеки, таким же образом, как его будет использовать внешний код. Хотя система видов Ржавчина и правила владения помогают предотвратить некоторые виды ошибок, проверки по-прежнему важны для уменьшения количества логических ошибок, связанных с поведением вашего кода.

-

Давайте объединим знания, полученные в этой и предыдущей главах, чтобы поработать над делом!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch12-00-an-io-project.html b/rustbook-ru/book/ch12-00-an-io-project.html deleted file mode 100644 index 4b27080f3..000000000 --- a/rustbook-ru/book/ch12-00-an-io-project.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - Дело с вводом-выводом: создание программы приказной строки - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Дело с вводом/выводом (I/O): создание с окном вывода приложения

-

В этой главе вы примените многие знания, полученные ранее, а также познакомитесь с ещё неизученными API встроенной библиотеки. Мы создадим окно выводаное приложение, которое будет взаимодействовать с файлом и с окно выводаным вводом / выводом, чтобы применить в некоторых подходах Rust, с которыми вы уже знакомы.

-

Скорость, безопасность, сборка в один исполняемый файл и кроссплатформенность делают Ржавчина наилучшим языком для создания окно выводаных средств, так что в нашем деле мы создадим свою собственную исполнение обычной утилиты поиска grep, что расшифровывается, как "вездесущеее средство поиска и печати" (globally search a regular expression and print). В простейшем случае grep используется для поиска в выбранном файле указанного текста. Для этого утилита grep получает имя файла и текст в качестве переменных. Далее она читает файл, находит и выводит строки, содержащие искомый текст.

-

Попутно мы покажем, как сделать так, чтобы наше окно выводаное приложение использовало возможности окна вызова, которые используются многими другими окно выводаными средствами. Мы будем читать значение переменной окружения, чтобы позволить пользователю настроить поведение нашего средства. Мы также будем печатать сообщения об ошибках в обычный окно выводаный поток ошибок ( stderr ) вместо принятого вывода ( stdout ), чтобы, к примеру, пользователь мог перенаправить успешный вывод в файл, в то время, как сообщения об ошибках останутся на экране.

-

Один из участников Rust-сообщества, Andrew Gallant, уже выполнил полновозможный, очень быстрый подобие программы grep и назвал его ripgrep. По сравнению с ним, наша исполнение будет довольно простой, но эта глава даст вам знания, которые нужны для понимания существующих дел, таких как ripgrep.

-

Наш дело grep будет использовать ранее изученные подходы:

-
    -
  • Создание кода (используя то, что вы узнали о звенах в главе 7)
  • -
  • Использование векторов и строк (собрания, глава 8)
  • -
  • Обработка ошибок (Глава 9)
  • -
  • Использование особенностей и времени жизни там, где это необходимо (глава 10)
  • -
  • Написание проверок ( Глава 11)
  • -
-

Мы также кратко представим замыкания, повторители и предметы особенности, которые будут объяснены подробно в главах 13 и 17.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch12-01-accepting-command-line-arguments.html b/rustbook-ru/book/ch12-01-accepting-command-line-arguments.html deleted file mode 100644 index 12888ac13..000000000 --- a/rustbook-ru/book/ch12-01-accepting-command-line-arguments.html +++ /dev/null @@ -1,302 +0,0 @@ - - - - - - Получение переменных приказной строки - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Принятие переменных приказной строки

-

Создадим новый дело с окном вывода приложения как обычно с помощью приказы cargo new. Мы назовём дело minigrep, чтобы различать наше приложение от grep, которое возможно уже есть в вашей системе.

-
$ cargo new minigrep
-     Created binary (application) `minigrep` project
-$ cd minigrep
-
-

Первая задача - заставить minigrep принимать два переменной приказной строки: путь к файлу и строку для поиска. То есть мы хотим иметь возможность запускать нашу программу через cargo run, с использованием двойного дефиса, чтобы указать, что следующие переменные предназначены для нашей программы, а не для cargo, строки для поиска и пути к файлу в котором нужно искать, как описано ниже:

-
$ cargo run -- searchstring example-filename.txt
-
-

В данный мгновение программа созданная cargo new не может обрабатывать переменные, которые мы ей передаём. Некоторые существующие библиотеки на crates.io могут помочь с написанием программы, которая принимает переменные приказной строки, но так как вы просто изучаете эту подход, давайте выполняем эту возможность сами.

-

Чтение значений переменных

-

Чтобы minigrep мог воспринимать значения переменных приказной строки, которые мы ему передаём, нам понадобится функция std::env::args, входящая в обычную библиотеку Rust. Эта функция возвращает повторитель переменных приказной строки, переданных в minigrep. Мы подробно рассмотрим повторители в главе 13. Пока вам достаточно знать две вещи об повторителях: повторители порождают серию значений, и мы можем вызвать способ collect у повторителя, чтобы создать из него собрание, например вектор, который будет содержать все элементы, произведённые повторителем.

-

Код представленный в Приложении 12-1 позволяет вашей программе minigrep читать любые переданные ей переменные приказной строки, а затем собирать значения в вектор.

-

Файл: src/main.rs

-
use std::env;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-    dbg!(args);
-}
-

Приложение 12-1: Собираем переменные приказной строки в вектор и выводим их на печать

-

Сначала мы вводим звено std::env в область видимости с помощью указания use, чтобы мы могли использовать его функцию args. Обратите внимание, что функция std::env::args вложена в два уровня звеньев. Как мы обсуждали в главе 7, в случаях, когда нужная функция оказывается вложенной в более чем один звено, советуется выносить в область видимости родительский звено, а не функцию. Таким образом, мы можем легко использовать другие функции из std::env. Это менее двусмысленно, чем добавление use std::env::args и последующий вызов функции только с args, потому что args может быть легко принят за функцию, определённую в текущем звене.

-
-

Функция args и недействительный Юникод символ (Unicode)

-

Обратите внимание, что std::env::args вызовет панику, если какой-либо переменная содержит недопустимый символ Юникода. Если вашей программе необходимо принимать переменные, содержащие недопустимые символы Unicode, используйте вместо этого std::env::args_os. Эта функция возвращает повторитель , который выдаёт значения OsString вместо значений String. Мы решили использовать std::env::args здесь для простоты, потому что значения OsString отличаются для каждой площадки и с ними сложнее работать, чем со значениями String.

-
-

В первой строке кода функции main мы вызываем env::args и сразу используем способ collect, чтобы превратить повторитель в вектор содержащий все полученные значения. Мы можем использовать функцию collect для создания многих видов собраний, поэтому мы явно определяем вид args чтобы указать, что мы хотим вектор строк. Хотя нам очень редко нужно определять виды в Rust, collect - это одна из функций, с которой вам часто нужна изложение вида, потому что Ржавчина не может сам вывести какую собрание вы хотите.

-

И в заключение мы печатаем вектор с помощью отладочного макроса. Попробуем запустить код сначала без переменных, а затем с двумя переменнойми:

-
$ cargo run
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
-     Running `target/debug/minigrep`
-[src/main.rs:5:5] args = [
-    "target/debug/minigrep",
-]
-
-
$ cargo run -- needle haystack
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.57s
-     Running `target/debug/minigrep needle haystack`
-[src/main.rs:5:5] args = [
-    "target/debug/minigrep",
-    "needle",
-    "haystack",
-]
-
-

Обратите внимание, что первое значение в векторе "target/debug/minigrep" является названием нашего двоичного файла. Это соответствует поведению списка переменных в Си, позволяя программам использовать название с которым они были вызваны при выполнении. Часто бывает удобно иметь доступ к имени программы, если вы хотите распечатать его в сообщениях или изменить поведение программы в зависимости от того, какой псевдоним приказной строки был использован для вызова программы. Но для целей этой главы, мы пренебрегаем его и сохраним только два переменной, которые нам нужны.

-

Сохранения значений переменных в переменные

-

На текущий мгновение программа может получить доступ к значениям, указанным в качестве переменных приказной строки. Теперь нам требуется сохранять значения этих двух переменных в переменных, чтобы мы могли использовать их в остальных частях программы. Мы сделаем это в приложении 12-2.

-

Файл: src/main.rs

-
use std::env;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let query = &args[1];
-    let file_path = &args[2];
-
-    println!("Searching for {query}");
-    println!("In file {file_path}");
-}
-

Приложение 12-2: Создание переменных для хранения значений переменных искомой подстроки и пути к файлу

-

Как видно из распечатки вектора, имя программы занимает первое значение в векторе по адресу args[0], значит, переменные начинаются с порядкового указателя 1. Первый переменная minigrep - это строка, которую мы ищем, поэтому мы помещаем ссылку на первый переменная в переменную query. Вторым переменнаяом является путь к файлу, поэтому мы помещаем ссылку на второй переменная в переменную file_path.

-

Для проверки соблюдения правил работы нашей программы, значения переменных выводятся в окно вывода. Далее, запустим нашу программу со следующими переменнойми: test и sample.txt:

-
$ cargo run -- test sample.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep test sample.txt`
-Searching for test
-In file sample.txt
-
-

Отлично, программа работает! Нам нужно чтобы значения переменных были сохранены в правильных переменных. Позже мы добавим обработку ошибок с некоторыми вероятными ошибочными случаейми, например, когда пользователь не предоставляет переменные; сейчас мы пренебрегаем эту случай и поработаем над добавлением возможности чтения файла.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch12-02-reading-a-file.html b/rustbook-ru/book/ch12-02-reading-a-file.html deleted file mode 100644 index aac28aeaf..000000000 --- a/rustbook-ru/book/ch12-02-reading-a-file.html +++ /dev/null @@ -1,288 +0,0 @@ - - - - - - Чтение файла - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Чтение файла

-

Теперь добавим возможность чтения файла, указанного как переменная приказной строки file_path. Во-первых, нам нужен пример файла для проверки: мы будем использовать файл с небольшим объёмом текста в несколько строк с несколькими повторяющимися словами. В приложении 12-3 представлено стихотворение Эмили Дикинсон, которое будет хорошо работать! Создайте файл с именем poem.txt в корне вашего дела и введите стихотворение "I’m nobody! Who are you?"

-

Файл: poem.txt

-
I'm nobody! Who are you?
-Are you nobody, too?
-Then there's a pair of us - don't tell!
-They'd banish us, you know.
-
-How dreary to be somebody!
-How public, like a frog
-To tell your name the livelong day
-To an admiring bog!
-
-

Приложение 12-3: Стихотворение Эмили Дикинсон - хороший пример для проверки

-

Текст на месте, изменените src/main.rs и добавьте код для чтения файла, как показано в приложении 12-4.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    // --snip--
-    let args: Vec<String> = env::args().collect();
-
-    let query = &args[1];
-    let file_path = &args[2];
-
-    println!("Searching for {query}");
-    println!("In file {file_path}");
-
-    let contents = fs::read_to_string(file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-

Приложение 12-4: Чтение содержимого файла указанного во втором переменной

-

Во-первых, мы добавляем ещё одну указанию use чтобы подключить соответствующую часть встроенной библиотеки: нам нужен std::fs для обработки файлов.

-

В main мы добавили новую указанию: функция fs::read_to_string принимает file_path, открывает этот файл и возвращает содержимое файла как std::io::Result<String>.

-

После этого, мы снова добавили временную указанию println! для печати значения contents после чтения файла, таким образом мы можем проверить, что программа отрабатывает до этого места.

-

Давайте запустим этот код с любой строкой в качестве первого переменной приказной строки (потому что мы ещё не выполнили поисковую часть) и файл poem.txt как второй переменная:

-
$ cargo run -- the poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep the poem.txt`
-Searching for the
-In file poem.txt
-With text:
-I'm nobody! Who are you?
-Are you nobody, too?
-Then there's a pair of us - don't tell!
-They'd banish us, you know.
-
-How dreary to be somebody!
-How public, like a frog
-To tell your name the livelong day
-To an admiring bog!
-
-
-

Отлично! Этот код прочитал и затем напечатал содержимое файла. Но у программы есть несколько недостатков. Прежде всего, функция main решает слишком много задач: как правило функция понятнее и проще в обслуживании если она воплощает только одну мысль. Другая неполадка заключается в том, что мы не обрабатываем ошибки так хорошо, как могли бы. Пока наша программа небольшая, то эти недостатки не являются большой неполадкой, но по мере роста программы эти недостатки будет всё труднее исправлять. Хорошей опытом является начинать переработка кода на ранней стадии разработки программы, потому что гораздо проще перерабатывать код меньшие объёмы кода. Мы сделаем это далее.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch12-03-improving-error-handling-and-modularity.html b/rustbook-ru/book/ch12-03-improving-error-handling-and-modularity.html deleted file mode 100644 index 07db9d227..000000000 --- a/rustbook-ru/book/ch12-03-improving-error-handling-and-modularity.html +++ /dev/null @@ -1,793 +0,0 @@ - - - - - - Переработка кода для обеспечения выделения на звенья и улучшения обработки ошибок - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Переработка кода для улучшения выделения на звенья и обработки ошибок

-

Для улучшения программы мы исправим 4 имеющихся сбоев, связанных со устройством программы и тем как обрабатываются вероятные ошибки. Во-первых, функция main на данный мгновение решает две задачи: анализирует переменные приказной строки и читает файлы. По мере роста программы количество отдельных задач, которые обрабатывает функция main, будет увеличиваться. Поскольку эта функция получает больше обязанностей, то становится все труднее понимать её, труднее проверять и труднее изменять, не сломав одну из её частей. Лучше всего разделить возможность, чтобы каждая функция отвечала за одну задачу.

-

Эта неполадка также связана со второй неполадкой: хотя переменные query и file_path являются переменными настройке нашей программы, переменные вида contents используются для выполнения логики программы. Чем длиннее становится main, тем больше переменных нам нужно будет добавить в область видимости; чем больше у нас переменных в области видимости, тем сложнее будет отслеживать назначение каждой переменной. Лучше всего собъединять переменные настройке в одну устройство, чтобы сделать их назначение понятным.

-

Третья неполадка заключается в том, что мы используем expect для вывода сведений об ошибке при неполадке с чтением файла, но сообщение об ошибке просто выведет текстShould have been able to read the file. Чтение файла может не сработать по разным причинам, например: файл не найден или у нас может не быть разрешения на его чтение. Сейчас же, независимо от случаи, мы напечатаем одно и то же сообщение об ошибке, что не даст пользователю никакой сведений!

-

В-четвёртых, мы используем expect неоднократно для обработки различных ошибок и если пользователь запускает нашу программу без указания достаточного количества переменных он получит ошибку index out of bounds из Rust, что не совсем понятно описывает неполадку. Было бы лучше, если бы весь код обработки ошибок находился в одном месте, чтобы тем, кто будет поддерживать наш код в дальнейшем, нужно было бы вносить изменения только здесь, если потребуется изменить логику обработки ошибок. Наличие всего кода обработки ошибок в одном месте заверяет, что мы напечатаем сообщения, которые будут иметь смысл для наших конечных пользователей.

-

Давайте решим эти четыре сбоев путём переработки кода нашего дела.

-

Разделение ответственности для двоичных дел

-

Внутренняя неполадка распределения ответственности за выполнение нескольких задач функции main является общей для многих двоичных дел. В итоге Ржавчина сообщество разработало этап для использования в качестве руководства по разделению ответственности двоичной программы, когда код в main начинает увеличиваться. Этап имеет следующие шаги:

-
    -
  • Разделите код программы на два файла main.rs и lib.rs. Перенесите всю логику работы программы в файл lib.rs.
  • -
  • Пока ваша логика синтаксического анализа приказной строки мала, она может оставаться в файле main.rs.
  • -
  • Когда логика синтаксического анализа приказной строки становится сложной, извлеките её из main.rs и переместите в lib.rs.
  • -
-

Полезные обязанности, которые остаются в функции main после этого этапа должно быть ограничено следующим:

-
    -
  • Вызов логики разбора приказной строки со значениями переменных
  • -
  • Настройка любой другой настройке
  • -
  • Вызов функции run в lib.rs
  • -
  • Обработка ошибки, если run возвращает ошибку
  • -
-

Этот образец о разделении ответственности: main.rs занимается запуском программы, а lib.rs обрабатывает всю логику задачи. Поскольку нельзя проверить функцию main напрямую, то такая устройства позволяет проверить всю логику программы путём перемещения её в функции внутри lib.rs. Единственный код, который остаётся в main.rs будет достаточно маленьким, чтобы проверить его соблюдение правил прочитав код. Давайте переработаем нашу программу, следуя этому этапу.

-

Извлечение обработчика переменных

-

Мы извлечём возможность для разбора переменных в функцию, которую вызовет main для подготовки к перемещению логики разбора приказной строки в файл src/lib.rs. Приложение 12-5 показывает новый запуск main, который вызывает новую функцию parse_config, которую мы определим сначала в src/main.rs.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let (query, file_path) = parse_config(&args);
-
-    // --snip--
-
-    println!("Searching for {query}");
-    println!("In file {file_path}");
-
-    let contents = fs::read_to_string(file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-
-fn parse_config(args: &[String]) -> (&str, &str) {
-    let query = &args[1];
-    let file_path = &args[2];
-
-    (query, file_path)
-}
-

Приложение 12-5: Выделение функции parse_config из main

-

Мы все ещё собираем переменные приказной строки в вектор, но вместо присваивания значение переменной с порядковым указателем 1 переменной query и значение переменной с порядковым указателем 2 переменной с именем file_path в функции main, мы передаём весь вектор в функцию parse_config. Функция parse_config затем содержит логику, которая определяет, какой переменная идёт в какую переменную и передаёт значения обратно в main. Мы все ещё создаём переменные query и file_path в main, но main больше не несёт ответственности за определение соответствия переменной приказной строки и соответствующей переменной.

-

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

-

Объединение настроечных переменных

-

Мы можем сделать ещё один маленький шаг для улучшения функции parse_config. На данный мгновение мы возвращаем упорядоченный ряд, но затем мы немедленно разделяем его снова на отдельные части. Это признак того, что, возможно, пока у нас нет правильной абстракции.

-

Ещё один индикатор, который показывает, что есть место для улучшения, это часть config из parse_config, что подразумевает, что два значения, которые мы возвращаем, связаны друг с другом и оба являются частью одного настроечного значения. В настоящее время мы не отражаем этого смысла в устройстве данных, кроме объединения двух значений в упорядоченный ряд; мы могли бы поместить оба значения в одну устройство и дать каждому из полей устройства понятное имя. Это облегчит будущую поддержку этого кода, чтобы понять, как различные значения относятся друг к другу и какое их назначение.

-

В приложении 12-6 показаны улучшения функции parse_config .

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = parse_config(&args);
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    // --snip--
-
-    println!("With text:\n{contents}");
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-fn parse_config(args: &[String]) -> Config {
-    let query = args[1].clone();
-    let file_path = args[2].clone();
-
-    Config { query, file_path }
-}
-

Приложение 12-6: Переработка кода функции parse_config, чтобы возвращать образец устройства Config

-

Мы добавили устройство с именем Config объявленную с полями назваными как query и file_path. Ярлык parse_config теперь указывает, что она возвращает значение Config. В теле parse_config, где мы возвращали срезы строк, которые ссылаются на значения String в args, теперь мы определяем Config как содержащие собственные String значения. Переменная args в main является владельцем значений переменной и позволяют функции parse_config только одалживать их, что означает, что мы бы нарушили правила заимствования Rust, если бы Config попытался бы взять во владение значения в args .

-

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

-
-

К при использовании способа cloneСуществует тенденция в среде программистов Ржавчина избегать использования clone, т.к. это понижает эффективность работы кода. В Главе 13, вы изучите более эффективные способы, которые могут подойти в подобной случаи. Но сейчас можно воспроизводить несколько строк, чтобы продолжить работу, потому что вы сделаете эти повторы только один раз, а ваше имя файла и строка запроса будут очень маленькими. Лучше иметь работающую программу, которая немного неэффективна, чем пытаться заранее перерабатывать код при первом написании. По мере приобретения опыта работы с Ржавчина вам будет проще начать с наиболее эффективного решения, но сейчас вполне приемлемо вызвать clone.

-
-

Мы обновили код в main поэтому он помещает образец Config возвращённый из parse_config в переменную с именем config, и мы обновили код, в котором ранее использовались отдельные переменные query и file_path, так что теперь он использует вместо этого поля в устройстве Config.

-

Теперь наш код более чётко передаёт то, что query и file_path связаны и что цель из использования состоит в том, чтобы настроить, как программа будет работать. Любой код, который использует эти значения знает, что может найти их в именованных полях образца config по их назначению.

-

Создание строителя для устройства Config

-

Пока что мы извлекли логику, отвечающую за синтаксический анализ переменных приказной строки из main и помеисполнения его в функцию parse_config. Это помогло нам увидеть, что значения query и file_path были связаны и что их отношения должны быть отражены в нашем коде. Затем мы добавили устройство Config в качестве названия связанных общей целью query и file_path и чтобы иметь возможность вернуть именованные значения как имена полей устройства из функции parse_config.

-

Итак, теперь целью функции parse_config является создание образца Config, мы можем изменить parse_config из простой функции на функцию названную new, которая связана со устройством Config. Выполняя это изменение мы сделаем код более идиоматичным. Можно создавать образцы видов в встроенной библиотеке, такие как String с помощью вызова String::new. Точно так же изменив название parse_config на название функции new, связанную с Config, мы будем уметь создавать образцы Config, вызывая Config::new. Приложение 12-7 показывает изменения, которые мы должны сделать.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::new(&args);
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-
-    // --snip--
-}
-
-// --snip--
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn new(args: &[String]) -> Config {
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Config { query, file_path }
-    }
-}
-

Приложение 12-7: Переименование parse_config в Config::new

-

Мы обновили main где вызывали parse_config, чтобы вместо этого вызывалась Config::new. Мы изменили имя parse_config на new и перенесли его внутрь раздела impl, который связывает функцию new с Config. Попробуйте снова собрать код, чтобы убедиться, что он работает.

-

Исправление ошибок обработки

-

Теперь мы поработаем над исправлением обработки ошибок. Напомним, что попытки получить доступ к значениям в векторе args с порядковым указателем 1 или порядковым указателем 2 приведут к панике, если вектор содержит менее трёх элементов. Попробуйте запустить программу без каких-либо переменных; это будет выглядеть так:

-
$ cargo run
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep`
-thread 'main' panicked at src/main.rs:27:21:
-index out of bounds: the len is 1 but the index is 1
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Строка index out of bounds: the len is 1 but the index is 1 является сообщением об ошибке предназначенной для программистов. Она не поможет нашим конечным пользователям понять, что случилось и что они должны сделать вместо этого. Давайте исправим это сейчас.

-

Улучшение сообщения об ошибке

-

В приложении 12-8 мы добавляем проверку в функцию new, которая будет проверять, что срез достаточно длинный, перед попыткой доступа по порядковым указателям 1 и 2. Если срез не достаточно длинный, программа паникует и отображает улучшенное сообщение об ошибке.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::new(&args);
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    // --snip--
-    fn new(args: &[String]) -> Config {
-        if args.len() < 3 {
-            panic!("not enough arguments");
-        }
-        // --snip--
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Config { query, file_path }
-    }
-}
-

Приложение 12-8: Добавление проверки количества переменных

-

Этот код похож на функцию Guess::new написанную в приложении 9-13, где мы вызывали panic!, когда value переменной вышло за пределы допустимых значений. Здесь вместо проверки на рядзначений, мы проверяем, что длина args не менее 3 и остальная часть функции может работать при условии, что это условие было выполнено. Если в args меньше трёх элементов, это условие будет истинным и мы вызываем макрос panic! для немедленного завершения программы.

-

Имея нескольких лишних строк кода в new, давайте запустим программу снова без переменных, чтобы увидеть, как выглядит ошибка:

-
$ cargo run
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep`
-thread 'main' panicked at src/main.rs:26:13:
-not enough arguments
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Этот вывод лучше: у нас теперь есть разумное сообщение об ошибке. Тем не менее, мы также имеем постороннюю сведения, которую мы не хотим предоставлять нашим пользователям. Возможно, использованная техника, которую мы использовали в приложении 9-13, не является лучшей для использования: вызов panic! больше подходит для программирования сбоев, чем решения сбоев, как обсуждалось в главе 9. Вместо этого мы можем использовать другую технику, о которой вы узнали в главе 9 [возвращая Result], которая указывает либо на успех, либо на ошибку.

- -

-

Возвращение Result вместо вызова panic!

-

Мы можем вернуть значение Result, которое будет содержать образец Config в успешном случае и опишет неполадку в случае ошибки. Мы так же изменим функцию new на build потому что многие программисты ожидают что new никогда не завершится неудачей. Когда Config::build взаимодействует с main, мы можем использовать вид Result как сигнал возникновения сбоев. Затем мы можем изменить main, чтобы преобразовать исход Err в более применимую ошибку для наших пользователей без окружающего текста вроде thread 'main' и RUST_BACKTRACE, что происходит при вызове panic!.

-

Приложение 12-9 показывает изменения, которые нужно внести в возвращаемое значения функции Config::build, и в тело функции, необходимые для возврата вида Result. Заметьте, что этот код не собирается, пока мы не обновим main, что мы и сделаем в следующем приложении.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::new(&args);
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-

Приложение 12-9. Возвращение вида Result из Config::build

-

Наша функция build теперь возвращает Result с образцом Config в случае успеха и &'static str в случае ошибки. Значения ошибок всегда будут строковыми записями, которые имеют время жизни 'static.

-

Мы внесли два изменения в тело функции build: вместо вызова panic!, когда пользователь не передаёт достаточно переменных, мы теперь возвращаем Err значение и мы завернули возвращаемое значение Config в Ok . Эти изменения заставят функцию соответствовать своей новой ярлыке вида.

-

Возвращение значения Err из Config::build позволяет функции main обработать значение Result возвращённое из функции build и выйти из этапа более чисто в случае ошибки.

- -

-

Вызов Config::build и обработка ошибок

-

Чтобы обработать ошибку и вывести более дружественное сообщение об ошибке, нам нужно обновить код main для обработки Result, возвращаемого из Config::build как показано в приложении 12-10. Мы также возьмём на себя ответственность за выход из программы приказной строки с ненулевым кодом ошибки panic! и выполняем это вручную. Не нулевой значение выхода - это соглашение, которое указывает этапу, который вызывает нашу программу, что программа завершилась с ошибкой.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-use std::process;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        println!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    // --snip--
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-

Приложение 12-10. Выход с кодом ошибки если создание новой Config терпит неудачу

-

В этом приложении мы использовали способ, который мы ещё не рассматривали подробно: unwrap_or_else, который в встроенной библиотеке определён как Result<T, E>. Использование unwrap_or_else позволяет нам определить некоторые пользовательские ошибки обработки, не содержащие panic!. Если Result является значением Ok, поведение этого способа подобно unwrap: возвращает внутреннее значение из обёртки Ok. Однако, если значение является значением Err, то этот способ вызывает код замыкания, которое является анонимной функцией, определённой заранее и передаваемую в качестве переменной в unwrap_or_else. Мы рассмотрим замыкания более подробно в главе 13. В данный мгновение, вам просто нужно знать, что unwrap_or_else передаст внутреннее значение Err, которое в этом случае является постоянной строкой not enough arguments, которое мы добавили в приложении 12-9, в наше замыкание как переменная err указанное между вертикальными линиями. Код в замыкании может затем использовать значение err при выполнении.

-

Мы добавили новую строку use, чтобы подключить process из встроенной библиотеки в область видимости. Код в замыкании, который будет запущен в случае ошибки содержит только две строчки: мы печатаем значение err и затем вызываем process::exit. Функция process::exit немедленно остановит программу и вернёт номер, который был передан в качестве кода состояния выхода. Это похоже на обработку с помощью макроса panic!, которую мы использовали в приложении 12-8, но мы больше не получаем весь дополнительный вывод. Давай попробуем:

-
$ cargo run
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
-     Running `target/debug/minigrep`
-Problem parsing arguments: not enough arguments
-
-

Замечательно! Этот вывод намного дружелюбнее для наших пользователей.

-

Извлечение логики из main

-

Теперь, когда мы закончили переработка кода разбора настройке, давайте обратимся к логике программы. Как мы указали в разделе «Разделение ответственности в двоичных делах», мы извлечём функцию с именем run, которая будет содержать всю логику, присутствующую в настоящее время в функции main и которая не связана с настройкой настройке или обработкой ошибок. Когда мы закончим, то main будет краткой, легко проверяемой и мы сможем написать проверки для всей остальной логики.

-

Код 12-11 отображает извлечённую логику в функцию run. Мы делаем маленькое, инкрементальное приближение к извлечению функции. Код всё ещё сосредоточен в файле src/main.rs:

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-use std::process;
-
-fn main() {
-    // --snip--
-
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        println!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    run(config);
-}
-
-fn run(config: Config) {
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-
-// --snip--
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-

Приложение 12-11. Извлечение функции run, содержащей остальную логику программы

-

Функция run теперь содержит всю оставшуюся логику из main, начиная от чтения файла. Функция run принимает образец Config как переменная.

-

Возврат ошибок из функции run

-

Оставшаяся логика программы выделена в функцию run, где мы можем улучшить обработку ошибок как мы уже делали с Config::build в приложении 12-9. Вместо того, чтобы позволить программе паниковать с помощью вызова expect, функция run вернёт Result<T, E>, если что-то пойдёт не так. Это позволит далее окне выводадировать логику обработки ошибок в main удобным способом. Приложение 12-12 показывает изменения, которые мы должны внести в ярлык и тело run.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-use std::process;
-use std::error::Error;
-
-// --snip--
-
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        println!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    run(config);
-}
-
-fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    println!("With text:\n{contents}");
-
-    Ok(())
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-

Приложение 12-12. Изменение функции run для возврата Result

-

Здесь мы сделали три значительных изменения. Во-первых, мы изменили вид возвращаемого значения функции run на Result<(), Box<dyn Error>> . Эта функция ранее возвращала вид () и мы сохраняли его как значение, возвращаемое в случае Ok.

-

Для вида ошибки мы использовали предмет особенность Box<dyn Error> (и вверху мы подключили вид std::error::Error в область видимости с помощью указания use). Мы рассмотрим особенности предметов в главе 17. Сейчас просто знайте, что Box<dyn Error> означает, что функция будет возвращать вид выполняющий особенность Error, но не нужно указывать, какой именно будет вид возвращаемого значения. Это даёт возможность возвращать значения ошибок, которые могут быть разных видов в разных случаях. Ключевое слово dyn сокращение для слова «изменяемый».

-

Во-вторых, мы убрали вызов expect в пользу использования оператора ?, как мы обсудили в главе 9. Скорее, чем вызывать panic! в случае ошибки, оператор ? вернёт значение ошибки из текущей функции для вызывающего, чтобы он её обработал.

-

В-третьих, функция run теперь возвращает значение Ok в случае успеха. В ярлыке функции run успешный вид объявлен как (), который означает, что нам нужно обернуть значение единичного вида в значение Ok. Данный правила написания Ok(()) поначалу может показаться немного странным, но использование () выглядит как идиоматический способ указать, что мы вызываем run для его побочных эффектов; он не возвращает значение, которое нам нужно.

-

Когда вы запустите этот код, он собирается, но отобразит предупреждение:

-
$ cargo run -- the poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-warning: unused `Result` that must be used
-  --> src/main.rs:19:5
-   |
-19 |     run(config);
-   |     ^^^^^^^^^^^
-   |
-   = note: this `Result` may be an `Err` variant, which should be handled
-   = note: `#[warn(unused_must_use)]` on by default
-help: use `let _ = ...` to ignore the resulting value
-   |
-19 |     let _ = run(config);
-   |     +++++++
-
-warning: `minigrep` (bin "minigrep") generated 1 warning
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.71s
-     Running `target/debug/minigrep the poem.txt`
-Searching for the
-In file poem.txt
-With text:
-I'm nobody! Who are you?
-Are you nobody, too?
-Then there's a pair of us - don't tell!
-They'd banish us, you know.
-
-How dreary to be somebody!
-How public, like a frog
-To tell your name the livelong day
-To an admiring bog!
-
-
-

Rust говорит, что наш код пренебрег Result значение и значение Result может указывать на то, что произошла ошибка. Но мы не проверяем, была ли ошибка и сборщик напоминает нам, что мы, вероятно, хотели здесь выполнить некоторый код обработки ошибок! Давайте исправим эту неполадку сейчас.

-

Обработка ошибок, возвращённых из run в main

-

Мы будем проверять и обрабатывать ошибки используя способику, подобную той, которую мы использовали для Config::build в приложении 12-10, но с небольшой разницей:

-

Файл: src/main.rs

-
use std::env;
-use std::error::Error;
-use std::fs;
-use std::process;
-
-fn main() {
-    // --snip--
-
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        println!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    if let Err(e) = run(config) {
-        println!("Application error: {e}");
-        process::exit(1);
-    }
-}
-
-fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    println!("With text:\n{contents}");
-
-    Ok(())
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-

Мы используем if let вместо unwrap_or_else чтобы проверить, возвращает ли run значение Err и вызывается process::exit(1), если это так. Функция run не возвращает значение, которое мы хотим развернуть способом unwrap, таким же образом как Config::build возвращает образец Config. Так как run возвращает () в случае успеха и мы заботимся только об обнаружении ошибки, то нам не нужно вызывать unwrap_or_else, чтобы вернуть развёрнутое значение, потому что оно будет только ().

-

Тело функций if let и unwrap_or_else одинаковы в обоих случаях: мы печатаем ошибку и выходим.

-

Разделение кода на библиотечный ящик

-

Наш дело minigrep пока выглядит хорошо! Теперь мы разделим файл src/main.rs и поместим некоторый код в файл src/lib.rs. Таким образом мы сможем его проверять и чтобы в файле src/main.rs было меньшее количество полезных обязанностей.

-

Давайте перенесём весь код не относящийся к функции main из файла src/main.rs в новый файл src/lib.rs:

-
    -
  • Определение функции run
  • -
  • Соответствующие указания use
  • -
  • Определение устройства Config
  • -
  • Определение функции Config::build
  • -
-

Содержимое src/lib.rs должно иметь ярлыки, показанные в приложении 12-13 (мы опуисполнения тела функций для краткости). Обратите внимание, что код не будет собираться пока мы не изменим src/main.rs в приложении 12-14.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        // --snip--
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    // --snip--
-    let contents = fs::read_to_string(config.file_path)?;
-
-    println!("With text:\n{contents}");
-
-    Ok(())
-}
-

Приложение 12-13. Перемещение Config и run в src/lib.rs

-

Мы добавили определетель доступа pub к устройстве Config, а также её полям, к способу build и функции run. Теперь у нас есть библиотечный ящик, который содержит открытый API, который мы можем проверять!

-

Теперь нам нужно подключить код, который мы перемеисполнения в src/lib.rs, в область видимости двоичного ящика внутри src/main.rs, как показано в приложении 12-14.

-

Файл: src/main.rs

-
use std::env;
-use std::process;
-
-use minigrep::Config;
-
-fn main() {
-    // --snip--
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        println!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    if let Err(e) = minigrep::run(config) {
-        // --snip--
-        println!("Application error: {e}");
-        process::exit(1);
-    }
-}
-

Приложение 12-14. Использование ящика библиотеки minigrep внутри src/main.rs

-

Мы добавляем use minigrep::Config для подключения вида Config из ящика библиотеки в область видимости двоичного ящика и добавляем к имени функции run приставка нашего ящика. Теперь все функции должны быть подключены и должны работать. Запустите программу с cargo run и убедитесь, что все работает правильно.

-

Уф! Было много работы, но мы настроены на будущий успех. Теперь проще обрабатывать ошибки и мы сделали код более состоящим из звеньев. С этого особенности почти вся наша работа будет выполняться внутри src/lib.rs.

-

Давайте воспользуемся этой новой выделения на звенья, сделав что-то, что было бы трудно со старым кодом, но легко с новым кодом: мы напишем несколько проверок!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch12-04-testing-the-librarys-functionality.html b/rustbook-ru/book/ch12-04-testing-the-librarys-functionality.html deleted file mode 100644 index dbca8241e..000000000 --- a/rustbook-ru/book/ch12-04-testing-the-librarys-functionality.html +++ /dev/null @@ -1,675 +0,0 @@ - - - - - - Разработка возможности библиотеки с помощью разработки через проверка - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Развитие возможности библиотеки разработкой на основе проверок

-

Теперь, когда мы извлекли логику в src/lib.rs и оставили разбор переменных приказной строки и обработку ошибок в src/main.rs, стало гораздо проще писать проверки для основной возможности нашего кода. Мы можем вызывать функции напрямую с различными переменнойми и проверить возвращаемые значения без необходимости вызова нашего двоичного файла из приказной строки.

-

В этом разделе в программу minigrep мы добавим логику поиска с использованием этапа разработки через проверка (TDD), который следует этим шагам:

-
    -
  1. Напишите проверка, который завершается неудачей, и запустите его, чтобы убедиться, что он не сработал именно по той причине, которую вы ожидаете.
  2. -
  3. Пишите или изменяйте ровно столько кода, чтобы успешно выполнился новый проверку.
  4. -
  5. Выполните переработка кода кода, который вы только что добавили или изменили, и убедитесь, что проверки продолжают проходить.
  6. -
  7. Повторите с шага 1!
  8. -
-

Хотя это всего лишь один из многих способов написания программного обеспечения, TDD может помочь в разработке кода. Написание проверки перед написанием кода, обеспечивающего прохождение проверки, помогает поддерживать высокое покрытие проверкими на протяжении всего этапа разработки.

-

Мы проверим выполнение возможности, которая делает поиск строки запроса в содержимом файла и создание списка строк, соответствующих запросу. Мы добавим эту возможность в функцию под названием search.

-

Написание проверки с ошибкой

-

Поскольку они нам больше не нужны, давайте удалим указания с println!, которые мы использовали для проверки поведения программы в src/lib.rs и src/main.rs. Затем в src/lib.rs мы добавим звено tests с проверочной функцией, как делали это в главе 11. Проверочная функция определяет поведение, которое мы хотим проверить в функции search: она должна принимать запрос и текст для поиска, а возвращать только те строки из текста, которые содержат запрос. В приложении 12-15 показан этот проверка, который пока не собирается.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 12-15: Создание безуспешного проверки для функции search, которую мы хотим создать

-

Этот проверка ищет строку "duct". Текст, в котором мы ищем, состоит из трёх строк, только одна из которых содержит "duct" (обратите внимание, что обратная косая черта после открывающей двойной кавычки говорит Ржавчина не помещать символ новой строки в начало содержимого этого строкового записи). Мы проверяем, что значение, возвращаемое функцией search, содержит только ожидаемую нами строку.

-

Мы не можем запустить этот проверка и увидеть сбой, потому что проверка даже не собирается: функции search ещё не существует! В соответствии с принципами TDD мы добавим ровно столько кода, чтобы проверка собирался и запускался, добавив определение функции search, которая всегда возвращает пустой вектор, как показано в приложении 12-16. Потом проверка должен собраться и потерпеть неудачу при запуске, потому что пустой вектор не равен вектору, содержащему строку "safe, fast, productive."

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    vec![]
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 12-16. Определение функции search, достаточное, чтобы проверка собрался

-

Заметьте, что в ярлыке search нужно явно указать время жизни 'a для переменной contents и возвращаемого значения. Напомним из Главы 10, что свойства времени жизни указывают с временем жизни какого переменной связано время жизни возвращаемого значения. В данном случае мы говорим, что возвращаемый вектор должен содержать срезы строк, ссылающиеся на содержимое переменной contents (а не переменной query).

-

Другими словами, мы говорим Rust, что данные, возвращаемые функцией search, будут жить до тех пор, пока живут данные, переданные в функцию search через переменная contents. Это важно! Чтобы ссылки были действительными, данные, на которые ссылаются с помощью срезов тоже должны быть действительными; если сборщик предполагает, что мы делаем строковые срезы переменной query, а не переменной contents, он неправильно выполнит проверку безопасности.

-

Если мы забудем изложении времени жизни и попробуем собрать эту функцию, то получим следующую ошибку:

-
$ cargo build
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-error[E0106]: missing lifetime specifier
-  --> src/lib.rs:28:51
-   |
-28 | pub fn search(query: &str, contents: &str) -> Vec<&str> {
-   |                      ----            ----         ^ expected named lifetime parameter
-   |
-   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `query` or `contents`
-help: consider introducing a named lifetime parameter
-   |
-28 | pub fn search<'a>(query: &'a str, contents: &'a str) -> Vec<&'a str> {
-   |              ++++         ++                 ++              ++
-
-For more information about this error, try `rustc --explain E0106`.
-error: could not compile `minigrep` (lib) due to 1 previous error
-
-

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

-

Другие языки программирования не требуют от вас связывания в ярлыке переменных с возвращаемыми значениями, но после определённой опытов вам станет проще. Можете сравнить этот пример с разделом «Проверка ссылок с временами жизни» главы 10.

-

Запустим проверку:

-
$ cargo test
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.97s
-     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
-
-running 1 test
-test tests::one_result ... FAILED
-
-failures:
-
----- tests::one_result stdout ----
-thread 'tests::one_result' panicked at src/lib.rs:44:9:
-assertion `left == right` failed
-  left: ["safe, fast, productive."]
- right: []
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::one_result
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Отлично. Наш проверка не сработал, как мы и ожидали. Давайте сделаем так, чтобы он срабатывал!

-

Написание кода для прохождения проверки

-

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

-
    -
  • Повторение по каждой строке содержимого.
  • -
  • Проверить, содержит ли данная строка искомую.
  • -
  • Если это так, добавить её в список значений, которые мы возвращаем.
  • -
  • Если это не так, ничего не делать.
  • -
  • Вернуть список итогов.
  • -
-

Давайте проработаем каждый шаг, начиная с перебора строк.

-

Перебор строк с помощью способа lines

-

В Ржавчина есть полезный способ для построчной повторения строк, удобно названный lines, как показано в приложении 12-17. Обратите внимание, код пока не собирается.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    for line in contents.lines() {
-        // do something with line
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 12-17: Повторение по каждой строке из contents

-

Способ lines возвращает повторитель . Мы подробно поговорим об повторителях в Главе 13, но вспомните, что вы видели этот способ использования повторителя в Приложении 3-5, где мы использовали цикл for с повторителем, чтобы выполнить некоторый код для каждого элемента в собрания.

-

Поиск в каждой строке текста запроса

-

Далее мы проверяем, содержит ли текущая строка нашу искомую строку. К счастью, у строк есть полезный способ contains, который именно это и делает! Добавьте вызов способа contains в функции search, как показано в приложении 12-18. Обратите внимание, что это все ещё не собирается.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    for line in contents.lines() {
-        if line.contains(query) {
-            // do something with line
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 12-18. Добавление проверки, содержится ли query в строке

-

На данный мгновение мы наращиваем возможность. Чтобы заставить это собираться, нам нужно вернуть значение из тела функции, как мы указали в ярлыке функции.

-

Сохранение совпавшей строки

-

Чтобы завершить эту функцию, нам нужен способ сохранить совпадающие строки, которые мы хотим вернуть. Для этого мы можем создать изменяемый вектор перед циклом for и вызывать способ push для сохранения line в векторе. После цикла for мы возвращаем вектор, как показано в приложении 12-19.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 12-19: Сохраняем совпавшие строки, чтобы впоследствии их можно было вернуть

-

Теперь функция search должна возвратить только строки, содержащие query, и проверка должен пройти. Запустим его:

-
$ cargo test
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.22s
-     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
-
-running 1 test
-test tests::one_result ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-     Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests minigrep
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Наш проверка пройден, значит он работает!

-

На этом этапе мы могли бы рассмотреть возможности изменения выполнения функции поиска, сохраняя прохождение проверок и поддерживая имеющуюся возможность. Код в функции поиска не так уж плох, но он не использует некоторые полезные функции повторителей. Вернёмся к этому примеру в главе 13, где будем исследовать повторители подробно, и посмотрим как его улучшить.

-

Использование функции search в функции run

-

Теперь, когда функция search работает и проверена, нужно вызвать search из нашей функции run. Нам нужно передать значение config.query и contents, которые run читает из файла, в функцию search. Тогда run напечатает каждую строку, возвращаемую из search:

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    for line in search(&config.query, &contents) {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Мы по-прежнему используем цикл for для возврата каждой строки из функции search и её печати.

-

Теперь вся программа должна работать! Давайте попробуем сначала запустить её со словом «frog», которое должно вернуть только одну строчку из стихотворения Эмили Дикинсон:

-
$ cargo run -- frog poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.38s
-     Running `target/debug/minigrep frog poem.txt`
-How public, like a frog
-
-

Здорово! Теперь давайте попробуем слово, которое будет соответствовать нескольким строкам, например «body»:

-
$ cargo run -- body poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep body poem.txt`
-I'm nobody! Who are you?
-Are you nobody, too?
-How dreary to be somebody!
-
-

И наконец, давайте удостоверимся, что мы не получаем никаких строк, когда ищем слово, отсутствующее в стихотворении, например «monomorphization»:

-
$ cargo run -- monomorphization poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep monomorphization poem.txt`
-
-

Отлично! Мы создали собственную простое-исполнение обычного средства и научились тому, как внутренне выстроить

-

приложения. Мы также немного узнали о файловом вводе и выводе, временах жизни, проверке и разборе переменных приказной строки.

-

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

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch12-05-working-with-environment-variables.html b/rustbook-ru/book/ch12-05-working-with-environment-variables.html deleted file mode 100644 index 41903b3f6..000000000 --- a/rustbook-ru/book/ch12-05-working-with-environment-variables.html +++ /dev/null @@ -1,776 +0,0 @@ - - - - - - Работа с переменными среды - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Работа с переменными окружения

-

Мы улучшим minigrep, добавив дополнительную функцию: возможность для поиска без учёта регистра, которую пользователь может включить с помощью переменной среды окружения. Мы могли бы сделать эту функцию свойствоом приказной строки и потребовать, чтобы пользователи вводили бы её каждый раз при её применении, но вместо этого мы будем использовать переменную среды окружения, что позволит нашим пользователям устанавливать переменную среды один раз и все поиски будут не чувствительны к регистру в этом окно вызоваьном сеансе.

-

Написание ошибочного проверки для функции search с учётом регистра

-

Мы, во-первых, добавим новую функцию search_case_insensitive, которую мы будем вызывать, когда переменная окружения содержит значение. Мы продолжим следовать этапу TDD, поэтому первый шаг - это снова написать не проходящий проверку. Мы добавим новый проверка для новой функции search_case_insensitive и переименуем наш старый проверка из one_result в case_sensitive, чтобы прояснить различия между двумя проверкими, как показано в приложении 12-20.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    for line in search(&config.query, &contents) {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 12-20. Добавление нового не проходящего проверки для функции поиска нечувствительной к регистру, которую мы собираемся добавить

-

Обратите внимание, что мы также отредактировали содержимое переменной contents из старого проверки. Мы добавили новую строку с текстом "Duct tape.", используя заглавную D, которая не должна соответствовать запросу "duct" при поиске с учётом регистра. Такое изменение старого проверки помогает избежать случайного нарушения возможности поиска чувствительного к регистру, который мы уже выполнили. Этот проверка должен пройти сейчас и должен продолжать выполняться успешно, пока мы работаем над поиском без учёта регистра.

-

Новый проверка для поиска нечувствительного к регистру использует "rUsT" качестве строки запроса. В функции search_case_insensitive, которую мы собираемся выполнить, запрос "rUsT" должен соответствовать строке содержащей "Rust:" с большой буквы R и соответствовать строке "Trust me.", хотя обе имеют разные регистры из запроса. Это наш не проходящий проверка, он не собирается, потому что мы ещё не определили функцию search_case_insensitive. Не стесняйтесь добавлять скелет выполнение, которая всегда возвращает пустой вектор, подобно тому, как мы это делали для функции search в приложении 12-16, чтобы увидеть сборку проверки и его сбой.

-

Выполнение функции search_case_insensitive

-

Функция search_case_insensitive, показанная в приложении 12-21, будет почти такая же, как функция search. Разница лишь в том, что текст будет в нижнем регистре для query и для каждой line, так что для любого регистра входных переменных это будет тот же случай, когда мы проверяем, содержит ли строка запрос.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    for line in search(&config.query, &contents) {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 12-21. Определение функции search_case_insensitive с уменьшением регистра строки запроса и строки содержимого перед их сравнением

-

Сначала преобразуем в нижний регистр строку query и сохраняем её в затенённой переменной с тем же именем. Вызов to_lowercase для строки запроса необходим, так что независимо от того, будет ли пользовательский запрос "rust" , "RUST", "Rust" или "rUsT", мы будем преобразовывать запрос к "rust" и делать значение нечувствительным к регистру. Хотя to_lowercase будет обрабатывать Unicode, он не будет точным на 100%. Если бы мы писали существующее приложение, мы бы хотели проделать здесь немного больше работы, но этот раздел посвящён переменным среды, а не Unicode, поэтому мы оставим это здесь.

-

Обратите внимание, что query теперь имеет вид String, а не срез строки, потому что вызов to_lowercase создаёт новые данные, а не ссылается на существующие. К примеру, запрос: "rUsT" это срез строки не содержащий строчных букв u или t, которые мы можем использовать, поэтому мы должны выделить новую String, содержащую «rust». Когда мы передаём запрос query в качестве переменной способа contains, нам нужно добавить знак, поскольку ярлык contains, определена для приёмы среза строки.

-

Затем мы добавляем вызов to_lowercase для каждой строки line для преобразования к нижнему регистру всех символов. Теперь, когда мы преобразовали line и query в нижний регистр, мы найдём совпадения независимо от того, в каком регистре находится переменная с запросом.

-

Давайте посмотрим, проходит ли эта выполнение проверки:

-
$ cargo test
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.33s
-     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
-
-running 2 tests
-test tests::case_insensitive ... ok
-test tests::case_sensitive ... ok
-
-test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-     Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests minigrep
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Отлично! Проверки прошли. Теперь давайте вызовем новую функцию search_case_insensitive из функции run. Во-первых, мы добавим свойство настройке в устройство Config для переключения между поиском с учётом регистра и без учёта регистра. Добавление этого поля приведёт к ошибкам сборщика, потому что мы ещё нигде не объявим это поле:

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Мы добавили поле ignore_case, которое содержит логическое значение. Далее нам нужна функция run, чтобы проверить значение поля ignore_case и использовать его, чтобы решить, вызывать ли функцию search или функцию search_case_insensitive, как показано в приложении 12-22. Этот код все ещё не собирается.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 12-22. Вызов либо search, либо search_case_insensitive на основе значения в config.ignore_case

-

Наконец, нам нужно проверить переменную среды. Функции для работы с переменными среды находятся в звене env встроенной библиотеки, поэтому мы хотим подключить этот звено в область видимости в верхней части src/lib.rs. Затем мы будем использовать функцию var из звена env для проверки установлено ли любое значение в переменной среды с именем IGNORE_CASE, как показано в приложении 12-23.

-

Файл: src/lib.rs

-
use std::env;
-// --snip--
-
-use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        let ignore_case = env::var("IGNORE_CASE").is_ok();
-
-        Ok(Config {
-            query,
-            file_path,
-            ignore_case,
-        })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 12-23. Проверка переменной среды с именем IGNORE_CASE

-

Здесь мы создаём новую переменную ignore_case. Чтобы установить её значение, мы вызываем функцию env::var и передаём ей имя переменной окружения IGNORE_CASE. Функция env::var возвращает Result, который будет успешным исходом Ok содержащий значение переменной среды, если переменная среды установлена. Он вернёт исход Err, если переменная окружения не установлена.

-

Мы используем способ is_ok у Result, чтобы проверить установлена ли переменная окружения, что будет означать, что программа должна выполнить поиск без учёта регистра. Если переменная среды IGNORE_CASE не содержит любого значения, то is_ok вернёт значение false и программа выполнит поиск c учётом регистра. Мы не заботимся о значении переменной среды, нас важно только установлена она или нет, поэтому мы проверяем is_ok, а не используем unwrap, expect или любой другой способ, который мы видели у Result.

-

Мы передаём значение переменной ignore_case образцу Config, чтобы функция run могла прочитать это значение и решить, следует ли вызывать search или search_case_insensitive, как мы выполнили в приложении 12-22.

-

Давайте попробуем! Во-первых, мы запустим нашу программу без установленной переменной среды и с помощью значения запроса to, который должен соответствовать любой строке, содержащей слово «to» в нижнем регистре:

-
$ cargo run -- to poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep to poem.txt`
-Are you nobody, too?
-How dreary to be somebody!
-
-

Похоже, все ещё работает! Теперь давайте запустим программу с IGNORE_CASE, установленным в 1, но с тем же значением запроса to.

-
$ IGNORE_CASE=1 cargo run -- to poem.txt
-
-

Если вы используете PowerShell, вам нужно установить переменную среды и запустить программу двумя приказми, а не одной:

-
PS> $Env:IGNORE_CASE=1; cargo run -- to poem.txt
-
-

Это заставит переменную окружения IGNORE_CASE сохраниться до конца сеанса работы окне вывода. Переменную можно отключить с помощью приказы Remove-Item:

-
PS> Remove-Item Env:IGNORE_CASE
-
-

Мы должны получить строки, содержащие «to», которые могут иметь заглавные буквы:

- -
Are you nobody, too?
-How dreary to be somebody!
-To tell your name the livelong day
-To an admiring bog!
-
-

Отлично, мы также получили строки, содержащие «To»! Наша программа minigrep теперь может выполнять поиск без учёта регистра, управляемая переменной среды. Теперь вы знаете, как управлять свойствами, заданными с помощью переменных приказной строки или переменных среды.

-

Некоторые программы допускают использование переменных и переменных среды для одной и той же настройке. В таких случаях программы решают, что из них имеет больший приоритет. Для другого самостоятельного упражнения попробуйте управлять чувствительностью к регистру с помощью переменной приказной строки или переменной окружения. Решите, переменная приказной строки или переменная среды будет иметь приоритет, если программа выполняется со значениями "учитывать регистр" в одном случае, и "пренебрегать регистр" в другом.

-

Звено std::env содержит много других полезных функций для работы с переменными среды: ознакомьтесь с его документацией, чтобы узнать доступные.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch12-06-writing-to-stderr-instead-of-stdout.html b/rustbook-ru/book/ch12-06-writing-to-stderr-instead-of-stdout.html deleted file mode 100644 index 90b5b344b..000000000 --- a/rustbook-ru/book/ch12-06-writing-to-stderr-instead-of-stdout.html +++ /dev/null @@ -1,282 +0,0 @@ - - - - - - Запись сообщений об ошибках в stderr вместо stdout - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Запись сообщений ошибок в поток ошибок вместо принятого потока вывода

-

В данный мгновение мы записываем весь наш вывод в окно вызова, используя функцию println!. В большинстве окно вызоваов предоставлено два вида вывода: обычный поток вывода ( stdout ) для общей сведений и обычный поток ошибок ( stderr ) для сообщений об ошибках. Это различие позволяет пользователям выбирать, направлять ли успешный вывод программы в файл, но при этом выводить сообщения об ошибках на экран.

-

Функция println! может печатать только в обычный вывод, поэтому мы должны использовать что-то ещё для печати в обычный поток ошибок.

-

Проверка, куда записываются ошибки

-

Во-первых, давайте посмотрим, как содержимое, напечатанное из minigrep в настоящее время записывается в обычный вывод, включая любые сообщения об ошибках, которые мы хотим вместо этого записать в обычный поток ошибок. Мы сделаем это, перенаправив обычный поток вывода в файл и намеренно вызовем ошибку. Мы не будем перенаправлять обычный поток ошибок, поэтому любой содержание, отправленный в поток принятых ошибок будет продолжать отображаться на экране.

-

Ожидается, что программы приказной строки будут отправлять сообщения об ошибках в обычный поток ошибок, поэтому мы все равно можем видеть сообщения об ошибках на экране, даже если мы перенаправляем обычный поток вывода в файл. Наша программа в настоящее время не ведёт себя правильно: мы увидим, что она сохраняет вывод сообщения об ошибке в файл!

-

Чтобы отобразить это поведение, мы запустим программу с помощью > и именем файла output.txt в который мы хотим перенаправить обычный поток вывода. Мы не будем передавать никаких переменных, что должно вызвать ошибку:

-
$  cargo run > output.txt
-
-

правила написания > указывает оболочке записывать содержимое принятого вывода в output.txt вместо экрана. Мы не увидели сообщение об ошибке, которое мы ожидали увидеть на экране, так что это означает, что оно должно быть в файле. Вот что содержит output.txt:

-
Problem parsing arguments: not enough arguments
-
-

Да, наше сообщение об ошибке выводится в обычный вывод. Гораздо более полезнее, чтобы подобные сообщения об ошибках печатались в встроенной поток ошибок, поэтому в файл попадают только данные из успешного запуска. Мы поменяем это.

-

Печать ошибок в поток ошибок

-

Мы будем использовать код в приложении 12-24, чтобы изменить способ вывода сообщений об ошибках. Из-за переработки кода, который мы делали ранее в этой главе, весь код, который печатает сообщения об ошибках, находится в одной функции: main. Обычная библиотека предоставляет макрос eprintln!который печатает в обычный поток ошибок, поэтому давайте изменим два места, где мы вызывали println! для печати ошибок, чтобы использовать eprintln! вместо этого.

-

Файл: src/main.rs

-
use std::env;
-use std::process;
-
-use minigrep::Config;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        eprintln!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    if let Err(e) = minigrep::run(config) {
-        eprintln!("Application error: {e}");
-        process::exit(1);
-    }
-}
-

Запись сообщений об ошибках в Standard Error вместо Standard Output используя eprintln!

-

Давайте снова запустим программу таким же образом, без каких-либо переменных и перенаправим обычный вывод с помощью >:

-
$ cargo run > output.txt
-Problem parsing arguments: not enough arguments
-
-

Теперь мы видим ошибку на экране и output.txt не содержит ничего, что мы ожидаем от программы приказной строки.

-

Давайте снова запустим программу с переменнойми, которые не вызывают ошибку, но все же перенаправляют обычный вывод в файл, например так:

-
$ cargo run -- to poem.txt > output.txt
-
-

Мы не увидим никакого вывода в окно вызова, а output.txt будет содержать наши итоги:

-

Файл: output.txt

-
Are you nobody, too?
-How dreary to be somebody!
-
-

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

-

Итоги

-

В этой главе были повторены некоторые основные подходы, которые вы изучили до сих пор и было рассказано, как выполнять обычные действия ввода-вывода в Rust. Используя переменные приказной строки, файлы, переменные среды и макросeprintln! для печати ошибок и вы теперь готовы писать приложения приказной строки. В сочетании с подходами из предыдущих главах, ваш код будет хорошо согласован, будет эффективно хранить данные в соответствующих устройствах, хорошо обрабатывать ошибки и хорошо проверяться.

-

Далее мы рассмотрим некоторые возможности Rust, на которые повлияли полезные языки: замыкания и повторители.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch13-00-functional-features.html b/rustbook-ru/book/ch13-00-functional-features.html deleted file mode 100644 index 3a2aca150..000000000 --- a/rustbook-ru/book/ch13-00-functional-features.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - Полезные возможности языка: повторители и замыкания - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Полезные возможности языка: повторители и замыкания

-

Внешний вид языка Ржавчина черпал вдохновение из многих других языков и техник, среди которых значительное влияние оказало функциональное программирование. Программирование в функциональном исполнении подразумевает использование функций взначении предметов, передавая их в качестве переменных, возвращая их из других функций, присваивая их переменным для последующего выполнения и так далее.

-

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

-

Более подробно мы поговорим про:

-
    -
  • Замыкания - устройства, подобные функциям, которые можно помещать в переменные
  • -
  • Повторители — способ обработки последовательности элементов,
  • -
  • То, как, используя замыкания и повторители, улучшить работу с действиеми ввода-вывода в деле из главы 12
  • -
  • Производительность замыканий и повторителей (спойлер: они быстрее, чем вы думаете!)
  • -
-

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

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch13-01-closures.html b/rustbook-ru/book/ch13-01-closures.html deleted file mode 100644 index c65faaade..000000000 --- a/rustbook-ru/book/ch13-01-closures.html +++ /dev/null @@ -1,595 +0,0 @@ - - - - - - Замыкания: анонимные функции, которые захватывают своё окружение - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- -

-

Замыкания: анонимные функции, которые запечатлевают ("захватывают") своё окружение

-

Замыкания в Ржавчина - это анонимные функции, которые можно сохранять в переменных или передавать в качестве переменных другим функциям. Вы можете создать замыкание в одном месте, а затем вызвать его в каком-нибудь другом, чтобы выполнить обработку в ином среде. В отличие от функций, замыкания могут использовать значения из области видимости в которой они были определены. Мы выполним, как эти функции замыканий открывают возможности для повторного использования кода и изменения его поведения.

- -

- -

-

Захват переменных окружения с помощью замыкания

-

Сначала мы рассмотрим, как с помощью замыканий можно использовать предметы из области, в которой они вместе были определены, для их последующего использования. Вот сценарий: Время от времени наша предприятие по производству футболок в качестве акции дарит эксклюзивные футболки, выпущенные ограниченным тиражом, каким-нибудь пользователям из нашего списка рассылки. Люди из списка рассылки при желании могут выбрать любимый цвет в своём профиле. Если человек, выбранный для получения бесплатной футболки, указал свой любимый цвет, он получает футболку этого цвета. Если человек не указал свой любимый цвет, он получит рубашку того цвета, которых у предприятия на данный мгновение больше всего.

-

Существует множество способов выполнить это. В данном примере мы будем использовать перечисление ShirtColor, которое может быть двух исходов Red и Blue (для простоты ограничим количество доступных цветов этими двумя). Запасы предприятия мы представим устройством Inventory, которая состоит из поля shirts, содержащего Vec<ShirtColor>, в котором перечислены рубашки тех цветов, которые есть в наличии. Способ giveaway, определённый в Inventory, принимает необязательный свойство - цвет, предпочитаемый пользователем, выбранным для получения бесплатной рубашки, и возвращает тот цвет рубашки, который он получит в действительности. Эта схема показана в приложении 13-1:

-

Имя файла: src/main.rs

-
#[derive(Debug, PartialEq, Copy, Clone)]
-enum ShirtColor {
-    Red,
-    Blue,
-}
-
-struct Inventory {
-    shirts: Vec<ShirtColor>,
-}
-
-impl Inventory {
-    fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
-        user_preference.unwrap_or_else(|| self.most_stocked())
-    }
-
-    fn most_stocked(&self) -> ShirtColor {
-        let mut num_red = 0;
-        let mut num_blue = 0;
-
-        for color in &self.shirts {
-            match color {
-                ShirtColor::Red => num_red += 1,
-                ShirtColor::Blue => num_blue += 1,
-            }
-        }
-        if num_red > num_blue {
-            ShirtColor::Red
-        } else {
-            ShirtColor::Blue
-        }
-    }
-}
-
-fn main() {
-    let store = Inventory {
-        shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
-    };
-
-    let user_pref1 = Some(ShirtColor::Red);
-    let giveaway1 = store.giveaway(user_pref1);
-    println!(
-        "The user with preference {:?} gets {:?}",
-        user_pref1, giveaway1
-    );
-
-    let user_pref2 = None;
-    let giveaway2 = store.giveaway(user_pref2);
-    println!(
-        "The user with preference {:?} gets {:?}",
-        user_pref2, giveaway2
-    );
-}
-

Приложение 13-1: Случаей с раздачей рубашек предприятием

-

В магазине store, определённом в main, осталось две синие и одна красная рубашки для этой ограниченной акции. Мы вызываем способ giveaway для пользователя предпочитающего красную рубашку и для пользователя без каких-либо предпочтений.

-

Опять же, этот код мог быть выполнен множеством способов, но в данном случае, чтобы сосредоточиться на замыканиях, мы придерживались изученных ранее подходов, за исключением тела способа giveaway, в котором используется замыкание. В способе giveaway мы получаем пользовательское предпочтение цвета как свойство вида Option<ShirtColor> и вызываем способ unwrap_or_else на user_preference. Способ unwrap_or_else перечисления Option<T> определён встроенной библиотекой. Он принимает один переменная: замыкание без переменных, которое возвращает значение T (преобразуется в вид значения, которое окажется в исходе Some перечисления Option<T>, в нашем случае ShirtColor). Если Option<T> окажется исходом Some, unwrap_or_else вернёт значение из Some. А если Option<T> будет является исходом None, unwrap_or_else вызовет замыкание и вернёт значение, возвращённое замыканием.

-

В качестве переменной unwrap_or_else мы передаём замыкание || self.most_stocked(). Это замыкание, которое не принимает никаких свойств (если бы у замыкания были свойства, они были бы перечислены между двумя вертикальными полосами). В теле замыкания вызывается self.most_stocked(). Здесь мы определили замыкание, а выполнение unwrap_or_else такова, что выполнится оно позднее, когда потребуется получить итог.

-

Выполнение этого кода выводит:

-
$ cargo run
-   Compiling shirt-company v0.1.0 (file:///projects/shirt-company)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
-     Running `target/debug/shirt-company`
-The user with preference Some(Red) gets Red
-The user with preference None gets Blue
-
-

Важной особенностью здесь является то, что мы передали замыкание, которое вызывает self.most_stocked() текущего образца Inventory. Обычной библиотеке не нужно знать ничего о видах Inventory или ShirtColor, которые мы определили, или о логике, которую мы хотим использовать в этом сценарии. Замыкание определяет неизменяемую ссылку на self Inventory и передаёт её с указанным нами кодом в способ unwrap_or_else. А вот функции не могут определять своё окружение таким образом.

-

Выведение и изложение видов замыкания

-

Есть и другие различия между функциями и замыканиями. Замыкания обычно не требуют определенния видов входных свойств или возвращаемого значения, как это делается в функциях fn. Изложения видов требуются для функций, потому что виды являются частью явного внешней оболочки, предоставляемого пользователям. Жёсткое определение таких внешних оболочек важно для того, чтобы все были согласованы в том, какие виды значений использует и возвращает функция. А вот замыкания, напротив, не употребляются взначении подобных открытых внешних оболочек: они хранятся в переменных, используются не имея имени и незримо для пользователей нашей библиотеки.

-

Замыкания, как правило, небольшие и уместны в каком-то узконаправленном среде, а не в произвольных случаях. В этих ограниченных средах сборщик может вывести виды свойств и возвращаемого вида, подобно тому, как он может вывести виды большинства переменных (есть редкие случаи, когда сборщику также нужны изложении видов замыканий).

-

Как и в случае с переменными, мы можем добавить изложении видов, если хотим повысить ясность и чёткость описания ценой увеличения многословности, большей чем это необходимо. Определение видов для замыкания будет выглядеть как определение, показанное в приложении 13-2. В этом примере мы определяем замыкание и храним его в переменной, а не определяем замыкание в том месте, куда мы передаём его в качестве переменной, как это было в приложении 13-1.

-

Имя файла: src/main.rs

-
use std::thread;
-use std::time::Duration;
-
-fn generate_workout(intensity: u32, random_number: u32) {
-    let expensive_closure = |num: u32| -> u32 {
-        println!("calculating slowly...");
-        thread::sleep(Duration::from_secs(2));
-        num
-    };
-
-    if intensity < 25 {
-        println!("Today, do {} pushups!", expensive_closure(intensity));
-        println!("Next, do {} situps!", expensive_closure(intensity));
-    } else {
-        if random_number == 3 {
-            println!("Take a break today! Remember to stay hydrated!");
-        } else {
-            println!(
-                "Today, run for {} minutes!",
-                expensive_closure(intensity)
-            );
-        }
-    }
-}
-
-fn main() {
-    let simulated_user_specified_value = 10;
-    let simulated_random_number = 7;
-
-    generate_workout(simulated_user_specified_value, simulated_random_number);
-}
-

Приложение 13-2: Добавление необязательных наставлений видов свойств и возвращаемых значений в замыкании

-

С добавлением наставлений видов правила написания замыканий выглядит более похожим на правила написания функций. Здесь мы, для сравнения, определяем функцию, которая добавляет 1 к своему свойству, и замыкание, которое имеет такое же поведение. Мы добавили несколько пробелов, чтобы выровнять соответствующие части. Это показывает, что правила написания замыкания похож на правила написания функции, за исключением использования труб (вертикальная черта) и количества необязательного правил написания:

-
fn  add_one_v1   (x: u32) -> u32 { x + 1 }
-let add_one_v2 = |x: u32| -> u32 { x + 1 };
-let add_one_v3 = |x|             { x + 1 };
-let add_one_v4 = |x|               x + 1  ;
-

В первой строке показано определение функции, а во второй - полностью определенное определение замыкания. В третьей строке мы удаляем изложении видов из определения замыкания. В четвёртой строке мы убираем скобки, которые являются необязательными, поскольку тело замыкания содержит только одну действие. Это всё правильные определения, которые будут иметь одинаковое поведение при вызове. Строки add_one_v3 и add_one_v4 требуют, чтобы замыкания были вычислены до сборки, поскольку виды будут выведены из их использования. Это похоже на let v = Vec::new();, когда в Vec необходимо вставить либо изложении видов, либо значения некоторого вида, чтобы Ржавчина смог вывести вид.

-

Для определений замыкания сборщик выводит определенные виды для каждого из свойств и возвращаемого значения. Например, в приложении 13-3 показано определение короткого замыкания, которое просто возвращает значение, полученное в качестве свойства. Это замыкание не очень полезно, кроме как для целей данного примера. Обратите внимание, что мы не добавили в определение никаких наставлений видов. Поскольку наставлений видов нет, мы можем вызвать замыкание для любого вида, что мы и сделали в первый раз с String. Если затем мы попытаемся вызвать example_closure для целого числа, мы получим ошибку.

-

Имя файла: src/main.rs

-
fn main() {
-    let example_closure = |x| x;
-
-    let s = example_closure(String::from("hello"));
-    let n = example_closure(5);
-}
-

Приложение 13-3: Попытка вызова замыкания, виды которого выводятся из двух разных видов

-

Сборщик вернёт нам вот такую ошибку:

-
$ cargo run
-   Compiling closure-example v0.1.0 (file:///projects/closure-example)
-error[E0308]: mismatched types
- --> src/main.rs:5:29
-  |
-5 |     let n = example_closure(5);
-  |             --------------- ^- help: try using a conversion method: `.to_string()`
-  |             |               |
-  |             |               expected `String`, found integer
-  |             arguments to this function are incorrect
-  |
-note: expected because the closure was earlier called with an argument of type `String`
- --> src/main.rs:4:29
-  |
-4 |     let s = example_closure(String::from("hello"));
-  |             --------------- ^^^^^^^^^^^^^^^^^^^^^ expected because this argument is of type `String`
-  |             |
-  |             in this closure call
-note: closure parameter defined here
- --> src/main.rs:2:28
-  |
-2 |     let example_closure = |x| x;
-  |                            ^
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `closure-example` (bin "closure-example") due to 1 previous error
-
-

При первом вызове example_closure со значением String сборщик определяет вид x и возвращаемый вид замыкания как String. Эти виды затем определятся в замыкании в example_closure, и мы получаем ошибку вида при следующей попытке использовать другой вид с тем же замыканием.

-

Захват ссылок или передача владения

-

Замыкания могут захватывать значения из своего окружения тремя способами, которые соответствуют тем же трём способам, которыми функция может принимать свойства: заимствование неизменяемых, заимствование изменяемых и получение владения. Замыкание самостоятельно определяет, какой из этих способов использовать, исходя из того, что тело функции делает с полученными значениями.

-

В приложении 13-4 мы определяем замыкание, которое захватывает неизменяемую ссылку на вектор с именем list, поскольку неизменяемой ссылки достаточно для печати значения:

-

Имя файла: src/main.rs

-
fn main() {
-    let list = vec![1, 2, 3];
-    println!("Before defining closure: {list:?}");
-
-    let only_borrows = || println!("From closure: {list:?}");
-
-    println!("Before calling closure: {list:?}");
-    only_borrows();
-    println!("After calling closure: {list:?}");
-}
-

Приложение 13-4: Определение и вызов замыкания, которое захватывает неизменяемую ссылку

-

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

-

Поскольку мы можем иметь несколько неизменяемых ссылок на list одновременно, list остаётся доступным из кода до определения замыкания, после определения замыкания, а также до вызова замыкания и после. Этот код собирается, выполняется и печатает:

-
$ cargo run
-     Locking 1 package to latest compatible version
-      Adding closure-example v0.1.0 (/Users/carolnichols/rust/book/tmp/listings/ch13-functional-features/listing-13-04)
-   Compiling closure-example v0.1.0 (file:///projects/closure-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s
-     Running `target/debug/closure-example`
-Before defining closure: [1, 2, 3]
-Before calling closure: [1, 2, 3]
-From closure: [1, 2, 3]
-After calling closure: [1, 2, 3]
-
-

В следующем приложении 13-5 мы изменили тело замыкания так, чтобы оно добавляло элемент в вектор list. Теперь замыкание захватывает изменяемую ссылку:

-

Имя файла: src/main.rs

-
fn main() {
-    let mut list = vec![1, 2, 3];
-    println!("Before defining closure: {list:?}");
-
-    let mut borrows_mutably = || list.push(7);
-
-    borrows_mutably();
-    println!("After calling closure: {list:?}");
-}
-

Приложение 13-5. Определение и вызов замыкания, захватывающего изменяемую ссылку

-

Этот код собирается, запускается и печатает:

-
$ cargo run
-     Locking 1 package to latest compatible version
-      Adding closure-example v0.1.0 (/Users/carolnichols/rust/book/tmp/listings/ch13-functional-features/listing-13-05)
-   Compiling closure-example v0.1.0 (file:///projects/closure-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s
-     Running `target/debug/closure-example`
-Before defining closure: [1, 2, 3]
-After calling closure: [1, 2, 3, 7]
-
-

Обратите внимание, что между определением и вызовом замыкания borrows_mutably больше нет println!: когда определяется borrows_mutably, оно захватывает изменяемую ссылку на list. После вызова замыкания мы больше не используем его, поэтому изменяемое заимствование заканчивается. Между определением замыкания и вызовом замыкания неизменяемое заимствование для печати недоступно, потому что при наличии изменяемого заимствования никакие другие заимствования недопустимы. Попробуйте добавить туда println! и посмотрите, какое сообщение об ошибке вы получите!

-

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

-

Эта техника в основном полезна при передаче замыкания новому потоку, чтобы переместить данные так, чтобы они принадлежали новому потоку. Мы подробно обсудим потоки и то, зачем их использовать, в главе 16, когда будем говорить о одновременности, а пока давайте вкратце рассмотрим порождение нового потока с помощью замыкания, в котором используется ключевое слово move. В приложении 13-6 показан код из приложения 13-4, измененный для печати вектора в новом потоке, а не в основном потоке:

-

Файл: src/main.rs

-
use std::thread;
-
-fn main() {
-    let list = vec![1, 2, 3];
-    println!("Before defining closure: {list:?}");
-
-    thread::spawn(move || println!("From thread: {list:?}"))
-        .join()
-        .unwrap();
-}
-

Приложение 13-6: Использование move для принуждения замыкания потока принять на себя владение list

-

Мы порождаем новый поток, передавая ему в качестве переменной замыкание для выполнения. Тело замыкания распечатывает список. В приложении 13-4 замыкание захватило list только с помощью неизменяемой ссылки, потому что это наименьше необходимый доступ к list для его печати. В этом примере, несмотря на то, что тело замыкания по-прежнему требует только неизменяемой ссылки, нам нужно указать, что list должен быть перемещён в замыкание, поместив ключевое слово move в начало определения замыкания. Новый поток может завершиться раньше, чем завершится основной поток, или основной поток может завершиться первым. Если основной поток сохранил владение list, но завершился раньше нового потока и удалил list, то неизменяемая ссылка в потоке будет недействительной. Поэтому сборщик требует, чтобы list был перемещён в замыкание, переданное новому потоку, чтобы ссылка была действительной. Попробуйте убрать ключевое слово move или использовать list в основном потоке после определения замыкания и посмотрите, какие ошибки сборщика вы получите!

- -

- -

-

Перемещение захваченных значений из замыканий и особенности Fn

-

После того, как замыкание захватило ссылку или владение значением из среды, в которой оно определено (тем самым влияя на то, что перемещается в замыкание), код в теле замыкания определяет, что происходит со ссылками или значениями, в мгновение последующего выполнения замыкания (тем самым влияя на то, что перемещается из замыкания). Тело замыкания может делать любое из следующих действий: перемещать захваченное значение из замыкания, изменять захваченное значение, не перемещать и не изменять значение или вообще ничего не захватывать из среды.

-

То, как замыкание получает и обрабатывает значения из своего окружения, указывает на то, какие особенности выполняет замыкание, а с помощью особенностей функции и устройства могут определять, какие виды замыканий они могут использовать. Замыканиям самостоятельно присваивается выполнение одного, двух или всех трёх из нижеперечисленных особенностей Fn, аддитивным образом, в зависимости от того, как тело замыкания обрабатывает значения:

-
    -
  1. FnOnce применяется к замыканиям, которые могут быть вызваны один раз. Все замыкания выполняют по крайней мере этот особенность, потому что все замыкания могут быть вызваны. Замыкание, которое перемещает захваченные значения из своего тела, выполняет только FnOnce и ни один из других признаков Fn, потому что оно может быть вызвано только один раз.
  2. -
  3. FnMut применяется к замыканиям, которые не перемещают захваченные значения из своего тела, но могут изменять захваченные значения. Такие замыкания могут вызываться более одного раза.
  4. -
  5. Fn применяется к замыканиям, которые не перемещают захваченные значения из своего тела и не изменяют захваченные значения, а также к замыканиям, которые ничего не захватывают из своего окружения. Такие замыкания могут выполняться более одного раза и не меняют ничего в своём окружении, что важно в таких случаях, как одновременный вызов замыкания несколько раз.
  6. -
-

Давайте рассмотрим определение способа unwrap_or_else у Option<T>, который мы использовали в приложении 13-1:

-
impl<T> Option<T> {
-    pub fn unwrap_or_else<F>(self, f: F) -> T
-    where
-        F: FnOnce() -> T
-    {
-        match self {
-            Some(x) => x,
-            None => f(),
-        }
-    }
-}
-

Напомним, что T - это гибкий вид, отображающий вид значения в Some исходе Option. Этот вид T также является возвращаемым видом функции unwrap_or_else: например, код, вызывающий unwrap_or_else у Option<String>, получит String.

-

Далее, обратите внимание, что функция unwrap_or_else имеет дополнительный свойство гибкого вида F. Здесь F - это вид входного свойства f, который является замыканием, заданным нами при вызове unwrap_or_else.

-

Ограничением особенности, заданным для обобщённого вида F, является FnOnce() -> T, что означает, что F должен вызываться один раз, не принимать никаких переменных и возвращать T. Использование FnOnce в ограничении особенности говорит о том, что unwrap_or_else должен вызывать f не более одного раза. В теле unwrap_or_else мы видим, что если Option будет равен Some, то f не будет вызван. Если же значение Option будет равным None, то f будет вызван один раз. Поскольку все замыкания выполняют FnOnce, unwrap_or_else принимает самые разные виды замыканий и является настолько гибким, насколько это возможно.

-
-

Примечание: Функции также могут выполнить все три особенности Fn. Если то, что мы хотим сделать, не требует захвата значения из среды, мы можем передавать имя какой-либо функции, а не замыкания, когда нам нужно что-то, выполняющее один из особенностей Fn. Например, для значения Option<Vec<T>> мы можем вызвать unwrap_or_else(Vec::new), чтобы получить новый пустой вектор, если значение окажется None.

-
-

Теперь рассмотрим способ встроенной библиотеки sort_by_key, определённый у срезов, чтобы увидеть, чем он отличается от unwrap_or_else и почему sort_by_key использует FnMut вместо FnOnce для ограничения особенности. Замыкание принимает единственный переменная в виде ссылки на текущий элемент в рассматриваемом срезе и возвращает значение вида K, к которому применима сортировка. Эта функция полезна, когда вы хотите отсортировать срез по определённому свойству каждого элемента. В приложении 13-7 у нас есть список образцов Rectangle, и мы используем sort_by_key, чтобы упорядочить их по свойству width от меньшего к большему:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let mut list = [
-        Rectangle { width: 10, height: 1 },
-        Rectangle { width: 3, height: 5 },
-        Rectangle { width: 7, height: 12 },
-    ];
-
-    list.sort_by_key(|r| r.width);
-    println!("{list:#?}");
-}
-

Приложение 13-7: Использование sort_by_key для сортировки прямоугольников по ширине

-

Этот код печатает:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s
-     Running `target/debug/rectangles`
-[
-    Rectangle {
-        width: 3,
-        height: 5,
-    },
-    Rectangle {
-        width: 7,
-        height: 12,
-    },
-    Rectangle {
-        width: 10,
-        height: 1,
-    },
-]
-
-

Причина, по которой sort_by_key определена как принимающая замыкание FnMut, заключается в том, что она вызывает замыкание несколько раз: по одному разу для каждого элемента в срезе. Замыкание |r| r.width не захватывает, не изменяет и не перемещает ничего из своего окружения, поэтому оно удовлетворяет требованиям связанности признаков.

-

И наоборот, в приложении 13-8 показан пример замыкания, которое выполняет только признак FnOnce, потому что оно перемещает значение из среды. Сборщик не позволит нам использовать это замыкание с sort_by_key:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let mut list = [
-        Rectangle { width: 10, height: 1 },
-        Rectangle { width: 3, height: 5 },
-        Rectangle { width: 7, height: 12 },
-    ];
-
-    let mut sort_operations = vec![];
-    let value = String::from("closure called");
-
-    list.sort_by_key(|r| {
-        sort_operations.push(value);
-        r.width
-    });
-    println!("{list:#?}");
-}
-

Приложение 13-8: Попытка использовать замыкание FnOnce с sort_by_key

-

Это надуманный, замысловатый способ (который не работает) подсчёта количества вызовов sort_by_key при сортировке list. Этот код пытается выполнить подсчёт, перемещая value - String из окружения замыкания - в вектор sort_operations. Замыкание захватывает value, затем перемещает value из замыкания, передавая владение на value вектору sort_operations. Это замыкание можно вызвать один раз; попытка вызвать его второй раз не сработает, потому что value уже не будет находиться в той среде, из которой его можно будет снова поместить в sort_operations! Поэтому это замыкание выполняет только FnOnce. Когда мы попытаемся собрать этот код, мы получим ошибку сообщающую о том что value не может быть перемещено из замыкания, потому что замыкание должно выполнить FnMut:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure
-  --> src/main.rs:18:30
-   |
-15 |     let value = String::from("closure called");
-   |         ----- captured outer variable
-16 |
-17 |     list.sort_by_key(|r| {
-   |                      --- captured by this `FnMut` closure
-18 |         sort_operations.push(value);
-   |                              ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait
-   |
-help: consider cloning the value if the performance cost is acceptable
-   |
-18 |         sort_operations.push(value.clone());
-   |                                   ++++++++
-
-For more information about this error, try `rustc --explain E0507`.
-error: could not compile `rectangles` (bin "rectangles") due to 1 previous error
-
-

Ошибка указывает на строку в теле замыкания, которая перемещает value из окружения. Чтобы исправить это, нужно изменить тело замыкания так, чтобы оно не перемещало значения из окружения. Для подсчёта количества вызовов sort_by_key более простым способом является хранение счётчика в окружении и увеличение его значения в теле замыкания. Замыкание в приложении 13-9 работает с sort_by_key, поскольку оно определяет только изменяемую ссылку на счётчик num_sort_operations и поэтому может быть вызвано более одного раза:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let mut list = [
-        Rectangle { width: 10, height: 1 },
-        Rectangle { width: 3, height: 5 },
-        Rectangle { width: 7, height: 12 },
-    ];
-
-    let mut num_sort_operations = 0;
-    list.sort_by_key(|r| {
-        num_sort_operations += 1;
-        r.width
-    });
-    println!("{list:#?}, sorted in {num_sort_operations} operations");
-}
-

Приложение 13-9: Использование замыкания FnMut с sort_by_key разрешено

-

Особенности Fn важны при определении или использовании функций или видов, использующих замыкания. В следующем разделе мы обсудим повторители. Многие способы повторителей принимают переменные в виде замыканий, поэтому не забывайте об этих подробностях, пока мы продвигаемся дальше!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch13-02-iterators.html b/rustbook-ru/book/ch13-02-iterators.html deleted file mode 100644 index 3e56c212b..000000000 --- a/rustbook-ru/book/ch13-02-iterators.html +++ /dev/null @@ -1,407 +0,0 @@ - - - - - - Обработка последовательности элементов с помощью повторителей - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Обработка последовательности элементов с помощью повторителей

-

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

-

В Ржавчина повторители ленивые (lazy), то есть они не делают ничего, пока вы не вызовете особые способы, потребляющие повторитель , чтобы задействовать его. Например, код в приложении 13-10 создаёт повторитель элементов вектора v1, вызывая способ iter, определённый у Vec<T>. Сам по себе этот код не делает ничего полезного.

-
fn main() {
-    let v1 = vec![1, 2, 3];
-
-    let v1_iter = v1.iter();
-}
-

Приложение 13-10: Создание повторителя

-

Повторитель хранится в переменной v1_iter. Создав повторитель , мы можем использовать его различными способами. В приложении 3-5 главы 3 мы совершали обход элементов массива используя цикл for для выполнения какого-то кода над каждым из его элементов. Под капотом это неявно создавало, а затем потребляло повторитель , но до сих пор мы не касались того, как именно это работает.

-

В примере из приложения 13-11 мы отделили создание повторителя от его использования в цикле for. В цикле for, использующем повторитель в v1_iter, каждый элемент повторителя участвует только в одной повторения цикла, в ходе которой выводится на экран его значение.

-
fn main() {
-    let v1 = vec![1, 2, 3];
-
-    let v1_iter = v1.iter();
-
-    for val in v1_iter {
-        println!("Got: {val}");
-    }
-}
-

Приложение 13-11: Использование повторителя в цикле for

-

В языках, обычные библиотеки которых не предоставляют повторители, вы, скорее всего, напишите эту же возможность так: создадите переменную со значением 0 затем, в цикле, использовав её для получения элемента вектора по порядковому указателю, будете увеличивать её значение, и так, пока оно не достигнет числа равного количеству элементов в векторе.

-

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

-

Особенность Iterator и способ next

-

Все повторители выполняют особенность Iterator, который определён в встроенной библиотеке. Его определение выглядит так:

-
#![allow(unused)]
-fn main() {
-pub trait Iterator {
-    type Item;
-
-    fn next(&mut self) -> Option<Self::Item>;
-
-    // methods with default implementations elided
-}
-}
-

Обратите внимание данное объявление использует новый правила написания: type Item и Self::Item, которые определяют сопряженный вид (associated type) с этим особенностью. Мы подробнее поговорим о сопряженных видах в главе 19. Сейчас вам нужно знать, что этот код требует от выполнений особенности Iterator определить требуемый им вид Item и данный вид Item используется в способе next. Другими словами, вид Item будет являться видом элемента, который возвращает повторитель .

-

Особенность Iterator требует, чтобы разработчики определяли только один способ: способ next, который возвращает один элемент повторителя за раз обёрнутый в исход Some и когда повторение завершена, возвращает None.

-

Мы можем вызывать способ next у повторителей напрямую; в приложении 13-12 показано, какие значения возвращаются при повторных вызовах next у повторителя, созданного из вектора.

-

Файл: src/lib.rs

-
#[cfg(test)]
-mod tests {
-    #[test]
-    fn iterator_demonstration() {
-        let v1 = vec![1, 2, 3];
-
-        let mut v1_iter = v1.iter();
-
-        assert_eq!(v1_iter.next(), Some(&1));
-        assert_eq!(v1_iter.next(), Some(&2));
-        assert_eq!(v1_iter.next(), Some(&3));
-        assert_eq!(v1_iter.next(), None);
-    }
-}
-

Приложение 13-12: Вызов способа next повторителя

-

Обратите внимание, что нам нужно сделать переменную v1_iter изменяемой: вызов способа next повторителя изменяет внутреннее состояние повторителя, которое повторитель использует для отслеживания того, где он находится в последовательности. Другими словами, этот код потребляет (consume) или использует повторитель . Каждый вызов next потребляет элемент из повторителя. Нам не нужно было делать изменяемой v1_iter при использовании цикла for, потому что цикл забрал во владение v1_iter и сделал её изменяемой неявно для нас.

-

Заметьте также, что значения, которые мы получаем при вызовах next являются неизменяемыми ссылками на значения в векторе. Способ iter создаёт повторитель по неизменяемым ссылкам. Если мы хотим создать повторитель , который становится владельцем v1 и возвращает принадлежащие ему значения, мы можем вызвать into_iter вместо iter. Точно так же, если мы хотим перебирать изменяемые ссылки, мы можем вызвать iter_mut вместо iter.

-

Способы, которые потребляют повторитель

-

У особенности Iterator есть несколько способов, выполнение которых по умолчанию предоставляется встроенной библиотекой; вы можете узнать об этих способах, просмотрев документацию API встроенной библиотеки для Iterator. Некоторые из этих способов вызывают next в своём определении, поэтому вам необходимо выполнить способ next при выполнения особенности Iterator.

-

Способы, вызывающие next, называются потребляющими переходниками, поскольку их вызов потребляет повторитель . Примером может служить способ sum, который забирает во владение повторитель и перебирает элементы, многократно вызывая next, тем самым потребляя повторитель . В этапе повторения он добавляет каждый элемент к текущей сумме и возвращает итоговое значение по завершении повторения. В приложении 13-13 приведён проверка, отображающий использование способа sum:

-

Файл: src/lib.rs

-
#[cfg(test)]
-mod tests {
-    #[test]
-    fn iterator_sum() {
-        let v1 = vec![1, 2, 3];
-
-        let v1_iter = v1.iter();
-
-        let total: i32 = v1_iter.sum();
-
-        assert_eq!(total, 6);
-    }
-}
-

Приложение 13-13: Вызов способа sum для получения суммы всех элементов в повторителе

-

Мы не можем использовать v1_iter после вызова способа sum, потому что sum забирает во владение повторитель у которого вызван способ.

-

Способы, которые создают другие повторители

-

Переходники повторителей - это способы, определённые для особенности Iterator, которые не потребляют повторитель . Вместо этого они создают различные повторители, изменяя некоторые особенности исходного повторителя.

-

В приложении 13-14 показан пример вызова способа переходника повторителя map, который принимает замыкание и вызывает его для каждого элемента по мере повторения элементов. Способ map возвращает новый повторитель , который создаёт изменённые элементы. Замыкание здесь создаёт новый повторитель , в котором каждый элемент из вектора будет увеличен на 1:

-

Файл: src/main.rs

-
fn main() {
-    let v1: Vec<i32> = vec![1, 2, 3];
-
-    v1.iter().map(|x| x + 1);
-}
-

Приложение 13-14: Вызов переходника повторителя map для создания нового повторителя

-

Однако этот код выдаёт предупреждение:

-
$ cargo run
-   Compiling iterators v0.1.0 (file:///projects/iterators)
-warning: unused `Map` that must be used
- --> src/main.rs:4:5
-  |
-4 |     v1.iter().map(|x| x + 1);
-  |     ^^^^^^^^^^^^^^^^^^^^^^^^
-  |
-  = note: iterators are lazy and do nothing unless consumed
-  = note: `#[warn(unused_must_use)]` on by default
-help: use `let _ = ...` to ignore the resulting value
-  |
-4 |     let _ = v1.iter().map(|x| x + 1);
-  |     +++++++
-
-warning: `iterators` (bin "iterators") generated 1 warning
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s
-     Running `target/debug/iterators`
-
-

Код в приложении 13-14 ничего не делает; указанное нами замыкание никогда не вызывается. Предупреждение напоминает нам, почему: переходники повторителей ленивы, и здесь нам нужно потребить повторитель .

-

Чтобы устранить это предупреждение и потребить повторитель , мы воспользуемся способом collect, который мы использовали в главе 12 с env::args в приложении 12-1. Этот способ потребляет повторитель и собирает полученные значения в собрание указанного вида.

-

В приложении 13-15 мы собираем в вектор итоги перебора повторителя, который возвращается в итоге вызова map. Этот вектор в итоге будет содержать каждый элемент исходного вектора, увеличенный на 1.

-

Файл: src/main.rs

-
fn main() {
-    let v1: Vec<i32> = vec![1, 2, 3];
-
-    let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
-
-    assert_eq!(v2, vec![2, 3, 4]);
-}
-

Приложение 13-15: Вызов способа map для создания нового повторителя, а затем вызов способа collect для потребления нового повторителя и создания вектора

-

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

-

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

-

Использование замыканий, которые захватывают переменные окружения

-

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

-

В этом примере мы будем использовать способ filter, который принимает замыкание. Замыкание получает элемент из повторителя и возвращает bool. Если замыкание возвращает true, значение будет включено в повторение, создаваемую filter. Если замыкание возвращает false, значение не будет включено.

-

В приложении 13-16 мы используем filter с замыканием, которое захватывает переменную shoe_size из своего окружения для повторения по собрания образцов устройства Shoe. Он будет возвращать обувь только указанного размера.

-

Файл: src/lib.rs

-
#[derive(PartialEq, Debug)]
-struct Shoe {
-    size: u32,
-    style: String,
-}
-
-fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
-    shoes.into_iter().filter(|s| s.size == shoe_size).collect()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn filters_by_size() {
-        let shoes = vec![
-            Shoe {
-                size: 10,
-                style: String::from("sneaker"),
-            },
-            Shoe {
-                size: 13,
-                style: String::from("sandal"),
-            },
-            Shoe {
-                size: 10,
-                style: String::from("boot"),
-            },
-        ];
-
-        let in_my_size = shoes_in_size(shoes, 10);
-
-        assert_eq!(
-            in_my_size,
-            vec![
-                Shoe {
-                    size: 10,
-                    style: String::from("sneaker")
-                },
-                Shoe {
-                    size: 10,
-                    style: String::from("boot")
-                },
-            ]
-        );
-    }
-}
-

Приложение 13-16. Использование способа filter с замыканием, определяющим shoe_size

-

Функция shoes_in_size принимает в качестве свойств вектор с образцами обуви и размер обуви, а возвращает вектор, содержащий только обувь указанного размера.

-

В теле shoes_in_my_size мы вызываем into_iter чтобы создать повторитель , который становится владельцем вектора. Затем мы вызываем filter, чтобы превратить этот повторитель в другой, который содержит только элементы, для которых замыкание возвращает true.

-

Замыкание захватывает свойство shoe_size из окружения и сравнивает его с размером каждой пары обуви, оставляя только обувь указанного размера. Наконец, вызов collect собирает значения, возвращаемые приспособленным повторителем, в вектор, возвращаемый функцией.

-

Проверка показывает, что когда мы вызываем shoes_in_my_size, мы возвращаем только туфли, размер которых совпадает с указанным нами значением.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch13-03-improving-our-io-project.html b/rustbook-ru/book/ch13-03-improving-our-io-project.html deleted file mode 100644 index 0d7e05fa5..000000000 --- a/rustbook-ru/book/ch13-03-improving-our-io-project.html +++ /dev/null @@ -1,789 +0,0 @@ - - - - - - Улучшение нашего дела с вводом/выводом - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Улучшение нашего дела с вводом/выводом

-

Вооружившись полученными знаниями об повторителях, мы можем улучшить выполнение работы с вводом/выводом в деле главы 12, применяя повторители для того, чтобы сделать некоторые места в коде более понятными и краткими. Давайте рассмотрим, как повторители могут улучшить нашу выполнение функции Config::build и функции search.

-

Удаляем clone, используем повторитель

-

В приложении 12-6 мы добавили код, который принимает срез значений String и создаёт образец устройства Config путём упорядочевания среза и клонирования значений, позволяя устройстве Config владеть этими значениями. В приложении 13-17 мы воспроизвели выполнение функции Config::build, как это было в приложении 12-23:

-

Файл: src/lib.rs

-
use std::env;
-use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        let ignore_case = env::var("IGNORE_CASE").is_ok();
-
-        Ok(Config {
-            query,
-            file_path,
-            ignore_case,
-        })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 13-17: Репродукция функции Config::build из приложения 12-23

-

Ранее мы говорили, что не стоит беспокоиться о неэффективных вызовах clone, потому что мы удалим их в будущем. Ну что же, время пришло!

-

Нам понадобился здесь clone, потому что в свойстве args у нас срез с элементами String, но функция build не владеет args. Чтобы образец Config владел значениями, нам пришлось клонировать их из args в переменные query и file_path.

-

Благодаря нашим новым знаниям об повторителях мы можем изменить функцию build, чтобы вместо заимствования среза она принимала в качестве переменной повторитель . Мы будем использовать возможность повторителя вместо кода, который проверяет длину среза и обращается по порядковому указателю к определённым значениям. Это позволит лучше понять, что делает функция Config::build, поскольку повторитель будет обращаться к значениям.

-

Как только Config::build получит в своё распоряжение повторитель и перестанет использовать действия упорядочевания с заимствованием, мы сможем переместить значения String из повторителя в Config вместо того, чтобы вызывать clone и создавать новое выделение памяти.

-

Использование возвращённого повторителя напрямую

-

Откройте файл src/main.rs дела ввода-вывода, который должен выглядеть следующим образом:

-

Файл: src/main.rs

-
use std::env;
-use std::process;
-
-use minigrep::Config;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        eprintln!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    // --snip--
-
-    if let Err(e) = minigrep::run(config) {
-        eprintln!("Application error: {e}");
-        process::exit(1);
-    }
-}
-

Сначала мы изменим начало функции main, которая была в приложении 12-24, на код в приложении 13-18, который теперь использует повторитель . Это не будет собираться, пока мы не обновим Config::build.

-

Файл: src/main.rs

-
use std::env;
-use std::process;
-
-use minigrep::Config;
-
-fn main() {
-    let config = Config::build(env::args()).unwrap_or_else(|err| {
-        eprintln!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    // --snip--
-
-    if let Err(e) = minigrep::run(config) {
-        eprintln!("Application error: {e}");
-        process::exit(1);
-    }
-}
-

Приложение 13-18: Передача возвращаемого значения из env::args в Config::build

-

Функция env::args возвращает повторитель ! Вместо того чтобы собирать значения повторителя в вектор и затем передавать срез в Config::build, теперь мы передаём владение повторителем, возвращённым из env::args в Config::build напрямую.

-

Далее нам нужно обновить определение Config::build. В файле src/lib.rs вашего дела ввода-вывода изменим ярлык Config::build так, чтобы она выглядела как в приложении 13-19. Это все ещё не собирается, потому что нам нужно обновить тело функции.

-

Файл: src/lib.rs

-
use std::env;
-use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(
-        mut args: impl Iterator<Item = String>,
-    ) -> Result<Config, &'static str> {
-        // --snip--
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        let ignore_case = env::var("IGNORE_CASE").is_ok();
-
-        Ok(Config {
-            query,
-            file_path,
-            ignore_case,
-        })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 13-19: Обновление ярлыки Config::build для определения повторителя как ожидаемого свойства

-

Документация встроенной библиотеки для функции env::args показывает, что вид возвращаемого ею повторителя - std::env::Args, и этот вид выполняет признак Iterator и возвращает значения String.

-

Мы обновили ярлык функции Config::build, чтобы свойство args имел гибкий вид ограниченный особенностью impl Iterator<Item = String> вместо &[String]. Такое использование правил написания impl Trait, который мы обсуждали в разделе " Особенности как свойства" главы 10, означает, что args может быть любым видом, выполняющим вид Iterator и возвращающим элементы String.

-

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

-

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

-

Далее мы подправим содержимое Config::build. Поскольку args выполняет признак Iterator, мы знаем, что можем вызвать у него способ next! В приложении 13-20 код из приложения 12-23 обновлён для использования способа next:

-

Файл: src/lib.rs

-
use std::env;
-use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(
-        mut args: impl Iterator<Item = String>,
-    ) -> Result<Config, &'static str> {
-        args.next();
-
-        let query = match args.next() {
-            Some(arg) => arg,
-            None => return Err("Didn't get a query string"),
-        };
-
-        let file_path = match args.next() {
-            Some(arg) => arg,
-            None => return Err("Didn't get a file path"),
-        };
-
-        let ignore_case = env::var("IGNORE_CASE").is_ok();
-
-        Ok(Config {
-            query,
-            file_path,
-            ignore_case,
-        })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 13-20: Изменяем тело Config::build так, чтобы использовать способы повторителя

-

Помните, что первое значение в возвращаемых данных env::args - это имя программы. Мы хотим пренебрегать его и перейти к следующему значению, поэтому сперва мы вызываем next и ничего не делаем с возвращаемым значением. Затем мы вызываем next, чтобы получить значение, которое мы хотим поместить в поле query в Config. Если next возвращает Some, мы используем match для извлечения значения. Если возвращается None, это означает, что было задано недостаточно переменных, и мы досрочно возвращаем значение Err. То же самое мы делаем для значения file_path.

-

Делаем код понятнее с помощью переходников повторителей

-

Мы также можем воспользоваться преимуществами повторителей в функции search в нашем деле с действиеми ввода-вывода, которая воспроизведена здесь в приложении 13-21 так же, как и в приложении 12-19:

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 13-21: Выполнение функции search из приложения 12-19

-

Мы можем написать этот код в более сжатом виде, используя способы переходника повторителя. Это также позволит нам избежать наличия изменяемого временного вектора results. Функциональный исполнение программирования предпочитает уменьшить количество изменяемого состояния, чтобы сделать код более понятным. Удаление изменяемого состояния может позволить в будущем сделать поиск одновременным, поскольку нам не придётся управлять одновременным доступом к вектору results. В приложении 13-22 показано это изменение:

-

Файл: src/lib.rs

-
use std::env;
-use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(
-        mut args: impl Iterator<Item = String>,
-    ) -> Result<Config, &'static str> {
-        args.next();
-
-        let query = match args.next() {
-            Some(arg) => arg,
-            None => return Err("Didn't get a query string"),
-        };
-
-        let file_path = match args.next() {
-            Some(arg) => arg,
-            None => return Err("Didn't get a file path"),
-        };
-
-        let ignore_case = env::var("IGNORE_CASE").is_ok();
-
-        Ok(Config {
-            query,
-            file_path,
-            ignore_case,
-        })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    contents
-        .lines()
-        .filter(|line| line.contains(query))
-        .collect()
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 13-22: Использование способов переходника повторителя в выполнения функции search

-

Напомним, что назначение функции search - вернуть все строки в contents, которые содержат query. Подобно примеру filter в приложении 13-16, этот код использует переходник filter, чтобы сохранить только те строки, для которых line.contains(query) возвращает true. Затем мы собираем совпадающие строки в другой вектор с помощью collect. Так гораздо проще! Не стесняйтесь сделать такое же изменение для использования способов повторителя в функции search_case_insensitive.

-

Выбор между циклами или повторителями

-

Следующий логичный вопрос - какой исполнение вы должны выбрать в своём коде и почему: подлинную выполнение в приложении 13-21 или исполнение с использованием повторителей в приложении 13-22. Большинство программистов на языке Ржавчина предпочитают использовать исполнение повторителей. Сначала разобраться с ним немного сложно, но как только вы почувствуете, что такое различные переходники повторителей и что они делают, понять повторители станет проще. Вместо того чтобы возиться с различными элементами цикла и создавать новые векторы, код сосредотачивается на высокоуровневой цели цикла. Это абстрагирует часть обычного кода, поэтому легче увидеть подходы, единственные для этого кода, такие как условие выборки, которое должен пройти каждый элемент в повторителе.

-

Но действительно ли эти две выполнения эквивалентны? Интуитивно можно предположить, что более низкоуровневый цикл будет быстрее. Давайте поговорим о производительности.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch13-04-performance.html b/rustbook-ru/book/ch13-04-performance.html deleted file mode 100644 index b7b3b71f7..000000000 --- a/rustbook-ru/book/ch13-04-performance.html +++ /dev/null @@ -1,260 +0,0 @@ - - - - - - Сравнение производительности: циклы и повторители - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Сравнение производительности циклов и повторителей

-

Чтобы определить, что лучше использовать циклы или повторители, нужно знать, какая выполнение быстрее: исполнение функции search с явным циклом for или исполнение с повторителями.

-

Мы выполнили проверка производительности, разместив всё содержимое книги (“The Adventures of Sherlock Holmes” by Sir Arthur Conan Doyle) в строку вида String и поискали слово the в её содержимом. Вот итоги проверки функции search с использованием цикла for и с использованием повторителей:

-
test bench_search_for  ... bench:  19,620,300 ns/iter (+/- 915,700)
-test bench_search_iter ... bench:  19,234,900 ns/iter (+/- 657,200)
-
-

Исполнение с использованием повторителей была немного быстрее! Мы не будем приводить здесь непосредственно код проверки, поскольку мысль не в том, чтобы доказать, что решения в точности эквивалентны, а в том, чтобы получить общее представление о том, как эти две выполнения близки по производительности.

-

Для более исчерпывающего проверки, вам нужно проверить различные тексты разных размеров в качестве содержимого для contents, разные слова и слова различной длины в качестве query и всевозможные другие исходы. Дело в том, что повторители, будучи высокоуровневой абстракцией, собираются примерно в тот же код, как если бы вы написали его низкоуровневый исход самостоятельно. Повторители - это одна из абстракций с нулевой стоимостью ( zero-cost abstractions ) в Rust, под которой мы подразумеваем, что использование абстракции не накладывает дополнительных расходов во время выполнения. Подобно тому, как Бьёрн Страуструп, внешнем видер и разработчик C++, определяет нулевые накладные расходы ( zero-overhead ) в книге “Foundations of C++” (2012):

-
-

В целом, выполнение C++ подчиняется принципу отсутствия накладных расходов: за то, чем вы не пользуетесь, платить не нужно. И далее: тот код, что вы используете, нельзя сделать ещё лучше.

-
-

В качестве другого примера приведём код, взятый из аудио декодера. Алгоритм декодирования использует математическую действие линейного предсказания для оценки будущих значений на основе линейной функции предыдущих выборок. Код использует соединение вызовов повторителя для выполнения математических вычислений для трёх переменных в области видимости: срез данных buffer, массив из 12 коэффициентов coefficients и число для сдвига данных в переменной qlp_shift. Переменные определены в примере, но не имеют начальных значений. Хотя этот код не имеет большого значения вне среды, он является кратким, существующим примером того, как Ржавчина переводит мысли высокого уровня в код низкого уровня.

-
let buffer: &mut [i32];
-let coefficients: [i64; 12];
-let qlp_shift: i16;
-
-for i in 12..buffer.len() {
-    let prediction = coefficients.iter()
-                                 .zip(&buffer[i - 12..i])
-                                 .map(|(&c, &s)| c * s as i64)
-                                 .sum::<i64>() >> qlp_shift;
-    let delta = buffer[i];
-    buffer[i] = prediction as i32 + delta;
-}
-

Чтобы вычислить значение переменной prediction, этот код перебирает каждое из 12 значений в переменной coefficients и использует способ zip для объединения значений коэффициентов с предыдущими 12 значениями в переменной buffer. Затем, для каждой пары мы перемножаем значения, суммируем все итоги и у суммы сдвигаем биты вправо в переменную qlp_shift.

-

Для вычислений в таких приложениях, как аудио декодеры, часто требуется производительность. Здесь мы создаём повторитель , используя два переходника, впоследствии потребляющих значение. В какой ассемблерный код будет собираться этот код на Rust? На мгновение написания этой главы он собирается в то же самое, что вы написали бы руками. Не существует цикла, соответствующего повторения по значениям в «коэффициентах»coefficients: Ржавчина знает, что существует двенадцать повторений, поэтому он «разворачивает» цикл. Разворачивание - это улучшение, которая устраняет издержки кода управления циклом и вместо этого порождает повторяющийся код для каждой повторения цикла.

-

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

-

Итоги

-

Замыкания (closures) и повторители (iterators) это возможности Rust, вдохновлённые мыслями полезных языков. Они позволяют Ржавчина ясно выражать мысли высокого уровня с производительностью низкоуровневого кода. Выполнения замыканий и повторителей таковы, что нет влияния на производительность выполнения кода. Это одна из целей Rust, направленных на обеспечение абстракций с нулевой стоимостью (zero-cost abstractions).

-

Теперь, когда мы улучшили представление кода в нашем деле, рассмотрим некоторые возможности, которые нам предоставляет cargo для обнародования нашего кода в хранилища.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch14-00-more-about-cargo.html b/rustbook-ru/book/ch14-00-more-about-cargo.html deleted file mode 100644 index a1087f023..000000000 --- a/rustbook-ru/book/ch14-00-more-about-cargo.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - Подробнее о Cargo и Crates.io - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Больше о Cargo и Crates.io

-

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

-
    -
  • Настройка сборки с помощью готовых профилей
  • -
  • Обнародование библиотеки на crates.io
  • -
  • Управление крупными делами с помощью рабочих пространств
  • -
  • Установка двоичных файлов с crates.io
  • -
  • Расширение возможностей Cargo с помощью возможности добавления собственных приказов
  • -
-

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

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch14-01-release-profiles.html b/rustbook-ru/book/ch14-01-release-profiles.html deleted file mode 100644 index 40a411446..000000000 --- a/rustbook-ru/book/ch14-01-release-profiles.html +++ /dev/null @@ -1,262 +0,0 @@ - - - - - - Настройка билдов с помощью профилей выпуска - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Настройка сборок с профилями исполнений

-

В Ржавчина профили выпуска — это предопределённые и настраиваемые профили с различными настройками, которые позволяют программисту лучше управлять различные свойства сборки кода. Каждый профиль настраивается независимо от других.

-

Cargo имеет два основных профиля: профиль dev, используемый Cargo при запуске cargo build, и профиль release, используемый Cargo при запуске cargo build --release. Профиль dev определён со значениями по умолчанию для разработки, а профиль release имеет значения по умолчанию для сборок в исполнение.

-

Эти имена профилей могут быть знакомы по итогам ваших сборок:

- -
$ cargo build
-    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
-$ cargo build --release
-    Finished release [optimized] target(s) in 0.0s
-
-

dev и release — это разные профили, используемые сборщиком.

-

Cargo содержит настройки по умолчанию для каждого профиля, которые применяются, если вы явно не указали разделы [profile.*] в файле дела Cargo.toml. Добавляя разделы [profile.*] для любого профиля, который вы хотите настроить, вы переопределяете любое подмножество свойств по умолчанию. Например, вот значения по умолчанию для свойства opt-level для профилей dev и release:

-

Файл: Cargo.toml

-
[profile.dev]
-opt-level = 0
-
-[profile.release]
-opt-level = 3
-
-

Свойство opt-level управляет количеством переработок, которые Ржавчина будет применять к вашему коду, в ряде от 0 до 3. Использование большего количества переработок увеличивает время сборки, поэтому если вы находитесь в этапе разработки и часто собираете свой код, целесообразно использовать меньшее количество переработок, чтобы сборка происходила быстрее, даже если в итоге код будет работать медленнее. Поэтому opt-level по умолчанию для dev установлен в 0. Когда вы готовы обнародовать свой код, то лучше потратить больше времени на сборку. Вы собираете программу в режиме исполнения только один раз, но выполняться она будет многократно, так что использование режима исполнения позволяет увеличить скорость выполнения кода за счёт времени сборки. Вот почему по умолчанию opt-level для профиля release равен 3.

-

Вы можете переопределить настройки по умолчанию, добавив другое значение для них в Cargo.toml. Например, если мы хотим использовать уровень переработки 1 в профиле разработки, мы можем добавить эти две строки в файл Cargo.toml нашего дела:

-

Файл: Cargo.toml

-
[profile.dev]
-opt-level = 1
-
-

Этот код переопределяет настройку по умолчанию 0. Теперь, когда мы запустим cargo build, Cargo будет использовать значения по умолчанию для профиля dev плюс нашу настройку для opt-level. Поскольку мы установили для opt-level значение 1, Cargo будет применять больше переработок, чем было задано по умолчанию, но не так много, как при сборке исполнения.

-

Полный список свойств настройке и значений по умолчанию для каждого профиля вы можете найти в документации Cargo.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch14-02-publishing-to-crates-io.html b/rustbook-ru/book/ch14-02-publishing-to-crates-io.html deleted file mode 100644 index 5d532c198..000000000 --- a/rustbook-ru/book/ch14-02-publishing-to-crates-io.html +++ /dev/null @@ -1,505 +0,0 @@ - - - - - - Обнародование ящика на Crates.io - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Обнародование библиотеки в Crates.io

-

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

-

В Ржавчина и Cargo есть функции, которые облегчают поиск и использование обнародованного дополнения. Далее мы поговорим о некоторых из этих функций, а затем объясним, как обнародовать дополнение.

-

Создание полезных примечаниев к документации

-

Правильноное документирование ваших дополнений поможет другим пользователям знать, как и когда их использовать, поэтому стоит потратить время на написание документации. В главе 3 мы обсуждали, как вносить примечания в код Rust, используя две косые черты, //. В Ржавчина также есть особый вид примечаниев к документации, который обычно называется примечанием к документации, который порождает документацию HTML. HTML-код отображает содержимое примечаниев к документации для открытых элементов API, предназначенных для программистов, увлеченных в знании того, как использовать вашу библиотеку, в отличие от того, как она выполнена.

-

Примечания к документации используют три слеша, /// вместо двух и поддерживают наставление Markdown для изменения текста. Размещайте примечания к документации непосредственно перед элементом, который они документируют. В приложении 14-1 показаны примечания к документации для функции add_one в библиотеке с именем my_crate:

-

Файл: src/lib.rs

-
/// Adds one to the number given.
-///
-/// # Examples
-///
-/// ```
-/// let arg = 5;
-/// let answer = my_crate::add_one(arg);
-///
-/// assert_eq!(6, answer);
-/// ```
-pub fn add_one(x: i32) -> i32 {
-    x + 1
-}
-

Приложение 14-1: Примечание к документации для функции

-

Здесь мы даём описание того, что делает функция add_one, начинаем раздел с заголовка Examples, а затем предоставляем код, который отображает, как использовать функцию add_one. Мы можем создать документацию HTML из этого примечания к документации, запустив cargo doc. Этот приказ запускает средство rustdoc, поставляемый с Rust, и помещает созданную HTML-документацию в папка target/doc.

-

Для удобства, запустив cargo doc --open, мы создадим HTML для документации вашей текущей библиотеки (а также документацию для всех зависимостей вашей библиотеки) и откроем итог в веб-браузере. Перейдите к функции add_one и вы увидите, как отображается текст в примечаниях к документации, что показано на рисунке 14-1:

- HTML-документация для функции `add_one`` my_crate` -

Рисунок 14-1: HTML документация для функции add_one

-

Часто используемые разделы

-

Мы использовали Markdown заголовок # Examples в приложении 14-1 для создания раздела в HTML с заголовком "Examples". Вот некоторые другие разделы, которые авторы библиотек обычно используют в своей документации:

-
    -
  • Panics: Сценарии, в которых документированная функция может вызывать панику. Вызывающие функцию, которые не хотят, чтобы их программы паниковали, должны убедиться, что они не вызывают функцию в этих случаейх.
  • -
  • Ошибки: Если функция возвращает Result, описание видов ошибок, которые могут произойти и какие условия могут привести к тому, что эти ошибки могут быть возвращены, может быть полезным для вызывающих, так что они могут написать код для обработки различных видов ошибок разными способами.
  • -
  • Безопасность: Если функция является unsafe для вызова (мы обсуждаем безопасность в главе 19), должен быть раздел, объясняющий, почему функция небезопасна и охватывающий неизменные величины, которые функция ожидает от вызывающих сторон.
  • -
-

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

-

Примечания к документации как проверки

-

Добавление примеров кода в примечания к документации может помочь отобразить, как использовать вашу библиотеку, и это даёт дополнительный бонус: запуск cargo test запустит примеры кода в вашей документации как проверки! Нет ничего лучше, чем документация с примерами. Но нет ничего хуже, чем примеры, которые не работают, потому что код изменился с особенности написания документации. Если мы запустим cargo test с документацией для функции add_one из приложения 14-1, мы увидим раздел итогов проверки, подобный этому:

- -
   Doc-tests my_crate
-
-running 1 test
-test src/lib.rs - add_one (line 5) ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s
-
-

Теперь, если мы изменим либо функцию, либо пример, так что assert_eq! в примере паникует, и снова запустим cargo test, мы увидим, что проверки документации обнаруживают, что пример и код не согласованы друг с другом!

-

Указание примечаний содержащихся элементов

-

Исполнение примечаниев к документам //! добавляет документацию к элементу, содержащему примечания, а не к элементам, следующим за примечаниями. Обычно мы используем эти примечания внутри корневого файла ящика (по соглашению src/lib.rs ) или внутри звена для документирования ящика или звена в целом.

-

Например, чтобы добавить документацию, описывающую назначение my_crate , содержащего функцию add_one , мы добавляем примечания к документации, начинающиеся с //! в начало файла src/lib.rs , как показано в приложении 14-2:

-

Файл: src/lib.rs

-
//! # My Crate
-//!
-//! `my_crate` is a collection of utilities to make performing certain
-//! calculations more convenient.
-
-/// Adds one to the number given.
-// --snip--
-///
-/// # Examples
-///
-/// ```
-/// let arg = 5;
-/// let answer = my_crate::add_one(arg);
-///
-/// assert_eq!(6, answer);
-/// ```
-pub fn add_one(x: i32) -> i32 {
-    x + 1
-}
-

Приложение 14-2: Документация для ящика my_crate в целом

-

Обратите внимание, что после последней строки, начинающейся с //!, нет никакого кода. Поскольку мы начали примечания с //! вместо ///, мы документируем элемент, который содержит этот примечание, а не элемент, который следует за этим примечанием. В данном случае таким элементом является файл src/lib.rs, который является корнем crate. Эти примечания описывают весь ящик.

-

Когда мы запускаем cargo doc --open, эти примечания будут отображаться на первой странице документации для my_crate над списком открытых элементов в библиотеке, как показано на рисунке 14-2:

- Документация для библиотеки `art`, в которой перечислены звенья `types` и `utils` -

Рисунок 14-2: Предоставленная документация для my_crate, включая примечание, описывающие ящик в целом

-

Примечания к документации внутри элементов полезны для описания ящиков и звеньев особенно. Используйте их, чтобы объяснить общую цель дополнения, чтобы помочь вашим пользователям понять устройство ящика.

-

Экспорт удобного общедоступного API с pub use

-

Устройства вашего открытого API является основным обстоятельством при обнародования ящика. Люди, которые используют вашу библиотеку, менее знакомы со устройством, чем вы и могут столкнуться с трудностями при поиске частей, которые они хотят использовать, если ваша библиотека имеет большую упорядочевание звеньев.

-

В главе 7 мы рассмотрели, как сделать элементы общедоступными с помощью ключевого слова pub и ввести элементы в область видимости с помощью ключевого слова use. Однако устройства, которая имеет смысл для вас при разработке ящика, может быть не очень удобной для пользователей. Вы можете согласовать устройство в виде упорядочевания с несколькими уровнями, но тогда люди, желающие использовать вид, который вы определили в глубине упорядочевания, могут столкнуться с неполадкой его поиска. Их также может раздражать необходимость вводить use my_crate::some_module::another_module::UsefulType; вместо use my_crate::UsefulType;.

-

Хорошей новостью является то, что если устройства не удобна для использования другими из другой библиотеки, вам не нужно перестраивать внутреннюю устройство: вместо этого вы можете реэкспортировать элементы, чтобы сделать открытую устройство, отличную от вашей внутренней устройства, используя pub use. Реэкспорт берет открытый элемент в одном месте и делает его открытым в другом месте, как если бы он был определён в другом месте.

-

Например, скажем, мы создали библиотеку с именем art для расчетов художественных подходов. Внутри этой библиотеки есть два звена: звено kinds содержащий два перечисления с именами PrimaryColor и SecondaryColor и звено utils, содержащий функцию с именем mix, как показано в приложении 14-3:

-

Файл: src/lib.rs

-
//! # Art
-//!
-//! A library for modeling artistic concepts.
-
-pub mod kinds {
-    /// The primary colors according to the RYB color model.
-    pub enum PrimaryColor {
-        Red,
-        Yellow,
-        Blue,
-    }
-
-    /// The secondary colors according to the RYB color model.
-    pub enum SecondaryColor {
-        Orange,
-        Green,
-        Purple,
-    }
-}
-
-pub mod utils {
-    use crate::kinds::*;
-
-    /// Combines two primary colors in equal amounts to create
-    /// a secondary color.
-    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
-        // --snip--
-        unimplemented!();
-    }
-}
-

Приложение 14-3: Библиотека art с элементами, согласованными в звенья kinds и utils

-

На рисунке 14-3 показано, как будет выглядеть титульная страница документации для этого ящика, созданный cargo doc:

- Предоставлена Документация для библиотеки `art` с реэкспортом на первой странице -

Рисунок 14-3: Первая страница документации для art, в которой перечислены звенья kinds и utils

-

Обратите внимание, что виды PrimaryColor и SecondaryColor не указаны на главной странице, равно как и функция mix. Мы должны нажать kinds и utils, чтобы увидеть их.

-

В другой библиотеке, которая зависит от этой библиотеки, потребуются операторы use, которые подключают элементы из art в область видимости, определяя устройство звена, которая определена в данный мгновение. В приложении 14-4 показан пример ящика, в котором используются элементы PrimaryColor и mix из ящика art:

-

Файл: src/main.rs

-
use art::kinds::PrimaryColor;
-use art::utils::mix;
-
-fn main() {
-    let red = PrimaryColor::Red;
-    let yellow = PrimaryColor::Yellow;
-    mix(red, yellow);
-}
-

Приложение 14-4: Ящик использующий элементы из ящика art с экспортированной внутренней устройством

-

Автору кода в приложении 14-4, который использует ящик art, пришлось выяснить, что PrimaryColor находится в звене kinds, а mix - в звене utils. Устройства звена art ящика больше подходит для разработчиков, работающих над art ящиком, чем для тех, кто его использует. Внутренняя устройства не содержит никакой полезной сведений для того, кто пытается понять, как использовать ящик art, а скорее вызывает путаницу, поскольку разработчики, использующие его, должны понять, где искать, и должны указывать имена звеньев в выражениях use.

-

Чтобы удалить внутреннюю устройство из общедоступного API, мы можем изменить код ящика art в приложении 14-3, чтобы добавить операторы pub use для повторного реэкспорта элементов на верхнем уровне, как показано в приложении 14-5:

-

Файл: src/lib.rs

-
//! # Art
-//!
-//! A library for modeling artistic concepts.
-
-pub use self::kinds::PrimaryColor;
-pub use self::kinds::SecondaryColor;
-pub use self::utils::mix;
-
-pub mod kinds {
-    // --snip--
-    /// The primary colors according to the RYB color model.
-    pub enum PrimaryColor {
-        Red,
-        Yellow,
-        Blue,
-    }
-
-    /// The secondary colors according to the RYB color model.
-    pub enum SecondaryColor {
-        Orange,
-        Green,
-        Purple,
-    }
-}
-
-pub mod utils {
-    // --snip--
-    use crate::kinds::*;
-
-    /// Combines two primary colors in equal amounts to create
-    /// a secondary color.
-    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
-        SecondaryColor::Orange
-    }
-}
-

Приложение 14-5: Добавление операторов pub use для реэкспорта элементов

-

Документация API, которую cargo doc порождает для этой библиотеки, теперь будет перечислять и связывать реэкспорты на главной странице, как показано на рисунке 14-4, упрощая поиск видов PrimaryColor, SecondaryColor и функции mix.

- HTML-документация с примечанием для библиотеки в целом -

Рисунок 14-4: Первая страница документации для art, которая перечисляет реэкспорт

-

Пользователи ящика art могут по-прежнему видеть и использовать внутреннюю устройство из приложения 14-3, как показано в приложении 14-4, или они могут использовать более удобную устройство в приложении 14-5, как показано в приложении 14-6:

-

Файл: src/main.rs

-
use art::mix;
-use art::PrimaryColor;
-
-fn main() {
-    // --snip--
-    let red = PrimaryColor::Red;
-    let yellow = PrimaryColor::Yellow;
-    mix(red, yellow);
-}
-

Приложение 14-6: Программа, использующая реэкспортированные элементы из ящика art

-

В случаях, когда имеется много вложенных звеньев, реэкспорт видов на верхнем уровне с помощью pub use может существенно повысить удобство работы для людей, использующих ящик. Ещё одно распространённое использование pub use - это реэкспорт определений зависимого звена в текущем ящике, чтобы сделать определения этого ящика частью открытого API вашего ящика.

-

Создание полезной открытой устройства API - это больше искусство чем наука, и вы можете повторять, чтобы найти API, который лучше всего подойдёт вашим пользователям. Использование pub use даёт вам гибкость в том, как вы внутренне выстраиваете

-

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

-

Настройка учётной записи Crates.io

-

Прежде чем вы сможете обнародовать любые библиотеки, вам необходимо создать учётную запись на crates.io и получить API токен. Для этого зайдите на домашнюю страницу crates.io и войдите в систему через учётную запись GitHub. (В настоящее время требуется наличие учётной записи GitHub, но сайт может поддерживать другие способы создания учётной записи в будущем.) Сразу после входа в систему перейдите в настройки своей учётной записи по адресу https://crates.io/me/ и получите свой ключ API. Затем выполните приказ cargo login с вашим ключом API, например:

-
$ cargo login abcdefghijklmnopqrstuvwxyz012345
-
-

Этот приказ сообщит Cargo о вашем API token и сохранит его местно в ~/.cargo/credentials. Обратите внимание, что этот токен является тайным: не делитесь им ни с кем другим. Если вы по какой-либо причине поделитесь им с кем-либо, вы должны отозвать его и создать новый токен на crates.io.

-

Добавление метаданных в новую библиотеку

-

Допустим, у вас есть ящик, который вы хотите обнародовать. Перед обнародованием вам нужно добавить некоторые метаданные в раздел [package] файла Cargo.toml ящика.

-

Вашему ящику понадобится не повторяющееся имя. Пока вы работаете над ящиком местно, вы можете назвать его как угодно. Однако названия ящиков на crates.io определятся в мгновение первой обнародования. Как только ящику присвоено название, никто другой не сможет обнародовать ящик с таким же именем. Перед тем как обнародовать ящик, поищите название, которое вы хотите использовать. Если такое имя уже используется, вам придётся подобрать другое и отредактировать поле name в файле Cargo.toml в разделе [package], чтобы использовать новое имя в качестве размещаяемого, например, так:

-

Файл: Cargo.toml

-
[package]
-name = "guessing_game"
-
-

Даже если вы выбрали не повторяющееся имя, когда вы запустите cargo publish чтобы обнародовать ящик, вы получите предупреждение, а затем ошибку:

- -
$ cargo publish
-    Updating crates.io index
-warning: manifest has no description, license, license-file, documentation, homepage or repository.
-See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
---snip--
-error: failed to publish to registry at https://crates.io
-
-Caused by:
-  the remote server responded with an error: missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata
-
-

Это ошибка, потому что вам не хватает важной сведений: необходимы описание и лицензия, чтобы люди знали, что делает ваш ящик и на каких условиях они могут его использовать. В поле Cargo.toml добавьте описание, состоящее из одного-двух предложений, поскольку оно будет появляться вместе с вашим ящиком в итогах поиска. Для поля license нужно указать значение определителя лицензии. В Linux Foundation's Software Package Data Exchange (SPDX) перечислены определители, которые можно использовать для этого значения. Например, чтобы указать, что вы лицензировали свой crate, используя лицензию MIT, добавьте определитель MIT:

-

Файл: Cargo.toml

-
[package]
-name = "guessing_game"
-license = "MIT"
-
-

Если вы хотите использовать лицензию, которая отсутствует в SPDX, вам нужно поместить текст этой лицензии в файл, включите файл в свой дело, а затем используйте license-file, чтобы указать имя этого файла вместо использования ключа license.

-

Руководство по выбору лицензии для вашего дела выходит за рамки этой книги. Многие люди в сообществе Ржавчина лицензируют свои дела так же, как и Rust, используя двойную лицензию MIT OR Apache 2.0. Эта применение отображает, что вы также можете указать несколько определителей лицензий, разделённых OR, чтобы иметь несколько лицензий для вашего дела.

-

С добавлением единственного имени, исполнения, вашего описания и лицензии, файл Cargo.toml для дела, который готов к обнародования может выглядеть следующим образом:

-

Файл: Cargo.toml

-
[package]
-name = "guessing_game"
-version = "0.1.0"
-edition = "2021"
-description = "A fun game where you guess what number the computer has chosen."
-license = "MIT OR Apache-2.0"
-
-[dependencies]
-
-

Документация Cargo описывает другие метаданные, которые вы можете указать, чтобы другие могли легче находить и использовать ваш ящик.

-

Обнародование на Crates.io

-

Теперь, когда вы создали учётную запись, сохранили свой токен API, выбрали имя для своего ящика и указали необходимые метаданные, вы готовы к обнародования! Обнародование библиотеки загружает определённую исполнение в crates.io для использования другими.

-

Будьте осторожны, потому что обнародование является перманентной действием. Исполнение никогда не сможет быть перезаписана, а код не подлежит удалению. Одна из основных целей crates.io - служить постоянным архивом кода, чтобы сборки всех дел, зависящих от crates из crates.io продолжали работать. Предоставление возможности удаления исполнений сделало бы выполнение этой цели невозможным. При этом количество исполнений ящиков, которые вы можете обнародовать, не ограничено.

-

Запустите приказ cargo publish ещё раз. Сейчас эта приказ должна выполниться успешно:

- -
$ cargo publish
-    Updating crates.io index
-   Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
-   Verifying guessing_game v0.1.0 (file:///projects/guessing_game)
-   Compiling guessing_game v0.1.0
-(file:///projects/guessing_game/target/package/guessing_game-0.1.0)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
-   Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
-
-

Поздравляем! Теперь вы поделились своим кодом с сообществом Ржавчина и любой может легко добавить вашу библиотеку в качестве зависимости их дела.

-

Обнародование новой исполнения существующей библиотеки

-

Когда вы внесли изменения в свой ящик и готовы выпустить новую исполнение, измените значение version, указанное в вашем файле Cargo.toml и повторите размещение. Воспользуйтесь Semantic Versioning rules, чтобы решить, какой номер следующей исполнения подходит для ваших изменений. Затем запустите cargo publish, чтобы загрузить новую исполнение.

- -

-

Устранение устаревших исполнений с Crates.io с помощью cargo yank

-

Хотя вы не можете удалить предыдущие исполнения ящика, вы можете помешать любым будущим делам добавлять его в качестве новой зависимости. Это полезно, когда исполнение ящика сломана по той или иной причине. В таких случаейх Cargo поддерживает выламывание (yanking) исполнения ящика.

-

Вычёркивание исполнения не позволяет новым делам зависеть от этой исполнения, но при этом позволяет всем существующим делам, зависящим от неё, продолжать работу. По сути, исключение означает, что все дела с Cargo.lock не сломаются, а любые файлы Cargo.lock, которые будут порождаться в будущем, не смогут использовать исключённую исполнение.

-

Чтобы вычеркнуть исполнение ящика, в папки ящика, который вы обнародовали ранее, выполните приказ cargo yank и укажите, какую исполнение вы хотите вычеркнуть. Например, если мы обнародовали ящик под названием guessing_game исполнения 1.0.1 и хотим вычеркнуть её, в папке дела для guessing_game мы выполним:

- -
$ cargo yank --vers 1.0.1
-    Updating crates.io index
-        Yank guessing_game@1.0.1
-
-

Добавив в приказ --undo, вы также можете отменить выламывание и разрешить делам начать зависеть от исполнения снова:

-
$ cargo yank --vers 1.0.1 --undo
-    Updating crates.io index
-      Unyank guessing_game@1.0.1
-
-

Вычёркивание не удаляет код. Оно не может, например, удалить случайно загруженные пароли. Если это произойдёт, вы должны немедленно сбросить эти пароли.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch14-03-cargo-workspaces.html b/rustbook-ru/book/ch14-03-cargo-workspaces.html deleted file mode 100644 index fb364f0ae..000000000 --- a/rustbook-ru/book/ch14-03-cargo-workspaces.html +++ /dev/null @@ -1,466 +0,0 @@ - - - - - - Рабочие области Cargo - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Рабочие пространства Cargo

-

В главе 12 мы создали дополнение, который включал в себя двоичный и библиотечный ящики. По мере развития вашего дела может возникнуть случаей, когда библиотечный ящик будет становиться все больше, и вы захотите разделить ваш дополнение на несколько библиотечных ящиков. Cargo предоставляет возможность под названием workspaces, которая помогает управлять несколькими взаимосвязанными дополнениями, которые разрабатываются в тандеме.

-

Создание рабочего пространства

-

Workspace - это набор дополнений, которые используют один и тот же Cargo.lock и папку для хранения итогов сборки. Давайте создадим дело с использованием workspace - мы будем использовать обыкновенный код, чтобы сосредоточиться на устройстве рабочего пространства. Существует несколько способов внутренне выстроить

-

рабочую область, но мы покажем только один из них. У нас будет рабочая область, содержащая двоичный файл и две библиотеки. Двоичный файл, который обеспечивает основную возможность, будет зависеть от двух библиотек. Одна библиотека предоставит функцию add_one, а вторая - add_two. Эти три ящика будут частью одного workspace. Начнём с создания папки для рабочего окружения:

-
$ mkdir add
-$ cd add
-
-

Далее в папке add мы создадим файл Cargo.toml, который будет определять настройку всего рабочего окружения. В этом файле не будет разделы [package]. Вместо этого он будет начинаться с разделы [workspace], которая позволит нам добавить звенья в рабочее пространство, указав путь к дополнению с нашим двоичным ящиком; в данном случае этот путь - adder:

-

Файл: Cargo.toml

-
[workspace]
-
-members = [
-    "adder",
-]
-
-

Затем мы создадим исполняемый ящик adder, запустив приказ cargo new в папке add:

- -
$ cargo new adder
-     Created binary (application) `adder` package
-
-

На этом этапе мы можем создать рабочее пространство, запустив приказ cargo build. Файлы в папке add должны выглядеть следующим образом:

-
├── Cargo.lock
-├── Cargo.toml
-├── adder
-│   ├── Cargo.toml
-│   └── src
-│       └── main.rs
-└── target
-
-

Рабочая область содержит на верхнем уровне один папка target, в который будут помещены собранные артефакты; дополнение adder не имеет собственного папки target. Даже если мы запустим cargo build из папки adder, собранные артефакты все равно окажутся в add/target, а не в add/adder/target. Cargo так определил папку target в рабочем пространстве, потому что ящики в рабочем пространстве должны зависеть друг от друга. Если бы каждый ящик имел свой собственный папка target, каждому ящику пришлось бы пересобирать каждый из других ящиков в рабочем пространстве, чтобы поместить артефакты в свой собственный папка target. Благодаря совместному использованию единого папки target ящики могут избежать ненужной пересборки.

-

Добавление второго ящика в рабочее пространство

-

Далее давайте создадим ещё одного участника дополнения в рабочей области и назовём его add_one. Внесите изменения в Cargo.toml верхнего уровня так, чтобы указать путь add_one в списке members:

-

Файл: Cargo.toml

-
[workspace]
-
-members = [
-    "adder",
-    "add_one",
-]
-
-

Затем создайте новый ящик библиотеки с именем add_one:

- -
$ cargo new add_one --lib
-     Created library `add_one` package
-
-

Ваш папка add должен теперь иметь следующие папки и файлы:

-
├── Cargo.lock
-├── Cargo.toml
-├── add_one
-│   ├── Cargo.toml
-│   └── src
-│       └── lib.rs
-├── adder
-│   ├── Cargo.toml
-│   └── src
-│       └── main.rs
-└── target
-
-

В файле add_one/src/lib.rs добавим функцию add_one:

-

Файл: add_one/src/lib.rs

-
pub fn add_one(x: i32) -> i32 {
-    x + 1
-}
-

Теперь мы можем сделать так, чтобы дополнение adder с нашим исполняемым файлом зависел от дополнения add_one, содержащего нашу библиотеку. Сначала нам нужно добавить зависимость пути от add_one в adder/Cargo.toml.

-

Файл: adder/Cargo.toml

-
[dependencies]
-add_one = { path = "../add_one" }
-
-

Cargo не исходит из того, что ящики в рабочем пространстве могут зависеть друг от друга, поэтому нам необходимо явно указать отношения зависимости.

-

Далее, давайте используем функцию add_one (из ящика add_one) в ящике adder. Откройте файл adder/src/main.rs и добавьте строку use в верхней части, чтобы ввести в область видимости новый библиотечный ящик add_one. Затем измените функцию main для вызова функции add_one, как показано в приложении 14-7.

-

Файл: adder/src/main.rs

-
use add_one;
-
-fn main() {
-    let num = 10;
-    println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
-}
-

Приложение 14-7: Использование возможностей библиотечного ящика add-one в ящике adder

-

Давайте соберём рабочее пространство, запустив приказ cargo build в папке верхнего уровня add!

- -
$ 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
-
-

Чтобы запустить двоичный ящик из папки add, нам нужно указать какой дополнение из рабочей области мы хотим использовать с помощью переменной -p и названия дополнения в приказу cargo run:

- -
$ cargo run -p adder
-    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/adder`
-Hello, world! 10 plus one is 11!
-
-

Запуск кода из 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

-
[dependencies]
-rand = "0.8.5"
-
-

Теперь мы можем добавить use rand; в файл add_one/src/lib.rs и сделать сборку рабочего пространства, запустив cargo build в папке add, что загрузит и собирает rand ящик:

- -
$ cargo build
-    Updating crates.io index
-  Downloaded rand v0.8.5
-   --snip--
-   Compiling rand v0.8.5
-   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: `add_one` (lib) generated 1 warning
-   Compiling adder v0.1.0 (file:///projects/add/adder)
-    Finished dev [unoptimized + debuginfo] target(s) in 10.18s
-
-

Файл Cargo.lock верхнего уровня теперь содержит сведения о зависимости add_one к ящику rand. Тем не менее, не смотря на то что rand использован где-то в рабочем пространстве, мы не можем использовать его в других ящиках рабочего пространства, пока не добавим ящик rand в отдельные Cargo.toml файлы. Например, если мы добавим use rand; в файл adder/src/main.rs ящика adder, то получим ошибку:

- -
$ 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.toml для дополнения adder и укажите, что rand также является его зависимостью. При сборке дополнения adder rand будет добавлен в список зависимостей для adder в Cargo.lock, но никаких дополнительных повторов rand загружено не будет. Cargo позаботился о том, чтобы все ящики во всех дополнениях рабочей области, использующих дополнение rand, использовали одну и ту же исполнение, экономя нам место и обеспечивая, что все ящики в рабочей области будут совместимы друг с другом.

-

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

-

В качестве ещё одного улучшения давайте добавим проверка функции add_one::add_one в add_one:

-

Файл: add_one/src/lib.rs

-
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 в папке верхнего уровня add. Запуск cargo test в рабочем пространстве, внутренне выстроеном

-

подобно этому, запустит проверки для всех ящиков в рабочем пространстве:

- -
$ 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 unittests src/lib.rs (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 unittests src/main.rs (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
-
-

Первая раздел вывода показывает, что проверка it_works в ящике add_one прошёл. Следующая раздел показывает, что в ящике adder не было обнаружено ни одного проверки, а последняя раздел показывает, что в ящике add_one не было найдено ни одного проверки документации.

-

Мы также можем запустить проверки для одного определенного ящика в рабочем пространстве из папка верхнего уровня с помощью флага -p и указанием имени ящика для которого мы хотим запустить проверки:

- -
$ cargo test -p add_one
-    Finished test [unoptimized + debuginfo] target(s) in 0.00s
-     Running unittests src/lib.rs (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 test запускает только проверки для ящика add-one и не запускает проверки ящика adder.

-

Если вы соберётесь обнародовать ящики из рабочего пространства на crates.io, каждый ящик будет необходимо будет обнародовать отдельно. Подобно cargo test, мы можем обнародовать определенный ящик из нашей рабочей области, используя флаг -p и указав имя ящика, который мы хотим обнародовать.

-

Для дополнительной опытов добавьте ящик add_two в данное рабочее пространство подобным способом, как делали с ящик add_one !

-

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

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch14-04-installing-binaries.html b/rustbook-ru/book/ch14-04-installing-binaries.html deleted file mode 100644 index b2ddce30a..000000000 --- a/rustbook-ru/book/ch14-04-installing-binaries.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - Установка двоичных файлов с Crates.io с помощью cargo install - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
- -

-

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

-

Приказ cargo install позволяет местно устанавливать и использовать исполняемые ящики. Она не предназначена для замены системных дополнений; она используется как удобный способ Ржавчина разработчикам устанавливать средства, которыми другие разработчики поделились на сайте crates.io. Заметьте, можно устанавливать только дополнения, имеющие исполняемые целевые ящики. Исполняемой целью (binary target) является запускаемая программа, созданная и имеющая в составе ящика файл src/main.rs или другой файл, указанный как исполняемый, в отличии от библиотечных ящиков, которые не могут запускаться сами по себе, но подходят для включения в другие программы. Обычно ящик содержит сведения в файле README, является ли он библиотекой, исполняемым файлом или обоими вместе.

-

Все исполняемые файлы установленные приказом cargo install сохранены в корневой установочной папке bin. Если вы установили Ржавчина с помощью rustup.rs и у вас его нет в пользовательских настройках, то этим папкой будет $HOME/.cargo/bin. Он заверяет, что папка находится в вашем окружении $PATH, чтобы вы имели возможность запускать программы, которые вы установили приказом cargo install.

-

Так, например, в главе 12 мы упоминали, что для поиска файлов существует выполнение утилиты grep на Ржавчина под названием ripgrep. Чтобы установить ripgrep, мы можем выполнить следующее:

- -
$ cargo install ripgrep
-    Updating crates.io index
-  Downloaded ripgrep v13.0.0
-  Downloaded 1 crate (243.3 KB) in 0.88s
-  Installing ripgrep v13.0.0
---snip--
-   Compiling ripgrep v13.0.0
-    Finished release [optimized + debuginfo] target(s) in 3m 10s
-  Installing ~/.cargo/bin/rg
-   Installed package `ripgrep v13.0.0` (executable `rg`)
-
-

Последняя строка вывода показывает местоположение и название установленного исполняемого файла, который в случае ripgrep называется rg. Если вашей установочной папкой является $PATH, как уже упоминалось ранее, вы можете запустить rg --help и начать использовать более быстрый и грубый средство для поиска файлов!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch14-05-extending-cargo.html b/rustbook-ru/book/ch14-05-extending-cargo.html deleted file mode 100644 index 7f1990d0f..000000000 --- a/rustbook-ru/book/ch14-05-extending-cargo.html +++ /dev/null @@ -1,234 +0,0 @@ - - - - - - Расширение возможностей Cargo путём добавления пользовательских приказов - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

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

-

Cargo расчитан так, что вы можете расширять его новыми субприказми без необходимости изменения самого Cargo. Если исполняемый файл доступен через переменную окружения $PATH и назван по образцу cargo-something, то его можно запускать как субприказ Cargo cargo something. Пользовательские приказы подобные этой также перечисляются в списке доступных через cargo --list. Возможность использовать cargo install для установки расширений и затем запускать их так же, как встроенные в Cargo средства, это очень удобное следствие продуманного внешнего вида Cargo!

-

Итоги

-

Совместное использование кода с Cargo и crates.io является частью того, что делает внутреннее устройство Ржавчина полезной для множества различных задач. Обычная библиотека Ржавчина небольшая и безотказная, но ящики легко распространять, использовать и улучшать независимо от самого языка. Не стесняйтесь делиться кодом, который был вам полезен, через crates.io; скорее всего, он будет полезен и кому-то ещё!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch15-00-smart-pointers.html b/rustbook-ru/book/ch15-00-smart-pointers.html deleted file mode 100644 index 17d3299e8..000000000 --- a/rustbook-ru/book/ch15-00-smart-pointers.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - Умные указатели - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Умные указатели

-

Указатель — это общая подход для переменной, которая содержит адрес участка памяти. Этот адрес «относится к», или «указывает на» некоторые другие данные. Наиболее общая разновидность указателя в Ржавчина — это ссылка, о которой вы узнали из главы 4. Ссылки обозначаются символом & и заимствуют значение, на которое указывают. Они не имеют каких-либо особых возможностей, кроме как ссылаться на данные, и не имеют никаких накладных расходов.

-

Умные указатели, с другой стороны, являются устройствами данных, которые не только действуют как указатель, но также имеют дополнительные метаданные и возможности. Подход умных указателей не неповторима для Rust: умные указатели возникли в C++ и существуют в других языках. В Ржавчина есть разные умные указатели, определённые в встроенной библиотеке, которые обеспечивают возможность, выходящую за рамки ссылок. Одним из примеров, который мы рассмотрим в этой главе, является вид умного указателя reference counting (подсчёт ссылок). Этот указатель позволяет иметь несколько владельцев с помощью отслеживания количества владельцев и, когда владельцев не остаётся, очищает данные.

-

Rust с его подходом владения и заимствования имеет дополнительное различие между ссылками и умными указателями: в то время, как ссылки только заимствуют данные, умные указатели часто владеют данными, на которые указывают.

-

Ранее мы уже сталкивались с умными указателями в этой книге, хотя и не называли их так, например String и Vec<T> в главе 8. Оба этих вида считаются умными указателями, потому что они владеют некоторой областью памяти и позволяют ею управлять. У них также есть метаданные и дополнительные возможности или заверения. String, например, хранит свой размер в виде метаданных и заверяет, что содержимое строки всегда будет в кодировке UTF-8.

-

Умные указатели обычно выполняются с помощью устройств. Присущей чертой, которая отличает умный указатель от обычной устройства, является то, что для умных указателей выполнены особенности Deref и Drop. Особенность Deref позволяет образцу умного указателя вести себя как ссылка, так что вы можете написать код, работающий с ним как со ссылкой, так и как с умным указателем. Особенность Drop позволяет написать код, который будет запускаться когда образец умного указателя выйдет из области видимости. В этой главе мы обсудим оба особенности и выясним, почему они важны для умных указателей.

-

Учитывая, что образец умного указателя является общим образцом разработки, часто используемым в Rust, эта глава не описывает все существующие умные указатели. Множество библиотек имеют свои умные указатели, и вы также можете написать свои. Мы охватим наиболее распространённые умные указатели из встроенной библиотеки:

-
    -
  • Box<T> для распределения значений в куче (памяти)
  • -
  • Rc<T> вид счётчика ссылок, который допускает множественное владение
  • -
  • Виды Ref<T> и RefMut<T>, доступ к которым осуществляется через вид RefCell<T>, который обеспечивает правила заимствования во время выполнения вместо времени сборки
  • -
-

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

-

Приступим!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch15-01-box.html b/rustbook-ru/book/ch15-01-box.html deleted file mode 100644 index f58cd7a89..000000000 --- a/rustbook-ru/book/ch15-01-box.html +++ /dev/null @@ -1,362 +0,0 @@ - - - - - - Использование Box<T> для указания на данные в куче - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Использование Box<T> для ссылки на данные в куче

-

Наиболее простой умный указатель - это box, чей вид записывается как Box<T>. Такие переменные позволяют хранить данные в куче, а не в обойме. То, что остаётся в обойме, является указателем на данные в куче. Обратитесь к Главе 4, чтобы рассмотреть разницу между обоймой и кучей.

-

У Box нет неполадок с производительностью, кроме хранения данных в куче вместо обоймы. Но он также и не имеет множества дополнительных возможностей. Вы будете использовать его чаще всего в следующих случаейх:

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

Мы выясним первую случай в разделе "Выполнение рекурсивных видов с помощью Box". Во втором случае, передача владения на большой размер данных может занять много времени, потому что данные повторяются через обойма. Для повышения производительности в этой случаи, мы можем хранить большое количество данных в куче с помощью Box. Затем только небольшое количество данных указателя воспроизводится в обойме, в то время как данные, на которые он ссылается, остаются в одном месте кучи. Третий случай известен как особенность предмет (trait object) и глава 17 посвящает целый раздел "Использование особенность предметов, которые допускают значения разных видов" только этой теме. Итак, то, что вы узнаете здесь, вы примените снова в Главе 17!

-

Использование Box<T> для хранения данных в куче

-

Прежде чем мы обсудим этот исход использования Box<T>, мы рассмотрим правила написания и то, как взаимодействовать со значениями, хранящимися в Box<T>.

-

В приложении 15-1 показано, как использовать поле для хранения значения i32 в куче:

-

Файл: src/main.rs

-
fn main() {
-    let b = Box::new(5);
-    println!("b = {b}");
-}
-

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

-

Мы объявляем переменную b со значением Box, указывающим на число 5, размещённое в куче. Эта программа выведет b = 5; в этом случае мы получаем доступ к данным в box так же, как если бы эти данные находились в обойме. Как и любое другое значение, когда box выйдет из области видимости, как b в конце main, он будет удалён. Деаллокация происходит как для box ( хранящегося в обойме), так и для данных, на которые он указывает (хранящихся в куче).

-

Размещать одиночные значения в куче не слишком целесообразно, поэтому вряд ли вы будете часто использовать box'ы таким образом. В большинстве случаев более уместно размещать такие значения, как i32, в обойме, где они и сохраняются по умолчанию. Давайте рассмотрим случай, когда box позволяет нам определить виды, которые мы не могли бы иметь, если бы у нас не было box.

-

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

-

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

-

Cons list не является часто используемой устройством данных в Rust. В большинстве случаев, когда вам нужен список элементов при использовании Rust, лучше использовать Vec<T>. Другие, более сложные рекурсивные виды данных полезны в определённых случаейх, но благодаря тому, что в этой главе мы начнём с cons list, мы сможем выяснить, как box позволяет нам определить рекурсивный вид данных без особого напряжения.

-

Приложение 15-2 содержит объявление перечисления cons списка. Обратите внимание, что этот код не будет собираться, потому что вид List не имеет известного размера, что мы и выясним.

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, List),
-    Nil,
-}
-
-fn main() {}
-

Приложение 15-2: Первая попытка определить перечисление в качестве устройства данных cons list, состоящей из i32 значений.

-
-

Примечание: В данном примере мы выполняем cons list, который содержит только значения i32. Мы могли бы выполнить его с помощью generics, о которых мы говорили в главе 10, чтобы определить вид cons list, который мог бы хранить значения любого вида.

-
-

Использование вида List для хранения списка 1, 2, 3 будет выглядеть как код в приложении 15-3:

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, List),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-
-fn main() {
-    let list = Cons(1, Cons(2, Cons(3, Nil)));
-}
-

Приложение 15-3: Использование перечисления List для хранения списка 1, 2, 3

-

Первое значение Cons содержит 1 и другой List. Это значение List является следующим значением Cons, которое содержит 2 и другой List. Это значение List является ещё один значением Cons, которое содержит 3 и значение List, которое наконец является Nil, не рекурсивным исходом, сигнализирующим об окончании списка.

-

Если мы попытаемся собрать код в приложении 15-3, мы получим ошибку, показанную в приложении 15-4:

-
$ cargo run
-   Compiling cons-list v0.1.0 (file:///projects/cons-list)
-error[E0072]: recursive type `List` has infinite size
- --> src/main.rs:1:1
-  |
-1 | enum List {
-  | ^^^^^^^^^
-2 |     Cons(i32, List),
-  |               ---- recursive without indirection
-  |
-help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
-  |
-2 |     Cons(i32, Box<List>),
-  |               ++++    +
-
-error[E0391]: cycle detected when computing when `List` needs drop
- --> src/main.rs:1:1
-  |
-1 | enum List {
-  | ^^^^^^^^^
-  |
-  = note: ...which immediately requires computing when `List` needs drop again
-  = note: cycle used when computing whether `List` needs drop
-  = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
-
-Some errors have detailed explanations: E0072, E0391.
-For more information about an error, try `rustc --explain E0072`.
-error: could not compile `cons-list` (bin "cons-list") due to 2 previous errors
-
-

Приложение 15-4: Ошибка, которую мы получаем при попытке определить рекурсивное перечисление

-

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

-

Вычисление размера нерекурсивного вида

-

Вспомните перечисление Message определённое в приложении 6-2, когда обсуждали объявление enum в главе 6:

-
enum Message {
-    Quit,
-    Move { x: i32, y: i32 },
-    Write(String),
-    ChangeColor(i32, i32, i32),
-}
-
-fn main() {}
-

Чтобы определить, сколько памяти выделять под значение Message, Ржавчина проходит каждый из исходов, чтобы увидеть, какой исход требует наибольшее количество памяти. Ржавчина видит, что для Message::Quit не требуется места, Message::Move хватает места для хранения двух значений i32 и т.д. Так как будет использоваться только один исход, то наибольшее пространство, которое потребуется для значения Message, это пространство, которое потребуется для хранения самого большого из исходов перечисления.

-

Сравните это с тем, что происходит, когда Ржавчина пытается определить, сколько места необходимо рекурсивному виду, такому как перечисление List в приложении 15-2. Сборщик смотрит на исход Cons, который содержит значение вида i32 и значение вида List. Следовательно, Cons нужно пространство, равное размеру i32 плюс размер List. Чтобы выяснить, сколько памяти необходимо виду List, сборщик смотрит на исходы, начиная с Cons. Исход Cons содержит значение вида i32 и значение вида List, и этот этап продолжается бесконечно, как показано на рисунке 15-1.

- Бесконечный список Cons -

Рисунок 15-1: Бесконечный List, состоящий из нескончаемого числа исходов Cons

-

Использование Box<T> для получения рекурсивного вида с известным размером

-

Поскольку Ржавчина не может определить, сколько места нужно выделить для видов с рекурсивным определением, сборщик выдаёт ошибку с этим полезным предложением:

- -
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable
-  |
-2 |     Cons(i32, Box<List>),
-  |               ++++    +
-
-

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

-

Поскольку Box<T> является указателем, Ржавчина всегда знает, сколько места нужно Box<T>: размер указателя не меняется в зависимости от объёма данных, на которые он указывает. Это означает, что мы можем поместить Box<T> внутрь образца Cons вместо значения List напрямую. Box<T> будет указывать на значение очередного List, который будет находиться в куче, а не внутри образца Cons. Мировозренческо у нас все ещё есть список, созданный из списков, содержащих другие списки, но эта выполнение теперь больше похожа на размещение элементов рядом друг с другом, а не внутри друг друга.

-

Мы можем изменить определение перечисления List в приложении 15-2 и использование List в приложении 15-3 на код из приложения 15-5, который будет собираться:

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, Box<List>),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-
-fn main() {
-    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
-}
-

Приложение 15-5: Определение List, которое использует Box<T> для того, чтобы иметь вычисляемый размер

-

Cons требуется объём i32 плюс место для хранения данных указателя box. Nil не хранит никаких значений, поэтому ему нужно меньше места, чем Cons. Теперь мы знаем, что любое значение List займёт размер i32 плюс размер данных указателя box. Используя box, мы разорвали бесконечную рекурсивную цепочку, поэтому сборщик может определить размер, необходимый для хранения значения List. На рисунке 15-2 показано, как теперь выглядит Cons.

- Бесконечный список Cons -

Рисунок 15-2: List, который не является бесконечно большим, потому что Cons хранит Box.

-

Box-ы обеспечивают только перенаправление и выделение в куче; у них нет никаких других особых возможностей, подобных тем, которые мы увидим у других видов умных указателей. У них также нет накладных расходов на производительность, которые несут эти особые возможности, поэтому они могут быть полезны в таких случаях, как cons list, где перенаправление - единственная функция, которая нам нужна. В главе 17 мы также рассмотрим другие случаи использования box.

-

Вид Box<T> является умным указателем, поскольку он выполняет особенность Deref, который позволяет обрабатывать значения Box<T> как ссылки. Когда значение Box<T> выходит из области видимости, данные кучи, на которые указывает box, также очищаются благодаря выполнения особенности Drop. Эти два особенности будут ещё более значимыми для возможности, предоставляемой другими видами умных указателей, которые мы обсудим в оставшейся части этой главы. Давайте рассмотрим эти два особенности более подробно.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch15-02-deref.html b/rustbook-ru/book/ch15-02-deref.html deleted file mode 100644 index 8806c9498..000000000 --- a/rustbook-ru/book/ch15-02-deref.html +++ /dev/null @@ -1,443 +0,0 @@ - - - - - - Работа с умными указателями как с обычными ссылками с помощью особенности Deref - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Обращение с умными указателями как с обычными ссылками с помощью Deref особенности

-

Используя особенность Deref, вы можете изменить поведение оператора разыменования * (не путать с операторами умножения или вездесущего подключения). Выполнив Deref таким образом, что умный указатель может рассматриваться как обычная ссылка, вы можете писать код, оперирующий ссылками, а также использовать этот код с умными указателями.

-

Давайте сначала посмотрим, как работает оператор разыменования с обычными ссылками. Затем мы попытаемся определить пользовательский вид, который ведёт себя как Box<T> и посмотрим, почему оператор разыменования не работает как ссылка для нового объявленного вида. Мы рассмотрим, как выполнение особенности Deref делает возможным работу умных указателей подобно ссылкам. Затем посмотрим на разыменованное приведение (deref coercion) в Ржавчина и как оно позволяет работать с любыми ссылками или умными указателями.

-
-

Примечание: есть одна большая разница между видом MyBox<T>, который мы собираемся создать и существующим Box<T>: наша исполнение не будет хранить свои данные в куче. В примере мы сосредоточимся на особенности Deref, поэтому менее важно то, где данные хранятся, чем поведение подобное указателю.

-
- -

-

Следуя за указателем на значение

-

Обычная ссылка - это разновидность указателя, а указатель можно рассматривать как своеобразную стрелочку направляющую к значению, хранящемуся в другом месте. В приложении 15-6 мы создаём ссылку на значение i32, а затем используем оператор разыменования для перехода от ссылки к значению:

-

Файл: src/main.rs

-
fn main() {
-    let x = 5;
-    let y = &x;
-
-    assert_eq!(5, x);
-    assert_eq!(5, *y);
-}
-

Приложение 15-6: Использование оператора разыменования для следования по ссылке к значению i32

-

Переменной x присвоено значение5 вида i32. Мы установили в качестве значения y ссылку на x. Мы можем утверждать, что значение x равно 5. Однако, если мы хотим сделать утверждение о значении в y, мы должны использовать *y, чтобы перейти по ссылке к значению, на которое она указывает (таким образом, происходит разыменование), для того чтобы сборщик при сравнении мог использовать действительное значение. Как только мы разыменуем y, мы получим доступ к целочисленному значению, на которое указывает y, которое и будем сравнивать с 5.

-

Если бы мы попытались написать assert_eq!(5, y);, то получили ошибку сборки:

-
$ cargo run
-   Compiling deref-example v0.1.0 (file:///projects/deref-example)
-error[E0277]: can't compare `{integer}` with `&{integer}`
- --> src/main.rs:6:5
-  |
-6 |     assert_eq!(5, y);
-  |     ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`
-  |
-  = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}`
-  = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `deref-example` (bin "deref-example") due to 1 previous error
-
-

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

-

Использование Box<T> как ссылку

-

Мы можем переписать код в приложении 15-6, чтобы использовать Box<T> вместо ссылки; оператор разыменования, используемый для Box<T> в приложении 15-7, работает так же, как оператор разыменования, используемый для ссылки в приложении 15-6:

-

Файл: src/main.rs

-
fn main() {
-    let x = 5;
-    let y = Box::new(x);
-
-    assert_eq!(5, x);
-    assert_eq!(5, *y);
-}
-

Приложение 15-7: Использование оператора разыменования с видом Box<i32>

-

Основное различие между приложением 15-7 и приложением 15-6 заключается в том, что здесь мы устанавливаем y как образец Box<T>, указывающий на воспроизведенное значение x, а не как ссылку, указывающую на значение x. В последнем утверждении мы можем использовать оператор разыменования, чтобы проследовать за указателем Box<T> так же, как мы это делали, когда y был ссылкой. Далее мы рассмотрим, что особенного в Box<T>, что позволяет нам использовать оператор разыменования, определяя наш собственный вид.

-

Определение собственного умного указателя

-

Давайте создадим умный указатель, похожий на вид Box<T> предоставляемый встроенной библиотекой, чтобы понять как поведение умных указателей отличается от поведения обычной ссылки. Затем мы рассмотрим вопрос, как добавить возможность использовать оператор разыменования.

-

Вид Box<T> в конечном итоге определяется как устройства упорядоченного ряда с одним элементом, поэтому в приложении 15-8 подобным образом определяется MyBox<T>. Мы также определим функцию new, чтобы она соответствовала функции new, определённой в Box<T>.

-

Файл: src/main.rs

-
struct MyBox<T>(T);
-
-impl<T> MyBox<T> {
-    fn new(x: T) -> MyBox<T> {
-        MyBox(x)
-    }
-}
-
-fn main() {}
-

Приложение 15-8: Определение вида MyBox<T>

-

Мы определяем устройство с именем MyBox и объявляем обобщённый свойство T, потому что мы хотим, чтобы наш вид хранил значения любого вида. Вид MyBox является устройством упорядоченного ряда с одним элементом вида T. Функция MyBox::new принимает один свойство вида T и возвращает образец MyBox, который содержит переданное значение.

-

Давайте попробуем добавить функцию main из приложения 15-7 в приложение 15-8 и изменим её на использование вида MyBox<T>, который мы определили вместо Box<T>. Код в приложении 15-9 не будет собираться, потому что Ржавчина не знает, как разыменовывать MyBox.

-

Файл: src/main.rs

-
struct MyBox<T>(T);
-
-impl<T> MyBox<T> {
-    fn new(x: T) -> MyBox<T> {
-        MyBox(x)
-    }
-}
-
-fn main() {
-    let x = 5;
-    let y = MyBox::new(x);
-
-    assert_eq!(5, x);
-    assert_eq!(5, *y);
-}
-

Приложение 15-9. Попытка использовать MyBox<T> таким же образом, как мы использовали ссылки и Box<T>

-

Вот итог ошибки сборки:

-
$ cargo run
-   Compiling deref-example v0.1.0 (file:///projects/deref-example)
-error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
-  --> src/main.rs:14:19
-   |
-14 |     assert_eq!(5, *y);
-   |                   ^^
-
-For more information about this error, try `rustc --explain E0614`.
-error: could not compile `deref-example` (bin "deref-example") due to 1 previous error
-
-

Наш вид MyBox<T> не может быть разыменован, потому что мы не выполнили эту возможность. Чтобы включить разыменование с помощью оператора *, мы выполняем особенность Deref.

-

Трактование вида как ссылки выполняя особенность Deref

-

Как обсуждалось в разделе “Выполнение особенности для типа” Главы 10, для выполнения особенности нужно предоставить выполнения требуемых способов особенности. Особенность Deref, предоставляемый встроенной библиотекой требует от нас выполнения одного способа с именем deref, который заимствует self и возвращает ссылку на внутренние данные. Приложение 15-10 содержит выполнение Deref добавленную к определению MyBox:

-

Файл: src/main.rs

-
use std::ops::Deref;
-
-impl<T> Deref for MyBox<T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-struct MyBox<T>(T);
-
-impl<T> MyBox<T> {
-    fn new(x: T) -> MyBox<T> {
-        MyBox(x)
-    }
-}
-
-fn main() {
-    let x = 5;
-    let y = MyBox::new(x);
-
-    assert_eq!(5, x);
-    assert_eq!(5, *y);
-}
-

Приложение 15-10: Выполнение Deref для вида MyBox<T>

-

правила написания type Target = T; определяет связанный вид для использования у особенности Deref. Связанные виды - это немного другой способ объявления обобщённого свойства, но пока вам не нужно о них беспокоиться; мы рассмотрим их более подробно в главе 19.

-

Мы заполним тело способа deref оператором &self.0 , чтобы deref вернул ссылку на значение, к которому мы хотим получить доступ с помощью оператора *; вспомним из раздела "Using Tuple Structs without Named Fields to Create Different Types" главы 5, что .0 получает доступ к первому значению в упорядоченной в ряд устройстве. Функция main в приложении 15-9, которая вызывает * для значения MyBox<T>, теперь собирается, и проверки проходят!

-

Без особенности Deref сборщик может только разыменовывать & ссылки. Способ deref даёт сборщику возможность принимать значение любого вида, выполняющего Deref и вызывать способ deref чтобы получить ссылку &, которую он знает, как разыменовывать.

-

Когда мы ввели *y в приложении 15-9, Ржавчина в действительности выполнил за кулисами такой код:

-
*(y.deref())
-

Rust заменяет оператор * вызовом способа deref и затем простое разыменование, поэтому нам не нужно думать о том, нужно ли нам вызывать способ deref. Эта функция Ржавчина позволяет писать код, который исполняется одинаково, независимо от того, есть ли у нас обычная ссылка или вид, выполняющий особенность Deref.

-

Причина, по которой способ deref возвращает ссылку на значение, и что простое разыменование вне круглых скобок в *(y.deref()) все ещё необходимо, связана с системой владения. Если бы способ deref возвращал значение напрямую, а не ссылку на него, значение переместилось бы из self. Мы не хотим передавать владение внутренним значением внутри MyBox<T> в этом случае и в большинстве случаев, когда мы используем оператор разыменования.

-

Обратите внимание, что оператор * заменён вызовом способа deref, а затем вызовом оператора * только один раз, каждый раз, когда мы используем * в коде. Поскольку замена оператора * не повторяется бесконечно, мы получаем данные вида i32, которые соответствуют 5 в assert_eq! приложения 15-9.

-

Неявные разыменованные приведения с функциями и способами

-

Разыменованное приведение преобразует ссылку на вид, который выполняет признак Deref, в ссылку на другой вид. Например, deref coercion может преобразовать &String в &str, потому что String выполняет признак Deref, который возвращает &str. Deref coercion - это удобный рычаг, который Ржавчина использует для переменных функций и способов, и работает только для видов, выполняющих признак Deref. Это происходит самостоятельно , когда мы передаём в качестве переменной функции или способа ссылку на значение определённого вида, которое не соответствует виду свойства в определении функции или способа. В итоге серии вызовов способа deref вид, который мы передали, преобразуется в вид, необходимый для свойства.

-

Разыменованное приведение было добавлено в Rust, так что программистам, пишущим вызовы функций и способов, не нужно добавлять множество явных ссылок и разыменований с помощью использования & и *. Возможность разыменованного приведения также позволяет писать больше кода, который может работать как с ссылками, так и с умными указателями.

-

Чтобы увидеть разыменованное приведение в действии, давайте воспользуемся видом MyBox<T> определённым в приложении 15-8, а также выполнение Deref добавленную в приложении 15-10. Приложение 15-11 показывает определение функции, у которой есть свойство вида срез строки:

-

Файл: src/main.rs

-
fn hello(name: &str) {
-    println!("Hello, {name}!");
-}
-
-fn main() {}
-

Приложение 15-11: Функция hello имеющая свойство name вида &str

-

Можно вызвать функцию hello со срезом строки в качестве переменной, например hello("Rust");. Разыменованное приведение делает возможным вызов hello со ссылкой на значение вида MyBox<String>, как показано в приложении 15-12.

-

Файл: src/main.rs

-
use std::ops::Deref;
-
-impl<T> Deref for MyBox<T> {
-    type Target = T;
-
-    fn deref(&self) -> &T {
-        &self.0
-    }
-}
-
-struct MyBox<T>(T);
-
-impl<T> MyBox<T> {
-    fn new(x: T) -> MyBox<T> {
-        MyBox(x)
-    }
-}
-
-fn hello(name: &str) {
-    println!("Hello, {name}!");
-}
-
-fn main() {
-    let m = MyBox::new(String::from("Rust"));
-    hello(&m);
-}
-

Приложение 15-12: Вызов hello со ссылкой на значение MyBox<String>, которое работает из-за разыменованного приведения

-

Здесь мы вызываем функцию hello с переменнаяом &m, который является ссылкой на значение MyBox<String>. Поскольку мы выполнили особенность Deref для MyBox<T> в приложении 15-10, то Ржавчина может преобразовать &MyBox<String> в &String вызывая deref. Обычная библиотека предоставляет выполнение особенности Deref для вида String, которая возвращает срез строки, это описано в документации API особенности Deref. Ржавчина снова вызывает deref, чтобы превратить &String в &str, что соответствует определению функции hello.

-

Если бы Ржавчина не выполнил разыменованное приведение, мы должны были бы написать код в приложении 15-13 вместо кода в приложении 15-12 для вызова способа hello со значением вида &MyBox<String>.

-

Файл: src/main.rs

-
use std::ops::Deref;
-
-impl<T> Deref for MyBox<T> {
-    type Target = T;
-
-    fn deref(&self) -> &T {
-        &self.0
-    }
-}
-
-struct MyBox<T>(T);
-
-impl<T> MyBox<T> {
-    fn new(x: T) -> MyBox<T> {
-        MyBox(x)
-    }
-}
-
-fn hello(name: &str) {
-    println!("Hello, {name}!");
-}
-
-fn main() {
-    let m = MyBox::new(String::from("Rust"));
-    hello(&(*m)[..]);
-}
-

Приложение 15-13: Код, который нам пришлось бы написать, если бы в Ржавчина не было разыменованного приведения ссылок

-

Код (*m) разыменовывает MyBox<String> в String. Затем & и [..] принимают строковый срез String, равный всей строке, чтобы соответствовать ярлыке hello. Код без разыменованного приведения сложнее читать, писать и понимать со всеми этими символами. Разыменованное приведение позволяет Ржавчина обрабатывать эти преобразования для нас самостоятельно .

-

Когда особенность Deref определён для задействованных видов, Ржавчина проанализирует виды и будет использовать Deref::deref столько раз, сколько необходимо, чтобы получить ссылку, соответствующую виду свойства. Количество раз, которое нужно вставить Deref::deref определяется во время сборки, поэтому использование разыменованного приведения не имеет накладных расходов во время выполнения!

-

Как разыменованное приведение взаимодействует с изменяемостью

-

Подобно тому, как вы используете особенность Deref для переопределения оператора * у неизменяемых ссылок, вы можете использовать особенность DerefMut для переопределения оператора * у изменяемых ссылок.

-

Rust выполняет разыменованное приведение, когда находит виды и выполнения особенностей в трёх случаях:

-
    -
  • Из вида &T в вид &U когда верно T: Deref<Target=U>
  • -
  • Из вида &mut T в вид &mut U когда верно T: DerefMut<Target=U>
  • -
  • Из вида &mut T в вид &U когда верно T: Deref<Target=U>
  • -
-

Первые два случая равноценны друг другу, за исключением того, что второй выполняет изменяемость. В первом случае говорится, что если у вас есть &T, а T выполняет Deref для некоторого вида U, вы сможете прозрачно получить &U. Во втором случае говорится, что такое же разыменованное приведение происходит и для изменяемых ссылок.

-

Третий случай хитрее: Ржавчина также приводит изменяемую ссылку к неизменяемой. Но обратное не представляется возможным: неизменяемые ссылки никогда не приводятся к изменяемым ссылкам. Из-за правил заимствования, если у вас есть изменяемая ссылка, эта изменяемая ссылка должна быть единственной ссылкой на данные (в противном случае программа не будет собираться). Преобразование одной изменяемой ссылки в неизменяемую ссылку никогда не нарушит правила заимствования. Преобразование неизменяемой ссылки в изменяемую ссылку потребует наличия только одной неизменяемой ссылки на эти данные, и правила заимствования не заверяют этого. Следовательно, Ржавчина не может сделать предположение, что преобразование неизменяемой ссылки в изменяемую ссылку возможно.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch15-03-drop.html b/rustbook-ru/book/ch15-03-drop.html deleted file mode 100644 index e27bc875a..000000000 --- a/rustbook-ru/book/ch15-03-drop.html +++ /dev/null @@ -1,346 +0,0 @@ - - - - - - Выполнение кода при очистке с помощью особенности Drop - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Запуск кода при очистке с помощью особенности Drop

-

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

-

Мы рассматриваем Drop в среде умных указателей, потому что возможность свойства Drop по сути всегда используется при выполнения умного указателя. Например, при сбросе Box<T> происходит деаллокация пространства на куче, на которое указывает box.

-

В некоторых языках для некоторых видов программист должен вызывать код для освобождения памяти или ресурсов каждый раз, когда он завершает использование образцов этих видов. Примерами могут служить указатели файлов, сокеты или блокировки. Если забыть об этом, система окажется перегруженной и может упасть. В Ржавчина вы можете указать, что определённый отрывок кода должен выполняться всякий раз, когда значение выходит из области видимости, и сборщик самостоятельно будет его вставлять. Как следствие, вам не нужно заботиться о размещении кода очистки везде в программе, где завершается работа образца определённого вида - утечки ресурсов все равно не будет!

-

Вы можете задать определённую логику, которая будет выполняться, когда значение выходит за пределы области видимости, выполнив признак Drop. Особенность Drop требует от вас выполнения одного способа drop, который принимает изменяемую ссылку на self. Чтобы увидеть, когда Ржавчина вызывает drop, давайте выполняем drop с помощью указаний println!.

-

В приложении 15-14 показана устройства CustomSmartPointer, единственной не имеющей себе подобных возможностью которой является печать Dropping CustomSmartPointer!, когда образец выходит из области видимости, чтобы показать, когда Ржавчина выполняет функцию drop.

-

Файл: src/main.rs

-
struct CustomSmartPointer {
-    data: String,
-}
-
-impl Drop for CustomSmartPointer {
-    fn drop(&mut self) {
-        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
-    }
-}
-
-fn main() {
-    let c = CustomSmartPointer {
-        data: String::from("my stuff"),
-    };
-    let d = CustomSmartPointer {
-        data: String::from("other stuff"),
-    };
-    println!("CustomSmartPointers created.");
-}
-

Приложение 15-14: Устройства CustomSmartPointer, выполняющая особенность Drop, куда мы поместим наш код очистки

-

Особенность Drop включён в прелюдию, поэтому нам не нужно вводить его в область видимости. Мы выполняем особенность Drop для CustomSmartPointer и выполняем способ drop, который будет вызывать println!. Тело функции drop - это место, где должна располагаться вся логика, которую вы захотите выполнять, когда образец вашего вида выйдет из области видимости. Мы печатаем здесь текст, чтобы наглядно отобразить, когда Ржавчина вызовет drop.

-

В main мы создаём два образца CustomSmartPointer и затем печатаем CustomSmartPointers created . В конце main наши образцы CustomSmartPointer выйдут из области видимости и Ржавчина вызовет код, который мы добавили в способ drop, который и напечатает наше окончательное сообщение. Обратите внимание, что нам не нужно вызывать способ drop явно.

-

Когда мы запустим эту программу, мы увидим следующий вывод:

-
$ cargo run
-   Compiling drop-example v0.1.0 (file:///projects/drop-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s
-     Running `target/debug/drop-example`
-CustomSmartPointers created.
-Dropping CustomSmartPointer with data `other stuff`!
-Dropping CustomSmartPointer with data `my stuff`!
-
-

Rust самостоятельно вызывал drop в мгновение выхода наших образцов из области видимости, тем самым выполнив заданный нами код. Переменные удаляются в обратном порядке их создания, поэтому d была удалена до c. Цель этого примера — дать вам наглядное представление о том, как работает способ drop; в типичных случаях вы будете задавать код очистки, который должен выполнить ваш вид, а не печатать сообщение.

-

Раннее удаление значения с помощью std::mem::drop

-

К сожалению, отключение функции самостоятельного удаления с помощью drop является не простым. Отключение drop обычно не требуется; весь смысл особенности Drop в том, чтобы о функции позаботились самостоятельно . Иногда, однако, вы можете захотеть очистить значение рано. Одним из примеров является использование умных указателей, которые управляют блокировками: вы могли бы потребовать принудительный вызов способа drop который снимает блокировку, чтобы другой код в той же области видимости мог получить блокировку. Ржавчина не позволяет вызвать способ особенности Drop вручную; вместо этого вы должны вызвать функцию std::mem::drop предоставляемую встроенной библиотекой, если хотите принудительно удалить значение до конца области видимости.

-

Если попытаться вызвать способ drop особенности Drop вручную, изменяя функцию main приложения 15-14 так, как показано в приложении 15-15, мы получим ошибку сборщика:

-

Файл: src/main.rs

-
struct CustomSmartPointer {
-    data: String,
-}
-
-impl Drop for CustomSmartPointer {
-    fn drop(&mut self) {
-        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
-    }
-}
-
-fn main() {
-    let c = CustomSmartPointer {
-        data: String::from("some data"),
-    };
-    println!("CustomSmartPointer created.");
-    c.drop();
-    println!("CustomSmartPointer dropped before the end of main.");
-}
-

Приложение 15-15: Попытка вызвать способ drop из особенности Drop вручную для досрочной очистки

-

Когда мы попытаемся собрать этот код, мы получим ошибку:

-
$ cargo run
-   Compiling drop-example v0.1.0 (file:///projects/drop-example)
-error[E0040]: explicit use of destructor method
-  --> src/main.rs:16:7
-   |
-16 |     c.drop();
-   |       ^^^^ explicit destructor calls not allowed
-   |
-help: consider using `drop` function
-   |
-16 |     drop(c);
-   |     +++++ ~
-
-For more information about this error, try `rustc --explain E0040`.
-error: could not compile `drop-example` (bin "drop-example") due to 1 previous error
-
-

Это сообщение об ошибке говорит, что мы не можем явно вызывать drop. В сообщении об ошибке используется понятие деструктор (destructor), который является общим понятием программирования для функции, которая очищает образец. Деструктор подобен строителю, который создаёт образец. Функция drop в Ржавчина является определённым деструктором.

-

Rust не позволяет обращаться к drop напрямую, потому что он все равно самостоятельно вызовет drop в конце main. Это вызвало бы ошибку double free, потому что в этом случае Ржавчина попытался бы дважды очистить одно и то же значение.

-

Невозможно отключить самостоятельную подстановку вызова drop, когда значение выходит из области видимости, и нельзя вызвать способ drop напрямую. Поэтому, если нам нужно принудительно избавиться от значения раньше времени, следует использовать функцию std::mem::drop.

-

Функция std::mem::drop отличается от способа drop особенности Drop. Мы вызываем её, передавая в качестве переменной значение, которое хотим принудительно уничтожить. Функция находится в прелюдии, поэтому мы можем изменить main в приложении 15-15 так, чтобы вызвать функцию drop, как показано в приложении 15-16:

-

Файл: src/main.rs

-
struct CustomSmartPointer {
-    data: String,
-}
-
-impl Drop for CustomSmartPointer {
-    fn drop(&mut self) {
-        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
-    }
-}
-
-fn main() {
-    let c = CustomSmartPointer {
-        data: String::from("some data"),
-    };
-    println!("CustomSmartPointer created.");
-    drop(c);
-    println!("CustomSmartPointer dropped before the end of main.");
-}
-

Приложение 15-16: Вызов std::mem::drop для принудительного удаления значения до того, как оно выйдет из области видимости

-

Выполнение данного кода выведет следующий итог::

-
$ cargo run
-   Compiling drop-example v0.1.0 (file:///projects/drop-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
-     Running `target/debug/drop-example`
-CustomSmartPointer created.
-Dropping CustomSmartPointer with data `some data`!
-CustomSmartPointer dropped before the end of main.
-
-

Текст Dropping CustomSmartPointer with data some data!, напечатанный между CustomSmartPointer created. и текстом CustomSmartPointer dropped before the end of main., показывает, что код способа drop вызывается для удаления c в этой точке.

-

Вы можете использовать код, указанный в выполнения особенности Drop, чтобы сделать очистку удобной и безопасной: например, вы можете использовать её для создания своего собственного управленца памяти! С помощью особенности Drop и системы владения Ржавчина не нужно целенаправленно заботиться о том, чтобы освобождать ресурсы, потому что Ржавчина делает это самостоятельно .

-

Также не нужно беспокоиться о неполадках, возникающих в итоге случайной очистки значений, которые всё ещё используются: система владения, которая заверяет, что ссылки всегда действительны, также заверяет, что drop вызывается только один раз, когда значение больше не используется.

-

После того, как мы познакомились с Box<T> и свойствами умных указателей, познакомимся с другими умными указателями, определёнными в встроенной библиотеке.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch15-04-rc.html b/rustbook-ru/book/ch15-04-rc.html deleted file mode 100644 index dd49e7e97..000000000 --- a/rustbook-ru/book/ch15-04-rc.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - Rc<T>, умный указатель с подсчётом ссылок - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Rc<T>, умный указатель с подсчётом ссылок

-

В большинстве случаев владение является однозначным: вы точно знаете, какая переменная владеет данным значением. Однако бывают случаи, когда у одного значения может быть несколько владельцев. Например, в Графовых устройствах может быть несколько рёбер, указывающих на один и тот же узел — таким образом, этот узел становится в действительности собственностью всех этих рёбер. Узел не подлежит удалению, за исключением тех случаев, когда на него не указывает ни одно ребро и, соответственно, у него нет владельцев.

-

Вы должны включить множественное владение явно, используя вид Ржавчина Rc<T>, который является аббревиатурой для подсчёта ссылок. Вид Rc<T> отслеживает количество ссылок на значение, чтобы определить, используется ли оно ещё. Если ссылок на значение нет, значение может быть очищено и при этом ни одна ссылка не станет недействительной.

-

Представьте себе Rc<T> как телевизор в гостиной. Когда один человек входит, чтобы смотреть телевизор, он включает его. Другие могут войти в комнату и посмотреть телевизор. Когда последний человек покидает комнату, он выключает телевизор, потому что он больше не используется. Если кто-то выключит телевизор во время его просмотра другими, то оставшиеся телезрители устроят шум!

-

Вид Rc<T> используется, когда мы хотим разместить в куче некоторые данные для чтения несколькими частями нашей программы и не можем определить во время сборки, какая из частей завершит использование данных последней. Если бы мы знали, какая часть завершит использование последней то, мы могли бы сделать эту часть владельцем данных и вступили бы в силу обычные правила владения, применяемые во время сборки.

-

Обратите внимание, что Rc<T> используется только в однопоточных сценариях. Когда мы обсудим состязательность в главе 16, мы рассмотрим, как выполнять подсчёт ссылок во многопоточных программах.

-

Использование Rc<T> для совместного использования данных

-

Давайте вернёмся к нашему примеру с cons списком в приложении 15-5. Напомним, что мы определили его с помощью вида Box<T>. В этот раз мы создадим два списка, оба из которых будут владеть третьим списком. Мировозренческо это похоже на рисунок 15-3:

- Two lists that share ownership of a third list -

Рисунок 15-3: Два списка, b и c, делят владение над третьим списком, a

-

Мы создадим список a, содержащий 5 и затем 10. Затем мы создадим ещё два списка: b начинающийся с 3 и c начинающийся с 4. Оба списка b и c затем продолжать первый список a, содержащий 5 и 10. Другими словами, оба списка будут разделять первый список, содержащий 5 и 10.

-

Попытка выполнить этот сценарий, используя определение List с видом Box<T> не будет работать, как показано в приложении 15-17:

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, Box<List>),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-
-fn main() {
-    let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
-    let b = Cons(3, Box::new(a));
-    let c = Cons(4, Box::new(a));
-}
-

Приложение 15-17: Отображение того, что нельзя иметь два списка, использующих Box<T>, которые пытаются совместно владеть третьим списком

-

При сборки этого кода, мы получаем эту ошибку:

-
$ cargo run
-   Compiling cons-list v0.1.0 (file:///projects/cons-list)
-error[E0382]: use of moved value: `a`
-  --> src/main.rs:11:30
-   |
-9  |     let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
-   |         - move occurs because `a` has type `List`, which does not implement the `Copy` trait
-10 |     let b = Cons(3, Box::new(a));
-   |                              - value moved here
-11 |     let c = Cons(4, Box::new(a));
-   |                              ^ value used here after move
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `cons-list` (bin "cons-list") due to 1 previous error
-
-

Исходы Cons владеют данными, которые они содержат, поэтому, когда мы создаём список b, то a перемещается в b, а b становится владельцем a. Затем, мы пытаемся использовать a снова при создании c, но нам не разрешают, потому что a был перемещён.

-

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

-

Вместо этого мы изменим наше определение вида List так, чтобы использовать Rc<T> вместо Box<T>, как показано в приложении 15-18. Каждый исход Cons теперь будет содержать значение и вид Rc<T>, указывающий на List. Когда мы создадим b то, вместо того чтобы стал владельцем a, мы будем клонировать Rc<List> который содержит a, тем самым увеличивая количество ссылок с единицы до двойки и позволяя переменным a и b разделять владение на данные в виде Rc<List>. Мы также клонируем a при создании c, увеличивая количество ссылок с двух до трёх. Каждый раз, когда мы вызываем Rc::clone, счётчик ссылок на данные внутри Rc<List> будет увеличиваться и данные не будут очищены, если на них нет нулевых ссылок.

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, Rc<List>),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-use std::rc::Rc;
-
-fn main() {
-    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
-    let b = Cons(3, Rc::clone(&a));
-    let c = Cons(4, Rc::clone(&a));
-}
-

Приложение 15-18: Определение List, использующее Rc<T>

-

Нам нужно добавить указанию use, чтобы подключить вид Rc<T> в область видимости, потому что он не входит в список самостоятельного подключения прелюдии. В main, мы создаём список владеющий 5 и 10, сохраняем его в новом Rc<List> переменной a. Затем при создании b и c, мы называем функцию Rc::clone и передаём ей ссылку на Rc<List> как переменная a.

-

Мы могли бы вызвать a.clone(), а не Rc::clone(&a), но в Ржавчина принято использовать Rc::clone в таком случае. Внутренняя выполнение Rc::clone не делает глубокого повторения всех данных, как это происходит в видах большинства выполнений clone. Вызов Rc::clone только увеличивает счётчик ссылок, что не занимает много времени. Глубокое повторение данных может занимать много времени. Используя Rc::clone для подсчёта ссылок, можно визуально различать виды клонирования с глубоким повторением и клонирования, которые увеличивают количество ссылок. При поиске в коде неполадок с производительностью нужно рассмотреть только клонирование с глубоким повторением и пренебрегать вызовы Rc::clone .

-

Клонирование Rc<T> увеличивает количество ссылок

-

Давайте изменим рабочий пример в приложении 15-18, чтобы увидеть как изменяется число ссылок при создании и удалении ссылок на Rc<List> внутри переменной a.

-

В приложении 15-19 мы изменим main так, чтобы она имела внутреннюю область видимости вокруг списка c; тогда мы сможем увидеть, как меняется счётчик ссылок при выходе c из внутренней области видимости.

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, Rc<List>),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-use std::rc::Rc;
-
-fn main() {
-    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
-    println!("count after creating a = {}", Rc::strong_count(&a));
-    let b = Cons(3, Rc::clone(&a));
-    println!("count after creating b = {}", Rc::strong_count(&a));
-    {
-        let c = Cons(4, Rc::clone(&a));
-        println!("count after creating c = {}", Rc::strong_count(&a));
-    }
-    println!("count after c goes out of scope = {}", Rc::strong_count(&a));
-}
-

Приложение 15-19: Печать количества ссылок

-

В каждой части программы, где количество ссылок меняется, мы выводим количество ссылок, которое получаем, вызывая функцию Rc::strong_count. Эта функция названа strong_count, а не count, потому что вид Rc<T> также имеет weak_count; мы увидим, для чего используется weak_count в разделе "Предотвращение замкнутых ссылок: Превращение Rc<T> в Weak<T>".

-

Код выводит в окно вывода:

-
$ cargo run
-   Compiling cons-list v0.1.0 (file:///projects/cons-list)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.45s
-     Running `target/debug/cons-list`
-count after creating a = 1
-count after creating b = 2
-count after creating c = 3
-count after c goes out of scope = 2
-
-

Можно увидеть, что Rc<List> в переменной a имеет начальный счётчик ссылок равный 1; затем каждый раз при вызове clone счётчик увеличивается на 1. Когда c выходит из области видимости, счётчик уменьшается на 1. Нам не нужно вызывать функцию уменьшения счётчика ссылок, как при вызове Rc::clone для увеличения счётчика ссылок: выполнение Drop самостоятельно уменьшает счётчик ссылок, когда значение Rc<T> выходит из области видимости.

-

В этом примере мы не наблюдаем того, что когда b, а затем a выходят из области видимости в конце main, счётчик становится равным 0, и Rc<List> полностью очищается. Использование Rc<T> позволяет одному значению иметь несколько владельцев, а счётчик заверяет, что значение остаётся действительным до тех пор, пока любой из владельцев ещё существует.

-

С помощью неизменяемых ссылок, вид Rc<T> позволяет обмениваться данными между несколькими частями вашей программы только для чтения данных. Если вид Rc<T> позволял бы иметь несколько изменяемых ссылок, вы могли бы нарушить одно из правил заимствования, описанных в главе 4: множественные изменяемые заимствования в одном и том же месте могут вызвать гонки данных (data races) и несогласованность данных. Но возможность изменять данные очень полезна! В следующем разделе мы обсудим образец внутренней изменчивости и вид RefCell<T>, который можно использовать вместе с Rc<T> для работы с этим ограничением.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch15-05-interior-mutability.html b/rustbook-ru/book/ch15-05-interior-mutability.html deleted file mode 100644 index 2b7e66e56..000000000 --- a/rustbook-ru/book/ch15-05-interior-mutability.html +++ /dev/null @@ -1,651 +0,0 @@ - - - - - - RefCell<T> и внутренняя изменяемость - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

RefCell<T> и образец внутренней изменяемости

-

Внутренняя изменяемость - это образец разработки Rust, который позволяет вам изменять данные даже при наличии неизменяемых ссылок на эти данные; обычно такое действие запрещено правилами заимствования. Для изменения данных образец использует unsafe код внутри устройства данных, чтобы обойти обычные правила Rust, управляющие изменяемость и заимствование. Небезопасный (unsafe) код даёт понять сборщику, что мы самостоятельно следим за соблюдением этих правил, а не полагаемся на то, что сборщик будет делать это для нас; подробнее о небезопасном коде мы поговорим в главе 19.

-

Мы можем использовать виды, в которых применяется образец внутренней изменяемости, только если мы можем обеспечить, что правила заимствования будут соблюдаться во время выполнения, несмотря на то, что сборщик не сможет этого обеспечить. В этом случае небезопасный код оборачивается безопасным API, и внешне вид остаётся неизменяемым.

-

Давайте изучим данную подход с помощью вида данных RefCell<T>, который выполняет этот образец.

-

Применение правил заимствования во время выполнения с помощью RefCell<T>

-

В отличие от Rc<T> вид RefCell<T> предоставляет единоличное владение данными, которые он содержит. В чем же отличие вида RefCell<T> от Box<T>? Давайте вспомним правила заимствования из Главы 4:

-
    -
  • В любой мгновение времени вы можете иметь либо одну изменяемую ссылку либо сколько угодно неизменяемых ссылок (но не оба вида ссылок одновременно).
  • -
  • Ссылки всегда должны быть действительными.
  • -
-

С помощью ссылок и вида Box<T> неизменные величины правил заимствования применяются на этапе сборки. С помощью RefCell<T> они применяются во время работы программы. Если вы нарушите эти правила, работая с ссылками, то будет ошибка сборки. Если вы работаете с RefCell<T> и нарушите эти правила, то программа вызовет панику и завершится.

-

Преимущества проверки правил заимствования во время сборки заключаются в том, что ошибки будут обнаруживаться раньше - ещё в этапе разработки, а производительность во время выполнения не пострадает, поскольку весь анализ завершён заранее. По этим причинам проверка правил заимствования во время сборки является лучшим выбором в большинстве случаев, и именно поэтому она используется в Ржавчина по умолчанию.

-

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

-

Поскольку некоторый анализ невозможен, то если сборщик Ржавчина не может быть уверен, что код соответствует правилам владения, он может отклонить правильную программу; таким образом он является консервативным. Если Ржавчина принял неправильную программу, то пользователи не смогут доверять заверениям, которые даёт Rust. Однако, если Ржавчина отклонит правильную программу, то программист будет испытывать неудобства, но ничего катастрофического не произойдёт. Вид RefCell<T> полезен, когда вы уверены, что ваш код соответствует правилам заимствования, но сборщик не может понять и обеспечить этого.

-

Подобно виду Rc<T>, вид RefCell<T> предназначен только для использования в однопоточных сценариях и выдаст ошибку времени сборки, если вы попытаетесь использовать его в многопоточном среде. Мы поговорим о том, как получить возможность RefCell<T> во многопоточной программе в главе 16.

-

Вот список причин выбора видов Box<T>, Rc<T> или RefCell<T>:

-
    -
  • Вид Rc<T> разрешает множественное владение одними и теми же данными; виды Box<T> и RefCell<T> разрешают иметь единственных владельцев.
  • -
  • Вид Box<T> разрешает неизменяемые или изменяемые владения, проверенные при сборки; вид Rc<T> разрешает только неизменяемые владения, проверенные при сборки; вид RefCell<T> разрешает неизменяемые или изменяемые владения, проверенные во время выполнения.
  • -
  • Поскольку RefCell<T> разрешает изменяемые заимствования, проверенные во время выполнения, можно изменять значение внутри RefCell<T> даже если RefCell<T> является неизменным.
  • -
-

Изменение значения внутри неизменного значения является образцом внутренней изменяемости (interior mutability). Давайте посмотрим на случай, в которой внутренняя изменяемость полезна и рассмотрим, как это возможно.

-

Внутренняя изменяемость: изменяемое заимствование неизменяемого значения

-

Следствием правил заимствования является то, что когда у вас есть неизменяемое значение, вы не можете заимствовать его с изменением. Например, этот код не будет собираться:

-
fn main() {
-    let x = 5;
-    let y = &mut x;
-}
-

Если вы попытаетесь собрать этот код, вы получите следующую ошибку:

-
$ cargo run
-   Compiling borrowing v0.1.0 (file:///projects/borrowing)
-error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
- --> src/main.rs:3:13
-  |
-3 |     let y = &mut x;
-  |             ^^^^^^ cannot borrow as mutable
-  |
-help: consider changing this to be mutable
-  |
-2 |     let mut x = 5;
-  |         +++
-
-For more information about this error, try `rustc --explain E0596`.
-error: could not compile `borrowing` (bin "borrowing") due to 1 previous error
-
-

Однако бывают случаи, в которых было бы полезно, чтобы предмет мог изменять себя при помощи своих способов, но казался неизменным для прочего кода. Код вне способов этого предмета не должен иметь возможности изменять его содержимое. Использование RefCell<T> - один из способов получить возможность внутренней изменяемости, но при этом RefCell<T> не позволяет полностью обойти правила заимствования: средство проверки правил заимствования в сборщике позволяет эту внутреннюю изменяемость, однако правила заимствования проверяются во время выполнения. Если вы нарушите правила, то вместо ошибки сборки вы получите panic!.

-

Давайте разберём опытный пример, в котором мы можем использовать RefCell<T> для изменения неизменяемого значения и посмотрим, почему это полезно.

-

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

-

Иногда во время проверки программист использует один вид вместо другого для того, чтобы проверить определённое поведение и убедиться, что оно выполнено правильно. Такой вид-заместитель называется проверочным повторителем. Воспринимайте его как «каскадёра» в кинематографе, когда повторитель заменяет актёра для выполнения определённой сложной сцены. Проверочные повторители заменяют другие виды при выполнении проверок. Инсценировочные (mock) предметы — это особый вид проверочных повторителей, которые сохраняют данные происходящих во время проверки действий тем самым позволяя вам убедиться впоследствии, что все действия были выполнены правильно.

-

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

-

Вот сценарий, который мы будем проверять: мы создадим библиотеку, которая отслеживает значение по отношению к заранее определённому наивысшему значению и отправляет сообщения в зависимости от того, насколько текущее значение находится близко к такому наивысшему значению. Эта библиотека может использоваться, например, для отслеживания квоты количества вызовов API пользователя, которые ему разрешено делать.

-

Наша библиотека будет предоставлять только функции отслеживания того, насколько близко к наивысшему значению находится значение и какие сообщения должны быть внутри в этот мгновение. Ожидается, что приложения, использующие нашу библиотеку, предоставят рычаг для отправки сообщений: приложение может поместить сообщение в приложение, отправить электронное письмо, отправить текстовое сообщение или что-то ещё. Библиотеке не нужно знать эту подробность. Все что ему нужно - это что-то, что выполняет особенность, который мы предоставим с названием Messenger. Приложение 15-20 показывает код библиотеки:

-

Файл: src/lib.rs

-
pub trait Messenger {
-    fn send(&self, msg: &str);
-}
-
-pub struct LimitTracker<'a, T: Messenger> {
-    messenger: &'a T,
-    value: usize,
-    max: usize,
-}
-
-impl<'a, T> LimitTracker<'a, T>
-where
-    T: Messenger,
-{
-    pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
-        LimitTracker {
-            messenger,
-            value: 0,
-            max,
-        }
-    }
-
-    pub fn set_value(&mut self, value: usize) {
-        self.value = value;
-
-        let percentage_of_max = self.value as f64 / self.max as f64;
-
-        if percentage_of_max >= 1.0 {
-            self.messenger.send("Error: You are over your quota!");
-        } else if percentage_of_max >= 0.9 {
-            self.messenger
-                .send("Urgent warning: You've used up over 90% of your quota!");
-        } else if percentage_of_max >= 0.75 {
-            self.messenger
-                .send("Warning: You've used up over 75% of your quota!");
-        }
-    }
-}
-

Приложение 15-20: Библиотека для отслеживания степени приближения того или иного значения к наиболее допустимой величине и предупреждения, в случае если значение достигает определённого уровня

-

Одна важная часть этого кода состоит в том, что особенность Messenger имеет один способ send, принимающий переменнойми неизменяемую ссылку на self и текст сообщения. Он является внешней оболочкой, который должен иметь наш мок предмет. Другой важной частью является то, что мы хотим проверить поведение способа set_value у вида LimitTracker. Мы можем изменить значение, которое передаём свойствоом value, но set_value ничего не возвращает и нет основания, чтобы мы могли бы проверить утверждения о выполнении способа. Мы хотим иметь возможность сказать, что если мы создаём LimitTracker с чем-то, что выполняет особенность Messenger и с определённым значением для max, то когда мы передаём разные числа в переменной value образец self.messenger отправляет соответствующие сообщения.

-

Нам нужен мок предмет, который вместо отправки электронного письма или текстового сообщения будет отслеживать сообщения, которые были ему поручены для отправки через send. Мы можем создать новый образец мок предмета. создать LimitTracker с использованием мок предмет для него, вызвать способ set_value у образца LimitTracker, а затем проверить, что мок предмет имеет ожидаемое сообщение. В приложении 15-21 показана попытка выполнить мок предмет, чтобы сделать именно то что хотим, но анализатор заимствований не разрешит такой код:

-

Файл: src/lib.rs

-
pub trait Messenger {
-    fn send(&self, msg: &str);
-}
-
-pub struct LimitTracker<'a, T: Messenger> {
-    messenger: &'a T,
-    value: usize,
-    max: usize,
-}
-
-impl<'a, T> LimitTracker<'a, T>
-where
-    T: Messenger,
-{
-    pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
-        LimitTracker {
-            messenger,
-            value: 0,
-            max,
-        }
-    }
-
-    pub fn set_value(&mut self, value: usize) {
-        self.value = value;
-
-        let percentage_of_max = self.value as f64 / self.max as f64;
-
-        if percentage_of_max >= 1.0 {
-            self.messenger.send("Error: You are over your quota!");
-        } else if percentage_of_max >= 0.9 {
-            self.messenger
-                .send("Urgent warning: You've used up over 90% of your quota!");
-        } else if percentage_of_max >= 0.75 {
-            self.messenger
-                .send("Warning: You've used up over 75% of your quota!");
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    struct MockMessenger {
-        sent_messages: Vec<String>,
-    }
-
-    impl MockMessenger {
-        fn new() -> MockMessenger {
-            MockMessenger {
-                sent_messages: vec![],
-            }
-        }
-    }
-
-    impl Messenger for MockMessenger {
-        fn send(&self, message: &str) {
-            self.sent_messages.push(String::from(message));
-        }
-    }
-
-    #[test]
-    fn it_sends_an_over_75_percent_warning_message() {
-        let mock_messenger = MockMessenger::new();
-        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
-
-        limit_tracker.set_value(80);
-
-        assert_eq!(mock_messenger.sent_messages.len(), 1);
-    }
-}
-

Приложение 15-21: Попытка выполнить MockMessenger, которая не была принята рычагом проверки заимствований

-

Этот проверочный код определяет устройство MockMessenger, в которой есть поле sent_messages со значениями вида Vec из String для отслеживания сообщений, которые поручены устройстве для отправки. Мы также определяем сопряженную функцию new, чтобы было удобно создавать новые образцы MockMessenger, которые создаются с пустым списком сообщений. Затем мы выполняем особенность Messenger для вида MockMessenger, чтобы передать MockMessenger в LimitTracker. В ярлыке способа send мы принимаем сообщение для передачи в качестве свойства и сохраняем его в MockMessenger внутри списка sent_messages.

-

В этом проверке мы проверяем, что происходит, когда LimitTracker сказано установить value в значение, превышающее 75 процентов от значения max. Сначала мы создаём новый MockMessenger, который будет иметь пустой список сообщений. Затем мы создаём новый LimitTracker и передаём ему ссылку на новый MockMessenger и max значение равное 100. Мы вызываем способ set_value у LimitTracker со значением 80, что составляет более 75 процентов от 100. Затем мы с помощью утверждения проверяем, что MockMessenger должен содержать одно сообщение из списка внутренних сообщений.

-

Однако с этим проверкой есть одна неполадка, показанная ниже:

-
$ cargo test
-   Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
-error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference
-  --> src/lib.rs:58:13
-   |
-58 |             self.sent_messages.push(String::from(message));
-   |             ^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
-   |
-help: consider changing this to be a mutable reference in the `impl` method and the `trait` definition
-   |
-2  ~     fn send(&mut self, msg: &str);
-3  | }
- ...
-56 |     impl Messenger for MockMessenger {
-57 ~         fn send(&mut self, message: &str) {
-   |
-
-For more information about this error, try `rustc --explain E0596`.
-error: could not compile `limit-tracker` (lib test) due to 1 previous error
-
-

Мы не можем изменять MockMessenger для отслеживания сообщений, потому что способ send принимает неизменяемую ссылку на self. Мы также не можем принять предложение из текста ошибки, чтобы использовать &mut self, потому что тогда ярлык send не будет соответствовать ярлыке в определении особенности Messenger (не стесняйтесь попробовать и посмотреть, какое сообщение об ошибке получите вы).

-

Это случаей, в которой внутренняя изменяемость может помочь! Мы сохраним sent_messages внутри вида RefCell<T>, а затем в способе send сообщение сможет изменить список sent_messages для хранения сообщений, которые мы видели. Приложение 15-22 показывает, как это выглядит:

-

Файл: src/lib.rs

-
pub trait Messenger {
-    fn send(&self, msg: &str);
-}
-
-pub struct LimitTracker<'a, T: Messenger> {
-    messenger: &'a T,
-    value: usize,
-    max: usize,
-}
-
-impl<'a, T> LimitTracker<'a, T>
-where
-    T: Messenger,
-{
-    pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
-        LimitTracker {
-            messenger,
-            value: 0,
-            max,
-        }
-    }
-
-    pub fn set_value(&mut self, value: usize) {
-        self.value = value;
-
-        let percentage_of_max = self.value as f64 / self.max as f64;
-
-        if percentage_of_max >= 1.0 {
-            self.messenger.send("Error: You are over your quota!");
-        } else if percentage_of_max >= 0.9 {
-            self.messenger
-                .send("Urgent warning: You've used up over 90% of your quota!");
-        } else if percentage_of_max >= 0.75 {
-            self.messenger
-                .send("Warning: You've used up over 75% of your quota!");
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use std::cell::RefCell;
-
-    struct MockMessenger {
-        sent_messages: RefCell<Vec<String>>,
-    }
-
-    impl MockMessenger {
-        fn new() -> MockMessenger {
-            MockMessenger {
-                sent_messages: RefCell::new(vec![]),
-            }
-        }
-    }
-
-    impl Messenger for MockMessenger {
-        fn send(&self, message: &str) {
-            self.sent_messages.borrow_mut().push(String::from(message));
-        }
-    }
-
-    #[test]
-    fn it_sends_an_over_75_percent_warning_message() {
-        // --snip--
-        let mock_messenger = MockMessenger::new();
-        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
-
-        limit_tracker.set_value(80);
-
-        assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
-    }
-}
-

Приложение 15-22: Использование RefCell<T> для изменения внутреннего значения, в то время как внешнее значение считается неизменяемым

-

Поле sent_messages теперь имеет вид RefCell<Vec<String>> вместо Vec<String>. В функции new мы создаём новый образец RefCell<Vec<String>> для пустого вектора.

-

Для выполнения способа send первый свойство по-прежнему является неизменяемым для заимствования self, которое соответствует определению особенности. Мы вызываем borrow_mut для RefCell<Vec<String>> в self.sent_messages, чтобы получить изменяемую ссылку на значение внутри RefCell<Vec<String>>, которое является вектором. Затем мы можем вызвать push у изменяемой ссылки на вектор, чтобы отслеживать сообщения, отправленные во время проверки.

-

Последнее изменение, которое мы должны сделать, заключается в утверждении для проверки: чтобы увидеть, сколько элементов находится во внутреннем векторе, мы вызываем способ borrow у RefCell<Vec<String>>, чтобы получить неизменяемую ссылку на внутренний вектор сообщений.

-

Теперь, когда вы увидели как использовать RefCell<T>, давайте изучим как он работает!

-

Отслеживание заимствований во время выполнения с помощью RefCell<T>

-

При создании неизменных и изменяемых ссылок мы используем правила написания & и &mut соответственно. У вида RefCell<T>, мы используем способы borrow и borrow_mut, которые являются частью безопасного API, который принадлежит RefCell<T>. Способ borrow возвращает вид умного указателя Ref<T>, способ borrow_mut возвращает вид умного указателя RefMut<T>. Оба вида выполняют особенность Deref, поэтому мы можем рассматривать их как обычные ссылки.

-

Вид RefCell<T> отслеживает сколько умных указателей Ref<T> и RefMut<T> активны в данное время. Каждый раз, когда мы вызываем borrow, вид RefCell<T> увеличивает количество активных заимствований. Когда значение Ref<T> выходит из области видимости, то количество неизменяемых заимствований уменьшается на единицу. Как и с правилами заимствования во время сборки, RefCell<T> позволяет иметь много неизменяемых заимствований или одно изменяемое заимствование в любой мгновение времени.

-

Если попытаться нарушить эти правила, то вместо получения ошибки сборщика, как это было бы со ссылками, выполнение RefCell<T> будет вызывать панику во время выполнения. В приложении 15-23 показана изменение выполнения send из приложения 15-22. Мы намеренно пытаемся создать два изменяемых заимствования активных для одной и той же области видимости, чтобы показать как RefCell<T> не позволяет нам делать так во время выполнения.

-

Файл: src/lib.rs

-
pub trait Messenger {
-    fn send(&self, msg: &str);
-}
-
-pub struct LimitTracker<'a, T: Messenger> {
-    messenger: &'a T,
-    value: usize,
-    max: usize,
-}
-
-impl<'a, T> LimitTracker<'a, T>
-where
-    T: Messenger,
-{
-    pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
-        LimitTracker {
-            messenger,
-            value: 0,
-            max,
-        }
-    }
-
-    pub fn set_value(&mut self, value: usize) {
-        self.value = value;
-
-        let percentage_of_max = self.value as f64 / self.max as f64;
-
-        if percentage_of_max >= 1.0 {
-            self.messenger.send("Error: You are over your quota!");
-        } else if percentage_of_max >= 0.9 {
-            self.messenger
-                .send("Urgent warning: You've used up over 90% of your quota!");
-        } else if percentage_of_max >= 0.75 {
-            self.messenger
-                .send("Warning: You've used up over 75% of your quota!");
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use std::cell::RefCell;
-
-    struct MockMessenger {
-        sent_messages: RefCell<Vec<String>>,
-    }
-
-    impl MockMessenger {
-        fn new() -> MockMessenger {
-            MockMessenger {
-                sent_messages: RefCell::new(vec![]),
-            }
-        }
-    }
-
-    impl Messenger for MockMessenger {
-        fn send(&self, message: &str) {
-            let mut one_borrow = self.sent_messages.borrow_mut();
-            let mut two_borrow = self.sent_messages.borrow_mut();
-
-            one_borrow.push(String::from(message));
-            two_borrow.push(String::from(message));
-        }
-    }
-
-    #[test]
-    fn it_sends_an_over_75_percent_warning_message() {
-        let mock_messenger = MockMessenger::new();
-        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
-
-        limit_tracker.set_value(80);
-
-        assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
-    }
-}
-

Приложение 15-23: Создание двух изменяемых ссылок в одной области видимости, чтобы убедиться, что RefCell<T> вызовет панику

-

Мы создаём переменную one_borrow для умного указателя RefMut<T> возвращаемого из способа borrow_mut. Затем мы создаём другое изменяемое заимствование таким же образом в переменной two_borrow. Это создаёт две изменяемые ссылки в одной области видимости, что недопустимо. Когда мы запускаем проверки для нашей библиотеки, код в приложении 15-23 собирается без ошибок, но проверка завершится неудачно:

-
$ cargo test
-   Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s
-     Running unittests src/lib.rs (target/debug/deps/limit_tracker-e599811fa246dbde)
-
-running 1 test
-test tests::it_sends_an_over_75_percent_warning_message ... FAILED
-
-failures:
-
----- tests::it_sends_an_over_75_percent_warning_message stdout ----
-thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at src/lib.rs:60:53:
-already borrowed: BorrowMutError
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::it_sends_an_over_75_percent_warning_message
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Обратите внимание, что код вызвал панику с сообщением already borrowed: BorrowMutError. Вот так вид RefCell<T> обрабатывает нарушения правил заимствования во время выполнения.

-

Решение отлавливать ошибки заимствования во время выполнения, а не во время сборки, как мы сделали здесь, означает, что вы возможно будете находить ошибки в своём коде на более поздних этапах разработки: возможно, не раньше, чем ваш код будет развернут в рабочем окружении. Кроме того, ваш код будет иметь небольшие потери производительности в этапе работы, поскольку заимствования будут отслеживаться во время выполнения, а не во время сборки. Однако использование RefCell<T> позволяет написать предмет-имитатор, который способен изменять себя, чтобы сохранять сведения о тех значениях, которые он получал, пока вы использовали его в среде, где разрешены только неизменяемые значения. Вы можете использовать RefCell<T>, несмотря на его недостатки, чтобы получить больше возможности, чем дают обычные ссылки.

-

Наличие нескольких владельцев изменяемых данных путём объединения видов Rc<T> и RefCell<T>

-

Обычный способ использования RefCell<T> заключается в его сочетании с видом Rc<T>. Напомним, что вид Rc<T> позволяет иметь нескольких владельцев некоторых данных, но даёт только неизменяемый доступ к этим данным. Если у вас есть Rc<T>, который внутри содержит вид RefCell<T>, вы можете получить значение, которое может иметь несколько владельцев и которое можно изменять!

-

Например, вспомните пример cons списка приложения 15-18, где мы использовали Rc<T>, чтобы несколько списков могли совместно владеть другим списком. Поскольку Rc<T> содержит только неизменяемые значения, мы не можем изменить ни одно из значений в списке после того, как мы их создали. Давайте добавим вид RefCell<T>, чтобы получить возможность изменять значения в списках. В приложении 15-24 показано использование RefCell<T> в определении Cons так, что мы можем изменить значение хранящееся во всех списках:

-

Файл: src/main.rs

-
#[derive(Debug)]
-enum List {
-    Cons(Rc<RefCell<i32>>, Rc<List>),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-use std::cell::RefCell;
-use std::rc::Rc;
-
-fn main() {
-    let value = Rc::new(RefCell::new(5));
-
-    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
-
-    let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
-    let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));
-
-    *value.borrow_mut() += 10;
-
-    println!("a after = {a:?}");
-    println!("b after = {b:?}");
-    println!("c after = {c:?}");
-}
-

Приложение 15-24: Использование Rc<RefCell<i32>> для создания List, который мы можем изменять

-

Мы создаём значение, которое является образцом Rc<RefCell<i32>> и сохраняем его в переменной с именем value, чтобы получить к ней прямой доступ позже. Затем мы создаём List в переменной a с исходом Cons, который содержит value. Нам нужно вызвать клонирование value, так как обе переменные a и value владеют внутренним значением 5, а не передают владение из value в переменную a или не выполняют заимствование с помощью a переменной value.

-

Мы оборачиваем список у переменной a в вид Rc<T>, поэтому при создании списков в переменные b и c они оба могут ссылаться на a, что мы и сделали в приложении 15-18.

-

После создания списков a, b и c мы хотим добавить 10 к значению в value. Для этого вызовем borrow_mut у value, который использует функцию самостоятельного разыменования, о которой мы говорили в главе 5 (см. раздел "Где находится оператор ->?") во внутреннее значение RefCell<T>. Способ borrow_mut возвращает умный указатель RefMut<T>, и мы используя оператор разыменования, изменяем внутреннее значение.

-

Когда мы печатаем a, b и c то видим, что все они имеют изменённое значение равное 15, а не 5:

-
$ cargo run
-   Compiling cons-list v0.1.0 (file:///projects/cons-list)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.63s
-     Running `target/debug/cons-list`
-a after = Cons(RefCell { value: 15 }, Nil)
-b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))
-c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))
-
-

Эта техника довольно изящна! Используя RefCell<T>, мы получаем внешне неизменяемое значение List. Но мы можем использовать способы RefCell<T>, которые предоставляют доступ к его внутренностям, чтобы мы могли изменять наши данные, когда это необходимо. Проверка правил заимствования во время выполнения защищает нас от гонок данных, и иногда стоит немного пожертвовать производительностью ради такой гибкости наших устройств данных. Обратите внимание, что RefCell<T> не работает для многопоточного кода! Mutex<T> - это thread-safe исполнение RefCell<T>, а Mutex<T> мы обсудим в главе 16.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch15-06-reference-cycles.html b/rustbook-ru/book/ch15-06-reference-cycles.html deleted file mode 100644 index c44b7e4b4..000000000 --- a/rustbook-ru/book/ch15-06-reference-cycles.html +++ /dev/null @@ -1,519 +0,0 @@ - - - - - - Ссылочные циклы могут привести к утечке памяти - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

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

-

Заверения безопасности памяти в Ржавчина затрудняют, но не делают невозможным случайное выделение памяти, которое никогда не очищается (известное как утечка памяти ). Полное предотвращение утечек памяти не является одной из заверений Rust, а это означает, что утечки памяти безопасны в Rust. Мы видим, что Ржавчина допускает утечку памяти с помощью Rc<T> и RefCell<T>: можно создавать ссылки, в которых элементы ссылаются друг на друга в цикле. Это создаёт утечки памяти, потому что счётчик ссылок каждого элемента в цикле никогда не достигнет 0, а значения никогда не будут удалены.

-

Создание ссылочного замыкания

-

Давайте посмотрим, как может произойти случаей ссылочного замыкания и как её предотвратить, начиная с определения перечисления List и способа tail в приложении 15-25:

-

Файл: src/main.rs

-
use crate::List::{Cons, Nil};
-use std::cell::RefCell;
-use std::rc::Rc;
-
-#[derive(Debug)]
-enum List {
-    Cons(i32, RefCell<Rc<List>>),
-    Nil,
-}
-
-impl List {
-    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
-        match self {
-            Cons(_, item) => Some(item),
-            Nil => None,
-        }
-    }
-}
-
-fn main() {}
-

Приложение 15-25: Объявление cons list, который содержит RefCell<T>, чтобы мы могли изменять то, на что ссылается образец Cons

-

Мы используем другую вариацию определения List из приложения 15-5. Второй элемент в исходе Cons теперь RefCell<Rc<List>>, что означает, что вместо возможности менять значение i32, как мы делали в приложении 15-24, мы хотим менять значение List, на которое указывает исход Cons. Мы также добавляем способ tail, чтобы нам было удобно обращаться ко второму элементу, если у нас есть исход Cons.

-

В приложении 15-26 мы добавляем main функцию, которая использует определения приложения 15-25. Этот код создаёт список в переменной a и список b, который указывает на список a. Затем он изменяет список внутри a так, чтобы он указывал на b, создавая ссылочное замыкание. В коде есть указания println!, чтобы показать значения счётчиков ссылок в различных точках этого этапа.

-

Файл: src/main.rs

-
use crate::List::{Cons, Nil};
-use std::cell::RefCell;
-use std::rc::Rc;
-
-#[derive(Debug)]
-enum List {
-    Cons(i32, RefCell<Rc<List>>),
-    Nil,
-}
-
-impl List {
-    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
-        match self {
-            Cons(_, item) => Some(item),
-            Nil => None,
-        }
-    }
-}
-
-fn main() {
-    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
-
-    println!("a initial rc count = {}", Rc::strong_count(&a));
-    println!("a next item = {:?}", a.tail());
-
-    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
-
-    println!("a rc count after b creation = {}", Rc::strong_count(&a));
-    println!("b initial rc count = {}", Rc::strong_count(&b));
-    println!("b next item = {:?}", b.tail());
-
-    if let Some(link) = a.tail() {
-        *link.borrow_mut() = Rc::clone(&b);
-    }
-
-    println!("b rc count after changing a = {}", Rc::strong_count(&b));
-    println!("a rc count after changing a = {}", Rc::strong_count(&a));
-
-    // Uncomment the next line to see that we have a cycle;
-    // it will overflow the stack
-    // println!("a next item = {:?}", a.tail());
-}
-

Приложение 15-26: Создание ссылочного цикла из двух значений List, указывающих друг на друга

-

Мы создаём образец Rc<List> содержащий значение List в переменной a с начальным списком 5, Nil. Затем мы создаём образец Rc<List> содержащий другое значение List в переменной b, которое содержит значение 10 и указывает на список в a.

-

Мы меняем a так, чтобы он указывал на b вместо Nil, создавая зацикленность. Мы делаем это с помощью способа tail, чтобы получить ссылку на RefCell<Rc<List>> из переменной a, которую мы помещаем в переменную link. Затем мы используем способ borrow_mut из вида RefCell<Rc<List>>, чтобы изменить внутреннее значение вида Rc<List>, содержащего начальное значение Nil на значение вида Rc<List> взятое из переменной b.

-

Когда мы запускаем этот код, оставив последний println! с примечаниями в данный мгновение, мы получим вывод:

-
$ cargo run
-   Compiling cons-list v0.1.0 (file:///projects/cons-list)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.53s
-     Running `target/debug/cons-list`
-a initial rc count = 1
-a next item = Some(RefCell { value: Nil })
-a rc count after b creation = 2
-b initial rc count = 1
-b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
-b rc count after changing a = 2
-a rc count after changing a = 2
-
-

Количество ссылок на образцы Rc<List> как в a, так и в b равно 2 после того, как мы заменили список в a на ссылку на b. В конце main Ржавчина уничтожает переменную b, что уменьшает количество ссылок на Rc<List> из b с 2 до 1. Память, которую Rc<List> занимает в куче, не будет освобождена в этот мгновение, потому что количество ссылок на неё равно 1, а не 0. Затем Ржавчина удаляет a, что уменьшает количество ссылок образца Rc<List> в a с 2 до 1. Память этого образца также не может быть освобождена, поскольку другой образец Rc<List> по-прежнему ссылается на него. Таким образом, память, выделенная для списка не будет освобождена никогда. Чтобы наглядно представить этот цикл ссылок, мы создали диаграмму на рисунке 15-4.

-Reference cycle of lists -

Рисунок 15-4: Ссылочный цикл списков a и b, указывающих друг на друга

-

Если вы удалите последний примечание с println! и запустите программу, Ржавчина будет пытаться печатать зацикленность в a, указывающей на b, указывающей на a и так далее, пока не переполниться обойма.

-

По сравнению с существующей программой, последствия создания цикла ссылок в этом примере не так страшны: сразу после создания цикла ссылок программа завершается. Однако если более сложная программа выделит много памяти в цикле и будет удерживать её в течение длительного времени, программа будет потреблять больше памяти, чем ей нужно, и может перенапрячь систему, что приведёт к исчерпанию доступной памяти.

-

Вызвать образование ссылочной зацикленности не просто, но и не невозможно. Если у вас есть значения RefCell<T> которые содержат значения Rc<T> или подобные вложенные сочетания видов с внутренней изменчивостью и подсчётом ссылок, вы должны убедиться, что вы не создаёте зацикленность; Вы не можете полагаться на то, что Ржавчина их обнаружит. Создание ссылочной зацикленности являлось бы логической ошибкой в программе, для которой вы должны использовать самостоятельно е проверки, проверку кода и другие опытов разработки программного обеспечения для её уменьшения.

-

Другое решение для избежания ссылочной зацикленности - это ресоздание ваших устройств данных, чтобы некоторые ссылки выражали владение, а другие - отсутствие владения. В итоге можно иметь циклы, построенные на некоторых отношениях владения и некоторые не основанные на отношениях владения, тогда только отношения владения влияют на то, можно ли удалить значение. В приложении 15-25 мы всегда хотим, чтобы исходы Cons владели своим списком, поэтому ресоздание устройства данных невозможна. Давайте рассмотрим пример с использованием графов, состоящих из родительских и дочерних узлов, чтобы увидеть, когда отношения владения не являются подходящим способом предотвращения ссылочной зацикленности.

-

Предотвращение ссылочной зацикленности: замена умного указателя Rc<T> на Weak<T>

-

До сих пор мы выясняли, что вызов Rc::clone увеличивает strong_count образца Rc<T>, а образец Rc<T> удаляется, только если его strong_count равен 0. Вы также можете создать слабую ссылку на значение внутри образца Rc<T>, вызвав Rc::downgrade и передав ссылку на Rc<T>. Сильные ссылки - это то с помощью чего вы можете поделиться владением образца Rc<T>. Слабые ссылки не отражают связи владения, и их подсчёт не влияет на то, когда образец Rc<T> будет очищен. Они не приведут к ссылочному циклу, потому что любой цикл, включающий несколько слабых ссылок, будет разорван, как только количество сильных ссылок для задействованных значений станет равным 0.

-

Когда вы вызываете Rc::downgrade, вы получаете умный указатель вида Weak<T>. Вместо того чтобы увеличить strong_count в образце Rc<T> на 1, вызов Rc::downgrade увеличивает weak_count на 1. Вид Rc<T> использует weak_count для отслеживания количества существующих ссылок Weak<T>, подобно strong_count. Разница в том, что weak_count не должен быть равен 0, чтобы образец Rc<T> мог быть удалён.

-

Поскольку значение, на которое ссылается Weak<T> могло быть удалено, то необходимо убедиться, что это значение все ещё существует, чтобы сделать что-либо со значением на которое указывает Weak<T>. Делайте это вызывая способ upgrade у образца вида Weak<T>, который вернёт Option<Rc<T>>. Вы получите итог Some, если значение Rc<T> ещё не было удалено и итог None, если значение Rc<T> было удалено. Поскольку upgrade возвращает вид Option<T>, Ржавчина обеспечит обработку обоих случаев Some и None и не будет неправильного указателя.

-

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

-

Создание древовидной устройства данных: Node с дочерними узлами

-

Для начала мы построим дерево с узлами, которые знают о своих дочерних узлах. Мы создадим устройство с именем Node, которая будет содержать собственное значение i32, а также ссылки на его дочерние значения Node:

-

Файл: src/main.rs

-
use std::cell::RefCell;
-use std::rc::Rc;
-
-#[derive(Debug)]
-struct Node {
-    value: i32,
-    children: RefCell<Vec<Rc<Node>>>,
-}
-
-fn main() {
-    let leaf = Rc::new(Node {
-        value: 3,
-        children: RefCell::new(vec![]),
-    });
-
-    let branch = Rc::new(Node {
-        value: 5,
-        children: RefCell::new(vec![Rc::clone(&leaf)]),
-    });
-}
-

Мы хотим, чтобы Node владел своими дочерними узлами и мы хотим поделиться этим владением с переменными так, чтобы мы могли напрямую обращаться к каждому Node в дереве. Для этого мы определяем внутренние элементы вида Vec<T> как значения вида Rc<Node>. Мы также хотим изменять те узлы, которые являются дочерними по отношению к другому узлу, поэтому у нас есть вид RefCell<T> в поле children оборачивающий вид Vec<Rc<Node>>.

-

Далее мы будем использовать наше определение устройства и создадим один образец Node с именем leaf со значением 3 и без дочерних элементов, а другой образец с именем branch со значением 5 и leaf в качестве одного из его дочерних элементов, как показано в приложении 15-27:

-

Файл: src/main.rs

-
use std::cell::RefCell;
-use std::rc::Rc;
-
-#[derive(Debug)]
-struct Node {
-    value: i32,
-    children: RefCell<Vec<Rc<Node>>>,
-}
-
-fn main() {
-    let leaf = Rc::new(Node {
-        value: 3,
-        children: RefCell::new(vec![]),
-    });
-
-    let branch = Rc::new(Node {
-        value: 5,
-        children: RefCell::new(vec![Rc::clone(&leaf)]),
-    });
-}
-

Приложение 15-27: Создание узла leaf без дочерних элементов и узла branch с leaf в качестве одного из дочерних элементов

-

Мы клонируем содержимое Rc<Node> из переменной leaf и сохраняем его в переменной branch, что означает, что Node в leaf теперь имеет двух владельцев: leaf и branch. Мы можем получить доступ из branch к leaf через обращение branch.children, но нет способа добраться из leaf к branch. Причина в том, что leaf не имеет ссылки на branch и не знает, что они связаны. Мы хотим, чтобы leaf знал, что branch является его родителем. Мы сделаем это далее.

-

Добавление ссылки от ребёнка к его родителю

-

Для того, чтобы дочерний узел знал о своём родительском узле нужно добавить поле parent в наше определение устройства Node. Неполадкав том, чтобы решить, каким должен быть вид parent. Мы знаем, что он не может содержать Rc<T>, потому что это создаст ссылочную зацикленность с leaf.parent указывающей на branch и branch.children, указывающей на leaf, что приведёт к тому, что их значения strong_count никогда не будут равны 0.

-

Подумаем об этих отношениях по-другому, родительский узел должен владеть своими потомками: если родительский узел удаляется, его дочерние узлы также должны быть удалены. Однако дочерний элемент не должен владеть своим родителем: если мы удаляем дочерний узел то родительский элемент все равно должен существовать. Это случай для использования слабых ссылок!

-

Поэтому вместо Rc<T> мы сделаем так, чтобы поле parent использовало вид Weak<T>, а именно RefCell<Weak<Node>>. Теперь наше определение устройства Node выглядит так:

-

Файл: src/main.rs

-
use std::cell::RefCell;
-use std::rc::{Rc, Weak};
-
-#[derive(Debug)]
-struct Node {
-    value: i32,
-    parent: RefCell<Weak<Node>>,
-    children: RefCell<Vec<Rc<Node>>>,
-}
-
-fn main() {
-    let leaf = Rc::new(Node {
-        value: 3,
-        parent: RefCell::new(Weak::new()),
-        children: RefCell::new(vec![]),
-    });
-
-    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
-
-    let branch = Rc::new(Node {
-        value: 5,
-        parent: RefCell::new(Weak::new()),
-        children: RefCell::new(vec![Rc::clone(&leaf)]),
-    });
-
-    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
-
-    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
-}
-

Узел сможет ссылаться на свой родительский узел, но не владеет своим родителем. В приложении 15-28 мы обновляем main на использование нового определения так, чтобы у узла leaf был бы способ ссылаться на его родительский узел branch:

-

Файл: src/main.rs

-
use std::cell::RefCell;
-use std::rc::{Rc, Weak};
-
-#[derive(Debug)]
-struct Node {
-    value: i32,
-    parent: RefCell<Weak<Node>>,
-    children: RefCell<Vec<Rc<Node>>>,
-}
-
-fn main() {
-    let leaf = Rc::new(Node {
-        value: 3,
-        parent: RefCell::new(Weak::new()),
-        children: RefCell::new(vec![]),
-    });
-
-    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
-
-    let branch = Rc::new(Node {
-        value: 5,
-        parent: RefCell::new(Weak::new()),
-        children: RefCell::new(vec![Rc::clone(&leaf)]),
-    });
-
-    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
-
-    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
-}
-

Приложение 15-28: Узел leaf со слабой ссылкой на его родительский узел branch

-

Создание узла leaf выглядит подобно примеру из Приложения 15-27, за исключением поля parent: leaf изначально не имеет родителя, поэтому мы создаём новый, пустой образец ссылки Weak<Node>.

-

На этом этапе, когда мы пытаемся получить ссылку на родительский узел у узла leaf с помощью способа upgrade, мы получаем значение None. Мы видим это в выводе первой указания println!:

-
leaf parent = None
-
-

Когда мы создаём узел branch у него также будет новая ссылка вида Weak<Node> в поле parent, потому что узел branch не имеет своего родительского узла. У нас все ещё есть leaf как один из потомков узла branch. Когда мы получили образец Node в переменной branch, мы можем изменить переменную leaf чтобы дать ей Weak<Node> ссылку на её родителя. Мы используем способ borrow_mut у вида RefCell<Weak<Node>> поля parent у leaf, а затем используем функцию Rc::downgrade для создания Weak<Node> ссылки на branch из Rc<Node> в branch.

-

Когда мы снова напечатаем родителя leaf то в этот раз мы получим исход Some содержащий branch, теперь leaf может получить доступ к своему родителю! Когда мы печатаем leaf, мы также избегаем цикла, который в конечном итоге заканчивался переполнением обоймы, как в приложении 15-26; ссылки вида Weak<Node> печатаются как (Weak):

-
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },
-children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },
-children: RefCell { value: [] } }] } })
-
-

Отсутствие бесконечного вывода означает, что этот код не создал ссылочной зацикленности. Мы также можем сказать это, посмотрев на значения, которые мы получаем при вызове Rc::strong_count и Rc::weak_count.

-

Визуализация изменений в strong_count и weak_count

-

Давайте посмотрим, как изменяются значения strong_count и weak_count образцов вида Rc<Node> с помощью создания новой внутренней области видимости и перемещая создания образца branch в эту область. Таким образом можно увидеть, что происходит, когда branch создаётся и затем удаляется при выходе из области видимости. Изменения показаны в приложении 15-29:

-

Файл: src/main.rs

-
use std::cell::RefCell;
-use std::rc::{Rc, Weak};
-
-#[derive(Debug)]
-struct Node {
-    value: i32,
-    parent: RefCell<Weak<Node>>,
-    children: RefCell<Vec<Rc<Node>>>,
-}
-
-fn main() {
-    let leaf = Rc::new(Node {
-        value: 3,
-        parent: RefCell::new(Weak::new()),
-        children: RefCell::new(vec![]),
-    });
-
-    println!(
-        "leaf strong = {}, weak = {}",
-        Rc::strong_count(&leaf),
-        Rc::weak_count(&leaf),
-    );
-
-    {
-        let branch = Rc::new(Node {
-            value: 5,
-            parent: RefCell::new(Weak::new()),
-            children: RefCell::new(vec![Rc::clone(&leaf)]),
-        });
-
-        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
-
-        println!(
-            "branch strong = {}, weak = {}",
-            Rc::strong_count(&branch),
-            Rc::weak_count(&branch),
-        );
-
-        println!(
-            "leaf strong = {}, weak = {}",
-            Rc::strong_count(&leaf),
-            Rc::weak_count(&leaf),
-        );
-    }
-
-    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
-    println!(
-        "leaf strong = {}, weak = {}",
-        Rc::strong_count(&leaf),
-        Rc::weak_count(&leaf),
-    );
-}
-

Приложение 15-29: Создание branch во внутренней области видимости и подсчёт сильных и слабых ссылок

-

После того, как leaf создан его Rc<Node> имеет значения strong count равное 1 и weak count равное 0. Во внутренней области мы создаём branch и связываем её с leaf, после чего при печати значений счётчиков Rc<Node> в branch они будет иметь strong count 1 и weak count 1 (для leaf.parent указывающего на branch с Weak<Node> ). Когда мы распечатаем счётчики из leaf, мы увидим, что они будут иметь strong count 2, потому что branch теперь имеет клон Rc<Node> переменной leaf хранящийся в branch.children, но все равно будет иметь weak count 0.

-

Когда заканчивается внутренняя область видимости, branch выходит из области видимости и strong count Rc<Node> уменьшается до 0, поэтому его Node удаляется. Weak count 1 из leaf.parent не имеет никакого отношения к тому, был ли Node удалён, поэтому не будет никаких утечек памяти!

-

Если мы попытаемся получить доступ к родителю переменной leaf после окончания области видимости, мы снова получим значение None. В конце программы Rc<Node> внутри leaf имеет strong count 1 и weak count 0 потому что переменная leaf снова является единственной ссылкой на Rc<Node>.

-

Вся логика, которая управляет счётчиками и сбросом их значений, встроена внутри Rc<T> и Weak<T> и их выполнений особенности Drop. Указав, что отношение из дочернего к родительскому элементу должно быть ссылкой вида Weak<T> в определении Node, делает возможным иметь родительские узлы, указывающие на дочерние узлы и наоборот, не создавая ссылочной зацикленности и утечек памяти.

-

Итоги

-

В этой главе рассказано как использовать умные указатели для обеспечения различных заверений и соглашений по сравнению с обычными ссылками, которые Ржавчина использует по умолчанию. Вид Box<T> имеет известный размер и указывает на данные размещённые в куче. Вид Rc<T> отслеживает количество ссылок на данные в куче, поэтому данные могут иметь несколько владельцев. Вид RefCell<T> с его внутренней изменяемостью предоставляет вид, который можно использовать при необходимости неизменного вида, но необходимости изменить внутреннее значение этого типа; он также обеспечивает соблюдение правил заимствования во время выполнения, а не во время сборки.

-

Мы обсудили также особенности Deref и Drop, которые обеспечивают большую возможность умных указателей. Мы исследовали ссылочную зацикленность, которая может вызывать утечки памяти и как это предотвратить с помощью вида Weak<T>.

-

Если эта глава вызвала у вас влечение и вы хотите выполнить свои собственные умные указатели, обратитесь к "The Rustonomicon" за более полезной сведениями.

-

Далее мы поговорим о одновременности в Rust. Вы даже узнаете о нескольких новых умных указателях.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch16-00-concurrency.html b/rustbook-ru/book/ch16-00-concurrency.html deleted file mode 100644 index 3c347151b..000000000 --- a/rustbook-ru/book/ch16-00-concurrency.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - Безбоязненный одновременность - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Многопоточность без страха

-

Безопасное и эффективное управление многопоточным программированием — ещё одна из основных целей Rust. Многопоточное программирование, когда разные части программы выполняются независимо, и одновременное программирование, когда разные части программы выполняются одновременно, становятся всё более важными, поскольку всё больше компьютеров используют преимущества нескольких процессоров. Исторически программирование в этих условиях было сложным и подверженным ошибкам: Ржавчина надеется изменить это.

-

Первоначально приказ Ржавчина считала, что обеспечение безопасности памяти и предотвращение неполадок многопоточности — это две отдельные сбоев, которые необходимо решать различными способами. Со временем приказ обнаружила, что системы владения и система видов являются мощным набором средств, помогающих управлять безопасностью памяти и неполадками многопоточного одновременности! Используя владение и проверку видов, многие ошибки многопоточности являются ошибками времени сборки в Rust, а не ошибками времени выполнения. Поэтому вместо того, чтобы тратить много времени на попытки воспроизвести точные обстоятельства, при которых возникает ошибка многопоточности во время выполнения, неправильный код будет отклонён с ошибкой. В итоге вы можете исправить свой код во время работы над ним, а не после развёртывания на рабочем сервере. Мы назвали этот особенность Ржавчина бесстрашной многопоточностью. Бесстрашная многопоточность позволяет вам писать код, который не содержит скрытых ошибок и легко ресогласуется без внесения новых.

-
-

Примечание: для простоты мы будем называть многие сбоев многопоточными, хотя более точный понятие здесь  — многопоточные и/или одновременные. Если бы эта книга была о многопоточности и/или одновременности, мы были бы более определены. В этой главе, пожалуйста, всякий раз, когда мы используем понятие «многопоточный», мысленно замените на понятие «многопоточный и/или одновременный».

-
-

Многие языки предлагают довольно устоявшиеся решения неполадок многопоточности. Например, Erlang обладает элегантной возможностью для многопоточности при передаче сообщений, но не определяет ясных способов совместного использования состояния между потоками. Поддержка только подмножества возможных решений является разумной подходом для языков более высокого уровня, поскольку язык более высокого уровня обещает выгоду при отказе от некоторого управления над получением абстракций. Однако ожидается, что языки низкого уровня обеспечат решение с наилучшей производительностью в любой именно случаи и будут иметь меньше абстракций по сравнению с аппаратным обеспечением. Поэтому Ржавчина предлагает множество средств для расчетов неполадок любым способом, который подходит для вашей случаи и требований.

-

Вот темы, которые мы рассмотрим в этой главе:

-
    -
  • Как создать потоки для одновременного запуска нескольких отрывков кода
  • -
  • Многопоточность передачи сообщений, где потоки передают сообщения между потоками
  • -
  • Многопоточность для совместно используемого состояния, когда несколько потоков имеют доступ к некоторому отрывку данных
  • -
  • Особенности Sync и Send, которые расширяют заверения многопоточности в Ржавчина для пользовательских видов, а также видов, предоставляемых встроенной библиотекой
  • -
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch16-01-threads.html b/rustbook-ru/book/ch16-01-threads.html deleted file mode 100644 index d5cebacf2..000000000 --- a/rustbook-ru/book/ch16-01-threads.html +++ /dev/null @@ -1,463 +0,0 @@ - - - - - - Использование потоков для одновременного выполнения кода - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Использование потоков для одновременного выполнения кода

-

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

-

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

-
    -
  • Состояния гонки, когда потоки обращаются к данным, либо ресурсам, несогласованно.
  • -
  • Взаимные блокировки, когда два потока ожидают друг друга, не позволяя тем самым продолжить работу каждому из потоков.
  • -
  • Ошибки, которые случаются только в определённых случаейх, которые трудно воспроизвести и, соответственно, трудно надёжно исправить.
  • -
-

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

-

Языки программирования выполняют потоки несколькими различными способами, и многие операционные системы предоставляют API, который язык может вызывать для создания новых потоков. Обычная библиотека Ржавчина использует прообраз выполнения потоков 1:1, при которой одному потоку операционной системы соответствует ровно один "языковой" поток. Существуют ящики, в которых выполнены другие подходы многопоточности, отличающиеся от подходы 1:1.

-

Создание нового потока с помощью spawn

-

Чтобы создать новый поток, мы вызываем функцию thread::spawn и передаём ей замыкание (мы говорили о замыканиях в главе 13), содержащее код, который мы хотим запустить в новом потоке. Пример в приложении 16-1 печатает некоторый текст из основного потока, а также другой текст из нового потока:

-

Файл: src/main.rs

-
use std::thread;
-use std::time::Duration;
-
-fn main() {
-    thread::spawn(|| {
-        for i in 1..10 {
-            println!("hi number {i} from the spawned thread!");
-            thread::sleep(Duration::from_millis(1));
-        }
-    });
-
-    for i in 1..5 {
-        println!("hi number {i} from the main thread!");
-        thread::sleep(Duration::from_millis(1));
-    }
-}
-

Приложение 16-1: Создание нового потока для печати определённого текста, в то время как основной поток печатает что-то другое

-

Обратите внимание, что когда основной поток программы на Ржавчина завершается, все порождённые потоки закрываются, независимо от того, завершили они работу или нет. Вывод этой программы может каждый раз немного отличаться, но он будет выглядеть примерно так:

- -
hi number 1 from the main thread!
-hi number 1 from the spawned thread!
-hi number 2 from the main thread!
-hi number 2 from the spawned thread!
-hi number 3 from the main thread!
-hi number 3 from the spawned thread!
-hi number 4 from the main thread!
-hi number 4 from the spawned thread!
-hi number 5 from the spawned thread!
-
-

Вызовы thread::sleep заставляют поток на короткое время останавливать своё выполнение, позволяя выполняться другим потокам. Очерёдность выполнения потоков вероятно будет меняться, но это не обязательно: это зависит от того, как ваша операционная система расчитывает потоки. В этом цикле основной поток печатает первым, несмотря на то, что указание печати из порождённого потока появляется раньше в коде. И даже несмотря на то, что мы указали порождённый поток печатать до тех пор, пока значение i не достигнет числа 9, оно успело дойти только до 5, когда основной поток завершился.

-

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

-

Ожидание завершения работы всех потоков используя join

-

Код в приложении 16-1 преждевременно останавливает порождённый поток в большинстве случаев, из-за завершения основного потока. Более того, так как порядок выполнения потоков чётко не определён, этот код не даёт заверения, что порождённый поток вообще начнёт исполняться!

-

Мы можем исправить неполадку, когда созданный поток не запускается или завершается преждевременно, сохранив возвращаемое значение thread::spawn в какой-либо переменной. Вид возвращаемого значения thread::spawnJoinHandle . JoinHandle — это владеющее значение, которое, при вызове способа join , будет ждать завершения своего потока. Приложение 16-2 отображает, как использовать JoinHandle потока, созданного в приложении 16-1, и вызывать функцию join , для того, чтобы убедиться, что порождённый поток завершится раньше, чем поток main:

-

Файл: src/main.rs

-
use std::thread;
-use std::time::Duration;
-
-fn main() {
-    let handle = thread::spawn(|| {
-        for i in 1..10 {
-            println!("hi number {i} from the spawned thread!");
-            thread::sleep(Duration::from_millis(1));
-        }
-    });
-
-    for i in 1..5 {
-        println!("hi number {i} from the main thread!");
-        thread::sleep(Duration::from_millis(1));
-    }
-
-    handle.join().unwrap();
-}
-

Приложение 16-2. Сохранение значения JoinHandle потока thread::spawn , обеспечивающее, что поток выполнит всю необходимую работу, перед тем, как завершится

-

Вызов join у указателя блокирует текущий поток, пока поток, представленный указателем не завершится. Блокировка потока означает, что потоку запрещено выполнять работу или выходить из него. Поскольку мы помеисполнения вызов join после цикла for основного потока, выполнение приложения 16-2 должно привести к выводу, подобному следующему:

- -
hi number 1 from the main thread!
-hi number 2 from the main thread!
-hi number 1 from the spawned thread!
-hi number 3 from the main thread!
-hi number 2 from the spawned thread!
-hi number 4 from the main thread!
-hi number 3 from the spawned thread!
-hi number 4 from the spawned thread!
-hi number 5 from the spawned thread!
-hi number 6 from the spawned thread!
-hi number 7 from the spawned thread!
-hi number 8 from the spawned thread!
-hi number 9 from the spawned thread!
-
-

Два потока продолжают чередоваться, но основной поток находится в ожидании из-за вызова handle.join() и не завершается до тех пор, пока не завершится запущенный поток.

-

Но давайте посмотрим, что произойдёт, если мы вместо этого переместим handle.join() перед циклом for в main, например так:

-

Файл: src/main.rs

-
use std::thread;
-use std::time::Duration;
-
-fn main() {
-    let handle = thread::spawn(|| {
-        for i in 1..10 {
-            println!("hi number {i} from the spawned thread!");
-            thread::sleep(Duration::from_millis(1));
-        }
-    });
-
-    handle.join().unwrap();
-
-    for i in 1..5 {
-        println!("hi number {i} from the main thread!");
-        thread::sleep(Duration::from_millis(1));
-    }
-}
-

Основной поток будет ждать завершения порождённого потока, а затем запустит свой цикл for , поэтому выходные данные больше не будут чередоваться, как показано ниже:

- -
hi number 1 from the spawned thread!
-hi number 2 from the spawned thread!
-hi number 3 from the spawned thread!
-hi number 4 from the spawned thread!
-hi number 5 from the spawned thread!
-hi number 6 from the spawned thread!
-hi number 7 from the spawned thread!
-hi number 8 from the spawned thread!
-hi number 9 from the spawned thread!
-hi number 1 from the main thread!
-hi number 2 from the main thread!
-hi number 3 from the main thread!
-hi number 4 from the main thread!
-
-

Небольшие подробности, такие как место вызова join, могут повлиять на то, выполняются ли ваши потоки одновременно.

-

Использование move-замыканий в потоках

-

Мы часто используем ключевое слово move с замыканиями, переданными в thread::spawn, потому что в этом случае замыкание получает из окружения права владения на используемые им значения, таким образом передавая права владения этими значениями от одного потока к другому. В разделе "Захват ссылок или перемещение прав владения" главы 13 мы обсудили move в среде замыканий. Теперь мы сосредоточимся на взаимодействии между move и thread::spawn.

-

Обратите внимание, что в приложении 16-1 замыкание, которое мы передаём в thread::spawn не принимает переменных: мы не используем никаких данных из основного потока в коде порождённого потока. Чтобы использовать данные из основного потока в порождённом потоке, замыкание порождённого потока должно захватывать значения, которые ему необходимы. Приложение 16-3 показывает попытку создать вектор в главном потоке и использовать его в порождённом потоке. Тем не менее, это не будет работать, как вы увидите через мгновение.

-

Файл: src/main.rs

-
use std::thread;
-
-fn main() {
-    let v = vec![1, 2, 3];
-
-    let handle = thread::spawn(|| {
-        println!("Here's a vector: {v:?}");
-    });
-
-    handle.join().unwrap();
-}
-

Приложение 16-3: Попытка использовать вектор, созданный основным потоком, в другом потоке

-

Замыкание использует переменную v, поэтому оно захватит v и сделает его частью окружения замыкания. Поскольку thread::spawn запускает это замыкание в новом потоке, мы должны иметь доступ к v внутри этого нового потока. Но при сборки этого примера, мы получаем следующую ошибку:

-
$ cargo run
-   Compiling threads v0.1.0 (file:///projects/threads)
-error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function
- --> src/main.rs:6:32
-  |
-6 |     let handle = thread::spawn(|| {
-  |                                ^^ may outlive borrowed value `v`
-7 |         println!("Here's a vector: {v:?}");
-  |                                     - `v` is borrowed here
-  |
-note: function requires argument type to outlive `'static`
- --> src/main.rs:6:18
-  |
-6 |       let handle = thread::spawn(|| {
-  |  __________________^
-7 | |         println!("Here's a vector: {v:?}");
-8 | |     });
-  | |______^
-help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
-  |
-6 |     let handle = thread::spawn(move || {
-  |                                ++++
-
-For more information about this error, try `rustc --explain E0373`.
-error: could not compile `threads` (bin "threads") due to 1 previous error
-
-

Rust выводит как захватить v и так как в println! нужна только ссылка на v, то замыкание пытается заимствовать v. Однако есть неполадка: Ржавчина не может определить, как долго будет работать порождённый поток, поэтому он не знает, будет ли всегда действительной ссылка на v.

-

В приложении 16-4 приведён сценарий, который с большей вероятностью будет иметь ссылку на v, что будет недопустимо:

-

Файл: src/main.rs

-
use std::thread;
-
-fn main() {
-    let v = vec![1, 2, 3];
-
-    let handle = thread::spawn(|| {
-        println!("Here's a vector: {v:?}");
-    });
-
-    drop(v); // oh no!
-
-    handle.join().unwrap();
-}
-

Приложение 16-4. Поток с замыканием, который пытается захватить ссылку на v из основного потока, удаляющего v

-

Если бы Ржавчина позволил нам запустить этот код, есть вероятность, что порождённый поток был бы немедленно переведён в фоновый режим, не выполнив ничего. Порождённый поток имеет ссылку на v, но основной поток немедленно удаляет v , используя функцию drop , которую мы обсуждали в главе 15. Затем, когда порождённый поток начинает выполняться, v уже не существует, поэтому ссылка на него также будет недействительной. О, нет!

-

Чтобы исправить ошибку сборщика в приложении 16-3, мы можем использовать совет из сообщения об ошибке:

- -
help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
-  |
-6 |     let handle = thread::spawn(move || {
-  |                                ++++
-
-

Добавляя ключевое слово move перед замыканием, мы заставляем замыкание забирать используемые значения во владение, вместо того, чтобы позволить Ржавчина вывести необходимость заимствования значения. Изменение Приложения 16-3, показанная в Приложении 16-5, будет собрана и запущена так, как мы ожидаем:

-

Файл: src/main.rs

-
use std::thread;
-
-fn main() {
-    let v = vec![1, 2, 3];
-
-    let handle = thread::spawn(move || {
-        println!("Here's a vector: {v:?}");
-    });
-
-    handle.join().unwrap();
-}
-

Приложение 16-5. Использование ключевого слова move , чтобы замыкание стало владельцем используемых им значений.

-

У нас может возникнуть соблазн попробовать то же самое, чтобы исправить код в приложении 16.4, где основной поток вызывал drop с помощью замыкания move . Однако это исправление не сработает, потому что то, что пытается сделать приложение 16.4, запрещено по другой причине. Если мы добавим move к замыканию, мы переместим v в окружение замыкания и больше не сможем вызывать для него drop в основном потоке. Вместо этого мы получим эту ошибку сборщика:

-
$ cargo run
-   Compiling threads v0.1.0 (file:///projects/threads)
-error[E0382]: use of moved value: `v`
-  --> src/main.rs:10:10
-   |
-4  |     let v = vec![1, 2, 3];
-   |         - move occurs because `v` has type `Vec<i32>`, which does not implement the `Copy` trait
-5  |
-6  |     let handle = thread::spawn(move || {
-   |                                ------- value moved into closure here
-7  |         println!("Here's a vector: {v:?}");
-   |                                     - variable moved due to use in closure
-...
-10 |     drop(v); // oh no!
-   |          ^ value used here after move
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `threads` (bin "threads") due to 1 previous error
-
-

Правила владения Ржавчина снова нас спасли! Мы получили ошибку кода из приложения 16-3, потому что Ржавчина был устоявшийся и заимствовал v только для потока, что означало, что основной поток предположительно может сделать недействительной ссылку на порождённый поток. Сообщив Ржавчина о передаче владения v в порождаемый поток, мы заверяем Rust, что основной поток больше не будет использовать v. Если мы изменим Приложение 16-4 таким же образом, то мы нарушаем правила владения при попытке использовать v в главном потоке. Ключевое слово move отменяет основное устоявшееся поведение Ржавчина по заимствованию, что не позволяет нам нарушать правила владения.

-

Имея достаточное понимание потоков и API потоков, давайте посмотрим, что мы можем делать с помощью потоков.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch16-02-message-passing.html b/rustbook-ru/book/ch16-02-message-passing.html deleted file mode 100644 index 01f39bd49..000000000 --- a/rustbook-ru/book/ch16-02-message-passing.html +++ /dev/null @@ -1,431 +0,0 @@ - - - - - - Пересылка сообщений для передачи данных между потоками - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Передача данных с помощью сообщений между потоками

-

Всё большую распространенность для обеспечения безопасной многопоточности набирает способ, называемый передача сообщений. В этом случае потоки или акторы взаимодействуют друг с другом путём отправки сообщений с данными. Мысль этого подхода выражена в слогане из документации языка Go таким образом: «Не стоит передавать сведения с помощью разделяемой памяти; лучше делитесь памятью, передавая сведения».

-

Для обеспечения отправки многопоточных сообщений в встроенной библиотеке языка Ржавчина выполнены потоки. Поток в программировании - это общепринятый рычаг, с помощью которого данные из одного потока отправляются другому потоку.

-

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

-

Поток состоит из двух половин: передатчика и приёмника. Передатчик — это место вверх по течению, где вы опускаете резиновых уточек в реку, а приёмник — это место, где резиновые уточки оказываются в конце пути. Одна часть вашего кода вызывает способы передатчика с данными, которые вы хотите отправить, а другая часть проверяет принимающую сторону на наличие поступающих сообщений. Поток считается закрытым , если либо передающая, либо принимающая его половина уничтожена.

-

Давайте создадим программу, в которой один поток будет порождать значения и отправлять их в поток, а другой поток будет получать значения и распечатывать их. Мы будем отправлять между потоками простые значения, используя поток, чтобы изобразить эту функцию. После того, как вы ознакомитесь с этим способом, вы сможете использовать потоки с любыми потоками, которым необходимо взаимодействовать друг с другом. Это может быть например система чата или система, в которой несколько вычислительных потоков выполняют свою часть расчёта, а затем отправляют эту часть в отдельный поток, который уже агрегирует полученные итоги.

-

Сначала в приложении 16-6 мы создадим поток, но не будем ничего с ним делать. Обратите внимание, что этот код ещё не собирается, потому что Ржавчина не может сказать, какой вид значений мы хотим отправить через поток.

-

Файл: src/main.rs

-
use std::sync::mpsc;
-
-fn main() {
-    let (tx, rx) = mpsc::channel();
-}
-

Приложение 16-6: Создание потока и присваивание двух значений переменным tx и rx

-

Мы создаём новый поток, используя функцию mpsc::channel; mpsc означает несколько производителей, один потребитель (multiple producer, single consumer). Коротко, способ которым обычная библиотека Ржавчина выполняет потоки, означает, что поток может иметь несколько отправляющих источников порождающих значения, но только одну принимающую сторону, которая потребляет эти значения. Представьте, что несколько ручьёв втекают в одну большую реку: всё, что плывёт вниз по любому из ручьёв, в конце концов окажется в одной реке. Сейчас мы пока начнём с одного производителя, а когда пример заработает, добавим ещё несколько.

-

Функция mpsc::channel возвращает упорядоченный ряд, первый элемент которого является отправляющей стороной (передатчиком), а вторым элементом является принимающая сторона (получатель). Аббревиатуры tx и rx привычно используются во многих полях для передатчика и приёмника соответственно, поэтому мы называем соответствующие переменные именно так. Мы используем указанию let с образцом, который разъединяет упорядоченные ряды; мы обсудим использование образцов в указаниях let и разъединение в главе 18. А пока знайте, что описанное использование указания let является удобным способом извлечения частей упорядоченного ряда, возвращаемых mpsc::channel .

-

Давайте переместим передающую часть в порождённый поток так, чтобы он отправлял одну строку и чтобы таким образом, порождённый поток связывался с основным потоком, как показано в приложении 16-7. Это похоже на то, как если бы вы помеисполнения резиновую утку в реку вверх по течению или отправили сообщение чата из одного потока в другой.

-

Файл: src/main.rs

-
use std::sync::mpsc;
-use std::thread;
-
-fn main() {
-    let (tx, rx) = mpsc::channel();
-
-    thread::spawn(move || {
-        let val = String::from("hi");
-        tx.send(val).unwrap();
-    });
-}
-

Приложение 16-7: Перемещение tx в созданный поток и отправка сообщения «привет»

-

Опять же, мы используем thread::spawn для создания нового потока, а затем используем move для перемещения tx в замыкание, чтобы порождённый поток владел tx . Порождённый поток должен владеть передатчиком, чтобы иметь возможность отправлять сообщения через поток. Передатчик имеет способ send , который принимает значение, которое мы хотим отправить. Способ send возвращает вид Result<T, E> , поэтому, если получатель уже удалён и отправить значение некуда, действие отправки вернёт ошибку. В этом примере мы вызываем unwrap для паники в случае ошибки. В существующем приложении мы обработали бы эту случай более правильно: вернитесь к главе 9, если хотите ещё раз разобрать стратегии правильной обработки ошибок.

-

В приложении 16-8 мы получим значение от приёмника в основном потоке. Это похоже на извлечение резиновой уточки из воды в конце реки или получение сообщения в чате.

-

Файл: src/main.rs

-
use std::sync::mpsc;
-use std::thread;
-
-fn main() {
-    let (tx, rx) = mpsc::channel();
-
-    thread::spawn(move || {
-        let val = String::from("hi");
-        tx.send(val).unwrap();
-    });
-
-    let received = rx.recv().unwrap();
-    println!("Got: {received}");
-}
-

Приложение 16-8: В основном потоке получаем сообщение "hi" и печатаем его

-

Получатель имеет два важных способа: recv и try_recv. Мы используем recv, что является сокращением от receive, который блокирует выполнение основного потока и ждёт, пока данные не будут переданы по потоку. Как только значение будет получено, recv вернёт его в виде Result<T, E>. Когда поток закроется, recv вернёт ошибку, чтобы дать понять, что больше никаких сообщений не поступит.

-

В свою очередь, способ try_recv не блокирует, а сразу возвращает итог Result<T, E>: значение Ok, содержащее сообщение, если оно доступно или значение Err, если никаких сообщений не поступило. Использование try_recv полезно, если у этого потока есть и другая работа в то время, пока происходит ожидание сообщений: так, мы можем написать цикл, который вызывает try_recv время от времени, обрабатывает сообщение, если оно доступно, а в промежутке выполняет другую работу до того особенности, как вновь будет произведена проверка.

-

Мы использовали recv в этом примере для простоты; у нас нет никакой другой работы для основного потока, кроме как ждать сообщений, поэтому блокировка основного потока уместна.

-

При запуске кода приложения 16-8, мы увидим значение, напечатанное из основного потока:

- -
Got: hi
-
-

Отлично!

-

потоки и передача владения

-

Правила владения играют жизненно важную значение в отправке сообщений, потому что они помогают писать безопасный многопоточный код. Предотвращение ошибок в многопоточном программировании является преимуществом для размышлений о владении во всех ваших Ржавчина программах. Давайте проведём эксперимент, чтобы показать как потоки и владение действуют совместно для предотвращения неполадок. мы попытаемся использовать значение val в порождённом потоке после того как отправим его в поток. Попробуйте собрать код в приложении 16-9, чтобы понять, почему этот код не разрешён:

-

Файл: src/main.rs

-
use std::sync::mpsc;
-use std::thread;
-
-fn main() {
-    let (tx, rx) = mpsc::channel();
-
-    thread::spawn(move || {
-        let val = String::from("hi");
-        tx.send(val).unwrap();
-        println!("val is {val}");
-    });
-
-    let received = rx.recv().unwrap();
-    println!("Got: {received}");
-}
-

Приложение 16-9: Попытка использовать val после того, как мы отправили его по потоку

-

Здесь мы пытаемся напечатать значение val после того, как отправили его в поток вызвав tx.send. Разрешить это было бы плохой мыслью: после того, как значение было отправлено в другой поток, текущий поток мог бы изменить или удалить значение, прежде чем мы попытались бы использовать значение снова. Вероятно изменения в другом потоке могут привести к ошибкам или не ожидаемым итогам из-за противоречивых или несуществующих данных. Однако Ржавчина выдаёт нам ошибку, если мы пытаемся собрать код в приложении 16-9:

-
$ cargo run
-   Compiling message-passing v0.1.0 (file:///projects/message-passing)
-error[E0382]: borrow of moved value: `val`
-  --> src/main.rs:10:26
-   |
-8  |         let val = String::from("hi");
-   |             --- move occurs because `val` has type `String`, which does not implement the `Copy` trait
-9  |         tx.send(val).unwrap();
-   |                 --- value moved here
-10 |         println!("val is {val}");
-   |                          ^^^^^ value borrowed here after move
-   |
-   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `message-passing` (bin "message-passing") due to 1 previous error
-
-

Наша ошибка для многопоточности привела к ошибке сборки. Функция send вступает во владение своим свойствоом и когда значение перемещается, получатель становится владельцем этого свойства. Это останавливает нас от случайного использования значения снова после его отправки; анализатор заимствования проверяет, что все в порядке.

-

Отправка нескольких значений и ожидание получателем

-

Код в приложении 16-8 собирается и выполняется, но в нем неясно показано то, что два отдельных потока общаются друг с другом через поток. В приложении 16-10 мы внесли некоторые изменения, которые докажут, что код в приложении 16-8 работает одновременно: порождённый поток теперь будет отправлять несколько сообщений и делать паузу на секунду между каждым сообщением.

-

Файл: src/main.rs

-
use std::sync::mpsc;
-use std::thread;
-use std::time::Duration;
-
-fn main() {
-    let (tx, rx) = mpsc::channel();
-
-    thread::spawn(move || {
-        let vals = vec![
-            String::from("hi"),
-            String::from("from"),
-            String::from("the"),
-            String::from("thread"),
-        ];
-
-        for val in vals {
-            tx.send(val).unwrap();
-            thread::sleep(Duration::from_secs(1));
-        }
-    });
-
-    for received in rx {
-        println!("Got: {received}");
-    }
-}
-

Приложение 16-10: Отправка нескольких сообщений и пауза между ними

-

На этот раз порождённый поток имеет вектор строк, которые мы хотим отправить основному потоку. Мы перебираем их, отправляя каждую строку по отдельности и делаем паузу между ними, вызывая функцию thread::sleep со значением Duration равным 1 секунде.

-

В основном потоке мы больше не вызываем функцию recv явно: вместо этого мы используем rx как повторитель . Для каждого полученного значения мы печатаем его. Когда поток будет закрыт, повторение закончится.

-

При выполнении кода в приложении 16-10 вы должны увидеть следующий вывод с паузой в 1 секунду между каждой строкой:

- -
Got: hi
-Got: from
-Got: the
-Got: thread
-
-

Поскольку у нас нет кода, который приостанавливает или задерживает цикл for в основном потоке, мы можем сказать, что основной поток ожидает получения значений из порождённого потока.

-

Создание нескольких отправителей путём клонирования передатчика

-

Ранее мы упоминали, что mpsc — это аббревиатура от множество поставщиков, один потребитель . Давайте используем mpsc в полной мере и расширим код в приложении 16.10, создав несколько потоков, которые отправляют значения одному и тому же получателю. Мы можем сделать это, клонировав передатчик, как показано в приложении 16.11:

-

Файл: src/main.rs

-
use std::sync::mpsc;
-use std::thread;
-use std::time::Duration;
-
-fn main() {
-    // --snip--
-
-    let (tx, rx) = mpsc::channel();
-
-    let tx1 = tx.clone();
-    thread::spawn(move || {
-        let vals = vec![
-            String::from("hi"),
-            String::from("from"),
-            String::from("the"),
-            String::from("thread"),
-        ];
-
-        for val in vals {
-            tx1.send(val).unwrap();
-            thread::sleep(Duration::from_secs(1));
-        }
-    });
-
-    thread::spawn(move || {
-        let vals = vec![
-            String::from("more"),
-            String::from("messages"),
-            String::from("for"),
-            String::from("you"),
-        ];
-
-        for val in vals {
-            tx.send(val).unwrap();
-            thread::sleep(Duration::from_secs(1));
-        }
-    });
-
-    for received in rx {
-        println!("Got: {received}");
-    }
-
-    // --snip--
-}
-

Приложение 16-11: Отправка нескольких сообщений от нескольких производителей

-

На этот раз, прежде чем мы создадим первый порождённый поток, мы вызовем функцию clone на передатчике. В итоге мы получим новый передатчик, который мы сможем передать первому порождённому потоку. Исходный передатчик мы передадим второму порождённому потоку. Это даст нам два потока, каждый из которых отправляет разные сообщения одному получателю.

-

Когда вы запустите код, вывод должен выглядеть примерно так:

- -
Got: hi
-Got: more
-Got: from
-Got: messages
-Got: for
-Got: the
-Got: thread
-Got: you
-
-

Вы можете увидеть значения в другом порядке, в зависимости от вашей системы. Именно такое поведение делает одновременность как важным, так и сложным. Если вы поэкспериментируете с thread::sleep , задавая различные значения переменной в разных потоках, каждый запуск будет более неопределенным и каждый раз будут выводиться разные данные.

-

Теперь, когда мы посмотрели, как работают потоки, давайте рассмотрим другой способ многопоточности.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch16-03-shared-state.html b/rustbook-ru/book/ch16-03-shared-state.html deleted file mode 100644 index 6faea03bd..000000000 --- a/rustbook-ru/book/ch16-03-shared-state.html +++ /dev/null @@ -1,422 +0,0 @@ - - - - - - Одновременность с общим состоянием - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Многопоточное разделяемое состояние

-

Передача сообщений — прекрасный способ обработки одновременности, но не единственный. Другим способом может быть доступ нескольких потоков к одним и тем же общим данным. Рассмотрим ещё раз часть слогана из документации по языку Go: «Не стоит передавать сведения с помощью разделяемой памяти».

-

Как бы выглядело общение, используя разделяемую память? Кроме того, почему энтузиасты передачи сообщений предостерегают от его использования?

-

В каком-то смысле потоки в любом языке программирования похожи на единоличное владение, потому что после передачи значения по потоку вам больше не следует использовать отправленное значение. Многопоточная, совместно используемая память подобна множественному владению: несколько потоков могут одновременно обращаться к одной и той же области памяти. Как вы видели в главе 15, где умные указатели сделали возможным множественное владение, множественное владение может добавить сложность, потому что нужно управлять этими разными владельцами. Система видов Ржавчина и правила владения очень помогают в их правильном управлении. Для примера давайте рассмотрим мьютексы, один из наиболее распространённых многопоточных простейших для разделяемой памяти.

-

Мьютексы предоставляют доступ к данным из одного потока (за раз)

-

Mutex - это сокращение от взаимное исключение (mutual exclusion), так как мьютекс позволяет только одному потоку получать доступ к некоторым данным в любой мгновение времени. Для того, чтобы получить доступ к данным в мьютексе, поток должен сначала подать сигнал, что он хочет получить доступ запрашивая блокировку (lock) мьютекса. Блокировка - это устройства данных, являющаяся частью мьютекса, которая отслеживает кто в настоящее время имеет эксклюзивный доступ к данным. Поэтому мьютекс описывается как предмет защищающий данные, которые он хранит через систему блокировки.

-

Мьютексы имеют репутацию трудных в использовании, потому что вы должны помнить два правила:

-
    -
  • Перед тем как попытаться получить доступ к данным необходимо получить блокировку.
  • -
  • Когда вы закончили работу с данными, которые защищает мьютекс, вы должны разблокировать данные, чтобы другие потоки могли получить блокировку.
  • -
-

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

-

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

-

Mutex<T> API

-

Давайте рассмотрим пример использования мьютекса в приложении 16-12 без использования нескольких потоков:

-

Файл: src/main.rs

-
use std::sync::Mutex;
-
-fn main() {
-    let m = Mutex::new(5);
-
-    {
-        let mut num = m.lock().unwrap();
-        *num = 6;
-    }
-
-    println!("m = {m:?}");
-}
-

Приложение 16-12: Изучение API Mutex<T> для простоты в однопоточном среде

-

Как и во многих других видах, мы создаём Mutex<T> с помощью сопутствующей функции new. Чтобы получить доступ к данным внутри мьютекса, мы используем способ lock для получения блокировки. Этот вызов блокирует выполнение текущего потока, так что он не сможет выполнять никакие действия, до тех пор пока не наступит наша очередь получить блокировку.

-

Вызов lock потерпит неудачу, если другой поток, удерживающий блокировку, запаникует. В таком случае никто не сможет получить блокировку, поэтому мы предпочли использовать unwrap и заставить этот поток паниковать, если мы окажемся в такой случаи.

-

После получения блокировки мы можем воспринимать возвращённое значение, названное в данном случае num, как изменяемую ссылку на содержащиеся внутри данные. Система видов заверяет, что мы получим блокировку перед использованием значения в m. Вид m - Mutex<i32>, а не i32, поэтому мы должны вызвать lock, чтобы иметь возможность использовать значение i32. Мы не должны об этом забывать, тем более что в иных случаях система видов и не даст нам доступ к внутреннему значению i32.

-

Как вы наверное подозреваете, Mutex<T> является умным указателем. Точнее, вызов lock возвращает умный указатель, называемый MutexGuard, обёрнутый в LockResult, который мы обработали с помощью вызова unwrap. Умный указатель вида MutexGuard выполняет особенность Deref для указания на внутренние данные; умный указатель также имеет выполнение особенности Drop, самостоятельно снимающего блокировку, когда MutexGuard выходит из области видимости, что происходит в конце внутренней области видимости. В итоге у нас нет риска забыть снять блокировку и оставить мьютекс в заблокированном состоянии, препятствуя его использованию другими потоками (снятие блокировки происходит самостоятельно ).

-

После снятия блокировки можно напечатать значение мьютекса и увидеть, что мы смогли изменить внутреннее i32 на 6.

-

Разделение Mutex<T> между множеством потоков

-

Теперь давайте попробуем с помощью Mutex<T> совместно использовать значение между несколькими потоками. Мы стартуем 10 потоков и каждый из них увеличивает значение счётчика на 1, поэтому счётчик изменяется от 0 до 10. Обратите внимание, что в следующих нескольких примерах будут ошибки сборщика и мы будем использовать эти ошибки, чтобы узнать больше об использовании вида Mutex<T> и как Ржавчина помогает нам правильно его использовать. Приложение 16-13 содержит наш начальный пример:

-

Файл: src/main.rs

-
use std::sync::Mutex;
-use std::thread;
-
-fn main() {
-    let counter = Mutex::new(0);
-    let mut handles = vec![];
-
-    for _ in 0..10 {
-        let handle = thread::spawn(move || {
-            let mut num = counter.lock().unwrap();
-
-            *num += 1;
-        });
-        handles.push(handle);
-    }
-
-    for handle in handles {
-        handle.join().unwrap();
-    }
-
-    println!("Result: {}", *counter.lock().unwrap());
-}
-

Приложение 16-13. Десять потоков, увеличивающих счётчик, защищённый Mutex<T>

-

Мы создаём переменную-счётчик counter для хранения i32 значения внутри Mutex<T>, как мы это делали в приложении 16-12. Затем мы создаём 10 потоков, перебирая рядчисел. Мы используем thread::spawn и передаём всем этим потокам одинаковое замыкание, которое перемещает счётчик в поток, запрашивает блокировку на Mutex<T>, вызывая способ lock, а затем добавляет 1 к значению в мьютексе. Когда поток завершит выполнение своего замыкания, num выйдет из области видимости и освободит блокировку, чтобы её мог получить другой поток.

-

В основном потоке мы собираем все указатели в переменную handles. Затем, как мы это делали в приложении 16-2, вызываем join для каждого указателя, чтобы убедиться в завершении всех потоков. В этот мгновение основной поток получит доступ к блокировке и тоже напечатает итог программы.

-

Сборщик намекнул, что этот пример не собирается. Давайте выясним почему!

-
$ cargo run
-   Compiling shared-state v0.1.0 (file:///projects/shared-state)
-error[E0382]: borrow of moved value: `counter`
-  --> src/main.rs:21:29
-   |
-5  |     let counter = Mutex::new(0);
-   |         ------- move occurs because `counter` has type `Mutex<i32>`, which does not implement the `Copy` trait
-...
-8  |     for _ in 0..10 {
-   |     -------------- inside of this loop
-9  |         let handle = thread::spawn(move || {
-   |                                    ------- value moved into closure here, in previous iteration of loop
-...
-21 |     println!("Result: {}", *counter.lock().unwrap());
-   |                             ^^^^^^^ value borrowed here after move
-   |
-help: consider moving the expression out of the loop so it is only moved once
-   |
-8  ~     let mut value = counter.lock();
-9  ~     for _ in 0..10 {
-10 |         let handle = thread::spawn(move || {
-11 ~             let mut num = value.unwrap();
-   |
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `shared-state` (bin "shared-state") due to 1 previous error
-
-

Сообщение об ошибке указывает, что значение counter было перемещёно в замыкание на предыдущей повторения цикла. Ржавчина говорит нам, что мы не можем передать counter во владение нескольким потокам. Давайте исправим ошибку сборщика с помощью способа множественного владения, который мы обсуждали в главе 15.

-

Множественное владение между множеством потоков

-

В главе 15 мы давали значение нескольким владельцам, используя умный указатель Rc<T> для создания значения подсчитанных ссылок. Давайте сделаем то же самое здесь и посмотрим, что произойдёт. Мы завернём Mutex<T> в Rc<T> в приложении 16-14 и клонируем Rc<T> перед передачей владения в поток. Теперь, когда мы увидели ошибки, мы также вернёмся к использованию цикла for и сохраним ключевое слово move у замыкания.

-

Файл: src/main.rs

-
use std::rc::Rc;
-use std::sync::Mutex;
-use std::thread;
-
-fn main() {
-    let counter = Rc::new(Mutex::new(0));
-    let mut handles = vec![];
-
-    for _ in 0..10 {
-        let counter = Rc::clone(&counter);
-        let handle = thread::spawn(move || {
-            let mut num = counter.lock().unwrap();
-
-            *num += 1;
-        });
-        handles.push(handle);
-    }
-
-    for handle in handles {
-        handle.join().unwrap();
-    }
-
-    println!("Result: {}", *counter.lock().unwrap());
-}
-

Приложение 16-14: Попытка использования Rc<T>, чтобы позволить нескольким потокам владеть Mutex<T>

-

Ещё раз, мы собираем и получаем ... другие ошибки! Сборщик учит нас.

-
$ cargo run
-   Compiling shared-state v0.1.0 (file:///projects/shared-state)
-error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely
-  --> src/main.rs:11:36
-   |
-11 |           let handle = thread::spawn(move || {
-   |                        ------------- ^------
-   |                        |             |
-   |  ______________________|_____________within this `{closure@src/main.rs:11:36: 11:43}`
-   | |                      |
-   | |                      required by a bound introduced by this call
-12 | |             let mut num = counter.lock().unwrap();
-13 | |
-14 | |             *num += 1;
-15 | |         });
-   | |_________^ `Rc<Mutex<i32>>` cannot be sent between threads safely
-   |
-   = help: within `{closure@src/main.rs:11:36: 11:43}`, the trait `Send` is not implemented for `Rc<Mutex<i32>>`, which is required by `{closure@src/main.rs:11:36: 11:43}: Send`
-note: required because it's used within this closure
-  --> src/main.rs:11:36
-   |
-11 |         let handle = thread::spawn(move || {
-   |                                    ^^^^^^^
-note: required by a bound in `spawn`
-  --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/thread/mod.rs:691:1
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `shared-state` (bin "shared-state") due to 1 previous error
-
-

Ничего себе, это сообщение об ошибке очень многословно! Вот важная часть, на которой следует сосредоточиться: ``Rc<Mutex> cannot be sent between threads safely. Сборщик также сообщает нам причину: the trait Sendis not implemented forRc<Mutex> . Мы поговорим о Send в следующем разделе: это один из особенностей, который заверяет, что виды которые мы используем с потоками, предназначены для использования в многопоточном коде.

-

К сожалению, Rc<T> небезопасен для совместного использования между потоками. Когда Rc<T> управляет счётчиком ссылок, он добавляется значение к счётчику для каждого вызова clone и вычитается значение из счётчика, когда каждое клонированное значение удаляется при выходе из области видимости. Но он не использует простейшие многопоточности, чтобы обеспечить, что изменения в подсчёте не могут быть прерваны другим потоком. Это может привести к неправильным подсчётам - незначительным ошибкам, которые в свою очередь, могут привести к утечкам памяти или удалению значения до того, как мы отработали с ним. Нам нужен вид точно такой же как Rc<T>, но который позволяет изменять счётчик ссылок безопасно из разных потоков.

-

Атомарный счётчик ссылок Arc<T>

-

К счастью, Arc<T> является видом подобным виду Rc<T>, который безопасен для использования в случаейх многопоточности. Буква А означает атомарное, что означает вид ссылка подсчитываемая атомарно. Atomics - это дополнительный вид простейших для многопоточности, который мы не будем здесь подробно описывать: дополнительную сведения смотрите в документации встроенной библиотеки для std::sync::atomic. На данный мгновение вам просто нужно знать, что atomics работают как простые виды, но безопасны для совместного использования между потоками.

-

Вы можете спросить, почему все простые виды не являются атомарными и почему обычные виды библиотек не выполнены для использования вместе с видом Arc<T> по умолчанию. Причина в том, что безопасность потоков сопровождается снижением производительности, которое вы хотите платить только тогда, когда вам это действительно нужно. Если вы просто выполняете действия со значениями в одном потоке, то ваш код может работать быстрее, если он не должен обеспечивать заверения предоставляемые atomics.

-

Давайте вернёмся к нашему примеру: виды Arc<T> и Rc<T> имеют одинаковый API, поэтому мы исправляем нашу программу, заменяя вид в строках use, вызове new и вызове clone. Код в приложении 16-15, наконец собирается и запустится:

-

Файл: src/main.rs

-
use std::sync::{Arc, Mutex};
-use std::thread;
-
-fn main() {
-    let counter = Arc::new(Mutex::new(0));
-    let mut handles = vec![];
-
-    for _ in 0..10 {
-        let counter = Arc::clone(&counter);
-        let handle = thread::spawn(move || {
-            let mut num = counter.lock().unwrap();
-
-            *num += 1;
-        });
-        handles.push(handle);
-    }
-
-    for handle in handles {
-        handle.join().unwrap();
-    }
-
-    println!("Result: {}", *counter.lock().unwrap());
-}
-

Приложение 16-15: Использование вида Arc<T> для обёртывания Mutex<T>, теперь несколько потоков могут совместно владеть мьютексом

-

Код напечатает следующее:

- -
Result: 10
-
-

Мы сделали это! Мы посчитали от 0 до 10, что может показаться не очень впечатляющим, но это позволило больше узнать про Mutex<T> и безопасность потоков. Вы также можете использовать устройство этой программы для выполнения более сложных действий, чем просто увеличение счётчика. Используя эту стратегию, вы можете разделить вычисления на независимые части, разделить эти части на потоки, а затем использовать Mutex<T>, чтобы каждый поток обновлял конечный итог своей частью кода.

-

Обратите внимание, что если вы выполняете простые числовые действия, то существуют виды более простые, чем Mutex<T>, которые предоставляет звено std::sync::atomic встроенной библиотеки. Эти виды обеспечивают безопасный, одновременный, атомарный доступ к простым видам. Мы решили использовать Mutex<T> с простым видом в этом примере, чтобы подробнее рассмотреть, как работает Mutex<T>.

-

Сходства RefCell<T> / Rc<T> и Mutex<T> / Arc<T>

-

Вы могли заметить, что counter сам по себе не изменяемый (immutable), но мы можем получить изменяемую ссылку на значение внутри него; это означает, что Mutex<T> обеспечивает внутреннюю изменяемость, также как и семейство Cell видов. Мы использовали RefCell<T> в главе 15, чтобы получить возможность изменять содержимое внутри Rc<T>, теперь подобным образом мы используем Mutex<T> для изменения содержимого внутри Arc<T> .

-

Ещё одна подробность, на которую стоит обратить внимание: Ржавчина не может защитить вас от всевозможных логических ошибок при использовании Mutex<T>. Вспомните в главе 15, что использование Rc<T> сопряжено с риском создания ссылочной зацикленности, где два значения Rc<T> ссылаются друг на друга, что приводит к утечкам памяти. Подобным образом, Mutex<T> сопряжён с риском создания взаимных блокировок (deadlocks). Это происходит, когда действия необходимо заблокировать два ресурса и каждый из двух потоков получил одну из блокировок, заставляя оба потока ждать друг друга вечно. Если вам важна направление взаимных блокировок, попробуйте создать программу Rust, которая её содержит; затем исследуйте стратегии устранения взаимных блокировок для мьютексов на любом языке и попробуйте выполнить их в Rust. Документация встроенной библиотеки для Mutex<T> и MutexGuard предлагает полезную сведения.

-

Мы завершим эту главу, рассказав о особенностях Send и Sync и о том, как мы можем использовать их с пользовательскими видами.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch16-04-extensible-concurrency-sync-and-send.html b/rustbook-ru/book/ch16-04-extensible-concurrency-sync-and-send.html deleted file mode 100644 index 7ae064ba4..000000000 --- a/rustbook-ru/book/ch16-04-extensible-concurrency-sync-and-send.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - Расширяемый одновременность с помощью особенностей Sync и Send - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Расширенная многопоточность с помощью особенностей Sync и Send

-

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

-

Тем не менее, в язык встроены две подходы многопоточности: std::marker особенности Sync и Send.

-

Разрешение передачи во владение между потоками с помощью Send

-

Маркерный особенность Send указывает, что владение видом выполняющим Send, может передаваться между потоками. Почти каждый вид Ржавчина является видом Send, но есть некоторые исключения, вроде Rc<T>: он не может быть Send, потому что если вы клонировали значение Rc<T> и попытались передать владение клоном в другой поток, оба потока могут обновить счётчик ссылок одновременно. По этой причине Rc<T> выполнен для использования в однопоточных случаейх, когда вы не хотите платить за снижение производительности.

-

Следовательно, система видов Ржавчина и ограничений особенности заверяют, что вы никогда не сможете случайно небезопасно отправлять значение Rc<T> между потоками. Когда мы попытались сделать это в приложении 16-14, мы получили ошибку, the trait Send is not implemented for Rc<Mutex<i32>>. Когда мы переключились на Arc<T>, который является видом Send, то код собрался.

-

Любой вид полностью состоящий из видов Send самостоятельно помечается как Send. Почти все простые виды являются Send, кроме сырых указателей, которые мы обсудим в главе 19.

-

Разрешение доступа из нескольких потоков с Sync

-

Маркерный особенность Sync указывает, что на вид выполняющий Sync можно безопасно ссылаться из нескольких потоков. Другими словами, любой вид T является видом Sync, если &T (ссылка на T ) является видом Send, что означает что ссылку можно безопасно отправить в другой поток. Подобно Send, простые виды являются видом Sync, а виды полностью объединенные из видов Sync, также являются Sync видом.

-

Умный указатель Rc<T> не является Sync видом по тем же причинам, по которым он не является Send. Вид RefCell<T> (о котором мы говорили в главе 15) и семейство связанных видов Cell<T> не являются Sync. Выполнение проверки заимствования, которую делает вид RefCell<T> во время выполнения программы не является поточно-безопасной. Умный указатель Mutex<T> является видом Sync и может использоваться для совместного доступа из нескольких потоков, как вы уже видели в разделе «Совместное использование Mutex<T> между несколькими потоками» .

-

Выполнение Send и Sync вручную небезопасна

-

Поскольку виды созданные из особенностей Send и Sync самостоятельно также являются видами Send и Sync, мы не должны выполнить эти особенности вручную. Являясь маркерными особенностями у них нет никаких способов для выполнения. Они просто полезны для выполнения неизменных величин, связанных с многопоточностью.

-

Ручная выполнение этих особенностей включает в себя выполнение небезопасного кода Rust. Мы поговорим об использовании небезопасного кода Ржавчина в главе 19; на данный мгновение важная сведения заключается в том, что для создания новых многопоточных видов, не состоящих из частей Send и Sync необходимо тщательно продумать заверения безопасности. В Rustonomicon есть больше сведений об этих заверениях и о том как их соблюдать.

-

Итоги

-

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

-

Как упоминалось ранее, поскольку в языке Ржавчина очень мало того, с помощью чего можно управлять многопоточностью, многие решения выполнены в виде ящиков. Они развиваются быстрее, чем обычная библиотека, поэтому обязательно поищите в Интернете текущие современные ящики.

-

Обычная библиотека Ржавчина предоставляет потоки для передачи сообщений и виды умных указателей, такие как Mutex<T> и Arc<T>, которые можно безопасно использовать в многопоточных средах. Система видов и анализатор заимствований заверяют, что код использующий эти решения не будет содержать гонки данных или недействительные ссылки. Получив собирающийся код, вы можете быть уверены, что он будет успешно работать в нескольких потоках без ошибок, которые трудно обнаружить в других языках. Многопоточное программирование больше не является подходом, которую стоит опасаться: иди вперёд и сделай свои программы многопоточными безбоязненно!

-

Далее мы поговорим об идиоматичных способах расчетов неполадок и внутреннего выстраивания -решений по мере усложнения ваших программ на Rust. Кроме того, мы обсудим как идиомы Ржавчина связаны с теми, с которыми вы, возможно, знакомы по предметно-направленному программированию.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch17-00-oop.html b/rustbook-ru/book/ch17-00-oop.html deleted file mode 100644 index 2d90fdf50..000000000 --- a/rustbook-ru/book/ch17-00-oop.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - Возможности предметно-направленного программирования Rust - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Возможности предметно-направленного программирования в Rust

-

Предметно-направленное программирование (ООП) — это способ построения программ. Предметы, как программная подход, были введены в язык программирования Simula в 1960-х годах. Эти предметы повлияли на архитектуру программирования Алана Кея, в которой предметы передают сообщения друг другу. Чтобы описать эту архитектуру, он ввёл понятие предметно-направленное программирование в 1967 году. Есть много состязающихся определений ООП, и по некоторым из этих определений Ржавчина является предметно-направленным, а по другим — нет. В этой главе мы рассмотрим некоторые свойства, которые обычно считаются предметно-направленными, и то, как эти свойства транслируются в идиомы языка Rust. Затем мы покажем, как выполнить образец предметно-направленного разработки в Rust, и обсудим соглашения между этим исходом и решением, использующим вместо этого некоторые сильные стороны Rust.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch17-01-what-is-oo.html b/rustbook-ru/book/ch17-01-what-is-oo.html deleted file mode 100644 index d6bd09453..000000000 --- a/rustbook-ru/book/ch17-01-what-is-oo.html +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - Свойства предметно-направленных языков - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Свойства предметно-направленных языков

-

В сообществе программистов нет единого мнения о том, какими свойствами должен обладать язык, чтобы считаться предметно-направленным. На Ржавчина повлияли многие парадигмы программирования, включая ООП - например, в главе 13 мы изучали особенности, пришедшие из функционального программирования. Однозначно можно утверждать, что ООП-языкам присущи следующие присущие особенности: предметы, инкапсуляция и наследование. Давайте рассмотрим, что каждая из них означает и поддерживает ли их Rust.

-

Предметы содержат данные и поведение

-

Книга Приёмы предметно-направленного разработки. Образцы разработки Erich Gamma, Richard Helm, Ralph Johnson, и John Vlissides (Addison-Wesley Professional, 1994), в просторечии называемая Книга банды четырёх, представляет собой сборник примеров предметно-направленного разработки. В ней даётся следующее определение ООП:

-
-

Предметно-направленные программы состоят из предметов. Предмет представляет собой сущность, своего рода дополнение, с данными и процедурами, которые работают с этими данными. Процедуры обычно называются способами или действиеми.

-
-

В соответствии с этим определением, Ржавчина является предметно-направленным языком - в устройствах и перечислениях содержатся данные, а в х impl определяются способы для них. Хотя устройства и перечисления, имеющие способы, не называются предметами, они обеспечивают возможность, соответствующую определению предметов в книге банды четырёх.

-

Инкапсуляция, скрывающая подробности выполнения

-

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

-

В главе 7 мы уже говорили о том, как управлять инкапсуляцией: мы можем использовать ключевое слово pub, чтобы определить, какие звенья, виды, функции и способы в нашем коде будут открытыми, а всё остальное по умолчанию будет закрытыми. Например, мы можем определить устройство AveragedCollection, в которой есть поле, содержащее вектор значений i32. Также, устройства будет иметь поле, содержащее среднее арифметическое чисел этого вектора, таким образом, среднее не нужно будет вычислять каждый раз, когда оно кому-то понадобится. Другими словами, AveragedCollection будет кэшировать вычисленное среднее для нас. В приложении 17-1 приведено определение устройства AveragedCollection:

-

Файл: src/lib.rs

-
pub struct AveragedCollection {
-    list: Vec<i32>,
-    average: f64,
-}
-

Приложение 17-1: устройства AveragedCollection содержит список целых чисел и их среднее арифметическое.

-

Обратите внимание, что устройства помечена ключевым словом pub, что позволяет другому коду её использовать, однако, поля устройства остаются недоступными. Это важно, потому что мы хотим обеспечить обновление среднего значения при добавлении или удалении элемента из списка. Мы можем получить нужное поведение, определив в устройстве способы add, remove и average, как показано в примере 17-2:

-

Файл: src/lib.rs

-
pub struct AveragedCollection {
-    list: Vec<i32>,
-    average: f64,
-}
-
-impl AveragedCollection {
-    pub fn add(&mut self, value: i32) {
-        self.list.push(value);
-        self.update_average();
-    }
-
-    pub fn remove(&mut self) -> Option<i32> {
-        let result = self.list.pop();
-        match result {
-            Some(value) => {
-                self.update_average();
-                Some(value)
-            }
-            None => None,
-        }
-    }
-
-    pub fn average(&self) -> f64 {
-        self.average
-    }
-
-    fn update_average(&mut self) {
-        let total: i32 = self.list.iter().sum();
-        self.average = total as f64 / self.list.len() as f64;
-    }
-}
-

Приложение 17-2: Выполнение открытых способов add,remove, и average для AveragedCollection

-

Открытые способы add, remove и average являются единственным способом получить или изменить данные в образце AveragedCollection. Когда элемент добавляется в list способом add, или удаляется с помощью способа remove, код выполнения каждого из этих способов вызывает закрытый способ update_average, который позаботится об обновлении поля average.

-

Мы оставляем поля list и average закрытыми, чтобы внешний код не мог добавлять или удалять элементы непосредственно в поле list; в противном случае поле average может оказаться не согласовано при подобном вмешательстве. Способ average возвращает значение в поле average, что позволяет внешнему коду читать значение average, но не изменять его.

-

Поскольку мы инкапсулировали подробности выполнения устройства AveragedCollection, мы можем легко изменить такие особенности, как устройства данных, в будущем. Например, мы могли бы использовать HashSet<i32> вместо Vec<i32> для поля list. Благодаря тому, что ярлыки открытых способов add, remove и average остаются неизменными, код, использующий AveragedCollection, также не будет нуждаться в изменении. У нас бы не получилось этого достичь, если бы мы сделали поле list доступным внешнему коду: HashSet<i32> иVec<i32> имеют разные способы для добавления и удаления элементов, поэтому внешний код, вероятно, должен измениться, если он изменяет list напрямую.

-

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

-

Наследование как система видов и способ совместного использования кода

-

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

-

Если язык должен иметь наследование, чтобы быть предметно-направленным, то Ржавчина таким не является. Здесь нет способа определить устройство, наследующую поля и выполнения способов родительской устройства, без использования макроса.

-

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

-

Вы могли бы выбрать наследование по двум основным причинам. Одна из них - возможность повторного использования кода: вы можете выполнить определённое поведение для одного вида, а наследование позволит вам повторно использовать эту выполнение для другого вида. В Ржавчина для этого есть ограниченный способ, использующий выполнение способа особенности по умолчанию, который вы видели в приложении 10-14, когда мы добавили выполнение по умолчанию в способе summarize особенности Summary. Любой вид, выполняющий свойство Summary будет иметь доступный способ summarize без дополнительного кода. Это похоже на то, как родительский класс имеет выполнение способа, и класс-наследник тоже имеет выполнение способа. Мы также можем переопределить выполнение по умолчанию для способа summarize, когда выполняем особенность Summary, что похоже на дочерний класс, переопределяющий выполнение способа, унаследованного от родительского класса.

-

Вторая причина использования наследования относится к системе видов: чтобы иметь возможность использовать дочерний вид в тех же места, что и родительский. Эта возможность также называется полиморфизм и означает возможность подменять предметы во время исполнения, если они имеют одинаковые свойства.

-
-

Полиморфизм

-

Для многих людей полиморфизм является родственным наследования. Но на самом деле это более общая подход, относящаяся к коду, который может работать с данными нескольких видов. Обычно такими видами выступают подклассы при наследовании.

-

Вместо этого Ржавчина использует обобщённые виды для абстрагирования от видов, и ограничения особенностей (trait bounds) для указания того, какие возможности эти виды должны предоставлять. Это иногда называют ограниченным свойствоическим полиморфизмом.

-
-

Наследование, как подход к разработке, в последнее время утратило распространенность во многих языках программирования, поскольку часто существует риск, что мы будем наследовать код чаще, чем это необходимо. Подклассы не всегда должны обладать всеми свойствами родительского класса, но при использовании наследования другого исхода нет. Это может сделать внешний вид программы менее гибким. Кроме этого, появляется возможность вызова у подклассов способов, которые не имеют смысла или вызывают ошибки, потому что эти способы неприменимы к подклассу. Кроме того, в некоторых языках разрешается только одиночное наследование (т.е. подкласс может наследоваться только от одного класса), что ещё больше ограничивает гибкость разработки программы.

-

По этим причинам в Ржавчина применяется иной подход, с использованием особенностей-предметов вместо наследования. Давайте посмотрим как особенности-предметы выполняют полиморфизм в Rust.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch17-02-trait-objects.html b/rustbook-ru/book/ch17-02-trait-objects.html deleted file mode 100644 index 041197178..000000000 --- a/rustbook-ru/book/ch17-02-trait-objects.html +++ /dev/null @@ -1,421 +0,0 @@ - - - - - - Использование особенность-предметов, допускающих значения разных видов - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

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

-

В главе 8 мы упоминали, что одним из ограничений векторов является то, что они могут хранить элементы только одного вида. Мы создали обходное решение в приложении 8-9, где мы определили перечисление SpreadsheetCell в котором были исходы для хранения целых чисел, чисел с плавающей точкой и текста. Это означало, что мы могли хранить разные виды данных в каждой ячейке и при этом иметь вектор, представляющий строку из ячеек. Это очень хорошее решение, когда наши взаимозаменяемые элементы вектора являются видами с конечным набором, известным при сборки кода.

-

Однако иногда мы хотим, чтобы пользователь нашей библиотеки мог расширить набор видов, которые допустимы в именно случаи. Чтобы показать как этого добиться, мы создадим пример средства с графическим внешней оболочкой пользователя (GUI), который просматривает список элементов, вызывает способ draw для каждого из них, чтобы нарисовать его на экране - это обычная техника для средств GUI. Мы создадим библиотечный ящик с именем gui, содержащий устройство библиотеки GUI. Этот ящик мог бы включать некоторые готовые виды для использования, такие как Button или TextField. Кроме того, пользователи такого ящика gui захотят создавать свои собственные виды, которые могут быть нарисованы: например, кто-то мог бы добавить вид Image, а кто-то другой добавить вид SelectBox.

-

Мы не будем выполнить полноценную библиотеку GUI для этого примера, но покажем, как её части будут подходить друг к другу. На мгновение написания библиотеки мы не можем знать и определить все виды, которые могут захотеть создать другие программисты. Но мы знаем, что gui должен отслеживать множество значений различных видов и ему нужно вызывать способ draw для каждого из этих значений различного вида. Ему не нужно точно знать, что произойдёт, когда вызывается способ draw, просто у значения будет доступен такой способ для вызова.

-

Чтобы сделать это на языке с наследованием, можно определить класс с именем Component у которого есть способ с названием draw. Другие классы, такие как Button, Image и SelectBox наследуются от Component и следовательно, наследуют способ draw. Каждый из них может переопределить выполнение способа draw, чтобы определить своё пользовательское поведение, но площадка может обрабатывать все виды, как если бы они были образцами Component и вызывать draw у них. Но поскольку в Ржавчина нет наследования, нам нужен другой способ внутренне выстроить

-

gui библиотеку, чтобы позволить пользователям расширять её новыми видами.

-

Определение особенности для общего поведения

-

Чтобы выполнить поведение, которое мы хотим иметь в gui, определим особенность с именем Draw, который будет содержать один способ с названием draw. Затем мы можем определить вектор, который принимает особенность-предмет. Особенность-предмет указывает как на образец вида, выполняющего указанный особенность, так и на внутреннюю таблицу, используемую для поиска способов особенности указанного вида во время выполнения. Мы создаём особенность-предмет в таком порядке: используем какой-нибудь вид указателя, например ссылку & или умный указатель Box<T>, затем ключевое слово dyn, а затем указываем соответствующий особенность. (Мы будем говорить о причине того, что особенность-предметы должны использовать указатель в разделе "Виды изменяемого размера и особенность Sized " главы 19). Мы можем использовать особенность-предметы вместо гибкого или определенного вида. Везде, где мы используем особенность-предмет, система видов Ржавчина проверит во время сборки, что любое значение, используемое в этом среде, будет выполнить нужный особенность у особенность-предмета. Следовательно, нам не нужно знать все возможные виды во время сборки.

-

Мы упоминали, что в Ржавчина мы воздерживаемся называть устройства и перечисления «предметами», чтобы отличать их от предметов в других языках. В устройстве или перечислении данные в полях устройства и поведение в разделах impl разделены, тогда как в других языках данные и поведение объединены в одну подход, часто обозначающуюся как предмет. Тем не менее, особенность-предметы являются более похожими на предметы на других языках, в том смысле, что они сочетают в себе данные и поведение. Но особенность-предметы отличаются от привычных предметов тем, что не позволяют добавлять данные к особенность-предмету. Особенность-предметы обычно не настолько полезны, как предметы в других языках: их определенная цель - обеспечить абстракцию через общее поведение.

-

В приложении 17.3 показано, как определить особенность с именем Draw с помощью одного способа с именем draw:

-

Файл: src/lib.rs

-
pub trait Draw {
-    fn draw(&self);
-}
-

Приложение 17-3: Определение особенности Draw

-

Этот правила написания должен выглядеть знакомым из наших дискуссий о том, как определять особенности в главе 10. Далее следует новый правила написания: в приложении 17.4 определена устройства с именем Screen, которая содержит вектор с именем components. Этот вектор имеет вид Box<dyn Draw>, который и является особенность-предметом; это замена для любого вида внутри Box который выполняет особенность Draw.

-

Файл: src/lib.rs

-
pub trait Draw {
-    fn draw(&self);
-}
-
-pub struct Screen {
-    pub components: Vec<Box<dyn Draw>>,
-}
-

Приложение 17-4: Определение устройства Screen с полем components, которое является вектором особенность-предметов, которые выполняют особенность Draw

-

В устройстве Screen, мы определим способ run, который будет вызывать способ draw каждого элемента вектора components, как показано в приложении 17-5:

-

Файл: src/lib.rs

-
pub trait Draw {
-    fn draw(&self);
-}
-
-pub struct Screen {
-    pub components: Vec<Box<dyn Draw>>,
-}
-
-impl Screen {
-    pub fn run(&self) {
-        for component in self.components.iter() {
-            component.draw();
-        }
-    }
-}
-

Приложение 17-5: Выполнение способа run у устройства Screen, который вызывает способ draw каждого составляющих из вектора

-

Это работает иначе, чем определение устройства, которая использует свойство общего вида с ограничениями особенности. Обобщённый свойство вида может быть заменён только одним определенным видом, тогда как особенность-предметы позволяют нескольким определенным видам замещать особенность-предмет во время выполнения. Например, мы могли бы определить устройство Screen используя общий вид и ограничение особенности, как показано в приложении 17-6:

-

Файл: src/lib.rs

-
pub trait Draw {
-    fn draw(&self);
-}
-
-pub struct Screen<T: Draw> {
-    pub components: Vec<T>,
-}
-
-impl<T> Screen<T>
-where
-    T: Draw,
-{
-    pub fn run(&self) {
-        for component in self.components.iter() {
-            component.draw();
-        }
-    }
-}
-

Приложение 17-6: Иная выполнение устройства Screen и способа run, используя обобщённый вид и ограничения особенности

-

Это исход ограничивает нас образцом Screen, который имеет список составляющих всех видов Button или всех видов TextField. Если у вас когда-либо будут только однородные собрания, использование обобщений и ограничений особенности является предпочтительным, поскольку определения будут мономорфизированы во время сборки для использования с определенными видами.

-

С другой стороны, с помощью способа, использующего особенность-предметы, один образец Screen может содержать Vec<T> который содержит Box<Button>, также как и Box<TextField>. Давайте посмотрим как это работает, а затем поговорим о влиянии на производительность во время выполнения.

-

Выполнения особенности

-

Теперь мы добавим несколько видов, выполняющих особенность Draw. Мы объявим вид Button. Опять же, действительная выполнение библиотеки GUI выходит за рамки этой книги, поэтому тело способа draw не будет иметь никакой полезной выполнения. Чтобы представить, как может выглядеть такая выполнение, устройства Button может иметь поля для width, height и label, как показано в приложении 17-7:

-

Файл: src/lib.rs

-
pub trait Draw {
-    fn draw(&self);
-}
-
-pub struct Screen {
-    pub components: Vec<Box<dyn Draw>>,
-}
-
-impl Screen {
-    pub fn run(&self) {
-        for component in self.components.iter() {
-            component.draw();
-        }
-    }
-}
-
-pub struct Button {
-    pub width: u32,
-    pub height: u32,
-    pub label: String,
-}
-
-impl Draw for Button {
-    fn draw(&self) {
-        // code to actually draw a button
-    }
-}
-

Приложение 17-7: Устройства Button выполняет особенность Draw

-

Поля width, height и label устройства Button будут отличаться от, например, полей других составляющих вроде вида TextField, которая могла бы иметь те же поля плюс поле placeholder. Каждый из видов, который мы хотим нарисовать на экране будет выполнить особенность Draw, но будет использовать отличающийся код способа draw для определения как именно рисовать определенный вид, например Button в этом примере (без действительного кода GUI, который выходит за рамки этой главы). Например, вид Button может иметь дополнительный разделimpl, содержащий способы, относящиеся к тому, что происходит, когда пользователь нажимает кнопку. Эти исходы способов не будут применяться к видам вроде TextField.

-

Если кто-то использующий нашу библиотеку решает выполнить устройство SelectBox, которая имеет width, height и поля options, он выполняет также и особенность Draw для вида SelectBox, как показано в приложении 17-8:

-

Файл: src/main.rs

-
use gui::Draw;
-
-struct SelectBox {
-    width: u32,
-    height: u32,
-    options: Vec<String>,
-}
-
-impl Draw for SelectBox {
-    fn draw(&self) {
-        // code to actually draw a select box
-    }
-}
-
-fn main() {}
-

Приложение 17-8: Другой ящик, использующий gui и выполняющий особенность Draw у устройства SelectBox

-

Пользователь нашей библиотеки теперь может написать свою функцию main для создания образца Screen. К образцу Screen он может добавить SelectBox и Button, поместив каждый из них в Box<T>, чтобы он стал особенность-предметом. Затем он может вызвать способ run у образца Screen, который вызовет draw для каждого из составляющих. Приложение 17-9 показывает эту выполнение:

-

Файл: src/main.rs

-
use gui::Draw;
-
-struct SelectBox {
-    width: u32,
-    height: u32,
-    options: Vec<String>,
-}
-
-impl Draw for SelectBox {
-    fn draw(&self) {
-        // code to actually draw a select box
-    }
-}
-
-use gui::{Button, Screen};
-
-fn main() {
-    let screen = Screen {
-        components: vec![
-            Box::new(SelectBox {
-                width: 75,
-                height: 10,
-                options: vec![
-                    String::from("Yes"),
-                    String::from("Maybe"),
-                    String::from("No"),
-                ],
-            }),
-            Box::new(Button {
-                width: 50,
-                height: 10,
-                label: String::from("OK"),
-            }),
-        ],
-    };
-
-    screen.run();
-}
-

Приложение 17-9: Использование особенность-предметов для хранения значений разных видов, выполняющих один и тот же особенность

-

Когда мы писали библиотеку, мы не знали, что кто-то может добавить вид SelectBox, но наша выполнение Screen могла работать с новым видом и рисовать его, потому что SelectBox выполняет особенность Draw, что означает, что он выполняет способ draw.

-

Эта подход, касающаяся только сообщений, на которые значение отвечает, в отличие от определенного вида у значения, подобна подходы duck typing в изменяемых строго определенных языках: если что-то ходит как утка и крякает как утка, то она должна быть утка! В выполнения способа run у Screen в приложении 17-5, run не нужно знать каким будет определенный вид каждого составляющих. Он не проверяет, является ли составляющая образцом Button или SelectBox, он просто вызывает способ draw составляющих. Указав Box<dyn Draw> в качестве вида значений в векторе components, мы определили Screen для значений у которых мы можем вызвать способ draw.

-

Преимущество использования особенность-предметов и системы видов Ржавчина для написания кода, похожего на код с использованием подходы duck typing состоит в том, что нам не нужно во время выполнения проверять выполняет ли значение в векторе определенный способ или беспокоиться о получении ошибок, если значение не выполняет способ, мы все равно вызываем способ. Ржавчина не собирает наш код, если значения не выполняют особенность, который нужен особенность-предмета..

-

Например, в приложении 17-10 показано, что произойдёт, если мы попытаемся создать Screen с String в качестве его составляющих:

-

Файл: src/main.rs

-
use gui::Screen;
-
-fn main() {
-    let screen = Screen {
-        components: vec![Box::new(String::from("Hi"))],
-    };
-
-    screen.run();
-}
-

Приложение 17-10: Попытка использования вида, который не выполняет особенность для особенность-предмета

-

Мы получим ошибку, потому что String не выполняет особенность Draw:

-
$ cargo run
-   Compiling gui v0.1.0 (file:///projects/gui)
-error[E0277]: the trait bound `String: Draw` is not satisfied
- --> src/main.rs:5:26
-  |
-5 |         components: vec![Box::new(String::from("Hi"))],
-  |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Draw` is not implemented for `String`
-  |
-  = help: the trait `Draw` is implemented for `Button`
-  = note: required for the cast from `Box<String>` to `Box<dyn Draw>`
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `gui` (bin "gui") due to 1 previous error
-
-

Эта ошибка даёт понять, что либо мы передаём в составляющая Screen что-то, что мы не собирались передавать и мы тогда должны передать другой вид, либо мы должны выполнить особенность Draw у вида String, чтобы Screen мог вызывать draw у него.

-

Особенность-предметы выполняют изменяемую управление (связывание)

-

Вспомните, в разделе «Производительность кода, использующего обобщённые виды» в главе 10 наше обсуждение этапа мономорфизации, выполняемого сборщиком, когда мы используем ограничения особенностей для обобщённых видов: сборщик порождает частные выполнения функций и способов для каждого определенного вида, который мы применяем для свойства обобщённого вида. Код, который получается в итоге мономорфизации, выполняет постоянную управление , то есть когда сборщик знает, какой способ вы вызываете во время сборки. Это противоположно изменяемой управления, когда сборщик не может определить во время сборки, какой способ вы вызываете. В случае изменяемой управления сборщик создает код, который во время выполнения определит, какой способ нужно вызвать.

-

Когда мы используем особенность-предметы, Ржавчина должен использовать изменяемую управление. Сборщик не знает всех видов, которые могут быть использованы с кодом, использующим особенность-предметы, поэтому он не знает, какой способ выполнен для какого вида при вызове. Вместо этого, во время выполнения, Ржавчина использует указатели внутри особенность-предмета. чтобы узнать какой способ вызвать. Такой поиск вызывает дополнительные затраты во время исполнения, которые не требуются при постоянной управления. Изменяемая управление также не позволяет сборщику выбрать встраивание кода способа, что в свою очередь делает невозможными некоторые переработки. Однако мы получили дополнительную гибкость в коде, который мы написали в приложении 17-5, и которую смогли поддержать в приложении 17-9, поэтому все "за" и "против" нужно рассматривать в совокупности.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch17-03-oo-design-patterns.html b/rustbook-ru/book/ch17-03-oo-design-patterns.html deleted file mode 100644 index 2c3641128..000000000 --- a/rustbook-ru/book/ch17-03-oo-design-patterns.html +++ /dev/null @@ -1,807 +0,0 @@ - - - - - - Выполнение образца предметно-направленного разработки - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Выполнение предметно-направленного образца разработки

-

Образец "Состояние" — это предметно-направленный образец разработки. Суть образца заключается в том, что мы определяем набор состояний, которые может иметь внутреннее значение. Состояния представлены набором предметов состояния, а поведение элемента изменяется в зависимости от его состояния. Мы рассмотрим пример устройства записи в блоге, в которой есть поле для хранения состояния, которое будет предметом состояния из набора «черновик», «обзор» или «обнародовано».

-

Предметы состояния имеют общую возможность: конечно в Ржавчина мы используем устройства и особенности, а не предметы и наследование. Каждый предмет состояния отвечает за своё поведение и сам определяет, когда он должен перейти в другое состояние. Элемент, который содержит предмет состояния, ничего не знает о различиях в поведении состояний или о том, когда одно состояние должно перейти в другое.

-

Преимуществом образца "Состояние" является то, что при изменении требований заказчика программы не требуется изменять код элемента, содержащего состояние, или код, использующий такой элемент. Нам нужно только обновить код внутри одного из предметов состояния, чтобы изменить его порядок действий, либо, возможно, добавить больше предметов состояния.

-

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

-

Окончательный возможности будет выглядеть так:

-
    -
  1. Запись в блоге создаётся как пустой черновик.
  2. -
  3. Когда черновик готов, запрашивается его проверка.
  4. -
  5. После проверки происходит обнародование записи.
  6. -
  7. Только обнародованные записи блога возвращают содержимое записи на печать, поэтому сообщения, не прошедшие проверку, не могут быть обнародованы случайно.
  8. -
-

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

-

В приложении 17-11 показан этот поток действий в виде кода: это пример использования API, который мы собираемся выполнить в библиотеке (ящике) с именем blog. Он пока не собирается, потому что ящик blog ещё не создан.

-

Файл: src/main.rs

-
use blog::Post;
-
-fn main() {
-    let mut post = Post::new();
-
-    post.add_text("I ate a salad for lunch today");
-    assert_eq!("", post.content());
-
-    post.request_review();
-    assert_eq!("", post.content());
-
-    post.approve();
-    assert_eq!("I ate a salad for lunch today", post.content());
-}
-

Приложение 17-11: Код, отображающий желаемое поведение, которое мы хотим получить в ящике blog

-

Мы хотим, чтобы пользователь мог создать новый черновик записи в блоге с помощью Post::new. Затем мы хотим разрешить добавление текста в запись блога. Если мы попытаемся получить содержимое записи сразу, до её проверки, мы не должны получить никакого текста на выходе, потому что запись все ещё является черновиком. Мы добавили утверждение (assert_eq!) в коде для опытных целей. Утверждение (assertion), что черновик записи блога должен возвращать пустую строку из способа content было бы отличным состоящим из звеньев проверкой, но мы не собираемся писать проверки для этого примера.

-

Далее мы хотим разрешить сделать запрос на проверку записи и хотим, чтобы content возвращал пустую строку, пока проверки не завершена. Когда запись пройдёт проверку, она должна быть обнародована, то есть при вызове content будет возвращён текст записи.

-

Обратите внимание, что единственный вид из ящика, с которым мы взаимодействуем - это вид Post. Этот вид будет использовать образец "Состояние" и будет содержать значение, которое будет являться одним из трёх предметов состояний, представляющих различные состояния, в которых может находиться запись: "черновик", "ожидание проверки" или "обнародовано". Управление переходом из одного состояния в другое будет осуществляться внутренней логикой вида Post. Состояния будут переключаться в итоге реакции на вызов способов образца Post пользователями нашей библиотеки, но пользователи не должны управлять изменениями состояния напрямую. Кроме того, пользователи не должны иметь возможность ошибиться с состояниями, например, обнародовать сообщение до его проверки.

-

Определение Post и создание нового образца в состоянии черновика

-

Приступим к выполнения библиотеки! Мы знаем, что нам нужна открытая устройства Post, хранящая некоторое содержимое, поэтому мы начнём с определения устройства и связанной с ней открытой функцией new для создания образца Post, как показано в приложении 17-12. Мы также сделаем закрытый особенность State, который будет определять поведение, которое должны будут иметь все предметы состояний устройства Post.

-

Затем Post будет содержать особенность-предмет Box<dyn State> внутри Option<T> в закрытом поле state для хранения предмета состояния. Чуть позже вы поймёте, зачем нужно использовать Option<T> .

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-}
-
-trait State {}
-
-struct Draft {}
-
-impl State for Draft {}
-

Приложение 17-12. Определение устройства Post и функции new, которая создаёт новый образец Post, особенности State и устройства Draft

-

Особенность State определяет поведение, совместно используемое различными состояниями поста. Все предметы состояний (Draft - "черновик", PendingReview - "ожидание проверки" и Published - "обнародовано") будут выполнить особенность State. Пока у этого особенности нет никаких способов, и мы начнём с определения состояния Draft, просто потому, что это первое состояние, с которого, как мы хотим, обнародование будет начинать свой путь.

-

Когда мы создаём новый образец Post, мы устанавливаем его поле state в значение Some, содержащее Box. Этот Box указывает на новый образец устройства Draft. Это заверяет, что всякий раз, когда мы создаём новый образец Post, он появляется как черновик. Поскольку поле state в устройстве Post является закрытым, нет никакого способа создать Post в каком-либо другом состоянии! В функции Post::new мы объявим поле content новой пустой строкой вида String.

-

Хранение текста содержимого записи

-

В приложении 17-11 показано, что мы хотим иметь возможность вызывать способ add_text и передать ему &str, которое добавляется к текстовому содержимому записи блога. Мы выполняем эту возможность как способ, а не делаем поле content открыто доступным, используя pub. Это означает, что позже мы сможем написать способ, который будет управлять, как именно читаются данные из поля content. Способ add_text довольно прост, поэтому давайте добавим его выполнение в разделimpl Postприложения 17-13:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    // --snip--
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-}
-
-trait State {}
-
-struct Draft {}
-
-impl State for Draft {}
-

Приложение 17-13. Выполнение add_text для добавления текста к content (содержимому записи)

-

Способ add_text принимает изменяемую ссылку на self, потому что мы меняем образец Post, для которого вызываем add_text. Затем мы вызываем push_str для String у поля content и передаём text переменнаяом для добавления к сохранённому content. Это поведение не зависит от состояния, в котором находится запись, таким образом оно не является частью образца "Состояние". Способ add_text вообще не взаимодействует с полем state, но это часть поведения, которое мы хотим поддерживать.

-

Убедимся, что содержание черновика будет пустым

-

Даже после того, как мы вызвали add_text и добавили некоторый содержание в нашу запись, мы хотим, чтобы способ content возвращал пустой отрывок строки, так как запись всё ещё находится в черновом состоянии, как это показано в строке 7 приложения 17-11. А пока давайте выполняем способ content наиболее простым способом, который будет удовлетворять этому требованию: будем всегда возвращать пустой отрывок строки. Мы изменим код позже, как только выполняем возможность изменить состояние записи, чтобы она могла бы быть обнародована. Пока что записи могут находиться только в черновом состоянии, поэтому содержимое записи всегда должно быть пустым. Приложение 17-14 показывает такую выполнение-заглушку:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    // --snip--
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn content(&self) -> &str {
-        ""
-    }
-}
-
-trait State {}
-
-struct Draft {}
-
-impl State for Draft {}
-

Приложение 17-14. Добавление выполнения-заглушки для способа content в Post, которая всегда возвращает пустой отрывок строки.

-

С добавленным таким образом способом content всё в приложении 17-11 работает, как задумано, вплоть до строки 7.

-

Запрос на проверку записи меняет её состояние

-

Далее нам нужно добавить возможность для запроса проверки записи, который должен изменить её состояние с Draft на PendingReview. Приложение 17-15 показывает такой код:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    // --snip--
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn content(&self) -> &str {
-        ""
-    }
-
-    pub fn request_review(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.request_review())
-        }
-    }
-}
-
-trait State {
-    fn request_review(self: Box<Self>) -> Box<dyn State>;
-}
-
-struct Draft {}
-
-impl State for Draft {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        Box::new(PendingReview {})
-    }
-}
-
-struct PendingReview {}
-
-impl State for PendingReview {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-

Приложение 17-15. Выполнение способов request_review в устройстве Post и особенности State

-

Мы добавляем в Post открытый способ с именем request_review ("запросить проверку"), который будет принимать изменяемую ссылку на self. Затем мы вызываем внутренний способ request_review для текущего состояния Post, и этот второй способ request_review поглощает текущее состояние и возвращает новое состояние.

-

Мы добавляем способ request_review в особенность State; все виды, выполняющие этот особенность, теперь должны будут выполнить способ request_review. Обратите внимание, что вместо self, &self или &mut self в качестве первого свойства способа у нас указан self: Box<Self>. Этот правила написания означает, что способ действителен только при его вызове с обёрткой Box, содержащей наш вид. Этот правила написания становится владельцем Box<Self>, делая старое состояние недействительным, поэтому значение состояния Post может быть преобразовано в новое состояние.

-

Чтобы поглотить старое состояние, способ request_review должен стать владельцем значения состояния. Это место, где приходит на помощь вид Option поля state записи Post: мы вызываем способ take, чтобы забрать значение Some из поля state и оставить вместо него значение None, потому что Ржавчина не позволяет иметь необъявленные поля в устройствах. Это позволяет перемещать значение state из Post, а не заимствовать его. Затем мы установим новое значение state как итог этой действия.

-

Нам нужно временно установить state в None, вместо того, чтобы установить его напрямую с помощью кода вроде self.state = self.state.request_review();. Нам нужно завладеть значением поля state. Это даст нам заверение, что Post не сможет использовать старое значение state после того, как мы преобразовали его в новое состояние.

-

Способ request_review в Draft должен вернуть новый образец новой устройства PendingReview, обёрнутый в Box. Эта устройства будет представлять состояние, в котором запись ожидает проверки. Устройства PendingReview также выполняет способ request_review, но не выполняет никаких преобразований. Она возвращает сама себя, потому что, когда мы запрашиваем проверку записи, уже находящейся в состоянии PendingReview, она всё так же должна продолжать оставаться в состоянии PendingReview.

-

Теперь мы начинаем видеть преимущества образца "Состояние": способ request_review для Post одинаков, он не зависит от значения state. Каждое состояние само несёт ответственность за свои действия.

-

Оставим способ content у Post таким как есть, возвращающим пустой отрывок строки. Теперь мы можем иметь Post как в состоянии PendingReview, так и в состоянии Draft, но мы хотим получить такое же поведение в состоянии PendingReview. Приложение 17-11 теперь работает до строки 10!

- -

-

Добавление approve для изменения поведения content

-

Способ approve ("одобрить") будет подобен способу request_review: он будет устанавливать у state значение, которое должна иметь запись при её одобрении, как показано в приложении 17-16:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    // --snip--
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn content(&self) -> &str {
-        ""
-    }
-
-    pub fn request_review(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.request_review())
-        }
-    }
-
-    pub fn approve(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.approve())
-        }
-    }
-}
-
-trait State {
-    fn request_review(self: Box<Self>) -> Box<dyn State>;
-    fn approve(self: Box<Self>) -> Box<dyn State>;
-}
-
-struct Draft {}
-
-impl State for Draft {
-    // --snip--
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        Box::new(PendingReview {})
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-
-struct PendingReview {}
-
-impl State for PendingReview {
-    // --snip--
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        Box::new(Published {})
-    }
-}
-
-struct Published {}
-
-impl State for Published {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-

Приложение 17-16. Выполнение способа approve для вида Post и особенности State

-

Мы добавляем способ approve в особенность State, добавляем новую устройство, которая выполняет этот особенность State и устройство для состояния Published.

-

Подобно тому, как работает request_review для PendingReview, если мы вызовем способ approve для Draft, он не будет иметь никакого эффекта, потому что approve вернёт self. Когда мы вызываем для PendingReview способ approve, то он возвращает новый упакованный образец устройства Published. Устройства Published выполняет особенность State, и как для способа request_review, так и для способа approve она возвращает себя, потому что в этих случаях запись должна оставаться в состоянии Published.

-

Теперь нам нужно обновить способ content для Post. Мы хотим, чтобы значение, возвращаемое из content, зависело от текущего состояния Post, поэтому мы собираемся перенести часть возможности Post в способ content, заданный для state, как показано в приложении 17.17:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    // --snip--
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn content(&self) -> &str {
-        self.state.as_ref().unwrap().content(self)
-    }
-    // --snip--
-
-    pub fn request_review(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.request_review())
-        }
-    }
-
-    pub fn approve(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.approve())
-        }
-    }
-}
-
-trait State {
-    fn request_review(self: Box<Self>) -> Box<dyn State>;
-    fn approve(self: Box<Self>) -> Box<dyn State>;
-}
-
-struct Draft {}
-
-impl State for Draft {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        Box::new(PendingReview {})
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-
-struct PendingReview {}
-
-impl State for PendingReview {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        Box::new(Published {})
-    }
-}
-
-struct Published {}
-
-impl State for Published {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-

Приложение 17-17: Обновление способа content в устройстве Post для делегирования части возможности способу content устройства State

-

Поскольку наша цель состоит в том, чтобы сохранить все эти действия внутри устройств, выполняющих особенность State, мы вызываем способ content у значения в поле state и передаём образец обнародования (то есть self ) в качестве переменной. Затем мы возвращаем значение, которое нам выдаёт вызов способа content поля state.

-

Мы вызываем способ as_ref у Option, потому что нам нужна ссылка на значение внутри Option, а не владение значением. Поскольку state является видом Option<Box<dyn State>>, то при вызове способа as_ref возвращается Option<&Box<dyn State>>. Если бы мы не вызывали as_ref, мы бы получили ошибку, потому что мы не можем переместить state из заимствованного свойства &self функции.

-

Затем мы вызываем способ unwrap. Мы знаем, что этот способ здесь никогда не приведёт к со сбоемму завершению программы, так все способы Post устроены таким образом, что после их выполнения, в поле state всегда содержится значение Some. Это один из случаев, про которых мы говорили в разделе "Случаи, когда у вас больше сведений, чем у сборщика" главы 9 - случай, когда мы знаем, что значение None никогда не встретится, даже если сборщик не может этого понять.

-

Теперь, когда мы вызываем content у вида &Box<dyn State>, в действие вступает принудительное приведение (deref coercion) для & и Box, поэтому в конечном итоге способ content будет вызван для вида, который выполняет особенность State. Это означает, что нам нужно добавить способ content в определение особенности State, и именно там мы поместим логику для определения того, какое содержимое возвращать, в зависимости от текущего состояния, как показано в приложении 17-18:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn content(&self) -> &str {
-        self.state.as_ref().unwrap().content(self)
-    }
-
-    pub fn request_review(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.request_review())
-        }
-    }
-
-    pub fn approve(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.approve())
-        }
-    }
-}
-
-trait State {
-    // --snip--
-    fn request_review(self: Box<Self>) -> Box<dyn State>;
-    fn approve(self: Box<Self>) -> Box<dyn State>;
-
-    fn content<'a>(&self, post: &'a Post) -> &'a str {
-        ""
-    }
-}
-
-// --snip--
-
-struct Draft {}
-
-impl State for Draft {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        Box::new(PendingReview {})
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-
-struct PendingReview {}
-
-impl State for PendingReview {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        Box::new(Published {})
-    }
-}
-
-struct Published {}
-
-impl State for Published {
-    // --snip--
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn content<'a>(&self, post: &'a Post) -> &'a str {
-        &post.content
-    }
-}
-

Приложение 17-18. Добавление способа content в особенность State

-

Мы добавляем выполнение по умолчанию способа content, который возвращает пустой отрывок строки. Это означает, что нам не придётся выполнить content в устройствах Draft и PendingReview. Устройства Published будет переопределять способ content и вернёт значение из post.content.

-

Обратите внимание, что для этого способа нам нужны изложении времени жизни, как мы обсуждали в главе 10. Мы берём ссылку на post в качестве переменной и возвращаем ссылку на часть этого post, поэтому время жизни возвращённой ссылки связано с временем жизни переменной post.

-

И вот, мы закончили - теперь всё из приложения 17-11 работает! Мы выполнили образец "Состояние", определяющий правила этапа работы с записью в блоге. Логика, связанная с этими правилами, находится в предмета. состояний, а не разбросана по всей устройстве Post.

-
-

Почему не перечисление?

-

Возможно, вам было важно, почему мы не использовали enum с различными возможными состояниями записи в качестве исходов. Это, безусловно, одно из возможных решений. Попробуйте его выполнить и сравните конечные итоги, чтобы выбрать, какой из исходов вам больше нравится! Одним из недостатков использования перечисления является то, что в каждом месте, где проверяется значение перечисления, потребуется выражение match или что-то подобное для обработки всех возможных исходов. Возможно в этом случае нам придётся повторять больше кода, чем это было в решении с особенность-предметом.

-
-

Соглашенияы образца "Состояние"

-

Мы показали, что Ржавчина способен выполнить предметно-направленный образец "Состояние" для инкапсуляции различных видов поведения, которые должна иметь запись в каждом состоянии. Способы в Post ничего не знают о различных видах поведения. При такой согласования кода, нам достаточно взглянуть только на один его участок, чтобы узнать отличия в поведении обнародованной обнародования: в выполнение особенности State у устройства Published.

-

Если бы мы захотели создать иную выполнение, не использующую образец состояния, мы могли бы вместо этого использовать выражения match в способах Post или даже в main, которые бы проверяли состояние записи и изменяли поведение в этих местах. Это приведёт к тому, что нам придётся в нескольких местах исследовать все следствия того, что пост перешёл в состояние "обнародовано"! И эта нагрузка будет только увеличиваться по мере добавления новых состояний: для каждого из этих выражений match потребуются дополнительные ответвления.

-

С помощью образца "Состояние" способы Post и участки, где мы используем Post, не требуют использования выражений match, а для добавления нового состояния нужно только добавить новую устройство и выполнить способы особенности у одной этой устройства.

-

Выполнение с использованием образца "Состояние" легко расширить для добавления новой возможности. Чтобы увидеть, как легко поддерживать код, использующий данный образец, попробуйте выполнить некоторые из предложений ниже:

-
    -
  • Добавьте способ reject, который изменяет состояние обнародования с PendingReview обратно на Draft.
  • -
  • Потребуйте два вызова способа approve, прежде чем переводить состояние в Published.
  • -
  • Разрешите пользователям добавлять текстовое содержимое только тогда, когда обнародование находится в состоянии Draft. Подсказка: пусть предмет состояния решает, можно ли менять содержимое, но не отвечает за изменение Post.
  • -
-

Одним из недостатков образца "Состояние" является то, что поскольку состояния сами выполняют переходы между собой, некоторые из состояний получаются связанными друг с другом. Если мы добавим другое состояние между PendingReview и Published, например Scheduled ("расчитано наперед"), то придётся изменить код в PendingReview, чтобы оно теперь переходило в Scheduled. Если бы не нужно было менять PendingReview при добавлении нового состояния, было бы меньше работы, но это означало бы, что мы переходим на другой образец разработки.

-

Другим недостатком является то, что мы сделали повторение некоторую логику. Чтобы устранить некоторое повторение, мы могли бы попытаться сделать выполнения по умолчанию для способов request_review и approve особенности State, которые возвращают self; однако это нарушило бы безопасность предмета. потому что особенность не знает, каким определенно будет self. Мы хотим иметь возможность использовать State в качестве особенность-предмета. поэтому нам нужно, чтобы его способы были предметно-безопасными.

-

Другое повторение включает в себя схожие выполнения способов request_review и approve у Post. Оба способа делегируют выполнения одного и того же способа значению поля state вида Option и устанавливают итогом новое значение поля state. Если бы у Post было много способов, которые следовали этому образцу, мы могли бы рассмотреть определение макроса для устранения повторения (смотри раздел "Макросы" в главе 19).

-

Выполняя образец "Состояние" точно так, как он определён для предметно-направленных языков, мы не настолько полно используем преимущества Rust, как могли бы. Давайте посмотрим на некоторые изменения, которые мы можем внести в ящик blog, чтобы недопустимые состояния и переходы превратить в ошибки времени сборки.

-

Кодирование состояний и поведения в виде видов

-

Мы покажем вам, как переосмыслить образец "Состояние", чтобы получить другой набор соглашений. Вместо того, чтобы полностью инкапсулировать состояния и переходы, так, чтобы внешний код не знал о них, мы будем кодировать состояния с помощью разных видов. Следовательно, система проверки видов Ржавчина предотвратит попытки использовать черновые обнародования, там где разрешены только обнародованные обнародования, вызывая ошибки сборки.

-

Давайте рассмотрим первую часть main в приложении 17-11:

-

Файл: src/main.rs

-
use blog::Post;
-
-fn main() {
-    let mut post = Post::new();
-
-    post.add_text("I ate a salad for lunch today");
-    assert_eq!("", post.content());
-
-    post.request_review();
-    assert_eq!("", post.content());
-
-    post.approve();
-    assert_eq!("I ate a salad for lunch today", post.content());
-}
-

Мы по-прежнему поддерживаем создание новых сообщений в состоянии "черновика" с помощью способа Post::new и возможность добавлять текст к содержимому обнародования. Но вместо способа content у чернового сообщения, возвращающего пустую строку, мы сделаем так, что у черновых сообщений вообще не будет способа content. Таким образом, если мы попытаемся получить содержимое черновика, мы получим ошибку сборщика, сообщающую, что способ не существует. В итоге мы не сможем случайно отобразить черновик содержимого записи в работающей программе, потому что этот код даже не собирается. В приложении 17-19 показано определение устройств Post и DraftPost, а также способов для каждой из них:

-

Файл: src/lib.rs

-
pub struct Post {
-    content: String,
-}
-
-pub struct DraftPost {
-    content: String,
-}
-
-impl Post {
-    pub fn new() -> DraftPost {
-        DraftPost {
-            content: String::new(),
-        }
-    }
-
-    pub fn content(&self) -> &str {
-        &self.content
-    }
-}
-
-impl DraftPost {
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-}
-

Приложение 17-19: Устройства Post с способом content и устройства DraftPost без способа content

-

Обе устройства, Post и DraftPost, имеют закрытое поле content, в котором хранится текст сообщения блога. Устройства больше не содержат поле state, потому что мы перемещаем кодирование состояния в виды устройств. Устройства Post будет представлять обнародованную размещение, и у неё есть способ content, который возвращает content.

-

У нас все ещё есть функция Post::new, но вместо возврата образца Post она возвращает образец DraftPost. Поскольку поле content является закрытым и нет никаких функций, которые возвращают Post, просто так создать образец Post уже невозможно.

-

Устройства DraftPost имеет способ add_text, поэтому мы можем добавлять текст к content как и раньше, но учтите, что в DraftPost не определён способ content! Так что теперь программа заверяет, что все записи начинаются как черновики, а черновики размещений не имеют своего содержания для отображения. Любая попытка обойти эти ограничения приведёт к ошибке сборщика.

-

Выполнение переходов в виде преобразований в другие виды

-

Так как же получить обнародованный пост? Мы хотим обеспечить соблюдение правила, согласно которому черновик записи должен быть рассмотрен и утверждён до того, как он будет обнародован. Запись, находящаяся в состоянии проверки, по-прежнему не должна отображать содержимое. Давайте выполняем эти ограничения, добавив ещё одну устройство, PendingReviewPost, определив способ request_review у DraftPost, возвращающий PendingReviewPost, и определив способ approve у PendingReviewPost, возвращающий Post, как показано в приложении 17-20:

-

Файл: src/lib.rs

-
pub struct Post {
-    content: String,
-}
-
-pub struct DraftPost {
-    content: String,
-}
-
-impl Post {
-    pub fn new() -> DraftPost {
-        DraftPost {
-            content: String::new(),
-        }
-    }
-
-    pub fn content(&self) -> &str {
-        &self.content
-    }
-}
-
-impl DraftPost {
-    // --snip--
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn request_review(self) -> PendingReviewPost {
-        PendingReviewPost {
-            content: self.content,
-        }
-    }
-}
-
-pub struct PendingReviewPost {
-    content: String,
-}
-
-impl PendingReviewPost {
-    pub fn approve(self) -> Post {
-        Post {
-            content: self.content,
-        }
-    }
-}
-

Приложение 17-20: Вид PendingReviewPost, который создаётся путём вызова request_review образца DraftPost и способ approve, который превращает PendingReviewPost в обнародованный Post.

-

Способы request_review и approve забирают во владение self, таким образом поглощая образцы DraftPost и PendingReviewPost, которые потом преобразуются в PendingReviewPost и обнародованную Post, соответственно. Таким образом, у нас не будет никаких долгоживущих образцов DraftPost, после того, как мы вызвали у них request_review и так далее. В устройстве PendingReviewPost не определён способ content, поэтому попытка прочитать его содержимое приводит к ошибке сборщика, также как и в случае с DraftPost. Так как единственным способом получить обнародованный образец Post, у которого действительно есть объявленный способ content, является вызов способа approve у образца PendingReviewPost, а единственный способ получить PendingReviewPost - это вызвать способ request_review у образца DraftPost, теперь мы закодировали этап смены состояний записи блога с помощью системы видов.

-

Кроме этого, нужно внести небольшие изменения в main. Так как способы request_review и approve теперь возвращают предметы, а не преобразуют устройство от которой были вызваны, нам нужно добавить больше затеняющих присваиваний let post =, чтобы сохранять возвращаемые предметы. Также, теперь мы не можем использовать утверждения (assertions) для проверки того является ли содержимое черновиков и записей, находящихся на рассмотрении, пустыми строками, да они нам и не нужны - теперь стало невозможным собрать код, который бы пытался использовать содержимое записей, находящихся в этих состояниях. Обновлённый код в main показан в приложении 17-21:

-

Файл: src/main.rs

-
use blog::Post;
-
-fn main() {
-    let mut post = Post::new();
-
-    post.add_text("I ate a salad for lunch today");
-
-    let post = post.request_review();
-
-    let post = post.approve();
-
-    assert_eq!("I ate a salad for lunch today", post.content());
-}
-

Приложение 17-21: Изменения в main, использующие новую выполнение этапа подготовки записи блога

-

Изменения, которые нам нужно было внести в main, чтобы переназначить post означают, что эта выполнение теперь не совсем соответствует предметно-направленному образцу "Состояние": преобразования между состояниями больше не инкапсулированы внутри выполнения Post полностью. Тем не менее, мы получили большую выгоду в том, что недопустимые состояния теперь невозможны из-за системы видов и проверки видов, которая происходит во время сборки! У нас есть заверенияия, что некоторые ошибки, такие как отображение содержимого необнародованной обнародования, будут обнаружены до того, как они дойдут до пользователей.

-

Попробуйте выполнить задачи, предложенные в начале этого раздела, в исполнения ящика blog, каким он стал после приложения 17-20, чтобы создать своё мнение о внешнем виде этой исполнения кода. Обратите внимание, что некоторые задачи в этом исходе могут быть уже выполнены.

-

Мы увидели, что хотя Ржавчина и способен выполнить предметно-направленные образцы разработки, в нём также доступны и другие образцы, такие как кодирование состояния с помощью системы видов. Эти подходы имеют различные соглашения. Хотя вы, возможно, очень хорошо знакомы с предметно-направленными образцами, переосмысление неполадок для использования преимуществ и возможностей Ржавчина может дать такие выгоды, как предотвращение некоторых ошибок во время сборки. Предметно-направленные образцы не всегда будут лучшим решением в Ржавчина из-за наличия определённых возможностей, таких как владение, которого нет у предметно-направленных языков.

-

Итоги

-

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

-

Далее мы рассмотрим образцы, которые являются ещё одной особенностью Rust, обеспечивающей высокую гибкость. Мы бегло рассказывали о них на протяжении всей книги, но ещё не видели всех их возможностей. Вперёд!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch18-00-patterns.html b/rustbook-ru/book/ch18-00-patterns.html deleted file mode 100644 index 345558c41..000000000 --- a/rustbook-ru/book/ch18-00-patterns.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - Образцы и сопоставление - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Образцы и сопоставление

-

Образцы - это особый правила написания в Ржавчина для сопоставления со устройством видов, как сложных, так и простых. Использование образцов в сочетании с выражениями match и другими устройствоми даёт вам больший управление над потоком управления программы. Образец состоит из некоторой сочетания следующего:

-
    -
  • Записи
  • -
  • Деупорядоченные массивы, перечисления, устройства или упорядоченные ряды
  • -
  • Переменные
  • -
  • Особые символы
  • -
  • Заполнители
  • -
-

Некоторые примеры образцов включают x , (a, 3) и Some(Color::Red) . В средах, в которых допустимы образцы, эти составляющие описывают разновидность данных. Затем наша программа сопоставляет значения с образцами, чтобы определить, имеет ли значение правильную разновидность данных для продолжения выполнения определённого отрывка кода.

-

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

-

Эта глава - справочник по всем особенностим, связанным с образцами. Мы расскажем о допустимых местах использования образцов, разнице между опровержимыми и неопровержимыми образцами и про различные виды правил написания образцов, которые вы можете увидеть. К концу главы вы узнаете, как использовать образцы для ясного выражения многих понятий.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch18-01-all-the-places-for-patterns.html b/rustbook-ru/book/ch18-01-all-the-places-for-patterns.html deleted file mode 100644 index e8083015b..000000000 --- a/rustbook-ru/book/ch18-01-all-the-places-for-patterns.html +++ /dev/null @@ -1,373 +0,0 @@ - - - - - - Все места, где могут использоваться образцы - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Все случаи, где могут быть использованы образцы

-

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

-

Ветки match

-

Как обсуждалось в главе 6, мы используем образцы в ветках выражений match. Условновыражения match определяется как ключевое слово match, значение используемое для сопоставления, одна или несколько веток, которые состоят из образца и выражения для выполнения, если значение соответствует образцу этой ветки, как здесь:

-
match VALUE {
-    PATTERN => EXPRESSION,
-    PATTERN => EXPRESSION,
-    PATTERN => EXPRESSION,
-}
-
-

Например, вот выражение match из приложения 6-5, которое соответствует значению Option<i32> в переменной x:

-
match x {
-    None => None,
-    Some(i) => Some(i + 1),
-}
-

Образцами в этом выражении match являются None и Some(i) слева от каждой стрелки.

-

Одно из требований к выражениям match состоит в том, что они должны быть исчерпывающими (exhaustive) в том смысле, что они должны учитывать все возможности для значения в выражении match. Один из способов убедиться, что вы рассмотрели каждую возможность - это иметь образец перехвата всех исходов в последней ветке выражения: например, имя переменной, совпадающее с любым значением, никогда не может потерпеть неудачу и таким образом, охватывает каждый оставшийся случай.

-

Особый образец _ будет соответствовать чему угодно, но он никогда не привязывается к переменной, поэтому он часто используется в последней ветке. Образец _ может быть полезен, если вы, например, хотите пренебрегать любое не указанное значение. Мы рассмотрим образец _ более подробно в разделе "Пренебрежение значений в образце позже в этой главе.

-

Условные выражения if let

-

В главе 6 мы обсуждали, как использовать выражения if let как правило в качестве более короткого способа записи эквивалента match, которое обрабатывает только один случай. Дополнительно if let может иметь соответствующий else, содержащий код для выполнения, если образец выражения if let не совпадает.

-

В приложении 18-1 показано, что можно также смешивать и сопоставлять выражения if let, else if и else if let. Это даёт больше гибкости, чем match выражение, в котором можно выразить только одно значение для сравнения с образцами. Кроме того, условия в серии if let, else if, else if let не обязаны соотноситься друг с другом.

-

Код в приложении 18-1 показывает последовательность проверок нескольких условий, определяющих каким должен быть цвет фона. В данном примере мы создали переменные с предопределёнными значениями, которые в существующей программе могли бы быть получены из пользовательского ввода.

-

Файл: src/main.rs

-
fn main() {
-    let favorite_color: Option<&str> = None;
-    let is_tuesday = false;
-    let age: Result<u8, _> = "34".parse();
-
-    if let Some(color) = favorite_color {
-        println!("Using your favorite color, {color}, as the background");
-    } else if is_tuesday {
-        println!("Tuesday is green day!");
-    } else if let Ok(age) = age {
-        if age > 30 {
-            println!("Using purple as the background color");
-        } else {
-            println!("Using orange as the background color");
-        }
-    } else {
-        println!("Using blue as the background color");
-    }
-}
-

Приложение 18-1: Использование условных устройств if let, else if, else if let, и else

-

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

-

Эта условная устройства позволяет поддерживать сложные требования. С жёстко закодированными значениями, которые у нас здесь есть, этот пример напечатает Using purple as the background color.

-

Можно увидеть, что if let может также вводить затенённые переменные, как это можно сделать в match ветках: строка if let Ok(age) = age вводит новую затенённую переменную age, которая содержит значение внутри исхода Ok. Это означает, что нам нужно поместить условие if age > 30 внутри этого блок: мы не можем объединить эти два условия в if let Ok(age) = age && age > 30. Затенённый age, который мы хотим сравнить с 30, не является действительным, пока не начнётся новая область видимости с фигурной скобки.

-

Недостатком использования if let выражений является то, что сборщик не проверяет полноту (exhaustiveness) всех исходов, в то время как с помощью выражения match это происходит. Если мы не напишем последний разделelse и, благодаря этому, пропустим обработку некоторых случаев, сборщик не предупредит нас о возможной логической ошибке.

-

Условные циклы while let

-

Подобно устройства if let, устройство условного цикла while let позволяет повторять цикл while до тех пор, пока образец продолжает совпадать. Пример в приложении 18-2 отображает цикл while let, который использует вектор в качестве обоймы и печатает значения вектора в порядке, обратном тому, в котором они были помещены.

-
fn main() {
-    let mut stack = Vec::new();
-
-    stack.push(1);
-    stack.push(2);
-    stack.push(3);
-
-    while let Some(top) = stack.pop() {
-        println!("{top}");
-    }
-}
-

Приложение 18-2: Использование цикла while let для печати значений до тех пор, пока stack.pop() возвращает Some

-

В этом примере выводится 3, 2, а затем 1. Способ pop извлекает последний элемент из вектора и возвращает Some(value). Если вектор пуст, то pop возвращает None. Цикл while продолжает выполнение кода в своём разделе, пока pop возвращает Some. Когда pop возвращает None, цикл останавливается. Мы можем использовать while let для удаления каждого элемента из обоймы.

-

Цикл for

-

В цикле for значение, которое следует непосредственно за ключевым словом for , является образцом. Например, в for x in y выражение x является образцом. В приложении 18-3 показано, как использовать образец в цикле for , чтобы разъединять или разбить упорядоченный ряд как часть цикла for .

-
fn main() {
-    let v = vec!['a', 'b', 'c'];
-
-    for (index, value) in v.iter().enumerate() {
-        println!("{value} is at index {index}");
-    }
-}
-

Приложение 18-3: Использование образца в цикле for для разъединения упорядоченного ряда

-

Код в приложении 18-3 выведет следующее:

-
$ cargo run
-   Compiling patterns v0.1.0 (file:///projects/patterns)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
-     Running `target/debug/patterns`
-a is at index 0
-b is at index 1
-c is at index 2
-
-

Мы приспособимтируем повторитель с помощью способа enumerate, чтобы он порождал упорядоченный ряд, состоящий из значения и порядкового указателя этого значения. Первым созданным значением будет упорядоченный ряд (0, 'a'). Когда это значение сопоставляется с образцом (index, value), index будет равен 0, а value будет равно 'a' и будет напечатана первая строка выходных данных.

-

Указание let

-

До этой главы мы подробно обсуждали только использование образцов с match и if let, но на самом деле, мы использовали образцы и в других местах, в том числе в указаниях let. Например, рассмотрим следующее простое назначение переменной с помощью let:

-
#![allow(unused)]
-fn main() {
-let x = 5;
-}
-

Каждый раз, когда вы использовали подобным образом указанию let, вы использовали образцы, хотя могли и не осознавать этого! Более условноуказание let выглядит так:

-
let PATTERN = EXPRESSION;
-
-

В указаниях вида let x = 5; с именем переменной в слоте PATTERN, имя переменной является просто отдельной, простой способом образца. Ржавчина сравнивает выражение с образцом и присваивает любые имена, которые он находит. Так что в примере let x = 5;, x - это образец, который означает "привязать то, что соответствует здесь, переменной x". Поскольку имя x является полностью образцом, этот образец в действительности означает "привязать все к переменной x независимо от значения".

-

Чтобы более чётко увидеть особенность сопоставления с образцом let, рассмотрим приложение 18-4, в котором используется образец с let для разъединения упорядоченного ряда.

-
fn main() {
-    let (x, y, z) = (1, 2, 3);
-}
-

Приложение 18-4. Использование образца для разъединения упорядоченного ряда и создания трёх переменных одновременно

-

Здесь мы сопоставляем упорядоченный ряд с образцом. Ржавчина сравнивает значение (1, 2, 3) с образцом (x, y, z) и видит, что значение соответствует образцу, поэтому Ржавчина связывает 1 с x, 2 с y и 3 с z. Вы можете думать об этом образце упорядоченного ряда как о вложении в него трёх отдельных образцов переменных.

-

Если количество элементов в образце не совпадает с количеством элементов в упорядоченном ряде, то весь вид не будет совпадать и мы получим ошибку сборщика. Например, в приложении 18-5 показана попытка разъединять упорядоченный ряд с тремя элементами в две переменные, что не будет работать.

-
fn main() {
-    let (x, y) = (1, 2, 3);
-}
-

Приложение 18-5: Неправильное построение образца, переменные не соответствуют количеству элементов в упорядоченном ряде

-

Попытка собрать этот код приводит к ошибке:

-
$ cargo run
-   Compiling patterns v0.1.0 (file:///projects/patterns)
-error[E0308]: mismatched types
- --> src/main.rs:2:9
-  |
-2 |     let (x, y) = (1, 2, 3);
-  |         ^^^^^^   --------- this expression has type `({integer}, {integer}, {integer})`
-  |         |
-  |         expected a tuple with 3 elements, found one with 2 elements
-  |
-  = note: expected tuple `({integer}, {integer}, {integer})`
-             found tuple `(_, _)`
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `patterns` (bin "patterns") due to 1 previous error
-
-

Чтобы исправить ошибку, мы могли бы пренебрегать одно или несколько значений в упорядоченном ряде, используя _ или .., как вы увидите в разделе “Пренебрежение значений в Образце” . Если образец содержит слишком много переменных в образце, можно решить неполадку, сделав виды совпадающими, удалив некоторые переменные таким образом, чтобы число переменных равнялось числу элементов в упорядоченном ряде.

-

Свойства функции

-

Свойства функции также могут быть образцами. Код в приложении 18-6 объявляет функцию с именем foo, которая принимает один свойство с именем x вида i32, к настоящему времени это должно выглядеть знакомым.

-
fn foo(x: i32) {
-    // code goes here
-}
-
-fn main() {}
-

Приложение 18-6: Ярлык функции использует образцы в свойствах

-

x это часть образца! Как и в случае с let, мы можем сопоставить упорядоченный ряд в переменных функции с образцом. Приложение 18-7 разделяет значения в упорядоченном ряде при его передачи в функцию.

-

Файл: src/main.rs

-
fn print_coordinates(&(x, y): &(i32, i32)) {
-    println!("Current location: ({x}, {y})");
-}
-
-fn main() {
-    let point = (3, 5);
-    print_coordinates(&point);
-}
-

Приложение 18-7: Функция с свойствами, которая разрушает упорядоченный ряд

-

Этот код печатает Current location: (3, 5). Значения &(3, 5) соответствуют образцу &(x, y), поэтому x - это значение 3, а y - это значение 5.

-

Добавляя к вышесказанному, мы можем использовать образцы в списках свойств замыкания таким же образом, как и в списках свойств функции, потому что, как обсуждалось в главе 13, замыкания похожи на функции.

-

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

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch18-02-refutability.html b/rustbook-ru/book/ch18-02-refutability.html deleted file mode 100644 index f2356143b..000000000 --- a/rustbook-ru/book/ch18-02-refutability.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - Опровержимость: может ли образец не соответствовать - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Возможность опровержения: может ли образец не совпадать

-

Образцы бывают двух видов: опровержимые и неопровержимые. Образцы, которые будут соответствовать любому возможному переданному значению, являются неопровержимыми (irrefutable). Примером может быть x в указания let x = 5;, потому что x соответствует чему-либо и, следовательно, не может не совпадать. Образцы, которые могут не соответствовать некоторому возможному значению, являются опровержимыми (refutable). Примером может быть Some(x) в выражении if let Some(x) = a_value, потому что если значение в переменной a_value равно None, а не Some, то образец Some(x) не будет совпадать.

-

Свойства функций, указания let и циклы for могут принимать только неопровержимые образцы, поскольку программа не может сделать ничего значимого, если значения не совпадают. А выражения if let и while let принимают опровержимые и неопровержимые образцы, но сборщик предостерегает от неопровержимых образцов, поскольку по определению они предназначены для обработки возможного сбоя: возможность условного выражения заключается в его способности выполнять разный код в зависимости от успеха или неудачи.

-

В общем случае, вам не нужно беспокоиться о разнице между опровержимыми (refutable) и неопровержимыми (irrefutable) образцами; тем не менее, вам необходимо ознакомиться с подходом возможности опровержения, чтобы вы могли отреагировать на неё, увидев в сообщении об ошибке. В таких случаях вам потребуется изменить либо образец, либо устройство, с которой вы используете образец, в зависимости от предполагаемого поведения кода.

-

Давайте посмотрим на пример того, что происходит, когда мы пытаемся использовать опровержимый образец, где Ржавчина требует неопровержимый образец, и наоборот. В приложении 18-8 показана указание let, но для образца мы указали Some(x), являющийся образцом, который можно опровергнуть. Как и следовало ожидать, этот код не будет собираться.

-
fn main() {
-    let some_option_value: Option<i32> = None;
-    let Some(x) = some_option_value;
-}
-

Приложение 18-8: Попытка использовать опровержимый образец вместе с let

-

Если some_option_value было бы значением None, то оно не соответствовало бы образцу Some(x), что означает, что образец является опровержимым. Тем не менее, указание let может принимать только неопровержимый образец, потому что нет правильного кода, который может что-то сделать со значением None. Во время сборки Ржавчина будет жаловаться на то, что мы пытались использовать опровержимый образец, для которого требуется неопровержимый образец:

-
$ cargo run
-   Compiling patterns v0.1.0 (file:///projects/patterns)
-error[E0005]: refutable pattern in local binding
- --> src/main.rs:3:9
-  |
-3 |     let Some(x) = some_option_value;
-  |         ^^^^^^^ pattern `None` not covered
-  |
-  = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
-  = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
-  = note: the matched value is of type `Option<i32>`
-help: you might want to use `let else` to handle the variant that isn't matched
-  |
-3 |     let Some(x) = some_option_value else { todo!() };
-  |                                     ++++++++++++++++
-
-For more information about this error, try `rustc --explain E0005`.
-error: could not compile `patterns` (bin "patterns") due to 1 previous error
-
-

Поскольку мы не покрыли (и не могли покрыть!) каждое допустимое значение с помощью образца Some(x), то Ржавчина выдаёт ошибку сборки.

-

Чтобы исправить неполадку наличия опровержимого образца, там, где нужен неопровержимый образец, можно изменить код, использующий образец: вместо использования let, можно использовать if let. Затем, если образец не совпадает, выполнение кода внутри фигурных скобок будет пропущено, что даст возможность продолжить правильное выполнение. В приложении 18-9 показано, как исправить код из приложения 18-8.

-
fn main() {
-    let some_option_value: Option<i32> = None;
-    if let Some(x) = some_option_value {
-        println!("{x}");
-    }
-}
-

Приложение 18-9. Использование if let и раздела с опровергнутыми образцами вместо let

-

Код исправлен! Этот код совершенно правильный, хотя это означает, что мы не можем использовать неопровержимый образец без получения ошибки. Если мы используем образец if let, который всегда будет совпадать, то для примера x, показанного в приложении 18-10, сборщик выдаст предупреждение.

-
fn main() {
-    if let x = 5 {
-        println!("{x}");
-    };
-}
-

Приложение 18-10. Попытка использовать неопровержимый образец с if let

-

Rust жалуется, что не имеет смысла использовать if let с неопровержимым образцом:

-
$ cargo run
-   Compiling patterns v0.1.0 (file:///projects/patterns)
-warning: irrefutable `if let` pattern
- --> src/main.rs:2:8
-  |
-2 |     if let x = 5 {
-  |        ^^^^^^^^^
-  |
-  = note: this pattern will always match, so the `if let` is useless
-  = help: consider replacing the `if let` with a `let`
-  = note: `#[warn(irrefutable_let_patterns)]` on by default
-
-warning: `patterns` (bin "patterns") generated 1 warning
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s
-     Running `target/debug/patterns`
-5
-
-

По этой причине совпадающие ветки выражений должны использовать опровержимые образцы, за исключением последнего, который должен сопоставлять любые оставшиеся значения с неопровержимым образцом. Ржавчина позволяет нам использовать неопровержимый образец в match только с одной веткой, но этот правила написания не особенно полезен и может быть заменён более простой указанием let.

-

Теперь, когда вы знаете, где использовать образцы и разницу между опровержимыми и неопровержимыми образцами, давайте рассмотрим весь правила написания, который мы можем использовать для создания образцов.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch18-03-pattern-syntax.html b/rustbook-ru/book/ch18-03-pattern-syntax.html deleted file mode 100644 index 77d48e16e..000000000 --- a/rustbook-ru/book/ch18-03-pattern-syntax.html +++ /dev/null @@ -1,646 +0,0 @@ - - - - - - правила написания образца - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

правила написания образцов

-

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

-

Сопоставление с записью

-

Как мы уже видели в главе 6, можно сопоставлять образцы с записями напрямую. В следующем коде есть несколько примеров:

-
fn main() {
-    let x = 1;
-
-    match x {
-        1 => println!("one"),
-        2 => println!("two"),
-        3 => println!("three"),
-        _ => println!("anything"),
-    }
-}
-

Этот код печатает one, потому что значение в x равно 1. Данный правила написания полезен, когда вы хотите, чтобы ваш код предпринял действие, если он получает определенное значение.

-

Сопоставление именованных переменных

-

Именованные переменные - это неопровержимые (irrefutable) образцы, которые соответствуют любому значению и мы использовали их много раз в книге. Однако при использовании именованных переменных в выражениях match возникает сложность. Поскольку match начинает новую область видимости, то переменные, объявленные как часть образца внутри выражения match, будут затенять переменные с тем же именем вне устройства match как и в случае со всеми переменными. В приложении 18-11 мы объявляем переменную с именем x со значением Some(5) и переменную y со значением 10. Затем мы создаём выражение match для значения x. Посмотрите на образцы в ветках, println! в конце и попытайтесь выяснить, какой код будет напечатан прежде чем запускать его или читать дальше.

-

Файл: src/main.rs

-
fn main() {
-    let x = Some(5);
-    let y = 10;
-
-    match x {
-        Some(50) => println!("Got 50"),
-        Some(y) => println!("Matched, y = {y}"),
-        _ => println!("Default case, x = {x:?}"),
-    }
-
-    println!("at the end: x = {x:?}, y = {y}");
-}
-

Приложение 18-11: Выражение match с веткой, которая добавляет затенённую переменную y

-

Давайте рассмотрим, что происходит, когда выполняется выражение match. Образец в первой ветке не соответствует определённому значению x, поэтому выполнение продолжается.

-

Образец во второй ветке вводит новую переменную с именем y, которая будет соответствовать любому значению в Some. Поскольку мы находимся в новой области видимости внутри выражения match, это новая переменная y, а не y которую мы объявили в начале со значением 10. Эта новая привязка y будет соответствовать любому значению из Some, которое находится в x. Следовательно, эта новая y связывается с внутренним значением Some из переменной x. Этим значением является 5, поэтому выражение для этой ветки выполняется и печатает Matched, y = 5.

-

Если бы x было значением None вместо Some(5), то образцы в первых двух ветках не совпали бы, поэтому значение соответствовало бы подчёркиванию. Мы не ввели переменную x в образце ветки со знаком подчёркивания, поэтому x в выражении все ещё является внешней переменной x, которая не была затенена. В этом гипотетическом случае совпадение match выведет Default case, x = None.

-

Когда выражение match завершается, заканчивается его область видимости как и область действия внутренней переменной y. Последний println! печатает at the end: x = Some(5), y = 10.

-

Чтобы создать выражение match, которое сравнивает значения внешних x и y, вместо введения затенённой переменной нужно использовать условие в сопоставлении образца. Мы поговорим про условие в сопоставлении образца позже в разделе “Дополнительные условия в сопоставлении образца”.

-

объединение образцов

-

В выражениях match можно сравнивать сразу с несколькими образцами, используя правила написания |, который является оператором образца or. Например, в следующем примере мы сопоставляем значение x с ветвями match, первая из которых содержит оператор or, так что если значение x совпадёт с любым из значений в этой ветви, то будет выполнен её код:

-
fn main() {
-    let x = 1;
-
-    match x {
-        1 | 2 => println!("one or two"),
-        3 => println!("three"),
-        _ => println!("anything"),
-    }
-}
-

Будет напечатано one or two.

-

Сопоставление рядов с помощью ..=

-

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

-
fn main() {
-    let x = 5;
-
-    match x {
-        1..=5 => println!("one through five"),
-        _ => println!("something else"),
-    }
-}
-

Если x равен 1, 2, 3, 4 или 5, то совпадение будет достигнуто в первой ветке. Этот правила написания более удобен при указании нескольких значений для сравнения, чем использование оператора | для определения этой же мысли; если бы мы решили использовать |, нам пришлось бы написать 1 | 2 | 3 | 4 | 5. Указание ряда намного короче, особенно если мы хотим подобрать, скажем, любое число от 1 до 1 000!

-

Сборщик проверяет, что рядне является пустым во время сборки, и поскольку единственными видами, для которых Ржавчина может определить, пуст рядили нет, являются char и числовые значения, ряды допускаются только с числовыми или char значениями.

-

Вот пример использования рядов значений char:

-
fn main() {
-    let x = 'c';
-
-    match x {
-        'a'..='j' => println!("early ASCII letter"),
-        'k'..='z' => println!("late ASCII letter"),
-        _ => println!("something else"),
-    }
-}
-

Rust может сообщить, что 'c' находится в ряде первого образца и напечатать early ASCII letter.

-

Разъединение для получения значений

-

Мы также можем использовать образцы для разъединения устройств, перечислений и упорядоченных рядов, чтобы использовать разные части этих значений. Давайте пройдёмся по каждому исходу.

-

Разъединение устройства

-

В приложении 18-12 показана устройства Point с двумя полями x и y, которые мы можем разделить, используя образец с указанием let.

-

Файл: src/main.rs

-
struct Point {
-    x: i32,
-    y: i32,
-}
-
-fn main() {
-    let p = Point { x: 0, y: 7 };
-
-    let Point { x: a, y: b } = p;
-    assert_eq!(0, a);
-    assert_eq!(7, b);
-}
-

Приложение 18-12: Разбиение полей устройства в отдельные переменные

-

Этот код создаёт переменные a и b , которые сопоставляются значениям полей x и y устройства p . Этот пример показывает, что имена переменных в образце не обязательно должны совпадать с именами полей устройства. Однако обычно имена переменных сопоставляются с именами полей, чтобы было легче запомнить, какие переменные взяты из каких полей. Из-за этого, а также из-за того, что строчка let Point { x: x, y: y } = p; содержит много повторения, в Ржавчина ввели особое сокращение для образцов, соответствующих полям устройства: вам нужно только указать имя поля устройства, и тогда переменные, созданные из образца, будут иметь те же имена. Код в приложении 18-13 подобен коду в Приложении 18-12, но в образце let создаются переменные x и y, вместо a и b .

-

Файл: src/main.rs

-
struct Point {
-    x: i32,
-    y: i32,
-}
-
-fn main() {
-    let p = Point { x: 0, y: 7 };
-
-    let Point { x, y } = p;
-    assert_eq!(0, x);
-    assert_eq!(7, y);
-}
-

Приложение 18-13: Разъединение полей устройства с использованием сокращённой записи

-

Этот код создаёт переменные x и y, которые соответствуют полям x и y из переменной p. В итоге переменные x и y содержат значения из устройства p.

-

А ещё, используя записанные значения в образце, мы можем разъединять, не создавая переменные для всех полей. Это даёт возможность, проверяя одни поля на соответствие определенным значениям, создавать переменные для разъединения других.

-

В приложении 18-14 показано выражение match, которое разделяет значения Point на три случая: точки, которые лежат непосредственно на оси x (что верно, когда y = 0), на оси y (x = 0) или ни то, ни другое.

-

Файл: src/main.rs

-
struct Point {
-    x: i32,
-    y: i32,
-}
-
-fn main() {
-    let p = Point { x: 0, y: 7 };
-
-    match p {
-        Point { x, y: 0 } => println!("On the x axis at {x}"),
-        Point { x: 0, y } => println!("On the y axis at {y}"),
-        Point { x, y } => {
-            println!("On neither axis: ({x}, {y})");
-        }
-    }
-}
-

Приложение 18-14: Разъединение и сопоставление с записями в одном образце

-

Первая ветвь будет соответствовать любой точке, лежащей на оси x, если значение поля y будет соответствовать записи 0. Образец по-прежнему создаёт переменную x, которую мы сможем использовать в коде этой ветви.

-

Подобно, вторая ветвь совпадёт с любой точкой на оси y, в случае, если значение поля x будет равно 0, а для значения поля y будет создана переменная y. Третья ветвь не содержит никаких записей, поэтому она соответствует любому другому Point и создаёт переменные как для поля x, так и для поля y.

-

В этом примере значение p совпадает по второй ветке, так как x содержит значение 0, поэтому этот код будет печатать On the y axis at 7.

-

Помните, что выражение match перестаёт проверять следующие ветви, как только оно находит первый совпадающий образец, поэтому, даже если Point { x: 0, y: 0} находится на оси x и оси y, этот код будет печатать только On the x axis at 0 .

-

Разъединение перечислений

-

Мы уже разъединили перечисления в книге (см., например, приложение 6-5 главы 6), но
не обсуждали явно, что образец для разъединения перечисления должен соответствовать способу объявления данных, хранящихся в перечислении. Например, в приложении 18-15 мы используем перечисление Message из приложения 6-2 и пишем match с образцами, которые будут разъединять каждое внутреннее значение.

-

Файл: src/main.rs

-
enum Message {
-    Quit,
-    Move { x: i32, y: i32 },
-    Write(String),
-    ChangeColor(i32, i32, i32),
-}
-
-fn main() {
-    let msg = Message::ChangeColor(0, 160, 255);
-
-    match msg {
-        Message::Quit => {
-            println!("The Quit variant has no data to destructure.");
-        }
-        Message::Move { x, y } => {
-            println!("Move in the x direction {x} and in the y direction {y}");
-        }
-        Message::Write(text) => {
-            println!("Text message: {text}");
-        }
-        Message::ChangeColor(r, g, b) => {
-            println!("Change the color to red {r}, green {g}, and blue {b}")
-        }
-    }
-}
-

Приложение 18-15: Разъединение исходов перечисления, содержащих разные виды значений

-

Этот код напечатает Change the color to red 0, green 160, and blue 255. Попробуйте изменить значение переменной msg, чтобы увидеть выполнение кода в других ветках.

-

Для исходов перечисления без каких-либо данных, вроде Message::Quit, мы не можем разъединять значение, которого нет. Мы можем сопоставить только буквальное значение Message::Quit в этом образце, но без переменных.

-

Для исходов перечисления похожих на устройства, таких как Message::Move, можно использовать образец, подобный образцу, который мы указываем для сопоставления устройств. После имени исхода мы помещаем фигурные скобки и затем перечисляем поля именами переменных. Таким образом мы разделяем отрывки, которые будут использоваться в коде этой ветки. Здесь мы используем сокращённую разновидность, как в приложении 18-13.

-

Для исходов перечисления, подобных упорядоченному ряду, вроде Message::Write, который содержит упорядоченный ряд с одним элементом и Message::ChangeColor, содержащему упорядоченный ряд с тремя элементами, образец подобен тому, который мы указываем для сопоставления упорядоченных рядов. Количество переменных в образце должно соответствовать количеству элементов в исходе, который мы сопоставляем.

-

Разъединение вложенных устройств и перечислений

-

До сих пор все наши примеры сопоставляли устройства или перечисления на один уровень глубины, но сопоставление может работать и с вложенными элементами! Например, мы можем ресогласовать код в приложении 18-15 для поддержки цветов RGB и HSV в сообщении ChangeColor , как показано в приложении 18-16.

-
enum Color {
-    Rgb(i32, i32, i32),
-    Hsv(i32, i32, i32),
-}
-
-enum Message {
-    Quit,
-    Move { x: i32, y: i32 },
-    Write(String),
-    ChangeColor(Color),
-}
-
-fn main() {
-    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
-
-    match msg {
-        Message::ChangeColor(Color::Rgb(r, g, b)) => {
-            println!("Change color to red {r}, green {g}, and blue {b}");
-        }
-        Message::ChangeColor(Color::Hsv(h, s, v)) => {
-            println!("Change color to hue {h}, saturation {s}, value {v}")
-        }
-        _ => (),
-    }
-}
-

Приложение 18-16: Сопоставление со вложенными перечислениями

-

Образец первой ветки в выражении match соответствует исходу перечисления Message::ChangeColor, который содержит исход Color::Rgb; затем образец привязывается к трём внутренними значениями i32. Образец второй ветки также соответствует исходу перечисления Message::ChangeColor, но внутреннее перечисление соответствует исходу Color::Hsv. Мы можем указать эти сложные условия в одном выражении match, даже если задействованы два перечисления.

-

Разъединение устройств и упорядоченных рядов

-

Можно смешивать, сопоставлять и вкладывать образцы разъединения ещё более сложными способами. В следующем примере показана сложная разъединение, где мы вкладываем устройства и упорядоченные ряды внутрь упорядоченного ряда и разъединим из него все простые значения:

-
fn main() {
-    struct Point {
-        x: i32,
-        y: i32,
-    }
-
-    let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
-}
-

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

-

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

-

Пренебрежение значений в образце

-

Вы видели, что иногда полезно пренебрегать значения в образце, например в последней ветке match, чтобы получить ветку, обрабатывающую любые значения, которая на самом деле ничего не делает, но учитывает все оставшиеся возможные значения. Есть несколько способов пренебрегать целые значения или части значений в образце: используя образец _ (который вы видели), используя образец _ внутри другого образца, используя имя, начинающееся с подчёркивания, либо используя .., чтобы пренебрегать оставшиеся части значения. Давайте рассмотрим, как и зачем использовать каждый из этих образцов.

-

Пренебрежение всего значения с помощью образца _

-

Мы использовали подчёркивание (_) в качестве образца подстановочного знака (wildcard), который будет сопоставляться с любом значением, но не будет привязываться к этому значению. Это особенно удобно в последней ветке выражения match, но мы также можем использовать его в любом образце, в том числе в свойствах функции, как показано в приложении 18-17.

-

Файл: src/main.rs

-
fn foo(_: i32, y: i32) {
-    println!("This code only uses the y parameter: {y}");
-}
-
-fn main() {
-    foo(3, 4);
-}
-

Приложение 18-15: Использование _ в ярлыке функции

-

Этот код полностью пренебрегает значение 3, переданное в качестве первого переменной, и выведет на печать This code only uses the y parameter: 4.

-

В большинстве случаев, когда вам больше не нужен какой-то из свойств функции, вы можете изменить её ярлык, убрав неиспользуемый свойство. Пренебрежение свойства функции может быть особенно полезно в случаях когда, например, вы выполняете особенность с определённой ярлыком, но тело функции в вашей выполнения не нуждается в одном из свойств. В таком случае сборщик не будет выдавать предупреждения о неиспользуемых свойствах функции, как это было бы, если бы вы указали имя свойства.

-

Пренебрежение частей значения с помощью вложенного _

-

Также, _ можно использовать внутри образцов, чтобы пренебрегать какую-то часть значения, например, когда мы хотим проверить только определённую подробность, а остальные свойства нам не понадобятся в коде, который нужно выполнить. В приложении 18-18 показан код, ответственный за управление значениями настроек. Согласно бизнес-требованиям, пользователь не может изменить установленное значение свойства, но может удалить его и задать ему новое значение, если на данный мгновение оно отсутствует.

-
fn main() {
-    let mut setting_value = Some(5);
-    let new_setting_value = Some(10);
-
-    match (setting_value, new_setting_value) {
-        (Some(_), Some(_)) => {
-            println!("Can't overwrite an existing customized value");
-        }
-        _ => {
-            setting_value = new_setting_value;
-        }
-    }
-
-    println!("setting is {setting_value:?}");
-}
-

Приложение 18-18: Использование подчёркивания в образцах, соответствующих исходам Some, когда нам не нужно использовать значение внутри Some

-

Этот код будет печатать Can't overwrite an existing customized value, а затем setting is Some(5). В первой ветке нам не нужно сопоставлять или использовать значения внутри исхода Some, но нам нужно проверить случай, когда setting_value и new_setting_value являются исходом Some. В этом случае мы печатаем причину, почему мы не меняем значение setting_value и оно не меняется.

-

Во всех других случаях (если либо setting_value, либо new_setting_value являются исходом None), выраженных образцом _ во второй ветке, мы хотим, чтобы new_setting_value стало равно setting_value.

-

Мы также можем использовать подчёркивание в нескольких местах в одном образце, чтобы пренебрегать определенные значения. Приложение 18-19 показывает пример пренебрежения второго и четвёртого значения в упорядоченном ряде из пяти элементов.

-
fn main() {
-    let numbers = (2, 4, 8, 16, 32);
-
-    match numbers {
-        (first, _, third, _, fifth) => {
-            println!("Some numbers: {first}, {third}, {fifth}")
-        }
-    }
-}
-

Приложение 18-19: Пренебрежение нескольких частей упорядоченного ряда

-

Этот код напечатает Some numbers: 2, 8, 32, а значения 4 и 16 будут пренебрежены.

-

Пренебрежение неиспользуемой переменной, начинающейся с символа _ в имени

-

Если вы создаёте переменную, но нигде её не используете, Ржавчина обычно выдаёт предупреждение, потому что неиспользуемая переменная может быть ошибкой. Но иногда полезно создать переменную, которую вы пока не используете, например, когда вы создаёте протовид или только начинаете дело. В этой случаи вы можете сказать Ржавчина не предупреждать вас о неиспользуемой переменной, начав имя переменной с подчёркивания. В приложении 18-20 мы создаём две неиспользуемые переменные, но когда мы собираем такой код, мы должны получить предупреждение только об одной из них.

-

Файл: src/main.rs

-
fn main() {
-    let _x = 5;
-    let y = 10;
-}
-

Приложение 18-20: Начинаем имя переменной с подчёркивания, чтобы не получить предупреждения о неиспользованных переменных

-

Здесь мы получаем предупреждение о том, что не используем переменную y, но мы не получаем предупреждения о неиспользовании переменной_x.

-

Обратите внимание, что есть небольшая разница между использованием только _ и использованием имени, начинающегося с подчёркивания. правила написания _x по-прежнему привязывает значение к переменной, тогда как _ не привязывает ничего. В приложении 18-21 представлена ошибка, показывающая, в каком случае это различие имеет значение.

-
fn main() {
-    let s = Some(String::from("Hello!"));
-
-    if let Some(_s) = s {
-        println!("found a string");
-    }
-
-    println!("{s:?}");
-}
-

Приложение 18-21: Неиспользуемая переменная, начинающаяся с подчёркивания, по-прежнему привязывает значение, что может привести к смене владельца значения

-

Мы получим ошибку, поскольку значение s все равно будет перемещено в _s, что не позволит нам больше воспользоваться s. Однако использование подчёркивания само по себе никогда не приводит к привязке к значению. Приложение 18-22 собирается без ошибок, поскольку s не будет перемещён в _.

-
fn main() {
-    let s = Some(String::from("Hello!"));
-
-    if let Some(_) = s {
-        println!("found a string");
-    }
-
-    println!("{s:?}");
-}
-

Приложение 18-22. Использование подчёркивания не привязывает значение

-

Этот код работает правильно, потому что мы никогда не привязываем s к чему либо; оно не перемещается.

-

Пренебрежение оставшихся частей значения с помощью ..

-

Со значениями, которые имеют много частей, можно использовать правила написания .., чтобы использовать только некоторые части и пренебрегать остальные, избегая необходимости перечислять подчёркивания для каждого пренебрегаемого значения. Образец .. пренебрегает любые части значения, которые мы явно не сопоставили в остальной частью образца. В приложении 18-23 мы имеем устройство Point, которая содержит координату в трёхмерном пространстве. В выражении match мы хотим работать только с координатой x и пренебрегать значения полей y и z.

-
fn main() {
-    struct Point {
-        x: i32,
-        y: i32,
-        z: i32,
-    }
-
-    let origin = Point { x: 0, y: 0, z: 0 };
-
-    match origin {
-        Point { x, .. } => println!("x is {x}"),
-    }
-}
-

Приложение 18-21: Пренебрежение полей устройства Point кроме поля x с помощью ..

-

Мы перечисляем значение x и затем просто включаем образец ... Это быстрее, чем перечислять y: _ и z: _, особенно когда мы работаем со устройствами, которые имеют много полей, в случаейх, когда только одно или два поля представляют для нас влечение.

-

правила написания .. раскроется до необходимого количества значений. В приложении 18-24 показано, как использовать .. с упорядоченным рядом.

-

Файл: src/main.rs

-
fn main() {
-    let numbers = (2, 4, 8, 16, 32);
-
-    match numbers {
-        (first, .., last) => {
-            println!("Some numbers: {first}, {last}");
-        }
-    }
-}
-

Приложение 18-24: Сопоставление только первого и последнего значений в упорядоченном ряде и пренебрежение всех других значений

-

В этом коде первое и последнее значение соответствуют first и last. Устройство .. будет соответствовать и пренебрегать всё, что находится между ними.

-

Однако использование .. должно быть однозначным. Если неясно, какие значения предназначены для сопоставления, а какие следует пренебрегать, Ржавчина выдаст ошибку. В приложении 18-25 показан пример неоднозначного использования .., поэтому он не будет собираться.

-

Файл: src/main.rs

-
fn main() {
-    let numbers = (2, 4, 8, 16, 32);
-
-    match numbers {
-        (.., second, ..) => {
-            println!("Some numbers: {second}")
-        },
-    }
-}
-

Приложение 18-25: Попытка использовать .. неоднозначным способом

-

При сборки примера, мы получаем эту ошибку:

-
$ cargo run
-   Compiling patterns v0.1.0 (file:///projects/patterns)
-error: `..` can only be used once per tuple pattern
- --> src/main.rs:5:22
-  |
-5 |         (.., second, ..) => {
-  |          --          ^^ can only be used once per tuple pattern
-  |          |
-  |          previously used here
-
-error: could not compile `patterns` (bin "patterns") due to 1 previous error
-
-

Rust не может определить, сколько значений в упорядоченном ряде нужно пренебрегать, прежде чем сопоставить значение с second, и сколько следующих значений пренебрегать после этого. Этот код может означать, что мы хотим пренебрегать 2, связать second с 4, а затем пренебрегать 8, 16 и 32; или что мы хотим пренебрегать 2 и 4, связать second с 8, а затем пренебрегать 16 и 32; и так далее. Имя переменной second не означает ничего особенного для Rust, поэтому мы получаем ошибку сборщика, так как использование .. в двух местах как здесь, является неоднозначным.

-

Дополнительные условия оператора сопоставления (Match Guards)

-

Условие сопоставления (match guard) является дополнительным условием if, указанным после образца в ветке match, которое также должно быть выполнено, чтобы ветка была выбрана. Условия сопоставления полезны для выражения более сложных мыслей, чем позволяет только образец.

-

Условие может использовать переменные, созданные в образце. В приложении 18-26 показан match, в котором первая ветка имеет образец Some(x), а также имеет условие сопоставления, if x % 2 == 0 (которое будет истинным, если число чётное).

-
fn main() {
-    let num = Some(4);
-
-    match num {
-        Some(x) if x % 2 == 0 => println!("The number {x} is even"),
-        Some(x) => println!("The number {x} is odd"),
-        None => (),
-    }
-}
-

Приложение 18-26: Добавление условия сопоставления в образец

-

В этом примере будет напечатано The number 4 is even. Когда num сравнивается с образцом в первой ветке, он совпадает, потому что Some(4) соответствует Some(x). Затем условие сопоставления проверяет, равен ли 0 остаток от деления x на 2 и если это так, то выбирается первая ветка.

-

Если бы num вместо этого было Some(5), условие в сопоставлении первой ветки было бы ложным, потому что остаток от 5 делённый на 2, равен 1, что не равно 0. Ржавчина тогда перешёл бы ко второй ветке, которое совпадает, потому что вторая ветка не имеет условия сопоставления и, следовательно, соответствует любому исходу Some.

-

Невозможно выразить условие if x % 2 == 0 внутри образца, поэтому условие в сопоставлении даёт нам возможность выразить эту логику. Недостатком этой дополнительной выразительности является то, что сборщик не пытается проверять полноту, когда задействованы выражения с условием в сопоставлении.

-

В приложении 18-11 мы упомянули, что можно использовать условия сопоставления для решения нашей сбоев затенения образца. Напомним, что внутри образца в выражении match была создана новая переменная, вместо использования внешней к match переменной. Эта новая переменная означала, что мы не могли выполнить сравнение с помощью значения внешней переменной. В приложении 18-27 показано, как мы можем использовать условие сопоставления для решения этой сбоев.

-

Файл: src/main.rs

-
fn main() {
-    let x = Some(5);
-    let y = 10;
-
-    match x {
-        Some(50) => println!("Got 50"),
-        Some(n) if n == y => println!("Matched, n = {n}"),
-        _ => println!("Default case, x = {x:?}"),
-    }
-
-    println!("at the end: x = {x:?}, y = {y}");
-}
-

Приложение 18-27. Использование условия сопоставления для проверки на равенство со значением внешней переменной

-

Этот код теперь напечатает Default case, x = Some(5). Образец во второй ветке не вводит новую переменную y, которая будет затенять внешнюю y, это означает, что теперь можно использовать внешнюю переменную y в условии сопоставления. Вместо указания образца как Some(y), который бы затенял бы внешнюю y, мы указываем Some(n). Это создаёт новую переменную n, которая ничего не затеняет, так как переменной n нет вне устройства match.

-

Условие сопоставления if n == y не является образцом и следовательно, не вводит новые переменные. Переменная y и есть внешняя y, а не новая затенённая y, и теперь мы можем искать элемент, который будет иметь то же значение, что и внешняя y, путём сравнения n и y.

-

Вы также можете использовать оператор или | в условии сопоставления, чтобы указать несколько образцов; условие сопоставления будет применяться ко всем образцам. В приложении 18-28 показан приоритет соединения условия сопоставления с образцом, который использует |. Важной частью этого примера является то, что условие сопоставления if y применяется к 4, 5, и к 6, хотя это может выглядеть как будто if y относится только к 6.

-
fn main() {
-    let x = 4;
-    let y = false;
-
-    match x {
-        4 | 5 | 6 if y => println!("yes"),
-        _ => println!("no"),
-    }
-}
-

Приложение 18-28: Соединение нескольких образцов с условием сопоставления

-

Условие сопоставления гласит, что ветка совпадает, только если значение x равно 4, 5 или 6, и если y равно true. Когда этот код выполняется, образец первой ветки совпадает, потому что x равно 4, но условие сопоставления if y равно false, поэтому первая ветка не выбрана. Код переходит ко второй ветке, которая совпадает, и эта программа печатает no. Причина в том, что условие if применяется ко всему образцу 4 | 5 | 6, а не только к последнему значению 6. Другими словами, приоритет условия сопоставления по отношению к образцу ведёт себя так:

-
(4 | 5 | 6) if y => ...
-
-

а не так:

-
4 | 5 | (6 if y) => ...
-
-

После запуска кода, старшинство в поведении становится очевидным: если условие сопоставления применялось бы только к конечному значению в списке, указанном с помощью оператора |, то ветка бы совпала и программа напечатала бы yes.

-

Связывание @

-

Оператор at (@) позволяет создать переменную, которая содержит значение, одновременно с тем, как мы проверяем, соответствует ли это значение образцу. В приложении 18-29 показан пример, в котором мы хотим проверить, что перечисление Message::Hello со значением поля id находится в ряде 3..=7. Но мы также хотим привязать такое значение к переменной id_variable, чтобы использовать его внутри кода данной ветки. Мы могли бы назвать эту переменную id, так же как поле, но для этого примера мы будем использовать другое имя.

-
fn main() {
-    enum Message {
-        Hello { id: i32 },
-    }
-
-    let msg = Message::Hello { id: 5 };
-
-    match msg {
-        Message::Hello {
-            id: id_variable @ 3..=7,
-        } => println!("Found an id in range: {id_variable}"),
-        Message::Hello { id: 10..=12 } => {
-            println!("Found an id in another range")
-        }
-        Message::Hello { id } => println!("Found some other id: {id}"),
-    }
-}
-

Приложение 18-29: Использование @ для привязывания значения в образце, с одновременной его проверкой

-

В этом примере будет напечатано Found an id in range: 5. Указывая id_variable @ перед рядом 3..=7, мы захватываем любое значение, попадающее в ряд, одновременно проверяя, что это значение соответствует ряду в образце.

-

Во второй ветке, где у нас в образце указан только ряд, код этой ветки не имеет переменной, которая содержит действительное значение поля id. Значение поля id могло бы быть 10, 11 или 12, но код, соответствующий этому образцу, не знает, чему оно равно. Код образца не может использовать значение из поля id, потому что мы не сохранили значение id в переменной.

-

В последней ветке, где мы указали переменную без ряда, у нас есть значение, доступное для использования в коде ветки, в переменной с именем id. Причина в том, что мы использовали упрощённый правила написания полей устройства. Но мы не применяли никакого сравнения со значением в поле id в этой ветке, как мы это делали в первых двух ветках: любое значение будет соответствовать этому образцу.

-

Использование @ позволяет проверять значение и сохранять его в переменной в пределах одного образца.

-

Итоги

-

Образцы Ржавчина очень помогают различать разные виды данных. При использовании их в выражениях match, Ржавчина заверяет, что ваши образцы охватывают все возможные значения, потому что иначе ваша программа не собирается. Образцы в указаниях let и свойствах функций делают такие устройства более полезными, позволяя разбивать элементы на более мелкие части, одновременно присваивая их значения переменным. Мы можем создавать простые или сложные образцы в соответствии с нашими потребностями.

-

Далее, в предпоследней главе книги, мы рассмотрим некоторые продвинутые особенности различных возможностей Rust.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch19-00-advanced-features.html b/rustbook-ru/book/ch19-00-advanced-features.html deleted file mode 100644 index 36394d37f..000000000 --- a/rustbook-ru/book/ch19-00-advanced-features.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - Расширенные возможности - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Расширенные возможности

-

На данный мгновение вы изучили все наиболее используемые части языка программирования Rust. Прежде чем мы выполним ещё один дело в главе 20, мы рассмотрим несколько особенностей языка, с которыми вы можете сталкиваться время от времени, но не использовать каждый день. Вы можете использовать эту главу в качестве справочника, когда столкнётесь с какими-либо незнакомыми вещами. Рассмотренные здесь функции будут полезны в очень отличительных случаейх. Хотя вы, возможно, не будете часто пользоваться ими, мы хотим убедиться, что вы знаете все возможности языка Rust.

-

В этой главе мы рассмотрим:

-
    -
  • Небезопасный Rust: как отказаться от некоторых заверений Ржавчина и взять на себя ответственность за их ручное соблюдение
  • -
  • Продвинутые особенности: сопряженные виды, свойства вида по умолчанию, полностью квалифицированный правила написания, супер-особенности и образец создания (newtype) по отношению к особенностям
  • -
  • Расширенные виды: больше о образце newtype, псевдонимах вида, вид never и виды изменяемыхх размеров
  • -
  • Расширенные функции и замыкания: указатели функций и возврат замыканий
  • -
  • Макросы: способы определения кода, который определяет большую часть кода во время сборки
  • -
-

Это набор возможностей Ржавчина для всех! Давайте погрузимся в него!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch19-01-unsafe-rust.html b/rustbook-ru/book/ch19-01-unsafe-rust.html deleted file mode 100644 index 9ea278dc3..000000000 --- a/rustbook-ru/book/ch19-01-unsafe-rust.html +++ /dev/null @@ -1,481 +0,0 @@ - - - - - - Небезопасный код в Rust - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Unsafe Rust

-

Во всех предыдущих главах этой книги мы обсуждали код на Rust, безопасность памяти в котором обеспечивается во время сборки. Однако внутри Ржавчина скрывается другой язык - небезопасный Rust, который не обеспечивает безопасной работы с памятью. Этот язык называется unsafe Rust и работает также как и первый, но предоставляет вам дополнительные возможности.

-

Небезопасный Ржавчина существует потому что по своей природе постоянной анализ довольно устоявшийся. Когда сборщик пытается определить, соответствует ли код заверениям, то он скорее отвергнет несколько допустимых программ, чем пропустит несколько недопустимых. Не смотря на то, что код может быть в порядке, если сборщик Ржавчина не будет располагать достаточной сведениями, чтобы убедиться в этом, он отвергнет код. В таких случаях вы можете использовать небезопасный код, чтобы сказать сборщику: "Поверь мне, я знаю, что делаю". Однако имейте в виду, что вы используете небезопасный Ржавчина на свой страх и риск: если вы неправильно используете небезопасный код, могут возникнуть сбоев, связанные с нарушением безопасности памяти, например, разыменование нулевого указателя.

-

Другая причина, по которой у Ржавчина есть небезопасное альтер эго, заключается в том, что по существу аппаратное обеспечение компьютера небезопасно. Если Ржавчина не позволял бы вам выполнять небезопасные действия, вы не могли бы выполнять определённые задачи. Ржавчина должен позволить вам использовать системное, низкоуровневое программирование, такое как прямое взаимодействие с операционной системой, или даже написание вашей собственной операционной системы. Возможность написания низкоуровневого, системного кода является одной из целей языка. Давайте рассмотрим, что и как можно делать с небезопасным Rust.

-

Небезопасные сверхспособности

-

Чтобы переключиться на небезопасный Rust, используйте ключевое слово unsafe, а затем начните новый блок, содержащий небезопасный код. В небезопасном Ржавчина можно выполнять пять действий, которые недоступны в безопасном Rust, которые мы называем небезопасными супер силами. Эти супер силы включают в себя следующее:

-
    -
  • Разыменование сырого указателя
  • -
  • Вызов небезопасной функции или небезопасного способа
  • -
  • Доступ или изменение изменяемой постоянной переменной
  • -
  • Выполнение небезопасного особенности
  • -
  • Доступ к полям в union
  • -
-

Важно понимать, что unsafe не отключает проверку заимствования или любые другие проверки безопасности Rust: если вы используете ссылку в небезопасном коде, она всё равно будет проверена. Единственное, что делает ключевое слово unsafe - даёт вам доступ к этим пяти возможностям, безопасность работы с памятью в которых не проверяет сборщик. Вы по-прежнему получаете некоторую степень безопасности внутри небезопасного раздела.

-

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

-

Люди подвержены ошибкам и ошибки будут происходить, но требуя размещение этих четырёх небезопасных действия внутри разделов, помеченных как unsafe, вы будете знать, что любые ошибки, связанные с безопасностью памяти, будут находиться внутри unsafe разделов. Делайте unsafe разделы маленькими; вы будете благодарны себе за это позже, при исследовании ошибок с памятью.

-

Чтобы наиболее изолировать небезопасный код, советуется заключить небезопасный код в безопасную абстракцию и предоставить безопасный API, который мы обсудим позже, когда будем обсуждать небезопасные функции и способы. Части встроенной библиотеки выполнены как проверенные, безопасные абстракции над небезопасным кодом. Оборачивание небезопасного кода в безопасную абстракцию предотвращает возможную утечку использования unsafe кода во всех местах, где вы или ваши пользователи могли бы захотеть напрямую использовать возможность, выполненную unsafe кодом, потому что использование безопасной абстракции само безопасно.

-

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

-

Разыменование сырых указателей

-

В главе 4 раздела "Недействительные ссылки" мы упоминали, что сборщик заверяет, что ссылки всегда действительны. Небезопасный Ржавчина имеет два новых вида, называемых сырыми указателями (raw pointers), которые похожи на ссылки. Как и в случае ссылок, сырые указатели могут быть неизменяемыми или изменяемыми и записываться как *const T и *mut T соответственно. Звёздочка не является оператором разыменования; это часть имени вида. В среде сырых указателей неизменяемый (immutable) означает, что указателю нельзя напрямую присвоить что-то после того как он разыменован.

-

В отличие от ссылок и умных указателей, сырые указатели:

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

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

-

В приложении 19-1 показано, как создать неизменяемый и изменяемый сырой указатель из ссылок.

-
fn main() {
-    let mut num = 5;
-
-    let r1 = &num as *const i32;
-    let r2 = &mut num as *mut i32;
-}
-

Приложение 19-1: Создание необработанных указателей из ссылок

-

Обратите внимание, что мы не используем ключевое слово unsafe в этом коде. Можно создавать сырые указатели в безопасном коде; мы просто не можем разыменовывать сырые указатели за пределами небезопасного раздела, как вы увидите чуть позже.

-

Мы создали сырые указатели, используя as для приведения неизменяемой и изменяемой ссылки к соответствующим им видам сырых указателей. Поскольку мы создали их непосредственно из ссылок, которые обязательно являются действительными, мы знаем, что эти определенные сырые указатели являются действительными, но мы не можем делать такое же предположение о любом сыром указателе.

-

Чтобы отобразить это, создадим сырой указатель, в достоверности которого мы не можем быть так уверены. В приложении 19-2 показано, как создать необработанный указатель на произвольное место в памяти. Попытка использовать произвольную память является непредсказуемой: по этому адресу могут быть данные, а могут и не быть, сборщик может перерабатывать код так, что доступа к памяти не будет, или программа может завершиться с ошибкой сегментации. Обычно нет веских причин писать такой код, но это возможно.

-
fn main() {
-    let address = 0x012345usize;
-    let r = address as *const i32;
-}
-

Приложение 19-2: Создание сырого указателя на произвольный адрес памяти

-

Напомним, что можно создавать сырые указатели в безопасном коде, но нельзя разыменовывать сырые указатели и читать данные, на которые они указывают. В приложении 19-3 мы используем оператор разыменования * для сырого указателя, который требует unsafe раздела.

-
fn main() {
-    let mut num = 5;
-
-    let r1 = &num as *const i32;
-    let r2 = &mut num as *mut i32;
-
-    unsafe {
-        println!("r1 is: {}", *r1);
-        println!("r2 is: {}", *r2);
-    }
-}
-

Приложение 19-3: Разыменование сырых указателей в разделе unsafe

-

Создание указателей безопасно. Только при попытке доступа к предмету по адресу в указателе мы можем получить недопустимое значение.

-

Также обратите внимание, что в примерах кода 19-1 и 19-3 мы создали *const i32 и *mut i32, которые ссылаются на одну и ту же область памяти, где хранится num. Если мы попытаемся создать неизменяемую и изменяемую ссылку на num вместо сырых указателей, такой код не собирается, т.к. будут нарушены правила заимствования, запрещающие наличие изменяемой ссылки одновременно с неизменяемыми ссылками. С помощью сырых указателей мы можем создать изменяемый указатель и неизменяемый указатель на одну и ту же область памяти и изменять данные с помощью изменяемого указателя, возможно создавая эффект гонки данных. Будьте осторожны!

-

С учётом всех этих опасностей, зачем тогда использовать сырые указатели? Одним из основных применений является взаимодействие с кодом C, как вы увидите в следующем разделе "Вызов небезопасной функции или способа". Другой случай это создание безопасных абстракций, которые не понимает анализатор заимствований. Мы введём понятие небезопасных функций и затем рассмотрим пример безопасной абстракции, которая использует небезопасный код.

-

Вызов небезопасной функции или способа

-

Второй вид действий, которые можно выполнять в небезопасном разделе - это вызов небезопасных функций. Небезопасные функции и способы выглядят точно так же, как обычные функции и способы, но перед остальным определением у них есть дополнительное unsafe. Ключевое слово unsafe в данном среде указывает на то, что к функции предъявляются требования, которые мы должны соблюдать при вызове этой функции, поскольку Ржавчина не может обеспечить, что мы их выполняем. Вызывая небезопасную функцию внутри раздела unsafe, мы говорим, что прочитали документацию к этой функции и берём на себя ответственность за соблюдение её условий.

-

Вот небезопасная функция с именем dangerous которая ничего не делает в своём теле:

-
fn main() {
-    unsafe fn dangerous() {}
-
-    unsafe {
-        dangerous();
-    }
-}
-

Мы должны вызвать функцию dangerous в отдельном unsafe разделе. Если мы попробуем вызвать dangerous без unsafe раздела, мы получим ошибку:

-
$ cargo run
-   Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
-error[E0133]: call to unsafe function `dangerous` is unsafe and requires unsafe function or block
- --> src/main.rs:4:5
-  |
-4 |     dangerous();
-  |     ^^^^^^^^^^^ call to unsafe function
-  |
-  = note: consult the function's documentation for information on how to avoid undefined behavior
-
-For more information about this error, try `rustc --explain E0133`.
-error: could not compile `unsafe-example` (bin "unsafe-example") due to 1 previous error
-
-

С помощью раздела unsafe мы сообщаем Rust, что прочитали документацию к функции, поняли, как правильно её использовать, и убедились, что выполняем договор функции.

-

Тела небезопасных функций являются в действительности unsafe разделами, поэтому для выполнения других небезопасных действий внутри небезопасной функции не нужно добавлять ещё один unsafe блок.

-

Создание безопасных абстракций вокруг небезопасного кода

-

То, что функция содержит небезопасный код, не означает, что мы должны пометить всю функцию как небезопасную. На самом деле, обёртывание небезопасного кода в безопасную функцию - это обычная абстракция. В качестве примера рассмотрим функцию split_at_mut из встроенной библиотеки, которая требует некоторого небезопасного кода. Рассмотрим, как мы могли бы её выполнить. Этот безопасный способ определён для изменяемых срезов: он берет один срез и превращает его в два, разделяя срез по порядковому указателю, указанному в качестве переменной. В приложении 19-4 показано, как использовать split_at_mut.

-
fn main() {
-    let mut v = vec![1, 2, 3, 4, 5, 6];
-
-    let r = &mut v[..];
-
-    let (a, b) = r.split_at_mut(3);
-
-    assert_eq!(a, &mut [1, 2, 3]);
-    assert_eq!(b, &mut [4, 5, 6]);
-}
-

Приложение 19-4: Использование безопасной функции split_at_mut

-

Эту функцию нельзя выполнить, используя только безопасный Rust. Попытка выполнения могла бы выглядеть примерно как в приложении 19-5, который не собирается. Для простоты мы выполняем split_at_mut как функцию, а не как способ, и только для значений вида i32, а не обобщённого вида T.

-
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
-    let len = values.len();
-
-    assert!(mid <= len);
-
-    (&mut values[..mid], &mut values[mid..])
-}
-
-fn main() {
-    let mut vector = vec![1, 2, 3, 4, 5, 6];
-    let (left, right) = split_at_mut(&mut vector, 3);
-}
-

Приложение 19-5: Попытка выполнения split_at_mut с использованием только безопасного Rust

-

Эта функция сначала получает общую длину среза. Затем она проверяет (assert), что порядковый указатель, переданный в качестве свойства, находится в границах среза, сравнивая его с длиной. Assert означает, что если мы передадим порядковый указатель, который больше, чем длина среза, функция запаникует ещё до попытки использования этого порядкового указателя.

-

Затем мы возвращаем два изменяемых отрывка в упорядоченном ряде: один от начала исходного отрывка до mid порядкового указателя (не включая сам mid), а другой - от mid (включая сам mid) до конца отрывка.

-

При попытке собрать код в приложении 19-5, мы получим ошибку.

-
$ cargo run
-   Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
-error[E0499]: cannot borrow `*values` as mutable more than once at a time
- --> src/main.rs:6:31
-  |
-1 | fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
-  |                         - let's call the lifetime of this reference `'1`
-...
-6 |     (&mut values[..mid], &mut values[mid..])
-  |     --------------------------^^^^^^--------
-  |     |     |                   |
-  |     |     |                   second mutable borrow occurs here
-  |     |     first mutable borrow occurs here
-  |     returning this value requires that `*values` is borrowed for `'1`
-  |
-  = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices
-
-For more information about this error, try `rustc --explain E0499`.
-error: could not compile `unsafe-example` (bin "unsafe-example") due to 1 previous error
-
-

Анализатор заимствований Ржавчина не может понять, что мы заимствуем различные части среза, он понимает лишь, что мы хотим осуществить заимствование частей одного среза дважды. Заимствование различных частей среза в принципе в порядке вещей, потому что они не перекрываются, но Ржавчина недостаточно умён, чтобы это понять. Когда мы знаем, что код верный, но Ржавчина этого не понимает, значит пришло время прибегнуть к небезопасному коду.

-

Приложение 19-6 отображает, как можно использовать unsafe блок, сырой указатель и вызовы небезопасных функций чтобы split_at_mut заработала:

-
use std::slice;
-
-fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
-    let len = values.len();
-    let ptr = values.as_mut_ptr();
-
-    assert!(mid <= len);
-
-    unsafe {
-        (
-            slice::from_raw_parts_mut(ptr, mid),
-            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
-        )
-    }
-}
-
-fn main() {
-    let mut vector = vec![1, 2, 3, 4, 5, 6];
-    let (left, right) = split_at_mut(&mut vector, 3);
-}
-

Приложение 19-6. Использование небезопасного кода в выполнения функции split_at_mut

-

Напомним, из раздела "Вид срез" главы 4, что срезы состоят из указателя на некоторые данные и длины. Мы используем способ len для получения длины среза и способ as_mut_ptr для доступа к сырому указателю среза. Поскольку у нас есть изменяемый срез на значения вида i32, функция as_mut_ptr возвращает сырой указатель вида *mut i32, который мы сохранили в переменной ptr.

-

Далее проверяем, что порядковый указательmid находится в границах среза. Затем мы обращаемся к небезопасному коду: функция slice::from_raw_parts_mut принимает сырой указатель, длину и создаёт срез. Мы используем эту функцию для создания среза, начинающегося с ptr и имеющего длину в mid элементов. Затем мы вызываем способ add у ptr с mid в качестве переменной, чтобы получить сырой указатель, который начинается с mid, и создаём срез, используя этот указатель и оставшееся количество элементов после mid в качестве длины.

-

Функция slice::from_raw_parts_mut является небезопасной, потому что она принимает необработанный указатель и должна полагаться на то, что этот указатель действителен. Способ add для необработанных указателей также небезопасен, поскольку он должен считать, что местоположение смещения также является действительным указателем. Поэтому мы были вынуждены разместить unsafe разделвокруг наших вызовов slice::from_raw_parts_mut и add, чтобы иметь возможность вызвать их. Посмотрев на код и добавив утверждение, что mid должен быть меньше или равен len, мы можем сказать, что все необработанные указатели, используемые в разделе unsafe, будут правильными указателями на данные внутри среза. Это приемлемое и уместное использование unsafe.

-

Обратите внимание, что нам не нужно помечать результирующую функцию split_at_mut как unsafe, и мы можем вызвать эту функцию из безопасного Rust. Мы создали безопасную абстракцию для небезопасного кода с помощью выполнения функции, которая использует код unsafe раздела безопасным образом, поскольку она создаёт только допустимые указатели из данных, к которым эта функция имеет доступ.

-

Напротив, использование slice::from_raw_parts_mut в приложении 19-7 приведёт к вероятному сбою при использовании среза. Этот код использует произвольный адрес памяти и создаёт срез из 10000 элементов.

-
fn main() {
-    use std::slice;
-
-    let address = 0x01234usize;
-    let r = address as *mut i32;
-
-    let values: &[i32] = unsafe { slice::from_raw_parts_mut(r, 10000) };
-}
-

Приложение 19-7: Создание среза из произвольного адреса памяти

-

Мы не владеем памятью в этом произвольном месте, и нет никакой заверения, что созданный этим кодом отрывок содержит допустимые значения i32. Попытка использовать values так, как будто это допустимый срез, приводит к неопределённому поведению.

-

Использование extern функций для вызова внешнего кода

-

Иногда вашему коду на языке Ржавчина может потребоваться взаимодействие с кодом, написанным на другом языке. Для этого в Ржавчина есть ключевое слово extern, которое облегчает создание и использование внешней оболочки внешних функций (Foreign Function Interface - FFI). FFI - это способ для языка программирования определить функции и позволить другому (внешнему) языку программирования вызывать эти функции.

-

Приложение 19-8 отображает, как настроить встраивание с функцией abs из встроенной библиотеки C. Функции, объявленные внутри разделов extern, всегда небезопасны для вызова из кода Rust. Причина в том, что другие языки не обеспечивают соблюдение правил и заверений Rust, Ржавчина также не может проверить заверения, поэтому ответственность за безопасность ложится на программиста.

-

Имя файла: src/main.rs

-
extern "C" {
-    fn abs(input: i32) -> i32;
-}
-
-fn main() {
-    unsafe {
-        println!("Absolute value of -3 according to C: {}", abs(-3));
-    }
-}
-

Приложение 19-8: Объявление и вызов extern функции, написанной на другом языке программирования

-

Внутри раздела extern "C" мы перечисляем имена и ярлыки внешних функций из другого языка, которые мы хотим вызвать. Часть "C" определяет какой application binary interface (ABI - двоичный внешняя оболочка приложений) использует внешняя функция. Внешнюю оболочку ABI определяет как вызвать функцию на уровне ассемблера. Использование ABI "C" является наиболее часто используемым и следует правилам ABI внешней оболочки языка Си.

-
-

Вызов функций Ржавчина из других языков

-

Также можно использовать extern для создания внешней оболочки, позволяющего другим языкам вызывать функции Rust. Вместо того чтобы создавать целый разделextern, мы добавляем ключевое слово extern и указываем ABI для использования непосредственно перед ключевым словом fn для необходимой функции. Нам также нужно добавить изложение #[no_mangle], чтобы сказать сборщику Ржавчина не искажать имя этой функции. Искажение - это когда сборщик меняет имя, которое мы дали функции, на другое имя, которое содержит больше сведений для других частей этапа сборки, но менее читабельно для человека. Сборщик каждого языка программирования искажает имена по-разному, поэтому, чтобы функция Ржавчина могла быть использована другими языками, мы должны отключить искажение имён в сборщике Rust.

-

В следующем примере мы делаем функцию call_from_c доступной из кода на C, после того как она будет собрана в разделяемую библиотеку и прилинкована с C:

-
#![allow(unused)]
-fn main() {
-#[no_mangle]
-pub extern "C" fn call_from_c() {
-    println!("Just called a Ржавчина function from C!");
-}
-}
-

Такое использование extern не требует unsafe.

-
-

Получение доступа и внесение изменений в изменяемую постоянную переменную

-

В этой книге мы ещё не говорили о вездесущих переменных, которые Ржавчина поддерживает, но с которыми могут возникнуть сбоев из-за действующих в Ржавчина правил владения. Если два потока обращаются к одной и той же изменяемой вездесущей переменной, это может привести к гонке данных.

-

Вездесущие переменные в Ржавчина называют постоянными (static). Приложение 19-9 отображает пример объявления и использования в качестве значения постоянной переменной, имеющей вид строкового среза:

-

Имя файла: src/main.rs

-
static HELLO_WORLD: &str = "Hello, world!";
-
-fn main() {
-    println!("name is: {HELLO_WORLD}");
-}
-

Приложение 19-9: Определение и использование неизменяемой постоянной переменной

-

Постоянные переменные похожи на постоянные значения, которые мы обсуждали в разделе “Различия между переменными и постоянными значениями” главы 3. Имена постоянных переменных по общему соглашению пишутся в наставлении SCREAMING_SNAKE_CASE, и мы должны указывать вид переменной, которым в данном случае является &'static str. Постоянные переменные могут хранить только ссылки со временем жизни 'static, это означает что сборщик Ржавчина может вывести время жизни и нам не нужно прописывать его явно. Доступ к неизменяемой постоянной переменной является безопасным.

-

Тонкое различие между постоянными значениями и неизменяемыми постоянными переменными заключается в том, что значения в постоянной переменной имеют определенный адрес в памяти. При использовании значения всегда будут доступны одни и те же данные. Постоянного значения, с другой стороны, могут повторять свои данные при каждом использовании. Ещё одно отличие заключается в том, что постоянные переменные могут быть изменяемыми. Обращение к изменяемым постоянном переменным и их изменение является небезопасным. В приложении 19-10 показано, как объявить, получить доступ и изменять изменяемую постоянную переменную с именем COUNTER.

-

Имя файла: src/main.rs

-
static mut COUNTER: u32 = 0;
-
-fn add_to_count(inc: u32) {
-    unsafe {
-        COUNTER += inc;
-    }
-}
-
-fn main() {
-    add_to_count(3);
-
-    unsafe {
-        println!("COUNTER: {COUNTER}");
-    }
-}
-

Приложение 19-10: Чтение из изменяемой постоянной переменной или запись в неё небезопасны

-

Как и с обычными переменными, мы определяем изменяемость с помощью ключевого слова mut. Любой код, который читает из или пишет в переменную COUNTER должен находиться в unsafe разделе. Этот код собирается и печатает COUNTER: 3, как и следовало ожидать, потому что выполняется в одном потоке. Наличие нескольких потоков с доступом к COUNTER приведёт к случаи гонки данных.

-

Наличие изменяемых данных, которые доступны вездесуще, делает трудным выполнение заверения отсутствия гонок данных, поэтому Ржавчина считает изменяемые постоянные переменные небезопасными. Там, где это возможно, предпочтительно использовать техники многопоточности и умные указатели, направленные на многопоточное исполнение, которые мы обсуждали в главе 16. Таким образом, сборщик сможет проверить, что обращение к данным, доступным из разных потоков, выполняется безопасно.

-

Выполнение небезопасных особенностей

-

Мы можем использовать unsafe для выполнения небезопасного особенности. Особенность является небезопасным, если хотя бы один из его способов имеет некоторый неизменная величина, который сборщик не может проверить. Мы объявляем особенности unsafe, добавляя ключевое слово unsafe перед trait и помечая выполнение особенности как unsafe, как показано в приложении 19-11.

-
unsafe trait Foo {
-    // methods go here
-}
-
-unsafe impl Foo for i32 {
-    // method implementations go here
-}
-
-fn main() {}
-

Приложение 19-11: Определение и выполнение небезопасного особенности

-

Используя unsafe impl, мы даём обещание поддерживать неизменные величины, которые сборщик не может проверить.

-

Для примера вспомним маркерные особенности Sync и Send, которые мы обсуждали в разделе "Расширяемый одновременность с помощью особенностей Sync и Send" главы 16: сборщик выполняет эти особенности самостоятельно , если наши виды полностью состоят из видов Send и Sync. Если мы создадим вид, который содержит вид, не являющийся Send или Sync, такой, как сырой указатель, и мы хотим пометить этот вид как Send или Sync, мы должны использовать unsafe блок. Ржавчина не может проверить, что наш вид поддерживает заверения того, что он может быть безопасно передан между потоками или доступен из нескольких потоков; поэтому нам нужно добавить эти проверки вручную и указать это с помощью unsafe.

-

Доступ к полям объединений (union)

-

Последнее действие, которое работает только с unsafe - это доступ к полям union. union похож на struct, но в каждом определенном образце одновременно может использоваться только одно объявленное поле. Объединения в основном используются для взаимодействия с объединениями в коде на языке Си. Доступ к полям объединений небезопасен, поскольку Ржавчина не может обязательно определить вид данных, которые в данный мгновение хранятся в образце объединения. Подробнее об объединениях вы можете узнать в the Ржавчина Reference.

-

Когда использовать небезопасный код

-

Использование unsafe для выполнения одного из пяти действий (супер способностей), которые только что обсуждались, не является ошибочным или не одобренным. Но получить правильный unsafe код сложнее, потому что сборщик не может помочь в обеспечении безопасности памяти. Если у вас есть причина использовать unsafe код, вы можете делать это, а наличие явной unsafe изложении облегчает отслеживание источника неполадок. если они возникают.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch19-03-advanced-traits.html b/rustbook-ru/book/ch19-03-advanced-traits.html deleted file mode 100644 index 83fec878e..000000000 --- a/rustbook-ru/book/ch19-03-advanced-traits.html +++ /dev/null @@ -1,717 +0,0 @@ - - - - - - Продвинутые особенности - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Продвинутые особенности

-

Мы познакомились с особенностями в разделе "Особенности: Определение общего поведения" в главе 10, но там мы не обсуждали более сложные подробности. Теперь, когда вы больше знаете о Rust, мы можем перейти к более подробному рассмотрению.

-

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

-

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

-

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

-

Одним из примеров особенности с сопряженным видом является особенность Iterator из встроенной библиотеки. Сопряженный вид называется Item и символизирует вид значений, по которым повторяется вид, выполняющий особенность Iterator. Определение особенности Iterator показано в приложении 19-12.

-
pub trait Iterator {
-    type Item;
-
-    fn next(&mut self) -> Option<Self::Item>;
-}
-

Приложение 19-12: Определение особенности Iterator, который имеет сопряженный вид Item

-

Вид Item является заполнителем и определение способа next показывает, что он будет возвращать значения вида Option<Self::Item>. Разработчики особенности Iterator определят определенный вид для Item, а способ next вернёт Option содержащий значение этого определенного вида.

-

Сопряженные виды могут показаться подходом похожей на обобщения, поскольку последние позволяют нам определять функцию, не указывая, какие виды она может обрабатывать. Чтобы изучить разницу между этими двумя подходами, мы рассмотрим выполнение особенности Iterator для вида с именем Counter, который указывает, что вид Item равен u32:

-

Файл: src/lib.rs

-
struct Counter {
-    count: u32,
-}
-
-impl Counter {
-    fn new() -> Counter {
-        Counter { count: 0 }
-    }
-}
-
-impl Iterator for Counter {
-    type Item = u32;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        // --snip--
-        if self.count < 5 {
-            self.count += 1;
-            Some(self.count)
-        } else {
-            None
-        }
-    }
-}
-

Этот правила написания весьма напоминает обобщённые виды. Так почему же особенность Iterator не определён обобщённым видом, как показано в приложении 19-13?

-
pub trait Iterator<T> {
-    fn next(&mut self) -> Option<T>;
-}
-

Приложение 19-13: Гипотетическое определение особенности Iterator используя обобщённые виды

-

Разница в том, что при использовании обобщений, как показано в приложении 19-13, мы должны определять виды в каждой выполнения; потому что мы также можем выполнить Iterator<String> for Counter или любого другого вида, мы могли бы иметь несколько выполнения Iterator для Counter. Другими словами, когда особенность имеет обобщённый свойство, он может быть выполнен для вида несколько раз, каждый раз меняя определенные виды свойств обобщённого вида. Когда мы используем способ next у Counter, нам пришлось бы предоставить изложении вида, указывая какую выполнение Iterator мы хотим использовать.

-

С сопряженными видами не нужно определять виды, потому что мы не можем выполнить особенность у вида несколько раз. В приложении 19-12 с определением, использующим сопряженные виды можно выбрать только один вид Item, потому что может быть только одно объявление impl Iterator for Counter. Нам не нужно указывать, что нужен повторитель значений вида u32 везде, где мы вызываем next у Counter.

-

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

-

Свойства обобщённого вида по умолчанию и перегрузка операторов

-

Когда мы используем свойства обобщённого вида, мы можем указать определенный вид по умолчанию для обобщённого вида. Это устраняет необходимость разработчикам указывать определенный вид, если работает вид по умолчанию. Вид по умолчанию указывается при объявлении обобщённого вида с помощью правил написания <PlaceholderType=ConcreteType>.

-

Отличным примером, когда этот способ полезен, является перегрузка оператора (operator overloading), когда вы настраиваете поведение оператора (например, + ) для определённых случаев.

-

Rust не позволяет создавать собственные операторы или перегружать произвольные операторы. Но можно перегрузить перечисленные действия и соответствующие им особенности из std::ops путём выполнения особенностей, связанных с этими операторами. Например, в приложении 19-14 мы перегружаем оператор +, чтобы складывать два образца Point. Мы делаем это выполняя особенность Add для устройства Point:

-

Файл: src/main.rs

-
use std::ops::Add;
-
-#[derive(Debug, Copy, Clone, PartialEq)]
-struct Point {
-    x: i32,
-    y: i32,
-}
-
-impl Add for Point {
-    type Output = Point;
-
-    fn add(self, other: Point) -> Point {
-        Point {
-            x: self.x + other.x,
-            y: self.y + other.y,
-        }
-    }
-}
-
-fn main() {
-    assert_eq!(
-        Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
-        Point { x: 3, y: 3 }
-    );
-}
-

Приложение 19-14: Выполнение особенности Add для перегрузки оператора + для образцов Point

-

Способ add складывает значения x двух образцов Point и значения y у Point для создания нового образца Point. Особенность Add имеет сопряженный вид с именем Output, который определяет вид, возвращаемый из способа add.

-

Обобщённый вид по умолчанию в этом коде находится в особенности Add . Вот его определение:

-
#![allow(unused)]
-fn main() {
-trait Add<Rhs = Self> {
-    type Output;
-
-    fn add(self, rhs: Rhs) -> Self::Output;
-}
-}
-

Этот код должен выглядеть знакомым: особенность с одним способом и сопряженным видом. Новый правила написания это RHS=Self. Такой правила написания называется свойства вида по умолчанию (default type parameters). Свойство обобщённого вида RHS (сокращённо “right hand side”) определяет вид свойства rhs в способе add. Если мы не укажем определенный вид для RHS при выполнения особенности Add, то видом для RHS по умолчанию будет Self, который будет видом для которого выполняется особенность Add.

-

Когда мы выполнили Add для устройства Point, мы использовали обычное значение для RHS, потому что хотели сложить два образца Point. Давайте посмотрим на пример выполнения особенности Add, где мы хотим пользовательский вид RHS вместо использования вида по умолчанию.

-

У нас есть две разные устройства Millimeters и Meters, хранящие значения в разных единицах измерения. Это тонкое обёртывание существующего вида в другую устройство известно как образец newtype, который мы более подробно опишем в разделе "Образец Newtype для выполнение внешних особенностей у внешних видов" . Мы хотим добавить значения в миллиметрах к значениям в метрах и хотим иметь выполнение особенности Add, которая делает правильное преобразование единиц. Можно выполнить Add для Millimeters с видом Meters в качестве Rhs, как показано в приложении 19-15.

-

Файл: src/lib.rs

-
use std::ops::Add;
-
-struct Millimeters(u32);
-struct Meters(u32);
-
-impl Add<Meters> for Millimeters {
-    type Output = Millimeters;
-
-    fn add(self, other: Meters) -> Millimeters {
-        Millimeters(self.0 + (other.0 * 1000))
-    }
-}
-

Приложение 19-15: Выполнение особенности Add для устройства Millimeters, чтобы складывать Millimeters и Meters

-

Чтобы сложить Millimeters и Meters, мы указываем impl Add<Meters>, чтобы указать значение свойства вида RHS (Meters) вместо использования значения по умолчанию Self (Millimeters).

-

Свойства вида по умолчанию используются в двух основных случаях:

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

Особенность Add из встроенной библиотеки является примером второй цели: обычно вы складываете два одинаковых вида, но особенность Add позволяет сделать больше. Использование свойства вида по умолчанию в объявлении особенности Add означает, что не нужно указывать дополнительный свойство большую часть времени. Другими словами, большая часть кода выполнения не нужна, что делает использование особенности проще.

-

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

-

Полностью квалифицированный правила написания для устранения неоднозначности: вызов способов с одинаковым именем

-

В Ржавчина ничего не мешает особенности иметь способ с одинаковым именем, таким же как способ другого особенности и Ржавчина не мешает выполнить оба таких особенности у одного вида. Также возможно выполнить способ с таким же именем непосредственно у вида, такой как и способы у особенностей.

-

При вызове способов с одинаковыми именами в Ржавчина нужно указать, какой из трёх возможных вы хотите использовать. Рассмотрим код в приложении 19-16, где мы определили два особенности: Pilot и Wizard, у обоих есть способ fly. Затем мы выполняем оба особенности у вида Human в котором уже выполнен способ с именем fly. Каждый способ fly делает что-то своё.

-

Файл: src/main.rs

-
trait Pilot {
-    fn fly(&self);
-}
-
-trait Wizard {
-    fn fly(&self);
-}
-
-struct Human;
-
-impl Pilot for Human {
-    fn fly(&self) {
-        println!("This is your captain speaking.");
-    }
-}
-
-impl Wizard for Human {
-    fn fly(&self) {
-        println!("Up!");
-    }
-}
-
-impl Human {
-    fn fly(&self) {
-        println!("*waving arms furiously*");
-    }
-}
-
-fn main() {}
-

Приложение 19-16: Два особенности определены с способом fly и выполнены у вида Human, а также способ fly выполнен непосредственно у Human

-

Когда мы вызываем fly у образца Human, то сборщик по умолчанию вызывает способ, который непосредственно выполнен для вида, как показано в приложении 19-17.

-

Файл: src/main.rs

-
trait Pilot {
-    fn fly(&self);
-}
-
-trait Wizard {
-    fn fly(&self);
-}
-
-struct Human;
-
-impl Pilot for Human {
-    fn fly(&self) {
-        println!("This is your captain speaking.");
-    }
-}
-
-impl Wizard for Human {
-    fn fly(&self) {
-        println!("Up!");
-    }
-}
-
-impl Human {
-    fn fly(&self) {
-        println!("*waving arms furiously*");
-    }
-}
-
-fn main() {
-    let person = Human;
-    person.fly();
-}
-

Приложение 19-17: Вызов fly у образца Human

-

Запуск этого кода напечатает *waving arms furiously* , показывая, что Ржавчина называется способ fly выполненный непосредственно у Human.

-

Чтобы вызвать способы fly у особенности Pilot или особенности Wizard нужно использовать более явный правила написания, указывая какой способ fly мы имеем в виду. Приложение 19-18 отображает такой правила написания.

-

Файл: src/main.rs

-
trait Pilot {
-    fn fly(&self);
-}
-
-trait Wizard {
-    fn fly(&self);
-}
-
-struct Human;
-
-impl Pilot for Human {
-    fn fly(&self) {
-        println!("This is your captain speaking.");
-    }
-}
-
-impl Wizard for Human {
-    fn fly(&self) {
-        println!("Up!");
-    }
-}
-
-impl Human {
-    fn fly(&self) {
-        println!("*waving arms furiously*");
-    }
-}
-
-fn main() {
-    let person = Human;
-    Pilot::fly(&person);
-    Wizard::fly(&person);
-    person.fly();
-}
-

Приложение 19-18: Указание какой способа fly мы хотим вызвать

-

Указание имени особенности перед именем способа проясняет сборщику Rust, какую именно выполнение fly мы хотим вызвать. Мы могли бы также написать Human::fly(&person), что эквивалентно используемому нами person.fly() в приложении 19-18, но это писание немного длиннее, когда нужна неоднозначность.

-

Выполнение этого кода выводит следующее:

-
$ cargo run
-   Compiling traits-example v0.1.0 (file:///projects/traits-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.46s
-     Running `target/debug/traits-example`
-This is your captain speaking.
-Up!
-*waving arms furiously*
-
-

Поскольку способ fly принимает свойство self, если у нас было два вида оба выполняющих один особенность, то Ржавчина может понять, какую выполнение особенности использовать в зависимости от вида self.

-

Однако, сопряженные функции, не являющиеся способами, не имеют свойства self. Когда существует несколько видов или особенностей, определяющих функции, не являющиеся способами, с одним и тем же именем функции, Ржавчина не всегда знает, какой вид вы имеете в виду, если только вы не используете полный правила написания. Например, в приложении 19-19 мы создаём особенность для приюта животных, который хочет назвать всех маленьких собак Spot. Мы создаём особенность Animal со связанной с ним функцией baby_name, не являющейся способом. Особенность Animal выполнен для устройства Dog, для которой мы также напрямую предоставляем связанную функцию baby_name, не являющуюся способом.

-

Файл: src/main.rs

-
trait Animal {
-    fn baby_name() -> String;
-}
-
-struct Dog;
-
-impl Dog {
-    fn baby_name() -> String {
-        String::from("Spot")
-    }
-}
-
-impl Animal for Dog {
-    fn baby_name() -> String {
-        String::from("puppy")
-    }
-}
-
-fn main() {
-    println!("A baby dog is called a {}", Dog::baby_name());
-}
-

Приложение 19-19: Особенность с сопряженной функцией и вид с сопряженной функцией с тем же именем, которая тоже выполняет особенность

-

Мы выполнили код для приюта для животных, который хочет назвать всех щенков именем Spot, в сопряженной функции baby_name, которая определена для Dog. Вид Dog также выполняет особенность Animal, который описывает свойства, которые есть у всех животных. Маленьких собак называют щенками, и это выражается в выполнения Animal у Dog в функции baby_name сопряженной с особенностью Animal.

-

В main мы вызываем функцию Dog::baby_name, которая вызывает сопряженную функцию определённую напрямую у Dog. Этот код печатает следующее:

-
$ cargo run
-   Compiling traits-example v0.1.0 (file:///projects/traits-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.54s
-     Running `target/debug/traits-example`
-A baby dog is called a Spot
-
-

Этот вывод не является тем, что мы хотели бы получить. Мы хотим вызвать функцию baby_name, которая является частью особенности Animal выполненного у Dog, так чтобы код печатал A baby dog is called a puppy. Техника указания имени особенности использованная в приложении 19-18 здесь не помогает; если мы изменим main код как в приложении 19-20, мы получим ошибку сборки.

-

Файл: src/main.rs

-
trait Animal {
-    fn baby_name() -> String;
-}
-
-struct Dog;
-
-impl Dog {
-    fn baby_name() -> String {
-        String::from("Spot")
-    }
-}
-
-impl Animal for Dog {
-    fn baby_name() -> String {
-        String::from("puppy")
-    }
-}
-
-fn main() {
-    println!("A baby dog is called a {}", Animal::baby_name());
-}
-

Приложение 19-20. Попытка вызвать функцию baby_name из особенности Animal, но Ржавчина не знает какую выполнение использовать

-

Поскольку Animal::baby_name не имеет свойства self, и могут быть другие виды, выполняющие особенность Animal, Ржавчина не может понять, какую выполнение Animal::baby_name мы хотим использовать. Мы получим эту ошибку сборщика:

-
$ cargo run
-   Compiling traits-example v0.1.0 (file:///projects/traits-example)
-error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type
-  --> src/main.rs:20:43
-   |
-2  |     fn baby_name() -> String;
-   |     ------------------------- `Animal::baby_name` defined here
-...
-20 |     println!("A baby dog is called a {}", Animal::baby_name());
-   |                                           ^^^^^^^^^^^^^^^^^^^ cannot call associated function of trait
-   |
-help: use the fully-qualified path to the only available implementation
-   |
-20 |     println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
-   |                                           +++++++       +
-
-For more information about this error, try `rustc --explain E0790`.
-error: could not compile `traits-example` (bin "traits-example") due to 1 previous error
-
-

Чтобы устранить неоднозначность и сказать Rust, что мы хотим использовать выполнение Animal для Dog, нужно использовать полный правила написания. Приложение 19-21 отображает, как использовать полный правила написания.

-

Файл: src/main.rs

-
trait Animal {
-    fn baby_name() -> String;
-}
-
-struct Dog;
-
-impl Dog {
-    fn baby_name() -> String {
-        String::from("Spot")
-    }
-}
-
-impl Animal for Dog {
-    fn baby_name() -> String {
-        String::from("puppy")
-    }
-}
-
-fn main() {
-    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
-}
-

Приложение 19-21: Использование полного правил написания для указания, что мы мы хотим вызвать функцию baby_name у особенности Animal выполненную в Dog

-

Мы указываем изложение вида в угловых скобках, которая указывает на то что мы хотим вызвать способ baby_name из особенности Animal выполненный в Dog, также указывая что мы хотим рассматривать вид Dog в качестве Animal для вызова этой функции. Этот код теперь напечатает то, что мы хотим:

-
$ cargo run
-   Compiling traits-example v0.1.0 (file:///projects/traits-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
-     Running `target/debug/traits-example`
-A baby dog is called a puppy
-
-

В общем, полный правила написания определяется следующим образом:

-
<Type as Trait>::function(receiver_if_method, next_arg, ...);
-

Для сопряженных функций, которые не являются способами, будет отсутствовать receiver (предмет приёмника): будет только список переменных. Вы можете использовать полный правила написания везде, где вызываете функции или способы. Тем не менее, разрешается опустить любую часть этого правил написания, которую Ржавчина может понять из другой сведений в программе. Вам нужно использовать более подробный правила написания только в тех случаях, когда существует несколько выполнений, использующих одно и то же название, и Ржавчина нужно помочь определить, какую выполнение вы хотите вызвать.

-

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

-

Иногда вы можете написать определение особенности, которое зависит от другого особенности: для вида, выполняющего первый особенность, вы хотите потребовать, чтобы этот вид также выполнил второй особенность. Вы должны сделать это, чтобы ваше определение особенности могло использовать связанные элементы второго особенности. Особенность, на который опирается ваше определение особенности, называется supertrait вашего особенности.

-

Например, мы хотим создать особенность OutlinePrint с способом outline_print, который будет печатать значение обрамлённое звёздочками. Мы хотим чтобы устройства Point, выполняющая особенность встроенной библиотеки Display, вывела на печать (x, y) при вызове outline_print у образца Point, который имеет значение 1 для x и значение 3 для y. Она должна напечатать следующее:

-
**********
-*        *
-* (1, 3) *
-*        *
-**********
-
-

В выполнения outline_print мы хотим использовать возможность особенности Display. Поэтому нам нужно указать, что особенность OutlinePrint будет работать только для видов, которые также выполняют Display и предоставляют возможность, которая нужна в OutlinePrint. Мы можем сделать это в объявлении особенности, указав OutlinePrint: Display. Этот способ похож на добавление ограничения в особенность. В приложении 19-22 показана выполнение особенности OutlinePrint.

-

Файл: src/main.rs

-
use std::fmt;
-
-trait OutlinePrint: fmt::Display {
-    fn outline_print(&self) {
-        let output = self.to_string();
-        let len = output.len();
-        println!("{}", "*".repeat(len + 4));
-        println!("*{}*", " ".repeat(len + 2));
-        println!("* {output} *");
-        println!("*{}*", " ".repeat(len + 2));
-        println!("{}", "*".repeat(len + 4));
-    }
-}
-
-fn main() {}
-

Приложение 19-22: Выполнение особенности OutlinePrint которая требует возможности особенности Display

-

Поскольку мы указали, что особенность OutlinePrint требует особенности Display, мы можем использовать функцию to_string, которая самостоятельно выполнена для любого вида выполняющего Display. Если бы мы попытались использовать to_string не добавляя двоеточие и не указывая особенность Display после имени особенности, мы получили бы сообщение о том, что способ с именем to_string не был найден у вида &Self в текущей области видимости.

-

Давайте посмотрим что происходит, если мы пытаемся выполнить особенность OutlinePrint для вида, который не выполняет Display, например устройства Point:

-

Файл: src/main.rs

-
use std::fmt;
-
-trait OutlinePrint: fmt::Display {
-    fn outline_print(&self) {
-        let output = self.to_string();
-        let len = output.len();
-        println!("{}", "*".repeat(len + 4));
-        println!("*{}*", " ".repeat(len + 2));
-        println!("* {output} *");
-        println!("*{}*", " ".repeat(len + 2));
-        println!("{}", "*".repeat(len + 4));
-    }
-}
-
-struct Point {
-    x: i32,
-    y: i32,
-}
-
-impl OutlinePrint for Point {}
-
-fn main() {
-    let p = Point { x: 1, y: 3 };
-    p.outline_print();
-}
-

Мы получаем сообщение о том, что требуется выполнение Display, но её нет:

-
$ cargo run
-   Compiling traits-example v0.1.0 (file:///projects/traits-example)
-error[E0277]: `Point` doesn't implement `std::fmt::Display`
-  --> src/main.rs:20:23
-   |
-20 | impl OutlinePrint for Point {}
-   |                       ^^^^^ `Point` cannot be formatted with the default formatter
-   |
-   = help: the trait `std::fmt::Display` is not implemented for `Point`
-   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
-note: required by a bound in `OutlinePrint`
-  --> src/main.rs:3:21
-   |
-3  | trait OutlinePrint: fmt::Display {
-   |                     ^^^^^^^^^^^^ required by this bound in `OutlinePrint`
-
-error[E0277]: `Point` doesn't implement `std::fmt::Display`
-  --> src/main.rs:24:7
-   |
-24 |     p.outline_print();
-   |       ^^^^^^^^^^^^^ `Point` cannot be formatted with the default formatter
-   |
-   = help: the trait `std::fmt::Display` is not implemented for `Point`
-   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
-note: required by a bound in `OutlinePrint::outline_print`
-  --> src/main.rs:3:21
-   |
-3  | trait OutlinePrint: fmt::Display {
-   |                     ^^^^^^^^^^^^ required by this bound in `OutlinePrint::outline_print`
-4  |     fn outline_print(&self) {
-   |        ------------- required by a bound in this associated function
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `traits-example` (bin "traits-example") due to 2 previous errors
-
-

Чтобы исправить, мы выполняем Display у устройства Point и выполняем требуемое ограничение OutlinePrint, вот так:

-

Файл: src/main.rs

-
trait OutlinePrint: fmt::Display {
-    fn outline_print(&self) {
-        let output = self.to_string();
-        let len = output.len();
-        println!("{}", "*".repeat(len + 4));
-        println!("*{}*", " ".repeat(len + 2));
-        println!("* {output} *");
-        println!("*{}*", " ".repeat(len + 2));
-        println!("{}", "*".repeat(len + 4));
-    }
-}
-
-struct Point {
-    x: i32,
-    y: i32,
-}
-
-impl OutlinePrint for Point {}
-
-use std::fmt;
-
-impl fmt::Display for Point {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "({}, {})", self.x, self.y)
-    }
-}
-
-fn main() {
-    let p = Point { x: 1, y: 3 };
-    p.outline_print();
-}
-

Тогда выполнение особенности OutlinePrint для устройства Point будет собрана успешно и мы можем вызвать outline_print у образца Point для отображения значения обрамлённое звёздочками.

-

Образец Newtype для выполнение внешних особенностей у внешних видов

-

В разделе "Выполнение особенности у типа" главы 10, мы упоминали "правило сироты" (orphan rule), которое гласит, что разрешается выполнить особенность у вида, если либо особенность, либо вид являются местными для нашего ящика. Можно обойти это ограничение, используя образец нового вида (newtype pattern), который включает в себя создание нового вида в упорядоченной в ряд устройстве. (Мы рассмотрели упорядоченные в ряд устройства в разделе "Использование устройств упорядоченных рядов без именованных полей для создания различных видов" главы 5.) Устройства упорядоченного ряда будет иметь одно поле и будет тонкой оболочкой для вида которому мы хотим выполнить особенность. Тогда вид оболочки является местным для нашего ящика и мы можем выполнить особенность для местной обёртки. Newtype это понятие, который происходит от языка программирования Haskell. В нем нет ухудшения производительности времени выполнения при использовании этого образца и вид оболочки исключается во время сборки.

-

В качестве примера, мы хотим выполнить особенность Display для вида Vec<T>, где "правило сироты" (orphan rule) не позволяет нам этого делать напрямую, потому что особенность Display и вид Vec<T> объявлены вне нашего ящика. Мы можем сделать устройство Wrapper, которая содержит образец Vec<T>; тогда мы можем выполнить Display у устройства Wrapper и использовать значение Vec<T> как показано в приложении 19-23.

-

Файл: src/main.rs

-
use std::fmt;
-
-struct Wrapper(Vec<String>);
-
-impl fmt::Display for Wrapper {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "[{}]", self.0.join(", "))
-    }
-}
-
-fn main() {
-    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
-    println!("w = {w}");
-}
-

Приложение 19-23. Создание вида Wrapper Vec<String> для выполнения Display

-

Выполнение Display использует self.0 для доступа к внутреннему Vec<T>, потому что Wrapper это устройства упорядоченного ряда, а Vec<T> это элемент с порядковым указателем 0 в упорядоченном ряде. Затем мы можем использовать полезные возможности вида Display у Wrapper.

-

Недостатком использования этой техники является то, что Wrapper является новым видом, поэтому он не имеет способов для значения, которое он держит в себе. Мы должны были бы выполнить все способы для Vec<T> непосредственно во Wrapper, так чтобы эти способы делегировались внутреннему self.0, что позволило бы нам обращаться с Wrapper точно так же, как с Vec<T>. Если бы мы хотели, чтобы новый вид имел каждый способ имеющийся у внутреннего вида, выполняя особенность Deref (обсуждается в разделе "Работа с умными указателями как с обычными ссылками с помощью Deref особенности" главы 15) у Wrapper для возвращения внутреннего вида, то это было бы решением. Если мы не хотим, чтобы вид Wrapper имел все способы внутреннего вида, например, для ограничения поведения вида Wrapper, то пришлось бы вручную выполнить только те способы, которые нам нужны.

-

Этот образец newtype также полезен, даже когда особенности не задействованы. Давайте переключим внимание и рассмотрим некоторые продвинутые способы взаимодействия с системой видов Rust.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch19-04-advanced-types.html b/rustbook-ru/book/ch19-04-advanced-types.html deleted file mode 100644 index c7cd52acc..000000000 --- a/rustbook-ru/book/ch19-04-advanced-types.html +++ /dev/null @@ -1,440 +0,0 @@ - - - - - - Продвинутые виды - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Продвинутые виды

-

Система видов Ржавчина имеет некоторые особенности, о которых мы уже упоминали, но ещё не обсуждали. Мы начнём с общего обзора newtypes, а затем разберёмся, чем они могут пригодиться в качестве видов. Далее мы перейдём к псевдонимам видов - возможности, похожей на newtypes, но с несколько иной смыслом. Мы также обсудим вид ! и виды с изменяемым размером.

-

Использование образца Newtype для обеспечения безопасности видов и создания абстракций

-
-

Примечание: В этом разделе предполагается, что вы прочитали предыдущий раздел "Использование образца Newtype для выполнения внешних особенностей для внешних видов."

-
-

Образец newtype полезен и для других задач, помимо тех, которые мы обсуждали до сих пор, в частности, для постоянного обеспечения того, чтобы значения никогда не путались, а также для указания единиц измерения значения. Пример использования newtypes для указания единиц измерения вы видели в приложении 19-15: вспомните, как устройства Millimeters и Meters обернули значения u32 в newtype. Если бы мы написали функцию с свойствоом вида Millimeters, мы не смогли бы собрать программу, которая случайно попыталась бы вызвать эту функцию со значением вида Meters или обычным u32.

-

Мы также можем использовать образец newtype для абстрагирования от некоторых подробностей выполнения вида: новый вид может предоставлять открытый API, который отличается от API скрытого внутри вида.

-

Newtypes также позволяют скрыть внутреннюю выполнение. Например, мы можем создать вид People, который обернёт HashMap<i32, String>, хранящий ID человека, связанный с его именем. Код, использующий People, будет взаимодействовать только с открытым API, который мы предоставляем, например, способ добавления имени в собрание People; этому коду не нужно будет знать, что внутри мы присваиваем i32 ID именам. Образец newtype - это лёгкий способ достижения инкапсуляции для скрытия подробностей выполнения, который мы обсуждали в разделе "Инкапсуляция, скрывающая подробности выполнения" главы 17.

-

Создание родственных вида с помощью псевдонимов вида

-

Rust предоставляет возможность объявить псевдоним вида чтобы дать существующему виду другое имя. Для этого мы используем ключевое слово type. Например, мы можем создать псевдоним вида Kilometers для i32 следующим образом:

-
fn main() {
-    type Kilometers = i32;
-
-    let x: i32 = 5;
-    let y: Kilometers = 5;
-
-    println!("x + y = {}", x + y);
-}
-

Теперь псевдоним Kilometers является родственным для i32; в отличие от видов Millimeters и Meters, которые мы создали в приложении 19-15, Kilometers не является отдельным, новым видом. Значения, имеющие вид Kilometers, будут обрабатываться так же, как и значения вида i32:

-
fn main() {
-    type Kilometers = i32;
-
-    let x: i32 = 5;
-    let y: Kilometers = 5;
-
-    println!("x + y = {}", x + y);
-}
-

Поскольку Kilometers и i32 являются одним и тем же видом, мы можем добавлять значения обоих видов и передавать значения Kilometers функциям, принимающим свойства i32. Однако, используя этот способ, мы не получаем тех преимуществ проверки видов, которые мы получаем от образца newtype, рассмотренного ранее. Другими словами, если мы где-то перепутаем значения Kilometers и i32, сборщик не выдаст нам ошибку.

-

Родственные в основном используются для сокращения повторений. Например, у нас может быть такой многословный тип:

-
Box<dyn Fn() + Send + 'static>
-

Написание таких длинных видов в ярлыках функций и в виде наставлений видов по всему коду может быть утомительным и чреватым ошибками. Представьте себе дело, наполненный таким кодом, как в приложении 19-24.

-
fn main() {
-    let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));
-
-    fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {
-        // --snip--
-    }
-
-    fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {
-        // --snip--
-        Box::new(|| ())
-    }
-}
-

Приложение 19-24: Использование длинного вида во многих местах

-

Псевдоним вида делает этот код более удобным для работы, сокращая количество повторений. В приложении 19-25 мы ввели псевдоним Thunk для вида verbose и можем заменить все использования этого вида более коротким псевдонимом Thunk.

-
fn main() {
-    type Thunk = Box<dyn Fn() + Send + 'static>;
-
-    let f: Thunk = Box::new(|| println!("hi"));
-
-    fn takes_long_type(f: Thunk) {
-        // --snip--
-    }
-
-    fn returns_long_type() -> Thunk {
-        // --snip--
-        Box::new(|| ())
-    }
-}
-

Приложение 19-25: Представление псевдонима Thunk для уменьшения количества повторений

-

Такой код гораздо легче читать и писать! Выбор осмысленного имени для псевдонима вида также может помочь прояснить ваши намерения (thunk - название для кода, который будет вычисляться позднее, поэтому это подходящее имя для сохраняемого замыкания).

-

Псевдонимы видов также часто используются с видом Result<T, E> для сокращения повторений. Рассмотрим звено std::io в встроенной библиотеке. Действия ввода-вывода часто возвращают Result<T, E> для обработки случаев, когда эти действия не удаются. В данной библиотеке есть устройства std::io::Error, которая отражает все возможные ошибки ввода/вывода. Многие функции в std::io будут возвращать Result<T, E>, где E - это std::io::Error, например, эти функции в особенности Write:

-
use std::fmt;
-use std::io::Error;
-
-pub trait Write {
-    fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
-    fn flush(&mut self) -> Result<(), Error>;
-
-    fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
-    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
-}
-

Result<..., Error> часто повторяется. Поэтому std::io содержит такое объявление псевдонима вида:

-
use std::fmt;
-
-type Result<T> = std::result::Result<T, std::io::Error>;
-
-pub trait Write {
-    fn write(&mut self, buf: &[u8]) -> Result<usize>;
-    fn flush(&mut self) -> Result<()>;
-
-    fn write_all(&mut self, buf: &[u8]) -> Result<()>;
-    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
-}
-

Поскольку это объявление находится в звене std::io, мы можем использовать полный псевдоним std::io::Result<T>; это и есть Result<T, E>, где в качестве E выступает std::io::Error. Ярлыки функций особенности Write в итоге выглядят следующим образом:

-
use std::fmt;
-
-type Result<T> = std::result::Result<T, std::io::Error>;
-
-pub trait Write {
-    fn write(&mut self, buf: &[u8]) -> Result<usize>;
-    fn flush(&mut self) -> Result<()>;
-
-    fn write_all(&mut self, buf: &[u8]) -> Result<()>;
-    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
-}
-

Псевдоним вида помогает двумя способами: он облегчает написание кода и даёт нам согласованный внешняя оболочка для всего из std::io. Поскольку это псевдоним, то это просто ещё один вид Result<T, E>, что означает, что с ним мы можем использовать любые способы, которые работают с Result<T, E>, а также особый правила написания вроде ? оператора.

-

Вид Never, который никогда не возвращается

-

В Ржавчина есть особый вид !, который на жаргоне теории видов известен как empty type (пустой вид), потому что он не содержит никаких значений. Мы предпочитаем называть его never type (никакой вид), потому что он используется в качестве возвращаемого вида, когда функция ничего не возвращает. Вот пример:

-
fn bar() -> ! {
-    // --snip--
-    panic!();
-}
-

Этот код читается как "функция bar ничего не возвращает". Функции, которые ничего не возвращают, называются рассеивающими функциями (diverging functions). Мы не можем производить значения вида !, поэтому bar никогда ничего не вернёт.

-

Но для чего нужен вид, для которого вы никогда не сможете создать значения? Напомним код из приложения 2-5, отрывка "игры в загадки"; мы воспроизвели его часть здесь в приложении 19-26.

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    loop {
-        println!("Please input your guess.");
-
-        let mut guess = String::new();
-
-        // --snip--
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: u32 = match guess.trim().parse() {
-            Ok(num) => num,
-            Err(_) => continue,
-        };
-
-        println!("You guessed: {guess}");
-
-        // --snip--
-
-        match guess.cmp(&secret_number) {
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => {
-                println!("You win!");
-                break;
-            }
-        }
-    }
-}
-

Приложение 19-26: Сопоставление match с веткой, которая заканчивается continue

-

В то время мы опуисполнения некоторые подробности в этом коде. В главе 6 раздела "Оператор управления потоком match" мы обсуждали, что все ветви match должны возвращать одинаковый вид. Например, следующий код не работает:

-
fn main() {
-    let guess = "3";
-    let guess = match guess.trim().parse() {
-        Ok(_) => 5,
-        Err(_) => "hello",
-    };
-}
-

Вид guess в этом коде должен быть целым и строкой, а Ржавчина требует, чтобы guess имел только один вид. Так что же возвращает continue? Как нам позволили вернуть u32 из одной ветви и при этом иметь другую ветвь, которая оканчивается continue в приложении 19-26?

-

Как вы уже возможно догадались, continue имеет значение !. То есть, когда Ржавчина вычисляет вид guess, он смотрит на обе сопоставляемые ветки, первая со значением u32 и последняя со значением !. Так как ! никогда не может иметь значение, то Ржавчина решает что видом guess является вид u32.

-

Условный подход к описанию такого поведения заключается в том, что выражения вида ! могут быть преобразованы в любой другой вид. Нам позволяется завершить этот match с помощью continue, потому что continue не возвращает никакого значения; вместо этого он передаёт управление обратно в начало цикла, поэтому в случае Err мы никогда не присваиваем значение guess.

-

Вид never полезен также для макроса panic!. Вспомните функцию unwrap, которую мы вызываем для значений Option<T>, чтобы создать значение или вызвать панику с этим определением:

-
enum Option<T> {
-    Some(T),
-    None,
-}
-
-use crate::Option::*;
-
-impl<T> Option<T> {
-    pub fn unwrap(self) -> T {
-        match self {
-            Some(val) => val,
-            None => panic!("called `Option::unwrap()` on a `None` value"),
-        }
-    }
-}
-

В этом коде происходит то же самое, что и в match в приложении 19-26: Ржавчина видит, что val имеет вид T, а panic! имеет вид !, поэтому итогом общего выражения match является T. Этот код работает, потому что panic! не производит никакого значения; он завершает программу. В случае None мы не будем возвращать значение из unwrap, поэтому этот код работает.

-

Последнее выражение, которое имеет вид ! это loop:

-
fn main() {
-    print!("forever ");
-
-    loop {
-        print!("and ever ");
-    }
-}
-

В данном случае цикл никогда не завершится, поэтому ! является значением выражения. Но это не будет так, если мы добавим break, так как цикл завершит свою работу, когда дойдёт до break.

-

Виды с изменяемым размером и особенность Sized

-

Rust необходимо знать некоторые подробности о видах, например, сколько места нужно выделить для значения определённого вида. Из-за этого один из особенностей системы видов поначалу вызывает некоторое недоумение: подход видов с изменяемым размером. Иногда называемые DST или безразмерные виды, эти виды позволяют нам писать код, используя значения, размер которых мы можем узнать только во время выполнения.

-

Давайте углубимся в подробности изменяемого вида str, который мы использовали на протяжении всей книги. Все верно, не вида &str, а вида str самого по себе, который является DST. Мы не можем знать, какой длины строка до особенности времени выполнения, то есть мы не можем создать переменную вида str и не можем принять переменная вида str. Рассмотрим следующий код, который не работает:

-
fn main() {
-    let s1: str = "Hello there!";
-    let s2: str = "How's it going?";
-}
-

Rust должен знать, сколько памяти выделить для любого значения определенного вида и все значения вида должны использовать одинаковый размер памяти. Если Ржавчина позволил бы нам написать такой код, то эти два значения str должны были бы занимать одинаковое количество памяти. Но они имеют разную длину: s1 нужно 12 байтов памяти, а для s2 нужно 15. Вот почему невозможно создать переменную имеющую вид изменяемого размера.

-

Так что же нам делать? В этом случае вы уже знаете ответ: мы преобразуем виды s1 и s2 в &str, а не в str. Вспомните из раздела "Строковые срезы" главы 4, что устройства данных среза просто хранит начальную положение и длину среза. Так, в отличие от &T, который содержит только одно значение - адрес памяти, где находится T, в &str хранятся два значения - адрес str и его длина. Таким образом, мы можем узнать размер значения &str во время сборки: он вдвое больше длины usize. То есть, мы всегда знаем размер &str, независимо от длины строки, на которую оно ссылается. В целом, именно так в Ржавчина используются виды изменяемого размера: они содержат дополнительный бит метаданных, который хранит размер изменяемой сведений. Золотое правило изменяемых размерных видов заключается в том, что мы всегда должны помещать значения таких видов за каким-либо указателем.

-

Мы можем соединенять str со всеми видами указателей: например, Box<str> или Rc<str>. На самом деле, вы уже видели это раньше, но с другим изменяемым размерным видом: особенностями. Каждый особенность - это изменяемый размерный вид, на который мы можем ссылаться, используя имя особенности. В главе 17 в разделе "Использование особенность-предметов, допускающих значения разных видов" мы упоминали, что для использования особенностей в качестве особенность-предметов мы должны поместить их за указателем, например &dyn Trait или Box<dyn Trait> (Rc<dyn Trait> тоже подойдёт).

-

Для работы с DST Ржавчина использует особенность Sized чтобы решить, будет ли размер вида известен на стадии сборки. Этот особенность самостоятельно выполняется для всего, чей размер известен к времени сборки. Кроме того, Ржавчина неявно добавляет ограничение на Sized к каждой гибкой функции. То есть, определение гибкой функции, такое как:

-
fn generic<T>(t: T) {
-    // --snip--
-}
-

на самом деле рассматривается как если бы мы написали её в виде:

-
fn generic<T: Sized>(t: T) {
-    // --snip--
-}
-

По умолчанию обобщённые функции будут работать только с видами чей размер известен во время сборки. Тем не менее, можно использовать следующий особый правила написания, чтобы ослабить это ограничение:

-
fn generic<T: ?Sized>(t: &T) {
-    // --snip--
-}
-

Ограничение особенности ?Sized означает «T может или не может быть Sized», эта наставление отменяет обычное правило, согласно которому гибкие виды должны иметь известный размер во время сборки. Использовать правила написания ?Trait в таком качестве можно только для Sized, и ни для каких других особенностей.

-

Также обратите внимание, что мы поменяли вид свойства t с T на &T. Поскольку вид мог бы не быть Sized, мы должны использовать его за каким-либо указателем. В данном случае мы выбрали ссылку.

-

Далее мы поговорим о функциях и замыканиях!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch19-05-advanced-functions-and-closures.html b/rustbook-ru/book/ch19-05-advanced-functions-and-closures.html deleted file mode 100644 index e909bc42a..000000000 --- a/rustbook-ru/book/ch19-05-advanced-functions-and-closures.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - - Продвинутые функции и замыкания - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Продвинутые функции и замыкания

-

В этом разделе рассматриваются некоторые продвинутые возможности, относящиеся к функциям и замыканиям, такие как указатели функций и возвращаемые замыкания.

-

Указатели функций

-

Мы уже обсуждали, как передавать замыкания в функции; но также можно передавать обычные функции в функции! Эта техника полезна, когда вы хотите передать ранее созданную функцию, а не определять новое замыкание. Функции соответствуют виду fn (со строчной буквой f), не путать с особенностью замыкания Fn. Вид fn называется указателем функции. Передача функций с помощью указателей функций позволяет использовать функции в качестве переменных других функций.

-

Для указания того, что свойство является указателем на функцию, используется правила написания, такой же, как и для замыканий, что отображается в приложении 19-27, где мы определили функцию add_one, которая добавляет единицу к переданному ей свойству. Функция do_twice принимает два свойства: указатель на любую функцию, принимающую свойство i32 и возвращающую i32, и число вида i32. Функция do_twice дважды вызывает функцию f, передавая ей значение arg, а затем складывает полученные итоги. Функция main вызывает функцию do_twice с переменнойми add_one и 5.

-

Файл: src/main.rs

-
fn add_one(x: i32) -> i32 {
-    x + 1
-}
-
-fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
-    f(arg) + f(arg)
-}
-
-fn main() {
-    let answer = do_twice(add_one, 5);
-
-    println!("The answer is: {answer}");
-}
-

Приложение 19-27: Использование вида fn для получения указателя на функцию в качестве переменной

-

Этот код выводит Ответ: 12. Мы указали, что свойство f в do_twice является fn, которая принимает на вход единственный свойство вида i32 и возвращает i32. Затем мы можем вызвать f в теле do_twice. В main мы можем передать имя функции add_one в качестве первого переменной в do_twice.

-

В отличие от замыканий, fn является видом, а не особенностью, поэтому мы указываем fn непосредственно в качестве вида свойства, а не объявляем свойство гибкого вида с одним из особенностей Fn в качестве связанного.

-

Указатели функций выполняют все три особенности замыканий (Fn, FnMut и FnOnce), то есть вы всегда можете передать указатель функции в качестве переменной функции, которая ожидает замыкание. Лучше всего для описания функции использовать гибкий вид и один из особенностей замыканий, чтобы ваши функции могли принимать как функции, так и замыкания.

-

Однако, одним из примеров, когда вы бы хотели принимать только fn, но не замыкания, является взаимодействие с внешним кодом, который не имеет замыканий: функции языка C могут принимать функции в качестве переменных, однако замыканий в языке C нет.

-

В качестве примера того, где можно использовать либо замыкание, определяемое непосредственно в месте передачи, либо именованную функцию, рассмотрим использование способа map, предоставляемого особенностью Iterator в встроенной библиотеке. Чтобы использовать функцию map для преобразования вектора чисел в вектор строк, мы можем использовать замыкание, например, так:

-
fn main() {
-    let list_of_numbers = vec![1, 2, 3];
-    let list_of_strings: Vec<String> =
-        list_of_numbers.iter().map(|i| i.to_string()).collect();
-}
-

Или мы можем использовать функцию в качестве переменной map вместо замыкания, например, так:

-
fn main() {
-    let list_of_numbers = vec![1, 2, 3];
-    let list_of_strings: Vec<String> =
-        list_of_numbers.iter().map(ToString::to_string).collect();
-}
-

Обратите внимание, что мы должны использовать полный правила написания, о котором мы говорили ранее в разделе "Продвинутые особенности", потому что доступно несколько функций с именем to_string. Здесь мы используем функцию to_string определённую в особенности ToString, который выполнен в встроенной библиотеке для любого вида выполняющего особенность Display.

-

Вспомните из раздела "Значения перечислений" главы 6, что имя каждого определённого нами исхода перечисления также становится функцией-объявителем. Мы можем использовать эти объявители в качестве указателей на функции, выполняющих особенности замыканий, что означает, что мы можем использовать объявители в качестве переменных для способов, принимающих замыкания, например, так:

-
fn main() {
-    enum Status {
-        Value(u32),
-        Stop,
-    }
-
-    let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
-}
-

Здесь мы создаём образцы Status::Value, используя каждое значение u32 в ряде (0..20), с которым вызывается map с помощью функции объявителя Status::Value. Некоторые люди предпочитают этот исполнение, а некоторые предпочитают использовать замыкания. Оба исхода собирается в один и тот же код, поэтому используйте любой исполнение, который вам понятнее.

-

Возврат замыканий

-

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

-

Следующий код пытается напрямую вернуть замыкание, но он не собирается:

-
fn returns_closure() -> dyn Fn(i32) -> i32 {
-    |x| x + 1
-}
-

Ошибка сборщика выглядит следующим образом:

-
$ cargo build
-   Compiling functions-example v0.1.0 (file:///projects/functions-example)
-error[E0746]: return type cannot have an unboxed trait object
- --> src/lib.rs:1:25
-  |
-1 | fn returns_closure() -> dyn Fn(i32) -> i32 {
-  |                         ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
-  |
-help: return an `impl Trait` instead of a `dyn Trait`, if all returned values are the same type
-  |
-1 | fn returns_closure() -> impl Fn(i32) -> i32 {
-  |                         ~~~~
-help: box the return type, and wrap all of the returned values in `Box::new`
-  |
-1 ~ fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
-2 ~     Box::new(|x| x + 1)
-  |
-
-For more information about this error, try `rustc --explain E0746`.
-error: could not compile `functions-example` (lib) due to 1 previous error
-
-

Ошибка снова ссылается на особенность Sized ! Ржавчина не знает, сколько памяти нужно будет выделить для замыкания. Мы видели решение этой сбоев ранее. Мы можем использовать особенность-предмет:

-
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
-    Box::new(|x| x + 1)
-}
-

Этот код просто отлично собирается. Для получения дополнительной сведений об особенность-предмета. обратитесь к разделу "Использование особенность-предметов которые допускают значения разных видов" главы 17.

-

Далее давайте посмотрим на макросы!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch19-06-macros.html b/rustbook-ru/book/ch19-06-macros.html deleted file mode 100644 index b7bea446d..000000000 --- a/rustbook-ru/book/ch19-06-macros.html +++ /dev/null @@ -1,449 +0,0 @@ - - - - - - Макросы - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Макросы

-

Мы использовали макросы, такие как println! на протяжении всей этой книги, но мы не изучили полностью, что такое макрос и как он работает. Понятие макрос относится к семейству возможностей в Rust. Это декларативные (declarative) макросы с помощью macro_rules! и три вида процедурных (procedural) макросов:

-
    -
  • Пользовательские (выводимые) #[derive] макросы, которые указывают код, добавленный с помощью свойства derive, используемые для устройств и перечислений
  • -
  • Макросы подобные свойствам (attribute-like), которые определяют настраиваемые свойства, используемые для любого элемента языка
  • -
  • Похожие на функции (function-like) макросы, которые выглядят как вызовы функций, но работают с TokenStream
  • -
-

Мы поговорим о каждом из них по очереди, но сначала давайте рассмотрим, зачем вообще нужны макросы, если есть функции.

-

Разница между макросами и функциями

-

По сути, макросы являются способом написания кода, который записывает другой код, что известно как мета программирование. В Приложении C мы обсуждаем свойство derive, который порождает за вас выполнение различных особенностей. Мы также использовали макросы println! и vec! на протяжении книги. Все эти макросы раскрываются для создания большего количества кода, чем исходный код написанный вами вручную.

-

Мета программирование полезно для уменьшения объёма кода, который вы должны написать и поддерживать, что также является одним из предназначений функций. Однако макросы имеют некоторые дополнительные возможности, которых функции не имеют.

-

Ярлык функции должна объявлять некоторое количество и вид этих свойств имеющихся у функции. Макросы, с другой стороны, могут принимать переменное число свойств: мы можем вызвать println!("hello") с одним переменнаяом или println!("hello {}", name) с двумя переменнойми. Также макросы раскрываются до того как сборщик преобразует смысл кода, поэтому макрос может, например, выполнить особенность заданного вида. Функция этого не может, потому что она вызывается во время выполнения и особенность должен быть выполнен во время сборки.

-

Обратной стороной выполнения макроса вместо функции является то, что определения макросов являются более сложными, чем определения функций, потому что вы создаёте Ржавчина код, который записывает другой Ржавчина код. Из-за этой косвенности, объявления макросов, как правило, труднее читать, понимать и поддерживать, чем объявления функций.

-

Другое важное различие между макросами и функциями заключается в том, что вы должны объявить макросы или добавить их в область видимости прежде чем можете вызывать их в файле, в отличии от функций, которые вы можете объявить где угодно и вызывать из любого места.

-

Декларативные макросы с macro_rules! для общего мета программирования

-

Наиболее широко используемой способом макросов в Ржавчина являются декларативные макросы. Они также иногда упоминаются как "макросы на примере", "macro_rules! макрос" или просто "макросы". По своей сути декларативные макросы позволяют писать нечто похожее на выражение match в Rust. Как обсуждалось в главе 6, match выражения являются управляющими устройствами, которые принимают некоторое выражение, итог значения выражения сопоставляют с образцами, а затем запускают код для сопоставляемой ветки. Макросы также сравнивают значение с образцами, которые связаны с определенным кодом: в этой случаи значение является записью исходного кода Rust, переданным в макрос. Образцы сравниваются со устройствами этого исходного кода и при совпадении код, связанный с каждым образцом, заменяет код переданный макросу. Все это происходит во время сборки.

-

Для определения макроса используется устройство macro_rules!. Давайте рассмотрим, как использовать macro_rules! глядя на то, как объявлен макрос vec!. В главе 8 рассказано, как можно использовать макрос vec! для создания нового вектора с определёнными значениями. Например, следующий макрос создаёт новый вектор, содержащий три целых числа:

-
#![allow(unused)]
-fn main() {
-let v: Vec<u32> = vec![1, 2, 3];
-}
-

Мы также могли использовать макрос vec! для создания вектора из двух целых чисел или вектора из пяти строковых срезов. Мы не смогли бы использовать функцию, чтобы сделать то же самое, потому что мы не знали бы заранее количество или вид значений.

-

В приложении 19-28 приведено несколько упрощённое определение макроса vec!.

-

Файл: src/lib.rs

-
#[macro_export]
-macro_rules! vec {
-    ( $( $x:expr ),* ) => {
-        {
-            let mut temp_vec = Vec::new();
-            $(
-                temp_vec.push($x);
-            )*
-            temp_vec
-        }
-    };
-}
-

Приложение 19-28: Упрощённая исполнение определения макроса vec!

-
-

Примечание: действительное определение макроса vec! в встроенной библиотеке содержит код для предварительного выделения правильного объёма памяти. Этот код является переработкой, которую мы здесь не используем, чтобы сделать пример проще.

-
-

Изложение #[macro_export] указывает, что данный макрос должен быть доступен всякий раз, когда ящик с объявленным макросом, добавлен в область видимости. Без этой изложении макрос нельзя добавить в область видимости.

-

Затем мы начинаем объявление макроса с помощью macro_rules! и имени макроса, который объявляется без восклицательного знака. Название, в данном случае vec, после которого следуют фигурные скобки, указывающие тело определения макроса.

-

Устройства в теле макроса vec! похожа на устройство match выражения. Здесь у нас есть одна ветвь с образцом ( $( $x:expr ),* ), затем следует ветвь => и раздел кода, связанный с этим образцом. Если образец сопоставлен успешно, то соответствующий раздел кода будет создан. Учитывая, что данный код является единственным образцом в этом макросе, существует только один действительный способ сопоставления, любой другой образец приведёт к ошибке. Более сложные макросы будут иметь более одной ветви.

-

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

-

Во-первых, мы используем набор скобок, чтобы охватить весь образец. Мы используем знак доллара ( $) для объявления переменной в системе макросов, которая будет содержать код на Rust, соответствующий образцу. Знак доллара показывает, что это макропеременная, а не обычная переменная Rust. Далее следует набор скобок, в котором определятся значения, соответствующие образцу в скобках, для использования в коде замены. Внутри $() находится $x:expr, которое соответствует любому выражению Ржавчина и даёт выражению имя $x.

-

Запятая, следующая за $() указывает на то, что буквенный символ-разделитель запятая может дополнительно появиться после кода, который соответствует коду в $(). Звёздочка * указывает, что образец соответствует ноль или больше раз тому, что предшествует *.

-

Когда вызывается этот макрос с помощью vec![1, 2, 3]; образец $x соответствует три раза всем трём выражениям 1, 2 и 3.

-

Теперь давайте посмотрим на образец в теле кода, связанного с этой ветвью: temp_vec.push() внутри $()* порождается для каждой части, которая соответствует символу $() в образце ноль или более раз в зависимости от того, сколько раз образец сопоставлен. Символ $x заменяется на каждое совпадающее выражение. Когда мы вызываем этот макрос с vec![1, 2, 3];, созданный код, заменяющий этот вызов макроса будет следующим:

-
{
-    let mut temp_vec = Vec::new();
-    temp_vec.push(1);
-    temp_vec.push(2);
-    temp_vec.push(3);
-    temp_vec
-}
-

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

-

Чтобы узнать больше о том, как писать макросы, обратитесь к онлайн-документации или другим ресурсам, таким как «Маленькая книга макросов Rust» , начатая Дэниелом Кипом и продолженная Лукасом Виртом.

-

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

-

Вторая разновидность макросов - это процедурные макросы (procedural macros), которые действуют как функции (и являются видом процедуры). Процедурные макросы принимают некоторый код в качестве входных данных, работают над этим кодом и создают некоторый код в качестве вывода, а не выполняют сопоставления с образцами и замену кода другим кодом, как это делают декларативные макросы. Процедурные макросы могут быть трёх видов: "пользовательского вывода" (custom-derive), "похожие на свойство" (attribute-like) и "похожие на функцию" (function-like), все они работают схожим образом.

-

При создании процедурных макросов объявления должны находиться в собственном ящике целенаправленного вида. Это из-за сложных технических причин, которые мы надеемся будут устранены в будущем. В приложении 19-29 показано, как задать процедурный макрос, где some_attribute является заполнителем для использования целенаправленного макроса.

-

Файл: src/lib.rs

-
use proc_macro;
-
-#[some_attribute]
-pub fn some_name(input: TokenStream) -> TokenStream {
-}
-

Приложение 19-29: Пример определения процедурного макроса

-

Функция, которая определяет процедурный макрос, принимает TokenStream в качестве входных данных и создаёт TokenStream в качестве вывода. Вид TokenStream объявлен ящиком proc_macro, включённым в Ржавчина и представляет собой последовательность токенов. Это ядро макроса: исходный код над которым работает макрос, является входным TokenStream, а код создаваемый макросом является выходным TokenStream. К функции имеет также прикреплённый свойство, определяющий какой вид процедурного макроса мы создаём. Можно иметь несколько видов процедурных макросов в одном и том же ящике.

-

Давайте посмотрим на различные виды процедурных макросов. Начнём с пользовательского, выводимого (derive) макроса и затем объясним небольшие различия, делающие другие разновидности отличающимися.

-

Как написать пользовательский derive макрос

-

Давайте создадим ящик с именем hello_macro, который определяет особенность с именем HelloMacro и имеет одну с ним сопряженную функцию с именем hello_macro. Вместо того, чтобы пользователи нашего ящика самостоятельно выполнили особенность HelloMacro для каждого из своих видов, мы предоставим им процедурный макрос, чтобы они могли определять свой вид с помощью свойства #[derive(HelloMacro)] и получили выполнение по умолчанию для функции hello_macro. Выполнение по умолчанию выведет Hello, Macro! My name is TypeName!, где TypeName - это имя вида, для которого был определён этот особенность. Другими словами, мы напишем ящик, использование которого позволит другому программисту писать код показанный в приложении 19-30.

-

Файл: src/main.rs

-
use hello_macro::HelloMacro;
-use hello_macro_derive::HelloMacro;
-
-#[derive(HelloMacro)]
-struct Pancakes;
-
-fn main() {
-    Pancakes::hello_macro();
-}
-

Приложение 19-30: Код, который сможет писать пользователь нашего ящика при использовании нашего процедурного макроса

-

Этот код напечатает Hello, Macro! My name is Pancakes!, когда мы закончим. Первый шаг - создать новый, библиотечный ящик так:

-
$ cargo new hello_macro --lib
-
-

Далее, мы определим особенность HelloMacro и сопряженную с ним функцию:

-

Файл: src/lib.rs

-
pub trait HelloMacro {
-    fn hello_macro();
-}
-

У нас есть особенность и его функция. На этом этапе пользователь ящика может выполнить особенность для достижения желаемой возможности, так:

-
use hello_macro::HelloMacro;
-
-struct Pancakes;
-
-impl HelloMacro for Pancakes {
-    fn hello_macro() {
-        println!("Hello, Macro! My name is Pancakes!");
-    }
-}
-
-fn main() {
-    Pancakes::hello_macro();
-}
-

Тем не менее, ему придётся написать разделвыполнения для каждого вида, который он хотел использовать вместе с hello_macro; а мы хотим избавить их от необходимости делать эту работу.

-

Кроме того, мы пока не можем предоставить функцию hello_macro с выполнением по умолчанию, которая будет печатать имя вида, для которого выполнен особенность: Ржавчина не имеет возможностей рефлексии (reflection), поэтому он не может выполнить поиск имени вида во время выполнения кода. Нам нужен макрос для создания кода во время сборки.

-

Следующим шагом является определение процедурного макроса. На мгновение написания этой статьи процедурные макросы должны быть в собственном ящике. Со временем это ограничение может быть отменено. Соглашение о внутреннем выстраивании

-

ящиков и макросов является следующим: для ящика с именем foo, его пользовательский, ящик с выводимым процедурным макросом называется foo_derive. Давайте начнём с создания нового ящика с именем hello_macro_derive внутри дела hello_macro:

-
$ cargo new hello_macro_derive --lib
-
-

Наши два ящика тесно связаны, поэтому мы создаём процедурный макрос-ящик в папке ящика hello_macro. Если мы изменим определение особенности в hello_macro, то нам придётся также изменить выполнение процедурного макроса в hello_macro_derive. Два ящика нужно будет обнародовать отдельно и программисты, использующие эти ящики, должны будут добавить их как зависимости, а затем добавить их в область видимости. Мы могли вместо этого сделать так, что ящик hello_macro использует hello_macro_derive как зависимость и реэкспортирует код процедурного макроса. Однако то, как мы внутренне выстраивали

-

дело, делает возможным программистам использовать hello_macro даже если они не хотят derive возможность.

-

Нам нужно объявить ящик hello_macro_derive как процедурный макрос-ящик. Также понадобятся возможности из ящиков syn и quote, как вы увидите через мгновение, поэтому нам нужно добавить их как зависимости. Добавьте следующее в файл Cargo.toml для hello_macro_derive:

-

Файл: hello_macro_derive/Cargo.toml

-
[lib]
-proc-macro = true
-
-[dependencies]
-syn = "2.0"
-quote = "1.0"
-
-

Чтобы начать определение процедурного макроса, поместите код приложения 19-31 в ваш файл src/lib.rs ящика hello_macro_derive. Обратите внимание, что этот код не собирается пока мы не добавим определение для функции impl_hello_macro.

-

Файл: hello_macro_derive/src/lib.rs

-
use proc_macro::TokenStream;
-use quote::quote;
-
-#[proc_macro_derive(HelloMacro)]
-pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
-    // Construct a representation of Ржавчина code as a syntax tree
-    // that we can manipulate
-    let ast = syn::parse(input).unwrap();
-
-    // Build the trait implementation
-    impl_hello_macro(&ast)
-}
-

Приложение 19-31: Код, который потребуется в большинстве процедурных макро ящиков для обработки Ржавчина кода

-

Обратите внимание, что мы разделили код на функцию hello_macro_derive, которая отвечает за синтаксический анализ TokenStream и функцию impl_hello_macro, которая отвечает за преобразование синтаксического дерева: это делает написание процедурного макроса удобнее. Код во внешней функции ( hello_macro_derive в данном случае) будет одинаковым для почти любого процедурного макрос ящика, который вы видите или создаёте. Код, который вы указываете в теле внутренней функции (в данном случае impl_hello_macro ) будет отличаться в зависимости от цели вашего процедурного макроса.

-

Мы представили три новых ящика: proc_macro syn и quote. Макрос proc_macro поставляется с Rust, поэтому нам не нужно было добавлять его в зависимости внутри Cargo.toml. Макрос proc_macro - это API сборщика, который позволяет нам читать и управлять Ржавчина кодом из нашего кода.

-

Ящик syn разбирает Ржавчина код из строки в устройство данных над которой мы может выполнять действия. Ящик quote превращает устройства данных syn обратно в код Rust. Эти ящики упрощают разбор любого вида Ржавчина кода, который мы хотели бы обрабатывать: написание полного синтаксического анализатора для кода Ржавчина не является простой задачей.

-

Функция hello_macro_derive будет вызываться, когда пользователь нашей библиотеки указывает своему виду #[derive(HelloMacro)]. Это возможно, потому что мы определяли функцию hello_macro_derive с помощью proc_macro_derive и указали имя HelloMacro, которое соответствует имени нашего особенности; это соглашение, которому следует большинство процедурных макросов.

-

Функция hello_macro_derive сначала преобразует input из TokenStream в устройство данных, которую мы можем затем преобразовать и над которой выполнять действия. Здесь ящик syn вступает в игру. Функция parse в syn принимает TokenStream и возвращает устройство DeriveInput, представляющую разобранный код Rust. Приложение 19-32 показывает соответствующие части устройства DeriveInput, которые мы получаем при разборе строки struct Pancakes;:

-
DeriveInput {
-    // --snip--
-
-    ident: Ident {
-        ident: "Pancakes",
-        span: #0 bytes(95..103)
-    },
-    data: Struct(
-        DataStruct {
-            struct_token: Struct,
-            fields: Unit,
-            semi_token: Some(
-                Semi
-            )
-        }
-    )
-}
-

Приложение 19-32: Образец DeriveInput получаемый, когда разбирается код имеющий свойство макроса из приложения 19-30

-

Поля этой устройства показывают, что код Rust, который мы разобрали, является разделустройства с ident (определителем, означающим имя) Pancakes. В этой устройстве есть больше полей для описания всех видов кода Rust; проверьте документацию syn о устройстве DeriveInput для получения дополнительной сведений.

-

Вскоре мы определим функцию impl_hello_macro, в которой построим новый, дополнительный код Rust. Но прежде чем мы это сделаем, обратите внимание, что выводом для нашего выводимого (derive) макроса также является TokenStream. Возвращаемый TokenStream добавляется в код, написанный пользователями макроса, поэтому, когда они соберут свой ящик, они получат дополнительную возможность, которую мы предоставляем в изменённом TokenStream.

-

Возможно, вы заметили, что мы вызываем unwrap чтобы выполнить панику в функции hello_macro_derive, если вызов функции syn::parse потерпит неудачу. Наш процедурный макрос должен паниковать при ошибках, потому что функции proc_macro_derive должны возвращать TokenStream, а не вид Result для соответствия API процедурного макроса. Мы упроисполнения этот пример с помощью unwrap, но в рабочем коде вы должны предоставить более определенные сообщения об ошибках, если что-то пошло не правильно, используя panic! или expect.

-

Теперь, когда у нас есть код для преобразования определеного Ржавчина кода из TokenStream в образец DeriveInput, давайте создадим код выполняющий особенность HelloMacro у определеного вида, как показано в приложении 19-33.

-

Файл: hello_macro_derive/src/lib.rs

-
use proc_macro::TokenStream;
-use quote::quote;
-
-#[proc_macro_derive(HelloMacro)]
-pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
-    // Construct a representation of Ржавчина code as a syntax tree
-    // that we can manipulate
-    let ast = syn::parse(input).unwrap();
-
-    // Build the trait implementation
-    impl_hello_macro(&ast)
-}
-
-fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
-    let name = &ast.ident;
-    let gen = quote! {
-        impl HelloMacro for #name {
-            fn hello_macro() {
-                println!("Hello, Macro! My name is {}!", stringify!(#name));
-            }
-        }
-    };
-    gen.into()
-}
-

Приложение 19-33: Выполнение особенности HelloMacro с использованием проанализированного кода Rust.

-

Мы получаем образец устройства Ident содержащий имя (определитель) определеного вида с использованием ast.ident. Устройства в приложении 19-32 показывает, что когда мы запускаем функцию impl_hello_macro для кода из приложения 19-30, то получаемый ident будет иметь поле ident со значением "Pancakes". Таким образом, переменная name в приложении 19-33 будет содержать образец устройства Ident, что при печати выдаст строку "Pancakes", что является именем устройства в приложении 19-30.

-

Макрос quote! позволяет определить код Rust, который мы хотим вернуть. Сборщик ожидает что-то отличное от прямого итога выполнения макроса quote!, поэтому нужно преобразовать его в TokenStream. Мы делаем это путём вызова способа into, который использует промежуточное представление и возвращает значение требуемого вида TokenStream.

-

Макрос quote! также предоставляет очень полезную механику образцов: мы можем ввести #name и quote! заменит его значением из переменной name. Вы можете даже сделать некоторое повторение, подобное тому, как работают обычные макросы. Проверьте документацию ящика quote для подробного введения.

-

Мы хотим, чтобы наш процедурный макрос порождал выполнение нашего особенности HelloMacro для вида, который определял пользователь, который мы можем получить, используя #name. Выполнение особенности имеет одну функцию hello_macro, тело которой содержит возможность, которую мы хотим предоставить: напечатать Hello, Macro! My name is с именем определеного вида.

-

Макрос stringify! используемый здесь, встроен в Rust. Он принимает Ржавчина выражение, такое как 1 + 2 и во время сборки сборщик превращает выражение в строковый запись, такой как "1 + 2". Он отличается от макросов format! или println!, которые вычисляют выражение, а затем превращают итог в виде вида String. Существует возможность того, что введённый #name может оказаться выражением для печати буквально как есть, поэтому здесь мы используем stringify!. Использование stringify! также уменьшает выделение памяти путём преобразования #name в строковый запись во время сборки.

-

На этом этапе приказ cargo build должна завершиться успешно для обоих hello_macro и hello_macro_derive. Давайте подключим эти ящики к коду в приложении 19-30, чтобы увидеть процедурный макрос в действии! Создайте новый двоичный дело в папке ваших дел с использованием приказы cargo new pancakes. Нам нужно добавить hello_macro и hello_macro_derive в качестве зависимостей для ящика pancakes в файл Cargo.toml. Если вы размещаете свои исполнения hello_macro и hello_macro_derive на сайт crates.io, они будут обычными зависимостями; если нет, вы можете указать их как path зависимости следующим образом:

-
hello_macro = { path = "../hello_macro" }
-hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }
-
-

Поместите код в приложении 19-30 в src/main.rs и выполните cargo run: он должен вывести Hello, Macro! My name is Pancakes!. Выполнение особенности HelloMacro из процедурного макроса была включена без необходимости его выполнения ящиком pancakes; #[derive(HelloMacro)] добавил выполнение особенности.

-

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

-

Макросы, похожие на свойство

-

Подобные свойствам макросы похожи на пользовательские выводимые макросы, но вместо создания кода для derive свойства, они позволяют создавать новые свойства. Они являются также более гибкими: derive работает только для устройств и перечислений; свойство-подобные могут применяться и к другим элементам, таким как функции. Вот пример использования имеющего свойство макроса: допустим, у вас есть свойство именованный route который определяет функции при использовании фреймворка для веб-приложений:

-
#[route(GET, "/")]
-fn index() {
-

Данный свойство #[route] будет определён платспособом как процедурный макрос. Ярлык функции определения макроса будет выглядеть так:

-
#[proc_macro_attribute]
-pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
-

Здесь есть два свойства вида TokenStream. Первый для содержимого свойства: часть GET, "/" . Второй это тело элемента, к которому прикреплён свойство: в данном случае fn index() {} и остальная часть тела функции.

-

Кроме того, имеющие свойства макросы работают так же как и пользовательские выводимые макросы: вы создаёте ящик с видом proc-macro и выполняете функцию, которая порождает код, который хотите!

-

Макросы, похожие на функции

-

Макросы, похожие на функции, выглядят подобно вызову функций. Подобно макросам macro_rules! они являются более гибкими, чем функции; например, они могут принимать неизвестное количество переменных. Тем не менее, макросы macro_rules! можно объявлять только с использованием правил написания подобного сопоставлению, который мы обсуждали ранее в разделе "Декларативные макросы macro_rules! для общего мета программирования". Макросы, похожие на функции, принимают свойство TokenStream и их определение управляет этим TokenStream, используя код Rust, как это делают два других вида процедурных макроса. Примером подобного возможностей макроса является макрос sql!, который можно вызвать так:

-
let sql = sql!(SELECT * FROM posts WHERE id=1);
-

Этот макрос будет разбирать SQL указанию внутри него и проверять, что она синтаксически правильная, что является гораздо более сложной обработкой, чем то что может сделать макрос macro_rules!. Макрос sql! мог бы быть определён так:

-
#[proc_macro]
-pub fn sql(input: TokenStream) -> TokenStream {
-

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

-

Итоги

-

Фух! Теперь у вас в распоряжении есть некоторые возможности Rust, которые вы не будете часто использовать, но вы будете знать, что они доступны в особых обстоятельствах. Мы представили несколько сложных тем, чтобы при появлении сообщения с предложением исправить ошибку или в коде других людей, вы могли бы распознать эти подходы и правила написания. Используйте эту главу как справочник, который поможет вам найти решение.

-

Далее мы применим в действительностивсе, что обсуждали на протяжении всей книги, и выполним ещё один дело!

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch20-00-final-project-a-web-server.html b/rustbook-ru/book/ch20-00-final-project-a-web-server.html deleted file mode 100644 index 0a611e135..000000000 --- a/rustbook-ru/book/ch20-00-final-project-a-web-server.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - Конечный дело: создание многопоточного веб-сервера - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Конечный дело: создание многопоточного веб-сервера

-

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

-

В качестве нашего конечного дела мы напишем веб-сервер, который выводит надпись “hello” в веб-браузере, как на рисунке 20-1.

-

hello from rust

-

Рисунок 20-1: Наш последний совместный дело

-

Для создания веб-сервера нам понадобится:

-
    -
  1. Узнать немного о протоколах TCP и HTTP.
  2. -
  3. Сделать прослушивание TCP соединения у сокета.
  4. -
  5. Создать возможность для парсинга небольшого количества HTTP-запросов.
  6. -
  7. Научить сервер отдавать правильный HTTP-ответ.
  8. -
  9. Улучшить пропускную способность нашего сервера с помощью объединения потоков.
  10. -
-

Прежде чем мы начнём, заметим: способ, который мы будем использовать - не лучшим способ создания веб-сервера на Rust. Члены сообщества уже обнародовали на crates.io несколько готовых к использованию ящиков, которые предоставляют более полные выполнения веб-сервера и объединения потоков, чем те, которые мы создадим. Однако наша цель в этой главе — научиться новому, а не идти по лёгкому пути. Поскольку Ржавчина — это язык системного программирования, мы можем выбирать тот уровень абстракции, который нам подходит, и можем переходить на более низкий уровень, что может быть невозможно или неприменимо в других языках. Поэтому мы напишем основной HTTP-сервер и объединениепотоков вручную, чтобы вы могли изучить общие мысли и способы, лежащие в основе ящиков, которые, возможно, вы будете использовать в будущем.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch20-01-single-threaded.html b/rustbook-ru/book/ch20-01-single-threaded.html deleted file mode 100644 index 503818e5f..000000000 --- a/rustbook-ru/book/ch20-01-single-threaded.html +++ /dev/null @@ -1,588 +0,0 @@ - - - - - - Создание однопоточного веб-сервера - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Создание однопоточного веб-сервера

-

Начнём с однопоточного веб-сервера. Перед тем, как начать, давайте сделаем краткий обзор протоколов, задействованных при создании веб-серверов. Детальное описание этих протоколов выходит за рамки этой книги, но краткий обзор даст вам необходимую сведения.

-

Двумя основными протоколами, используемыми в веб-серверах, являются протокол передачи гипертекста (HTTP - Hypertext Transfer Protocol) и Протокол управления передачей (TCP - Transmission Control Protocol). Оба протокола являются протоколами вида запрос-ответ (request-response), то есть клиент объявляет запросы, а сервер слушает эти запросы и предоставляет ответ клиенту. Содержимое этих запросов и ответов определяется протоколами.

-

TCP - это протокол нижнего уровня, который описывает подробности того, как сведения передаётся от одного сервера к другому, но не определяет, что это за сведения. HTTP строится поверх TCP, определяя содержимое запросов и ответов. Технически возможно использовать HTTP с другими протоколами, но в подавляющем большинстве случаев HTTP отправляет свои данные поверх TCP. Мы будем работать с необработанными байтами в TCP и запросами и ответами в HTTP.

-

Прослушивание TCP соединения

-

Нашему веб-серверу необходимо прослушивать TCP-соединение, так что это первая часть, над которой мы будем работать. Обычная библиотека предлагает для этого звено std::net. Сделаем новый дело обычным способом:

-
$ cargo new hello
-      Created binary (application) `hello` project
-$ cd hello
-
-

Дл начала добавьте код из приложения 20-1 в файл src/main.rs. Этот код будет прослушивать входящие TCP потоки по адресу 127.0.0.1:7878. Когда сервер примет входящий поток, он напечатает Connection established! ("Соединение установлено!").

-

Файл: src/main.rs

-
use std::net::TcpListener;
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        println!("Connection established!");
-    }
-}
-

Приложение 20-1: Прослушивание входящих потоков и печать сообщения при получении потока

-

Используя TcpListener мы можем слушать TCP соединения к адресу 127.0.0.1:7878. В адресе, в его части перед двоеточием, сначала идёт IP-адрес, относящийся к вашему компьютеру (он одинаковый на каждом компьютере и не представляет определенный компьютер автора), а часть 7878 является портом. Мы выбрали этот порт по двум причинам: HTTP обычно не используется на этом порту, поэтому маловероятно, что наш сервер будет враждовать с каким-нибудь другим сервером, который может выполняться на вашей машине, и ещё 7878 - это слово rust, набранное на телефоне.

-

Функция bind в этом сценарии работает так же, как функция new, поскольку она возвращает новый образец TcpListener . Причина, по которой функция называется bind заключается в том, что в сетевой совокупности понятий подключение к порту для прослушивания называется «привязка к порту» (“binding to a port”).

-

Функция bind возвращает Result<T, E>, а это значит, что привязка может не состояться. Так, например, подключение к порту 80 предполагает наличие привилегий администратора (прочие пользователи могут прослушивать порты только от 1023-го и выше), поэтому если мы попытаемся подключиться к порту 80, не будучи администратором, привязка не сработает. Привязка также не выполнится, например, если мы запустим два образца нашей программы, прослушивающие один и тот же порт. Поскольку мы пишем простейший сервер в учебных целях, мы не будем беспокоиться об обработке подобных ошибок; вместо этого мы используем unwrap для прекращения работы программы в случае возникновения ошибок.

-

Способ incoming в TcpListener возвращает повторитель , который даёт нам последовательность потоков (определеннее, потоков вида TcpStream ). Один поток представляет собой открытое соединение между клиентом и сервером. Соединением называется полный этап запроса и ответа, в котором клиент подключается к серверу, сервер порождает ответ, и сервер закрывает соединение. Таким образом, мы будем читать из потока TcpStream то, что отправил клиент, а затем записывать наш ответ в поток, для отправки его обратно клиенту. В целом, цикл for будет обрабатывать каждое соединение по очереди и создавать серию потоков, которые мы будем обрабатывать.

-

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

-

Попробуем запустить этот код! Вызовите cargo run в окне вызова, а затем загрузите 127.0.0.1:7878 в веб-браузере. В браузере должно отображаться сообщение об ошибке, например «Connection reset», поскольку сервер в настоящее время не отправляет обратно никаких данных. Но когда вы посмотрите на свой окно вызова, вы должны увидеть несколько сообщений, которые были напечатаны, когда браузер подключался к серверу!

-
     Running `target/debug/hello`
- Connection established!
- Connection established!
- Connection established!
-
-

Иногда вы видите несколько сообщений, напечатанных для одного запроса браузера; Причина может заключаться в том, что браузер выполняет запрос страницы, а также других ресурсов, таких как значок favicon.ico, который отображается на вкладке браузера.

-

Также может быть, что браузер пытается подключиться к серверу несколько раз, потому что сервер не отвечает. Когда stream выходит из области видимости и отбрасывается в конце цикла, соединение закрывается как часть выполнения drop. Браузеры иногда обрабатывают закрытые соединения, повторяя попытки, потому что неполадка может быть временной. Важным обстоятельством является то, что мы успешно получили указатель TCP-соединения!

-

Не забудьте остановить программу, нажав ctrl-c, когда вы закончите выполнение определённой исполнения кода. Затем перезапустите программу, вызвав приказ cargo run, после того, как вы внесли какой-либо набор изменений, чтобы убедиться, что выполняется самая свежая исполнение кода.

-

Чтение запросов

-

Выполняем возможности чтения запроса из браузера! Чтобы разделить части, связанные с получением соединения и последующим действием с ним, мы запустим новую функцию для обработки соединения. В этой новой функции handle_connection мы будем читать данные из потока TCP и распечатывать их, чтобы мы могли видеть данные, отправленные из браузера. Измените код, чтобы он выглядел как в приложении 20-2.

-

Файл: src/main.rs

-
use std::{
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let http_request: Vec<_> = buf_reader
-        .lines()
-        .map(|result| result.unwrap())
-        .take_while(|line| !line.is_empty())
-        .collect();
-
-    println!("Request: {http_request:#?}");
-}
-

Приложение 20-2: Чтение из TcpStream и печать данных

-

Мы добавляем std::io::prelude и std::io::BufReader в область видимости, чтобы получить доступ к особенностям и видам, которые позволяют нам читать и писать в поток. В цикле for функции main вместо вывода сообщения о том, что мы установили соединение, мы теперь вызываем новую функцию handle_connection и передаём ей stream.

-

В функции handle_connection мы создаём новый образец BufReader, который оборачивает изменяемую ссылку на stream. BufReader добавляет буферизацию, управляя вызовами способов особенности std::io::Read за нас.

-

Мы создаём переменную http_request для сбора строк запроса, который браузер отправляет на наш сервер. Мы указываем, что хотим собрать эти строки в вектор, добавляя изложение вида Vec<_>.

-

BufReader выполняет особенность std::io::BufRead, который выполняет способ lines. Способ lines возвращает повторитель Result<String, std::io::Error>, разделяющий поток данных на части всякий раз, когда ему попадается байт новой строки. Чтобы получить все строки String, мы с помощью map вызываем unwrap у каждого Result. Значение Result может быть ошибкой, если данные не соответствуют исполнению UTF-8 или если возникли сбоев с чтением из потока. Опять же, программа в промышленном исполнении должна обрабатывать эти ошибки более изящно, но мы для простоты решили прекращать работу программы в случае ошибки.

-

Браузер указывает об окончании HTTP-запроса, отправляя два символа перевода строки подряд, поэтому, чтобы получить один запрос из потока, мы забираем строки, пока не получим строку, которая является пустой строкой. После того, как мы собрали строки в вектор, мы распечатываем их, используя красивое отладочное изменение -, чтобы мы могли взглянуть на указания, которые веб-браузер отправляет на наш сервер.

-

Попробуем этот код! Запустите программу и снова сделайте запрос в веб-браузере. Обратите внимание, что мы по-прежнему будем получать в браузере страницу с ошибкой, но вывод нашей программы в окне вызова теперь будет выглядеть примерно так:

-
$ cargo run
-   Compiling hello v0.1.0 (file:///projects/hello)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.42s
-     Running `target/debug/hello`
-Request: [
-    "GET / HTTP/1.1",
-    "Host: 127.0.0.1:7878",
-    "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0) Gecko/20100101 Firefox/99.0",
-    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
-    "Accept-Language: en-US,en;q=0.5",
-    "Accept-Encoding: gzip, deflate, br",
-    "DNT: 1",
-    "Connection: keep-alive",
-    "Upgrade-Insecure-Requests: 1",
-    "Sec-Fetch-Dest: document",
-    "Sec-Fetch-Mode: navigate",
-    "Sec-Fetch-Site: none",
-    "Sec-Fetch-User: ?1",
-    "Cache-Control: max-age=0",
-]
-
-

В зависимости от вашего браузера итог может немного отличаться. Теперь, когда мы печатаем данные запроса, мы можем понять, почему мы получаем несколько подключений из одного запроса браузера, посмотрев на путь после GET в первой строке запроса. Если все повторяющиеся соединения запрашивают / , мы знаем, что браузер пытается получить / повторно, потому что он не получает ответа от нашей программы.

-

Давайте разберём эти данные запроса, чтобы понять, что браузер запрашивает у нашей программы.

-

Пристальный взгляд на HTTP запрос

-

HTTP - это текстовый протокол и запрос имеет следующий вид:

-
Method Request-URI HTTP-Version CRLF
-headers CRLF
-message-body
-
-

Первая строка - это строка запроса , содержащая сведения о том, что запрашивает клиент. Первая часть строки запроса указывает используемый способ , например GET или POST , который описывает, как клиент выполняет этот запрос. Наш клиент использовал запрос GET, что означает, что он просит нас предоставить сведения.

-

Следующая часть строки запроса - это /, которая указывает унифицированный определитель ресурса (URI), который запрашивает клиент: URI почти, но не совсем то же самое, что и унифицированный указатель ресурса (URL). Разница между URI и URL-адресами не важна для наших целей в этой главе, но согласно принятых требований HTTP использует понятие URI, поэтому мы можем просто мысленно заменить URL-адрес здесь.

-

Последняя часть - это исполнение HTTP, которую использует клиент, а затем строка запроса заканчивается последовательностью CRLF . (CRLF обозначает возврат каретки и перевод строки , что является понятием из дней пишущих машинок!) Последовательность CRLF также может быть записана как \r\n , где \r - возврат каретки, а \n - перевод строки. Последовательность CRLF отделяет строку запроса от остальных данных запроса. Обратите внимание, что при печати CRLF мы видим начало новой строки, а не \r\n .

-

Глядя на данные строки запроса, которые мы получили от запуска нашей программы, мы видим, что GET - это способ, / - это URI запроса, а HTTP/1.1 - это исполнение.

-

После строки запроса оставшиеся строки, начиная с Host: далее, являются заголовками. GET запросы не имеют тела.

-

Попробуйте сделать запрос из другого браузера или запросить другой адрес, например 127.0.0.1:7878/test , чтобы увидеть, как изменяются данные запроса.

-

Теперь, когда мы знаем, что запрашивает браузер, давайте отправим обратно в ответ некоторые данные!

-

Написание ответа

-

Теперь выполняем отправку данных в ответ на запрос клиента. Ответы имеют следующий вид:

-
HTTP-Version Status-Code Reason-Phrase CRLF
-headers CRLF
-message-body
-
-

Первая строка - это строка состояния, которая содержит исполнение HTTP, используемую в ответе, числовой код состояния, который суммирует итог запроса, и фразу причины, которая предоставляет текстовое описание кода состояния. После последовательности CRLF идут любые заголовки, другая последовательность CRLF и тело ответа.

-

Вот пример ответа, который использует HTTP исполнения 1.1, имеет код состояния 200, фразу причины OK, без заголовков и без тела:

-
HTTP/1.1 200 OK\r\n\r\n
-
-

Код состояния 200 - это обычный успешный ответ. Текст представляет собой крошечный успешный HTTP-ответ. Давайте запишем это в поток как наш ответ на успешный запрос! Из функции handle_connection удалите println! который печатал данные запроса и заменял их кодом из Приложения 20-3.

-

Файл: src/main.rs

-
use std::{
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let http_request: Vec<_> = buf_reader
-        .lines()
-        .map(|result| result.unwrap())
-        .take_while(|line| !line.is_empty())
-        .collect();
-
-    let response = "HTTP/1.1 200 OK\r\n\r\n";
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-3: Запись крошечного успешного HTTP-ответа в поток

-

Первый перевод строки определяет переменную response, которая содержит данные сообщения об успешном выполнении. Затем мы вызываем as_bytes в нашем response, чтобы преобразовать строковые данные в байты. Способ write_all в stream принимает вид &[u8] и отправляет эти байты непосредственно получателю. Поскольку действие write_all может завершиться с ошибкой, мы, как и ранее, используем unwrap на любом возможно ошибочном итоге. И опять, в существующем приложении здесь вам нужно было бы добавить обработку ошибок.

-

После этих изменений давайте запустим наш код и сделаем запрос. Мы больше не печатаем никаких данных в окно вызова, поэтому мы не увидим никакого вывода, кроме сообщений от Cargo. Когда вы загрузите 127.0.0.1:7878 в веб-браузере, вы должны получить пустую страницу вместо ошибки. Вы только что вручную написали код получения HTTP-запроса и отправки ответа на него!

-

Возвращение существующего HTML

-

Давайте выполняем возможности чего-нибудь большего, чем просто пустой страницы. Создайте новый файл hello.html в корне папки вашего дела, а не в папке src . Вы можете ввести любой HTML-код, который вам заблагорассудится; В приложении 20-4 показан один из исходов.

-

Файл: hello.html

-
<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8">
-    <title>Hello!</title>
-  </head>
-  <body>
-    <h1>Hello!</h1>
-    <p>Hi from Rust</p>
-  </body>
-</html>
-
-

Приложение 20-4: Пример HTML-файла для ответа на запрос

-

Это простейший HTML5-документ с заголовком и каким-то текстом. Чтобы сервер возвращал его в ответ на полученный запрос, мы изменим handle_connection, как показано в приложении 20-5, чтобы считать HTML-файл, добавить его в ответ в качестве тела и отправить.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-// --snip--
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let http_request: Vec<_> = buf_reader
-        .lines()
-        .map(|result| result.unwrap())
-        .take_while(|line| !line.is_empty())
-        .collect();
-
-    let status_line = "HTTP/1.1 200 OK";
-    let contents = fs::read_to_string("hello.html").unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-5. Отправка содержимого hello.html в качестве тела ответа

-

Мы добавили элемент fs в указанию use, чтобы включить в область видимости звено файловой системы встроенной библиотеки. Код для чтения содержимого файла в строку должен выглядеть знакомым для вас; мы использовали его в главе 12, когда читали содержимое файла для нашего дела ввода-вывода в приложении 12-4.

-

Далее мы используем format! чтобы добавить содержимое файла в качестве тела ответа об успешном завершении. Чтобы обеспечить действительный HTTP-ответ, мы добавляем заголовок Content-Length который имеет размер тела нашего ответа, в данном случае размер hello.html .

-

Запустите этот код приказом cargo run и загрузите 127.0.0.1:7878 в браузере; вы должны увидеть выведенный HTML в браузере!

-

В настоящее время мы пренебрегаем данные запроса в переменной http_request и в любом случае просто отправляем обратно содержимое HTML-файла. Это означает, что если вы попытаетесь запросить адрес 127.0.0.1:7878/something-else в своём браузере, вы все равно получите тот же самый HTML-ответ. Пока что наш сервер очень ограничен, и не умеет делать то, что делает большинство веб-серверов. Мы хотим настроить наши ответы в зависимости от запроса и отправлять обратно HTML-файл только для правильно созданного запроса к пути / .

-

Проверка запроса и выборочное возвращение ответа

-

Сейчас наш веб-сервер возвращает HTML из файла независимо от того, что определенно запросил клиент. Давайте добавим проверку того, что браузер запрашивает /, прежде чем вернуть HTML-файл, и будем возвращать ошибку, если браузер запрашивает что-то постороннее. Для этого нам нужно изменять handle_connection, как показано в приложении 20-6. Новый код проверяет соответствует ли требуемый запросом ресурс с определителем /, и содержит разделы if и else, чтобы иначе обрабатывать другие запросы.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-// --snip--
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    if request_line == "GET / HTTP/1.1" {
-        let status_line = "HTTP/1.1 200 OK";
-        let contents = fs::read_to_string("hello.html").unwrap();
-        let length = contents.len();
-
-        let response = format!(
-            "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
-        );
-
-        stream.write_all(response.as_bytes()).unwrap();
-    } else {
-        // some other request
-    }
-}
-

Приложение 20-6: Обрабатываем запросы для корневого ресурса / не так, как запросы для других ресурсов

-

Мы будем рассматривать только первую строку HTTP-запроса, поэтому вместо того, чтобы читать весь запрос в вектор, мы вызываем next , чтобы получить первый элемент из повторителя. Первый вызов unwrap заботится об обработке Option и останавливает программу, если в повторителе нет элементов. Второй unwrap обрабатывает Result и имеет тот же эффект, что и unwrap, который был в map, добавленном в приложении 20-2.

-

Затем мы проверяем переменную request_line, чтобы увидеть, равна ли она строке запроса, соответствующей запросу GET для пути / . Если это так, разделif возвращает содержимое нашего HTML-файла.

-

Если request_line не равна запросу GET для пути /, это означает, что мы получили какой-то другой запрос. Мы скоро добавим код в разделelse, чтобы ответить на все остальные запросы.

-

Запустите этот код сейчас и запросите 127.0.0.1:7878 ; вы должны получить HTML в hello.html . Если вы сделаете любой другой запрос, например 127.0.0.1:7878/something-else , вы получите ошибку соединения, подобную той, которую вы видели при запуске кода из Приложения 20-1 и Приложения 20-2.

-

Теперь давайте добавим код из приложения 20-7 в разделelse чтобы вернуть ответ с кодом состояния 404, который указывает о том, что содержание для запроса не найден. Мы также вернём HTML-код для страницы, отображаемой в браузере, с указанием ответа конечному пользователю.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    if request_line == "GET / HTTP/1.1" {
-        let status_line = "HTTP/1.1 200 OK";
-        let contents = fs::read_to_string("hello.html").unwrap();
-        let length = contents.len();
-
-        let response = format!(
-            "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
-        );
-
-        stream.write_all(response.as_bytes()).unwrap();
-    // --snip--
-    } else {
-        let status_line = "HTTP/1.1 404 NOT FOUND";
-        let contents = fs::read_to_string("404.html").unwrap();
-        let length = contents.len();
-
-        let response = format!(
-            "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
-        );
-
-        stream.write_all(response.as_bytes()).unwrap();
-    }
-}
-

Приложение 20-7: Отвечаем кодом состояния 404 и страницей ошибки, если было запрошено что-то, отличающееся от ресурса /

-

Здесь ответ имеет строку состояния с кодом 404 и фразу причины NOT FOUND. Тело ответа будет HTML из файла 404.html. Вам нужно создать файл 404.html рядом с hello.html для этой страницы ошибки; снова не стесняйтесь использовать любой HTML код или пример HTML кода в приложении 20-8.

-

Файл: 404.html

-
<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8">
-    <title>Hello!</title>
-  </head>
-  <body>
-    <h1>Oops!</h1>
-    <p>Sorry, I don't know what you're asking for.</p>
-  </body>
-</html>
-
-

Приложение 20-8. Пример содержимого страницы для отправки с любым ответом 404

-

С этими изменениями снова запустите сервер. Запрос на 127.0.0.1:7878 должен возвращать содержимое hello.html, и любой другой запрос, как 127.0.0.1:7878/foo, должен возвращать сообщение об ошибке HTML от 404.html.

-

Переработка кода

-

На текущий мгновение разделы if и else во многом повторяются: они оба читают файлы и записывают содержимое файлов в поток. Разница лишь в строке состояния и имени файла. Давайте сделаем код более кратким, вынеся эти отличия в отдельные разделы if и else, в которых переменным будут присвоены значения строки состояния и имени файла; далее эти переменные мы сможем использовать в коде для чтения файла и создания ответа. В приложении 20-9 показан код после изменения объёмных разделов if и else.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-// --snip--
-
-fn handle_connection(mut stream: TcpStream) {
-    // --snip--
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = if request_line == "GET / HTTP/1.1" {
-        ("HTTP/1.1 200 OK", "hello.html")
-    } else {
-        ("HTTP/1.1 404 NOT FOUND", "404.html")
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-9: Переработка кода разделов if и else, чтобы они содержали только код, который отличается для каждого из случаев

-

Теперь разделы if и else возвращают только соответствующие значения для строки состояния и имени файла в упорядоченном ряде. Затем мы используем разъединение, чтобы присвоить эти два значения status_line и filename используя образец в указания let, как обсуждалось в главе 18.

-

Ранее повторяющийся код теперь находится вне разделов if и else и использует переменные status_line и filename. Это позволяет легче увидеть разницу между этими двумя случаями и означает, что у нас есть только одно место для обновления кода, если захотим изменить работу чтения файлов и записи ответов. Поведение кода в приложении 20-9 будет таким же, как и в 20-8.

-

Потрясающие! Теперь у нас есть простой веб-сервер примерно на 40 строках кода Rust, который отвечает на один запрос страницей с содержанием и отвечает на все остальные запросы ответом 404.

-

В настоящее время наш сервер работает в одном потоке, что означает, что он может обслуживать только один запрос за раз. Давайте разберёмся, почему это может быть неполадкой, сымитировав несколько медленных запросов. Затем мы исправим случай так, чтобы наш сервер мог обрабатывать несколько запросов одновременно.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch20-02-multithreaded.html b/rustbook-ru/book/ch20-02-multithreaded.html deleted file mode 100644 index ca4bc0061..000000000 --- a/rustbook-ru/book/ch20-02-multithreaded.html +++ /dev/null @@ -1,1154 +0,0 @@ - - - - - - Превращение нашего однопоточного сервера в многопоточный сервер - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Превращение однопоточного сервера в многопоточный сервер

-

В текущей выполнения сервер обрабатывает каждый запрос по очереди, то есть, он не начнёт обрабатывать второе соединение, пока не завершит обработку первого. При росте числа запросов к серверу, такое последовательное выполнение было бы все менее и менее разумным. Если сервер получает какой-то запрос, обработка которого занимает достаточно много времени, последующим запросам придётся ждать завершения обработки длительного запроса, даже если эти новые запросы сами по себе могут быть обработаны быстро. Нам нужно это исправить, но сначала рассмотрим неполадку в действии.

-

Подражание медленного запроса в текущей выполнения сервера

-

Мы посмотрим, как запрос с медленной обработкой может повлиять на другие запросы, сделанные к серверу в текущей выполнения. В приложении 20-10 выполнена обработка запроса к ресурсу /sleep с эмуляцией медленного ответа, при которой сервер будет ждать 5 секунд перед тем, как ответить.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-// --snip--
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    // --snip--
-
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    // --snip--
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-10: Подражание медленного запроса с помощью 5-секундной задержки

-

Мы переключились с if на match, так как теперь у нас есть три случая. Нам придётся явно сопоставить срез от request_line для проверки совпадения образца со строковыми записями; match не делает самостоятельно е ссылки и разыменования, как это делает способ равенства.

-

Первая ветка совпадает с разделом if из приложения 20-9. Вторая ветка соответствует запросу /sleep . Когда этот запрос получен, сервер заснёт на 5 секунд, прежде чем отдать успешную HTML-страницу. Третья ветка совпадает с разделом else из приложения 20-9.

-

Можно увидеть, насколько прост наш сервер: в существующих библиотеках распознавание разных запросов осуществлялось бы гораздо менее многословно!

-

Запустите сервер приказом cargo run. Затем откройте два окна браузера: одно с адресом http://127.0.0.1:7878/, другое с http://127.0.0.1:7878/sleep. Если вы несколько раз обратитесь к URI /, то как и раньше увидите, что сервер быстро ответит. Но если вы введёте URI /sleep, а затем загрузите URI /, то увидите что / ждёт, пока /sleep не отработает полные 5 секунд перед загрузкой страницы.

-

Есть несколько способов, которые можно использовать, чтобы избавиться от подтормаживания запросов после одного медленного запроса; способ, который мы выполняем, называется объединением потоков.

-

Улучшение пропускной способности с помощью объединения потоков

-

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

-

Мы ограничим число потоков в объединении небольшим числом, чтобы защитить нас от атак вида «отказ в обслуживании» (DoS - Denial of Service); если бы наша программа создавала новый поток в мгновение поступления каждого запроса, то кто-то сделавший 10 миллионов запросов к серверу, мог бы создать хаос, использовать все ресурсы нашего сервера и остановить обработку запросов.

-

Вместо порождения неограниченного количества потоков, у нас будет определенное количество потоков, ожидающих в объединении. Поступающие запросы будут отправляться в объединениедля обработки. Объединение будет иметь очередь входящих запросов. Каждый из потоков в объединении будет извлекать запрос из этой очереди, обрабатывать запрос и затем запрашивать в очереди следующий запрос. При таком внешнем виде мы можем обрабатывать N запросов одновременно, где N - количество потоков. Если каждый поток отвечает на длительный запрос, последующие запросы могут по-прежнему задержаться в очереди, но теперь мы увеличили количество "длинных" запросов, которые мы можем обработать, перед тем, как эта случаей снова возникнет.

-

Этот подход - лишь один из многих способов улучшить пропускную способность веб-сервера. Другими исходами, на которые возможно стоило бы обратить внимание, являются: прообраз fork/join, прообраз однопоточного не согласованного ввода-вывода или прообраз многопоточного не согласованного ввода-вывода. Если вам важна эта тема, вы можете почитать больше сведений о других решениях и попробовать выполнить их самостоятельно. С таким низкоуровневым языком как Rust, любой из этих исходов осуществим.

-

Прежде чем приступить к выполнения объединения потоков, давайте поговорим о том, как должно выглядеть использование объединения . Когда вы пытаетесь создать код, сначала необходимо написать клиентский внешнюю оболочку. Напишите API кода, чтобы он был внутренне выстроен так, как вы хотите его вызывать, затем выполните возможность данной устройства, вместо подхода выполнить возможности. а затем разрабатывать общедоступный API.

-

Подобно тому, как мы использовали разработку через проверка (test-driven) в деле главы 12, мы будем использовать здесь разработку, управляемую сборщиком (compiler-driven). Мы напишем код, вызывающий нужные нам функции, а затем посмотрим на ошибки сборщика, чтобы определить, что мы должны изменить дальше, чтобы заставить код работать. Однако перед этим, в качестве отправной точки, мы рассмотрим технику, которую мы не будем применять в дальнейшем.

- -

-

Порождение потока для каждого запроса

-

Сначала давайте рассмотрим, как мог бы выглядеть код, если бы он создавал бы новый поток для каждого соединения. Как упоминалось ранее, мы не собираемся использовать этот способ в окончательной выполнения, из-за возможных неполадок при возможно неограниченном числе порождённых потоков. Это лишь отправная точка, с которой начнёт работу наш многопоточный сервер. Затем мы улучшим код, добавив объединениепотоков, и тогда разницу между этими двумя решениями будет легче заметить. В приложении 20-11 показаны изменения, которые нужно внести в код main, чтобы порождать новый поток для обработки каждого входящего соединения внутри цикла for.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        thread::spawn(|| {
-            handle_connection(stream);
-        });
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-11: Порождение нового потока для каждого соединения

-

Как вы изучили в главе 16, функция thread::spawn создаст новый поток и затем запустит код замыкания в этом новом потоке. Если вы запустите этот код и загрузите /sleep в своём браузере, а затем загрузите / в двух других вкладках браузера, вы действительно увидите, что запросам к / не приходится ждать завершения /sleep. Но, как мы уже упоминали, это в какой-то мгновение приведёт к сильному снижению производительности системы, так как вы будете создавать новые потоки без каких-либо ограничений.

- -

-

Создание конечного числа потоков

-

Мы хотим, чтобы наш объединениепотоков работал подобным, знакомым образом, чтобы переключение с потоков на объединениепотоков не требовало больших изменений в коде использующем наш API. В приложении 20-12 показан гипотетический внешняя оболочка для устройства ThreadPool, который мы хотим использовать вместо thread::spawn.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-    let pool = ThreadPool::new(4);
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        pool.execute(|| {
-            handle_connection(stream);
-        });
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-12: Наш наилучший внешняя оболочка ThreadPool

-

Мы используем ThreadPool::new, чтобы создать новый объединениепотоков с конфигурируемым числом потоков, в данном случае четырьмя. Затем в цикле for функция pool.execute имеет внешнюю оболочку, похожий на thread::spawn, в том смысле, что он так же принимает замыкание, код которого объединениедолжен выполнить для каждого соединения. Нам нужно выполнить pool.execute, чтобы он принимал замыкание и передавал его потоку из объединения для выполнения. Этот код пока не собирается, но мы постараемся, чтобы сборщик помог нам это исправить.

- -

-

Создание ThreadPool с помощью разработки, управляемой сборщиком

-

Внесите изменения приложения 20-12 в файл src/main.rs, а затем давайте воспользуемся ошибками сборщика из приказы cargo check для управления нашей разработкой. Вот первая ошибка, которую мы получаем:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0433]: failed to resolve: use of undeclared type `ThreadPool`
-  --> src/main.rs:11:16
-   |
-11 |     let pool = ThreadPool::new(4);
-   |                ^^^^^^^^^^ use of undeclared type `ThreadPool`
-
-For more information about this error, try `rustc --explain E0433`.
-error: could not compile `hello` (bin "hello") due to 1 previous error
-
-

Замечательно! Ошибка говорит о том, что нам нужен вид или звено ThreadPool, поэтому мы сейчас его создадим. Наша выполнение ThreadPool не будет зависеть от того, что делает наш веб-сервер. Итак, давайте переделаем ящик hello из двоичного в библиотечный, чтобы хранить там нашу выполнение ThreadPool. После того, как мы переключимся в библиотечный ящик, мы также сможем использовать отдельную библиотеку объединения потоков для любой подходящей работы, а не только для обслуживания веб-запросов.

-

Создайте файл src/lib.rs, который содержит следующий код, который является простейшим определением устройства ThreadPool, которое мы можем иметь на данный мгновение:

-

Файл: src/lib.rs

-
pub struct ThreadPool;
-

Затем изменените файл main.rs, чтобы внести ThreadPool из библиотечного ящика в текущую область видимости, добавив следующий код в начало src/main.rs:

-

Файл: src/main.rs

-
use hello::ThreadPool;
-use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-    let pool = ThreadPool::new(4);
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        pool.execute(|| {
-            handle_connection(stream);
-        });
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Этот код по-прежнему не будет работать, но давайте проверим его ещё раз, чтобы получить следующую ошибку, которую нам нужно устранить:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0599]: no function or associated item named `new` found for struct `ThreadPool` in the current scope
-  --> src/main.rs:12:28
-   |
-12 |     let pool = ThreadPool::new(4);
-   |                            ^^^ function or associated item not found in `ThreadPool`
-
-For more information about this error, try `rustc --explain E0599`.
-error: could not compile `hello` (bin "hello") due to 1 previous error
-
-

Эта ошибка указывает, что далее нам нужно создать сопряженную функцию с именем new для ThreadPool. Мы также знаем, что new должна иметь один свойство, который может принимать 4 в качестве переменной и должен возвращать образец ThreadPool. Давайте выполняем простейшую функцию new, которая будет иметь эти свойства:

-

Файл: src/lib.rs

-
pub struct ThreadPool;
-
-impl ThreadPool {
-    pub fn new(size: usize) -> ThreadPool {
-        ThreadPool
-    }
-}
-

Мы выбираем usize в качестве вида свойства size, потому что мы знаем, что отрицательное число потоков не имеет никакого смысла. Мы также знаем, что мы будем использовать число 4 в качестве количества элементов в собрания потоков, для чего предназначен вид usize, как обсуждалось в разделе "Целочисленные виды" главы 3.

-

Давайте проверим код ещё раз:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0599]: no method named `execute` found for struct `ThreadPool` in the current scope
-  --> src/main.rs:17:14
-   |
-17 |         pool.execute(|| {
-   |         -----^^^^^^^ method not found in `ThreadPool`
-
-For more information about this error, try `rustc --explain E0599`.
-error: could not compile `hello` (bin "hello") due to 1 previous error
-
-

Теперь мы ошибка возникает из-за того, что у нас нет способа execute в устройстве ThreadPool. Вспомните раздел "Создание конечного числа потоков", в котором мы решили, что наш объединениепотоков должен иметь внешнюю оболочку, похожий на thread::spawn. Кроме того, мы выполняем функцию execute, чтобы она принимала замыкание и передавала его свободному потоку из объединения для запуска.

-

Мы определим способ execute у ThreadPool, принимающий замыкание в качестве свойства. Вспомните из раздела "Перемещение захваченных значений из замыканий и особенности Fn" главы 13 сведения о том, что мы можем принимать замыкания в качестве свойств тремя различными особенностями: Fn , FnMut и FnOnce. Нам нужно решить, какой вид замыкания использовать здесь. Мы знаем, что в конечном счёте мы сделаем что-то похожее на выполнение встроенной библиотеки thread::spawn, поэтому мы можем посмотреть, какие ограничения накладывает на свой свойство ярлык функции thread::spawn. Документация показывает следующее:

-
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
-    where
-        F: FnOnce() -> T,
-        F: Send + 'static,
-        T: Send + 'static,
-

Свойство вида F - это как раз то, что нас важно; свойство вида T относится к возвращаемому значению и нам он не важен. Можно увидеть, что spawn использует FnOnce в качестве ограничения особенности у F. Возможно это как раз то, чего мы хотим, так как в конечном итоге мы передадим полученный в execute переменная в функцию spawn. Дополнительную уверенность в том, что FnOnce - это именно тот особенность, который мы хотим использовать, нам даётобстоятельство, что поток для выполнения запроса будет выполнять замыкание этого запроса только один раз, что соответствует части Once ("единожды") в названии особенности FnOnce.

-

Свойство вида F также имеет ограничение особенности Send и ограничение времени жизни 'static, которые полезны в нашей случаи: нам нужен Send для передачи замыкания из одного потока в другой и 'static, потому что мы не знаем, сколько времени поток будет выполняться. Давайте создадим способ execute для ThreadPool, который будет принимать обобщённый свойство вида F со следующими ограничениями:

-

Файл: src/lib.rs

-
pub struct ThreadPool;
-
-impl ThreadPool {
-    // --snip--
-    pub fn new(size: usize) -> ThreadPool {
-        ThreadPool
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-

Мы по-прежнему используем () после FnOnce потому что особенность FnOnce представляет замыкание, которое не принимает свойств и возвращает единичный вид (). Также как и при определении функций, вид возвращаемого значения в ярлыке может быть опущен, но даже если у нас нет свойств, нам все равно нужны скобки.

-

Опять же, это самая простая выполнение способа execute: она ничего не делает, мы просто пытаемся сделать код собираемым. Давайте проверим снова:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.24s
-
-

Сейчас мы получаем только предупреждения, что означает, что код собирается! Но обратите внимание, если вы попробуете cargo run и сделаете запрос в браузере, вы увидите ошибки в браузере, которые мы видели в начале главы. Наша библиотека на самом деле ещё не вызывает замыкание, переданное в execute!

-
-

Примечание: вы возможно слышали высказывание о языках со строгими сборщиками, таких как Haskell и Rust, которое звучит так: «Если код собирается, то он работает». Но это высказывание не всегда верно. Наш дело собирается, но абсолютно ничего не делает! Если бы мы создавали существующий, законченный дело, это был бы хороший мгновение начать писать состоящие из звеньев проверки, чтобы проверять, что код собирается и имеет желаемое поведение.

-
-

Проверка количества потоков в new

-

Мы ничего не делаем с свойствами new и execute. Давайте выполняем тела этих функций с нужным нам поведением. Для начала давайте подумаем о new. Ранее мы выбрали беззнаковый вид для свойства size, потому что объединениес отрицательным числом потоков не имеет смысла. Объединение с нулём потоков также не имеет смысла, однако ноль - это вполне допустимое значение usize. Мы добавим код для проверки того, что size больше нуля, прежде чем вернуть образец ThreadPool, и заставим программу паниковать, если она получит ноль, используя макрос assert!, как показано в приложении 20-13.

-

Файл: src/lib.rs

-
pub struct ThreadPool;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        ThreadPool
-    }
-
-    // --snip--
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-

Приложение 20-13: Выполнение ThreadPool::new с со сбоем завершениям работы, если size равен нулю

-

Мы добавили немного документации для нашей устройства ThreadPool с помощью примечаниев. Обратите внимание, что мы следовали хорошим применением документирования, добавив раздел, в котором указывается случаей, при которой функция может со сбоем завершаться, как это обсуждалось в главе 14. Попробуйте запустить cargo doc --open и кликнуть на устройство ThreadPool, чтобы увидеть как выглядит созданная документация для new!

-

Вместо добавления макроса assert!, как мы здесь сделали, мы могли бы преобразовать функцию new в функцию build таким образом, чтобы она возвращала Result , подобно тому, как мы делали в функции Config::new дела ввода/вывода в приложении 12-9. Но в данном случае мы решили, что попытка создания объединения потоков без указания хотя бы одного потока должна быть непоправимой ошибкой. Если вы чувствуете такое стремление, попробуйте написать функцию build с ярлыком ниже, для сравнения с функцией new:

-
pub fn build(size: usize) -> Result<ThreadPool, PoolCreationError> {
-

Создание места для хранения потоков

-

Теперь, имея возможность удостовериться, что количество потоков для хранения в объединении соответствует требованиям, мы можем создавать эти потоки и сохранять их в устройстве ThreadPool перед тем как возвратить её. Но как мы "сохраним" поток? Давайте ещё раз посмотрим на ярлык thread::spawn:

-
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
-    where
-        F: FnOnce() -> T,
-        F: Send + 'static,
-        T: Send + 'static,
-

Функция spawn возвращает вид JoinHandle<T>, где T является видом, который возвращает замыкание. Давайте попробуем использовать JoinHandle и посмотрим, что произойдёт. В нашем случае замыкания, которые мы передаём объединению потоков, будут обрабатывать соединение и не будут возвращать ничего, поэтому T будет единичным (unit) видом ().

-

Код в приложении 20-14 собирается, но пока не создаст ни одного потока. Мы изменили определение ThreadPool так, чтобы он содержал вектор образцов thread::JoinHandle<()>, объявляли вектор ёмкостью size, установили цикл for, который будет выполнять некоторый код для создания потоков, и вернули образец ThreadPool, содержащий их.

-

Файл: src/lib.rs

-
use std::thread;
-
-pub struct ThreadPool {
-    threads: Vec<thread::JoinHandle<()>>,
-}
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let mut threads = Vec::with_capacity(size);
-
-        for _ in 0..size {
-            // create some threads and store them in the vector
-        }
-
-        ThreadPool { threads }
-    }
-    // --snip--
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-

Приложение 20-14: Создание вектора в ThreadPool для хранения потоков

-

Мы включили std::thread в область видимости библиотечного ящика, потому что мы используем thread::JoinHandle в качестве вида элементов вектора в ThreadPool.

-

После получения правильного значения size, наш ThreadPool создаёт новый вектор, который может содержать size элементов. Функция with_capacity выполняет ту же задачу, что и Vec::new, но с важным отличием: она заранее выделяет необходимый объём памяти в векторе. Поскольку мы знаем, что нам нужно хранить size элементов в векторе, предварительное выделение памяти для этих элементов будет немного более эффективным, чем использование Vec::new, при котором размер вектора будет увеличиваться по мере вставки элементов.

-

Если вы снова запустите приказ cargo check, она должна завершиться успешно.

-

Устройства Worker, ответственная за отправку кода из ThreadPool в поток

-

Мы целенаправленно оставили примечание в цикле for в Приложении 20-14 по поводу создания потоков. Сейчас мы разберёмся, как на самом деле создаются потоки. Обычная библиотека предоставляет thread::spawn для создания потоков, причём thread::spawn ожидает получить некоторый код, который поток должен выполнить, как только он будет создан. Однако в нашем случае мы хотим создавать потоки и заставлять их ожидать код, который мы будем передавать им позже. Выполнение потоков в встроенной библиотеке не предоставляет никакого способа сделать это, мы должны выполнить это вручную.

-

Мы будем выполнить это поведение, добавив новую устройство данных между ThreadPool и потоками, которая будет управлять этим новым поведением. Мы назовём эту устройство Worker ("работник"), это общепринятое имя в выполнения объединений. Работник берёт код, который нужно выполнить, и запускает этот код внутри рабочего потока. Представьте людей, работающих на кухне ресторана: работники ожидают, пока не поступят заказы от клиентов, а затем они несут ответственность за принятие этих заказов и их выполнение.

-

Вместо того чтобы хранить вектор образцов JoinHandle<()> в объединении потоков, мы будем хранить образцы устройства Worker. Каждый Worker будет хранить один образец JoinHandle<()>. Затем мы выполняем способ у Worker, который будет принимать замыкание и отправлять его в существующий поток для выполнения. Для того чтобы мы могли различать работники в объединении при логировании или отладке, мы также присвоим каждому работнику id.

-

Вот как выглядит новая последовательность действий, которые будут происходить при создании ThreadPool. Мы выполняем код, который будет отправлять замыкание в поток, после того, как у нас будет Worker , заданный следующим образом:

-
    -
  1. Определим устройство Worker, которая содержит id и JoinHandle<()>.
  2. -
  3. Изменим ThreadPool, чтобы он содержал вектор образцов Worker.
  4. -
  5. Определим функцию Worker::new, которая принимает номер id и возвращает образец Worker, который содержит id и поток, порождённый с пустым замыканием.
  6. -
  7. В ThreadPool::new используем счётчик цикла for для создания id, создаём новый Worker с этим id и сохраняем образец "работника" в вектор.
  8. -
-

Если вы готовы принять вызов, попробуйте выполнить эти изменения самостоятельно, не глядя на код в приложении 20-15.

-

Готовы? Вот приложение 20-15 с одним из способов сделать указанные ранее изменения.

-

Файл: src/lib.rs

-
use std::thread;
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-}
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id));
-        }
-
-        ThreadPool { workers }
-    }
-    // --snip--
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize) -> Worker {
-        let thread = thread::spawn(|| {});
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-15: Изменение ThreadPool для хранения образцов Worker вместо непосредственного хранения потоков

-

Мы изменили название поля в ThreadPool с threads на workers, поскольку теперь оно содержит образцы Worker вместо образцов JoinHandle<()>. Мы используем счётчик в цикле for для передачи цифрового определителя в качестве переменной Worker::new, и сохраняем каждый новый Worker в векторе с именем workers.

-

Внешний код (вроде нашего сервера в src/bin/main.rs) не обязательно должен знать подробности выполнения, касающиеся использования устройства Worker внутри ThreadPool, поэтому мы делаем устройство Worker и её функцию new закрытыми. Функция Worker::new использует заданный нами id и сохраняет образец JoinHandle<()>, который создаётся при порождении нового потока с пустым замыканием.

-
-

Примечание: Если операционная система не может создать поток из-за нехватки системных ресурсов, thread::spawn со сбоем завершится. Это приведёт к со сбоемму завершению нашего сервера целиком, даже если некоторые потоки были созданы успешно. Для простоты будем считать, что нас устраивает такое поведение, но в существующей выполнения объединения потоков вы, вероятно, захотите использовать std::thread::Builder и его способ spawn, который вместо этого возвращает Result .

-
-

Этот код собирается и будет хранить количество образцов Worker, которое мы указали в качестве переменной функции ThreadPool::new. Но мы всё ещё не обрабатываем замыкание, которое мы получаем в способе execute. Давайте посмотрим, как это сделать далее.

-

Отправка запросов в потоки через потоки

-

Следующая неполадка, с которой мы будем бороться, заключается в том, что замыкания, переданные в thread::spawn абсолютно ничего не делают. Сейчас мы получаем замыкание, которое хотим выполнить, в способе execute. Но мы должны передать какое-то замыкание в способ thread::spawn, при создании каждого Worker во время создания ThreadPool.

-

Мы хотим, чтобы вновь созданные устройства Worker извлекали код для запуска из очереди, хранящейся в ThreadPool и отправляли этот код в свой поток для выполнения.

-

потоки (channels), простой способ связи между двумя потоками, с которыми мы познакомились в главе 16, кажется наилучше подойдут для этого сценария. Мы будем использовать поток в качестве очереди заданий, а приказ execute отправит задание из ThreadPool образцам Worker, которые будут отправлять задание в свой поток. Расчет таков:

-
    -
  1. ThreadPool создаст поток и будет хранить отправитель.
  2. -
  3. Каждый Worker будет хранить приёмник.
  4. -
  5. Мы создадим новую устройство Job, которая будет хранить замыкания, которые мы хотим отправить в поток.
  6. -
  7. Способ execute отправит задание, которое он хочет выполнить, в отправляющую сторону потока.
  8. -
  9. В своём потоке Worker будет замкнуто опрашивать принимающую сторону потока и выполнять замыкание любого задания, которое он получит.
  10. -
-

Давайте начнём с создания потока в ThreadPool::new и удержания отправляющей стороны в образце ThreadPool, как показано в приложении 20-16. В устройстве Job сейчас ничего не содержится, но это будет вид элемента, который мы отправляем в поток.

-

Файл: src/lib.rs

-
use std::{sync::mpsc, thread};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-struct Job;
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id));
-        }
-
-        ThreadPool { workers, sender }
-    }
-    // --snip--
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize) -> Worker {
-        let thread = thread::spawn(|| {});
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-16: Изменение ThreadPool для хранения отправляющей части потока, который отправляет образцы Job

-

В ThreadPool::new мы создаём наш новый поток и сохраняем в объединении его отправляющую сторону. Код успешно собирается.

-

Давайте попробуем передавать принимающую сторону потока каждому "работнику" (устройстве Worker), когда объединениепотоков создаёт поток. Мы знаем, что хотим использовать получающую часть потока в потоке, порождаемым "работником", поэтому мы будем ссылаться на свойство receiver в замыкании. Код 20-17 пока не собирается.

-

Файл: src/lib.rs

-
use std::{sync::mpsc, thread};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-struct Job;
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, receiver));
-        }
-
-        ThreadPool { workers, sender }
-    }
-    // --snip--
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-
-// --snip--
-
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: mpsc::Receiver<Job>) -> Worker {
-        let thread = thread::spawn(|| {
-            receiver;
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-17: Передача принимающей части потока "работникам"

-

Мы внесли несколько небольших и простых изменений: мы передаём принимающую часть потока в Worker::new, а затем используем его внутри замыкания.

-

При попытке проверить код, мы получаем ошибку:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0382]: use of moved value: `receiver`
-  --> src/lib.rs:26:42
-   |
-21 |         let (sender, receiver) = mpsc::channel();
-   |                      -------- move occurs because `receiver` has type `std::sync::mpsc::Receiver<Job>`, which does not implement the `Copy` trait
-...
-25 |         for id in 0..size {
-   |         ----------------- inside of this loop
-26 |             workers.push(Worker::new(id, receiver));
-   |                                          ^^^^^^^^ value moved here, in previous iteration of loop
-   |
-note: consider changing this parameter type in method `new` to borrow instead if owning the value isn't necessary
-  --> src/lib.rs:47:33
-   |
-47 |     fn new(id: usize, receiver: mpsc::Receiver<Job>) -> Worker {
-   |        --- in this method       ^^^^^^^^^^^^^^^^^^^ this parameter takes ownership of the value
-help: consider moving the expression out of the loop so it is only moved once
-   |
-25 ~         let mut value = Worker::new(id, receiver);
-26 ~         for id in 0..size {
-27 ~             workers.push(value);
-   |
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `hello` (lib) due to 1 previous error
-
-

Код пытается передать receiver нескольким образцам Worker. Это не сработает, поскольку, как вы можете помнить из главы 16: выполнение потока, которую предоставляет Ржавчина - несколько производителей, один потребитель. Это означает, что мы не можем просто клонировать принимающую сторону потока, чтобы исправить этот код. Кроме этого, мы не хотим отправлять одно и то же сообщение нескольким потребителям, поэтому нам нужен единый список сообщений для множества обработчиков, чтобы каждое сообщение обрабатывалось лишь один раз.

-

Кроме того, удаление задачи из очереди потока включает изменение receiver, поэтому потокам необходим безопасный способ делиться и изменять receiver, в противном случае мы можем получить условия гонки (как описано в главе 16).

-

Вспомните умные указатели, которые обсуждались в главе 16: чтобы делиться владением между несколькими потоками и разрешать потокам изменять значение, нам нужно использовать вид Arc<Mutex<T>>. Вид Arc позволит нескольким "работникам" владеть получателем (receiver), а Mutex заверяет что только один "работник" сможет получить задание (job) от получателя за раз. Приложение 20-18 показывает изменения, которые мы должны сделать.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-// --snip--
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-struct Job;
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    // --snip--
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-
-// --snip--
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        // --snip--
-        let thread = thread::spawn(|| {
-            receiver;
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-18. Совместное использование приёмника в "работниках" с применением Arc и Mutex

-

В ThreadPool::new мы помещаем принимающую сторону потока внутрь Arc и Mutex. Для каждого нового "работника" мы клонируем Arc, чтобы увеличить счётчик ссылок так, что "работники" могут разделять владение принимающей стороной потока.

-

С этими изменениями код собирается! Мы подбираемся к цели!

-

Выполнение способа execute

-

Давайте выполняем наконец способ execute у устройства ThreadPool. Мы также изменим вид Job со устройства на псевдоним вида для особенность-предмета. который будет содержать вид замыкания, принимаемый способом execute. Как описано в разделе "Создание родственных вида с помощью псевдонимов типа" главы 19, псевдонимы видов позволяют делать длинные виды короче, облегчая их использование. Посмотрите на приложение 20-19.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-// --snip--
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-// --snip--
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(|| {
-            receiver;
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-19: Создание псевдонима вида Job для указателя Box, содержащего каждое замыкание и затем отправляющее задание (job) в поток

-

После создания нового образца Job с замыканием, полученным в execute, мы посылаем его через отправляющий конец потока. На тот случай, если отправка не удастся, вызываем unwrap у send. Это может произойти, например, если мы остановим выполнение всех наших потоков, что означает, что принимающая сторона прекратила получать новые сообщения. На данный мгновение мы не можем остановить выполнение наших потоков: наши потоки будут исполняться до тех пор, пока существует объединение Причина, по которой мы используем unwrap, заключается в том, что, хотя мы знаем, что сбой не произойдёт, сборщик этого не знает.

-

Но мы ещё не закончили! В "работнике" (worker) наше замыкание, переданное в thread::spawn все ещё ссылается только на принимающую сторону потока. Вместо этого нам нужно, чтобы замыкание работало в бесконечном цикле, запрашивая задание у принимающей части потока и выполняя задание, когда оно принято. Давайте внесём изменения, показанные в приложении 20-20 внутри Worker::new.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-// --snip--
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-20: Получение и выполнение заданий в потоке "работника"

-

Здесь мы сначала вызываем lock у receiver, чтобы получить мьютекс, а затем вызываем unwrap, чтобы со сбоем завершить работу при любых ошибках. Захват блокировки может завершиться неудачей, если мьютекс находится в отравленном состоянии (poisoned state), что может произойти, если какой-то другой поток завершился со сбоем, удерживая блокировку, вместо снятия блокировки. В этой случаи вызвать unwrap для со сбоемго завершения потока вполне оправдано. Не стесняйтесь заменить unwrap на expect с сообщением об ошибке, которое имеет для вас значение.

-

Если мы получили блокировку мьютекса, мы вызываем recv, чтобы получить Job из потока. Последний вызов unwrap позволяет миновать любые ошибки, которые могут возникнуть, если поток, управляющий отправитель, прекратил исполняться, подобно тому, как способ send возвращает Err, если получатель не принимает сообщение.

-

Вызов recv - блокирующий, поэтому пока задач нет, текущий поток будет ждать, пока задача не появится. Mutex<T> заверяет, что только один поток Worker за раз попытается запросить задачу.

-

Наш объединениепотоков теперь находится в рабочем состоянии! Выполните cargo run и сделайте несколько запросов:

- -
$ cargo run
-   Compiling hello v0.1.0 (file:///projects/hello)
-warning: field is never read: `workers`
- --> src/lib.rs:7:5
-  |
-7 |     workers: Vec<Worker>,
-  |     ^^^^^^^^^^^^^^^^^^^^
-  |
-  = note: `#[warn(dead_code)]` on by default
-
-warning: field is never read: `id`
-  --> src/lib.rs:48:5
-   |
-48 |     id: usize,
-   |     ^^^^^^^^^
-
-warning: field is never read: `thread`
-  --> src/lib.rs:49:5
-   |
-49 |     thread: thread::JoinHandle<()>,
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-warning: `hello` (lib) generated 3 warnings
-    Finished dev [unoptimized + debuginfo] target(s) in 1.40s
-     Running `target/debug/hello`
-Worker 0 got a job; executing.
-Worker 2 got a job; executing.
-Worker 1 got a job; executing.
-Worker 3 got a job; executing.
-Worker 0 got a job; executing.
-Worker 2 got a job; executing.
-Worker 1 got a job; executing.
-Worker 3 got a job; executing.
-Worker 0 got a job; executing.
-Worker 2 got a job; executing.
-
-

Успех! Теперь у нас есть объединениепотоков, который обрабатывает соединения не согласованно. Никогда не создаётся более четырёх потоков, поэтому наша система не будет перегружена, если сервер получит много запросов. Если мы отправим запрос ресурса /sleep, сервер сможет обслуживать другие запросы, обрабатывая их в другом потоке.

-
-

Примечание: если вы запросите /sleep в нескольких окнах браузера одновременно, они могут загружаться по одному, с интервалами в 5 секунд. Некоторые веб-браузеры выполняют несколько образцов одного и того же запроса последовательно из-за кэширования. Такое ограничение не связано с работой нашего веб-сервера.

-
-

После изучения цикла while let в главе 18 вы можете удивиться, почему мы не написали код рабочего потока (worker thread), как показано в приложении 20-22.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-// --snip--
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || {
-            while let Ok(job) = receiver.lock().unwrap().recv() {
-                println!("Worker {id} got a job; executing.");
-
-                job();
-            }
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-22: Иная выполнение Worker::new с использованием while let

-

Этот код собирается и запускается, но не даёт желаемого поведения: медленный запрос всё равно приведёт к тому, что другие запросы будут ждать обработки. Причина здесь несколько тоньше: устройства Mutex не имеет открытого способа unlock, так как владение блокировкой основано на времени жизни MutexGuard<T> внутри LockResult<MutexGuard<T>>, которое возвращает способ lock. Во время сборки анализатор заимствований может проследить за выполнением правила, согласно которому к ресурсу, охраняемому Mutex, нельзя получить доступ пока мы удерживаем блокировку. Однако в этой выполнение мы также можем получить случай, когда блокировка будет удерживаться дольше, чем предполагалось, если мы не будем внимательно учитывать время жизни MutexGuard<T>.

-

Код в приложении 20-20, использующий let job = receiver.lock().unwrap().recv().unwrap(); работает, потому что при использовании let любые промежуточные значения, используемые в выражении справа от знака равенства, немедленно уничтожаются после завершения указания let. Однако while letif let и match) не удаляет временные значения до конца связанного раздела. Таким образом, в приложении 20-21 блокировка не снимается в течение всего времени вызова job(), что означает, что другие работники не могут получать задания.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/ch20-03-graceful-shutdown-and-cleanup.html b/rustbook-ru/book/ch20-03-graceful-shutdown-and-cleanup.html deleted file mode 100644 index ea00cfe2f..000000000 --- a/rustbook-ru/book/ch20-03-graceful-shutdown-and-cleanup.html +++ /dev/null @@ -1,1020 +0,0 @@ - - - - - - Мягкое завершение работы и очистка - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Мягкое завершение работы и очистка

-

Приложение 20-20 не согласованно отвечает на запросы с помощью использования объединения потоков, как мы и хотели. Мы получаем некоторые предупреждения про workers, id и поля thread, которые мы не используем напрямую, что напоминает нам о том, что мы не освобождаем все ресурсы. Когда мы используем менее элегантный способ остановки основного потока клавишной сочетанием ctrl-c, все остальные потоки также немедленно останавливаются, даже если они находятся в середине обработки запроса.

-

Далее, выполняем особенность Drop для вызова join у каждого потока в объединении, чтобы они могли завершить запросы, над которыми они работают, перед закрытием. Затем мы выполняем способ сообщить потокам, что они должны перестать принимать новые запросы и завершить работу. Чтобы увидеть этот код в действии, мы изменим наш сервер так, чтобы он принимал только два запроса, после чего правильно завершал работу объединения потоков.

-

Выполнение особенности Drop для ThreadPool

-

Давайте начнём с выполнения Drop у нашего объединения потоков. Когда объединениеудаляется, все наши потоки должны объединиться (join), чтобы убедиться, что они завершают свою работу. В приложении 20-22 показана первая попытка выполнения Drop, код пока не будет работать.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            worker.thread.join().unwrap();
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-22: Присоединение (Joining) каждого потока, когда объединениепотоков выходит из области видимости

-

Сначала мы пройдёмся по каждому worker из объединения потоков. Для этого мы используем &mut с self, потому что нам нужно иметь возможность изменять worker. Для каждого обработчика мы выводим сообщение о том, что он завершает работу, а затем вызываем join у потока этого обработчика. Для случаев, когда вызов join не удался, мы используем unwrap, чтобы заставить Ржавчина запаниковать и перейти в режим грубого завершения работы.

-

Ошибка получаемая при сборки этого кода:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0507]: cannot move out of `worker.thread` which is behind a mutable reference
-  --> src/lib.rs:52:13
-   |
-52 |             worker.thread.join().unwrap();
-   |             ^^^^^^^^^^^^^ ------ `worker.thread` moved due to this method call
-   |             |
-   |             move occurs because `worker.thread` has type `JoinHandle<()>`, which does not implement the `Copy` trait
-   |
-note: `JoinHandle::<T>::join` takes ownership of the receiver `self`, which moves `worker.thread`
-  --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/thread/mod.rs:1718:17
-
-For more information about this error, try `rustc --explain E0507`.
-error: could not compile `hello` (lib) due to 1 previous error
-
-

Ошибка говорит нам, что мы не можем вызвать join, потому что у нас есть только изменяемое заимствование каждого worker, а join забирает во владение свой переменная. Чтобы решить эту неполадку, нам нужно извлечь поток из образца Worker, который владеет thread, чтобы join мог его использовать. Мы сделали это в приложении 17-15: теперь, когда Worker хранит в себе Option<thread::JoinHandle<()>>, мы можем воспользоваться способом take у Option, чтобы извлечь значение из исхода Some, тем самым оставляя на его месте None. Другими словами, в рабочем состоянии Worker будет использовать исход Some содержащий thread, а когда мы захотим завершить Worker, мы заменим Some на None, чтобы у Worker не было потока для работы.

-

Итак, мы хотим обновить объявление Worker следующим образом:

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            worker.thread.join().unwrap();
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker { id, thread }
-    }
-}
-

Теперь давайте опираться на сборщик, чтобы найти другие места, которые нужно изменить. Проверяя код, мы получаем две ошибки:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0599]: no method named `join` found for enum `Option` in the current scope
-  --> src/lib.rs:52:27
-   |
-52 |             worker.thread.join().unwrap();
-   |                           ^^^^ method not found in `Option<JoinHandle<()>>`
-   |
-note: the method `join` exists on the type `JoinHandle<()>`
-  --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/thread/mod.rs:1718:5
-help: consider using `Option::expect` to unwrap the `JoinHandle<()>` value, panicking if the value is an `Option::None`
-   |
-52 |             worker.thread.expect("REASON").join().unwrap();
-   |                          +++++++++++++++++
-
-error[E0308]: mismatched types
-  --> src/lib.rs:72:22
-   |
-72 |         Worker { id, thread }
-   |                      ^^^^^^ expected `Option<JoinHandle<()>>`, found `JoinHandle<_>`
-   |
-   = note: expected enum `Option<JoinHandle<()>>`
-            found struct `JoinHandle<_>`
-help: try wrapping the expression in `Some`
-   |
-72 |         Worker { id, thread: Some(thread) }
-   |                      +++++++++++++      +
-
-Some errors have detailed explanations: E0308, E0599.
-For more information about an error, try `rustc --explain E0308`.
-error: could not compile `hello` (lib) due to 2 previous errors
-
-

Давайте обратимся ко второй ошибке, которая указывает на код в конце Worker::new; нам нужно обернуть значение thread в исход Some при создании нового Worker. Внесите следующие изменения, чтобы исправить эту ошибку:

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            worker.thread.join().unwrap();
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        // --snip--
-
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker {
-            id,
-            thread: Some(thread),
-        }
-    }
-}
-

Первая ошибка находится в нашей выполнения Drop. Ранее мы упоминали, что намеревались вызвать take для свойства Option, чтобы забрать thread из этапа worker. Следующие изменения делают это:

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            if let Some(thread) = worker.thread.take() {
-                thread.join().unwrap();
-            }
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker {
-            id,
-            thread: Some(thread),
-        }
-    }
-}
-

Как уже говорилось в главе 17, способ take у вида Option забирает значение из исхода Some и оставляет исход None в этом месте. Мы используем if let, чтобы разъединять Some и получить поток; затем вызываем join у потока. Если поток "работника" уже None, мы знаем, что этот "работник" уже очистил свой поток, поэтому в этом случае ничего не происходит.

-

Тревожное оповещение потокам прекратить прослушивание получения задач

-

Теперь, после всех внесённых нами изменений, код собирается без каких-либо предупреждений. Но плохая новость в том, что этот код всё ещё не работает так, как мы этого хотим. Причина заключается в логике замыканий, запускаемых потоками образцов Worker: в данный мгновение мы вызываем join, но это не приводит к завершению потоков, так как они находятся в бесконечном цикле, ожидая новую задачу. Если мы попытаемся удалить ThreadPool в текущей выполнения drop, основной поток навсегда заблокируется в ожидании завершения первого потока из объединения .

-

Чтобы решить эту неполадку, нам нужно будет изменить выполнение drop в ThreadPool, а затем внести изменения в цикл Worker .

-

Во-первых, изменим выполнение drop ThreadPool таким образом, чтобы явно удалять sender перед тем, как начнём ожидать завершения потоков. В приложении 20-23 показаны изменения в ThreadPool для явного удаления sender . Мы используем ту же технику Option и take, что и с потоком, чтобы переместить sender из ThreadPool:

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: Option<mpsc::Sender<Job>>,
-}
-// --snip--
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        // --snip--
-
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool {
-            workers,
-            sender: Some(sender),
-        }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.as_ref().unwrap().send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        drop(self.sender.take());
-
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            if let Some(thread) = worker.thread.take() {
-                thread.join().unwrap();
-            }
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker {
-            id,
-            thread: Some(thread),
-        }
-    }
-}
-

Приложение 20-23. Явное удаление sender перед ожиданием завершения рабочих потоков

-

Удаление sender закрывает поток, что указывает на то, что сообщения больше не будут отправляться. Когда это произойдёт, все вызовы recv, выполняемые рабочими этапами в бесконечном цикле, вернут ошибку. В приложении 20-24 мы меняем цикл Worker для правильного выхода из него в этом случае, что означает, что потоки завершатся, когда выполнение drop ThreadPool вызовет для них join.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: Option<mpsc::Sender<Job>>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool {
-            workers,
-            sender: Some(sender),
-        }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.as_ref().unwrap().send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        drop(self.sender.take());
-
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            if let Some(thread) = worker.thread.take() {
-                thread.join().unwrap();
-            }
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let message = receiver.lock().unwrap().recv();
-
-            match message {
-                Ok(job) => {
-                    println!("Worker {id} got a job; executing.");
-
-                    job();
-                }
-                Err(_) => {
-                    println!("Worker {id} disconnected; shutting down.");
-                    break;
-                }
-            }
-        });
-
-        Worker {
-            id,
-            thread: Some(thread),
-        }
-    }
-}
-

Приложение 20-24: Явный выход из цикла, когда recv возвращает ошибку

-

Чтобы увидеть этот код в действии, давайте изменим main, чтобы принимать только два запроса, прежде чем правильно завершить работу сервера как показано в приложении 20-25.

-

Файл: src/main.rs

-
use hello::ThreadPool;
-use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-    let pool = ThreadPool::new(4);
-
-    for stream in listener.incoming().take(2) {
-        let stream = stream.unwrap();
-
-        pool.execute(|| {
-            handle_connection(stream);
-        });
-    }
-
-    println!("Shutting down.");
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Код 20-25. Выключение сервера после обслуживания двух запросов с помощью выхода из цикла

-

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

-

Способ take определён в особенности Iterator и ограничивает повторение самое большее первыми двумя элементами. ThreadPool выйдет из области видимости в конце main и будет запущена его выполнение drop.

-

Запустите сервер с cargo run и сделайте три запроса. Третий запрос должен выдать ошибку и в окне вызова вы должны увидеть вывод, подобный следующему:

- -
$ cargo run
-   Compiling hello v0.1.0 (file:///projects/hello)
-    Finished dev [unoptimized + debuginfo] target(s) in 1.0s
-     Running `target/debug/hello`
-Worker 0 got a job; executing.
-Shutting down.
-Shutting down worker 0
-Worker 3 got a job; executing.
-Worker 1 disconnected; shutting down.
-Worker 2 disconnected; shutting down.
-Worker 3 disconnected; shutting down.
-Worker 0 disconnected; shutting down.
-Shutting down worker 1
-Shutting down worker 2
-Shutting down worker 3
-
-

Вы возможно увидите другой порядок рабочих потоков и напечатанных сообщений. Мы можем увидеть, как этот код работает по сообщениям: "работники" номер 0 и 3 получили первые два запроса. Сервер прекратил принимать соединения после второго подключения, а выполнение Drop для ThreadPool начинает выполняется ещё тогда, когда как работник 3 даже не приступил к выполнению своей работы. Удаление sender отключает все рабочие потоки от потока и просит их завершить работу. Каждый рабочий поток при отключении печатает сообщение, а затем объединениепотоков вызывает join, чтобы дождаться, пока каждый из рабочих потоков завершится.

-

Обратите внимание на один важная особенность этого определенного запуска: ThreadPool удалил sender, и прежде чем какой-либо из работников получил ошибку, мы попытались присоединить (join) рабочий поток с номером 0. Рабочий поток 0 ещё не получил ошибку от recv, поэтому основной поток заблокировался, ожидания завершения потока работника 0. Тем временем, работник 3 получил задание, а затем каждый из рабочих потоков получил ошибку. Когда рабочий поток 0 завершился, основной поток ждал окончания завершения выполнения остальных рабочих потоков. В этот мгновение все они вышли из своих циклов и остановились.

-

Примите поздравления! Теперь мы завершили дело; у нас есть основной веб-сервер, использующий объединениепотоков для не согласованных ответов. Мы можем выполнить правильное завершение работы сервера, очистив все потоки в объединении.

-

Вот полный код для справки:

-

Файл: src/main.rs

-
use hello::ThreadPool;
-use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-    let pool = ThreadPool::new(4);
-
-    for stream in listener.incoming().take(2) {
-        let stream = stream.unwrap();
-
-        pool.execute(|| {
-            handle_connection(stream);
-        });
-    }
-
-    println!("Shutting down.");
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: Option<mpsc::Sender<Job>>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool {
-            workers,
-            sender: Some(sender),
-        }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.as_ref().unwrap().send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        drop(self.sender.take());
-
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            if let Some(thread) = worker.thread.take() {
-                thread.join().unwrap();
-            }
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let message = receiver.lock().unwrap().recv();
-
-            match message {
-                Ok(job) => {
-                    println!("Worker {id} got a job; executing.");
-
-                    job();
-                }
-                Err(_) => {
-                    println!("Worker {id} disconnected; shutting down.");
-                    break;
-                }
-            }
-        });
-
-        Worker {
-            id,
-            thread: Some(thread),
-        }
-    }
-}
-

Мы могли бы сделать ещё больше! Если вы хотите продолжить совершенствование этого дела, вот несколько мыслей:

-
    -
  • Добавьте больше документации в ThreadPool и его открытые способы.
  • -
  • Добавьте проверки для возможностей, исполняемого библиотекой.
  • -
  • Замените вызовы unwrap на более устойчивую обработку ошибок.
  • -
  • Используйте ThreadPool для выполнения некоторых других задач, помимо обслуживания веб-запросов.
  • -
  • На crates.io найдите ящик для работы с объединениями потоков и на его основе выполните подобный веб-сервер. Затем сравните его API и надёжность с выполненным нами объединением потоков.
  • -
-

Итоги

-

Отличная работа! Вы сделали это к концу книги! Мы хотим поблагодарить вас за то, что присоединились к нам в этом путешествии по языку Rust. Теперь вы готовы выполнить свои собственные дела на Ржавчина и помочь с делами другим людям. Имейте в виду, что сообщество Ржавчина разработчиков довольно гостеприимно, они с удовольствием постараются помочь вам с любыми трудностями, с которыми вы можете столкнуться в своём путешествии по Rust.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/clipboard.min.js b/rustbook-ru/book/clipboard.min.js deleted file mode 100644 index 02c549e35..000000000 --- a/rustbook-ru/book/clipboard.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * clipboard.js v2.0.4 - * https://zenorocha.github.io/clipboard.js - * - * Licensed MIT © Zeno Rocha - */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(n){var o={};function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n .hljs { - color: var(--links); -} - -/* - body-container is necessary because mobile browsers don't seem to like - overflow-x on the body tag when there is a tag. -*/ -#body-container { - /* - This is used when the sidebar pushes the body content off the side of - the screen on small screens. Without it, dragging on mobile Safari - will want to reposition the viewport in a weird way. - */ - overflow-x: clip; -} - -/* Menu Bar */ - -#menu-bar, -#menu-bar-hover-placeholder { - z-index: 101; - margin: auto calc(0px - var(--page-padding)); -} -#menu-bar { - position: relative; - display: flex; - flex-wrap: wrap; - background-color: var(--bg); - border-block-end-color: var(--bg); - border-block-end-width: 1px; - border-block-end-style: solid; -} -#menu-bar.sticky, -.js #menu-bar-hover-placeholder:hover + #menu-bar, -.js #menu-bar:hover, -.js.sidebar-visible #menu-bar { - position: -webkit-sticky; - position: sticky; - top: 0 !important; -} -#menu-bar-hover-placeholder { - position: sticky; - position: -webkit-sticky; - top: 0; - height: var(--menu-bar-height); -} -#menu-bar.bordered { - border-block-end-color: var(--table-border-color); -} -#menu-bar i, #menu-bar .icon-button { - position: relative; - padding: 0 8px; - z-index: 10; - line-height: var(--menu-bar-height); - cursor: pointer; - transition: color 0.5s; -} -@media only screen and (max-width: 420px) { - #menu-bar i, #menu-bar .icon-button { - padding: 0 5px; - } -} - -.icon-button { - border: none; - background: none; - padding: 0; - color: inherit; -} -.icon-button i { - margin: 0; -} - -.right-buttons { - margin: 0 15px; -} -.right-buttons a { - text-decoration: none; -} - -.left-buttons { - display: flex; - margin: 0 5px; -} -.no-js .left-buttons button { - display: none; -} - -.menu-title { - display: inline-block; - font-weight: 200; - font-size: 2.4rem; - line-height: var(--menu-bar-height); - text-align: center; - margin: 0; - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.js .menu-title { - cursor: pointer; -} - -.menu-bar, -.menu-bar:visited, -.nav-chapters, -.nav-chapters:visited, -.mobile-nav-chapters, -.mobile-nav-chapters:visited, -.menu-bar .icon-button, -.menu-bar a i { - color: var(--icons); -} - -.menu-bar i:hover, -.menu-bar .icon-button:hover, -.nav-chapters:hover, -.mobile-nav-chapters i:hover { - color: var(--icons-hover); -} - -/* Nav Icons */ - -.nav-chapters { - font-size: 2.5em; - text-align: center; - text-decoration: none; - - position: fixed; - top: 0; - bottom: 0; - margin: 0; - max-width: 150px; - min-width: 90px; - - display: flex; - justify-content: center; - align-content: center; - flex-direction: column; - - transition: color 0.5s, background-color 0.5s; -} - -.nav-chapters:hover { - text-decoration: none; - background-color: var(--theme-hover); - transition: background-color 0.15s, color 0.15s; -} - -.nav-wrapper { - margin-block-start: 50px; - display: none; -} - -.mobile-nav-chapters { - font-size: 2.5em; - text-align: center; - text-decoration: none; - width: 90px; - border-radius: 5px; - background-color: var(--sidebar-bg); -} - -/* Only Firefox supports flow-relative values */ -.previous { float: left; } -[dir=rtl] .previous { float: right; } - -/* Only Firefox supports flow-relative values */ -.next { - float: right; - right: var(--page-padding); -} -[dir=rtl] .next { - float: left; - right: unset; - left: var(--page-padding); -} - -/* Use the correct buttons for RTL layouts*/ -[dir=rtl] .previous i.fa-angle-left:before {content:"\f105";} -[dir=rtl] .next i.fa-angle-right:before { content:"\f104"; } - -@media only screen and (max-width: 1080px) { - .nav-wide-wrapper { display: none; } - .nav-wrapper { display: block; } -} - -/* sidebar-visible */ -@media only screen and (max-width: 1380px) { - #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wide-wrapper { display: none; } - #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wrapper { display: block; } -} - -/* Inline code */ - -:not(pre) > .hljs { - display: inline; - padding: 0.1em 0.3em; - border-radius: 3px; -} - -:not(pre):not(a) > .hljs { - color: var(--inline-code-color); - overflow-x: initial; -} - -a:hover > .hljs { - text-decoration: underline; -} - -pre { - position: relative; -} -pre > .buttons { - position: absolute; - z-index: 100; - right: 0px; - top: 2px; - margin: 0px; - padding: 2px 0px; - - color: var(--sidebar-fg); - cursor: pointer; - visibility: hidden; - opacity: 0; - transition: visibility 0.1s linear, opacity 0.1s linear; -} -pre:hover > .buttons { - visibility: visible; - opacity: 1 -} -pre > .buttons :hover { - color: var(--sidebar-active); - border-color: var(--icons-hover); - background-color: var(--theme-hover); -} -pre > .buttons i { - margin-inline-start: 8px; -} -pre > .buttons button { - cursor: inherit; - margin: 0px 5px; - padding: 3px 5px; - font-size: 14px; - - border-style: solid; - border-width: 1px; - border-radius: 4px; - border-color: var(--icons); - background-color: var(--theme-popup-bg); - transition: 100ms; - transition-property: color,border-color,background-color; - color: var(--icons); -} -@media (pointer: coarse) { - pre > .buttons button { - /* On mobile, make it easier to tap buttons. */ - padding: 0.3rem 1rem; - } - - .sidebar-resize-indicator { - /* Hide resize indicator on devices with limited accuracy */ - display: none; - } -} -pre > code { - display: block; - padding: 1rem; -} - -/* FIXME: ACE editors overlap their buttons because ACE does absolute - positioning within the code block which breaks padding. The only solution I - can think of is to move the padding to the outer pre tag (or insert a div - wrapper), but that would require fixing a whole bunch of CSS rules. -*/ -.hljs.ace_editor { - padding: 0rem 0rem; -} - -pre > .result { - margin-block-start: 10px; -} - -/* Search */ - -#searchresults a { - text-decoration: none; -} - -mark { - border-radius: 2px; - padding-block-start: 0; - padding-block-end: 1px; - padding-inline-start: 3px; - padding-inline-end: 3px; - margin-block-start: 0; - margin-block-end: -1px; - margin-inline-start: -3px; - margin-inline-end: -3px; - background-color: var(--search-mark-bg); - transition: background-color 300ms linear; - cursor: pointer; -} - -mark.fade-out { - background-color: rgba(0,0,0,0) !important; - cursor: auto; -} - -.searchbar-outer { - margin-inline-start: auto; - margin-inline-end: auto; - max-width: var(--content-max-width); -} - -#searchbar { - width: 100%; - margin-block-start: 5px; - margin-block-end: 0; - margin-inline-start: auto; - margin-inline-end: auto; - padding: 10px 16px; - transition: box-shadow 300ms ease-in-out; - border: 1px solid var(--searchbar-border-color); - border-radius: 3px; - background-color: var(--searchbar-bg); - color: var(--searchbar-fg); -} -#searchbar:focus, -#searchbar.active { - box-shadow: 0 0 3px var(--searchbar-shadow-color); -} - -.searchresults-header { - font-weight: bold; - font-size: 1em; - padding-block-start: 18px; - padding-block-end: 0; - padding-inline-start: 5px; - padding-inline-end: 0; - color: var(--searchresults-header-fg); -} - -.searchresults-outer { - margin-inline-start: auto; - margin-inline-end: auto; - max-width: var(--content-max-width); - border-block-end: 1px dashed var(--searchresults-border-color); -} - -ul#searchresults { - list-style: none; - padding-inline-start: 20px; -} -ul#searchresults li { - margin: 10px 0px; - padding: 2px; - border-radius: 2px; -} -ul#searchresults li.focus { - background-color: var(--searchresults-li-bg); -} -ul#searchresults span.teaser { - display: block; - clear: both; - margin-block-start: 5px; - margin-block-end: 0; - margin-inline-start: 20px; - margin-inline-end: 0; - font-size: 0.8em; -} -ul#searchresults span.teaser em { - font-weight: bold; - font-style: normal; -} - -/* Sidebar */ - -.sidebar { - position: fixed; - left: 0; - top: 0; - bottom: 0; - width: var(--sidebar-width); - font-size: 0.875em; - box-sizing: border-box; - -webkit-overflow-scrolling: touch; - overscroll-behavior-y: contain; - background-color: var(--sidebar-bg); - color: var(--sidebar-fg); -} -[dir=rtl] .sidebar { left: unset; right: 0; } -.sidebar-resizing { - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; -} -.no-js .sidebar, -.js:not(.sidebar-resizing) .sidebar { - transition: transform 0.3s; /* Animation: slide away */ -} -.sidebar code { - line-height: 2em; -} -.sidebar .sidebar-scrollbox { - overflow-y: auto; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - padding: 10px 10px; -} -.sidebar .sidebar-resize-handle { - position: absolute; - cursor: col-resize; - width: 0; - right: calc(var(--sidebar-resize-indicator-width) * -1); - top: 0; - bottom: 0; - display: flex; - align-items: center; -} - -.sidebar-resize-handle .sidebar-resize-indicator { - width: 100%; - height: 12px; - background-color: var(--icons); - margin-inline-start: var(--sidebar-resize-indicator-space); -} - -[dir=rtl] .sidebar .sidebar-resize-handle { - left: calc(var(--sidebar-resize-indicator-width) * -1); - right: unset; -} -.js .sidebar .sidebar-resize-handle { - cursor: col-resize; - width: calc(var(--sidebar-resize-indicator-width) - var(--sidebar-resize-indicator-space)); -} -/* sidebar-hidden */ -#sidebar-toggle-anchor:not(:checked) ~ .sidebar { - transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width))); - z-index: -1; -} -[dir=rtl] #sidebar-toggle-anchor:not(:checked) ~ .sidebar { - transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width))); -} -.sidebar::-webkit-scrollbar { - background: var(--sidebar-bg); -} -.sidebar::-webkit-scrollbar-thumb { - background: var(--scrollbar); -} - -/* sidebar-visible */ -#sidebar-toggle-anchor:checked ~ .page-wrapper { - transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width))); -} -[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { - transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width))); -} -@media only screen and (min-width: 620px) { - #sidebar-toggle-anchor:checked ~ .page-wrapper { - transform: none; - margin-inline-start: calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width)); - } - [dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { - transform: none; - } -} - -.chapter { - list-style: none outside none; - padding-inline-start: 0; - line-height: 2.2em; -} - -.chapter ol { - width: 100%; -} - -.chapter li { - display: flex; - color: var(--sidebar-non-existant); -} -.chapter li a { - display: block; - padding: 0; - text-decoration: none; - color: var(--sidebar-fg); -} - -.chapter li a:hover { - color: var(--sidebar-active); -} - -.chapter li a.active { - color: var(--sidebar-active); -} - -.chapter li > a.toggle { - cursor: pointer; - display: block; - margin-inline-start: auto; - padding: 0 10px; - user-select: none; - opacity: 0.68; -} - -.chapter li > a.toggle div { - transition: transform 0.5s; -} - -/* collapse the section */ -.chapter li:not(.expanded) + li > ol { - display: none; -} - -.chapter li.chapter-item { - line-height: 1.5em; - margin-block-start: 0.6em; -} - -.chapter li.expanded > a.toggle div { - transform: rotate(90deg); -} - -.spacer { - width: 100%; - height: 3px; - margin: 5px 0px; -} -.chapter .spacer { - background-color: var(--sidebar-spacer); -} - -@media (-moz-touch-enabled: 1), (pointer: coarse) { - .chapter li a { padding: 5px 0; } - .spacer { margin: 10px 0; } -} - -.section { - list-style: none outside none; - padding-inline-start: 20px; - line-height: 1.9em; -} - -/* Theme Menu Popup */ - -.theme-popup { - position: absolute; - left: 10px; - top: var(--menu-bar-height); - z-index: 1000; - border-radius: 4px; - font-size: 0.7em; - color: var(--fg); - background: var(--theme-popup-bg); - border: 1px solid var(--theme-popup-border); - margin: 0; - padding: 0; - list-style: none; - display: none; - /* Don't let the children's background extend past the rounded corners. */ - overflow: hidden; -} -[dir=rtl] .theme-popup { left: unset; right: 10px; } -.theme-popup .default { - color: var(--icons); -} -.theme-popup .theme { - width: 100%; - border: 0; - margin: 0; - padding: 2px 20px; - line-height: 25px; - white-space: nowrap; - text-align: start; - cursor: pointer; - color: inherit; - background: inherit; - font-size: inherit; -} -.theme-popup .theme:hover { - background-color: var(--theme-hover); -} - -.theme-selected::before { - display: inline-block; - content: "✓"; - margin-inline-start: -14px; - width: 14px; -} diff --git a/rustbook-ru/book/css/general.css b/rustbook-ru/book/css/general.css deleted file mode 100644 index 7670b087d..000000000 --- a/rustbook-ru/book/css/general.css +++ /dev/null @@ -1,232 +0,0 @@ -/* Base styles and content styles */ - -:root { - /* Browser default font-size is 16px, this way 1 rem = 10px */ - font-size: 62.5%; - color-scheme: var(--color-scheme); -} - -html { - font-family: "Open Sans", sans-serif; - color: var(--fg); - background-color: var(--bg); - text-size-adjust: none; - -webkit-text-size-adjust: none; -} - -body { - margin: 0; - font-size: 1.6rem; - overflow-x: hidden; -} - -code { - font-family: var(--mono-font) !important; - font-size: var(--code-font-size); - direction: ltr !important; -} - -/* make long words/inline code not x overflow */ -main { - overflow-wrap: break-word; -} - -/* make wide tables scroll if they overflow */ -.table-wrapper { - overflow-x: auto; -} - -/* Don't change font size in headers. */ -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - font-size: unset; -} - -.left { float: left; } -.right { float: right; } -.boring { opacity: 0.6; } -.hide-boring .boring { display: none; } -.hidden { display: none !important; } - -h2, h3 { margin-block-start: 2.5em; } -h4, h5 { margin-block-start: 2em; } - -.header + .header h3, -.header + .header h4, -.header + .header h5 { - margin-block-start: 1em; -} - -h1:target::before, -h2:target::before, -h3:target::before, -h4:target::before, -h5:target::before, -h6:target::before { - display: inline-block; - content: "»"; - margin-inline-start: -30px; - width: 30px; -} - -/* This is broken on Safari as of version 14, but is fixed - in Safari Technology Preview 117 which I think will be Safari 14.2. - https://bugs.webkit.org/show_bug.cgi?id=218076 -*/ -:target { - /* Safari does not support logical properties */ - scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); -} - -.page { - outline: 0; - padding: 0 var(--page-padding); - margin-block-start: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ -} -.page-wrapper { - box-sizing: border-box; - background-color: var(--bg); -} -.no-js .page-wrapper, -.js:not(.sidebar-resizing) .page-wrapper { - transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ -} -[dir=rtl] .js:not(.sidebar-resizing) .page-wrapper { - transition: margin-right 0.3s ease, transform 0.3s ease; /* Animation: slide away */ -} - -.content { - overflow-y: auto; - padding: 0 5px 50px 5px; -} -.content main { - margin-inline-start: auto; - margin-inline-end: auto; - max-width: var(--content-max-width); -} -.content p { line-height: 1.45em; } -.content ol { line-height: 1.45em; } -.content ul { line-height: 1.45em; } -.content a { text-decoration: none; } -.content a:hover { text-decoration: underline; } -.content img, .content video { max-width: 100%; } -.content .header:link, -.content .header:visited { - color: var(--fg); -} -.content .header:link, -.content .header:visited:hover { - text-decoration: none; -} - -table { - margin: 0 auto; - border-collapse: collapse; -} -table td { - padding: 3px 20px; - border: 1px var(--table-border-color) solid; -} -table thead { - background: var(--table-header-bg); -} -table thead td { - font-weight: 700; - border: none; -} -table thead th { - padding: 3px 20px; -} -table thead tr { - border: 1px var(--table-header-bg) solid; -} -/* Alternate background colors for rows */ -table tbody tr:nth-child(2n) { - background: var(--table-alternate-bg); -} - - -blockquote { - margin: 20px 0; - padding: 0 20px; - color: var(--fg); - background-color: var(--quote-bg); - border-block-start: .1em solid var(--quote-border); - border-block-end: .1em solid var(--quote-border); -} - -.warning { - margin: 20px; - padding: 0 20px; - border-inline-start: 2px solid var(--warning-border); -} - -.warning:before { - position: absolute; - width: 3rem; - height: 3rem; - margin-inline-start: calc(-1.5rem - 21px); - content: "ⓘ"; - text-align: center; - background-color: var(--bg); - color: var(--warning-border); - font-weight: bold; - font-size: 2rem; -} - -blockquote .warning:before { - background-color: var(--quote-bg); -} - -kbd { - background-color: var(--table-border-color); - border-radius: 4px; - border: solid 1px var(--theme-popup-border); - box-shadow: inset 0 -1px 0 var(--theme-hover); - display: inline-block; - font-size: var(--code-font-size); - font-family: var(--mono-font); - line-height: 10px; - padding: 4px 5px; - vertical-align: middle; -} - -:not(.footnote-definition) + .footnote-definition, -.footnote-definition + :not(.footnote-definition) { - margin-block-start: 2em; -} -.footnote-definition { - font-size: 0.9em; - margin: 0.5em 0; -} -.footnote-definition p { - display: inline; -} - -.tooltiptext { - position: absolute; - visibility: hidden; - color: #fff; - background-color: #333; - transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ - left: -8px; /* Half of the width of the icon */ - top: -35px; - font-size: 0.8em; - text-align: center; - border-radius: 6px; - padding: 5px 8px; - margin: 5px; - z-index: 1000; -} -.tooltipped .tooltiptext { - visibility: visible; -} - -.chapter li.part-title { - color: var(--sidebar-fg); - margin: 5px 0px; - font-weight: bold; -} - -.result-no-output { - font-style: italic; -} diff --git a/rustbook-ru/book/css/print.css b/rustbook-ru/book/css/print.css deleted file mode 100644 index 80ec3a544..000000000 --- a/rustbook-ru/book/css/print.css +++ /dev/null @@ -1,50 +0,0 @@ - -#sidebar, -#menu-bar, -.nav-chapters, -.mobile-nav-chapters { - display: none; -} - -#page-wrapper.page-wrapper { - transform: none !important; - margin-inline-start: 0px; - overflow-y: initial; -} - -#content { - max-width: none; - margin: 0; - padding: 0; -} - -.page { - overflow-y: initial; -} - -code { - direction: ltr !important; -} - -pre > .buttons { - z-index: 2; -} - -a, a:visited, a:active, a:hover { - color: #4183c4; - text-decoration: none; -} - -h1, h2, h3, h4, h5, h6 { - page-break-inside: avoid; - page-break-after: avoid; -} - -pre, code { - page-break-inside: avoid; - white-space: pre-wrap; -} - -.fa { - display: none !important; -} diff --git a/rustbook-ru/book/css/variables.css b/rustbook-ru/book/css/variables.css deleted file mode 100644 index 0da55e8c9..000000000 --- a/rustbook-ru/book/css/variables.css +++ /dev/null @@ -1,279 +0,0 @@ - -/* Globals */ - -:root { - --sidebar-width: 300px; - --sidebar-resize-indicator-width: 8px; - --sidebar-resize-indicator-space: 2px; - --page-padding: 15px; - --content-max-width: 750px; - --menu-bar-height: 50px; - --mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; - --code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */ -} - -/* Themes */ - -.ayu { - --bg: hsl(210, 25%, 8%); - --fg: #c5c5c5; - - --sidebar-bg: #14191f; - --sidebar-fg: #c8c9db; - --sidebar-non-existant: #5c6773; - --sidebar-active: #ffb454; - --sidebar-spacer: #2d334f; - - --scrollbar: var(--sidebar-fg); - - --icons: #737480; - --icons-hover: #b7b9cc; - - --links: #0096cf; - - --inline-code-color: #ffb454; - - --theme-popup-bg: #14191f; - --theme-popup-border: #5c6773; - --theme-hover: #191f26; - - --quote-bg: hsl(226, 15%, 17%); - --quote-border: hsl(226, 15%, 22%); - - --warning-border: #ff8e00; - - --table-border-color: hsl(210, 25%, 13%); - --table-header-bg: hsl(210, 25%, 28%); - --table-alternate-bg: hsl(210, 25%, 11%); - - --searchbar-border-color: #848484; - --searchbar-bg: #424242; - --searchbar-fg: #fff; - --searchbar-shadow-color: #d4c89f; - --searchresults-header-fg: #666; - --searchresults-border-color: #888; - --searchresults-li-bg: #252932; - --search-mark-bg: #e3b171; - - --color-scheme: dark; -} - -.coal { - --bg: hsl(200, 7%, 8%); - --fg: #98a3ad; - - --sidebar-bg: #292c2f; - --sidebar-fg: #a1adb8; - --sidebar-non-existant: #505254; - --sidebar-active: #3473ad; - --sidebar-spacer: #393939; - - --scrollbar: var(--sidebar-fg); - - --icons: #43484d; - --icons-hover: #b3c0cc; - - --links: #2b79a2; - - --inline-code-color: #c5c8c6; - - --theme-popup-bg: #141617; - --theme-popup-border: #43484d; - --theme-hover: #1f2124; - - --quote-bg: hsl(234, 21%, 18%); - --quote-border: hsl(234, 21%, 23%); - - --warning-border: #ff8e00; - - --table-border-color: hsl(200, 7%, 13%); - --table-header-bg: hsl(200, 7%, 28%); - --table-alternate-bg: hsl(200, 7%, 11%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #b7b7b7; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #666; - --searchresults-border-color: #98a3ad; - --searchresults-li-bg: #2b2b2f; - --search-mark-bg: #355c7d; - - --color-scheme: dark; -} - -.light { - --bg: hsl(0, 0%, 100%); - --fg: hsl(0, 0%, 0%); - - --sidebar-bg: #fafafa; - --sidebar-fg: hsl(0, 0%, 0%); - --sidebar-non-existant: #aaaaaa; - --sidebar-active: #1f1fff; - --sidebar-spacer: #f4f4f4; - - --scrollbar: #8F8F8F; - - --icons: #747474; - --icons-hover: #000000; - - --links: #20609f; - - --inline-code-color: #301900; - - --theme-popup-bg: #fafafa; - --theme-popup-border: #cccccc; - --theme-hover: #e6e6e6; - - --quote-bg: hsl(197, 37%, 96%); - --quote-border: hsl(197, 37%, 91%); - - --warning-border: #ff8e00; - - --table-border-color: hsl(0, 0%, 95%); - --table-header-bg: hsl(0, 0%, 80%); - --table-alternate-bg: hsl(0, 0%, 97%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #fafafa; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #666; - --searchresults-border-color: #888; - --searchresults-li-bg: #e4f2fe; - --search-mark-bg: #a2cff5; - - --color-scheme: light; -} - -.navy { - --bg: hsl(226, 23%, 11%); - --fg: #bcbdd0; - - --sidebar-bg: #282d3f; - --sidebar-fg: #c8c9db; - --sidebar-non-existant: #505274; - --sidebar-active: #2b79a2; - --sidebar-spacer: #2d334f; - - --scrollbar: var(--sidebar-fg); - - --icons: #737480; - --icons-hover: #b7b9cc; - - --links: #2b79a2; - - --inline-code-color: #c5c8c6; - - --theme-popup-bg: #161923; - --theme-popup-border: #737480; - --theme-hover: #282e40; - - --quote-bg: hsl(226, 15%, 17%); - --quote-border: hsl(226, 15%, 22%); - - --warning-border: #ff8e00; - - --table-border-color: hsl(226, 23%, 16%); - --table-header-bg: hsl(226, 23%, 31%); - --table-alternate-bg: hsl(226, 23%, 14%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #aeaec6; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #5f5f71; - --searchresults-border-color: #5c5c68; - --searchresults-li-bg: #242430; - --search-mark-bg: #a2cff5; - - --color-scheme: dark; -} - -.rust { - --bg: hsl(60, 9%, 87%); - --fg: #262625; - - --sidebar-bg: #3b2e2a; - --sidebar-fg: #c8c9db; - --sidebar-non-existant: #505254; - --sidebar-active: #e69f67; - --sidebar-spacer: #45373a; - - --scrollbar: var(--sidebar-fg); - - --icons: #737480; - --icons-hover: #262625; - - --links: #2b79a2; - - --inline-code-color: #6e6b5e; - - --theme-popup-bg: #e1e1db; - --theme-popup-border: #b38f6b; - --theme-hover: #99908a; - - --quote-bg: hsl(60, 5%, 75%); - --quote-border: hsl(60, 5%, 70%); - - --warning-border: #ff8e00; - - --table-border-color: hsl(60, 9%, 82%); - --table-header-bg: #b3a497; - --table-alternate-bg: hsl(60, 9%, 84%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #fafafa; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #666; - --searchresults-border-color: #888; - --searchresults-li-bg: #dec2a2; - --search-mark-bg: #e69f67; - - --color-scheme: light; -} - -@media (prefers-color-scheme: dark) { - .light.no-js { - --bg: hsl(200, 7%, 8%); - --fg: #98a3ad; - - --sidebar-bg: #292c2f; - --sidebar-fg: #a1adb8; - --sidebar-non-existant: #505254; - --sidebar-active: #3473ad; - --sidebar-spacer: #393939; - - --scrollbar: var(--sidebar-fg); - - --icons: #43484d; - --icons-hover: #b3c0cc; - - --links: #2b79a2; - - --inline-code-color: #c5c8c6; - - --theme-popup-bg: #141617; - --theme-popup-border: #43484d; - --theme-hover: #1f2124; - - --quote-bg: hsl(234, 21%, 18%); - --quote-border: hsl(234, 21%, 23%); - - --warning-border: #ff8e00; - - --table-border-color: hsl(200, 7%, 13%); - --table-header-bg: hsl(200, 7%, 28%); - --table-alternate-bg: hsl(200, 7%, 11%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #b7b7b7; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #666; - --searchresults-border-color: #98a3ad; - --searchresults-li-bg: #2b2b2f; - --search-mark-bg: #355c7d; - } -} diff --git a/rustbook-ru/book/elasticlunr.min.js b/rustbook-ru/book/elasticlunr.min.js deleted file mode 100644 index 94b20dd2e..000000000 --- a/rustbook-ru/book/elasticlunr.min.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * elasticlunr - http://weixsong.github.io - * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5 - * - * Copyright (C) 2017 Oliver Nightingale - * Copyright (C) 2017 Wei Song - * MIT Licensed - * @license - */ -!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o - - - - diff --git a/rustbook-ru/book/ferris.css b/rustbook-ru/book/ferris.css deleted file mode 100644 index 371207924..000000000 --- a/rustbook-ru/book/ferris.css +++ /dev/null @@ -1,33 +0,0 @@ -body.light .does_not_compile, -body.light .panics, -body.light .not_desired_behavior, -body.rust .does_not_compile, -body.rust .panics, -body.rust .not_desired_behavior { - background: #fff1f1; -} - -body.coal .does_not_compile, -body.coal .panics, -body.coal .not_desired_behavior, -body.navy .does_not_compile, -body.navy .panics, -body.navy .not_desired_behavior, -body.ayu .does_not_compile, -body.ayu .panics, -body.ayu .not_desired_behavior { - background: #501f21; -} - -.ferris { - position: absolute; - z-index: 99; - right: 5px; - top: 30px; - width: 10%; - height: auto; -} - -.ferris-explain { - width: 100px; -} diff --git a/rustbook-ru/book/ferris.js b/rustbook-ru/book/ferris.js deleted file mode 100644 index 5e79b3c71..000000000 --- a/rustbook-ru/book/ferris.js +++ /dev/null @@ -1,51 +0,0 @@ -var ferrisTypes = [ - { - attr: 'does_not_compile', - title: 'This code does not compile!' - }, - { - attr: 'panics', - title: 'This code panics!' - }, - { - attr: 'unsafe', - title: 'This code block contains unsafe code.' - }, - { - attr: 'not_desired_behavior', - title: 'This code does not produce the desired behavior.' - } -] - -document.addEventListener('DOMContentLoaded', () => { - for (var ferrisType of ferrisTypes) { - attachFerrises(ferrisType) - } -}) - -function attachFerrises (type) { - var elements = document.getElementsByClassName(type.attr) - - for (var codeBlock of elements) { - var lines = codeBlock.textContent.split(/\r|\r\n|\n/).length - 1; - - if (lines >= 4) { - attachFerris(codeBlock, type) - } - } -} - -function attachFerris (element, type) { - var a = document.createElement('a') - a.setAttribute('href', 'ch00-00-introduction.html#ferris') - a.setAttribute('target', '_blank') - - var img = document.createElement('img') - img.setAttribute('src', 'img/ferris/' + type.attr + '.svg') - img.setAttribute('title', type.title) - img.className = 'ferris' - - a.appendChild(img) - - element.parentElement.insertBefore(a, element) -} diff --git a/rustbook-ru/book/fonts/OPEN-SANS-LICENSE.txt b/rustbook-ru/book/fonts/OPEN-SANS-LICENSE.txt deleted file mode 100644 index d64569567..000000000 --- a/rustbook-ru/book/fonts/OPEN-SANS-LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/rustbook-ru/book/fonts/SOURCE-CODE-PRO-LICENSE.txt b/rustbook-ru/book/fonts/SOURCE-CODE-PRO-LICENSE.txt deleted file mode 100644 index 366206f54..000000000 --- a/rustbook-ru/book/fonts/SOURCE-CODE-PRO-LICENSE.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/rustbook-ru/book/fonts/fonts.css b/rustbook-ru/book/fonts/fonts.css deleted file mode 100644 index 858efa598..000000000 --- a/rustbook-ru/book/fonts/fonts.css +++ /dev/null @@ -1,100 +0,0 @@ -/* Open Sans is licensed under the Apache License, Version 2.0. See http://www.apache.org/licenses/LICENSE-2.0 */ -/* Source Code Pro is under the Open Font License. See https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL */ - -/* open-sans-300 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - src: local('Open Sans Light'), local('OpenSans-Light'), - url('open-sans-v17-all-charsets-300.woff2') format('woff2'); -} - -/* open-sans-300italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), - url('open-sans-v17-all-charsets-300italic.woff2') format('woff2'); -} - -/* open-sans-regular - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: local('Open Sans Regular'), local('OpenSans-Regular'), - url('open-sans-v17-all-charsets-regular.woff2') format('woff2'); -} - -/* open-sans-italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - src: local('Open Sans Italic'), local('OpenSans-Italic'), - url('open-sans-v17-all-charsets-italic.woff2') format('woff2'); -} - -/* open-sans-600 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), - url('open-sans-v17-all-charsets-600.woff2') format('woff2'); -} - -/* open-sans-600italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), - url('open-sans-v17-all-charsets-600italic.woff2') format('woff2'); -} - -/* open-sans-700 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 700; - src: local('Open Sans Bold'), local('OpenSans-Bold'), - url('open-sans-v17-all-charsets-700.woff2') format('woff2'); -} - -/* open-sans-700italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 700; - src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), - url('open-sans-v17-all-charsets-700italic.woff2') format('woff2'); -} - -/* open-sans-800 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 800; - src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), - url('open-sans-v17-all-charsets-800.woff2') format('woff2'); -} - -/* open-sans-800italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 800; - src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), - url('open-sans-v17-all-charsets-800italic.woff2') format('woff2'); -} - -/* source-code-pro-500 - latin_vietnamese_latin-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Source Code Pro'; - font-style: normal; - font-weight: 500; - src: url('source-code-pro-v11-all-charsets-500.woff2') format('woff2'); -} diff --git a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-300.woff2 b/rustbook-ru/book/fonts/open-sans-v17-all-charsets-300.woff2 deleted file mode 100644 index 9f51be370..000000000 Binary files a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-300.woff2 and /dev/null differ diff --git a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-300italic.woff2 b/rustbook-ru/book/fonts/open-sans-v17-all-charsets-300italic.woff2 deleted file mode 100644 index 2f5454484..000000000 Binary files a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-300italic.woff2 and /dev/null differ diff --git a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-600.woff2 b/rustbook-ru/book/fonts/open-sans-v17-all-charsets-600.woff2 deleted file mode 100644 index f503d558d..000000000 Binary files a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-600.woff2 and /dev/null differ diff --git a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-600italic.woff2 b/rustbook-ru/book/fonts/open-sans-v17-all-charsets-600italic.woff2 deleted file mode 100644 index c99aabe80..000000000 Binary files a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-600italic.woff2 and /dev/null differ diff --git a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-700.woff2 b/rustbook-ru/book/fonts/open-sans-v17-all-charsets-700.woff2 deleted file mode 100644 index 421a1ab25..000000000 Binary files a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-700.woff2 and /dev/null differ diff --git a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-700italic.woff2 b/rustbook-ru/book/fonts/open-sans-v17-all-charsets-700italic.woff2 deleted file mode 100644 index 12ce3d20d..000000000 Binary files a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-700italic.woff2 and /dev/null differ diff --git a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-800.woff2 b/rustbook-ru/book/fonts/open-sans-v17-all-charsets-800.woff2 deleted file mode 100644 index c94a223b0..000000000 Binary files a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-800.woff2 and /dev/null differ diff --git a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-800italic.woff2 b/rustbook-ru/book/fonts/open-sans-v17-all-charsets-800italic.woff2 deleted file mode 100644 index eed7d3c63..000000000 Binary files a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-800italic.woff2 and /dev/null differ diff --git a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-italic.woff2 b/rustbook-ru/book/fonts/open-sans-v17-all-charsets-italic.woff2 deleted file mode 100644 index 398b68a08..000000000 Binary files a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-italic.woff2 and /dev/null differ diff --git a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-regular.woff2 b/rustbook-ru/book/fonts/open-sans-v17-all-charsets-regular.woff2 deleted file mode 100644 index 8383e94c6..000000000 Binary files a/rustbook-ru/book/fonts/open-sans-v17-all-charsets-regular.woff2 and /dev/null differ diff --git a/rustbook-ru/book/fonts/source-code-pro-v11-all-charsets-500.woff2 b/rustbook-ru/book/fonts/source-code-pro-v11-all-charsets-500.woff2 deleted file mode 100644 index 722245682..000000000 Binary files a/rustbook-ru/book/fonts/source-code-pro-v11-all-charsets-500.woff2 and /dev/null differ diff --git a/rustbook-ru/book/foreword.html b/rustbook-ru/book/foreword.html deleted file mode 100644 index 36fac236f..000000000 --- a/rustbook-ru/book/foreword.html +++ /dev/null @@ -1,238 +0,0 @@ - - - - - - Предисловие - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Предисловие

-

Не всегда было ясно, но язык программирования Ржавчина в основном посвящён расширению возможностей: независимо от того, какой код вы пишете сейчас, Ржавчина позволяет вам достичь большего, чтобы программировать уверенно в более широком ряде областей, чем вы делали раньше.

-

Возьмём, к примеру, работу «системного уровня», которая касается низкоуровневых подробностей управления памятью, представления данных и многопоточности. Привычно эта область программирования считается загадочной, доступной лишь немногим избранным, посвятившим долгие годы изучению всех её печально известных подводных камней. И даже те, кто опытют это, делают всё с осторожностью, чтобы их код не был уязвим для уязвимостей, сбоев или повреждений.

-

Rust разрушает эти преграды, устраняя старые подводные камни и предоставляя дружелюбный, отполированный набор средств, который поможет вам на этом пути. Программисты, которым необходимо «погрузиться» в низкоуровневое управление, могут сделать это с помощью Rust, не беря на себя привычный риск сбоев или дыр в безопасности и не изучая тонкости изменчивых наборов средств. Более того, язык предназначен для того, чтобы легко вести вас к надёжному коду, который эффективен с точки зрения скорости и использования памяти.

-

Программисты, которые уже работают с низкоуровневым кодом, могут использовать Ржавчина для повышения своих чувства собственной значимости. Например, внедрение одновременности в Ржавчина является действием с относительно низким риском: сборщик поймает для вас привычные ошибки. И вы можете заняться более враждебной переработкой в своём коде с уверенностью, что не будете случайно добавлять в код сбои или уязвимости.

-

Но Ржавчина не ограничивается низкоуровневым системным программированием. Он достаточно выразителен и удобен, чтобы приложения CLI (Command Line Interface – окно выводаные программы), веб-серверы и многие другие виды кода были довольно приятными для написания — позже вы найдёте простые примеры того и другого в книге. Работа с Ржавчина позволяет вырабатывать навыки, которые переносятся из одной предметной области в другую; вы можете изучить Rust, написав веб-приложение, а затем применить те же навыки для Raspberry Pi.

-

Эта книга полностью раскрывает возможности Ржавчина для расширения возможностей его пользователей. Это дружелюбный и доступный источник, призванный помочь вам повысить уровень не только ваших знаний о Rust, но и ваших возможностей и уверенности как программиста в целом. Так что погружайтесь, готовьтесь учиться и добро пожаловать в сообщество Rust!

-

— Nicholas Matsakis и Aaron Turon

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/highlight.css b/rustbook-ru/book/highlight.css deleted file mode 100644 index ba57b82b2..000000000 --- a/rustbook-ru/book/highlight.css +++ /dev/null @@ -1,82 +0,0 @@ -/* - * An increased contrast highlighting scheme loosely based on the - * "Base16 Atelier Dune Light" theme by Bram de Haan - * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) - * Original Base16 color scheme by Chris Kempson - * (https://github.com/chriskempson/base16) - */ - -/* Comment */ -.hljs-comment, -.hljs-quote { - color: #575757; -} - -/* Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #d70025; -} - -/* Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #b21e00; -} - -/* Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #008200; -} - -/* Blue */ -.hljs-title, -.hljs-section { - color: #0030f2; -} - -/* Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #9d00ec; -} - -.hljs { - display: block; - overflow-x: auto; - background: #f6f7f6; - color: #000; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-addition { - color: #22863a; - background-color: #f0fff4; -} - -.hljs-deletion { - color: #b31d28; - background-color: #ffeef0; -} diff --git a/rustbook-ru/book/highlight.js b/rustbook-ru/book/highlight.js deleted file mode 100644 index 18d24345b..000000000 --- a/rustbook-ru/book/highlight.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - Highlight.js 10.1.1 (93fd0d73) - License: BSD-3-Clause - Copyright (c) 2006-2020, Ivan Sagalaev -*/ -var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"
":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); -hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}()); -hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}()); -hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}()); -hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}()); -hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}()); -hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}()); -hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}()); -hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}()); -hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}()); -hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:"e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}()); -hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}()); -hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}()); -hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}()); -hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}()); -hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}()); -hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}()); -hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}()); -hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}()); -hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}()); -hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}()); -hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}()); -hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}()); -hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}()); -hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}()); -hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}()); -hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}()); -hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}()); -hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}()); -hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:""}]}}}()); -hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}()); -hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}()); -hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}()); -hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}()); -hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}()); -hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}()); -hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}()); -hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}()); -hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}()); -hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}()); -hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}()); -hljs.registerLanguage("nim",function(){"use strict";return function(e){return{name:"Nim",aliases:["nim"],keywords:{keyword:"addr and as asm bind block break case cast const continue converter discard distinct div do elif else end enum except export finally for from func generic if import in include interface is isnot iterator let macro method mixin mod nil not notin object of or out proc ptr raise ref return shl shr static template try tuple type using var when while with without xor yield",literal:"shared guarded stdin stdout stderr result true false",built_in:"int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float float32 float64 bool char string cstring pointer expr stmt void auto any range array openarray varargs seq set clong culong cchar cschar cshort cint csize clonglong cfloat cdouble clongdouble cuchar cushort cuint culonglong cstringarray semistatic"},contains:[{className:"meta",begin:/{\./,end:/\.}/,relevance:10},{className:"string",begin:/[a-zA-Z]\w*"/,end:/"/,contains:[{begin:/""/}]},{className:"string",begin:/([a-zA-Z]\w*)?"""/,end:/"""/},e.QUOTE_STRING_MODE,{className:"type",begin:/\b[A-Z]\w+\b/,relevance:0},{className:"number",relevance:0,variants:[{begin:/\b(0[xX][0-9a-fA-F][_0-9a-fA-F]*)('?[iIuU](8|16|32|64))?/},{begin:/\b(0o[0-7][_0-7]*)('?[iIuUfF](8|16|32|64))?/},{begin:/\b(0(b|B)[01][_01]*)('?[iIuUfF](8|16|32|64))?/},{begin:/\b(\d[_\d]*)('?[iIuUfF](8|16|32|64))?/}]},e.HASH_COMMENT_MODE]}}}()); -hljs.registerLanguage("nix",function(){"use strict";return function(e){var n={keyword:"rec with let in inherit assert if else then",literal:"true false or and null",built_in:"import abort baseNameOf dirOf isNull builtins map removeAttrs throw toString derivation"},i={className:"subst",begin:/\$\{/,end:/}/,keywords:n},t={className:"string",contains:[i],variants:[{begin:"''",end:"''"},{begin:'"',end:'"'}]},s=[e.NUMBER_MODE,e.HASH_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t,{begin:/[a-zA-Z0-9-_]+(\s*=)/,returnBegin:!0,relevance:0,contains:[{className:"attr",begin:/\S+/}]}];return i.contains=s,{name:"Nix",aliases:["nixos"],keywords:n,contains:s}}}()); -hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}()); -hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}()); -hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}()); \ No newline at end of file diff --git a/rustbook-ru/book/img/ferris/does_not_compile.svg b/rustbook-ru/book/img/ferris/does_not_compile.svg deleted file mode 100644 index 5d345f14e..000000000 --- a/rustbook-ru/book/img/ferris/does_not_compile.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rustbook-ru/book/img/ferris/not_desired_behavior.svg b/rustbook-ru/book/img/ferris/not_desired_behavior.svg deleted file mode 100644 index 47f402455..000000000 --- a/rustbook-ru/book/img/ferris/not_desired_behavior.svg +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rustbook-ru/book/img/ferris/panics.svg b/rustbook-ru/book/img/ferris/panics.svg deleted file mode 100644 index be55f5e09..000000000 --- a/rustbook-ru/book/img/ferris/panics.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rustbook-ru/book/img/ferris/unsafe.svg b/rustbook-ru/book/img/ferris/unsafe.svg deleted file mode 100644 index d4fdc08dd..000000000 --- a/rustbook-ru/book/img/ferris/unsafe.svg +++ /dev/null @@ -1,291 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rustbook-ru/book/img/trpl04-01.svg b/rustbook-ru/book/img/trpl04-01.svg deleted file mode 100644 index 314f53ba1..000000000 --- a/rustbook-ru/book/img/trpl04-01.svg +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - -%3 - - - -table0 - -s1 - -name - -value - -ptr - - -len - -5 - -capacity - -5 - - - -table1 - -index - -value - -0 - -h - -1 - -e - -2 - -l - -3 - -l - -4 - -o - - - -table0:c->table1:pointee - - - - - diff --git a/rustbook-ru/book/img/trpl04-02.svg b/rustbook-ru/book/img/trpl04-02.svg deleted file mode 100644 index 70d490f0b..000000000 --- a/rustbook-ru/book/img/trpl04-02.svg +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - -%3 - - - -table0 - -s1 - -name - -value - -ptr - - -len - -5 - -capacity - -5 - - - -table1 - -index - -value - -0 - -h - -1 - -e - -2 - -l - -3 - -l - -4 - -o - - - -table0:c->table1:pointee - - - - - -table3 - -s2 - -name - -value - -ptr - - -len - -5 - -capacity - -5 - - - -table3:c->table1:pointee - - - - - diff --git a/rustbook-ru/book/img/trpl04-03.svg b/rustbook-ru/book/img/trpl04-03.svg deleted file mode 100644 index 7c153e23a..000000000 --- a/rustbook-ru/book/img/trpl04-03.svg +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - -%3 - - - -table0 - -s2 - -name - -value - -ptr - - -len - -5 - -capacity - -5 - - - -table1 - -index - -value - -0 - -h - -1 - -e - -2 - -l - -3 - -l - -4 - -o - - - -table0:c->table1:pointee - - - - - -table3 - -s1 - -name - -value - -ptr - - -len - -5 - -capacity - -5 - - - -table4 - -index - -value - -0 - -h - -1 - -e - -2 - -l - -3 - -l - -4 - -o - - - -table3:c->table4:pointee - - - - - diff --git a/rustbook-ru/book/img/trpl04-04.svg b/rustbook-ru/book/img/trpl04-04.svg deleted file mode 100644 index a0513abd9..000000000 --- a/rustbook-ru/book/img/trpl04-04.svg +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - -%3 - - - -table0 - - -s1 - -name - -value - -ptr - - -len - -5 - -capacity - -5 - - - -table1 - -index - -value - -0 - -h - -1 - -e - -2 - -l - -3 - -l - -4 - -o - - - -table0:c->table1:pointee - - - - - -table3 - -s2 - -name - -value - -ptr - - -len - -5 - -capacity - -5 - - - -table3:c->table1:pointee - - - - - diff --git a/rustbook-ru/book/img/trpl04-05.svg b/rustbook-ru/book/img/trpl04-05.svg deleted file mode 100644 index b4bf2ebee..000000000 --- a/rustbook-ru/book/img/trpl04-05.svg +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - -%3 - - - -table0 - -s - -name - -value - -ptr - - - - -table1 - -s1 - -name - -value - -ptr - - -len - -5 - -capacity - -5 - - - -table0:c->table1:borrowee - - - - - -table2 - -index - -value - -0 - -h - -1 - -e - -2 - -l - -3 - -l - -4 - -o - - - -table1:c->table2:pointee - - - - - diff --git a/rustbook-ru/book/img/trpl04-06.svg b/rustbook-ru/book/img/trpl04-06.svg deleted file mode 100644 index e64415fe4..000000000 --- a/rustbook-ru/book/img/trpl04-06.svg +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - -%3 - - - -table0 - -world - -name - -value - -ptr - - -len - -5 - - - -table4 - -index - -value - -0 - -h - -1 - -e - -2 - -l - -3 - -l - -4 - -o - -5 - - - -6 - -w - -7 - -o - -8 - -r - -9 - -l - -10 - -d - - - -table0:c->table4:pointee2 - - - - - -table3 - -s - -name - -value - -ptr - - -len - -11 - -capacity - -11 - - - -table3:c->table4:pointee - - - - - diff --git a/rustbook-ru/book/img/trpl14-01.png b/rustbook-ru/book/img/trpl14-01.png deleted file mode 100644 index d7deaaf95..000000000 Binary files a/rustbook-ru/book/img/trpl14-01.png and /dev/null differ diff --git a/rustbook-ru/book/img/trpl14-02.png b/rustbook-ru/book/img/trpl14-02.png deleted file mode 100644 index 121801b76..000000000 Binary files a/rustbook-ru/book/img/trpl14-02.png and /dev/null differ diff --git a/rustbook-ru/book/img/trpl14-03.png b/rustbook-ru/book/img/trpl14-03.png deleted file mode 100644 index cf5a15c41..000000000 Binary files a/rustbook-ru/book/img/trpl14-03.png and /dev/null differ diff --git a/rustbook-ru/book/img/trpl14-04.png b/rustbook-ru/book/img/trpl14-04.png deleted file mode 100644 index dc1caaba9..000000000 Binary files a/rustbook-ru/book/img/trpl14-04.png and /dev/null differ diff --git a/rustbook-ru/book/img/trpl14-05.png b/rustbook-ru/book/img/trpl14-05.png deleted file mode 100644 index 139011f00..000000000 Binary files a/rustbook-ru/book/img/trpl14-05.png and /dev/null differ diff --git a/rustbook-ru/book/img/trpl14-07.png b/rustbook-ru/book/img/trpl14-07.png deleted file mode 100644 index ef8414507..000000000 Binary files a/rustbook-ru/book/img/trpl14-07.png and /dev/null differ diff --git a/rustbook-ru/book/img/trpl14-10.png b/rustbook-ru/book/img/trpl14-10.png deleted file mode 100644 index d0ed2ca18..000000000 Binary files a/rustbook-ru/book/img/trpl14-10.png and /dev/null differ diff --git a/rustbook-ru/book/img/trpl15-01.svg b/rustbook-ru/book/img/trpl15-01.svg deleted file mode 100644 index bbeef968a..000000000 --- a/rustbook-ru/book/img/trpl15-01.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - -%3 - - - -table0 - -Cons - -i32 - - -Cons - -i32 - - -Cons - -i32 - - -Cons - -i32 - - -Cons - -i32 - - - - - diff --git a/rustbook-ru/book/img/trpl15-02.svg b/rustbook-ru/book/img/trpl15-02.svg deleted file mode 100644 index 4454df8c3..000000000 --- a/rustbook-ru/book/img/trpl15-02.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - -%3 - - - -table0 - -Cons - -i32 - - -Box - -usize - - - diff --git a/rustbook-ru/book/img/trpl15-03.svg b/rustbook-ru/book/img/trpl15-03.svg deleted file mode 100644 index dbc3b5cdb..000000000 --- a/rustbook-ru/book/img/trpl15-03.svg +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - -%3 - - - -table4 -b - - - -table5 - -3 - -   - - - -table4:c->table5:pte4 - - - - - -table1 - -5 - -   - - - -table5:c->table1:pte0 - - - - - -table0 -a - - - -table0:c->table1:pte0 - - - - - -table2 - -10 - -   - - - -table1:c->table2:pte1 - - - - - -table3 - -Nil - - - -table2:c->table3:pte2 - - - - - -table6 -c - - - -table7 - -4 - -   - - - -table6:c->table7:pte6 - - - - - -table7:c->table1:pte0 - - - - - diff --git a/rustbook-ru/book/img/trpl15-04.svg b/rustbook-ru/book/img/trpl15-04.svg deleted file mode 100644 index 7285ae673..000000000 --- a/rustbook-ru/book/img/trpl15-04.svg +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - -l1 - -5 - - - - - -l2 - -10 - - - - - -l1:c->l2:data - - - - - -invisible_end - - - - -l2:c->invisible_end:n - - - - -invisible_start - - - - -invisible_start:n->l1 - - - - - -invisible_start:s->invisible_end:s - - - - -a - -a - - - -a->l1:n - - - - - -b - -b - - - -b->l2:n - - - - - diff --git a/rustbook-ru/book/img/trpl20-01.png b/rustbook-ru/book/img/trpl20-01.png deleted file mode 100644 index 19e2cbc0a..000000000 Binary files a/rustbook-ru/book/img/trpl20-01.png and /dev/null differ diff --git a/rustbook-ru/book/index.html b/rustbook-ru/book/index.html deleted file mode 100644 index c67b60e90..000000000 --- a/rustbook-ru/book/index.html +++ /dev/null @@ -1,233 +0,0 @@ - - - - - - Язык программирования Rust - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

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

-

От Стива Клабника и Кэрол Николс, при поддержке других участников сообщества Rust

-

В этой исполнения учебника предполагается, что вы используете Ржавчина 1.67.1 (выпущен 09.02.2023) или новее. См. раздел «Установка» главы 1 для установки или обновления Rust.

-

HTML-исполнение книги доступна онлайн по адресам https://doc.rust-lang.org/stable/book/(англ.) и https://doc.rust-lang.ru/book(рус.) и офлайн. При установке Ржавчина с помощью rustup: просто запустите rustup docs --book, чтобы её открыть.

-

Также доступны несколько переводов от сообщества.

-

Этот источник доступен в виде печатной книги в мягкой обложке и в виде электронной книги от No Starch Press .

-
-

🚨 Предпочитаете более увлекательный этап обучения? Попробуйте другую исполнение Ржавчина Book, в которой есть: проверочные вопросы, цветовое выделение, наглядные визуализации и многое другое: https://rust-book.cs.brown.edu

-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/mark.min.js b/rustbook-ru/book/mark.min.js deleted file mode 100644 index 163623188..000000000 --- a/rustbook-ru/book/mark.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*!*************************************************** -* mark.js v8.11.1 -* https://markjs.io/ -* Copyright (c) 2014–2018, Julian Kühnel -* Released under the MIT license https://git.io/vwTVl -*****************************************************/ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Mark=t()}(this,function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=i,this.iframesTimeout=o}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach(function(t){var n=e.filter(function(e){return e.contains(t)}).length>0;-1!==e.indexOf(t)||n||e.push(t)}),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var i=e.contentWindow;if(r=i.document,!i||!r)throw new Error("iframe inaccessible")}catch(e){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,i=!1,o=null,a=function a(){if(!i){i=!0,clearTimeout(o);try{r.isIframeBlank(e)||(e.removeEventListener("load",a),r.getIframeContents(e,t,n))}catch(e){n()}}};e.addEventListener("load",a),o=setTimeout(a,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(e){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,function(){return!0},function(e){r++,n.waitForIframes(e.querySelector("html"),function(){--r||t()})},function(e){e||t()})}},{key:"forEachIframe",value:function(t,n,r){var i=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},a=t.querySelectorAll("iframe"),s=a.length,c=0;a=Array.prototype.slice.call(a);var u=function(){--s<=0&&o(c)};s||u(),a.forEach(function(t){e.matches(t,i.exclude)?u():i.onIframeReady(t,function(e){n(t)&&(c++,r(e)),u()},u)})}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:null===t?e.nextNode():e.nextNode()&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var i=!1,o=!1;return r.forEach(function(e,t){e.val===n&&(i=t,o=e.handled)}),this.compareNodeIframe(e,t,n)?(!1!==i||o?!1===i||o||(r[i].handled=!0):r.push({val:n,handled:!0}),!0):(!1===i&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var i=this;e.forEach(function(e){e.handled||i.getIframeContents(e.val,function(e){i.createInstanceOnIframe(e).forEachNode(t,n,r)})})}},{key:"iterateThroughNodes",value:function(e,t,n,r,i){for(var o,a=this,s=this.createIterator(t,e,r),c=[],u=[],l=void 0,h=void 0;void 0,o=a.getIteratorNode(s),h=o.prevNode,l=o.node;)this.iframes&&this.forEachIframe(t,function(e){return a.checkIframeFilter(l,h,e,c)},function(t){a.createInstanceOnIframe(t).forEachNode(e,function(e){return u.push(e)},r)}),u.push(l);u.forEach(function(e){n(e)}),this.iframes&&this.handleOpenIframes(c,e,n,r),i()}},{key:"forEachNode",value:function(e,t,n){var r=this,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},o=this.getContexts(),a=o.length;a||i(),o.forEach(function(o){var s=function(){r.iterateThroughNodes(e,o,t,n,function(){--a<=0&&i()})};r.iframes?r.waitForIframes(o,s):s()})}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var i=!1;return n.every(function(t){return!r.call(e,t)||(i=!0,!1)}),i}return!1}}]),e}(),o=function(){function e(n){t(this,e),this.opt=r({},{diacritics:!0,synonyms:{},accuracy:"partially",caseSensitive:!1,ignoreJoiners:!1,ignorePunctuation:[],wildcards:"disabled"},n)}return n(e,[{key:"create",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),new RegExp(e,"gm"+(this.opt.caseSensitive?"":"i"))}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var i in t)if(t.hasOwnProperty(i)){var o=t[i],a="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(i):this.escapeStr(i),s="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o);""!==a&&""!==s&&(e=e.replace(new RegExp("("+this.escapeStr(a)+"|"+this.escapeStr(s)+")","gm"+n),r+"("+this.processSynonyms(a)+"|"+this.processSynonyms(s)+")"+r))}return e}},{key:"processSynonyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,function(e){return"\\"===e.charAt(0)?"?":""})).replace(/(?:\\)*\*/g,function(e){return"\\"===e.charAt(0)?"*":""})}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"})}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"],r=[];return e.split("").forEach(function(i){n.every(function(n){if(-1!==n.indexOf(i)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0})}),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n=this.opt.accuracy,r="string"==typeof n?n:n.value,i="";switch(("string"==typeof n?[]:n.limiters).forEach(function(e){i+="|"+t.escapeStr(e)}),r){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(i="\\s"+(i||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿")))+"]*"+e+"[^"+i+"]*)";case"exactly":return"(^|\\s"+i+")("+e+")(?=$|\\s"+i+")"}}}]),e}(),a=function(){function a(e){t(this,a),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(a,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach(function(e){t.opt.separateWordSearch?e.split(" ").forEach(function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)}):e.trim()&&-1===n.indexOf(e)&&n.push(e)}),{keywords:n.sort(function(e,t){return t.length-e.length}),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort(function(e,t){return e.start-t.start}).forEach(function(e){var i=t.callNoMatchOnInvalidRanges(e,r),o=i.start,a=i.end;i.valid&&(e.start=o,e.length=a-o,n.push(e),r=a)}),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,i=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:i}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,i=!0,o=n.length,a=t-o,s=parseInt(e.start,10)-a;return(r=(s=s>o?o:s)+parseInt(e.length,10))>o&&(r=o,this.log("End range automatically set to the max value of "+o)),s<0||r-s<0||s>o||r>o?(i=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(s,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:s,end:r,valid:i}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})},function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},function(){e({value:n,nodes:r})})}},{key:"matchesExclude",value:function(e){return i.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",i=e.splitText(t),o=i.splitText(n-t),a=document.createElement(r);return a.setAttribute("data-markjs","true"),this.opt.className&&a.setAttribute("class",this.opt.className),a.textContent=i.textContent,i.parentNode.replaceChild(a,i),o}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,i){var o=this;e.nodes.every(function(a,s){var c=e.nodes[s+1];if(void 0===c||c.start>t){if(!r(a.node))return!1;var u=t-a.start,l=(n>a.end?a.end:n)-a.start,h=e.value.substr(0,a.start),f=e.value.substr(l+a.start);if(a.node=o.wrapRangeInTextNode(a.node,u,l),e.value=h+f,e.nodes.forEach(function(t,n){n>=s&&(e.nodes[n].start>0&&n!==s&&(e.nodes[n].start-=l),e.nodes[n].end-=l)}),n-=l,i(a.node.previousSibling,a.start),!(n>a.end))return!1;t=a.end}return!0})}},{key:"wrapGroups",value:function(e,t,n,r){return r((e=this.wrapRangeInTextNode(e,t,t+n)).previousSibling),e}},{key:"separateGroups",value:function(e,t,n,r,i){for(var o=t.length,a=1;a-1&&r(t[a],e)&&(e=this.wrapGroups(e,s,t[a].length,i))}return e}},{key:"wrapMatches",value:function(e,t,n,r,i){var o=this,a=0===t?0:t+1;this.getTextNodes(function(t){t.nodes.forEach(function(t){t=t.node;for(var i=void 0;null!==(i=e.exec(t.textContent))&&""!==i[a];){if(o.opt.separateGroups)t=o.separateGroups(t,i,a,n,r);else{if(!n(i[a],t))continue;var s=i.index;if(0!==a)for(var c=1;c - - - - - Язык программирования Rust - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

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

-

От Стива Клабника и Кэрол Николс, при поддержке других участников сообщества Rust

-

В этой исполнения учебника предполагается, что вы используете Ржавчина 1.67.1 (выпущен 09.02.2023) или новее. См. раздел «Установка» главы 1 для установки или обновления Rust.

-

HTML-исполнение книги доступна онлайн по адресам https://doc.rust-lang.org/stable/book/(англ.) и https://doc.rust-lang.ru/book(рус.) и офлайн. При установке Ржавчина с помощью rustup: просто запустите rustup docs --book, чтобы её открыть.

-

Также доступны несколько переводов от сообщества.

-

Этот источник доступен в виде печатной книги в мягкой обложке и в виде электронной книги от No Starch Press .

-
-

🚨 Предпочитаете более увлекательный этап обучения? Попробуйте другую исполнение Ржавчина Book, в которой есть: проверочные вопросы, цветовое выделение, наглядные визуализации и многое другое: https://rust-book.cs.brown.edu

-
-

Предисловие

-

Не всегда было ясно, но язык программирования Ржавчина в основном посвящён расширению возможностей: независимо от того, какой код вы пишете сейчас, Ржавчина позволяет вам достичь большего, чтобы программировать уверенно в более широком ряде областей, чем вы делали раньше.

-

Возьмём, к примеру, работу «системного уровня», которая касается низкоуровневых подробностей управления памятью, представления данных и многопоточности. Привычно эта область программирования считается загадочной, доступной лишь немногим избранным, посвятившим долгие годы изучению всех её печально известных подводных камней. И даже те, кто опытют это, делают всё с осторожностью, чтобы их код не был уязвим для уязвимостей, сбоев или повреждений.

-

Rust разрушает эти преграды, устраняя старые подводные камни и предоставляя дружелюбный, отполированный набор средств, который поможет вам на этом пути. Программисты, которым необходимо «погрузиться» в низкоуровневое управление, могут сделать это с помощью Rust, не беря на себя привычный риск сбоев или дыр в безопасности и не изучая тонкости изменчивых наборов средств. Более того, язык предназначен для того, чтобы легко вести вас к надёжному коду, который эффективен с точки зрения скорости и использования памяти.

-

Программисты, которые уже работают с низкоуровневым кодом, могут использовать Ржавчина для повышения своих чувства собственной значимости. Например, внедрение одновременности в Ржавчина является действием с относительно низким риском: сборщик поймает для вас привычные ошибки. И вы можете заняться более враждебной переработкой в своём коде с уверенностью, что не будете случайно добавлять в код сбои или уязвимости.

-

Но Ржавчина не ограничивается низкоуровневым системным программированием. Он достаточно выразителен и удобен, чтобы приложения CLI (Command Line Interface – окно выводаные программы), веб-серверы и многие другие виды кода были довольно приятными для написания — позже вы найдёте простые примеры того и другого в книге. Работа с Ржавчина позволяет вырабатывать навыки, которые переносятся из одной предметной области в другую; вы можете изучить Rust, написав веб-приложение, а затем применить те же навыки для Raspberry Pi.

-

Эта книга полностью раскрывает возможности Ржавчина для расширения возможностей его пользователей. Это дружелюбный и доступный источник, призванный помочь вам повысить уровень не только ваших знаний о Rust, но и ваших возможностей и уверенности как программиста в целом. Так что погружайтесь, готовьтесь учиться и добро пожаловать в сообщество Rust!

-

— Nicholas Matsakis и Aaron Turon

-

Введение

-
-

Примечание. Это издание книги такое же, как и Язык программирования Rust, доступное в печатном и электронном виде от No Starch Press.

-
-

Добро пожаловать в The Ржавчина Programming Language, вводную книгу о Rust. Язык программирования Ржавчина помогает создавать быстрые, более надёжные приложения. Хорошая удобство и низкоуровневый управление часто являются противоречивыми требованиями для внешнего видаязыков программирования; Ржавчина бросает вызов этому вражде. Благодаря уравновешенности мощных технических возможностей c большим удобством разработки, Ржавчина предоставляет возможности управления низкоуровневыми элементами (например, использование памяти) без трудностей, привычно связанных с таким управлением.

-

Кому подходит Rust

-

Rust наилучше подходит для многих людей по целому ряду причин. Давайте рассмотрим несколько наиболее важных объединений.

-

Объединения разработчиков

-

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

-

Rust также привносит современные средства разработчика в мир системного программирования:

-
    -
  • Cargo, входящий в состав управленец зависимостей и средство сборки, делает добавление, сборку и управление зависимостями безболезненным и согласованным в рамках всей внутреннего устройства Rust.
  • -
  • Средство изменения Rustfmt обеспечивает единый исполнение кодирования для всех разработчиков.
  • -
  • Ржавчина Language Server обеспечивает встраивание с встроенной средой разработки (IDE) для автодополнения кода и встроенных сообщений об ошибках.
  • -
-

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

-

Студенты

-

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

-

Предприятия

-

Сотни больших и малых предприятий используют Ржавчина в промышленных условиях для решения различных задач, включая средства приказной строки, веб-сервисы, средства DevOps, встраиваемые устройства, анализ и транскодирование аудио и видео, криптовалюты, биоинформатику, поисковые системы, приложения Интернета вещей, машинное обучение и даже основные части веб-браузера Firefox.

-

Разработчики Open Source

-

Rust предназначен для людей, которые хотят развивать язык программирования Rust, сообщество, средства для разработчиков и библиотеки. Мы будем рады, если вы внесёте свой вклад в развитие языка Rust.

-

Люди, ценящие скорость и безотказность

-

Rust предназначен для любителей скорости и безотказности в языке. Под скоростью мы подразумеваем как быстродействие программы на Rust, так и быстроту, с которой Ржавчина позволяет писать программы. Проверки сборщика Ржавчина обеспечивают безотказность за счёт полезных дополнений и переработки кода. Это выгодно отличается от хрупкого унаследованного кода в языках без таких проверок, который разработчики часто боятся изменять. Благодаря обеспечению абстракций с нулевой стоимостью, высокоуровневых возможностей, собираемых в низкоуровневый код такой же быстрый, как и написанный вручную, Ржавчина стремится сделать безопасный код ещё и быстрым.

-

Язык Ржавчина надеется поддержать и многих других пользователей; перечисленные здесь - лишь самые значимые увлеченные лица. В целом, главная цель Ржавчина - избавиться от соглашений, на которые программисты шли десятилетиями, обеспечив безопасность и производительность, скорость и удобство. Попробуйте Ржавчина и убедитесь, подойдут ли вам его решения.

-

Для кого эта книга

-

В этой книге предполагается, что вы писали код на другом языке программирования, но не оговаривается, на каком именно. Мы постарались сделать источник доступным для широкого круга людей с разным уровнем подготовки в области программирования. Мы не будем тратить время на обсуждение сути понятия программирования или как его понимать. Если вы совсем новичок в программировании, советуем прочитать книгу, посвящённую введению в программирование.

-

Как использовать эту книгу

-

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

-

В этой книге вы найдёте два вида глав: главы о подходах и главы с делом. В главах о подходах вы узнаете о каком-либо особенности Rust. В главах дела мы будем вместе создавать небольшие программы, применяя то, что вы уже узнали. Главы 2, 12 и 20 - это главы дела; остальные - главы о подходах.

-

Глава 1 объясняет, как установить Rust, как написать программу "Hello, world!" и как использовать Cargo, управленец дополнений и средство сборки Rust. Глава 2 - это опытное введение в написание программы на Rust, в которой вам предлагается создать игру для угадывания чисел. Здесь мы рассмотрим подходы на высоком уровне, а в последующих главах будет предоставлена дополнительная сведения. Если вы хотите сразу же приступить к работе, глава 2 - самое подходящее место для этого. В главе 3 рассматриваются возможности Rust, схожие с возможностями других языков программирования, а в главе 4 вы узнаете о системе владения Rust. Если вы особенно дотошный ученик и предпочитаете изучить каждую подробность, прежде чем переходить к следующей, возможно, вы захотите пропустить главу 2 и сразу перейти к главе 3, вернувшись к главе 2, когда захотите поработать над делом, применяя изученные подробности.

-

Глава 5 описывает устройства и способы, а глава 6 охватывает перечисления, выражения match и устройства управления потоком if let. Вы будете использовать устройства и перечисления для создания пользовательских видов в Rust.

-

В главе 7 вы узнаете о системе звеньев Rust, о правилах согласования закрытости вашего кода и его открытом внешней оболочке прикладного программирования (API). В главе 8 обсуждаются некоторые распространённые устройства данных - собрания, которые предоставляет обычная библиотека, такие как векторы, строки и HashMaps. В главе 9 рассматриваются философия и способы обработки ошибок в Rust.

-

В главе 10 рассматриваются образцовые виды данных, особенности и времена жизни, позволяющие написать код, который может использоваться разными видами. Глава 11 посвящена проверке, которое даже с заверениями безопасности в Ржавчина необходимо для обеспечения правильной логики вашей программы. В главе 12 мы создадим собственную выполнение подмножества возможности средства приказной строки grep, предназначенного для поиска текста в файлах. Для этого мы будем использовать многие подходы, которые обсуждались в предыдущих главах.

-

В главе 13 рассматриваются замыкания и повторители: особенности Rust, пришедшие из полезных языков программирования. В главе 14 мы более подробно рассмотрим Cargo и поговорим о лучших способах распространения ваших библиотек среди других разработчиков. В главе 15 обсуждаются умные указатели, которые предоставляет обычная библиотека, и особенности, обеспечивающие их возможность.

-

В главе 16 мы рассмотрим различные подходы одновременного программирования и поговорим о возможности Ржавчина для безбоязненного многопоточно программирования. В главе 17 рассматривается сравнение идиом Ржавчина с принципами предметно-направленного программирования, которые наверняка вам знакомы.

-

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

-

В главе 20 мы завершим дело, в котором выполняем низкоуровневый многопоточный веб-сервер!

-

Наконец, некоторые приложения содержат полезную сведения о языке в более справочном виде. В приложении A рассматриваются ключевые слова Rust, в приложении B — операторы и символы Rust, в приложении C — производные особенности, предоставляемые встроенной библиотекой, в приложении D — некоторые полезные средства разработки, а в приложении E — издания Rust. В приложении F вы найдёте переводы книги, а в приложении G мы расскажем о том, как создаётся Ржавчина и что такое nightly Rust.

-

Нет неправильного способа читать эту книгу: если вы хотите пропустить главу - сделайте это! Возможно, вам придётся вернуться к предыдущим главам, если возникнет недопонимание. Делайте все, как вам удобно.

-

-

Важной частью этапа обучения Ржавчина является изучение того, как читать сообщения об ошибках, которые отображает сборщик: они приведут вас к работающему коду. Мы изучим много примеров, которые не собираются и отображают ошибки в сообщениях сборщика в разных случаейх. Знайте, что если вы введёте и запустите случайный пример, он может не собраться! Убедитесь, что вы прочитали окружающий текст, чтобы понять, не предназначен ли пример, который вы пытаетесь запустить, для отображения ошибки. Ferris также поможет вам различить код, который не предназначен для работы:

-
- - - -
FerrisПояснения
Ferris with a question markЭтот код не собирается!
Феррис вскидывает рукиЭтот код вызывает панику!
Феррис с одним когтем вверх, пожимая плечамиЭтот код не приводит к желаемому поведению.
-
-

В большинстве случаев мы приведём вас к правильной исполнения любого кода, который не собирается.

-

Исходные коды

-

Файлы с исходным кодом, используемым в этой книге, можно найти на GitHub.

-

Начало работы

-

Начнём наше путешествие в Rust! Нужно много всего изучить, но каждое путешествие с чего-то начинается. В этой главе мы обсудим:

-
    -
  • установку Ржавчина на Linux, macOS и Windows,
  • -
  • написание программы, печатающей Hello, world!,
  • -
  • использование cargo, управленца дополнений и системы сборки в одном лице для Rust.
  • -
-

Установка

-

Первым шагом является установка Rust. Мы загрузим Rust, используя средство приказной строки rustup, предназначенный для управлениями исполнениями Ржавчина и другими связанными с ним средствами. Вам понадобится интернет-соединение для его загрузки.

-
-

Примечание: если вы по каким-то причинам предпочитаете не использовать rustup, пожалуйста, посетите страницу «Другие способы установки Rust» для получения дополнительных возможностей.

-
-

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

-
-

Условные обозначения приказной строки

-

В этой главе и во всей книге мы будем выполнять некоторые приказы, используемые в окне вызова. Строки, которые вы должны вводить в окне вызова, начинаются с $. Вам не нужно вводить символ $; это подсказка приказной строки, отображаемая для обозначения начала каждой приказы. Строки, которые не начинаются с $, обычно показывают вывод предыдущей приказы. Кроме того, в примерах, своеобразных для PowerShell, будет использоваться >, а не $.

-
-

Установка rustup на Linux или macOS

-

Если вы используете Linux или macOS, пожалуйста, выполните следующую приказ:

-
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
-
-

Приказ загружает сценарий и запускает установку средства rustup, который устанавливает последнюю безотказную исполнение Rust. Вам может быть предложено ввести пазначение. Если установка прошла успешно, появится следующая строка:

-
Rust is installed now. Great!
-
-

Вам также понадобится составитель (linker) — программа, которую Ржавчина использует для объединения своих собранных выходных данных в один файл. Скорее всего, он у вас уже есть. При возникновении ошибок объединения, вам следует установить сборщик C, который обычно будет включать в себя и составитель. Сборщик C также полезен, потому что некоторые распространённые дополнения Ржавчина зависят от кода C и нуждаются в сборщике C.

-

На macOS вы можете получить сборщик C, выполнив приказ:

-
$ xcode-select --install
-
-

Пользователи Linux, как правило, должны устанавливать GCC или Clang в соответствии с документацией их установочного набора. Например, при использовании Ubuntu можно установить дополнение build-essential.

-

Установка rustup на Windows

-

На Windows перейдите по адресу https://www.rust-lang.org/tools/install и следуйте указаниям по установке Rust. На определённом этапе установки вы получите сообщение, предупреждающее, что вам также понадобятся средства сборки MSVC для Visual Studio 2013 или более поздней исполнения.

-

Чтобы получить средства сборки, вам потребуется установить Visual Studio 2022. На вопрос о том, какие составляющие необходимо установить, выберите:

-
    -
  • “Desktop Development with C++”
  • -
  • The Windows 10 or 11 SDK
  • -
  • Английский языковой дополнение вместе с любым другим языковым дополнением по вашему выбору.
  • -
-

В остальной части этой книги используются приказы, которые работают как в cmd.exe, так и в PowerShell. При наличии отличительных различий мы объясним, что необходимо сделать в таких случаях.

-

Устранение возможных ошибок

-

Чтобы проверить, правильно ли у вас установлен Rust, откройте оболочку и введите эту строку:

-
$ rustc --version
-
-

Вы должны увидеть номер исполнения, хэш определения и дату определения для последней безотказной исполнения, которая была выпущена, в следующем виде:

-
rustc x.y.z (abcabcabc yyyy-mm-dd)
-
-

Если вы видите эту сведения, вы успешно установили Rust! Если вы не видите эту сведения, убедитесь, что Ржавчина находится в вашей системной переменной %PATH% следующим образом:

-

В Windows CMD:

-
> echo %PATH%
-
-

В PowerShell:

-
> echo $env:Path
-
-

В Linux и macOS:

-
$ echo $PATH
-
-

Если все было сделано правильно, но Ржавчина все ещё не работает, есть несколько мест, где вам могут помочь. Узнайте, как связаться с другими Rustaceans (так мы себя называем) на странице сообщества.

-

Обновление и удаление

-

После установки Ржавчина с помощью rustup обновление до новой исполнения не составит труда. В приказной оболочке запустите следующий скрипт обновления:

-
$ rustup update
-
-

Чтобы удалить Ржавчина и rustup, выполните следующую приказ:

-
$ rustup self uninstall
-
-

Местная документация

-

Установка Ржавчина также включает местную повтор документации, чтобы вы могли читать её в без доступа к мировой сети режиме. Выполните rustup doc, чтобы открыть местную документацию в браузере.

-

Если обычная библиотека предоставляет вид или функцию, а вы не знаете, что она делает или как её использовать, воспользуйтесь документацией внешней оболочки прикладного программирования (API), чтобы это узнать!

-

Привет, мир!

-

Теперь, когда вы установили Rust, пришло время написать свою первую программу на Rust. Привычно при изучении нового языка принято писать небольшую программу, которая печатает на экране текст Привет, мир!, поэтому мы сделаем то же самое!

-
-

Примечание: Эта книга предполагает наличие достаточного навыка работы с приказной строкой. Ржавчина не предъявляет особых требований к тому, каким набором средств вы пользуетесь для изменения или хранения вашего кода, поэтому если вы предпочитаете использовать встроенную среду разработки (IDE) вместо приказной строки, смело используйте вашу любимую IDE. Многие IDE сейчас в той или иной степени поддерживают Rust; подробности можно узнать из документации к IDE. Объединение Ржавчина сосредоточилась на обеспечении отличной поддержки IDE с помощью rust-analyzer. Более подробную сведения смотрите в Приложении D.

-
-

Создание папки дела

-

Прежде всего начнём с создания папки, в которой будем сохранять наш код на языке Rust. На самом деле не важно, где сохранять наш код. Однако, для упражнений и дел, обсуждаемых в данной книге, мы советуем создать папку projects в вашем домашнем папке, там же и хранить в будущем код программ из книги.

-

Откройте окно вызова и введите следующие приказы для того, чтобы создать папку projects для хранения кода разных дел, и, внутри неё, папку hello_world для дела “Привет, мир!”.

-

Для Linux, macOS и PowerShell на Windows, введите:

-
$ mkdir ~/projects
-$ cd ~/projects
-$ mkdir hello_world
-$ cd hello_world
-
-

Для Windows в CMD, введите:

-
> mkdir "%USERPROFILE%\projects"
-> cd /d "%USERPROFILE%\projects"
-> mkdir hello_world
-> cd hello_world
-
-

Написание и запуск первой Ржавчина программы

-

Затем создайте новый исходный файл и назовите его main.rs. Файлы Ржавчина всегда заканчиваются расширением .rs. Если вы используете более одного слова в имени файла, принято разделять их символом подчёркивания. Например, используйте hello_world.rs вместо helloworld.rs.

-

Теперь откроем файл main.rs для изменения и введём следующие строки кода:

-

Название файла: main.rs

-
fn main() {
-    println!("Привет, мир!");
-}
-

Приложение 1-1: Программа, которая печатает Привет, мир!

-

Сохраните файл и вернитесь в окно окна вызова в папка ~/projects/hello_world. В Linux или macOS введите следующие приказы для сборки и запуска файла:

-
$ rustc main.rs
-$ ./main
-Привет, мир!
-
-

В Windows, введите приказ .\main.exe вместо ./main:

-
> rustc main.rs
-> .\main.exe
-Привет, мир!
-
-

Независимо от вашей операционной системы, строка Привет, мир! должна быть выведена на окно вызова. Если вы не видите такого вывода, обратитесь к разделу "Устранение неполадок ", чтобы узнать, как получить помощь.

-

Если напечаталось Привет, мир!, то примите наши поздравления! Вы написали программу на Rust, что делает вас Ржавчина программистом — добро пожаловать!

-

Анатомия программы на Rust

-

Давайте рассмотрим «Привет, мир!» программу в подробностях. Вот первая часть головоломки:

-
fn main() {
-
-}
-

Эти строки определяют функцию с именем main. Функция main особенная: это всегда первый код, который запускается в каждой исполняемой программе Rust. Первая строка объявляет функцию с именем main, которая не имеет свойств и ничего не возвращает. Если бы были свойства, они бы заключались в круглые скобки ().

-

Тело функции заключено в {}. Ржавчина требует фигурных скобок вокруг всех тел функций. Хороший исполнение — поместить открывающую фигурную скобку на ту же строку, что и объявление функции, добавив между ними один пробел.

-
-

Примечание: Если хотите придерживаться принятого исполнения во всех делах Rust, вы можете использовать средство самостоятельного изменения под названием rustfmt для изменения кода в определённом исполнении (подробнее о rustfmt в Приложении D. Объединение Ржавчина включила этот средство в обычный установочный набор Rust, как rustc, поэтому он уже должен быть установлен на вашем компьютере!

-
-

Тело функции main содержит следующий код:

-
#![allow(unused)]
-fn main() {
-    println!("Привет, мир!");
-}
-

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

-

Во-первых, исполнение Ржавчина предполагает отступ в четыре пробела, а не табуляцию.

-

Во-вторых, println! вызывается макрос Rust. Если бы вместо него была вызвана функция, она была бы набрана как println (без !). Более подробно мы обсудим макросы Ржавчина в главе 19. Пока достаточно знать, что использование ! подразумевает вызов макроса вместо обычной функции, и что макросы не всегда подчиняются тем же правилам как функции.

-

В-третьих, вы видите строку "Привет, мир!". Мы передаём её в качестве переменной макросу println!, и она выводится на экран.

-

В-четвёртых, мы завершаем строку точкой с запятой (;), которая указывает на окончание этого выражения и возможность начала следующего. Большинство строк кода Ржавчина заканчиваются точкой с запятой.

-

Сборка и запуск - это отдельные шаги

-

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

-

Перед запуском программы на Ржавчина вы должны собрать её с помощью сборщика Rust, введя приказ rustc и передав ей имя вашего исходного файла, например:

-
$ rustc main.rs
-
-

Если у вас есть опыт работы с C или C++, вы заметите, что это похоже на gcc или clang. После успешной сборки Ржавчина выводит двоичный исполняемый файл.

-

В Linux, macOS и PowerShell в Windows вы можете увидеть исполняемый файл, введя приказ ls в оболочке:

-
$ ls
-main  main.rs
-
-

В Linux и macOS вы увидите два файла. При использовании PowerShell в Windows вы увидите такие же три файла, как и при использовании CMD. Используя CMD в Windows, введите следующее:

-
> dir /B %= the /B option says to only show the file names =%
-main.exe
-main.pdb
-main.rs
-
-

Это показывает исходный код файла с расширением .rs, исполняемый файл (main.exe на Windows, но main на всех других площадках) и, при использовании Windows, файл, содержащий отладочную сведения с расширением .pdb. Отсюда вы запускаете файлы main или main.exe, например:

-
$ ./main # для Linux
-> .\main.exe # для Windows
-
-

Если ваш main.rs — это ваша программа «Привет, мир!», эта строка выведет в окно вызова Привет, мир!.

-

Если вы лучше знакомы с изменяемыми языками, такими как Ruby, Python или JavaScript, возможно, вы не привыкли собирать и запускать программу как отдельные шаги. Ржавчина — это предварительно собранный язык, то есть вы можете собрать программу и передать исполняемый файл кому-то другому, и он сможет запустить его даже без установленного Rust. Если вы даёте кому-то файл .rb , .py или .js, у него должна быть установлена выполнение Ruby, Python или JavaScript (соответственно). Но в этих языках вам нужна только одна приказ для сборки и запуска вашей программы. В внешнем виде языков программирования всё — соглашение.

-

Сборка с помощью rustc подходит для простых программ, но по мере роста вашего дела вы захотите управлять всеми свойствами и упростить передачу кода. Далее мы познакомим вас с средством Cargo, который поможет вам писать программы из существующего мира на Rust.

-

Привет, Cargo!

-

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

-

Самые простые программы на Rust, подобные той, которую мы написали, не имеют никаких зависимостей. Если бы мы сделали дело «Hello, world!» с Cargo, он бы использовал только ту часть Cargo, которая отвечает за сборку вашего кода. По мере написания более сложных программ на Ржавчина вы будете добавлять зависимости, а если вы начнёте дело с использованием Cargo, добавлять зависимости станет намного проще.

-

Поскольку значительное число дел Ржавчина используют Cargo, оставшаяся часть книги подразумевает, что вы тоже используете Cargo. Cargo входит в состав поставки Rust, если вы использовали напрямую от разрабочиков программы установки, рассмотренные в разделе "Установка". Если вы установили Ржавчина другим способом, проверьте, установлен ли Cargo, введя в окне вызова следующее:

-
$ cargo --version
-
-

Если приказ выдал номер исполнения, то значит Cargo установлен. Если вы видите ошибку, вроде command not found ("приказ не найдена"), загляните в документацию для использованного вами способа установки, чтобы выполнить установку Cargo отдельно.

-

Создание дела с помощью Cargo

-

Давайте создадим новый дело с помощью Cargo и посмотрим, как он отличается от нашего начального дела "Hello, world!". Перейдите обратно в папку projects (или любую другую, где вы решили сохранять код). Затем, в любой операционной системе, запустите приказ:

-
$ cargo new hello_cargo
-$ cd hello_cargo
-
-

Первая приказ создаёт новый папка и дело с именем hello_cargo. Мы назвали наш дело hello_cargo, и Cargo создаёт свои файлы в папке с тем же именем.

-

Перейдём в папка hello_cargo и посмотрим файлы. Увидим, что Cargo создал два файла и одну папку: файл Cargo.toml и папка src с файлом main.rs внутри.

-

Кроме того, cargo объявлял новый хранилище Git вместе с файлом .gitignore. Файлы Git не будут созданы, если вы запустите cargo new в существующем хранилища Git; вы можете изменить это поведение, используя cargo new --vcs=git.

-
-

Примечание. Git — это распространённая система управления исполнений. Вы можете изменить cargo new, чтобы использовать другую систему управления исполнений или не использовать систему управления исполнений, используя флаг --vcs. Запустите cargo new --help, чтобы увидеть доступные свойства.

-
-

Откройте файл Cargo.toml в любом текстовом редакторе. Он должен выглядеть как код в приложении 1-2.

-

Файл: Cargo.toml

-
[package]
-name = "hello_cargo"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-
-

Приложение 1-2: Содержимое файла Cargo.toml, созданное приказом cargo new

-

Это файл в видеTOML (Tom’s Obvious, Minimal Language), который является видом настроек Cargo.

-

Первая строка, [package], является заголовочной разделом, которая указывает что следующие указания настраивают дополнение. По мере добавления больше сведений в данный файл, будет добавляться больше разделов и указаний (строк).

-

Следующие три строки задают сведения о настройке, необходимую Cargo для сборки вашей программы: имя, исполнение и издание Rust, который будет использоваться. Мы поговорим о ключе edition в Приложении E.

-

Последняя строка, [dependencies] является началом разделы для списка любых зависимостей вашего дела. В Rust, это внешние дополнения кода, на которые ссылаются ключевым словом crate. Нам не нужны никакие зависимости в данном деле, но мы будем использовать их в первом деле главы 2, так что нам пригодится данная раздел зависимостей потом.

-

Откройте файл src/main.rs и загляните в него:

-

Файл: src/main.rs

-
fn main() {
-    println!("Hello, world!");
-}
-

Cargo создал для вас программу "Hello, world!", подобную той, которую мы написали в Приложении 1-1! Пока что различия между нашим предыдущим делом и делом, созданным при помощи Cargo, заключаются в том, что Cargo поместил исходный код в папка src, и у нас есть настроечный файл Cargo.toml в верхнем папке дела.

-

Cargo ожидает, что ваши исходные файлы находятся внутри папки src. Папка верхнего уровня дела предназначен только для файлов README, сведений о лицензии, файлы настройке и чего то ещё не относящего к вашему коду. Использование Cargo помогает создавать дело. Есть место для всего и все находится на своём месте.

-

Если вы начали дело без использования Cargo, как мы делали для "Hello, world!" дела, то можно преобразовывать его в дело с использованием Cargo. Переместите код в подпапка src и создайте соответствующий файл Cargo.toml в папке.

-

Сборка и запуск Cargo дела

-

Посмотрим, в чем разница при сборке и запуске программы "Hello, world!" с помощью Cargo. В папке hello_cargo соберите дело следующей приказом:

-
$ cargo build
-   Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
-    Finished dev [unoptimized + debuginfo] target(s) in 2.85 secs
-
-

Этот приказ создаёт исполняемый файл в target/debug/hello_cargo (или target\debug\hello_cargo.exe в Windows), а не в вашем текущем папке. Поскольку обычная сборка является отладочной, Cargo помещает двоичный файл в папка с именем debug. Вы можете запустить исполняемый файл с помощью этой приказы:

-
$ ./target/debug/hello_cargo # or .\target\debug\hello_cargo.exe on Windows
-Hello, world!
-
-

Если все хорошо, то Hello, world! печатается в окне вызова. Запуск приказы cargo build в первый раз также приводит к созданию нового файла Cargo.lock в папке верхнего уровня. Данный файл хранит точные исполнения зависимостей вашего дела. Так как у нас нет зависимостей, то файл пустой. Вы никогда не должны менять этот файл вручную: Cargo сам управляет его содержимым для вас.

-

Только что мы собрали дело приказом cargo build и запустили его из ./target/debug/hello_cargo. Но мы также можем при помощи приказы cargo run сразу и собрать код, и затем запустить полученный исполняемый файл всего лишь одной приказом:

-
$ cargo run
-    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
-     Running `target/debug/hello_cargo`
-Hello, world!
-
-

Использование cargo run более удобно, чем необходимость помнить и запускать cargo build, а затем использовать весь путь к двоичному файлу, поэтому большинство разработчиков используют cargo run.

-

Обратите внимание, что на этот раз мы не видели вывода, указывающего на то, что Cargo собирает hello_cargo. Cargo выяснил, что файлы не изменились, поэтому не стал пересобирать, а просто запустил двоичный файл. Если бы вы изменили свой исходный код, Cargo пересобрал бы дело перед его запуском, и вы бы увидели этот вывод:

-
$ cargo run
-   Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.33 secs
-     Running `target/debug/hello_cargo`
-Hello, world!
-
-

Cargo также предоставляет приказ, называемую cargo check. Этот приказ быстро проверяет ваш код, чтобы убедиться, что он собирается, но не создаёт исполняемый файл:

-
$ cargo check
-   Checking hello_cargo v0.1.0 (file:///projects/hello_cargo)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs
-
-

Почему вам не нужен исполняемый файл? Часто cargo check выполняется намного быстрее, чем cargo build, поскольку пропускает этап создания исполняемого файла. Если вы постоянно проверяете свою работу во время написания кода, использование cargo check ускорит этап уведомления вас о том, что ваш дело всё ещё собирается! Таким образом, многие Rustacean периодически запускают cargo check, когда пишут свои программы, чтобы убедиться, что она собирается. Затем они запускают cargo build, когда готовы использовать исполняемый файл.

-

Давайте подытожим, что мы уже узнали о Cargo:

-
    -
  • Мы можем создать дело с помощью cargo new.
  • -
  • можно собирать дело, используя приказ cargo build,
  • -
  • можно одновременно собирать и запускать дело одной приказом cargo run,
  • -
  • можно собрать дело для проверки ошибок с помощью cargo check, не тратя время на кодосоздание исполняемого файла,
  • -
  • cargo сохраняет итоги сборки не в папку с исходным кодом, а в отдельный папка target/debug.
  • -
-

Дополнительным преимуществом использования Cargo является то, что его приказы одинаковы для разных операционных систем. С этой точки зрения, мы больше не будем предоставлять отдельные указания для Linux, macOS или Windows.

-

Сборка конечной исполнения (Release)

-

Когда дело, наконец, готов к исполнению, можно использовать приказ cargo build --release для его сборки с переработкой. Данная приказ создаёт исполняемый файл в папке target/release в отличии от папки target/debug. Переработки делают так, что Ржавчина код работает быстрее, но их включение увеличивает время сборки. По этой причине есть два отдельных профиля: один для разработки, когда нужно осуществлять сборку быстро и часто, и другой, для сборки конечной программы, которую будете отдавать пользователям, которая готова к работе и будет выполняться сверх быстро. Если вы замеряете время выполнения вашего кода, убедитесь, что собрали дело с переработкой cargo build --release и проверяете исполняемый файл из папки target/release.

-

Cargo как Условие

-

В простых делах Cargo не даёт больших преимуществ по сравнению с использованием rustc, но он проявит себя, когда ваши программы станут более сложными. Когда программы вырастают до нескольких файлов или нуждаются в зависимостях, гораздо проще позволить Cargo согласовывать сборку.

-

Не смотря на то, что дело hello_cargo простой, теперь он использует большую часть существующего набора средств, который вы будете повседневно использовать в вашей развитии, связанной с Rust. Когда потребуется работать над делами размещёнными в сети, вы сможете просто использовать следующую последовательность приказов для получения кода с помощью Git, перехода в папка дела, сборку дела:

-
$ git clone example.org/someproject
-$ cd someproject
-$ cargo build
-
-

Для получения дополнительной сведений о Cargo ознакомьтесь с его документацией .

-

Итоги

-

Теперь вы готовы начать своё Ржавчина путешествие! В данной главе вы изучили как:

-
    -
  • установить последнюю безотказную исполнение Rust, используя rustup,
  • -
  • обновить Ржавчина до последней исполнения,
  • -
  • открыть местно установленную документацию,
  • -
  • написать и запустить программу вида "Hello, world!", используя напрямую сборщик rustc,
  • -
  • создать и запустить новый дело, используя соглашения и приказы Cargo.
  • -
-

Это отличное время для создания более существенной программы, чтобы привыкнуть читать и писать код на языке Rust. Итак, в главе 2 мы построим программу для игры в угадай число. Если вы предпочитаете начать с изучения того, как работают общие подходы программирования в Rust, обратитесь к главе 3, а затем вернитесь к главе 2.

-

Программируем игру в загадки

-

Давайте окунёмся в Rust, вместе поработав над опытным делом! В этой главе вы познакомитесь с несколькими общими подходами Rust, показав, как использовать их в существующей программе. Вы узнаете о let , match, способах, сопряженных функциях, внешних дополнениях и многом другом! В следующих главах мы рассмотрим эти мысли более подробно. В этой главе вы просто примените в основах.

-

Мы выполняем привычную для начинающих программистов задачу — игру в загадки. Вот как это работает: программа порождает случайное целое число в ряде от 1 до 100. Затем она предлагает игроку его угадать. После ввода числа программа укажет, меньше или больше было загаданное число. Если догадка верна, игра напечатает поздравительное сообщение и завершится.

-

Настройка нового дела

-

Для настройки нового дела перейдите в папка projects, который вы создали в главе 1, и создайте новый дело с использованием Cargo, как показано ниже:

-
$ cargo new guessing_game
-$ cd guessing_game
-
-

Первая приказ, cargo new, принимает в качестве первого переменной имя дела (guessing_game). Вторая приказ изменяет папка на новый папка дела.

-

Загляните в созданный файл Cargo.toml:

- -

Файл: Cargo.toml

-
[package]
-name = "guessing_game"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-
-

Как вы уже видели в главе 1, cargo new создаёт программу «Hello, world!». Посмотрите файл src/main.rs:

-

Файл: src/main.rs

-
fn main() {
-    println!("Hello, world!");
-}
-

Теперь давайте соберем программу «Hello, world!» и сразу на этом же этапе запустим её с помощью приказы cargo run:

-
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.50s
-     Running `target/debug/guessing_game`
-Hello, world!
-
-

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

-

Снова откройте файл src/main.rs. Весь код вы будете писать в нем.

-

Обработка догадки

-

Первая часть программы запрашивает ввод данных пользователем, обрабатывает их и проверяет, что они в ожидаемой виде. Начнём с того, что позволим игроку ввести догадку. Вставьте код из приложения 2-1 в src/main.rs.

-

Файл: src/main.rs

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Приложение 2-1: код, который получает догадку от пользователя и выводит её на экран

-

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

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

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

-

Если вид, который требуется использовать, отсутствует в прелюдии, его нужно явно ввести в область видимости с помощью оператора use. Использование библиотеки std::io предоставляет ряд полезных полезных возможностей, включая способность принимать пользовательский ввод.

-

Как уже отмечалось в главе 1, функция main является точкой входа в программу:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Ключевое слово fn объявляет новую функцию, круглые скобки () показывают, что у функции нет входных свойств, фигурная скобка { - обозначение начала тела функции.

-

Также в главе 1 упоминалось, что println! — это макрос, который выводит строку на экран:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Этот код показывает сведения о ходе игры и запрашивает пользовательский ввод.

-

Хранение значений с помощью переменных

-

Далее мы создаём переменную для хранения пользовательского ввода, как показано ниже:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Вот теперь программа становится важнее! В этой маленькой строке на самом деле происходит очень многое. Для создания переменной мы используем оператор let. Вот ещё один пример:

-
let apples = 5;
-

Эта строка создаёт новую переменную с именем apples и привязывает её к значению 5. В Ржавчина переменные неизменяемы по умолчанию, то есть как только мы присвоим переменной значение, оно не изменится. Мы подробно обсудим эту подход в разделе "Переменные и изменчивость". в главе 3. Чтобы сделать переменную изменяемой, мы добавляем mut перед её именем:

-
let apples = 5; // неизменяемая
-let mut bananas = 5; // изменяемая
-
-

Примечание: сочетание знаков // начинает примечание, который продолжается до конца строки. Ржавчина пренебрегает всё, что находится в примечаниях. Мы обсудим примечания более подробно в Главе 3.

-
-

Возвращаясь к программе игры "Угадайка" — теперь вы знаете, что let mut guess предоставит изменяемую переменную с именем guess. Знак равенства (=) сообщает Rust, что сейчас нужно связать что-то с этой переменной. Справа от знака равенства находится значение, связанное с guess, которое является итогом вызова функции String::new, возвращающей новый образец String. String — это вид строки, предоставляемый встроенной библиотекой, который является расширяемым отрывком текста в кодировке UTF-8.

-

правила написания :: в строке ::new указывает, что new является сопряженной функцией вида String. Сопряженная функция — это функция, выполненная для вида, в данном случае String. Функция new создаёт новую пустую строку. Функцию new можно встретить во многих видах, это привычное название для функции, которая создаёт новое значение какого-либо вида.

-

В конечном итоге строка let mut guess = String::new(); создала изменяемую переменную, которая связывается с новым пустым образцом String. Фух!

-

Получение пользовательского ввода

-

Напомним: мы подключили возможность ввода/вывода из встроенной библиотеки с помощью use std::io; в первой строке программы. Теперь мы вызовем функцию stdin из звена io, которая позволит нам обрабатывать пользовательский ввод:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Если бы мы не подключили библиотеку io с помощью use std::io в начале программы, мы все равно могли бы использовать эту функцию, записав её вызов как std::io::stdin. Функция stdin возвращает образец std::io::Stdin, который является видом, представляющим указатель принятого ввода для вашего окна вызова.

-

Далее строка .read_line(&mut guess) вызывает способ read_line на указателе принятого ввода для получения ввода от пользователя. Мы также передаём &mut guess в качестве переменной read_line, сообщая ему, в какой строке хранить пользовательский ввод. Главная задача read_line — принять все, что пользователь вводит в обычный ввод, и сложить это в строку (не переписывая её содержимое), поэтому мы передаём эту строку в качестве переменной. Строковый переменная должен быть изменяемым, чтобы способ мог изменить содержимое строки.

-

Символ & указывает, что этот переменная является ссылкой, которая предоставляет возможность нескольким частям вашего кода получить доступ к одному отрывку данных без необходимости воспроизводить эти данные в память несколько раз. Ссылки — это сложная полезная возможность, а одним из главных преимуществ Ржавчина является безопасность и простота использования ссылок. Чтобы дописать эту программу, вам не понадобится знать много таких подробностей. Пока вам достаточно знать, что ссылки, как и переменные, по умолчанию неизменяемы. Соответственно, чтобы сделать её изменяемой, нужно написать &mut guess, а не &guess. (В главе 4 ссылки будут описаны более подробно).

- -

-

Обработка возможного сбоя с помощью вида Result

-

Мы всё ещё работаем над этой строкой кода. Сейчас мы обсуждаем третью строку, но обратите внимание, что она по-прежнему является частью одной логической строки. Следующая часть — способ:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Мы могли бы написать этот код так:

-
io::stdin().read_line(&mut guess).expect("Failed to read line");
-

Однако одну длинную строку трудно читать, поэтому лучше разделить её. При вызове способа с помощью правил написания .method_name() часто целесообразно вводить новую строку и другие пробельные символы, чтобы разбить длинные строки. Теперь давайте обсудим, что делает эта строка.

-

Как упоминалось ранее, read_line помещает всё, что вводит пользователь, в строку, которую мы ему передаём, но также возвращает значение Result. Result — это перечисление, часто называемое enum, то есть вид, который может находиться в одном из нескольких возможных состояний. Мы называем каждое такое состояние исходом.

-

В Главе 6 рассмотрим перечисления более подробно. Задачей видов Result является кодирование сведений для обработки ошибок.

-

Исходами Result являются Ok и Err. Исход Ok указывает, что действие завершилась успешно, а внутри Ok находится успешно созданное значение. Исход Err означает, что действие не удалась, а Err содержит сведения о причинах неудачи.

-

Значения вида Result, как и значения любого вида, имеют определённые для них способы. У образца Result есть способ expect, который можно вызвать. Если этот образец Result является значением Err, expect вызовет сбой программы и отобразит сообщение, которое вы передали в качестве переменной. Если способ read_line возвращает Err, то это, скорее всего, итог ошибки основной операционной системы. Если образец Result является значением Ok, expect возьмёт возвращаемое значение, которое удерживает Ok, и вернёт вам только это значение, чтобы вы могли его использовать далее. В данном случае это значение представляет собой количество байтов, введённых пользователем.

-

Если не вызвать expect, программа собирается, но будет получено предупреждение:

-
$ cargo build
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-warning: unused `Result` that must be used
-  --> src/main.rs:10:5
-   |
-10 |     io::stdin().read_line(&mut guess);
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: this `Result` may be an `Err` variant, which should be handled
-   = note: `#[warn(unused_must_use)]` on by default
-help: use `let _ = ...` to ignore the resulting value
-   |
-10 |     let _ = io::stdin().read_line(&mut guess);
-   |     +++++++
-
-warning: `guessing_game` (bin "guessing_game") generated 1 warning
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.59s
-
-

Rust предупреждает о неиспользованном значении Result, возвращаемого из read_line, показывая, что программа не учла возможность возникновения ошибки.

-

Правильный способ убрать предупреждение — это написать обработку ошибок, но в нашем случае мы просто хотим со сбоем завершить программу при возникновении сбоев, поэтому используем expect. О способах восстановления после ошибок вы узнаете в главе 9.

-

Вывод значений с помощью заполнителей println!

-

Кроме закрывающей фигурной скобки, в коде на данный мгновение есть ещё только одно место для обсуждения:

-
use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {}", guess);
-}
-

Этот код выводит строку, которая теперь содержит ввод пользователя. Набор фигурных скобок {} является заполнителем: думайте о {} как о маленьких клешнях краба, которые удерживают значение на месте. При печати значения переменной имя переменной может заключаться в фигурные скобки. При печати итога вычисления выражения поместите пустые фигурные скобки в строку вида, затем после строки вида укажите список выражений, разделённых запятыми, которые будут напечатаны в каждом заполнителе пустой фигурной скобки в том же порядке. Печать переменной и итога выражения одним вызовом println! будет выглядеть так:

-
#![allow(unused)]
-fn main() {
-let x = 5;
-let y = 10;
-
-println!("x = {x} and y + 2 = {}", y + 2);
-}
-

Этот код выведет x = 5 and y + 2 = 12.

-

Проверка первой части

-

Давайте проверим первую часть игры. Запустите её используя cargo run:

- -
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 6.44s
-     Running `target/debug/guessing_game`
-Guess the number!
-Please input your guess.
-6
-You guessed: 6
-
-

На данном этапе первая часть игры завершена: мы получаем ввод с клавиатуры и затем печатаем его.

-

Создание тайного числа

-

Далее нам нужно создать тайное число, которое пользователь попытается угадать. Тайное число должно быть каждый раз разным, чтобы в игру можно было играть несколько раз. Мы будем использовать случайное число в ряде от 1 до 100, чтобы игра не была слишком сложной. Ржавчина пока не включает возможность случайных чисел в свою обычную библиотеку. Однако приказ Ржавчина предоставляет [ящик rand] с подобной возможностью.

-

Использование ящика для получения дополнительного возможностей

-

Помните, что дополнение (crate) - это собрание файлов исходного кода Rust. Дело, создаваемый нами, представляет собой
двоичный дополнение (binary crate), который является исполняемым файлом. Дополнение rand - это библиотечный дополнение (library crate), содержащий код, который предназначен для использования в других программах и поэтому не может исполняться сам по себе.

-

Согласование работы внешних дополнений является тем местом, где Cargo на самом деле блистает. Чтобы начать писать код, использующий rand, необходимо изменить файл Cargo.toml, включив в него в качестве зависимости дополнение rand. Итак, откройте этот файл и добавьте следующую строку внизу под заголовком разделы [dependencies], созданным для вас Cargo. Обязательно укажите rand в точности так же, как здесь, с таким же номером исполнения, иначе примеры кода из этого урока могут не заработать.

- -

Имя файла: Cargo.toml

-
[dependencies]
-rand = "0.8.5"
-
-

В файле Cargo.toml всё, что следует за заголовком, является частью этой разделы, которая продолжается до тех пор, пока не начнётся следующая. В [dependencies] вы сообщаете Cargo, от каких внешних ящиков зависит ваш дело и какие исполнения этих ящиков вам нужны. В этом случае мы указываем ящик rand со определетелем смысловой исполнения 0.8.5. Cargo понимает смысловое управление исполнениями (иногда называемое SemVer), которое является исполнением для описания исполнений. Число 0.8.5 на самом деле является сокращением от ^0.8.5, что означает любую исполнение не ниже 0.8.5, но ниже 0.9.0.

-

Cargo рассчитывает, что эти исполнения имеют общедоступное API, совместимое с исполнением 0.8.5, и вы получите последние исполнения исправлений, которые по-прежнему будут собираться с кодом из этой главы. Не обеспечивается, что исполнение 0.9.0 или выше будет иметь тот же API, что и в следующих примерах.

-

Теперь, не меняя ничего в коде, давайте соберём дело, как показано в приложении 2-2.

- -
$ cargo build
-    Updating crates.io index
-  Downloaded rand v0.8.5
-  Downloaded libc v0.2.127
-  Downloaded getrandom v0.2.7
-  Downloaded cfg-if v1.0.0
-  Downloaded ppv-lite86 v0.2.16
-  Downloaded rand_chacha v0.3.1
-  Downloaded rand_core v0.6.3
-   Compiling libc v0.2.127
-   Compiling getrandom v0.2.7
-   Compiling cfg-if v1.0.0
-   Compiling ppv-lite86 v0.2.16
-   Compiling rand_core v0.6.3
-   Compiling rand_chacha v0.3.1
-   Compiling rand v0.8.5
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 2.53s
-
-

Приложение 2-2: итог выполнения cargo build после добавления ящика rand в качестве зависимости

-

Вы можете увидеть другие номера исполнений (но все они будут совместимы с кодом благодаря SemVer), другие строки (в зависимости от операционной системы), а также строки могут быть расположены в другом порядке.

-

Когда мы включаем внешнюю зависимость, Cargo берет последние исполнения всего, что нужно этой зависимости, из реестра (registry), который является повтором данных с Crates.io. Crates.io — это место, где участники внутреннего устройства Ржавчина размещают свои дела с открытым исходным кодом для использования другими.

-

После обновления реестра Cargo проверяет раздел [dependencies] и загружает все указанные в списке дополнения, которые ещё не были загружены. В нашем случае, хотя мы указали только rand в качестве зависимости, Cargo также захватил другие дополнения, от которых зависит работа rand. После загрузки дополнений Ржавчина собирает их, а затем собирает дело с имеющимися зависимостями.

-

Если сразу же запустить cargo build снова, не внося никаких изменений, то кроме строки Finished вы не получите никакого вывода. Cargo знает, что он уже загрузил и собрал зависимости, и вы не вносили никаких изменений в файл Cargo.toml. Cargo также знает, что вы ничего не изменили в своём коде, поэтому он не пересоберет и его. Если делать нечего, он просто завершает работу.

-

Если вы откроете файл src/main.rs, внесёте обыкновенное изменение, а затем сохраните его и снова соберёте, вы увидите только две строки вывода:

- -
$ cargo build
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
-
-

Эти строки показывают, что Cargo обновляет сборку только с вашим крошечным изменением в файле src/main.rs. Ваши зависимости не изменились, поэтому Cargo знает, что может повторно использовать то, что уже скачано и собрано для них.

-

Обеспечение воспроизводимых сборок с помощью файла Cargo.lock

-

В Cargo есть рычаг, обеспечивающий возможность пересобрать всё тот же артефакт каждый раз, когда вы или кто-либо другой собирает ваш код. Пока вы не укажете обратное, Cargo будет использовать только те исполнения зависимостей, которые были заданы ранее. Например, допустим, что на следующей неделе выходит исполнение 0.8.6 дополнения rand , и она содержит важное исправление ошибки, но также отступление, которая может сломать ваш код. Чтобы справиться с этим, Ржавчина создаёт файл Cargo.lock при первом запуске cargo build, поэтому теперь он есть в папке guessing_game.

-

Когда вы создаёте дело в первый раз, Cargo определяет все исполнения зависимостей, которые соответствуют условиям, а затем записывает их в файл Cargo.lock. Когда вы будете собирать свой дело в будущем, Cargo увидит, что файл Cargo.lock существует, и будет использовать указанные там исполнения, а не выполнять всю работу по выяснению исполнений заново. Это позволяет самостоятельно создавать воспроизводимую сборку. Другими словами, ваш дело останется на 0.8.5 до тех пор, пока вы явно не обновите его благодаря файлу Cargo.lock. Поскольку файл Cargo.lock важен для воспроизводимых сборок, он часто хранится в системе управления исполнениями вместе с остальным кодом дела.

-

Обновление дополнения для получения новой исполнения

-

Если вы захотите обновить дополнение, Cargo предоставляет приказ update, которая пренебрегает файл Cargo.lock и определяет последние исполнения, соответствующие вашим согласно принятых требованийм из файла Cargo.toml. После этого Cargo запишет эти исполнения в файл Cargo.lock. Иначе по умолчанию Cargo будет искать только исполнения больше 0.8.5, но при этом меньше 0.9.0. Если дополнение rand имеет две новые исполнения — 0.8.6 и 0.9.0 — то при запуске cargo update вы увидите следующее:

- -
$ cargo update
-    Updating crates.io index
-    Updating rand v0.8.5 -> v0.8.6
-
-

Cargo пренебрегает исполнение 0.9.0. В этот мгновение также появится изменение в файле Cargo.lock, указывающее на то, что исполнение rand, которая теперь используется, равна 0.8.6. Чтобы использовать rand исполнения 0.9.0 или любой другой исполнения из серии 0.9.x, необходимо обновить файл Cargo.toml следующим образом:

-
[dependencies]
-rand = "0.9.0"
-
-

В следующий раз, при запуске cargo build, Cargo обновит реестр доступных дополнений и пересмотрит ваши требования к rand в соответствии с новой исполнением, которую вы указали.

-

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

-

Создание случайного числа

-

Давайте начнём использовать rand, чтобы создать число для угадывания. Следующим шагом будет обновление src/main.rs, как показано в приложении 2-3.

-

Файл: src/main.rs

-
use std::io;
-use rand::Rng;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {guess}");
-}
-

Приложение 2-3: Добавление кода который порождает случайное число

-

Сначала мы добавляем строку use rand::Rng. Особенность Rng определяет способы, выполняющие породители случайных чисел, и этот особенность должен быть в области видимости, чтобы эти способы можно было использовать. В главе 10 мы рассмотрим особенности подробно.

-

Затем мы добавляем две строки посередине. В первой строке мы вызываем функцию rand::thread_rng, дающую нам породитель случайных чисел, который мы собираемся использовать: тот самый, который является местным для текущего потока выполнения и запускается операционной системой. Затем мы вызываем его способ gen_range. Этот способ определяется Rng, который мы включили в область видимости с помощью оператора use rand::Rng. Способ gen_range принимает в качестве переменной выражение ряда и порождает случайное число в этом ряде. Вид используемого выражения ряда принимает разновидность start..=end и включает нижнюю и верхнюю границы, поэтому, чтобы запросить число от 1 до 100, нам нужно указать 1..=100.

-
-

Примечание: непросто сразу разобраться, какие особенности использовать, какие способы и функции вызывать из дополнения, поэтому каждый дополнение имеет документацию с указаниями по его использованию. Ещё одной замечательной особенностью Cargo является выполнение приказы cargo doc --open, которая местно собирает документацию, предоставляемую всеми вашими зависимостями, и открывает её в браузере. К примеру, если важна другая возможность из дополнения rand, запустите cargo doc --open и нажмите rand в боковой панели слева.

-
-

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

-

Попробуйте запустить программу несколько раз:

- -
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 2.53s
-     Running `target/debug/guessing_game`
-Guess the number!
-The secret number is: 7
-Please input your guess.
-4
-You guessed: 4
-
-$ cargo run
-    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
-     Running `target/debug/guessing_game`
-Guess the number!
-The secret number is: 83
-Please input your guess.
-5
-You guessed: 5
-
-

Вы должны получить разные случайные числа, и все они должны быть числами в ряде от 1 до 100. Отличная работа!

-

Сравнение догадки с тайным числом

-

Теперь, когда у нас есть пользовательский ввод и случайное число, мы можем сравнить их. Этот шаг показан в приложении 2-4. Учтите, что этот код ещё не собирается, подробнее мы объясним дальше.

-

Имя файла: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    // --snip--
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {guess}");
-
-    match guess.cmp(&secret_number) {
-        Ordering::Less => println!("Too small!"),
-        Ordering::Greater => println!("Too big!"),
-        Ordering::Equal => println!("You win!"),
-    }
-}
-

Приложение 2-4: Обработка возможных возвращаемых значений при сравнении двух чисел

-

Сначала добавим ещё один оператор use, который вводит вид с именем std::cmp::Ordering в область видимости из встроенной библиотеки. Вид Ordering является ещё одним перечислением и имеет исходы Less, Greater и Equal. Это три возможных исхода при сравнении двух величин.

-

После чего ниже добавляем пять новых строк, использующих вид Ordering. Способ cmp сравнивает два значения и может вызываться для всего, что можно сравнить. Он принимает ссылку на все, что требуется сравнить: здесь сравнивается guess с secret_number. В итоге возвращается исход перечисления Ordering, которое мы ввели в область видимости с помощью оператора use. Для принятия решения о том, что делать дальше, мы используем выражение match, определяющее, какой исход Ordering был возвращён из вызова cmp со значениями guess и secret_number.

-

Выражение match состоит из веток (arms). Ветка состоит из образца для сопоставления и кода, который будет запущен, если значение, переданное в match, соответствует образцу этой ветки. Ржавчина принимает значение, заданное match, и по очереди просматривает образец каждой ветки. Образцы и устройство match — это мощные возможности Rust, позволяющие выразить множество случаев, с которыми может столкнуться ваш код, и обеспечить их обработку. Эти возможности будут подробно раскрыты в главе 6 и главе 18 соответственно.

-

Давайте рассмотрим пример с выражением match, которое мы здесь используем. Скажем, пользователь угадал 50, а случайно созданное тайное число на этот раз — 38.

-

Когда код сравнивает 50 с 38, способ cmp вернёт Ordering::Greater, поскольку 50 больше, чем 38. Выражение match получит значение Ordering::Greater и начнёт проверять образец в каждой ветке. Он просмотрит образец первой ветки, Ordering::Less, и увидит, что значение Ordering::Greater не соответствует Ordering::Less, поэтому пренебрегает код этой ветки и перейдёт к следующей. Образец следующей ветки — Ordering::Greater, который соответствует Ordering::Greater! Код этой ветки будет выполнен и напечатает Too big! на экран. Выражение match заканчивается после первого успешного совпадения, поэтому в этом сценарии оно не будет рассматривать последнюю ветку.

-

Однако код в приложении 2-4 всё ещё не собирается. Давайте попробуем:

- -
$ cargo build
-   Compiling libc v0.2.86
-   Compiling getrandom v0.2.2
-   Compiling cfg-if v1.0.0
-   Compiling ppv-lite86 v0.2.10
-   Compiling rand_core v0.6.2
-   Compiling rand_chacha v0.3.0
-   Compiling rand v0.8.5
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-error[E0308]: mismatched types
-  --> src/main.rs:22:21
-   |
-22 |     match guess.cmp(&secret_number) {
-   |                 --- ^^^^^^^^^^^^^^ expected `&String`, found `&{integer}`
-   |                 |
-   |                 arguments to this method are incorrect
-   |
-   = note: expected reference `&String`
-              found reference `&{integer}`
-note: method defined here
-  --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/cmp.rs:840:8
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `guessing_game` (bin "guessing_game") due to 1 previous error
-
-

Суть ошибки заключается в наличии несовпадающих видов. У Ржавчина строгая постоянная система видов. Однако в нем также есть рычаг вывода видов. Когда мы написали let mut guess = String::new(), Ржавчина смог сделать вывод, что guess должна быть String и не заставил указывать вид. С другой стороны, secret_number — это числовой вид. Несколько видов чисел в Ржавчина могут иметь значение от 1 до 100: i32, 32-битное число; u32, беззнаковое 32-битное число; i64, 64-битное число, и так далее. Если не указано иное, Ржавчина по умолчанию использует i32, который будет видом secret_number, если вы не добавите сведения о виде где-то ещё, чтобы заставить Ржавчина вывести другой числовой вид. Причина ошибки заключается в том, что Ржавчина не может сравнить строку и числовой вид.

-

В конечном итоге необходимо преобразовать String, считываемую программой в качестве входных данных, в существующий числовой вид, чтобы иметь возможность числового сравнения с загаданным числом. Для этого добавьте в тело функции main следующую строку:

-

Имя файла: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    // --snip--
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    let guess: u32 = guess.trim().parse().expect("Please type a number!");
-
-    println!("You guessed: {guess}");
-
-    match guess.cmp(&secret_number) {
-        Ordering::Less => println!("Too small!"),
-        Ordering::Greater => println!("Too big!"),
-        Ordering::Equal => println!("You win!"),
-    }
-}
-

Вот эта строка:

-
let guess: u32 = guess.trim().parse().expect("Please type a number!");
-

Мы создаём переменную с именем guess. Но подождите, разве в программе уже нет переменной с этим именем guess? Так и есть, но Ржавчина позволяет нам затенять предыдущее значение guess новым. Затенение позволяет нам повторно использовать имя переменной guess, чтобы избежать создания двух единственных переменных, таких как guess_str и guess, например. Мы рассмотрим это более подробно в главе 3, а пока знайте, что эта функция часто используется, когда необходимо преобразовать значение из одного вида в другой.

-

Мы связываем эту новую переменную с выражением guess.trim().parse(). Переменная guess в этом выражении относится к исходной переменной guess, которая содержала входные данные в виде строки. Способ trim на образце String удалит любые пробельные символы в начале и конце строки для того, чтобы мы могли сопоставить строку с u32, который содержит только числовые данные. Пользователь должен нажать enter, чтобы выполнить read_line и ввести свою догадку, при этом в строку добавится символ новой строки. Например, если пользователь набирает 5 и нажимает enter, guess будет выглядеть так: 5\n. Символ \n означает "новая строка". (В Windows нажатие enter сопровождается возвратом каретки и новой строкой, \r\n). Способ trim убирает \n или \r\n, оставляя только 5.

-

Способ parse строк преобразует строку в другой вид. Здесь мы используем его для преобразования строки в число. Нам нужно сообщить Ржавчина точный числовой вид, который мы хотим получить, используя let guess: u32. Двоеточие ( : ) после guess говорит Rust, что мы определяем вид переменной. В Ржавчина есть несколько встроенных числовых видов; u32, показанный здесь, представляет собой 32-битное целое число без знака. Это хороший выбор по умолчанию для небольшого положительного числа. Вы узнаете о других видах чисел в главе 3.

-

Кроме того, изложение u32 в этом примере программы и сравнение с secret_number означает, что Ржавчина сделает вывод, что secret_number должен быть u32. Итак, теперь сравнение будет между двумя значениями одного типа!

-

Способ parse будет работать только с символами, которые логически могут быть преобразованы в числа, и поэтому легко может вызвать ошибки. Если, например, строка содержит A👍%, преобразовать её в число невозможно. Так как способ parse может потерпеть неудачу, он возвращает вид Result — так же как и способ read_line (обсуждалось ранее в разделе «Обработка возможной ошибки с помощью вида Result»). Мы будем точно так же обрабатывать данный Result, вновь используя способ expect. Если parse вернёт исход Result Err, так как не смог создать число из строки, вызов expect со сбоем завершит игру и отобразит переданное ему сообщение. Если parse сможет успешно преобразовать строку в число, он вернёт исход Result Ok, а expect вернёт число, полученное из значения Ok.

-

Давайте запустим программу теперь:

- -
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.43s
-     Running `target/debug/guessing_game`
-Guess the number!
-The secret number is: 58
-Please input your guess.
-  76
-You guessed: 76
-Too big!
-
-

Хорошо! Несмотря на то, что были добавлены пробелы в строке ввода, программа всё равно поняла, что пользователь имел в виду число 76. Запустите программу несколько раз, чтобы проверить разное поведение при различных видах ввода: задайте число правильно, задайте слишком большое число и задайте слишком маленькое число.

-

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

-

Возможность нескольких догадок с помощью циклов

-

Ключевое слово loop создаёт бесконечный цикл. Мы добавляем цикл, чтобы дать пользователям больше шансов угадать число:

-

Имя файла: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    // --snip--
-
-    println!("The secret number is: {secret_number}");
-
-    loop {
-        println!("Please input your guess.");
-
-        // --snip--
-
-
-        let mut guess = String::new();
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: u32 = guess.trim().parse().expect("Please type a number!");
-
-        println!("You guessed: {guess}");
-
-        match guess.cmp(&secret_number) {
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => println!("You win!"),
-        }
-    }
-}
-

Как видите, мы перемеисполнения всё, начиная с подсказки ввода догадки, в цикл. Не забудьте добавить ещё по четыре пробела на отступы строк внутри цикла и запустите программу снова. Теперь программа будет бесконечно запрашивать ещё одну догадку, что в действительности создаёт новую неполадку. Похоже, пользователь не сможет выйти из игры!

-

Пользователь может прервать выполнение программы с помощью сочетания клавиш ctrl+c. Но есть и другой способ спастись от этого ненасытного монстра, о котором говорилось при обсуждении parse в «Сравнение догадки с тайным числом»: если пользователь введёт нечисловой ответ, программа завершится со сбоем. Мы можем воспользоваться этим, чтобы позволить пользователю выйти из игры, как показано здесь:

- -
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 1.50s
-     Running `target/debug/guessing_game`
-Guess the number!
-The secret number is: 59
-Please input your guess.
-45
-You guessed: 45
-Too small!
-Please input your guess.
-60
-You guessed: 60
-Too big!
-Please input your guess.
-59
-You guessed: 59
-You win!
-Please input your guess.
-quit
-thread 'main' panicked at 'Please type a number!: ParseIntError { kind: InvalidDigit }', src/main.rs:28:47
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Ввод quit приведёт к выходу из игры, но, как вы заметите, так же будет и при любом другом нечисловом вводе. Однако это, мягко говоря, не разумно. Мы хотим, чтобы игра самостоятельно остановилась, когда будет угадано правильное число.

-

Выход после правильной догадки

-

Давайте запрограммируем игру на выход при выигрыше пользователя, добавив оператор break:

-

Файл: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    loop {
-        println!("Please input your guess.");
-
-        let mut guess = String::new();
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: u32 = guess.trim().parse().expect("Please type a number!");
-
-        println!("You guessed: {guess}");
-
-        // --snip--
-
-        match guess.cmp(&secret_number) {
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => {
-                println!("You win!");
-                break;
-            }
-        }
-    }
-}
-

Добавление строки break после You win! заставляет программу выйти из цикла, когда пользователь правильно угадает тайное число. Выход из цикла также означает выход из программы, так как цикл является последней частью main.

-

Обработка недопустимого ввода

-

Чтобы улучшить поведение игры, вместо со сбоемго завершения программы, когда пользователь вводит не число, давайте заставим игру пренебрегать этотобстоятельство, позволяя пользователю продолжить угадывание. Для этого необходимо изменить строку, в которой guess преобразуется из String в u32, как показано в приложении 2-5.

-

Файл: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    loop {
-        println!("Please input your guess.");
-
-        let mut guess = String::new();
-
-        // --snip--
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: u32 = match guess.trim().parse() {
-            Ok(num) => num,
-            Err(_) => continue,
-        };
-
-        println!("You guessed: {guess}");
-
-        // --snip--
-
-        match guess.cmp(&secret_number) {
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => {
-                println!("You win!");
-                break;
-            }
-        }
-    }
-}
-

Приложение 2-5. Пренебрежение нечисловой догадки и запрос другой догадки вместо завершения программы

-

Мы заменяем вызов expect на выражение match, чтобы перейти от со сбоемго завершения при ошибке к обработке ошибки. Помните, что parse возвращает вид Result, а Result — это перечисление, которое имеет исходы Ok и Err. Здесь мы используем выражение match, как и в случае с итогом Ordering способа cmp.

-

Если parse успешно преобразует строку в число, он вернёт значение Ok, содержащее полученное число. Это значение Ok будет соответствовать образцу первой ветки, а выражение match просто вернёт значение num, которое parse произвёл и поместил внутрь значения Ok. Это число окажется в нужной нам переменной guess, которую мы создали.

-

Если способ parse не способен превратить строку в число, он вернёт значение Err, которое содержит более подробную сведения об ошибке. Значение Err не совпадает с образцом Ok(num) в первой ветке match, но совпадает с образцом Err(_) второй ветки. Подчёркивание _ является всеохватывающим выражением. В этой ветке мы говорим, что хотим обработать совпадение всех значений Err, независимо от того, какая сведения находится внутри. Поэтому программа выполнит код второй ветки, continue, который сообщает программе перейти к следующей повторения loop и запросить ещё одну догадку. В этом случае программа эффективно пренебрегает все ошибки, с которыми parse может столкнуться!

-

Всё в программе теперь должно работать как положено. Давайте попробуем:

- -
$ cargo run
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished dev [unoptimized + debuginfo] target(s) in 4.45s
-     Running `target/debug/guessing_game`
-Guess the number!
-The secret number is: 61
-Please input your guess.
-10
-You guessed: 10
-Too small!
-Please input your guess.
-99
-You guessed: 99
-Too big!
-Please input your guess.
-foo
-Please input your guess.
-61
-You guessed: 61
-You win!
-
-

Потрясающе! С помощью одной маленькой последней правки мы закончим игру в угадывание. Напомним, что программа все ещё печатает тайное число. Это хорошо подходило для проверки, но это портит игру. Давайте удалим println!, который выводит тайное число. В Приложении 2-6 показан окончательный исход кода.

-

Файл: src/main.rs

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    loop {
-        println!("Please input your guess.");
-
-        let mut guess = String::new();
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: u32 = match guess.trim().parse() {
-            Ok(num) => num,
-            Err(_) => continue,
-        };
-
-        println!("You guessed: {guess}");
-
-        match guess.cmp(&secret_number) {
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => {
-                println!("You win!");
-                break;
-            }
-        }
-    }
-}
-

Приложение 2-6: полный код игры

-

На данный мгновение вы успешно создали игру в загадки. Поздравляем!

-

Заключение

-

Этот дело — опытный способ познакомить вас со многими новыми подходами Rust: let, match, функции, использование внешних ящиков и многое другое. В следующих нескольких главах вы изучите эти подходы более подробно. Глава 3 охватывает понятия, которые есть в большинстве языков программирования, такие как переменные, виды данных и функции, и показывает, как использовать их в Rust. В главе 4 рассматривается владение — особенность, которая отличает Ржавчина от других языков. В главе 5 обсуждаются устройства и правила написания способов, а в главе 6 объясняется, как работают перечисления.

-

Общие подходы программирования

-

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

-

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

-
-

Ключевые слова

-

В языке Ржавчина как и в других языках есть набор ключевых слов, зарезервированных только для использования в языке. Помните, что нельзя использовать эти слова в качестве имён переменных или функций. Большинство этих ключевых слов имеют особые назначения, и вы будете использовать их для выполнения различных задач в своих программах на Rust. Некоторые из них сейчас не имеют функционального назначения, но зарезервированы для возможности, которая может быть добавлена в Ржавчина в будущем. Список ключевых слов вы можете найти в Приложении А.

-
-

Переменные и изменяемость

-

Как упоминалось в разделе "Хранение значений с помощью переменных", по умолчанию переменные неизменяемы. Это один из многих стимулов Rust, позволяющий писать код с использованием преимущества безопасности и удобной состязательности (concurrency), предоставляемых Rust. Тем не менее, существует возможность сделать переменные изменяемыми. Давайте рассмотрим, как и почему Ржавчина побуждает предпочесть неизменяемость и почему иногда можно отказаться от этого.

-

Если переменная является неизменяемой, то после привязки значения к имени изменить его будет нельзя. Чтобы показать это, создайте новый дело под названием variables в папке projects с помощью приказы cargo new variables.

-

Далее, в новом папке variables откройте src/main.rs и замените в нем код на ниже приведённый, который пока не будет собираться:

-

Имя файла: src/main.rs

-
fn main() {
-let x = 5;
-println!("The value of x is: {}", x);
-x = 6;
-println!("The value of x is: {}", x);
-}
-

Сохраните и запустите программу, используя cargo run. Будет получено сообщение об ошибке относительно неизменяемости, как показано в этом выводе:

-
error[E0384]: cannot assign twice to immutable variable `x`  --> src/main.rs:4:5   | 2 |     let x = 5;   |         - first assignment to `x` 3 |     println!("The value of x is: {}", x); 4 |     x = 6;   |     ^^^^^ cannot assign twice to immutable variable
-
-

В этом примере показано, как сборщик помогает находить ошибки в ваших программах. Ошибки сборщика могут расстраивать, но в действительности они означают, что программа пока не делает правильно то, что вы ожидаете; это не значит, что вы плохой программист! Даже опытные Rustaceans иногда сталкиваются с ошибками сборщика.

-

Вы получили сообщение об ошибке cannot assign twice to immutable variable x``, потому что попытались присвоить новое значение неизменяемой переменной x.

-

Важно, чтобы при попытке изменить значение, объявленное неизменяемым, выдавались ошибки времени сборки, так как подобная случаей может привести к сбоям. Если одна часть нашего кода исполняется исходя из уверенности в неизменяемости значения, а другая часть изменяет это значение, то велика вероятность , что первая часть не выполнит своего предназначения. Причину такой ошибки бывает трудно отследить, особенно если вторая часть кода изменяет значение лишь изредка. Сборщик Ржавчина предоставляет заверение, что если объявить значение неизменяемым, то оно действительно не изменится, а значит, не нужно следить за этим самим. Таким образом, ваш код становится проще для понимания.

-

Однако изменяемость может быть очень полезной и может сделать код более удобным для написания. Хотя переменные по умолчанию неизменяемы, их можно сделать изменяемыми, добавив mut перед именем переменной, как это было сделано в Главе 2. Добавление mut также передаёт будущим читателям кода намерение, обозначая, что другие части кода будут изменять значение этой переменной.

-

Например, изменим src/main.rs на следующий код:

-

Имя файла: src/main.rs

-
fn main() {
-    let mut x = 5;
-    println!("The value of x is: {x}");
-    x = 6;
-    println!("The value of x is: {x}");
-}
-

Запустив программу, мы получим итог:

-
$ cargo run
-   Compiling variables v0.1.0 (file:///projects/variables)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
-     Running `target/debug/variables`
-The value of x is: 5
-The value of x is: 6
-
-

Нам разрешено изменить значение, связанное с x, с 5 на 6 при помощи mut. В конечном счёте, решение об использовании изменяемости остаётся за вами и зависит от вашего мнения о наилучшем исходе в данной именно случаи.

-

Постоянного значения

-

Подобно неизменяемым переменным, постоянные значения — это значения, которые связаны с именем и не могут изменяться, но между постоянными значениями и переменными есть несколько различий.

-

Во-первых, нельзя использовать mut с постоянными значениями. Постоянного значения не просто неизменяемы по умолчанию — они неизменяемы всегда. Для объявления постоянных значенийиспользуется ключевое слово const вместо let, а также вид значения должен быть указан в изложении. Мы рассмотрим виды и изложении видов в следующем разделе «Виды данных»., так что не беспокойтесь о подробностях прямо сейчас. Просто знайте, что вы всегда должны определять вид.

-

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

-

Последнее отличие в том, что постоянные значения могут быть заданы только постоянным выражением, но не итогом вычисленного во время выполнения значения.

-

Вот пример объявления постоянные значения:

-
#![allow(unused)]
-fn main() {
-const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
-}
-

Имя постоянные значения - THREE_HOURS_IN_SECONDS, а её значение устанавливается как итог умножения 60 (количество секунд в минуте) на 60 (количество минут в часе) на 3 (количество часов, которые нужно посчитать в этой программе). Соглашение Ржавчина для именования постоянных значенийтребует использования всех заглавных букв с подчёркиванием между словами. Сборщик может вычислять ограниченный набор действий во время сборки, позволяющий записать это значение более понятным и простым для проверки способом, чем установка этой постоянные значения в значение 10 800. Дополнительную сведения о том, какие действия можно использовать при объявлении постоянных значений, см. в разделе Раздел справки Ржавчина по вычислениям постоянных значений.

-

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

-

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

-

Затенение (переменных)

-

Как было показано в уроке по игре в Угадайка в главе 2, можно объявить новую переменную с тем же именем, как и у существующей переменной. Rustaceans говорят, что первая переменная затеняется второй, то есть вторая переменная - это то, что увидит сборщик, когда вы будете использовать имя переменной. По сути, вторая переменная затеняет первую, принимая любое использование имени переменной на себя до тех пор, пока либо она сама не станет тенью, либо не закончится область видимости. Мы можем затенять переменную, используя то же имя переменной и повторяя использование ключевого слова let следующим образом:

-

Имя файла: src/main.rs

-
fn main() {
-    let x = 5;
-
-    let x = x + 1;
-
-    {
-        let x = x * 2;
-        println!("The value of x in the inner scope is: {x}");
-    }
-
-    println!("The value of x is: {x}");
-}
-

Эта программа сначала привязывает x к значению 5. Затем она создаёт новую переменную x, повторяя let x =, беря исходное значение и добавляя 1, чтобы значение x стало равным 6. Затем во внутренней области видимости, созданной с помощью фигурных скобок, третий оператор let также затеняет x и создаёт новую переменную, умножая предыдущее значение на 2, чтобы дать x значение 12. Когда эта область заканчивается, внутреннее затенение заканчивается, и x возвращается к значению 6. Запустив эту программу, она выведет следующее:

-
$ cargo run
-   Compiling variables v0.1.0 (file:///projects/variables)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
-     Running `target/debug/variables`
-The value of x in the inner scope is: 12
-The value of x is: 6
-
-

Затенение отличается от объявления переменной с помощью mut, так как мы получим ошибку сборки, если случайно попробуем переназначить значение без использования ключевого слова let. Используя let, можно выполнить несколько превращений над значением, при этом оставляя переменную неизменяемой, после того как все эти превращения завершены.

-

Другой разницей между mut и затенением является то, что мы создаём совершенно новую переменную, когда снова используем слово let (ещё одну). Мы можем даже изменить вид значения, но снова использовать прежнее имя. К примеру, наша программа спрашивает пользователя, сколько пробелов он хочет разместить между некоторым текстом, запрашивая символы пробела, но мы на самом деле хотим сохранить данный ввод как число:

-
fn main() {
-    let spaces = "   ";
-    let spaces = spaces.len();
-}
-

Первая переменная spaces — является строковым видом, а вторая переменная spaces — числовым видом. Таким образом, затенение избавляет нас от необходимости придумывать разные имена, такие как spaces_str и spaces_num. Вместо этого мы можем повторно использовать более простое имя spaces. Однако, если мы попытаемся использовать для этого mut, как показано далее, то получим ошибку времени сборки:

-
fn main() {
-    let mut spaces = "   ";
-    spaces = spaces.len();
-}
-

Ошибка говорит, что не разрешается менять вид переменной:

-
$ cargo run
-   Compiling variables v0.1.0 (file:///projects/variables)
-error[E0308]: mismatched types
- --> src/main.rs:3:14
-  |
-2 |     let mut spaces = "   ";
-  |                      ----- expected due to this value
-3 |     spaces = spaces.len();
-  |              ^^^^^^^^^^^^ expected `&str`, found `usize`
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `variables` (bin "variables") due to 1 previous error
-
-

Теперь, когда мы изучили, как работают переменные, давайте рассмотрим различные виды данных, которые они могут иметь.

-

Виды Данных

-

Каждое значение в Ржавчина относится к определённому виду данных, который указывает на вид данных, что позволяет Ржавчина знать, как работать с этими данными. Мы рассмотрим два подмножества видов данных: одиночные и составные.

-

Не забывайте, что Ржавчина является постоянно строго определенным (statically typed) языком. Это означает, что он должен знать виды всех переменных во время сборки. Обычно сборщик может предположить, какой вид используется (вывести его), основываясь на значении и на том, как мы с ним работаем. В случаях, когда может быть выведено несколько видов, необходимо добавлять изложение вида вручную. Например, когда мы преобразовали String в число с помощью вызова parse в разделе «Сравнение предположения с загаданным номером» главы 2, мы должны добавить такую изложение:

-
#![allow(unused)]
-fn main() {
-let guess: u32 = "42".parse().expect("Not a number!");
-}
-

Если мы не добавим изложение вида : u32, показанную в предыдущем коде, Ржавчина отобразит следующую ошибку, которая означает, что сборщику нужно от нас больше сведений, чтобы узнать, какой вид мы хотим использовать:

-
$ cargo build
-   Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)
-error[E0284]: type annotations needed
- --> src/main.rs:2:9
-  |
-2 |     let guess = "42".parse().expect("Not a number!");
-  |         ^^^^^        ----- type must be known at this point
-  |
-  = note: cannot satisfy `<_ as FromStr>::Err == _`
-help: consider giving `guess` an explicit type
-  |
-2 |     let guess: /* Type */ = "42".parse().expect("Not a number!");
-  |              ++++++++++++
-
-For more information about this error, try `rustc --explain E0284`.
-error: could not compile `no_type_annotations` (bin "no_type_annotations") due to 1 previous error
-
-

В будущем вы увидите различные изложении для разных видов данных.

-

Одиночные виды данных

-

Одиночный вид представляет собой единичное значение. В Ржавчина есть четыре основных одиночных вида: целочисленный, числа с плавающей точкой, логический и символы. Вы наверняка знакомы с этими видами по другим языкам программирования. Давайте разберёмся, как они работают в Rust.

-

Целочисленные виды

-

Целочисленный вид (integer) — это число без дробной части. В главе 2 мы использовали один целочисленный вид — вид u32. Такое объявление вида указывает, что значение, с которым оно связано, должно быть целым числом без знака (виды целых чисел со знаком начинаются с i вместо u), которое занимает 32 бита памяти. В Таблице 3-1 показаны встроенные целочисленные виды в Rust. Мы можем использовать любой из этих исходов для объявления вида целочисленного значения.

-

Таблица 3-1: целочисленные виды в Rust

-
- - - - - - -
ДлинаСо знакомБез знака
8 битi8u8
16 битi16u16
32 битаi32u32
64 битаi64u64
128 битi128u128
архитектурно-зависимаяisizeusize
-
-

Каждый исход может быть как со знаком, так и без знака и имеет явный размер. Такая свойство вида как знаковый и беззнаковый определяет возможность числа быть отрицательным. Другими словами, должно ли число иметь знак (знаковое) или оно всегда будет только положительным и, следовательно, может быть представлено без знака (беззнаковое). Это похоже на написание чисел на бумаге: когда знак имеет значение, число отображается со знаком плюс или со знаком -; однако, когда можно с уверенностью предположить, что число положительное, оно отображается без знака. Числа со знаком хранятся с использованием дополнительного кода.

-

Каждый исход со знаком может хранить числа от -(2 n - 1 ) до 2 n - 1 - 1 включительно, где n — количество битов, которые использует этот исход. Таким образом, i8 может хранить числа от -(2 7 ) до 2 7 - 1, что равно значениям от -128 до 127. Исходы без знака могут хранить числа от 0 до 2 n - 1, поэтому u8 может хранить числа от 0 до 2 8 - 1, что равно значениям от 0 до 255.

-

Кроме того, виды isize и usize зависят от архитектуры компьютера, на котором выполняется программа, и обозначаются в таблице как "arch": 64 бита, если используется 64-битная архитектура, и 32 бита, если используется 32-битная архитектура.

-

Вы можете записывать целочисленные записи в любой из разновидностей, показанных в таблице 3-2. Заметьте, что числовые записи, имеющие несколько числовых видов, допускают использование вставки вида, например 57u8, для обозначения вида. Числовые записи также могут использовать _ в качестве визуального разделителя для облегчения чтения числа, например 1_000, который будет иметь такое же значение, как если бы было задано 1000.

-

Таблица 3-2: Целочисленные записи в Rust

-
- - - - - -
Числовой записьПример
Десятичный98_222
Шестнадцатеричный0xff
восьмеричный0o77
Двоичный0b1111_0000
Байт (только u8)b'A'
-
-

Как же узнать, какой вид целого числа использовать? Если вы не уверены, значения по умолчанию в Rust, как правило, подходят для начала: целочисленные виды по умолчанию i32. Основной случай, в котором вы должны использовать isize или usize, — это упорядочевание какой-либо собрания.

-
-

Целочисленное переполнение Допустим, имеется переменная вида u8, которая может хранить значения от 0 до 255. Если попытаться изменить переменную на значение вне этого ряда, например, 256, произойдёт целочисленное переполнение, что может привести к одному из двух исходов поведения. Если выполняется сборка в режиме отладки, Ржавчина включает проверку на целочисленное переполнение, приводящую вашу программу к панике во время выполнения, когда возникает такое поведение. Ржавчина использует понятие паника(panicking), когда программа завершается с ошибкой. Мы обсудим панику более подробно в разделе "Неустранимые ошибки с panic!" в главе 9. . При сборки в режиме release с флагом --release, Ржавчина не включает проверки на целочисленное переполнение, которое вызывает панику. Вместо этого, в случае переполнения, Ржавчина выполняет обёртывание второго дополнения. Проще говоря, значения, превышающие наибольшее значение, которое может хранить вид, "оборачиваются" к наименьшему из значений, которые может хранить вид. В случае u8 значение 256 становится 0, значение 257 становится 1, и так далее. Программа не запаникует, но переменная будет иметь значение, которое, вероятно, не будет соответствовать вашим ожиданиям. Полагаться на поведение обёртывания целочисленного переполнения считается ошибкой. Для явной обработки возможности переполнения существует семейство способов, предоставляемых встроенной библиотекой для простых числовых видов:

-
    -
  • Обёртывание во всех режимах с помощью способов wrapping_*, таких как wrapping_add.
  • -
  • Возврат значения None при переполнении с помощью способов checked_*.
  • -
  • Возврат значения и логический индикатор, указывающий, произошло ли переполнение при использовании способов overflowing_*.
  • -
  • Насыщение наименьшим или наибольшим значением с помощью способов saturating_*.
  • -
-
-

Числа с плавающей запятой

-

Также в Ржавчина есть два простых вида для чисел с плавающей запятой, представляющих собой числа с десятичной точкой. Виды с плавающей точкой в Ржавчина - это f32 и f64, размер которых составляет 32 бита и 64 бита соответственно. По умолчанию используется вид f64, поскольку на современных процессорах он работает примерно с той же скоростью, как и f32, но обладает большей точностью. Все виды с плавающей запятой являются знаковыми.

-

Вот пример, отображающий числа с плавающей запятой в действии:

-

Файл: src/main.rs

-
fn main() {
-    let x = 2.0; // f64
-
-    let y: f32 = 3.0; // f32
-}
-

Числа с плавающей запятой представлены в соответствии со исполнением IEEE-754. Вид f32 является плавающей запятой одинарной точности, а f64 - двойной точности.

-

Числовые действия

-

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

-

Файл: src/main.rs

-
fn main() {
-    // addition
-    let sum = 5 + 10;
-
-    // subtraction
-    let difference = 95.5 - 4.3;
-
-    // multiplication
-    let product = 4 * 30;
-
-    // division
-    let quotient = 56.7 / 32.2;
-    let truncated = -5 / 3; // Results in -1
-
-    // remainder
-    let remainder = 43 % 5;
-}
-

Каждое выражение в этих указаниях использует математический оператор и вычисляется в одно значение, которое связывается с переменной. Приложении B содержит список всех операторов, которые предоставляет Rust.

-

Логический вид данных

-

Как и в большинстве других языков программирования, логический вид в Ржавчина имеет два возможных значения: true и false. Значения логических видов имеют размер в один байт. Логический вид в Ржавчина задаётся с помощью bool. Например:

-

Файл: src/main.rs

-
fn main() {
-    let t = true;
-
-    let f: bool = false; // with explicit type annotation
-}
-

Основной способ использования логических значений - это использование условий, таких как выражение if. Мы рассмотрим, как выражения if работают в Ржавчина в разделе "Поток управления".

-

Символьный вид данных

-

Вид char в Ржавчина является самым простым алфавитным видом языка. Вот несколько примеров объявления значений char:

-

Файл: src/main.rs

-
fn main() {
-    let c = 'z';
-    let z: char = 'ℤ'; // with explicit type annotation
-    let heart_eyed_cat = '😻';
-}
-

Заметьте, мы указываем записи char с одинарными кавычками, в отличие от строковых записей, для которых используются двойные кавычки. Вид char в Ржавчина имеет размер четыре байта и представляет собой одиночное значение Unicode, а значит, может представлять собой не только ASCII. Акцентированные буквы, китайские, японские и корейские символы, эмодзи и пробелы нулевой ширины - все это допустимые значения вида char в Rust. Одиночные значения Unicode находятся в ряде от U+0000 до U+D7FF и от U+E000 до U+10FFFF включительно. Однако "символ" не является понятием в Unicode, поэтому ваше человеческое представление о том, что такое "символ", может не совпадать с тем, что такое char в Rust. Мы подробно обсудим эту тему в главе 8 "Хранение текста в кодировке UTF-8 с помощью строк".

-

Составные виды данных

-

Составные виды могут объединять различные значения в один вид. В Ржавчина есть два простых составных вида: упорядоченные ряды и массивы.

-

Упорядоченные ряды

-

Упорядоченный ряд- это гибкий способ объединения нескольких значений с различными видами в один составной вид. Упорядоченные ряды имеют конечную длину: после объявления они не могут увеличиваться или уменьшаться в размерах.

-

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

-

Файл: src/main.rs

-
fn main() {
-    let tup: (i32, f64, u8) = (500, 6.4, 1);
-}
-

Переменная tup связана со всем упорядоченным рядом, поскольку упорядоченный ряд является одним составным элементом. Чтобы получить отдельные значения из упорядоченного ряда, можно использовать сопоставление с образцом для разъединения значения упорядоченного ряда, например, так:

-

Файл: src/main.rs

-
fn main() {
-    let tup = (500, 6.4, 1);
-
-    let (x, y, z) = tup;
-
-    println!("The value of y is: {y}");
-}
-

Эта программа сначала создаёт упорядоченный ряд и связывает его с переменной tup. Затем с помощью образца let берётся tup и превращается в три отдельные переменные, x, y и z. Это называется разъединением, поскольку разбивает единый упорядоченный ряд на три части. Наконец, программа печатает значение y, которое равно 6.4.

-

Мы также можем получить доступ к элементу упорядоченного ряда напрямую, используя точку (.), за которой следует порядковый указательзначения, требуемого для доступа. Например:

-

Файл: src/main.rs

-
fn main() {
-    let x: (i32, f64, u8) = (500, 6.4, 1);
-
-    let five_hundred = x.0;
-
-    let six_point_four = x.1;
-
-    let one = x.2;
-}
-

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

-

Упорядоченный ряд, не имеющий значений, имеет особое имя единичный вид (unit). Это значение и соответствующий ему вид записываются как () и представляет собой пустое значение или пустой возвращаемый вид. Выражения неявно возвращают значение единичного вида, если не возвращают никакого другого значения.

-

Массивы

-

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

-

Мы записываем значения в массиве в виде списка, разделённого запятыми, внутри квадратных скобок:

-

Файл: src/main.rs

-
fn main() {
-    let a = [1, 2, 3, 4, 5];
-}
-

Массивы удобно использовать, если данные необходимо разместить в обойме, а не в куче (мы подробнее обсудим обойма и кучу в Главе 4) или если требуется, чтобы количество элементов всегда было конечным. Однако массив не так гибок, как вектор. Вектор - это подобный вид собрания, предоставляемый встроенной библиотекой, который может увеличиваться или уменьшаться в размере. Если вы не уверены, что лучше использовать - массив или вектор, то, скорее всего, вам следует использовать вектор. Более подробно векторы рассматриваются в Главе 8.

-

Однако массивы более полезны, когда вы знаете, что количество элементов не нужно будет изменять. Например, если бы вы использовали названия месяцев в программе, вы, вероятно, использовали бы массив, а не вектор, потому что вы знаете, что он всегда будет содержать 12 элементов:

-
#![allow(unused)]
-fn main() {
-let months = ["January", "February", "March", "April", "May", "June", "July",
-              "August", "September", "October", "November", "December"];
-}
-

Вид массива записывается следующим образом: в квадратных скобках обозначается вид элементов массива, а затем, через точку с запятой, количество элементов. Например:

-
#![allow(unused)]
-fn main() {
-let a: [i32; 5] = [1, 2, 3, 4, 5];
-}
-

Здесь i32 является видом каждого элемента массива. После точки с запятой указано число 5, показывающее, что массив содержит 5 элементов.

-

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

-
#![allow(unused)]
-fn main() {
-let a = [3; 5];
-}
-

Массив в переменной a будет включать 5 элементов, значение которых будет равно 3. Данная запись подобна коду let a = [3, 3, 3, 3, 3];, но является более краткой.

-
Доступ к элементам массива
-

Массив — это единый отрывок памяти известного конечного размера, который может быть размещён в обойме. Вы можете получить доступ к элементам массива с помощью упорядочевания, например:

-

Файл: src/main.rs

-
fn main() {
-    let a = [1, 2, 3, 4, 5];
-
-    let first = a[0];
-    let second = a[1];
-}
-

В этом примере переменная с именем first получит значение 1, потому что это значение находится по порядковому указателю [0] в массиве. Переменная с именем second получит значение 2 по порядковому указателю [1] в массиве.

-
Неправильный доступ к элементу массива
-

Давайте посмотрим, что произойдёт, если попытаться получить доступ к элементу массива, находящемуся за его пределами. Допустим, вы запускаете данный код, похожий на игру в угадывание из Главы 2, чтобы получить от пользователя порядковый указательмассива:

-

Файл: src/main.rs

-
use std::io;
-
-fn main() {
-    let a = [1, 2, 3, 4, 5];
-
-    println!("Please enter an array index.");
-
-    let mut index = String::new();
-
-    io::stdin()
-        .read_line(&mut index)
-        .expect("Failed to read line");
-
-    let index: usize = index
-        .trim()
-        .parse()
-        .expect("Index entered was not a number");
-
-    let element = a[index];
-
-    println!("The value of the element at index {index} is: {element}");
-}
-

Этот код успешно собирается. Если запустить этот код с помощью cargo run и ввести 0, 1, 2, 3 или 4, программа напечатает соответствующее значение по данному порядковому указателю в массиве. Если вместо этого ввести число за пределами массива, например, 10, то программа выведет следующее:

- -
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Программа столкнулась с ошибкой во времени выполнения на этапе применения недопустимого значения в действия упорядочевания. Программа завершилась с сообщением об ошибке и не выполнила конечную указанию println!. При попытке доступа к элементу с помощью упорядочевания Ржавчина проверяет, что указанный порядковый указательменьше длины массива. Если порядковый указательбольше или равен длине, Ржавчина паникует. Эта проверка должна происходить во время выполнения, особенно в данном случае, потому что сборщик не может знать, какое значение введёт пользователь при последующем выполнении кода.

-

Это пример принципов безопасности памяти Ржавчина в действии. Во многих низкоуровневых языках такая проверка не выполняется, и когда вы указываете неправильный порядковый указатель, доступ к памяти может быть неправильным. Ржавчина защищает вас от такого рода ошибок, немедленно закрываясь вместо того, чтобы разрешать доступ к памяти и продолжать работу. В главе 9 подробнее обсуждается обработка ошибок в Ржавчина и то, как вы можете написать читаемый, безопасный код, который не вызывает панику и не разрешает неправильный доступ к памяти.

-

Функции

-

Функции широко распространены в коде Rust. Вы уже познакомились с одной из самых важных функций в языке: функцией main, которая является точкой входа большинства программ. Вы также видели ключевое слово fn, позволяющее объявлять новые функции.

-

Код Ржавчина использует змеиный регистр (snake case) как основной исполнение для имён функций и переменных, в котором все буквы строчные, а символ подчёркивания разделяет слова. Вот программа, содержащая пример определения функции:

-

Имя файла: src/main.rs

-
fn main() {
-    println!("Hello, world!");
-
-    another_function();
-}
-
-fn another_function() {
-    println!("Another function.");
-}
-

Для определения функции в Ржавчина необходимо указать fn, за которым следует имя функции и набор круглых скобок. Фигурные скобки указывают сборщику, где начинается и заканчивается тело функции.

-

Мы можем вызвать любую функцию, которую мы определили ранее, введя её имя и набор скобок следом. Поскольку в программе определена another_function, её можно вызвать из функции main. Обратите внимание, что another_function определена после функции main в исходном коде; мы могли бы определить её и раньше. Ржавчина не важно, где вы определяете свои функции, главное, чтобы они были определены где-то в той области видимости, которую может видеть вызывающий их код.

-

Создадим новый двоичный дело с названием functions для дальнейшего изучения функций. Поместите пример another_function в файл src/main.rs и запустите его. Вы должны увидеть следующий вывод:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
-     Running `target/debug/functions`
-Hello, world!
-Another function.
-
-

Строки выполняются в том порядке, в котором они расположены в функции main. Сначала печатается сообщение "Hello, world!", а затем вызывается another_function, которая также печатает сообщение.

-

Свойства функции

-

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

-

В этой исполнения another_function мы добавляем свойство:

-

Имя файла: src/main.rs

-
fn main() {
-    another_function(5);
-}
-
-fn another_function(x: i32) {
-    println!("The value of x is: {x}");
-}
-

Попробуйте запустить эту программу. Должны получить следующий итог:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.21s
-     Running `target/debug/functions`
-The value of x is: 5
-
-

Объявление another_function содержит один свойство с именем x. Вид x задан как i32. Когда мы передаём 5 в another_function, макрос println! помещает 5 на место пары фигурных скобок, содержащих x в строке вида.

-

В ярлыках функций вы обязаны указывать вид каждого свойства. Это намеренное решение в внешнем виде Rust: требование наставлений видов в определениях функций позволяет сборщику в дальнейшем избежать необходимости использовать их в других местах кода, чтобы определить, какой вид вы имеете в виду. Сборщик также может выдавать более полезные сообщения об ошибках, если он знает, какие виды ожидает функция.

-

При определении нескольких свойств, разделяйте объявления свойств запятыми, как показано ниже:

-

Имя файла: src/main.rs

-
fn main() {
-    print_labeled_measurement(5, 'h');
-}
-
-fn print_labeled_measurement(value: i32, unit_label: char) {
-    println!("The measurement is: {value}{unit_label}");
-}
-

Этот пример создаёт функцию под именем print_labeled_measurement с двумя свойствами. Первый свойство называется value с видом i32. Второй называется unit_label и имеет вид char. Затем функция печатает текст, содержащий value и unit_label.

-

Попробуем запустить этот код. Замените текущую программу дела functions в файле src/main.rs на предыдущий пример и запустите его с помощью cargo run:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
-     Running `target/debug/functions`
-The measurement is: 5h
-
-

Поскольку мы вызвали функцию с 5 в качестве значения для value и 'h' в качестве значения для unit_label, вывод программы содержит эти значения.

-

Указания и выражения

-

Тела функций состоят из ряда указаний, необязательно заканчивающихся выражением. До сих пор функции, которые мы рассматривали, не включали завершающее выражение, но вы видели выражение как часть указания. Поскольку Ржавчина является языком, основанным на выражениях, это важное различие необходимо понимать. В других языках таких различий нет, поэтому давайте рассмотрим, что такое указания и выражения, и как их различия влияют на тела функций.

-
    -
  • Указания выполняют какое-либо действие и не возвращают значения.
  • -
  • Выражения вычисляются до результирующего значения. Давайте рассмотрим несколько примеров.
  • -
-

На самом деле мы уже использовали указания и выражения. Создание переменной и присвоение ей значения с помощью ключевого слова let является оператором. В Приложении 3-1, let y = 6; — это указание.

-

Имя файла: src/main.rs

-
fn main() {
-    let y = 6;
-}
-

Приложение 3-1: Объявление функции main, содержащей одну указанию

-

Определения функций также являются указанием. Весь предыдущий пример сам по себе является указанием.

-

Указания не возвращают значения. Следовательно вы не можете присвоить let указанию другой переменной, как это пытается сделать следующий код. Вы получите ошибку:

-

Имя файла: src/main.rs

-
fn main() {
-    let x = (let y = 6);
-}
-

Если вы запустите эту программу, то ошибка будет выглядеть так:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-error: expected expression, found `let` statement
- --> src/main.rs:2:14
-  |
-2 |     let x = (let y = 6);
-  |              ^^^
-  |
-  = note: only supported directly in conditions of `if` and `while` expressions
-
-warning: unnecessary parentheses around assigned value
- --> src/main.rs:2:13
-  |
-2 |     let x = (let y = 6);
-  |             ^         ^
-  |
-  = note: `#[warn(unused_parens)]` on by default
-help: remove these parentheses
-  |
-2 -     let x = (let y = 6);
-2 +     let x = let y = 6;
-  |
-
-warning: `functions` (bin "functions") generated 1 warning
-error: could not compile `functions` (bin "functions") due to 1 previous error; 1 warning emitted
-
-

Указание let y = 6 не возвращает значение, поэтому не с чем связать переменную x. Это отличается от поведения в других языках, таких как C и Ruby, где присваивание возвращает присвоенное значение. В таких языках можно писать код x = y = 6 и обе переменные x и y будут иметь значение 6. Но в Ржавчина не так.

-

Выражения вычисляют значение и составляют большую часть остального кода, который вы напишете на Rust. Рассмотрим математическую действие, к примеру 5 + 6, которая является выражением, вычисляющим значение 11. Выражения могут быть частью указаний: в приложении 3-1 6 в указания let y = 6; является выражением, которое вычисляется в значение 6. Вызов функции — это выражение. Вызов макроса — это выражение. Новый разделобласти видимости, созданный с помощью фигурных скобок, представляет собой выражение, например:

-

Имя файла: src/main.rs

-
fn main() {
-    let y = {
-        let x = 3;
-        x + 1
-    };
-
-    println!("The value of y is: {y}");
-}
-

Это выражение:

-
{
-    let x = 3;
-    x + 1
-}
-

это блок, который в данном случае вычисляется в значение 4. Это значение связывается с y как часть указания let. Обратите внимание, что строка x + 1 не имеет точки с запятой в конце, что отличается от большинства строк, которые вы видели до сих пор. Выражения не содержат завершающих точек с запятой. Если вы добавите точку с запятой в конец выражения, вы превратите его в указанию, и тогда она не будет возвращать значение. Помните об этом, когда будете изучать возвращаемые значения функций и выражения.

-

Функции с возвращаемыми значениями

-

Функции могут возвращать значения коду, который их вызывает. Мы не называем возвращаемые значения, но мы должны объявить их вид после стрелки ( -> ). В Ржавчина возвращаемое значение функции является родственным значения конечного выражения в разделе тела функции. Вы можете раньше выйти из функции и вернуть значение, используя ключевое слово return и указав значение, но большинство функций неявно возвращают последнее выражение. Вот пример такой функции:

-

Имя файла: src/main.rs

-
fn five() -> i32 {
-    5
-}
-
-fn main() {
-    let x = five();
-
-    println!("The value of x is: {x}");
-}
-

В коде функции five нет вызовов функций, макросов или даже указаний let — есть только одно число 5. Это является абсолютно правильной функцией в Rust. Заметьте, что возвращаемый вид у данной функции определён как -> i32. Попробуйте запустить этот код. Вывод будет таким:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
-     Running `target/debug/functions`
-The value of x is: 5
-
-

Значение 5 в five является возвращаемым функцией значением, поэтому возвращаемый вид - i32. Рассмотрим пример более подробно. Здесь есть два важных особенности: во-первых, строка let x = five(); показывает использование возвращаемого функцией значения для объявления переменной. Так как функция five возвращает 5, то эта строка эквивалентна следующей:

-
#![allow(unused)]
-fn main() {
-let x = 5;
-}
-

Во-вторых, у функции five нет свойств и определён вид возвращаемого значения, но тело функции представляет собой одинокую 5 без точки с запятой, потому что это выражение, значение которого мы хотим вернуть.

-

Рассмотрим другой пример:

-

Имя файла: src/main.rs

-
fn main() {
-    let x = plus_one(5);
-
-    println!("The value of x is: {x}");
-}
-
-fn plus_one(x: i32) -> i32 {
-    x + 1
-}
-

Запуск кода напечатает The value of x is: 6. Но если поставить точку с запятой в конце строки, содержащей x + 1, превратив её из выражения в указанию, мы получим ошибку:

-

Имя файла: src/main.rs

-
fn main() {
-    let x = plus_one(5);
-
-    println!("The value of x is: {x}");
-}
-
-fn plus_one(x: i32) -> i32 {
-    x + 1;
-}
-

Сборка данного кода вызывает следующую ошибку:

-
$ cargo run
-   Compiling functions v0.1.0 (file:///projects/functions)
-error[E0308]: mismatched types
- --> src/main.rs:7:24
-  |
-7 | fn plus_one(x: i32) -> i32 {
-  |    --------            ^^^ expected `i32`, found `()`
-  |    |
-  |    implicitly returns `()` as its body has no tail or `return` expression
-8 |     x + 1;
-  |          - help: remove this semicolon to return this value
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `functions` (bin "functions") due to 1 previous error
-
-

Основное сообщение об ошибке, несовпадение видов, раскрывает ключевую неполадку этого кода. Определение функции plus_one сообщает, что будет возвращено i32, но указания не вычисляются в значение, что и выражается единичным видом (). Следовательно, ничего не возвращается, что противоречит определению функции и приводит к ошибке. В этом выводе Ржавчина выдаёт сообщение, которое, возможно, поможет исправить эту неполадку: он предлагает удалить точку с запятой для устранения ошибки.

-

Примечания

-

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

-

Пример простого примечания:

-
#![allow(unused)]
-fn main() {
-// Hello, world.
-}
-

В Ржавчина принят идиоматический исполнение примечаниев, который начинает примечание с двух косых черт, и примечание продолжается до конца строки. Для примечаниев, выходящих за пределы одной строки, необходимо включить // в каждую строку, как показано ниже:

-
#![allow(unused)]
-fn main() {
-// Итак, мы делаем что-то сложное, настолько длинное, что нам нужно
-// несколько строк примечаниев, чтобы сделать это! Ух! Надеюсь, этот примечание
-// объясняет, что происходит.
-}
-

Примечания также можно размещать в конце строк, содержащих код:

-

Имя файла: src/main.rs

-
fn main() {
-    let lucky_number = 7; // I’m feeling lucky today
-}
-

Но чаще всего они используются в таком виде: примечание располагается на отдельной строке над кодом, который он определяет:

-

Имя файла: src/main.rs

-
fn main() {
-    // I’m feeling lucky today
-    let lucky_number = 7;
-}
-

В Ржавчина есть ещё один вид примечаниев - документационные примечания, которые мы обсудим в разделе "Обнародование дополнения на Crates.io" главы 14.

-

Управляющие устройства

-

Возможности запуска некоторого кода в зависимости от некоторого условия, и замкнутого выполнения некоторого кода, являются основными элементами в большинстве языков программирования. Наиболее распространёнными устройствоми, позволяющими управлять потоком выполнения кода Rust, являются выражения if и циклы.

-

Выражения if

-

Выражение if позволяет выполнять части кода в зависимости от условий. Вы задаёте условие, а затем указываете: "Если это условие выполняется, выполните этот разделкода. Если условие не выполняется, не выполняйте этот разделкода".

-

Для изучения выражения if создайте новый дело под названием branches в папке projects. В файл src/main.rs поместите следующий код:

-

Имя файла: src/main.rs

-
fn main() {
-    let number = 3;
-
-    if number < 5 {
-        println!("condition was true");
-    } else {
-        println!("condition was false");
-    }
-}
-

Условие начинается с ключевого слова if, за которым следует условное выражение. В данном случае условное выражение проверяет, имеет ли переменная number значение меньше 5. Сразу после условного выражения внутри фигурных скобок мы помещаем разделкода, который будет выполняться, если итог равен true. Блоки кода, связанные с условными выражениями, иногда называют ветками, как и ветки в выражениях match, которые мы обсуждали в разделе "Сравнение догадки с тайным числом" главы 2.

-

Это необязательно, но мы также можем использовать ключевое слово else, которое мы используем в данном примере, чтобы предоставить программе иной разделвыполнения кода, выполняющийся если итог вычисления будет ложным. Если не указать выражение else и условие будет ложным, программа просто пропустит разделif и перейдёт к следующему отрывку кода.

-

Попробуйте запустить этот код. Появится следующий итог:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
-     Running `target/debug/branches`
-condition was true
-
-

Попробуйте изменить значение number на значение, которое делает условие false и посмотрите, что произойдёт:

-
fn main() {
-    let number = 7;
-
-    if number < 5 {
-        println!("condition was true");
-    } else {
-        println!("condition was false");
-    }
-}
-

Запустите программу снова и посмотрите на вывод:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
-     Running `target/debug/branches`
-condition was false
-
-

Также стоит отметить, что условие в этом коде должно быть логического вида bool. Если условие не является bool, возникнет ошибка. Например, попробуйте запустить следующий код:

-

Имя файла: src/main.rs

-
fn main() {
-    let number = 3;
-
-    if number {
-        println!("number was three");
-    }
-}
-

На этот раз условие if вычисляется в значение 3, и Ржавчина бросает ошибку:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-error[E0308]: mismatched types
- --> src/main.rs:4:8
-  |
-4 |     if number {
-  |        ^^^^^^ expected `bool`, found integer
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `branches` (bin "branches") due to 1 previous error
-
-

Ошибка говорит, что Ржавчина ожидал вид bool, но получил значение целочисленного вида. В отличии от других языков вроде Ruby и JavaScript, Ржавчина не будет пытаться самостоятельно преобразовывать нелогические виды в логические. Необходимо явно и всегда использовать if с логическим видом в качестве условия. Если нужно, чтобы разделкода if запускался только, когда число не равно 0, то, например, мы можем изменить выражение if на следующее:

-

Имя файла: src/main.rs

-
fn main() {
-    let number = 3;
-
-    if number != 0 {
-        println!("number was something other than zero");
-    }
-}
-

Будет напечатана следующая строка number was something other than zero.

-

Обработка нескольких условий с помощью else if

-

Можно использовать несколько условий, сочетая if и else в выражении else if. Например:

-

Имя файла: src/main.rs

-
fn main() {
-    let number = 6;
-
-    if number % 4 == 0 {
-        println!("number is divisible by 4");
-    } else if number % 3 == 0 {
-        println!("number is divisible by 3");
-    } else if number % 2 == 0 {
-        println!("number is divisible by 2");
-    } else {
-        println!("number is not divisible by 4, 3, or 2");
-    }
-}
-

У этой программы есть четыре возможных пути выполнения. После её запуска вы должны увидеть следующий итог:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
-     Running `target/debug/branches`
-number is divisible by 3
-
-

Во время выполнения этой программы по очереди проверяется каждое выражение if и выполняется первый блок, для которого условие true. Заметьте, что хотя 6 делится на 2, мы не видим ни вывода number is divisible by 2, ни текста number is not divisible by 4, 3, or 2 из раздела else. Так происходит потому, что Ржавчина выполняет разделтолько для первого истинного условия, а обнаружив его, даже не проверяет остальные.

-

Использование множества выражений else if приводит к загромождению кода, поэтому при наличии более чем одного выражения, возможно, стоит провести переработка кода кода. В главе 6 описана мощная устройство ветвления Ржавчина для таких случаев, называемая match.

-

Использование if в указания let

-

Поскольку if является выражением, его можно использовать в правой части указания let для присвоения итога переменной, как в приложении 3-2.

-

Имя файла: src/main.rs

-
fn main() {
-    let condition = true;
-    let number = if condition { 5 } else { 6 };
-
-    println!("The value of number is: {number}");
-}
-

Приложение 3-2: Присвоение итога выражения if переменной

-

Переменная number будет привязана к значению, которое является итогом выражения if. Запустим код и посмотрим, что происходит:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
-     Running `target/debug/branches`
-The value of number is: 5
-
-

Вспомните, что разделы кода вычисляются последним выражением в них, а числа сами по себе также являются выражениями. В данном случае, значение всего выражения if зависит от того, какой разделвыполняется. При этом значения, которые могут быть итогами каждого из ветвей if, должны быть одного вида. В Приложении 3-2, итогами обеих ветвей if и else являются целочисленный вид i32. Если виды не совпадают, как в следующем примере, мы получим ошибку:

-

Имя файла: src/main.rs

-
fn main() {
-    let condition = true;
-
-    let number = if condition { 5 } else { "six" };
-
-    println!("The value of number is: {number}");
-}
-

При попытке сборки этого кода, мы получим ошибку. Ветви if и else представляют несовместимые виды значений, и Ржавчина точно указывает, где искать неполадку в программе:

-
$ cargo run
-   Compiling branches v0.1.0 (file:///projects/branches)
-error[E0308]: `if` and `else` have incompatible types
- --> src/main.rs:4:44
-  |
-4 |     let number = if condition { 5 } else { "six" };
-  |                                 -          ^^^^^ expected integer, found `&str`
-  |                                 |
-  |                                 expected because of this
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `branches` (bin "branches") due to 1 previous error
-
-

Выражение в разделе if вычисляется как целочисленное, а выражение в разделе else вычисляется как строка. Это не сработает, потому что переменные должны иметь один вид, а Ржавчина должен знать во время сборки, какого вида переменная number. Зная вид number, сборщик может убедиться, что вид действителен везде, где мы используем number. Ржавчина не смог бы этого сделать, если бы вид number определялся только во время выполнения. Сборщик усложнился бы и давал бы меньше заверений в отношении кода, если бы ему приходилось отслеживать несколько гипотетических видов для любой переменной.

-

Повторное выполнение кода с помощью циклов

-

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

-

В Ржавчина есть три вида циклов: loop, while и for. Давайте попробуем каждый из них.

-

Повторение выполнения кода с помощью loop

-

Ключевое слово loop говорит Ржавчина выполнять разделкода снова и снова до бесконечности или пока не будет явно приказано остановиться.

-

В качестве примера, измените код файла src/main.rs в папке дела loops на код ниже:

-

Имя файла: src/main.rs

-
fn main() {
-    loop {
-        println!("again!");
-    }
-}
-

Когда запустим эту программу, увидим, как again! печатается снова и снова, пока не остановить программу вручную. Большинство окно вызоваов поддерживают сочетание клавиш ctrl-c для прерывания программы, которая застряла в непрерывном цикле. Попробуйте:

- -
$ cargo run
-   Compiling loops v0.1.0 (file:///projects/loops)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.29s
-     Running `target/debug/loops`
-again!
-again!
-again!
-again!
-^Cagain!
-
-

Символ ^C обозначает место, где было нажато ctrl-c . В зависимости от того, где находился код в цикле в мгновение получения звонка отпрерывания, вы можете увидеть или не увидеть слово again!, напечатанное после ^C.

-

К счастью, Ржавчина также предоставляет способ выйти из цикла с помощью кода. Ключевое слово break нужно поместить в цикл, чтобы указать программе, когда следует прекратить выполнение цикла. Напоминаем, мы делали так в игре "Угадайка" в разделе "Выход после правильной догадки" Главы 2, чтобы выйти из программы, когда пользователь выиграл игру, угадав правильное число.

-

Мы также использовали continue в игре "Угадайка", которое указывает программе в цикле пропустить весь оставшийся код в данной повторения цикла и перейти к следующей повторения.

-

Возвращение значений из циклов

-

Одно из применений loop - это повторение действия, которая может закончиться неудачей, например, проверка успешности выполнения потоком своего задания. Также может понадобиться передать из цикла итог этой действия в остальную часть кода. Для этого можно добавить возвращаемое значение после выражения break, которое используется для остановки цикла. Это значение будет возвращено из цикла, и его можно будет использовать, как показано здесь:

-
fn main() {
-    let mut counter = 0;
-
-    let result = loop {
-        counter += 1;
-
-        if counter == 10 {
-            break counter * 2;
-        }
-    };
-
-    println!("The result is {result}");
-}
-

Перед циклом мы объявляем переменную с именем counter и объявим её значением 0. Затем мы объявляем переменную с именем result для хранения значения, возвращаемого из цикла. На каждой повторения цикла мы добавляем 1 к переменной counter, а затем проверяем, равняется ли 10 переменная counter. Когда это происходит, мы используем ключевое слово break со значением counter * 2. После цикла мы ставим точку с запятой для завершения указания, присваивающей значение result. Наконец, мы выводим значение в result, равное в данном случае 20.

-

Метки циклов для устранения неоднозначности между несколькими циклами

-

Если у вас есть циклы внутри циклов, break и continue применяются к самому внутреннему циклу в этой цепочке. При желании вы можете создать метку цикла, которую вы затем сможете использовать с break или continue для указания, что эти ключевые слова применяются к помеченному циклу, а не к самому внутреннему циклу. Метки цикла должны начинаться с одинарной кавычки. Вот пример с двумя вложенными циклами:

-
fn main() {
-    let mut count = 0;
-    'counting_up: loop {
-        println!("count = {count}");
-        let mut remaining = 10;
-
-        loop {
-            println!("remaining = {remaining}");
-            if remaining == 9 {
-                break;
-            }
-            if count == 2 {
-                break 'counting_up;
-            }
-            remaining -= 1;
-        }
-
-        count += 1;
-    }
-    println!("End count = {count}");
-}
-

Внешний цикл имеет метку 'counting_up, и он будет считать от 0 до 2. Внутренний цикл без метки ведёт обратный отсчёт от 10 до 9. Первый break, который не содержит метку, выйдет только из внутреннего цикла. Указание break 'counting_up; завершит внешний цикл. Этот код напечатает:

-
$ cargo run
-   Compiling loops v0.1.0 (file:///projects/loops)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s
-     Running `target/debug/loops`
-count = 0
-remaining = 10
-remaining = 9
-count = 1
-remaining = 10
-remaining = 9
-count = 2
-remaining = 10
-End count = 2
-
-

Циклы с условием while

-

В программе часто требуется проверить состояние условия в цикле. Пока условие истинно, цикл выполняется. Когда условие перестаёт быть истинным, программа вызывает break, останавливая цикл. Такое поведение можно выполнить с помощью сочетания loop, if, else и break. При желании попробуйте сделать это в программе. Это настолько распространённый образец, что в Ржавчина выполнена встроенная языковая устройство для него, называемая цикл while. В приложении 3-3 мы используем while, чтобы выполнить три цикла программы, производя каждый раз обратный отсчёт, а затем, после завершения цикла, печатаем сообщение и выходим.

-

Имя файла: src/main.rs

-
fn main() {
-    let mut number = 3;
-
-    while number != 0 {
-        println!("{number}!");
-
-        number -= 1;
-    }
-
-    println!("LIFTOFF!!!");
-}
-

Приложение 3-3: Использование цикла while для выполнения кода, пока условие истинно

-

Эта устройство устраняет множество вложений, которые потребовались бы при использовании loop, if, else и break, и она более понятна. Пока условие вычисляется в true, код выполняется; в противном случае происходит выход из цикла.

-

Цикл по элементам собрания с помощью for

-

Для перебора элементов собрания, например, массива, можно использовать устройство while. Например, цикл в приложении 3-4 печатает каждый элемент массива a.

-

Имя файла: src/main.rs

-
fn main() {
-    let a = [10, 20, 30, 40, 50];
-    let mut index = 0;
-
-    while index < 5 {
-        println!("the value is: {}", a[index]);
-
-        index += 1;
-    }
-}
-

Приложение 3-4: Перебор каждого элемента собрания с помощью цикла while

-

Этот код выполняет перебор элементов массива. Он начинается с порядкового указателя 0, а затем замкнуто выполняется, пока не достигнет последнего порядкового указателя в массиве (то есть, когда index < 5 уже не является истиной). Выполнение этого кода напечатает каждый элемент массива:

-
$ cargo run
-   Compiling loops v0.1.0 (file:///projects/loops)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s
-     Running `target/debug/loops`
-the value is: 10
-the value is: 20
-the value is: 30
-the value is: 40
-the value is: 50
-
-

Все пять значений массива появляются в окне вызова, как и ожидалось. Поскольку index в какой-то мгновение достигнет значения 5, цикл прекратит выполнение перед попыткой извлечь шестое значение из массива.

-

Однако такой подход чреват ошибками; мы можем вызвать панику в программе, если значение порядкового указателя или условие проверки неверны. Например, если изменить определение массива a на четыре элемента, но забыть обновить условие на while index < 4, код вызовет панику. Также это медленно, поскольку сборщик добавляет код времени выполнения для обеспечения проверки нахождения порядкового указателя в границах массива на каждой повторения цикла.

-

В качестве более краткой иного решения можно использовать цикл for и выполнять некоторый код для каждого элемента собрания. Цикл for может выглядеть как код в приложении 3-5.

-

Имя файла: src/main.rs

-
fn main() {
-    let a = [10, 20, 30, 40, 50];
-
-    for element in a {
-        println!("the value is: {element}");
-    }
-}
-

Приложение 3-5: Перебор каждого элемента собрания с помощью цикла for

-

При выполнении этого кода мы увидим тот же итог, что и в приложении 3-4. Что важнее, теперь мы повысили безопасность кода и устранили вероятность ошибок, которые могут возникнуть в итоге выхода за пределы массива или недостаточно далёкого перехода и пропуска некоторых элементов.

-

При использовании цикла for не нужно помнить о внесении изменений в другой код, в случае изменения количества значений в массиве, как это было бы с способом, использованным в приложении 3-4.

-

Безопасность и краткость циклов for делают их наиболее часто используемой устройством цикла в Rust. Даже в случаейх необходимости выполнения некоторого кода определённое количество раз, как в примере обратного отсчёта, в котором использовался цикл while из Приложения 3-3, большинство Rustaceans использовали бы цикл for. Для этого можно использовать Range, предоставляемый встроенной библиотекой, который порождает последовательность всех чисел, начиная с первого числа и заканчивая вторым числом, но не включая его (т.е. (1..4) эквивалентно [1, 2, 3] или в общем случае (start..end) эквивалентно [start, start+1, start+2, ... , end-2, end-1] - прим.переводчика).

-

Вот как будет выглядеть обратный отсчёт с использованием цикла for и другого способа, о котором мы ещё не говорили, rev, для разворота ряда:

-

Имя файла: src/main.rs

-
fn main() {
-    for number in (1..4).rev() {
-        println!("{number}!");
-    }
-    println!("LIFTOFF!!!");
-}
-

Данный код выглядит лучше, не так ли?

-

Итоги

-

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

-
    -
  • Преобразование температур между значениями по Фаренгейту к Цельсию.
  • -
  • Порождение n-го числа Фибоначчи.
  • -
  • Распечатайте текст рождественской песни "Двенадцать дней Рождества", воспользовавшись повторами в песне.
  • -
-

Когда вы будете готовы двигаться дальше, мы поговорим о подходы в Rust, которая не существует обычно в других языках программирования: владение.

-

Понимание Владения

-

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

-

Что такое владение?

-

Владение — это набор правил, определяющих, как программа на языке Ржавчина управляет памятью. Все программы так или иначе должны управлять тем, как они используют память компьютера во время работы. Некоторые языки имеют сборщик мусора, постоянно отслеживающий неиспользуемую память во время работы программы; в других языках программист должен явно выделять и освобождать память. В Ржавчина используется третий подход: память управляется через систему владения с набором правил, которые проверяются сборщиком. При нарушении любого из правил программа не будет собрана. Ни одна из особенностей системы владения не замедлит работу вашей программы.

-

Поскольку владение является новой подходом для многих программистов, требуется некоторое время, чтобы привыкнуть к ней. Хорошая новость заключается в том, что чем больше у вас будет опыта с Ржавчина и с правилами системы владения, тем легче вам будет естественным образом разрабатывать безопасный и эффективный код. Держитесь! Не сдавайтесь!

-

Понимание подходы владения даст вам основу для понимания всех остальных особенностей, делающих Ржавчина единственным. В этой главе вы изучите владение на примерах, которые сосредоточены на наиболее часто используемой устройстве данных: строках.

-
-

Обойма и куча

-

Многие языки программирования не требуют, чтобы вы слишком часто думали о обойме и куче. Но в языках системного программирования, одним из которых является Rust, то, какое значение находится в обойме или в куче, влияет на поведение языка и на принятие вами определённых решений. Владение будет описано через призму обоймы и кучи позже в этой главе, а пока — краткое пояснение.

-

И обойма, и куча — это части памяти, доступные вашему коду для использования во время выполнения. Однако они внутренне выстроенны

-
-

по-разному. Обойма хранит значения в порядке их получения, а удаляет — в обратном. Это называется «последним пришёл — первым ушёл». Подумайте о стопке тарелок: когда вы добавляете тарелки, вы кладёте их сверху стопки — когда вам нужна тарелка, вы берёте одну так же сверху. Добавление или удаление тарелок посередине или снизу не сработает! Добавление данных называется помещением в обойма, а удаление — извлечением из обоймы. Все данные, хранящиеся в обойме, должны иметь известный определенный размер. Данные, размер которых во время сборки неизвестен или может измениться, должны храниться в куче.

-
-

Куча устроена менее согласованно: когда вы кладёте данные в кучу, вы запрашиваете определённый объём пространства. Операционная система находит в куче свободный участок памяти достаточного размера, помечает его как используемый и возвращает указатель, являющийся адресом этого участка памяти. Этот этап называется выделением памяти в куче и иногда сокращается до выделения памяти (помещение значений в обойма не считается выделением). Поскольку указатель на участок памяти в куче имеет определённый определенный размер, его можно расположить в обойме, однако когда вам понадобятся актуальные данные, вам придётся проследовать по указателю. Представьте, что вы сидите в ресторане. Когда вы входите, вы называете количество человек в вашей объединении, и человек находит свободный стол, которого хватит на всех, и ведёт вас туда. Если кто-то из вашей объединение опоздает, он может спросить, куда вас посадили, чтобы найти вас.

-

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

-

Доступ к данным в куче медленнее, чем доступ к данным в обойме, потому что вам нужно следовать по адресу указателя, чтобы добраться туда. Современные процессоры работают быстрее, если они меньше прыгают по памяти. Продолжая подобие, рассмотрим официанта в ресторане, принимающего заказы со многих столов. Наиболее эффективно будет получить все заказы за одним столом, прежде чем переходить к следующему столу. Получение заказа со стола А, затем со стола В, затем снова одного с А, а затем снова одного с В было бы гораздо более медленным делом. Точно так же процессор может выполнять свою работу лучше, если он работает с данными, которые находятся близко к другим данным (как в обойме), а не далеко (как это может быть в куче).

-

Когда ваш код вызывает функцию, значения, переданные в неё (возможно включающие указатели на данные в куче), и местные переменные помещаются в обойма. Когда функция завершается, эти значения извлекаются из обоймы.

-

Отслеживание того, какие части кода используют какие данные, уменьшение количества повторяющихся данных и очистка неиспользуемых данных в куче, чтобы не исчерпать пространство, — все эти сбоев решает владение. Как только вы поймёте, что такое владение, вам не нужно будет слишком часто думать о обойме и куче. Однако знание того, что основная цель владения — управление данными кучи, может помочь объяснить, почему оно работает именно так.

-
-

Правила владения

-

Во-первых, давайте взглянем на правила владения. Помните об этих правилах, пока мы работаем с примерами, которые их отображают:

-
    -
  • У каждого значения в Ржавчина есть владелец,
  • -
  • У значения может быть только один владелец в один мгновение времени,
  • -
  • Когда владелец покидает область видимости, значение удаляется.
  • -
-

Область видимости переменной

-

Теперь, когда мы прошли основной правила написания Rust, мы не будем включать весь код fn main() { в примеры. Поэтому, если вы будете следовать этому курсу, убедитесь, что следующие примеры помещены в функцию main вручную. В итоге наши примеры будут более краткими, что позволит нам сосредоточиться на существующих подробностях, а не на образцовом коде.

-

В качестве первого примера владения мы рассмотрим область видимости некоторых переменных. Область видимости — это рядвнутри программы, для которого допустим элемент. Возьмём следующую переменную:

-
#![allow(unused)]
-fn main() {
-let s = "hello";
-}
-

Переменная s относится к строковому записи, где значение строки жёстко прописано в тексте нашей программы. Переменная действительна с особенности её объявления до конца текущей области видимости. В приложении 4-1 показана программа с примечаниями, указывающими, где допустима переменная s .

-
fn main() {
-    {                      // s is not valid here, it’s not yet declared
-        let s = "hello";   // s is valid from this point forward
-
-        // do stuff with s
-    }                      // this scope is now over, and s is no longer valid
-}
-

Приложение 4-1: переменная и область действия, в которой она допустима

-

Другими словами, здесь есть два важных особенности:

-
    -
  • Когда переменная s появляется в области видимости, она считается действительной,
  • -
  • Она остаётся действительной до особенности выхода за границы этой области.
  • -
-

На этом этапе объяснения взаимосвязь между областями видимости и допустимостью переменных подобна той, что существует в других языках программирования. Теперь мы будем опираться на это понимание, введя вид String.

-

Вид данных String

-

Для отображения правил владения нам требуется более сложный вид данных чем те, что мы обсуждали в части "Виды данных" Главы 3. Виды, рассмотренные ранее, имеют определённый размер, а значит могут быть размещены на обойме и извлечены из него, когда их область видимости закончится, и могут быть быстро и обыкновенно воспроизведены для создания новой, независимой повторы, если другой части кода нужно использовать то же самое значение в другой области видимости. Но мы хотим посмотреть на данные, хранящиеся в куче, и выяснить, как Ржавчина узнаёт, когда нужно очистить эти данные, поэтому вид String — отличный пример.

-

Мы сосредоточимся на тех частях String, которые связаны с владением. Эти особенности также применимы к другим сложным видам данных, независимо от того, предоставлены они встроенной библиотекой или созданы вами. Более подробно мы обсудим String в главе 8.

-

Мы уже видели строковые записи, где строковое значение жёстко прописано в нашей программе. Строковые записи удобны, но они подходят не для каждой случаи, где мы можем хотеть использовать текст. Одна из причин заключается в том, что они неизменны. Кроме того, не каждое строковое значение может быть известно во время написания кода: что, если мы захотим принять и сохранить пользовательский ввод? Для таких случаев в Ржавчина есть ещё один строковый вид — String. Этот вид управляет данными, выделенными в куче, и поэтому может хранить объём текста, который во время сборки неизвестен. Также вы можете создать String из строкового записи, используя функцию from, например:

-
#![allow(unused)]
-fn main() {
-let s = String::from("hello");
-}
-

Оператор "Двойное двоеточие" :: позволяет использовать пространство имён данной именно функции from с видом String, а не какое-то иное имя, такое как string_from. Мы обсудим этот правила написания более подробно в разделе «Синтаксис способа». раздел Главы 5, и в ходе обсуждения пространств имён с звенами в «Пути для обращения к элементу в дереве звеньев» в главе 7.

-

Строка такого вида может быть изменяема:

-
fn main() {
-    let mut s = String::from("hello");
-
-    s.push_str(", world!"); // push_str() appends a literal to a String
-
-    println!("{s}"); // This will print `hello, world!`
-}
-

В чем же тут разница? Почему строку String можно изменить, а записи — нельзя? Разница заключается в том, как эти два вида работают с памятью.

-

Память и способы её выделения

-

В случае строкового записи мы знаем его содержимое во время сборки, и оно жёстко прописано в итоговом исполняемом файле. Причина того, что строковые записи более быстрые и эффективные, в их неизменяемости. К сожалению, нельзя поместить неопределённый кусок памяти в выполняемый файл для текста, размер которого неизвестен при сборки и может меняться во время выполнения программы.

-

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

-
    -
  • Память должна запрашиваться у операционной системы во время выполнения программы,
  • -
  • Необходим способ возврата этой памяти операционной системе, когда мы закончили в программе работу со String.
  • -
-

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

-

Однако вторая часть отличается. В языках со сборщиком мусора (GC), память, которая больше не используется, отслеживается и очищается с его помощью — нам не нужно об этом думать. В большинстве языков без сборщика мусора мы обязаны сами определять, когда память больше не используется, и вызывать код для явного её освобождения, точно так же, как мы делали это для её запроса. Правильное выполнение этого этапа исторически было сложной неполадкой программирования. Если мы забудем освободить память, она будет потеряна. Если мы сделаем это слишком рано, у нас будет недопустимая переменная. Сделать это дважды — тоже будет ошибкой. Нам нужно соединить ровно один allocate ровно с одним free.

-

Rust выбирает другой путь: память самостоятельно возвращается, как только владеющая памятью переменная выходит из области видимости. Вот исполнение примера с областью видимости из приложения 4-1, в котором используется вид String вместо строкового записи:

-
fn main() {
-    {
-        let s = String::from("hello"); // s is valid from this point forward
-
-        // do stuff with s
-    }                                  // this scope is now over, and s is no
-                                       // longer valid
-}
-

Существует естественный мгновение, когда мы можем вернуть память, необходимую нашему String, обратно распределителю — когда s выходит за пределы области видимости. Когда переменная выходит за пределы области видимости, Ржавчина вызывает для нас особую функцию. Эта функция называется drop, и именно здесь автор String может поместить код для возврата памяти. Ржавчина самостоятельно вызывает drop после закрывающей фигурной скобки.

-
-

Примечание: в C++ этот образец освобождения ресурсов в конце времени жизни элемента иногда называется «Получение ресурса есть объявление» (англ. Resource Acquisition Is Initialization (RAII)). Функция drop в Ржавчина покажется вам знакомой, если вы использовали образцы RAII.

-
-

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

- -

-

Взаимодействие переменных и данных с помощью перемещения

-

Несколько переменных могут по-разному взаимодействовать с одними и теми же данными в Rust. Давайте рассмотрим пример использования целого числа в приложении 4-2.

-
fn main() {
-    let x = 5;
-    let y = x;
-}
-

Приложение 4-2. Присвоение целочисленного значения переменной x переменной y

-

Мы можем догадаться, что делает этот код: «привязать значение 5 к x; затем сделать повтор значения в x и привязать его к y». Теперь у нас есть две переменные: x и y, и обе равны 5. Это то, что происходит на самом деле, потому что целые числа — это простые значения с известным конечным размером, и эти два значения 5 помещаются в обойма.

-

Теперь рассмотрим исполнение с видом String:

-
fn main() {
-    let s1 = String::from("hello");
-    let s2 = s1;
-}
-

Это выглядит очень похоже, поэтому мы можем предположить, что происходит то же самое: вторая строка сделает повтор значения в s1 и привяжет его к s2. Но это не совсем так.

-

Взгляните на рисунок 4-1, чтобы увидеть, что происходит со String под капотом. String состоит из трёх частей, показанных слева: указатель на память, в которой хранится содержимое строки, длина и ёмкость. Эта объединение данных хранится в обойме. Справа — память в куче, которая содержит содержимое.

-Two tables: the first table contains the representation of s1 on the<br>stack, consisting of its length (5), capacity (5), and a pointer to the first<br>value in the second table. The second table contains the representation of the<br>string data on the heap, byte by byte. -

Рисунок 4-1: представление в памяти String, содержащей значение "hello", привязанное к s1

-

Длина — это объём памяти в байтах, который в настоящее время использует содержимое String. Ёмкость — это общий объём памяти в байтах, который String получил от распределителя. Разница между длиной и ёмкостью имеет значение, но не в этом среде, поэтому на данный мгновение можно пренебрегать ёмкость.

-

Когда мы присваиваем s1 значению s2, данные String повторяются, то есть мы повторяем указатель, длину и ёмкость, которые находятся в обойме. Мы не повторяем данные в куче, на которые указывает указатель. Другими словами, представление данных в памяти выглядит так, как показано на рис. 4-2.

-Three tables: tables s1 and s2 representing those strings on the<br>stack, respectively, and both pointing to the same string data on the heap. -

Рисунок 4-2: представление в памяти переменной s2, имеющей повтор указателя, длины и ёмкости s1

-

Представление не похоже на рисунок 4-3, как выглядела бы память, если бы вместо этого Ржавчина также воспроизвел данные кучи. Если бы Ржавчина сделал это, действие s2 = s1 могла бы быть очень дорогой с точки зрения производительности во время выполнения, если бы данные в куче были большими.

-Two tables: the first table contains the representation of s1 on the<br>stack, consisting of its length (5), capacity (5), and a pointer to the first<br>value in the second table. The second table contains the representation of the<br>string data on the heap, byte by byte. -

Рисунок 4-3: другой исход того, что может сделать s2 = s1, если Ржавчина также воспроизведет данные кучи

-

Ранее мы сказали, что когда переменная выходит за пределы области видимости, Ржавчина самостоятельно вызывает функцию drop и очищает память в куче для данной переменной. Но на рис. 4.2 оба указателя данных указывают на одно и то же место. Это неполадка: когда переменные s2 и s1 выходят из области видимости, они обе будут пытаться освободить одну и ту же память в куче. Это известно как ошибка двойного освобождения (double free) и является одной из ошибок безопасности памяти, упоминаемых ранее. Освобождение памяти дважды может привести к повреждению памяти, что возможно может привести к уязвимостям безопасности.

-

Чтобы обеспечить безопасность памяти, после строки let s2 = s1; , Ржавчина считает s1 более недействительным. Следовательно, Ржавчина не нужно ничего освобождать, когда s1 выходит за пределы области видимости. Посмотрите, что происходит, когда вы пытаетесь использовать s1 после создания s2 ; это не сработает:

-
fn main() {
-    let s1 = String::from("hello");
-    let s2 = s1;
-
-    println!("{s1}, world!");
-}
-

Вы получите похожую ошибку, потому что Ржавчина не позволяет вам использовать недействительную ссылку:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0382]: borrow of moved value: `s1`
- --> src/main.rs:5:15
-  |
-2 |     let s1 = String::from("hello");
-  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
-3 |     let s2 = s1;
-  |              -- value moved here
-4 |
-5 |     println!("{s1}, world!");
-  |               ^^^^ value borrowed here after move
-  |
-  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
-help: consider cloning the value if the performance cost is acceptable
-  |
-3 |     let s2 = s1.clone();
-  |                ++++++++
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `ownership` (bin "ownership") due to 1 previous error
-
-

Если вы слышали понятия поверхностное повторение и глубокое повторение при работе с другими языками, подход повторения указателя, длины и ёмкости без повторения данных, вероятно, звучит как создание поверхностной повторы. Но поскольку Ржавчина также аннулирует первую переменную, вместо того, чтобы называть это поверхностным повторением, это называется перемещением. В этом примере мы бы сказали, что s1 был перемещён в s2. Итак, что на самом деле происходит, показано на рисунке 4-4.

-Три таблицы: таблицы s1 и s2, представляющие эти строки в обойме соответственно, и обе указывающие на одни и те же строковые данные в куче. Таблица s1 выделена серым цветом, потому что s1 больше недействительна; только s2 можно использовать для доступа к данным кучи. -

Рисунок 4-4: представление в памяти после того, как s1 был признан недействительным

-

Это решает нашу неполадку! Действительной остаётся только переменная s2. Когда она выходит из области видимости, то она одна будет освобождать память в куче.

-

Такой выбор внешнего вида языка даёт дополнительное преимущество: Ржавчина никогда не будет самостоятельно создавать «глубокие» повторы ваших данных. Следовательно любое такое самостоятельное повторение можно считать недорогим с точки зрения производительности во время выполнения.

- -

-

Взаимодействие переменных и данных с помощью клонирования

-

Если мы хотим глубоко воспроизвести данные кучи String, а не только данные обоймы, мы можем использовать общий способ, называемый clone. Мы обсудим правила написания способов в главе 5, но поскольку способы являются общей чертой многих языков программирования, вы, вероятно, уже встречались с ними.

-

Вот пример работы способа clone:

-
fn main() {
-    let s1 = String::from("hello");
-    let s2 = s1.clone();
-
-    println!("s1 = {s1}, s2 = {s2}");
-}
-

Это отлично работает и очевидно приводит к поведению, представленному на рисунке 4-3, где данные кучи были воспроизведены.

-

Когда вы видите вызов clone, вы знаете о выполнении некоторого кода, который может быть дорогим. В то же время использование clone является визуальным индикатором того, что тут происходит что-то необычное.

-

Из обоймы данные: повторение

-

Это ещё одна особенность о которой мы ранее не говорили. Этот код, часть которого была показа ранее в приложении 4-2, использует целые числа. Он работает без ошибок:

-
fn main() {
-    let x = 5;
-    let y = x;
-
-    println!("x = {x}, y = {y}");
-}
-

Но этот код, кажется, противоречит тому, что мы только что узнали: у нас нет вызова clone, но x всё ещё действителен и не был перемещён в y.

-

Причина в том, что такие виды, как целые числа, размер которых известен во время сборки, полностью хранятся в обойме, поэтому повторы действительных значений создаются быстро. Это означает, что нет причин, по которым мы хотели бы предотвратить доступность x после того, как создадим переменную y. Другими словами, здесь нет разницы между глубоким и поверхностным повторением, поэтому вызов clone ничем не отличается от обычного поверхностного повторения, и мы можем его опустить.

-

В Ржавчина есть особая изложение, называемая особенностью Copy, которую мы можем размещать на видах, хранящихся в обойме, как и целые числа (подробнее о видах мы поговорим в главе 10). Если вид выполняет особенность Copy, переменные, которые его используют, не перемещаются, а обыкновенно повторяются, что делает их действительными после присвоения другой переменной.

-

Rust не позволит нам определять вид с помощью Copy, если вид или любая из его частей выполняет Drop. Если для вида нужно, чтобы произошло что-то особенное, когда значение выходит за пределы области видимости, и мы добавляем изложение Copy к этому виду, мы получим ошибку времени сборки. Чтобы узнать, как добавить изложение Copy к вашему виду для выполнения особенности, смотрите раздел «Производные особенности» в приложении С.

-

Но какие же виды выполняют особенность Copy? Можно проверить документацию любого вида для уверенности, но как правило любая объединение простых одиночных значений может быть выполнить Copy, и никакие виды, которые требуют выделения памяти в куче или являются некоторой способом ресурсов, не выполняют особенности Copy. Вот некоторые виды, которые выполняют Copy:

-
    -
  • Все целочисленные виды, такие как u32,
  • -
  • Логический вид данных bool, возможные значения которого true и false,
  • -
  • Все виды с плавающей запятой, такие как f64.
  • -
  • Символьный вид char,
  • -
  • Упорядоченные ряды, но только если они содержат виды, которые также выполняют Copy. Например, (i32, i32) будет с Copy, но упорядоченный ряд (i32, String) уже нет.
  • -
-

Владение и функции

-

Механика передачи значения функции подобна тому, что происходит при присвоении значения переменной. Передача переменной в функцию приведёт к перемещению или воспроизведению, как и присваивание. В приложении 4-3 есть пример с некоторыми изложениями, показывающими, где переменные входят в область видимости и выходят из неё.

-

Файл: src/main.rs

-
fn main() {
-    let s = String::from("hello");  // s comes into scope
-
-    takes_ownership(s);             // s's value moves into the function...
-                                    // ... and so is no longer valid here
-
-    let x = 5;                      // x comes into scope
-
-    makes_copy(x);                  // x would move into the function,
-                                    // but i32 is Copy, so it's okay to still
-                                    // use x afterward
-
-} // Here, x goes out of scope, then s. But because s's value was moved, nothing
-  // special happens.
-
-fn takes_ownership(some_string: String) { // some_string comes into scope
-    println!("{some_string}");
-} // Here, some_string goes out of scope and `drop` is called. The backing
-  // memory is freed.
-
-fn makes_copy(some_integer: i32) { // some_integer comes into scope
-    println!("{some_integer}");
-} // Here, some_integer goes out of scope. Nothing special happens.
-

Приложение 4-3. Функции с определенными владельцами и областью действия

-

Если попытаться использовать s после вызова takes_ownership, Ржавчина выдаст ошибку времени сборки. Такие постоянные проверки защищают от ошибок. Попробуйте добавить код в main, который использует переменную s и x, чтобы увидеть где их можно использовать и где правила владения предотвращают их использование.

-

Возвращение значений и область видимости

-

Возвращаемые значения также могут передавать право владения. В приложении 4-4 показан пример функции, возвращающей некоторое значение, с такими же изложениями, как в приложении 4-3.

-

Файл: src/main.rs

-
fn main() {
-    let s1 = gives_ownership();         // gives_ownership moves its return
-                                        // value into s1
-
-    let s2 = String::from("hello");     // s2 comes into scope
-
-    let s3 = takes_and_gives_back(s2);  // s2 is moved into
-                                        // takes_and_gives_back, which also
-                                        // moves its return value into s3
-} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
-  // happens. s1 goes out of scope and is dropped.
-
-fn gives_ownership() -> String {             // gives_ownership will move its
-                                             // return value into the function
-                                             // that calls it
-
-    let some_string = String::from("yours"); // some_string comes into scope
-
-    some_string                              // some_string is returned and
-                                             // moves out to the calling
-                                             // function
-}
-
-// This function takes a String and returns one
-fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
-                                                      // scope
-
-    a_string  // a_string is returned and moves out to the calling function
-}
-

Приложение 4-4: передача права владения на возвращаемые значения

-

Владение переменной каждый раз следует одному и тому же образцу: присваивание значения другой переменной перемещает его. Когда переменная, содержащая данные в куче, выходит из области видимости, содержимое в куче будет очищено функцией drop, если только данные не были перемещены во владение другой переменной.

-

Хотя это работает, получение права владения, а затем возвращение владения каждой функцией немного утомительно. Что, если мы хотим, чтобы функция использовала значение, но не становилась владельцем? Очень раздражает, что всё, что мы передаём, также должно быть передано обратно, если мы хотим использовать это снова, в дополнение к любым данным, полученным из тела функции, которые мы также можем захотеть вернуть.

-

Rust позволяет нам возвращать несколько значений с помощью упорядоченного ряда, как показано в приложении 4-5.

-

Файл: src/main.rs

-
fn main() {
-    let s1 = String::from("hello");
-
-    let (s2, len) = calculate_length(s1);
-
-    println!("The length of '{s2}' is {len}.");
-}
-
-fn calculate_length(s: String) -> (String, usize) {
-    let length = s.len(); // len() returns the length of a String
-
-    (s, length)
-}
-

Приложение 4-5: возврат права владения на свойства

-

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

-

Ссылки и заимствование

-

Неполадкас кодом упорядоченного ряда в приложении 4-5 заключается в том, что мы должны вернуть String из вызванной функции, чтобы использовать String после вызова calculate_length, потому что String была перемещена в calculate_length. Вместо этого мы можем предоставить ссылку на значение String. Ссылка похожа на указатель в том смысле, что это адрес, по которому мы можем проследовать, чтобы получить доступ к данным, хранящимся по этому адресу; эти данные принадлежат какой-то другой переменной. В отличие от указателя, ссылка обязательно указывает на допустимое значение определённого вида в течение всего срока существования этой ссылки.

-

Вот как вы могли бы определить и использовать функцию calculate_length, имеющую ссылку на предмет в качестве свойства, вместо того, чтобы брать на себя ответственность за значение:

-

Файл: src/main.rs

-
fn main() {
-    let s1 = String::from("hello");
-
-    let len = calculate_length(&s1);
-
-    println!("The length of '{s1}' is {len}.");
-}
-
-fn calculate_length(s: &String) -> usize {
-    s.len()
-}
-

Во-первых, обратите внимание, что весь код упорядоченного ряда в объявлении переменной и возвращаемое значение функции исчезли. Во-вторых, обратите внимание, что мы передаём &s1 в calculate_length и в его определении используем &String, а не String. Эти знаки представляют собой ссылки, и они позволяют вам ссылаться на некоторое значение, не принимая владение над ним. Рисунок 4-5 изображает эту подход.

-&String s pointing at String s1 -

Рисунок 4-5: диаграмма для &String s, указывающей на String s1

-
-

Примечание: противоположностью ссылки с использованием & является разыменование, выполняемое с помощью оператора разыменования *. Мы увидим некоторые исходы использования оператора разыменования в главе 8 и обсудим подробности разыменования в главе 15.

-
-

Давайте подробнее рассмотрим рычаг вызова функции:

-
fn main() {
-    let s1 = String::from("hello");
-
-    let len = calculate_length(&s1);
-
-    println!("The length of '{s1}' is {len}.");
-}
-
-fn calculate_length(s: &String) -> usize {
-    s.len()
-}
-

&s1 позволяет нам создать ссылку, которая ссылается на значение s1, но не владеет им. Поскольку она не владеет им, значение, на которое она указывает, не будет удалено, когда ссылка перестанет использоваться.

-

Ярлык функции использует & для индикации того, что вид свойства s является ссылкой. Добавим объясняющие примечания:

-
fn main() {
-    let s1 = String::from("hello");
-
-    let len = calculate_length(&s1);
-
-    println!("The length of '{s1}' is {len}.");
-}
-
-fn calculate_length(s: &String) -> usize { // s is a reference to a String
-    s.len()
-} // Here, s goes out of scope. But because it does not have ownership of what
-  // it refers to, it is not dropped.
-

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

-

Мы называем этап создания ссылки заимствованием. Как и в существующей жизни, если человек чем-то владеет, вы можете это у него позаимствовать. Когда вы закончите, вы должны вернуть это законному владельцу.

-

А что произойдёт, если попытаться изменить то, что было позаимствовано? Попробуйте код приложения 4-6 Спойлер: этот код не сработает!

-

Файл: src/main.rs

-
fn main() {
-    let s = String::from("hello");
-
-    change(&s);
-}
-
-fn change(some_string: &String) {
-    some_string.push_str(", world");
-}
-

Приложение 4-6: попытка изменения заимствованной переменной

-

Вот ошибка:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
- --> src/main.rs:8:5
-  |
-8 |     some_string.push_str(", world");
-  |     ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
-  |
-help: consider changing this to be a mutable reference
-  |
-7 | fn change(some_string: &mut String) {
-  |                         +++
-
-For more information about this error, try `rustc --explain E0596`.
-error: could not compile `ownership` (bin "ownership") due to 1 previous error
-
-

Как переменные неизменяемы по умолчанию, так и ссылки. Нам не разрешено изменять то, на что у нас есть ссылка.

-

Изменяемые ссылки

-

Мы можем исправить код из приложения 4-6, чтобы позволить себе изменять заимствованное значение, с помощью нескольких небольших настроек, которые используют изменяемую ссылку:

-

Файл: src/main.rs

-
fn main() {
-    let mut s = String::from("hello");
-
-    change(&mut s);
-}
-
-fn change(some_string: &mut String) {
-    some_string.push_str(", world");
-}
-

Сначала мы меняем s на mut. Затем мы создаём изменяемую ссылку с помощью &mut s, у которой вызываем change и обновляем ярлык функции, чтобы принять изменяемую ссылку с помощью some_string: &mut String. Это даёт понять, что change изменит значение, которое заимствует.

-

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

-

Файл: src/main.rs

-
fn main() {
-    let mut s = String::from("hello");
-
-    let r1 = &mut s;
-    let r2 = &mut s;
-
-    println!("{}, {}", r1, r2);
-}
-

Описание ошибки:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0499]: cannot borrow `s` as mutable more than once at a time
- --> src/main.rs:5:14
-  |
-4 |     let r1 = &mut s;
-  |              ------ first mutable borrow occurs here
-5 |     let r2 = &mut s;
-  |              ^^^^^^ second mutable borrow occurs here
-6 |
-7 |     println!("{}, {}", r1, r2);
-  |                        -- first borrow later used here
-
-For more information about this error, try `rustc --explain E0499`.
-error: could not compile `ownership` (bin "ownership") due to 1 previous error
-
-

Эта ошибка говорит о том, что код недействителен, потому что мы не можем заимствовать s как изменяемые более одного раза в один мгновение. Первое изменяемое заимствование находится в r1 и должно длиться до тех пор, пока оно не будет использовано в println!, но между созданием этой изменяемой ссылки и её использованием мы попытались создать другую изменяемую ссылку в r2, которая заимствует те же данные, что и r1.

-

Ограничение, предотвращающее одновременное использование нескольких изменяемых ссылок на одни и те же данные, допускает изменение, но очень управляющим образом. Это то, с чем борются новые Rustaceans, потому что большинство языков позволяют изменять значение в любой мгновение. Преимущество этого ограничения заключается в том, что Ржавчина может предотвратить гонку данных во время сборки. Гонка данных похожа на состояние гонки и происходит, когда возникают следующие три сценария:

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

Гонки данных вызывают неопределённое поведение, и их может быть сложно диагностировать и исправить, когда вы пытаетесь отследить их во время выполнения. Ржавчина предотвращает такую неполадку, отказываясь собирать код с гонками данных!

-

Как всегда, мы можем использовать фигурные скобки для создания новой области видимости, позволяющей использовать несколько изменяемых ссылок, но не одновременно:

-
fn main() {
-    let mut s = String::from("hello");
-
-    {
-        let r1 = &mut s;
-    } // r1 goes out of scope here, so we can make a new reference with no problems.
-
-    let r2 = &mut s;
-}
-

Rust применяет подобное правило для соединения изменяемых и неизменяемых ссылок. Этот код приводит к ошибке:

-
fn main() {
-    let mut s = String::from("hello");
-
-    let r1 = &s; // no problem
-    let r2 = &s; // no problem
-    let r3 = &mut s; // BIG PROBLEM
-
-    println!("{}, {}, and {}", r1, r2, r3);
-}
-

Ошибка:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
- --> src/main.rs:6:14
-  |
-4 |     let r1 = &s; // no problem
-  |              -- immutable borrow occurs here
-5 |     let r2 = &s; // no problem
-6 |     let r3 = &mut s; // BIG PROBLEM
-  |              ^^^^^^ mutable borrow occurs here
-7 |
-8 |     println!("{}, {}, and {}", r1, r2, r3);
-  |                                -- immutable borrow later used here
-
-For more information about this error, try `rustc --explain E0502`.
-error: could not compile `ownership` (bin "ownership") due to 1 previous error
-
-

Вау! У нас также не может быть изменяемой ссылки, пока у нас есть неизменяемая ссылка на то же значение.

-

Пользователи неизменяемой ссылки не ожидают, что значение внезапно изменится из-под них! Однако разрешены множественные неизменяемые ссылки, потому что никто, кто просто читает данные, не может повлиять на чтение данных кем-либо ещё.

-

Обратите внимание, что область действия ссылки начинается с того места, где она была введена, и продолжается до последнего использования этой ссылки. Например, этот код будет собираться, потому что последнее использование неизменяемых ссылок println!, происходит до того, как вводится изменяемая ссылка:

-
fn main() {
-    let mut s = String::from("hello");
-
-    let r1 = &s; // no problem
-    let r2 = &s; // no problem
-    println!("{r1} and {r2}");
-    // variables r1 and r2 will not be used after this point
-
-    let r3 = &mut s; // no problem
-    println!("{r3}");
-}
-

Области неизменяемых ссылок r1 и r2 заканчиваются после println! где они использовались в последний раз, то есть до создания изменяемой ссылки r3. Эти области не перекрываются, поэтому этот код разрешён: сборщик может сказать, что ссылка больше не используется в точке перед концом области.

-

Несмотря на то, что ошибки заимствования могут иногда вызывать разочарование, помните, что сборщик Ржавчина заранее указывает на вероятную ошибку (во время сборки, а не во время выполнения) и точно показывает, в чем неполадка. Тогда вам не придётся выяснять, почему ваши данные оказались не такими, как вы ожидали.

-

Висячие ссылки

-

В языках с указателями весьма легко ошибочно создать недействительную (висячую) (dangling) ссылку. Ссылку указывающую на участок памяти, который мог быть передан кому-то другому, путём освобождения некоторой памяти при сохранении указателя на эту память. Ржавчина сборщик заверяет, что ссылки никогда не станут недействительными: если у вас есть ссылка на какие-то данные, сборщик обеспечит что эти данные не выйдут из области видимости прежде, чем из области видимости исчезнет ссылка.

-

Давайте попробуем создать висячую ссылку, чтобы увидеть, как Ржавчина предотвращает их появление с помощью ошибки во время сборки:

-

Файл: src/main.rs

-
fn main() {
-    let reference_to_nothing = dangle();
-}
-
-fn dangle() -> &String {
-    let s = String::from("hello");
-
-    &s
-}
-

Здесь ошибка:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0106]: missing lifetime specifier
- --> src/main.rs:5:16
-  |
-5 | fn dangle() -> &String {
-  |                ^ expected named lifetime parameter
-  |
-  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
-help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
-  |
-5 | fn dangle() -> &'static String {
-  |                 +++++++
-help: instead, you are more likely to want to return an owned value
-  |
-5 - fn dangle() -> &String {
-5 + fn dangle() -> String {
-  |
-
-error[E0515]: cannot return reference to local variable `s`
- --> src/main.rs:8:5
-  |
-8 |     &s
-  |     ^^ returns a reference to data owned by the current function
-
-Some errors have detailed explanations: E0106, E0515.
-For more information about an error, try `rustc --explain E0106`.
-error: could not compile `ownership` (bin "ownership") due to 2 previous errors
-
-

Это сообщение об ошибке относится к особенности языка, которую мы ещё не рассмотрели: времени жизни. Мы подробно обсудим времена жизни в главе 10. Но если вы не обращаете внимания на части, касающиеся времени жизни, сообщение будет содержать ключ к тому, почему этот код является неполадкой:

-
this function's return type contains a borrowed value, but there is no value
-for it to be borrowed from
-
-

Давайте подробнее рассмотрим, что именно происходит на каждом этапе нашего кода dangle:

-

Файл: src/main.rs

-
fn main() {
-    let reference_to_nothing = dangle();
-}
-
-fn dangle() -> &String { // dangle returns a reference to a String
-
-    let s = String::from("hello"); // s is a new String
-
-    &s // we return a reference to the String, s
-} // Here, s goes out of scope, and is dropped. Its memory goes away.
-  // Danger!
-

Поскольку s создаётся внутри dangle, когда код dangle будет завершён, s будет освобождена. Но мы попытались вернуть ссылку на неё. Это означает, что эта ссылка будет указывать на недопустимую String. Это нехорошо! Ржавчина не позволит нам сделать это.

-

Решением будет вернуть непосредственно String:

-
fn main() {
-    let string = no_dangle();
-}
-
-fn no_dangle() -> String {
-    let s = String::from("hello");
-
-    s
-}
-

Это работает без неполадок. Владение перемещено, и ничего не освобождено.

-

Правила работы с ссылками

-

Давайте повторим все, что мы обсудили про ссылки:

-
    -
  • В любой мгновение времени у вас может быть одна (но не обе) изменяемая ссылка или любое количество неизменяемых ссылок.
  • -
  • Все ссылки должны быть действительными.
  • -
-

В следующей главе мы рассмотрим другой вид ссылок — срезы.

-

Вид срезы

-

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

-

Вот небольшая неполадка программирования: напишите функцию, которая принимает строку слов, разделённых пробелами, и возвращает первое слово, которое она находит в этой строке. Если функция не находит пробела в строке, вся строка должна состоять из одного слова, поэтому должна быть возвращена вся строка.

-

Давайте рассмотрим, как бы мы написали ярлык этой функции без использования срезов, чтобы понять неполадку, которую решат срезы:

-
fn first_word(s: &String) -> ?
-

Функция first_word имеет &String в качестве свойства. Мы не хотим владения, так что всё в порядке. Но что мы должны вернуть? На самом деле у нас нет способа говорить о части строки. Однако мы могли бы вернуть порядковый указательконца слова, обозначенного пробелом. Давайте попробуем, как показано в Приложении 4-7.

-

Файл: src/main.rs

-
fn first_word(s: &String) -> usize {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return i;
-        }
-    }
-
-    s.len()
-}
-
-fn main() {}
-

Приложение 4-7. Функция first_word, возвращающая значение порядкового указателя байта в свойство String

-

Поскольку нам нужно просмотреть String поэлементно и проверить, является ли значение пробелом, мы преобразуем нашу String в массив байтов с помощью способа as_bytes.

-
fn first_word(s: &String) -> usize {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return i;
-        }
-    }
-
-    s.len()
-}
-
-fn main() {}
-

Далее, мы создаём повторитель по массиву байт используя способ iter:

-
fn first_word(s: &String) -> usize {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return i;
-        }
-    }
-
-    s.len()
-}
-
-fn main() {}
-

Мы обсудим повторители более подробно в Главе 13. На данный мгновение знайте, что iter — это способ, который возвращает каждый элемент в собрания, а enumerate оборачивает итог iter и вместо этого возвращает каждый элемент как часть упорядоченного ряда. Первый элемент упорядоченного ряда, возвращаемый из enumerate, является порядковым указателем, а второй элемент — ссылкой на элемент. Это немного удобнее, чем вычислять порядковый указательсамостоятельно.

-

Поскольку способ enumerate возвращает упорядоченный ряд, мы можем использовать образцы для разъединения этого упорядоченного ряда. Мы подробнее обсудим образцы в Главе 6.. В цикле for мы указываем образец, имеющий i для порядкового указателя в упорядоченном ряде и &item для одного байта в упорядоченном ряде. Поскольку мы получаем ссылку на элемент из .iter().enumerate(), мы используем & в образце.

-

Внутри цикла for мы ищем байт, представляющий пробел, используя правила написания байтового записи. Если мы находим пробел, мы возвращаем положение. В противном случае мы возвращаем длину строки с помощью s.len().

-
fn first_word(s: &String) -> usize {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return i;
-        }
-    }
-
-    s.len()
-}
-
-fn main() {}
-

Теперь у нас есть способ узнать порядковый указательбайта указывающего на конец первого слова в строке, но есть неполадка. Мы возвращаем сам usize, но это число имеет значение только в среде &String. Другими словами, поскольку это значение отдельное от String, то нет заверения, что оно все ещё будет действительным в будущем. Рассмотрим программу из приложения 4-8, которая использует функцию first_word приложения 4-7.

-

Файл: src/main.rs

-
fn first_word(s: &String) -> usize {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return i;
-        }
-    }
-
-    s.len()
-}
-
-fn main() {
-    let mut s = String::from("hello world");
-
-    let word = first_word(&s); // word will get the value 5
-
-    s.clear(); // this empties the String, making it equal to ""
-
-    // word still has the value 5 here, but there's no more string that
-    // we could meaningfully use the value 5 with. word is now totally invalid!
-}
-

Приложение 4-8. Сохранение итога вызова функции first_word и последующего изменения содержимого String

-

Данная программа собирается без ошибок и будет успешно работать, даже после того как мы воспользуемся переменной word после вызова s.clear(). Так как значение word совсем не связано с состоянием переменной s, то word сохраняет своё значение 5 без изменений. Мы бы могли воспользоваться значением 5 чтобы получить первое слово из переменной s, но это приведёт к ошибке, потому что содержимое s изменилось после того как мы сохранили 5 в переменной word (стало пустой строкой в вызове s.clear()).

-

Необходимость беспокоиться о том, что порядковый указательв переменной word не согласуется с данными в переменной s является утомительной и подверженной ошибкам! Управление этими порядковыми указателями становится ещё более хрупким, если мы напишем функцию second_word. Её ярлык могла бы выглядеть так:

-
fn second_word(s: &String) -> (usize, usize) {
-

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

-

К счастью в Ржавчина есть решение данной сбоев: строковые срезы.

-

Строковые срезы

-

Строковый срез - это ссылка на часть строки String и он выглядит следующим образом:

-
fn main() {
-    let s = String::from("hello world");
-
-    let hello = &s[0..5];
-    let world = &s[6..11];
-}
-

Вместо ссылки на всю String hello является ссылкой на часть String, указанную в дополнительном куске кода [0..5]. Мы создаём срезы, используя рядв квадратных скобках, указав [starting_index..ending_index], где starting_index — это первая позиция, аending_index конечный_порядковый указатель— это на единицу больше, чем последняя позиция в срезе. Внутри устройства данных среза хранит начальную положение и длину среза, что соответствует ending_index - starting_index. Итак, в случае let world = &s[6..11];, world будет срезом, содержащим указатель на байт с порядковым указателем 6 s со значением длины 5.

-

Рисунок 4-6 отображает это на диаграмме.

- world containing a pointer to the 6th byte of String s and a length 5 -

Рисунок 4-6: Строковый срез ссылается на часть String

-

С правилами написания Ржавчина .., если вы хотите начать с порядкового указателя 0, вы можете отбросить значение перед двумя точками. Другими словами, они равны:

-
#![allow(unused)]
-fn main() {
-let s = String::from("hello");
-
-let slice = &s[0..2];
-let slice = &s[..2];
-}
-

Таким же образом, если ваш срез включает последний байт String, вы можете отбросить конечный номер. Это означает, что они равны:

-
#![allow(unused)]
-fn main() {
-let s = String::from("hello");
-
-let len = s.len();
-
-let slice = &s[3..len];
-let slice = &s[3..];
-}
-

Вы также можете отбросить оба значения, чтобы получить часть всей строки. Итак, они равны:

-
#![allow(unused)]
-fn main() {
-let s = String::from("hello");
-
-let len = s.len();
-
-let slice = &s[0..len];
-let slice = &s[..];
-}
-
-

Примечание. Порядковые указатели ряда срезов строк должны располагаться на допустимых границах символов UTF-8. Если вы попытаетесь создать отрывок строки нарушая границы символа в котором больше одного байта, ваша программа завершится с ошибкой. В целях введения срезов строк мы предполагаем, что в этом разделе используется только ASCII; более подробное обсуждение обработки UTF-8 находится в разделе «Сохранение закодированного текста UTF-8 со строками». раздел главы 8.

-
-

Давайте используем полученную сведения и перепишем способ first_word так, чтобы он возвращал срез. Для обозначения вида "срез строки" существует запись &str:

-

Файл: src/main.rs

-
fn first_word(s: &String) -> &str {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return &s[0..i];
-        }
-    }
-
-    &s[..]
-}
-
-fn main() {}
-

Мы получаем порядковый указательконца слова так же, как в приложении 4.7, ища первое вхождение пробела. Когда мы находим пробел, мы возвращаем отрывок строки, используя начало строки и порядковый указательпробела в качестве начального и конечного порядковых указателей.

-

Теперь, когда мы вызываем first_word, мы возвращаем одно значение, привязанное к основным данным. Значение состоит из ссылки на начальную точку среза и количества элементов в срезе.

-

Подобным образом можно переписать и второй способ second_word:

-
fn second_word(s: &String) -> &str {
-

Теперь у нас есть простой API, который гораздо сложнее испортить, потому что сборщик заверяет, что ссылки в String останутся действительными. Помните ошибку в программе в приложении 4-8, когда мы получили порядковый указательдо конца первого слова, но затем очиисполнения строку, так что наш порядковый указательстал недействительным? Этот код был логически неправильным, но не показывал немедленных ошибок. Неполадки проявятся позже, если мы попытаемся использовать порядковый указательпервого слова с пустой строкой. Срезы делают эту ошибку невозможной и сообщают нам о неполадке с нашим кодом гораздо раньше. Так, использование исполнения способа first_word со срезом вернёт ошибку сборки:

-

Файл: src/main.rs

-
fn first_word(s: &String) -> &str {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return &s[0..i];
-        }
-    }
-
-    &s[..]
-}
-
-fn main() {
-    let mut s = String::from("hello world");
-
-    let word = first_word(&s);
-
-    s.clear(); // error!
-
-    println!("the first word is: {word}");
-}
-

Ошибка сборки:

-
$ cargo run
-   Compiling ownership v0.1.0 (file:///projects/ownership)
-error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
-  --> src/main.rs:18:5
-   |
-16 |     let word = first_word(&s);
-   |                           -- immutable borrow occurs here
-17 |
-18 |     s.clear(); // error!
-   |     ^^^^^^^^^ mutable borrow occurs here
-19 |
-20 |     println!("the first word is: {word}");
-   |                                  ------ immutable borrow later used here
-
-For more information about this error, try `rustc --explain E0502`.
-error: could not compile `ownership` (bin "ownership") due to 1 previous error
-
-

Напомним из правил заимствования, что если у нас есть неизменяемая ссылка на что-то, мы не можем также взять изменяемую ссылку. Поскольку для clear необходимо обрезать String, необходимо получить изменяемую ссылку. println! после вызова clear использует ссылку в word, поэтому неизменяемая ссылка в этот мгновение всё ещё должна быть активной. Ржавчина запрещает одновременное существование изменяемой ссылки в видеclear и неизменяемой ссылки в word, и сборка завершается ошибкой. Ржавчина не только упростил использование нашего API, но и устранил целый класс ошибок во время сборки!

- -

-

Строковые записи - это срезы

-

Напомним, что мы говорили о строковых записях, хранящихся внутри двоичного файла. Теперь, когда мы знаем чем являются срезы, мы правильно понимаем что такое строковые записи:

-
#![allow(unused)]
-fn main() {
-let s = "Hello, world!";
-}
-

Вид s здесь &str: это срез, указывающий на эту определенную точку двоичного файла. Вот почему строковые записи неизменяемы; &str — неизменяемая ссылка.

-

Строковые срезы как свойства

-

Знание того, что вы можете брать срезы записей и String значений, приводит нас к ещё одному улучшению first_word, и это его ярлык:

-
fn first_word(s: &String) -> &str {
-

Более опытный пользователь Rustacean вместо этого написал бы ярлык, показанную в приложении 4.9, потому что это позволяет нам использовать одну и ту же функцию как для значений &String, так и для значений &str.

-
fn first_word(s: &str) -> &str {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return &s[0..i];
-        }
-    }
-
-    &s[..]
-}
-
-fn main() {
-    let my_string = String::from("hello world");
-
-    // `first_word` works on slices of `String`s, whether partial or whole
-    let word = first_word(&my_string[0..6]);
-    let word = first_word(&my_string[..]);
-    // `first_word` also works on references to `String`s, which are equivalent
-    // to whole slices of `String`s
-    let word = first_word(&my_string);
-
-    let my_string_literal = "hello world";
-
-    // `first_word` works on slices of string literals, whether partial or whole
-    let word = first_word(&my_string_literal[0..6]);
-    let word = first_word(&my_string_literal[..]);
-
-    // Because string literals *are* string slices already,
-    // this works too, without the slice syntax!
-    let word = first_word(my_string_literal);
-}
-

Приложение 4-9: Улучшение функции first_word используя вид строкового среза для свойства s

-

Если у нас есть отрывок строки, мы можем передать его напрямую. Если у нас есть String, мы можем передать часть String или ссылку на String. Эта гибкость использует преимущества приведения deref, функции, которую мы рассмотрим в разделе «Неявное приведение Deref с функциями и способами». раздел главы 15.

-

Определение функции для получения отрывка строки вместо ссылки на String делает наш API более общим и полезным без потери какой-либо возможности:

-

Файл: src/main.rs

-
fn first_word(s: &str) -> &str {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return &s[0..i];
-        }
-    }
-
-    &s[..]
-}
-
-fn main() {
-    let my_string = String::from("hello world");
-
-    // `first_word` works on slices of `String`s, whether partial or whole
-    let word = first_word(&my_string[0..6]);
-    let word = first_word(&my_string[..]);
-    // `first_word` also works on references to `String`s, which are equivalent
-    // to whole slices of `String`s
-    let word = first_word(&my_string);
-
-    let my_string_literal = "hello world";
-
-    // `first_word` works on slices of string literals, whether partial or whole
-    let word = first_word(&my_string_literal[0..6]);
-    let word = first_word(&my_string_literal[..]);
-
-    // Because string literals *are* string slices already,
-    // this works too, without the slice syntax!
-    let word = first_word(my_string_literal);
-}
-

Другие срезы

-

Срезы строк, как вы можете себе представить, отличительны для строк. Но есть и более общий вид среза. Рассмотрим этот массив:

-
#![allow(unused)]
-fn main() {
-let a = [1, 2, 3, 4, 5];
-}
-

Точно так же, как мы можем захотеть сослаться на часть строки, мы можем захотеть сослаться на часть массива. Мы бы сделали так:

-
#![allow(unused)]
-fn main() {
-let a = [1, 2, 3, 4, 5];
-
-let slice = &a[1..3];
-
-assert_eq!(slice, &[2, 3]);
-}
-

Этот срез имеет вид &[i32]. Он работает так же, как и срезы строк, сохраняя ссылку на первый элемент и его длину. Вы будете использовать этот вид отрывка для всех видов других собраний. Мы подробно обсудим эти собрания, когда будем говорить о векторах в главе 8.

-

Итоги

-

Подходы владения, заимствования и срезов обеспечивают безопасность памяти в программах на Ржавчина во время сборки. Язык Ржавчина даёт вам управление над использованием памяти так же, как и другие языки системного программирования, но то, что владелец данных самостоятельно очищает эти данные, когда владелец выходит за рамки, означает, что вам не нужно писать и отлаживать дополнительный код, чтобы получить этот управление.

-

Владение влияет на множество других частей и подходов языка Rust. Мы будем говорить об этих подходах на протяжении оставшихся частей книги. Давайте перейдём к Главе 5 и рассмотрим объединение частей данных в устройства struct.

-

Использование устройств для внутреннего выстраивания

-

связанных данных

-

Устройства (struct) — это пользовательский вид данных, позволяющий назвать и упаковать вместе несколько связанных значений, составляющих значимую логическую объединение. Если вы знакомы с предметно-направленными языками, устройства похожа на свойства данных предмета. В этой главе мы сравним и сопоставим упорядоченные ряды со устройствами, чтобы опираться на то, что вы уже знаете, и отобразим, когда устройства являются лучшим способом объединения данных.

-

Мы отобразим, как определять устройства и создавать их образцы. Мы обсудим, как определить сопряженные функции, особенно сопряженные функции, называемые способами, для указания поведения, сопряженного с видом устройства. Устройства и перечисления (обсуждаемые в главе 6) являются строительными разделами для создания новых видов в предметной области вашей программы. Они дают возможность в полной мере воспользоваться преимуществами проверки видов во время сборки Rust.

-

Определение и объявление устройств

-

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

-

Для определения устройства указывается ключевое слово struct и её название. Название должно описывать значение частей данных, объединенных вместе. Далее, в фигурных скобках для каждой новой части данных поочерёдно определяются имя части данных и её вид. Каждая пара имя: тип называется полем. Приложение 5-1 описывает устройство для хранения сведений об учётной записи пользователя:

-

Имя файла: src/main.rs

-
struct User {
-    active: bool,
-    username: String,
-    email: String,
-    sign_in_count: u64,
-}
-
-fn main() {}
-

Приложение 5-1: Определение устройства User

-

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

-

Файл: src/main.rs

-
struct User {
-    active: bool,
-    username: String,
-    email: String,
-    sign_in_count: u64,
-}
-
-fn main() {
-    let user1 = User {
-        active: true,
-        username: String::from("someusername123"),
-        email: String::from("someone@example.com"),
-        sign_in_count: 1,
-    };
-}
-

Приложение 5-2: Создание образца устройства User

-

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

-

Файл: src/main.rs

-
struct User {
-    active: bool,
-    username: String,
-    email: String,
-    sign_in_count: u64,
-}
-
-fn main() {
-    let mut user1 = User {
-        active: true,
-        username: String::from("someusername123"),
-        email: String::from("someone@example.com"),
-        sign_in_count: 1,
-    };
-
-    user1.email = String::from("anotheremail@example.com");
-}
-

Приложение 5-3: Изменение значения в поле email образца User

-

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

-

На приложении 5-4 функция build_user возвращает образец User с указанным адресом и именем. Поле active получает значение true, а поле sign_in_count получает значение 1.

-

Файл: src/main.rs

-
struct User {
-    active: bool,
-    username: String,
-    email: String,
-    sign_in_count: u64,
-}
-
-fn build_user(email: String, username: String) -> User {
-    User {
-        active: true,
-        username: username,
-        email: email,
-        sign_in_count: 1,
-    }
-}
-
-fn main() {
-    let user1 = build_user(
-        String::from("someone@example.com"),
-        String::from("someusername123"),
-    );
-}
-

Приложение 5-4: Функция build_user, которая принимает email и имя пользователя и возвращает образец User

-

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

- -

-

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

-

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

-

Файл: src/main.rs

-
struct User {
-    active: bool,
-    username: String,
-    email: String,
-    sign_in_count: u64,
-}
-
-fn build_user(email: String, username: String) -> User {
-    User {
-        active: true,
-        username,
-        email,
-        sign_in_count: 1,
-    }
-}
-
-fn main() {
-    let user1 = build_user(
-        String::from("someone@example.com"),
-        String::from("someusername123"),
-    );
-}
-

Приложение 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.

-

Файл: src/main.rs

-
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-6: Создание нового образца User с использованием некоторых значений из образца user1

-

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

-

Файл: src/main.rs

-
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: Использование правил написания обновления устройства для установки нового значения 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:

-

Файл: 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:

-

Файл: src/main.rs

-
struct AlwaysEqual;
-
-fn main() {
-    let subject = AlwaysEqual;
-}
-

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

-
-

Владение данными устройства

-

В определении устройства User в приложении 5-1 мы использовали владеющий вид String вместо вида строковый срез &str. Это осознанный выбор, поскольку мы хотим, чтобы каждый образец этой устройства владел всеми своими данными и чтобы эти данные были действительны до тех пор, пока действительна вся устройства.

-

Устройства также могут хранить ссылки на данные, принадлежащие кому-то другому, но для этого необходимо использовать возможность Ржавчина время жизни, которую мы обсудим в главе 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: "someone@example.com",
-        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
-
-

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

-
- -

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

-

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

-

Давайте создадим новый дело программы при помощи Cargo и назовём его rectangles. Наша программа будет получать на вход длину и ширину прямоугольника в пикселях и затем рассчитывать площадь прямоугольника. Приложение 5-8 показывает один из коротких исходов кода, который позволит нам сделать именно то, что надо, в файле дела src/main.rs.

-

Файл: src/main.rs

-
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
-}
-

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

-

Теперь запустим программу, используя cargo run:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s
-     Running `target/debug/rectangles`
-The area of the rectangle is 1500 square pixels.
-
-

Этот код успешно вычисляет площадь прямоугольника, вызывая функцию 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
-}
-

Функция area должна вычислять площадь одного прямоугольника, но функция, которую мы написали, имеет два свойства, и нигде в нашей программе не ясно, что эти свойства взаимосвязаны. Было бы более читабельным и управляемым собъединять ширину и высоту вместе. В разделе «Упорядоченные ряды» главы 3 мы уже обсуждали один из способов сделать это — использовать упорядоченные ряды.

-

Переработка кода при помощи упорядоченных рядов

-

Приложение 5-9 — это другая исполнение программы, использующая упорядоченные ряды.

-

Файл: 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-9: определение ширины и высоты прямоугольника с помощью упорядоченного ряда

-

С одной стороны, эта программа лучше. Упорядоченные ряды позволяют добавить немного устройства, и теперь мы передаём только один переменная. Но с другой стороны, эта исполнение менее понятна: упорядоченные ряды не называют свои элементы, поэтому нам приходится упорядочивать части упорядоченного ряда, что делает наше вычисление менее очевидным.

-

Если мы перепутаем местами ширину с высотой при расчёте площади, то это не имеет значения. Но если мы хотим нарисовать прямоугольник на экране, то это уже будет важно! Мы должны помнить, что ширина width находится в упорядоченном ряде с порядковым указателем 0, а высота height — с порядковым указателем 1. Если кто-то другой поработал бы с кодом, ему бы пришлось разобраться в этом и также помнить про порядок. Легко забыть и перепутать эти значения — и это вызовет ошибки, потому что данный код не передаёт наши намерения.

-

Переработка кода при помощи устройств: добавим больше смысла

-

Мы используем устройства, чтобы добавить смысл данным при помощи назначения им осмысленных имён . Мы можем переделать используемый упорядоченный ряд в устройство с единым именем для сущности и частными названиями её частей, как показано в приложении 5-10.

-

Файл: src/main.rs

-
struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-
-    println!(
-        "The area of the rectangle is {} square pixels.",
-        area(&rect1)
-    );
-}
-
-fn area(rectangle: &Rectangle) -> u32 {
-    rectangle.width * rectangle.height
-}
-

Приложение 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. Это торжество ясности.

-

Добавление полезной возможности при помощи выводимых особенностей

-

Было бы полезно иметь возможность печатать образец Rectangle во время отладки программы и видеть значения всех полей. Приложение 5-11 использует макрос println!, который мы уже использовали в предыдущих главах. Тем не менее, это не работает.

-

Файл: src/main.rs

-
struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-
-    println!("rect1 is {}", rect1);
-}
-

Приложение 5-11: Попытка вывести значения образца Rectangle

-

При сборки этого кода мы получаем ошибку с сообщением:

-
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
-
-

Макрос println! умеет выполнять множество видов изменения, и по умолчанию фигурные скобки в println! означают использование изменение -, известное как особенность Display. Его вывод предназначен для непосредственного использования конечным пользователем. Простые виды, изученные ранее, по умолчанию выполняют особенность Display, потому что есть только один способ отобразить число 1 или любой другой простой вид. Но для устройств изменение -println! менее очевидно, потому что есть гораздо больше способов отображения: Вы хотите запятые или нет? Вы хотите печатать фигурные скобки? Должны ли отображаться все поля? Из-за этой неоднозначности Ржавчина не пытается угадать, что нам нужно, а устройства не имеют встроенной выполнения Display для использования в println! с заполнителем {}.

-

Продолжив чтение текста ошибки, мы найдём полезное замечание:

-
   = help: the trait `std::fmt::Display` is not implemented for `Rectangle`
-   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
-
-

Давайте попробуем! Вызов макроса println! теперь будет выглядеть так println!("rect1 is {:?}", rect1);. Ввод определетеля :? внутри фигурных скобок говорит макросу println!, что мы хотим использовать другой вид вывода, известный как Debug. Особенность Debug позволяет печатать устройство способом, удобным для разработчиков, чтобы видеть значение во время отладки кода.

-

Соберем код с этими изменениями. Упс! Мы всё ещё получаем ошибку:

-
error[E0277]: `Rectangle` doesn't implement `Debug`
-
-

Снова сборщик даёт нам полезное замечание:

-
   = help: the trait `Debug` is not implemented for `Rectangle`
-   = note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
-
-

Rust выполняет возможность для печати отладочной сведений, но не включает (не выводит) её по умолчанию. Мы должны явно включить эту возможность для нашей устройства. Чтобы это сделать, добавляем внешний свойство #[derive(Debug)] сразу перед определением устройства, как показано в приложении 5-12.

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-
-    println!("rect1 is {rect1:?}");
-}
-

Приложение 5-12: добавление свойства для вывода особенности Debug и печати образца Rectangle с отладочным изменением -

-

Теперь при запуске программы мы не получим ошибок и увидим следующий вывод:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
-     Running `target/debug/rectangles`
-rect1 is Rectangle { width: 30, height: 50 }
-
-

Отлично! Это не самый красивый вывод, но он показывает значения всех полей образца, которые определённо помогут при отладке. Когда у нас более крупные устройства, то полезно иметь более простой для чтения вывод; в таких случаях можно использовать код {:#?} вместо {:?} в строке макроса println!. В этом примере использование исполнения {:#?} приведёт к такому выводу:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
-     Running `target/debug/rectangles`
-rect1 is Rectangle {
-    width: 30,
-    height: 50,
-}
-
-

Другой способ распечатать значение в видеDebug — использовать макрос dbg!, который становится владельцем выражения (в отличие от println!, принимающего ссылку), печатает номер файла и строки, где происходит вызов макроса dbg!, вместе с результирующим значением этого выражения и возвращает владение на значение.

-
-

Примечание: при вызове макроса dbg! выполняется печать в обычный поток ошибок (stderr), в отличие от println!, который использует обычный поток вывода в окно вывода (stdout). Подробнее о stderr и stdout мы поговорим в разделе «Запись сообщений об ошибках в обычный вывод ошибок вместо принятого вывода» главы 12.

-
-

Вот пример, когда нас важно значение, которое присваивается полю width, а также значение всей устройства в rect1:

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let scale = 2;
-    let rect1 = Rectangle {
-        width: dbg!(30 * scale),
-        height: 50,
-    };
-
-    dbg!(&rect1);
-}
-

Можем написать макрос dbg! вокруг выражения 30 * scale, потому что dbg! возвращает владение значения выражения. Поле width получит то же значение, как если бы у нас не было вызова dbg!. Мы не хотим, чтобы макрос dbg! становился владельцем rect1, поэтому используем ссылку на rect1 в следующем вызове. Вот как выглядит вывод этого примера:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
-     Running `target/debug/rectangles`
-[src/main.rs:10:16] 30 * scale = 60
-[src/main.rs:14:5] &rect1 = Rectangle {
-    width: 60,
-    height: 50,
-}
-
-

Мы можем увидеть, что первый отладочный вывод поступил из строки 10 src/main.rs, там, где мы отлаживаем выражение 30 * scale, и его результирующее значение равно 60 (Debug изменение -, выполненное для целых чисел, заключается в печати только их значения). Вызов dbg! в строке 14 src/main.rs выводит значение &rect1, которое является устройством Rectangle. В этом выводе используется красивое изменение -Debug вида Rectangle. Макрос dbg! может быть очень полезен, когда вы пытаетесь понять, что делает ваш код!

-

В дополнение к Debug, Ржавчина предоставил нам ряд особенностей, которые мы можем использовать с свойством derive для добавления полезного поведения к нашим пользовательским видам. Эти особенности и их поведение перечислены в приложении C. Мы расскажем, как выполнить эти особенности с пользовательским поведением, а также как создать свои собственные особенности в главе 10. Кроме того, есть много других свойств помимо derive; для получения дополнительной сведений смотрите раздел “Свойства” справочника Rust.

-

Функция area является довольно отличительной: она считает только площадь прямоугольников. Было бы полезно привязать данное поведение как можно ближе к устройстве Rectangle, потому что наш отличительный код не будет работать с любым другим видом. Давайте рассмотрим, как можно улучшить наш код превращая функцию area в способ area, определённый для вида Rectangle.

-

правила написания способа

-

Способы похожи на функции: мы объявляем их с помощью ключевого слова fn и имени, они могут иметь свойства и возвращаемое значение, и они содержат код, запускающийся в случае вызова способа. В отличие от функций, способы определяются в среде устройства (или предмета перечисления или особенности, которые мы рассмотрим в главе 6) и главе 17 соответственно), а их первым свойствоом всегда является self, представляющий собой образец устройства, с которой вызывается этот способ.

-

Определение способов

-

Давайте изменим функцию area так, чтобы она имела образец Rectangle в качестве входного свойства и сделаем её способом area, определённым для устройства Rectangle, как показано в приложении 5-13:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn area(&self) -> u32 {
-        self.width * self.height
-    }
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-
-    println!(
-        "The area of the rectangle is {} square pixels.",
-        rect1.area()
-    );
-}
-

Приложение 5-13: Определение способа area для устройства Rectangle

-

Чтобы определить функцию в среде Rectangle, мы создаём разделimpl (implementation - выполнение) для Rectangle. Всё в impl будет связано с видом Rectangle. Затем мы перемещаем функцию area внутрь фигурных скобок impl и меняем первый (и в данном случае единственный) свойство на self в ярлыке и в теле. В main, где мы вызвали функцию area и передали rect1 в качестве переменной, теперь мы можем использовать правила написания способа для вызова способа area нашего образца Rectangle. правила написания способа идёт после образца: мы добавляем точку, за которой следует имя способа, круглые скобки и любые переменные.

-

В ярлыке area мы используем &self вместо rectangle: &Rectangle. &self на самом деле является сокращением от self: &Self. Внутри раздела impl вид Self является псевдонимом вида, для которого выполнен разделimpl. Способы обязаны иметь свойство с именем self вида Self, поэтому Ржавчина позволяет вам сокращать его, используя только имя self на месте первого свойства. Обратите внимание, что нам по-прежнему нужно использовать & перед сокращением self, чтобы указать на то, что этот способ заимствует образец Self, точно так же, как мы делали это в rectangle: &Rectangle. Как и любой другой свойство, способы могут брать во владение self, заимствовать неизменяемый self, как мы поступили в данном случае, или заимствовать изменяемый self.

-

Мы выбрали &self здесь по той же причине, по которой использовали &Rectangle в исполнения кода с функцией: мы не хотим брать устройство во владение, мы просто хотим прочитать данные в устройстве, а не писать в неё. Если бы мы хотели изменить образец, на котором мы вызывали способ силами самого способа, то мы бы использовали &mut self в качестве первого свойства. Наличие способа, который берёт образец во владение, используя только self в качестве первого свойства, является редким; эта техника обычно используется, когда способ превращает self во что-то ещё, и вы хотите запретить вызывающей стороне использовать исходный образец после превращения.

-

Основная причина использования способов вместо функций, помимо правил написания способа, где нет необходимости повторять вид self в ярлыке каждого способа, заключается в согласования кода. Мы помеисполнения все, что мы можем сделать с образцом вида, в один impl вместо того, чтобы заставлять будущих пользователей нашего кода искать доступный возможности Rectangle в разных местах предоставляемой нами библиотеки.

-

Обратите внимание, что мы можем дать способу то же имя, что и одному из полей устройства. Например, для Rectangle мы можем определить способ, также названный width:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn width(&self) -> bool {
-        self.width > 0
-    }
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-
-    if rect1.width() {
-        println!("The rectangle has a nonzero width; it is {}", rect1.width);
-    }
-}
-

Здесь мы определили, чтобы способ width возвращал значение true, если значение в поле width образца больше 0, и значение false, если значение равно 0, но мы можем использовать поле в способе с тем же именем для любых целей. В main, когда мы ставим после rect1.width круглые скобки, Ржавчина знает, что мы имеем в виду способ width. Когда мы не используем круглые скобки, Ржавчина понимает, что мы имеем в виду поле width.

-

Часто, но не всегда, когда мы создаём способы с тем же именем, что и у поля, мы хотим, чтобы он только возвращал значение одноимённого поля и больше ничего не делал. Подобные способы называются геттерами, и Ржавчина не выполняет их самостоятельно для полей устройства, как это делают некоторые другие языки. Геттеры полезны, поскольку вы можете сделать поле закрытым, а способ открытым и, таким образом, включить доступ только для чтения к этому полю как часть общедоступного API вида. Мы обсудим, что такое открытость и закрытость, и как обозначить поле или способ в качестве открытого или закрытого в главе 7.

-
-

Где используется оператор ->?

-

В языках C и C++, используются два различных оператора для вызова способов: используется ., если вызывается способ непосредственно у образца устройства и используется ->, если вызывается способ для указателя на предмет. Другими словами, если object является указателем, то вызовы способа object->something() и (*object).something() являются подобными.

-

Ржавчина не имеет эквивалента оператора ->, наоборот, в Ржавчина есть возможность называемая самостоятельное обращение по ссылке и разыменование (automatic referencing and dereferencing). Вызов способов является одним из немногих мест в Rust, в котором есть такое поведение.

-

Вот как это работает: когда вы вызываете способ object.something(), Ржавчина самостоятельно добавляет &, &mut или *, таким образом, чтобы object соответствовал ярлыке способа. Другими словами, это то же самое:

- -
#![allow(unused)]
-fn main() {
-#[derive(Debug,Copy,Clone)]
-struct Point {
-    x: f64,
-    y: f64,
-}
-
-impl Point {
-   fn distance(&self, other: &Point) -> f64 {
-       let x_squared = f64::powi(other.x - self.x, 2);
-       let y_squared = f64::powi(other.y - self.y, 2);
-
-       f64::sqrt(x_squared + y_squared)
-   }
-}
-let p1 = Point { x: 0.0, y: 0.0 };
-let p2 = Point { x: 5.0, y: 6.5 };
-p1.distance(&p2);
-(&p1).distance(&p2);
-}
-

Первый пример выглядит намного понятнее. Самостоятельный вывод ссылки работает потому, что способы имеют понятного получателя - вид self. Учитывая получателя и имя способа, Ржавчина может точно определить, что в данном случае делает код: читает ли способ (&self), делает ли изменение (&mut self) или поглощает (self). Тотобстоятельство, что Ржавчина делает заимствование неявным для принимающего способа, в значительной степени способствует тому, чтобы сделать владение удобным на опыте.

-
-

Способы с несколькими свойствами

-

Давайте применим в использовании способов, выполнив второй способ в устройстве Rectangle. На этот раз мы хотим, чтобы образец Rectangle брал другой образец Rectangle и возвращал true, если второй Rectangle может полностью поместиться внутри self (первый Rectangle); в противном случае он должен вернуть false. То есть, как только мы определим способ can_hold, мы хотим иметь возможность написать программу, показанную в Приложении 5-14.

-

Файл: src/main.rs

-
fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-    let rect2 = Rectangle {
-        width: 10,
-        height: 40,
-    };
-    let rect3 = Rectangle {
-        width: 60,
-        height: 45,
-    };
-
-    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
-    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
-}
-

Приложение 5-14: Использование ещё не написанного способа can_hold

-

Ожидаемый итог будет выглядеть следующим образом, т.к. оба размера в образце rect2 меньше, чем размеры в образце rect1, а rect3 шире, чем rect1:

-
Can rect1 hold rect2? true
-Can rect1 hold rect3? false
-
-

Мы знаем, что хотим определить способ, поэтому он будет находится в impl Rectangle разделе. Имя способа будет can_hold, и оно будет принимать неизменяемое заимствование на другой Rectangle в качестве свойства. Мы можем сказать, какой это будет вид свойства, посмотрев на код вызывающего способа: способ rect1.can_hold(&rect2) передаёт в него &rect2 , который является неизменяемым заимствованием образца rect2 вида Rectangle. В этом есть смысл, потому что нам нужно только читать rect2 (а не писать, что означало бы, что нужно изменяемое заимствование), и мы хотим, чтобы main сохранил право собственности на образец rect2, чтобы мы могли использовать его снова после вызов способа can_hold. Возвращаемое значение can_hold имеет булевый вид, а выполнение проверяет, являются ли ширина и высота self больше, чем ширина и высота другого Rectangle соответственно. Давайте добавим новый способ can_hold в impl разделиз приложения 5-13, как показано в приложении 5-15.

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn area(&self) -> u32 {
-        self.width * self.height
-    }
-
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width > other.width && self.height > other.height
-    }
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-    let rect2 = Rectangle {
-        width: 10,
-        height: 40,
-    };
-    let rect3 = Rectangle {
-        width: 60,
-        height: 45,
-    };
-
-    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
-    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
-}
-

Приложение 5-15: Выполнение способа can_hold для Rectangle, принимающего другой образец Rectangle в качестве свойства

-

Когда мы запустим код с функцией main приложения 5-14, мы получим желаемый вывод. Способы могут принимать несколько свойств, которые мы добавляем в ярлык после первого свойства self, и эти свойства работают так же, как свойства в функциях.

-

Сопряженные функции

-

Все функции, определённые в разделе impl, называются сопряженными функциями, потому что они сопряжены с видом, указанным после ключевого слова impl. Мы можем определить сопряженные функции, которые не имеют self в качестве первого свойства (и, следовательно, не являются способами), потому что им не нужен образец вида для работы. Мы уже использовали одну подобную функцию: функцию String::from, определённую для вида String.

-

Сопряженные функции, не являющиеся способами, часто используются для строителей, возвращающих новый образец устройства. Их часто называют new, но new не является особым именем и не встроена в язык. Например, мы можем предоставить сопряженную функцию с именем square, которая будет иметь один свойство размера и использовать его как ширину и высоту, что упростит создание квадратного Rectangle, вместо того, чтобы указывать одно и то же значение дважды:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn square(size: u32) -> Self {
-        Self {
-            width: size,
-            height: size,
-        }
-    }
-}
-
-fn main() {
-    let sq = Rectangle::square(3);
-}
-

Ключевые слова Self в возвращаемом виде и в теле функции являются псевдонимами для вида, указанного после ключевого слова impl, которым в данном случае является Rectangle.

-

Чтобы вызвать эту связанную функцию, используется правила написания :: с именем устройства; например let sq = Rectangle::square(3);. Эта функция находится в пространстве имён устройства. правила написания :: используется как для связанных функций, так и для пространств имён, созданных звенами. Мы обсудим звенья в главе 7.

-

Несколько разделов impl

-

Каждая устройства может иметь несколько impl. Например, Приложение 5-15 эквивалентен коду, показанному в приложении 5-16, в котором каждый способ находится в своём собственном разделе impl.

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn area(&self) -> u32 {
-        self.width * self.height
-    }
-}
-
-impl Rectangle {
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width > other.width && self.height > other.height
-    }
-}
-
-fn main() {
-    let rect1 = Rectangle {
-        width: 30,
-        height: 50,
-    };
-    let rect2 = Rectangle {
-        width: 10,
-        height: 40,
-    };
-    let rect3 = Rectangle {
-        width: 60,
-        height: 45,
-    };
-
-    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
-    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
-}
-

Приложение 5-16: Переписанный Приложения 5-15 с использованием нескольких impl

-

Здесь нет причин разделять способы на несколько impl, но это допустимый правила написания. Мы увидим случай, когда несколько impl могут оказаться полезными, в Главе 10, рассматривающей обобщённые виды и свойства.

-

Итоги

-

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

-

Но устройства — не единственный способ создавать собственные виды: давайте обратимся к перечислениям в Rust, чтобы добавить ещё один средство в свой арсенал.

-

Перечисления и сопоставление с образцом

-

В этой главе мы рассмотрим перечисления (enumerations), также называемые enums. Перечисления позволяют определить вид путём перечисления его возможных исходов . Сначала мы определим и используем перечисление, чтобы показать, как оно может объединить значения и данные. Далее мы рассмотрим особенно полезное перечисление под названием Option, которое выражает, что значение может быть либо чем-то, либо ничем. Затем мы рассмотрим, как сопоставление с образцом в выражении match позволяет легко запускать разный код для разных значений перечисления. Наконец, мы узнаем, насколько устройство if let удобна и кратка для обработки перечислений в вашем коде.

-

Определение перечисления

-

Там, где устройства дают вам возможность объединять связанные поля и данные, например Rectangle с его width и height, перечисления дают вам способ сказать, что значение является одним из возможных наборов значений. Например, мы можем захотеть сказать, что Rectangle — это одна из множества возможных фигур, в которую также входят Circle и Triangle. Для этого Ржавчина позволяет нам закодировать эти возможности в виде перечисления.

-

Давайте рассмотрим случай, которую мы могли бы захотеть отразить в коде, и поймём, почему перечисления полезны и более уместны, чем устройства в этом случае. Допустим, нам нужно работать с IP-адресами. В настоящее время для обозначения IP-адресов используются два основных исполнения: четвёртая и шестая исполнения. Поскольку это единственно возможные исходы IP-адресов, с которыми может столкнуться наша программа, мы можем перечислить все возможные исходы, откуда перечисление и получило своё название.

-

Любой IP-адрес может быть либо четвёртой, либо шестой исполнения, но не обеими одновременно. Эта особенность IP-адресов делает устройство данных enum подходящей, поскольку значение enum может представлять собой только один из его возможных исходов. Адреса как четвёртой, так и шестой исполнения по своей сути все равно являются IP-адресами, поэтому их следует рассматривать как один и тот же вид, когда в коде обрабатываются задачи, относящиеся к любому виду IP-адресов.

-

Можно выразить эту подход в коде, определив перечисление IpAddrKind и составив список возможных видов IP-адресов, V4 и V6. Вот исходы перечислений:

-
enum IpAddrKind {
-    V4,
-    V6,
-}
-
-fn main() {
-    let four = IpAddrKind::V4;
-    let six = IpAddrKind::V6;
-
-    route(IpAddrKind::V4);
-    route(IpAddrKind::V6);
-}
-
-fn route(ip_kind: IpAddrKind) {}
-

IpAddrKind теперь является пользовательским видом данных, который мы можем использовать в другом месте нашего кода.

-

Значения перечислений

-

Образцы каждого исхода перечисления IpAddrKind можно создать следующим образом:

-
enum IpAddrKind {
-    V4,
-    V6,
-}
-
-fn main() {
-    let four = IpAddrKind::V4;
-    let six = IpAddrKind::V6;
-
-    route(IpAddrKind::V4);
-    route(IpAddrKind::V6);
-}
-
-fn route(ip_kind: IpAddrKind) {}
-

Обратите внимание, что исходы перечисления находятся в пространстве имён вместе с его определителем, а для их обособления мы используем двойное двоеточие. Это удобно тем, что теперь оба значения IpAddrKind::V4 и IpAddrKind::V6 относятся к одному виду: IpAddrKind. Затем мы можем, например, определить функцию, которая принимает любой из исходов IpAddrKind:

-
enum IpAddrKind {
-    V4,
-    V6,
-}
-
-fn main() {
-    let four = IpAddrKind::V4;
-    let six = IpAddrKind::V6;
-
-    route(IpAddrKind::V4);
-    route(IpAddrKind::V6);
-}
-
-fn route(ip_kind: IpAddrKind) {}
-

Можно вызвать эту функцию с любым из исходов:

-
enum IpAddrKind {
-    V4,
-    V6,
-}
-
-fn main() {
-    let four = IpAddrKind::V4;
-    let six = IpAddrKind::V6;
-
-    route(IpAddrKind::V4);
-    route(IpAddrKind::V6);
-}
-
-fn route(ip_kind: IpAddrKind) {}
-

Использование перечислений позволяет получить ещё больше преимуществ. Если подумать о нашем виде для IP-адреса, то выяснится, что на данный мгновение у нас нет возможности хранить собственно сам IP-адрес; мы будем знать только его вид. Учитывая, что недавно в главе 5 вы узнали о устройствах, у вас может возникнуть соблазн решить эту неполадку с помощью устройств, как показано в приложении 6-1.

-
fn main() {
-    enum IpAddrKind {
-        V4,
-        V6,
-    }
-
-    struct IpAddr {
-        kind: IpAddrKind,
-        address: String,
-    }
-
-    let home = IpAddr {
-        kind: IpAddrKind::V4,
-        address: String::from("127.0.0.1"),
-    };
-
-    let loopback = IpAddr {
-        kind: IpAddrKind::V6,
-        address: String::from("::1"),
-    };
-}
-

Приложение 6-1. Сохранение данных и IpAddrKind IP-адреса с использованием struct

-

Здесь мы определили устройство IpAddr, у которой есть два поля: kind вида IpAddrKind (перечисление, которое мы определили ранее) и address вида String. У нас есть два образца этой устройства. Первый - home, который является IpAddrKind::V4 в качестве значения kind с соответствующим адресом 127.0.0.1. Второй образец - loopback. Он в качестве значения kind имеет другой исход IpAddrKind, V6, и с ним сопряжен адрес ::1. Мы использовали устройство для объединения значений kind и address вместе, таким образом вид вида адреса теперь сопряжен со значением.

-

Однако представление этой же подходы с помощью перечисления более кратко: вместо того, чтобы помещать перечисление в устройство, мы можем поместить данные непосредственно в любой из исходов перечисления. Это новое определение перечисления IpAddr гласит, что оба исхода V4 и V6 будут иметь соответствующие значения String:

-
fn main() {
-    enum IpAddr {
-        V4(String),
-        V6(String),
-    }
-
-    let home = IpAddr::V4(String::from("127.0.0.1"));
-
-    let loopback = IpAddr::V6(String::from("::1"));
-}
-

Мы прикрепляем данные к каждому исходу перечисления напрямую, поэтому нет необходимости в дополнительной устройстве. Здесь также легче увидеть ещё одну подробность того, как работают перечисления: имя каждого исхода перечисления, который мы определяем, также становится функцией, которая создаёт образец перечисления. То есть IpAddr::V4() - это вызов функции, который принимает String и возвращает образец вида IpAddr. Мы самостоятельно получаем эту функцию-строитель, определяемую в итоге определения перечисления.

-

Ещё одно преимущество использования перечисления вместо устройства заключается в том, что каждый исход перечисления может иметь разное количество сопряженных данных представленных в разных видах. Исполнение 4 для IP адресов всегда будет содержать четыре цифровых составляющих, которые будут иметь значения между 0 и 255. При необходимости сохранить адреса вида V4 как четыре значения вида u8, а также описать адреса вида V6 как единственное значение вида String, мы не смогли бы с помощью устройства. Перечисления решают эту задачу легко:

-
fn main() {
-    enum IpAddr {
-        V4(u8, u8, u8, u8),
-        V6(String),
-    }
-
-    let home = IpAddr::V4(127, 0, 0, 1);
-
-    let loopback = IpAddr::V6(String::from("::1"));
-}
-

Мы показали несколько различных способов определения устройств данных для хранения IP-адресов четвёртой и шестой исполнений. Однако, как оказалось, желание хранить IP-адреса и указывать их вид настолько распространено, что в встроенной библиотеке есть определение, которое мы можем использовать! Давайте посмотрим, как обычная библиотека определяет IpAddr: в ней есть точно такое же перечисление с исходами, которое мы определили и использовали, но она помещает данные об адресе внутрь этих исходов в виде двух различных устройств, которые имеют различные определения для каждого из исходов:

-
#![allow(unused)]
-fn main() {
-struct Ipv4Addr {
-    // --snip--
-}
-
-struct Ipv6Addr {
-    // --snip--
-}
-
-enum IpAddr {
-    V4(Ipv4Addr),
-    V6(Ipv6Addr),
-}
-}
-

Этот код отображает что мы можем добавлять любой вид данных в значение перечисления: строку, число, устройство и пр. Вы даже можете включить в перечисление другие перечисления! Обычные виды данных не очень сложны, хотя, возможно, могут быть очень сложными (вложенность данных может быть очень глубокой).

-

Обратите внимание, что хотя определение перечисления IpAddr есть в встроенной библиотеке, мы смогли объявлять и использовать свою собственную выполнение с подобным названием без каких-либо несоответствий, потому что мы не добавили определение встроенной библиотеки в область видимости кода. Подробнее об этом поговорим в Главе 7.

-

Рассмотрим другой пример перечисления в приложении 6-2: в этом примере каждый элемент перечисления имеет свой особый вид данных внутри:

-
enum Message {
-    Quit,
-    Move { x: i32, y: i32 },
-    Write(String),
-    ChangeColor(i32, i32, i32),
-}
-
-fn main() {}
-

Приложение 6-2. Перечисление Message, в каждом из исходов которого хранятся разные количества и виды значений.

-

Это перечисление имеет 4 элемента:

-
    -
  • Quit - пустой элемент без сопряженных данных,
  • -
  • Move имеет именованные поля, как и устройства.
  • -
  • Write - элемент с единственной строкой вида String,
  • -
  • ChangeColor - упорядоченный ряд из трёх значений вида i32.
  • -
-

Определение перечисления с исходами, такими как в приложении 6-2, похоже на определение значений различных видов внутри устройств, за исключением того, что перечисление не использует ключевое слово struct и все исходы объединены внутри вида Message. Следующие устройства могут содержать те же данные, что и предыдущие исходы перечислений:

-
struct QuitMessage; // unit struct
-struct MoveMessage {
-    x: i32,
-    y: i32,
-}
-struct WriteMessage(String); // tuple struct
-struct ChangeColorMessage(i32, i32, i32); // tuple struct
-
-fn main() {}
-

Но когда мы использовали различные устройства, каждая из которых имеет свои собственные виды, мы не могли легко определять функции, которые принимают любые виды сообщений, как это можно сделать с помощью перечисления вида Message, объявленного в приложении 6-2, который является единым видом.

-

Есть ещё одно сходство между перечислениями и устройствами: так же, как мы можем определять способы для устройств с помощью impl раздела, мы можем определять и способы для перечисления. Вот пример способа с именем call, который мы могли бы определить в нашем перечислении Message:

-
fn main() {
-    enum Message {
-        Quit,
-        Move { x: i32, y: i32 },
-        Write(String),
-        ChangeColor(i32, i32, i32),
-    }
-
-    impl Message {
-        fn call(&self) {
-            // method body would be defined here
-        }
-    }
-
-    let m = Message::Write(String::from("hello"));
-    m.call();
-}
-

В теле способа будет использоваться self для получения значение того предмета. у которого мы вызвали этот способ. В этом примере мы создали переменную m, содержащую значение Message::Write(String::from("hello")), и именно это значение будет представлять self в теле способа call при выполнении m.call().

-

Теперь посмотрим на другое наиболее часто используемое перечисление из встроенной библиотеки, которое является очень распространённым и полезным: Option.

-

Перечисление Option и его преимущества перед Null-значениями

-

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

-

Например, если вы запросите первый элемент из непустого списка, вы получите значение. Если вы запросите первый элемент пустого списка, вы ничего не получите. Выражение этой подходы в понятиях системы видов означает, что сборщик может проверить, обработали ли вы все случаи, которые должны были обработать; эта возможность может предотвратить ошибки, которые чрезвычайно распространены в других языках программирования.

-

Внешний вид языка программирования часто рассматривается с точки зрения того, какие функции вы включаете в него, но те функции, которые вы исключаете, также важны. Например в Ржавчина нет такого возможностей как null значения, однако он есть во многих других языках. Null значение - это значение, которое означает, что значения нет. В языках с null значением переменные всегда могут находиться в одном из двух состояний: нет значения (null) или есть значение (not-null).

-

В своей презентации 2009 года «Null ссылки: ошибка в миллиард долларов» Тони Хоар (Tony Hoare), изобретатель null, сказал следующее:

-
-

Я называю это своей ошибкой на миллиард долларов. В то время я разрабатывал первую комплексную систему видов для ссылок на предметно-направленном языке. Моя цель состояла в том, чтобы обеспечить, что любое использование ссылок должно быть абсолютно безопасным, с самостоятельной проверкой сборщиком. Но я не мог устоять перед соблазном вставить пустую ссылку просто потому, что это было так легко выполнить. Это привело к бесчисленным ошибкам, уязвимостям и системным сбоям, которые, вероятно, причинили боль и ущерб на миллиард долларов за последние сорок лет.

-
-

Неполадкас null значениями заключается в том, что если вы попытаетесь использовать null значение в качестве not-null значения, вы получите ошибку определённого рода. Поскольку свойство null или not-null распространено повсеместно, сделать такую ошибку очень просто.

-

Тем не менее, подход, которую null пытается выразить, является полезной: null - это значение, которое в настоящее время по какой-то причине недействительно или отсутствует.

-

Неполадкана самом деле не в подходы, а в именно выполнения. Таким образом, в Ржавчина нет значений null, но есть перечисление, которое может закодировать подход присутствия или отсутствия значения. Это перечисление Option<T> , и оно определено встроенной библиотекой следующим образом:

-
#![allow(unused)]
-fn main() {
-enum Option<T> {
-    None,
-    Some(T),
-}
-}
-

Перечисление Option<T> настолько полезно, что оно даже включено в прелюдию; вам не нужно явно вводить его в область видимости. Его исходы также включены в прелюдию: вы можете использовать Some и None напрямую, без приставки Option::. При всём при этом, Option<T> является обычным перечислением, а Some(T) и None представляют собой его исходы.

-

<T> - это особенность Rust, о которой мы ещё не говорили. Это свойство обобщённого вида, и мы рассмотрим его более подробно в главе 10. На данный мгновение всё, что вам нужно знать, это то, что <T> означает, что исход Some Option может содержать один отрывок данных любого вида, и что каждый определенный вид, который используется вместо T делает общий Option<T> другим видом. Вот несколько примеров использования Option для хранения числовых и строковых видов:

-
fn main() {
-    let some_number = Some(5);
-    let some_char = Some('e');
-
-    let absent_number: Option<i32> = None;
-}
-

Вид some_number - Option<i32>. Вид some_char - Option<char>, это другой вид. Ржавчина может вывести эти виды, потому что мы указали значение внутри исхода Some. Для absent_number Ржавчина требует, чтобы мы определяли общий вид для Option: сборщик не может вывести вид, который будет в Some, глядя только на значение None. Здесь мы сообщаем Rust, что absent_number должен иметь вид Option<i32>.

-

Когда есть значение Some, мы знаем, что значение присутствует и содержится внутри Some. Когда есть значение None, это означает то же самое, что и null в некотором смысле: у нас нет действительного значения. Так почему наличие Option<T> лучше, чем null?

-

Вкратце, поскольку Option<T> и T (где T может быть любым видом) относятся к разным видам, сборщик не позволит нам использовать значение Option<T> даже если бы оно было определённо допустимым значением. Например, этот код не будет собираться, потому что он пытается добавить i8 к значению вида Option<i8>:

-
fn main() {
-    let x: i8 = 5;
-    let y: Option<i8> = Some(5);
-
-    let sum = x + y;
-}
-

Если мы запустим этот код, то получим такое сообщение об ошибке:

-
$ cargo run
-   Compiling enums v0.1.0 (file:///projects/enums)
-error[E0277]: cannot add `Option<i8>` to `i8`
- --> src/main.rs:5:17
-  |
-5 |     let sum = x + y;
-  |                 ^ no implementation for `i8 + Option<i8>`
-  |
-  = help: the trait `Add<Option<i8>>` is not implemented for `i8`
-  = help: the following other types implement trait `Add<Rhs>`:
-            <&'a i8 as Add<i8>>
-            <&i8 as Add<&i8>>
-            <i8 as Add<&i8>>
-            <i8 as Add>
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `enums` (bin "enums") due to 1 previous error
-
-

Сильно! В действительности, это сообщение об ошибке означает, что Ржавчина не понимает, как сложить i8 и Option<i8>, потому что это разные виды. Когда у нас есть значение вида наподобие i8, сборщик заверяет, что у нас всегда есть допустимое значение вида. Мы можем уверенно продолжать работу, не проверяя его на null перед использованием. Однако, когда у нас есть значение вида Option<T> (где T - это любое значение любого вида T, упакованное в Option, например значение вида i8 или String), мы должны беспокоиться о том, что значение вида T возможно не имеет значения (является исходом None), и сборщик позаботится о том, чтобы мы обработали такой случай, прежде чем мы бы попытались использовать None значение.

-

Другими словами, вы должны преобразовать Option<T> в T прежде чем вы сможете выполнять действия с этим T. Как правило, это помогает выявить одну из наиболее распространённых неполадок с null: предполагая, что что-то не равно null, когда оно на самом деле равно null.

-

Устранение риска ошибочного предположения касательно не-null значения помогает вам быть более уверенным в своём коде. Чтобы иметь значение, которое может быть null, вы должны явно описать вид этого значения с помощью Option<T>. Затем, когда вы используете это значение, вы обязаны явно обрабатывать случай, когда значение равно null. Везде, где значение имеет вид, отличный от Option<T>, вы можете смело рассчитывать на то, что значение не равно null. Это продуманное расчетное решение в Rust, ограничивающее распространение null и увеличивающее безопасность кода на Rust.

-

Итак, как же получить значение T из исхода Some, если у вас на руках есть только предмет Option<T>, и как можно его, вообще, использовать? Перечисление Option<T> имеет большое количество способов, полезных в различных случаейх; вы можете ознакомиться с ними в его документации. Знакомство с способами перечисления Option<T> будет чрезвычайно полезным в вашем путешествии с Rust.

-

В общем случае, чтобы использовать значение Option<T>, нужен код, который будет обрабатывать все исходы перечисления Option<T>. Вам понадобится некоторый код, который будет работать только тогда, когда у вас есть значение Some(T), и этому коду разрешено использовать внутри T. Также вам понадобится другой код, который будет работать, если у вас есть значение None, и у этого кода не будет доступного значения T. Выражение match — это устройство управления потоком выполнения программы, которая делает именно это при работе с перечислениями: она запускает разный код в зависимости от того, какой исход перечисления имеется, и этот код может использовать данные, находящиеся внутри совпавшего исхода.

-
-

-

Управляющая устройство match

-

В Ржавчина есть чрезвычайно мощный рычаг управления потоком, именуемый match, который позволяет сравнивать значение с различными образцами и затем выполнять код в зависимости от того, какой из образцов совпал. Образцы могут состоять из записанных значений, имён переменных, подстановочных знаков и многого другого; в главе 18 рассматриваются все различные виды образцов и то, что они делают. Сила match заключается в выразительности образцов и в том, что сборщик проверяет, что все возможные случаи обработаны.

-

Думайте о выражении match как о машине для сортировки монет: монеты скользят по дорожке с различными по размеру отверстиями, и каждая монета падает через первое попавшееся отверстие, в которое она поместилась. Таким же образом значения проходят через каждый образец в match, и при первом же "подходящем" образце значение попадает в соответствующий раздел кода, который будет использоваться во время выполнения.

-

Говоря о монетах, давайте используем их в качестве примера, используя match! Для этого мы напишем функцию, которая будет получать на вход неизвестную монету Соединённых Штатов и, подобно счётной машине, определять, какая это монета, и возвращать её стоимость в центах, как показано в приложении 6-3.

-
enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter,
-}
-
-fn value_in_cents(coin: Coin) -> u8 {
-    match coin {
-        Coin::Penny => 1,
-        Coin::Nickel => 5,
-        Coin::Dime => 10,
-        Coin::Quarter => 25,
-    }
-}
-
-fn main() {}
-

Приложение 6-3: Перечисление и выражение match, использующее в качестве образцов его исходы

-

Давайте разберём match в функции value_in_cents. Сначала пишется ключевое слово match, затем следует выражение, которое в данном случае является значением coin. Это выглядит очень похоже на условное выражение, используемое в if, но есть большая разница: с if выражение должно возвращать булево значение, а здесь это может быть любой вид. Вид coin в этом примере — перечисление вида Coin, объявленное в строке 1.

-

Далее идут ветки match. Ветки состоят из двух частей: образец и некоторый код. Здесь первая ветка имеет образец, который является значением Coin::Penny, затем идёт оператор =>, который разделяет образец и код для выполнения. Код в этом случае - это просто значение 1. Каждая ветка отделяется от последующей при помощи запятой.

-

Когда выполняется выражение match, оно сравнивает полученное значение с образцом каждого ответвления по порядку. Если образец совпадает со значением, то выполняется код, связанный с этим образцом. Если этот образец не соответствует значению, то выполнение продолжается со следующей ветки, так же, как в автомате по сортировке монет. У нас может быть столько ответвлений, сколько нужно: в приложении 6-3 наш match состоит из четырёх ответвлений.

-

Код, связанный с каждым ответвлением, является выражением, а полученное значение выражения в соответствующем ответвлении — это значение, которое возвращается для всего выражения match.

-

Обычно фигурные скобки не используются, если код совпадающей ветви невелик, как в приложении 6-3, где каждая ветвь просто возвращает значение. Если вы хотите выполнить несколько строк кода в одной ветви, вы должны использовать фигурные скобки, а запятая после этой ветви необязательна. Например, следующий код печатает "Lucky penny!" каждый раз, когда способ вызывается с Coin::Penny, но при этом он возвращает последнее значение раздела - 1:

-
enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter,
-}
-
-fn value_in_cents(coin: Coin) -> u8 {
-    match coin {
-        Coin::Penny => {
-            println!("Lucky penny!");
-            1
-        }
-        Coin::Nickel => 5,
-        Coin::Dime => 10,
-        Coin::Quarter => 25,
-    }
-}
-
-fn main() {}
-

Образцы, привязывающие значения

-

Есть ещё одно полезное качество у веток в выражении match: они могут привязываться к частям тех значений, которые совпали с образцом. Благодаря этому можно извлекать значения из исходов перечисления.

-

В качестве примера, давайте изменим один из исходов перечисления так, чтобы он хранил в себе данные. С 1999 по 2008 год Соединённые Штаты чеканили 25 центов с различным внешнем видом на одной стороне для каждого из 50 штатов. Ни одна другая монета не получила внешнего видаштата, только четверть доллара имела эту дополнительную особенность. Мы можем добавить эту сведения в наш enum путём изменения исхода Quarter и включить в него значение UsState, как сделано в приложении 6-4.

-
#[derive(Debug)] // so we can inspect the state in a minute
-enum UsState {
-    Alabama,
-    Alaska,
-    // --snip--
-}
-
-enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter(UsState),
-}
-
-fn main() {}
-

Приложение 6-4: Перечисление Coin, в котором исход Quarter также сохраняет значение UsState

-

Представьте, что ваш друг пытается собрать четвертаки всех 50 штатов. Сортируя монеты по виду, мы также будем сообщать название штата, к которому относится каждый четвертак, чтобы, если у нашего друга нет такой монеты, он мог добавить её в свою собрание.

-

В выражении match для этого кода мы добавляем переменную с именем state в образец, который соответствует значениям исхода Coin::Quarter. Когда Coin::Quarter совпадёт с образцом, переменная state будет привязана к значению штата этого четвертака. Затем мы сможем использовать state в коде этой ветки, вот так:

-
#[derive(Debug)]
-enum UsState {
-    Alabama,
-    Alaska,
-    // --snip--
-}
-
-enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter(UsState),
-}
-
-fn value_in_cents(coin: Coin) -> u8 {
-    match coin {
-        Coin::Penny => 1,
-        Coin::Nickel => 5,
-        Coin::Dime => 10,
-        Coin::Quarter(state) => {
-            println!("State quarter from {state:?}!");
-            25
-        }
-    }
-}
-
-fn main() {
-    value_in_cents(Coin::Quarter(UsState::Alaska));
-}
-

Если мы сделаем вызов функции value_in_cents(Coin::Quarter(UsState::Alaska)), то coin будет иметь значение Coin::Quarter(UsState::Alaska). Когда мы будем сравнивать это значение с каждой из веток, ни одна из них не будет совпадать, пока мы не достигнем исхода Coin::Quarter(state). В этот мгновение state привяжется к значению UsState::Alaska. Затем мы сможем использовать эту привязку в выражении println!, получив таким образом внутреннее значение исхода Quarter перечисления Coin.

-

Сопоставление образца для Option<T>

-

В предыдущем разделе мы хотели получить внутреннее значение T для случая Some при использовании Option<T>; мы можем обработать вид Option<T> используя match, как уже делали с перечислением Coin! Вместо сравнения монет мы будем сравнивать исходы Option<T>, независимо от этого изменения рычаг работы выражения match останется прежним.

-

Допустим, мы хотим написать функцию, которая принимает Option<i32> и если есть значение внутри, то добавляет 1 к существующему значению. Если значения нет, то функция должна возвращать значение None и не пытаться выполнить какие-либо действия.

-

Такую функцию довольно легко написать благодаря выражению match, код будет выглядеть как в приложении 6-5.

-
fn main() {
-    fn plus_one(x: Option<i32>) -> Option<i32> {
-        match x {
-            None => None,
-            Some(i) => Some(i + 1),
-        }
-    }
-
-    let five = Some(5);
-    let six = plus_one(five);
-    let none = plus_one(None);
-}
-

Приложение 6-5: Функция, использующая выражение match для Option<i32>

-

Давайте более подробно рассмотрим первое выполнение plus_one. Когда мы вызываем plus_one(five), переменная x в теле plus_one будет иметь значение Some(5). Затем мы сравниваем это значение с каждой ветвью сопоставления:

-
fn main() {
-    fn plus_one(x: Option<i32>) -> Option<i32> {
-        match x {
-            None => None,
-            Some(i) => Some(i + 1),
-        }
-    }
-
-    let five = Some(5);
-    let six = plus_one(five);
-    let none = plus_one(None);
-}
-

Значение Some(5) не соответствует образцу None, поэтому мы продолжаем со следующим ответвлением:

-
fn main() {
-    fn plus_one(x: Option<i32>) -> Option<i32> {
-        match x {
-            None => None,
-            Some(i) => Some(i + 1),
-        }
-    }
-
-    let five = Some(5);
-    let six = plus_one(five);
-    let none = plus_one(None);
-}
-

Совпадает ли Some(5) с образцом Some(i)? Да, это так! У нас такой же исход. Тогда переменная i привязывается к значению, содержащемуся внутри Some, поэтому i получает значение 5. Затем выполняется код сопряженный для данного ответвления, поэтому мы добавляем 1 к значению i и создаём новое значение Some со значением 6 внутри.

-

Теперь давайте рассмотрим второй вызов plus_one в приложении 6-5, где x является None. Мы входим в выражение match и сравниваем значение с первым ответвлением:

-
fn main() {
-    fn plus_one(x: Option<i32>) -> Option<i32> {
-        match x {
-            None => None,
-            Some(i) => Some(i + 1),
-        }
-    }
-
-    let five = Some(5);
-    let six = plus_one(five);
-    let none = plus_one(None);
-}
-

Оно совпадает! Для данной ветки образец (None) не подразумевает наличие какого-то значения к которому можно было бы что-то добавить, поэтому программа останавливается и возвращает значение которое находится справа от => - т.е. None. Так как образец первой ветки совпал, то никакие другие образцы веток не сравниваются.

-

Соединение match и перечислений полезно во многих случаейх. Вы часто будете видеть подобную сочетание в коде на Rust: сделать сопоставление значений перечисления используя match, привязать переменную к данным внутри значения, выполнить код на основе привязанных данных. Сначала это может показаться немного сложным, но как только вы привыкнете, то захотите чтобы такая возможность была бы во всех языках. Это неизменно любимый пользователями приём.

-

Match охватывает все исходы значения

-

Есть ещё один особенность match, который мы должны обсудить: образцы должны покрывать все возможные исходы. Рассмотрим эту исполнение нашей функции plus_one, которая содержит ошибку и не собирается:

-
fn main() {
-    fn plus_one(x: Option<i32>) -> Option<i32> {
-        match x {
-            Some(i) => Some(i + 1),
-        }
-    }
-
-    let five = Some(5);
-    let six = plus_one(five);
-    let none = plus_one(None);
-}
-

Мы не обработали исход None, поэтому этот код вызовет изъян в программе. К счастью, Ржавчина знает и умеет ловить такой случай. Если мы попытаемся собрать такой код, мы получим ошибку сборки:

-
$ cargo run
-   Compiling enums v0.1.0 (file:///projects/enums)
-error[E0004]: non-exhaustive patterns: `None` not covered
- --> src/main.rs:3:15
-  |
-3 |         match x {
-  |               ^ pattern `None` not covered
-  |
-note: `Option<i32>` defined here
- --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/option.rs:571:1
- ::: /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/option.rs:575:5
-  |
-  = note: not covered
-  = note: the matched value is of type `Option<i32>`
-help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
-  |
-4 ~             Some(i) => Some(i + 1),
-5 ~             None => todo!(),
-  |
-
-For more information about this error, try `rustc --explain E0004`.
-error: could not compile `enums` (bin "enums") due to 1 previous error
-
-

Rust знает, что мы не описали все возможные случаи, и даже знает, какой именно из образцов мы упуисполнения! Сопоставления в Ржавчина являются исчерпывающими: мы должны покрыть все возможные исходы, чтобы код был правильным. Особенно в случае Option<T>, когда Ржавчина не даёт нам забыть обработать явным образом значение None, тем самым он защищает нас от предположения, что у нас есть значение, в то время как у нас может быть и null, что делает невозможным совершить ошибку на миллиард долларов, о которой говорилось ранее.

-

Гибкие образцы и заполнитель _

-

Используя перечисления, мы также можем выполнять особые действия для нескольких определённых значений, а для всех остальных значений выполнять одно действие по умолчанию. Представьте, что мы выполняем игру, в которой при выпадении 3 игрок не двигается, а получает новую нового образца шляпу. Если выпадает 7, игрок теряет шляпу. При всех остальных значениях ваш игрок перемещается на столько-то мест на игровом поле. Вот match, выполняющий эту логику, в котором итог броска костей жёстко закодирован, а не является случайным значением, а вся остальная логика представлена функциями без тел, поскольку их выполнение не входит в рамки данного примера:

-
fn main() {
-    let dice_roll = 9;
-    match dice_roll {
-        3 => add_fancy_hat(),
-        7 => remove_fancy_hat(),
-        other => move_player(other),
-    }
-
-    fn add_fancy_hat() {}
-    fn remove_fancy_hat() {}
-    fn move_player(num_spaces: u8) {}
-}
-

Для первых двух веток образцами являются записанные значения 3 и 7. Для последней ветки, которая охватывает все остальные возможные значения, образцом является переменная, которую мы решили назвать other. Код, выполняемый для ветки other, использует эту переменную, передавая её в функцию move_player.

-

Этот код собирается, даже если мы не перечислили все возможные значения u8, потому что последний образец будет соответствовать всем значениям, не указанным в определенном списке. Этот гибкий образец удовлетворяет требованию, что соответствие должно быть исчерпывающим. Обратите внимание, что мы должны поместить ветку с гибким образцом последней, потому что образцы оцениваются по порядку. Ржавчина предупредит нас, если мы добавим ветки после гибкого образца, потому что эти последующие ветки никогда не будут выполняться!

-

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

-

Давайте изменим правила игры так: если выпадает что-то, кроме 3 или 7, нужно бросить ещё раз. Нам не нужно использовать значение в этом случае, поэтому мы можем изменить наш код, чтобы использовать _ вместо переменной с именем other:

-
fn main() {
-    let dice_roll = 9;
-    match dice_roll {
-        3 => add_fancy_hat(),
-        7 => remove_fancy_hat(),
-        _ => reroll(),
-    }
-
-    fn add_fancy_hat() {}
-    fn remove_fancy_hat() {}
-    fn reroll() {}
-}
-

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

-

Если мы изменим правила игры ещё раз, чтобы в ваш ход не происходило ничего другого, если вы бросаете не 3 или 7, мы можем выразить это, используя единичное значение (пустой вид упорядоченного ряда, о котором мы упоминали в разделе "Упорядоченные ряды") в качестве кода, который идёт вместе с веткой _:

-
fn main() {
-    let dice_roll = 9;
-    match dice_roll {
-        3 => add_fancy_hat(),
-        7 => remove_fancy_hat(),
-        _ => (),
-    }
-
-    fn add_fancy_hat() {}
-    fn remove_fancy_hat() {}
-}
-

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

-

Подробнее о образцах и совпадениях мы поговорим в Главе 18. Пока же мы перейдём к правилам написания if let, который может быть полезен в случаейх, когда выражение match слишком многословно.

-

Краткое управление потоком выполнения с if let

-

правила написания if let позволяет ссоединенять if и let в менее многословную устройство, и затем обработать значения соответствующе только одному образцу, одновременно пренебрегая все остальные. Рассмотрим программу в приложении 6-6, которая обрабатывает сопоставление значения Option<u8> в переменной config_max, но хочет выполнить код только в том случае, если значение является исходом Some.

-
fn main() {
-    let config_max = Some(3u8);
-    match config_max {
-        Some(max) => println!("The maximum is configured to be {max}"),
-        _ => (),
-    }
-}
-

Приложение 6-6. Выражение match, которое выполнит код только при значении равном Some

-

Если значение равно Some, мы распечатываем значение в исходе Some, привязывая значение к переменной max в образце. Мы не хотим ничего делать со значением None. Чтобы удовлетворить выражение match, мы должны добавить _ => () после обработки первой и единственной ветки, и добавление образцового кода раздражает.

-

Вместо этого, мы могли бы написать это более коротким способом, используя if let. Следующий код ведёт себя так же, как выражение match в приложении 6-6:

-
fn main() {
-    let config_max = Some(3u8);
-    if let Some(max) = config_max {
-        println!("The maximum is configured to be {max}");
-    }
-}
-

правила написания if let принимает образец и выражение, разделённые знаком равенства. Он работает так же, как match, когда в него на вход передадут выражение и подходящим образцом для этого выражения окажется первая ветка. В данном случае образцом является Some(max), где max привязывается к значению внутри Some. Затем мы можем использовать max в теле раздела if let так же, как мы использовали max в соответствующей ветке match. Код в разделе if let не запускается, если значение не соответствует образцу.

-

Используя if let мы меньше печатаем, меньше делаем отступов и меньше получаем образцового кода. Тем не менее, мы теряем полную проверку всех исходов, предоставляемую выражением match. Выбор между match и if let зависит от того, что вы делаете в вашем определенном случае и является ли получение краткости при потере полноты проверки подходящим соглашением.

-

Другими словами, вы можете думать о устройства if let как о синтаксическом сахаре для match, который выполнит код если входное значение будет соответствовать единственному образцу, и пренебрегает все остальные значения.

-

Можно добавлять else к if let. Разделкода, который находится внутри else подобен по смыслу блоку кода ветки связанной с образцом _ выражения match (которое эквивалентно сборной устройства if let и else). Вспомним объявление перечисления Coin в приложении 6-4, где исход Quarter также содержит внутри значение штата вида UsState. Если бы мы хотели посчитать все монеты не являющиеся четвертями, а для четвертей печатать название штата, то мы могли бы сделать это с помощью выражения match таким образом:

-
#[derive(Debug)]
-enum UsState {
-    Alabama,
-    Alaska,
-    // --snip--
-}
-
-enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter(UsState),
-}
-
-fn main() {
-    let coin = Coin::Penny;
-    let mut count = 0;
-    match coin {
-        Coin::Quarter(state) => println!("State quarter from {state:?}!"),
-        _ => count += 1,
-    }
-}
-

Или мы могли бы использовать выражение if let и else так:

-
#[derive(Debug)]
-enum UsState {
-    Alabama,
-    Alaska,
-    // --snip--
-}
-
-enum Coin {
-    Penny,
-    Nickel,
-    Dime,
-    Quarter(UsState),
-}
-
-fn main() {
-    let coin = Coin::Penny;
-    let mut count = 0;
-    if let Coin::Quarter(state) = coin {
-        println!("State quarter from {state:?}!");
-    } else {
-        count += 1;
-    }
-}
-

Если у вас есть случаей в которой ваша программа имеет логику которая слишком многословна для того чтобы её выражать используя match, помните, о том, что также в вашем наборе средств Ржавчина есть if let.

-

Итоги

-

Мы рассмотрели как использовать перечисления для создания пользовательских видов, которые могут быть одним из наборов перечисляемых значений. Мы показали, как вид Option<T> из встроенной библиотеки помогает использовать систему видов для предотвращения ошибок. А когда значения перечисления имеют данные внутри них, можно использовать match или if let, чтобы извлечь и пользоваться значением, в зависимости от того, сколько случаев нужно обработать.

-

Теперь ваши программы на Ржавчина могут выражать подходы вашей предметной области, используя устройства и перечисления. Создание и использование пользовательских видов в API обеспечивает типобезопасность: сборщик позаботится о том, чтобы функции получали значения только того вида, который они ожидают.

-

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

-

Управление растущими делами с помощью дополнений, ящиков и звеньев

-

По мере роста кодовой хранилища ваших программ, создание дела будет иметь большое значение, ведь отслеживание всей программы в голове будет становиться всё более сложным. Объединенияя связанные функции и разделяя код по основным возможностям (фичам, feature), вы делаете более прозрачным понимание о том, где искать код выполняющий определённую функцию и где стоит вносить изменения для того чтобы изменить её поведение.

-

Программы, которые мы писали до сих пор, были в одном файле одного звена. По мере роста дела, мы можем создавать код иначе, разделив его на несколько звеньев и несколько файлов. Дополнение может содержать несколько двоичных ящиков и дополнительно один ящик библиотеки. Дополнение может включать в себя много двоичных ящиков и дополнительно один библиотечный ящик. По мере роста дополнения вы можете извлекать части программы в отдельные ящики, которые затем станут внешними зависимостями для основного кода нашей программы. Эта глава охватывает все эти техники. В свою очередь для очень крупных дел, состоящих из набора взаимосвязанных дополнений развивающихся вместе, Cargo предоставляет рабочие пространства, workspaces, их мы рассмотрим за пределами данной главы, в разделе "Рабочие пространства Cargo" Главы 14.

-

Мы также обсудим инкапсуляцию подробностей, которая позволяет использовать код снова на более высоком уровне: единожды выполнив какую-то действие, другой код может вызывать этот код через открытый внешняя оболочка, не зная как работает выполнение. То, как вы пишете код, определяет какие части общедоступны для использования другим кодом и какие части являются закрытыми деталями выполнения для которых вы оставляете право на изменения только за собой. Это ещё один способ ограничить количество подробностей, которые вы должны держать в голове.

-

Связанное понятие - это область видимости: вложенный среда в котором написан код имеющий набор имён, которые определены «в текущей области видимости». При чтении, письме и сборки кода, программистам и сборщикам необходимо знать, относится ли определенное имя в определённом месте к переменной, к функции, к устройстве, к перечислению, к звену, к постоянных значенийе или другому элементу и что означает этот элемент. Можно создавать области видимости и изменять какие имена входят или выходят за их рамки. Нельзя иметь два элемента с тем же именем в одной области; есть доступные средства для разрешения несоответствий имён.

-

Rust имеет ряд функций, которые позволяют управлять согласованием кода, в том числе управлять тем какие подробности открыты, какие подробности являются частными, какие имена есть в каждой области вашей программы. Эти функции иногда вместе именуемые состоящей из звеньев системой включают в себя:

-
    -
  • Дополнения: Возможности Cargo позволяющий собирать, проверять и делиться ящиками
  • -
  • Ящики: Дерево звеньев, которое создаёт библиотечный или исполняемый файл
  • -
  • Звенья и use: Позволяют вместе управлять устройство, область видимости и скрытие путей
  • -
  • Пути: способ именования элемента, такого как устройства, функция или звено
  • -
-

В этой главе мы рассмотрим все эти функции, обсудим как они взаимодействуют и объясним, как использовать их для управления областью видимости. К концу у вас должно появиться солидное понимание состоящей из звеньев системы и умение работать с областями видимости на уровне искуссника!

-

Дополнения и ящики

-

Первые части состоящей из звеньев системы, которые мы рассмотрим — это дополнения и ящики.

-

Ящик — это наименьший размер кода, который сборщик Ржавчина рассматривает за раз. Даже если вы запустите rustc вместо cargo и передадите один файл с исходным кодом (как мы уже делали в разделе «Написание и запуск программы на Rust» Главы 1), сборщик считает этот файл ящиком. Ящики могут содержать звенья, и звенья могут быть определены в других файлах, которые собираются вместе с ящиком, как мы увидим в следующих разделах.

-

Ящик может быть одним из двух видов: двоичный ящик или библиотечный ящик. Бинарные ящики — это программы, которые вы можете собрать в исполняемые файлы, которые вы можете запускать, например программу приказной строки или сервер. У каждого двоичного ящика должна быть функция с именем main, которая определяет, что происходит при запуске исполняемого файла. Все ящики, которые мы создали до сих пор, были двоичными ящиками.

-

Библиотечные ящики не имеют функции main и не собираются в исполняемый файл. Вместо этого они определяют возможность, предназначенную для совместного использования другими делами. Например, ящик rand, который мы использовали в Главе 2 обеспечивает возможность, которая порождает случайные числа. В большинстве случаев, когда Rustaceans говорят «ящик», они имеют в виду библиотечный ящик, и они используют «ящик» взаимозаменяемо с общей подходом программирования «библиотека».

-

Корневой звено ящика — это исходный файл, из которого сборщик Ржавчина начинает собирать корневой звено вашего ящика (мы подробно объясним звенья в разделе «Определение звеньев для управления видимости и закрытости»).

-

Дополнение — это набор из одного или нескольких ящиков, предоставляющий набор возможности. Дополнение содержит файл Cargo.toml, в котором описывается, как собирать эти ящики. На самом деле Cargo — это дополнение, содержащий двоичный ящик для средства приказной строки, который вы использовали для создания своего кода. Дополнение Cargo также содержит библиотечный ящик, от которого зависит двоичный ящик. Другие дела тоже могут зависеть от библиотечного ящика Cargo, чтобы использовать ту же логику, что и средство приказной строки Cargo.

-

Дополнение может содержать сколько угодно двоичных ящиков, но не более одного библиотечного ящика. Дополнение должен содержать хотя бы один ящик, библиотечный или двоичный.

-

Давайте пройдёмся по тому, что происходит, когда мы создаём дополнение. Сначала введём приказ cargo new:

-
$ cargo new my-project
-     Created binary (application) `my-project` package
-$ ls my-project
-Cargo.toml
-src
-$ ls my-project/src
-main.rs
-
-

После того, как мы запустили 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: каждый файл будет отдельным двоичным ящиком.

-

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

-

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

-
backyard
-├── Cargo.lock
-├── Cargo.toml
-└── src
-    ├── garden
-    │   └── vegetables.rs
-    ├── garden.rs
-    └── main.rs
-
-

Файл корневого звена ящика в нашем случае src/main.rs, и его содержимое:

-

Файл: src/main.rs

-
use crate::garden::vegetables::Asparagus;
-
-pub mod garden;
-
-fn main() {
-    let plant = Asparagus {};
-    println!("I'm growing {plant:?}!");
-}
-

Строка pub mod garden; говорит сборщику о подключении кода, найденном в src/garden.rs:

-

Файл: src/garden.rs

-
pub mod vegetables;
-

А здесь pub mod vegetables; указывает на подключаемый код в src/garden/vegetables.rs. Этот код:

-
#[derive(Debug)]
-pub struct Asparagus {}
-

Теперь давайте рассмотрим подробности этих правил и отобразим их в действии!

-

Объединение связанного кода в звенах

-

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

-

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

-

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

-

Чтобы внутренне выстроить

-

ящик подобно тому, как работает настоящий ресторан, можно согласовать размещение функций во вложенных звенах. Создадим новую библиотеку (библиотечный ящик) с именем restaurant выполнив приказ cargo new restaurant --lib; затем вставим код из приложения 7-1 в 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.

-
crate
- └── front_of_house
-     ├── hosting
-     │   ├── add_to_waitlist
-     │   └── seat_at_table
-     └── serving
-         ├── take_order
-         ├── serve_order
-         └── take_payment
-
-

Приложение 7-2: Древо звеньев для программы из Приложения 7-1

-

Это дерево показывает, как некоторые из звеньев вкладываются друг в друга; например, hosting находится внутри front_of_house. Дерево также показывает, что некоторые звенья являются братьями (siblings) друг для друга, то есть они определены в одном звене; hosting и serving это братья которые определены внутри front_of_house. Если звено A содержится внутри звена B, мы говорим, что звено A является потомком (child) звена B, а звено B является родителем (parent) звена A. Обратите внимание, что родителем всего дерева звеньев является неявный звено с именем crate.

-

Дерево звеньев может напомнить вам дерево папок файловой системы на компьютере; это очень удачное сравнение! По подобию с папкими в файловой системе, мы используется звенья для согласования кода. И так же, как нам надо искать файлы в папких на компьютере, нам требуется способ поиска нужных звеньев.

-

Пути для ссылки на элемент в дереве звеньев

-

Чтобы показать 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

-
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();
-}
-

Приложение 7-3. Вызов функции 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.

-
$ cargo build
-   Compiling restaurant v0.1.0 (file:///projects/restaurant)
-error[E0603]: module `hosting` is private
- --> src/lib.rs:9:28
-  |
-9 |     crate::front_of_house::hosting::add_to_waitlist();
-  |                            ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported
-  |                            |
-  |                            private module
-  |
-note: the module `hosting` is defined here
- --> src/lib.rs:2:5
-  |
-2 |     mod hosting {
-  |     ^^^^^^^^^^^
-
-error[E0603]: module `hosting` is private
-  --> src/lib.rs:12:21
-   |
-12 |     front_of_house::hosting::add_to_waitlist();
-   |                     ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported
-   |                     |
-   |                     private module
-   |
-note: the module `hosting` is defined here
-  --> src/lib.rs:2:5
-   |
-2  |     mod hosting {
-   |     ^^^^^^^^^^^
-
-For more information about this error, try `rustc --explain E0603`.
-error: could not compile `restaurant` (lib) due to 2 previous errors
-
-

Приложение 7-4. Ошибки сборки при сборке кода из приложения 7-3

-

Сообщения об ошибках говорят о том, что звено hosting является закрытым. Другими словами, у нас есть правильные пути к звену hosting и функции add_to_waitlist, но Ржавчина не позволяет нам использовать их, потому что у него нет доступа к закрытым разделам. В Ржавчина все элементы (функции, способы, устройства, перечисления, звенья и постоянные значения) по умолчанию являются закрытыми для родительских звеньев. Если вы хотите сделать элемент, например функцию или устройство, закрытым, вы помещаете его в звено.

-

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

-

В Ржавчина решили, что система звеньев должна исполняться таким образом, чтобы по умолчанию скрывать подробности выполнения. Таким образом, вы знаете, какие части внутреннего кода вы можете изменять не нарушая работы внешнего кода. Тем не менее, Ржавчина даёт нам возможность открывать внутренние части кода дочерних звеньев для внешних звеньев-предков, используя ключевое слово pub, чтобы сделать элемент общедоступным.

-

Раскрываем закрытые пути с помощью ключевого слова pub

-

Давайте вернёмся к ошибке в приложении 7-4, которая говорит, что звено hosting является закрытым. Мы хотим, чтобы функция eat_at_restaurant из родительского звена имела доступ к функции add_to_waitlist в дочернем звене, поэтому мы помечаем звено hosting ключевым словом pub, как показано в приложении 7-5.

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub 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();
-}
-

Приложение 7-5. Объявление звена hosting как pub для его использования из eat_at_restaurant

-

К сожалению, код в приложении 7-5 всё ещё приводит к ошибке, как показано в приложении 7-6.

-
$ cargo build
-   Compiling restaurant v0.1.0 (file:///projects/restaurant)
-error[E0603]: function `add_to_waitlist` is private
- --> src/lib.rs:9:37
-  |
-9 |     crate::front_of_house::hosting::add_to_waitlist();
-  |                                     ^^^^^^^^^^^^^^^ private function
-  |
-note: the function `add_to_waitlist` is defined here
- --> src/lib.rs:3:9
-  |
-3 |         fn add_to_waitlist() {}
-  |         ^^^^^^^^^^^^^^^^^^^^
-
-error[E0603]: function `add_to_waitlist` is private
-  --> src/lib.rs:12:30
-   |
-12 |     front_of_house::hosting::add_to_waitlist();
-   |                              ^^^^^^^^^^^^^^^ private function
-   |
-note: the function `add_to_waitlist` is defined here
-  --> src/lib.rs:3:9
-   |
-3  |         fn add_to_waitlist() {}
-   |         ^^^^^^^^^^^^^^^^^^^^
-
-For more information about this error, try `rustc --explain E0603`.
-error: could not compile `restaurant` (lib) due to 2 previous errors
-
-

Приложение 7-6: Ошибки сборки при сборке кода в приложении 7-5

-

Что произошло? Добавление ключевого слова pub перед mod hosting сделало звено общедоступным. После этого изменения, если мы можем получить доступ к звену front_of_house, то мы можем получить доступ к звену hosting. Но содержимое звена hosting всё ещё является закрытым: превращение звена в общедоступный звено не делает его содержимое общедоступным. Ключевое слово pub позволяет внешнему коду в звенах-предках обращаться только к звену, без доступа ко внутреннему коду. Поскольку звенья являются дополнениями, мы мало что можем сделать, просто сделав звено общедоступным; нам нужно пойти дальше и сделать один или несколько элементов в звене общедоступными.

-

Ошибки в приложении 7-6 говорят, что функция add_to_waitlist является закрытой. Правила закрытости применяются к устройствам, перечислениям, функциям и способам, также как и к звенам.

-

Давайте также сделаем функцию add_to_waitlist общедоступной, добавив ключевое слово pub перед её определением, как показано в приложении 7-7.

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub mod hosting {
-        pub 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();
-}
-

Приложение 7-7. Добавление ключевого слова pub к mod hosting и к fn add_to_waitlist позволяет нам вызывать функцию из eat_at_restaurant

-

Теперь код собирается! Чтобы понять, почему добавление ключевого слова pub позволяет нам использовать эти пути для add_to_waitlist в соответствии с правилами закрытости, давайте рассмотрим абсолютный и относительный пути.

-

В случае абсолютного пути мы начинаем с crate, корня дерева звеньев нашего ящика. Звено front_of_house определён в корневом звене ящика. Хотя front_of_house не является общедоступным, но поскольку функция eat_at_restaurant определена в том же звене, что и front_of_house (то есть, eat_at_restaurant и front_of_house являются потомками одного родителя), мы можем ссылаться на front_of_house из eat_at_restaurant. Далее идёт звено hosting, помеченный как pub. Мы можем получить доступ к родительскому звену звена hosting, поэтому мы можем получить доступ и к hosting. Наконец, функция add_to_waitlist помечена как pub, и так как мы можем получить доступ к её родительскому звену, то вызов этой функции разрешён!

-

В случае относительного пути логика такая же как для абсолютного пути, за исключением первого шага: вместо того, чтобы начинать с корневого звена ящика, путь начинается с front_of_house. Звено front_of_house определён в том же звене, что и eat_at_restaurant, поэтому относительный путь, начинающийся с звена, в котором определена eat_at_restaurant тоже работает. Тогда, по причине того, что hosting и add_to_waitlist помечены как pub, остальная часть пути работает и вызов этой функции разрешён!

-

Если вы собираетесь предоставить общий доступ к своему библиотечному ящику, чтобы другие дела могли использовать ваш код, ваш общедоступный API — это ваш договор с пользователями вашего ящика, определяющий, как они могут взаимодействовать с вашим кодом. Есть много соображений по поводу управления изменениями в вашем общедоступном API, чтобы сделать необременительным для людей зависимость от вашего ящика. Эти соображения выходят за рамки этой книги; если вам важна эта тема, см. The Ржавчина API Guidelines.

-
-

Лучшие опытов для дополнений с двоичным и библиотечным ящиками

-

Мы упоминали, что дополнение может содержать как корневой звено двоичного ящика src/main.rs, так и корневой звено библиотечного ящика src/lib.rs, и оба ящика будут по умолчанию иметь имя дополнения. Как правило, дополнения с таким образцом, содержащим как библиотечный, так и двоичный ящик, будут иметь достаточно кода в двоичном ящике, чтобы запустить исполняемый файл, который вызывает код из библиотечного ящика. Это позволяет другим делам извлечь выгоду из большей части возможности, предоставляемой дополнением, поскольку код библиотечного ящика можно использовать совместно.

-

Дерево звеньев должно быть определено в src/lib.rs. Затем любые общедоступные элементы можно использовать в двоичном ящике, начав пути с имени дополнения. Двоичный ящик становится пользователем библиотечного ящика точно так же, как полностью внешний ящик использует библиотечный ящик: он может использовать только общедоступный API. Это поможет вам разработать хороший API; вы не только автор, но и пользователь!

-

В Главе 12 мы эту опыт согласования кода с помощью окно выводаной программы, которая будет содержать как двоичный, так и библиотечный ящики.

-
-

Начинаем относительный путь с помощью super

-

Также можно построить относительные пути, которые начинаются в родительском звене, используя ключевое слово super в начале пути. Это похоже на правила написания начала пути файловой системы ... Использование super позволяет нам сослаться на элемент, который, как мы знаем, находится в родительском звене, что может упростить переупорядочение дерева звеньев, чем когда звено тесно связан с родителем, но родитель может когда-нибудь быть перемещён в другое место в дереве звеньев.

-

Рассмотрим код в приложении 7-8, где расчитывается случаей, в которой повар исправляет неправильный заказ и лично приносит его клиенту. Функция fix_incorrect_order вызывает функцию deliver_order, определённую в родительском звене, указывая путь к deliver_order, начинающийся с super:

-

Файл: src/lib.rs

-
fn deliver_order() {}
-
-mod back_of_house {
-    fn fix_incorrect_order() {
-        cook_order();
-        super::deliver_order();
-    }
-
-    fn cook_order() {}
-}
-

Приложение 7-8: Вызов функции с использованием относительного пути, начинающегося с super

-

Функция fix_incorrect_order находится в звене back_of_house, поэтому мы можем использовать super для перехода к родительскому звену звена back_of_house, который в этом случае является crate, корневым звеном. В этом звене мы ищем deliver_order и находим его. Успех! Мы думаем, что звено back_of_house и функция deliver_order, скорее всего, останутся в тех же родственных отношениях друг с другом, и должны будут перемещены вместе, если мы решим ресогласовать дерево звеньев ящика. Поэтому мы использовали super, чтобы в будущем у нас было меньше мест для обновления кода, если этот код будет перемещён в другой звено.

-

Делаем общедоступными устройства и перечисления

-

Мы также можем использовать pub для обозначения устройств и перечислений как общедоступных, но есть несколько дополнительных подробностей использования pub со устройствами и перечислениями. Если мы используем pub перед определением устройства, мы делаем устройство общедоступной, но поля устройства по-прежнему остаются закрытыми. Мы можем сделать каждое поле общедоступным или нет в каждом определенном случае. В приложении 7-9 мы определили общедоступную устройство back_of_house::Breakfast с общедоступным полем toast и с закрытым полем seasonal_fruit. Это расчитывает случай в ресторане, когда клиент может выбрать вид хлеба, который подаётся с едой, а шеф-повар решает какие фрукты сопровождают еду, исходя из того, что сезонно и что есть в наличии. Доступные фрукты быстро меняются, поэтому клиенты не могут выбирать фрукты или даже увидеть, какие фрукты они получат.

-

Файл: src/lib.rs

-
mod back_of_house {
-    pub struct Breakfast {
-        pub toast: String,
-        seasonal_fruit: String,
-    }
-
-    impl Breakfast {
-        pub fn summer(toast: &str) -> Breakfast {
-            Breakfast {
-                toast: String::from(toast),
-                seasonal_fruit: String::from("peaches"),
-            }
-        }
-    }
-}
-
-pub fn eat_at_restaurant() {
-    // Order a breakfast in the summer with Rye toast
-    let mut meal = back_of_house::Breakfast::summer("Rye");
-    // Change our mind about what bread we'd like
-    meal.toast = String::from("Wheat");
-    println!("I'd like {} toast please", meal.toast);
-
-    // The next line won't compile if we uncomment it; we're not allowed
-    // to see or modify the seasonal fruit that comes with the meal
-    // meal.seasonal_fruit = String::from("blueberries");
-}
-

Приложение 7-9: Устройства с общедоступными и закрытыми полями

-

Поскольку поле toast в устройстве back_of_house::Breakfast является открытым, то в функции eat_at_restaurant можно писать и читать поле toast, используя точечную наставление. Обратите внимание, что мы не можем использовать поле seasonal_fruit в eat_at_restaurant, потому что seasonal_fruit является закрытым. Попробуйте убрать примечания с последней строки для значения поля seasonal_fruit, чтобы увидеть какую ошибку вы получите!

-

Также обратите внимание, что поскольку back_of_house::Breakfast имеет закрытое поле, то устройства должна предоставить открытую сопряженную функцию, которая создаёт образец Breakfast (мы назвали её summer). Если Breakfast не имел бы такой функции, мы бы не могли создать образец Breakfast внутри eat_at_restaurant, потому что мы не смогли бы установить значение закрытого поля seasonal_fruit в функции eat_at_restaurant.

-

В отличии от устройства, если мы сделаем общедоступным перечисление, то все его исходы будут общедоступными. Нужно только указать pub перед ключевым словом enum, как показано в приложении 7-10.

-

Файл: src/lib.rs

-
mod back_of_house {
-    pub enum Appetizer {
-        Soup,
-        Salad,
-    }
-}
-
-pub fn eat_at_restaurant() {
-    let order1 = back_of_house::Appetizer::Soup;
-    let order2 = back_of_house::Appetizer::Salad;
-}
-

Приложение 7-10. Определяя перечисление общедоступным мы делаем все его исходы общедоступными

-

Поскольку мы сделали общедоступным перечисление Appetizer, то можно использовать исходы Soup и Salad в функции eat_at_restaurant.

-

Перечисления не очень полезны, если их исходы не являются общедоступными: было бы досадно каждый раз определять все исходы перечисления как pub. По этой причине по умолчанию исходы перечислений являются общедоступными. Устройства часто полезны, если их поля не являются общедоступными, поэтому поля устройства следуют общему правилу, согласно которому, всё по умолчанию является закрытым, если не указано pub.

-

Есть ещё одна случаей с pub, которую мы не освещали, и это последняя особенность состоящей из звеньев системы: ключевое слово use. Мы сначала опишем use само по себе, а затем покажем как сочетать pub и use вместе.

-

Подключение путей в область видимости с помощью ключевого слова use

-

Необходимость записывать пути к функциям вызова может показаться неудобной и повторяющейся. В приложении 7-7 независимо от того, выбирали ли мы абсолютный или относительный путь к функции add_to_waitlist , каждый раз, когда мы хотели вызвать add_to_waitlist , нам приходилось также указывать front_of_house и hosting . К счастью, есть способ упростить этот этап: мы можем один раз создать псевдоним на путь при помощи ключевого слова use, а затем использовать более короткое имя везде в области видимости.

-

В приложении 7-11 мы подключили звено crate::front_of_house::hosting в область действия функции eat_at_restaurant, поэтому нам достаточно только указать hosting::add_to_waitlist для вызова функции add_to_waitlist внутри eat_at_restaurant.

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub mod hosting {
-        pub fn add_to_waitlist() {}
-    }
-}
-
-use crate::front_of_house::hosting;
-
-pub fn eat_at_restaurant() {
-    hosting::add_to_waitlist();
-}
-

Приложение 7-11. Добавление звена в область видимости при помощи use

-

Добавление use и пути в область видимости подобно созданию символической ссылки в файловой системе. С добавлением use crate::front_of_house::hosting в корневой звено ящика, hosting становится допустимым именем в этой области, как если бы звено hosting был определён в корневом звене ящика. Пути, подключённые в область видимости с помощью use, также проверяются на доступность, как и любые другие пути.

-

Обратите внимание, что use создаёт псевдоним только для той именно области, в которой это объявление use и находится. В приложении 7-12 функция eat_at_restaurant перемещается в новый дочерний звено с именем customer, область действия которого отличается от области действия указания use, поэтому тело функции не будет собираться:

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub mod hosting {
-        pub fn add_to_waitlist() {}
-    }
-}
-
-use crate::front_of_house::hosting;
-
-mod customer {
-    pub fn eat_at_restaurant() {
-        hosting::add_to_waitlist();
-    }
-}
-

Приложение 7-12. Указание use применяется только в её собственной области видимости

-

Ошибка сборщика показывает, что данный псевдоним не может использоваться в звене customer:

-
$ cargo build
-   Compiling restaurant v0.1.0 (file:///projects/restaurant)
-error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
-  --> src/lib.rs:11:9
-   |
-11 |         hosting::add_to_waitlist();
-   |         ^^^^^^^ use of undeclared crate or module `hosting`
-   |
-help: consider importing this module through its public re-export
-   |
-10 +     use crate::hosting;
-   |
-
-warning: unused import: `crate::front_of_house::hosting`
- --> src/lib.rs:7:5
-  |
-7 | use crate::front_of_house::hosting;
-  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-  |
-  = note: `#[warn(unused_imports)]` on by default
-
-For more information about this error, try `rustc --explain E0433`.
-warning: `restaurant` (lib) generated 1 warning
-error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted
-
-

Обратите внимание, что есть также предупреждение о том, что use не используется в своей области! Чтобы решить эту неполадку, можно переместить use в звено customer, или же можно сослаться на псевдоним в родительском звене с помощью super::hosting в дочернем звене customer.

-

Создание идиоматических путей с use

-

В приложении 7-11 вы могли бы задаться вопросом, почему мы указали use crate::front_of_house::hosting, а затем вызвали hosting::add_to_waitlist внутри eat_at_restaurant вместо указания в use полного пути прямо до функции add_to_waitlist для получения того же итога, что в приложении 7-13.

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub mod hosting {
-        pub fn add_to_waitlist() {}
-    }
-}
-
-use crate::front_of_house::hosting::add_to_waitlist;
-
-pub fn eat_at_restaurant() {
-    add_to_waitlist();
-}
-

Приложение 7-13: Добавление функции add_to_waitlist в область видимости с use неидиоматическим способом

-

Хотя приложениеи 7-11 и 7-13 выполняют одну и ту же задачу, приложение 7-11 является идиоматическим способом подключения функции в область видимости с помощью use. Подключение родительского звена функции в область видимости при помощи use означает, что мы должны указывать родительский звено при вызове функции. Указание родительского звена при вызове функции даёт понять, что функция не определена местно, но в то же время сводя к уменьшению повторение полного пути. В коде приложения 7-13 не ясно, где именно определена add_to_waitlist.

-

С другой стороны, при подключении устройств, перечислений и других элементов используя use, идиоматически правильным будет указывать полный путь. Приложение 7-14 показывает идиоматический способ подключения устройства встроенной библиотеки HashMap в область видимости двоичного ящика.

-

Файл: src/main.rs

-
use std::collections::HashMap;
-
-fn main() {
-    let mut map = HashMap::new();
-    map.insert(1, 2);
-}
-

Приложение 7-14. Включение HashMap в область видимости идиоматическим способом

-

За этой идиомой нет веской причины: это просто соглашение, которое появилось само собой. Люди привыкли читать и писать код на Ржавчина таким образом.

-

Исключением из этой идиомы является случай, когда мы подключаем два элемента с одинаковыми именами в область видимости используя указанию use — Ржавчина просто не позволяет этого сделать. Приложение 7-15 показывает, как подключить в область действия два вида с одинаковыми именами Result, но из разных родительских звеньев и как на них ссылаться.

-

Файл: src/lib.rs

-
use std::fmt;
-use std::io;
-
-fn function1() -> fmt::Result {
-    // --snip--
-    Ok(())
-}
-
-fn function2() -> io::Result<()> {
-    // --snip--
-    Ok(())
-}
-

Приложение 7-15. Для включения двух видов с одинаковыми именами в одну область видимости необходимо использовать их родительские звенья.

-

Как видите, использование имени родительских звеньев позволяет различать два вида Result. Если бы вместо этого мы указали use std::fmt::Result и use std::io::Result, мы бы имели два вида Result в одной области видимости, и Ржавчина не смог бы понять какой из двух Result мы имели в виду, когда нашёл бы их употребление в коде.

-

Предоставление новых имён с помощью ключевого слова as

-

Есть другое решение сбоев добавления двух видов с одинаковыми именами в одну и ту же область видимости используя use: после пути можно указать as и новое местное имя (псевдоним) для вида. Приложение 7-16 показывает как по-другому написать код из приложения 7-15, путём переименования одного из двух видов Result используя as.

-

Файл: src/lib.rs

-
use std::fmt::Result;
-use std::io::Result as IoResult;
-
-fn function1() -> Result {
-    // --snip--
-    Ok(())
-}
-
-fn function2() -> IoResult<()> {
-    // --snip--
-    Ok(())
-}
-

Приложение 7-16: Переименование вида, когда он включён в область видимости с помощью ключевого слова as

-

Во второй указания use мы выбрали новое имя IoResult для вида std::io::Result, которое теперь не будет враждовать с видом Result из std::fmt, который также подключён в область видимости. Приложения 7-15 и 7-16 считаются идиоматичными, поэтому выбор за вами!

-

Реэкспорт имён с pub use

-

Когда мы подключаем имя в область видимости, используя ключевое слово use, то имя, доступное в новой области видимости, является закрытым. Чтобы позволить коду, который вызывает наш код, ссылаться на это имя, как если бы оно было определено в области видимости данного кода, можно объединить pub и use. Этот способ называется реэкспортом (re-exporting), потому что мы подключаем элемент в область видимости, но также делаем этот элемент доступным для подключения в других областях видимости.

-

Приложение 7-17 показывает код из приложения 7-11, где use в корневом звене заменено на pub use.

-

Файл: src/lib.rs

-
mod front_of_house {
-    pub mod hosting {
-        pub fn add_to_waitlist() {}
-    }
-}
-
-pub use crate::front_of_house::hosting;
-
-pub fn eat_at_restaurant() {
-    hosting::add_to_waitlist();
-}
-

Приложение 7-17. Предоставление имени для использования любым кодом из новой области при помощи pub use

-

До этого изменения внешний код должен был вызывать функцию add_to_waitlist , используя путь restaurant::front_of_house::hosting::add_to_waitlist() . Теперь, когда это объявление pub use повторно экспортировало звено hosting из корневого звена, внешний код теперь может использовать вместо него путь restaurant::hosting::add_to_waitlist() .

-

Реэкспорт полезен, когда внутренняя устройства вашего кода отличается от того, как программисты, вызывающие ваш код, думают о предметной области. Например, по подобию с рестораном люди, управляющие им, думают о «передней части дома» и «задней части дома». Но клиенты, посещающие ресторан, вероятно, не будут думать о частях ресторана в таких понятиях. Используя pub use , мы можем написать наш код с одной устройством, но сделать общедоступной другую устройство. Благодаря этому наша библиотека хорошо согласована для программистов, работающих над библиотекой, и для программистов, вызывающих библиотеку. Мы рассмотрим ещё один пример pub use и его влияние на документацию вашего ящика в разделе «Экспорт удобного общедоступного API с pub use» Главы 14.

-

Использование внешних дополнений

-

В Главе 2 мы запрограммировали игру угадывания числа, где использовался внешний дополнение с именем rand для создания случайного числа. Чтобы использовать rand в нашем деле, мы добавили эту строку в Cargo.toml:

- -

Файл: Cargo.toml

-
rand = "0.8.5"
-
-

Добавление rand в качестве зависимости в Cargo.toml указывает Cargo загрузить дополнение rand и все его зависимости из crates.io и сделать rand доступным для нашего дела.

-

Затем, чтобы подключить определения rand в область видимости нашего дополнения, мы добавили строку use начинающуюся с названия дополнения rand и списка элементов, которые мы хотим подключить в область видимости. Напомним, что в разделе "Создание случайного числа" Главы 2, мы подключили особенность Rng в область видимости и вызвали функцию rand::thread_rng:

-
use std::io;
-use rand::Rng;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {guess}");
-}
-

Члены сообщества Ржавчина сделали много дополнений доступными на ресурсе crates.io, и добавление любого из них в ваш дополнение включает в себя одни и те же шаги: добавить внешние дополнения в файл Cargo.toml вашего дополнения, использовать use для подключения элементов внешних дополнений в область видимости.

-

Обратите внимание, что обычная библиотека std также является ящиком, внешним по отношению к нашему дополнению. Поскольку обычная библиотека поставляется с языком Rust, нам не нужно изменять Cargo.toml для подключения std. Но нам нужно ссылаться на неё при помощи use, чтобы добавить элементы оттуда в область видимости нашего дополнения. Например, с HashMap мы использовали бы эту строку:

-
#![allow(unused)]
-fn main() {
-use std::collections::HashMap;
-}
-

Это абсолютный путь, начинающийся с std, имени ящика встроенной библиотеки.

-

Использование вложенных путей для уменьшения длинных списков use

-

Если мы используем несколько элементов, определённых в одном ящике или в том же звене, то перечисление каждого элемента в отдельной строке может занимать много вертикального пространства в файле. Например, эти две указания use используются в программе угадывания числа (приложение 2-4) для подключения элементов из std в область видимости:

-

Файл: src/main.rs

-
use rand::Rng;
-// --snip--
-use std::cmp::Ordering;
-use std::io;
-// --snip--
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    println!("You guessed: {guess}");
-
-    match guess.cmp(&secret_number) {
-        Ordering::Less => println!("Too small!"),
-        Ordering::Greater => println!("Too big!"),
-        Ordering::Equal => println!("You win!"),
-    }
-}
-

Вместо этого, мы можем использовать вложенные пути, чтобы добавить эти элементы в область видимости одной строкой. Мы делаем это, как показано в приложении 7-18, указывая общую часть пути, за которой следуют два двоеточия, а затем фигурные скобки вокруг списка тех частей продолжения пути, которые отличаются.

-

Файл: src/main.rs

-
use rand::Rng;
-// --snip--
-use std::{cmp::Ordering, io};
-// --snip--
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    println!("Please input your guess.");
-
-    let mut guess = String::new();
-
-    io::stdin()
-        .read_line(&mut guess)
-        .expect("Failed to read line");
-
-    let guess: u32 = guess.trim().parse().expect("Please type a number!");
-
-    println!("You guessed: {guess}");
-
-    match guess.cmp(&secret_number) {
-        Ordering::Less => println!("Too small!"),
-        Ordering::Greater => println!("Too big!"),
-        Ordering::Equal => println!("You win!"),
-    }
-}
-

Приложение 7-18. Указание вложенного пути для добавления нескольких элементов с одинаковым приставкой в область видимости

-

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

-

Можно использовать вложенный путь на любом уровне, что полезно при объединении двух указаний use, которые имеют общую часть пути. Например, в приложении 7-19 показаны две указания use: одна подключает std::io, а другая подключает std::io::Write в область видимости.

-

Файл: src/lib.rs

-
use std::io;
-use std::io::Write;
-

Приложение 7-19: Две указания use, в которых один путь является частью другого

-

Общей частью этих двух путей является std::io, и это полный первый путь. Чтобы объединить эти два пути в одной указания use, мы можем использовать ключевое слово self во вложенном пути, как показано в приложении 7-20.

-

Файл: src/lib.rs

-
use std::io::{self, Write};
-

Приложение 7-20: Объединение путей из Приложения 7-19 в одну указанию use

-

Эта строка подключает std::io и std::io::Write в область видимости.

-

Оператор * (glob)

-

Если мы хотим включить в область видимости все общедоступные элементы, определённые в пути, мы можем указать этот путь, за которым следует оператор *:

-
#![allow(unused)]
-fn main() {
-use std::collections::*;
-}
-

Эта указание use подключает все открытые элементы из звена std::collections в текущую область видимости. Будьте осторожны при использовании оператора *! Он может усложнить понимание, какие имена находятся в области видимости и где были определены имена, используемые в вашей программе.

-

Оператор * часто используется при проверке для подключения всего что есть в звене tests; мы поговорим об этом в разделе "Как писать проверки" Главы 11. Оператор * также иногда используется как часть образца самостоятельного подключения (prelude): смотрите документацию по встроенной библиотеке для получения дополнительной сведений об этом образце.

-

Разделение звеньев на разные файлы

-

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

-

Например, давайте начнём с кода из приложения 7-17, в котором было несколько звеньев ресторана. Мы будем извлекать звенья в файлы вместо того, чтобы определять все звенья в корневом звене ящика. В нашем случае корневой звено ящика - src/lib.rs, но это разделение также работает и с двоичными ящиками, у которых корневой звено ящика — src/main.rs.

-

Сначала мы извлечём звено front_of_house в свой собственный файл. Удалите код внутри фигурных скобок для звена front_of_house, оставив только объявление mod front_of_house;, так что теперь src/lib.rs содержит код, показанный в приложении 7-21. Обратите внимание, что этот исход не собирается, пока мы не создадим файл src/front_of_house.rs из приложении 7-22.

-

Файл: src/lib.rs

-
mod front_of_house;
-
-pub use crate::front_of_house::hosting;
-
-pub fn eat_at_restaurant() {
-    hosting::add_to_waitlist();
-}
-

Приложение 7-21. Объявление звена front_of_house, чьё содержимое будет в src/front_of_house.rs

-

Затем поместим код, который был в фигурных скобках, в новый файл с именем src/front_of_house.rs, как показано в приложении 7-22. Сборщик знает, что нужно искать в этом файле, потому что он наткнулся в корневом звене ящика на объявление звена с именем front_of_house.

-

Файл: src/front_of_house.rs

-
pub mod hosting {
-    pub fn add_to_waitlist() {}
-}
-

Приложение 7-22. Определение содержимого звена front_of_house в файле src/front_of_house.rs

-

Обратите внимание, что вам нужно только один раз загрузить файл с помощью объявления mod в вашем дереве звеньев. Как только сборщик узнает, что файл является частью дела (и узнает, где в дереве звеньев находится код из-за того, куда вы помеисполнения указанию mod), другие файлы в вашем деле должны ссылаться на код загруженного файла, используя путь к месту, где он был объявлен, как описано в разделе «Пути для ссылки на элемент в дереве звеньев». Другими словами, mod — это не действие «включения», которую вы могли видеть в других языках программирования.

-

Далее мы извлечём звено hosting в его собственный файл. Этап немного отличается, потому что hosting является дочерним звеном для front_of_house, а не корневого звена. Мы поместим файл для hosting в новый папка, который будет назван по имени его предка в дереве звеньев, в данном случае это src/front_of_house/.

-

Чтобы начать перенос hosting, мы меняем src/front_of_house.rs так, чтобы он содержал только объявление звена hosting:

-

Файл: src/front_of_house.rs

-
pub mod hosting;
-

Затем мы создаём папка src/front_of_house и файл hosting.rs, в котором будут определения, сделанные в звене hosting:

-

Файл: src/front_of_house/hosting.rs

-
pub fn add_to_waitlist() {}
-

Если вместо этого мы поместим hosting.rs в папка src, сборщик будет думать, что код в hosting.rs это звено hosting, объявленный в корне ящика, а не объявленный как дочерний звено front_of_house. Правила сборщика для проверки какие файлы содержат код каких звеньев предполагают, что папки и файлы точно соответствуют дереву звеньев.

-
-

Иные пути к файлам

-

До сих пор мы рассматривали наиболее идиоматические пути к файлам, используемые сборщиком Rust, но Ржавчина также поддерживает и старый исполнение пути к файлу. Для звена с именем front_of_house, объявленного в корневом звене ящика, сборщик будет искать код звена в:

-
    -
  • src/front_of_house.rs (что мы рассматривали)
  • -
  • src/front_of_house/mod.rs (старый исполнение, всё ещё поддерживаемый путь)
  • -
-

Для звена с именем hosting, который является подзвеном front_of_house, сборщик будет искать код звена в:

-
    -
  • src/front_of_house/hosting.rs (что мы рассматривали)
  • -
  • src/front_of_house/hosting/mod.rs (старый исполнение, всё ещё поддерживаемый путь)
  • -
-

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

-

Основным недостатком исполнения, в котором используются файлы с именами mod.rs, является то, что в вашем деле может оказаться много файлов с именами mod.rs, что может привести к путанице, если вы одновременно откроете их в редакторе.

-
-

Мы перенесли код каждого звена в отдельный файл, а дерево звеньев осталось прежним. Вызовы функций в eat_at_restaurant будут работать без каких-либо изменений, несмотря на то, что определения находятся в разных файлах. Этот способ позволяет перемещать звенья в новые файлы по мере увеличения их размеров.

-

Обратите внимание, что указание pub use crate::front_of_house::hosting в src/lib.rs также не изменилась, и use не влияет на то, какие файлы собираются как часть ящика. Ключевое слово mod объявляет звенья, и Ржавчина ищет в файле с тем же именем, что и у звена, код, который входит в этот звено.

-

Итог

-

Rust позволяет разбить дополнение на несколько ящиков и ящик - на звенья, так что вы можете ссылаться на элементы, определённые в одном звене, из другого звена. Это можно делать при помощи указания абсолютных или относительных путей. Эти пути можно добавить в область видимости указанием use, поэтому вы можете пользоваться более короткими путями для многократного использования элементов в этой области видимости. Код звена по умолчанию является закрытым, но можно сделать определения общедоступными, добавив ключевое слово pub.

-

В следующей главе мы рассмотрим некоторые собрания устройств данных из встроенной библиотеки, которые вы можете использовать в своём правильноно согласованном коде.

-

Общие собрания

-

Обычная библиотека содержит несколько полезных устройств данных, которые называются собраниями. Большая часть других видов данных представляют собой хранение определенного значения, но особенностью собраний является хранение множества однотипных значений. В отличии от массива или упорядоченного ряда данные собраний хранятся в куче, а это значит, что размер собрания может быть неизвестен в мгновение сборки программы. Он может изменяться (увеличиваться, уменьшаться) во время работы программы. Каждый вид собраний имеет свои возможности и отличается по производительности, так что выбор именно собрания зависит от случаи и является умением разработчика, вырабатываемым со временем. В этой главе будет рассмотрено несколько собраний:

-
    -
  • Вектор (vector) - позволяет нам сохранять различное количество последовательно хранящихся значений,
  • -
  • Строка (string) - это последовательность символов. Мы же упоминали вид String ранее, но в данной главе мы поговорим о нем подробнее.
  • -
  • Хеш-таблица (hash map) - собрание которая позволяет хранить перечень ассоциаций значения с ключом (перечень пар ключ:значение). Является именно выполнением более общей устройства данных называемой map.
  • -
-

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

-

Мы обсудим как создавать и обновлять векторы, строки и хеш-таблицы, а также объясним что делает каждую из них особенной.

-

Хранение списков значений в векторах

-

Первым видом собрания, который мы разберём, будет Vec<T>, также известный как вектор (vector). Векторы позволяют хранить более одного значения в единой устройстве данных, хранящей элементы в памяти один за другим. Векторы могут хранить данные только одного вида. Их удобно использовать, когда нужно хранить список элементов, например, список текстовых строк из файла, или список цен товаров в корзине покупок.

-

Создание нового вектора

-

Чтобы создать новый пустой вектор, мы вызываем функцию Vec::new, как показано в приложении 8-1.

-
fn main() {
-    let v: Vec<i32> = Vec::new();
-}
-

Приложение 8-1: Создание нового пустого вектора для хранения значений вида i32

-

Обратите внимание, что здесь мы добавили изложение вида. Поскольку мы не вставляем никаких значений в этот вектор, Ржавчина не знает, какие элементы мы собираемся хранить. Это важный мгновение. Векторы выполнены с использованием обобщённых видов; мы рассмотрим, как использовать обобщённые виды с вашими собственными видами в Главе 10. А пока знайте, что вид Vec<T>, предоставляемый встроенной библиотекой, может хранить любой вид. Когда мы создаём новый вектор для хранения определенного вида, мы можем указать этот вид в угловых скобках. В приложении 8-1 мы сообщили Rust, что Vec<T> в v будет хранить элементы вида i32.

-

Чаще всего вы будете создавать Vec<T> с начальными значениями и Ржавчина может определить вид сохраняемых вами значений, но иногда вам всё же придётся указывать изложение вида. Для удобства Ржавчина предоставляет макрос vec!, который создаст новый вектор, содержащий заданные вами значения. В приложении 8-2 создаётся новый Vec<i32>, который будет хранить значения 1, 2 и 3. Числовым видом является i32, потому что это вид по умолчанию для целочисленных значений, о чём упоминалось в разделе “Виды данных” Главы 3.

-
fn main() {
-    let v = vec![1, 2, 3];
-}
-

Приложение 8-2: Создание нового вектора, содержащего значения

-

Поскольку мы указали начальные значения вида i32, Ржавчина может сделать вывод, что вид переменной v это Vec<i32> и изложение вида здесь не нужна. Далее мы посмотрим как изменять вектор.

-

Изменение вектора

-

Чтобы создать вектор и затем добавить к нему элементы, можно использовать способ push показанный в приложении 8-3.

-
fn main() {
-    let mut v = Vec::new();
-
-    v.push(5);
-    v.push(6);
-    v.push(7);
-    v.push(8);
-}
-

Приложение 8-3: Использование способа push для добавления значений в вектор

-

Как и с любой переменной, если мы хотим изменить её значение, нам нужно сделать её изменяемой с помощью ключевого слова mut, что обсуждалось в Главе 3. Все числа которые мы помещаем в вектор имеют вид i32 по этому Ржавчина с лёгкостью выводит вид вектора, по этой причине нам не нужна здесь изложение вида вектора Vec<i32>.

-

Чтение данных вектора

-

Есть два способа сослаться на значение, хранящееся в векторе: с помощью порядкового указателя или способа get . В следующих примерах для большей ясности мы указали виды значений, возвращаемых этими функциями.

-

В приложении 8-4 показаны оба способа доступа к значению в векторе: либо с помощью правил написания упорядочевания и с помощью способа get.

-
fn main() {
-    let v = vec![1, 2, 3, 4, 5];
-
-    let third: &i32 = &v[2];
-    println!("The third element is {third}");
-
-    let third: Option<&i32> = v.get(2);
-    match third {
-        Some(third) => println!("The third element is {third}"),
-        None => println!("There is no third element."),
-    }
-}
-

Приложение 8-4. Использование правил написания упорядочевания и способа get для доступа к элементу в векторе

-

Обратите внимание здесь на пару подробностей. Мы используем значение порядкового указателя 2 для получения третьего элемента: векторы упорядочеваются начиная с нуля. Указывая & и [] мы получаем ссылку на элемент по указанному порядковому указателю. Когда мы используем способ get содержащего порядковый указатель, переданный в качестве переменной, мы получаем вид Option<&T>, который мы можем проверить с помощью match.

-

Причина, по которой Ржавчина предоставляет два способа ссылки на элемент, заключается в том, что вы можете выбрать, как программа будет себя вести, когда вы попытаетесь использовать значение порядкового указателя за пределами ряда существующих элементов. В качестве примера давайте посмотрим, что происходит, когда у нас есть вектор из пяти элементов, а затем мы пытаемся получить доступ к элементу с порядковым указателем 100 с помощью каждого способа, как показано в приложении 8-5.

-
fn main() {
-    let v = vec![1, 2, 3, 4, 5];
-
-    let does_not_exist = &v[100];
-    let does_not_exist = v.get(100);
-}
-

Приложение 8-5. Попытка доступа к элементу с порядковым указателем 100 в векторе, содержащем пять элементов

-

Когда мы запускаем этот код, первая строка с &v[100] вызовет панику программы, потому что происходит попытка получить ссылку на несуществующий элемент. Такой подход лучше всего использовать, когда вы хотите, чтобы ваша программа со сбоем завершила работу при попытке доступа к элементу за пределами вектора.

-

Когда способу get передаётся порядковый указатель, который находится за пределами вектора, он без паники возвращает None. Вы могли бы использовать такой подход, если доступ к элементу за пределами рядавектора происходит время от времени при обычных обстоятельствах. Тогда ваш код будет иметь логику для обработки наличия Some(&element) или None, как обсуждалось в Главе 6. Например, порядковый указательможет исходить от человека, вводящего число. Если пользователь случайно введёт слишком большое число, то программа получит значение None и у вас будет возможность сообщить пользователю, сколько элементов находится в текущем векторе, и дать ему возможность ввести допустимое значение. Такое поведение было бы более дружелюбным для пользователя, чем внезапный сбой программы из-за опечатки!

-

Когда у программы есть действительная ссылка, borrow checker (средство проверки заимствований), обеспечивает соблюдение правил владения и заимствования (описанные в Главе 4), чтобы обеспечить, что эта ссылка и любые другие ссылки на содержимое вектора остаются действительными. Вспомните правило, которое гласит, что у вас не может быть изменяемых и неизменяемых ссылок в одной и той же области. Это правило применяется в приложении 8-6, где мы храним неизменяемую ссылку на первый элемент вектора и затем пытаемся добавить элемент в конец вектора. Данная программа не будет работать, если мы также попробуем сослаться на данный элемент позже в функции:

-
fn main() {
-    let mut v = vec![1, 2, 3, 4, 5];
-
-    let first = &v[0];
-
-    v.push(6);
-
-    println!("The first element is: {first}");
-}
-

Приложение 8-6. Попытка добавить некоторый элемент в вектор, в то время когда есть ссылка на элемент вектора

-

Сборка этого кода приведёт к ошибке:

-
$ cargo run
-   Compiling collections v0.1.0 (file:///projects/collections)
-error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
- --> src/main.rs:6:5
-  |
-4 |     let first = &v[0];
-  |                  - immutable borrow occurs here
-5 |
-6 |     v.push(6);
-  |     ^^^^^^^^^ mutable borrow occurs here
-7 |
-8 |     println!("The first element is: {first}");
-  |                                     ------- immutable borrow later used here
-
-For more information about this error, try `rustc --explain E0502`.
-error: could not compile `collections` (bin "collections") due to 1 previous error
-
-

Код в приложении 8-6 может выглядеть так, как будто он должен работать. Почему ссылка на первый элемент должна заботиться об изменениях в конце вектора? Эта ошибка возникает из-за особенности того, как работают векторы: поскольку векторы размещают значения в памяти друг за другом, добавление нового элемента в конец вектора может потребовать выделения новой памяти и повторения старых элементов в новое пространство, если нет достаточного места, чтобы разместить все элементы друг за другом там, где в данный мгновение хранится вектор. В этом случае ссылка на первый элемент будет указывать на освобождённую память. Правила заимствования предотвращают попадание программ в такую случай.

-
-

Примечание: Дополнительные сведения о выполнения вида Vec<T> смотрите в разделе "The Rustonomicon".

-
-

Перебор значений в векторе

-

Для доступа к каждому элементу вектора по очереди, мы повторяем все элементы, вместо использования порядковых указателей для доступа к одному за раз. В приложении 8-7 показано, как использовать цикл for для получения неизменяемых ссылок на каждый элемент в векторе значений вида i32 и их вывода.

-
fn main() {
-    let v = vec![100, 32, 57];
-    for i in &v {
-        println!("{i}");
-    }
-}
-

Приложение 8-7. Печать каждого элемента векторе, при помощи повторения по элементам вектора с помощью цикла for

-

Мы также можем повторять изменяемые ссылки на каждый элемент изменяемого вектора, чтобы вносить изменения во все элементы. Цикл for в приложении 8-8 добавит 50 к каждому элементу.

-
fn main() {
-    let mut v = vec![100, 32, 57];
-    for i in &mut v {
-        *i += 50;
-    }
-}
-

Приложение 8-8. Повторение и изменение элементов вектора по изменяемым ссылкам

-

Чтобы изменить значение на которое ссылается изменяемая ссылка, мы должны использовать оператор разыменования ссылки * для получения значения по ссылке в переменной i прежде чем использовать оператор +=. Мы поговорим подробнее об операторе разыменования в разделе “Следование по указателю к значению с помощью оператора разыменования” Главы 15.

-

Перебор вектора, будь то неизменяемый или изменяемый, безопасен из-за правил проверки заимствования. Если бы мы попытались вставить или удалить элементы в телах цикла for в приложениях 8-7 и 8-8, мы бы получили ошибку сборщика, подобную той, которую мы получили с кодом в приложении 8-6. Ссылка на вектор, содержащийся в цикле for, предотвращает одновременную изменение всего вектора.

-

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

-

Векторы могут хранить значения только одинакового вида. Это может быть неудобно; определённо могут быть случаи когда надо хранить список элементов разных видов. К счастью, исходы перечисления определены для одного и того же вида перечисления, поэтому, когда нам нужен один вид для представления элементов разных видов, мы можем определить и использовать перечисление!

-

Например, мы хотим получить значения из строки в электронной таблице где некоторые столбцы строки содержат целые числа, некоторые числа с плавающей точкой, а другие - строковые значения. Можно определить перечисление, исходы которого будут содержать разные виды значений и тогда все исходы перечисления будут считаться одним и тем же видом: видом самого перечисления. Затем мы можем создать вектор для хранения этого перечисления и, в конечном счёте, для хранения различных видов. Мы покажем это в приложении 8-9.

-
fn main() {
-    enum SpreadsheetCell {
-        Int(i32),
-        Float(f64),
-        Text(String),
-    }
-
-    let row = vec![
-        SpreadsheetCell::Int(3),
-        SpreadsheetCell::Text(String::from("blue")),
-        SpreadsheetCell::Float(10.12),
-    ];
-}
-

Приложение 8-9: Определение enum для хранения значений разных видов в одном векторе

-

Rust должен знать, какие виды будут в векторе во время сборки, чтобы точно знать сколько памяти в куче потребуется для хранения каждого элемента. Мы также должны чётко указать, какие виды разрешены в этом векторе. Если бы Ржавчина позволял вектору содержать любой вид, то был бы шанс что один или несколько видов вызовут ошибки при выполнении действий над элементами вектора. Использование перечисления вместе с выражением match означает, что во время сборки Ржавчина заверяет, что все возможные случаи будут обработаны, как обсуждалось в главе 6.

-

Если вы не знаете исчерпывающий набор видов, которые программа получит во время выполнения для хранения в векторе, то техника использования перечисления не сработает. Вместо этого вы можете использовать особенность-предмет, который мы рассмотрим в главе 17.

-

Теперь, когда мы обсудили некоторые из наиболее распространённых способов использования векторов, обязательно ознакомьтесь с документацией по API вектора, чтобы узнать о множестве полезных способов, определённых в Vec<T> встроенной библиотеки. Например, в дополнение к способу push, существует способ pop, который удаляет и возвращает последний элемент.

-

Удаление элементов из вектора

-

Подобно устройствам struct, вектор высвобождает свою память когда выходит из области видимости, что показано в приложении 8-10.

-
fn main() {
-    {
-        let v = vec![1, 2, 3, 4];
-
-        // do stuff with v
-    } // <- v goes out of scope and is freed here
-}
-

Приложение 8-10. Показано как удаляется вектор и его элементы

-

Когда вектор удаляется, всё его содержимое также удаляется: удаление вектора означает и удаление значений, которые он содержит. Средство проверки заимствования заверяет, что любые ссылки на содержимое вектора используются только тогда, когда сам вектор действителен.

-

Давайте перейдём к следующему виду собрания: String!

-

Хранение закодированного текста UTF-8 в строках

-

Мы говорили о строках в главе 4, но сейчас мы рассмотрим их более подробно. Новички в Ржавчина обычно застревают на строках из-за сочетания трёх причин: склонность Ржавчина сборщика к выявлению возможных ошибок, более сложная устройства данных чем считают многие программисты и UTF-8. Эти обстоятельства объединяются таким образом, что направление может показаться сложной, если вы пришли из других языков программирования.

-

Полезно обсуждать строки в среде собраний, потому что строки выполнены в виде набора байтов, плюс некоторые способы для обеспечения полезной возможности, когда эти байты преобразуются как текст. В этом разделе мы поговорим об действиех над String таких как создание, обновление и чтение, которые есть у каждого вида собраний. Мы также обсудим какими особенностями String отличается от других собраний, а именно каким образом упорядочевание в String осложняется различием между тем как люди и компьютеры преобразуют данные заключённые в String.

-

Что же такое строка?

-

Сначала мы определим, что мы подразумеваем под понятием строка (string). В Ржавчина есть только один строковый вид в ядре языка - срез строки str, обычно используемый в заимствованном виде как &str. В Главе 4 мы говорили о срезах строк, string slices, которые являются ссылками на некоторые строковые данные в кодировке UTF-8. Например, строковые записи хранятся в двоичном файле программы и поэтому являются срезами строк.

-

Вид String предоставляемый встроенной библиотекой Rust, не встроен в ядро языка и является расширяемым, изменяемым, владеющим, строковым видом в UTF-8 кодировке. Когда Rustaceans говорят о "строках" то, они обычно имеют в виду виды String или строковые срезы &str, а не просто один из них. Хотя этот раздел в основном посвящён String, оба вида усиленно используются в встроенной библиотеке Rust, оба, и String и строковые срезы, кодируются в UTF-8.

-

Создание новых строк

-

Многие из тех же действий, которые доступны Vec<T> , доступны также в String, потому что String в действительности выполнен как обёртка вокруг вектора байтов с некоторыми дополнительными заверениями, ограничениями и возможностями. Примером функции, которая одинаково работает с Vec<T> и String, является функция new, создающая новый образец вида, и показана в Приложении 8-11.

-
fn main() {
-    let mut s = String::new();
-}
-

Приложение 8-11. Создание новой пустой String строки

-

Эта строка создаёт новую пустую строковую переменную с именем s, в которую мы можем затем загрузить данные. Часто у нас есть некоторые начальные данные, которые мы хотим назначить строке. Для этого мы используем способ to_string доступный для любого вида, который выполняет особенность Display, как у строковых записей. Приложение 8-12 показывает два примера.

-
fn main() {
-    let data = "initial contents";
-
-    let s = data.to_string();
-
-    // the method also works on a literal directly:
-    let s = "initial contents".to_string();
-}
-

Приложение 8-12: Использование способа to_string для создания образца вида String из строкового записи

-

Эти выражения создают строку с initial contents.

-

Мы также можем использовать функцию String::from для создания String из строкового записи. Код приложения 8-13 является эквивалентным коду из приложения 8-12, который использует функцию to_string:

-
fn main() {
-    let s = String::from("initial contents");
-}
-

Приложение 8-13: Использование функции String::from для создания образца вида String из строкового записи

-

Поскольку строки используются для очень многих вещей, можно использовать множество API для строк, предоставляющих множество возможностей. Некоторые из них могут показаться избыточными, но все они занимаются своим делом! В данном случае String::from и to_string делают одно и тоже, поэтому выбор зависит от исполнения который вам больше импонирует.

-

Запомните, что строки хранятся в кодировке UTF-8, поэтому можно использовать любые правильно кодированные данные в них, как показано в приложении 8-14:

-
fn main() {
-    let hello = String::from("السلام عليكم");
-    let hello = String::from("Dobrý den");
-    let hello = String::from("Hello");
-    let hello = String::from("שלום");
-    let hello = String::from("नमस्ते");
-    let hello = String::from("こんにちは");
-    let hello = String::from("안녕하세요");
-    let hello = String::from("你好");
-    let hello = String::from("Olá");
-    let hello = String::from("Здравствуйте");
-    let hello = String::from("Hola");
-}
-

Приложение 8-14: Хранение приветствий в строках на разных языках

-

Все это допустимые String значения.

-

Обновление строковых данных

-

Строка String может увеличиваться в размере, а её содержимое может меняться, по подобию как содержимое Vec<T> при вставке в него большего количества данных. Кроме того, можно использовать оператор + или макрос format! для объединения значений String.

-

Присоединение к строке с помощью push_str и push

-

Мы можем нарастить String используя способ push_str который добавит в исходное значение новый строковый срез, как показано в приложении 8-15.

-
fn main() {
-    let mut s = String::from("foo");
-    s.push_str("bar");
-}
-

Приложение 8-15. Добавление среза строки к String с помощью способа push_str

-

После этих двух строк кода s будет содержать foobar. Способ push_str принимает строковый срез, потому что мы не всегда хотим владеть входным свойствоом. Например, код в приложении 8-16 показывает исход, когда будет не желательно поведение, при котором мы не сможем использовать s2 после его добавления к содержимому значения переменной s1.

-
fn main() {
-    let mut s1 = String::from("foo");
-    let s2 = "bar";
-    s1.push_str(s2);
-    println!("s2 is {s2}");
-}
-

Приложение 8-16: Использование среза строки после добавления её содержимого к другой String

-

Если способ push_str стал бы владельцем переменнойs2, мы не смогли бы напечатать его значение в последней строке. Однако этот код работает так, как мы ожидали!

-

Способ push принимает один символ в качестве свойства и добавляет его к String. В приложении 8-17 показан код, добавляющий букву “l” к String используя способ push.

-
fn main() {
-    let mut s = String::from("lo");
-    s.push('l');
-}
-

Приложение 8-17: Добавление одного символа в String значение используя push

-

В итоге s будет содержать lol.

-

Объединение строк с помощью оператора + или макроса format!

-

Часто хочется объединять две существующие строки. Один из возможных способов — это использование оператора + из приложения 8-18:

-
fn main() {
-    let s1 = String::from("Hello, ");
-    let s2 = String::from("world!");
-    let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
-}
-

Приложение 8-18: Использование оператора + для объединения двух значений String в новое String значение

-

Строка s3 будет содержать Hello, world!. Причина того, что s1 после добавления больше недействительна и причина, по которой мы использовали ссылку на s2 имеют отношение к ярлыке вызываемого способа при использовании оператора +. Оператор + использует способ add, чья ярлык выглядит примерно так:

-
fn add(self, s: &str) -> String {
-

В встроенной библиотеке вы увидите способ add определённым с использованием обобщённых и связанных видов. Здесь мы видим ярлык с определенными видами, заменяющими обобщённый, что происходит когда вызывается данный способ со значениями String. Мы обсудим обобщённые виды в Главе 10. Эта ярлык даёт нам ключ для понимания особенностей оператора +.

-

Во-первых, перед s2 мы видим &, что означает что мы складываем ссылку на вторую строку с первой строкой. Это происходит из-за свойства s в функции add: мы можем добавить только &str к String; мы не можем сложить два значения String. Но подождите — вид &s2 это &String, а не &str, как определён второй свойство в add. Так почему код в приложении 8-18 собирается?

-

Причина, по которой мы можем использовать &s2 в вызове add заключается в том, что сборщик может принудительно привести (coerce) переменная вида &String к виду &str. Когда мы вызываем способ add в Ржавчина используется принудительное приведение (deref coercion), которое превращает &s2 в &s2[..]. Мы подробно обсудим принудительное приведение в Главе 15. Так как add не забирает во владение свойство s, s2 по прежнему будет действительной строкой String после применения действия.

-

Во-вторых, как можно видеть в ярлыке, add забирает во владение self, потому что self не имеет &. Это означает, что s1 в приложении 8-18 будет перемещён в вызов add и больше не будет действителен после этого вызова. Не смотря на то, что код let s3 = s1 + &s2; выглядит как будто он воспроизведет обе строки и создаёт новую, эта указание в действительности забирает во владение переменную s1, присоединяет к ней повтор содержимого s2, а затем возвращает владение итогом. Другими словами, это выглядит как будто код создаёт множество повторов, но это не так; данная выполнение более эффективна, чем повторение.

-

Если нужно объединить несколько строк, поведение оператора + становится громоздким:

-
fn main() {
-    let s1 = String::from("tic");
-    let s2 = String::from("tac");
-    let s3 = String::from("toe");
-
-    let s = s1 + "-" + &s2 + "-" + &s3;
-}
-

Здесь переменная s будет содержать tic-tac-toe. С множеством символов + и " становится трудно понять, что происходит. Для более сложного соединения строк можно использовать макрос format!:

-
fn main() {
-    let s1 = String::from("tic");
-    let s2 = String::from("tac");
-    let s3 = String::from("toe");
-
-    let s = format!("{s1}-{s2}-{s3}");
-}
-

Этот код также устанавливает переменную s в значение tic-tac-toe. Макрос format! работает тем же способом что макрос println!, но вместо вывода на экран возвращает вид String с содержимым. Исполнение кода с использованием format! значительно легче читается, а также код, созданный макросом format!, использует ссылки, а значит не забирает во владение ни один из его свойств.

-

Упорядочевание в строках

-

Доступ к отдельным символам в строке, при помощи ссылки на них по порядковому указателю, является допустимой и распространённой действием во многих других языках программирования. Тем не менее, если вы попытаетесь получить доступ к частям String, используя правила написания упорядочевания в Rust, то вы получите ошибку. Рассмотрим неверный код в приложении 8-19.

-
fn main() {
-    let s1 = String::from("hello");
-    let h = s1[0];
-}
-

Приложение 8-19: Попытка использовать правила написания порядкового указателя со строкой

-

Этот код приведёт к следующей ошибке:

-
$ cargo run
-   Compiling collections v0.1.0 (file:///projects/collections)
-error[E0277]: the type `str` cannot be indexed by `{integer}`
- --> src/main.rs:3:16
-  |
-3 |     let h = s1[0];
-  |                ^ string indices are ranges of `usize`
-  |
-  = help: the trait `SliceIndex<str>` is not implemented for `{integer}`, which is required by `String: Index<_>`
-  = note: you can use `.chars().nth()` or `.bytes().nth()`
-          for more information, see chapter 8 in The Book: <https://doc.rust-lang.org/book/ch08-02-strings.html#indexing-into-strings>
-  = help: the trait `SliceIndex<[_]>` is implemented for `usize`
-  = help: for that trait implementation, expected `[_]`, found `str`
-  = note: required for `String` to implement `Index<{integer}>`
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `collections` (bin "collections") due to 1 previous error
-
-

Ошибка и примечание говорит, что в Ржавчина строки не поддерживают упорядочевание. Но почему так? Чтобы ответить на этот вопрос, нужно обсудить то, как Ржавчина хранит строки в памяти.

-

Внутреннее представление

-

Вид String является оболочкой над видом Vec<u8>. Давайте посмотрим на несколько закодированных правильным образом в UTF-8 строк из примера приложения 8-14. Начнём с этой:

-
fn main() {
-    let hello = String::from("السلام عليكم");
-    let hello = String::from("Dobrý den");
-    let hello = String::from("Hello");
-    let hello = String::from("שלום");
-    let hello = String::from("नमस्ते");
-    let hello = String::from("こんにちは");
-    let hello = String::from("안녕하세요");
-    let hello = String::from("你好");
-    let hello = String::from("Olá");
-    let hello = String::from("Здравствуйте");
-    let hello = String::from("Hola");
-}
-

В этом случае len будет 4, что означает вектор, хранит строку "Hola" длиной 4 байта. Каждая из этих букв занимает 1 байт при кодировании в UTF-8. Но как насчёт следующей строки? (Обратите внимание, что эта строка начинается с заглавной кириллической "З", а не цифры 3.)

-
fn main() {
-    let hello = String::from("السلام عليكم");
-    let hello = String::from("Dobrý den");
-    let hello = String::from("Hello");
-    let hello = String::from("שלום");
-    let hello = String::from("नमस्ते");
-    let hello = String::from("こんにちは");
-    let hello = String::from("안녕하세요");
-    let hello = String::from("你好");
-    let hello = String::from("Olá");
-    let hello = String::from("Здравствуйте");
-    let hello = String::from("Hola");
-}
-

Отвечая на вопрос, какова длина строки, вы можете ответить 12. Однако ответ Ржавчина - 24, что равно числу байт, необходимых для кодирования «Здравствуйте» в UTF-8, так происходит, потому что каждое одиночное значение Unicode символа в этой строке занимает 2 байта памяти. Следовательно, порядковый указательпо байтам строки не всегда бы соответствовал действительному одиночному Unicode значению. Для отображения рассмотрим этот недопустимый код Rust:

-
let hello = "Здравствуйте";
-let answer = &hello[0];
-

Каким должно быть значение переменной answer? Должно ли оно быть значением первой буквы З? При кодировке в UTF-8, первый байт значения З равен 208, а второй - 151, поэтому значение в answer на самом деле должно быть 208, но само по себе 208 не является действительным символом. Возвращение 208, скорее всего не то, что хотел бы получить пользователь: ведь он ожидает первую букву этой строки; тем не менее, это единственный байт данных, который в Ржавчина доступен по порядковому указателю 0. Пользователи обычно не хотят получить значение байта, даже если строка содержит только латинские буквы: если &"hello"[0] было бы допустимым кодом, который вернул значение байта, то он вернул бы 104, а не h.

-

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

-

Байты, одиночные значения и кластеры графем! Боже мой!

-

Ещё один мгновение, касающийся UTF-8, заключается в том, что на самом деле существует три способа рассмотрения строк с точки зрения Rust: как байты, как одиночные значения и как кластеры графем (самая близкая вещь к тому, что мы назвали бы буквами).

-

Если посмотреть на слово языка хинди «नमस्ते», написанное в транскрипции Devanagari, то оно хранится как вектор значений u8 который выглядит следующим образом:

-
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
-224, 165, 135]
-
-

Эти 18 байт являются именно тем, как компьютеры в конечном итоге сохранят в памяти эту строку. Если мы посмотрим на 18 байт как на одиночные Unicode значения, которые являются Ржавчина видом char, то байты будут выглядеть так:

-
['न', 'म', 'स', '्', 'त', 'े']
-
-

Здесь есть шесть значений вида char, но четвёртый и шестой являются не буквами: они диакритики, особые обозначения которые не имеют смысла сами по себе. Наконец, если мы посмотрим на байты как на кластеры графем, то получим то, что человек назвал бы словом на хинди состоящем из четырёх букв:

-
["न", "म", "स्", "ते"]
-
-

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

-

Последняя причина, по которой Ржавчина не позволяет нам упорядочивать String для получения символов является то, что программисты ожидают, что действия упорядочевания всегда имеют постоянное время (O(1)) выполнения. Но невозможно обеспечить такую производительность для String, потому что Ржавчина понадобилось бы пройтись по содержимому от начала до порядкового указателя, чтобы определить, сколько было действительных символов.

-

Срезы строк

-

Упорядочевание строк часто является плохой мыслью, потому что не ясно каким должен быть возвращаемый вид такой действия: байтовым значением, символом, кластером графем или срезом строки. Поэтому Ржавчина просит вас быть более определенным, если действительно требуется использовать порядковые указатели для создания срезов строк.

-

Вместо упорядочевания с помощью числового порядкового указателя [], вы можете использовать оператор ряда[] при создании среза строки в котором содержится указание на то, срез каких байтов надо делать:

-
#![allow(unused)]
-fn main() {
-let hello = "Здравствуйте";
-
-let s = &hello[0..4];
-}
-

Здесь переменная s будет вида &str который содержит первые 4 байта строки. Ранее мы упоминали, что каждый из этих символов был по 2 байта, что означает, что s будет содержать "Зд".

-

Что бы произошло, если бы мы использовали &hello[0..1]? Ответ: Ржавчина бы запаниковал во время выполнения точно так же, как если бы обращались к недействительному порядковому указателю в векторе:

-
$ cargo run
-   Compiling collections v0.1.0 (file:///projects/collections)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s
-     Running `target/debug/collections`
-thread 'main' panicked at src/main.rs:4:19:
-byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

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

-

Способы для перебора строк

-

Лучший способ работать с отрывками строк — чётко указать, нужны ли вам символы или байты. Для отдельных одиночных значений в Юникоде используйте способ chars. Вызов chars у "Зд" выделяет и возвращает два значения вида char, и вы можете выполнить повторение по итогу для доступа к каждому элементу:

-
#![allow(unused)]
-fn main() {
-for c in "Зд".chars() {
-    println!("{c}");
-}
-}
-

Код напечатает следующее:

-
З
-д
-
-

Способ bytes возвращает каждый байт, который может быть подходящим в другой предметной области:

-
#![allow(unused)]
-fn main() {
-for b in "Зд".bytes() {
-    println!("{b}");
-}
-}
-

Этот код выведет четыре байта, составляющих эту строку:

-
208
-151
-208
-180
-
-

Но делая так, обязательно помните, что валидные одиночные Unicode значения могут состоять более чем из одного байта.

-

Извлечение кластеров графем из строк, как в случае с языком хинди, является сложным, поэтому эта возможность не предусмотрена встроенной библиотекой. На crates.io есть доступные библиотеки, если Вам нужен данный возможности.

-

Строки не так просты

-

Подводя итог, становится ясно, что строки сложны. Различные языки программирования выполняют различные исходы того, как представить эту сложность для программиста. В Ржавчина решили сделать правильную обработку данных String поведением по умолчанию для всех программ Rust, что означает, что программисты должны заранее продумать обработку UTF-8 данных. Этот соглашение раскрывает большую сложность строк, чем в других языках программирования, но это предотвращает от необходимости обрабатывать ошибки, связанные с не-ASCII символами которые могут появиться в ходе разработки позже.

-

Хорошая новость состоит в том что обычная библиотека предлагает множество полезных возможностей, построенных на основе видов String и &str, чтобы помочь правильно обрабатывать эти сложные случаи. Обязательно ознакомьтесь с документацией для полезных способов, таких как contains для поиска в строке и replace для замены частей строки другой строкой.

-

Давайте переключимся на что-то немного менее сложное: HashMap!

-

Хранение ключей со связанными значениями в HashMap

-

Последняя собрание, которую мы рассмотрим, будет hash map (хеш-карта). Вид HashMap<K, V> хранит ключи вида K на значения вида V. Данная устройства согласует и хранит данные с помощью функции хеширования. Во множестве языков программирования выполнена данная устройства, но часто с разными наименованиями: такими как hash, map, object, hash table, dictionary или ассоциативный массив.

-

Хеш-карты полезны, когда нужно искать данные не используя порядковый указатель, как это например делается в векторах, а с помощью ключа, который может быть любого вида. Например, в игре вы можете отслеживать счёт каждой приказы в хеш-карте, в которой каждый ключ - это название приказы, а значение - счёт приказы. Имея имя приказы, вы можете получить её счёт из хеш-карты.

-

В этом разделе мы рассмотрим основной API хеш-карт. Остальной набор полезных функций скрывается в объявлении вида HashMap<K, V>. Как и прежде, советуем обратиться к документации по встроенной библиотеке для получения дополнительной сведений.

-

Создание новой хеш-карты

-

Создать пустую хеш-карту можно с помощью new, а добавить в неё элементы - с помощью insert. В приложении 8-20 мы отслеживаем счёт двух приказов, синей Blue и жёлтой Yellow. Синяя приказ набрала 10 очков, а жёлтая приказ - 50.

-
fn main() {
-    use std::collections::HashMap;
-
-    let mut scores = HashMap::new();
-
-    scores.insert(String::from("Blue"), 10);
-    scores.insert(String::from("Yellow"), 50);
-}
-

Приложение 8-20: Создание новой хеш-карты и вставка в неё пары ключей и значений

-

Обратите внимание, что нужно сначала указать строку use std::collections::HashMap; для её подключения из собраний встроенной библиотеки. Из трёх собраний данная является наименее используемой, поэтому она не подключается в область видимости функцией самостоятельного подключения (prelude). Хеш-карты также имеют меньшую поддержку со стороны встроенной библиотеки; например, нет встроенного макроса для их разработки.

-

Подобно векторам, хеш-карты хранят свои данные в куче. Здесь вид HashMap имеет в качестве вида ключей String, а в качестве вида значений вид i32. Как и векторы, HashMap однородны: все ключи должны иметь одинаковый вид и все значения должны иметь тоже одинаковый вид.

-

Доступ к данным в HashMap

-

Мы можем получить значение из HashMap по ключу, с помощью способа get, как показано в приложении 8-21.

-
fn main() {
-    use std::collections::HashMap;
-
-    let mut scores = HashMap::new();
-
-    scores.insert(String::from("Blue"), 10);
-    scores.insert(String::from("Yellow"), 50);
-
-    let team_name = String::from("Blue");
-    let score = scores.get(&team_name).copied().unwrap_or(0);
-}
-

Приложение 8-21: Доступ к очкам приказы "Blue", которые хранятся в хеш-карте

-

Здесь score будет иметь количество очков, связанное с приказом "Blue", итог будет 10. Способ get возвращает Option<&V>; если для какого-то ключа нет значения в HashMap, get вернёт None. Из-за такого подхода программе следует обрабатывать Option, вызывая copied для получения Option<i32> вместо Option<&i32>, затем unwrap_or для установки score в ноль, если scores не содержит данных по этому ключу.

-

Мы можем перебирать каждую пару ключ/значение в HashMap таким же образом, как мы делали с векторами, используя цикл for:

-
fn main() {
-    use std::collections::HashMap;
-
-    let mut scores = HashMap::new();
-
-    scores.insert(String::from("Blue"), 10);
-    scores.insert(String::from("Yellow"), 50);
-
-    for (key, value) in &scores {
-        println!("{key}: {value}");
-    }
-}
-

Этот код будет печатать каждую пару в произвольном порядке:

-
Yellow: 50
-Blue: 10
-
-

Хеш-карты и владение

-

Для видов, которые выполняют особенность Copy, например i32, значения повторяются в HashMap. Для значений со владением, таких как String, значения будут перемещены в хеш-карту и она станет владельцем этих значений, как показано в приложении 8-22.

-
fn main() {
-    use std::collections::HashMap;
-
-    let field_name = String::from("Favorite color");
-    let field_value = String::from("Blue");
-
-    let mut map = HashMap::new();
-    map.insert(field_name, field_value);
-    // field_name and field_value are invalid at this point, try using them and
-    // see what compiler error you get!
-}
-

Приложение 8-22: Показывает, что ключи и значения находятся во владении HashMap, как только они были вставлены

-

Мы не можем использовать переменные field_name и field_value после того, как их значения были перемещены в HashMap вызовом способа insert.

-

Если мы вставим в HashMap ссылки на значения, то они не будут перемещены в HashMap. Значения, на которые указывают ссылки, должны быть действительными хотя бы до тех пор, пока хеш-карта действительна. Мы поговорим подробнее об этих вопросах в разделе "Валидация ссылок при помощи времён жизни" главы 10.

-

Обновление данных в HashMap

-

Хотя количество ключей и значений может увеличиваться в HashMap, каждый ключ может иметь только одно значение, связанное с ним в один мгновение времени (обратное утверждение неверно: приказы "Blue" и "Yellow" могут хранить в хеш-карте scores одинаковое количество очков, например 10).

-

Когда вы хотите изменить данные в хеш-карте, необходимо решить, как обрабатывать случай, когда ключ уже имеет назначенное значение. Можно заменить старое значение новым, полностью пренебрегая старое. Можно сохранить старое значение и пренебрегать новое, или добавлять новое значение, если только ключ ещё не имел значения. Или можно было бы объединить старое значение и новое значение. Давайте посмотрим, как сделать каждый из исходов!

-

Перезапись старых значений

-

Если мы вставим ключ и значение в HashMap, а затем вставим тот же ключ с новым значением, то старое значение связанное с этим ключом, будет заменено на новое. Даже несмотря на то, что код в приложении 8-23 вызывает insert дважды, хеш-карта будет содержать только одну пару ключ/значение, потому что мы вставляем значения для одного и того же ключа - ключа приказы "Blue".

-
fn main() {
-    use std::collections::HashMap;
-
-    let mut scores = HashMap::new();
-
-    scores.insert(String::from("Blue"), 10);
-    scores.insert(String::from("Blue"), 25);
-
-    println!("{scores:?}");
-}
-

Приложение 8-23: Замена значения, хранимого в определенном ключе

-

Код напечатает {"Blue": 25}. Начальное значение 10 было перезаписано.

- -

-

Вставка значения только в том случае, когда ключ не имеет значения

-

Обычно проверяют, существует ли определенный ключ в хеш-карте со значением, а затем предпринимаются следующие действия: если ключ существует в хеш-карте, существующее значение должно оставаться таким, какое оно есть. Если ключ не существует, то вставляют его и значение для него.

-

Хеш-карты имеют для этого особый API, называемый entry , который принимает ключ для проверки в качестве входного свойства. Возвращаемое значение способа entry - это перечисление Entry, с двумя исходами: первый представляет значение, которое может существовать, а второй говорит о том, что значение отсутствует. Допустим, мы хотим проверить, имеется ли ключ и связанное с ним значение для приказы "Yellow". Если хеш-карта не имеет значения для такого ключа, то мы хотим вставить значение 50. То же самое мы хотим проделать и для приказы "Blue". Используем API entry в коде приложения 8-24.

-
fn main() {
-    use std::collections::HashMap;
-
-    let mut scores = HashMap::new();
-    scores.insert(String::from("Blue"), 10);
-
-    scores.entry(String::from("Yellow")).or_insert(50);
-    scores.entry(String::from("Blue")).or_insert(50);
-
-    println!("{scores:?}");
-}
-

Приложение 8-24: Использование способа entry для вставки значения только в том случае, когда ключ не имеет значения

-

Способ or_insert определён в Entry так, чтобы возвращать изменяемую ссылку на соответствующее значение ключа внутри исхода перечисления Entry, когда этот ключ существует, а если его нет, то вставлять свойство в качестве нового значения этого ключа и возвращать изменяемую ссылку на новое значение. Эта техника намного чище, чем самостоятельное написание логики и, кроме того, она более безопасна и согласуется с правилами заимствования.

-

При выполнении кода приложения 8-24 будет напечатано {"Yellow": 50, "Blue": 10}. Первый вызов способа entry вставит ключ для приказы "Yellow" со значением 50, потому что для жёлтой приказы ещё не имеется значения в HashMap. Второй вызов entry не изменит хеш-карту, потому что для ключа приказы "Blue" уже имеется значение 10.

-

Создание нового значения на основе старого значения

-

Другим распространённым исходом использования хеш-карт является поиск значения по ключу, а затем обновление этого значения на основе старого значения. Например, в приложении 8-25 показан код, который подсчитывает, сколько раз определённое слово встречается в некотором тексте. Мы используем HashMap со словами в качестве ключей и увеличиваем соответствующее слову значение, чтобы отслеживать, сколько раз мы встретили это слово. Если мы впервые встретили слово, то сначала вставляем значение 0.

-
fn main() {
-    use std::collections::HashMap;
-
-    let text = "hello world wonderful world";
-
-    let mut map = HashMap::new();
-
-    for word in text.split_whitespace() {
-        let count = map.entry(word).or_insert(0);
-        *count += 1;
-    }
-
-    println!("{map:?}");
-}
-

Приложение 8-25: Подсчёт количества вхождений слов с использованием хеш-карты, которая хранит слова и счётчики

-

Этот код напечатает {"world": 2, "hello": 1, "wonderful": 1}. Если вы увидите, что пары ключ/значение печатаются в другом порядке, то вспомните, что мы писали в разделы "Доступ к данным в HashMap", что повторение по хеш-карте происходит в произвольном порядке.

-

Способ split_whitespace возвращает повторитель по срезам строки, разделённых пробелам, для строки text. Способ or_insert возвращает изменяемую ссылку (&mut V) на значение ключа. Мы сохраняем изменяемую ссылку в переменной count, для этого, чтобы присвоить переменной значение, необходимо произвести разыменование с помощью звёздочки (*). Изменяемая ссылка удаляется сразу же после выхода из области видимости цикла for, поэтому все эти изменения безопасны и согласуются с правилами заимствования.

-

Функция хеширования

-

По умолчанию HashMap использует функцию хеширования SipHash, которая может противостоять атакам класса отказ в обслуживании, Denial of Service (DoS) с использованием хеш-таблиц siphash. Это не самый быстрый из возможных алгоритмов хеширования, в данном случае производительность идёт на соглашение с обеспечением лучшей безопасности. Если после профилирования вашего кода окажется, что хеш-функция, используемая по умолчанию, очень медленная, вы можете заменить её используя другой hasher. Hasher - это вид, выполняющий особенность BuildHasher. Подробнее о особенностях мы поговорим в Главе 10. Вам совсем не обязательно выполнить свою собственную функцию хеширования; crates.io имеет достаточное количество библиотек, предоставляющих разные выполнения hasher с множеством общих алгоритмов хеширования.

-

Итоги

-

Векторы, строки и хеш-карты предоставят большое количество возможностей для программ, когда необходимо сохранять, получать доступ и изменять данные. Теперь вы готовы решить следующие учебные задания:

-
    -
  • Есть список целых чисел. Создайте функцию, используйте вектор и верните из списка: среднее значение; медиану (значение элемента из середины списка после его сортировки); режиму списка (mode of list, то значение которое встречается в списке наибольшее количество раз; HashMap будет полезна в данном случае).
  • -
  • Преобразуйте строку в кодировку "поросячьей латыни" (Pig Latin). Первая согласная каждого слова перемещается в конец и к ней добавляется окончание "ay", так "first" станет "irst-fay". Слову, начинающемуся на гласную, в конец добавляется "hay" ("apple" становится "apple-hay"). Помните о подробностях работы с кодировкой UTF-8!
  • -
  • Используя хеш-карту и векторы, создайте текстовый внешняя оболочка позволяющий пользователю добавлять имена сотрудников к названию отдела предприятия. Например, "Add Sally to Engineering" или "Add Amir to Sales". Затем позвольте пользователю получить список всех людей из отдела или всех людей в предприятия, отсортированных по отделам в алфавитном порядке.
  • -
-

Документация API встроенной библиотеки описывает способы у векторов, строк и HashMap. Советуем воспользоваться ей при решении упражнений.

-

Потихоньку мы переходим к более сложным программам, в которых действия могут потерпеть неудачу. Наступило наилучшее время для обсуждения обработки ошибок.

-

Обработка ошибок

-

Возникновение ошибок в ходе выполнения программ — это суровая действительность в жизни программного обеспечения, поэтому Ржавчина имеет ряд функций для обработки случаев, в которых что-то идёт не так. Во многих случаях Ржавчина требует, чтобы вы признали возможность ошибки и предприняли некоторые действия, прежде чем ваш код будет собран. Это требование делает вашу программу более надёжной, обеспечивая, что вы обнаружите ошибки и обработаете их надлежащим образом, прежде чем развернёте свой код в производственной среде!

-

В Ржавчина ошибки объединяются на две основные разряды: исправимые (recoverable) и неисправимые (unrecoverable). В случае исправимой ошибки, такой как файл не найден, мы, скорее всего, просто хотим сообщить о неполадке пользователю и повторить действие. Неисправимые ошибки всегда являются симптомами изъянов в коде, например, попытка доступа к ячейке за пределами границ массива, и поэтому мы хотим немедленно остановить программу.

-

Большинство языков не различают эти два вида ошибок и обрабатывают оба вида одинаково, используя такие рычаги, как исключения. В Ржавчина нет исключений. Вместо этого он имеет вид Result<T, E> для обрабатываемых (исправимых) ошибок и макрос panic!, который останавливает выполнение, когда программа встречает необрабатываемую (неисправимую) ошибку. Сначала эта глава расскажет про вызов panic!, а потом расскажет о возврате значений Result<T, E>. Кроме того, мы рассмотрим, что нужно учитывать при принятии решения о том, следует ли попытаться исправить ошибку или остановить выполнение.

-

Неустранимые ошибки с макросом panic!

-

Иногда в коде происходят плохие вещи, и вы ничего не можете с этим поделать. В этих случаях у Ржавчина есть макрос panic! В действительностисуществует два способа вызвать панику: путём выполнения действия, которое вызывает панику в нашем коде (например, обращение к массиву за пределами его размера) или путём явного вызова макроса panic!. В обоих случаях мы вызываем панику в нашей программе. По умолчанию паника выводит сообщение об ошибке, раскручивает и очищает обойма вызовов, и завершают работу. С помощью переменной окружения вы также можете заставить Ржавчина отображать обойма вызовов при возникновении паники, чтобы было легче отследить источник паники.

-
-

Раскручивать обойма или прерывать выполнение программы в ответ на панику?

-

По умолчанию, когда происходит паника, программа начинает этап раскрутки обоймы, означающий в Ржавчина проход обратно по обойме вызовов и очистку данных для каждой обнаруженной функции. Тем не менее, этот обратный проход по обойме и очистка порождают много работы. Ржавчина как иное решение предоставляет вам возможность немедленного прерывания (aborting), которое завершает работу программы без очистки.

-

Память, которую использовала программа, должна быть очищена операционной системой. Если в вашем деле нужно насколько это возможно сделать маленьким исполняемый файл, вы можете переключиться с исхода раскрутки обоймы на исход прерывания при панике, добавьте panic = 'abort' в раздел [profile] вашего Cargo.toml файла. Например, если вы хотите прервать панику в режиме исполнения, добавьте это:

-
[profile.release]
-panic = 'abort'
-
-
-

Давайте попробуем вызвать panic! в простой программе:

-

Файл: src/main.rs

-
fn main() {
-    panic!("crash and burn");
-}
-

При запуске программы, вы увидите что-то вроде этого:

-
$ cargo run
-   Compiling panic v0.1.0 (file:///projects/panic)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
-     Running `target/debug/panic`
-thread 'main' panicked at src/main.rs:2:5:
-crash and burn
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-

Выполнение макроса panic! вызывает сообщение об ошибке, содержащееся в двух последних строках. Первая строка показывает сообщение паники и место в исходном коде, где возникла паника: src/main.rs:2:5 указывает, что это вторая строка, пятый символ внутри нашего файла src/main.rs

-

В этом случае указанная строка является частью нашего кода, и если мы перейдём к этой строке, мы увидим вызов макроса panic!. В других случаях вызов panic! мог бы произойти в стороннем коде, который вызывает наш код, тогда имя файла и номер строки для сообщения об ошибке будет из чужого кода, где макрос panic! выполнен, а не из строк нашего кода, которые в конечном итоге привели к выполнению panic!. Мы можем использовать обратную трассировку вызовов функций которые вызвали panic! чтобы выяснить, какая часть нашего кода вызывает неполадку. Мы обсудим обратную трассировку более подробно далее.

-

Использование обратной трассировки panic!

-

Давайте посмотрим на другой пример, где, вызов panic! происходит в сторонней библиотеке из-за ошибки в нашем коде (а не как в примере ранее, из-за вызова макроса нашим кодом напрямую). В приложении 9-1 приведён код, который пытается получить доступ по порядковому указателю в векторе за пределами допустимого рядазначений порядкового указателя.

-

Файл: src/main.rs

-
fn main() {
-    let v = vec![1, 2, 3];
-
-    v[99];
-}
-

Приложение 9-1: Попытка доступа к элементу за пределами вектора, которая вызовет panic!

-

Здесь мы пытаемся получить доступ к 100-му элементу вектора (который находится по порядковому указателю 99, потому что упорядочевание начинается с нуля), но вектор имеет только 3 элемента. В этой случаи, Ржавчина будет вызывать панику. Использование [] должно возвращать элемент, но вы передаёте неверный порядковый указатель: не существует элемента, который Ржавчина мог бы вернуть.

-

В языке C, например, попытка прочесть за пределами конца устройства данных (в нашем случае векторе) приведёт к неопределённому поведению, undefined behavior, UB. Вы всё равно получите значение, которое находится в том месте памяти компьютера, которое соответствовало бы этому элементу в векторе, несмотря на то, что память по тому адресу совсем не принадлежит вектору (всё просто: C рассчитал бы место хранения элемента с порядковым указателем 99 и считал бы то, что там хранится, упс). Это называется чтением за пределом буфера, buffer overread, и может привести к уязвимостям безопасности. Если злоумышленник может управлять порядковым указателем таким образом, то у него появляется возможность читать данные, которые он не должен иметь возможности читать.

-

Чтобы защитить вашу программу от такого рода уязвимостей при попытке прочитать элемент с порядковым указателем, которого не существует, Ржавчина остановит выполнение и откажется продолжить работу программы. Давайте попробуем так сделать и посмотрим на поведение Rust:

-
$ cargo run
-   Compiling panic v0.1.0 (file:///projects/panic)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
-     Running `target/debug/panic`
-thread 'main' panicked at src/main.rs:4:6:
-index out of bounds: the len is 3 but the index is 99
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Следующая строка говорит, что мы можем установить переменную среды RUST_BACKTRACE, чтобы получить обратную трассировку того, что именно стало причиной ошибки. Обратная трассировка создаёт список всех функций, которые были вызваны до какой-то определённой точки выполнения программы. Обратная трассировка в Ржавчина работает так же, как и в других языках. По этому предлагаем вам читать данные обратной трассировки как и везде - читать сверху вниз, пока не увидите сведения о файлах написанных вами. Это место, где возникла неполадка. Другие строки, которые выше над строками с упоминанием наших файлов, - это код, который вызывается нашим кодом; строки ниже являются кодом, который вызывает наш код. Эти строки могут включать основной код Rust, код встроенной библиотеки или используемые ящики. Давайте попробуем получить обратную трассировку с помощью установки переменной среды RUST_BACKTRACE в любое значение, кроме 0. Приложение 9-2 показывает вывод, подобный тому, что вы увидите.

- -
$ RUST_BACKTRACE=1 cargo run
-thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
-stack backtrace:
-   0: rust_begin_unwind
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/panicking.rs:584:5
-   1: core::panicking::panic_fmt
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:142:14
-   2: core::panicking::panic_bounds_check
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:84:5
-   3: <usize as core::slice::index::SliceIndex<[T]>>::index
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:242:10
-   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:18:9
-   5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/alloc/src/vec/mod.rs:2591:9
-   6: panic::main
-             at ./src/main.rs:4:5
-   7: core::ops::function::FnOnce::call_once
-             at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/ops/function.rs:248:5
-note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
-
-

Приложение 9-2: Обратная трассировка, созданная вызовом panic!, когда установлена переменная окружения RUST_BACKTRACE

-

Тут много вывода! Вывод, который вы увидите, может отличаться от представленного, в зависимости от вашей операционной системы и исполнения Rust. Для того, чтобы получить обратную трассировку с этой сведениями, должны быть включены символы отладки, debug symbols. Символы отладки включены по умолчанию при использовании cargo build или cargo run без флага --release, как у нас в примере.

-

В выводе обратной трассировки приложения 9-2, строка #6 указывает на строку в нашем деле, которая вызывала неполадку: строка 4 из файла src/main.rs. Если мы не хотим, чтобы наша программа запаниковала, мы должны начать исследование с места, на которое указывает первая строка с упоминанием нашего файла. В приложении 9-1, где мы для отображения обратной трассировки сознательно написали код, который паникует, способ исправления паники состоит в том, чтобы не запрашивать элемент за пределами ряда значений порядковых указателей вектора. Когда ваш код запаникует в будущем, вам нужно будет выяснить, какое выполняющееся кодом действие, с какими значениями вызывает панику и что этот код должен делать вместо этого.

-

Мы вернёмся к обсуждению макроса panic!, и того когда нам следует и не следует использовать panic! для обработки ошибок в разделе "panic! или НЕ panic!" этой главы. Далее мы рассмотрим, как восстановить выполнение программы после исправляемых ошибок, использующих вид Result.

-

Исправимые ошибки с Result

-

Многие ошибки являются не настолько критичными, чтобы останавливать выполнение программы. Иногда, когда в функции происходит сбой, необходима просто правильная преобразование и обработка ошибки. К примеру, при попытке открыть файл может произойти ошибка из-за отсутствия файла. Вы, возможно, захотите исправить случай и создать новый файл вместо остановки программы.

-

Вспомните раздел ["Обработка возможного сбоя с помощью Result"] главы 2: мы использовали там перечисление Result, имеющее два исхода. Ok и Err для обработки сбоев. Само перечисление определено следующим образом:

-
#![allow(unused)]
-fn main() {
-enum Result<T, E> {
-    Ok(T),
-    Err(E),
-}
-}
-

Виды T и E являются свойствами обобщённого вида: мы обсудим обобщённые виды более подробно в Главе 10. Все что вам нужно знать прямо сейчас - это то, что T представляет вид значения, которое будет возвращено в случае успеха внутри исхода Ok, а E представляет вид ошибки, которая будет возвращена при сбое внутри исхода Err. Так как вид Result имеет эти обобщённые свойства (generic type parameters), мы можем использовать вид Result и функции, которые определены для него, в разных случаейх, когда вид успешного значение и значения ошибки, которые мы хотим вернуть, отличаются.

-

Давайте вызовем функцию, которая возвращает значение Result, потому что может потерпеть неудачу. В приложении 9-3 мы пытаемся открыть файл.

-

Файл: src/main.rs

-
use std::fs::File;
-
-fn main() {
-    let greeting_file_result = File::open("hello.txt");
-}
-

Приложение 9-3: Открытие файла

-

File::open возвращает значения вида Result<T, E>. Гибкий вид T в выполнения File::open соответствует виду успешно полученного значения, std::fs::File, а именно указателю файла. Вид E, используемый для значения в случае возникновения ошибки, - std::io::Error. Такой возвращаемый вид означает, что вызов File::open может быть успешным и вернуть указатель файла, из которого мы можем читать или в который можем писать. Также вызов функции может завершиться неудачей: например, файл может не существовать, или у нас может не быть разрешения на доступ к файлу. Функция File::open должна иметь способ сообщить нам об успехе или неудаче и в то же время дать нам либо указатель файла, либо сведения об ошибке. Эту возможность как раз и предоставляет перечисление Result.

-

В случае успеха File::open значением переменной greeting_file_result будет образец Ok, содержащий указатель файла. В случае неудачи значение в переменной greeting_file_result будет образцом Err, содержащим дополнительную сведения о том, какая именно ошибка произошла.

-

Необходимо дописать в код приложения 9-3 выполнение разных действий в зависимости от значения, которое вернёт вызов File::open. Приложение 9-4 показывает один из способов обработки Result - пользуясь основным средством языка, таким как выражение match, рассмотренным в Главе 6.

-

Файл: src/main.rs

-
use std::fs::File;
-
-fn main() {
-    let greeting_file_result = File::open("hello.txt");
-
-    let greeting_file = match greeting_file_result {
-        Ok(file) => file,
-        Err(error) => panic!("Problem opening the file: {error:?}"),
-    };
-}
-

Приложение 9-4: Использование выражения match для обработки возвращаемых исходов вида Result

-

Обратите внимание, что также как перечисление Option, перечисление Result и его исходы, входят в область видимости благодаря авто-подключения (prelude), поэтому не нужно указывать Result:: перед использованием исходов Ok и Err в ветках выражения match.

-

Если итогом будет Ok, этот код вернёт значение file из исхода Ok, а мы затем присвоим это значение файлового указателя переменной greeting_file. После match мы можем использовать указатель файла для чтения или записи.

-

Другая ветвь match обрабатывает случай, где мы получаем значение Err после вызова File::open. В этом примере мы решили вызвать макрос panic!. Если в нашей текущей папки нет файла с именем hello.txt и мы выполним этот код, то мы увидим следующее сообщение от макроса panic!:

-
$ cargo run
-   Compiling error-handling v0.1.0 (file:///projects/error-handling)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
-     Running `target/debug/error-handling`
-thread 'main' panicked at src/main.rs:8:23:
-Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Как обычно, данное сообщение точно говорит, что пошло не так.

-

Обработка различных ошибок с помощью match

-

Код в приложении 9-4 будет вызывать panic! независимо от того, почему вызов File::open не удался. Однако мы хотим предпринять различные действия для разных причин сбоя. Если открытие File::open не удалось из-за отсутствия файла, мы хотим создать файл и вернуть его указатель. Если вызов File::open не удался по любой другой причине - например, потому что у нас не было прав на открытие файла, то все равно мы хотим вызвать panic! как у нас сделано в приложении 9-4. Для этого мы добавляем выражение внутреннего match, показанное в приложении 9-5.

-

Файл: src/main.rs

- -
use std::fs::File;
-use std::io::ErrorKind;
-
-fn main() {
-    let greeting_file_result = File::open("hello.txt");
-
-    let greeting_file = match greeting_file_result {
-        Ok(file) => file,
-        Err(error) => match error.kind() {
-            ErrorKind::NotFound => match File::create("hello.txt") {
-                Ok(fc) => fc,
-                Err(e) => panic!("Problem creating the file: {e:?}"),
-            },
-            other_error => {
-                panic!("Problem opening the file: {other_error:?}");
-            }
-        },
-    };
-}
-

Приложение 9-5: Обработка различных ошибок разными способами

-

Видом значения возвращаемого функцией File::open внутри Err исхода является io::Error, устройства из встроенной библиотеки. Данная устройства имеет способ kind, который можно вызвать для получения значения io::ErrorKind. Перечисление io::ErrorKind из встроенной библиотеки имеет исходы, представляющие различные виды ошибок, которые могут появиться при выполнении действий в io. Исход, который мы хотим использовать, это ErrorKind::NotFound, который даёт сведения, о том, что файл который мы пытаемся открыть ещё не существует. Итак, во второй строке мы вызываем сопоставление образца с переменной greeting_file_result и попадаем в ветку с обработкой ошибки, но также у нас есть внутренняя проверка для сопоставления error.kind() ошибки.

-

Условие, которое мы хотим проверить во внутреннем match, заключается в том, является ли значение, возвращаемое error.kind(), исходом NotFound перечисления ErrorKind. Если это так, мы пытаемся создать файл с помощью функции File::create. Однако, поскольку вызов File::create тоже может завершиться ошибкой, нам нужна обработка ещё одной ошибки, теперь уже во внутреннем выражении match. Заметьте: если файл не может быть создан, выводится другое, особое сообщение об ошибке. Вторая же ветка внешнего match (который обрабатывает вызов error.kind()), остаётся той же самой - в итоге программа паникует при любой ошибке, кроме ошибки отсутствия файла.

-
-

Иные использованию match с Result<T, E>

-

Как много match! Выражение match является очень полезным, но в то же время довольно простым. В главе 13 вы узнаете о замыканиях (closures), которые используются во многих способах вида Result<T, E>. Эти способы помогают быть более кратким, чем использование match при работе со значениями Result<T, E> в вашем коде.

-

Например, вот другой способ написать ту же логику, что показана в Приложении 9-5, но с использованием замыканий и способа unwrap_or_else:

- -
use std::fs::File;
-use std::io::ErrorKind;
-
-fn main() {
-    let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
-        if error.kind() == ErrorKind::NotFound {
-            File::create("hello.txt").unwrap_or_else(|error| {
-                panic!("Problem creating the file: {:?}", error);
-            })
-        } else {
-            panic!("Problem opening the file: {:?}", error);
-        }
-    });
-}
-

Несмотря на то, что данный код имеет такое же поведение как в приложении 9-5, он не содержит ни одного выражения match и проще для чтения. Советуем вам вернуться к примеру этого раздела после того как вы прочитаете Главу 13 и изучите способ unwrap_or_else по документации встроенной библиотеки. Многие из способов о которых вы узнаете в документации и Главе 13 могут очистить код от больших, вложенных выражений match при обработке ошибок.

-
-

Краткие способы обработки ошибок - unwrap и expect

-

Использование match работает достаточно хорошо, но может быть довольно многословным и не всегда хорошо передаёт смысл. Вид Result<T, E> имеет множество вспомогательных способов для выполнения различных, более отличительных задач. Способ unwrap - это способ быстрого доступа к значениям, выполненный так же, как и выражение match, которое мы написали в Приложении 9-4. Если значение Result является исходом Ok, unwrap возвращает значение внутри Ok. Если Result - исход Err, то unwrap вызовет для нас макрос panic!. Вот пример unwrap в действии:

-

Файл: src/main.rs

-
use std::fs::File;
-
-fn main() {
-    let greeting_file = File::open("hello.txt").unwrap();
-}
-

Если мы запустим этот код при отсутствии файла hello.txt, то увидим сообщение об ошибке из вызова panic! способа unwrap:

- -
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os {
-code: 2, kind: NotFound, message: "No such file or directory" }',
-src/main.rs:4:49
-
-

Другой способ, похожий на unwrap, это expect, позволяющий указать сообщение об ошибке для макроса panic!. Использование expect вместо unwrap с предоставлением хорошего сообщения об ошибке выражает ваше намерение и делает более простым отслеживание источника паники. правила написания способа expect выглядит так:

-

Файл: src/main.rs

-
use std::fs::File;
-
-fn main() {
-    let greeting_file = File::open("hello.txt")
-        .expect("hello.txt should be included in this project");
-}
-

expect используется так же как и unwrap: либо возвращается указатель файла либо вызывается макрос panic!.
Наше сообщение об ошибке в expect будет передано в panic! и заменит обычное используемое сообщение.
Вот как это выглядит:

- -
thread 'main' panicked at 'hello.txt should be included in this project: Os {
-code: 2, kind: NotFound, message: "No such file or directory" }',
-src/main.rs:5:10
-
-

В рабочем коде, большинство выбирает expect в угоду unwrap и добавляет описание, почему действие должна закончиться успешно. Но даже если предположение оказалось неверным, сведений для отладки будет больше.

-

Проброс ошибок

-

Когда вы пишете функцию, выполнение которой вызывает что-то, что может завершиться ошибкой, вместо обработки ошибки в этой функции, вы можете вернуть ошибку в вызывающий код, чтобы он мог решить, что с ней делать. Такой приём известен как распространение ошибки (propagating the error). Благодаря нему мы даём больше управления вызывающему коду, где может быть больше сведений или логики, которая диктует, как ошибка должна обрабатываться, чем было бы в месте появления этой ошибки.

-

Например, код программы 9-6 читает имя пользователя из файла. Если файл не существует или не может быть прочтён, то функция возвращает ошибку в код, который вызвал данную функцию.

-

Файл: src/main.rs

- -
#![allow(unused)]
-fn main() {
-use std::fs::File;
-use std::io::{self, Read};
-
-fn read_username_from_file() -> Result<String, io::Error> {
-    let username_file_result = File::open("hello.txt");
-
-    let mut username_file = match username_file_result {
-        Ok(file) => file,
-        Err(e) => return Err(e),
-    };
-
-    let mut username = String::new();
-
-    match username_file.read_to_string(&mut username) {
-        Ok(_) => Ok(username),
-        Err(e) => Err(e),
-    }
-}
-}
-

Приложение 9-6: Функция, которая возвращает ошибки в вызывающий код, используя оператор match

-

Эта функция может быть написана гораздо более коротким способом, но мы начнём с того, что многое сделаем вручную, чтобы изучить обработку ошибок; а в конце покажем более короткий способ. Давайте сначала рассмотрим вид возвращаемого значения: Result<String, io::Error>. Здесь есть возвращаемое значение функции вида Result<T, E> где образцовый свойство T был заполнен определенным видом String и образцовый свойство E был заполнен определенным видом io::Error.

-

Если эта функция выполнится без неполадок. то код, вызывающий эту функцию, получит значение Ok, содержащее String - имя пользователя, которое эта функция прочитала из файла. Если функция столкнётся с какими-либо неполадками, вызывающий код получит значение Err, содержащее образец io::Error, который включает дополнительную сведения о том, какие сбоев возникли. Мы выбрали io::Error в качестве возвращаемого вида этой функции, потому что это вид значения ошибки, возвращаемого из обеих действий, которые мы вызываем в теле этой функции и которые могут завершиться неудачей: функция File::open и способ read_to_string.

-

Тело функции начинается с вызова File::open. Затем мы обрабатываем значение Result с помощью match, подобно match из приложения 9-4. Если File::open завершается успешно, то указатель файла в переменной образца file становится значением в изменяемой переменной username_file и функция продолжит свою работу. В случае Err, вместо вызова panic!, мы используем ключевое слово return для досрочного возврата из функции и передаём значение ошибки из File::open, которое теперь находится в переменной образца e, обратно в вызывающий код как значение ошибки этой функции.

-

Таким образом, если у нас есть файловый указатель в username_file, функция создаёт новую String в переменной username и вызывает способ read_to_string для файлового указателя в username_file, чтобы прочитать содержимое файла в username. Способ read_to_string также возвращает Result, потому что он может потерпеть неудачу, даже если File::open завершился успешно. Поэтому нам нужен ещё один match для обработки этого Result: если read_to_string завершится успешно, то наша функция сработала, и мы возвращаем имя пользователя из файла, которое теперь находится в username, обёрнутое в Ok. Если read_to_string потерпит неудачу, мы возвращаем значение ошибки таким же образом, как мы возвращали значение ошибки в match, который обрабатывал возвращаемое значение File::open. Однако нам не нужно явно указывать return, потому что это последнее выражение в функции.

-

Затем код, вызывающий этот, будет обрабатывать получение либо значения Ok, содержащего имя пользователя, либо значения Err, содержащего io::Error. Вызывающий код должен решить, что делать с этими значениями. Если вызывающий код получает значение Err, он может вызвать panic! и завершить работу программы, использовать имя пользователя по умолчанию или найти имя пользователя, например, не в файле. У нас недостаточно сведений о том, что на самом деле пытается сделать вызывающий код, поэтому мы распространяем всю сведения об успехах или ошибках вверх, чтобы она могла обрабатываться соответствующим образом.

-

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

-

Сокращение для проброса ошибок: оператор ?

-

В приложении 9-7 показана выполнение read_username_from_file, которая имеет ту же возможность, что и в приложении 9-6, но в этой выполнения используется оператор ?.

-

Файл: src/main.rs

- -
#![allow(unused)]
-fn main() {
-use std::fs::File;
-use std::io::{self, Read};
-
-fn read_username_from_file() -> Result<String, io::Error> {
-    let mut username_file = File::open("hello.txt")?;
-    let mut username = String::new();
-    username_file.read_to_string(&mut username)?;
-    Ok(username)
-}
-}
-

Приложение 9-7: Функция, возвращающая ошибки в вызывающий код с помощью оператора ?

-

Выражение ?, расположенное после Result, работает почти так же, как и те выражения match, которые мы использовали для обработки значений Result в приложении 9-6. Если в качестве значения Result будет Ok, то значение внутри Ok будет возвращено из этого выражения, и программа продолжит работу. Если же значение представляет собой Err, то Err будет возвращено из всей функции, как если бы мы использовали ключевое слово return, так что значение ошибки будет передано в вызывающий код.

-

Существует разница между тем, что делает выражение match из приложения 9-6 и тем, что делает оператор ?: значения ошибок, для которых вызван оператор ?, проходят через функцию from, определённую в особенности From встроенной библиотеки, которая используется для преобразования значений из одного вида в другой. Когда оператор ? вызывает функцию from, полученный вид ошибки преобразуется в вид ошибки, определённый в возвращаемом виде текущей функции. Это полезно, когда функция возвращает только один вид ошибки, для описания всех возможных исходов сбоев, даже если её отдельные составляющие могут выходить из строя по разным причинам.

-

Например, мы могли бы изменить функцию read_username_from_file в приложении 9-7, чтобы возвращать пользовательский вид ошибки с именем OurError, который мы определим. Если мы также определим impl From<io::Error> for OurError для создания образца OurError из io::Error, то оператор ?, вызываемый в теле read_username_from_file, вызовет from и преобразует виды ошибок без необходимости добавления дополнительного кода в функцию.

-

В случае приложения 9-7 оператор ? в конце вызова File::open вернёт значение внутри Ok в переменную username_file. Если произойдёт ошибка, оператор ? выполнит ранний возврат значения Err вызывающему коду. То же самое относится к оператору ? в конце вызова read_to_string.

-

Оператор ? позволяет избавиться от большого количества образцового кода и упростить выполнение этой функции. Мы могли бы даже ещё больше сократить этот код, если бы использовали цепочку вызовов способов сразу после ?, как показано в приложении 9-8.

-

Файл: src/main.rs

- -
#![allow(unused)]
-fn main() {
-use std::fs::File;
-use std::io::{self, Read};
-
-fn read_username_from_file() -> Result<String, io::Error> {
-    let mut username = String::new();
-
-    File::open("hello.txt")?.read_to_string(&mut username)?;
-
-    Ok(username)
-}
-}
-

Приложение 9-8: Цепочка вызовов способов после оператора ?

-

Мы перенесли создание новой String в username в начало функции; эта часть не изменилась. Вместо создания переменной username_file мы соединили вызов read_to_string непосредственно с итогом File::open("hello.txt")?. У нас по-прежнему есть ? в конце вызова read_to_string, и мы по-прежнему возвращаем значение Ok, содержащее username, когда и File::open и read_to_string завершаются успешно, а не возвращают ошибки. Возможность снова такая же, как в Приложении 9-6 и Приложении 9-7; это просто другой, более удобный способ её написания.

-

Продолжая рассматривать разные способы записи данной функции, приложение 9-9 отображает способ сделать её ещё короче с помощью fs::read_to_string.

-

Файл: src/main.rs

- -
#![allow(unused)]
-fn main() {
-use std::fs;
-use std::io;
-
-fn read_username_from_file() -> Result<String, io::Error> {
-    fs::read_to_string("hello.txt")
-}
-}
-

Приложение 9-9: Использование fs::read_to_string вместо открытия и последующего чтения файла

-

Чтение файла в строку довольно распространённая действие, так что обычная библиотека предоставляет удобную функцию fs::read_to_string, которая открывает файл, создаёт новую String, читает содержимое файла, размещает его в String и возвращает её. Конечно, использование функции fs::read_to_string не даёт возможности объяснить обработку всех ошибок, поэтому мы сначала изучили длинный способ.

-

Где можно использовать оператор ?

-

Оператор ? может использоваться только в функциях, вид возвращаемого значения которых совместим со значением, для которого используется ?. Это потому, что оператор ? определён для выполнения раннего возврата значения из функции таким же образом, как и выражение match, которое мы определили в приложении 9-6. В приложении 9-6 match использовало значение Result, а ответвление с ранним возвратом вернуло значение Err(e). Вид возвращаемого значения функции должен быть Result, чтобы он был совместим с этим return.

-

В приложении 9-10 давайте посмотрим на ошибку, которую мы получим, если воспользуемся оператором ? в функции main с видом возвращаемого значения, несовместимым с видом значения, для которого мы используем ?:

-

Файл: src/main.rs

-
use std::fs::File;
-
-fn main() {
-    let greeting_file = File::open("hello.txt")?;
-}
-

Приложение 9-10: Попытка использовать ? в main функции, которая возвращает () , не будет собираться

-

Этот код открывает файл, что может привести к сбою. ? оператор следует за значением Result , возвращаемым File::open , но эта main функция имеет возвращаемый вид () , а не Result . Когда мы собираем этот код, мы получаем следующее сообщение об ошибке:

-
$ cargo run
-   Compiling error-handling v0.1.0 (file:///projects/error-handling)
-error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
- --> src/main.rs:4:48
-  |
-3 | fn main() {
-  | --------- this function should return `Result` or `Option` to accept `?`
-4 |     let greeting_file = File::open("hello.txt")?;
-  |                                                ^ cannot use the `?` operator in a function that returns `()`
-  |
-  = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `error-handling` (bin "error-handling") due to 1 previous error
-
-

Эта ошибка указывает на то, что оператор ? разрешено использовать только в функции, которая возвращает Result, Option или другой вид, выполняющий FromResidual.

-

Для исправления ошибки есть два исхода. Первый - изменить возвращаемый вид вашей функции так, чтобы он был совместим со значением, для которого вы используете оператор ?, если у вас нет ограничений, препятствующих этому. Другой способ - использовать match или один из способов Result<T, E> для обработки Result<T, E> любым подходящим способом.

-

В сообщении об ошибке также упоминалось, что ? можно использовать и со значениями Option<T>. Как и при использовании ? для Result, вы можете использовать ? только для Option в функции, которая возвращает Option. Поведение оператора ? при вызове Option<T> похоже на его поведение при вызове Result<T, E>: если значение равно None, то None будет возвращено раньше из функции в этот мгновение. Если значение Some, значение внутри Some является результирующим значением выражения, и функция продолжает исполняться. В приложении 9-11 приведён пример функции, которая находит последний символ первой строки заданного текста:

-
fn last_char_of_first_line(text: &str) -> Option<char> {
-    text.lines().next()?.chars().last()
-}
-
-fn main() {
-    assert_eq!(
-        last_char_of_first_line("Hello, world\nHow are you today?"),
-        Some('d')
-    );
-
-    assert_eq!(last_char_of_first_line(""), None);
-    assert_eq!(last_char_of_first_line("\nhi"), None);
-}
-

Приложение 9-11: Использование оператора ? для значения Option<T>

-

Эта функция возвращает Option<char>, потому что возможно, что там есть символ, но также возможно, что его нет. Этот код принимает переменная среза text строки и вызывает для него способ lines, который возвращает повторитель для строк в строке. Поскольку эта функция хочет проверить первую строку, она вызывает next у повторителя, чтобы получить первое значение от повторителя. Если text является пустой строкой, этот вызов next вернёт None, и в этом случае мы используем ? чтобы остановить и вернуть None из last_char_of_first_line. Если text не является пустой строкой, next вернёт значение Some, содержащее отрывок строки первой строки в text.

-

Символ ? извлекает отрывок строки, и мы можем вызвать chars для этого отрывка строки. чтобы получить повторитель символов. Нас важно последний символ в первой строке, поэтому мы вызываем last, чтобы вернуть последний элемент в повторителе. Вернётся Option, потому что возможно, что первая строка пустая - например, если text начинается с пустой строки, но имеет символы в других строках, как в "\nhi". Однако, если в первой строке есть последний символ, он будет возвращён в исходе Some. Оператор ? в середине даёт нам краткий способ выразить эту логику, позволяя выполнить функцию в одной строке. Если бы мы не могли использовать оператор ? в Option, нам пришлось бы выполнить эту логику, используя больше вызовов способов или выражение match.

-

Обратите внимание, что вы можете использовать оператор ? Result в функции, которая возвращает Result , и вы можете использовать оператор ? для Option в функции, которая возвращает Option , но вы не можете смешивать и сопоставлять. Оператор ? не будет самостоятельно преобразовывать Result в Option или наоборот; в этих случаях вы можете использовать такие способы, как способ ok для Result или способ ok_or для Option, чтобы выполнить преобразование явно.

-

До сих пор все функции main, которые мы использовали, возвращали (). Функция main - особенная, потому что это точка входа и выхода исполняемых программ, и существуют ограничения на вид возвращаемого значения, чтобы программы вели себя так, как ожидается.

-

К счастью, main также может возвращать Result<(), E> . В приложении 9-12 используется код из приложения 9-10, но мы изменили возвращаемый вид main на Result<(), Box<dyn Error>> и добавили возвращаемое значение Ok(()) в конец. Теперь этот код будет собран:

-
use std::error::Error;
-use std::fs::File;
-
-fn main() -> Result<(), Box<dyn Error>> {
-    let greeting_file = File::open("hello.txt")?;
-
-    Ok(())
-}
-

Приложение 9-12: Замена main на return Result<(), E> позволяет использовать оператор ? оператор над значениями Result

-

Вид Box<dyn Error> является особенность-предметом, о котором мы поговорим в разделе "Использование особенность-предметов, допускающих значения разных видов" в главе 17. Пока что вы можете считать, что Box<dyn Error> означает "любой вид ошибки". Использование ? для значения Result в функции main с видом ошибки Box<dyn Error> разрешено, так как позволяет вернуть любое значение Err раньше времени. Даже если тело этой функции main будет возвращать только ошибки вида std::io::Error, указав Box<dyn Error>, эта ярлык останется правильной, даже если в тело main будет добавлен код, возвращающий другие ошибки.

-

Когда main функция возвращает Result<(), E>, исполняемый файл завершится со значением 0, если main вернёт Ok(()), и выйдет с ненулевым значением, если main вернёт значение Err. Исполняемые файлы, написанные на C, при выходе возвращают целые числа: успешно завершённые программы возвращают целое число 0, а программы с ошибкой возвращают целое число, отличное от 0. Ржавчина также возвращает целые числа из исполняемых файлов, чтобы быть совместимым с этим соглашением.

-

Функция main может возвращать любые виды, выполняющие особенность std::process::Termination, в которых имеется функция report, возвращающая ExitCode. Обратитесь к документации встроенной библиотеки за дополнительной сведениями о порядке выполнения особенности Termination для ваших собственных видов.

-

Теперь, когда мы обсудили подробности вызова panic! или возврата Result, давайте вернёмся к тому, как решить, какой из случаев подходит для какой случаи.

-

panic! или не panic!

-

Итак, как принимается решение о том, когда следует вызывать panic!, а когда вернуть Result? При панике код не имеет возможности восстановить своё выполнение. Можно было бы вызывать panic! для любой ошибочной случаи, независимо от того, имеется ли способ восстановления или нет, но с другой стороны, вы принимаете решение от имени вызывающего вас кода, что случаей необратима. Когда вы возвращаете значение Result, вы делегируете принятие решения вызывающему коду. Вызывающий код может попытаться выполнить восстановление способом, который подходит в данной случаи, или же он может решить, что из ошибки в Err нельзя восстановиться и вызовет panic!, превратив вашу исправимую ошибку в неисправимую. Поэтому возвращение Result является хорошим выбором по умолчанию для функции, которая может дать сбой.

-

В таких случаей как примеры, протовиды и проверки, более уместно писать код, который паникует вместо возвращения Result. Давайте рассмотрим почему, а затем мы обсудим случаи, в которых сборщик не может доказать, что ошибка невозможна, но вы, как человек, можете это сделать. Глава будет заканчиваться некоторыми общими руководящими принципами о том, как решить, стоит ли паниковать в коде библиотеки.

-

Примеры, прототипирование и проверки

-

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

-

Точно так же способы unwrap и expect являются очень удобными при создании протовида, прежде чем вы будете готовы решить, как обрабатывать ошибки. Они оставляют чёткие отступыв коде до особенности, когда вы будете готовы сделать программу более надёжной.

-

Если в проверке происходит сбой при вызове способа, то вы бы хотели, чтобы весь проверка не прошёл, даже если этот способ не является проверяемой возможностью. Поскольку вызов panic! это способ, которым проверка помечается как провалившийся, использование unwrap или expect - именно то, что нужно.

-

Случаи, в которых у вас больше сведений, чем у сборщика

-

Также было бы целесообразно вызывать unwrap или expect когда у вас есть какая-то другая логика, которая заверяет, что Result будет иметь значение Ok, но вашу логику не понимает сборщик. У вас по-прежнему будет значение Result которое нужно обработать: любая действие, которую вы вызываете, все ещё имеет возможность неудачи в целом, хотя это логически невозможно в вашей именно случаи. Если, проверяя код вручную, вы можете убедиться, что никогда не будет исход с Err, то вполне допустимо вызывать unwrap, а ещё лучше задокументировать причину, по которой, по вашему мнению, у вас никогда не будет исхода Err в тексте expect. Вот пример:

-
fn main() {
-    use std::net::IpAddr;
-
-    let home: IpAddr = "127.0.0.1"
-        .parse()
-        .expect("Hardcoded IP address should be valid");
-}
-

Мы создаём образец IpAddr, анализируя жёстко закодированную строку. Можно увидеть, что 127.0.0.1 является действительным IP-адресом, поэтому здесь допустимо использование expect. Однако наличие жёстко закодированной допустимой строки не меняет вид возвращаемого значения способа parse: мы все ещё получаем значение Result и сборщик все также заставляет нас обращаться с Resultтак, будто возможен исход Err, потому что сборщик недостаточно умён, чтобы увидеть, что эта строка всегда действительный IP-адрес. Если строка IP-адреса пришла от пользователя, то она не является жёстко запрограммированной в программе и, следовательно, может привести к ошибке, мы определённо хотели бы обработать Result более надёжным способом. Упоминание предположения о том, что этот IP-адрес жёстко закодирован, побудит нас изменить expect для лучшей обработки ошибок, если в будущем нам потребуется вместо этого получить IP-адрес из какого-либо другого источника.

-

Руководство по обработке ошибок

-

Желательно, чтобы код паниковал, если он может оказаться в неправильном состоянии. В этом среде неправильное состояние это когда некоторое допущение, заверение, договор или неизменная величина были нарушены. Например, когда недопустимые, противоречивые или пропущенные значения передаются в ваш код - плюс один или несколько пунктов из следующего перечисленного в списке:

-
    -
  • Неправильное состояние — это что-то неожиданное, отличается от того, что может происходить время от времени, например, когда пользователь вводит данные в неправильном виде.
  • -
  • Ваш код после этой точки должен полагаться на то, что он не находится в неправильном состоянии, вместо проверок наличия сбоев на каждом этапе.
  • -
  • Нет хорошего способа закодировать данную сведения в видах, которые вы используете. Мы рассмотрим пример того, что мы имеем в виду в разделе “Кодирование состояний и поведения на основе видов” главы 17.
  • -
-

Если кто-то вызывает ваш код и передаёт значения, которые не имеют смысла, лучше всего вернуть ошибку, если вы это можете, чтобы пользователь библиотеки мог решить, что он хочет делать в этом случае. Однако в тех случаях, когда продолжение выполнения программы может быть небезопасным или вредным, лучшим выбором будет вызов panic! и оповещение пользователя, использующего вашу библиотеку, об ошибке в его коде, чтобы он мог исправить её во время разработки. Подобно panic! подходит, если вы вызываете внешний, неподуправлениеный вам код, и он возвращает недопустимое состояние, которое вы не можете исправить.

-

Однако, когда ожидается сбой, лучше вернуть Result, чем выполнить вызов panic!. В качестве примера можно привести синтаксический анализатор, которому передали неправильно созданные данные, или HTTP-запрос, возвращающий значение указывающий на то, что вы достигли ограничения на частоту запросов. В этих случаях возврат Result означает, что ошибка является ожидаемой и вызывающий код должен решить, как её обрабатывать.

-

Когда ваш код выполняет действие, которая может подвергнуть пользователя риску, если она вызывается с использованием недопустимых значений, ваш код должен сначала проверить допустимость значений и паниковать, если значения недопустимы. Так советуется делать в основном из соображений безопасности: попытка оперировать неправильными данными может привести к уязвимостям. Это основная причина, по которой обычная библиотека будет вызывать panic!, если попытаться получить доступ к памяти вне границ массива: доступ к памяти, не относящейся к текущей устройстве данных, является известной неполадкой безопасности. Функции часто имеют договоры: их поведение обеспечивается, только если входные данные отвечают определённым требованиям. Паника при нарушении договора имеет смысл, потому что это всегда указывает на изъян со стороны вызывающего кода, и это не ошибка, которую вы хотели бы, чтобы вызывающий код явно обрабатывал. На самом деле, нет разумного способа для восстановления вызывающего кода; программисты, вызывающие ваш код, должны исправить свой. Договоры для функции, особенно когда нарушение вызывает панику, следует описать в документации по API функции.

-

Тем не менее, наличие множества проверок ошибок во всех ваших функциях было бы многословным и раздражительным. К счастью, можно использовать систему видов Ржавчина (следовательно и проверку видов сборщиком), чтобы она сделала множество проверок вместо вас. Если ваша функция имеет определённый вид в качестве свойства, вы можете продолжить работу с логикой кода зная, что сборщик уже обеспечил правильное значение. Например, если используется обычный вид, а не вид Option, то ваша программа ожидает наличие чего-то вместо ничего. Ваш код не должен будет обрабатывать оба исхода Some и None: он будет иметь только один исход для определённого значения. Код, пытающийся ничего не передавать в функцию, не будет даже собираться, поэтому ваша функция не должна проверять такой случай во время выполнения. Другой пример - это использование целого вида без знака, такого как u32, который заверяет, что свойство никогда не будет отрицательным.

-

Создание пользовательских видов для проверки

-

Давайте разовьём мысль использования системы видов Ржавчина чтобы убедиться, что у нас есть правильное значение, и рассмотрим создание пользовательского вида для валидации. Вспомним игру угадывания числа из Главы 2, в которой наш код просил пользователя угадать число между 1 и 100. Мы никогда не проверяли, что предположение пользователя лежит между этими числами, перед сравнением предположения с загаданным нами числом; мы только проверяли, что оно положительно. В этом случае последствия были не очень страшными: наши сообщения «Слишком много» или «Слишком мало», выводимые в окно вывода, все равно были правильными. Но было бы лучше подталкивать пользователя к правильным догадкам и иметь различное поведение для случаев, когда пользователь предлагает число за пределами ряда, и когда пользователь вводит, например, буквы вместо цифр.

-

Один из способов добиться этого - пытаться разобрать введённое значение как i32, а не как u32, чтобы разрешить возможно отрицательные числа, а затем добавить проверку для нахождение числа в ряде, например, так:

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    loop {
-        // --snip--
-
-        println!("Please input your guess.");
-
-        let mut guess = String::new();
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: i32 = match guess.trim().parse() {
-            Ok(num) => num,
-            Err(_) => continue,
-        };
-
-        if guess < 1 || guess > 100 {
-            println!("The secret number will be between 1 and 100.");
-            continue;
-        }
-
-        match guess.cmp(&secret_number) {
-            // --snip--
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => {
-                println!("You win!");
-                break;
-            }
-        }
-    }
-}
-

Выражение if проверяет, находится ли наше значение вне ряда, сообщает пользователю о неполадке и вызывает continue, чтобы начать следующую повторение цикла и попросить ввести другое число. После выражения if мы можем продолжить сравнение значения guess с загаданным числом, зная, что guess лежит в ряде от 1 до 100.

-

Однако это не наилучшее решение: если бы было чрезвычайно важно, чтобы программа работала только со значениями от 1 до 100, существовало бы много функций, требующих этого, то такая проверка в каждой функции была бы утомительной (и могла бы отрицательно повлиять на производительность).

-

Вместо этого можно создать новый вид и поместить проверки в функцию создания образца этого вида, не повторяя их везде. Таким образом, функции могут использовать новый вид в своих ярлыках и быть уверены в значениях, которые им передают. Приложение 9-13 показывает один из способов, как определить вид Guess, чтобы образец Guess создавался только при условии, что функция new получает значение от 1 до 100.

- -
#![allow(unused)]
-
-fn main() {
-}
-

Приложение 9-13. Вид Guess, который будет создавать образцы только для значений от 1 до 100

-

Сначала мы определяем устройство с именем Guess, которая имеет поле с именем value вида i32, в котором будет храниться число.

-

Затем мы выполняем сопряженную функцию new, создающую образцы значений вида Guess. Функция new имеет один свойство value вида i32, и возвращает Guess. Код в теле функции new проверяет, что значение value находится между 1 и 100. Если value не проходит эту проверку, мы вызываем panic!, которая оповестит программиста, написавшего вызывающий код, что в его коде есть ошибка, которую необходимо исправить, поскольку попытка создания Guess со значением value вне заданного ряда нарушает договор, на который полагается Guess::new. Условия, в которых Guess::new паникует, должны быть описаны в документации к API; мы рассмотрим соглашения о документации, указывающие на возможность появления panic! в документации API, которую вы создадите в Главе 14. Если value проходит проверку, мы создаём новый образец Guess, у которого значение поля value равно значению свойства value, и возвращаем Guess.

-

Затем мы выполняем способ с названием value, который заимствует self, не имеет других свойств, и возвращает значение вида i32. Этот способ иногда называют извлекатель (getter), потому что его цель состоит в том, чтобы извлечь данные из полей устройства и вернуть их. Этот открытый способ является необходимым, поскольку поле value устройства Guess является закрытым. Важно, чтобы поле value было закрытым, чтобы код, использующий устройство Guess, не мог устанавливать value напрямую: код снаружи звена должен использовать функцию Guess::new для создания образца Guess, таким образом обеспечивая, что у Guess нет возможности получить value, не проверенное условиями в функции Guess::new.

-

Функция, которая принимает или возвращает только числа от 1 до 100, может объявить в своей ярлыке, что она принимает или возвращает Guess, вместо i32, таким образом не будет необходимости делать дополнительные проверки в теле такой функции.

-

Итоги

-

Функции обработки ошибок в Ржавчина призваны помочь написанию более надёжного кода. Макрос panic! указывает , что ваша программа находится в состоянии, которое она не может обработать, и позволяет сказать этапу чтобы он прекратил своё выполнение, вместо попытки продолжить выполнение с неправильными или неверными значениями. Перечисление Result использует систему видов Rust, чтобы сообщить, что действия могут завершиться неудачей, и ваш код мог восстановиться. Можно использовать Result, чтобы сообщить вызывающему коду, что он должен обрабатывать вероятный успех или вероятную неудачу. Использование panic! и Result правильным образом сделает ваш код более надёжным перед лицом неизбежных неполадок.

-

Теперь, когда вы увидели полезные способы использования обобщённых видов Option и Result в встроенной библиотеке, мы поговорим о том, как работают обобщённые виды и как вы можете использовать их в своём коде.

-

Обобщённые виды, особенности и время жизни

-

Каждый язык программирования имеет в своём арсенале эффективные средства борьбы с повторением кода. В Ржавчина одним из таких средств являются обобщённые виды данных - generics. Это абстрактные подставные виды на место которых возможно поставить какой-либо определенный вид или другое свойство. Когда мы пишем код, мы можем выразить поведение обобщённых видов или их связь с другими обобщёнными видами, не зная какой вид будет использован на их месте при сборки и запуске кода.

-

Функции могут принимать свойства некоторого "обобщённого" вида вместо привычных "определенных" видов, вроде i32 или String. Подобно, функция принимает свойства с неизвестными заранее значениями, чтобы выполнять одинаковые действия над несколькими определенными значениями. На самом деле мы уже использовали обобщённые виды данных в Главе 6 (Option<T>), в Главе 8 (Vec<T> и HashMap<K, V>) и в Главе 9 (Result<T, E>). В этой главе вы узнаете, как определить собственные виды данных, функции и способы, используя возможности обобщённых видов.

-

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

-

После этого мы изучим как использовать особенности (traits) для определения поведения в обобщённом виде. Можно соединенять особенности с обобщёнными видами, чтобы обобщённый вид мог принимать только такие виды, которые имеют определённое поведение, а не все подряд.

-

В конце мы обсудим времена жизни (lifetimes), вариации обобщённых видов, которые дают сборщику сведения о том, как сроки жизни ссылок относятся друг к другу. Времена жизни позволяют нам указать дополнительную сведения об "одолженных" (borrowed) значениях, которая позволит сборщику удостовериться в соблюдения правил используемых ссылок в тех случаейх, когда сборщик не может сделать это самостоятельно .

-

Удаление повторения кода с помощью выделения общей возможности

-

В обобщениях мы можем заменить определенный вид на "заполнитель" (placeholder), обозначающую несколько видов, что позволяет удалить повторяющийся код. Прежде чем углубляться в правила написания обобщённых видов, давайте сначала посмотрим, как удалить повторение, не задействуя гибкие виды, путём извлечения функции, которая заменяет определённые значения заполнителем, представляющим несколько значений. Затем мы применим ту же технику для извлечения гибкой функции! Изучив, как распознать повторяющийся код, который можно извлечь в функцию, вы начнёте распознавать повторяющийся код, который может использовать обобщённые виды.

-

Начнём с короткой программы в приложении 10-1, которая находит наибольшее число в списке.

-

Файл: src/main.rs

-
fn main() {
-    let number_list = vec![34, 50, 25, 100, 65];
-
-    let mut largest = &number_list[0];
-
-    for number in &number_list {
-        if number > largest {
-            largest = number;
-        }
-    }
-
-    println!("The largest number is {largest}");
-    assert_eq!(*largest, 100);
-}
-

Приложение 10-1: Поиск наибольшего числа в списке чисел

-

Сохраним список целых чисел в переменной number_list и поместим первое значение из списка в переменную largest. Далее, переберём все элементы списка, и, если текущий элемент больше числа сохранённого в переменной largest, заменим значение в этой переменной. Если текущий элемент меньше или равен "наибольшему", найденному ранее, значение переменной оставим прежним и перейдём к следующему элементу списка. После перебора всех элементов списка переменная largest должна содержать наибольшее значение, которое в нашем случае будет равно 100.

-

Теперь перед нами стоит задача найти наибольшее число в двух разных списках. Для этого мы можем повторять код из приложения 10-1 и использовать ту же логику в двух разных местах программы, как показано в приложении 10-2.

-

Файл: src/main.rs

-
fn main() {
-    let number_list = vec![34, 50, 25, 100, 65];
-
-    let mut largest = &number_list[0];
-
-    for number in &number_list {
-        if number > largest {
-            largest = number;
-        }
-    }
-
-    println!("The largest number is {largest}");
-
-    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
-
-    let mut largest = &number_list[0];
-
-    for number in &number_list {
-        if number > largest {
-            largest = number;
-        }
-    }
-
-    println!("The largest number is {largest}");
-}
-

Приложение 10-2: Код для поиска наибольшего числа в двух списках чисел

-

Несмотря на то, что код программы работает, повторение кода утомительно и подвержено ошибкам. При внесении изменений мы должны не забыть обновить каждое место, где код повторяется.

-

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

-

В приложении 10-3 мы извлекаем код, который находит наибольшее число, в функцию с именем largest. Затем мы вызываем функцию, чтобы найти наибольшее число в двух списках из приложения 10-2. Мы также можем использовать эту функцию для любого другого списка значений i32 , который может встретиться позже.

-

Файл: src/main.rs

-
fn largest(list: &[i32]) -> &i32 {
-    let mut largest = &list[0];
-
-    for item in list {
-        if item > largest {
-            largest = item;
-        }
-    }
-
-    largest
-}
-
-fn main() {
-    let number_list = vec![34, 50, 25, 100, 65];
-
-    let result = largest(&number_list);
-    println!("The largest number is {result}");
-    assert_eq!(*result, 100);
-
-    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
-
-    let result = largest(&number_list);
-    println!("The largest number is {result}");
-    assert_eq!(*result, 6000);
-}
-

Приложение 10-3: Абстрактный код для поиска наибольшего числа в двух списках

-

Функция largest имеет свойство с именем list, который представляет любой срез значений вида i32, которые мы можем передать в неё. В итоге вызова функции, код выполнится с определенными, переданными в неё значениями.

-

Итак, вот шаги выполненные для изменения кода из приложения 10-2 в приложение 10-3:

-
    -
  1. Определить повторяющийся код.
  2. -
  3. Извлечь повторяющийся код и поместить его в тело функции, определив входные и выходные значения этого кода в ярлыке функции.
  4. -
  5. Обновить и заменить два участка повторяющегося кода вызовом одной функции.
  6. -
-

Далее, чтобы уменьшить повторение кода, мы воспользуемся теми же шагами для обобщённых видов. Обобщённые виды позволяют работать над абстрактными видами таким же образом, как тело функции может работать над абстрактным списком list вместо определенных значений.

-

Например, у нас есть две функции: одна ищет наибольший элемент внутри среза значений вида i32, а другая внутри среза значений вида char. Как уменьшить такое повторение? Давайте выяснять!

-

Обобщённые виды данных

-

Мы используем обобщённые виды данных для объявления функций или устройств, которые затем можно использовать с различными определенными видами данных. Давайте сначала посмотрим, как объявлять функции, устройства, перечисления и способы, используя обобщённые виды данных. Затем мы обсудим, как обобщённые виды данных влияют на производительность кода.

-

В объявлении функций

-

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

-

Рассмотрим пример с функцией largest. Приложение 10-4 показывает две функции, каждая из которых находит самое большое значение в срезе своего вида. Позже мы объединим их в одну функцию, использующую обобщённые виды данных.

-

Файл: src/main.rs

-
fn largest_i32(list: &[i32]) -> &i32 {
-    let mut largest = &list[0];
-
-    for item in list {
-        if item > largest {
-            largest = item;
-        }
-    }
-
-    largest
-}
-
-fn largest_char(list: &[char]) -> &char {
-    let mut largest = &list[0];
-
-    for item in list {
-        if item > largest {
-            largest = item;
-        }
-    }
-
-    largest
-}
-
-fn main() {
-    let number_list = vec![34, 50, 25, 100, 65];
-
-    let result = largest_i32(&number_list);
-    println!("The largest number is {result}");
-    assert_eq!(*result, 100);
-
-    let char_list = vec!['y', 'm', 'a', 'q'];
-
-    let result = largest_char(&char_list);
-    println!("The largest char is {result}");
-    assert_eq!(*result, 'y');
-}
-

Приложение 10-4: две функции, отличающиеся только именем и видом обрабатываемых данных

-

Функция largest_i32 уже встречалась нам: мы извлекли её в приложении 10-3, когда боролись с повторением кода — она находит наибольшее значение вида i32 в срезе. Функция largest_char находит самое большое значение вида char в срезе. Тело у этих функций одинаковое, поэтому давайте избавимся от повторяемлшл кода, используя свойство обобщённого вида в одной функции.

-

Для свойствоизации видов данных в новой объявляемой функции нам нужно дать имя обобщённому виду — так же, как мы это делаем для переменных функций. Можно использовать любой определитель для имени свойства вида, но мы будем использовать T, потому что по соглашению имена свойств в Ржавчина должны быть короткими (обычно длиной в один символ), а именование видов в Ржавчина делается в наставлении UpperCamelCase. Сокращение слова «type» до одной буквы T является обычным выбором большинства программистов, использующих язык Rust.

-

Когда мы используем свойство в теле функции, мы должны объявить имя свойства в ярлыке, чтобы сборщик знал, что означает это имя. Подобно когда мы используем имя вида свойства в ярлыке функции, мы должны объявить это имя раньше, чем мы его используем. Чтобы определить обобщённую функцию largest, поместим объявление имён свойств в треугольные скобки <> между именем функции и списком свойств, как здесь:

-
fn largest<T>(list: &[T]) -> &T {
-

Объявление читается так: функция largest является обобщённой по виду T. Эта функция имеет один свойство с именем list, который является срезом значений с видом данных T. Функция largest возвращает значение этого же вида T.

-

Приложение 10-5 показывает определение функции largest с использованием обобщённых видов данных в её ярлыке. Приложение также показывает, как мы можем вызвать функцию со срезом данных вида i32 или char. Данный код пока не будет собираться, но мы исправим это к концу раздела.

-

Файл: src/main.rs

-
fn largest<T>(list: &[T]) -> &T {
-    let mut largest = &list[0];
-
-    for item in list {
-        if item > largest {
-            largest = item;
-        }
-    }
-
-    largest
-}
-
-fn main() {
-    let number_list = vec![34, 50, 25, 100, 65];
-
-    let result = largest(&number_list);
-    println!("The largest number is {result}");
-
-    let char_list = vec!['y', 'm', 'a', 'q'];
-
-    let result = largest(&char_list);
-    println!("The largest char is {result}");
-}
-

Приложение 10-5: функция largest, использующая свойства обобщённого типа; пока ещё не собирается

-

Если мы соберем программу сейчас, мы получим следующую ошибку:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0369]: binary operation `>` cannot be applied to type `&T`
- --> src/main.rs:5:17
-  |
-5 |         if item > largest {
-  |            ---- ^ ------- &T
-  |            |
-  |            &T
-  |
-help: consider restricting type parameter `T`
-  |
-1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
-  |             ++++++++++++++++++++++
-
-For more information about this error, try `rustc --explain E0369`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

В подсказке упоминается std::cmp::PartialOrd, который является особенностью. Мы поговорим про особенности в следующем разделе. Сейчас ошибка в функции largest указывает, что функция не будет работать для всех возможных видов T. Так как мы хотим сравнивать значения вида T в теле функции, мы можем использовать только те виды, данные которых можно упорядочить: можем упорядочить — значит, можем и сравнить. Чтобы можно было задействовать сравнения, обычная библиотека имеет особенность std::cmp::PartialOrd, который вы можете выполнить для видов (смотрите дополнение С для большей сведений про данный особенность). Следуя совету в сообщении сборщика, ограничим вид T теми исходами, которые поддерживают особенность PartialOrd, и тогда пример успешно собирается, так как обычная библиотека выполняет PartialOrd как для вида i32, так и для вида char.

-

В определении устройств

-

Мы также можем определить устройства, использующие обобщённые виды в одном или нескольких своих полях, с помощью правил написания <>. Приложение 10-6 показывает, как определить устройство Point<T>, чтобы хранить поля координат x и y любого вида данных.

-

Файл: src/main.rs

-
struct Point<T> {
-    x: T,
-    y: T,
-}
-
-fn main() {
-    let integer = Point { x: 5, y: 10 };
-    let float = Point { x: 1.0, y: 4.0 };
-}
-

Приложение 10-6: устройства Point, содержащая поля x и y вида T

-

правила написания использования обобщённых видов в определении устройства очень похож на правила написания в определении функции. Сначала мы объявляем имена видов свойств внутри треугольных скобок сразу после названия устройства. Затем мы можем использовать обобщённые виды в определении устройства в тех местах, где ранее мы указывали бы определенные виды.

-

Так как мы используем только один обобщённый вид данных для определения устройства Point<T>, это определение означает, что устройства Point<T> является обобщённой с видом T, и оба поля x и y имеют одинаковый вид, каким бы он не являлся. Если мы создадим образец устройства Point<T> со значениями разных видов, как показано в приложении 10-7, наш код не собирается.

-

Файл: src/main.rs

-
struct Point<T> {
-    x: T,
-    y: T,
-}
-
-fn main() {
-    let wont_work = Point { x: 5, y: 4.0 };
-}
-

Приложение 10-7: поля x и y должны быть одного вида, так как они имеют один и тот же обобщённый вид T

-

В этом примере, когда мы присваиваем целочисленное значение 5 переменной x , мы сообщаем сборщику, что обобщённый вид T будет целым числом для этого образца Point<T>. Затем, когда мы указываем значение 4.0 (имеющее вид, отличный от целого числа) для y, который по нашему определению должен иметь тот же вид, что и x, мы получим ошибку несоответствия видов:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0308]: mismatched types
- --> src/main.rs:7:38
-  |
-7 |     let wont_work = Point { x: 5, y: 4.0 };
-  |                                      ^^^ expected integer, found floating-point number
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

Чтобы определить устройство Point, где оба значения x и y являются обобщёнными, но различными видами, можно использовать несколько свойств обобщённого вида. Например, в приложении 10-8 мы изменим определение Point таким образом, чтобы оно использовало обобщённые виды T и U, где x имеет вид T а y имеет вид U.

-

Файл: src/main.rs

-
struct Point<T, U> {
-    x: T,
-    y: U,
-}
-
-fn main() {
-    let both_integer = Point { x: 5, y: 10 };
-    let both_float = Point { x: 1.0, y: 4.0 };
-    let integer_and_float = Point { x: 5, y: 4.0 };
-}
-

Приложение 10-8: устройства Point<T, U> обобщена для двух видов, так что x и y могут быть значениями разных видов

-

Теперь разрешены все показанные образцы вида Point! В объявлении можно использовать сколь угодно много свойств обобщённого вида, но если делать это в большом количестве, код будет тяжело читать. Если в вашем коде требуется много обобщённых видов, возможно, стоит разбить его на более мелкие части.

-

В определениях перечислений

-

Как и устройства, перечисления также могут хранить обобщённые виды в своих исхода.. Давайте ещё раз посмотрим на перечисление Option<T>, предоставленное встроенной библиотекой, которое мы использовали в главе 6:

-
#![allow(unused)]
-fn main() {
-enum Option<T> {
-    Some(T),
-    None,
-}
-}
-

Это определение теперь должно быть вам более понятно. Как видите, перечисление Option<T> является обобщённым по виду T и имеет два исхода: исход Some, который содержит одно значение вида T, и исход None, который не содержит никакого значения. Используя перечисление Option<T>, можно выразить абстрактную подход необязательного значения — и так как Option<T> является обобщённым, можно использовать эту абстракцию независимо от того, каким будет вид необязательного значения.

-

Перечисления также могут использовать несколько обобщённых видов. Определение перечисления Result, которое мы упоминали в главе 9, является примером такого использования:

-
#![allow(unused)]
-fn main() {
-enum Result<T, E> {
-    Ok(T),
-    Err(E),
-}
-}
-

Перечисление Result имеет два обобщённых вида: T и E — и два исхода: Ok, который содержит вид T, и Err, содержащий вид E. С таким определением удобно использовать перечисление Result везде, где действия могут быть выполнены успешно (возвращая значение вида T) или неуспешно (возвращая ошибку вида E). Это то, что мы делали при открытии файла в приложении 9-3, где T заполнялось видом std::fs::File, если файл был открыт успешно, либо E заполнялось видом std::io::Error, если при открытии файла возникали какие-либо сбоев.

-

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

-

В определении способов

-

Мы можем выполнить способы для устройств и перечислений (как мы делали в главе 5) и в определениях этих способов также использовать обобщённые виды. В приложении 10-9 показана устройства Point<T>, которую мы определили в приложении 10-6, с добавленным для неё способом x.

-

Файл: src/main.rs

-
struct Point<T> {
-    x: T,
-    y: T,
-}
-
-impl<T> Point<T> {
-    fn x(&self) -> &T {
-        &self.x
-    }
-}
-
-fn main() {
-    let p = Point { x: 5, y: 10 };
-
-    println!("p.x = {}", p.x());
-}
-

Приложение 10-9: Выполнение способа с именем x у устройства Point<T>, которая будет возвращать ссылку на поле x вида T

-

Здесь мы определили способ с именем x у устройства Point<T>, который возвращает ссылку на данные в поле x.

-

Обратите внимание, что мы должны объявить T сразу после impl . В этом случае мы можем использовать T для указания на то, что выполняем способ для вида Point<T>. Объявив T гибким видом сразу после impl , Ржавчина может определить, что вид в угловых скобках в Point является гибким, а не определенным видом. Мы могли бы выбрать другое имя для этого обобщённого свойства, отличное от имени, использованного в определении устройства, но обычно используют одно и то же имя. Способы, написанные внутри раздела impl , который использует обобщённый вид, будут определены для любого образца вида, независимо от того, какой определенный вид в конечном итоге будет подставлен вместо этого обобщённого.

-

Мы можем также указать ограничения, какие обобщённые виды разрешено использовать при определении способов. Например, мы могли бы выполнить способы только для образцов вида Point<f32>, а не для образцов Point<T>, в которых используется произвольный обобщённый вид. В приложении 10-10 мы используем определенный вид f32, что означает, что мы не определяем никакие виды после impl.

-

Файл: src/main.rs

-
struct Point<T> {
-    x: T,
-    y: T,
-}
-
-impl<T> Point<T> {
-    fn x(&self) -> &T {
-        &self.x
-    }
-}
-
-impl Point<f32> {
-    fn distance_from_origin(&self) -> f32 {
-        (self.x.powi(2) + self.y.powi(2)).sqrt()
-    }
-}
-
-fn main() {
-    let p = Point { x: 5, y: 10 };
-
-    println!("p.x = {}", p.x());
-}
-

Приложение 10-10: разделimpl, который применяется только к устройстве, имеющей определенный вид для свойства обобщённого вида T

-

Этот код означает, что вид Point<f32> будет иметь способ с именем distance_from_origin, а другие образцы Point<T>, где T имеет вид, отличный от f32, не будут иметь этого способа. Способ вычисляет, насколько далеко наша точка находится от точки с координатами (0.0, 0.0), и использует математические действия, доступные только для видов с плавающей точкой.

-

Свойства обобщённого вида, которые мы используем в определении устройства, не всегда совпадают с подобиями, использующимися в ярлыках способов этой устройства. Чтобы пример был более очевидным, в приложении 10-11 используются обобщённые виды X1 и Y1 для определения устройства Point и виды X2 Y2 для ярлыки способа mixup. Способ создаёт новый образец устройства Point, где значение x берётся из self Point (имеющей вид X1), а значение y - из переданной устройства Point (где эта переменная имеет вид Y2).

-

Файл: src/main.rs

-
struct Point<X1, Y1> {
-    x: X1,
-    y: Y1,
-}
-
-impl<X1, Y1> Point<X1, Y1> {
-    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
-        Point {
-            x: self.x,
-            y: other.y,
-        }
-    }
-}
-
-fn main() {
-    let p1 = Point { x: 5, y: 10.4 };
-    let p2 = Point { x: "Hello", y: 'c' };
-
-    let p3 = p1.mixup(p2);
-
-    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
-}
-

Приложение 10-11: способ, использующий обобщённые виды, отличающиеся от видов, используемых в определении устройства

-

В функции main мы определили вид Point, который имеет вид i32 для x (со значением 5 ) и вид f64 для y (со значением 10.4). Переменная p2 является устройством Point, которая имеет строковый срез для x (со значением «Hello») и char для y (со значением c). Вызов mixup на p1 с переменнаяом p2 создаст для нас образец устройства p3, который будет иметь вид i32 для x (потому что x взят из p1). Переменная p3 будет иметь вид char для y (потому что y взят из p2). Вызов макроса println! выведет p3.x = 5, p3.y = c.

-

Цель этого примера — отобразить случай, в которой некоторые обобщённые свойства объявлены с помощью impl, а некоторые объявлены в определении способа. Здесь обобщённые свойства X1 и Y1 объявляются после impl, потому что они относятся к определению устройства. Обобщённые свойства X2 и Y2 объявляются после fn mixup, так как они относятся только к способу.

-

Производительность кода, использующего обобщённые виды

-

Вы могли бы задаться вопросом, возникают ли какие-нибудь дополнительные издержки при использовании свойств обобщённого вида. Хорошая новость в том, что при использовании обобщённых видов ваша программа работает ничуть ни медленнее, чем если бы она работала с использованием определенных видов.

-

В Ржавчина это достигается во время сборки при помощи мономорфизации кода, использующего обобщённые виды. Мономорфизация — это этап превращения обобщённого кода в определенный код путём подстановки определенных видов, использующихся при сборки. В этом этапе сборщик выполняет шаги, противоположные тем, которые мы использовали для создания обобщённой функции в приложении 10-5: он просматривает все места, где вызывается обобщённый код, и порождает код для определенных видов, использовавшихся для вызова в обобщённом.

-

Давайте посмотрим, как это работает при использовании перечисления Option<T> из встроенной библиотеки:

-
#![allow(unused)]
-fn main() {
-let integer = Some(5);
-let float = Some(5.0);
-}
-

Когда Ржавчина собирает этот код, он выполняет мономорфизацию. Во время этого этапа сборщик считывает значения, которые были использованы в образцах Option<T>, и определяет два вида Option<T>: один для вида i32, а другой — для f64. Таким образом, он разворачивает обобщённое определение Option<T> в два определения, именно для i32 и f64, тем самым заменяя обобщённое определение определенными.

-

Мономорфизированная исполнение кода выглядит примерно так (сборщик использует имена, отличные от тех, которые мы используем здесь для отображения):

-

Файл: src/main.rs

-
enum Option_i32 {
-    Some(i32),
-    None,
-}
-
-enum Option_f64 {
-    Some(f64),
-    None,
-}
-
-fn main() {
-    let integer = Option_i32::Some(5);
-    let float = Option_f64::Some(5.0);
-}
-

Обобщённое Option<T> заменяется определенными определениями, созданными сборщиком. Поскольку Ржавчина собирает обобщённый код в код, определяющий вид в каждом образце, мы не платим за использование обобщённых видов во время выполнения. Когда код запускается, он работает точно так же, как если бы мы сделали повторение каждое определение вручную. Этап мономорфизации делает обобщённые виды Ржавчина чрезвычайно эффективными во время выполнения.

-

Особенности: определение общего поведения

-

Особенность сообщает сборщику Ржавчина о возможности, которой обладает определённый вид и которой он может поделиться с другими видами. Можно использовать особенности, чтобы определять общее поведение абстрактным способом. Мы можем использовать ограничение особенности (trait bounds) чтобы указать, что общим видом может быть любой вид, который имеет определённое поведение.

-
-

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

-
-

Определение особенности

-

Поведение вида определяется теми способами, которые мы можем вызвать у данного вида. Различные виды разделяют одинаковое поведение, если мы можем вызвать одни и те же способы у этих видов. Определение особенностей - это способ собъединять ярлыки способов вместе для того, чтобы описать общее поведение, необходимое для достижения определённой цели.

-

Например, пусть есть несколько устройств, которые имеют различный вид и различный размер текста: устройства NewsArticle, которая содержит новость, напечатанную в каком-то месте мира; устройства Tweet, которая содержит 280 символьную строку твита и мета-данные, обозначающие является ли твит новым или ответом на другой твит.

-

Мы хотим создать ящик библиотеки медиа-агрегатора aggregator, которая может отображать сводку данных сохранённых в образцах устройств NewsArticle или Tweet. Чтобы этого достичь, нам необходимо иметь возможность для каждой устройства получить короткую сводку на основе имеющихся данных, и для этого мы запросим сводку вызвав способ summarize. Приложение 10-12 показывает определение особенности Summary, который выражает это поведение.

-

Файл: src/lib.rs

-
pub trait Summary {
-    fn summarize(&self) -> String;
-}
-

Приложение 10-12: Определение особенности Summary, который содержит поведение предоставленное способом summarize

-

Здесь мы объявляем особенность с использованием ключевого слова trait, а затем его название, которым в нашем случае является Summary. Также мы объявляем ящик как pub что позволяет ящикам, зависящим от нашего ящика, тоже использовать наш ящик, что мы увидим в последующих примерах. Внутри фигурных скобок объявляются ярлыки способов, которые описывают поведения видов, выполняющих данный особенность, в данном случае поведение определяется только одной ярлыком способа fn summarize(&self) -> String.

-

После ярлыки способа, вместо предоставления выполнения в фигурных в скобках, мы используем точку с запятой. Каждый вид, выполняющий данный особенность, должен предоставить своё собственное поведение для данного способа. Сборщик обеспечит, что любой вид содержащий особенность Summary, будет также иметь и способ summarize объявленный с точно такой же ярлыком.

-

Особенность может иметь несколько способов в описании его тела: ярлыки способов перечисляются по одной на каждой строке и должны закачиваться символом ;.

-

Выполнение особенности у вида

-

Теперь, после того как мы определили желаемое поведение используя особенность Summary, можно выполнить его у видов в нашем медиа-агрегаторе. Приложение 10-13 показывает выполнение особенности Summary у устройства NewsArticle, которая использует для создания сводки в способе summarize заголовок, автора и место обнародования статьи. Для устройства Tweet мы определяем выполнение summarize используя имя пользователя и следующий за ним полный текст твита, полагая что содержание твита уже ограничено 280 символами.

-

Файл: src/lib.rs

-
pub trait Summary {
-    fn summarize(&self) -> String;
-}
-
-pub struct NewsArticle {
-    pub headline: String,
-    pub location: String,
-    pub author: String,
-    pub content: String,
-}
-
-impl Summary for NewsArticle {
-    fn summarize(&self) -> String {
-        format!("{}, by {} ({})", self.headline, self.author, self.location)
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize(&self) -> String {
-        format!("{}: {}", self.username, self.content)
-    }
-}
-

Приложение 10-13: Выполнение особенности Summary для устройств NewsArticle и Tweet

-

Выполнение особенности у вида подобна выполнения обычных способов. Разница в том что после impl мы ставим имя особенности, который мы хотим выполнить, затем используем ключевое слово for, а затем указываем имя вида, для которого мы хотим сделать выполнение особенности. Внутри раздела impl мы помещаем ярлык способа объявленную в особенности. Вместо добавления точки с запятой в конце, после каждой ярлыки используются фигурные скобки и тело способа заполняется определенным поведением, которое мы хотим получить у способов особенности для определенного вида.

-

Теперь когда библиотека выполнила особенность Summary для NewsArticle и Tweet, программисты использующие ящик могут вызывать способы особенности у образцов видов NewsArticle и Tweet точно так же как если бы это были обычные способы. Единственное отличие состоит в том, что программист должен ввести особенность в область видимости точно так же как и виды. Здесь пример того как двоичный ящик может использовать наш aggregator:

-
use aggregator::{Summary, Tweet};
-
-fn main() {
-    let tweet = Tweet {
-        username: String::from("horse_ebooks"),
-        content: String::from(
-            "of course, as you probably already know, people",
-        ),
-        reply: false,
-        retweet: false,
-    };
-
-    println!("1 new tweet: {}", tweet.summarize());
-}
-

Данный код напечатает: 1 new tweet: horse_ebooks: of course, as you probably already know, people.

-

Другие ящики, которые зависят от aggregator, тоже могу включить особенность Summary в область видимости для выполнения Summary в их собственных видах. Одно ограничение, на которое следует обратить внимание, заключается в том, что мы можем выполнить особенность для вида только в том случае, если хотя бы один из особенностей вида является местным для нашего ящика. Например, мы можем выполнить обычный библиотечный особенность Display на собственном виде Tweet как часть возможности нашего ящика aggregator потому что вид Tweet является местным для ящика aggregator. Также мы можем выполнить Summary для Vec<T> в нашем ящике aggregator, потому что особенность Summary является местным для нашего ящика aggregator.

-

Но мы не можем выполнить внешние особенности для внешних видов. Например, мы не можем выполнить особенность Display для Vec<T> внутри нашего ящика aggregator, потому что Display и Vec<T> оба определены в встроенной библиотеке а не местно в нашем ящике aggregator. Это ограничение является частью свойства называемого согласованность (coherence), а ещё точнее сиротское правило (orphan rule), которое называется так потому что не представлен родительский вид. Это правило заверяет, что код других людей не может сломать ваш код и наоборот. Без этого правила два ящика могли бы выполнить один особенность для одинакового вида и Ржавчина не сможет понять, какой выполнением нужно пользоваться.

-

Выполнение поведения по умолчанию

-

Иногда полезно иметь поведение по умолчанию для некоторых или всех способов в особенности вместо того, чтобы требовать выполнения всех способов в каждом виде, выполняющим данный особенность. Затем, когда мы выполняем особенность для определённого вида, можно сохранить или переопределить поведение каждого способа по умолчанию уже внутри видов.

-

В примере 10-14 показано, как указать строку по умолчанию для способа summarize из особенности Summary вместо определения только ярлыки способа, как мы сделали в примере 10-12.

-

Файл: src/lib.rs

-
pub trait Summary {
-    fn summarize(&self) -> String {
-        String::from("(Read more...)")
-    }
-}
-
-pub struct NewsArticle {
-    pub headline: String,
-    pub location: String,
-    pub author: String,
-    pub content: String,
-}
-
-impl Summary for NewsArticle {}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize(&self) -> String {
-        format!("{}: {}", self.username, self.content)
-    }
-}
-

Приложение 10-14: Определение особенности Summary с выполнением способа summarize по умолчанию

-

Для использования выполнения по умолчанию при создании сводки у образцов NewsArticle вместо определения пользовательской выполнения, мы указываем пустой разделimpl с impl Summary for NewsArticle {}.

-

Хотя мы больше не определяем способ summarize непосредственно в NewsArticle, мы предоставили выполнение по умолчанию и указали, что NewsArticle выполняет особенность Summary. В итоге мы всё ещё можем вызвать способ summarize у образца NewsArticle, например так:

-
use aggregator::{self, NewsArticle, Summary};
-
-fn main() {
-    let article = NewsArticle {
-        headline: String::from("Penguins win the Stanley Cup Championship!"),
-        location: String::from("Pittsburgh, PA, USA"),
-        author: String::from("Iceburgh"),
-        content: String::from(
-            "The Pittsburgh Penguins once again are the best \
-             hockey team in the NHL.",
-        ),
-    };
-
-    println!("New article available! {}", article.summarize());
-}
-

Этот код печатает New article available! (Read more...) .

-

Создание выполнения по умолчанию не требует от нас изменений чего-либо в выполнения Summary для Tweet в приложении 10-13. Причина заключается в том, что правила написания для переопределения выполнения по умолчанию является таким же, как правила написания для выполнения способа особенности, который не имеет выполнения по умолчанию.

-

Выполнения по умолчанию могут вызывать другие способы в том же особенности, даже если эти другие способы не имеют выполнения по умолчанию. Таким образом, особенность может предоставить много полезной возможности и только требует от разработчиков указывать небольшую его часть. Например, мы могли бы определить особенность Summary имеющий способ summarize_author, выполнение которого требуется, а затем определить способ summarize который имеет выполнение по умолчанию, которая внутри вызывает способ summarize_author:

-
pub trait Summary {
-    fn summarize_author(&self) -> String;
-
-    fn summarize(&self) -> String {
-        format!("(Read more from {}...)", self.summarize_author())
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize_author(&self) -> String {
-        format!("@{}", self.username)
-    }
-}
-

Чтобы использовать такую исполнение особенности Summary, нужно только определить способ summarize_author, при выполнения особенности для вида:

-
pub trait Summary {
-    fn summarize_author(&self) -> String;
-
-    fn summarize(&self) -> String {
-        format!("(Read more from {}...)", self.summarize_author())
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize_author(&self) -> String {
-        format!("@{}", self.username)
-    }
-}
-

После того, как мы определим summarize_author, можно вызвать summarize для образцов устройства Tweet и выполнение по умолчанию способа summarize будет вызывать определение summarize_author которое мы уже предоставили. Так как мы выполнили способ summarize_author особенности Summary, то особенность даёт нам поведение способа summarize без необходимости писать код.

-
use aggregator::{self, Summary, Tweet};
-
-fn main() {
-    let tweet = Tweet {
-        username: String::from("horse_ebooks"),
-        content: String::from(
-            "of course, as you probably already know, people",
-        ),
-        reply: false,
-        retweet: false,
-    };
-
-    println!("1 new tweet: {}", tweet.summarize());
-}
-

Этот код печатает 1 new tweet: (Read more from @horse_ebooks...) .

-

Обратите внимание, что невозможно вызвать выполнение по умолчанию из переопределённой выполнения того же способа.

-

Особенности как свойства

-

Теперь, когда вы знаете, как определять и выполнить особенности, можно изучить, как использовать особенности, чтобы определить функции, которые принимают много различных видов. Мы будем использовать особенность Summary, выполненный для видов NewsArticle и Tweet в приложении 10-13, чтобы определить функцию notify, которая вызывает способ summarize для его свойства item, который имеет некоторый вид, выполняющий особенность Summary. Для этого мы используем правила написания impl Trait примерно так:

-
pub trait Summary {
-    fn summarize(&self) -> String;
-}
-
-pub struct NewsArticle {
-    pub headline: String,
-    pub location: String,
-    pub author: String,
-    pub content: String,
-}
-
-impl Summary for NewsArticle {
-    fn summarize(&self) -> String {
-        format!("{}, by {} ({})", self.headline, self.author, self.location)
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize(&self) -> String {
-        format!("{}: {}", self.username, self.content)
-    }
-}
-
-pub fn notify(item: &impl Summary) {
-    println!("Breaking news! {}", item.summarize());
-}
-

Вместо определенного вида у свойства item указывается ключевое слово impl и имя особенности. Этот свойство принимает любой вид, который выполняет указанный особенность. В теле notify мы можем вызывать любые способы у образца item , которые приходят с особенностью Summary, такие как способ summarize. Мы можем вызвать notify и передать в него любой образец NewsArticle или Tweet. Код, который вызывает данную функцию с любым другим видом, таким как String или i32, не будет собираться, потому что эти виды не выполняют особенность Summary.

- -

-

правила написания ограничения особенности

-

правила написания impl Trait работает для простых случаев, но на самом деле является синтаксическим сахаром для более длинной видовы, которая называется ограничением особенности (trait bound); это выглядит так:

-
pub fn notify<T: Summary>(item: &T) {
-    println!("Breaking news! {}", item.summarize());
-}
-

Эта более длинная разновидность эквивалентна примеру в предыдущем разделе, но она более многословна. Мы помещаем объявление свойства обобщённого вида с ограничением особенности после двоеточия внутри угловых скобок.

-

правила написания impl Trait удобен и делает код более сжатым в простых случаях, в то время как более полный правила написания с ограничением особенности в других случаях может выразить большую сложность. Например, у нас может быть два свойства, которые выполняют особенность Summary. Использование правил написания impl Trait выглядит так:

-
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
-

Использовать impl Trait удобнее если мы хотим разрешить функции иметь разные виды для item1 и item2 (но оба вида должны выполнить Summary). Если же мы хотим заставить оба свойства иметь один и тот же вид, то мы должны использовать ограничение особенности так:

-
pub fn notify<T: Summary>(item1: &T, item2: &T) {
-

Обобщённый вид T указан для видов свойств item1 и item2 и ограничивает функцию так, что определенные значения видов переданные переменнойми для item1 и item2 должны быть одинаковыми.

-

Задание нескольких границ особенностей с помощью правил написания +

-

Также можно указать более одного ограничения особенности. Допустим, мы хотели бы чтобы notify использовал как изменение -вывода так и summarize для свойства item:
тогда мы указываем что в notify свойство item должен выполнить оба особенности Display и Summary. Мы можем сделать это используя правила написания +:

-
pub fn notify(item: &(impl Summary + Display)) {
-

правила написания + также допустим с ограничениями особенности для обобщённых видов:

-
pub fn notify<T: Summary + Display>(item: &T) {
-

При наличии двух ограничений особенности, тело способа notify может вызывать summarize и использовать {} для изменения item при его печати.

-

Более ясные границы особенности с помощью where

-

Использование слишком большого количества ограничений особенности имеет свои недостатки. Каждый обобщённый вид имеет свои границы особенности, поэтому функции с несколькими свойствами обобщённого вида могут содержать много сведений об ограничениях между названием функции и списком её свойств затрудняющих чтение ярлыки. По этой причине в Ржавчина есть иной правила написания для определения ограничений особенности внутри предложения where после ярлыки функции. Поэтому вместо того, чтобы писать так:

-
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
-

можно использовать where таким образом:

-
fn some_function<T, U>(t: &T, u: &U) -> i32
-where
-    T: Display + Clone,
-    U: Clone + Debug,
-{
-    unimplemented!()
-}
-

Ярлык этой функции менее загромождена: название функции, список свойств, и возвращаемый вид находятся рядом, а ярлык не содержит в себе множество ограничений особенности.

-

Возврат значений вида выполняющего определённый особенность

-

Также можно использовать правила написания impl Trait в возвращаемой позиции, чтобы вернуть значение некоторого вида выполняющего особенность, как показано здесь:

-
pub trait Summary {
-    fn summarize(&self) -> String;
-}
-
-pub struct NewsArticle {
-    pub headline: String,
-    pub location: String,
-    pub author: String,
-    pub content: String,
-}
-
-impl Summary for NewsArticle {
-    fn summarize(&self) -> String {
-        format!("{}, by {} ({})", self.headline, self.author, self.location)
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize(&self) -> String {
-        format!("{}: {}", self.username, self.content)
-    }
-}
-
-fn returns_summarizable() -> impl Summary {
-    Tweet {
-        username: String::from("horse_ebooks"),
-        content: String::from(
-            "of course, as you probably already know, people",
-        ),
-        reply: false,
-        retweet: false,
-    }
-}
-

Используя impl Summary для возвращаемого вида, мы указываем, что функция returns_summarizable возвращает некоторый вид, который выполняет особенность Summary без обозначения определенного вида. В этом случае returns_summarizable возвращает Tweet, но код, вызывающий эту функцию, этого не знает.

-

Возможность возвращать вид, который определяется только выполняемым им признаком, особенно полезна в среде замыканий и повторителей, которые мы рассмотрим в Главе 13. Замыкания и повторители создают виды, которые знает только сборщик или виды, которые очень долго указывать. правила написания impl Trait позволяет кратко указать, что функция возвращает некоторый вид, который выполняет особенность Iterator без необходимости писать очень длинный вид.

-

Однако, impl Trait возможно использовать, если возвращаете только один вид. Например, данный код, который возвращает значения или вида NewsArticle или вида Tweet, но в качестве возвращаемого вида объявляет impl Summary , не будет работать:

-
pub trait Summary {
-    fn summarize(&self) -> String;
-}
-
-pub struct NewsArticle {
-    pub headline: String,
-    pub location: String,
-    pub author: String,
-    pub content: String,
-}
-
-impl Summary for NewsArticle {
-    fn summarize(&self) -> String {
-        format!("{}, by {} ({})", self.headline, self.author, self.location)
-    }
-}
-
-pub struct Tweet {
-    pub username: String,
-    pub content: String,
-    pub reply: bool,
-    pub retweet: bool,
-}
-
-impl Summary for Tweet {
-    fn summarize(&self) -> String {
-        format!("{}: {}", self.username, self.content)
-    }
-}
-
-fn returns_summarizable(switch: bool) -> impl Summary {
-    if switch {
-        NewsArticle {
-            headline: String::from(
-                "Penguins win the Stanley Cup Championship!",
-            ),
-            location: String::from("Pittsburgh, PA, USA"),
-            author: String::from("Iceburgh"),
-            content: String::from(
-                "The Pittsburgh Penguins once again are the best \
-                 hockey team in the NHL.",
-            ),
-        }
-    } else {
-        Tweet {
-            username: String::from("horse_ebooks"),
-            content: String::from(
-                "of course, as you probably already know, people",
-            ),
-            reply: false,
-            retweet: false,
-        }
-    }
-}
-

Возврат либо NewsArticle либо Tweet не допускается из-за ограничений того, как выполнен правила написания impl Trait в сборщике. Мы рассмотрим, как написать функцию с таким поведением в разделе "Использование предметов особенностей, которые разрешены для значений или разных видов" Главы 17.

-

Использование ограничений особенности для условной выполнения способов

-

Используя ограничение особенности с разделом impl, который использует свойства обобщённого вида, можно выполнить способы условно, для тех видов, которые выполняют указанный особенность. Например, вид Pair<T> в приложении 10-15 всегда выполняет функцию new для возврата нового образца Pair<T> (вспомните раздел “Определение способов” Главы 5 где Self является псевдонимом вида для вида раздела impl, который в данном случае является Pair<T>). Но в следующем разделе impl вид Pair<T> выполняет способ cmp_display только если его внутренний вид T выполняет особенность PartialOrd (позволяющий сравнивать) и особенность Display (позволяющий выводить на печать).

-

Файл: src/lib.rs

-
use std::fmt::Display;
-
-struct Pair<T> {
-    x: T,
-    y: T,
-}
-
-impl<T> Pair<T> {
-    fn new(x: T, y: T) -> Self {
-        Self { x, y }
-    }
-}
-
-impl<T: Display + PartialOrd> Pair<T> {
-    fn cmp_display(&self) {
-        if self.x >= self.y {
-            println!("The largest member is x = {}", self.x);
-        } else {
-            println!("The largest member is y = {}", self.y);
-        }
-    }
-}
-

Приложение 10-15: Условная выполнение способов у обобщённых видов в зависимости от ограничений особенности

-

Мы также можем условно выполнить особенность для любого вида, который выполняет другой особенность. Выполнения особенности для любого вида, который удовлетворяет ограничениям особенности, называются общими выполнениеми и широко используются в встроенной библиотеке Rust. Например, обычная библиотека выполняет особенность ToString для любого вида, который выполняет особенность Display. Разделimpl в встроенной библиотеке выглядит примерно так:

-
impl<T: Display> ToString for T {
-    // --snip--
-}
-

Поскольку обычная библиотека имеет эту общую выполнение, то можно вызвать способ to_string определённый особенностью ToString для любого вида, который выполняет особенность Display. Например, мы можем превратить целые числа в их соответствующие String значения, потому что целые числа выполняют особенность Display:

-
#![allow(unused)]
-fn main() {
-let s = 3.to_string();
-}
-

Общие выполнения приведены в документации к особенности в разделе "Implementors".

-

Особенности и ограничения особенностей позволяют писать код, который использует свойства обобщённого вида для уменьшения повторения кода, а также указывая сборщику, что мы хотим обобщённый вид, чтобы иметь определённое поведение. Затем сборщик может использовать сведения про ограничения особенности, чтобы проверить, что все определенные виды, используемые с нашим кодом, обеспечивают правильное поведение. В изменяемых строго определенных языках мы получили бы ошибку во время выполнения, если бы вызвали способ для вида, который не выполняет вид определяемый способом. Но Ржавчина перемещает эти ошибки на время сборки, поэтому мы вынуждены исправить сбоев, прежде чем наш код начнёт работать. Кроме того, мы не должны писать код, который проверяет своё поведение во время выполнения, потому что это уже проверено во время сборки. Это повышает производительность без необходимости отказываться от гибкости обобщённых видов.

-

Валидация ссылок при помощи времён жизни

-

Сроки (времена) жизни - ещё один вид обобщений, с которыми мы уже встречались. Если раньше мы использовали обобщения, чтобы убедиться, что вид обладает нужным нам поведением, теперь мы будем использовать сроки жизни для того, чтобы быть уверенными, что ссылки действительны как самое меньшее столько времени в этапе исполнения программы, сколько нам требуется.

-

В разделе "Ссылки и заимствование" главы 4, мы кое о чём умолчали: у каждой ссылки в Ржавчина есть своё время жизни — область кода, на протяжении которого данная ссылка действительна (valid). В большинстве случаев сроки жизни выводятся неявно — так же, как у видов (нам требуется явно объявлять виды лишь в тех случаях, когда при самостоятельном выведении вида возможны исходы). Точно так же мы должны явно объявлять сроки жизни тех ссылок, для которых времена жизни могут быть определены сборщиком по-разному. Ржавчина требует от нас объявлять взаимосвязи посредством обобщённых свойств сроков жизни, чтобы убедиться в том, что во время исполнения все действующие ссылки будут правильными.

-

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

-

Времена жизни предотвращают появление "повисших" ссылок

-

Основное предназначение сроков жизни — предотвращать появление так называемых "повисших ссылок" (dangling references), из-за которых программа обращается не к тем данным, к которым она собиралась обратиться. Рассмотрим программу из приложения 10-16, имеющую внешнюю и внутреннюю области видимости.

-
fn main() {
-    let r;
-
-    {
-        let x = 5;
-        r = &x;
-    }
-
-    println!("r: {r}");
-}
-

Приложение 10-16: Попытка использования ссылки, значение которой вышло из области видимости

-
-

Примечание: примеры в приложениях 10-16, 10-17 и 10-23 объявляют переменные без указания их начального значения, поэтому имя переменной существует во внешней области видимости. На первый взгляд может показаться, что это противоречит отсутствию в Ржавчина нулевых (null) значений. Однако, если мы попытаемся использовать переменную, прежде чем присвоить ей значение, мы получим ошибку сборки, которая показывает, что Ржавчина действительно не разрешает нулевые (null) значения.

-
-

Внешняя область видимости объявляет переменную с именем r без начального значения, а внутренняя область объявляет переменную с именем x с начальным значением 5. Во внутренней области мы пытаемся установить значение r как ссылку на x. Затем внутренняя область видимости заканчивается и мы пытаемся напечатать значение из r. Этот код не будет собран, потому что значение на которое ссылается r исчезает из области видимости, прежде чем мы попробуем использовать его. Вот сообщение об ошибке:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0597]: `x` does not live long enough
- --> src/main.rs:6:13
-  |
-5 |         let x = 5;
-  |             - binding `x` declared here
-6 |         r = &x;
-  |             ^^ borrowed value does not live long enough
-7 |     }
-  |     - `x` dropped here while still borrowed
-8 |
-9 |     println!("r: {r}");
-  |                  --- borrow later used here
-
-For more information about this error, try `rustc --explain E0597`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

Переменная x «не живёт достаточно долго». Причина в том, что x выйдет из области видимости, когда эта внутренняя область закончится в строке 7. Но r все ещё является действительной во внешней области видимости; поскольку её охват больше, мы говорим, что она «живёт дольше». Если бы Ржавчина позволил такому коду работать, то переменная r смогла бы ссылаться на память, которая уже была освобождена (в тот мгновение, когда x вышла из внутренней области видимости), и всё что мы попытались бы сделать с r работало бы неправильно. Как же Ржавчина определяет, что этот код неправилен? Он использует для этого анализатор заимствований (borrow checker).

-

Анализатор заимствований

-

Сборщик Ржавчина имеет в своём составе анализатор заимствований, который сравнивает области видимости для определения, являются ли все заимствования действительными. В приложении 10-17 показан тот же код, что и в приложении 10-16, но с изложениями, показывающими времена жизни переменных.

-
fn main() {
-    let r;                // ---------+-- 'a
-                          //          |
-    {                     //          |
-        let x = 5;        // -+-- 'b  |
-        r = &x;           //  |       |
-    }                     // -+       |
-                          //          |
-    println!("r: {r}");   //          |
-}                         // ---------+
-

Пример 10-17: Изложение времён жизни переменных r и x, с помощью определителей времени жизни 'a и 'b, соответственно

-

Здесь мы описали время жизни для r с помощью 'a и время жизни x с помощью 'b . Как видите, время жизни 'b внутреннего раздела гораздо меньше, чем время жизни 'a внешнего раздела. Во время сборки Ржавчина сравнивает продолжительность двух времён жизни и видит, что r имеет время жизни 'a, но ссылается на память со временем жизни 'b. Программа отклоняется, потому что 'b короче, чем 'a: предмет ссылки не живёт так же долго, как сама ссылка.

-

Приложение 10-18 исправляет код, чтобы в нём не было повисшей ссылки, и собирается без ошибок.

-
fn main() {
-    let x = 5;            // ----------+-- 'b
-                          //           |
-    let r = &x;           // --+-- 'a  |
-                          //   |       |
-    println!("r: {r}");   //   |       |
-                          // --+       |
-}                         // ----------+
-

Приложение 10-18: Ссылка правильна, так как данные имеют более продолжительное время жизни, чем ссылка на эти данные

-

Здесь переменная x имеет время жизни 'b, которое больше, чем время жизни 'a. Это означает, что переменная r может ссылаться на переменную x потому что Ржавчина знает, что ссылка в переменной r будет всегда действительной до тех пор, пока переменная x является валидной.

-

После того, как мы на примерах рассмотрели времена жизни ссылок и обсудили как Ржавчина их анализирует, давайте поговорим об обобщённых временах жизни входных свойств и возвращаемых значений функций.

-

Обобщённые времена жизни в функциях

-

Напишем функцию, которая возвращает более длинный из двух срезов строки. Эта функция принимает два среза строки и возвращает один срез строки. После того как мы выполнили функцию longest, код в приложении 10-19 должен вывести The longest string is abcd.

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "xyz";
-
-    let result = longest(string1.as_str(), string2);
-    println!("The longest string is {result}");
-}
-

Приложение 10-19: Функция main вызывает функцию longest для поиска наибольшего из двух срезов строки

-

Обратите внимание, что мы хотим чтобы функция принимала строковые срезы, которые являются ссылками, а не строки, потому что мы не хотим, чтобы функция longest забирала во владение свои свойства. Обратитесь к разделу "Строковые срезы как свойства" Главы 4 для более подробного обсуждения того, почему свойства используемые в приложении 10-19 выбраны именно таким образом.

-

Если мы попробуем выполнить функцию longest так, как это показано в приложении 10-20, программа не собирается:

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "xyz";
-
-    let result = longest(string1.as_str(), string2);
-    println!("The longest string is {result}");
-}
-
-fn longest(x: &str, y: &str) -> &str {
-    if x.len() > y.len() {
-        x
-    } else {
-        y
-    }
-}
-

Приложение 10-20: Выполнение функции longest, которая возвращает наибольший срез строки, но пока не собирается

-

Вместо этого мы получим следующую ошибку, говорящую о временах жизни:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0106]: missing lifetime specifier
- --> src/main.rs:9:33
-  |
-9 | fn longest(x: &str, y: &str) -> &str {
-  |               ----     ----     ^ expected named lifetime parameter
-  |
-  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
-help: consider introducing a named lifetime parameter
-  |
-9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
-  |           ++++     ++          ++          ++
-
-For more information about this error, try `rustc --explain E0106`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

Текст ошибки показывает, что возвращаемому виду нужен обобщённый свойство времени жизни, потому что Ржавчина не может определить, относится ли возвращаемая ссылка к x или к y. На самом деле, мы тоже не знаем, потому что разделif в теле функции возвращает ссылку на x, а разделelse возвращает ссылку на y!

-

Когда мы определяем эту функцию, мы не знаем определенных значений, которые будут в неё передаваться. Поэтому мы не знаем какая из ветвей оператора if или else будет выполнена. Мы также не знаем определенных времён жизни ссылок, которые будут переданы в функцию, поэтому мы не можем посмотреть на их области видимости, как мы делали в примерах 10-17 и 10-18, чтобы определить, будет ли возвращаемая нами ссылка правильной во всех случаях. Анализатор заимствований также не может этого определить, потому что он не знает как времена жизни переменных x и y соотносятся с временем жизни возвращаемого значения. Чтобы исправить эту ошибку, мы добавим обобщённый свойство времени жизни, который определит отношения между ссылками таким образом, чтобы анализатор заимствований мог провести свой анализ.

-

правила написания изложении времени жизни

-

Изложения времени жизни не меняют срок, как долго живёт та или иная ссылка. Они скорее описывают, как соотносятся между собой времена жизни нескольких ссылок, не влияя на само время жизни. Точно так же, как функции могут принимать любой вид, когда в ярлыке указан свойство обобщённого вида, функции могут принимать ссылки с любым временем жизни, указанным с помощью свойства обобщённого времени жизни.

-

Изложения времени жизни имеют немного необычный правила написания: имена свойств времени жизни должны начинаться с апострофа ('), пишутся маленькими буквами, и обычно очень короткие, как и имена обобщённых видов. Большинство людей использует имя 'a в качестве первой изложении времени жизни. Изложения свойств времени жизни следуют после символа & и отделяются пробелом от названия ссылочного вида.

-

Приведём несколько примеров: у нас есть ссылка на i32 без указания времени жизни, ссылка на i32, с временем жизни имеющим имя 'a и изменяемая ссылка на i32, которая также имеет время жизни 'a.

-
&i32        // a reference
-&'a i32     // a reference with an explicit lifetime
-&'a mut i32 // a mutable reference with an explicit lifetime
-

Одна изложение времени жизни сама по себе не имеет большого значения, поскольку изложении предназначены для того, чтобы уведомить Ржавчина о том, как времена жизни нескольких ссылок соотносятся между собой. Давайте рассмотрим, как изложении времени жизни связаны друг с другом в среде функции longest.

-

Изложения времени жизни в ярлыках функций

-

Чтобы использовать изложении времени жизни в ярлыках функций, нам нужно объявить свойства обобщённого времени жизни внутри угловых скобок между именем функции и списком свойств, как мы это делали с свойствами обобщённого вида .

-

Мы хотим, чтобы ярлык отражала следующее ограничение: возвращаемая ссылка будет действительна до тех пор, пока валидны оба свойства. Это связь между временами жизни свойств и возвращаемого значения. Мы назовём это время жизни 'a, а затем добавим его к каждой ссылке, как показано в приложении 10-21.

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "xyz";
-
-    let result = longest(string1.as_str(), string2);
-    println!("The longest string is {result}");
-}
-
-fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
-    if x.len() > y.len() {
-        x
-    } else {
-        y
-    }
-}
-

Приложение 10-21: В определении функции longest указано, что все ссылки должны иметь одинаковое время жизни, обозначенное как 'a

-

Этот код должен собираться и давать желаемый итог, когда мы вызовем его в функции main приложения 10-19.

-

Ярлык функции теперь сообщает Rust, что для некоторого времени жизни 'a функция принимает два свойства, оба из которых являются срезами строк, которые живут не меньше, чем время жизни 'a. Ярлык функции также сообщает Rust, что срез строки, возвращаемый функцией, будет жить как самое меньшее столько, сколько длится время жизни 'a. В действительностиэто означает, что время жизни ссылки, возвращаемой функцией longest, равно меньшему времени жизни передаваемых в неё ссылок. Мы хотим, чтобы Ржавчина использовал именно такие отношения при анализе этого кода.

-

Помните, когда мы указываем свойства времени жизни в этой ярлыке функции, мы не меняем время жизни каких-либо переданных или возвращённых значений. Скорее, мы указываем, что анализатор заимствований должен отклонять любые значения, которые не соответствуют этим ограничениям. Обратите внимание, что самой функции longest не нужно точно знать, как долго будут жить x и y, достаточно того, что некоторая область может быть заменена на 'a, которая будет удовлетворять этой ярлыке.

-

При определении времён жизни функций, изложении помещаются в ярлык функции, а не в тело функции. Изложения времени жизни становятся частью договора функции, как и виды в ярлыке. Наличие ярлыков функций, содержащих договор времени жизни, означает, что анализ который выполняет сборщик Rust, может быть проще. Если есть неполадка с тем, как функция определяется или как она вызывается, ошибки сборщика могут указать на часть нашего кода и ограничения более точно. Если бы вместо этого сборщик Ржавчина сделал больше предположений о том, какие отношения времён жизни мы хотели получить, сборщик смог бы указать только на использование нашего кода за много шагов от источника сбоев.

-

Когда мы передаём определенные ссылки в функцию longest, определенным временем жизни, которое будет заменено на 'a, является часть области видимости x, которая пересекается с областью видимости y. Другими словами, обобщённое время жизни 'a получит определенное время жизни, равное меньшему из времён жизни x и y. Так как мы определяли возвращаемую ссылку тем же свойствоом времени жизни 'a, то возвращённая ссылка также будет действительна на протяжении меньшего из времён жизни x и y.

-

Давайте посмотрим, как изложении времени жизни ограничивают функцию longest путём передачи в неё ссылок, которые имеют разные определенные времена жизни. Приложение 10-22 является очевидным примером.

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("long string is long");
-
-    {
-        let string2 = String::from("xyz");
-        let result = longest(string1.as_str(), string2.as_str());
-        println!("The longest string is {result}");
-    }
-}
-
-fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
-    if x.len() > y.len() {
-        x
-    } else {
-        y
-    }
-}
-

Приложение 10-22: Использование функции longest со ссылками на значения вида String, имеющими разное время жизни

-

В этом примере переменная string1 действительна до конца внешней области, string2 действует до конца внутренней области видимости и result ссылается на что-то, что является действительным до конца внутренней области видимости. Запустите этот код, и вы увидите что анализатор заимствований разрешает такой код; он собирает и напечатает The longest string is long string is long.

-

Теперь, давайте попробуем пример, который показывает, что время жизни ссылки result должно быть меньшим временем жизни одного из двух переменных. Мы переместим объявление переменной result за пределы внутренней области видимости, но оставим присвоение значения переменной result в области видимости string2. Затем мы переместим println!, который использует result за пределы внутренней области видимости, после того как внутренняя область видимости закончилась. Код в приложении 10-23 не собирается.

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("long string is long");
-    let result;
-    {
-        let string2 = String::from("xyz");
-        result = longest(string1.as_str(), string2.as_str());
-    }
-    println!("The longest string is {result}");
-}
-
-fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
-    if x.len() > y.len() {
-        x
-    } else {
-        y
-    }
-}
-

Приложение 10-23: Попытка использования result, после того как string2 вышла из области видимости

-

При попытке собрать этот код, мы получим такую ошибку:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0597]: `string2` does not live long enough
- --> src/main.rs:6:44
-  |
-5 |         let string2 = String::from("xyz");
-  |             ------- binding `string2` declared here
-6 |         result = longest(string1.as_str(), string2.as_str());
-  |                                            ^^^^^^^ borrowed value does not live long enough
-7 |     }
-  |     - `string2` dropped here while still borrowed
-8 |     println!("The longest string is {result}");
-  |                                     -------- borrow later used here
-
-For more information about this error, try `rustc --explain E0597`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

Эта ошибка говорит о том, что если мы хотим использовать result в указания println!, переменная string2 должна бы быть действительной до конца внешней области видимости. Ржавчина знает об этом, потому что мы определяли свойства функции и её возвращаемое значение одинаковым временем жизни 'a.

-

Будучи людьми, мы можем посмотреть на этот код и увидеть, что string1 длиннее, чем string2 и, следовательно, result будет содержать ссылку на string1. Поскольку string1 ещё не вышла из области видимости, ссылка на string1 будет все ещё действительной в указания println!. Однако сборщик не видит, что ссылка в этом случае валидна. Мы сказали Rust, что время жизни ссылки, возвращаемой из функции longest, равняется меньшему из времён жизни переданных в неё ссылок. Таким образом, анализатор заимствований запрещает код в приложении 10-23, как возможно имеющий недействительную ссылку.

-

Попробуйте провести больше экспериментов с различными значениями и временами жизни ссылок, передаваемых в функцию longest, а также с тем, как используется возвращаемое значение Перед сборкой делайте предположения о том, пройдёт ли ваш код анализ заимствований, а затем проверяйте, насколько вы были правы.

-

Мышление в понятиях времён жизни

-

В зависимости от того, что делает ваша функция, следует использовать разные способы указания свойств времени жизни. Например, если мы изменим выполнение функции longest таким образом, чтобы она всегда возвращала свой первый переменная вместо самого длинного среза строки, то время жизни для свойства y можно совсем не указывать. Этот код собирается:

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "efghijklmnopqrstuvwxyz";
-
-    let result = longest(string1.as_str(), string2);
-    println!("The longest string is {result}");
-}
-
-fn longest<'a>(x: &'a str, y: &str) -> &'a str {
-    x
-}
-

Мы указали свойство времени жизни 'a для свойства x и возвращаемого значения, но не для свойства y, поскольку время жизни свойства y никак не соотносится с временем жизни свойства x или возвращаемого значения.

-

При возврате ссылки из функции, свойство времени жизни для возвращаемого вида должен соответствовать свойству времени жизни одного из переменных. Если возвращаемая ссылка не ссылается на один из свойств, она должна ссылаться на значение, созданное внутри функции. Однако, это приведёт к недействительной ссылке, поскольку значение, на которое она ссылается, выйдет из области видимости в конце функции. Посмотрите на попытку выполнения функции longest, которая не собирается:

-

Файл: src/main.rs

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "xyz";
-
-    let result = longest(string1.as_str(), string2);
-    println!("The longest string is {result}");
-}
-
-fn longest<'a>(x: &str, y: &str) -> &'a str {
-    let result = String::from("really long string");
-    result.as_str()
-}
-

Здесь, несмотря на то, что мы указали свойство времени жизни 'a для возвращаемого вида, выполнение не будет собрана, потому что время жизни возвращаемого значения никак не связано с временем жизни свойств. Получаем сообщение об ошибке:

-
$ cargo run
-   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
-error[E0515]: cannot return value referencing local variable `result`
-  --> src/main.rs:11:5
-   |
-11 |     result.as_str()
-   |     ------^^^^^^^^^
-   |     |
-   |     returns a value referencing data owned by the current function
-   |     `result` is borrowed here
-
-For more information about this error, try `rustc --explain E0515`.
-error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
-
-

Неполадказаключается в том, что result выходит за область видимости и очищается в конце функции longest. Мы также пытаемся вернуть ссылку на result из функции. Мы не можем указать свойства времени жизни, которые могли бы изменить недействительную ссылку, а Ржавчина не позволит нам создать недействительную ссылку. В этом случае лучшим решением будет вернуть владеющий вид данных, а не ссылку: в этом случае вызывающая функция будет нести ответственность за очистку полученного ею значения.

-

В конечном итоге, правила написания времён жизни выполняет связывание времён жизни различных переменных и возвращаемых значений функций. Описывая времена жизни, мы даём Ржавчина достаточно сведений, чтобы разрешить безопасные действия с памятью и запретить действия, которые могли бы создать недействительные ссылки или иным способом нарушить безопасность памяти.

-

Определение времён жизни при объявлении устройств

-

До сих пор мы объявляли устройства, которые всегда содержали владеющие виды данных. Устройства могут содержать и ссылки, но при этом необходимо добавить изложение времени жизни для каждой ссылки в определении устройства. Приложение 10-24 описывает устройство ImportantExcerpt, содержащую срез строки:

-

Файл: src/main.rs

-
struct ImportantExcerpt<'a> {
-    part: &'a str,
-}
-
-fn main() {
-    let novel = String::from("Call me Ishmael. Some years ago...");
-    let first_sentence = novel.split('.').next().unwrap();
-    let i = ImportantExcerpt {
-        part: first_sentence,
-    };
-}
-

Приложение 10-25. Устройства, содержащая ссылку, требует изложении времени жизни

-

У устройства имеется одно поле part, хранящее срез строки, который сам по себе является ссылкой. Как и в случае с обобщёнными видами данных, мы объявляем имя обобщённого свойства времени жизни внутри угловых скобок после имени устройства, чтобы иметь возможность использовать его внутри определения устройства. Данная изложение означает, что образец ImportantExcerpt не может пережить ссылку, которую он содержит в своём поле part.

-

Функция main здесь создаёт образец устройства ImportantExcerpt, который содержит ссылку на первое предложение вида String принадлежащее переменной novel. Данные в novel существуют до создания образца ImportantExcerpt. Кроме того, novel не выходит из области видимости до тех пор, пока ImportantExcerpt не выйдет за область видимости, поэтому ссылка в внутри образца ImportantExcerpt является действительной.

-

Правила неявного выведения времени жизни

-

Вы изучили, что у каждой ссылки есть время жизни и что нужно указывать свойства времени жизни для функций или устройств, которые используют ссылки. Однако в Главе 4 у нас была функция в приложении 4-9, которая затем снова показана в приложении 10-25, в которой код собрался без наставлений времени жизни.

-

Файл: src/lib.rs

-
fn first_word(s: &str) -> &str {
-    let bytes = s.as_bytes();
-
-    for (i, &item) in bytes.iter().enumerate() {
-        if item == b' ' {
-            return &s[0..i];
-        }
-    }
-
-    &s[..]
-}
-
-fn main() {
-    let my_string = String::from("hello world");
-
-    // first_word works on slices of `String`s
-    let word = first_word(&my_string[..]);
-
-    let my_string_literal = "hello world";
-
-    // first_word works on slices of string literals
-    let word = first_word(&my_string_literal[..]);
-
-    // Because string literals *are* string slices already,
-    // this works too, without the slice syntax!
-    let word = first_word(my_string_literal);
-}
-

Приложение 10-25: Функция, которую мы определили в приложении 4-9 собирается без наставлений времени жизни, несмотря на то, что входной и возвращаемый вид свойств являются ссылками

-

Причина, по которой этот код собирается — историческая. В ранних (до-1.0) исполнениях Ржавчина этот код не собрался бы, поскольку каждой ссылке нужно было явно назначать время жизни. В те времена, ярлык функции была бы написана примерно так:

-
fn first_word<'a>(s: &'a str) -> &'a str {
-

После написания большого количества кода на Ржавчина разработчики языка обнаружили, что в определённых случаейх программисты описывают одни и те же изложении времён жизни снова и снова. Эти случаи были предсказуемы и следовали нескольким определенным образцовым моделям. Объединение Ржавчина решила запрограммировать эти образцы в код сборщика Rust, чтобы анализатор заимствований мог вывести времена жизни в таких случаейх без необходимости явного указания наставлений программистами.

-

Мы упоминаем этот отрывок истории Rust, потому что возможно, что в будущем появится больше образцов для самостоятельного выведения времён жизни, которые будут добавлены в сборщик. Таким образом, в будущем может понадобится ещё меньшее количество наставлений.

-

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

-

Правила выведения не предоставляют полного заключения. Если Ржавчина определенно применяет правила, но некоторая неясность относительно времён жизни ссылок все ещё остаётся, сборщик не будет догадываться, какими должны быть времена жизни оставшихся ссылок. В этом случае, вместо угадывания сборщик выдаст ошибку, которую вы можете устранить, добавив изложении времени жизни.

-

Времена жизни свойств функции или способа называются временем жизни ввода, а времена жизни возвращаемых значений называются временем жизни вывода.

-

Сборщик использует три правила, чтобы выяснить времена жизни ссылок при отсутствии явных наставлений. Первое правило относится ко времени жизни ввода, второе и третье правила применяются ко временам жизни вывода. Если сборщик доходит до конца проверки трёх правил и всё ещё есть ссылки, для которых он не может выяснить время жизни, сборщик остановится с ошибкой. Эти правила применяются к объявлениям fn, а также к разделам impl.

-

Первое правило заключается в том, что каждый свойство являющийся ссылкой, получает свой собственный свойство времени жизни. Другими словами, функция с одним свойствоом получит один свойство времени жизни: fn foo<'a>(x: &'a i32); функция с двумя переменнойми получит два отдельных свойства времени жизни: fn foo<'a, 'b>(x: &'a i32, y: &'b i32), и так далее.

-

Второе правило говорит, что если есть ровно один входной свойство времени жизни, то его время жизни назначается всем выходным свойствам: fn foo<'a>(x: &'a i32) -> &'a i32.

-

Третье правило о том, что если есть множество входных свойств времени жизни, но один из них является ссылкой &self или &mut self, так как эта функция является способом, то время жизни self назначается временем жизни всем выходным свойствам. Это третье правило делает способы намного приятнее для чтения и записи, потому что требуется меньше символов.

-

Представим, что мы сборщик и применим эти правила, чтобы вывести времена жизни ссылок в ярлыке функции first_word приложения 10-25. Ярлык этой функции начинается без объявления времён жизни ссылок:

-
fn first_word(s: &str) -> &str {
-

Теперь мы (в качестве сборщика) применим первое правило, утверждающее, что каждый свойство функции получает своё собственное время жизни. Как обычно, назовём его 'a и теперь ярлык выглядит так:

-
fn first_word<'a>(s: &'a str) -> &str {
-

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

-
fn first_word<'a>(s: &'a str) -> &'a str {
-

Теперь все ссылки в этой функции имеют свойства времени жизни и сборщик может продолжить свой анализ без необходимости просить у программиста указать изложении времён жизни в ярлыке этой функции.

-

Давайте рассмотрим ещё один пример: на этот раз функцию longest, в которой не было свойств времени жизни, когда мы начали с ней работать в приложении 10-20:

-
fn longest(x: &str, y: &str) -> &str {
-

Применим первое правило: каждому свойству назначается собственное время жизни. На этот раз у функции есть два свойства, поэтому есть два времени жизни:

-
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
-

Можно заметить, что второе правило здесь не применимо, так как в ярлыке указано больше одного входного свойства времени жизни. Третье правило также не применимо, так как longest — функция, а не способ, следовательно, в ней нет свойства self. Итак, мы прошли все три правила, но так и не смогли вычислить время жизни выходного свойства. Поэтому мы и получили ошибку при попытке собрать код приложения 10-20: сборщик работал по правилам неявного выведения времён жизни, но не мог выяснить все времена жизни ссылок в ярлыке.

-

Так как третье правило применяется только к способам, далее мы рассмотрим времена жизни в этом среде, чтобы понять, почему нам часто не требуется определять времена жизни в ярлыках способов.

-

Изложение времён жизни в определении способов

-

Когда мы выполняем способы для устройств с временами жизни, мы используем тот же правила написания, который применялся для наставлений обобщённых видов данных на приложении 10-11. Место, где мы объявляем и используем времена жизни, зависит от того, с чем они связаны — с полями устройства, либо с переменнойми способов и возвращаемыми значениями.

-

Имена свойств времени жизни для полей устройств всегда описываются после ключевого слова impl и затем используются после имени устройства, поскольку эти времена жизни являются частью вида устройства.

-

В ярлыках способов внутри раздела impl ссылки могут быть привязаны ко времени жизни ссылок в полях устройства, либо могут быть независимыми. Вдобавок, правила неявного выведения времён жизни часто делают так, что изложении переменных времён жизни являются необязательными в ярлыках способов. Рассмотрим несколько примеров, использующих устройство с названием ImportantExcerpt, которую мы определили в приложении 10-24.

-

Сначала, воспользуемся способом level, чей единственный свойство является ссылкой на self, а возвращаемое значение i32, не является ссылкой ни на что:

-
struct ImportantExcerpt<'a> {
-    part: &'a str,
-}
-
-impl<'a> ImportantExcerpt<'a> {
-    fn level(&self) -> i32 {
-        3
-    }
-}
-
-impl<'a> ImportantExcerpt<'a> {
-    fn announce_and_return_part(&self, announcement: &str) -> &str {
-        println!("Attention please: {announcement}");
-        self.part
-    }
-}
-
-fn main() {
-    let novel = String::from("Call me Ishmael. Some years ago...");
-    let first_sentence = novel.split('.').next().unwrap();
-    let i = ImportantExcerpt {
-        part: first_sentence,
-    };
-}
-

Объявление свойства времени жизни после impl и его использование после имени вида является обязательным, но нам не нужно определять время жизни ссылки на self, благодаря первому правилу неявного выведения времён жизни.

-

Вот пример, где применяется третье правило неявного выведения времён жизни:

-
struct ImportantExcerpt<'a> {
-    part: &'a str,
-}
-
-impl<'a> ImportantExcerpt<'a> {
-    fn level(&self) -> i32 {
-        3
-    }
-}
-
-impl<'a> ImportantExcerpt<'a> {
-    fn announce_and_return_part(&self, announcement: &str) -> &str {
-        println!("Attention please: {announcement}");
-        self.part
-    }
-}
-
-fn main() {
-    let novel = String::from("Call me Ishmael. Some years ago...");
-    let first_sentence = novel.split('.').next().unwrap();
-    let i = ImportantExcerpt {
-        part: first_sentence,
-    };
-}
-

В этом способе имеется два входных свойства, поэтому Ржавчина применит первое правило и назначит обоим свойствам &self и announcement собственные времена жизни. Далее, поскольку один из свойств является &self, то возвращаемое значение получает время жизни переменой &self и все времена жизни теперь выведены.

-

Постоянное время жизни

-

Одно особенное время жизни, которое мы должны обсудить, называется 'static. Оно означает, что данная ссылка может жить всю продолжительность работы программы. Все строковые записи по умолчанию имеют время жизни 'static, но мы можем указать его явным образом:

-
#![allow(unused)]
-fn main() {
-let s: &'static str = "I have a static lifetime.";
-}
-

Содержание этой строки сохраняется внутри двоичного файл программы и всегда доступно для использования. Следовательно, время жизни всех строковых записей равно 'static.

-

Сообщения сборщика об ошибках в качестве решения сбоев могут предлагать вам использовать время жизни 'static. Но прежде чем указывать 'static как время жизни для ссылки, подумайте, на самом ли деле данная ссылка будет доступна во всё время работы программы. В большинстве случаев, сообщения об ошибках, предлагающие использовать время жизни 'static появляются при попытках создания недействительных ссылок или несовпадения имеющихся времён жизни. В таких случаях, решение заключается в исправлении таких неполадок. а не в указании постоянного времени жизни 'static.

-

Обобщённые виды свойств, ограничения особенностей и времена жизни вместе

-

Давайте кратко рассмотрим правила написания задания свойств обобщённых видов, ограничений особенности и времён жизни совместно в одной функции:

-
fn main() {
-    let string1 = String::from("abcd");
-    let string2 = "xyz";
-
-    let result = longest_with_an_announcement(
-        string1.as_str(),
-        string2,
-        "Today is someone's birthday!",
-    );
-    println!("The longest string is {result}");
-}
-
-use std::fmt::Display;
-
-fn longest_with_an_announcement<'a, T>(
-    x: &'a str,
-    y: &'a str,
-    ann: T,
-) -> &'a str
-where
-    T: Display,
-{
-    println!("Announcement! {ann}");
-    if x.len() > y.len() {
-        x
-    } else {
-        y
-    }
-}
-

Это функция longest из приложения 10-21, которая возвращает наибольший из двух срезов строки. Но теперь у неё есть дополнительный свойство с именем ann обобщённого вида T, который может быть представлен любым видом, выполняющим особенность Display, как указано в предложении where. Этот дополнительный свойство будет напечатан с использованием {} , поэтому ограничение особенности Display необходимо. Поскольку время жизни является обобщённым видом, то объявления свойства времени жизни 'a и свойства обобщённого вида T помещаются в один список внутри угловых скобок после имени функции.

-

Итоги

-

В этой главе мы рассмотрели много всего! Теперь вы знакомы с свойствами обобщённого вида, особенностями и ограничениями особенности, обобщёнными свойствами времени жизни, вы готовы писать код без повторений, который будет работать во множестве различных случаев. Свойства обобщённого вида позволяют использовать код для различных видов данных. Особенности и ограничения особенности помогают убедиться, что, хотя виды и обобщённые, они будут вести себя, как этого требует ваш код. Вы изучили, как использовать изложении времени жизни чтобы убедиться, что этот гибкий код не будет порождать никаких повисших ссылок. И весь этот анализ происходит в мгновение сборки и не влияет на производительность программы во время работы!

-

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

-

Написание автоматизированных проверок

-

В своём эссе 1972 года “The Humble Programmer,” Edsger W. Dijkstra сказал, что «Проверка программы может быть очень эффективным способом показать наличие ошибок, но это безнадёжно неадекватно для показа их отсутствия». Это не значит, что мы не должны пытаться проверять столько, сколько мы можем!

-

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

-

Допустим, мы пишем функцию add_two, которая прибавляет 2 к любому переданному ей числу. Ярлык этой функции принимает целое число в качестве свойства и возвращает целое число в качестве итога. Когда мы выполняем и собираем эту функцию, Ржавчина выполняет всю проверку видов и проверку заимствований, которую вы уже изучили, чтобы убедиться, что, например, мы не передаём значение String или недопустимую ссылку в эту функцию. Но Ржавчина не способен проверить, что эта функция сделает именно то, что мы задумали, то есть вернёт свойство плюс 2, а не, скажем, свойство плюс 10 или свойство - 50! Вот тут-то и приходят на помощь проверки.

-

Мы можем написать проверки, которые утверждают, например, что когда мы передаём 3 в функцию add_two, возвращаемое значение будет 5. Мы можем запускать эти проверки всякий раз, когда мы вносим изменения в наш код, чтобы убедиться, что любое существующее правильное поведение не изменилось.

-

Проверка - сложный навык: мы не сможем охватить все подробности написания хороших проверок в одной главе, но мы обсудим основные подходы к проверке в Rust. Мы поговорим об изложениех и макросах, доступных вам для написания проверок, о поведении по умолчанию и свойствах, предусмотренных для запуска проверок, а также о том, как согласовать проверки в состоящие из звеньев проверки и встроенные проверки.

-

Как писать проверки

-

Проверки - это функции Rust, которые проверяют, что не проверочный код работает ожидаемым образом. Содержимое проверочных функций обычно выполняет следующие три действия:

-
    -
  1. Установка любых необходимых данных или состояния.
  2. -
  3. Запуск кода, который вы хотите проверить.
  4. -
  5. Утверждение, что итоги являются теми, которые вы ожидаете.
  6. -
-

Давайте рассмотрим функции предоставляемые в Ржавчина целенаправленно для написания проверок, которые выполнят все эти действия, включая свойство test, несколько макросов и свойство should_panic.

-

Устройства проверяющей функции

-

В простейшем случае в Ржавчина проверка - это функция, определенная свойством test. Свойства представляют собой метаданные о отрывках кода Rust; один из примеров свойство derive, который мы использовали со устройствами в главе 5. Чтобы превратить функцию в проверяющую функцию добавьте #[test] в строку перед fn. Когда вы запускаете проверки приказом cargo test, Ржавчина создаёт двоичный звено выполняющий функции определеные свойством test и сообщающий о том, успешно или нет прошла каждая проверяющая функция.

-

Когда мы создаём новый дело библиотеки с помощью Cargo, то в нём самостоятельно порождается проверочный звено с проверку-функцией для нас. Этот звено даст вам образец для написания ваших проверок, так что вам не нужно искать точную устройство и правила написания проверочных функций каждый раз, когда вы начинаете новый дело. Вы можете добавить столько дополнительных проверочных функций и столько проверочных звеньев, сколько захотите!

-

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

-

Давайте создадим новый библиотечный дело под названием adder, который складывает два числа:

-
$ cargo new adder --lib
-     Created library `adder` project
-$ cd adder
-
-

Содержимое файла src/lib.rs вашей библиотеки adder должно выглядеть как в приложении 11-1.

-

Файл: src/lib.rs

- -
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_works() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-}
-

Приложение 11-1: Проверочный звено и функция, созданные самостоятельно с помощью cargo new

-

Сейчас давайте пренебрегаем первые две строчки кода и сосредоточимся на функции. Обратите внимание на правила написания изложении #[test]: этот свойство указывает, что это проверочная функция, поэтому запускающий проверка знает, что эту функцию следует рассматривать как проверочную. У нас также могут быть не проверяемые функции в звене tests, которые помогут настроить общие сценарии или выполнить общие действия, поэтому нам всегда нужно указывать, какие функции являются проверкими.

-

В теле функции проверки используется макрос assert_eq!, чтобы утверждать, что result, который содержит итог сложения 2 и 2, равен 4. Это утверждение служит примером вида для типичного проверки. Давайте запустим его, чтобы убедиться, что этот проверка пройден.

-

Приказ cargo test выполнит все проверки в выбранном деле и сообщит о итогах как в приложении 11-2:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.57s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-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 adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Приложение 11-2: Вывод сведений о работе самостоятельно созданных проверок

-

Cargo собрал и выполнил проверку. Мы видим строку running 1 test. Следующая строка показывает имя созданной проверочной функции, называемой it_works, и итог запуска этого проверки равный ok. Текст test result: ok. означает, что все проверки пройдены успешно и часть вывода 1 passed; 0 failed сообщает общее количество проверок, которые прошли или были ошибочными.

-

Можно пометить проверка как пренебрегаемый, чтобы он не выполнялся в определенном случае; мы рассмотрим это в разделе “Пренебрежение некоторых проверок, если их целенаправленно не запрашивать” позже в этой главе. Поскольку в данный мгновение мы этого не сделали, в сводке показано, что 0 ignored. Мы также можем передать переменная приказу cargo test для запуска только тех проверок, имя которых соответствует строке; это называется выборкой, и мы рассмотрим это в разделе “Запуск подмножества проверок по имени”. Мы также не фильтровали выполняемые проверки, поэтому в конце сводки показано, что 0 filtered out.

-

Исчисление 0 measured предназначена для проверок производительности. На мгновение написания этой статьи такие проверки доступны только в ночной сборке Rust. Посмотрите документацию о проверках производительности, чтобы узнать больше.

-

Следующая часть вывода проверок начинается с Doc-tests adder - это сведения о проверках в документации. У нас пока нет проверок документации, но Ржавчина может собирать любые примеры кода, которые находятся в API документации. Такая возможность помогает поддерживать документацию и код в согласованном состоянии. Мы поговорим о написании проверок документации в разделы "Примечания документации как проверки" Главы 14. Пока просто пренебрегаем часть Doc-tests вывода.

-

Давайте начнём настраивать проверка в соответствии с нашими собственными потребностями. Сначала поменяем название нашего проверки it_works на exploration, вот так:

-

Файл: src/lib.rs

-
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn exploration() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-}
-

Снова выполним приказ cargo test. Вывод показывает наименование нашей проверку-функции - exploration вместо it_works:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.59s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test tests::exploration ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Добавим ещё один проверка, но в этот раз целенаправленно сделаем так, чтобы этот новый проверка не отработал! Проверка терпит неудачу, когда что-то паникует в проверяемой функции. Каждый проверка запускается в новом потоке и когда главный поток видит, что проверочный поток упал, то помечает проверка как завершившийся со сбоем. Мы говорили о простейшем способе вызвать панику в главе 9, используя для этого известный макрос panic!. Введём код проверку-функции another, как в файле src/lib.rs из приложения 11-3.

-

Файл: src/lib.rs

-
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn exploration() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-
-    #[test]
-    fn another() {
-        panic!("Make this test fail");
-    }
-}
-

Приложение 11-3: Добавление второго проверки, который завершится ошибкой, потому что мы вызываем panic! макрос

-

Запустим приказ cargo test. Вывод итогов показан в приложении 11-4, который сообщает, что проверка exploration пройден, а another нет:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.72s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 2 tests
-test tests::another ... FAILED
-test tests::exploration ... ok
-
-failures:
-
----- tests::another stdout ----
-thread 'tests::another' panicked at src/lib.rs:17:9:
-Make this test fail
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::another
-
-test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Приложение 11-4. Итоги проверки, когда один проверка пройден, а другой нет

-

Вместо ok, строка test tests::another сообщает FAILED. Две новые разделы появились между отдельными итогами и сводкой: в первом отображается подробная причина каждого сбоя проверки. В данном случае проверка another не сработал, потому что panicked at 'Make this test fail', произошло в строке 10 файла src/lib.rs. В следующем разделе перечисляют имена всех не пройденных проверок, что удобно, когда есть много проверок и много подробных итогов неудачных проверок. Мы можем использовать имя не пройденного проверки для его дальнейшей отладки; мы больше поговорим о способах запуска проверок в разделе "Управление хода выполнения проверок".

-

Итоговая строка отображается в конце: общий итог нашего проверки FAILED. У нас один проверка пройден и один проверка завершён со сбоем.

-

Теперь, когда вы увидели, как выглядят итоги проверки при разных сценариях, давайте рассмотрим другие макросы полезные в проверках, кроме panic!.

-

Проверка итогов с помощью макроса assert!

-

Макрос assert! доступен из встроенной библиотеки и является удобным, когда вы хотите проверить что некоторое условие в проверке вычисляется в значение true. Мы передаём в макрос assert! переменная, который вычисляется в логическое значение. Если оно true, то ничего не происходит и проверка считается пройденным. Если же значение вычисляется в false, то макрос assert! вызывает макрос panic!, чтобы вызвать сбой проверки. Использование макроса assert! помогает проверить, что код исполняется как ожидалось.

-

В главе 5, приложении 5-15, мы использовали устройство Rectangle и способ can_hold, который повторён в приложении 11-5. Давайте поместим этот код в файл src/lib.rs и напишем несколько проверок для него используя макрос assert!.

-

Файл: src/lib.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width > other.width && self.height > other.height
-    }
-}
-

Приложение 11-5: Использование устройства Rectangle и её способа can_hold из главы 5

-

Способ can_hold возвращает логическое значение, что означает, что он является наилучшим исходом использования в макросе assert!. В приложении 11-6 мы пишем проверка, который выполняет способ can_hold путём создания образца Rectangle шириной 8 и высотой 7 и убеждаемся, что он может содержать другой образец Rectangle имеющий ширину 5 и высоту 1.

-

Файл: src/lib.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width > other.width && self.height > other.height
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn larger_can_hold_smaller() {
-        let larger = Rectangle {
-            width: 8,
-            height: 7,
-        };
-        let smaller = Rectangle {
-            width: 5,
-            height: 1,
-        };
-
-        assert!(larger.can_hold(&smaller));
-    }
-}
-

Приложение 11-6: Проверка для способа can_hold, который проверяет что больший прямоугольник действительно может содержать меньший

-

Также, в звене tests обратите внимание на новую добавленную строку use super::*;. Звено tests является обычным и подчиняется тем же правилам видимости, которые мы обсуждали в главе 7 "Пути для ссылки на элементы внутри дерева звена". Так как этот звено tests является внутренним, нужно подключить проверяемый код из внешнего звена в область видимости внутреннего звена с проверкими. Для этого используется вездесущеее подключение, так что все что определено во внешнем звене становится доступным внутри tests звена.

-

Мы назвали наш проверка larger_can_hold_smaller и создали два нужных образца Rectangle. Затем вызвали макрос assert! и передали итог вызова larger.can_hold(&smaller) в него. Это выражение должно возвращать true, поэтому наш проверка должен пройти. Давайте выясним!

-
$ cargo test
-   Compiling rectangle v0.1.0 (file:///projects/rectangle)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
-     Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
-
-running 1 test
-test tests::larger_can_hold_smaller ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests rectangle
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

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

-

Файл: src/lib.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-impl Rectangle {
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width > other.width && self.height > other.height
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn larger_can_hold_smaller() {
-        // --snip--
-        let larger = Rectangle {
-            width: 8,
-            height: 7,
-        };
-        let smaller = Rectangle {
-            width: 5,
-            height: 1,
-        };
-
-        assert!(larger.can_hold(&smaller));
-    }
-
-    #[test]
-    fn smaller_cannot_hold_larger() {
-        let larger = Rectangle {
-            width: 8,
-            height: 7,
-        };
-        let smaller = Rectangle {
-            width: 5,
-            height: 1,
-        };
-
-        assert!(!smaller.can_hold(&larger));
-    }
-}
-

Поскольку правильный итог функции can_hold в этом случае false, то мы должны инвертировать этот итог, прежде чем передадим его в assert! макро. Как итог, наш проверка пройдёт, если can_hold вернёт false:

-
$ cargo test
-   Compiling rectangle v0.1.0 (file:///projects/rectangle)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
-     Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
-
-running 2 tests
-test tests::larger_can_hold_smaller ... ok
-test tests::smaller_cannot_hold_larger ... ok
-
-test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests rectangle
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

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

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-// --snip--
-impl Rectangle {
-    fn can_hold(&self, other: &Rectangle) -> bool {
-        self.width < other.width && self.height > other.height
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn larger_can_hold_smaller() {
-        let larger = Rectangle {
-            width: 8,
-            height: 7,
-        };
-        let smaller = Rectangle {
-            width: 5,
-            height: 1,
-        };
-
-        assert!(larger.can_hold(&smaller));
-    }
-
-    #[test]
-    fn smaller_cannot_hold_larger() {
-        let larger = Rectangle {
-            width: 8,
-            height: 7,
-        };
-        let smaller = Rectangle {
-            width: 5,
-            height: 1,
-        };
-
-        assert!(!smaller.can_hold(&larger));
-    }
-}
-

Запуск проверок теперь производит следующее:

-
$ cargo test
-   Compiling rectangle v0.1.0 (file:///projects/rectangle)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
-     Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e)
-
-running 2 tests
-test tests::larger_can_hold_smaller ... FAILED
-test tests::smaller_cannot_hold_larger ... ok
-
-failures:
-
----- tests::larger_can_hold_smaller stdout ----
-thread 'tests::larger_can_hold_smaller' panicked at src/lib.rs:28:9:
-assertion failed: larger.can_hold(&smaller)
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::larger_can_hold_smaller
-
-test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Наши проверки нашли ошибку! Так как в проверке larger.width равно 8 и smaller.width равно 5 сравнение ширины в способе can_hold возвращает итог false, поскольку число 8 не меньше чем 5.

-

Проверка на равенство с помощью макросов assert_eq! и assert_ne!

-

Общим способом проверки возможности является использование сравнения итога проверяемого кода и ожидаемого значения, чтобы убедиться в их равенстве. Для этого можно использовать макрос assert!, передавая ему выражение с использованием оператора ==. Важно также знать, что кроме этого обычная библиотека предлагает пару макросов assert_eq! и assert_ne!, чтобы сделать проверка более удобным. Эти макросы сравнивают два переменной на равенство или неравенство соответственно. Макросы также печатают два значения входных свойств, если проверка завершился ошибкой, что позволяет легче увидеть почему проверка ошибочен. Противоположно этому, макрос assert! может только отобразить, что он вычислил значение false для выражения ==, но не значения, которые привели к итогу false.

-

В приложении 11-7, мы напишем функцию add_two, которая прибавляет к входному свойству 2 и возвращает значение. Затем, проверим эту функцию с помощью макроса assert_eq!:

-

Файл: src/lib.rs

-
pub fn add_two(a: usize) -> usize {
-    a + 2
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_adds_two() {
-        let result = add_two(2);
-        assert_eq!(result, 4);
-    }
-}
-

Приложение 11-7: Проверка функции add_two с помощью макроса assert_eq!

-

Проверим, что проверки проходят!

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test tests::it_adds_two ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Первый переменная, который мы передаём в макрос assert_eq! число 4 чей итог вызова равен add_two(2) . Строка для этого проверки - test tests::it_adds_two ... ok , а текст ok означает, что наш проверка пройден!

-

Давайте введём ошибку в код, чтобы увидеть, как она выглядит, когда проверка, который использует assert_eq! завершается ошибкой. Измените выполнение функции add_two, чтобы добавлять 3:

-
pub fn add_two(a: usize) -> usize {
-    a + 3
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_adds_two() {
-        let result = add_two(2);
-        assert_eq!(result, 4);
-    }
-}
-

Попробуем выполнить данный проверка ещё раз:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test tests::it_adds_two ... FAILED
-
-failures:
-
----- tests::it_adds_two stdout ----
-thread 'tests::it_adds_two' panicked at src/lib.rs:12:9:
-assertion `left == right` failed
-  left: 5
- right: 4
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::it_adds_two
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Наш проверка нашёл ошибку! Проверка it_adds_two не выполнился, отображается сообщение assertion failed: (left == right)`` и показывает, что left было 4, а right было 5. Это сообщение полезно и помогает начать отладку: это означает left переменная assert_eq! имел значение 4, но right переменная для вызова add_two(2) был со значением 5.

-

Обратите внимание, что в некоторых языках (таких как Java) в библиотеках кода для проверки принято именовать входные свойства проверочных функций как "ожидаемое" (expected) и "действительное" (actual). В Ржавчина приняты следующие обозначения left и right соответственно, а порядок в котором определяются ожидаемое значение и производимое проверяемым кодом значение не имеют значения. Мы могли бы написать выражение в проверке как assert_eq!(add_two(2), 4), что приведёт к отображаемому сообщению об ошибке assertion failed: (left == right)``, слева left было бы 5, а справа right было бы 4.

-

Макрос assert_ne! сработает успешно, если входные свойства не равны друг другу и завершится с ошибкой, если значения равны. Этот макрос наиболее полезен в тех случаях, когда мы не знаем заранее, каким значение будет, но знаем точно, каким оно не может быть. К примеру, если проверяется функция, которая обязательно изменяет входные данные определённым образом, но способ изменения входного свойства зависит от дня недели, в который запускаются проверки, что лучший способ проверить правильность работы такой функции - это сравнить и убедиться, что выходное значение функции не должно быть равным входному значению.

-

В своей работе макросы assert_eq! и assert_ne! неявным образом используют операторы == и != соответственно. Когда проверка не срабатывает, макросы печатают значения переменных с помощью отладочного изменения и это означает, что значения сравниваемых переменных должны выполнить особенности PartialEq и Debug. Все простые и большая часть видов встроенной библиотеки Ржавчина выполняют эти особенности. Для устройств и перечислений, которые вы выполняете сами будет необходимо выполнить особенность PartialEq для сравнения значений на равенство или неравенство. Для печати отладочной сведений в виде сообщений в строку вывода окне вывода необходимо выполнить особенность Debug. Так как оба особенности являются выводимыми особенностями, как упоминалось в приложении 5-12 главы 5, то эти особенности можно выполнить добавив изложение #[derive(PartialEq, Debug)] к определению устройства или перечисления. Смотрите больше подробностей в Appendix C "Выводимые особенности" про эти и другие выводимые особенности.

-

Создание сообщений об ошибках

-

Также можно добавить пользовательское сообщение как дополнительный переменная макросов для печати в сообщении об ошибке проверки assert!, assert_eq!, и assert_ne!. Любые переменные, указанные после обязательных переменных, далее передаются в макрос format! (он обсуждается в разделе "Сцепление с помощью оператора + или макроса format!"), так что вы можете передать измененную строку, которая содержит {} для заполнителей и значения, заменяющие эти заполнители. Пользовательские сообщения полезны для пояснения того, что означает утверждение (assertion); когда проверка завершается неудачей, у вас будет лучшее представление о том, в чем неполадка с кодом.

-

Например, есть функция, которая приветствует человека по имени и мы хотим проверять эту функцию. Мы хотим чтобы передаваемое ей имя выводилось в окно вывода:

-

Файл: src/lib.rs

-
pub fn greeting(name: &str) -> String {
-    format!("Hello {name}!")
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn greeting_contains_name() {
-        let result = greeting("Carol");
-        assert!(result.contains("Carol"));
-    }
-}
-

Требования к этой программе ещё не были согласованы и мы вполне уверены, что текст Hello в начале приветствия ещё изменится. Мы решили, что не хотим обновлять проверка при изменении требований, поэтому вместо проверки на точное равенство со значением возвращённым из greeting, мы просто будем проверять, что вывод содержит текст из входного свойства.

-

Давайте внесём ошибку в этот код, изменив greeting так, чтобы оно не включало name и увидим, как выглядит сбой этого проверки:

-
pub fn greeting(name: &str) -> String {
-    String::from("Hello!")
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn greeting_contains_name() {
-        let result = greeting("Carol");
-        assert!(result.contains("Carol"));
-    }
-}
-

Запуск этого проверки выводит следующее:

-
$ cargo test
-   Compiling greeter v0.1.0 (file:///projects/greeter)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s
-     Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
-
-running 1 test
-test tests::greeting_contains_name ... FAILED
-
-failures:
-
----- tests::greeting_contains_name stdout ----
-thread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:
-assertion failed: result.contains("Carol")
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::greeting_contains_name
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Сообщение содержит лишь сведения о том что сравнение не было успешным и в какой строке это произошло. В данном случае, более полезный текст сообщения был бы, если бы также выводилось значение из функции greeting. Изменим проверяющую функцию так, чтобы выводились пользовательское сообщение измененное строкой с заменителем и действительными данными из кода greeting:

-
pub fn greeting(name: &str) -> String {
-    String::from("Hello!")
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn greeting_contains_name() {
-        let result = greeting("Carol");
-        assert!(
-            result.contains("Carol"),
-            "Greeting did not contain name, value was `{result}`"
-        );
-    }
-}
-

После того, как выполним проверка ещё раз мы получим подробное сообщение об ошибке:

-
$ cargo test
-   Compiling greeter v0.1.0 (file:///projects/greeter)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.93s
-     Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
-
-running 1 test
-test tests::greeting_contains_name ... FAILED
-
-failures:
-
----- tests::greeting_contains_name stdout ----
-thread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:
-Greeting did not contain name, value was `Hello!`
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::greeting_contains_name
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Мы можем увидеть значение, которое мы на самом деле получили в проверочном выводе, что поможет нам отлаживать произошедшее, а не то, что мы ожидали.

-

Проверка с помощью макроса should_panic

-

В дополнение к проверке того, что наш код возвращает правильные, ожидаемые значения, важным также является проверить, что наш код обрабатывает ошибки, которые мы ожидаем. Например, рассмотрим вид Guess который мы создали в главе 9, приложения 9-10. Другой код, который использует Guess зависит от заверения того, что Guess образцы будут содержать значения только от 1 до 100. Мы можем написать проверка, который заверяет, что попытка создать образец Guess со значением вне этого ряда вызывает панику.

-

Выполняем это с помощью другого свойства проверку-функции #[should_panic]. Этот свойство сообщает системе проверки, что проверка проходит, когда способ порождает ошибку. Если ошибка не порождается - проверка считается не пройденным.

-

Приложение 11-8 показывает проверка, который проверяет, что условия ошибки Guess::new произойдут, когда мы их ожидаем их.

-

Файл: src/lib.rs

-
pub struct Guess {
-    value: i32,
-}
-
-impl Guess {
-    pub fn new(value: i32) -> Guess {
-        if value < 1 || value > 100 {
-            panic!("Guess value must be between 1 and 100, got {value}.");
-        }
-
-        Guess { value }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    #[should_panic]
-    fn greater_than_100() {
-        Guess::new(200);
-    }
-}
-

Приложение 11-8: Проверка того, что условие вызовет макрос panic!

-

Свойство #[should_panic] следует после #[test] и до объявления проверочной функции. Посмотрим на вывод итога, когда проверка проходит:

-
$ cargo test
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
-     Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
-
-running 1 test
-test tests::greater_than_100 - should panic ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests guessing_game
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Выглядит хорошо! Теперь давайте внесём ошибку в наш код, убрав условие о том, что функция new будет паниковать если значение больше 100:

-
pub struct Guess {
-    value: i32,
-}
-
-// --snip--
-impl Guess {
-    pub fn new(value: i32) -> Guess {
-        if value < 1 {
-            panic!("Guess value must be between 1 and 100, got {value}.");
-        }
-
-        Guess { value }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    #[should_panic]
-    fn greater_than_100() {
-        Guess::new(200);
-    }
-}
-

Когда мы запустим проверка в приложении 11-8, он потерпит неудачу:

-
$ cargo test
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s
-     Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
-
-running 1 test
-test tests::greater_than_100 - should panic ... FAILED
-
-failures:
-
----- tests::greater_than_100 stdout ----
-note: test did not panic as expected
-
-failures:
-    tests::greater_than_100
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Мы получаем не очень полезное сообщение в этом случае, но когда мы смотрим на проверяющую функцию, мы видим, что она #[should_panic]. Со сбоеме выполнение, которое мы получили означает, что код в проверяющей функции не вызвал паники.

-

Проверки, которые используют should_panic могут быть неточными, потому что они только указывают, что код вызвал панику. Проверка с свойством should_panic пройдёт, даже если проверка паникует по причине, отличной от той, которую мы ожидали. Чтобы сделать проверки с should_panic более точными, мы можем добавить необязательный свойство expected для свойства should_panic. Такая подробностизация проверки позволит удостовериться, что сообщение об ошибке содержит предоставленный текст. Например, рассмотрим измененный код для Guess в приложении 11-9, где new функция паникует с различными сообщениями в зависимости от того, является ли значение слишком маленьким или слишком большим.

-

Файл: src/lib.rs

-
pub struct Guess {
-    value: i32,
-}
-
-// --snip--
-
-impl Guess {
-    pub fn new(value: i32) -> Guess {
-        if value < 1 {
-            panic!(
-                "Guess value must be greater than or equal to 1, got {value}."
-            );
-        } else if value > 100 {
-            panic!(
-                "Guess value must be less than or equal to 100, got {value}."
-            );
-        }
-
-        Guess { value }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    #[should_panic(expected = "less than or equal to 100")]
-    fn greater_than_100() {
-        Guess::new(200);
-    }
-}
-

Приложение 11-9: Проверка panic! на наличие в его сообщении указанной подстроки

-

Этот проверка пройдёт, потому что значение, которое мы помеисполнения для should_panic в свойство свойства expected является подстрокой сообщения, с которым функция Guess::new вызывает панику. Мы могли бы указать полное, ожидаемое сообщение для паники, в этом случае это будет Guess value must be less than or equal to 100, got 200. То что вы выберите для указания как ожидаемого свойства у should_panic зависит от того, какая часть сообщения о панике неповторима или динамична, насколько вы хотите, чтобы ваш проверка был точным. В этом случае достаточно подстроки из сообщения паники, чтобы обеспечить выполнение кода в проверочной функции else if value > 100 .

-

Чтобы увидеть, что происходит, когда проверка should_panic неуспешно завершается с сообщением expected, давайте снова внесём ошибку в наш код, поменяв местами if value < 1 и else if value > 100 блоки:

-
pub struct Guess {
-    value: i32,
-}
-
-impl Guess {
-    pub fn new(value: i32) -> Guess {
-        if value < 1 {
-            panic!(
-                "Guess value must be less than or equal to 100, got {value}."
-            );
-        } else if value > 100 {
-            panic!(
-                "Guess value must be greater than or equal to 1, got {value}."
-            );
-        }
-
-        Guess { value }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    #[should_panic(expected = "less than or equal to 100")]
-    fn greater_than_100() {
-        Guess::new(200);
-    }
-}
-

На этот раз, когда мы выполним should_panic проверка, он потерпит неудачу:

-
$ cargo test
-   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s
-     Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
-
-running 1 test
-test tests::greater_than_100 - should panic ... FAILED
-
-failures:
-
----- tests::greater_than_100 stdout ----
-thread 'tests::greater_than_100' panicked at src/lib.rs:12:13:
-Guess value must be greater than or equal to 1, got 200.
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-note: panic did not contain expected string
-      panic message: `"Guess value must be greater than or equal to 1, got 200."`,
- expected substring: `"less than or equal to 100"`
-
-failures:
-    tests::greater_than_100
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Сообщение об ошибке указывает, что этот проверка действительно вызвал панику, как мы и ожидали, но сообщение о панике не включено ожидаемую строку 'Guess value must be less than or equal to 100'. Сообщение о панике, которое мы получили в этом случае, было Guess value must be greater than or equal to 1, got 200. Теперь мы можем начать выяснение, где ошибка!

-

Использование Result<T, E> в проверках

-

Пока что мы написали проверки, которые паникуют, когда терпят неудачу. Мы также можем написать проверки которые используют Result<T, E>! Вот проверка из приложения 11-1, переписанный с использованием Result<T, E> и возвращающий Err вместо паники:

-
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    // ANCHOR: here
-    #[test]
-    fn it_works() -> Result<(), String> {
-        let result = add(2, 2);
-
-        if result == 4 {
-            Ok(())
-        } else {
-            Err(String::from("two plus two does not equal four"))
-        }
-    }
-    // ANCHOR_END: here
-}
-

Функция it_works теперь имеет возвращаемый вид Result<(), String>. В теле функции, вместо вызова макроса assert_eq!, мы возвращаем Ok(()) когда проверка успешно выполнен и Err со String внутри, когда проверка не проходит.

-

Написание проверок так, чтобы они возвращали Result<T, E> позволяет использовать оператор "вопросительный знак" в теле проверок, который может быть удобным способом писать проверки, которые должны выполниться не успешно, если какая-либо действие внутри них возвращает исход ошибки Err.

-

Вы не можете использовать изложение #[should_panic] в проверках, использующих Result<T, E>. Чтобы утверждать, что действие возвращает исход Err, не используйте оператор вопросительного знака для значения Result<T, E>. Вместо этого используйте assert!(value.is_err()).

-

Теперь, когда вы знаете несколько способов написания проверок, давайте взглянем на то, что происходит при запуске проверок и исследуем разные возможности используемые с приказом cargo test.

-

Управление хода выполнения проверок

-

Подобно тому, как cargo run выполняет сборку вашего кода, а затем запускает полученный двоичный файл, cargo test собирает ваш код в режиме проверки и запускает полученный двоичный файл с проверкими. Двоичный файл, создаваемый cargo test, по умолчанию запускает все проверки одновременно и перехватывает вывод, порождаемый во время выполнения проверок, предотвращая их вывод на экран для облегчения чтения вывода, относящегося к итогам проверки. Однако вы можете указать свойства приказной строки, чтобы изменить это поведение по умолчанию.

-

Часть свойств приказной строки передаётся в cargo test, а часть - в итоговый двоичный файл с проверкими. Чтобы разделить эти два вида переменных, нужно сначала указать переменные, которые идут в cargo test, затем использовать разделитель --, а потом те, которые попадут в двоичный файл проверки. Выполнение cargo test --help выводит возможности, которые вы можете использовать с cargo test, а выполнение cargo test -- --help выводит возможности, которые вы можете использовать за разделителем.

-

Выполнение проверок одновременно или последовательно

-

Когда вы запускаете несколько проверок, по умолчанию они выполняются одновременно с использованием потоков, что означает, что они завершатся быстрее, и вы быстрее получите итоги. Поскольку проверки выполняются одновременно, вы должны убедиться, что ваши проверки не зависят друг от друга или от какого-либо общего состояния, включая общее окружение, например, текущий рабочий папка или переменные окружения.

-

Например, допустим, каждый из ваших проверок запускает код, который создаёт файл на диске с именем test-output.txt и записывает некоторые данные в этот файл. Затем каждый проверка считывает данные из этого файла и утверждает, что файл содержит определённое значение, которое в каждом проверке разное. Поскольку все проверки выполняются одновременно, один из проверок может перезаписать файл в промежутке между записью и чтением файла другим проверкой. Тогда второй проверка потерпит неудачу, но не потому, что код неверен, а потому, что эти проверки мешали друг другу при одновременном выполнении. Одно из решений - убедиться, что каждый проверка пишет в свой отдельный файл; другое решение - запускать проверки по одному.

-

Если вы не хотите запускать проверки одновременно или хотите более подробный управление над количеством используемых потоков, можно установить флаг --test-threads и то количество потоков, которое вы хотите использовать для проверки. Взгляните на следующий пример:

-
$ cargo test -- --test-threads=1
-
-

Мы устанавливаем количество проверочных потоков равным 1 , указывая программе не использовать одновременность. Выполнение проверок с использованием одного потока займёт больше времени, чем их одновременное выполнение, но проверки не будут мешать друг другу, если они совместно используют состояние.

-

Отображение итогов работы функции

-

По умолчанию, если проверка пройден, система управления запуска проверок блокирует вывод на печать, т.е. если вы вызовете макрос println! внутри кода проверки и проверка будет пройден, вы не увидите вывода на окно вывода итогов вызова println!. Если же проверка не был пройден, все несущие сведения сообщения, а также описание ошибки будут выведены на окно вывода.

-

Например, в коде (11-10) функция выводит значение свойства с поясняющим текстовым сообщением, а также возвращает целочисленное постоянных значенийное значение 10. Далее следует проверка, который имеет правильный входной свойство и проверка, который имеет ошибочный входной свойство:

-

Файл: src/lib.rs

-
fn prints_and_returns_10(a: i32) -> i32 {
-    println!("I got the value {a}");
-    10
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn this_test_will_pass() {
-        let value = prints_and_returns_10(4);
-        assert_eq!(value, 10);
-    }
-
-    #[test]
-    fn this_test_will_fail() {
-        let value = prints_and_returns_10(8);
-        assert_eq!(value, 5);
-    }
-}
-

Приложение 11-10: Проверка функции, которая использует макрос println!

-

Итог вывода на окно вывода приказы cargo test:

-
$ cargo test
-   Compiling silly-function v0.1.0 (file:///projects/silly-function)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
-     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)
-
-running 2 tests
-test tests::this_test_will_fail ... FAILED
-test tests::this_test_will_pass ... ok
-
-failures:
-
----- tests::this_test_will_fail stdout ----
-I got the value 8
-thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
-assertion `left == right` failed
-  left: 10
- right: 5
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::this_test_will_fail
-
-test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Обратите внимание, что нигде в этом выводе мы не видим сообщения I got the value 4 , которое печатается при выполнении пройденного проверки. Этот вывод был записан. Итог неудачного проверки, I got the value 8 , появляется в разделе итоговых итогов проверки, который также показывает причину неудачного проверки.

-

Если мы хотим видеть напечатанные итоги прохождения проверок, мы можем сказать Rust, чтобы он также показывал итоги успешных проверок с помощью --show-output.

-
$ cargo test -- --show-output
-
-

Когда мы снова запускаем проверки из Приложения 11-10 с флагом --show-output , мы видим следующий итог:

-
$ cargo test -- --show-output
-   Compiling silly-function v0.1.0 (file:///projects/silly-function)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
-     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)
-
-running 2 tests
-test tests::this_test_will_fail ... FAILED
-test tests::this_test_will_pass ... ok
-
-successes:
-
----- tests::this_test_will_pass stdout ----
-I got the value 4
-
-
-successes:
-    tests::this_test_will_pass
-
-failures:
-
----- tests::this_test_will_fail stdout ----
-I got the value 8
-thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
-assertion `left == right` failed
-  left: 5
- right: 10
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::this_test_will_fail
-
-test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Запуск подмножества проверок по имени

-

Бывают случаи, когда в запуске всех проверок нет необходимости и нужно запустить только несколько проверок. Если вы работаете над функцией и хотите запустить проверки, которые исследуют её работу - это было бы удобно. Вы можете это сделать, используя приказ cargo test, передав в качестве переменной имена проверок.

-

Для отображения, как запустить объединение проверок, мы создадим объединение проверок для функции add_two function, как показано в Приложении 11-11, и постараемся выбрать какие из них запускать.

-

Файл: src/lib.rs

-
pub fn add_two(a: usize) -> usize {
-    a + 2
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn add_two_and_two() {
-        let result = add_two(2);
-        assert_eq!(result, 4);
-    }
-
-    #[test]
-    fn add_three_and_two() {
-        let result = add_two(3);
-        assert_eq!(result, 5);
-    }
-
-    #[test]
-    fn one_hundred() {
-        let result = add_two(100);
-        assert_eq!(result, 102);
-    }
-}
-

Приложение 11-11: Три проверки с различными именами

-

Если вы выполните приказ cargo test без уточняющих переменных, все проверки выполнятся одновременно:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 3 tests
-test tests::add_three_and_two ... ok
-test tests::add_two_and_two ... ok
-test tests::one_hundred ... ok
-
-test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Запуск одного проверки

-

Мы можем запустить один проверка с помощью указания его имени в приказу cargo test:

-
$ cargo test one_hundred
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.69s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test tests::one_hundred ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
-
-
-

Был запущен только проверка с названием one_hundred; два других проверки не соответствовали этому названию. Итоги проверки с помощью вывода 2 filtered out дают нам понять, что у нас было больше проверок, но они не были запущены.

-

Таким образом мы не можем указать имена нескольких проверок; будет использоваться только первое значение, указанное для cargo test . Но есть способ запустить несколько проверок.

-

Использование фильтров для запуска нескольких проверок

-

Мы можем указать часть имени проверки, и будет запущен любой проверка, имя которого соответствует этому значению. Например, поскольку имена двух наших проверок содержат add, мы можем запустить эти два, запустив cargo test add:

-
$ cargo test add
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 2 tests
-test tests::add_three_and_two ... ok
-test tests::add_two_and_two ... ok
-
-test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
-
-
-

Этот приказ запускала все проверки с add в имени и отфильтровывала проверка с именем one_hundred . Также обратите внимание, что звено, в котором появляется проверка, становится частью имени проверки, поэтому мы можем запускать все проверки в звене, фильтруя имя звена.

-

Пренебрежение проверок

-

Бывает, что некоторые проверки требуют продолжительного времени для своего исполнения, и вы хотите исключить их из исполнения при запуске cargo test. Вместо перечисления в приказной строке всех проверок, которые вы хотите запускать, вы можете определять проверки, требующие много времени для прогона, свойством ignore, чтобы исключить их, как показано здесь:

-

Файл: src/lib.rs

-
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-// ANCHOR: here
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_works() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-
-    #[test]
-    #[ignore]
-    fn expensive_test() {
-        // code that takes an hour to run
-    }
-}
-// ANCHOR_END: here
-

После #[test] мы добавляем строку #[ignore] в проверка, который хотим исключить. Теперь, когда мы запускаем наши проверки, it_works запускается, а expensive_test пренебрегается:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 2 tests
-test tests::expensive_test ... ignored
-test tests::it_works ... ok
-
-test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Функция expensive_test помечена как ignored. Если вы хотите выполнить только пренебреженные проверки, вы можете воспользоваться приказом cargo test -- --ignored:

-
$ cargo test -- --ignored
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test expensive_test ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Управляя тем, какие проверки запускать, вы можете быть уверены, что итоги вашего cargo test будут быстрыми. Когда вы дойдёте до особенности, где имеет смысл проверить итоги проверок ignored, и у вас есть время дождаться их итогов, вы можете запустить их с помощью cargo test -- --ignored. Если вы хотите запустить все проверки независимо от того, пренебрегаются они или нет, выполните cargo test -- --include-ignored.

-

Создание проверок

-

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

-

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

-

Состоящие из звеньев проверки

-

Целью состоящих из звеньев проверок является проверка каждого раздела кода, изолированное от остального возможностей, чтобы можно было быстро понять, что работает неправильно или не так как ожидается. Мы разместим состоящие из звеньев проверки в папке src, в каждый проверяемый файл. Но в Ржавчина принято создавать проверяемый звено tests и код проверки сохранять в файлы с таким же именем, как составляющие которые предстоит проверять. Также необходимо добавить изложение cfg(test) к этому звену.

-

Звено проверок и изложение #[cfg(test)]

-

Изложение #[cfg(test)] у звена с проверкими указывает Ржавчина собирать и запускать только код проверок, когда выполняется приказ cargo test, а не когда запускается cargo build. Это уменьшает время сборки, если вы только хотите собрать библиотеку и уменьшить место для результирующих собранных артефактов, потому что проверки не будут включены. Вы увидите что, по причине того, что встроенные проверки помещаются в другой папка им не нужна изложение #[cfg(test)]. Тем не менее, так как состоящие из звеньев проверки идут в тех же файлах что и основной код, вы будете использовать #[cfg(test)] чтобы указать, что они не должны быть включены в собранный итог.

-

Напомним, что когда мы порождали новый дело adder в первом разделе этой главы, то Cargo создал для нас код ниже:

-

Файл: src/lib.rs

-
pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_works() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-}
-

Этот код является самостоятельно созданным проверочным звеном. Свойство cfg предназначен для настройке и говорит Rust, что следующий элемент должен быть включён только учитывая определённую возможность настройке. В этом случае возможностью настройке является test, который предоставлен в Ржавчина для сборки и запуска текущих проверок. Используя свойство cfg, Cargo собирает только проверочный код при активном запуске проверок приказом cargo test. Это включает в себя любые вспомогательные функции, которые могут быть в этом звене в дополнение к функциям помеченным #[test].

-

Проверка закрытых функций (private)

-

Сообщество программистов не имеет однозначного мнения по поводу проверять или нет закрытые функции. В некоторых языках весьма сложно или даже невозможно проверять такие функции. Независимо от того, какой технологии проверки вы придерживаетесь, в Ржавчина закрытые функции можно проверять. Рассмотрим приложение 11-12 с закрытой функцией internal_adder.

-

Файл: src/lib.rs

-
pub fn add_two(a: usize) -> usize {
-    internal_adder(a, 2)
-}
-
-fn internal_adder(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn internal() {
-        let result = internal_adder(2, 2);
-        assert_eq!(result, 4);
-    }
-}
-

Приложение 11-12: Проверка закрытых функций

-

Обратите внимание, что функция internal_adder не помечена как pub. Проверки — это просто Ржавчина код, а звено tests — это ещё один звено. Как мы обсуждали в разделе “Пути для ссылки на элемент в дереве звеньев“, элементы в дочерних звенах могут использовать элементы из своих родительских звеньев. В этом проверке мы помещаем все элементы родительского звена test в область видимости с помощью use super::* и затем проверка может вызывать internal_adder. Если вы считаете, что закрытые функции не нужно проверять, то Ржавчина не заставит вас это сделать.

-

Встраиваемые проверки

-

В Ржавчина встроенные проверки являются полностью внешними по отношению к вашей библиотеке. Они используют вашу библиотеку так же, как любой другой код, что означает, что они могут вызывать только функции, которые являются частью открытого API библиотеки. Их целью является проверка, много ли частей вашей библиотеки работают вместе правильно. У звеньев кода правильно работающих самостоятельно, могут возникнуть сбоев при встраивани, поэтому проверочное покрытие встроенного кода также важно. Для создания встроенных проверок сначала нужен папка tests .

-

Папка tests

-

Мы создаём папку tests в корневой папке вашего дела, рядом с папкой src. Cargo знает, что искать файлы с встроенными проверкими нужно в этой папки. После этого мы можем создать столько проверочных файлов, сколько захотим, и Cargo собирает каждый из файлов в отдельный ящик.

-

Давайте создадим встроенный проверку. Рядом с кодом из приложения 11-12, который всё ещё в файле src/lib.rs, создайте папка tests, создайте новый файл с именем tests/integration_test.rs. Устройства папок должна выглядеть так:

-
adder
-├── Cargo.lock
-├── Cargo.toml
-├── src
-│   └── lib.rs
-└── tests
-    └── integration_test.rs
-
-

Введите код из приложения 11-13 в файл tests/integration_test.rs file:

-

Файл: tests/integration_test.rs

-
use adder::add_two;
-
-#[test]
-fn it_adds_two() {
-    let result = add_two(2);
-    assert_eq!(result, 4);
-}
-

Приложение 11-13: Встраиваемая проверка функция из ящика adder

-

Каждый файл в папке tests представляет собой отдельный ящик, поэтому нам нужно подключить нашу библиотеку в область видимости каждого проверочного ящика. По этой причине мы добавляем use adder в верхней части кода, что не нужно нам делать в состоящих из звеньев проверках.

-

Нам не нужно вносить примечания в код в tests/integration_test.rs с помощью #[cfg(test)]. Cargo особым образом обрабатывает папка tests и собирает файлы в этом папке только тогда, когда мы запускаем приказ cargo test. Запустите cargo test сейчас:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.31s
-     Running unittests src/lib.rs (target/debug/deps/adder-1082c4b063a8fbe6)
-
-running 1 test
-test tests::internal ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-     Running tests/integration_test.rs (target/debug/deps/integration_test-1082c4b063a8fbe6)
-
-running 1 test
-test it_adds_two ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Выходные данные представлены тремя разделами: состоящие из звеньев проверки, встроенные проверки и проверки документации. Обратите внимание, что если какой-нибудь проверка в одной из разделов не пройдёт, последующие разделы выполняться не будут. Например, если состоящий из звеньев проверка провалился, не будет выведено итогов встроенных и документационных проверок, потому что эти проверки будут выполняться только в том случае, если все состоящие из звеньев проверки завершатся успешно.

-

Первый раздел для состоящих из звеньев проверок такой же, как мы видели: одна строка для каждого состоящего из звеньев проверки (один с именем internal, который мы добавили в приложении 11-12), а затем сводная строка для состоящих из звеньев проверок.

-

Раздел встроенных проверок начинается со строки Running tests/integration_test.rs. Далее идёт строка для каждой проверочной функции в этом встроенном проверке и итоговая строка для итогов встроенного проверки непосредственно перед началом раздела Doc-tests adder.

-

Каждый файл встроенного проверки имеет свой собственный раздел, поэтому, если мы добавим больше файлов в папка tests, то здесь будет больше разделов встроенного проверки.

-

Мы всё ещё можем запустить определённую функцию в встроенных проверках, указав имя проверка функции в качестве переменной в cargo test. Чтобы запустить все проверки в определенном файле встроенных проверок, используйте переменная --test сопровождаемый именем файла у приказы cargo test:

-
$ cargo test --test integration_test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.64s
-     Running tests/integration_test.rs (target/debug/deps/integration_test-82e7799c1bc62298)
-
-running 1 test
-test it_adds_two ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Этот приказ запускает только проверки в файле tests/integration_test.rs.

-

Подзвенья в встроенных проверках

-

По мере добавления большего количества встроенных проверок, можно создать более одного файла в папке tests, чтобы легче создавать их; например, вы можете собъединять функции проверки по возможности, которую они проверяют. Как упоминалось ранее, каждый файл в папке tests собран как отдельный ящик, что полезно для создания отдельных областей видимости, чтобы более точно создавать видимость то, как конечные пользователи будут использовать ваш ящик. Однако это означает, что файлы в папке tests ведут себя не так, как файлы в src, как вы узнали в Главе 7 относительно того как разделить код на звенья и файлы.

-

Различное поведение файлов в папке tests наиболее заметно, когда у вас есть набор вспомогательных функций, которые будут полезны в нескольких встроенных проверочных файлах. Представим, что вы пытаетесь выполнить действия, описанные в разделе «Разделение звеньев в разные файлы» главы 7, чтобы извлечь их в общий звено. Например, вы создали файл tests/common.rs и помеисполнения в него функцию setup, содержащую некоторый код, который вы будете вызывать из разных проверочных функций в нескольких проверочных файлах

-

Файл: tests/common.rs

-
pub fn setup() {
-    // setup code specific to your library's tests would go here
-}
-

Когда мы снова запустим проверки, мы увидим новый раздел в итогах проверок для файла common.rs, хотя этот файл не содержит никаких проверочных функций, более того, мы даже не вызывали функцию setup откуда либо:

-
$ cargo test
-   Compiling adder v0.1.0 (file:///projects/adder)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.89s
-     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
-
-running 1 test
-test tests::internal ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-     Running tests/common.rs (target/debug/deps/common-92948b65e88960b4)
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-     Running tests/integration_test.rs (target/debug/deps/integration_test-92948b65e88960b4)
-
-running 1 test
-test it_adds_two ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests adder
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Упоминание файла common и появление в итогах выполнения проверок сообщения вида running 0 tests - это не то, чего мы хотели. Мы только хотели выделить некоторый общий код, который будет использоваться другими файлами встроенных проверок.

-

Чтобы звено common больше не появлялся в итогах выполнения проверок, вместо файла tests/common.rs мы создадим файл tests/common/mod.rs. Директория дела теперь выглядит следующим образом:

-
├── Cargo.lock
-├── Cargo.toml
-├── src
-│   └── lib.rs
-└── tests
-    ├── common
-    │   └── mod.rs
-    └── integration_test.rs
-
-

Здесь используется более раннее соглашение об именовании файлов, которое Ржавчина также понимает. Мы говорили об этом в разделе “Иные пути к файлам” главы 7. Именование файла таким образом говорит, что Ржавчина не должен рассматривать звено common как файл встроенных проверок. Когда мы перемещаем код функции setup в файл tests/common/mod.rs и удаляем файл tests/common.rs, дополнительный раздел больше не будет отображаться в итогах проверок. Файлы в подпапких папки tests не собираются как отдельные ящики или не появляются в итогах выполнения проверок.

-

После того, как мы создали файл tests/common/mod.rs, мы можем использовать его в любых файлах встроенных проверок как обычный звено. Вот пример вызова функции setup из проверки it_adds_two в файле tests/integration_test.rs:

-

Файл: tests/integration_test.rs

-
use adder::add_two;
-
-mod common;
-
-#[test]
-fn it_adds_two() {
-    common::setup();
-
-    let result = add_two(2);
-    assert_eq!(result, 4);
-}
-

Обратите внимание, что объявление mod common; совпадает с объявлением звена, которое отображено в приложении 7-21. Затем в проверочной функции мы можем вызвать функцию common::setup().

-

Встраиваемые проверки для двоичных ящиков

-

Если наш дело является двоичным ящиком, который содержит только src/main.rs и не содержит src/lib.rs, мы не сможем создать встроенные проверки в папке tests и подключить функции определённые в файле src/main.rs в область видимости с помощью указания use. Только библиотечные ящики могут предоставлять функции, которые можно использовать в других ящиках; двоичные ящики предназначены только для самостоятельного запуска.

-

Это одна из причин, почему дела на Rust, которые порождают исполняемые звенья, обычно имеют простой файл src/main.rs, который в свою очередь вызывает логику, которая находится в файле src/lib.rs. Используя такую устройство, встроенные проверки могут проверить библиотечный ящик, используя оператор use для подключения важного возможностей. Если этот важный возможности работает, то и небольшое количество кода в файле src/main.rs также будет работать, а значит этот небольшой объём кода не нуждается в проверке.

-

Итоги

-

Средства проверки языка Ржавчина предоставляют способ задать ожидаемое поведение кода, чтобы убедиться, что он всё ещё соответствует вашим ожиданиям даже после внесения изменений. Состоящие из звеньев проверки проверяют различные части библиотеки по отдельности и могут проверять закрытые подробности выполнения. Встраиваемые проверки проверяют, что части библиотеки работают правильно сообща. Эти проверки используют для проверки кода открытый API библиотеки, таким же образом, как его будет использовать внешний код. Хотя система видов Ржавчина и правила владения помогают предотвратить некоторые виды ошибок, проверки по-прежнему важны для уменьшения количества логических ошибок, связанных с поведением вашего кода.

-

Давайте объединим знания, полученные в этой и предыдущей главах, чтобы поработать над делом!

-

Дело с вводом/выводом (I/O): создание с окном вывода приложения

-

В этой главе вы примените многие знания, полученные ранее, а также познакомитесь с ещё неизученными API встроенной библиотеки. Мы создадим окно выводаное приложение, которое будет взаимодействовать с файлом и с окно выводаным вводом / выводом, чтобы применить в некоторых подходах Rust, с которыми вы уже знакомы.

-

Скорость, безопасность, сборка в один исполняемый файл и кроссплатформенность делают Ржавчина наилучшим языком для создания окно выводаных средств, так что в нашем деле мы создадим свою собственную исполнение обычной утилиты поиска grep, что расшифровывается, как "вездесущеее средство поиска и печати" (globally search a regular expression and print). В простейшем случае grep используется для поиска в выбранном файле указанного текста. Для этого утилита grep получает имя файла и текст в качестве переменных. Далее она читает файл, находит и выводит строки, содержащие искомый текст.

-

Попутно мы покажем, как сделать так, чтобы наше окно выводаное приложение использовало возможности окна вызова, которые используются многими другими окно выводаными средствами. Мы будем читать значение переменной окружения, чтобы позволить пользователю настроить поведение нашего средства. Мы также будем печатать сообщения об ошибках в обычный окно выводаный поток ошибок ( stderr ) вместо принятого вывода ( stdout ), чтобы, к примеру, пользователь мог перенаправить успешный вывод в файл, в то время, как сообщения об ошибках останутся на экране.

-

Один из участников Rust-сообщества, Andrew Gallant, уже выполнил полновозможный, очень быстрый подобие программы grep и назвал его ripgrep. По сравнению с ним, наша исполнение будет довольно простой, но эта глава даст вам знания, которые нужны для понимания существующих дел, таких как ripgrep.

-

Наш дело grep будет использовать ранее изученные подходы:

-
    -
  • Создание кода (используя то, что вы узнали о звенах в главе 7)
  • -
  • Использование векторов и строк (собрания, глава 8)
  • -
  • Обработка ошибок (Глава 9)
  • -
  • Использование особенностей и времени жизни там, где это необходимо (глава 10)
  • -
  • Написание проверок ( Глава 11)
  • -
-

Мы также кратко представим замыкания, повторители и предметы особенности, которые будут объяснены подробно в главах 13 и 17.

-

Принятие переменных приказной строки

-

Создадим новый дело с окном вывода приложения как обычно с помощью приказы cargo new. Мы назовём дело minigrep, чтобы различать наше приложение от grep, которое возможно уже есть в вашей системе.

-
$ cargo new minigrep
-     Created binary (application) `minigrep` project
-$ cd minigrep
-
-

Первая задача - заставить minigrep принимать два переменной приказной строки: путь к файлу и строку для поиска. То есть мы хотим иметь возможность запускать нашу программу через cargo run, с использованием двойного дефиса, чтобы указать, что следующие переменные предназначены для нашей программы, а не для cargo, строки для поиска и пути к файлу в котором нужно искать, как описано ниже:

-
$ cargo run -- searchstring example-filename.txt
-
-

В данный мгновение программа созданная cargo new не может обрабатывать переменные, которые мы ей передаём. Некоторые существующие библиотеки на crates.io могут помочь с написанием программы, которая принимает переменные приказной строки, но так как вы просто изучаете эту подход, давайте выполняем эту возможность сами.

-

Чтение значений переменных

-

Чтобы minigrep мог воспринимать значения переменных приказной строки, которые мы ему передаём, нам понадобится функция std::env::args, входящая в обычную библиотеку Rust. Эта функция возвращает повторитель переменных приказной строки, переданных в minigrep. Мы подробно рассмотрим повторители в главе 13. Пока вам достаточно знать две вещи об повторителях: повторители порождают серию значений, и мы можем вызвать способ collect у повторителя, чтобы создать из него собрание, например вектор, который будет содержать все элементы, произведённые повторителем.

-

Код представленный в Приложении 12-1 позволяет вашей программе minigrep читать любые переданные ей переменные приказной строки, а затем собирать значения в вектор.

-

Файл: src/main.rs

-
use std::env;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-    dbg!(args);
-}
-

Приложение 12-1: Собираем переменные приказной строки в вектор и выводим их на печать

-

Сначала мы вводим звено std::env в область видимости с помощью указания use, чтобы мы могли использовать его функцию args. Обратите внимание, что функция std::env::args вложена в два уровня звеньев. Как мы обсуждали в главе 7, в случаях, когда нужная функция оказывается вложенной в более чем один звено, советуется выносить в область видимости родительский звено, а не функцию. Таким образом, мы можем легко использовать другие функции из std::env. Это менее двусмысленно, чем добавление use std::env::args и последующий вызов функции только с args, потому что args может быть легко принят за функцию, определённую в текущем звене.

-
-

Функция args и недействительный Юникод символ (Unicode)

-

Обратите внимание, что std::env::args вызовет панику, если какой-либо переменная содержит недопустимый символ Юникода. Если вашей программе необходимо принимать переменные, содержащие недопустимые символы Unicode, используйте вместо этого std::env::args_os. Эта функция возвращает повторитель , который выдаёт значения OsString вместо значений String. Мы решили использовать std::env::args здесь для простоты, потому что значения OsString отличаются для каждой площадки и с ними сложнее работать, чем со значениями String.

-
-

В первой строке кода функции main мы вызываем env::args и сразу используем способ collect, чтобы превратить повторитель в вектор содержащий все полученные значения. Мы можем использовать функцию collect для создания многих видов собраний, поэтому мы явно определяем вид args чтобы указать, что мы хотим вектор строк. Хотя нам очень редко нужно определять виды в Rust, collect - это одна из функций, с которой вам часто нужна изложение вида, потому что Ржавчина не может сам вывести какую собрание вы хотите.

-

И в заключение мы печатаем вектор с помощью отладочного макроса. Попробуем запустить код сначала без переменных, а затем с двумя переменнойми:

-
$ cargo run
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
-     Running `target/debug/minigrep`
-[src/main.rs:5:5] args = [
-    "target/debug/minigrep",
-]
-
-
$ cargo run -- needle haystack
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.57s
-     Running `target/debug/minigrep needle haystack`
-[src/main.rs:5:5] args = [
-    "target/debug/minigrep",
-    "needle",
-    "haystack",
-]
-
-

Обратите внимание, что первое значение в векторе "target/debug/minigrep" является названием нашего двоичного файла. Это соответствует поведению списка переменных в Си, позволяя программам использовать название с которым они были вызваны при выполнении. Часто бывает удобно иметь доступ к имени программы, если вы хотите распечатать его в сообщениях или изменить поведение программы в зависимости от того, какой псевдоним приказной строки был использован для вызова программы. Но для целей этой главы, мы пренебрегаем его и сохраним только два переменной, которые нам нужны.

-

Сохранения значений переменных в переменные

-

На текущий мгновение программа может получить доступ к значениям, указанным в качестве переменных приказной строки. Теперь нам требуется сохранять значения этих двух переменных в переменных, чтобы мы могли использовать их в остальных частях программы. Мы сделаем это в приложении 12-2.

-

Файл: src/main.rs

-
use std::env;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let query = &args[1];
-    let file_path = &args[2];
-
-    println!("Searching for {query}");
-    println!("In file {file_path}");
-}
-

Приложение 12-2: Создание переменных для хранения значений переменных искомой подстроки и пути к файлу

-

Как видно из распечатки вектора, имя программы занимает первое значение в векторе по адресу args[0], значит, переменные начинаются с порядкового указателя 1. Первый переменная minigrep - это строка, которую мы ищем, поэтому мы помещаем ссылку на первый переменная в переменную query. Вторым переменнаяом является путь к файлу, поэтому мы помещаем ссылку на второй переменная в переменную file_path.

-

Для проверки соблюдения правил работы нашей программы, значения переменных выводятся в окно вывода. Далее, запустим нашу программу со следующими переменнойми: test и sample.txt:

-
$ cargo run -- test sample.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep test sample.txt`
-Searching for test
-In file sample.txt
-
-

Отлично, программа работает! Нам нужно чтобы значения переменных были сохранены в правильных переменных. Позже мы добавим обработку ошибок с некоторыми вероятными ошибочными случаейми, например, когда пользователь не предоставляет переменные; сейчас мы пренебрегаем эту случай и поработаем над добавлением возможности чтения файла.

-

Чтение файла

-

Теперь добавим возможность чтения файла, указанного как переменная приказной строки file_path. Во-первых, нам нужен пример файла для проверки: мы будем использовать файл с небольшим объёмом текста в несколько строк с несколькими повторяющимися словами. В приложении 12-3 представлено стихотворение Эмили Дикинсон, которое будет хорошо работать! Создайте файл с именем poem.txt в корне вашего дела и введите стихотворение "I’m nobody! Who are you?"

-

Файл: poem.txt

-
I'm nobody! Who are you?
-Are you nobody, too?
-Then there's a pair of us - don't tell!
-They'd banish us, you know.
-
-How dreary to be somebody!
-How public, like a frog
-To tell your name the livelong day
-To an admiring bog!
-
-

Приложение 12-3: Стихотворение Эмили Дикинсон - хороший пример для проверки

-

Текст на месте, изменените src/main.rs и добавьте код для чтения файла, как показано в приложении 12-4.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    // --snip--
-    let args: Vec<String> = env::args().collect();
-
-    let query = &args[1];
-    let file_path = &args[2];
-
-    println!("Searching for {query}");
-    println!("In file {file_path}");
-
-    let contents = fs::read_to_string(file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-

Приложение 12-4: Чтение содержимого файла указанного во втором переменной

-

Во-первых, мы добавляем ещё одну указанию use чтобы подключить соответствующую часть встроенной библиотеки: нам нужен std::fs для обработки файлов.

-

В main мы добавили новую указанию: функция fs::read_to_string принимает file_path, открывает этот файл и возвращает содержимое файла как std::io::Result<String>.

-

После этого, мы снова добавили временную указанию println! для печати значения contents после чтения файла, таким образом мы можем проверить, что программа отрабатывает до этого места.

-

Давайте запустим этот код с любой строкой в качестве первого переменной приказной строки (потому что мы ещё не выполнили поисковую часть) и файл poem.txt как второй переменная:

-
$ cargo run -- the poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep the poem.txt`
-Searching for the
-In file poem.txt
-With text:
-I'm nobody! Who are you?
-Are you nobody, too?
-Then there's a pair of us - don't tell!
-They'd banish us, you know.
-
-How dreary to be somebody!
-How public, like a frog
-To tell your name the livelong day
-To an admiring bog!
-
-
-

Отлично! Этот код прочитал и затем напечатал содержимое файла. Но у программы есть несколько недостатков. Прежде всего, функция main решает слишком много задач: как правило функция понятнее и проще в обслуживании если она воплощает только одну мысль. Другая неполадка заключается в том, что мы не обрабатываем ошибки так хорошо, как могли бы. Пока наша программа небольшая, то эти недостатки не являются большой неполадкой, но по мере роста программы эти недостатки будет всё труднее исправлять. Хорошей опытом является начинать переработка кода на ранней стадии разработки программы, потому что гораздо проще перерабатывать код меньшие объёмы кода. Мы сделаем это далее.

-

Переработка кода для улучшения выделения на звенья и обработки ошибок

-

Для улучшения программы мы исправим 4 имеющихся сбоев, связанных со устройством программы и тем как обрабатываются вероятные ошибки. Во-первых, функция main на данный мгновение решает две задачи: анализирует переменные приказной строки и читает файлы. По мере роста программы количество отдельных задач, которые обрабатывает функция main, будет увеличиваться. Поскольку эта функция получает больше обязанностей, то становится все труднее понимать её, труднее проверять и труднее изменять, не сломав одну из её частей. Лучше всего разделить возможность, чтобы каждая функция отвечала за одну задачу.

-

Эта неполадка также связана со второй неполадкой: хотя переменные query и file_path являются переменными настройке нашей программы, переменные вида contents используются для выполнения логики программы. Чем длиннее становится main, тем больше переменных нам нужно будет добавить в область видимости; чем больше у нас переменных в области видимости, тем сложнее будет отслеживать назначение каждой переменной. Лучше всего собъединять переменные настройке в одну устройство, чтобы сделать их назначение понятным.

-

Третья неполадка заключается в том, что мы используем expect для вывода сведений об ошибке при неполадке с чтением файла, но сообщение об ошибке просто выведет текстShould have been able to read the file. Чтение файла может не сработать по разным причинам, например: файл не найден или у нас может не быть разрешения на его чтение. Сейчас же, независимо от случаи, мы напечатаем одно и то же сообщение об ошибке, что не даст пользователю никакой сведений!

-

В-четвёртых, мы используем expect неоднократно для обработки различных ошибок и если пользователь запускает нашу программу без указания достаточного количества переменных он получит ошибку index out of bounds из Rust, что не совсем понятно описывает неполадку. Было бы лучше, если бы весь код обработки ошибок находился в одном месте, чтобы тем, кто будет поддерживать наш код в дальнейшем, нужно было бы вносить изменения только здесь, если потребуется изменить логику обработки ошибок. Наличие всего кода обработки ошибок в одном месте заверяет, что мы напечатаем сообщения, которые будут иметь смысл для наших конечных пользователей.

-

Давайте решим эти четыре сбоев путём переработки кода нашего дела.

-

Разделение ответственности для двоичных дел

-

Внутренняя неполадка распределения ответственности за выполнение нескольких задач функции main является общей для многих двоичных дел. В итоге Ржавчина сообщество разработало этап для использования в качестве руководства по разделению ответственности двоичной программы, когда код в main начинает увеличиваться. Этап имеет следующие шаги:

-
    -
  • Разделите код программы на два файла main.rs и lib.rs. Перенесите всю логику работы программы в файл lib.rs.
  • -
  • Пока ваша логика синтаксического анализа приказной строки мала, она может оставаться в файле main.rs.
  • -
  • Когда логика синтаксического анализа приказной строки становится сложной, извлеките её из main.rs и переместите в lib.rs.
  • -
-

Полезные обязанности, которые остаются в функции main после этого этапа должно быть ограничено следующим:

-
    -
  • Вызов логики разбора приказной строки со значениями переменных
  • -
  • Настройка любой другой настройке
  • -
  • Вызов функции run в lib.rs
  • -
  • Обработка ошибки, если run возвращает ошибку
  • -
-

Этот образец о разделении ответственности: main.rs занимается запуском программы, а lib.rs обрабатывает всю логику задачи. Поскольку нельзя проверить функцию main напрямую, то такая устройства позволяет проверить всю логику программы путём перемещения её в функции внутри lib.rs. Единственный код, который остаётся в main.rs будет достаточно маленьким, чтобы проверить его соблюдение правил прочитав код. Давайте переработаем нашу программу, следуя этому этапу.

-

Извлечение обработчика переменных

-

Мы извлечём возможность для разбора переменных в функцию, которую вызовет main для подготовки к перемещению логики разбора приказной строки в файл src/lib.rs. Приложение 12-5 показывает новый запуск main, который вызывает новую функцию parse_config, которую мы определим сначала в src/main.rs.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let (query, file_path) = parse_config(&args);
-
-    // --snip--
-
-    println!("Searching for {query}");
-    println!("In file {file_path}");
-
-    let contents = fs::read_to_string(file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-
-fn parse_config(args: &[String]) -> (&str, &str) {
-    let query = &args[1];
-    let file_path = &args[2];
-
-    (query, file_path)
-}
-

Приложение 12-5: Выделение функции parse_config из main

-

Мы все ещё собираем переменные приказной строки в вектор, но вместо присваивания значение переменной с порядковым указателем 1 переменной query и значение переменной с порядковым указателем 2 переменной с именем file_path в функции main, мы передаём весь вектор в функцию parse_config. Функция parse_config затем содержит логику, которая определяет, какой переменная идёт в какую переменную и передаёт значения обратно в main. Мы все ещё создаём переменные query и file_path в main, но main больше не несёт ответственности за определение соответствия переменной приказной строки и соответствующей переменной.

-

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

-

Объединение настроечных переменных

-

Мы можем сделать ещё один маленький шаг для улучшения функции parse_config. На данный мгновение мы возвращаем упорядоченный ряд, но затем мы немедленно разделяем его снова на отдельные части. Это признак того, что, возможно, пока у нас нет правильной абстракции.

-

Ещё один индикатор, который показывает, что есть место для улучшения, это часть config из parse_config, что подразумевает, что два значения, которые мы возвращаем, связаны друг с другом и оба являются частью одного настроечного значения. В настоящее время мы не отражаем этого смысла в устройстве данных, кроме объединения двух значений в упорядоченный ряд; мы могли бы поместить оба значения в одну устройство и дать каждому из полей устройства понятное имя. Это облегчит будущую поддержку этого кода, чтобы понять, как различные значения относятся друг к другу и какое их назначение.

-

В приложении 12-6 показаны улучшения функции parse_config .

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = parse_config(&args);
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    // --snip--
-
-    println!("With text:\n{contents}");
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-fn parse_config(args: &[String]) -> Config {
-    let query = args[1].clone();
-    let file_path = args[2].clone();
-
-    Config { query, file_path }
-}
-

Приложение 12-6: Переработка кода функции parse_config, чтобы возвращать образец устройства Config

-

Мы добавили устройство с именем Config объявленную с полями назваными как query и file_path. Ярлык parse_config теперь указывает, что она возвращает значение Config. В теле parse_config, где мы возвращали срезы строк, которые ссылаются на значения String в args, теперь мы определяем Config как содержащие собственные String значения. Переменная args в main является владельцем значений переменной и позволяют функции parse_config только одалживать их, что означает, что мы бы нарушили правила заимствования Rust, если бы Config попытался бы взять во владение значения в args .

-

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

-
-

К при использовании способа cloneСуществует тенденция в среде программистов Ржавчина избегать использования clone, т.к. это понижает эффективность работы кода. В Главе 13, вы изучите более эффективные способы, которые могут подойти в подобной случаи. Но сейчас можно воспроизводить несколько строк, чтобы продолжить работу, потому что вы сделаете эти повторы только один раз, а ваше имя файла и строка запроса будут очень маленькими. Лучше иметь работающую программу, которая немного неэффективна, чем пытаться заранее перерабатывать код при первом написании. По мере приобретения опыта работы с Ржавчина вам будет проще начать с наиболее эффективного решения, но сейчас вполне приемлемо вызвать clone.

-
-

Мы обновили код в main поэтому он помещает образец Config возвращённый из parse_config в переменную с именем config, и мы обновили код, в котором ранее использовались отдельные переменные query и file_path, так что теперь он использует вместо этого поля в устройстве Config.

-

Теперь наш код более чётко передаёт то, что query и file_path связаны и что цель из использования состоит в том, чтобы настроить, как программа будет работать. Любой код, который использует эти значения знает, что может найти их в именованных полях образца config по их назначению.

-

Создание строителя для устройства Config

-

Пока что мы извлекли логику, отвечающую за синтаксический анализ переменных приказной строки из main и помеисполнения его в функцию parse_config. Это помогло нам увидеть, что значения query и file_path были связаны и что их отношения должны быть отражены в нашем коде. Затем мы добавили устройство Config в качестве названия связанных общей целью query и file_path и чтобы иметь возможность вернуть именованные значения как имена полей устройства из функции parse_config.

-

Итак, теперь целью функции parse_config является создание образца Config, мы можем изменить parse_config из простой функции на функцию названную new, которая связана со устройством Config. Выполняя это изменение мы сделаем код более идиоматичным. Можно создавать образцы видов в встроенной библиотеке, такие как String с помощью вызова String::new. Точно так же изменив название parse_config на название функции new, связанную с Config, мы будем уметь создавать образцы Config, вызывая Config::new. Приложение 12-7 показывает изменения, которые мы должны сделать.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::new(&args);
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-
-    // --snip--
-}
-
-// --snip--
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn new(args: &[String]) -> Config {
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Config { query, file_path }
-    }
-}
-

Приложение 12-7: Переименование parse_config в Config::new

-

Мы обновили main где вызывали parse_config, чтобы вместо этого вызывалась Config::new. Мы изменили имя parse_config на new и перенесли его внутрь раздела impl, который связывает функцию new с Config. Попробуйте снова собрать код, чтобы убедиться, что он работает.

-

Исправление ошибок обработки

-

Теперь мы поработаем над исправлением обработки ошибок. Напомним, что попытки получить доступ к значениям в векторе args с порядковым указателем 1 или порядковым указателем 2 приведут к панике, если вектор содержит менее трёх элементов. Попробуйте запустить программу без каких-либо переменных; это будет выглядеть так:

-
$ cargo run
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep`
-thread 'main' panicked at src/main.rs:27:21:
-index out of bounds: the len is 1 but the index is 1
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Строка index out of bounds: the len is 1 but the index is 1 является сообщением об ошибке предназначенной для программистов. Она не поможет нашим конечным пользователям понять, что случилось и что они должны сделать вместо этого. Давайте исправим это сейчас.

-

Улучшение сообщения об ошибке

-

В приложении 12-8 мы добавляем проверку в функцию new, которая будет проверять, что срез достаточно длинный, перед попыткой доступа по порядковым указателям 1 и 2. Если срез не достаточно длинный, программа паникует и отображает улучшенное сообщение об ошибке.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::new(&args);
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    // --snip--
-    fn new(args: &[String]) -> Config {
-        if args.len() < 3 {
-            panic!("not enough arguments");
-        }
-        // --snip--
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Config { query, file_path }
-    }
-}
-

Приложение 12-8: Добавление проверки количества переменных

-

Этот код похож на функцию Guess::new написанную в приложении 9-13, где мы вызывали panic!, когда value переменной вышло за пределы допустимых значений. Здесь вместо проверки на рядзначений, мы проверяем, что длина args не менее 3 и остальная часть функции может работать при условии, что это условие было выполнено. Если в args меньше трёх элементов, это условие будет истинным и мы вызываем макрос panic! для немедленного завершения программы.

-

Имея нескольких лишних строк кода в new, давайте запустим программу снова без переменных, чтобы увидеть, как выглядит ошибка:

-
$ cargo run
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep`
-thread 'main' panicked at src/main.rs:26:13:
-not enough arguments
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-

Этот вывод лучше: у нас теперь есть разумное сообщение об ошибке. Тем не менее, мы также имеем постороннюю сведения, которую мы не хотим предоставлять нашим пользователям. Возможно, использованная техника, которую мы использовали в приложении 9-13, не является лучшей для использования: вызов panic! больше подходит для программирования сбоев, чем решения сбоев, как обсуждалось в главе 9. Вместо этого мы можем использовать другую технику, о которой вы узнали в главе 9 [возвращая Result], которая указывает либо на успех, либо на ошибку.

- -

-

Возвращение Result вместо вызова panic!

-

Мы можем вернуть значение Result, которое будет содержать образец Config в успешном случае и опишет неполадку в случае ошибки. Мы так же изменим функцию new на build потому что многие программисты ожидают что new никогда не завершится неудачей. Когда Config::build взаимодействует с main, мы можем использовать вид Result как сигнал возникновения сбоев. Затем мы можем изменить main, чтобы преобразовать исход Err в более применимую ошибку для наших пользователей без окружающего текста вроде thread 'main' и RUST_BACKTRACE, что происходит при вызове panic!.

-

Приложение 12-9 показывает изменения, которые нужно внести в возвращаемое значения функции Config::build, и в тело функции, необходимые для возврата вида Result. Заметьте, что этот код не собирается, пока мы не обновим main, что мы и сделаем в следующем приложении.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::new(&args);
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-

Приложение 12-9. Возвращение вида Result из Config::build

-

Наша функция build теперь возвращает Result с образцом Config в случае успеха и &'static str в случае ошибки. Значения ошибок всегда будут строковыми записями, которые имеют время жизни 'static.

-

Мы внесли два изменения в тело функции build: вместо вызова panic!, когда пользователь не передаёт достаточно переменных, мы теперь возвращаем Err значение и мы завернули возвращаемое значение Config в Ok . Эти изменения заставят функцию соответствовать своей новой ярлыке вида.

-

Возвращение значения Err из Config::build позволяет функции main обработать значение Result возвращённое из функции build и выйти из этапа более чисто в случае ошибки.

- -

-

Вызов Config::build и обработка ошибок

-

Чтобы обработать ошибку и вывести более дружественное сообщение об ошибке, нам нужно обновить код main для обработки Result, возвращаемого из Config::build как показано в приложении 12-10. Мы также возьмём на себя ответственность за выход из программы приказной строки с ненулевым кодом ошибки panic! и выполняем это вручную. Не нулевой значение выхода - это соглашение, которое указывает этапу, который вызывает нашу программу, что программа завершилась с ошибкой.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-use std::process;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        println!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    // --snip--
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-

Приложение 12-10. Выход с кодом ошибки если создание новой Config терпит неудачу

-

В этом приложении мы использовали способ, который мы ещё не рассматривали подробно: unwrap_or_else, который в встроенной библиотеке определён как Result<T, E>. Использование unwrap_or_else позволяет нам определить некоторые пользовательские ошибки обработки, не содержащие panic!. Если Result является значением Ok, поведение этого способа подобно unwrap: возвращает внутреннее значение из обёртки Ok. Однако, если значение является значением Err, то этот способ вызывает код замыкания, которое является анонимной функцией, определённой заранее и передаваемую в качестве переменной в unwrap_or_else. Мы рассмотрим замыкания более подробно в главе 13. В данный мгновение, вам просто нужно знать, что unwrap_or_else передаст внутреннее значение Err, которое в этом случае является постоянной строкой not enough arguments, которое мы добавили в приложении 12-9, в наше замыкание как переменная err указанное между вертикальными линиями. Код в замыкании может затем использовать значение err при выполнении.

-

Мы добавили новую строку use, чтобы подключить process из встроенной библиотеки в область видимости. Код в замыкании, который будет запущен в случае ошибки содержит только две строчки: мы печатаем значение err и затем вызываем process::exit. Функция process::exit немедленно остановит программу и вернёт номер, который был передан в качестве кода состояния выхода. Это похоже на обработку с помощью макроса panic!, которую мы использовали в приложении 12-8, но мы больше не получаем весь дополнительный вывод. Давай попробуем:

-
$ cargo run
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
-     Running `target/debug/minigrep`
-Problem parsing arguments: not enough arguments
-
-

Замечательно! Этот вывод намного дружелюбнее для наших пользователей.

-

Извлечение логики из main

-

Теперь, когда мы закончили переработка кода разбора настройке, давайте обратимся к логике программы. Как мы указали в разделе «Разделение ответственности в двоичных делах», мы извлечём функцию с именем run, которая будет содержать всю логику, присутствующую в настоящее время в функции main и которая не связана с настройкой настройке или обработкой ошибок. Когда мы закончим, то main будет краткой, легко проверяемой и мы сможем написать проверки для всей остальной логики.

-

Код 12-11 отображает извлечённую логику в функцию run. Мы делаем маленькое, инкрементальное приближение к извлечению функции. Код всё ещё сосредоточен в файле src/main.rs:

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-use std::process;
-
-fn main() {
-    // --snip--
-
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        println!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    run(config);
-}
-
-fn run(config: Config) {
-    let contents = fs::read_to_string(config.file_path)
-        .expect("Should have been able to read the file");
-
-    println!("With text:\n{contents}");
-}
-
-// --snip--
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-

Приложение 12-11. Извлечение функции run, содержащей остальную логику программы

-

Функция run теперь содержит всю оставшуюся логику из main, начиная от чтения файла. Функция run принимает образец Config как переменная.

-

Возврат ошибок из функции run

-

Оставшаяся логика программы выделена в функцию run, где мы можем улучшить обработку ошибок как мы уже делали с Config::build в приложении 12-9. Вместо того, чтобы позволить программе паниковать с помощью вызова expect, функция run вернёт Result<T, E>, если что-то пойдёт не так. Это позволит далее окне выводадировать логику обработки ошибок в main удобным способом. Приложение 12-12 показывает изменения, которые мы должны внести в ярлык и тело run.

-

Файл: src/main.rs

-
use std::env;
-use std::fs;
-use std::process;
-use std::error::Error;
-
-// --snip--
-
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        println!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    run(config);
-}
-
-fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    println!("With text:\n{contents}");
-
-    Ok(())
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-

Приложение 12-12. Изменение функции run для возврата Result

-

Здесь мы сделали три значительных изменения. Во-первых, мы изменили вид возвращаемого значения функции run на Result<(), Box<dyn Error>> . Эта функция ранее возвращала вид () и мы сохраняли его как значение, возвращаемое в случае Ok.

-

Для вида ошибки мы использовали предмет особенность Box<dyn Error> (и вверху мы подключили вид std::error::Error в область видимости с помощью указания use). Мы рассмотрим особенности предметов в главе 17. Сейчас просто знайте, что Box<dyn Error> означает, что функция будет возвращать вид выполняющий особенность Error, но не нужно указывать, какой именно будет вид возвращаемого значения. Это даёт возможность возвращать значения ошибок, которые могут быть разных видов в разных случаях. Ключевое слово dyn сокращение для слова «изменяемый».

-

Во-вторых, мы убрали вызов expect в пользу использования оператора ?, как мы обсудили в главе 9. Скорее, чем вызывать panic! в случае ошибки, оператор ? вернёт значение ошибки из текущей функции для вызывающего, чтобы он её обработал.

-

В-третьих, функция run теперь возвращает значение Ok в случае успеха. В ярлыке функции run успешный вид объявлен как (), который означает, что нам нужно обернуть значение единичного вида в значение Ok. Данный правила написания Ok(()) поначалу может показаться немного странным, но использование () выглядит как идиоматический способ указать, что мы вызываем run для его побочных эффектов; он не возвращает значение, которое нам нужно.

-

Когда вы запустите этот код, он собирается, но отобразит предупреждение:

-
$ cargo run -- the poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-warning: unused `Result` that must be used
-  --> src/main.rs:19:5
-   |
-19 |     run(config);
-   |     ^^^^^^^^^^^
-   |
-   = note: this `Result` may be an `Err` variant, which should be handled
-   = note: `#[warn(unused_must_use)]` on by default
-help: use `let _ = ...` to ignore the resulting value
-   |
-19 |     let _ = run(config);
-   |     +++++++
-
-warning: `minigrep` (bin "minigrep") generated 1 warning
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.71s
-     Running `target/debug/minigrep the poem.txt`
-Searching for the
-In file poem.txt
-With text:
-I'm nobody! Who are you?
-Are you nobody, too?
-Then there's a pair of us - don't tell!
-They'd banish us, you know.
-
-How dreary to be somebody!
-How public, like a frog
-To tell your name the livelong day
-To an admiring bog!
-
-
-

Rust говорит, что наш код пренебрег Result значение и значение Result может указывать на то, что произошла ошибка. Но мы не проверяем, была ли ошибка и сборщик напоминает нам, что мы, вероятно, хотели здесь выполнить некоторый код обработки ошибок! Давайте исправим эту неполадку сейчас.

-

Обработка ошибок, возвращённых из run в main

-

Мы будем проверять и обрабатывать ошибки используя способику, подобную той, которую мы использовали для Config::build в приложении 12-10, но с небольшой разницей:

-

Файл: src/main.rs

-
use std::env;
-use std::error::Error;
-use std::fs;
-use std::process;
-
-fn main() {
-    // --snip--
-
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        println!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    if let Err(e) = run(config) {
-        println!("Application error: {e}");
-        process::exit(1);
-    }
-}
-
-fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    println!("With text:\n{contents}");
-
-    Ok(())
-}
-
-struct Config {
-    query: String,
-    file_path: String,
-}
-
-impl Config {
-    fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-

Мы используем if let вместо unwrap_or_else чтобы проверить, возвращает ли run значение Err и вызывается process::exit(1), если это так. Функция run не возвращает значение, которое мы хотим развернуть способом unwrap, таким же образом как Config::build возвращает образец Config. Так как run возвращает () в случае успеха и мы заботимся только об обнаружении ошибки, то нам не нужно вызывать unwrap_or_else, чтобы вернуть развёрнутое значение, потому что оно будет только ().

-

Тело функций if let и unwrap_or_else одинаковы в обоих случаях: мы печатаем ошибку и выходим.

-

Разделение кода на библиотечный ящик

-

Наш дело minigrep пока выглядит хорошо! Теперь мы разделим файл src/main.rs и поместим некоторый код в файл src/lib.rs. Таким образом мы сможем его проверять и чтобы в файле src/main.rs было меньшее количество полезных обязанностей.

-

Давайте перенесём весь код не относящийся к функции main из файла src/main.rs в новый файл src/lib.rs:

-
    -
  • Определение функции run
  • -
  • Соответствующие указания use
  • -
  • Определение устройства Config
  • -
  • Определение функции Config::build
  • -
-

Содержимое src/lib.rs должно иметь ярлыки, показанные в приложении 12-13 (мы опуисполнения тела функций для краткости). Обратите внимание, что код не будет собираться пока мы не изменим src/main.rs в приложении 12-14.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        // --snip--
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    // --snip--
-    let contents = fs::read_to_string(config.file_path)?;
-
-    println!("With text:\n{contents}");
-
-    Ok(())
-}
-

Приложение 12-13. Перемещение Config и run в src/lib.rs

-

Мы добавили определетель доступа pub к устройстве Config, а также её полям, к способу build и функции run. Теперь у нас есть библиотечный ящик, который содержит открытый API, который мы можем проверять!

-

Теперь нам нужно подключить код, который мы перемеисполнения в src/lib.rs, в область видимости двоичного ящика внутри src/main.rs, как показано в приложении 12-14.

-

Файл: src/main.rs

-
use std::env;
-use std::process;
-
-use minigrep::Config;
-
-fn main() {
-    // --snip--
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        println!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    println!("Searching for {}", config.query);
-    println!("In file {}", config.file_path);
-
-    if let Err(e) = minigrep::run(config) {
-        // --snip--
-        println!("Application error: {e}");
-        process::exit(1);
-    }
-}
-

Приложение 12-14. Использование ящика библиотеки minigrep внутри src/main.rs

-

Мы добавляем use minigrep::Config для подключения вида Config из ящика библиотеки в область видимости двоичного ящика и добавляем к имени функции run приставка нашего ящика. Теперь все функции должны быть подключены и должны работать. Запустите программу с cargo run и убедитесь, что все работает правильно.

-

Уф! Было много работы, но мы настроены на будущий успех. Теперь проще обрабатывать ошибки и мы сделали код более состоящим из звеньев. С этого особенности почти вся наша работа будет выполняться внутри src/lib.rs.

-

Давайте воспользуемся этой новой выделения на звенья, сделав что-то, что было бы трудно со старым кодом, но легко с новым кодом: мы напишем несколько проверок!

-

Развитие возможности библиотеки разработкой на основе проверок

-

Теперь, когда мы извлекли логику в src/lib.rs и оставили разбор переменных приказной строки и обработку ошибок в src/main.rs, стало гораздо проще писать проверки для основной возможности нашего кода. Мы можем вызывать функции напрямую с различными переменнойми и проверить возвращаемые значения без необходимости вызова нашего двоичного файла из приказной строки.

-

В этом разделе в программу minigrep мы добавим логику поиска с использованием этапа разработки через проверка (TDD), который следует этим шагам:

-
    -
  1. Напишите проверка, который завершается неудачей, и запустите его, чтобы убедиться, что он не сработал именно по той причине, которую вы ожидаете.
  2. -
  3. Пишите или изменяйте ровно столько кода, чтобы успешно выполнился новый проверку.
  4. -
  5. Выполните переработка кода кода, который вы только что добавили или изменили, и убедитесь, что проверки продолжают проходить.
  6. -
  7. Повторите с шага 1!
  8. -
-

Хотя это всего лишь один из многих способов написания программного обеспечения, TDD может помочь в разработке кода. Написание проверки перед написанием кода, обеспечивающего прохождение проверки, помогает поддерживать высокое покрытие проверкими на протяжении всего этапа разработки.

-

Мы проверим выполнение возможности, которая делает поиск строки запроса в содержимом файла и создание списка строк, соответствующих запросу. Мы добавим эту возможность в функцию под названием search.

-

Написание проверки с ошибкой

-

Поскольку они нам больше не нужны, давайте удалим указания с println!, которые мы использовали для проверки поведения программы в src/lib.rs и src/main.rs. Затем в src/lib.rs мы добавим звено tests с проверочной функцией, как делали это в главе 11. Проверочная функция определяет поведение, которое мы хотим проверить в функции search: она должна принимать запрос и текст для поиска, а возвращать только те строки из текста, которые содержат запрос. В приложении 12-15 показан этот проверка, который пока не собирается.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 12-15: Создание безуспешного проверки для функции search, которую мы хотим создать

-

Этот проверка ищет строку "duct". Текст, в котором мы ищем, состоит из трёх строк, только одна из которых содержит "duct" (обратите внимание, что обратная косая черта после открывающей двойной кавычки говорит Ржавчина не помещать символ новой строки в начало содержимого этого строкового записи). Мы проверяем, что значение, возвращаемое функцией search, содержит только ожидаемую нами строку.

-

Мы не можем запустить этот проверка и увидеть сбой, потому что проверка даже не собирается: функции search ещё не существует! В соответствии с принципами TDD мы добавим ровно столько кода, чтобы проверка собирался и запускался, добавив определение функции search, которая всегда возвращает пустой вектор, как показано в приложении 12-16. Потом проверка должен собраться и потерпеть неудачу при запуске, потому что пустой вектор не равен вектору, содержащему строку "safe, fast, productive."

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    vec![]
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 12-16. Определение функции search, достаточное, чтобы проверка собрался

-

Заметьте, что в ярлыке search нужно явно указать время жизни 'a для переменной contents и возвращаемого значения. Напомним из Главы 10, что свойства времени жизни указывают с временем жизни какого переменной связано время жизни возвращаемого значения. В данном случае мы говорим, что возвращаемый вектор должен содержать срезы строк, ссылающиеся на содержимое переменной contents (а не переменной query).

-

Другими словами, мы говорим Rust, что данные, возвращаемые функцией search, будут жить до тех пор, пока живут данные, переданные в функцию search через переменная contents. Это важно! Чтобы ссылки были действительными, данные, на которые ссылаются с помощью срезов тоже должны быть действительными; если сборщик предполагает, что мы делаем строковые срезы переменной query, а не переменной contents, он неправильно выполнит проверку безопасности.

-

Если мы забудем изложении времени жизни и попробуем собрать эту функцию, то получим следующую ошибку:

-
$ cargo build
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-error[E0106]: missing lifetime specifier
-  --> src/lib.rs:28:51
-   |
-28 | pub fn search(query: &str, contents: &str) -> Vec<&str> {
-   |                      ----            ----         ^ expected named lifetime parameter
-   |
-   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `query` or `contents`
-help: consider introducing a named lifetime parameter
-   |
-28 | pub fn search<'a>(query: &'a str, contents: &'a str) -> Vec<&'a str> {
-   |              ++++         ++                 ++              ++
-
-For more information about this error, try `rustc --explain E0106`.
-error: could not compile `minigrep` (lib) due to 1 previous error
-
-

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

-

Другие языки программирования не требуют от вас связывания в ярлыке переменных с возвращаемыми значениями, но после определённой опытов вам станет проще. Можете сравнить этот пример с разделом «Проверка ссылок с временами жизни» главы 10.

-

Запустим проверку:

-
$ cargo test
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.97s
-     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
-
-running 1 test
-test tests::one_result ... FAILED
-
-failures:
-
----- tests::one_result stdout ----
-thread 'tests::one_result' panicked at src/lib.rs:44:9:
-assertion `left == right` failed
-  left: ["safe, fast, productive."]
- right: []
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::one_result
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Отлично. Наш проверка не сработал, как мы и ожидали. Давайте сделаем так, чтобы он срабатывал!

-

Написание кода для прохождения проверки

-

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

-
    -
  • Повторение по каждой строке содержимого.
  • -
  • Проверить, содержит ли данная строка искомую.
  • -
  • Если это так, добавить её в список значений, которые мы возвращаем.
  • -
  • Если это не так, ничего не делать.
  • -
  • Вернуть список итогов.
  • -
-

Давайте проработаем каждый шаг, начиная с перебора строк.

-

Перебор строк с помощью способа lines

-

В Ржавчина есть полезный способ для построчной повторения строк, удобно названный lines, как показано в приложении 12-17. Обратите внимание, код пока не собирается.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    for line in contents.lines() {
-        // do something with line
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 12-17: Повторение по каждой строке из contents

-

Способ lines возвращает повторитель . Мы подробно поговорим об повторителях в Главе 13, но вспомните, что вы видели этот способ использования повторителя в Приложении 3-5, где мы использовали цикл for с повторителем, чтобы выполнить некоторый код для каждого элемента в собрания.

-

Поиск в каждой строке текста запроса

-

Далее мы проверяем, содержит ли текущая строка нашу искомую строку. К счастью, у строк есть полезный способ contains, который именно это и делает! Добавьте вызов способа contains в функции search, как показано в приложении 12-18. Обратите внимание, что это все ещё не собирается.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    for line in contents.lines() {
-        if line.contains(query) {
-            // do something with line
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 12-18. Добавление проверки, содержится ли query в строке

-

На данный мгновение мы наращиваем возможность. Чтобы заставить это собираться, нам нужно вернуть значение из тела функции, как мы указали в ярлыке функции.

-

Сохранение совпавшей строки

-

Чтобы завершить эту функцию, нам нужен способ сохранить совпадающие строки, которые мы хотим вернуть. Для этого мы можем создать изменяемый вектор перед циклом for и вызывать способ push для сохранения line в векторе. После цикла for мы возвращаем вектор, как показано в приложении 12-19.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 12-19: Сохраняем совпавшие строки, чтобы впоследствии их можно было вернуть

-

Теперь функция search должна возвратить только строки, содержащие query, и проверка должен пройти. Запустим его:

-
$ cargo test
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.22s
-     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
-
-running 1 test
-test tests::one_result ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-     Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests minigrep
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Наш проверка пройден, значит он работает!

-

На этом этапе мы могли бы рассмотреть возможности изменения выполнения функции поиска, сохраняя прохождение проверок и поддерживая имеющуюся возможность. Код в функции поиска не так уж плох, но он не использует некоторые полезные функции повторителей. Вернёмся к этому примеру в главе 13, где будем исследовать повторители подробно, и посмотрим как его улучшить.

-

Использование функции search в функции run

-

Теперь, когда функция search работает и проверена, нужно вызвать search из нашей функции run. Нам нужно передать значение config.query и contents, которые run читает из файла, в функцию search. Тогда run напечатает каждую строку, возвращаемую из search:

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    for line in search(&config.query, &contents) {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Мы по-прежнему используем цикл for для возврата каждой строки из функции search и её печати.

-

Теперь вся программа должна работать! Давайте попробуем сначала запустить её со словом «frog», которое должно вернуть только одну строчку из стихотворения Эмили Дикинсон:

-
$ cargo run -- frog poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.38s
-     Running `target/debug/minigrep frog poem.txt`
-How public, like a frog
-
-

Здорово! Теперь давайте попробуем слово, которое будет соответствовать нескольким строкам, например «body»:

-
$ cargo run -- body poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep body poem.txt`
-I'm nobody! Who are you?
-Are you nobody, too?
-How dreary to be somebody!
-
-

И наконец, давайте удостоверимся, что мы не получаем никаких строк, когда ищем слово, отсутствующее в стихотворении, например «monomorphization»:

-
$ cargo run -- monomorphization poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep monomorphization poem.txt`
-
-

Отлично! Мы создали собственную простое-исполнение обычного средства и научились тому, как внутренне выстроить

-

приложения. Мы также немного узнали о файловом вводе и выводе, временах жизни, проверке и разборе переменных приказной строки.

-

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

-

Работа с переменными окружения

-

Мы улучшим minigrep, добавив дополнительную функцию: возможность для поиска без учёта регистра, которую пользователь может включить с помощью переменной среды окружения. Мы могли бы сделать эту функцию свойствоом приказной строки и потребовать, чтобы пользователи вводили бы её каждый раз при её применении, но вместо этого мы будем использовать переменную среды окружения, что позволит нашим пользователям устанавливать переменную среды один раз и все поиски будут не чувствительны к регистру в этом окно вызоваьном сеансе.

-

Написание ошибочного проверки для функции search с учётом регистра

-

Мы, во-первых, добавим новую функцию search_case_insensitive, которую мы будем вызывать, когда переменная окружения содержит значение. Мы продолжим следовать этапу TDD, поэтому первый шаг - это снова написать не проходящий проверку. Мы добавим новый проверка для новой функции search_case_insensitive и переименуем наш старый проверка из one_result в case_sensitive, чтобы прояснить различия между двумя проверкими, как показано в приложении 12-20.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    for line in search(&config.query, &contents) {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 12-20. Добавление нового не проходящего проверки для функции поиска нечувствительной к регистру, которую мы собираемся добавить

-

Обратите внимание, что мы также отредактировали содержимое переменной contents из старого проверки. Мы добавили новую строку с текстом "Duct tape.", используя заглавную D, которая не должна соответствовать запросу "duct" при поиске с учётом регистра. Такое изменение старого проверки помогает избежать случайного нарушения возможности поиска чувствительного к регистру, который мы уже выполнили. Этот проверка должен пройти сейчас и должен продолжать выполняться успешно, пока мы работаем над поиском без учёта регистра.

-

Новый проверка для поиска нечувствительного к регистру использует "rUsT" качестве строки запроса. В функции search_case_insensitive, которую мы собираемся выполнить, запрос "rUsT" должен соответствовать строке содержащей "Rust:" с большой буквы R и соответствовать строке "Trust me.", хотя обе имеют разные регистры из запроса. Это наш не проходящий проверка, он не собирается, потому что мы ещё не определили функцию search_case_insensitive. Не стесняйтесь добавлять скелет выполнение, которая всегда возвращает пустой вектор, подобно тому, как мы это делали для функции search в приложении 12-16, чтобы увидеть сборку проверки и его сбой.

-

Выполнение функции search_case_insensitive

-

Функция search_case_insensitive, показанная в приложении 12-21, будет почти такая же, как функция search. Разница лишь в том, что текст будет в нижнем регистре для query и для каждой line, так что для любого регистра входных переменных это будет тот же случай, когда мы проверяем, содержит ли строка запрос.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    for line in search(&config.query, &contents) {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 12-21. Определение функции search_case_insensitive с уменьшением регистра строки запроса и строки содержимого перед их сравнением

-

Сначала преобразуем в нижний регистр строку query и сохраняем её в затенённой переменной с тем же именем. Вызов to_lowercase для строки запроса необходим, так что независимо от того, будет ли пользовательский запрос "rust" , "RUST", "Rust" или "rUsT", мы будем преобразовывать запрос к "rust" и делать значение нечувствительным к регистру. Хотя to_lowercase будет обрабатывать Unicode, он не будет точным на 100%. Если бы мы писали существующее приложение, мы бы хотели проделать здесь немного больше работы, но этот раздел посвящён переменным среды, а не Unicode, поэтому мы оставим это здесь.

-

Обратите внимание, что query теперь имеет вид String, а не срез строки, потому что вызов to_lowercase создаёт новые данные, а не ссылается на существующие. К примеру, запрос: "rUsT" это срез строки не содержащий строчных букв u или t, которые мы можем использовать, поэтому мы должны выделить новую String, содержащую «rust». Когда мы передаём запрос query в качестве переменной способа contains, нам нужно добавить знак, поскольку ярлык contains, определена для приёмы среза строки.

-

Затем мы добавляем вызов to_lowercase для каждой строки line для преобразования к нижнему регистру всех символов. Теперь, когда мы преобразовали line и query в нижний регистр, мы найдём совпадения независимо от того, в каком регистре находится переменная с запросом.

-

Давайте посмотрим, проходит ли эта выполнение проверки:

-
$ cargo test
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.33s
-     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
-
-running 2 tests
-test tests::case_insensitive ... ok
-test tests::case_sensitive ... ok
-
-test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-     Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-   Doc-tests minigrep
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-
-

Отлично! Проверки прошли. Теперь давайте вызовем новую функцию search_case_insensitive из функции run. Во-первых, мы добавим свойство настройке в устройство Config для переключения между поиском с учётом регистра и без учёта регистра. Добавление этого поля приведёт к ошибкам сборщика, потому что мы ещё нигде не объявим это поле:

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Мы добавили поле ignore_case, которое содержит логическое значение. Далее нам нужна функция run, чтобы проверить значение поля ignore_case и использовать его, чтобы решить, вызывать ли функцию search или функцию search_case_insensitive, как показано в приложении 12-22. Этот код все ещё не собирается.

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 12-22. Вызов либо search, либо search_case_insensitive на основе значения в config.ignore_case

-

Наконец, нам нужно проверить переменную среды. Функции для работы с переменными среды находятся в звене env встроенной библиотеки, поэтому мы хотим подключить этот звено в область видимости в верхней части src/lib.rs. Затем мы будем использовать функцию var из звена env для проверки установлено ли любое значение в переменной среды с именем IGNORE_CASE, как показано в приложении 12-23.

-

Файл: src/lib.rs

-
use std::env;
-// --snip--
-
-use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        let ignore_case = env::var("IGNORE_CASE").is_ok();
-
-        Ok(Config {
-            query,
-            file_path,
-            ignore_case,
-        })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 12-23. Проверка переменной среды с именем IGNORE_CASE

-

Здесь мы создаём новую переменную ignore_case. Чтобы установить её значение, мы вызываем функцию env::var и передаём ей имя переменной окружения IGNORE_CASE. Функция env::var возвращает Result, который будет успешным исходом Ok содержащий значение переменной среды, если переменная среды установлена. Он вернёт исход Err, если переменная окружения не установлена.

-

Мы используем способ is_ok у Result, чтобы проверить установлена ли переменная окружения, что будет означать, что программа должна выполнить поиск без учёта регистра. Если переменная среды IGNORE_CASE не содержит любого значения, то is_ok вернёт значение false и программа выполнит поиск c учётом регистра. Мы не заботимся о значении переменной среды, нас важно только установлена она или нет, поэтому мы проверяем is_ok, а не используем unwrap, expect или любой другой способ, который мы видели у Result.

-

Мы передаём значение переменной ignore_case образцу Config, чтобы функция run могла прочитать это значение и решить, следует ли вызывать search или search_case_insensitive, как мы выполнили в приложении 12-22.

-

Давайте попробуем! Во-первых, мы запустим нашу программу без установленной переменной среды и с помощью значения запроса to, который должен соответствовать любой строке, содержащей слово «to» в нижнем регистре:

-
$ cargo run -- to poem.txt
-   Compiling minigrep v0.1.0 (file:///projects/minigrep)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/minigrep to poem.txt`
-Are you nobody, too?
-How dreary to be somebody!
-
-

Похоже, все ещё работает! Теперь давайте запустим программу с IGNORE_CASE, установленным в 1, но с тем же значением запроса to.

-
$ IGNORE_CASE=1 cargo run -- to poem.txt
-
-

Если вы используете PowerShell, вам нужно установить переменную среды и запустить программу двумя приказми, а не одной:

-
PS> $Env:IGNORE_CASE=1; cargo run -- to poem.txt
-
-

Это заставит переменную окружения IGNORE_CASE сохраниться до конца сеанса работы окне вывода. Переменную можно отключить с помощью приказы Remove-Item:

-
PS> Remove-Item Env:IGNORE_CASE
-
-

Мы должны получить строки, содержащие «to», которые могут иметь заглавные буквы:

- -
Are you nobody, too?
-How dreary to be somebody!
-To tell your name the livelong day
-To an admiring bog!
-
-

Отлично, мы также получили строки, содержащие «To»! Наша программа minigrep теперь может выполнять поиск без учёта регистра, управляемая переменной среды. Теперь вы знаете, как управлять свойствами, заданными с помощью переменных приказной строки или переменных среды.

-

Некоторые программы допускают использование переменных и переменных среды для одной и той же настройке. В таких случаях программы решают, что из них имеет больший приоритет. Для другого самостоятельного упражнения попробуйте управлять чувствительностью к регистру с помощью переменной приказной строки или переменной окружения. Решите, переменная приказной строки или переменная среды будет иметь приоритет, если программа выполняется со значениями "учитывать регистр" в одном случае, и "пренебрегать регистр" в другом.

-

Звено std::env содержит много других полезных функций для работы с переменными среды: ознакомьтесь с его документацией, чтобы узнать доступные.

-

Запись сообщений ошибок в поток ошибок вместо принятого потока вывода

-

В данный мгновение мы записываем весь наш вывод в окно вызова, используя функцию println!. В большинстве окно вызоваов предоставлено два вида вывода: обычный поток вывода ( stdout ) для общей сведений и обычный поток ошибок ( stderr ) для сообщений об ошибках. Это различие позволяет пользователям выбирать, направлять ли успешный вывод программы в файл, но при этом выводить сообщения об ошибках на экран.

-

Функция println! может печатать только в обычный вывод, поэтому мы должны использовать что-то ещё для печати в обычный поток ошибок.

-

Проверка, куда записываются ошибки

-

Во-первых, давайте посмотрим, как содержимое, напечатанное из minigrep в настоящее время записывается в обычный вывод, включая любые сообщения об ошибках, которые мы хотим вместо этого записать в обычный поток ошибок. Мы сделаем это, перенаправив обычный поток вывода в файл и намеренно вызовем ошибку. Мы не будем перенаправлять обычный поток ошибок, поэтому любой содержание, отправленный в поток принятых ошибок будет продолжать отображаться на экране.

-

Ожидается, что программы приказной строки будут отправлять сообщения об ошибках в обычный поток ошибок, поэтому мы все равно можем видеть сообщения об ошибках на экране, даже если мы перенаправляем обычный поток вывода в файл. Наша программа в настоящее время не ведёт себя правильно: мы увидим, что она сохраняет вывод сообщения об ошибке в файл!

-

Чтобы отобразить это поведение, мы запустим программу с помощью > и именем файла output.txt в который мы хотим перенаправить обычный поток вывода. Мы не будем передавать никаких переменных, что должно вызвать ошибку:

-
$  cargo run > output.txt
-
-

правила написания > указывает оболочке записывать содержимое принятого вывода в output.txt вместо экрана. Мы не увидели сообщение об ошибке, которое мы ожидали увидеть на экране, так что это означает, что оно должно быть в файле. Вот что содержит output.txt:

-
Problem parsing arguments: not enough arguments
-
-

Да, наше сообщение об ошибке выводится в обычный вывод. Гораздо более полезнее, чтобы подобные сообщения об ошибках печатались в встроенной поток ошибок, поэтому в файл попадают только данные из успешного запуска. Мы поменяем это.

-

Печать ошибок в поток ошибок

-

Мы будем использовать код в приложении 12-24, чтобы изменить способ вывода сообщений об ошибках. Из-за переработки кода, который мы делали ранее в этой главе, весь код, который печатает сообщения об ошибках, находится в одной функции: main. Обычная библиотека предоставляет макрос eprintln!который печатает в обычный поток ошибок, поэтому давайте изменим два места, где мы вызывали println! для печати ошибок, чтобы использовать eprintln! вместо этого.

-

Файл: src/main.rs

-
use std::env;
-use std::process;
-
-use minigrep::Config;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        eprintln!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    if let Err(e) = minigrep::run(config) {
-        eprintln!("Application error: {e}");
-        process::exit(1);
-    }
-}
-

Запись сообщений об ошибках в Standard Error вместо Standard Output используя eprintln!

-

Давайте снова запустим программу таким же образом, без каких-либо переменных и перенаправим обычный вывод с помощью >:

-
$ cargo run > output.txt
-Problem parsing arguments: not enough arguments
-
-

Теперь мы видим ошибку на экране и output.txt не содержит ничего, что мы ожидаем от программы приказной строки.

-

Давайте снова запустим программу с переменнойми, которые не вызывают ошибку, но все же перенаправляют обычный вывод в файл, например так:

-
$ cargo run -- to poem.txt > output.txt
-
-

Мы не увидим никакого вывода в окно вызова, а output.txt будет содержать наши итоги:

-

Файл: output.txt

-
Are you nobody, too?
-How dreary to be somebody!
-
-

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

-

Итоги

-

В этой главе были повторены некоторые основные подходы, которые вы изучили до сих пор и было рассказано, как выполнять обычные действия ввода-вывода в Rust. Используя переменные приказной строки, файлы, переменные среды и макросeprintln! для печати ошибок и вы теперь готовы писать приложения приказной строки. В сочетании с подходами из предыдущих главах, ваш код будет хорошо согласован, будет эффективно хранить данные в соответствующих устройствах, хорошо обрабатывать ошибки и хорошо проверяться.

-

Далее мы рассмотрим некоторые возможности Rust, на которые повлияли полезные языки: замыкания и повторители.

-

Полезные возможности языка: повторители и замыкания

-

Внешний вид языка Ржавчина черпал вдохновение из многих других языков и техник, среди которых значительное влияние оказало функциональное программирование. Программирование в функциональном исполнении подразумевает использование функций взначении предметов, передавая их в качестве переменных, возвращая их из других функций, присваивая их переменным для последующего выполнения и так далее.

-

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

-

Более подробно мы поговорим про:

-
    -
  • Замыкания - устройства, подобные функциям, которые можно помещать в переменные
  • -
  • Повторители — способ обработки последовательности элементов,
  • -
  • То, как, используя замыкания и повторители, улучшить работу с действиеми ввода-вывода в деле из главы 12
  • -
  • Производительность замыканий и повторителей (спойлер: они быстрее, чем вы думаете!)
  • -
-

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

-
-

-

Замыкания: анонимные функции, которые запечатлевают ("захватывают") своё окружение

-

Замыкания в Ржавчина - это анонимные функции, которые можно сохранять в переменных или передавать в качестве переменных другим функциям. Вы можете создать замыкание в одном месте, а затем вызвать его в каком-нибудь другом, чтобы выполнить обработку в ином среде. В отличие от функций, замыкания могут использовать значения из области видимости в которой они были определены. Мы выполним, как эти функции замыканий открывают возможности для повторного использования кода и изменения его поведения.

- -

- -

-

Захват переменных окружения с помощью замыкания

-

Сначала мы рассмотрим, как с помощью замыканий можно использовать предметы из области, в которой они вместе были определены, для их последующего использования. Вот сценарий: Время от времени наша предприятие по производству футболок в качестве акции дарит эксклюзивные футболки, выпущенные ограниченным тиражом, каким-нибудь пользователям из нашего списка рассылки. Люди из списка рассылки при желании могут выбрать любимый цвет в своём профиле. Если человек, выбранный для получения бесплатной футболки, указал свой любимый цвет, он получает футболку этого цвета. Если человек не указал свой любимый цвет, он получит рубашку того цвета, которых у предприятия на данный мгновение больше всего.

-

Существует множество способов выполнить это. В данном примере мы будем использовать перечисление ShirtColor, которое может быть двух исходов Red и Blue (для простоты ограничим количество доступных цветов этими двумя). Запасы предприятия мы представим устройством Inventory, которая состоит из поля shirts, содержащего Vec<ShirtColor>, в котором перечислены рубашки тех цветов, которые есть в наличии. Способ giveaway, определённый в Inventory, принимает необязательный свойство - цвет, предпочитаемый пользователем, выбранным для получения бесплатной рубашки, и возвращает тот цвет рубашки, который он получит в действительности. Эта схема показана в приложении 13-1:

-

Имя файла: src/main.rs

-
#[derive(Debug, PartialEq, Copy, Clone)]
-enum ShirtColor {
-    Red,
-    Blue,
-}
-
-struct Inventory {
-    shirts: Vec<ShirtColor>,
-}
-
-impl Inventory {
-    fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
-        user_preference.unwrap_or_else(|| self.most_stocked())
-    }
-
-    fn most_stocked(&self) -> ShirtColor {
-        let mut num_red = 0;
-        let mut num_blue = 0;
-
-        for color in &self.shirts {
-            match color {
-                ShirtColor::Red => num_red += 1,
-                ShirtColor::Blue => num_blue += 1,
-            }
-        }
-        if num_red > num_blue {
-            ShirtColor::Red
-        } else {
-            ShirtColor::Blue
-        }
-    }
-}
-
-fn main() {
-    let store = Inventory {
-        shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
-    };
-
-    let user_pref1 = Some(ShirtColor::Red);
-    let giveaway1 = store.giveaway(user_pref1);
-    println!(
-        "The user with preference {:?} gets {:?}",
-        user_pref1, giveaway1
-    );
-
-    let user_pref2 = None;
-    let giveaway2 = store.giveaway(user_pref2);
-    println!(
-        "The user with preference {:?} gets {:?}",
-        user_pref2, giveaway2
-    );
-}
-

Приложение 13-1: Случаей с раздачей рубашек предприятием

-

В магазине store, определённом в main, осталось две синие и одна красная рубашки для этой ограниченной акции. Мы вызываем способ giveaway для пользователя предпочитающего красную рубашку и для пользователя без каких-либо предпочтений.

-

Опять же, этот код мог быть выполнен множеством способов, но в данном случае, чтобы сосредоточиться на замыканиях, мы придерживались изученных ранее подходов, за исключением тела способа giveaway, в котором используется замыкание. В способе giveaway мы получаем пользовательское предпочтение цвета как свойство вида Option<ShirtColor> и вызываем способ unwrap_or_else на user_preference. Способ unwrap_or_else перечисления Option<T> определён встроенной библиотекой. Он принимает один переменная: замыкание без переменных, которое возвращает значение T (преобразуется в вид значения, которое окажется в исходе Some перечисления Option<T>, в нашем случае ShirtColor). Если Option<T> окажется исходом Some, unwrap_or_else вернёт значение из Some. А если Option<T> будет является исходом None, unwrap_or_else вызовет замыкание и вернёт значение, возвращённое замыканием.

-

В качестве переменной unwrap_or_else мы передаём замыкание || self.most_stocked(). Это замыкание, которое не принимает никаких свойств (если бы у замыкания были свойства, они были бы перечислены между двумя вертикальными полосами). В теле замыкания вызывается self.most_stocked(). Здесь мы определили замыкание, а выполнение unwrap_or_else такова, что выполнится оно позднее, когда потребуется получить итог.

-

Выполнение этого кода выводит:

-
$ cargo run
-   Compiling shirt-company v0.1.0 (file:///projects/shirt-company)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
-     Running `target/debug/shirt-company`
-The user with preference Some(Red) gets Red
-The user with preference None gets Blue
-
-

Важной особенностью здесь является то, что мы передали замыкание, которое вызывает self.most_stocked() текущего образца Inventory. Обычной библиотеке не нужно знать ничего о видах Inventory или ShirtColor, которые мы определили, или о логике, которую мы хотим использовать в этом сценарии. Замыкание определяет неизменяемую ссылку на self Inventory и передаёт её с указанным нами кодом в способ unwrap_or_else. А вот функции не могут определять своё окружение таким образом.

-

Выведение и изложение видов замыкания

-

Есть и другие различия между функциями и замыканиями. Замыкания обычно не требуют определенния видов входных свойств или возвращаемого значения, как это делается в функциях fn. Изложения видов требуются для функций, потому что виды являются частью явного внешней оболочки, предоставляемого пользователям. Жёсткое определение таких внешних оболочек важно для того, чтобы все были согласованы в том, какие виды значений использует и возвращает функция. А вот замыкания, напротив, не употребляются взначении подобных открытых внешних оболочек: они хранятся в переменных, используются не имея имени и незримо для пользователей нашей библиотеки.

-

Замыкания, как правило, небольшие и уместны в каком-то узконаправленном среде, а не в произвольных случаях. В этих ограниченных средах сборщик может вывести виды свойств и возвращаемого вида, подобно тому, как он может вывести виды большинства переменных (есть редкие случаи, когда сборщику также нужны изложении видов замыканий).

-

Как и в случае с переменными, мы можем добавить изложении видов, если хотим повысить ясность и чёткость описания ценой увеличения многословности, большей чем это необходимо. Определение видов для замыкания будет выглядеть как определение, показанное в приложении 13-2. В этом примере мы определяем замыкание и храним его в переменной, а не определяем замыкание в том месте, куда мы передаём его в качестве переменной, как это было в приложении 13-1.

-

Имя файла: src/main.rs

-
use std::thread;
-use std::time::Duration;
-
-fn generate_workout(intensity: u32, random_number: u32) {
-    let expensive_closure = |num: u32| -> u32 {
-        println!("calculating slowly...");
-        thread::sleep(Duration::from_secs(2));
-        num
-    };
-
-    if intensity < 25 {
-        println!("Today, do {} pushups!", expensive_closure(intensity));
-        println!("Next, do {} situps!", expensive_closure(intensity));
-    } else {
-        if random_number == 3 {
-            println!("Take a break today! Remember to stay hydrated!");
-        } else {
-            println!(
-                "Today, run for {} minutes!",
-                expensive_closure(intensity)
-            );
-        }
-    }
-}
-
-fn main() {
-    let simulated_user_specified_value = 10;
-    let simulated_random_number = 7;
-
-    generate_workout(simulated_user_specified_value, simulated_random_number);
-}
-

Приложение 13-2: Добавление необязательных наставлений видов свойств и возвращаемых значений в замыкании

-

С добавлением наставлений видов правила написания замыканий выглядит более похожим на правила написания функций. Здесь мы, для сравнения, определяем функцию, которая добавляет 1 к своему свойству, и замыкание, которое имеет такое же поведение. Мы добавили несколько пробелов, чтобы выровнять соответствующие части. Это показывает, что правила написания замыкания похож на правила написания функции, за исключением использования труб (вертикальная черта) и количества необязательного правил написания:

-
fn  add_one_v1   (x: u32) -> u32 { x + 1 }
-let add_one_v2 = |x: u32| -> u32 { x + 1 };
-let add_one_v3 = |x|             { x + 1 };
-let add_one_v4 = |x|               x + 1  ;
-

В первой строке показано определение функции, а во второй - полностью определенное определение замыкания. В третьей строке мы удаляем изложении видов из определения замыкания. В четвёртой строке мы убираем скобки, которые являются необязательными, поскольку тело замыкания содержит только одну действие. Это всё правильные определения, которые будут иметь одинаковое поведение при вызове. Строки add_one_v3 и add_one_v4 требуют, чтобы замыкания были вычислены до сборки, поскольку виды будут выведены из их использования. Это похоже на let v = Vec::new();, когда в Vec необходимо вставить либо изложении видов, либо значения некоторого вида, чтобы Ржавчина смог вывести вид.

-

Для определений замыкания сборщик выводит определенные виды для каждого из свойств и возвращаемого значения. Например, в приложении 13-3 показано определение короткого замыкания, которое просто возвращает значение, полученное в качестве свойства. Это замыкание не очень полезно, кроме как для целей данного примера. Обратите внимание, что мы не добавили в определение никаких наставлений видов. Поскольку наставлений видов нет, мы можем вызвать замыкание для любого вида, что мы и сделали в первый раз с String. Если затем мы попытаемся вызвать example_closure для целого числа, мы получим ошибку.

-

Имя файла: src/main.rs

-
fn main() {
-    let example_closure = |x| x;
-
-    let s = example_closure(String::from("hello"));
-    let n = example_closure(5);
-}
-

Приложение 13-3: Попытка вызова замыкания, виды которого выводятся из двух разных видов

-

Сборщик вернёт нам вот такую ошибку:

-
$ cargo run
-   Compiling closure-example v0.1.0 (file:///projects/closure-example)
-error[E0308]: mismatched types
- --> src/main.rs:5:29
-  |
-5 |     let n = example_closure(5);
-  |             --------------- ^- help: try using a conversion method: `.to_string()`
-  |             |               |
-  |             |               expected `String`, found integer
-  |             arguments to this function are incorrect
-  |
-note: expected because the closure was earlier called with an argument of type `String`
- --> src/main.rs:4:29
-  |
-4 |     let s = example_closure(String::from("hello"));
-  |             --------------- ^^^^^^^^^^^^^^^^^^^^^ expected because this argument is of type `String`
-  |             |
-  |             in this closure call
-note: closure parameter defined here
- --> src/main.rs:2:28
-  |
-2 |     let example_closure = |x| x;
-  |                            ^
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `closure-example` (bin "closure-example") due to 1 previous error
-
-

При первом вызове example_closure со значением String сборщик определяет вид x и возвращаемый вид замыкания как String. Эти виды затем определятся в замыкании в example_closure, и мы получаем ошибку вида при следующей попытке использовать другой вид с тем же замыканием.

-

Захват ссылок или передача владения

-

Замыкания могут захватывать значения из своего окружения тремя способами, которые соответствуют тем же трём способам, которыми функция может принимать свойства: заимствование неизменяемых, заимствование изменяемых и получение владения. Замыкание самостоятельно определяет, какой из этих способов использовать, исходя из того, что тело функции делает с полученными значениями.

-

В приложении 13-4 мы определяем замыкание, которое захватывает неизменяемую ссылку на вектор с именем list, поскольку неизменяемой ссылки достаточно для печати значения:

-

Имя файла: src/main.rs

-
fn main() {
-    let list = vec![1, 2, 3];
-    println!("Before defining closure: {list:?}");
-
-    let only_borrows = || println!("From closure: {list:?}");
-
-    println!("Before calling closure: {list:?}");
-    only_borrows();
-    println!("After calling closure: {list:?}");
-}
-

Приложение 13-4: Определение и вызов замыкания, которое захватывает неизменяемую ссылку

-

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

-

Поскольку мы можем иметь несколько неизменяемых ссылок на list одновременно, list остаётся доступным из кода до определения замыкания, после определения замыкания, а также до вызова замыкания и после. Этот код собирается, выполняется и печатает:

-
$ cargo run
-     Locking 1 package to latest compatible version
-      Adding closure-example v0.1.0 (/Users/carolnichols/rust/book/tmp/listings/ch13-functional-features/listing-13-04)
-   Compiling closure-example v0.1.0 (file:///projects/closure-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s
-     Running `target/debug/closure-example`
-Before defining closure: [1, 2, 3]
-Before calling closure: [1, 2, 3]
-From closure: [1, 2, 3]
-After calling closure: [1, 2, 3]
-
-

В следующем приложении 13-5 мы изменили тело замыкания так, чтобы оно добавляло элемент в вектор list. Теперь замыкание захватывает изменяемую ссылку:

-

Имя файла: src/main.rs

-
fn main() {
-    let mut list = vec![1, 2, 3];
-    println!("Before defining closure: {list:?}");
-
-    let mut borrows_mutably = || list.push(7);
-
-    borrows_mutably();
-    println!("After calling closure: {list:?}");
-}
-

Приложение 13-5. Определение и вызов замыкания, захватывающего изменяемую ссылку

-

Этот код собирается, запускается и печатает:

-
$ cargo run
-     Locking 1 package to latest compatible version
-      Adding closure-example v0.1.0 (/Users/carolnichols/rust/book/tmp/listings/ch13-functional-features/listing-13-05)
-   Compiling closure-example v0.1.0 (file:///projects/closure-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s
-     Running `target/debug/closure-example`
-Before defining closure: [1, 2, 3]
-After calling closure: [1, 2, 3, 7]
-
-

Обратите внимание, что между определением и вызовом замыкания borrows_mutably больше нет println!: когда определяется borrows_mutably, оно захватывает изменяемую ссылку на list. После вызова замыкания мы больше не используем его, поэтому изменяемое заимствование заканчивается. Между определением замыкания и вызовом замыкания неизменяемое заимствование для печати недоступно, потому что при наличии изменяемого заимствования никакие другие заимствования недопустимы. Попробуйте добавить туда println! и посмотрите, какое сообщение об ошибке вы получите!

-

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

-

Эта техника в основном полезна при передаче замыкания новому потоку, чтобы переместить данные так, чтобы они принадлежали новому потоку. Мы подробно обсудим потоки и то, зачем их использовать, в главе 16, когда будем говорить о одновременности, а пока давайте вкратце рассмотрим порождение нового потока с помощью замыкания, в котором используется ключевое слово move. В приложении 13-6 показан код из приложения 13-4, измененный для печати вектора в новом потоке, а не в основном потоке:

-

Файл: src/main.rs

-
use std::thread;
-
-fn main() {
-    let list = vec![1, 2, 3];
-    println!("Before defining closure: {list:?}");
-
-    thread::spawn(move || println!("From thread: {list:?}"))
-        .join()
-        .unwrap();
-}
-

Приложение 13-6: Использование move для принуждения замыкания потока принять на себя владение list

-

Мы порождаем новый поток, передавая ему в качестве переменной замыкание для выполнения. Тело замыкания распечатывает список. В приложении 13-4 замыкание захватило list только с помощью неизменяемой ссылки, потому что это наименьше необходимый доступ к list для его печати. В этом примере, несмотря на то, что тело замыкания по-прежнему требует только неизменяемой ссылки, нам нужно указать, что list должен быть перемещён в замыкание, поместив ключевое слово move в начало определения замыкания. Новый поток может завершиться раньше, чем завершится основной поток, или основной поток может завершиться первым. Если основной поток сохранил владение list, но завершился раньше нового потока и удалил list, то неизменяемая ссылка в потоке будет недействительной. Поэтому сборщик требует, чтобы list был перемещён в замыкание, переданное новому потоку, чтобы ссылка была действительной. Попробуйте убрать ключевое слово move или использовать list в основном потоке после определения замыкания и посмотрите, какие ошибки сборщика вы получите!

- -

- -

-

Перемещение захваченных значений из замыканий и особенности Fn

-

После того, как замыкание захватило ссылку или владение значением из среды, в которой оно определено (тем самым влияя на то, что перемещается в замыкание), код в теле замыкания определяет, что происходит со ссылками или значениями, в мгновение последующего выполнения замыкания (тем самым влияя на то, что перемещается из замыкания). Тело замыкания может делать любое из следующих действий: перемещать захваченное значение из замыкания, изменять захваченное значение, не перемещать и не изменять значение или вообще ничего не захватывать из среды.

-

То, как замыкание получает и обрабатывает значения из своего окружения, указывает на то, какие особенности выполняет замыкание, а с помощью особенностей функции и устройства могут определять, какие виды замыканий они могут использовать. Замыканиям самостоятельно присваивается выполнение одного, двух или всех трёх из нижеперечисленных особенностей Fn, аддитивным образом, в зависимости от того, как тело замыкания обрабатывает значения:

-
    -
  1. FnOnce применяется к замыканиям, которые могут быть вызваны один раз. Все замыкания выполняют по крайней мере этот особенность, потому что все замыкания могут быть вызваны. Замыкание, которое перемещает захваченные значения из своего тела, выполняет только FnOnce и ни один из других признаков Fn, потому что оно может быть вызвано только один раз.
  2. -
  3. FnMut применяется к замыканиям, которые не перемещают захваченные значения из своего тела, но могут изменять захваченные значения. Такие замыкания могут вызываться более одного раза.
  4. -
  5. Fn применяется к замыканиям, которые не перемещают захваченные значения из своего тела и не изменяют захваченные значения, а также к замыканиям, которые ничего не захватывают из своего окружения. Такие замыкания могут выполняться более одного раза и не меняют ничего в своём окружении, что важно в таких случаях, как одновременный вызов замыкания несколько раз.
  6. -
-

Давайте рассмотрим определение способа unwrap_or_else у Option<T>, который мы использовали в приложении 13-1:

-
impl<T> Option<T> {
-    pub fn unwrap_or_else<F>(self, f: F) -> T
-    where
-        F: FnOnce() -> T
-    {
-        match self {
-            Some(x) => x,
-            None => f(),
-        }
-    }
-}
-

Напомним, что T - это гибкий вид, отображающий вид значения в Some исходе Option. Этот вид T также является возвращаемым видом функции unwrap_or_else: например, код, вызывающий unwrap_or_else у Option<String>, получит String.

-

Далее, обратите внимание, что функция unwrap_or_else имеет дополнительный свойство гибкого вида F. Здесь F - это вид входного свойства f, который является замыканием, заданным нами при вызове unwrap_or_else.

-

Ограничением особенности, заданным для обобщённого вида F, является FnOnce() -> T, что означает, что F должен вызываться один раз, не принимать никаких переменных и возвращать T. Использование FnOnce в ограничении особенности говорит о том, что unwrap_or_else должен вызывать f не более одного раза. В теле unwrap_or_else мы видим, что если Option будет равен Some, то f не будет вызван. Если же значение Option будет равным None, то f будет вызван один раз. Поскольку все замыкания выполняют FnOnce, unwrap_or_else принимает самые разные виды замыканий и является настолько гибким, насколько это возможно.

-
-

Примечание: Функции также могут выполнить все три особенности Fn. Если то, что мы хотим сделать, не требует захвата значения из среды, мы можем передавать имя какой-либо функции, а не замыкания, когда нам нужно что-то, выполняющее один из особенностей Fn. Например, для значения Option<Vec<T>> мы можем вызвать unwrap_or_else(Vec::new), чтобы получить новый пустой вектор, если значение окажется None.

-
-

Теперь рассмотрим способ встроенной библиотеки sort_by_key, определённый у срезов, чтобы увидеть, чем он отличается от unwrap_or_else и почему sort_by_key использует FnMut вместо FnOnce для ограничения особенности. Замыкание принимает единственный переменная в виде ссылки на текущий элемент в рассматриваемом срезе и возвращает значение вида K, к которому применима сортировка. Эта функция полезна, когда вы хотите отсортировать срез по определённому свойству каждого элемента. В приложении 13-7 у нас есть список образцов Rectangle, и мы используем sort_by_key, чтобы упорядочить их по свойству width от меньшего к большему:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let mut list = [
-        Rectangle { width: 10, height: 1 },
-        Rectangle { width: 3, height: 5 },
-        Rectangle { width: 7, height: 12 },
-    ];
-
-    list.sort_by_key(|r| r.width);
-    println!("{list:#?}");
-}
-

Приложение 13-7: Использование sort_by_key для сортировки прямоугольников по ширине

-

Этот код печатает:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s
-     Running `target/debug/rectangles`
-[
-    Rectangle {
-        width: 3,
-        height: 5,
-    },
-    Rectangle {
-        width: 7,
-        height: 12,
-    },
-    Rectangle {
-        width: 10,
-        height: 1,
-    },
-]
-
-

Причина, по которой sort_by_key определена как принимающая замыкание FnMut, заключается в том, что она вызывает замыкание несколько раз: по одному разу для каждого элемента в срезе. Замыкание |r| r.width не захватывает, не изменяет и не перемещает ничего из своего окружения, поэтому оно удовлетворяет требованиям связанности признаков.

-

И наоборот, в приложении 13-8 показан пример замыкания, которое выполняет только признак FnOnce, потому что оно перемещает значение из среды. Сборщик не позволит нам использовать это замыкание с sort_by_key:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let mut list = [
-        Rectangle { width: 10, height: 1 },
-        Rectangle { width: 3, height: 5 },
-        Rectangle { width: 7, height: 12 },
-    ];
-
-    let mut sort_operations = vec![];
-    let value = String::from("closure called");
-
-    list.sort_by_key(|r| {
-        sort_operations.push(value);
-        r.width
-    });
-    println!("{list:#?}");
-}
-

Приложение 13-8: Попытка использовать замыкание FnOnce с sort_by_key

-

Это надуманный, замысловатый способ (который не работает) подсчёта количества вызовов sort_by_key при сортировке list. Этот код пытается выполнить подсчёт, перемещая value - String из окружения замыкания - в вектор sort_operations. Замыкание захватывает value, затем перемещает value из замыкания, передавая владение на value вектору sort_operations. Это замыкание можно вызвать один раз; попытка вызвать его второй раз не сработает, потому что value уже не будет находиться в той среде, из которой его можно будет снова поместить в sort_operations! Поэтому это замыкание выполняет только FnOnce. Когда мы попытаемся собрать этот код, мы получим ошибку сообщающую о том что value не может быть перемещено из замыкания, потому что замыкание должно выполнить FnMut:

-
$ cargo run
-   Compiling rectangles v0.1.0 (file:///projects/rectangles)
-error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure
-  --> src/main.rs:18:30
-   |
-15 |     let value = String::from("closure called");
-   |         ----- captured outer variable
-16 |
-17 |     list.sort_by_key(|r| {
-   |                      --- captured by this `FnMut` closure
-18 |         sort_operations.push(value);
-   |                              ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait
-   |
-help: consider cloning the value if the performance cost is acceptable
-   |
-18 |         sort_operations.push(value.clone());
-   |                                   ++++++++
-
-For more information about this error, try `rustc --explain E0507`.
-error: could not compile `rectangles` (bin "rectangles") due to 1 previous error
-
-

Ошибка указывает на строку в теле замыкания, которая перемещает value из окружения. Чтобы исправить это, нужно изменить тело замыкания так, чтобы оно не перемещало значения из окружения. Для подсчёта количества вызовов sort_by_key более простым способом является хранение счётчика в окружении и увеличение его значения в теле замыкания. Замыкание в приложении 13-9 работает с sort_by_key, поскольку оно определяет только изменяемую ссылку на счётчик num_sort_operations и поэтому может быть вызвано более одного раза:

-

Файл: src/main.rs

-
#[derive(Debug)]
-struct Rectangle {
-    width: u32,
-    height: u32,
-}
-
-fn main() {
-    let mut list = [
-        Rectangle { width: 10, height: 1 },
-        Rectangle { width: 3, height: 5 },
-        Rectangle { width: 7, height: 12 },
-    ];
-
-    let mut num_sort_operations = 0;
-    list.sort_by_key(|r| {
-        num_sort_operations += 1;
-        r.width
-    });
-    println!("{list:#?}, sorted in {num_sort_operations} operations");
-}
-

Приложение 13-9: Использование замыкания FnMut с sort_by_key разрешено

-

Особенности Fn важны при определении или использовании функций или видов, использующих замыкания. В следующем разделе мы обсудим повторители. Многие способы повторителей принимают переменные в виде замыканий, поэтому не забывайте об этих подробностях, пока мы продвигаемся дальше!

-

Обработка последовательности элементов с помощью повторителей

-

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

-

В Ржавчина повторители ленивые (lazy), то есть они не делают ничего, пока вы не вызовете особые способы, потребляющие повторитель , чтобы задействовать его. Например, код в приложении 13-10 создаёт повторитель элементов вектора v1, вызывая способ iter, определённый у Vec<T>. Сам по себе этот код не делает ничего полезного.

-
fn main() {
-    let v1 = vec![1, 2, 3];
-
-    let v1_iter = v1.iter();
-}
-

Приложение 13-10: Создание повторителя

-

Повторитель хранится в переменной v1_iter. Создав повторитель , мы можем использовать его различными способами. В приложении 3-5 главы 3 мы совершали обход элементов массива используя цикл for для выполнения какого-то кода над каждым из его элементов. Под капотом это неявно создавало, а затем потребляло повторитель , но до сих пор мы не касались того, как именно это работает.

-

В примере из приложения 13-11 мы отделили создание повторителя от его использования в цикле for. В цикле for, использующем повторитель в v1_iter, каждый элемент повторителя участвует только в одной повторения цикла, в ходе которой выводится на экран его значение.

-
fn main() {
-    let v1 = vec![1, 2, 3];
-
-    let v1_iter = v1.iter();
-
-    for val in v1_iter {
-        println!("Got: {val}");
-    }
-}
-

Приложение 13-11: Использование повторителя в цикле for

-

В языках, обычные библиотеки которых не предоставляют повторители, вы, скорее всего, напишите эту же возможность так: создадите переменную со значением 0 затем, в цикле, использовав её для получения элемента вектора по порядковому указателю, будете увеличивать её значение, и так, пока оно не достигнет числа равного количеству элементов в векторе.

-

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

-

Особенность Iterator и способ next

-

Все повторители выполняют особенность Iterator, который определён в встроенной библиотеке. Его определение выглядит так:

-
#![allow(unused)]
-fn main() {
-pub trait Iterator {
-    type Item;
-
-    fn next(&mut self) -> Option<Self::Item>;
-
-    // methods with default implementations elided
-}
-}
-

Обратите внимание данное объявление использует новый правила написания: type Item и Self::Item, которые определяют сопряженный вид (associated type) с этим особенностью. Мы подробнее поговорим о сопряженных видах в главе 19. Сейчас вам нужно знать, что этот код требует от выполнений особенности Iterator определить требуемый им вид Item и данный вид Item используется в способе next. Другими словами, вид Item будет являться видом элемента, который возвращает повторитель .

-

Особенность Iterator требует, чтобы разработчики определяли только один способ: способ next, который возвращает один элемент повторителя за раз обёрнутый в исход Some и когда повторение завершена, возвращает None.

-

Мы можем вызывать способ next у повторителей напрямую; в приложении 13-12 показано, какие значения возвращаются при повторных вызовах next у повторителя, созданного из вектора.

-

Файл: src/lib.rs

-
#[cfg(test)]
-mod tests {
-    #[test]
-    fn iterator_demonstration() {
-        let v1 = vec![1, 2, 3];
-
-        let mut v1_iter = v1.iter();
-
-        assert_eq!(v1_iter.next(), Some(&1));
-        assert_eq!(v1_iter.next(), Some(&2));
-        assert_eq!(v1_iter.next(), Some(&3));
-        assert_eq!(v1_iter.next(), None);
-    }
-}
-

Приложение 13-12: Вызов способа next повторителя

-

Обратите внимание, что нам нужно сделать переменную v1_iter изменяемой: вызов способа next повторителя изменяет внутреннее состояние повторителя, которое повторитель использует для отслеживания того, где он находится в последовательности. Другими словами, этот код потребляет (consume) или использует повторитель . Каждый вызов next потребляет элемент из повторителя. Нам не нужно было делать изменяемой v1_iter при использовании цикла for, потому что цикл забрал во владение v1_iter и сделал её изменяемой неявно для нас.

-

Заметьте также, что значения, которые мы получаем при вызовах next являются неизменяемыми ссылками на значения в векторе. Способ iter создаёт повторитель по неизменяемым ссылкам. Если мы хотим создать повторитель , который становится владельцем v1 и возвращает принадлежащие ему значения, мы можем вызвать into_iter вместо iter. Точно так же, если мы хотим перебирать изменяемые ссылки, мы можем вызвать iter_mut вместо iter.

-

Способы, которые потребляют повторитель

-

У особенности Iterator есть несколько способов, выполнение которых по умолчанию предоставляется встроенной библиотекой; вы можете узнать об этих способах, просмотрев документацию API встроенной библиотеки для Iterator. Некоторые из этих способов вызывают next в своём определении, поэтому вам необходимо выполнить способ next при выполнения особенности Iterator.

-

Способы, вызывающие next, называются потребляющими переходниками, поскольку их вызов потребляет повторитель . Примером может служить способ sum, который забирает во владение повторитель и перебирает элементы, многократно вызывая next, тем самым потребляя повторитель . В этапе повторения он добавляет каждый элемент к текущей сумме и возвращает итоговое значение по завершении повторения. В приложении 13-13 приведён проверка, отображающий использование способа sum:

-

Файл: src/lib.rs

-
#[cfg(test)]
-mod tests {
-    #[test]
-    fn iterator_sum() {
-        let v1 = vec![1, 2, 3];
-
-        let v1_iter = v1.iter();
-
-        let total: i32 = v1_iter.sum();
-
-        assert_eq!(total, 6);
-    }
-}
-

Приложение 13-13: Вызов способа sum для получения суммы всех элементов в повторителе

-

Мы не можем использовать v1_iter после вызова способа sum, потому что sum забирает во владение повторитель у которого вызван способ.

-

Способы, которые создают другие повторители

-

Переходники повторителей - это способы, определённые для особенности Iterator, которые не потребляют повторитель . Вместо этого они создают различные повторители, изменяя некоторые особенности исходного повторителя.

-

В приложении 13-14 показан пример вызова способа переходника повторителя map, который принимает замыкание и вызывает его для каждого элемента по мере повторения элементов. Способ map возвращает новый повторитель , который создаёт изменённые элементы. Замыкание здесь создаёт новый повторитель , в котором каждый элемент из вектора будет увеличен на 1:

-

Файл: src/main.rs

-
fn main() {
-    let v1: Vec<i32> = vec![1, 2, 3];
-
-    v1.iter().map(|x| x + 1);
-}
-

Приложение 13-14: Вызов переходника повторителя map для создания нового повторителя

-

Однако этот код выдаёт предупреждение:

-
$ cargo run
-   Compiling iterators v0.1.0 (file:///projects/iterators)
-warning: unused `Map` that must be used
- --> src/main.rs:4:5
-  |
-4 |     v1.iter().map(|x| x + 1);
-  |     ^^^^^^^^^^^^^^^^^^^^^^^^
-  |
-  = note: iterators are lazy and do nothing unless consumed
-  = note: `#[warn(unused_must_use)]` on by default
-help: use `let _ = ...` to ignore the resulting value
-  |
-4 |     let _ = v1.iter().map(|x| x + 1);
-  |     +++++++
-
-warning: `iterators` (bin "iterators") generated 1 warning
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s
-     Running `target/debug/iterators`
-
-

Код в приложении 13-14 ничего не делает; указанное нами замыкание никогда не вызывается. Предупреждение напоминает нам, почему: переходники повторителей ленивы, и здесь нам нужно потребить повторитель .

-

Чтобы устранить это предупреждение и потребить повторитель , мы воспользуемся способом collect, который мы использовали в главе 12 с env::args в приложении 12-1. Этот способ потребляет повторитель и собирает полученные значения в собрание указанного вида.

-

В приложении 13-15 мы собираем в вектор итоги перебора повторителя, который возвращается в итоге вызова map. Этот вектор в итоге будет содержать каждый элемент исходного вектора, увеличенный на 1.

-

Файл: src/main.rs

-
fn main() {
-    let v1: Vec<i32> = vec![1, 2, 3];
-
-    let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
-
-    assert_eq!(v2, vec![2, 3, 4]);
-}
-

Приложение 13-15: Вызов способа map для создания нового повторителя, а затем вызов способа collect для потребления нового повторителя и создания вектора

-

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

-

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

-

Использование замыканий, которые захватывают переменные окружения

-

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

-

В этом примере мы будем использовать способ filter, который принимает замыкание. Замыкание получает элемент из повторителя и возвращает bool. Если замыкание возвращает true, значение будет включено в повторение, создаваемую filter. Если замыкание возвращает false, значение не будет включено.

-

В приложении 13-16 мы используем filter с замыканием, которое захватывает переменную shoe_size из своего окружения для повторения по собрания образцов устройства Shoe. Он будет возвращать обувь только указанного размера.

-

Файл: src/lib.rs

-
#[derive(PartialEq, Debug)]
-struct Shoe {
-    size: u32,
-    style: String,
-}
-
-fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
-    shoes.into_iter().filter(|s| s.size == shoe_size).collect()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn filters_by_size() {
-        let shoes = vec![
-            Shoe {
-                size: 10,
-                style: String::from("sneaker"),
-            },
-            Shoe {
-                size: 13,
-                style: String::from("sandal"),
-            },
-            Shoe {
-                size: 10,
-                style: String::from("boot"),
-            },
-        ];
-
-        let in_my_size = shoes_in_size(shoes, 10);
-
-        assert_eq!(
-            in_my_size,
-            vec![
-                Shoe {
-                    size: 10,
-                    style: String::from("sneaker")
-                },
-                Shoe {
-                    size: 10,
-                    style: String::from("boot")
-                },
-            ]
-        );
-    }
-}
-

Приложение 13-16. Использование способа filter с замыканием, определяющим shoe_size

-

Функция shoes_in_size принимает в качестве свойств вектор с образцами обуви и размер обуви, а возвращает вектор, содержащий только обувь указанного размера.

-

В теле shoes_in_my_size мы вызываем into_iter чтобы создать повторитель , который становится владельцем вектора. Затем мы вызываем filter, чтобы превратить этот повторитель в другой, который содержит только элементы, для которых замыкание возвращает true.

-

Замыкание захватывает свойство shoe_size из окружения и сравнивает его с размером каждой пары обуви, оставляя только обувь указанного размера. Наконец, вызов collect собирает значения, возвращаемые приспособленным повторителем, в вектор, возвращаемый функцией.

-

Проверка показывает, что когда мы вызываем shoes_in_my_size, мы возвращаем только туфли, размер которых совпадает с указанным нами значением.

-

Улучшение нашего дела с вводом/выводом

-

Вооружившись полученными знаниями об повторителях, мы можем улучшить выполнение работы с вводом/выводом в деле главы 12, применяя повторители для того, чтобы сделать некоторые места в коде более понятными и краткими. Давайте рассмотрим, как повторители могут улучшить нашу выполнение функции Config::build и функции search.

-

Удаляем clone, используем повторитель

-

В приложении 12-6 мы добавили код, который принимает срез значений String и создаёт образец устройства Config путём упорядочевания среза и клонирования значений, позволяя устройстве Config владеть этими значениями. В приложении 13-17 мы воспроизвели выполнение функции Config::build, как это было в приложении 12-23:

-

Файл: src/lib.rs

-
use std::env;
-use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        let ignore_case = env::var("IGNORE_CASE").is_ok();
-
-        Ok(Config {
-            query,
-            file_path,
-            ignore_case,
-        })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 13-17: Репродукция функции Config::build из приложения 12-23

-

Ранее мы говорили, что не стоит беспокоиться о неэффективных вызовах clone, потому что мы удалим их в будущем. Ну что же, время пришло!

-

Нам понадобился здесь clone, потому что в свойстве args у нас срез с элементами String, но функция build не владеет args. Чтобы образец Config владел значениями, нам пришлось клонировать их из args в переменные query и file_path.

-

Благодаря нашим новым знаниям об повторителях мы можем изменить функцию build, чтобы вместо заимствования среза она принимала в качестве переменной повторитель . Мы будем использовать возможность повторителя вместо кода, который проверяет длину среза и обращается по порядковому указателю к определённым значениям. Это позволит лучше понять, что делает функция Config::build, поскольку повторитель будет обращаться к значениям.

-

Как только Config::build получит в своё распоряжение повторитель и перестанет использовать действия упорядочевания с заимствованием, мы сможем переместить значения String из повторителя в Config вместо того, чтобы вызывать clone и создавать новое выделение памяти.

-

Использование возвращённого повторителя напрямую

-

Откройте файл src/main.rs дела ввода-вывода, который должен выглядеть следующим образом:

-

Файл: src/main.rs

-
use std::env;
-use std::process;
-
-use minigrep::Config;
-
-fn main() {
-    let args: Vec<String> = env::args().collect();
-
-    let config = Config::build(&args).unwrap_or_else(|err| {
-        eprintln!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    // --snip--
-
-    if let Err(e) = minigrep::run(config) {
-        eprintln!("Application error: {e}");
-        process::exit(1);
-    }
-}
-

Сначала мы изменим начало функции main, которая была в приложении 12-24, на код в приложении 13-18, который теперь использует повторитель . Это не будет собираться, пока мы не обновим Config::build.

-

Файл: src/main.rs

-
use std::env;
-use std::process;
-
-use minigrep::Config;
-
-fn main() {
-    let config = Config::build(env::args()).unwrap_or_else(|err| {
-        eprintln!("Problem parsing arguments: {err}");
-        process::exit(1);
-    });
-
-    // --snip--
-
-    if let Err(e) = minigrep::run(config) {
-        eprintln!("Application error: {e}");
-        process::exit(1);
-    }
-}
-

Приложение 13-18: Передача возвращаемого значения из env::args в Config::build

-

Функция env::args возвращает повторитель ! Вместо того чтобы собирать значения повторителя в вектор и затем передавать срез в Config::build, теперь мы передаём владение повторителем, возвращённым из env::args в Config::build напрямую.

-

Далее нам нужно обновить определение Config::build. В файле src/lib.rs вашего дела ввода-вывода изменим ярлык Config::build так, чтобы она выглядела как в приложении 13-19. Это все ещё не собирается, потому что нам нужно обновить тело функции.

-

Файл: src/lib.rs

-
use std::env;
-use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(
-        mut args: impl Iterator<Item = String>,
-    ) -> Result<Config, &'static str> {
-        // --snip--
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        let ignore_case = env::var("IGNORE_CASE").is_ok();
-
-        Ok(Config {
-            query,
-            file_path,
-            ignore_case,
-        })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 13-19: Обновление ярлыки Config::build для определения повторителя как ожидаемого свойства

-

Документация встроенной библиотеки для функции env::args показывает, что вид возвращаемого ею повторителя - std::env::Args, и этот вид выполняет признак Iterator и возвращает значения String.

-

Мы обновили ярлык функции Config::build, чтобы свойство args имел гибкий вид ограниченный особенностью impl Iterator<Item = String> вместо &[String]. Такое использование правил написания impl Trait, который мы обсуждали в разделе " Особенности как свойства" главы 10, означает, что args может быть любым видом, выполняющим вид Iterator и возвращающим элементы String.

-

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

-

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

-

Далее мы подправим содержимое Config::build. Поскольку args выполняет признак Iterator, мы знаем, что можем вызвать у него способ next! В приложении 13-20 код из приложения 12-23 обновлён для использования способа next:

-

Файл: src/lib.rs

-
use std::env;
-use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(
-        mut args: impl Iterator<Item = String>,
-    ) -> Result<Config, &'static str> {
-        args.next();
-
-        let query = match args.next() {
-            Some(arg) => arg,
-            None => return Err("Didn't get a query string"),
-        };
-
-        let file_path = match args.next() {
-            Some(arg) => arg,
-            None => return Err("Didn't get a file path"),
-        };
-
-        let ignore_case = env::var("IGNORE_CASE").is_ok();
-
-        Ok(Config {
-            query,
-            file_path,
-            ignore_case,
-        })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 13-20: Изменяем тело Config::build так, чтобы использовать способы повторителя

-

Помните, что первое значение в возвращаемых данных env::args - это имя программы. Мы хотим пренебрегать его и перейти к следующему значению, поэтому сперва мы вызываем next и ничего не делаем с возвращаемым значением. Затем мы вызываем next, чтобы получить значение, которое мы хотим поместить в поле query в Config. Если next возвращает Some, мы используем match для извлечения значения. Если возвращается None, это означает, что было задано недостаточно переменных, и мы досрочно возвращаем значение Err. То же самое мы делаем для значения file_path.

-

Делаем код понятнее с помощью переходников повторителей

-

Мы также можем воспользоваться преимуществами повторителей в функции search в нашем деле с действиеми ввода-вывода, которая воспроизведена здесь в приложении 13-21 так же, как и в приложении 12-19:

-

Файл: src/lib.rs

-
use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-}
-
-impl Config {
-    pub fn build(args: &[String]) -> Result<Config, &'static str> {
-        if args.len() < 3 {
-            return Err("not enough arguments");
-        }
-
-        let query = args[1].clone();
-        let file_path = args[2].clone();
-
-        Ok(Config { query, file_path })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.contains(query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn one_result() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-}
-

Приложение 13-21: Выполнение функции search из приложения 12-19

-

Мы можем написать этот код в более сжатом виде, используя способы переходника повторителя. Это также позволит нам избежать наличия изменяемого временного вектора results. Функциональный исполнение программирования предпочитает уменьшить количество изменяемого состояния, чтобы сделать код более понятным. Удаление изменяемого состояния может позволить в будущем сделать поиск одновременным, поскольку нам не придётся управлять одновременным доступом к вектору results. В приложении 13-22 показано это изменение:

-

Файл: src/lib.rs

-
use std::env;
-use std::error::Error;
-use std::fs;
-
-pub struct Config {
-    pub query: String,
-    pub file_path: String,
-    pub ignore_case: bool,
-}
-
-impl Config {
-    pub fn build(
-        mut args: impl Iterator<Item = String>,
-    ) -> Result<Config, &'static str> {
-        args.next();
-
-        let query = match args.next() {
-            Some(arg) => arg,
-            None => return Err("Didn't get a query string"),
-        };
-
-        let file_path = match args.next() {
-            Some(arg) => arg,
-            None => return Err("Didn't get a file path"),
-        };
-
-        let ignore_case = env::var("IGNORE_CASE").is_ok();
-
-        Ok(Config {
-            query,
-            file_path,
-            ignore_case,
-        })
-    }
-}
-
-pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
-    let contents = fs::read_to_string(config.file_path)?;
-
-    let results = if config.ignore_case {
-        search_case_insensitive(&config.query, &contents)
-    } else {
-        search(&config.query, &contents)
-    };
-
-    for line in results {
-        println!("{line}");
-    }
-
-    Ok(())
-}
-
-pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
-    contents
-        .lines()
-        .filter(|line| line.contains(query))
-        .collect()
-}
-
-pub fn search_case_insensitive<'a>(
-    query: &str,
-    contents: &'a str,
-) -> Vec<&'a str> {
-    let query = query.to_lowercase();
-    let mut results = Vec::new();
-
-    for line in contents.lines() {
-        if line.to_lowercase().contains(&query) {
-            results.push(line);
-        }
-    }
-
-    results
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn case_sensitive() {
-        let query = "duct";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Duct tape.";
-
-        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
-    }
-
-    #[test]
-    fn case_insensitive() {
-        let query = "rUsT";
-        let contents = "\
-Rust:
-safe, fast, productive.
-Pick three.
-Trust me.";
-
-        assert_eq!(
-            vec!["Rust:", "Trust me."],
-            search_case_insensitive(query, contents)
-        );
-    }
-}
-

Приложение 13-22: Использование способов переходника повторителя в выполнения функции search

-

Напомним, что назначение функции search - вернуть все строки в contents, которые содержат query. Подобно примеру filter в приложении 13-16, этот код использует переходник filter, чтобы сохранить только те строки, для которых line.contains(query) возвращает true. Затем мы собираем совпадающие строки в другой вектор с помощью collect. Так гораздо проще! Не стесняйтесь сделать такое же изменение для использования способов повторителя в функции search_case_insensitive.

-

Выбор между циклами или повторителями

-

Следующий логичный вопрос - какой исполнение вы должны выбрать в своём коде и почему: подлинную выполнение в приложении 13-21 или исполнение с использованием повторителей в приложении 13-22. Большинство программистов на языке Ржавчина предпочитают использовать исполнение повторителей. Сначала разобраться с ним немного сложно, но как только вы почувствуете, что такое различные переходники повторителей и что они делают, понять повторители станет проще. Вместо того чтобы возиться с различными элементами цикла и создавать новые векторы, код сосредотачивается на высокоуровневой цели цикла. Это абстрагирует часть обычного кода, поэтому легче увидеть подходы, единственные для этого кода, такие как условие выборки, которое должен пройти каждый элемент в повторителе.

-

Но действительно ли эти две выполнения эквивалентны? Интуитивно можно предположить, что более низкоуровневый цикл будет быстрее. Давайте поговорим о производительности.

-

Сравнение производительности циклов и повторителей

-

Чтобы определить, что лучше использовать циклы или повторители, нужно знать, какая выполнение быстрее: исполнение функции search с явным циклом for или исполнение с повторителями.

-

Мы выполнили проверка производительности, разместив всё содержимое книги (“The Adventures of Sherlock Holmes” by Sir Arthur Conan Doyle) в строку вида String и поискали слово the в её содержимом. Вот итоги проверки функции search с использованием цикла for и с использованием повторителей:

-
test bench_search_for  ... bench:  19,620,300 ns/iter (+/- 915,700)
-test bench_search_iter ... bench:  19,234,900 ns/iter (+/- 657,200)
-
-

Исполнение с использованием повторителей была немного быстрее! Мы не будем приводить здесь непосредственно код проверки, поскольку мысль не в том, чтобы доказать, что решения в точности эквивалентны, а в том, чтобы получить общее представление о том, как эти две выполнения близки по производительности.

-

Для более исчерпывающего проверки, вам нужно проверить различные тексты разных размеров в качестве содержимого для contents, разные слова и слова различной длины в качестве query и всевозможные другие исходы. Дело в том, что повторители, будучи высокоуровневой абстракцией, собираются примерно в тот же код, как если бы вы написали его низкоуровневый исход самостоятельно. Повторители - это одна из абстракций с нулевой стоимостью ( zero-cost abstractions ) в Rust, под которой мы подразумеваем, что использование абстракции не накладывает дополнительных расходов во время выполнения. Подобно тому, как Бьёрн Страуструп, внешнем видер и разработчик C++, определяет нулевые накладные расходы ( zero-overhead ) в книге “Foundations of C++” (2012):

-
-

В целом, выполнение C++ подчиняется принципу отсутствия накладных расходов: за то, чем вы не пользуетесь, платить не нужно. И далее: тот код, что вы используете, нельзя сделать ещё лучше.

-
-

В качестве другого примера приведём код, взятый из аудио декодера. Алгоритм декодирования использует математическую действие линейного предсказания для оценки будущих значений на основе линейной функции предыдущих выборок. Код использует соединение вызовов повторителя для выполнения математических вычислений для трёх переменных в области видимости: срез данных buffer, массив из 12 коэффициентов coefficients и число для сдвига данных в переменной qlp_shift. Переменные определены в примере, но не имеют начальных значений. Хотя этот код не имеет большого значения вне среды, он является кратким, существующим примером того, как Ржавчина переводит мысли высокого уровня в код низкого уровня.

-
let buffer: &mut [i32];
-let coefficients: [i64; 12];
-let qlp_shift: i16;
-
-for i in 12..buffer.len() {
-    let prediction = coefficients.iter()
-                                 .zip(&buffer[i - 12..i])
-                                 .map(|(&c, &s)| c * s as i64)
-                                 .sum::<i64>() >> qlp_shift;
-    let delta = buffer[i];
-    buffer[i] = prediction as i32 + delta;
-}
-

Чтобы вычислить значение переменной prediction, этот код перебирает каждое из 12 значений в переменной coefficients и использует способ zip для объединения значений коэффициентов с предыдущими 12 значениями в переменной buffer. Затем, для каждой пары мы перемножаем значения, суммируем все итоги и у суммы сдвигаем биты вправо в переменную qlp_shift.

-

Для вычислений в таких приложениях, как аудио декодеры, часто требуется производительность. Здесь мы создаём повторитель , используя два переходника, впоследствии потребляющих значение. В какой ассемблерный код будет собираться этот код на Rust? На мгновение написания этой главы он собирается в то же самое, что вы написали бы руками. Не существует цикла, соответствующего повторения по значениям в «коэффициентах»coefficients: Ржавчина знает, что существует двенадцать повторений, поэтому он «разворачивает» цикл. Разворачивание - это улучшение, которая устраняет издержки кода управления циклом и вместо этого порождает повторяющийся код для каждой повторения цикла.

-

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

-

Итоги

-

Замыкания (closures) и повторители (iterators) это возможности Rust, вдохновлённые мыслями полезных языков. Они позволяют Ржавчина ясно выражать мысли высокого уровня с производительностью низкоуровневого кода. Выполнения замыканий и повторителей таковы, что нет влияния на производительность выполнения кода. Это одна из целей Rust, направленных на обеспечение абстракций с нулевой стоимостью (zero-cost abstractions).

-

Теперь, когда мы улучшили представление кода в нашем деле, рассмотрим некоторые возможности, которые нам предоставляет cargo для обнародования нашего кода в хранилища.

-

Больше о Cargo и Crates.io

-

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

-
    -
  • Настройка сборки с помощью готовых профилей
  • -
  • Обнародование библиотеки на crates.io
  • -
  • Управление крупными делами с помощью рабочих пространств
  • -
  • Установка двоичных файлов с crates.io
  • -
  • Расширение возможностей Cargo с помощью возможности добавления собственных приказов
  • -
-

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

-

Настройка сборок с профилями исполнений

-

В Ржавчина профили выпуска — это предопределённые и настраиваемые профили с различными настройками, которые позволяют программисту лучше управлять различные свойства сборки кода. Каждый профиль настраивается независимо от других.

-

Cargo имеет два основных профиля: профиль dev, используемый Cargo при запуске cargo build, и профиль release, используемый Cargo при запуске cargo build --release. Профиль dev определён со значениями по умолчанию для разработки, а профиль release имеет значения по умолчанию для сборок в исполнение.

-

Эти имена профилей могут быть знакомы по итогам ваших сборок:

- -
$ cargo build
-    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
-$ cargo build --release
-    Finished release [optimized] target(s) in 0.0s
-
-

dev и release — это разные профили, используемые сборщиком.

-

Cargo содержит настройки по умолчанию для каждого профиля, которые применяются, если вы явно не указали разделы [profile.*] в файле дела Cargo.toml. Добавляя разделы [profile.*] для любого профиля, который вы хотите настроить, вы переопределяете любое подмножество свойств по умолчанию. Например, вот значения по умолчанию для свойства opt-level для профилей dev и release:

-

Файл: Cargo.toml

-
[profile.dev]
-opt-level = 0
-
-[profile.release]
-opt-level = 3
-
-

Свойство opt-level управляет количеством переработок, которые Ржавчина будет применять к вашему коду, в ряде от 0 до 3. Использование большего количества переработок увеличивает время сборки, поэтому если вы находитесь в этапе разработки и часто собираете свой код, целесообразно использовать меньшее количество переработок, чтобы сборка происходила быстрее, даже если в итоге код будет работать медленнее. Поэтому opt-level по умолчанию для dev установлен в 0. Когда вы готовы обнародовать свой код, то лучше потратить больше времени на сборку. Вы собираете программу в режиме исполнения только один раз, но выполняться она будет многократно, так что использование режима исполнения позволяет увеличить скорость выполнения кода за счёт времени сборки. Вот почему по умолчанию opt-level для профиля release равен 3.

-

Вы можете переопределить настройки по умолчанию, добавив другое значение для них в Cargo.toml. Например, если мы хотим использовать уровень переработки 1 в профиле разработки, мы можем добавить эти две строки в файл Cargo.toml нашего дела:

-

Файл: Cargo.toml

-
[profile.dev]
-opt-level = 1
-
-

Этот код переопределяет настройку по умолчанию 0. Теперь, когда мы запустим cargo build, Cargo будет использовать значения по умолчанию для профиля dev плюс нашу настройку для opt-level. Поскольку мы установили для opt-level значение 1, Cargo будет применять больше переработок, чем было задано по умолчанию, но не так много, как при сборке исполнения.

-

Полный список свойств настройке и значений по умолчанию для каждого профиля вы можете найти в документации Cargo.

-

Обнародование библиотеки в Crates.io

-

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

-

В Ржавчина и Cargo есть функции, которые облегчают поиск и использование обнародованного дополнения. Далее мы поговорим о некоторых из этих функций, а затем объясним, как обнародовать дополнение.

-

Создание полезных примечаниев к документации

-

Правильноное документирование ваших дополнений поможет другим пользователям знать, как и когда их использовать, поэтому стоит потратить время на написание документации. В главе 3 мы обсуждали, как вносить примечания в код Rust, используя две косые черты, //. В Ржавчина также есть особый вид примечаниев к документации, который обычно называется примечанием к документации, который порождает документацию HTML. HTML-код отображает содержимое примечаниев к документации для открытых элементов API, предназначенных для программистов, увлеченных в знании того, как использовать вашу библиотеку, в отличие от того, как она выполнена.

-

Примечания к документации используют три слеша, /// вместо двух и поддерживают наставление Markdown для изменения текста. Размещайте примечания к документации непосредственно перед элементом, который они документируют. В приложении 14-1 показаны примечания к документации для функции add_one в библиотеке с именем my_crate:

-

Файл: src/lib.rs

-
/// Adds one to the number given.
-///
-/// # Examples
-///
-/// ```
-/// let arg = 5;
-/// let answer = my_crate::add_one(arg);
-///
-/// assert_eq!(6, answer);
-/// ```
-pub fn add_one(x: i32) -> i32 {
-    x + 1
-}
-

Приложение 14-1: Примечание к документации для функции

-

Здесь мы даём описание того, что делает функция add_one, начинаем раздел с заголовка Examples, а затем предоставляем код, который отображает, как использовать функцию add_one. Мы можем создать документацию HTML из этого примечания к документации, запустив cargo doc. Этот приказ запускает средство rustdoc, поставляемый с Rust, и помещает созданную HTML-документацию в папка target/doc.

-

Для удобства, запустив cargo doc --open, мы создадим HTML для документации вашей текущей библиотеки (а также документацию для всех зависимостей вашей библиотеки) и откроем итог в веб-браузере. Перейдите к функции add_one и вы увидите, как отображается текст в примечаниях к документации, что показано на рисунке 14-1:

- HTML-документация для функции `add_one`` my_crate` -

Рисунок 14-1: HTML документация для функции add_one

-

Часто используемые разделы

-

Мы использовали Markdown заголовок # Examples в приложении 14-1 для создания раздела в HTML с заголовком "Examples". Вот некоторые другие разделы, которые авторы библиотек обычно используют в своей документации:

-
    -
  • Panics: Сценарии, в которых документированная функция может вызывать панику. Вызывающие функцию, которые не хотят, чтобы их программы паниковали, должны убедиться, что они не вызывают функцию в этих случаейх.
  • -
  • Ошибки: Если функция возвращает Result, описание видов ошибок, которые могут произойти и какие условия могут привести к тому, что эти ошибки могут быть возвращены, может быть полезным для вызывающих, так что они могут написать код для обработки различных видов ошибок разными способами.
  • -
  • Безопасность: Если функция является unsafe для вызова (мы обсуждаем безопасность в главе 19), должен быть раздел, объясняющий, почему функция небезопасна и охватывающий неизменные величины, которые функция ожидает от вызывающих сторон.
  • -
-

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

-

Примечания к документации как проверки

-

Добавление примеров кода в примечания к документации может помочь отобразить, как использовать вашу библиотеку, и это даёт дополнительный бонус: запуск cargo test запустит примеры кода в вашей документации как проверки! Нет ничего лучше, чем документация с примерами. Но нет ничего хуже, чем примеры, которые не работают, потому что код изменился с особенности написания документации. Если мы запустим cargo test с документацией для функции add_one из приложения 14-1, мы увидим раздел итогов проверки, подобный этому:

- -
   Doc-tests my_crate
-
-running 1 test
-test src/lib.rs - add_one (line 5) ... ok
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s
-
-

Теперь, если мы изменим либо функцию, либо пример, так что assert_eq! в примере паникует, и снова запустим cargo test, мы увидим, что проверки документации обнаруживают, что пример и код не согласованы друг с другом!

-

Указание примечаний содержащихся элементов

-

Исполнение примечаниев к документам //! добавляет документацию к элементу, содержащему примечания, а не к элементам, следующим за примечаниями. Обычно мы используем эти примечания внутри корневого файла ящика (по соглашению src/lib.rs ) или внутри звена для документирования ящика или звена в целом.

-

Например, чтобы добавить документацию, описывающую назначение my_crate , содержащего функцию add_one , мы добавляем примечания к документации, начинающиеся с //! в начало файла src/lib.rs , как показано в приложении 14-2:

-

Файл: src/lib.rs

-
//! # My Crate
-//!
-//! `my_crate` is a collection of utilities to make performing certain
-//! calculations more convenient.
-
-/// Adds one to the number given.
-// --snip--
-///
-/// # Examples
-///
-/// ```
-/// let arg = 5;
-/// let answer = my_crate::add_one(arg);
-///
-/// assert_eq!(6, answer);
-/// ```
-pub fn add_one(x: i32) -> i32 {
-    x + 1
-}
-

Приложение 14-2: Документация для ящика my_crate в целом

-

Обратите внимание, что после последней строки, начинающейся с //!, нет никакого кода. Поскольку мы начали примечания с //! вместо ///, мы документируем элемент, который содержит этот примечание, а не элемент, который следует за этим примечанием. В данном случае таким элементом является файл src/lib.rs, который является корнем crate. Эти примечания описывают весь ящик.

-

Когда мы запускаем cargo doc --open, эти примечания будут отображаться на первой странице документации для my_crate над списком открытых элементов в библиотеке, как показано на рисунке 14-2:

- Документация для библиотеки `art`, в которой перечислены звенья `types` и `utils` -

Рисунок 14-2: Предоставленная документация для my_crate, включая примечание, описывающие ящик в целом

-

Примечания к документации внутри элементов полезны для описания ящиков и звеньев особенно. Используйте их, чтобы объяснить общую цель дополнения, чтобы помочь вашим пользователям понять устройство ящика.

-

Экспорт удобного общедоступного API с pub use

-

Устройства вашего открытого API является основным обстоятельством при обнародования ящика. Люди, которые используют вашу библиотеку, менее знакомы со устройством, чем вы и могут столкнуться с трудностями при поиске частей, которые они хотят использовать, если ваша библиотека имеет большую упорядочевание звеньев.

-

В главе 7 мы рассмотрели, как сделать элементы общедоступными с помощью ключевого слова pub и ввести элементы в область видимости с помощью ключевого слова use. Однако устройства, которая имеет смысл для вас при разработке ящика, может быть не очень удобной для пользователей. Вы можете согласовать устройство в виде упорядочевания с несколькими уровнями, но тогда люди, желающие использовать вид, который вы определили в глубине упорядочевания, могут столкнуться с неполадкой его поиска. Их также может раздражать необходимость вводить use my_crate::some_module::another_module::UsefulType; вместо use my_crate::UsefulType;.

-

Хорошей новостью является то, что если устройства не удобна для использования другими из другой библиотеки, вам не нужно перестраивать внутреннюю устройство: вместо этого вы можете реэкспортировать элементы, чтобы сделать открытую устройство, отличную от вашей внутренней устройства, используя pub use. Реэкспорт берет открытый элемент в одном месте и делает его открытым в другом месте, как если бы он был определён в другом месте.

-

Например, скажем, мы создали библиотеку с именем art для расчетов художественных подходов. Внутри этой библиотеки есть два звена: звено kinds содержащий два перечисления с именами PrimaryColor и SecondaryColor и звено utils, содержащий функцию с именем mix, как показано в приложении 14-3:

-

Файл: src/lib.rs

-
//! # Art
-//!
-//! A library for modeling artistic concepts.
-
-pub mod kinds {
-    /// The primary colors according to the RYB color model.
-    pub enum PrimaryColor {
-        Red,
-        Yellow,
-        Blue,
-    }
-
-    /// The secondary colors according to the RYB color model.
-    pub enum SecondaryColor {
-        Orange,
-        Green,
-        Purple,
-    }
-}
-
-pub mod utils {
-    use crate::kinds::*;
-
-    /// Combines two primary colors in equal amounts to create
-    /// a secondary color.
-    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
-        // --snip--
-        unimplemented!();
-    }
-}
-

Приложение 14-3: Библиотека art с элементами, согласованными в звенья kinds и utils

-

На рисунке 14-3 показано, как будет выглядеть титульная страница документации для этого ящика, созданный cargo doc:

- Предоставлена Документация для библиотеки `art` с реэкспортом на первой странице -

Рисунок 14-3: Первая страница документации для art, в которой перечислены звенья kinds и utils

-

Обратите внимание, что виды PrimaryColor и SecondaryColor не указаны на главной странице, равно как и функция mix. Мы должны нажать kinds и utils, чтобы увидеть их.

-

В другой библиотеке, которая зависит от этой библиотеки, потребуются операторы use, которые подключают элементы из art в область видимости, определяя устройство звена, которая определена в данный мгновение. В приложении 14-4 показан пример ящика, в котором используются элементы PrimaryColor и mix из ящика art:

-

Файл: src/main.rs

-
use art::kinds::PrimaryColor;
-use art::utils::mix;
-
-fn main() {
-    let red = PrimaryColor::Red;
-    let yellow = PrimaryColor::Yellow;
-    mix(red, yellow);
-}
-

Приложение 14-4: Ящик использующий элементы из ящика art с экспортированной внутренней устройством

-

Автору кода в приложении 14-4, который использует ящик art, пришлось выяснить, что PrimaryColor находится в звене kinds, а mix - в звене utils. Устройства звена art ящика больше подходит для разработчиков, работающих над art ящиком, чем для тех, кто его использует. Внутренняя устройства не содержит никакой полезной сведений для того, кто пытается понять, как использовать ящик art, а скорее вызывает путаницу, поскольку разработчики, использующие его, должны понять, где искать, и должны указывать имена звеньев в выражениях use.

-

Чтобы удалить внутреннюю устройство из общедоступного API, мы можем изменить код ящика art в приложении 14-3, чтобы добавить операторы pub use для повторного реэкспорта элементов на верхнем уровне, как показано в приложении 14-5:

-

Файл: src/lib.rs

-
//! # Art
-//!
-//! A library for modeling artistic concepts.
-
-pub use self::kinds::PrimaryColor;
-pub use self::kinds::SecondaryColor;
-pub use self::utils::mix;
-
-pub mod kinds {
-    // --snip--
-    /// The primary colors according to the RYB color model.
-    pub enum PrimaryColor {
-        Red,
-        Yellow,
-        Blue,
-    }
-
-    /// The secondary colors according to the RYB color model.
-    pub enum SecondaryColor {
-        Orange,
-        Green,
-        Purple,
-    }
-}
-
-pub mod utils {
-    // --snip--
-    use crate::kinds::*;
-
-    /// Combines two primary colors in equal amounts to create
-    /// a secondary color.
-    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
-        SecondaryColor::Orange
-    }
-}
-

Приложение 14-5: Добавление операторов pub use для реэкспорта элементов

-

Документация API, которую cargo doc порождает для этой библиотеки, теперь будет перечислять и связывать реэкспорты на главной странице, как показано на рисунке 14-4, упрощая поиск видов PrimaryColor, SecondaryColor и функции mix.

- HTML-документация с примечанием для библиотеки в целом -

Рисунок 14-4: Первая страница документации для art, которая перечисляет реэкспорт

-

Пользователи ящика art могут по-прежнему видеть и использовать внутреннюю устройство из приложения 14-3, как показано в приложении 14-4, или они могут использовать более удобную устройство в приложении 14-5, как показано в приложении 14-6:

-

Файл: src/main.rs

-
use art::mix;
-use art::PrimaryColor;
-
-fn main() {
-    // --snip--
-    let red = PrimaryColor::Red;
-    let yellow = PrimaryColor::Yellow;
-    mix(red, yellow);
-}
-

Приложение 14-6: Программа, использующая реэкспортированные элементы из ящика art

-

В случаях, когда имеется много вложенных звеньев, реэкспорт видов на верхнем уровне с помощью pub use может существенно повысить удобство работы для людей, использующих ящик. Ещё одно распространённое использование pub use - это реэкспорт определений зависимого звена в текущем ящике, чтобы сделать определения этого ящика частью открытого API вашего ящика.

-

Создание полезной открытой устройства API - это больше искусство чем наука, и вы можете повторять, чтобы найти API, который лучше всего подойдёт вашим пользователям. Использование pub use даёт вам гибкость в том, как вы внутренне выстраиваете

-

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

-

Настройка учётной записи Crates.io

-

Прежде чем вы сможете обнародовать любые библиотеки, вам необходимо создать учётную запись на crates.io и получить API токен. Для этого зайдите на домашнюю страницу crates.io и войдите в систему через учётную запись GitHub. (В настоящее время требуется наличие учётной записи GitHub, но сайт может поддерживать другие способы создания учётной записи в будущем.) Сразу после входа в систему перейдите в настройки своей учётной записи по адресу https://crates.io/me/ и получите свой ключ API. Затем выполните приказ cargo login с вашим ключом API, например:

-
$ cargo login abcdefghijklmnopqrstuvwxyz012345
-
-

Этот приказ сообщит Cargo о вашем API token и сохранит его местно в ~/.cargo/credentials. Обратите внимание, что этот токен является тайным: не делитесь им ни с кем другим. Если вы по какой-либо причине поделитесь им с кем-либо, вы должны отозвать его и создать новый токен на crates.io.

-

Добавление метаданных в новую библиотеку

-

Допустим, у вас есть ящик, который вы хотите обнародовать. Перед обнародованием вам нужно добавить некоторые метаданные в раздел [package] файла Cargo.toml ящика.

-

Вашему ящику понадобится не повторяющееся имя. Пока вы работаете над ящиком местно, вы можете назвать его как угодно. Однако названия ящиков на crates.io определятся в мгновение первой обнародования. Как только ящику присвоено название, никто другой не сможет обнародовать ящик с таким же именем. Перед тем как обнародовать ящик, поищите название, которое вы хотите использовать. Если такое имя уже используется, вам придётся подобрать другое и отредактировать поле name в файле Cargo.toml в разделе [package], чтобы использовать новое имя в качестве размещаяемого, например, так:

-

Файл: Cargo.toml

-
[package]
-name = "guessing_game"
-
-

Даже если вы выбрали не повторяющееся имя, когда вы запустите cargo publish чтобы обнародовать ящик, вы получите предупреждение, а затем ошибку:

- -
$ cargo publish
-    Updating crates.io index
-warning: manifest has no description, license, license-file, documentation, homepage or repository.
-See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
---snip--
-error: failed to publish to registry at https://crates.io
-
-Caused by:
-  the remote server responded with an error: missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata
-
-

Это ошибка, потому что вам не хватает важной сведений: необходимы описание и лицензия, чтобы люди знали, что делает ваш ящик и на каких условиях они могут его использовать. В поле Cargo.toml добавьте описание, состоящее из одного-двух предложений, поскольку оно будет появляться вместе с вашим ящиком в итогах поиска. Для поля license нужно указать значение определителя лицензии. В Linux Foundation's Software Package Data Exchange (SPDX) перечислены определители, которые можно использовать для этого значения. Например, чтобы указать, что вы лицензировали свой crate, используя лицензию MIT, добавьте определитель MIT:

-

Файл: Cargo.toml

-
[package]
-name = "guessing_game"
-license = "MIT"
-
-

Если вы хотите использовать лицензию, которая отсутствует в SPDX, вам нужно поместить текст этой лицензии в файл, включите файл в свой дело, а затем используйте license-file, чтобы указать имя этого файла вместо использования ключа license.

-

Руководство по выбору лицензии для вашего дела выходит за рамки этой книги. Многие люди в сообществе Ржавчина лицензируют свои дела так же, как и Rust, используя двойную лицензию MIT OR Apache 2.0. Эта применение отображает, что вы также можете указать несколько определителей лицензий, разделённых OR, чтобы иметь несколько лицензий для вашего дела.

-

С добавлением единственного имени, исполнения, вашего описания и лицензии, файл Cargo.toml для дела, который готов к обнародования может выглядеть следующим образом:

-

Файл: Cargo.toml

-
[package]
-name = "guessing_game"
-version = "0.1.0"
-edition = "2021"
-description = "A fun game where you guess what number the computer has chosen."
-license = "MIT OR Apache-2.0"
-
-[dependencies]
-
-

Документация Cargo описывает другие метаданные, которые вы можете указать, чтобы другие могли легче находить и использовать ваш ящик.

-

Обнародование на Crates.io

-

Теперь, когда вы создали учётную запись, сохранили свой токен API, выбрали имя для своего ящика и указали необходимые метаданные, вы готовы к обнародования! Обнародование библиотеки загружает определённую исполнение в crates.io для использования другими.

-

Будьте осторожны, потому что обнародование является перманентной действием. Исполнение никогда не сможет быть перезаписана, а код не подлежит удалению. Одна из основных целей crates.io - служить постоянным архивом кода, чтобы сборки всех дел, зависящих от crates из crates.io продолжали работать. Предоставление возможности удаления исполнений сделало бы выполнение этой цели невозможным. При этом количество исполнений ящиков, которые вы можете обнародовать, не ограничено.

-

Запустите приказ cargo publish ещё раз. Сейчас эта приказ должна выполниться успешно:

- -
$ cargo publish
-    Updating crates.io index
-   Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
-   Verifying guessing_game v0.1.0 (file:///projects/guessing_game)
-   Compiling guessing_game v0.1.0
-(file:///projects/guessing_game/target/package/guessing_game-0.1.0)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
-   Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
-
-

Поздравляем! Теперь вы поделились своим кодом с сообществом Ржавчина и любой может легко добавить вашу библиотеку в качестве зависимости их дела.

-

Обнародование новой исполнения существующей библиотеки

-

Когда вы внесли изменения в свой ящик и готовы выпустить новую исполнение, измените значение version, указанное в вашем файле Cargo.toml и повторите размещение. Воспользуйтесь Semantic Versioning rules, чтобы решить, какой номер следующей исполнения подходит для ваших изменений. Затем запустите cargo publish, чтобы загрузить новую исполнение.

- -

-

Устранение устаревших исполнений с Crates.io с помощью cargo yank

-

Хотя вы не можете удалить предыдущие исполнения ящика, вы можете помешать любым будущим делам добавлять его в качестве новой зависимости. Это полезно, когда исполнение ящика сломана по той или иной причине. В таких случаейх Cargo поддерживает выламывание (yanking) исполнения ящика.

-

Вычёркивание исполнения не позволяет новым делам зависеть от этой исполнения, но при этом позволяет всем существующим делам, зависящим от неё, продолжать работу. По сути, исключение означает, что все дела с Cargo.lock не сломаются, а любые файлы Cargo.lock, которые будут порождаться в будущем, не смогут использовать исключённую исполнение.

-

Чтобы вычеркнуть исполнение ящика, в папки ящика, который вы обнародовали ранее, выполните приказ cargo yank и укажите, какую исполнение вы хотите вычеркнуть. Например, если мы обнародовали ящик под названием guessing_game исполнения 1.0.1 и хотим вычеркнуть её, в папке дела для guessing_game мы выполним:

- -
$ cargo yank --vers 1.0.1
-    Updating crates.io index
-        Yank guessing_game@1.0.1
-
-

Добавив в приказ --undo, вы также можете отменить выламывание и разрешить делам начать зависеть от исполнения снова:

-
$ cargo yank --vers 1.0.1 --undo
-    Updating crates.io index
-      Unyank guessing_game@1.0.1
-
-

Вычёркивание не удаляет код. Оно не может, например, удалить случайно загруженные пароли. Если это произойдёт, вы должны немедленно сбросить эти пароли.

-

Рабочие пространства Cargo

-

В главе 12 мы создали дополнение, который включал в себя двоичный и библиотечный ящики. По мере развития вашего дела может возникнуть случаей, когда библиотечный ящик будет становиться все больше, и вы захотите разделить ваш дополнение на несколько библиотечных ящиков. Cargo предоставляет возможность под названием workspaces, которая помогает управлять несколькими взаимосвязанными дополнениями, которые разрабатываются в тандеме.

-

Создание рабочего пространства

-

Workspace - это набор дополнений, которые используют один и тот же Cargo.lock и папку для хранения итогов сборки. Давайте создадим дело с использованием workspace - мы будем использовать обыкновенный код, чтобы сосредоточиться на устройстве рабочего пространства. Существует несколько способов внутренне выстроить

-

рабочую область, но мы покажем только один из них. У нас будет рабочая область, содержащая двоичный файл и две библиотеки. Двоичный файл, который обеспечивает основную возможность, будет зависеть от двух библиотек. Одна библиотека предоставит функцию add_one, а вторая - add_two. Эти три ящика будут частью одного workspace. Начнём с создания папки для рабочего окружения:

-
$ mkdir add
-$ cd add
-
-

Далее в папке add мы создадим файл Cargo.toml, который будет определять настройку всего рабочего окружения. В этом файле не будет разделы [package]. Вместо этого он будет начинаться с разделы [workspace], которая позволит нам добавить звенья в рабочее пространство, указав путь к дополнению с нашим двоичным ящиком; в данном случае этот путь - adder:

-

Файл: Cargo.toml

-
[workspace]
-
-members = [
-    "adder",
-]
-
-

Затем мы создадим исполняемый ящик adder, запустив приказ cargo new в папке add:

- -
$ cargo new adder
-     Created binary (application) `adder` package
-
-

На этом этапе мы можем создать рабочее пространство, запустив приказ cargo build. Файлы в папке add должны выглядеть следующим образом:

-
├── Cargo.lock
-├── Cargo.toml
-├── adder
-│   ├── Cargo.toml
-│   └── src
-│       └── main.rs
-└── target
-
-

Рабочая область содержит на верхнем уровне один папка target, в который будут помещены собранные артефакты; дополнение adder не имеет собственного папки target. Даже если мы запустим cargo build из папки adder, собранные артефакты все равно окажутся в add/target, а не в add/adder/target. Cargo так определил папку target в рабочем пространстве, потому что ящики в рабочем пространстве должны зависеть друг от друга. Если бы каждый ящик имел свой собственный папка target, каждому ящику пришлось бы пересобирать каждый из других ящиков в рабочем пространстве, чтобы поместить артефакты в свой собственный папка target. Благодаря совместному использованию единого папки target ящики могут избежать ненужной пересборки.

-

Добавление второго ящика в рабочее пространство

-

Далее давайте создадим ещё одного участника дополнения в рабочей области и назовём его add_one. Внесите изменения в Cargo.toml верхнего уровня так, чтобы указать путь add_one в списке members:

-

Файл: Cargo.toml

-
[workspace]
-
-members = [
-    "adder",
-    "add_one",
-]
-
-

Затем создайте новый ящик библиотеки с именем add_one:

- -
$ cargo new add_one --lib
-     Created library `add_one` package
-
-

Ваш папка add должен теперь иметь следующие папки и файлы:

-
├── Cargo.lock
-├── Cargo.toml
-├── add_one
-│   ├── Cargo.toml
-│   └── src
-│       └── lib.rs
-├── adder
-│   ├── Cargo.toml
-│   └── src
-│       └── main.rs
-└── target
-
-

В файле add_one/src/lib.rs добавим функцию add_one:

-

Файл: add_one/src/lib.rs

-
pub fn add_one(x: i32) -> i32 {
-    x + 1
-}
-

Теперь мы можем сделать так, чтобы дополнение adder с нашим исполняемым файлом зависел от дополнения add_one, содержащего нашу библиотеку. Сначала нам нужно добавить зависимость пути от add_one в adder/Cargo.toml.

-

Файл: adder/Cargo.toml

-
[dependencies]
-add_one = { path = "../add_one" }
-
-

Cargo не исходит из того, что ящики в рабочем пространстве могут зависеть друг от друга, поэтому нам необходимо явно указать отношения зависимости.

-

Далее, давайте используем функцию add_one (из ящика add_one) в ящике adder. Откройте файл adder/src/main.rs и добавьте строку use в верхней части, чтобы ввести в область видимости новый библиотечный ящик add_one. Затем измените функцию main для вызова функции add_one, как показано в приложении 14-7.

-

Файл: adder/src/main.rs

-
use add_one;
-
-fn main() {
-    let num = 10;
-    println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
-}
-

Приложение 14-7: Использование возможностей библиотечного ящика add-one в ящике adder

-

Давайте соберём рабочее пространство, запустив приказ cargo build в папке верхнего уровня add!

- -
$ 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
-
-

Чтобы запустить двоичный ящик из папки add, нам нужно указать какой дополнение из рабочей области мы хотим использовать с помощью переменной -p и названия дополнения в приказу cargo run:

- -
$ cargo run -p adder
-    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
-     Running `target/debug/adder`
-Hello, world! 10 plus one is 11!
-
-

Запуск кода из 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

-
[dependencies]
-rand = "0.8.5"
-
-

Теперь мы можем добавить use rand; в файл add_one/src/lib.rs и сделать сборку рабочего пространства, запустив cargo build в папке add, что загрузит и собирает rand ящик:

- -
$ cargo build
-    Updating crates.io index
-  Downloaded rand v0.8.5
-   --snip--
-   Compiling rand v0.8.5
-   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: `add_one` (lib) generated 1 warning
-   Compiling adder v0.1.0 (file:///projects/add/adder)
-    Finished dev [unoptimized + debuginfo] target(s) in 10.18s
-
-

Файл Cargo.lock верхнего уровня теперь содержит сведения о зависимости add_one к ящику rand. Тем не менее, не смотря на то что rand использован где-то в рабочем пространстве, мы не можем использовать его в других ящиках рабочего пространства, пока не добавим ящик rand в отдельные Cargo.toml файлы. Например, если мы добавим use rand; в файл adder/src/main.rs ящика adder, то получим ошибку:

- -
$ 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.toml для дополнения adder и укажите, что rand также является его зависимостью. При сборке дополнения adder rand будет добавлен в список зависимостей для adder в Cargo.lock, но никаких дополнительных повторов rand загружено не будет. Cargo позаботился о том, чтобы все ящики во всех дополнениях рабочей области, использующих дополнение rand, использовали одну и ту же исполнение, экономя нам место и обеспечивая, что все ящики в рабочей области будут совместимы друг с другом.

-

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

-

В качестве ещё одного улучшения давайте добавим проверка функции add_one::add_one в add_one:

-

Файл: add_one/src/lib.rs

-
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 в папке верхнего уровня add. Запуск cargo test в рабочем пространстве, внутренне выстроеном

-

подобно этому, запустит проверки для всех ящиков в рабочем пространстве:

- -
$ 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 unittests src/lib.rs (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 unittests src/main.rs (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
-
-

Первая раздел вывода показывает, что проверка it_works в ящике add_one прошёл. Следующая раздел показывает, что в ящике adder не было обнаружено ни одного проверки, а последняя раздел показывает, что в ящике add_one не было найдено ни одного проверки документации.

-

Мы также можем запустить проверки для одного определенного ящика в рабочем пространстве из папка верхнего уровня с помощью флага -p и указанием имени ящика для которого мы хотим запустить проверки:

- -
$ cargo test -p add_one
-    Finished test [unoptimized + debuginfo] target(s) in 0.00s
-     Running unittests src/lib.rs (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 test запускает только проверки для ящика add-one и не запускает проверки ящика adder.

-

Если вы соберётесь обнародовать ящики из рабочего пространства на crates.io, каждый ящик будет необходимо будет обнародовать отдельно. Подобно cargo test, мы можем обнародовать определенный ящик из нашей рабочей области, используя флаг -p и указав имя ящика, который мы хотим обнародовать.

-

Для дополнительной опытов добавьте ящик add_two в данное рабочее пространство подобным способом, как делали с ящик add_one !

-

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

-
-

-

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

-

Приказ cargo install позволяет местно устанавливать и использовать исполняемые ящики. Она не предназначена для замены системных дополнений; она используется как удобный способ Ржавчина разработчикам устанавливать средства, которыми другие разработчики поделились на сайте crates.io. Заметьте, можно устанавливать только дополнения, имеющие исполняемые целевые ящики. Исполняемой целью (binary target) является запускаемая программа, созданная и имеющая в составе ящика файл src/main.rs или другой файл, указанный как исполняемый, в отличии от библиотечных ящиков, которые не могут запускаться сами по себе, но подходят для включения в другие программы. Обычно ящик содержит сведения в файле README, является ли он библиотекой, исполняемым файлом или обоими вместе.

-

Все исполняемые файлы установленные приказом cargo install сохранены в корневой установочной папке bin. Если вы установили Ржавчина с помощью rustup.rs и у вас его нет в пользовательских настройках, то этим папкой будет $HOME/.cargo/bin. Он заверяет, что папка находится в вашем окружении $PATH, чтобы вы имели возможность запускать программы, которые вы установили приказом cargo install.

-

Так, например, в главе 12 мы упоминали, что для поиска файлов существует выполнение утилиты grep на Ржавчина под названием ripgrep. Чтобы установить ripgrep, мы можем выполнить следующее:

- -
$ cargo install ripgrep
-    Updating crates.io index
-  Downloaded ripgrep v13.0.0
-  Downloaded 1 crate (243.3 KB) in 0.88s
-  Installing ripgrep v13.0.0
---snip--
-   Compiling ripgrep v13.0.0
-    Finished release [optimized + debuginfo] target(s) in 3m 10s
-  Installing ~/.cargo/bin/rg
-   Installed package `ripgrep v13.0.0` (executable `rg`)
-
-

Последняя строка вывода показывает местоположение и название установленного исполняемого файла, который в случае ripgrep называется rg. Если вашей установочной папкой является $PATH, как уже упоминалось ранее, вы можете запустить rg --help и начать использовать более быстрый и грубый средство для поиска файлов!

-

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

-

Cargo расчитан так, что вы можете расширять его новыми субприказми без необходимости изменения самого Cargo. Если исполняемый файл доступен через переменную окружения $PATH и назван по образцу cargo-something, то его можно запускать как субприказ Cargo cargo something. Пользовательские приказы подобные этой также перечисляются в списке доступных через cargo --list. Возможность использовать cargo install для установки расширений и затем запускать их так же, как встроенные в Cargo средства, это очень удобное следствие продуманного внешнего вида Cargo!

-

Итоги

-

Совместное использование кода с Cargo и crates.io является частью того, что делает внутреннее устройство Ржавчина полезной для множества различных задач. Обычная библиотека Ржавчина небольшая и безотказная, но ящики легко распространять, использовать и улучшать независимо от самого языка. Не стесняйтесь делиться кодом, который был вам полезен, через crates.io; скорее всего, он будет полезен и кому-то ещё!

-

Умные указатели

-

Указатель — это общая подход для переменной, которая содержит адрес участка памяти. Этот адрес «относится к», или «указывает на» некоторые другие данные. Наиболее общая разновидность указателя в Ржавчина — это ссылка, о которой вы узнали из главы 4. Ссылки обозначаются символом & и заимствуют значение, на которое указывают. Они не имеют каких-либо особых возможностей, кроме как ссылаться на данные, и не имеют никаких накладных расходов.

-

Умные указатели, с другой стороны, являются устройствами данных, которые не только действуют как указатель, но также имеют дополнительные метаданные и возможности. Подход умных указателей не неповторима для Rust: умные указатели возникли в C++ и существуют в других языках. В Ржавчина есть разные умные указатели, определённые в встроенной библиотеке, которые обеспечивают возможность, выходящую за рамки ссылок. Одним из примеров, который мы рассмотрим в этой главе, является вид умного указателя reference counting (подсчёт ссылок). Этот указатель позволяет иметь несколько владельцев с помощью отслеживания количества владельцев и, когда владельцев не остаётся, очищает данные.

-

Rust с его подходом владения и заимствования имеет дополнительное различие между ссылками и умными указателями: в то время, как ссылки только заимствуют данные, умные указатели часто владеют данными, на которые указывают.

-

Ранее мы уже сталкивались с умными указателями в этой книге, хотя и не называли их так, например String и Vec<T> в главе 8. Оба этих вида считаются умными указателями, потому что они владеют некоторой областью памяти и позволяют ею управлять. У них также есть метаданные и дополнительные возможности или заверения. String, например, хранит свой размер в виде метаданных и заверяет, что содержимое строки всегда будет в кодировке UTF-8.

-

Умные указатели обычно выполняются с помощью устройств. Присущей чертой, которая отличает умный указатель от обычной устройства, является то, что для умных указателей выполнены особенности Deref и Drop. Особенность Deref позволяет образцу умного указателя вести себя как ссылка, так что вы можете написать код, работающий с ним как со ссылкой, так и как с умным указателем. Особенность Drop позволяет написать код, который будет запускаться когда образец умного указателя выйдет из области видимости. В этой главе мы обсудим оба особенности и выясним, почему они важны для умных указателей.

-

Учитывая, что образец умного указателя является общим образцом разработки, часто используемым в Rust, эта глава не описывает все существующие умные указатели. Множество библиотек имеют свои умные указатели, и вы также можете написать свои. Мы охватим наиболее распространённые умные указатели из встроенной библиотеки:

-
    -
  • Box<T> для распределения значений в куче (памяти)
  • -
  • Rc<T> вид счётчика ссылок, который допускает множественное владение
  • -
  • Виды Ref<T> и RefMut<T>, доступ к которым осуществляется через вид RefCell<T>, который обеспечивает правила заимствования во время выполнения вместо времени сборки
  • -
-

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

-

Приступим!

-

Использование Box<T> для ссылки на данные в куче

-

Наиболее простой умный указатель - это box, чей вид записывается как Box<T>. Такие переменные позволяют хранить данные в куче, а не в обойме. То, что остаётся в обойме, является указателем на данные в куче. Обратитесь к Главе 4, чтобы рассмотреть разницу между обоймой и кучей.

-

У Box нет неполадок с производительностью, кроме хранения данных в куче вместо обоймы. Но он также и не имеет множества дополнительных возможностей. Вы будете использовать его чаще всего в следующих случаейх:

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

Мы выясним первую случай в разделе "Выполнение рекурсивных видов с помощью Box". Во втором случае, передача владения на большой размер данных может занять много времени, потому что данные повторяются через обойма. Для повышения производительности в этой случаи, мы можем хранить большое количество данных в куче с помощью Box. Затем только небольшое количество данных указателя воспроизводится в обойме, в то время как данные, на которые он ссылается, остаются в одном месте кучи. Третий случай известен как особенность предмет (trait object) и глава 17 посвящает целый раздел "Использование особенность предметов, которые допускают значения разных видов" только этой теме. Итак, то, что вы узнаете здесь, вы примените снова в Главе 17!

-

Использование Box<T> для хранения данных в куче

-

Прежде чем мы обсудим этот исход использования Box<T>, мы рассмотрим правила написания и то, как взаимодействовать со значениями, хранящимися в Box<T>.

-

В приложении 15-1 показано, как использовать поле для хранения значения i32 в куче:

-

Файл: src/main.rs

-
fn main() {
-    let b = Box::new(5);
-    println!("b = {b}");
-}
-

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

-

Мы объявляем переменную b со значением Box, указывающим на число 5, размещённое в куче. Эта программа выведет b = 5; в этом случае мы получаем доступ к данным в box так же, как если бы эти данные находились в обойме. Как и любое другое значение, когда box выйдет из области видимости, как b в конце main, он будет удалён. Деаллокация происходит как для box ( хранящегося в обойме), так и для данных, на которые он указывает (хранящихся в куче).

-

Размещать одиночные значения в куче не слишком целесообразно, поэтому вряд ли вы будете часто использовать box'ы таким образом. В большинстве случаев более уместно размещать такие значения, как i32, в обойме, где они и сохраняются по умолчанию. Давайте рассмотрим случай, когда box позволяет нам определить виды, которые мы не могли бы иметь, если бы у нас не было box.

-

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

-

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

-

Cons list не является часто используемой устройством данных в Rust. В большинстве случаев, когда вам нужен список элементов при использовании Rust, лучше использовать Vec<T>. Другие, более сложные рекурсивные виды данных полезны в определённых случаейх, но благодаря тому, что в этой главе мы начнём с cons list, мы сможем выяснить, как box позволяет нам определить рекурсивный вид данных без особого напряжения.

-

Приложение 15-2 содержит объявление перечисления cons списка. Обратите внимание, что этот код не будет собираться, потому что вид List не имеет известного размера, что мы и выясним.

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, List),
-    Nil,
-}
-
-fn main() {}
-

Приложение 15-2: Первая попытка определить перечисление в качестве устройства данных cons list, состоящей из i32 значений.

-
-

Примечание: В данном примере мы выполняем cons list, который содержит только значения i32. Мы могли бы выполнить его с помощью generics, о которых мы говорили в главе 10, чтобы определить вид cons list, который мог бы хранить значения любого вида.

-
-

Использование вида List для хранения списка 1, 2, 3 будет выглядеть как код в приложении 15-3:

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, List),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-
-fn main() {
-    let list = Cons(1, Cons(2, Cons(3, Nil)));
-}
-

Приложение 15-3: Использование перечисления List для хранения списка 1, 2, 3

-

Первое значение Cons содержит 1 и другой List. Это значение List является следующим значением Cons, которое содержит 2 и другой List. Это значение List является ещё один значением Cons, которое содержит 3 и значение List, которое наконец является Nil, не рекурсивным исходом, сигнализирующим об окончании списка.

-

Если мы попытаемся собрать код в приложении 15-3, мы получим ошибку, показанную в приложении 15-4:

-
$ cargo run
-   Compiling cons-list v0.1.0 (file:///projects/cons-list)
-error[E0072]: recursive type `List` has infinite size
- --> src/main.rs:1:1
-  |
-1 | enum List {
-  | ^^^^^^^^^
-2 |     Cons(i32, List),
-  |               ---- recursive without indirection
-  |
-help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
-  |
-2 |     Cons(i32, Box<List>),
-  |               ++++    +
-
-error[E0391]: cycle detected when computing when `List` needs drop
- --> src/main.rs:1:1
-  |
-1 | enum List {
-  | ^^^^^^^^^
-  |
-  = note: ...which immediately requires computing when `List` needs drop again
-  = note: cycle used when computing whether `List` needs drop
-  = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
-
-Some errors have detailed explanations: E0072, E0391.
-For more information about an error, try `rustc --explain E0072`.
-error: could not compile `cons-list` (bin "cons-list") due to 2 previous errors
-
-

Приложение 15-4: Ошибка, которую мы получаем при попытке определить рекурсивное перечисление

-

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

-

Вычисление размера нерекурсивного вида

-

Вспомните перечисление Message определённое в приложении 6-2, когда обсуждали объявление enum в главе 6:

-
enum Message {
-    Quit,
-    Move { x: i32, y: i32 },
-    Write(String),
-    ChangeColor(i32, i32, i32),
-}
-
-fn main() {}
-

Чтобы определить, сколько памяти выделять под значение Message, Ржавчина проходит каждый из исходов, чтобы увидеть, какой исход требует наибольшее количество памяти. Ржавчина видит, что для Message::Quit не требуется места, Message::Move хватает места для хранения двух значений i32 и т.д. Так как будет использоваться только один исход, то наибольшее пространство, которое потребуется для значения Message, это пространство, которое потребуется для хранения самого большого из исходов перечисления.

-

Сравните это с тем, что происходит, когда Ржавчина пытается определить, сколько места необходимо рекурсивному виду, такому как перечисление List в приложении 15-2. Сборщик смотрит на исход Cons, который содержит значение вида i32 и значение вида List. Следовательно, Cons нужно пространство, равное размеру i32 плюс размер List. Чтобы выяснить, сколько памяти необходимо виду List, сборщик смотрит на исходы, начиная с Cons. Исход Cons содержит значение вида i32 и значение вида List, и этот этап продолжается бесконечно, как показано на рисунке 15-1.

- Бесконечный список Cons -

Рисунок 15-1: Бесконечный List, состоящий из нескончаемого числа исходов Cons

-

Использование Box<T> для получения рекурсивного вида с известным размером

-

Поскольку Ржавчина не может определить, сколько места нужно выделить для видов с рекурсивным определением, сборщик выдаёт ошибку с этим полезным предложением:

- -
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable
-  |
-2 |     Cons(i32, Box<List>),
-  |               ++++    +
-
-

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

-

Поскольку Box<T> является указателем, Ржавчина всегда знает, сколько места нужно Box<T>: размер указателя не меняется в зависимости от объёма данных, на которые он указывает. Это означает, что мы можем поместить Box<T> внутрь образца Cons вместо значения List напрямую. Box<T> будет указывать на значение очередного List, который будет находиться в куче, а не внутри образца Cons. Мировозренческо у нас все ещё есть список, созданный из списков, содержащих другие списки, но эта выполнение теперь больше похожа на размещение элементов рядом друг с другом, а не внутри друг друга.

-

Мы можем изменить определение перечисления List в приложении 15-2 и использование List в приложении 15-3 на код из приложения 15-5, который будет собираться:

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, Box<List>),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-
-fn main() {
-    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
-}
-

Приложение 15-5: Определение List, которое использует Box<T> для того, чтобы иметь вычисляемый размер

-

Cons требуется объём i32 плюс место для хранения данных указателя box. Nil не хранит никаких значений, поэтому ему нужно меньше места, чем Cons. Теперь мы знаем, что любое значение List займёт размер i32 плюс размер данных указателя box. Используя box, мы разорвали бесконечную рекурсивную цепочку, поэтому сборщик может определить размер, необходимый для хранения значения List. На рисунке 15-2 показано, как теперь выглядит Cons.

- Бесконечный список Cons -

Рисунок 15-2: List, который не является бесконечно большим, потому что Cons хранит Box.

-

Box-ы обеспечивают только перенаправление и выделение в куче; у них нет никаких других особых возможностей, подобных тем, которые мы увидим у других видов умных указателей. У них также нет накладных расходов на производительность, которые несут эти особые возможности, поэтому они могут быть полезны в таких случаях, как cons list, где перенаправление - единственная функция, которая нам нужна. В главе 17 мы также рассмотрим другие случаи использования box.

-

Вид Box<T> является умным указателем, поскольку он выполняет особенность Deref, который позволяет обрабатывать значения Box<T> как ссылки. Когда значение Box<T> выходит из области видимости, данные кучи, на которые указывает box, также очищаются благодаря выполнения особенности Drop. Эти два особенности будут ещё более значимыми для возможности, предоставляемой другими видами умных указателей, которые мы обсудим в оставшейся части этой главы. Давайте рассмотрим эти два особенности более подробно.

-

Обращение с умными указателями как с обычными ссылками с помощью Deref особенности

-

Используя особенность Deref, вы можете изменить поведение оператора разыменования * (не путать с операторами умножения или вездесущего подключения). Выполнив Deref таким образом, что умный указатель может рассматриваться как обычная ссылка, вы можете писать код, оперирующий ссылками, а также использовать этот код с умными указателями.

-

Давайте сначала посмотрим, как работает оператор разыменования с обычными ссылками. Затем мы попытаемся определить пользовательский вид, который ведёт себя как Box<T> и посмотрим, почему оператор разыменования не работает как ссылка для нового объявленного вида. Мы рассмотрим, как выполнение особенности Deref делает возможным работу умных указателей подобно ссылкам. Затем посмотрим на разыменованное приведение (deref coercion) в Ржавчина и как оно позволяет работать с любыми ссылками или умными указателями.

-
-

Примечание: есть одна большая разница между видом MyBox<T>, который мы собираемся создать и существующим Box<T>: наша исполнение не будет хранить свои данные в куче. В примере мы сосредоточимся на особенности Deref, поэтому менее важно то, где данные хранятся, чем поведение подобное указателю.

-
- -

-

Следуя за указателем на значение

-

Обычная ссылка - это разновидность указателя, а указатель можно рассматривать как своеобразную стрелочку направляющую к значению, хранящемуся в другом месте. В приложении 15-6 мы создаём ссылку на значение i32, а затем используем оператор разыменования для перехода от ссылки к значению:

-

Файл: src/main.rs

-
fn main() {
-    let x = 5;
-    let y = &x;
-
-    assert_eq!(5, x);
-    assert_eq!(5, *y);
-}
-

Приложение 15-6: Использование оператора разыменования для следования по ссылке к значению i32

-

Переменной x присвоено значение5 вида i32. Мы установили в качестве значения y ссылку на x. Мы можем утверждать, что значение x равно 5. Однако, если мы хотим сделать утверждение о значении в y, мы должны использовать *y, чтобы перейти по ссылке к значению, на которое она указывает (таким образом, происходит разыменование), для того чтобы сборщик при сравнении мог использовать действительное значение. Как только мы разыменуем y, мы получим доступ к целочисленному значению, на которое указывает y, которое и будем сравнивать с 5.

-

Если бы мы попытались написать assert_eq!(5, y);, то получили ошибку сборки:

-
$ cargo run
-   Compiling deref-example v0.1.0 (file:///projects/deref-example)
-error[E0277]: can't compare `{integer}` with `&{integer}`
- --> src/main.rs:6:5
-  |
-6 |     assert_eq!(5, y);
-  |     ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}`
-  |
-  = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}`
-  = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `deref-example` (bin "deref-example") due to 1 previous error
-
-

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

-

Использование Box<T> как ссылку

-

Мы можем переписать код в приложении 15-6, чтобы использовать Box<T> вместо ссылки; оператор разыменования, используемый для Box<T> в приложении 15-7, работает так же, как оператор разыменования, используемый для ссылки в приложении 15-6:

-

Файл: src/main.rs

-
fn main() {
-    let x = 5;
-    let y = Box::new(x);
-
-    assert_eq!(5, x);
-    assert_eq!(5, *y);
-}
-

Приложение 15-7: Использование оператора разыменования с видом Box<i32>

-

Основное различие между приложением 15-7 и приложением 15-6 заключается в том, что здесь мы устанавливаем y как образец Box<T>, указывающий на воспроизведенное значение x, а не как ссылку, указывающую на значение x. В последнем утверждении мы можем использовать оператор разыменования, чтобы проследовать за указателем Box<T> так же, как мы это делали, когда y был ссылкой. Далее мы рассмотрим, что особенного в Box<T>, что позволяет нам использовать оператор разыменования, определяя наш собственный вид.

-

Определение собственного умного указателя

-

Давайте создадим умный указатель, похожий на вид Box<T> предоставляемый встроенной библиотекой, чтобы понять как поведение умных указателей отличается от поведения обычной ссылки. Затем мы рассмотрим вопрос, как добавить возможность использовать оператор разыменования.

-

Вид Box<T> в конечном итоге определяется как устройства упорядоченного ряда с одним элементом, поэтому в приложении 15-8 подобным образом определяется MyBox<T>. Мы также определим функцию new, чтобы она соответствовала функции new, определённой в Box<T>.

-

Файл: src/main.rs

-
struct MyBox<T>(T);
-
-impl<T> MyBox<T> {
-    fn new(x: T) -> MyBox<T> {
-        MyBox(x)
-    }
-}
-
-fn main() {}
-

Приложение 15-8: Определение вида MyBox<T>

-

Мы определяем устройство с именем MyBox и объявляем обобщённый свойство T, потому что мы хотим, чтобы наш вид хранил значения любого вида. Вид MyBox является устройством упорядоченного ряда с одним элементом вида T. Функция MyBox::new принимает один свойство вида T и возвращает образец MyBox, который содержит переданное значение.

-

Давайте попробуем добавить функцию main из приложения 15-7 в приложение 15-8 и изменим её на использование вида MyBox<T>, который мы определили вместо Box<T>. Код в приложении 15-9 не будет собираться, потому что Ржавчина не знает, как разыменовывать MyBox.

-

Файл: src/main.rs

-
struct MyBox<T>(T);
-
-impl<T> MyBox<T> {
-    fn new(x: T) -> MyBox<T> {
-        MyBox(x)
-    }
-}
-
-fn main() {
-    let x = 5;
-    let y = MyBox::new(x);
-
-    assert_eq!(5, x);
-    assert_eq!(5, *y);
-}
-

Приложение 15-9. Попытка использовать MyBox<T> таким же образом, как мы использовали ссылки и Box<T>

-

Вот итог ошибки сборки:

-
$ cargo run
-   Compiling deref-example v0.1.0 (file:///projects/deref-example)
-error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
-  --> src/main.rs:14:19
-   |
-14 |     assert_eq!(5, *y);
-   |                   ^^
-
-For more information about this error, try `rustc --explain E0614`.
-error: could not compile `deref-example` (bin "deref-example") due to 1 previous error
-
-

Наш вид MyBox<T> не может быть разыменован, потому что мы не выполнили эту возможность. Чтобы включить разыменование с помощью оператора *, мы выполняем особенность Deref.

-

Трактование вида как ссылки выполняя особенность Deref

-

Как обсуждалось в разделе “Выполнение особенности для типа” Главы 10, для выполнения особенности нужно предоставить выполнения требуемых способов особенности. Особенность Deref, предоставляемый встроенной библиотекой требует от нас выполнения одного способа с именем deref, который заимствует self и возвращает ссылку на внутренние данные. Приложение 15-10 содержит выполнение Deref добавленную к определению MyBox:

-

Файл: src/main.rs

-
use std::ops::Deref;
-
-impl<T> Deref for MyBox<T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-struct MyBox<T>(T);
-
-impl<T> MyBox<T> {
-    fn new(x: T) -> MyBox<T> {
-        MyBox(x)
-    }
-}
-
-fn main() {
-    let x = 5;
-    let y = MyBox::new(x);
-
-    assert_eq!(5, x);
-    assert_eq!(5, *y);
-}
-

Приложение 15-10: Выполнение Deref для вида MyBox<T>

-

правила написания type Target = T; определяет связанный вид для использования у особенности Deref. Связанные виды - это немного другой способ объявления обобщённого свойства, но пока вам не нужно о них беспокоиться; мы рассмотрим их более подробно в главе 19.

-

Мы заполним тело способа deref оператором &self.0 , чтобы deref вернул ссылку на значение, к которому мы хотим получить доступ с помощью оператора *; вспомним из раздела "Using Tuple Structs without Named Fields to Create Different Types" главы 5, что .0 получает доступ к первому значению в упорядоченной в ряд устройстве. Функция main в приложении 15-9, которая вызывает * для значения MyBox<T>, теперь собирается, и проверки проходят!

-

Без особенности Deref сборщик может только разыменовывать & ссылки. Способ deref даёт сборщику возможность принимать значение любого вида, выполняющего Deref и вызывать способ deref чтобы получить ссылку &, которую он знает, как разыменовывать.

-

Когда мы ввели *y в приложении 15-9, Ржавчина в действительности выполнил за кулисами такой код:

-
*(y.deref())
-

Rust заменяет оператор * вызовом способа deref и затем простое разыменование, поэтому нам не нужно думать о том, нужно ли нам вызывать способ deref. Эта функция Ржавчина позволяет писать код, который исполняется одинаково, независимо от того, есть ли у нас обычная ссылка или вид, выполняющий особенность Deref.

-

Причина, по которой способ deref возвращает ссылку на значение, и что простое разыменование вне круглых скобок в *(y.deref()) все ещё необходимо, связана с системой владения. Если бы способ deref возвращал значение напрямую, а не ссылку на него, значение переместилось бы из self. Мы не хотим передавать владение внутренним значением внутри MyBox<T> в этом случае и в большинстве случаев, когда мы используем оператор разыменования.

-

Обратите внимание, что оператор * заменён вызовом способа deref, а затем вызовом оператора * только один раз, каждый раз, когда мы используем * в коде. Поскольку замена оператора * не повторяется бесконечно, мы получаем данные вида i32, которые соответствуют 5 в assert_eq! приложения 15-9.

-

Неявные разыменованные приведения с функциями и способами

-

Разыменованное приведение преобразует ссылку на вид, который выполняет признак Deref, в ссылку на другой вид. Например, deref coercion может преобразовать &String в &str, потому что String выполняет признак Deref, который возвращает &str. Deref coercion - это удобный рычаг, который Ржавчина использует для переменных функций и способов, и работает только для видов, выполняющих признак Deref. Это происходит самостоятельно , когда мы передаём в качестве переменной функции или способа ссылку на значение определённого вида, которое не соответствует виду свойства в определении функции или способа. В итоге серии вызовов способа deref вид, который мы передали, преобразуется в вид, необходимый для свойства.

-

Разыменованное приведение было добавлено в Rust, так что программистам, пишущим вызовы функций и способов, не нужно добавлять множество явных ссылок и разыменований с помощью использования & и *. Возможность разыменованного приведения также позволяет писать больше кода, который может работать как с ссылками, так и с умными указателями.

-

Чтобы увидеть разыменованное приведение в действии, давайте воспользуемся видом MyBox<T> определённым в приложении 15-8, а также выполнение Deref добавленную в приложении 15-10. Приложение 15-11 показывает определение функции, у которой есть свойство вида срез строки:

-

Файл: src/main.rs

-
fn hello(name: &str) {
-    println!("Hello, {name}!");
-}
-
-fn main() {}
-

Приложение 15-11: Функция hello имеющая свойство name вида &str

-

Можно вызвать функцию hello со срезом строки в качестве переменной, например hello("Rust");. Разыменованное приведение делает возможным вызов hello со ссылкой на значение вида MyBox<String>, как показано в приложении 15-12.

-

Файл: src/main.rs

-
use std::ops::Deref;
-
-impl<T> Deref for MyBox<T> {
-    type Target = T;
-
-    fn deref(&self) -> &T {
-        &self.0
-    }
-}
-
-struct MyBox<T>(T);
-
-impl<T> MyBox<T> {
-    fn new(x: T) -> MyBox<T> {
-        MyBox(x)
-    }
-}
-
-fn hello(name: &str) {
-    println!("Hello, {name}!");
-}
-
-fn main() {
-    let m = MyBox::new(String::from("Rust"));
-    hello(&m);
-}
-

Приложение 15-12: Вызов hello со ссылкой на значение MyBox<String>, которое работает из-за разыменованного приведения

-

Здесь мы вызываем функцию hello с переменнаяом &m, который является ссылкой на значение MyBox<String>. Поскольку мы выполнили особенность Deref для MyBox<T> в приложении 15-10, то Ржавчина может преобразовать &MyBox<String> в &String вызывая deref. Обычная библиотека предоставляет выполнение особенности Deref для вида String, которая возвращает срез строки, это описано в документации API особенности Deref. Ржавчина снова вызывает deref, чтобы превратить &String в &str, что соответствует определению функции hello.

-

Если бы Ржавчина не выполнил разыменованное приведение, мы должны были бы написать код в приложении 15-13 вместо кода в приложении 15-12 для вызова способа hello со значением вида &MyBox<String>.

-

Файл: src/main.rs

-
use std::ops::Deref;
-
-impl<T> Deref for MyBox<T> {
-    type Target = T;
-
-    fn deref(&self) -> &T {
-        &self.0
-    }
-}
-
-struct MyBox<T>(T);
-
-impl<T> MyBox<T> {
-    fn new(x: T) -> MyBox<T> {
-        MyBox(x)
-    }
-}
-
-fn hello(name: &str) {
-    println!("Hello, {name}!");
-}
-
-fn main() {
-    let m = MyBox::new(String::from("Rust"));
-    hello(&(*m)[..]);
-}
-

Приложение 15-13: Код, который нам пришлось бы написать, если бы в Ржавчина не было разыменованного приведения ссылок

-

Код (*m) разыменовывает MyBox<String> в String. Затем & и [..] принимают строковый срез String, равный всей строке, чтобы соответствовать ярлыке hello. Код без разыменованного приведения сложнее читать, писать и понимать со всеми этими символами. Разыменованное приведение позволяет Ржавчина обрабатывать эти преобразования для нас самостоятельно .

-

Когда особенность Deref определён для задействованных видов, Ржавчина проанализирует виды и будет использовать Deref::deref столько раз, сколько необходимо, чтобы получить ссылку, соответствующую виду свойства. Количество раз, которое нужно вставить Deref::deref определяется во время сборки, поэтому использование разыменованного приведения не имеет накладных расходов во время выполнения!

-

Как разыменованное приведение взаимодействует с изменяемостью

-

Подобно тому, как вы используете особенность Deref для переопределения оператора * у неизменяемых ссылок, вы можете использовать особенность DerefMut для переопределения оператора * у изменяемых ссылок.

-

Rust выполняет разыменованное приведение, когда находит виды и выполнения особенностей в трёх случаях:

-
    -
  • Из вида &T в вид &U когда верно T: Deref<Target=U>
  • -
  • Из вида &mut T в вид &mut U когда верно T: DerefMut<Target=U>
  • -
  • Из вида &mut T в вид &U когда верно T: Deref<Target=U>
  • -
-

Первые два случая равноценны друг другу, за исключением того, что второй выполняет изменяемость. В первом случае говорится, что если у вас есть &T, а T выполняет Deref для некоторого вида U, вы сможете прозрачно получить &U. Во втором случае говорится, что такое же разыменованное приведение происходит и для изменяемых ссылок.

-

Третий случай хитрее: Ржавчина также приводит изменяемую ссылку к неизменяемой. Но обратное не представляется возможным: неизменяемые ссылки никогда не приводятся к изменяемым ссылкам. Из-за правил заимствования, если у вас есть изменяемая ссылка, эта изменяемая ссылка должна быть единственной ссылкой на данные (в противном случае программа не будет собираться). Преобразование одной изменяемой ссылки в неизменяемую ссылку никогда не нарушит правила заимствования. Преобразование неизменяемой ссылки в изменяемую ссылку потребует наличия только одной неизменяемой ссылки на эти данные, и правила заимствования не заверяют этого. Следовательно, Ржавчина не может сделать предположение, что преобразование неизменяемой ссылки в изменяемую ссылку возможно.

-

Запуск кода при очистке с помощью особенности Drop

-

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

-

Мы рассматриваем Drop в среде умных указателей, потому что возможность свойства Drop по сути всегда используется при выполнения умного указателя. Например, при сбросе Box<T> происходит деаллокация пространства на куче, на которое указывает box.

-

В некоторых языках для некоторых видов программист должен вызывать код для освобождения памяти или ресурсов каждый раз, когда он завершает использование образцов этих видов. Примерами могут служить указатели файлов, сокеты или блокировки. Если забыть об этом, система окажется перегруженной и может упасть. В Ржавчина вы можете указать, что определённый отрывок кода должен выполняться всякий раз, когда значение выходит из области видимости, и сборщик самостоятельно будет его вставлять. Как следствие, вам не нужно заботиться о размещении кода очистки везде в программе, где завершается работа образца определённого вида - утечки ресурсов все равно не будет!

-

Вы можете задать определённую логику, которая будет выполняться, когда значение выходит за пределы области видимости, выполнив признак Drop. Особенность Drop требует от вас выполнения одного способа drop, который принимает изменяемую ссылку на self. Чтобы увидеть, когда Ржавчина вызывает drop, давайте выполняем drop с помощью указаний println!.

-

В приложении 15-14 показана устройства CustomSmartPointer, единственной не имеющей себе подобных возможностью которой является печать Dropping CustomSmartPointer!, когда образец выходит из области видимости, чтобы показать, когда Ржавчина выполняет функцию drop.

-

Файл: src/main.rs

-
struct CustomSmartPointer {
-    data: String,
-}
-
-impl Drop for CustomSmartPointer {
-    fn drop(&mut self) {
-        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
-    }
-}
-
-fn main() {
-    let c = CustomSmartPointer {
-        data: String::from("my stuff"),
-    };
-    let d = CustomSmartPointer {
-        data: String::from("other stuff"),
-    };
-    println!("CustomSmartPointers created.");
-}
-

Приложение 15-14: Устройства CustomSmartPointer, выполняющая особенность Drop, куда мы поместим наш код очистки

-

Особенность Drop включён в прелюдию, поэтому нам не нужно вводить его в область видимости. Мы выполняем особенность Drop для CustomSmartPointer и выполняем способ drop, который будет вызывать println!. Тело функции drop - это место, где должна располагаться вся логика, которую вы захотите выполнять, когда образец вашего вида выйдет из области видимости. Мы печатаем здесь текст, чтобы наглядно отобразить, когда Ржавчина вызовет drop.

-

В main мы создаём два образца CustomSmartPointer и затем печатаем CustomSmartPointers created . В конце main наши образцы CustomSmartPointer выйдут из области видимости и Ржавчина вызовет код, который мы добавили в способ drop, который и напечатает наше окончательное сообщение. Обратите внимание, что нам не нужно вызывать способ drop явно.

-

Когда мы запустим эту программу, мы увидим следующий вывод:

-
$ cargo run
-   Compiling drop-example v0.1.0 (file:///projects/drop-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s
-     Running `target/debug/drop-example`
-CustomSmartPointers created.
-Dropping CustomSmartPointer with data `other stuff`!
-Dropping CustomSmartPointer with data `my stuff`!
-
-

Rust самостоятельно вызывал drop в мгновение выхода наших образцов из области видимости, тем самым выполнив заданный нами код. Переменные удаляются в обратном порядке их создания, поэтому d была удалена до c. Цель этого примера — дать вам наглядное представление о том, как работает способ drop; в типичных случаях вы будете задавать код очистки, который должен выполнить ваш вид, а не печатать сообщение.

-

Раннее удаление значения с помощью std::mem::drop

-

К сожалению, отключение функции самостоятельного удаления с помощью drop является не простым. Отключение drop обычно не требуется; весь смысл особенности Drop в том, чтобы о функции позаботились самостоятельно . Иногда, однако, вы можете захотеть очистить значение рано. Одним из примеров является использование умных указателей, которые управляют блокировками: вы могли бы потребовать принудительный вызов способа drop который снимает блокировку, чтобы другой код в той же области видимости мог получить блокировку. Ржавчина не позволяет вызвать способ особенности Drop вручную; вместо этого вы должны вызвать функцию std::mem::drop предоставляемую встроенной библиотекой, если хотите принудительно удалить значение до конца области видимости.

-

Если попытаться вызвать способ drop особенности Drop вручную, изменяя функцию main приложения 15-14 так, как показано в приложении 15-15, мы получим ошибку сборщика:

-

Файл: src/main.rs

-
struct CustomSmartPointer {
-    data: String,
-}
-
-impl Drop for CustomSmartPointer {
-    fn drop(&mut self) {
-        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
-    }
-}
-
-fn main() {
-    let c = CustomSmartPointer {
-        data: String::from("some data"),
-    };
-    println!("CustomSmartPointer created.");
-    c.drop();
-    println!("CustomSmartPointer dropped before the end of main.");
-}
-

Приложение 15-15: Попытка вызвать способ drop из особенности Drop вручную для досрочной очистки

-

Когда мы попытаемся собрать этот код, мы получим ошибку:

-
$ cargo run
-   Compiling drop-example v0.1.0 (file:///projects/drop-example)
-error[E0040]: explicit use of destructor method
-  --> src/main.rs:16:7
-   |
-16 |     c.drop();
-   |       ^^^^ explicit destructor calls not allowed
-   |
-help: consider using `drop` function
-   |
-16 |     drop(c);
-   |     +++++ ~
-
-For more information about this error, try `rustc --explain E0040`.
-error: could not compile `drop-example` (bin "drop-example") due to 1 previous error
-
-

Это сообщение об ошибке говорит, что мы не можем явно вызывать drop. В сообщении об ошибке используется понятие деструктор (destructor), который является общим понятием программирования для функции, которая очищает образец. Деструктор подобен строителю, который создаёт образец. Функция drop в Ржавчина является определённым деструктором.

-

Rust не позволяет обращаться к drop напрямую, потому что он все равно самостоятельно вызовет drop в конце main. Это вызвало бы ошибку double free, потому что в этом случае Ржавчина попытался бы дважды очистить одно и то же значение.

-

Невозможно отключить самостоятельную подстановку вызова drop, когда значение выходит из области видимости, и нельзя вызвать способ drop напрямую. Поэтому, если нам нужно принудительно избавиться от значения раньше времени, следует использовать функцию std::mem::drop.

-

Функция std::mem::drop отличается от способа drop особенности Drop. Мы вызываем её, передавая в качестве переменной значение, которое хотим принудительно уничтожить. Функция находится в прелюдии, поэтому мы можем изменить main в приложении 15-15 так, чтобы вызвать функцию drop, как показано в приложении 15-16:

-

Файл: src/main.rs

-
struct CustomSmartPointer {
-    data: String,
-}
-
-impl Drop for CustomSmartPointer {
-    fn drop(&mut self) {
-        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
-    }
-}
-
-fn main() {
-    let c = CustomSmartPointer {
-        data: String::from("some data"),
-    };
-    println!("CustomSmartPointer created.");
-    drop(c);
-    println!("CustomSmartPointer dropped before the end of main.");
-}
-

Приложение 15-16: Вызов std::mem::drop для принудительного удаления значения до того, как оно выйдет из области видимости

-

Выполнение данного кода выведет следующий итог::

-
$ cargo run
-   Compiling drop-example v0.1.0 (file:///projects/drop-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
-     Running `target/debug/drop-example`
-CustomSmartPointer created.
-Dropping CustomSmartPointer with data `some data`!
-CustomSmartPointer dropped before the end of main.
-
-

Текст Dropping CustomSmartPointer with data some data!, напечатанный между CustomSmartPointer created. и текстом CustomSmartPointer dropped before the end of main., показывает, что код способа drop вызывается для удаления c в этой точке.

-

Вы можете использовать код, указанный в выполнения особенности Drop, чтобы сделать очистку удобной и безопасной: например, вы можете использовать её для создания своего собственного управленца памяти! С помощью особенности Drop и системы владения Ржавчина не нужно целенаправленно заботиться о том, чтобы освобождать ресурсы, потому что Ржавчина делает это самостоятельно .

-

Также не нужно беспокоиться о неполадках, возникающих в итоге случайной очистки значений, которые всё ещё используются: система владения, которая заверяет, что ссылки всегда действительны, также заверяет, что drop вызывается только один раз, когда значение больше не используется.

-

После того, как мы познакомились с Box<T> и свойствами умных указателей, познакомимся с другими умными указателями, определёнными в встроенной библиотеке.

-

Rc<T>, умный указатель с подсчётом ссылок

-

В большинстве случаев владение является однозначным: вы точно знаете, какая переменная владеет данным значением. Однако бывают случаи, когда у одного значения может быть несколько владельцев. Например, в Графовых устройствах может быть несколько рёбер, указывающих на один и тот же узел — таким образом, этот узел становится в действительности собственностью всех этих рёбер. Узел не подлежит удалению, за исключением тех случаев, когда на него не указывает ни одно ребро и, соответственно, у него нет владельцев.

-

Вы должны включить множественное владение явно, используя вид Ржавчина Rc<T>, который является аббревиатурой для подсчёта ссылок. Вид Rc<T> отслеживает количество ссылок на значение, чтобы определить, используется ли оно ещё. Если ссылок на значение нет, значение может быть очищено и при этом ни одна ссылка не станет недействительной.

-

Представьте себе Rc<T> как телевизор в гостиной. Когда один человек входит, чтобы смотреть телевизор, он включает его. Другие могут войти в комнату и посмотреть телевизор. Когда последний человек покидает комнату, он выключает телевизор, потому что он больше не используется. Если кто-то выключит телевизор во время его просмотра другими, то оставшиеся телезрители устроят шум!

-

Вид Rc<T> используется, когда мы хотим разместить в куче некоторые данные для чтения несколькими частями нашей программы и не можем определить во время сборки, какая из частей завершит использование данных последней. Если бы мы знали, какая часть завершит использование последней то, мы могли бы сделать эту часть владельцем данных и вступили бы в силу обычные правила владения, применяемые во время сборки.

-

Обратите внимание, что Rc<T> используется только в однопоточных сценариях. Когда мы обсудим состязательность в главе 16, мы рассмотрим, как выполнять подсчёт ссылок во многопоточных программах.

-

Использование Rc<T> для совместного использования данных

-

Давайте вернёмся к нашему примеру с cons списком в приложении 15-5. Напомним, что мы определили его с помощью вида Box<T>. В этот раз мы создадим два списка, оба из которых будут владеть третьим списком. Мировозренческо это похоже на рисунок 15-3:

- Two lists that share ownership of a third list -

Рисунок 15-3: Два списка, b и c, делят владение над третьим списком, a

-

Мы создадим список a, содержащий 5 и затем 10. Затем мы создадим ещё два списка: b начинающийся с 3 и c начинающийся с 4. Оба списка b и c затем продолжать первый список a, содержащий 5 и 10. Другими словами, оба списка будут разделять первый список, содержащий 5 и 10.

-

Попытка выполнить этот сценарий, используя определение List с видом Box<T> не будет работать, как показано в приложении 15-17:

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, Box<List>),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-
-fn main() {
-    let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
-    let b = Cons(3, Box::new(a));
-    let c = Cons(4, Box::new(a));
-}
-

Приложение 15-17: Отображение того, что нельзя иметь два списка, использующих Box<T>, которые пытаются совместно владеть третьим списком

-

При сборки этого кода, мы получаем эту ошибку:

-
$ cargo run
-   Compiling cons-list v0.1.0 (file:///projects/cons-list)
-error[E0382]: use of moved value: `a`
-  --> src/main.rs:11:30
-   |
-9  |     let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
-   |         - move occurs because `a` has type `List`, which does not implement the `Copy` trait
-10 |     let b = Cons(3, Box::new(a));
-   |                              - value moved here
-11 |     let c = Cons(4, Box::new(a));
-   |                              ^ value used here after move
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `cons-list` (bin "cons-list") due to 1 previous error
-
-

Исходы Cons владеют данными, которые они содержат, поэтому, когда мы создаём список b, то a перемещается в b, а b становится владельцем a. Затем, мы пытаемся использовать a снова при создании c, но нам не разрешают, потому что a был перемещён.

-

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

-

Вместо этого мы изменим наше определение вида List так, чтобы использовать Rc<T> вместо Box<T>, как показано в приложении 15-18. Каждый исход Cons теперь будет содержать значение и вид Rc<T>, указывающий на List. Когда мы создадим b то, вместо того чтобы стал владельцем a, мы будем клонировать Rc<List> который содержит a, тем самым увеличивая количество ссылок с единицы до двойки и позволяя переменным a и b разделять владение на данные в виде Rc<List>. Мы также клонируем a при создании c, увеличивая количество ссылок с двух до трёх. Каждый раз, когда мы вызываем Rc::clone, счётчик ссылок на данные внутри Rc<List> будет увеличиваться и данные не будут очищены, если на них нет нулевых ссылок.

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, Rc<List>),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-use std::rc::Rc;
-
-fn main() {
-    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
-    let b = Cons(3, Rc::clone(&a));
-    let c = Cons(4, Rc::clone(&a));
-}
-

Приложение 15-18: Определение List, использующее Rc<T>

-

Нам нужно добавить указанию use, чтобы подключить вид Rc<T> в область видимости, потому что он не входит в список самостоятельного подключения прелюдии. В main, мы создаём список владеющий 5 и 10, сохраняем его в новом Rc<List> переменной a. Затем при создании b и c, мы называем функцию Rc::clone и передаём ей ссылку на Rc<List> как переменная a.

-

Мы могли бы вызвать a.clone(), а не Rc::clone(&a), но в Ржавчина принято использовать Rc::clone в таком случае. Внутренняя выполнение Rc::clone не делает глубокого повторения всех данных, как это происходит в видах большинства выполнений clone. Вызов Rc::clone только увеличивает счётчик ссылок, что не занимает много времени. Глубокое повторение данных может занимать много времени. Используя Rc::clone для подсчёта ссылок, можно визуально различать виды клонирования с глубоким повторением и клонирования, которые увеличивают количество ссылок. При поиске в коде неполадок с производительностью нужно рассмотреть только клонирование с глубоким повторением и пренебрегать вызовы Rc::clone .

-

Клонирование Rc<T> увеличивает количество ссылок

-

Давайте изменим рабочий пример в приложении 15-18, чтобы увидеть как изменяется число ссылок при создании и удалении ссылок на Rc<List> внутри переменной a.

-

В приложении 15-19 мы изменим main так, чтобы она имела внутреннюю область видимости вокруг списка c; тогда мы сможем увидеть, как меняется счётчик ссылок при выходе c из внутренней области видимости.

-

Файл: src/main.rs

-
enum List {
-    Cons(i32, Rc<List>),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-use std::rc::Rc;
-
-fn main() {
-    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
-    println!("count after creating a = {}", Rc::strong_count(&a));
-    let b = Cons(3, Rc::clone(&a));
-    println!("count after creating b = {}", Rc::strong_count(&a));
-    {
-        let c = Cons(4, Rc::clone(&a));
-        println!("count after creating c = {}", Rc::strong_count(&a));
-    }
-    println!("count after c goes out of scope = {}", Rc::strong_count(&a));
-}
-

Приложение 15-19: Печать количества ссылок

-

В каждой части программы, где количество ссылок меняется, мы выводим количество ссылок, которое получаем, вызывая функцию Rc::strong_count. Эта функция названа strong_count, а не count, потому что вид Rc<T> также имеет weak_count; мы увидим, для чего используется weak_count в разделе "Предотвращение замкнутых ссылок: Превращение Rc<T> в Weak<T>".

-

Код выводит в окно вывода:

-
$ cargo run
-   Compiling cons-list v0.1.0 (file:///projects/cons-list)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.45s
-     Running `target/debug/cons-list`
-count after creating a = 1
-count after creating b = 2
-count after creating c = 3
-count after c goes out of scope = 2
-
-

Можно увидеть, что Rc<List> в переменной a имеет начальный счётчик ссылок равный 1; затем каждый раз при вызове clone счётчик увеличивается на 1. Когда c выходит из области видимости, счётчик уменьшается на 1. Нам не нужно вызывать функцию уменьшения счётчика ссылок, как при вызове Rc::clone для увеличения счётчика ссылок: выполнение Drop самостоятельно уменьшает счётчик ссылок, когда значение Rc<T> выходит из области видимости.

-

В этом примере мы не наблюдаем того, что когда b, а затем a выходят из области видимости в конце main, счётчик становится равным 0, и Rc<List> полностью очищается. Использование Rc<T> позволяет одному значению иметь несколько владельцев, а счётчик заверяет, что значение остаётся действительным до тех пор, пока любой из владельцев ещё существует.

-

С помощью неизменяемых ссылок, вид Rc<T> позволяет обмениваться данными между несколькими частями вашей программы только для чтения данных. Если вид Rc<T> позволял бы иметь несколько изменяемых ссылок, вы могли бы нарушить одно из правил заимствования, описанных в главе 4: множественные изменяемые заимствования в одном и том же месте могут вызвать гонки данных (data races) и несогласованность данных. Но возможность изменять данные очень полезна! В следующем разделе мы обсудим образец внутренней изменчивости и вид RefCell<T>, который можно использовать вместе с Rc<T> для работы с этим ограничением.

-

RefCell<T> и образец внутренней изменяемости

-

Внутренняя изменяемость - это образец разработки Rust, который позволяет вам изменять данные даже при наличии неизменяемых ссылок на эти данные; обычно такое действие запрещено правилами заимствования. Для изменения данных образец использует unsafe код внутри устройства данных, чтобы обойти обычные правила Rust, управляющие изменяемость и заимствование. Небезопасный (unsafe) код даёт понять сборщику, что мы самостоятельно следим за соблюдением этих правил, а не полагаемся на то, что сборщик будет делать это для нас; подробнее о небезопасном коде мы поговорим в главе 19.

-

Мы можем использовать виды, в которых применяется образец внутренней изменяемости, только если мы можем обеспечить, что правила заимствования будут соблюдаться во время выполнения, несмотря на то, что сборщик не сможет этого обеспечить. В этом случае небезопасный код оборачивается безопасным API, и внешне вид остаётся неизменяемым.

-

Давайте изучим данную подход с помощью вида данных RefCell<T>, который выполняет этот образец.

-

Применение правил заимствования во время выполнения с помощью RefCell<T>

-

В отличие от Rc<T> вид RefCell<T> предоставляет единоличное владение данными, которые он содержит. В чем же отличие вида RefCell<T> от Box<T>? Давайте вспомним правила заимствования из Главы 4:

-
    -
  • В любой мгновение времени вы можете иметь либо одну изменяемую ссылку либо сколько угодно неизменяемых ссылок (но не оба вида ссылок одновременно).
  • -
  • Ссылки всегда должны быть действительными.
  • -
-

С помощью ссылок и вида Box<T> неизменные величины правил заимствования применяются на этапе сборки. С помощью RefCell<T> они применяются во время работы программы. Если вы нарушите эти правила, работая с ссылками, то будет ошибка сборки. Если вы работаете с RefCell<T> и нарушите эти правила, то программа вызовет панику и завершится.

-

Преимущества проверки правил заимствования во время сборки заключаются в том, что ошибки будут обнаруживаться раньше - ещё в этапе разработки, а производительность во время выполнения не пострадает, поскольку весь анализ завершён заранее. По этим причинам проверка правил заимствования во время сборки является лучшим выбором в большинстве случаев, и именно поэтому она используется в Ржавчина по умолчанию.

-

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

-

Поскольку некоторый анализ невозможен, то если сборщик Ржавчина не может быть уверен, что код соответствует правилам владения, он может отклонить правильную программу; таким образом он является консервативным. Если Ржавчина принял неправильную программу, то пользователи не смогут доверять заверениям, которые даёт Rust. Однако, если Ржавчина отклонит правильную программу, то программист будет испытывать неудобства, но ничего катастрофического не произойдёт. Вид RefCell<T> полезен, когда вы уверены, что ваш код соответствует правилам заимствования, но сборщик не может понять и обеспечить этого.

-

Подобно виду Rc<T>, вид RefCell<T> предназначен только для использования в однопоточных сценариях и выдаст ошибку времени сборки, если вы попытаетесь использовать его в многопоточном среде. Мы поговорим о том, как получить возможность RefCell<T> во многопоточной программе в главе 16.

-

Вот список причин выбора видов Box<T>, Rc<T> или RefCell<T>:

-
    -
  • Вид Rc<T> разрешает множественное владение одними и теми же данными; виды Box<T> и RefCell<T> разрешают иметь единственных владельцев.
  • -
  • Вид Box<T> разрешает неизменяемые или изменяемые владения, проверенные при сборки; вид Rc<T> разрешает только неизменяемые владения, проверенные при сборки; вид RefCell<T> разрешает неизменяемые или изменяемые владения, проверенные во время выполнения.
  • -
  • Поскольку RefCell<T> разрешает изменяемые заимствования, проверенные во время выполнения, можно изменять значение внутри RefCell<T> даже если RefCell<T> является неизменным.
  • -
-

Изменение значения внутри неизменного значения является образцом внутренней изменяемости (interior mutability). Давайте посмотрим на случай, в которой внутренняя изменяемость полезна и рассмотрим, как это возможно.

-

Внутренняя изменяемость: изменяемое заимствование неизменяемого значения

-

Следствием правил заимствования является то, что когда у вас есть неизменяемое значение, вы не можете заимствовать его с изменением. Например, этот код не будет собираться:

-
fn main() {
-    let x = 5;
-    let y = &mut x;
-}
-

Если вы попытаетесь собрать этот код, вы получите следующую ошибку:

-
$ cargo run
-   Compiling borrowing v0.1.0 (file:///projects/borrowing)
-error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
- --> src/main.rs:3:13
-  |
-3 |     let y = &mut x;
-  |             ^^^^^^ cannot borrow as mutable
-  |
-help: consider changing this to be mutable
-  |
-2 |     let mut x = 5;
-  |         +++
-
-For more information about this error, try `rustc --explain E0596`.
-error: could not compile `borrowing` (bin "borrowing") due to 1 previous error
-
-

Однако бывают случаи, в которых было бы полезно, чтобы предмет мог изменять себя при помощи своих способов, но казался неизменным для прочего кода. Код вне способов этого предмета не должен иметь возможности изменять его содержимое. Использование RefCell<T> - один из способов получить возможность внутренней изменяемости, но при этом RefCell<T> не позволяет полностью обойти правила заимствования: средство проверки правил заимствования в сборщике позволяет эту внутреннюю изменяемость, однако правила заимствования проверяются во время выполнения. Если вы нарушите правила, то вместо ошибки сборки вы получите panic!.

-

Давайте разберём опытный пример, в котором мы можем использовать RefCell<T> для изменения неизменяемого значения и посмотрим, почему это полезно.

-

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

-

Иногда во время проверки программист использует один вид вместо другого для того, чтобы проверить определённое поведение и убедиться, что оно выполнено правильно. Такой вид-заместитель называется проверочным повторителем. Воспринимайте его как «каскадёра» в кинематографе, когда повторитель заменяет актёра для выполнения определённой сложной сцены. Проверочные повторители заменяют другие виды при выполнении проверок. Инсценировочные (mock) предметы — это особый вид проверочных повторителей, которые сохраняют данные происходящих во время проверки действий тем самым позволяя вам убедиться впоследствии, что все действия были выполнены правильно.

-

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

-

Вот сценарий, который мы будем проверять: мы создадим библиотеку, которая отслеживает значение по отношению к заранее определённому наивысшему значению и отправляет сообщения в зависимости от того, насколько текущее значение находится близко к такому наивысшему значению. Эта библиотека может использоваться, например, для отслеживания квоты количества вызовов API пользователя, которые ему разрешено делать.

-

Наша библиотека будет предоставлять только функции отслеживания того, насколько близко к наивысшему значению находится значение и какие сообщения должны быть внутри в этот мгновение. Ожидается, что приложения, использующие нашу библиотеку, предоставят рычаг для отправки сообщений: приложение может поместить сообщение в приложение, отправить электронное письмо, отправить текстовое сообщение или что-то ещё. Библиотеке не нужно знать эту подробность. Все что ему нужно - это что-то, что выполняет особенность, который мы предоставим с названием Messenger. Приложение 15-20 показывает код библиотеки:

-

Файл: src/lib.rs

-
pub trait Messenger {
-    fn send(&self, msg: &str);
-}
-
-pub struct LimitTracker<'a, T: Messenger> {
-    messenger: &'a T,
-    value: usize,
-    max: usize,
-}
-
-impl<'a, T> LimitTracker<'a, T>
-where
-    T: Messenger,
-{
-    pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
-        LimitTracker {
-            messenger,
-            value: 0,
-            max,
-        }
-    }
-
-    pub fn set_value(&mut self, value: usize) {
-        self.value = value;
-
-        let percentage_of_max = self.value as f64 / self.max as f64;
-
-        if percentage_of_max >= 1.0 {
-            self.messenger.send("Error: You are over your quota!");
-        } else if percentage_of_max >= 0.9 {
-            self.messenger
-                .send("Urgent warning: You've used up over 90% of your quota!");
-        } else if percentage_of_max >= 0.75 {
-            self.messenger
-                .send("Warning: You've used up over 75% of your quota!");
-        }
-    }
-}
-

Приложение 15-20: Библиотека для отслеживания степени приближения того или иного значения к наиболее допустимой величине и предупреждения, в случае если значение достигает определённого уровня

-

Одна важная часть этого кода состоит в том, что особенность Messenger имеет один способ send, принимающий переменнойми неизменяемую ссылку на self и текст сообщения. Он является внешней оболочкой, который должен иметь наш мок предмет. Другой важной частью является то, что мы хотим проверить поведение способа set_value у вида LimitTracker. Мы можем изменить значение, которое передаём свойствоом value, но set_value ничего не возвращает и нет основания, чтобы мы могли бы проверить утверждения о выполнении способа. Мы хотим иметь возможность сказать, что если мы создаём LimitTracker с чем-то, что выполняет особенность Messenger и с определённым значением для max, то когда мы передаём разные числа в переменной value образец self.messenger отправляет соответствующие сообщения.

-

Нам нужен мок предмет, который вместо отправки электронного письма или текстового сообщения будет отслеживать сообщения, которые были ему поручены для отправки через send. Мы можем создать новый образец мок предмета. создать LimitTracker с использованием мок предмет для него, вызвать способ set_value у образца LimitTracker, а затем проверить, что мок предмет имеет ожидаемое сообщение. В приложении 15-21 показана попытка выполнить мок предмет, чтобы сделать именно то что хотим, но анализатор заимствований не разрешит такой код:

-

Файл: src/lib.rs

-
pub trait Messenger {
-    fn send(&self, msg: &str);
-}
-
-pub struct LimitTracker<'a, T: Messenger> {
-    messenger: &'a T,
-    value: usize,
-    max: usize,
-}
-
-impl<'a, T> LimitTracker<'a, T>
-where
-    T: Messenger,
-{
-    pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
-        LimitTracker {
-            messenger,
-            value: 0,
-            max,
-        }
-    }
-
-    pub fn set_value(&mut self, value: usize) {
-        self.value = value;
-
-        let percentage_of_max = self.value as f64 / self.max as f64;
-
-        if percentage_of_max >= 1.0 {
-            self.messenger.send("Error: You are over your quota!");
-        } else if percentage_of_max >= 0.9 {
-            self.messenger
-                .send("Urgent warning: You've used up over 90% of your quota!");
-        } else if percentage_of_max >= 0.75 {
-            self.messenger
-                .send("Warning: You've used up over 75% of your quota!");
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    struct MockMessenger {
-        sent_messages: Vec<String>,
-    }
-
-    impl MockMessenger {
-        fn new() -> MockMessenger {
-            MockMessenger {
-                sent_messages: vec![],
-            }
-        }
-    }
-
-    impl Messenger for MockMessenger {
-        fn send(&self, message: &str) {
-            self.sent_messages.push(String::from(message));
-        }
-    }
-
-    #[test]
-    fn it_sends_an_over_75_percent_warning_message() {
-        let mock_messenger = MockMessenger::new();
-        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
-
-        limit_tracker.set_value(80);
-
-        assert_eq!(mock_messenger.sent_messages.len(), 1);
-    }
-}
-

Приложение 15-21: Попытка выполнить MockMessenger, которая не была принята рычагом проверки заимствований

-

Этот проверочный код определяет устройство MockMessenger, в которой есть поле sent_messages со значениями вида Vec из String для отслеживания сообщений, которые поручены устройстве для отправки. Мы также определяем сопряженную функцию new, чтобы было удобно создавать новые образцы MockMessenger, которые создаются с пустым списком сообщений. Затем мы выполняем особенность Messenger для вида MockMessenger, чтобы передать MockMessenger в LimitTracker. В ярлыке способа send мы принимаем сообщение для передачи в качестве свойства и сохраняем его в MockMessenger внутри списка sent_messages.

-

В этом проверке мы проверяем, что происходит, когда LimitTracker сказано установить value в значение, превышающее 75 процентов от значения max. Сначала мы создаём новый MockMessenger, который будет иметь пустой список сообщений. Затем мы создаём новый LimitTracker и передаём ему ссылку на новый MockMessenger и max значение равное 100. Мы вызываем способ set_value у LimitTracker со значением 80, что составляет более 75 процентов от 100. Затем мы с помощью утверждения проверяем, что MockMessenger должен содержать одно сообщение из списка внутренних сообщений.

-

Однако с этим проверкой есть одна неполадка, показанная ниже:

-
$ cargo test
-   Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
-error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference
-  --> src/lib.rs:58:13
-   |
-58 |             self.sent_messages.push(String::from(message));
-   |             ^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
-   |
-help: consider changing this to be a mutable reference in the `impl` method and the `trait` definition
-   |
-2  ~     fn send(&mut self, msg: &str);
-3  | }
- ...
-56 |     impl Messenger for MockMessenger {
-57 ~         fn send(&mut self, message: &str) {
-   |
-
-For more information about this error, try `rustc --explain E0596`.
-error: could not compile `limit-tracker` (lib test) due to 1 previous error
-
-

Мы не можем изменять MockMessenger для отслеживания сообщений, потому что способ send принимает неизменяемую ссылку на self. Мы также не можем принять предложение из текста ошибки, чтобы использовать &mut self, потому что тогда ярлык send не будет соответствовать ярлыке в определении особенности Messenger (не стесняйтесь попробовать и посмотреть, какое сообщение об ошибке получите вы).

-

Это случаей, в которой внутренняя изменяемость может помочь! Мы сохраним sent_messages внутри вида RefCell<T>, а затем в способе send сообщение сможет изменить список sent_messages для хранения сообщений, которые мы видели. Приложение 15-22 показывает, как это выглядит:

-

Файл: src/lib.rs

-
pub trait Messenger {
-    fn send(&self, msg: &str);
-}
-
-pub struct LimitTracker<'a, T: Messenger> {
-    messenger: &'a T,
-    value: usize,
-    max: usize,
-}
-
-impl<'a, T> LimitTracker<'a, T>
-where
-    T: Messenger,
-{
-    pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
-        LimitTracker {
-            messenger,
-            value: 0,
-            max,
-        }
-    }
-
-    pub fn set_value(&mut self, value: usize) {
-        self.value = value;
-
-        let percentage_of_max = self.value as f64 / self.max as f64;
-
-        if percentage_of_max >= 1.0 {
-            self.messenger.send("Error: You are over your quota!");
-        } else if percentage_of_max >= 0.9 {
-            self.messenger
-                .send("Urgent warning: You've used up over 90% of your quota!");
-        } else if percentage_of_max >= 0.75 {
-            self.messenger
-                .send("Warning: You've used up over 75% of your quota!");
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use std::cell::RefCell;
-
-    struct MockMessenger {
-        sent_messages: RefCell<Vec<String>>,
-    }
-
-    impl MockMessenger {
-        fn new() -> MockMessenger {
-            MockMessenger {
-                sent_messages: RefCell::new(vec![]),
-            }
-        }
-    }
-
-    impl Messenger for MockMessenger {
-        fn send(&self, message: &str) {
-            self.sent_messages.borrow_mut().push(String::from(message));
-        }
-    }
-
-    #[test]
-    fn it_sends_an_over_75_percent_warning_message() {
-        // --snip--
-        let mock_messenger = MockMessenger::new();
-        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
-
-        limit_tracker.set_value(80);
-
-        assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
-    }
-}
-

Приложение 15-22: Использование RefCell<T> для изменения внутреннего значения, в то время как внешнее значение считается неизменяемым

-

Поле sent_messages теперь имеет вид RefCell<Vec<String>> вместо Vec<String>. В функции new мы создаём новый образец RefCell<Vec<String>> для пустого вектора.

-

Для выполнения способа send первый свойство по-прежнему является неизменяемым для заимствования self, которое соответствует определению особенности. Мы вызываем borrow_mut для RefCell<Vec<String>> в self.sent_messages, чтобы получить изменяемую ссылку на значение внутри RefCell<Vec<String>>, которое является вектором. Затем мы можем вызвать push у изменяемой ссылки на вектор, чтобы отслеживать сообщения, отправленные во время проверки.

-

Последнее изменение, которое мы должны сделать, заключается в утверждении для проверки: чтобы увидеть, сколько элементов находится во внутреннем векторе, мы вызываем способ borrow у RefCell<Vec<String>>, чтобы получить неизменяемую ссылку на внутренний вектор сообщений.

-

Теперь, когда вы увидели как использовать RefCell<T>, давайте изучим как он работает!

-

Отслеживание заимствований во время выполнения с помощью RefCell<T>

-

При создании неизменных и изменяемых ссылок мы используем правила написания & и &mut соответственно. У вида RefCell<T>, мы используем способы borrow и borrow_mut, которые являются частью безопасного API, который принадлежит RefCell<T>. Способ borrow возвращает вид умного указателя Ref<T>, способ borrow_mut возвращает вид умного указателя RefMut<T>. Оба вида выполняют особенность Deref, поэтому мы можем рассматривать их как обычные ссылки.

-

Вид RefCell<T> отслеживает сколько умных указателей Ref<T> и RefMut<T> активны в данное время. Каждый раз, когда мы вызываем borrow, вид RefCell<T> увеличивает количество активных заимствований. Когда значение Ref<T> выходит из области видимости, то количество неизменяемых заимствований уменьшается на единицу. Как и с правилами заимствования во время сборки, RefCell<T> позволяет иметь много неизменяемых заимствований или одно изменяемое заимствование в любой мгновение времени.

-

Если попытаться нарушить эти правила, то вместо получения ошибки сборщика, как это было бы со ссылками, выполнение RefCell<T> будет вызывать панику во время выполнения. В приложении 15-23 показана изменение выполнения send из приложения 15-22. Мы намеренно пытаемся создать два изменяемых заимствования активных для одной и той же области видимости, чтобы показать как RefCell<T> не позволяет нам делать так во время выполнения.

-

Файл: src/lib.rs

-
pub trait Messenger {
-    fn send(&self, msg: &str);
-}
-
-pub struct LimitTracker<'a, T: Messenger> {
-    messenger: &'a T,
-    value: usize,
-    max: usize,
-}
-
-impl<'a, T> LimitTracker<'a, T>
-where
-    T: Messenger,
-{
-    pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
-        LimitTracker {
-            messenger,
-            value: 0,
-            max,
-        }
-    }
-
-    pub fn set_value(&mut self, value: usize) {
-        self.value = value;
-
-        let percentage_of_max = self.value as f64 / self.max as f64;
-
-        if percentage_of_max >= 1.0 {
-            self.messenger.send("Error: You are over your quota!");
-        } else if percentage_of_max >= 0.9 {
-            self.messenger
-                .send("Urgent warning: You've used up over 90% of your quota!");
-        } else if percentage_of_max >= 0.75 {
-            self.messenger
-                .send("Warning: You've used up over 75% of your quota!");
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use std::cell::RefCell;
-
-    struct MockMessenger {
-        sent_messages: RefCell<Vec<String>>,
-    }
-
-    impl MockMessenger {
-        fn new() -> MockMessenger {
-            MockMessenger {
-                sent_messages: RefCell::new(vec![]),
-            }
-        }
-    }
-
-    impl Messenger for MockMessenger {
-        fn send(&self, message: &str) {
-            let mut one_borrow = self.sent_messages.borrow_mut();
-            let mut two_borrow = self.sent_messages.borrow_mut();
-
-            one_borrow.push(String::from(message));
-            two_borrow.push(String::from(message));
-        }
-    }
-
-    #[test]
-    fn it_sends_an_over_75_percent_warning_message() {
-        let mock_messenger = MockMessenger::new();
-        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
-
-        limit_tracker.set_value(80);
-
-        assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
-    }
-}
-

Приложение 15-23: Создание двух изменяемых ссылок в одной области видимости, чтобы убедиться, что RefCell<T> вызовет панику

-

Мы создаём переменную one_borrow для умного указателя RefMut<T> возвращаемого из способа borrow_mut. Затем мы создаём другое изменяемое заимствование таким же образом в переменной two_borrow. Это создаёт две изменяемые ссылки в одной области видимости, что недопустимо. Когда мы запускаем проверки для нашей библиотеки, код в приложении 15-23 собирается без ошибок, но проверка завершится неудачно:

-
$ cargo test
-   Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
-    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s
-     Running unittests src/lib.rs (target/debug/deps/limit_tracker-e599811fa246dbde)
-
-running 1 test
-test tests::it_sends_an_over_75_percent_warning_message ... FAILED
-
-failures:
-
----- tests::it_sends_an_over_75_percent_warning_message stdout ----
-thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at src/lib.rs:60:53:
-already borrowed: BorrowMutError
-note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
-
-
-failures:
-    tests::it_sends_an_over_75_percent_warning_message
-
-test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-error: test failed, to rerun pass `--lib`
-
-

Обратите внимание, что код вызвал панику с сообщением already borrowed: BorrowMutError. Вот так вид RefCell<T> обрабатывает нарушения правил заимствования во время выполнения.

-

Решение отлавливать ошибки заимствования во время выполнения, а не во время сборки, как мы сделали здесь, означает, что вы возможно будете находить ошибки в своём коде на более поздних этапах разработки: возможно, не раньше, чем ваш код будет развернут в рабочем окружении. Кроме того, ваш код будет иметь небольшие потери производительности в этапе работы, поскольку заимствования будут отслеживаться во время выполнения, а не во время сборки. Однако использование RefCell<T> позволяет написать предмет-имитатор, который способен изменять себя, чтобы сохранять сведения о тех значениях, которые он получал, пока вы использовали его в среде, где разрешены только неизменяемые значения. Вы можете использовать RefCell<T>, несмотря на его недостатки, чтобы получить больше возможности, чем дают обычные ссылки.

-

Наличие нескольких владельцев изменяемых данных путём объединения видов Rc<T> и RefCell<T>

-

Обычный способ использования RefCell<T> заключается в его сочетании с видом Rc<T>. Напомним, что вид Rc<T> позволяет иметь нескольких владельцев некоторых данных, но даёт только неизменяемый доступ к этим данным. Если у вас есть Rc<T>, который внутри содержит вид RefCell<T>, вы можете получить значение, которое может иметь несколько владельцев и которое можно изменять!

-

Например, вспомните пример cons списка приложения 15-18, где мы использовали Rc<T>, чтобы несколько списков могли совместно владеть другим списком. Поскольку Rc<T> содержит только неизменяемые значения, мы не можем изменить ни одно из значений в списке после того, как мы их создали. Давайте добавим вид RefCell<T>, чтобы получить возможность изменять значения в списках. В приложении 15-24 показано использование RefCell<T> в определении Cons так, что мы можем изменить значение хранящееся во всех списках:

-

Файл: src/main.rs

-
#[derive(Debug)]
-enum List {
-    Cons(Rc<RefCell<i32>>, Rc<List>),
-    Nil,
-}
-
-use crate::List::{Cons, Nil};
-use std::cell::RefCell;
-use std::rc::Rc;
-
-fn main() {
-    let value = Rc::new(RefCell::new(5));
-
-    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
-
-    let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
-    let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));
-
-    *value.borrow_mut() += 10;
-
-    println!("a after = {a:?}");
-    println!("b after = {b:?}");
-    println!("c after = {c:?}");
-}
-

Приложение 15-24: Использование Rc<RefCell<i32>> для создания List, который мы можем изменять

-

Мы создаём значение, которое является образцом Rc<RefCell<i32>> и сохраняем его в переменной с именем value, чтобы получить к ней прямой доступ позже. Затем мы создаём List в переменной a с исходом Cons, который содержит value. Нам нужно вызвать клонирование value, так как обе переменные a и value владеют внутренним значением 5, а не передают владение из value в переменную a или не выполняют заимствование с помощью a переменной value.

-

Мы оборачиваем список у переменной a в вид Rc<T>, поэтому при создании списков в переменные b и c они оба могут ссылаться на a, что мы и сделали в приложении 15-18.

-

После создания списков a, b и c мы хотим добавить 10 к значению в value. Для этого вызовем borrow_mut у value, который использует функцию самостоятельного разыменования, о которой мы говорили в главе 5 (см. раздел "Где находится оператор ->?") во внутреннее значение RefCell<T>. Способ borrow_mut возвращает умный указатель RefMut<T>, и мы используя оператор разыменования, изменяем внутреннее значение.

-

Когда мы печатаем a, b и c то видим, что все они имеют изменённое значение равное 15, а не 5:

-
$ cargo run
-   Compiling cons-list v0.1.0 (file:///projects/cons-list)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.63s
-     Running `target/debug/cons-list`
-a after = Cons(RefCell { value: 15 }, Nil)
-b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))
-c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))
-
-

Эта техника довольно изящна! Используя RefCell<T>, мы получаем внешне неизменяемое значение List. Но мы можем использовать способы RefCell<T>, которые предоставляют доступ к его внутренностям, чтобы мы могли изменять наши данные, когда это необходимо. Проверка правил заимствования во время выполнения защищает нас от гонок данных, и иногда стоит немного пожертвовать производительностью ради такой гибкости наших устройств данных. Обратите внимание, что RefCell<T> не работает для многопоточного кода! Mutex<T> - это thread-safe исполнение RefCell<T>, а Mutex<T> мы обсудим в главе 16.

-

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

-

Заверения безопасности памяти в Ржавчина затрудняют, но не делают невозможным случайное выделение памяти, которое никогда не очищается (известное как утечка памяти ). Полное предотвращение утечек памяти не является одной из заверений Rust, а это означает, что утечки памяти безопасны в Rust. Мы видим, что Ржавчина допускает утечку памяти с помощью Rc<T> и RefCell<T>: можно создавать ссылки, в которых элементы ссылаются друг на друга в цикле. Это создаёт утечки памяти, потому что счётчик ссылок каждого элемента в цикле никогда не достигнет 0, а значения никогда не будут удалены.

-

Создание ссылочного замыкания

-

Давайте посмотрим, как может произойти случаей ссылочного замыкания и как её предотвратить, начиная с определения перечисления List и способа tail в приложении 15-25:

-

Файл: src/main.rs

-
use crate::List::{Cons, Nil};
-use std::cell::RefCell;
-use std::rc::Rc;
-
-#[derive(Debug)]
-enum List {
-    Cons(i32, RefCell<Rc<List>>),
-    Nil,
-}
-
-impl List {
-    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
-        match self {
-            Cons(_, item) => Some(item),
-            Nil => None,
-        }
-    }
-}
-
-fn main() {}
-

Приложение 15-25: Объявление cons list, который содержит RefCell<T>, чтобы мы могли изменять то, на что ссылается образец Cons

-

Мы используем другую вариацию определения List из приложения 15-5. Второй элемент в исходе Cons теперь RefCell<Rc<List>>, что означает, что вместо возможности менять значение i32, как мы делали в приложении 15-24, мы хотим менять значение List, на которое указывает исход Cons. Мы также добавляем способ tail, чтобы нам было удобно обращаться ко второму элементу, если у нас есть исход Cons.

-

В приложении 15-26 мы добавляем main функцию, которая использует определения приложения 15-25. Этот код создаёт список в переменной a и список b, который указывает на список a. Затем он изменяет список внутри a так, чтобы он указывал на b, создавая ссылочное замыкание. В коде есть указания println!, чтобы показать значения счётчиков ссылок в различных точках этого этапа.

-

Файл: src/main.rs

-
use crate::List::{Cons, Nil};
-use std::cell::RefCell;
-use std::rc::Rc;
-
-#[derive(Debug)]
-enum List {
-    Cons(i32, RefCell<Rc<List>>),
-    Nil,
-}
-
-impl List {
-    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
-        match self {
-            Cons(_, item) => Some(item),
-            Nil => None,
-        }
-    }
-}
-
-fn main() {
-    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
-
-    println!("a initial rc count = {}", Rc::strong_count(&a));
-    println!("a next item = {:?}", a.tail());
-
-    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
-
-    println!("a rc count after b creation = {}", Rc::strong_count(&a));
-    println!("b initial rc count = {}", Rc::strong_count(&b));
-    println!("b next item = {:?}", b.tail());
-
-    if let Some(link) = a.tail() {
-        *link.borrow_mut() = Rc::clone(&b);
-    }
-
-    println!("b rc count after changing a = {}", Rc::strong_count(&b));
-    println!("a rc count after changing a = {}", Rc::strong_count(&a));
-
-    // Uncomment the next line to see that we have a cycle;
-    // it will overflow the stack
-    // println!("a next item = {:?}", a.tail());
-}
-

Приложение 15-26: Создание ссылочного цикла из двух значений List, указывающих друг на друга

-

Мы создаём образец Rc<List> содержащий значение List в переменной a с начальным списком 5, Nil. Затем мы создаём образец Rc<List> содержащий другое значение List в переменной b, которое содержит значение 10 и указывает на список в a.

-

Мы меняем a так, чтобы он указывал на b вместо Nil, создавая зацикленность. Мы делаем это с помощью способа tail, чтобы получить ссылку на RefCell<Rc<List>> из переменной a, которую мы помещаем в переменную link. Затем мы используем способ borrow_mut из вида RefCell<Rc<List>>, чтобы изменить внутреннее значение вида Rc<List>, содержащего начальное значение Nil на значение вида Rc<List> взятое из переменной b.

-

Когда мы запускаем этот код, оставив последний println! с примечаниями в данный мгновение, мы получим вывод:

-
$ cargo run
-   Compiling cons-list v0.1.0 (file:///projects/cons-list)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.53s
-     Running `target/debug/cons-list`
-a initial rc count = 1
-a next item = Some(RefCell { value: Nil })
-a rc count after b creation = 2
-b initial rc count = 1
-b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
-b rc count after changing a = 2
-a rc count after changing a = 2
-
-

Количество ссылок на образцы Rc<List> как в a, так и в b равно 2 после того, как мы заменили список в a на ссылку на b. В конце main Ржавчина уничтожает переменную b, что уменьшает количество ссылок на Rc<List> из b с 2 до 1. Память, которую Rc<List> занимает в куче, не будет освобождена в этот мгновение, потому что количество ссылок на неё равно 1, а не 0. Затем Ржавчина удаляет a, что уменьшает количество ссылок образца Rc<List> в a с 2 до 1. Память этого образца также не может быть освобождена, поскольку другой образец Rc<List> по-прежнему ссылается на него. Таким образом, память, выделенная для списка не будет освобождена никогда. Чтобы наглядно представить этот цикл ссылок, мы создали диаграмму на рисунке 15-4.

-Reference cycle of lists -

Рисунок 15-4: Ссылочный цикл списков a и b, указывающих друг на друга

-

Если вы удалите последний примечание с println! и запустите программу, Ржавчина будет пытаться печатать зацикленность в a, указывающей на b, указывающей на a и так далее, пока не переполниться обойма.

-

По сравнению с существующей программой, последствия создания цикла ссылок в этом примере не так страшны: сразу после создания цикла ссылок программа завершается. Однако если более сложная программа выделит много памяти в цикле и будет удерживать её в течение длительного времени, программа будет потреблять больше памяти, чем ей нужно, и может перенапрячь систему, что приведёт к исчерпанию доступной памяти.

-

Вызвать образование ссылочной зацикленности не просто, но и не невозможно. Если у вас есть значения RefCell<T> которые содержат значения Rc<T> или подобные вложенные сочетания видов с внутренней изменчивостью и подсчётом ссылок, вы должны убедиться, что вы не создаёте зацикленность; Вы не можете полагаться на то, что Ржавчина их обнаружит. Создание ссылочной зацикленности являлось бы логической ошибкой в программе, для которой вы должны использовать самостоятельно е проверки, проверку кода и другие опытов разработки программного обеспечения для её уменьшения.

-

Другое решение для избежания ссылочной зацикленности - это ресоздание ваших устройств данных, чтобы некоторые ссылки выражали владение, а другие - отсутствие владения. В итоге можно иметь циклы, построенные на некоторых отношениях владения и некоторые не основанные на отношениях владения, тогда только отношения владения влияют на то, можно ли удалить значение. В приложении 15-25 мы всегда хотим, чтобы исходы Cons владели своим списком, поэтому ресоздание устройства данных невозможна. Давайте рассмотрим пример с использованием графов, состоящих из родительских и дочерних узлов, чтобы увидеть, когда отношения владения не являются подходящим способом предотвращения ссылочной зацикленности.

-

Предотвращение ссылочной зацикленности: замена умного указателя Rc<T> на Weak<T>

-

До сих пор мы выясняли, что вызов Rc::clone увеличивает strong_count образца Rc<T>, а образец Rc<T> удаляется, только если его strong_count равен 0. Вы также можете создать слабую ссылку на значение внутри образца Rc<T>, вызвав Rc::downgrade и передав ссылку на Rc<T>. Сильные ссылки - это то с помощью чего вы можете поделиться владением образца Rc<T>. Слабые ссылки не отражают связи владения, и их подсчёт не влияет на то, когда образец Rc<T> будет очищен. Они не приведут к ссылочному циклу, потому что любой цикл, включающий несколько слабых ссылок, будет разорван, как только количество сильных ссылок для задействованных значений станет равным 0.

-

Когда вы вызываете Rc::downgrade, вы получаете умный указатель вида Weak<T>. Вместо того чтобы увеличить strong_count в образце Rc<T> на 1, вызов Rc::downgrade увеличивает weak_count на 1. Вид Rc<T> использует weak_count для отслеживания количества существующих ссылок Weak<T>, подобно strong_count. Разница в том, что weak_count не должен быть равен 0, чтобы образец Rc<T> мог быть удалён.

-

Поскольку значение, на которое ссылается Weak<T> могло быть удалено, то необходимо убедиться, что это значение все ещё существует, чтобы сделать что-либо со значением на которое указывает Weak<T>. Делайте это вызывая способ upgrade у образца вида Weak<T>, который вернёт Option<Rc<T>>. Вы получите итог Some, если значение Rc<T> ещё не было удалено и итог None, если значение Rc<T> было удалено. Поскольку upgrade возвращает вид Option<T>, Ржавчина обеспечит обработку обоих случаев Some и None и не будет неправильного указателя.

-

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

-

Создание древовидной устройства данных: Node с дочерними узлами

-

Для начала мы построим дерево с узлами, которые знают о своих дочерних узлах. Мы создадим устройство с именем Node, которая будет содержать собственное значение i32, а также ссылки на его дочерние значения Node:

-

Файл: src/main.rs

-
use std::cell::RefCell;
-use std::rc::Rc;
-
-#[derive(Debug)]
-struct Node {
-    value: i32,
-    children: RefCell<Vec<Rc<Node>>>,
-}
-
-fn main() {
-    let leaf = Rc::new(Node {
-        value: 3,
-        children: RefCell::new(vec![]),
-    });
-
-    let branch = Rc::new(Node {
-        value: 5,
-        children: RefCell::new(vec![Rc::clone(&leaf)]),
-    });
-}
-

Мы хотим, чтобы Node владел своими дочерними узлами и мы хотим поделиться этим владением с переменными так, чтобы мы могли напрямую обращаться к каждому Node в дереве. Для этого мы определяем внутренние элементы вида Vec<T> как значения вида Rc<Node>. Мы также хотим изменять те узлы, которые являются дочерними по отношению к другому узлу, поэтому у нас есть вид RefCell<T> в поле children оборачивающий вид Vec<Rc<Node>>.

-

Далее мы будем использовать наше определение устройства и создадим один образец Node с именем leaf со значением 3 и без дочерних элементов, а другой образец с именем branch со значением 5 и leaf в качестве одного из его дочерних элементов, как показано в приложении 15-27:

-

Файл: src/main.rs

-
use std::cell::RefCell;
-use std::rc::Rc;
-
-#[derive(Debug)]
-struct Node {
-    value: i32,
-    children: RefCell<Vec<Rc<Node>>>,
-}
-
-fn main() {
-    let leaf = Rc::new(Node {
-        value: 3,
-        children: RefCell::new(vec![]),
-    });
-
-    let branch = Rc::new(Node {
-        value: 5,
-        children: RefCell::new(vec![Rc::clone(&leaf)]),
-    });
-}
-

Приложение 15-27: Создание узла leaf без дочерних элементов и узла branch с leaf в качестве одного из дочерних элементов

-

Мы клонируем содержимое Rc<Node> из переменной leaf и сохраняем его в переменной branch, что означает, что Node в leaf теперь имеет двух владельцев: leaf и branch. Мы можем получить доступ из branch к leaf через обращение branch.children, но нет способа добраться из leaf к branch. Причина в том, что leaf не имеет ссылки на branch и не знает, что они связаны. Мы хотим, чтобы leaf знал, что branch является его родителем. Мы сделаем это далее.

-

Добавление ссылки от ребёнка к его родителю

-

Для того, чтобы дочерний узел знал о своём родительском узле нужно добавить поле parent в наше определение устройства Node. Неполадкав том, чтобы решить, каким должен быть вид parent. Мы знаем, что он не может содержать Rc<T>, потому что это создаст ссылочную зацикленность с leaf.parent указывающей на branch и branch.children, указывающей на leaf, что приведёт к тому, что их значения strong_count никогда не будут равны 0.

-

Подумаем об этих отношениях по-другому, родительский узел должен владеть своими потомками: если родительский узел удаляется, его дочерние узлы также должны быть удалены. Однако дочерний элемент не должен владеть своим родителем: если мы удаляем дочерний узел то родительский элемент все равно должен существовать. Это случай для использования слабых ссылок!

-

Поэтому вместо Rc<T> мы сделаем так, чтобы поле parent использовало вид Weak<T>, а именно RefCell<Weak<Node>>. Теперь наше определение устройства Node выглядит так:

-

Файл: src/main.rs

-
use std::cell::RefCell;
-use std::rc::{Rc, Weak};
-
-#[derive(Debug)]
-struct Node {
-    value: i32,
-    parent: RefCell<Weak<Node>>,
-    children: RefCell<Vec<Rc<Node>>>,
-}
-
-fn main() {
-    let leaf = Rc::new(Node {
-        value: 3,
-        parent: RefCell::new(Weak::new()),
-        children: RefCell::new(vec![]),
-    });
-
-    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
-
-    let branch = Rc::new(Node {
-        value: 5,
-        parent: RefCell::new(Weak::new()),
-        children: RefCell::new(vec![Rc::clone(&leaf)]),
-    });
-
-    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
-
-    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
-}
-

Узел сможет ссылаться на свой родительский узел, но не владеет своим родителем. В приложении 15-28 мы обновляем main на использование нового определения так, чтобы у узла leaf был бы способ ссылаться на его родительский узел branch:

-

Файл: src/main.rs

-
use std::cell::RefCell;
-use std::rc::{Rc, Weak};
-
-#[derive(Debug)]
-struct Node {
-    value: i32,
-    parent: RefCell<Weak<Node>>,
-    children: RefCell<Vec<Rc<Node>>>,
-}
-
-fn main() {
-    let leaf = Rc::new(Node {
-        value: 3,
-        parent: RefCell::new(Weak::new()),
-        children: RefCell::new(vec![]),
-    });
-
-    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
-
-    let branch = Rc::new(Node {
-        value: 5,
-        parent: RefCell::new(Weak::new()),
-        children: RefCell::new(vec![Rc::clone(&leaf)]),
-    });
-
-    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
-
-    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
-}
-

Приложение 15-28: Узел leaf со слабой ссылкой на его родительский узел branch

-

Создание узла leaf выглядит подобно примеру из Приложения 15-27, за исключением поля parent: leaf изначально не имеет родителя, поэтому мы создаём новый, пустой образец ссылки Weak<Node>.

-

На этом этапе, когда мы пытаемся получить ссылку на родительский узел у узла leaf с помощью способа upgrade, мы получаем значение None. Мы видим это в выводе первой указания println!:

-
leaf parent = None
-
-

Когда мы создаём узел branch у него также будет новая ссылка вида Weak<Node> в поле parent, потому что узел branch не имеет своего родительского узла. У нас все ещё есть leaf как один из потомков узла branch. Когда мы получили образец Node в переменной branch, мы можем изменить переменную leaf чтобы дать ей Weak<Node> ссылку на её родителя. Мы используем способ borrow_mut у вида RefCell<Weak<Node>> поля parent у leaf, а затем используем функцию Rc::downgrade для создания Weak<Node> ссылки на branch из Rc<Node> в branch.

-

Когда мы снова напечатаем родителя leaf то в этот раз мы получим исход Some содержащий branch, теперь leaf может получить доступ к своему родителю! Когда мы печатаем leaf, мы также избегаем цикла, который в конечном итоге заканчивался переполнением обоймы, как в приложении 15-26; ссылки вида Weak<Node> печатаются как (Weak):

-
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },
-children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },
-children: RefCell { value: [] } }] } })
-
-

Отсутствие бесконечного вывода означает, что этот код не создал ссылочной зацикленности. Мы также можем сказать это, посмотрев на значения, которые мы получаем при вызове Rc::strong_count и Rc::weak_count.

-

Визуализация изменений в strong_count и weak_count

-

Давайте посмотрим, как изменяются значения strong_count и weak_count образцов вида Rc<Node> с помощью создания новой внутренней области видимости и перемещая создания образца branch в эту область. Таким образом можно увидеть, что происходит, когда branch создаётся и затем удаляется при выходе из области видимости. Изменения показаны в приложении 15-29:

-

Файл: src/main.rs

-
use std::cell::RefCell;
-use std::rc::{Rc, Weak};
-
-#[derive(Debug)]
-struct Node {
-    value: i32,
-    parent: RefCell<Weak<Node>>,
-    children: RefCell<Vec<Rc<Node>>>,
-}
-
-fn main() {
-    let leaf = Rc::new(Node {
-        value: 3,
-        parent: RefCell::new(Weak::new()),
-        children: RefCell::new(vec![]),
-    });
-
-    println!(
-        "leaf strong = {}, weak = {}",
-        Rc::strong_count(&leaf),
-        Rc::weak_count(&leaf),
-    );
-
-    {
-        let branch = Rc::new(Node {
-            value: 5,
-            parent: RefCell::new(Weak::new()),
-            children: RefCell::new(vec![Rc::clone(&leaf)]),
-        });
-
-        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
-
-        println!(
-            "branch strong = {}, weak = {}",
-            Rc::strong_count(&branch),
-            Rc::weak_count(&branch),
-        );
-
-        println!(
-            "leaf strong = {}, weak = {}",
-            Rc::strong_count(&leaf),
-            Rc::weak_count(&leaf),
-        );
-    }
-
-    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
-    println!(
-        "leaf strong = {}, weak = {}",
-        Rc::strong_count(&leaf),
-        Rc::weak_count(&leaf),
-    );
-}
-

Приложение 15-29: Создание branch во внутренней области видимости и подсчёт сильных и слабых ссылок

-

После того, как leaf создан его Rc<Node> имеет значения strong count равное 1 и weak count равное 0. Во внутренней области мы создаём branch и связываем её с leaf, после чего при печати значений счётчиков Rc<Node> в branch они будет иметь strong count 1 и weak count 1 (для leaf.parent указывающего на branch с Weak<Node> ). Когда мы распечатаем счётчики из leaf, мы увидим, что они будут иметь strong count 2, потому что branch теперь имеет клон Rc<Node> переменной leaf хранящийся в branch.children, но все равно будет иметь weak count 0.

-

Когда заканчивается внутренняя область видимости, branch выходит из области видимости и strong count Rc<Node> уменьшается до 0, поэтому его Node удаляется. Weak count 1 из leaf.parent не имеет никакого отношения к тому, был ли Node удалён, поэтому не будет никаких утечек памяти!

-

Если мы попытаемся получить доступ к родителю переменной leaf после окончания области видимости, мы снова получим значение None. В конце программы Rc<Node> внутри leaf имеет strong count 1 и weak count 0 потому что переменная leaf снова является единственной ссылкой на Rc<Node>.

-

Вся логика, которая управляет счётчиками и сбросом их значений, встроена внутри Rc<T> и Weak<T> и их выполнений особенности Drop. Указав, что отношение из дочернего к родительскому элементу должно быть ссылкой вида Weak<T> в определении Node, делает возможным иметь родительские узлы, указывающие на дочерние узлы и наоборот, не создавая ссылочной зацикленности и утечек памяти.

-

Итоги

-

В этой главе рассказано как использовать умные указатели для обеспечения различных заверений и соглашений по сравнению с обычными ссылками, которые Ржавчина использует по умолчанию. Вид Box<T> имеет известный размер и указывает на данные размещённые в куче. Вид Rc<T> отслеживает количество ссылок на данные в куче, поэтому данные могут иметь несколько владельцев. Вид RefCell<T> с его внутренней изменяемостью предоставляет вид, который можно использовать при необходимости неизменного вида, но необходимости изменить внутреннее значение этого типа; он также обеспечивает соблюдение правил заимствования во время выполнения, а не во время сборки.

-

Мы обсудили также особенности Deref и Drop, которые обеспечивают большую возможность умных указателей. Мы исследовали ссылочную зацикленность, которая может вызывать утечки памяти и как это предотвратить с помощью вида Weak<T>.

-

Если эта глава вызвала у вас влечение и вы хотите выполнить свои собственные умные указатели, обратитесь к "The Rustonomicon" за более полезной сведениями.

-

Далее мы поговорим о одновременности в Rust. Вы даже узнаете о нескольких новых умных указателях.

-

Многопоточность без страха

-

Безопасное и эффективное управление многопоточным программированием — ещё одна из основных целей Rust. Многопоточное программирование, когда разные части программы выполняются независимо, и одновременное программирование, когда разные части программы выполняются одновременно, становятся всё более важными, поскольку всё больше компьютеров используют преимущества нескольких процессоров. Исторически программирование в этих условиях было сложным и подверженным ошибкам: Ржавчина надеется изменить это.

-

Первоначально приказ Ржавчина считала, что обеспечение безопасности памяти и предотвращение неполадок многопоточности — это две отдельные сбоев, которые необходимо решать различными способами. Со временем приказ обнаружила, что системы владения и система видов являются мощным набором средств, помогающих управлять безопасностью памяти и неполадками многопоточного одновременности! Используя владение и проверку видов, многие ошибки многопоточности являются ошибками времени сборки в Rust, а не ошибками времени выполнения. Поэтому вместо того, чтобы тратить много времени на попытки воспроизвести точные обстоятельства, при которых возникает ошибка многопоточности во время выполнения, неправильный код будет отклонён с ошибкой. В итоге вы можете исправить свой код во время работы над ним, а не после развёртывания на рабочем сервере. Мы назвали этот особенность Ржавчина бесстрашной многопоточностью. Бесстрашная многопоточность позволяет вам писать код, который не содержит скрытых ошибок и легко ресогласуется без внесения новых.

-
-

Примечание: для простоты мы будем называть многие сбоев многопоточными, хотя более точный понятие здесь  — многопоточные и/или одновременные. Если бы эта книга была о многопоточности и/или одновременности, мы были бы более определены. В этой главе, пожалуйста, всякий раз, когда мы используем понятие «многопоточный», мысленно замените на понятие «многопоточный и/или одновременный».

-
-

Многие языки предлагают довольно устоявшиеся решения неполадок многопоточности. Например, Erlang обладает элегантной возможностью для многопоточности при передаче сообщений, но не определяет ясных способов совместного использования состояния между потоками. Поддержка только подмножества возможных решений является разумной подходом для языков более высокого уровня, поскольку язык более высокого уровня обещает выгоду при отказе от некоторого управления над получением абстракций. Однако ожидается, что языки низкого уровня обеспечат решение с наилучшей производительностью в любой именно случаи и будут иметь меньше абстракций по сравнению с аппаратным обеспечением. Поэтому Ржавчина предлагает множество средств для расчетов неполадок любым способом, который подходит для вашей случаи и требований.

-

Вот темы, которые мы рассмотрим в этой главе:

-
    -
  • Как создать потоки для одновременного запуска нескольких отрывков кода
  • -
  • Многопоточность передачи сообщений, где потоки передают сообщения между потоками
  • -
  • Многопоточность для совместно используемого состояния, когда несколько потоков имеют доступ к некоторому отрывку данных
  • -
  • Особенности Sync и Send, которые расширяют заверения многопоточности в Ржавчина для пользовательских видов, а также видов, предоставляемых встроенной библиотекой
  • -
-

Использование потоков для одновременного выполнения кода

-

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

-

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

-
    -
  • Состояния гонки, когда потоки обращаются к данным, либо ресурсам, несогласованно.
  • -
  • Взаимные блокировки, когда два потока ожидают друг друга, не позволяя тем самым продолжить работу каждому из потоков.
  • -
  • Ошибки, которые случаются только в определённых случаейх, которые трудно воспроизвести и, соответственно, трудно надёжно исправить.
  • -
-

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

-

Языки программирования выполняют потоки несколькими различными способами, и многие операционные системы предоставляют API, который язык может вызывать для создания новых потоков. Обычная библиотека Ржавчина использует прообраз выполнения потоков 1:1, при которой одному потоку операционной системы соответствует ровно один "языковой" поток. Существуют ящики, в которых выполнены другие подходы многопоточности, отличающиеся от подходы 1:1.

-

Создание нового потока с помощью spawn

-

Чтобы создать новый поток, мы вызываем функцию thread::spawn и передаём ей замыкание (мы говорили о замыканиях в главе 13), содержащее код, который мы хотим запустить в новом потоке. Пример в приложении 16-1 печатает некоторый текст из основного потока, а также другой текст из нового потока:

-

Файл: src/main.rs

-
use std::thread;
-use std::time::Duration;
-
-fn main() {
-    thread::spawn(|| {
-        for i in 1..10 {
-            println!("hi number {i} from the spawned thread!");
-            thread::sleep(Duration::from_millis(1));
-        }
-    });
-
-    for i in 1..5 {
-        println!("hi number {i} from the main thread!");
-        thread::sleep(Duration::from_millis(1));
-    }
-}
-

Приложение 16-1: Создание нового потока для печати определённого текста, в то время как основной поток печатает что-то другое

-

Обратите внимание, что когда основной поток программы на Ржавчина завершается, все порождённые потоки закрываются, независимо от того, завершили они работу или нет. Вывод этой программы может каждый раз немного отличаться, но он будет выглядеть примерно так:

- -
hi number 1 from the main thread!
-hi number 1 from the spawned thread!
-hi number 2 from the main thread!
-hi number 2 from the spawned thread!
-hi number 3 from the main thread!
-hi number 3 from the spawned thread!
-hi number 4 from the main thread!
-hi number 4 from the spawned thread!
-hi number 5 from the spawned thread!
-
-

Вызовы thread::sleep заставляют поток на короткое время останавливать своё выполнение, позволяя выполняться другим потокам. Очерёдность выполнения потоков вероятно будет меняться, но это не обязательно: это зависит от того, как ваша операционная система расчитывает потоки. В этом цикле основной поток печатает первым, несмотря на то, что указание печати из порождённого потока появляется раньше в коде. И даже несмотря на то, что мы указали порождённый поток печатать до тех пор, пока значение i не достигнет числа 9, оно успело дойти только до 5, когда основной поток завершился.

-

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

-

Ожидание завершения работы всех потоков используя join

-

Код в приложении 16-1 преждевременно останавливает порождённый поток в большинстве случаев, из-за завершения основного потока. Более того, так как порядок выполнения потоков чётко не определён, этот код не даёт заверения, что порождённый поток вообще начнёт исполняться!

-

Мы можем исправить неполадку, когда созданный поток не запускается или завершается преждевременно, сохранив возвращаемое значение thread::spawn в какой-либо переменной. Вид возвращаемого значения thread::spawnJoinHandle . JoinHandle — это владеющее значение, которое, при вызове способа join , будет ждать завершения своего потока. Приложение 16-2 отображает, как использовать JoinHandle потока, созданного в приложении 16-1, и вызывать функцию join , для того, чтобы убедиться, что порождённый поток завершится раньше, чем поток main:

-

Файл: src/main.rs

-
use std::thread;
-use std::time::Duration;
-
-fn main() {
-    let handle = thread::spawn(|| {
-        for i in 1..10 {
-            println!("hi number {i} from the spawned thread!");
-            thread::sleep(Duration::from_millis(1));
-        }
-    });
-
-    for i in 1..5 {
-        println!("hi number {i} from the main thread!");
-        thread::sleep(Duration::from_millis(1));
-    }
-
-    handle.join().unwrap();
-}
-

Приложение 16-2. Сохранение значения JoinHandle потока thread::spawn , обеспечивающее, что поток выполнит всю необходимую работу, перед тем, как завершится

-

Вызов join у указателя блокирует текущий поток, пока поток, представленный указателем не завершится. Блокировка потока означает, что потоку запрещено выполнять работу или выходить из него. Поскольку мы помеисполнения вызов join после цикла for основного потока, выполнение приложения 16-2 должно привести к выводу, подобному следующему:

- -
hi number 1 from the main thread!
-hi number 2 from the main thread!
-hi number 1 from the spawned thread!
-hi number 3 from the main thread!
-hi number 2 from the spawned thread!
-hi number 4 from the main thread!
-hi number 3 from the spawned thread!
-hi number 4 from the spawned thread!
-hi number 5 from the spawned thread!
-hi number 6 from the spawned thread!
-hi number 7 from the spawned thread!
-hi number 8 from the spawned thread!
-hi number 9 from the spawned thread!
-
-

Два потока продолжают чередоваться, но основной поток находится в ожидании из-за вызова handle.join() и не завершается до тех пор, пока не завершится запущенный поток.

-

Но давайте посмотрим, что произойдёт, если мы вместо этого переместим handle.join() перед циклом for в main, например так:

-

Файл: src/main.rs

-
use std::thread;
-use std::time::Duration;
-
-fn main() {
-    let handle = thread::spawn(|| {
-        for i in 1..10 {
-            println!("hi number {i} from the spawned thread!");
-            thread::sleep(Duration::from_millis(1));
-        }
-    });
-
-    handle.join().unwrap();
-
-    for i in 1..5 {
-        println!("hi number {i} from the main thread!");
-        thread::sleep(Duration::from_millis(1));
-    }
-}
-

Основной поток будет ждать завершения порождённого потока, а затем запустит свой цикл for , поэтому выходные данные больше не будут чередоваться, как показано ниже:

- -
hi number 1 from the spawned thread!
-hi number 2 from the spawned thread!
-hi number 3 from the spawned thread!
-hi number 4 from the spawned thread!
-hi number 5 from the spawned thread!
-hi number 6 from the spawned thread!
-hi number 7 from the spawned thread!
-hi number 8 from the spawned thread!
-hi number 9 from the spawned thread!
-hi number 1 from the main thread!
-hi number 2 from the main thread!
-hi number 3 from the main thread!
-hi number 4 from the main thread!
-
-

Небольшие подробности, такие как место вызова join, могут повлиять на то, выполняются ли ваши потоки одновременно.

-

Использование move-замыканий в потоках

-

Мы часто используем ключевое слово move с замыканиями, переданными в thread::spawn, потому что в этом случае замыкание получает из окружения права владения на используемые им значения, таким образом передавая права владения этими значениями от одного потока к другому. В разделе "Захват ссылок или перемещение прав владения" главы 13 мы обсудили move в среде замыканий. Теперь мы сосредоточимся на взаимодействии между move и thread::spawn.

-

Обратите внимание, что в приложении 16-1 замыкание, которое мы передаём в thread::spawn не принимает переменных: мы не используем никаких данных из основного потока в коде порождённого потока. Чтобы использовать данные из основного потока в порождённом потоке, замыкание порождённого потока должно захватывать значения, которые ему необходимы. Приложение 16-3 показывает попытку создать вектор в главном потоке и использовать его в порождённом потоке. Тем не менее, это не будет работать, как вы увидите через мгновение.

-

Файл: src/main.rs

-
use std::thread;
-
-fn main() {
-    let v = vec![1, 2, 3];
-
-    let handle = thread::spawn(|| {
-        println!("Here's a vector: {v:?}");
-    });
-
-    handle.join().unwrap();
-}
-

Приложение 16-3: Попытка использовать вектор, созданный основным потоком, в другом потоке

-

Замыкание использует переменную v, поэтому оно захватит v и сделает его частью окружения замыкания. Поскольку thread::spawn запускает это замыкание в новом потоке, мы должны иметь доступ к v внутри этого нового потока. Но при сборки этого примера, мы получаем следующую ошибку:

-
$ cargo run
-   Compiling threads v0.1.0 (file:///projects/threads)
-error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function
- --> src/main.rs:6:32
-  |
-6 |     let handle = thread::spawn(|| {
-  |                                ^^ may outlive borrowed value `v`
-7 |         println!("Here's a vector: {v:?}");
-  |                                     - `v` is borrowed here
-  |
-note: function requires argument type to outlive `'static`
- --> src/main.rs:6:18
-  |
-6 |       let handle = thread::spawn(|| {
-  |  __________________^
-7 | |         println!("Here's a vector: {v:?}");
-8 | |     });
-  | |______^
-help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
-  |
-6 |     let handle = thread::spawn(move || {
-  |                                ++++
-
-For more information about this error, try `rustc --explain E0373`.
-error: could not compile `threads` (bin "threads") due to 1 previous error
-
-

Rust выводит как захватить v и так как в println! нужна только ссылка на v, то замыкание пытается заимствовать v. Однако есть неполадка: Ржавчина не может определить, как долго будет работать порождённый поток, поэтому он не знает, будет ли всегда действительной ссылка на v.

-

В приложении 16-4 приведён сценарий, который с большей вероятностью будет иметь ссылку на v, что будет недопустимо:

-

Файл: src/main.rs

-
use std::thread;
-
-fn main() {
-    let v = vec![1, 2, 3];
-
-    let handle = thread::spawn(|| {
-        println!("Here's a vector: {v:?}");
-    });
-
-    drop(v); // oh no!
-
-    handle.join().unwrap();
-}
-

Приложение 16-4. Поток с замыканием, который пытается захватить ссылку на v из основного потока, удаляющего v

-

Если бы Ржавчина позволил нам запустить этот код, есть вероятность, что порождённый поток был бы немедленно переведён в фоновый режим, не выполнив ничего. Порождённый поток имеет ссылку на v, но основной поток немедленно удаляет v , используя функцию drop , которую мы обсуждали в главе 15. Затем, когда порождённый поток начинает выполняться, v уже не существует, поэтому ссылка на него также будет недействительной. О, нет!

-

Чтобы исправить ошибку сборщика в приложении 16-3, мы можем использовать совет из сообщения об ошибке:

- -
help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
-  |
-6 |     let handle = thread::spawn(move || {
-  |                                ++++
-
-

Добавляя ключевое слово move перед замыканием, мы заставляем замыкание забирать используемые значения во владение, вместо того, чтобы позволить Ржавчина вывести необходимость заимствования значения. Изменение Приложения 16-3, показанная в Приложении 16-5, будет собрана и запущена так, как мы ожидаем:

-

Файл: src/main.rs

-
use std::thread;
-
-fn main() {
-    let v = vec![1, 2, 3];
-
-    let handle = thread::spawn(move || {
-        println!("Here's a vector: {v:?}");
-    });
-
-    handle.join().unwrap();
-}
-

Приложение 16-5. Использование ключевого слова move , чтобы замыкание стало владельцем используемых им значений.

-

У нас может возникнуть соблазн попробовать то же самое, чтобы исправить код в приложении 16.4, где основной поток вызывал drop с помощью замыкания move . Однако это исправление не сработает, потому что то, что пытается сделать приложение 16.4, запрещено по другой причине. Если мы добавим move к замыканию, мы переместим v в окружение замыкания и больше не сможем вызывать для него drop в основном потоке. Вместо этого мы получим эту ошибку сборщика:

-
$ cargo run
-   Compiling threads v0.1.0 (file:///projects/threads)
-error[E0382]: use of moved value: `v`
-  --> src/main.rs:10:10
-   |
-4  |     let v = vec![1, 2, 3];
-   |         - move occurs because `v` has type `Vec<i32>`, which does not implement the `Copy` trait
-5  |
-6  |     let handle = thread::spawn(move || {
-   |                                ------- value moved into closure here
-7  |         println!("Here's a vector: {v:?}");
-   |                                     - variable moved due to use in closure
-...
-10 |     drop(v); // oh no!
-   |          ^ value used here after move
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `threads` (bin "threads") due to 1 previous error
-
-

Правила владения Ржавчина снова нас спасли! Мы получили ошибку кода из приложения 16-3, потому что Ржавчина был устоявшийся и заимствовал v только для потока, что означало, что основной поток предположительно может сделать недействительной ссылку на порождённый поток. Сообщив Ржавчина о передаче владения v в порождаемый поток, мы заверяем Rust, что основной поток больше не будет использовать v. Если мы изменим Приложение 16-4 таким же образом, то мы нарушаем правила владения при попытке использовать v в главном потоке. Ключевое слово move отменяет основное устоявшееся поведение Ржавчина по заимствованию, что не позволяет нам нарушать правила владения.

-

Имея достаточное понимание потоков и API потоков, давайте посмотрим, что мы можем делать с помощью потоков.

-

Передача данных с помощью сообщений между потоками

-

Всё большую распространенность для обеспечения безопасной многопоточности набирает способ, называемый передача сообщений. В этом случае потоки или акторы взаимодействуют друг с другом путём отправки сообщений с данными. Мысль этого подхода выражена в слогане из документации языка Go таким образом: «Не стоит передавать сведения с помощью разделяемой памяти; лучше делитесь памятью, передавая сведения».

-

Для обеспечения отправки многопоточных сообщений в встроенной библиотеке языка Ржавчина выполнены потоки. Поток в программировании - это общепринятый рычаг, с помощью которого данные из одного потока отправляются другому потоку.

-

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

-

Поток состоит из двух половин: передатчика и приёмника. Передатчик — это место вверх по течению, где вы опускаете резиновых уточек в реку, а приёмник — это место, где резиновые уточки оказываются в конце пути. Одна часть вашего кода вызывает способы передатчика с данными, которые вы хотите отправить, а другая часть проверяет принимающую сторону на наличие поступающих сообщений. Поток считается закрытым , если либо передающая, либо принимающая его половина уничтожена.

-

Давайте создадим программу, в которой один поток будет порождать значения и отправлять их в поток, а другой поток будет получать значения и распечатывать их. Мы будем отправлять между потоками простые значения, используя поток, чтобы изобразить эту функцию. После того, как вы ознакомитесь с этим способом, вы сможете использовать потоки с любыми потоками, которым необходимо взаимодействовать друг с другом. Это может быть например система чата или система, в которой несколько вычислительных потоков выполняют свою часть расчёта, а затем отправляют эту часть в отдельный поток, который уже агрегирует полученные итоги.

-

Сначала в приложении 16-6 мы создадим поток, но не будем ничего с ним делать. Обратите внимание, что этот код ещё не собирается, потому что Ржавчина не может сказать, какой вид значений мы хотим отправить через поток.

-

Файл: src/main.rs

-
use std::sync::mpsc;
-
-fn main() {
-    let (tx, rx) = mpsc::channel();
-}
-

Приложение 16-6: Создание потока и присваивание двух значений переменным tx и rx

-

Мы создаём новый поток, используя функцию mpsc::channel; mpsc означает несколько производителей, один потребитель (multiple producer, single consumer). Коротко, способ которым обычная библиотека Ржавчина выполняет потоки, означает, что поток может иметь несколько отправляющих источников порождающих значения, но только одну принимающую сторону, которая потребляет эти значения. Представьте, что несколько ручьёв втекают в одну большую реку: всё, что плывёт вниз по любому из ручьёв, в конце концов окажется в одной реке. Сейчас мы пока начнём с одного производителя, а когда пример заработает, добавим ещё несколько.

-

Функция mpsc::channel возвращает упорядоченный ряд, первый элемент которого является отправляющей стороной (передатчиком), а вторым элементом является принимающая сторона (получатель). Аббревиатуры tx и rx привычно используются во многих полях для передатчика и приёмника соответственно, поэтому мы называем соответствующие переменные именно так. Мы используем указанию let с образцом, который разъединяет упорядоченные ряды; мы обсудим использование образцов в указаниях let и разъединение в главе 18. А пока знайте, что описанное использование указания let является удобным способом извлечения частей упорядоченного ряда, возвращаемых mpsc::channel .

-

Давайте переместим передающую часть в порождённый поток так, чтобы он отправлял одну строку и чтобы таким образом, порождённый поток связывался с основным потоком, как показано в приложении 16-7. Это похоже на то, как если бы вы помеисполнения резиновую утку в реку вверх по течению или отправили сообщение чата из одного потока в другой.

-

Файл: src/main.rs

-
use std::sync::mpsc;
-use std::thread;
-
-fn main() {
-    let (tx, rx) = mpsc::channel();
-
-    thread::spawn(move || {
-        let val = String::from("hi");
-        tx.send(val).unwrap();
-    });
-}
-

Приложение 16-7: Перемещение tx в созданный поток и отправка сообщения «привет»

-

Опять же, мы используем thread::spawn для создания нового потока, а затем используем move для перемещения tx в замыкание, чтобы порождённый поток владел tx . Порождённый поток должен владеть передатчиком, чтобы иметь возможность отправлять сообщения через поток. Передатчик имеет способ send , который принимает значение, которое мы хотим отправить. Способ send возвращает вид Result<T, E> , поэтому, если получатель уже удалён и отправить значение некуда, действие отправки вернёт ошибку. В этом примере мы вызываем unwrap для паники в случае ошибки. В существующем приложении мы обработали бы эту случай более правильно: вернитесь к главе 9, если хотите ещё раз разобрать стратегии правильной обработки ошибок.

-

В приложении 16-8 мы получим значение от приёмника в основном потоке. Это похоже на извлечение резиновой уточки из воды в конце реки или получение сообщения в чате.

-

Файл: src/main.rs

-
use std::sync::mpsc;
-use std::thread;
-
-fn main() {
-    let (tx, rx) = mpsc::channel();
-
-    thread::spawn(move || {
-        let val = String::from("hi");
-        tx.send(val).unwrap();
-    });
-
-    let received = rx.recv().unwrap();
-    println!("Got: {received}");
-}
-

Приложение 16-8: В основном потоке получаем сообщение "hi" и печатаем его

-

Получатель имеет два важных способа: recv и try_recv. Мы используем recv, что является сокращением от receive, который блокирует выполнение основного потока и ждёт, пока данные не будут переданы по потоку. Как только значение будет получено, recv вернёт его в виде Result<T, E>. Когда поток закроется, recv вернёт ошибку, чтобы дать понять, что больше никаких сообщений не поступит.

-

В свою очередь, способ try_recv не блокирует, а сразу возвращает итог Result<T, E>: значение Ok, содержащее сообщение, если оно доступно или значение Err, если никаких сообщений не поступило. Использование try_recv полезно, если у этого потока есть и другая работа в то время, пока происходит ожидание сообщений: так, мы можем написать цикл, который вызывает try_recv время от времени, обрабатывает сообщение, если оно доступно, а в промежутке выполняет другую работу до того особенности, как вновь будет произведена проверка.

-

Мы использовали recv в этом примере для простоты; у нас нет никакой другой работы для основного потока, кроме как ждать сообщений, поэтому блокировка основного потока уместна.

-

При запуске кода приложения 16-8, мы увидим значение, напечатанное из основного потока:

- -
Got: hi
-
-

Отлично!

-

потоки и передача владения

-

Правила владения играют жизненно важную значение в отправке сообщений, потому что они помогают писать безопасный многопоточный код. Предотвращение ошибок в многопоточном программировании является преимуществом для размышлений о владении во всех ваших Ржавчина программах. Давайте проведём эксперимент, чтобы показать как потоки и владение действуют совместно для предотвращения неполадок. мы попытаемся использовать значение val в порождённом потоке после того как отправим его в поток. Попробуйте собрать код в приложении 16-9, чтобы понять, почему этот код не разрешён:

-

Файл: src/main.rs

-
use std::sync::mpsc;
-use std::thread;
-
-fn main() {
-    let (tx, rx) = mpsc::channel();
-
-    thread::spawn(move || {
-        let val = String::from("hi");
-        tx.send(val).unwrap();
-        println!("val is {val}");
-    });
-
-    let received = rx.recv().unwrap();
-    println!("Got: {received}");
-}
-

Приложение 16-9: Попытка использовать val после того, как мы отправили его по потоку

-

Здесь мы пытаемся напечатать значение val после того, как отправили его в поток вызвав tx.send. Разрешить это было бы плохой мыслью: после того, как значение было отправлено в другой поток, текущий поток мог бы изменить или удалить значение, прежде чем мы попытались бы использовать значение снова. Вероятно изменения в другом потоке могут привести к ошибкам или не ожидаемым итогам из-за противоречивых или несуществующих данных. Однако Ржавчина выдаёт нам ошибку, если мы пытаемся собрать код в приложении 16-9:

-
$ cargo run
-   Compiling message-passing v0.1.0 (file:///projects/message-passing)
-error[E0382]: borrow of moved value: `val`
-  --> src/main.rs:10:26
-   |
-8  |         let val = String::from("hi");
-   |             --- move occurs because `val` has type `String`, which does not implement the `Copy` trait
-9  |         tx.send(val).unwrap();
-   |                 --- value moved here
-10 |         println!("val is {val}");
-   |                          ^^^^^ value borrowed here after move
-   |
-   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `message-passing` (bin "message-passing") due to 1 previous error
-
-

Наша ошибка для многопоточности привела к ошибке сборки. Функция send вступает во владение своим свойствоом и когда значение перемещается, получатель становится владельцем этого свойства. Это останавливает нас от случайного использования значения снова после его отправки; анализатор заимствования проверяет, что все в порядке.

-

Отправка нескольких значений и ожидание получателем

-

Код в приложении 16-8 собирается и выполняется, но в нем неясно показано то, что два отдельных потока общаются друг с другом через поток. В приложении 16-10 мы внесли некоторые изменения, которые докажут, что код в приложении 16-8 работает одновременно: порождённый поток теперь будет отправлять несколько сообщений и делать паузу на секунду между каждым сообщением.

-

Файл: src/main.rs

-
use std::sync::mpsc;
-use std::thread;
-use std::time::Duration;
-
-fn main() {
-    let (tx, rx) = mpsc::channel();
-
-    thread::spawn(move || {
-        let vals = vec![
-            String::from("hi"),
-            String::from("from"),
-            String::from("the"),
-            String::from("thread"),
-        ];
-
-        for val in vals {
-            tx.send(val).unwrap();
-            thread::sleep(Duration::from_secs(1));
-        }
-    });
-
-    for received in rx {
-        println!("Got: {received}");
-    }
-}
-

Приложение 16-10: Отправка нескольких сообщений и пауза между ними

-

На этот раз порождённый поток имеет вектор строк, которые мы хотим отправить основному потоку. Мы перебираем их, отправляя каждую строку по отдельности и делаем паузу между ними, вызывая функцию thread::sleep со значением Duration равным 1 секунде.

-

В основном потоке мы больше не вызываем функцию recv явно: вместо этого мы используем rx как повторитель . Для каждого полученного значения мы печатаем его. Когда поток будет закрыт, повторение закончится.

-

При выполнении кода в приложении 16-10 вы должны увидеть следующий вывод с паузой в 1 секунду между каждой строкой:

- -
Got: hi
-Got: from
-Got: the
-Got: thread
-
-

Поскольку у нас нет кода, который приостанавливает или задерживает цикл for в основном потоке, мы можем сказать, что основной поток ожидает получения значений из порождённого потока.

-

Создание нескольких отправителей путём клонирования передатчика

-

Ранее мы упоминали, что mpsc — это аббревиатура от множество поставщиков, один потребитель . Давайте используем mpsc в полной мере и расширим код в приложении 16.10, создав несколько потоков, которые отправляют значения одному и тому же получателю. Мы можем сделать это, клонировав передатчик, как показано в приложении 16.11:

-

Файл: src/main.rs

-
use std::sync::mpsc;
-use std::thread;
-use std::time::Duration;
-
-fn main() {
-    // --snip--
-
-    let (tx, rx) = mpsc::channel();
-
-    let tx1 = tx.clone();
-    thread::spawn(move || {
-        let vals = vec![
-            String::from("hi"),
-            String::from("from"),
-            String::from("the"),
-            String::from("thread"),
-        ];
-
-        for val in vals {
-            tx1.send(val).unwrap();
-            thread::sleep(Duration::from_secs(1));
-        }
-    });
-
-    thread::spawn(move || {
-        let vals = vec![
-            String::from("more"),
-            String::from("messages"),
-            String::from("for"),
-            String::from("you"),
-        ];
-
-        for val in vals {
-            tx.send(val).unwrap();
-            thread::sleep(Duration::from_secs(1));
-        }
-    });
-
-    for received in rx {
-        println!("Got: {received}");
-    }
-
-    // --snip--
-}
-

Приложение 16-11: Отправка нескольких сообщений от нескольких производителей

-

На этот раз, прежде чем мы создадим первый порождённый поток, мы вызовем функцию clone на передатчике. В итоге мы получим новый передатчик, который мы сможем передать первому порождённому потоку. Исходный передатчик мы передадим второму порождённому потоку. Это даст нам два потока, каждый из которых отправляет разные сообщения одному получателю.

-

Когда вы запустите код, вывод должен выглядеть примерно так:

- -
Got: hi
-Got: more
-Got: from
-Got: messages
-Got: for
-Got: the
-Got: thread
-Got: you
-
-

Вы можете увидеть значения в другом порядке, в зависимости от вашей системы. Именно такое поведение делает одновременность как важным, так и сложным. Если вы поэкспериментируете с thread::sleep , задавая различные значения переменной в разных потоках, каждый запуск будет более неопределенным и каждый раз будут выводиться разные данные.

-

Теперь, когда мы посмотрели, как работают потоки, давайте рассмотрим другой способ многопоточности.

-

Многопоточное разделяемое состояние

-

Передача сообщений — прекрасный способ обработки одновременности, но не единственный. Другим способом может быть доступ нескольких потоков к одним и тем же общим данным. Рассмотрим ещё раз часть слогана из документации по языку Go: «Не стоит передавать сведения с помощью разделяемой памяти».

-

Как бы выглядело общение, используя разделяемую память? Кроме того, почему энтузиасты передачи сообщений предостерегают от его использования?

-

В каком-то смысле потоки в любом языке программирования похожи на единоличное владение, потому что после передачи значения по потоку вам больше не следует использовать отправленное значение. Многопоточная, совместно используемая память подобна множественному владению: несколько потоков могут одновременно обращаться к одной и той же области памяти. Как вы видели в главе 15, где умные указатели сделали возможным множественное владение, множественное владение может добавить сложность, потому что нужно управлять этими разными владельцами. Система видов Ржавчина и правила владения очень помогают в их правильном управлении. Для примера давайте рассмотрим мьютексы, один из наиболее распространённых многопоточных простейших для разделяемой памяти.

-

Мьютексы предоставляют доступ к данным из одного потока (за раз)

-

Mutex - это сокращение от взаимное исключение (mutual exclusion), так как мьютекс позволяет только одному потоку получать доступ к некоторым данным в любой мгновение времени. Для того, чтобы получить доступ к данным в мьютексе, поток должен сначала подать сигнал, что он хочет получить доступ запрашивая блокировку (lock) мьютекса. Блокировка - это устройства данных, являющаяся частью мьютекса, которая отслеживает кто в настоящее время имеет эксклюзивный доступ к данным. Поэтому мьютекс описывается как предмет защищающий данные, которые он хранит через систему блокировки.

-

Мьютексы имеют репутацию трудных в использовании, потому что вы должны помнить два правила:

-
    -
  • Перед тем как попытаться получить доступ к данным необходимо получить блокировку.
  • -
  • Когда вы закончили работу с данными, которые защищает мьютекс, вы должны разблокировать данные, чтобы другие потоки могли получить блокировку.
  • -
-

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

-

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

-

Mutex<T> API

-

Давайте рассмотрим пример использования мьютекса в приложении 16-12 без использования нескольких потоков:

-

Файл: src/main.rs

-
use std::sync::Mutex;
-
-fn main() {
-    let m = Mutex::new(5);
-
-    {
-        let mut num = m.lock().unwrap();
-        *num = 6;
-    }
-
-    println!("m = {m:?}");
-}
-

Приложение 16-12: Изучение API Mutex<T> для простоты в однопоточном среде

-

Как и во многих других видах, мы создаём Mutex<T> с помощью сопутствующей функции new. Чтобы получить доступ к данным внутри мьютекса, мы используем способ lock для получения блокировки. Этот вызов блокирует выполнение текущего потока, так что он не сможет выполнять никакие действия, до тех пор пока не наступит наша очередь получить блокировку.

-

Вызов lock потерпит неудачу, если другой поток, удерживающий блокировку, запаникует. В таком случае никто не сможет получить блокировку, поэтому мы предпочли использовать unwrap и заставить этот поток паниковать, если мы окажемся в такой случаи.

-

После получения блокировки мы можем воспринимать возвращённое значение, названное в данном случае num, как изменяемую ссылку на содержащиеся внутри данные. Система видов заверяет, что мы получим блокировку перед использованием значения в m. Вид m - Mutex<i32>, а не i32, поэтому мы должны вызвать lock, чтобы иметь возможность использовать значение i32. Мы не должны об этом забывать, тем более что в иных случаях система видов и не даст нам доступ к внутреннему значению i32.

-

Как вы наверное подозреваете, Mutex<T> является умным указателем. Точнее, вызов lock возвращает умный указатель, называемый MutexGuard, обёрнутый в LockResult, который мы обработали с помощью вызова unwrap. Умный указатель вида MutexGuard выполняет особенность Deref для указания на внутренние данные; умный указатель также имеет выполнение особенности Drop, самостоятельно снимающего блокировку, когда MutexGuard выходит из области видимости, что происходит в конце внутренней области видимости. В итоге у нас нет риска забыть снять блокировку и оставить мьютекс в заблокированном состоянии, препятствуя его использованию другими потоками (снятие блокировки происходит самостоятельно ).

-

После снятия блокировки можно напечатать значение мьютекса и увидеть, что мы смогли изменить внутреннее i32 на 6.

-

Разделение Mutex<T> между множеством потоков

-

Теперь давайте попробуем с помощью Mutex<T> совместно использовать значение между несколькими потоками. Мы стартуем 10 потоков и каждый из них увеличивает значение счётчика на 1, поэтому счётчик изменяется от 0 до 10. Обратите внимание, что в следующих нескольких примерах будут ошибки сборщика и мы будем использовать эти ошибки, чтобы узнать больше об использовании вида Mutex<T> и как Ржавчина помогает нам правильно его использовать. Приложение 16-13 содержит наш начальный пример:

-

Файл: src/main.rs

-
use std::sync::Mutex;
-use std::thread;
-
-fn main() {
-    let counter = Mutex::new(0);
-    let mut handles = vec![];
-
-    for _ in 0..10 {
-        let handle = thread::spawn(move || {
-            let mut num = counter.lock().unwrap();
-
-            *num += 1;
-        });
-        handles.push(handle);
-    }
-
-    for handle in handles {
-        handle.join().unwrap();
-    }
-
-    println!("Result: {}", *counter.lock().unwrap());
-}
-

Приложение 16-13. Десять потоков, увеличивающих счётчик, защищённый Mutex<T>

-

Мы создаём переменную-счётчик counter для хранения i32 значения внутри Mutex<T>, как мы это делали в приложении 16-12. Затем мы создаём 10 потоков, перебирая рядчисел. Мы используем thread::spawn и передаём всем этим потокам одинаковое замыкание, которое перемещает счётчик в поток, запрашивает блокировку на Mutex<T>, вызывая способ lock, а затем добавляет 1 к значению в мьютексе. Когда поток завершит выполнение своего замыкания, num выйдет из области видимости и освободит блокировку, чтобы её мог получить другой поток.

-

В основном потоке мы собираем все указатели в переменную handles. Затем, как мы это делали в приложении 16-2, вызываем join для каждого указателя, чтобы убедиться в завершении всех потоков. В этот мгновение основной поток получит доступ к блокировке и тоже напечатает итог программы.

-

Сборщик намекнул, что этот пример не собирается. Давайте выясним почему!

-
$ cargo run
-   Compiling shared-state v0.1.0 (file:///projects/shared-state)
-error[E0382]: borrow of moved value: `counter`
-  --> src/main.rs:21:29
-   |
-5  |     let counter = Mutex::new(0);
-   |         ------- move occurs because `counter` has type `Mutex<i32>`, which does not implement the `Copy` trait
-...
-8  |     for _ in 0..10 {
-   |     -------------- inside of this loop
-9  |         let handle = thread::spawn(move || {
-   |                                    ------- value moved into closure here, in previous iteration of loop
-...
-21 |     println!("Result: {}", *counter.lock().unwrap());
-   |                             ^^^^^^^ value borrowed here after move
-   |
-help: consider moving the expression out of the loop so it is only moved once
-   |
-8  ~     let mut value = counter.lock();
-9  ~     for _ in 0..10 {
-10 |         let handle = thread::spawn(move || {
-11 ~             let mut num = value.unwrap();
-   |
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `shared-state` (bin "shared-state") due to 1 previous error
-
-

Сообщение об ошибке указывает, что значение counter было перемещёно в замыкание на предыдущей повторения цикла. Ржавчина говорит нам, что мы не можем передать counter во владение нескольким потокам. Давайте исправим ошибку сборщика с помощью способа множественного владения, который мы обсуждали в главе 15.

-

Множественное владение между множеством потоков

-

В главе 15 мы давали значение нескольким владельцам, используя умный указатель Rc<T> для создания значения подсчитанных ссылок. Давайте сделаем то же самое здесь и посмотрим, что произойдёт. Мы завернём Mutex<T> в Rc<T> в приложении 16-14 и клонируем Rc<T> перед передачей владения в поток. Теперь, когда мы увидели ошибки, мы также вернёмся к использованию цикла for и сохраним ключевое слово move у замыкания.

-

Файл: src/main.rs

-
use std::rc::Rc;
-use std::sync::Mutex;
-use std::thread;
-
-fn main() {
-    let counter = Rc::new(Mutex::new(0));
-    let mut handles = vec![];
-
-    for _ in 0..10 {
-        let counter = Rc::clone(&counter);
-        let handle = thread::spawn(move || {
-            let mut num = counter.lock().unwrap();
-
-            *num += 1;
-        });
-        handles.push(handle);
-    }
-
-    for handle in handles {
-        handle.join().unwrap();
-    }
-
-    println!("Result: {}", *counter.lock().unwrap());
-}
-

Приложение 16-14: Попытка использования Rc<T>, чтобы позволить нескольким потокам владеть Mutex<T>

-

Ещё раз, мы собираем и получаем ... другие ошибки! Сборщик учит нас.

-
$ cargo run
-   Compiling shared-state v0.1.0 (file:///projects/shared-state)
-error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely
-  --> src/main.rs:11:36
-   |
-11 |           let handle = thread::spawn(move || {
-   |                        ------------- ^------
-   |                        |             |
-   |  ______________________|_____________within this `{closure@src/main.rs:11:36: 11:43}`
-   | |                      |
-   | |                      required by a bound introduced by this call
-12 | |             let mut num = counter.lock().unwrap();
-13 | |
-14 | |             *num += 1;
-15 | |         });
-   | |_________^ `Rc<Mutex<i32>>` cannot be sent between threads safely
-   |
-   = help: within `{closure@src/main.rs:11:36: 11:43}`, the trait `Send` is not implemented for `Rc<Mutex<i32>>`, which is required by `{closure@src/main.rs:11:36: 11:43}: Send`
-note: required because it's used within this closure
-  --> src/main.rs:11:36
-   |
-11 |         let handle = thread::spawn(move || {
-   |                                    ^^^^^^^
-note: required by a bound in `spawn`
-  --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/thread/mod.rs:691:1
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `shared-state` (bin "shared-state") due to 1 previous error
-
-

Ничего себе, это сообщение об ошибке очень многословно! Вот важная часть, на которой следует сосредоточиться: ``Rc<Mutex> cannot be sent between threads safely. Сборщик также сообщает нам причину: the trait Sendis not implemented forRc<Mutex> . Мы поговорим о Send в следующем разделе: это один из особенностей, который заверяет, что виды которые мы используем с потоками, предназначены для использования в многопоточном коде.

-

К сожалению, Rc<T> небезопасен для совместного использования между потоками. Когда Rc<T> управляет счётчиком ссылок, он добавляется значение к счётчику для каждого вызова clone и вычитается значение из счётчика, когда каждое клонированное значение удаляется при выходе из области видимости. Но он не использует простейшие многопоточности, чтобы обеспечить, что изменения в подсчёте не могут быть прерваны другим потоком. Это может привести к неправильным подсчётам - незначительным ошибкам, которые в свою очередь, могут привести к утечкам памяти или удалению значения до того, как мы отработали с ним. Нам нужен вид точно такой же как Rc<T>, но который позволяет изменять счётчик ссылок безопасно из разных потоков.

-

Атомарный счётчик ссылок Arc<T>

-

К счастью, Arc<T> является видом подобным виду Rc<T>, который безопасен для использования в случаейх многопоточности. Буква А означает атомарное, что означает вид ссылка подсчитываемая атомарно. Atomics - это дополнительный вид простейших для многопоточности, который мы не будем здесь подробно описывать: дополнительную сведения смотрите в документации встроенной библиотеки для std::sync::atomic. На данный мгновение вам просто нужно знать, что atomics работают как простые виды, но безопасны для совместного использования между потоками.

-

Вы можете спросить, почему все простые виды не являются атомарными и почему обычные виды библиотек не выполнены для использования вместе с видом Arc<T> по умолчанию. Причина в том, что безопасность потоков сопровождается снижением производительности, которое вы хотите платить только тогда, когда вам это действительно нужно. Если вы просто выполняете действия со значениями в одном потоке, то ваш код может работать быстрее, если он не должен обеспечивать заверения предоставляемые atomics.

-

Давайте вернёмся к нашему примеру: виды Arc<T> и Rc<T> имеют одинаковый API, поэтому мы исправляем нашу программу, заменяя вид в строках use, вызове new и вызове clone. Код в приложении 16-15, наконец собирается и запустится:

-

Файл: src/main.rs

-
use std::sync::{Arc, Mutex};
-use std::thread;
-
-fn main() {
-    let counter = Arc::new(Mutex::new(0));
-    let mut handles = vec![];
-
-    for _ in 0..10 {
-        let counter = Arc::clone(&counter);
-        let handle = thread::spawn(move || {
-            let mut num = counter.lock().unwrap();
-
-            *num += 1;
-        });
-        handles.push(handle);
-    }
-
-    for handle in handles {
-        handle.join().unwrap();
-    }
-
-    println!("Result: {}", *counter.lock().unwrap());
-}
-

Приложение 16-15: Использование вида Arc<T> для обёртывания Mutex<T>, теперь несколько потоков могут совместно владеть мьютексом

-

Код напечатает следующее:

- -
Result: 10
-
-

Мы сделали это! Мы посчитали от 0 до 10, что может показаться не очень впечатляющим, но это позволило больше узнать про Mutex<T> и безопасность потоков. Вы также можете использовать устройство этой программы для выполнения более сложных действий, чем просто увеличение счётчика. Используя эту стратегию, вы можете разделить вычисления на независимые части, разделить эти части на потоки, а затем использовать Mutex<T>, чтобы каждый поток обновлял конечный итог своей частью кода.

-

Обратите внимание, что если вы выполняете простые числовые действия, то существуют виды более простые, чем Mutex<T>, которые предоставляет звено std::sync::atomic встроенной библиотеки. Эти виды обеспечивают безопасный, одновременный, атомарный доступ к простым видам. Мы решили использовать Mutex<T> с простым видом в этом примере, чтобы подробнее рассмотреть, как работает Mutex<T>.

-

Сходства RefCell<T> / Rc<T> и Mutex<T> / Arc<T>

-

Вы могли заметить, что counter сам по себе не изменяемый (immutable), но мы можем получить изменяемую ссылку на значение внутри него; это означает, что Mutex<T> обеспечивает внутреннюю изменяемость, также как и семейство Cell видов. Мы использовали RefCell<T> в главе 15, чтобы получить возможность изменять содержимое внутри Rc<T>, теперь подобным образом мы используем Mutex<T> для изменения содержимого внутри Arc<T> .

-

Ещё одна подробность, на которую стоит обратить внимание: Ржавчина не может защитить вас от всевозможных логических ошибок при использовании Mutex<T>. Вспомните в главе 15, что использование Rc<T> сопряжено с риском создания ссылочной зацикленности, где два значения Rc<T> ссылаются друг на друга, что приводит к утечкам памяти. Подобным образом, Mutex<T> сопряжён с риском создания взаимных блокировок (deadlocks). Это происходит, когда действия необходимо заблокировать два ресурса и каждый из двух потоков получил одну из блокировок, заставляя оба потока ждать друг друга вечно. Если вам важна направление взаимных блокировок, попробуйте создать программу Rust, которая её содержит; затем исследуйте стратегии устранения взаимных блокировок для мьютексов на любом языке и попробуйте выполнить их в Rust. Документация встроенной библиотеки для Mutex<T> и MutexGuard предлагает полезную сведения.

-

Мы завершим эту главу, рассказав о особенностях Send и Sync и о том, как мы можем использовать их с пользовательскими видами.

-

Расширенная многопоточность с помощью особенностей Sync и Send

-

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

-

Тем не менее, в язык встроены две подходы многопоточности: std::marker особенности Sync и Send.

-

Разрешение передачи во владение между потоками с помощью Send

-

Маркерный особенность Send указывает, что владение видом выполняющим Send, может передаваться между потоками. Почти каждый вид Ржавчина является видом Send, но есть некоторые исключения, вроде Rc<T>: он не может быть Send, потому что если вы клонировали значение Rc<T> и попытались передать владение клоном в другой поток, оба потока могут обновить счётчик ссылок одновременно. По этой причине Rc<T> выполнен для использования в однопоточных случаейх, когда вы не хотите платить за снижение производительности.

-

Следовательно, система видов Ржавчина и ограничений особенности заверяют, что вы никогда не сможете случайно небезопасно отправлять значение Rc<T> между потоками. Когда мы попытались сделать это в приложении 16-14, мы получили ошибку, the trait Send is not implemented for Rc<Mutex<i32>>. Когда мы переключились на Arc<T>, который является видом Send, то код собрался.

-

Любой вид полностью состоящий из видов Send самостоятельно помечается как Send. Почти все простые виды являются Send, кроме сырых указателей, которые мы обсудим в главе 19.

-

Разрешение доступа из нескольких потоков с Sync

-

Маркерный особенность Sync указывает, что на вид выполняющий Sync можно безопасно ссылаться из нескольких потоков. Другими словами, любой вид T является видом Sync, если &T (ссылка на T ) является видом Send, что означает что ссылку можно безопасно отправить в другой поток. Подобно Send, простые виды являются видом Sync, а виды полностью объединенные из видов Sync, также являются Sync видом.

-

Умный указатель Rc<T> не является Sync видом по тем же причинам, по которым он не является Send. Вид RefCell<T> (о котором мы говорили в главе 15) и семейство связанных видов Cell<T> не являются Sync. Выполнение проверки заимствования, которую делает вид RefCell<T> во время выполнения программы не является поточно-безопасной. Умный указатель Mutex<T> является видом Sync и может использоваться для совместного доступа из нескольких потоков, как вы уже видели в разделе «Совместное использование Mutex<T> между несколькими потоками» .

-

Выполнение Send и Sync вручную небезопасна

-

Поскольку виды созданные из особенностей Send и Sync самостоятельно также являются видами Send и Sync, мы не должны выполнить эти особенности вручную. Являясь маркерными особенностями у них нет никаких способов для выполнения. Они просто полезны для выполнения неизменных величин, связанных с многопоточностью.

-

Ручная выполнение этих особенностей включает в себя выполнение небезопасного кода Rust. Мы поговорим об использовании небезопасного кода Ржавчина в главе 19; на данный мгновение важная сведения заключается в том, что для создания новых многопоточных видов, не состоящих из частей Send и Sync необходимо тщательно продумать заверения безопасности. В Rustonomicon есть больше сведений об этих заверениях и о том как их соблюдать.

-

Итоги

-

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

-

Как упоминалось ранее, поскольку в языке Ржавчина очень мало того, с помощью чего можно управлять многопоточностью, многие решения выполнены в виде ящиков. Они развиваются быстрее, чем обычная библиотека, поэтому обязательно поищите в Интернете текущие современные ящики.

-

Обычная библиотека Ржавчина предоставляет потоки для передачи сообщений и виды умных указателей, такие как Mutex<T> и Arc<T>, которые можно безопасно использовать в многопоточных средах. Система видов и анализатор заимствований заверяют, что код использующий эти решения не будет содержать гонки данных или недействительные ссылки. Получив собирающийся код, вы можете быть уверены, что он будет успешно работать в нескольких потоках без ошибок, которые трудно обнаружить в других языках. Многопоточное программирование больше не является подходом, которую стоит опасаться: иди вперёд и сделай свои программы многопоточными безбоязненно!

-

Далее мы поговорим об идиоматичных способах расчетов неполадок и внутреннего выстраивания -решений по мере усложнения ваших программ на Rust. Кроме того, мы обсудим как идиомы Ржавчина связаны с теми, с которыми вы, возможно, знакомы по предметно-направленному программированию.

-

Возможности предметно-направленного программирования в Rust

-

Предметно-направленное программирование (ООП) — это способ построения программ. Предметы, как программная подход, были введены в язык программирования Simula в 1960-х годах. Эти предметы повлияли на архитектуру программирования Алана Кея, в которой предметы передают сообщения друг другу. Чтобы описать эту архитектуру, он ввёл понятие предметно-направленное программирование в 1967 году. Есть много состязающихся определений ООП, и по некоторым из этих определений Ржавчина является предметно-направленным, а по другим — нет. В этой главе мы рассмотрим некоторые свойства, которые обычно считаются предметно-направленными, и то, как эти свойства транслируются в идиомы языка Rust. Затем мы покажем, как выполнить образец предметно-направленного разработки в Rust, и обсудим соглашения между этим исходом и решением, использующим вместо этого некоторые сильные стороны Rust.

-

Свойства предметно-направленных языков

-

В сообществе программистов нет единого мнения о том, какими свойствами должен обладать язык, чтобы считаться предметно-направленным. На Ржавчина повлияли многие парадигмы программирования, включая ООП - например, в главе 13 мы изучали особенности, пришедшие из функционального программирования. Однозначно можно утверждать, что ООП-языкам присущи следующие присущие особенности: предметы, инкапсуляция и наследование. Давайте рассмотрим, что каждая из них означает и поддерживает ли их Rust.

-

Предметы содержат данные и поведение

-

Книга Приёмы предметно-направленного разработки. Образцы разработки Erich Gamma, Richard Helm, Ralph Johnson, и John Vlissides (Addison-Wesley Professional, 1994), в просторечии называемая Книга банды четырёх, представляет собой сборник примеров предметно-направленного разработки. В ней даётся следующее определение ООП:

-
-

Предметно-направленные программы состоят из предметов. Предмет представляет собой сущность, своего рода дополнение, с данными и процедурами, которые работают с этими данными. Процедуры обычно называются способами или действиеми.

-
-

В соответствии с этим определением, Ржавчина является предметно-направленным языком - в устройствах и перечислениях содержатся данные, а в х impl определяются способы для них. Хотя устройства и перечисления, имеющие способы, не называются предметами, они обеспечивают возможность, соответствующую определению предметов в книге банды четырёх.

-

Инкапсуляция, скрывающая подробности выполнения

-

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

-

В главе 7 мы уже говорили о том, как управлять инкапсуляцией: мы можем использовать ключевое слово pub, чтобы определить, какие звенья, виды, функции и способы в нашем коде будут открытыми, а всё остальное по умолчанию будет закрытыми. Например, мы можем определить устройство AveragedCollection, в которой есть поле, содержащее вектор значений i32. Также, устройства будет иметь поле, содержащее среднее арифметическое чисел этого вектора, таким образом, среднее не нужно будет вычислять каждый раз, когда оно кому-то понадобится. Другими словами, AveragedCollection будет кэшировать вычисленное среднее для нас. В приложении 17-1 приведено определение устройства AveragedCollection:

-

Файл: src/lib.rs

-
pub struct AveragedCollection {
-    list: Vec<i32>,
-    average: f64,
-}
-

Приложение 17-1: устройства AveragedCollection содержит список целых чисел и их среднее арифметическое.

-

Обратите внимание, что устройства помечена ключевым словом pub, что позволяет другому коду её использовать, однако, поля устройства остаются недоступными. Это важно, потому что мы хотим обеспечить обновление среднего значения при добавлении или удалении элемента из списка. Мы можем получить нужное поведение, определив в устройстве способы add, remove и average, как показано в примере 17-2:

-

Файл: src/lib.rs

-
pub struct AveragedCollection {
-    list: Vec<i32>,
-    average: f64,
-}
-
-impl AveragedCollection {
-    pub fn add(&mut self, value: i32) {
-        self.list.push(value);
-        self.update_average();
-    }
-
-    pub fn remove(&mut self) -> Option<i32> {
-        let result = self.list.pop();
-        match result {
-            Some(value) => {
-                self.update_average();
-                Some(value)
-            }
-            None => None,
-        }
-    }
-
-    pub fn average(&self) -> f64 {
-        self.average
-    }
-
-    fn update_average(&mut self) {
-        let total: i32 = self.list.iter().sum();
-        self.average = total as f64 / self.list.len() as f64;
-    }
-}
-

Приложение 17-2: Выполнение открытых способов add,remove, и average для AveragedCollection

-

Открытые способы add, remove и average являются единственным способом получить или изменить данные в образце AveragedCollection. Когда элемент добавляется в list способом add, или удаляется с помощью способа remove, код выполнения каждого из этих способов вызывает закрытый способ update_average, который позаботится об обновлении поля average.

-

Мы оставляем поля list и average закрытыми, чтобы внешний код не мог добавлять или удалять элементы непосредственно в поле list; в противном случае поле average может оказаться не согласовано при подобном вмешательстве. Способ average возвращает значение в поле average, что позволяет внешнему коду читать значение average, но не изменять его.

-

Поскольку мы инкапсулировали подробности выполнения устройства AveragedCollection, мы можем легко изменить такие особенности, как устройства данных, в будущем. Например, мы могли бы использовать HashSet<i32> вместо Vec<i32> для поля list. Благодаря тому, что ярлыки открытых способов add, remove и average остаются неизменными, код, использующий AveragedCollection, также не будет нуждаться в изменении. У нас бы не получилось этого достичь, если бы мы сделали поле list доступным внешнему коду: HashSet<i32> иVec<i32> имеют разные способы для добавления и удаления элементов, поэтому внешний код, вероятно, должен измениться, если он изменяет list напрямую.

-

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

-

Наследование как система видов и способ совместного использования кода

-

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

-

Если язык должен иметь наследование, чтобы быть предметно-направленным, то Ржавчина таким не является. Здесь нет способа определить устройство, наследующую поля и выполнения способов родительской устройства, без использования макроса.

-

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

-

Вы могли бы выбрать наследование по двум основным причинам. Одна из них - возможность повторного использования кода: вы можете выполнить определённое поведение для одного вида, а наследование позволит вам повторно использовать эту выполнение для другого вида. В Ржавчина для этого есть ограниченный способ, использующий выполнение способа особенности по умолчанию, который вы видели в приложении 10-14, когда мы добавили выполнение по умолчанию в способе summarize особенности Summary. Любой вид, выполняющий свойство Summary будет иметь доступный способ summarize без дополнительного кода. Это похоже на то, как родительский класс имеет выполнение способа, и класс-наследник тоже имеет выполнение способа. Мы также можем переопределить выполнение по умолчанию для способа summarize, когда выполняем особенность Summary, что похоже на дочерний класс, переопределяющий выполнение способа, унаследованного от родительского класса.

-

Вторая причина использования наследования относится к системе видов: чтобы иметь возможность использовать дочерний вид в тех же места, что и родительский. Эта возможность также называется полиморфизм и означает возможность подменять предметы во время исполнения, если они имеют одинаковые свойства.

-
-

Полиморфизм

-

Для многих людей полиморфизм является родственным наследования. Но на самом деле это более общая подход, относящаяся к коду, который может работать с данными нескольких видов. Обычно такими видами выступают подклассы при наследовании.

-

Вместо этого Ржавчина использует обобщённые виды для абстрагирования от видов, и ограничения особенностей (trait bounds) для указания того, какие возможности эти виды должны предоставлять. Это иногда называют ограниченным свойствоическим полиморфизмом.

-
-

Наследование, как подход к разработке, в последнее время утратило распространенность во многих языках программирования, поскольку часто существует риск, что мы будем наследовать код чаще, чем это необходимо. Подклассы не всегда должны обладать всеми свойствами родительского класса, но при использовании наследования другого исхода нет. Это может сделать внешний вид программы менее гибким. Кроме этого, появляется возможность вызова у подклассов способов, которые не имеют смысла или вызывают ошибки, потому что эти способы неприменимы к подклассу. Кроме того, в некоторых языках разрешается только одиночное наследование (т.е. подкласс может наследоваться только от одного класса), что ещё больше ограничивает гибкость разработки программы.

-

По этим причинам в Ржавчина применяется иной подход, с использованием особенностей-предметов вместо наследования. Давайте посмотрим как особенности-предметы выполняют полиморфизм в Rust.

-

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

-

В главе 8 мы упоминали, что одним из ограничений векторов является то, что они могут хранить элементы только одного вида. Мы создали обходное решение в приложении 8-9, где мы определили перечисление SpreadsheetCell в котором были исходы для хранения целых чисел, чисел с плавающей точкой и текста. Это означало, что мы могли хранить разные виды данных в каждой ячейке и при этом иметь вектор, представляющий строку из ячеек. Это очень хорошее решение, когда наши взаимозаменяемые элементы вектора являются видами с конечным набором, известным при сборки кода.

-

Однако иногда мы хотим, чтобы пользователь нашей библиотеки мог расширить набор видов, которые допустимы в именно случаи. Чтобы показать как этого добиться, мы создадим пример средства с графическим внешней оболочкой пользователя (GUI), который просматривает список элементов, вызывает способ draw для каждого из них, чтобы нарисовать его на экране - это обычная техника для средств GUI. Мы создадим библиотечный ящик с именем gui, содержащий устройство библиотеки GUI. Этот ящик мог бы включать некоторые готовые виды для использования, такие как Button или TextField. Кроме того, пользователи такого ящика gui захотят создавать свои собственные виды, которые могут быть нарисованы: например, кто-то мог бы добавить вид Image, а кто-то другой добавить вид SelectBox.

-

Мы не будем выполнить полноценную библиотеку GUI для этого примера, но покажем, как её части будут подходить друг к другу. На мгновение написания библиотеки мы не можем знать и определить все виды, которые могут захотеть создать другие программисты. Но мы знаем, что gui должен отслеживать множество значений различных видов и ему нужно вызывать способ draw для каждого из этих значений различного вида. Ему не нужно точно знать, что произойдёт, когда вызывается способ draw, просто у значения будет доступен такой способ для вызова.

-

Чтобы сделать это на языке с наследованием, можно определить класс с именем Component у которого есть способ с названием draw. Другие классы, такие как Button, Image и SelectBox наследуются от Component и следовательно, наследуют способ draw. Каждый из них может переопределить выполнение способа draw, чтобы определить своё пользовательское поведение, но площадка может обрабатывать все виды, как если бы они были образцами Component и вызывать draw у них. Но поскольку в Ржавчина нет наследования, нам нужен другой способ внутренне выстроить

-

gui библиотеку, чтобы позволить пользователям расширять её новыми видами.

-

Определение особенности для общего поведения

-

Чтобы выполнить поведение, которое мы хотим иметь в gui, определим особенность с именем Draw, который будет содержать один способ с названием draw. Затем мы можем определить вектор, который принимает особенность-предмет. Особенность-предмет указывает как на образец вида, выполняющего указанный особенность, так и на внутреннюю таблицу, используемую для поиска способов особенности указанного вида во время выполнения. Мы создаём особенность-предмет в таком порядке: используем какой-нибудь вид указателя, например ссылку & или умный указатель Box<T>, затем ключевое слово dyn, а затем указываем соответствующий особенность. (Мы будем говорить о причине того, что особенность-предметы должны использовать указатель в разделе "Виды изменяемого размера и особенность Sized " главы 19). Мы можем использовать особенность-предметы вместо гибкого или определенного вида. Везде, где мы используем особенность-предмет, система видов Ржавчина проверит во время сборки, что любое значение, используемое в этом среде, будет выполнить нужный особенность у особенность-предмета. Следовательно, нам не нужно знать все возможные виды во время сборки.

-

Мы упоминали, что в Ржавчина мы воздерживаемся называть устройства и перечисления «предметами», чтобы отличать их от предметов в других языках. В устройстве или перечислении данные в полях устройства и поведение в разделах impl разделены, тогда как в других языках данные и поведение объединены в одну подход, часто обозначающуюся как предмет. Тем не менее, особенность-предметы являются более похожими на предметы на других языках, в том смысле, что они сочетают в себе данные и поведение. Но особенность-предметы отличаются от привычных предметов тем, что не позволяют добавлять данные к особенность-предмету. Особенность-предметы обычно не настолько полезны, как предметы в других языках: их определенная цель - обеспечить абстракцию через общее поведение.

-

В приложении 17.3 показано, как определить особенность с именем Draw с помощью одного способа с именем draw:

-

Файл: src/lib.rs

-
pub trait Draw {
-    fn draw(&self);
-}
-

Приложение 17-3: Определение особенности Draw

-

Этот правила написания должен выглядеть знакомым из наших дискуссий о том, как определять особенности в главе 10. Далее следует новый правила написания: в приложении 17.4 определена устройства с именем Screen, которая содержит вектор с именем components. Этот вектор имеет вид Box<dyn Draw>, который и является особенность-предметом; это замена для любого вида внутри Box который выполняет особенность Draw.

-

Файл: src/lib.rs

-
pub trait Draw {
-    fn draw(&self);
-}
-
-pub struct Screen {
-    pub components: Vec<Box<dyn Draw>>,
-}
-

Приложение 17-4: Определение устройства Screen с полем components, которое является вектором особенность-предметов, которые выполняют особенность Draw

-

В устройстве Screen, мы определим способ run, который будет вызывать способ draw каждого элемента вектора components, как показано в приложении 17-5:

-

Файл: src/lib.rs

-
pub trait Draw {
-    fn draw(&self);
-}
-
-pub struct Screen {
-    pub components: Vec<Box<dyn Draw>>,
-}
-
-impl Screen {
-    pub fn run(&self) {
-        for component in self.components.iter() {
-            component.draw();
-        }
-    }
-}
-

Приложение 17-5: Выполнение способа run у устройства Screen, который вызывает способ draw каждого составляющих из вектора

-

Это работает иначе, чем определение устройства, которая использует свойство общего вида с ограничениями особенности. Обобщённый свойство вида может быть заменён только одним определенным видом, тогда как особенность-предметы позволяют нескольким определенным видам замещать особенность-предмет во время выполнения. Например, мы могли бы определить устройство Screen используя общий вид и ограничение особенности, как показано в приложении 17-6:

-

Файл: src/lib.rs

-
pub trait Draw {
-    fn draw(&self);
-}
-
-pub struct Screen<T: Draw> {
-    pub components: Vec<T>,
-}
-
-impl<T> Screen<T>
-where
-    T: Draw,
-{
-    pub fn run(&self) {
-        for component in self.components.iter() {
-            component.draw();
-        }
-    }
-}
-

Приложение 17-6: Иная выполнение устройства Screen и способа run, используя обобщённый вид и ограничения особенности

-

Это исход ограничивает нас образцом Screen, который имеет список составляющих всех видов Button или всех видов TextField. Если у вас когда-либо будут только однородные собрания, использование обобщений и ограничений особенности является предпочтительным, поскольку определения будут мономорфизированы во время сборки для использования с определенными видами.

-

С другой стороны, с помощью способа, использующего особенность-предметы, один образец Screen может содержать Vec<T> который содержит Box<Button>, также как и Box<TextField>. Давайте посмотрим как это работает, а затем поговорим о влиянии на производительность во время выполнения.

-

Выполнения особенности

-

Теперь мы добавим несколько видов, выполняющих особенность Draw. Мы объявим вид Button. Опять же, действительная выполнение библиотеки GUI выходит за рамки этой книги, поэтому тело способа draw не будет иметь никакой полезной выполнения. Чтобы представить, как может выглядеть такая выполнение, устройства Button может иметь поля для width, height и label, как показано в приложении 17-7:

-

Файл: src/lib.rs

-
pub trait Draw {
-    fn draw(&self);
-}
-
-pub struct Screen {
-    pub components: Vec<Box<dyn Draw>>,
-}
-
-impl Screen {
-    pub fn run(&self) {
-        for component in self.components.iter() {
-            component.draw();
-        }
-    }
-}
-
-pub struct Button {
-    pub width: u32,
-    pub height: u32,
-    pub label: String,
-}
-
-impl Draw for Button {
-    fn draw(&self) {
-        // code to actually draw a button
-    }
-}
-

Приложение 17-7: Устройства Button выполняет особенность Draw

-

Поля width, height и label устройства Button будут отличаться от, например, полей других составляющих вроде вида TextField, которая могла бы иметь те же поля плюс поле placeholder. Каждый из видов, который мы хотим нарисовать на экране будет выполнить особенность Draw, но будет использовать отличающийся код способа draw для определения как именно рисовать определенный вид, например Button в этом примере (без действительного кода GUI, который выходит за рамки этой главы). Например, вид Button может иметь дополнительный разделimpl, содержащий способы, относящиеся к тому, что происходит, когда пользователь нажимает кнопку. Эти исходы способов не будут применяться к видам вроде TextField.

-

Если кто-то использующий нашу библиотеку решает выполнить устройство SelectBox, которая имеет width, height и поля options, он выполняет также и особенность Draw для вида SelectBox, как показано в приложении 17-8:

-

Файл: src/main.rs

-
use gui::Draw;
-
-struct SelectBox {
-    width: u32,
-    height: u32,
-    options: Vec<String>,
-}
-
-impl Draw for SelectBox {
-    fn draw(&self) {
-        // code to actually draw a select box
-    }
-}
-
-fn main() {}
-

Приложение 17-8: Другой ящик, использующий gui и выполняющий особенность Draw у устройства SelectBox

-

Пользователь нашей библиотеки теперь может написать свою функцию main для создания образца Screen. К образцу Screen он может добавить SelectBox и Button, поместив каждый из них в Box<T>, чтобы он стал особенность-предметом. Затем он может вызвать способ run у образца Screen, который вызовет draw для каждого из составляющих. Приложение 17-9 показывает эту выполнение:

-

Файл: src/main.rs

-
use gui::Draw;
-
-struct SelectBox {
-    width: u32,
-    height: u32,
-    options: Vec<String>,
-}
-
-impl Draw for SelectBox {
-    fn draw(&self) {
-        // code to actually draw a select box
-    }
-}
-
-use gui::{Button, Screen};
-
-fn main() {
-    let screen = Screen {
-        components: vec![
-            Box::new(SelectBox {
-                width: 75,
-                height: 10,
-                options: vec![
-                    String::from("Yes"),
-                    String::from("Maybe"),
-                    String::from("No"),
-                ],
-            }),
-            Box::new(Button {
-                width: 50,
-                height: 10,
-                label: String::from("OK"),
-            }),
-        ],
-    };
-
-    screen.run();
-}
-

Приложение 17-9: Использование особенность-предметов для хранения значений разных видов, выполняющих один и тот же особенность

-

Когда мы писали библиотеку, мы не знали, что кто-то может добавить вид SelectBox, но наша выполнение Screen могла работать с новым видом и рисовать его, потому что SelectBox выполняет особенность Draw, что означает, что он выполняет способ draw.

-

Эта подход, касающаяся только сообщений, на которые значение отвечает, в отличие от определенного вида у значения, подобна подходы duck typing в изменяемых строго определенных языках: если что-то ходит как утка и крякает как утка, то она должна быть утка! В выполнения способа run у Screen в приложении 17-5, run не нужно знать каким будет определенный вид каждого составляющих. Он не проверяет, является ли составляющая образцом Button или SelectBox, он просто вызывает способ draw составляющих. Указав Box<dyn Draw> в качестве вида значений в векторе components, мы определили Screen для значений у которых мы можем вызвать способ draw.

-

Преимущество использования особенность-предметов и системы видов Ржавчина для написания кода, похожего на код с использованием подходы duck typing состоит в том, что нам не нужно во время выполнения проверять выполняет ли значение в векторе определенный способ или беспокоиться о получении ошибок, если значение не выполняет способ, мы все равно вызываем способ. Ржавчина не собирает наш код, если значения не выполняют особенность, который нужен особенность-предмета..

-

Например, в приложении 17-10 показано, что произойдёт, если мы попытаемся создать Screen с String в качестве его составляющих:

-

Файл: src/main.rs

-
use gui::Screen;
-
-fn main() {
-    let screen = Screen {
-        components: vec![Box::new(String::from("Hi"))],
-    };
-
-    screen.run();
-}
-

Приложение 17-10: Попытка использования вида, который не выполняет особенность для особенность-предмета

-

Мы получим ошибку, потому что String не выполняет особенность Draw:

-
$ cargo run
-   Compiling gui v0.1.0 (file:///projects/gui)
-error[E0277]: the trait bound `String: Draw` is not satisfied
- --> src/main.rs:5:26
-  |
-5 |         components: vec![Box::new(String::from("Hi"))],
-  |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Draw` is not implemented for `String`
-  |
-  = help: the trait `Draw` is implemented for `Button`
-  = note: required for the cast from `Box<String>` to `Box<dyn Draw>`
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `gui` (bin "gui") due to 1 previous error
-
-

Эта ошибка даёт понять, что либо мы передаём в составляющая Screen что-то, что мы не собирались передавать и мы тогда должны передать другой вид, либо мы должны выполнить особенность Draw у вида String, чтобы Screen мог вызывать draw у него.

-

Особенность-предметы выполняют изменяемую управление (связывание)

-

Вспомните, в разделе «Производительность кода, использующего обобщённые виды» в главе 10 наше обсуждение этапа мономорфизации, выполняемого сборщиком, когда мы используем ограничения особенностей для обобщённых видов: сборщик порождает частные выполнения функций и способов для каждого определенного вида, который мы применяем для свойства обобщённого вида. Код, который получается в итоге мономорфизации, выполняет постоянную управление , то есть когда сборщик знает, какой способ вы вызываете во время сборки. Это противоположно изменяемой управления, когда сборщик не может определить во время сборки, какой способ вы вызываете. В случае изменяемой управления сборщик создает код, который во время выполнения определит, какой способ нужно вызвать.

-

Когда мы используем особенность-предметы, Ржавчина должен использовать изменяемую управление. Сборщик не знает всех видов, которые могут быть использованы с кодом, использующим особенность-предметы, поэтому он не знает, какой способ выполнен для какого вида при вызове. Вместо этого, во время выполнения, Ржавчина использует указатели внутри особенность-предмета. чтобы узнать какой способ вызвать. Такой поиск вызывает дополнительные затраты во время исполнения, которые не требуются при постоянной управления. Изменяемая управление также не позволяет сборщику выбрать встраивание кода способа, что в свою очередь делает невозможными некоторые переработки. Однако мы получили дополнительную гибкость в коде, который мы написали в приложении 17-5, и которую смогли поддержать в приложении 17-9, поэтому все "за" и "против" нужно рассматривать в совокупности.

-

Выполнение предметно-направленного образца разработки

-

Образец "Состояние" — это предметно-направленный образец разработки. Суть образца заключается в том, что мы определяем набор состояний, которые может иметь внутреннее значение. Состояния представлены набором предметов состояния, а поведение элемента изменяется в зависимости от его состояния. Мы рассмотрим пример устройства записи в блоге, в которой есть поле для хранения состояния, которое будет предметом состояния из набора «черновик», «обзор» или «обнародовано».

-

Предметы состояния имеют общую возможность: конечно в Ржавчина мы используем устройства и особенности, а не предметы и наследование. Каждый предмет состояния отвечает за своё поведение и сам определяет, когда он должен перейти в другое состояние. Элемент, который содержит предмет состояния, ничего не знает о различиях в поведении состояний или о том, когда одно состояние должно перейти в другое.

-

Преимуществом образца "Состояние" является то, что при изменении требований заказчика программы не требуется изменять код элемента, содержащего состояние, или код, использующий такой элемент. Нам нужно только обновить код внутри одного из предметов состояния, чтобы изменить его порядок действий, либо, возможно, добавить больше предметов состояния.

-

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

-

Окончательный возможности будет выглядеть так:

-
    -
  1. Запись в блоге создаётся как пустой черновик.
  2. -
  3. Когда черновик готов, запрашивается его проверка.
  4. -
  5. После проверки происходит обнародование записи.
  6. -
  7. Только обнародованные записи блога возвращают содержимое записи на печать, поэтому сообщения, не прошедшие проверку, не могут быть обнародованы случайно.
  8. -
-

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

-

В приложении 17-11 показан этот поток действий в виде кода: это пример использования API, который мы собираемся выполнить в библиотеке (ящике) с именем blog. Он пока не собирается, потому что ящик blog ещё не создан.

-

Файл: src/main.rs

-
use blog::Post;
-
-fn main() {
-    let mut post = Post::new();
-
-    post.add_text("I ate a salad for lunch today");
-    assert_eq!("", post.content());
-
-    post.request_review();
-    assert_eq!("", post.content());
-
-    post.approve();
-    assert_eq!("I ate a salad for lunch today", post.content());
-}
-

Приложение 17-11: Код, отображающий желаемое поведение, которое мы хотим получить в ящике blog

-

Мы хотим, чтобы пользователь мог создать новый черновик записи в блоге с помощью Post::new. Затем мы хотим разрешить добавление текста в запись блога. Если мы попытаемся получить содержимое записи сразу, до её проверки, мы не должны получить никакого текста на выходе, потому что запись все ещё является черновиком. Мы добавили утверждение (assert_eq!) в коде для опытных целей. Утверждение (assertion), что черновик записи блога должен возвращать пустую строку из способа content было бы отличным состоящим из звеньев проверкой, но мы не собираемся писать проверки для этого примера.

-

Далее мы хотим разрешить сделать запрос на проверку записи и хотим, чтобы content возвращал пустую строку, пока проверки не завершена. Когда запись пройдёт проверку, она должна быть обнародована, то есть при вызове content будет возвращён текст записи.

-

Обратите внимание, что единственный вид из ящика, с которым мы взаимодействуем - это вид Post. Этот вид будет использовать образец "Состояние" и будет содержать значение, которое будет являться одним из трёх предметов состояний, представляющих различные состояния, в которых может находиться запись: "черновик", "ожидание проверки" или "обнародовано". Управление переходом из одного состояния в другое будет осуществляться внутренней логикой вида Post. Состояния будут переключаться в итоге реакции на вызов способов образца Post пользователями нашей библиотеки, но пользователи не должны управлять изменениями состояния напрямую. Кроме того, пользователи не должны иметь возможность ошибиться с состояниями, например, обнародовать сообщение до его проверки.

-

Определение Post и создание нового образца в состоянии черновика

-

Приступим к выполнения библиотеки! Мы знаем, что нам нужна открытая устройства Post, хранящая некоторое содержимое, поэтому мы начнём с определения устройства и связанной с ней открытой функцией new для создания образца Post, как показано в приложении 17-12. Мы также сделаем закрытый особенность State, который будет определять поведение, которое должны будут иметь все предметы состояний устройства Post.

-

Затем Post будет содержать особенность-предмет Box<dyn State> внутри Option<T> в закрытом поле state для хранения предмета состояния. Чуть позже вы поймёте, зачем нужно использовать Option<T> .

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-}
-
-trait State {}
-
-struct Draft {}
-
-impl State for Draft {}
-

Приложение 17-12. Определение устройства Post и функции new, которая создаёт новый образец Post, особенности State и устройства Draft

-

Особенность State определяет поведение, совместно используемое различными состояниями поста. Все предметы состояний (Draft - "черновик", PendingReview - "ожидание проверки" и Published - "обнародовано") будут выполнить особенность State. Пока у этого особенности нет никаких способов, и мы начнём с определения состояния Draft, просто потому, что это первое состояние, с которого, как мы хотим, обнародование будет начинать свой путь.

-

Когда мы создаём новый образец Post, мы устанавливаем его поле state в значение Some, содержащее Box. Этот Box указывает на новый образец устройства Draft. Это заверяет, что всякий раз, когда мы создаём новый образец Post, он появляется как черновик. Поскольку поле state в устройстве Post является закрытым, нет никакого способа создать Post в каком-либо другом состоянии! В функции Post::new мы объявим поле content новой пустой строкой вида String.

-

Хранение текста содержимого записи

-

В приложении 17-11 показано, что мы хотим иметь возможность вызывать способ add_text и передать ему &str, которое добавляется к текстовому содержимому записи блога. Мы выполняем эту возможность как способ, а не делаем поле content открыто доступным, используя pub. Это означает, что позже мы сможем написать способ, который будет управлять, как именно читаются данные из поля content. Способ add_text довольно прост, поэтому давайте добавим его выполнение в разделimpl Postприложения 17-13:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    // --snip--
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-}
-
-trait State {}
-
-struct Draft {}
-
-impl State for Draft {}
-

Приложение 17-13. Выполнение add_text для добавления текста к content (содержимому записи)

-

Способ add_text принимает изменяемую ссылку на self, потому что мы меняем образец Post, для которого вызываем add_text. Затем мы вызываем push_str для String у поля content и передаём text переменнаяом для добавления к сохранённому content. Это поведение не зависит от состояния, в котором находится запись, таким образом оно не является частью образца "Состояние". Способ add_text вообще не взаимодействует с полем state, но это часть поведения, которое мы хотим поддерживать.

-

Убедимся, что содержание черновика будет пустым

-

Даже после того, как мы вызвали add_text и добавили некоторый содержание в нашу запись, мы хотим, чтобы способ content возвращал пустой отрывок строки, так как запись всё ещё находится в черновом состоянии, как это показано в строке 7 приложения 17-11. А пока давайте выполняем способ content наиболее простым способом, который будет удовлетворять этому требованию: будем всегда возвращать пустой отрывок строки. Мы изменим код позже, как только выполняем возможность изменить состояние записи, чтобы она могла бы быть обнародована. Пока что записи могут находиться только в черновом состоянии, поэтому содержимое записи всегда должно быть пустым. Приложение 17-14 показывает такую выполнение-заглушку:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    // --snip--
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn content(&self) -> &str {
-        ""
-    }
-}
-
-trait State {}
-
-struct Draft {}
-
-impl State for Draft {}
-

Приложение 17-14. Добавление выполнения-заглушки для способа content в Post, которая всегда возвращает пустой отрывок строки.

-

С добавленным таким образом способом content всё в приложении 17-11 работает, как задумано, вплоть до строки 7.

-

Запрос на проверку записи меняет её состояние

-

Далее нам нужно добавить возможность для запроса проверки записи, который должен изменить её состояние с Draft на PendingReview. Приложение 17-15 показывает такой код:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    // --snip--
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn content(&self) -> &str {
-        ""
-    }
-
-    pub fn request_review(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.request_review())
-        }
-    }
-}
-
-trait State {
-    fn request_review(self: Box<Self>) -> Box<dyn State>;
-}
-
-struct Draft {}
-
-impl State for Draft {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        Box::new(PendingReview {})
-    }
-}
-
-struct PendingReview {}
-
-impl State for PendingReview {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-

Приложение 17-15. Выполнение способов request_review в устройстве Post и особенности State

-

Мы добавляем в Post открытый способ с именем request_review ("запросить проверку"), который будет принимать изменяемую ссылку на self. Затем мы вызываем внутренний способ request_review для текущего состояния Post, и этот второй способ request_review поглощает текущее состояние и возвращает новое состояние.

-

Мы добавляем способ request_review в особенность State; все виды, выполняющие этот особенность, теперь должны будут выполнить способ request_review. Обратите внимание, что вместо self, &self или &mut self в качестве первого свойства способа у нас указан self: Box<Self>. Этот правила написания означает, что способ действителен только при его вызове с обёрткой Box, содержащей наш вид. Этот правила написания становится владельцем Box<Self>, делая старое состояние недействительным, поэтому значение состояния Post может быть преобразовано в новое состояние.

-

Чтобы поглотить старое состояние, способ request_review должен стать владельцем значения состояния. Это место, где приходит на помощь вид Option поля state записи Post: мы вызываем способ take, чтобы забрать значение Some из поля state и оставить вместо него значение None, потому что Ржавчина не позволяет иметь необъявленные поля в устройствах. Это позволяет перемещать значение state из Post, а не заимствовать его. Затем мы установим новое значение state как итог этой действия.

-

Нам нужно временно установить state в None, вместо того, чтобы установить его напрямую с помощью кода вроде self.state = self.state.request_review();. Нам нужно завладеть значением поля state. Это даст нам заверение, что Post не сможет использовать старое значение state после того, как мы преобразовали его в новое состояние.

-

Способ request_review в Draft должен вернуть новый образец новой устройства PendingReview, обёрнутый в Box. Эта устройства будет представлять состояние, в котором запись ожидает проверки. Устройства PendingReview также выполняет способ request_review, но не выполняет никаких преобразований. Она возвращает сама себя, потому что, когда мы запрашиваем проверку записи, уже находящейся в состоянии PendingReview, она всё так же должна продолжать оставаться в состоянии PendingReview.

-

Теперь мы начинаем видеть преимущества образца "Состояние": способ request_review для Post одинаков, он не зависит от значения state. Каждое состояние само несёт ответственность за свои действия.

-

Оставим способ content у Post таким как есть, возвращающим пустой отрывок строки. Теперь мы можем иметь Post как в состоянии PendingReview, так и в состоянии Draft, но мы хотим получить такое же поведение в состоянии PendingReview. Приложение 17-11 теперь работает до строки 10!

- -

-

Добавление approve для изменения поведения content

-

Способ approve ("одобрить") будет подобен способу request_review: он будет устанавливать у state значение, которое должна иметь запись при её одобрении, как показано в приложении 17-16:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    // --snip--
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn content(&self) -> &str {
-        ""
-    }
-
-    pub fn request_review(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.request_review())
-        }
-    }
-
-    pub fn approve(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.approve())
-        }
-    }
-}
-
-trait State {
-    fn request_review(self: Box<Self>) -> Box<dyn State>;
-    fn approve(self: Box<Self>) -> Box<dyn State>;
-}
-
-struct Draft {}
-
-impl State for Draft {
-    // --snip--
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        Box::new(PendingReview {})
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-
-struct PendingReview {}
-
-impl State for PendingReview {
-    // --snip--
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        Box::new(Published {})
-    }
-}
-
-struct Published {}
-
-impl State for Published {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-

Приложение 17-16. Выполнение способа approve для вида Post и особенности State

-

Мы добавляем способ approve в особенность State, добавляем новую устройство, которая выполняет этот особенность State и устройство для состояния Published.

-

Подобно тому, как работает request_review для PendingReview, если мы вызовем способ approve для Draft, он не будет иметь никакого эффекта, потому что approve вернёт self. Когда мы вызываем для PendingReview способ approve, то он возвращает новый упакованный образец устройства Published. Устройства Published выполняет особенность State, и как для способа request_review, так и для способа approve она возвращает себя, потому что в этих случаях запись должна оставаться в состоянии Published.

-

Теперь нам нужно обновить способ content для Post. Мы хотим, чтобы значение, возвращаемое из content, зависело от текущего состояния Post, поэтому мы собираемся перенести часть возможности Post в способ content, заданный для state, как показано в приложении 17.17:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    // --snip--
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn content(&self) -> &str {
-        self.state.as_ref().unwrap().content(self)
-    }
-    // --snip--
-
-    pub fn request_review(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.request_review())
-        }
-    }
-
-    pub fn approve(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.approve())
-        }
-    }
-}
-
-trait State {
-    fn request_review(self: Box<Self>) -> Box<dyn State>;
-    fn approve(self: Box<Self>) -> Box<dyn State>;
-}
-
-struct Draft {}
-
-impl State for Draft {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        Box::new(PendingReview {})
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-
-struct PendingReview {}
-
-impl State for PendingReview {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        Box::new(Published {})
-    }
-}
-
-struct Published {}
-
-impl State for Published {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-

Приложение 17-17: Обновление способа content в устройстве Post для делегирования части возможности способу content устройства State

-

Поскольку наша цель состоит в том, чтобы сохранить все эти действия внутри устройств, выполняющих особенность State, мы вызываем способ content у значения в поле state и передаём образец обнародования (то есть self ) в качестве переменной. Затем мы возвращаем значение, которое нам выдаёт вызов способа content поля state.

-

Мы вызываем способ as_ref у Option, потому что нам нужна ссылка на значение внутри Option, а не владение значением. Поскольку state является видом Option<Box<dyn State>>, то при вызове способа as_ref возвращается Option<&Box<dyn State>>. Если бы мы не вызывали as_ref, мы бы получили ошибку, потому что мы не можем переместить state из заимствованного свойства &self функции.

-

Затем мы вызываем способ unwrap. Мы знаем, что этот способ здесь никогда не приведёт к со сбоемму завершению программы, так все способы Post устроены таким образом, что после их выполнения, в поле state всегда содержится значение Some. Это один из случаев, про которых мы говорили в разделе "Случаи, когда у вас больше сведений, чем у сборщика" главы 9 - случай, когда мы знаем, что значение None никогда не встретится, даже если сборщик не может этого понять.

-

Теперь, когда мы вызываем content у вида &Box<dyn State>, в действие вступает принудительное приведение (deref coercion) для & и Box, поэтому в конечном итоге способ content будет вызван для вида, который выполняет особенность State. Это означает, что нам нужно добавить способ content в определение особенности State, и именно там мы поместим логику для определения того, какое содержимое возвращать, в зависимости от текущего состояния, как показано в приложении 17-18:

-

Файл: src/lib.rs

-
pub struct Post {
-    state: Option<Box<dyn State>>,
-    content: String,
-}
-
-impl Post {
-    pub fn new() -> Post {
-        Post {
-            state: Some(Box::new(Draft {})),
-            content: String::new(),
-        }
-    }
-
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn content(&self) -> &str {
-        self.state.as_ref().unwrap().content(self)
-    }
-
-    pub fn request_review(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.request_review())
-        }
-    }
-
-    pub fn approve(&mut self) {
-        if let Some(s) = self.state.take() {
-            self.state = Some(s.approve())
-        }
-    }
-}
-
-trait State {
-    // --snip--
-    fn request_review(self: Box<Self>) -> Box<dyn State>;
-    fn approve(self: Box<Self>) -> Box<dyn State>;
-
-    fn content<'a>(&self, post: &'a Post) -> &'a str {
-        ""
-    }
-}
-
-// --snip--
-
-struct Draft {}
-
-impl State for Draft {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        Box::new(PendingReview {})
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-}
-
-struct PendingReview {}
-
-impl State for PendingReview {
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        Box::new(Published {})
-    }
-}
-
-struct Published {}
-
-impl State for Published {
-    // --snip--
-    fn request_review(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn approve(self: Box<Self>) -> Box<dyn State> {
-        self
-    }
-
-    fn content<'a>(&self, post: &'a Post) -> &'a str {
-        &post.content
-    }
-}
-

Приложение 17-18. Добавление способа content в особенность State

-

Мы добавляем выполнение по умолчанию способа content, который возвращает пустой отрывок строки. Это означает, что нам не придётся выполнить content в устройствах Draft и PendingReview. Устройства Published будет переопределять способ content и вернёт значение из post.content.

-

Обратите внимание, что для этого способа нам нужны изложении времени жизни, как мы обсуждали в главе 10. Мы берём ссылку на post в качестве переменной и возвращаем ссылку на часть этого post, поэтому время жизни возвращённой ссылки связано с временем жизни переменной post.

-

И вот, мы закончили - теперь всё из приложения 17-11 работает! Мы выполнили образец "Состояние", определяющий правила этапа работы с записью в блоге. Логика, связанная с этими правилами, находится в предмета. состояний, а не разбросана по всей устройстве Post.

-
-

Почему не перечисление?

-

Возможно, вам было важно, почему мы не использовали enum с различными возможными состояниями записи в качестве исходов. Это, безусловно, одно из возможных решений. Попробуйте его выполнить и сравните конечные итоги, чтобы выбрать, какой из исходов вам больше нравится! Одним из недостатков использования перечисления является то, что в каждом месте, где проверяется значение перечисления, потребуется выражение match или что-то подобное для обработки всех возможных исходов. Возможно в этом случае нам придётся повторять больше кода, чем это было в решении с особенность-предметом.

-
-

Соглашенияы образца "Состояние"

-

Мы показали, что Ржавчина способен выполнить предметно-направленный образец "Состояние" для инкапсуляции различных видов поведения, которые должна иметь запись в каждом состоянии. Способы в Post ничего не знают о различных видах поведения. При такой согласования кода, нам достаточно взглянуть только на один его участок, чтобы узнать отличия в поведении обнародованной обнародования: в выполнение особенности State у устройства Published.

-

Если бы мы захотели создать иную выполнение, не использующую образец состояния, мы могли бы вместо этого использовать выражения match в способах Post или даже в main, которые бы проверяли состояние записи и изменяли поведение в этих местах. Это приведёт к тому, что нам придётся в нескольких местах исследовать все следствия того, что пост перешёл в состояние "обнародовано"! И эта нагрузка будет только увеличиваться по мере добавления новых состояний: для каждого из этих выражений match потребуются дополнительные ответвления.

-

С помощью образца "Состояние" способы Post и участки, где мы используем Post, не требуют использования выражений match, а для добавления нового состояния нужно только добавить новую устройство и выполнить способы особенности у одной этой устройства.

-

Выполнение с использованием образца "Состояние" легко расширить для добавления новой возможности. Чтобы увидеть, как легко поддерживать код, использующий данный образец, попробуйте выполнить некоторые из предложений ниже:

-
    -
  • Добавьте способ reject, который изменяет состояние обнародования с PendingReview обратно на Draft.
  • -
  • Потребуйте два вызова способа approve, прежде чем переводить состояние в Published.
  • -
  • Разрешите пользователям добавлять текстовое содержимое только тогда, когда обнародование находится в состоянии Draft. Подсказка: пусть предмет состояния решает, можно ли менять содержимое, но не отвечает за изменение Post.
  • -
-

Одним из недостатков образца "Состояние" является то, что поскольку состояния сами выполняют переходы между собой, некоторые из состояний получаются связанными друг с другом. Если мы добавим другое состояние между PendingReview и Published, например Scheduled ("расчитано наперед"), то придётся изменить код в PendingReview, чтобы оно теперь переходило в Scheduled. Если бы не нужно было менять PendingReview при добавлении нового состояния, было бы меньше работы, но это означало бы, что мы переходим на другой образец разработки.

-

Другим недостатком является то, что мы сделали повторение некоторую логику. Чтобы устранить некоторое повторение, мы могли бы попытаться сделать выполнения по умолчанию для способов request_review и approve особенности State, которые возвращают self; однако это нарушило бы безопасность предмета. потому что особенность не знает, каким определенно будет self. Мы хотим иметь возможность использовать State в качестве особенность-предмета. поэтому нам нужно, чтобы его способы были предметно-безопасными.

-

Другое повторение включает в себя схожие выполнения способов request_review и approve у Post. Оба способа делегируют выполнения одного и того же способа значению поля state вида Option и устанавливают итогом новое значение поля state. Если бы у Post было много способов, которые следовали этому образцу, мы могли бы рассмотреть определение макроса для устранения повторения (смотри раздел "Макросы" в главе 19).

-

Выполняя образец "Состояние" точно так, как он определён для предметно-направленных языков, мы не настолько полно используем преимущества Rust, как могли бы. Давайте посмотрим на некоторые изменения, которые мы можем внести в ящик blog, чтобы недопустимые состояния и переходы превратить в ошибки времени сборки.

-

Кодирование состояний и поведения в виде видов

-

Мы покажем вам, как переосмыслить образец "Состояние", чтобы получить другой набор соглашений. Вместо того, чтобы полностью инкапсулировать состояния и переходы, так, чтобы внешний код не знал о них, мы будем кодировать состояния с помощью разных видов. Следовательно, система проверки видов Ржавчина предотвратит попытки использовать черновые обнародования, там где разрешены только обнародованные обнародования, вызывая ошибки сборки.

-

Давайте рассмотрим первую часть main в приложении 17-11:

-

Файл: src/main.rs

-
use blog::Post;
-
-fn main() {
-    let mut post = Post::new();
-
-    post.add_text("I ate a salad for lunch today");
-    assert_eq!("", post.content());
-
-    post.request_review();
-    assert_eq!("", post.content());
-
-    post.approve();
-    assert_eq!("I ate a salad for lunch today", post.content());
-}
-

Мы по-прежнему поддерживаем создание новых сообщений в состоянии "черновика" с помощью способа Post::new и возможность добавлять текст к содержимому обнародования. Но вместо способа content у чернового сообщения, возвращающего пустую строку, мы сделаем так, что у черновых сообщений вообще не будет способа content. Таким образом, если мы попытаемся получить содержимое черновика, мы получим ошибку сборщика, сообщающую, что способ не существует. В итоге мы не сможем случайно отобразить черновик содержимого записи в работающей программе, потому что этот код даже не собирается. В приложении 17-19 показано определение устройств Post и DraftPost, а также способов для каждой из них:

-

Файл: src/lib.rs

-
pub struct Post {
-    content: String,
-}
-
-pub struct DraftPost {
-    content: String,
-}
-
-impl Post {
-    pub fn new() -> DraftPost {
-        DraftPost {
-            content: String::new(),
-        }
-    }
-
-    pub fn content(&self) -> &str {
-        &self.content
-    }
-}
-
-impl DraftPost {
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-}
-

Приложение 17-19: Устройства Post с способом content и устройства DraftPost без способа content

-

Обе устройства, Post и DraftPost, имеют закрытое поле content, в котором хранится текст сообщения блога. Устройства больше не содержат поле state, потому что мы перемещаем кодирование состояния в виды устройств. Устройства Post будет представлять обнародованную размещение, и у неё есть способ content, который возвращает content.

-

У нас все ещё есть функция Post::new, но вместо возврата образца Post она возвращает образец DraftPost. Поскольку поле content является закрытым и нет никаких функций, которые возвращают Post, просто так создать образец Post уже невозможно.

-

Устройства DraftPost имеет способ add_text, поэтому мы можем добавлять текст к content как и раньше, но учтите, что в DraftPost не определён способ content! Так что теперь программа заверяет, что все записи начинаются как черновики, а черновики размещений не имеют своего содержания для отображения. Любая попытка обойти эти ограничения приведёт к ошибке сборщика.

-

Выполнение переходов в виде преобразований в другие виды

-

Так как же получить обнародованный пост? Мы хотим обеспечить соблюдение правила, согласно которому черновик записи должен быть рассмотрен и утверждён до того, как он будет обнародован. Запись, находящаяся в состоянии проверки, по-прежнему не должна отображать содержимое. Давайте выполняем эти ограничения, добавив ещё одну устройство, PendingReviewPost, определив способ request_review у DraftPost, возвращающий PendingReviewPost, и определив способ approve у PendingReviewPost, возвращающий Post, как показано в приложении 17-20:

-

Файл: src/lib.rs

-
pub struct Post {
-    content: String,
-}
-
-pub struct DraftPost {
-    content: String,
-}
-
-impl Post {
-    pub fn new() -> DraftPost {
-        DraftPost {
-            content: String::new(),
-        }
-    }
-
-    pub fn content(&self) -> &str {
-        &self.content
-    }
-}
-
-impl DraftPost {
-    // --snip--
-    pub fn add_text(&mut self, text: &str) {
-        self.content.push_str(text);
-    }
-
-    pub fn request_review(self) -> PendingReviewPost {
-        PendingReviewPost {
-            content: self.content,
-        }
-    }
-}
-
-pub struct PendingReviewPost {
-    content: String,
-}
-
-impl PendingReviewPost {
-    pub fn approve(self) -> Post {
-        Post {
-            content: self.content,
-        }
-    }
-}
-

Приложение 17-20: Вид PendingReviewPost, который создаётся путём вызова request_review образца DraftPost и способ approve, который превращает PendingReviewPost в обнародованный Post.

-

Способы request_review и approve забирают во владение self, таким образом поглощая образцы DraftPost и PendingReviewPost, которые потом преобразуются в PendingReviewPost и обнародованную Post, соответственно. Таким образом, у нас не будет никаких долгоживущих образцов DraftPost, после того, как мы вызвали у них request_review и так далее. В устройстве PendingReviewPost не определён способ content, поэтому попытка прочитать его содержимое приводит к ошибке сборщика, также как и в случае с DraftPost. Так как единственным способом получить обнародованный образец Post, у которого действительно есть объявленный способ content, является вызов способа approve у образца PendingReviewPost, а единственный способ получить PendingReviewPost - это вызвать способ request_review у образца DraftPost, теперь мы закодировали этап смены состояний записи блога с помощью системы видов.

-

Кроме этого, нужно внести небольшие изменения в main. Так как способы request_review и approve теперь возвращают предметы, а не преобразуют устройство от которой были вызваны, нам нужно добавить больше затеняющих присваиваний let post =, чтобы сохранять возвращаемые предметы. Также, теперь мы не можем использовать утверждения (assertions) для проверки того является ли содержимое черновиков и записей, находящихся на рассмотрении, пустыми строками, да они нам и не нужны - теперь стало невозможным собрать код, который бы пытался использовать содержимое записей, находящихся в этих состояниях. Обновлённый код в main показан в приложении 17-21:

-

Файл: src/main.rs

-
use blog::Post;
-
-fn main() {
-    let mut post = Post::new();
-
-    post.add_text("I ate a salad for lunch today");
-
-    let post = post.request_review();
-
-    let post = post.approve();
-
-    assert_eq!("I ate a salad for lunch today", post.content());
-}
-

Приложение 17-21: Изменения в main, использующие новую выполнение этапа подготовки записи блога

-

Изменения, которые нам нужно было внести в main, чтобы переназначить post означают, что эта выполнение теперь не совсем соответствует предметно-направленному образцу "Состояние": преобразования между состояниями больше не инкапсулированы внутри выполнения Post полностью. Тем не менее, мы получили большую выгоду в том, что недопустимые состояния теперь невозможны из-за системы видов и проверки видов, которая происходит во время сборки! У нас есть заверенияия, что некоторые ошибки, такие как отображение содержимого необнародованной обнародования, будут обнаружены до того, как они дойдут до пользователей.

-

Попробуйте выполнить задачи, предложенные в начале этого раздела, в исполнения ящика blog, каким он стал после приложения 17-20, чтобы создать своё мнение о внешнем виде этой исполнения кода. Обратите внимание, что некоторые задачи в этом исходе могут быть уже выполнены.

-

Мы увидели, что хотя Ржавчина и способен выполнить предметно-направленные образцы разработки, в нём также доступны и другие образцы, такие как кодирование состояния с помощью системы видов. Эти подходы имеют различные соглашения. Хотя вы, возможно, очень хорошо знакомы с предметно-направленными образцами, переосмысление неполадок для использования преимуществ и возможностей Ржавчина может дать такие выгоды, как предотвращение некоторых ошибок во время сборки. Предметно-направленные образцы не всегда будут лучшим решением в Ржавчина из-за наличия определённых возможностей, таких как владение, которого нет у предметно-направленных языков.

-

Итоги

-

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

-

Далее мы рассмотрим образцы, которые являются ещё одной особенностью Rust, обеспечивающей высокую гибкость. Мы бегло рассказывали о них на протяжении всей книги, но ещё не видели всех их возможностей. Вперёд!

-

Образцы и сопоставление

-

Образцы - это особый правила написания в Ржавчина для сопоставления со устройством видов, как сложных, так и простых. Использование образцов в сочетании с выражениями match и другими устройствоми даёт вам больший управление над потоком управления программы. Образец состоит из некоторой сочетания следующего:

-
    -
  • Записи
  • -
  • Деупорядоченные массивы, перечисления, устройства или упорядоченные ряды
  • -
  • Переменные
  • -
  • Особые символы
  • -
  • Заполнители
  • -
-

Некоторые примеры образцов включают x , (a, 3) и Some(Color::Red) . В средах, в которых допустимы образцы, эти составляющие описывают разновидность данных. Затем наша программа сопоставляет значения с образцами, чтобы определить, имеет ли значение правильную разновидность данных для продолжения выполнения определённого отрывка кода.

-

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

-

Эта глава - справочник по всем особенностим, связанным с образцами. Мы расскажем о допустимых местах использования образцов, разнице между опровержимыми и неопровержимыми образцами и про различные виды правил написания образцов, которые вы можете увидеть. К концу главы вы узнаете, как использовать образцы для ясного выражения многих понятий.

-

Все случаи, где могут быть использованы образцы

-

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

-

Ветки match

-

Как обсуждалось в главе 6, мы используем образцы в ветках выражений match. Условновыражения match определяется как ключевое слово match, значение используемое для сопоставления, одна или несколько веток, которые состоят из образца и выражения для выполнения, если значение соответствует образцу этой ветки, как здесь:

-
match VALUE {
-    PATTERN => EXPRESSION,
-    PATTERN => EXPRESSION,
-    PATTERN => EXPRESSION,
-}
-
-

Например, вот выражение match из приложения 6-5, которое соответствует значению Option<i32> в переменной x:

-
match x {
-    None => None,
-    Some(i) => Some(i + 1),
-}
-

Образцами в этом выражении match являются None и Some(i) слева от каждой стрелки.

-

Одно из требований к выражениям match состоит в том, что они должны быть исчерпывающими (exhaustive) в том смысле, что они должны учитывать все возможности для значения в выражении match. Один из способов убедиться, что вы рассмотрели каждую возможность - это иметь образец перехвата всех исходов в последней ветке выражения: например, имя переменной, совпадающее с любым значением, никогда не может потерпеть неудачу и таким образом, охватывает каждый оставшийся случай.

-

Особый образец _ будет соответствовать чему угодно, но он никогда не привязывается к переменной, поэтому он часто используется в последней ветке. Образец _ может быть полезен, если вы, например, хотите пренебрегать любое не указанное значение. Мы рассмотрим образец _ более подробно в разделе "Пренебрежение значений в образце позже в этой главе.

-

Условные выражения if let

-

В главе 6 мы обсуждали, как использовать выражения if let как правило в качестве более короткого способа записи эквивалента match, которое обрабатывает только один случай. Дополнительно if let может иметь соответствующий else, содержащий код для выполнения, если образец выражения if let не совпадает.

-

В приложении 18-1 показано, что можно также смешивать и сопоставлять выражения if let, else if и else if let. Это даёт больше гибкости, чем match выражение, в котором можно выразить только одно значение для сравнения с образцами. Кроме того, условия в серии if let, else if, else if let не обязаны соотноситься друг с другом.

-

Код в приложении 18-1 показывает последовательность проверок нескольких условий, определяющих каким должен быть цвет фона. В данном примере мы создали переменные с предопределёнными значениями, которые в существующей программе могли бы быть получены из пользовательского ввода.

-

Файл: src/main.rs

-
fn main() {
-    let favorite_color: Option<&str> = None;
-    let is_tuesday = false;
-    let age: Result<u8, _> = "34".parse();
-
-    if let Some(color) = favorite_color {
-        println!("Using your favorite color, {color}, as the background");
-    } else if is_tuesday {
-        println!("Tuesday is green day!");
-    } else if let Ok(age) = age {
-        if age > 30 {
-            println!("Using purple as the background color");
-        } else {
-            println!("Using orange as the background color");
-        }
-    } else {
-        println!("Using blue as the background color");
-    }
-}
-

Приложение 18-1: Использование условных устройств if let, else if, else if let, и else

-

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

-

Эта условная устройства позволяет поддерживать сложные требования. С жёстко закодированными значениями, которые у нас здесь есть, этот пример напечатает Using purple as the background color.

-

Можно увидеть, что if let может также вводить затенённые переменные, как это можно сделать в match ветках: строка if let Ok(age) = age вводит новую затенённую переменную age, которая содержит значение внутри исхода Ok. Это означает, что нам нужно поместить условие if age > 30 внутри этого блок: мы не можем объединить эти два условия в if let Ok(age) = age && age > 30. Затенённый age, который мы хотим сравнить с 30, не является действительным, пока не начнётся новая область видимости с фигурной скобки.

-

Недостатком использования if let выражений является то, что сборщик не проверяет полноту (exhaustiveness) всех исходов, в то время как с помощью выражения match это происходит. Если мы не напишем последний разделelse и, благодаря этому, пропустим обработку некоторых случаев, сборщик не предупредит нас о возможной логической ошибке.

-

Условные циклы while let

-

Подобно устройства if let, устройство условного цикла while let позволяет повторять цикл while до тех пор, пока образец продолжает совпадать. Пример в приложении 18-2 отображает цикл while let, который использует вектор в качестве обоймы и печатает значения вектора в порядке, обратном тому, в котором они были помещены.

-
fn main() {
-    let mut stack = Vec::new();
-
-    stack.push(1);
-    stack.push(2);
-    stack.push(3);
-
-    while let Some(top) = stack.pop() {
-        println!("{top}");
-    }
-}
-

Приложение 18-2: Использование цикла while let для печати значений до тех пор, пока stack.pop() возвращает Some

-

В этом примере выводится 3, 2, а затем 1. Способ pop извлекает последний элемент из вектора и возвращает Some(value). Если вектор пуст, то pop возвращает None. Цикл while продолжает выполнение кода в своём разделе, пока pop возвращает Some. Когда pop возвращает None, цикл останавливается. Мы можем использовать while let для удаления каждого элемента из обоймы.

-

Цикл for

-

В цикле for значение, которое следует непосредственно за ключевым словом for , является образцом. Например, в for x in y выражение x является образцом. В приложении 18-3 показано, как использовать образец в цикле for , чтобы разъединять или разбить упорядоченный ряд как часть цикла for .

-
fn main() {
-    let v = vec!['a', 'b', 'c'];
-
-    for (index, value) in v.iter().enumerate() {
-        println!("{value} is at index {index}");
-    }
-}
-

Приложение 18-3: Использование образца в цикле for для разъединения упорядоченного ряда

-

Код в приложении 18-3 выведет следующее:

-
$ cargo run
-   Compiling patterns v0.1.0 (file:///projects/patterns)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
-     Running `target/debug/patterns`
-a is at index 0
-b is at index 1
-c is at index 2
-
-

Мы приспособимтируем повторитель с помощью способа enumerate, чтобы он порождал упорядоченный ряд, состоящий из значения и порядкового указателя этого значения. Первым созданным значением будет упорядоченный ряд (0, 'a'). Когда это значение сопоставляется с образцом (index, value), index будет равен 0, а value будет равно 'a' и будет напечатана первая строка выходных данных.

-

Указание let

-

До этой главы мы подробно обсуждали только использование образцов с match и if let, но на самом деле, мы использовали образцы и в других местах, в том числе в указаниях let. Например, рассмотрим следующее простое назначение переменной с помощью let:

-
#![allow(unused)]
-fn main() {
-let x = 5;
-}
-

Каждый раз, когда вы использовали подобным образом указанию let, вы использовали образцы, хотя могли и не осознавать этого! Более условноуказание let выглядит так:

-
let PATTERN = EXPRESSION;
-
-

В указаниях вида let x = 5; с именем переменной в слоте PATTERN, имя переменной является просто отдельной, простой способом образца. Ржавчина сравнивает выражение с образцом и присваивает любые имена, которые он находит. Так что в примере let x = 5;, x - это образец, который означает "привязать то, что соответствует здесь, переменной x". Поскольку имя x является полностью образцом, этот образец в действительности означает "привязать все к переменной x независимо от значения".

-

Чтобы более чётко увидеть особенность сопоставления с образцом let, рассмотрим приложение 18-4, в котором используется образец с let для разъединения упорядоченного ряда.

-
fn main() {
-    let (x, y, z) = (1, 2, 3);
-}
-

Приложение 18-4. Использование образца для разъединения упорядоченного ряда и создания трёх переменных одновременно

-

Здесь мы сопоставляем упорядоченный ряд с образцом. Ржавчина сравнивает значение (1, 2, 3) с образцом (x, y, z) и видит, что значение соответствует образцу, поэтому Ржавчина связывает 1 с x, 2 с y и 3 с z. Вы можете думать об этом образце упорядоченного ряда как о вложении в него трёх отдельных образцов переменных.

-

Если количество элементов в образце не совпадает с количеством элементов в упорядоченном ряде, то весь вид не будет совпадать и мы получим ошибку сборщика. Например, в приложении 18-5 показана попытка разъединять упорядоченный ряд с тремя элементами в две переменные, что не будет работать.

-
fn main() {
-    let (x, y) = (1, 2, 3);
-}
-

Приложение 18-5: Неправильное построение образца, переменные не соответствуют количеству элементов в упорядоченном ряде

-

Попытка собрать этот код приводит к ошибке:

-
$ cargo run
-   Compiling patterns v0.1.0 (file:///projects/patterns)
-error[E0308]: mismatched types
- --> src/main.rs:2:9
-  |
-2 |     let (x, y) = (1, 2, 3);
-  |         ^^^^^^   --------- this expression has type `({integer}, {integer}, {integer})`
-  |         |
-  |         expected a tuple with 3 elements, found one with 2 elements
-  |
-  = note: expected tuple `({integer}, {integer}, {integer})`
-             found tuple `(_, _)`
-
-For more information about this error, try `rustc --explain E0308`.
-error: could not compile `patterns` (bin "patterns") due to 1 previous error
-
-

Чтобы исправить ошибку, мы могли бы пренебрегать одно или несколько значений в упорядоченном ряде, используя _ или .., как вы увидите в разделе “Пренебрежение значений в Образце” . Если образец содержит слишком много переменных в образце, можно решить неполадку, сделав виды совпадающими, удалив некоторые переменные таким образом, чтобы число переменных равнялось числу элементов в упорядоченном ряде.

-

Свойства функции

-

Свойства функции также могут быть образцами. Код в приложении 18-6 объявляет функцию с именем foo, которая принимает один свойство с именем x вида i32, к настоящему времени это должно выглядеть знакомым.

-
fn foo(x: i32) {
-    // code goes here
-}
-
-fn main() {}
-

Приложение 18-6: Ярлык функции использует образцы в свойствах

-

x это часть образца! Как и в случае с let, мы можем сопоставить упорядоченный ряд в переменных функции с образцом. Приложение 18-7 разделяет значения в упорядоченном ряде при его передачи в функцию.

-

Файл: src/main.rs

-
fn print_coordinates(&(x, y): &(i32, i32)) {
-    println!("Current location: ({x}, {y})");
-}
-
-fn main() {
-    let point = (3, 5);
-    print_coordinates(&point);
-}
-

Приложение 18-7: Функция с свойствами, которая разрушает упорядоченный ряд

-

Этот код печатает Current location: (3, 5). Значения &(3, 5) соответствуют образцу &(x, y), поэтому x - это значение 3, а y - это значение 5.

-

Добавляя к вышесказанному, мы можем использовать образцы в списках свойств замыкания таким же образом, как и в списках свойств функции, потому что, как обсуждалось в главе 13, замыкания похожи на функции.

-

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

-

Возможность опровержения: может ли образец не совпадать

-

Образцы бывают двух видов: опровержимые и неопровержимые. Образцы, которые будут соответствовать любому возможному переданному значению, являются неопровержимыми (irrefutable). Примером может быть x в указания let x = 5;, потому что x соответствует чему-либо и, следовательно, не может не совпадать. Образцы, которые могут не соответствовать некоторому возможному значению, являются опровержимыми (refutable). Примером может быть Some(x) в выражении if let Some(x) = a_value, потому что если значение в переменной a_value равно None, а не Some, то образец Some(x) не будет совпадать.

-

Свойства функций, указания let и циклы for могут принимать только неопровержимые образцы, поскольку программа не может сделать ничего значимого, если значения не совпадают. А выражения if let и while let принимают опровержимые и неопровержимые образцы, но сборщик предостерегает от неопровержимых образцов, поскольку по определению они предназначены для обработки возможного сбоя: возможность условного выражения заключается в его способности выполнять разный код в зависимости от успеха или неудачи.

-

В общем случае, вам не нужно беспокоиться о разнице между опровержимыми (refutable) и неопровержимыми (irrefutable) образцами; тем не менее, вам необходимо ознакомиться с подходом возможности опровержения, чтобы вы могли отреагировать на неё, увидев в сообщении об ошибке. В таких случаях вам потребуется изменить либо образец, либо устройство, с которой вы используете образец, в зависимости от предполагаемого поведения кода.

-

Давайте посмотрим на пример того, что происходит, когда мы пытаемся использовать опровержимый образец, где Ржавчина требует неопровержимый образец, и наоборот. В приложении 18-8 показана указание let, но для образца мы указали Some(x), являющийся образцом, который можно опровергнуть. Как и следовало ожидать, этот код не будет собираться.

-
fn main() {
-    let some_option_value: Option<i32> = None;
-    let Some(x) = some_option_value;
-}
-

Приложение 18-8: Попытка использовать опровержимый образец вместе с let

-

Если some_option_value было бы значением None, то оно не соответствовало бы образцу Some(x), что означает, что образец является опровержимым. Тем не менее, указание let может принимать только неопровержимый образец, потому что нет правильного кода, который может что-то сделать со значением None. Во время сборки Ржавчина будет жаловаться на то, что мы пытались использовать опровержимый образец, для которого требуется неопровержимый образец:

-
$ cargo run
-   Compiling patterns v0.1.0 (file:///projects/patterns)
-error[E0005]: refutable pattern in local binding
- --> src/main.rs:3:9
-  |
-3 |     let Some(x) = some_option_value;
-  |         ^^^^^^^ pattern `None` not covered
-  |
-  = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
-  = note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
-  = note: the matched value is of type `Option<i32>`
-help: you might want to use `let else` to handle the variant that isn't matched
-  |
-3 |     let Some(x) = some_option_value else { todo!() };
-  |                                     ++++++++++++++++
-
-For more information about this error, try `rustc --explain E0005`.
-error: could not compile `patterns` (bin "patterns") due to 1 previous error
-
-

Поскольку мы не покрыли (и не могли покрыть!) каждое допустимое значение с помощью образца Some(x), то Ржавчина выдаёт ошибку сборки.

-

Чтобы исправить неполадку наличия опровержимого образца, там, где нужен неопровержимый образец, можно изменить код, использующий образец: вместо использования let, можно использовать if let. Затем, если образец не совпадает, выполнение кода внутри фигурных скобок будет пропущено, что даст возможность продолжить правильное выполнение. В приложении 18-9 показано, как исправить код из приложения 18-8.

-
fn main() {
-    let some_option_value: Option<i32> = None;
-    if let Some(x) = some_option_value {
-        println!("{x}");
-    }
-}
-

Приложение 18-9. Использование if let и раздела с опровергнутыми образцами вместо let

-

Код исправлен! Этот код совершенно правильный, хотя это означает, что мы не можем использовать неопровержимый образец без получения ошибки. Если мы используем образец if let, который всегда будет совпадать, то для примера x, показанного в приложении 18-10, сборщик выдаст предупреждение.

-
fn main() {
-    if let x = 5 {
-        println!("{x}");
-    };
-}
-

Приложение 18-10. Попытка использовать неопровержимый образец с if let

-

Rust жалуется, что не имеет смысла использовать if let с неопровержимым образцом:

-
$ cargo run
-   Compiling patterns v0.1.0 (file:///projects/patterns)
-warning: irrefutable `if let` pattern
- --> src/main.rs:2:8
-  |
-2 |     if let x = 5 {
-  |        ^^^^^^^^^
-  |
-  = note: this pattern will always match, so the `if let` is useless
-  = help: consider replacing the `if let` with a `let`
-  = note: `#[warn(irrefutable_let_patterns)]` on by default
-
-warning: `patterns` (bin "patterns") generated 1 warning
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s
-     Running `target/debug/patterns`
-5
-
-

По этой причине совпадающие ветки выражений должны использовать опровержимые образцы, за исключением последнего, который должен сопоставлять любые оставшиеся значения с неопровержимым образцом. Ржавчина позволяет нам использовать неопровержимый образец в match только с одной веткой, но этот правила написания не особенно полезен и может быть заменён более простой указанием let.

-

Теперь, когда вы знаете, где использовать образцы и разницу между опровержимыми и неопровержимыми образцами, давайте рассмотрим весь правила написания, который мы можем использовать для создания образцов.

-

правила написания образцов

-

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

-

Сопоставление с записью

-

Как мы уже видели в главе 6, можно сопоставлять образцы с записями напрямую. В следующем коде есть несколько примеров:

-
fn main() {
-    let x = 1;
-
-    match x {
-        1 => println!("one"),
-        2 => println!("two"),
-        3 => println!("three"),
-        _ => println!("anything"),
-    }
-}
-

Этот код печатает one, потому что значение в x равно 1. Данный правила написания полезен, когда вы хотите, чтобы ваш код предпринял действие, если он получает определенное значение.

-

Сопоставление именованных переменных

-

Именованные переменные - это неопровержимые (irrefutable) образцы, которые соответствуют любому значению и мы использовали их много раз в книге. Однако при использовании именованных переменных в выражениях match возникает сложность. Поскольку match начинает новую область видимости, то переменные, объявленные как часть образца внутри выражения match, будут затенять переменные с тем же именем вне устройства match как и в случае со всеми переменными. В приложении 18-11 мы объявляем переменную с именем x со значением Some(5) и переменную y со значением 10. Затем мы создаём выражение match для значения x. Посмотрите на образцы в ветках, println! в конце и попытайтесь выяснить, какой код будет напечатан прежде чем запускать его или читать дальше.

-

Файл: src/main.rs

-
fn main() {
-    let x = Some(5);
-    let y = 10;
-
-    match x {
-        Some(50) => println!("Got 50"),
-        Some(y) => println!("Matched, y = {y}"),
-        _ => println!("Default case, x = {x:?}"),
-    }
-
-    println!("at the end: x = {x:?}, y = {y}");
-}
-

Приложение 18-11: Выражение match с веткой, которая добавляет затенённую переменную y

-

Давайте рассмотрим, что происходит, когда выполняется выражение match. Образец в первой ветке не соответствует определённому значению x, поэтому выполнение продолжается.

-

Образец во второй ветке вводит новую переменную с именем y, которая будет соответствовать любому значению в Some. Поскольку мы находимся в новой области видимости внутри выражения match, это новая переменная y, а не y которую мы объявили в начале со значением 10. Эта новая привязка y будет соответствовать любому значению из Some, которое находится в x. Следовательно, эта новая y связывается с внутренним значением Some из переменной x. Этим значением является 5, поэтому выражение для этой ветки выполняется и печатает Matched, y = 5.

-

Если бы x было значением None вместо Some(5), то образцы в первых двух ветках не совпали бы, поэтому значение соответствовало бы подчёркиванию. Мы не ввели переменную x в образце ветки со знаком подчёркивания, поэтому x в выражении все ещё является внешней переменной x, которая не была затенена. В этом гипотетическом случае совпадение match выведет Default case, x = None.

-

Когда выражение match завершается, заканчивается его область видимости как и область действия внутренней переменной y. Последний println! печатает at the end: x = Some(5), y = 10.

-

Чтобы создать выражение match, которое сравнивает значения внешних x и y, вместо введения затенённой переменной нужно использовать условие в сопоставлении образца. Мы поговорим про условие в сопоставлении образца позже в разделе “Дополнительные условия в сопоставлении образца”.

-

объединение образцов

-

В выражениях match можно сравнивать сразу с несколькими образцами, используя правила написания |, который является оператором образца or. Например, в следующем примере мы сопоставляем значение x с ветвями match, первая из которых содержит оператор or, так что если значение x совпадёт с любым из значений в этой ветви, то будет выполнен её код:

-
fn main() {
-    let x = 1;
-
-    match x {
-        1 | 2 => println!("one or two"),
-        3 => println!("three"),
-        _ => println!("anything"),
-    }
-}
-

Будет напечатано one or two.

-

Сопоставление рядов с помощью ..=

-

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

-
fn main() {
-    let x = 5;
-
-    match x {
-        1..=5 => println!("one through five"),
-        _ => println!("something else"),
-    }
-}
-

Если x равен 1, 2, 3, 4 или 5, то совпадение будет достигнуто в первой ветке. Этот правила написания более удобен при указании нескольких значений для сравнения, чем использование оператора | для определения этой же мысли; если бы мы решили использовать |, нам пришлось бы написать 1 | 2 | 3 | 4 | 5. Указание ряда намного короче, особенно если мы хотим подобрать, скажем, любое число от 1 до 1 000!

-

Сборщик проверяет, что рядне является пустым во время сборки, и поскольку единственными видами, для которых Ржавчина может определить, пуст рядили нет, являются char и числовые значения, ряды допускаются только с числовыми или char значениями.

-

Вот пример использования рядов значений char:

-
fn main() {
-    let x = 'c';
-
-    match x {
-        'a'..='j' => println!("early ASCII letter"),
-        'k'..='z' => println!("late ASCII letter"),
-        _ => println!("something else"),
-    }
-}
-

Rust может сообщить, что 'c' находится в ряде первого образца и напечатать early ASCII letter.

-

Разъединение для получения значений

-

Мы также можем использовать образцы для разъединения устройств, перечислений и упорядоченных рядов, чтобы использовать разные части этих значений. Давайте пройдёмся по каждому исходу.

-

Разъединение устройства

-

В приложении 18-12 показана устройства Point с двумя полями x и y, которые мы можем разделить, используя образец с указанием let.

-

Файл: src/main.rs

-
struct Point {
-    x: i32,
-    y: i32,
-}
-
-fn main() {
-    let p = Point { x: 0, y: 7 };
-
-    let Point { x: a, y: b } = p;
-    assert_eq!(0, a);
-    assert_eq!(7, b);
-}
-

Приложение 18-12: Разбиение полей устройства в отдельные переменные

-

Этот код создаёт переменные a и b , которые сопоставляются значениям полей x и y устройства p . Этот пример показывает, что имена переменных в образце не обязательно должны совпадать с именами полей устройства. Однако обычно имена переменных сопоставляются с именами полей, чтобы было легче запомнить, какие переменные взяты из каких полей. Из-за этого, а также из-за того, что строчка let Point { x: x, y: y } = p; содержит много повторения, в Ржавчина ввели особое сокращение для образцов, соответствующих полям устройства: вам нужно только указать имя поля устройства, и тогда переменные, созданные из образца, будут иметь те же имена. Код в приложении 18-13 подобен коду в Приложении 18-12, но в образце let создаются переменные x и y, вместо a и b .

-

Файл: src/main.rs

-
struct Point {
-    x: i32,
-    y: i32,
-}
-
-fn main() {
-    let p = Point { x: 0, y: 7 };
-
-    let Point { x, y } = p;
-    assert_eq!(0, x);
-    assert_eq!(7, y);
-}
-

Приложение 18-13: Разъединение полей устройства с использованием сокращённой записи

-

Этот код создаёт переменные x и y, которые соответствуют полям x и y из переменной p. В итоге переменные x и y содержат значения из устройства p.

-

А ещё, используя записанные значения в образце, мы можем разъединять, не создавая переменные для всех полей. Это даёт возможность, проверяя одни поля на соответствие определенным значениям, создавать переменные для разъединения других.

-

В приложении 18-14 показано выражение match, которое разделяет значения Point на три случая: точки, которые лежат непосредственно на оси x (что верно, когда y = 0), на оси y (x = 0) или ни то, ни другое.

-

Файл: src/main.rs

-
struct Point {
-    x: i32,
-    y: i32,
-}
-
-fn main() {
-    let p = Point { x: 0, y: 7 };
-
-    match p {
-        Point { x, y: 0 } => println!("On the x axis at {x}"),
-        Point { x: 0, y } => println!("On the y axis at {y}"),
-        Point { x, y } => {
-            println!("On neither axis: ({x}, {y})");
-        }
-    }
-}
-

Приложение 18-14: Разъединение и сопоставление с записями в одном образце

-

Первая ветвь будет соответствовать любой точке, лежащей на оси x, если значение поля y будет соответствовать записи 0. Образец по-прежнему создаёт переменную x, которую мы сможем использовать в коде этой ветви.

-

Подобно, вторая ветвь совпадёт с любой точкой на оси y, в случае, если значение поля x будет равно 0, а для значения поля y будет создана переменная y. Третья ветвь не содержит никаких записей, поэтому она соответствует любому другому Point и создаёт переменные как для поля x, так и для поля y.

-

В этом примере значение p совпадает по второй ветке, так как x содержит значение 0, поэтому этот код будет печатать On the y axis at 7.

-

Помните, что выражение match перестаёт проверять следующие ветви, как только оно находит первый совпадающий образец, поэтому, даже если Point { x: 0, y: 0} находится на оси x и оси y, этот код будет печатать только On the x axis at 0 .

-

Разъединение перечислений

-

Мы уже разъединили перечисления в книге (см., например, приложение 6-5 главы 6), но
не обсуждали явно, что образец для разъединения перечисления должен соответствовать способу объявления данных, хранящихся в перечислении. Например, в приложении 18-15 мы используем перечисление Message из приложения 6-2 и пишем match с образцами, которые будут разъединять каждое внутреннее значение.

-

Файл: src/main.rs

-
enum Message {
-    Quit,
-    Move { x: i32, y: i32 },
-    Write(String),
-    ChangeColor(i32, i32, i32),
-}
-
-fn main() {
-    let msg = Message::ChangeColor(0, 160, 255);
-
-    match msg {
-        Message::Quit => {
-            println!("The Quit variant has no data to destructure.");
-        }
-        Message::Move { x, y } => {
-            println!("Move in the x direction {x} and in the y direction {y}");
-        }
-        Message::Write(text) => {
-            println!("Text message: {text}");
-        }
-        Message::ChangeColor(r, g, b) => {
-            println!("Change the color to red {r}, green {g}, and blue {b}")
-        }
-    }
-}
-

Приложение 18-15: Разъединение исходов перечисления, содержащих разные виды значений

-

Этот код напечатает Change the color to red 0, green 160, and blue 255. Попробуйте изменить значение переменной msg, чтобы увидеть выполнение кода в других ветках.

-

Для исходов перечисления без каких-либо данных, вроде Message::Quit, мы не можем разъединять значение, которого нет. Мы можем сопоставить только буквальное значение Message::Quit в этом образце, но без переменных.

-

Для исходов перечисления похожих на устройства, таких как Message::Move, можно использовать образец, подобный образцу, который мы указываем для сопоставления устройств. После имени исхода мы помещаем фигурные скобки и затем перечисляем поля именами переменных. Таким образом мы разделяем отрывки, которые будут использоваться в коде этой ветки. Здесь мы используем сокращённую разновидность, как в приложении 18-13.

-

Для исходов перечисления, подобных упорядоченному ряду, вроде Message::Write, который содержит упорядоченный ряд с одним элементом и Message::ChangeColor, содержащему упорядоченный ряд с тремя элементами, образец подобен тому, который мы указываем для сопоставления упорядоченных рядов. Количество переменных в образце должно соответствовать количеству элементов в исходе, который мы сопоставляем.

-

Разъединение вложенных устройств и перечислений

-

До сих пор все наши примеры сопоставляли устройства или перечисления на один уровень глубины, но сопоставление может работать и с вложенными элементами! Например, мы можем ресогласовать код в приложении 18-15 для поддержки цветов RGB и HSV в сообщении ChangeColor , как показано в приложении 18-16.

-
enum Color {
-    Rgb(i32, i32, i32),
-    Hsv(i32, i32, i32),
-}
-
-enum Message {
-    Quit,
-    Move { x: i32, y: i32 },
-    Write(String),
-    ChangeColor(Color),
-}
-
-fn main() {
-    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
-
-    match msg {
-        Message::ChangeColor(Color::Rgb(r, g, b)) => {
-            println!("Change color to red {r}, green {g}, and blue {b}");
-        }
-        Message::ChangeColor(Color::Hsv(h, s, v)) => {
-            println!("Change color to hue {h}, saturation {s}, value {v}")
-        }
-        _ => (),
-    }
-}
-

Приложение 18-16: Сопоставление со вложенными перечислениями

-

Образец первой ветки в выражении match соответствует исходу перечисления Message::ChangeColor, который содержит исход Color::Rgb; затем образец привязывается к трём внутренними значениями i32. Образец второй ветки также соответствует исходу перечисления Message::ChangeColor, но внутреннее перечисление соответствует исходу Color::Hsv. Мы можем указать эти сложные условия в одном выражении match, даже если задействованы два перечисления.

-

Разъединение устройств и упорядоченных рядов

-

Можно смешивать, сопоставлять и вкладывать образцы разъединения ещё более сложными способами. В следующем примере показана сложная разъединение, где мы вкладываем устройства и упорядоченные ряды внутрь упорядоченного ряда и разъединим из него все простые значения:

-
fn main() {
-    struct Point {
-        x: i32,
-        y: i32,
-    }
-
-    let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
-}
-

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

-

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

-

Пренебрежение значений в образце

-

Вы видели, что иногда полезно пренебрегать значения в образце, например в последней ветке match, чтобы получить ветку, обрабатывающую любые значения, которая на самом деле ничего не делает, но учитывает все оставшиеся возможные значения. Есть несколько способов пренебрегать целые значения или части значений в образце: используя образец _ (который вы видели), используя образец _ внутри другого образца, используя имя, начинающееся с подчёркивания, либо используя .., чтобы пренебрегать оставшиеся части значения. Давайте рассмотрим, как и зачем использовать каждый из этих образцов.

-

Пренебрежение всего значения с помощью образца _

-

Мы использовали подчёркивание (_) в качестве образца подстановочного знака (wildcard), который будет сопоставляться с любом значением, но не будет привязываться к этому значению. Это особенно удобно в последней ветке выражения match, но мы также можем использовать его в любом образце, в том числе в свойствах функции, как показано в приложении 18-17.

-

Файл: src/main.rs

-
fn foo(_: i32, y: i32) {
-    println!("This code only uses the y parameter: {y}");
-}
-
-fn main() {
-    foo(3, 4);
-}
-

Приложение 18-15: Использование _ в ярлыке функции

-

Этот код полностью пренебрегает значение 3, переданное в качестве первого переменной, и выведет на печать This code only uses the y parameter: 4.

-

В большинстве случаев, когда вам больше не нужен какой-то из свойств функции, вы можете изменить её ярлык, убрав неиспользуемый свойство. Пренебрежение свойства функции может быть особенно полезно в случаях когда, например, вы выполняете особенность с определённой ярлыком, но тело функции в вашей выполнения не нуждается в одном из свойств. В таком случае сборщик не будет выдавать предупреждения о неиспользуемых свойствах функции, как это было бы, если бы вы указали имя свойства.

-

Пренебрежение частей значения с помощью вложенного _

-

Также, _ можно использовать внутри образцов, чтобы пренебрегать какую-то часть значения, например, когда мы хотим проверить только определённую подробность, а остальные свойства нам не понадобятся в коде, который нужно выполнить. В приложении 18-18 показан код, ответственный за управление значениями настроек. Согласно бизнес-требованиям, пользователь не может изменить установленное значение свойства, но может удалить его и задать ему новое значение, если на данный мгновение оно отсутствует.

-
fn main() {
-    let mut setting_value = Some(5);
-    let new_setting_value = Some(10);
-
-    match (setting_value, new_setting_value) {
-        (Some(_), Some(_)) => {
-            println!("Can't overwrite an existing customized value");
-        }
-        _ => {
-            setting_value = new_setting_value;
-        }
-    }
-
-    println!("setting is {setting_value:?}");
-}
-

Приложение 18-18: Использование подчёркивания в образцах, соответствующих исходам Some, когда нам не нужно использовать значение внутри Some

-

Этот код будет печатать Can't overwrite an existing customized value, а затем setting is Some(5). В первой ветке нам не нужно сопоставлять или использовать значения внутри исхода Some, но нам нужно проверить случай, когда setting_value и new_setting_value являются исходом Some. В этом случае мы печатаем причину, почему мы не меняем значение setting_value и оно не меняется.

-

Во всех других случаях (если либо setting_value, либо new_setting_value являются исходом None), выраженных образцом _ во второй ветке, мы хотим, чтобы new_setting_value стало равно setting_value.

-

Мы также можем использовать подчёркивание в нескольких местах в одном образце, чтобы пренебрегать определенные значения. Приложение 18-19 показывает пример пренебрежения второго и четвёртого значения в упорядоченном ряде из пяти элементов.

-
fn main() {
-    let numbers = (2, 4, 8, 16, 32);
-
-    match numbers {
-        (first, _, third, _, fifth) => {
-            println!("Some numbers: {first}, {third}, {fifth}")
-        }
-    }
-}
-

Приложение 18-19: Пренебрежение нескольких частей упорядоченного ряда

-

Этот код напечатает Some numbers: 2, 8, 32, а значения 4 и 16 будут пренебрежены.

-

Пренебрежение неиспользуемой переменной, начинающейся с символа _ в имени

-

Если вы создаёте переменную, но нигде её не используете, Ржавчина обычно выдаёт предупреждение, потому что неиспользуемая переменная может быть ошибкой. Но иногда полезно создать переменную, которую вы пока не используете, например, когда вы создаёте протовид или только начинаете дело. В этой случаи вы можете сказать Ржавчина не предупреждать вас о неиспользуемой переменной, начав имя переменной с подчёркивания. В приложении 18-20 мы создаём две неиспользуемые переменные, но когда мы собираем такой код, мы должны получить предупреждение только об одной из них.

-

Файл: src/main.rs

-
fn main() {
-    let _x = 5;
-    let y = 10;
-}
-

Приложение 18-20: Начинаем имя переменной с подчёркивания, чтобы не получить предупреждения о неиспользованных переменных

-

Здесь мы получаем предупреждение о том, что не используем переменную y, но мы не получаем предупреждения о неиспользовании переменной_x.

-

Обратите внимание, что есть небольшая разница между использованием только _ и использованием имени, начинающегося с подчёркивания. правила написания _x по-прежнему привязывает значение к переменной, тогда как _ не привязывает ничего. В приложении 18-21 представлена ошибка, показывающая, в каком случае это различие имеет значение.

-
fn main() {
-    let s = Some(String::from("Hello!"));
-
-    if let Some(_s) = s {
-        println!("found a string");
-    }
-
-    println!("{s:?}");
-}
-

Приложение 18-21: Неиспользуемая переменная, начинающаяся с подчёркивания, по-прежнему привязывает значение, что может привести к смене владельца значения

-

Мы получим ошибку, поскольку значение s все равно будет перемещено в _s, что не позволит нам больше воспользоваться s. Однако использование подчёркивания само по себе никогда не приводит к привязке к значению. Приложение 18-22 собирается без ошибок, поскольку s не будет перемещён в _.

-
fn main() {
-    let s = Some(String::from("Hello!"));
-
-    if let Some(_) = s {
-        println!("found a string");
-    }
-
-    println!("{s:?}");
-}
-

Приложение 18-22. Использование подчёркивания не привязывает значение

-

Этот код работает правильно, потому что мы никогда не привязываем s к чему либо; оно не перемещается.

-

Пренебрежение оставшихся частей значения с помощью ..

-

Со значениями, которые имеют много частей, можно использовать правила написания .., чтобы использовать только некоторые части и пренебрегать остальные, избегая необходимости перечислять подчёркивания для каждого пренебрегаемого значения. Образец .. пренебрегает любые части значения, которые мы явно не сопоставили в остальной частью образца. В приложении 18-23 мы имеем устройство Point, которая содержит координату в трёхмерном пространстве. В выражении match мы хотим работать только с координатой x и пренебрегать значения полей y и z.

-
fn main() {
-    struct Point {
-        x: i32,
-        y: i32,
-        z: i32,
-    }
-
-    let origin = Point { x: 0, y: 0, z: 0 };
-
-    match origin {
-        Point { x, .. } => println!("x is {x}"),
-    }
-}
-

Приложение 18-21: Пренебрежение полей устройства Point кроме поля x с помощью ..

-

Мы перечисляем значение x и затем просто включаем образец ... Это быстрее, чем перечислять y: _ и z: _, особенно когда мы работаем со устройствами, которые имеют много полей, в случаейх, когда только одно или два поля представляют для нас влечение.

-

правила написания .. раскроется до необходимого количества значений. В приложении 18-24 показано, как использовать .. с упорядоченным рядом.

-

Файл: src/main.rs

-
fn main() {
-    let numbers = (2, 4, 8, 16, 32);
-
-    match numbers {
-        (first, .., last) => {
-            println!("Some numbers: {first}, {last}");
-        }
-    }
-}
-

Приложение 18-24: Сопоставление только первого и последнего значений в упорядоченном ряде и пренебрежение всех других значений

-

В этом коде первое и последнее значение соответствуют first и last. Устройство .. будет соответствовать и пренебрегать всё, что находится между ними.

-

Однако использование .. должно быть однозначным. Если неясно, какие значения предназначены для сопоставления, а какие следует пренебрегать, Ржавчина выдаст ошибку. В приложении 18-25 показан пример неоднозначного использования .., поэтому он не будет собираться.

-

Файл: src/main.rs

-
fn main() {
-    let numbers = (2, 4, 8, 16, 32);
-
-    match numbers {
-        (.., second, ..) => {
-            println!("Some numbers: {second}")
-        },
-    }
-}
-

Приложение 18-25: Попытка использовать .. неоднозначным способом

-

При сборки примера, мы получаем эту ошибку:

-
$ cargo run
-   Compiling patterns v0.1.0 (file:///projects/patterns)
-error: `..` can only be used once per tuple pattern
- --> src/main.rs:5:22
-  |
-5 |         (.., second, ..) => {
-  |          --          ^^ can only be used once per tuple pattern
-  |          |
-  |          previously used here
-
-error: could not compile `patterns` (bin "patterns") due to 1 previous error
-
-

Rust не может определить, сколько значений в упорядоченном ряде нужно пренебрегать, прежде чем сопоставить значение с second, и сколько следующих значений пренебрегать после этого. Этот код может означать, что мы хотим пренебрегать 2, связать second с 4, а затем пренебрегать 8, 16 и 32; или что мы хотим пренебрегать 2 и 4, связать second с 8, а затем пренебрегать 16 и 32; и так далее. Имя переменной second не означает ничего особенного для Rust, поэтому мы получаем ошибку сборщика, так как использование .. в двух местах как здесь, является неоднозначным.

-

Дополнительные условия оператора сопоставления (Match Guards)

-

Условие сопоставления (match guard) является дополнительным условием if, указанным после образца в ветке match, которое также должно быть выполнено, чтобы ветка была выбрана. Условия сопоставления полезны для выражения более сложных мыслей, чем позволяет только образец.

-

Условие может использовать переменные, созданные в образце. В приложении 18-26 показан match, в котором первая ветка имеет образец Some(x), а также имеет условие сопоставления, if x % 2 == 0 (которое будет истинным, если число чётное).

-
fn main() {
-    let num = Some(4);
-
-    match num {
-        Some(x) if x % 2 == 0 => println!("The number {x} is even"),
-        Some(x) => println!("The number {x} is odd"),
-        None => (),
-    }
-}
-

Приложение 18-26: Добавление условия сопоставления в образец

-

В этом примере будет напечатано The number 4 is even. Когда num сравнивается с образцом в первой ветке, он совпадает, потому что Some(4) соответствует Some(x). Затем условие сопоставления проверяет, равен ли 0 остаток от деления x на 2 и если это так, то выбирается первая ветка.

-

Если бы num вместо этого было Some(5), условие в сопоставлении первой ветки было бы ложным, потому что остаток от 5 делённый на 2, равен 1, что не равно 0. Ржавчина тогда перешёл бы ко второй ветке, которое совпадает, потому что вторая ветка не имеет условия сопоставления и, следовательно, соответствует любому исходу Some.

-

Невозможно выразить условие if x % 2 == 0 внутри образца, поэтому условие в сопоставлении даёт нам возможность выразить эту логику. Недостатком этой дополнительной выразительности является то, что сборщик не пытается проверять полноту, когда задействованы выражения с условием в сопоставлении.

-

В приложении 18-11 мы упомянули, что можно использовать условия сопоставления для решения нашей сбоев затенения образца. Напомним, что внутри образца в выражении match была создана новая переменная, вместо использования внешней к match переменной. Эта новая переменная означала, что мы не могли выполнить сравнение с помощью значения внешней переменной. В приложении 18-27 показано, как мы можем использовать условие сопоставления для решения этой сбоев.

-

Файл: src/main.rs

-
fn main() {
-    let x = Some(5);
-    let y = 10;
-
-    match x {
-        Some(50) => println!("Got 50"),
-        Some(n) if n == y => println!("Matched, n = {n}"),
-        _ => println!("Default case, x = {x:?}"),
-    }
-
-    println!("at the end: x = {x:?}, y = {y}");
-}
-

Приложение 18-27. Использование условия сопоставления для проверки на равенство со значением внешней переменной

-

Этот код теперь напечатает Default case, x = Some(5). Образец во второй ветке не вводит новую переменную y, которая будет затенять внешнюю y, это означает, что теперь можно использовать внешнюю переменную y в условии сопоставления. Вместо указания образца как Some(y), который бы затенял бы внешнюю y, мы указываем Some(n). Это создаёт новую переменную n, которая ничего не затеняет, так как переменной n нет вне устройства match.

-

Условие сопоставления if n == y не является образцом и следовательно, не вводит новые переменные. Переменная y и есть внешняя y, а не новая затенённая y, и теперь мы можем искать элемент, который будет иметь то же значение, что и внешняя y, путём сравнения n и y.

-

Вы также можете использовать оператор или | в условии сопоставления, чтобы указать несколько образцов; условие сопоставления будет применяться ко всем образцам. В приложении 18-28 показан приоритет соединения условия сопоставления с образцом, который использует |. Важной частью этого примера является то, что условие сопоставления if y применяется к 4, 5, и к 6, хотя это может выглядеть как будто if y относится только к 6.

-
fn main() {
-    let x = 4;
-    let y = false;
-
-    match x {
-        4 | 5 | 6 if y => println!("yes"),
-        _ => println!("no"),
-    }
-}
-

Приложение 18-28: Соединение нескольких образцов с условием сопоставления

-

Условие сопоставления гласит, что ветка совпадает, только если значение x равно 4, 5 или 6, и если y равно true. Когда этот код выполняется, образец первой ветки совпадает, потому что x равно 4, но условие сопоставления if y равно false, поэтому первая ветка не выбрана. Код переходит ко второй ветке, которая совпадает, и эта программа печатает no. Причина в том, что условие if применяется ко всему образцу 4 | 5 | 6, а не только к последнему значению 6. Другими словами, приоритет условия сопоставления по отношению к образцу ведёт себя так:

-
(4 | 5 | 6) if y => ...
-
-

а не так:

-
4 | 5 | (6 if y) => ...
-
-

После запуска кода, старшинство в поведении становится очевидным: если условие сопоставления применялось бы только к конечному значению в списке, указанном с помощью оператора |, то ветка бы совпала и программа напечатала бы yes.

-

Связывание @

-

Оператор at (@) позволяет создать переменную, которая содержит значение, одновременно с тем, как мы проверяем, соответствует ли это значение образцу. В приложении 18-29 показан пример, в котором мы хотим проверить, что перечисление Message::Hello со значением поля id находится в ряде 3..=7. Но мы также хотим привязать такое значение к переменной id_variable, чтобы использовать его внутри кода данной ветки. Мы могли бы назвать эту переменную id, так же как поле, но для этого примера мы будем использовать другое имя.

-
fn main() {
-    enum Message {
-        Hello { id: i32 },
-    }
-
-    let msg = Message::Hello { id: 5 };
-
-    match msg {
-        Message::Hello {
-            id: id_variable @ 3..=7,
-        } => println!("Found an id in range: {id_variable}"),
-        Message::Hello { id: 10..=12 } => {
-            println!("Found an id in another range")
-        }
-        Message::Hello { id } => println!("Found some other id: {id}"),
-    }
-}
-

Приложение 18-29: Использование @ для привязывания значения в образце, с одновременной его проверкой

-

В этом примере будет напечатано Found an id in range: 5. Указывая id_variable @ перед рядом 3..=7, мы захватываем любое значение, попадающее в ряд, одновременно проверяя, что это значение соответствует ряду в образце.

-

Во второй ветке, где у нас в образце указан только ряд, код этой ветки не имеет переменной, которая содержит действительное значение поля id. Значение поля id могло бы быть 10, 11 или 12, но код, соответствующий этому образцу, не знает, чему оно равно. Код образца не может использовать значение из поля id, потому что мы не сохранили значение id в переменной.

-

В последней ветке, где мы указали переменную без ряда, у нас есть значение, доступное для использования в коде ветки, в переменной с именем id. Причина в том, что мы использовали упрощённый правила написания полей устройства. Но мы не применяли никакого сравнения со значением в поле id в этой ветке, как мы это делали в первых двух ветках: любое значение будет соответствовать этому образцу.

-

Использование @ позволяет проверять значение и сохранять его в переменной в пределах одного образца.

-

Итоги

-

Образцы Ржавчина очень помогают различать разные виды данных. При использовании их в выражениях match, Ржавчина заверяет, что ваши образцы охватывают все возможные значения, потому что иначе ваша программа не собирается. Образцы в указаниях let и свойствах функций делают такие устройства более полезными, позволяя разбивать элементы на более мелкие части, одновременно присваивая их значения переменным. Мы можем создавать простые или сложные образцы в соответствии с нашими потребностями.

-

Далее, в предпоследней главе книги, мы рассмотрим некоторые продвинутые особенности различных возможностей Rust.

-

Расширенные возможности

-

На данный мгновение вы изучили все наиболее используемые части языка программирования Rust. Прежде чем мы выполним ещё один дело в главе 20, мы рассмотрим несколько особенностей языка, с которыми вы можете сталкиваться время от времени, но не использовать каждый день. Вы можете использовать эту главу в качестве справочника, когда столкнётесь с какими-либо незнакомыми вещами. Рассмотренные здесь функции будут полезны в очень отличительных случаейх. Хотя вы, возможно, не будете часто пользоваться ими, мы хотим убедиться, что вы знаете все возможности языка Rust.

-

В этой главе мы рассмотрим:

-
    -
  • Небезопасный Rust: как отказаться от некоторых заверений Ржавчина и взять на себя ответственность за их ручное соблюдение
  • -
  • Продвинутые особенности: сопряженные виды, свойства вида по умолчанию, полностью квалифицированный правила написания, супер-особенности и образец создания (newtype) по отношению к особенностям
  • -
  • Расширенные виды: больше о образце newtype, псевдонимах вида, вид never и виды изменяемыхх размеров
  • -
  • Расширенные функции и замыкания: указатели функций и возврат замыканий
  • -
  • Макросы: способы определения кода, который определяет большую часть кода во время сборки
  • -
-

Это набор возможностей Ржавчина для всех! Давайте погрузимся в него!

-

Unsafe Rust

-

Во всех предыдущих главах этой книги мы обсуждали код на Rust, безопасность памяти в котором обеспечивается во время сборки. Однако внутри Ржавчина скрывается другой язык - небезопасный Rust, который не обеспечивает безопасной работы с памятью. Этот язык называется unsafe Rust и работает также как и первый, но предоставляет вам дополнительные возможности.

-

Небезопасный Ржавчина существует потому что по своей природе постоянной анализ довольно устоявшийся. Когда сборщик пытается определить, соответствует ли код заверениям, то он скорее отвергнет несколько допустимых программ, чем пропустит несколько недопустимых. Не смотря на то, что код может быть в порядке, если сборщик Ржавчина не будет располагать достаточной сведениями, чтобы убедиться в этом, он отвергнет код. В таких случаях вы можете использовать небезопасный код, чтобы сказать сборщику: "Поверь мне, я знаю, что делаю". Однако имейте в виду, что вы используете небезопасный Ржавчина на свой страх и риск: если вы неправильно используете небезопасный код, могут возникнуть сбоев, связанные с нарушением безопасности памяти, например, разыменование нулевого указателя.

-

Другая причина, по которой у Ржавчина есть небезопасное альтер эго, заключается в том, что по существу аппаратное обеспечение компьютера небезопасно. Если Ржавчина не позволял бы вам выполнять небезопасные действия, вы не могли бы выполнять определённые задачи. Ржавчина должен позволить вам использовать системное, низкоуровневое программирование, такое как прямое взаимодействие с операционной системой, или даже написание вашей собственной операционной системы. Возможность написания низкоуровневого, системного кода является одной из целей языка. Давайте рассмотрим, что и как можно делать с небезопасным Rust.

-

Небезопасные сверхспособности

-

Чтобы переключиться на небезопасный Rust, используйте ключевое слово unsafe, а затем начните новый блок, содержащий небезопасный код. В небезопасном Ржавчина можно выполнять пять действий, которые недоступны в безопасном Rust, которые мы называем небезопасными супер силами. Эти супер силы включают в себя следующее:

-
    -
  • Разыменование сырого указателя
  • -
  • Вызов небезопасной функции или небезопасного способа
  • -
  • Доступ или изменение изменяемой постоянной переменной
  • -
  • Выполнение небезопасного особенности
  • -
  • Доступ к полям в union
  • -
-

Важно понимать, что unsafe не отключает проверку заимствования или любые другие проверки безопасности Rust: если вы используете ссылку в небезопасном коде, она всё равно будет проверена. Единственное, что делает ключевое слово unsafe - даёт вам доступ к этим пяти возможностям, безопасность работы с памятью в которых не проверяет сборщик. Вы по-прежнему получаете некоторую степень безопасности внутри небезопасного раздела.

-

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

-

Люди подвержены ошибкам и ошибки будут происходить, но требуя размещение этих четырёх небезопасных действия внутри разделов, помеченных как unsafe, вы будете знать, что любые ошибки, связанные с безопасностью памяти, будут находиться внутри unsafe разделов. Делайте unsafe разделы маленькими; вы будете благодарны себе за это позже, при исследовании ошибок с памятью.

-

Чтобы наиболее изолировать небезопасный код, советуется заключить небезопасный код в безопасную абстракцию и предоставить безопасный API, который мы обсудим позже, когда будем обсуждать небезопасные функции и способы. Части встроенной библиотеки выполнены как проверенные, безопасные абстракции над небезопасным кодом. Оборачивание небезопасного кода в безопасную абстракцию предотвращает возможную утечку использования unsafe кода во всех местах, где вы или ваши пользователи могли бы захотеть напрямую использовать возможность, выполненную unsafe кодом, потому что использование безопасной абстракции само безопасно.

-

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

-

Разыменование сырых указателей

-

В главе 4 раздела "Недействительные ссылки" мы упоминали, что сборщик заверяет, что ссылки всегда действительны. Небезопасный Ржавчина имеет два новых вида, называемых сырыми указателями (raw pointers), которые похожи на ссылки. Как и в случае ссылок, сырые указатели могут быть неизменяемыми или изменяемыми и записываться как *const T и *mut T соответственно. Звёздочка не является оператором разыменования; это часть имени вида. В среде сырых указателей неизменяемый (immutable) означает, что указателю нельзя напрямую присвоить что-то после того как он разыменован.

-

В отличие от ссылок и умных указателей, сырые указатели:

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

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

-

В приложении 19-1 показано, как создать неизменяемый и изменяемый сырой указатель из ссылок.

-
fn main() {
-    let mut num = 5;
-
-    let r1 = &num as *const i32;
-    let r2 = &mut num as *mut i32;
-}
-

Приложение 19-1: Создание необработанных указателей из ссылок

-

Обратите внимание, что мы не используем ключевое слово unsafe в этом коде. Можно создавать сырые указатели в безопасном коде; мы просто не можем разыменовывать сырые указатели за пределами небезопасного раздела, как вы увидите чуть позже.

-

Мы создали сырые указатели, используя as для приведения неизменяемой и изменяемой ссылки к соответствующим им видам сырых указателей. Поскольку мы создали их непосредственно из ссылок, которые обязательно являются действительными, мы знаем, что эти определенные сырые указатели являются действительными, но мы не можем делать такое же предположение о любом сыром указателе.

-

Чтобы отобразить это, создадим сырой указатель, в достоверности которого мы не можем быть так уверены. В приложении 19-2 показано, как создать необработанный указатель на произвольное место в памяти. Попытка использовать произвольную память является непредсказуемой: по этому адресу могут быть данные, а могут и не быть, сборщик может перерабатывать код так, что доступа к памяти не будет, или программа может завершиться с ошибкой сегментации. Обычно нет веских причин писать такой код, но это возможно.

-
fn main() {
-    let address = 0x012345usize;
-    let r = address as *const i32;
-}
-

Приложение 19-2: Создание сырого указателя на произвольный адрес памяти

-

Напомним, что можно создавать сырые указатели в безопасном коде, но нельзя разыменовывать сырые указатели и читать данные, на которые они указывают. В приложении 19-3 мы используем оператор разыменования * для сырого указателя, который требует unsafe раздела.

-
fn main() {
-    let mut num = 5;
-
-    let r1 = &num as *const i32;
-    let r2 = &mut num as *mut i32;
-
-    unsafe {
-        println!("r1 is: {}", *r1);
-        println!("r2 is: {}", *r2);
-    }
-}
-

Приложение 19-3: Разыменование сырых указателей в разделе unsafe

-

Создание указателей безопасно. Только при попытке доступа к предмету по адресу в указателе мы можем получить недопустимое значение.

-

Также обратите внимание, что в примерах кода 19-1 и 19-3 мы создали *const i32 и *mut i32, которые ссылаются на одну и ту же область памяти, где хранится num. Если мы попытаемся создать неизменяемую и изменяемую ссылку на num вместо сырых указателей, такой код не собирается, т.к. будут нарушены правила заимствования, запрещающие наличие изменяемой ссылки одновременно с неизменяемыми ссылками. С помощью сырых указателей мы можем создать изменяемый указатель и неизменяемый указатель на одну и ту же область памяти и изменять данные с помощью изменяемого указателя, возможно создавая эффект гонки данных. Будьте осторожны!

-

С учётом всех этих опасностей, зачем тогда использовать сырые указатели? Одним из основных применений является взаимодействие с кодом C, как вы увидите в следующем разделе "Вызов небезопасной функции или способа". Другой случай это создание безопасных абстракций, которые не понимает анализатор заимствований. Мы введём понятие небезопасных функций и затем рассмотрим пример безопасной абстракции, которая использует небезопасный код.

-

Вызов небезопасной функции или способа

-

Второй вид действий, которые можно выполнять в небезопасном разделе - это вызов небезопасных функций. Небезопасные функции и способы выглядят точно так же, как обычные функции и способы, но перед остальным определением у них есть дополнительное unsafe. Ключевое слово unsafe в данном среде указывает на то, что к функции предъявляются требования, которые мы должны соблюдать при вызове этой функции, поскольку Ржавчина не может обеспечить, что мы их выполняем. Вызывая небезопасную функцию внутри раздела unsafe, мы говорим, что прочитали документацию к этой функции и берём на себя ответственность за соблюдение её условий.

-

Вот небезопасная функция с именем dangerous которая ничего не делает в своём теле:

-
fn main() {
-    unsafe fn dangerous() {}
-
-    unsafe {
-        dangerous();
-    }
-}
-

Мы должны вызвать функцию dangerous в отдельном unsafe разделе. Если мы попробуем вызвать dangerous без unsafe раздела, мы получим ошибку:

-
$ cargo run
-   Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
-error[E0133]: call to unsafe function `dangerous` is unsafe and requires unsafe function or block
- --> src/main.rs:4:5
-  |
-4 |     dangerous();
-  |     ^^^^^^^^^^^ call to unsafe function
-  |
-  = note: consult the function's documentation for information on how to avoid undefined behavior
-
-For more information about this error, try `rustc --explain E0133`.
-error: could not compile `unsafe-example` (bin "unsafe-example") due to 1 previous error
-
-

С помощью раздела unsafe мы сообщаем Rust, что прочитали документацию к функции, поняли, как правильно её использовать, и убедились, что выполняем договор функции.

-

Тела небезопасных функций являются в действительности unsafe разделами, поэтому для выполнения других небезопасных действий внутри небезопасной функции не нужно добавлять ещё один unsafe блок.

-

Создание безопасных абстракций вокруг небезопасного кода

-

То, что функция содержит небезопасный код, не означает, что мы должны пометить всю функцию как небезопасную. На самом деле, обёртывание небезопасного кода в безопасную функцию - это обычная абстракция. В качестве примера рассмотрим функцию split_at_mut из встроенной библиотеки, которая требует некоторого небезопасного кода. Рассмотрим, как мы могли бы её выполнить. Этот безопасный способ определён для изменяемых срезов: он берет один срез и превращает его в два, разделяя срез по порядковому указателю, указанному в качестве переменной. В приложении 19-4 показано, как использовать split_at_mut.

-
fn main() {
-    let mut v = vec![1, 2, 3, 4, 5, 6];
-
-    let r = &mut v[..];
-
-    let (a, b) = r.split_at_mut(3);
-
-    assert_eq!(a, &mut [1, 2, 3]);
-    assert_eq!(b, &mut [4, 5, 6]);
-}
-

Приложение 19-4: Использование безопасной функции split_at_mut

-

Эту функцию нельзя выполнить, используя только безопасный Rust. Попытка выполнения могла бы выглядеть примерно как в приложении 19-5, который не собирается. Для простоты мы выполняем split_at_mut как функцию, а не как способ, и только для значений вида i32, а не обобщённого вида T.

-
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
-    let len = values.len();
-
-    assert!(mid <= len);
-
-    (&mut values[..mid], &mut values[mid..])
-}
-
-fn main() {
-    let mut vector = vec![1, 2, 3, 4, 5, 6];
-    let (left, right) = split_at_mut(&mut vector, 3);
-}
-

Приложение 19-5: Попытка выполнения split_at_mut с использованием только безопасного Rust

-

Эта функция сначала получает общую длину среза. Затем она проверяет (assert), что порядковый указатель, переданный в качестве свойства, находится в границах среза, сравнивая его с длиной. Assert означает, что если мы передадим порядковый указатель, который больше, чем длина среза, функция запаникует ещё до попытки использования этого порядкового указателя.

-

Затем мы возвращаем два изменяемых отрывка в упорядоченном ряде: один от начала исходного отрывка до mid порядкового указателя (не включая сам mid), а другой - от mid (включая сам mid) до конца отрывка.

-

При попытке собрать код в приложении 19-5, мы получим ошибку.

-
$ cargo run
-   Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
-error[E0499]: cannot borrow `*values` as mutable more than once at a time
- --> src/main.rs:6:31
-  |
-1 | fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
-  |                         - let's call the lifetime of this reference `'1`
-...
-6 |     (&mut values[..mid], &mut values[mid..])
-  |     --------------------------^^^^^^--------
-  |     |     |                   |
-  |     |     |                   second mutable borrow occurs here
-  |     |     first mutable borrow occurs here
-  |     returning this value requires that `*values` is borrowed for `'1`
-  |
-  = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices
-
-For more information about this error, try `rustc --explain E0499`.
-error: could not compile `unsafe-example` (bin "unsafe-example") due to 1 previous error
-
-

Анализатор заимствований Ржавчина не может понять, что мы заимствуем различные части среза, он понимает лишь, что мы хотим осуществить заимствование частей одного среза дважды. Заимствование различных частей среза в принципе в порядке вещей, потому что они не перекрываются, но Ржавчина недостаточно умён, чтобы это понять. Когда мы знаем, что код верный, но Ржавчина этого не понимает, значит пришло время прибегнуть к небезопасному коду.

-

Приложение 19-6 отображает, как можно использовать unsafe блок, сырой указатель и вызовы небезопасных функций чтобы split_at_mut заработала:

-
use std::slice;
-
-fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
-    let len = values.len();
-    let ptr = values.as_mut_ptr();
-
-    assert!(mid <= len);
-
-    unsafe {
-        (
-            slice::from_raw_parts_mut(ptr, mid),
-            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
-        )
-    }
-}
-
-fn main() {
-    let mut vector = vec![1, 2, 3, 4, 5, 6];
-    let (left, right) = split_at_mut(&mut vector, 3);
-}
-

Приложение 19-6. Использование небезопасного кода в выполнения функции split_at_mut

-

Напомним, из раздела "Вид срез" главы 4, что срезы состоят из указателя на некоторые данные и длины. Мы используем способ len для получения длины среза и способ as_mut_ptr для доступа к сырому указателю среза. Поскольку у нас есть изменяемый срез на значения вида i32, функция as_mut_ptr возвращает сырой указатель вида *mut i32, который мы сохранили в переменной ptr.

-

Далее проверяем, что порядковый указательmid находится в границах среза. Затем мы обращаемся к небезопасному коду: функция slice::from_raw_parts_mut принимает сырой указатель, длину и создаёт срез. Мы используем эту функцию для создания среза, начинающегося с ptr и имеющего длину в mid элементов. Затем мы вызываем способ add у ptr с mid в качестве переменной, чтобы получить сырой указатель, который начинается с mid, и создаём срез, используя этот указатель и оставшееся количество элементов после mid в качестве длины.

-

Функция slice::from_raw_parts_mut является небезопасной, потому что она принимает необработанный указатель и должна полагаться на то, что этот указатель действителен. Способ add для необработанных указателей также небезопасен, поскольку он должен считать, что местоположение смещения также является действительным указателем. Поэтому мы были вынуждены разместить unsafe разделвокруг наших вызовов slice::from_raw_parts_mut и add, чтобы иметь возможность вызвать их. Посмотрев на код и добавив утверждение, что mid должен быть меньше или равен len, мы можем сказать, что все необработанные указатели, используемые в разделе unsafe, будут правильными указателями на данные внутри среза. Это приемлемое и уместное использование unsafe.

-

Обратите внимание, что нам не нужно помечать результирующую функцию split_at_mut как unsafe, и мы можем вызвать эту функцию из безопасного Rust. Мы создали безопасную абстракцию для небезопасного кода с помощью выполнения функции, которая использует код unsafe раздела безопасным образом, поскольку она создаёт только допустимые указатели из данных, к которым эта функция имеет доступ.

-

Напротив, использование slice::from_raw_parts_mut в приложении 19-7 приведёт к вероятному сбою при использовании среза. Этот код использует произвольный адрес памяти и создаёт срез из 10000 элементов.

-
fn main() {
-    use std::slice;
-
-    let address = 0x01234usize;
-    let r = address as *mut i32;
-
-    let values: &[i32] = unsafe { slice::from_raw_parts_mut(r, 10000) };
-}
-

Приложение 19-7: Создание среза из произвольного адреса памяти

-

Мы не владеем памятью в этом произвольном месте, и нет никакой заверения, что созданный этим кодом отрывок содержит допустимые значения i32. Попытка использовать values так, как будто это допустимый срез, приводит к неопределённому поведению.

-

Использование extern функций для вызова внешнего кода

-

Иногда вашему коду на языке Ржавчина может потребоваться взаимодействие с кодом, написанным на другом языке. Для этого в Ржавчина есть ключевое слово extern, которое облегчает создание и использование внешней оболочки внешних функций (Foreign Function Interface - FFI). FFI - это способ для языка программирования определить функции и позволить другому (внешнему) языку программирования вызывать эти функции.

-

Приложение 19-8 отображает, как настроить встраивание с функцией abs из встроенной библиотеки C. Функции, объявленные внутри разделов extern, всегда небезопасны для вызова из кода Rust. Причина в том, что другие языки не обеспечивают соблюдение правил и заверений Rust, Ржавчина также не может проверить заверения, поэтому ответственность за безопасность ложится на программиста.

-

Имя файла: src/main.rs

-
extern "C" {
-    fn abs(input: i32) -> i32;
-}
-
-fn main() {
-    unsafe {
-        println!("Absolute value of -3 according to C: {}", abs(-3));
-    }
-}
-

Приложение 19-8: Объявление и вызов extern функции, написанной на другом языке программирования

-

Внутри раздела extern "C" мы перечисляем имена и ярлыки внешних функций из другого языка, которые мы хотим вызвать. Часть "C" определяет какой application binary interface (ABI - двоичный внешняя оболочка приложений) использует внешняя функция. Внешнюю оболочку ABI определяет как вызвать функцию на уровне ассемблера. Использование ABI "C" является наиболее часто используемым и следует правилам ABI внешней оболочки языка Си.

-
-

Вызов функций Ржавчина из других языков

-

Также можно использовать extern для создания внешней оболочки, позволяющего другим языкам вызывать функции Rust. Вместо того чтобы создавать целый разделextern, мы добавляем ключевое слово extern и указываем ABI для использования непосредственно перед ключевым словом fn для необходимой функции. Нам также нужно добавить изложение #[no_mangle], чтобы сказать сборщику Ржавчина не искажать имя этой функции. Искажение - это когда сборщик меняет имя, которое мы дали функции, на другое имя, которое содержит больше сведений для других частей этапа сборки, но менее читабельно для человека. Сборщик каждого языка программирования искажает имена по-разному, поэтому, чтобы функция Ржавчина могла быть использована другими языками, мы должны отключить искажение имён в сборщике Rust.

-

В следующем примере мы делаем функцию call_from_c доступной из кода на C, после того как она будет собрана в разделяемую библиотеку и прилинкована с C:

-
#![allow(unused)]
-fn main() {
-#[no_mangle]
-pub extern "C" fn call_from_c() {
-    println!("Just called a Ржавчина function from C!");
-}
-}
-

Такое использование extern не требует unsafe.

-
-

Получение доступа и внесение изменений в изменяемую постоянную переменную

-

В этой книге мы ещё не говорили о вездесущих переменных, которые Ржавчина поддерживает, но с которыми могут возникнуть сбоев из-за действующих в Ржавчина правил владения. Если два потока обращаются к одной и той же изменяемой вездесущей переменной, это может привести к гонке данных.

-

Вездесущие переменные в Ржавчина называют постоянными (static). Приложение 19-9 отображает пример объявления и использования в качестве значения постоянной переменной, имеющей вид строкового среза:

-

Имя файла: src/main.rs

-
static HELLO_WORLD: &str = "Hello, world!";
-
-fn main() {
-    println!("name is: {HELLO_WORLD}");
-}
-

Приложение 19-9: Определение и использование неизменяемой постоянной переменной

-

Постоянные переменные похожи на постоянные значения, которые мы обсуждали в разделе “Различия между переменными и постоянными значениями” главы 3. Имена постоянных переменных по общему соглашению пишутся в наставлении SCREAMING_SNAKE_CASE, и мы должны указывать вид переменной, которым в данном случае является &'static str. Постоянные переменные могут хранить только ссылки со временем жизни 'static, это означает что сборщик Ржавчина может вывести время жизни и нам не нужно прописывать его явно. Доступ к неизменяемой постоянной переменной является безопасным.

-

Тонкое различие между постоянными значениями и неизменяемыми постоянными переменными заключается в том, что значения в постоянной переменной имеют определенный адрес в памяти. При использовании значения всегда будут доступны одни и те же данные. Постоянного значения, с другой стороны, могут повторять свои данные при каждом использовании. Ещё одно отличие заключается в том, что постоянные переменные могут быть изменяемыми. Обращение к изменяемым постоянном переменным и их изменение является небезопасным. В приложении 19-10 показано, как объявить, получить доступ и изменять изменяемую постоянную переменную с именем COUNTER.

-

Имя файла: src/main.rs

-
static mut COUNTER: u32 = 0;
-
-fn add_to_count(inc: u32) {
-    unsafe {
-        COUNTER += inc;
-    }
-}
-
-fn main() {
-    add_to_count(3);
-
-    unsafe {
-        println!("COUNTER: {COUNTER}");
-    }
-}
-

Приложение 19-10: Чтение из изменяемой постоянной переменной или запись в неё небезопасны

-

Как и с обычными переменными, мы определяем изменяемость с помощью ключевого слова mut. Любой код, который читает из или пишет в переменную COUNTER должен находиться в unsafe разделе. Этот код собирается и печатает COUNTER: 3, как и следовало ожидать, потому что выполняется в одном потоке. Наличие нескольких потоков с доступом к COUNTER приведёт к случаи гонки данных.

-

Наличие изменяемых данных, которые доступны вездесуще, делает трудным выполнение заверения отсутствия гонок данных, поэтому Ржавчина считает изменяемые постоянные переменные небезопасными. Там, где это возможно, предпочтительно использовать техники многопоточности и умные указатели, направленные на многопоточное исполнение, которые мы обсуждали в главе 16. Таким образом, сборщик сможет проверить, что обращение к данным, доступным из разных потоков, выполняется безопасно.

-

Выполнение небезопасных особенностей

-

Мы можем использовать unsafe для выполнения небезопасного особенности. Особенность является небезопасным, если хотя бы один из его способов имеет некоторый неизменная величина, который сборщик не может проверить. Мы объявляем особенности unsafe, добавляя ключевое слово unsafe перед trait и помечая выполнение особенности как unsafe, как показано в приложении 19-11.

-
unsafe trait Foo {
-    // methods go here
-}
-
-unsafe impl Foo for i32 {
-    // method implementations go here
-}
-
-fn main() {}
-

Приложение 19-11: Определение и выполнение небезопасного особенности

-

Используя unsafe impl, мы даём обещание поддерживать неизменные величины, которые сборщик не может проверить.

-

Для примера вспомним маркерные особенности Sync и Send, которые мы обсуждали в разделе "Расширяемый одновременность с помощью особенностей Sync и Send" главы 16: сборщик выполняет эти особенности самостоятельно , если наши виды полностью состоят из видов Send и Sync. Если мы создадим вид, который содержит вид, не являющийся Send или Sync, такой, как сырой указатель, и мы хотим пометить этот вид как Send или Sync, мы должны использовать unsafe блок. Ржавчина не может проверить, что наш вид поддерживает заверения того, что он может быть безопасно передан между потоками или доступен из нескольких потоков; поэтому нам нужно добавить эти проверки вручную и указать это с помощью unsafe.

-

Доступ к полям объединений (union)

-

Последнее действие, которое работает только с unsafe - это доступ к полям union. union похож на struct, но в каждом определенном образце одновременно может использоваться только одно объявленное поле. Объединения в основном используются для взаимодействия с объединениями в коде на языке Си. Доступ к полям объединений небезопасен, поскольку Ржавчина не может обязательно определить вид данных, которые в данный мгновение хранятся в образце объединения. Подробнее об объединениях вы можете узнать в the Ржавчина Reference.

-

Когда использовать небезопасный код

-

Использование unsafe для выполнения одного из пяти действий (супер способностей), которые только что обсуждались, не является ошибочным или не одобренным. Но получить правильный unsafe код сложнее, потому что сборщик не может помочь в обеспечении безопасности памяти. Если у вас есть причина использовать unsafe код, вы можете делать это, а наличие явной unsafe изложении облегчает отслеживание источника неполадок. если они возникают.

-

Продвинутые особенности

-

Мы познакомились с особенностями в разделе "Особенности: Определение общего поведения" в главе 10, но там мы не обсуждали более сложные подробности. Теперь, когда вы больше знаете о Rust, мы можем перейти к более подробному рассмотрению.

-

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

-

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

-

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

-

Одним из примеров особенности с сопряженным видом является особенность Iterator из встроенной библиотеки. Сопряженный вид называется Item и символизирует вид значений, по которым повторяется вид, выполняющий особенность Iterator. Определение особенности Iterator показано в приложении 19-12.

-
pub trait Iterator {
-    type Item;
-
-    fn next(&mut self) -> Option<Self::Item>;
-}
-

Приложение 19-12: Определение особенности Iterator, который имеет сопряженный вид Item

-

Вид Item является заполнителем и определение способа next показывает, что он будет возвращать значения вида Option<Self::Item>. Разработчики особенности Iterator определят определенный вид для Item, а способ next вернёт Option содержащий значение этого определенного вида.

-

Сопряженные виды могут показаться подходом похожей на обобщения, поскольку последние позволяют нам определять функцию, не указывая, какие виды она может обрабатывать. Чтобы изучить разницу между этими двумя подходами, мы рассмотрим выполнение особенности Iterator для вида с именем Counter, который указывает, что вид Item равен u32:

-

Файл: src/lib.rs

-
struct Counter {
-    count: u32,
-}
-
-impl Counter {
-    fn new() -> Counter {
-        Counter { count: 0 }
-    }
-}
-
-impl Iterator for Counter {
-    type Item = u32;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        // --snip--
-        if self.count < 5 {
-            self.count += 1;
-            Some(self.count)
-        } else {
-            None
-        }
-    }
-}
-

Этот правила написания весьма напоминает обобщённые виды. Так почему же особенность Iterator не определён обобщённым видом, как показано в приложении 19-13?

-
pub trait Iterator<T> {
-    fn next(&mut self) -> Option<T>;
-}
-

Приложение 19-13: Гипотетическое определение особенности Iterator используя обобщённые виды

-

Разница в том, что при использовании обобщений, как показано в приложении 19-13, мы должны определять виды в каждой выполнения; потому что мы также можем выполнить Iterator<String> for Counter или любого другого вида, мы могли бы иметь несколько выполнения Iterator для Counter. Другими словами, когда особенность имеет обобщённый свойство, он может быть выполнен для вида несколько раз, каждый раз меняя определенные виды свойств обобщённого вида. Когда мы используем способ next у Counter, нам пришлось бы предоставить изложении вида, указывая какую выполнение Iterator мы хотим использовать.

-

С сопряженными видами не нужно определять виды, потому что мы не можем выполнить особенность у вида несколько раз. В приложении 19-12 с определением, использующим сопряженные виды можно выбрать только один вид Item, потому что может быть только одно объявление impl Iterator for Counter. Нам не нужно указывать, что нужен повторитель значений вида u32 везде, где мы вызываем next у Counter.

-

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

-

Свойства обобщённого вида по умолчанию и перегрузка операторов

-

Когда мы используем свойства обобщённого вида, мы можем указать определенный вид по умолчанию для обобщённого вида. Это устраняет необходимость разработчикам указывать определенный вид, если работает вид по умолчанию. Вид по умолчанию указывается при объявлении обобщённого вида с помощью правил написания <PlaceholderType=ConcreteType>.

-

Отличным примером, когда этот способ полезен, является перегрузка оператора (operator overloading), когда вы настраиваете поведение оператора (например, + ) для определённых случаев.

-

Rust не позволяет создавать собственные операторы или перегружать произвольные операторы. Но можно перегрузить перечисленные действия и соответствующие им особенности из std::ops путём выполнения особенностей, связанных с этими операторами. Например, в приложении 19-14 мы перегружаем оператор +, чтобы складывать два образца Point. Мы делаем это выполняя особенность Add для устройства Point:

-

Файл: src/main.rs

-
use std::ops::Add;
-
-#[derive(Debug, Copy, Clone, PartialEq)]
-struct Point {
-    x: i32,
-    y: i32,
-}
-
-impl Add for Point {
-    type Output = Point;
-
-    fn add(self, other: Point) -> Point {
-        Point {
-            x: self.x + other.x,
-            y: self.y + other.y,
-        }
-    }
-}
-
-fn main() {
-    assert_eq!(
-        Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
-        Point { x: 3, y: 3 }
-    );
-}
-

Приложение 19-14: Выполнение особенности Add для перегрузки оператора + для образцов Point

-

Способ add складывает значения x двух образцов Point и значения y у Point для создания нового образца Point. Особенность Add имеет сопряженный вид с именем Output, который определяет вид, возвращаемый из способа add.

-

Обобщённый вид по умолчанию в этом коде находится в особенности Add . Вот его определение:

-
#![allow(unused)]
-fn main() {
-trait Add<Rhs = Self> {
-    type Output;
-
-    fn add(self, rhs: Rhs) -> Self::Output;
-}
-}
-

Этот код должен выглядеть знакомым: особенность с одним способом и сопряженным видом. Новый правила написания это RHS=Self. Такой правила написания называется свойства вида по умолчанию (default type parameters). Свойство обобщённого вида RHS (сокращённо “right hand side”) определяет вид свойства rhs в способе add. Если мы не укажем определенный вид для RHS при выполнения особенности Add, то видом для RHS по умолчанию будет Self, который будет видом для которого выполняется особенность Add.

-

Когда мы выполнили Add для устройства Point, мы использовали обычное значение для RHS, потому что хотели сложить два образца Point. Давайте посмотрим на пример выполнения особенности Add, где мы хотим пользовательский вид RHS вместо использования вида по умолчанию.

-

У нас есть две разные устройства Millimeters и Meters, хранящие значения в разных единицах измерения. Это тонкое обёртывание существующего вида в другую устройство известно как образец newtype, который мы более подробно опишем в разделе "Образец Newtype для выполнение внешних особенностей у внешних видов" . Мы хотим добавить значения в миллиметрах к значениям в метрах и хотим иметь выполнение особенности Add, которая делает правильное преобразование единиц. Можно выполнить Add для Millimeters с видом Meters в качестве Rhs, как показано в приложении 19-15.

-

Файл: src/lib.rs

-
use std::ops::Add;
-
-struct Millimeters(u32);
-struct Meters(u32);
-
-impl Add<Meters> for Millimeters {
-    type Output = Millimeters;
-
-    fn add(self, other: Meters) -> Millimeters {
-        Millimeters(self.0 + (other.0 * 1000))
-    }
-}
-

Приложение 19-15: Выполнение особенности Add для устройства Millimeters, чтобы складывать Millimeters и Meters

-

Чтобы сложить Millimeters и Meters, мы указываем impl Add<Meters>, чтобы указать значение свойства вида RHS (Meters) вместо использования значения по умолчанию Self (Millimeters).

-

Свойства вида по умолчанию используются в двух основных случаях:

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

Особенность Add из встроенной библиотеки является примером второй цели: обычно вы складываете два одинаковых вида, но особенность Add позволяет сделать больше. Использование свойства вида по умолчанию в объявлении особенности Add означает, что не нужно указывать дополнительный свойство большую часть времени. Другими словами, большая часть кода выполнения не нужна, что делает использование особенности проще.

-

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

-

Полностью квалифицированный правила написания для устранения неоднозначности: вызов способов с одинаковым именем

-

В Ржавчина ничего не мешает особенности иметь способ с одинаковым именем, таким же как способ другого особенности и Ржавчина не мешает выполнить оба таких особенности у одного вида. Также возможно выполнить способ с таким же именем непосредственно у вида, такой как и способы у особенностей.

-

При вызове способов с одинаковыми именами в Ржавчина нужно указать, какой из трёх возможных вы хотите использовать. Рассмотрим код в приложении 19-16, где мы определили два особенности: Pilot и Wizard, у обоих есть способ fly. Затем мы выполняем оба особенности у вида Human в котором уже выполнен способ с именем fly. Каждый способ fly делает что-то своё.

-

Файл: src/main.rs

-
trait Pilot {
-    fn fly(&self);
-}
-
-trait Wizard {
-    fn fly(&self);
-}
-
-struct Human;
-
-impl Pilot for Human {
-    fn fly(&self) {
-        println!("This is your captain speaking.");
-    }
-}
-
-impl Wizard for Human {
-    fn fly(&self) {
-        println!("Up!");
-    }
-}
-
-impl Human {
-    fn fly(&self) {
-        println!("*waving arms furiously*");
-    }
-}
-
-fn main() {}
-

Приложение 19-16: Два особенности определены с способом fly и выполнены у вида Human, а также способ fly выполнен непосредственно у Human

-

Когда мы вызываем fly у образца Human, то сборщик по умолчанию вызывает способ, который непосредственно выполнен для вида, как показано в приложении 19-17.

-

Файл: src/main.rs

-
trait Pilot {
-    fn fly(&self);
-}
-
-trait Wizard {
-    fn fly(&self);
-}
-
-struct Human;
-
-impl Pilot for Human {
-    fn fly(&self) {
-        println!("This is your captain speaking.");
-    }
-}
-
-impl Wizard for Human {
-    fn fly(&self) {
-        println!("Up!");
-    }
-}
-
-impl Human {
-    fn fly(&self) {
-        println!("*waving arms furiously*");
-    }
-}
-
-fn main() {
-    let person = Human;
-    person.fly();
-}
-

Приложение 19-17: Вызов fly у образца Human

-

Запуск этого кода напечатает *waving arms furiously* , показывая, что Ржавчина называется способ fly выполненный непосредственно у Human.

-

Чтобы вызвать способы fly у особенности Pilot или особенности Wizard нужно использовать более явный правила написания, указывая какой способ fly мы имеем в виду. Приложение 19-18 отображает такой правила написания.

-

Файл: src/main.rs

-
trait Pilot {
-    fn fly(&self);
-}
-
-trait Wizard {
-    fn fly(&self);
-}
-
-struct Human;
-
-impl Pilot for Human {
-    fn fly(&self) {
-        println!("This is your captain speaking.");
-    }
-}
-
-impl Wizard for Human {
-    fn fly(&self) {
-        println!("Up!");
-    }
-}
-
-impl Human {
-    fn fly(&self) {
-        println!("*waving arms furiously*");
-    }
-}
-
-fn main() {
-    let person = Human;
-    Pilot::fly(&person);
-    Wizard::fly(&person);
-    person.fly();
-}
-

Приложение 19-18: Указание какой способа fly мы хотим вызвать

-

Указание имени особенности перед именем способа проясняет сборщику Rust, какую именно выполнение fly мы хотим вызвать. Мы могли бы также написать Human::fly(&person), что эквивалентно используемому нами person.fly() в приложении 19-18, но это писание немного длиннее, когда нужна неоднозначность.

-

Выполнение этого кода выводит следующее:

-
$ cargo run
-   Compiling traits-example v0.1.0 (file:///projects/traits-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.46s
-     Running `target/debug/traits-example`
-This is your captain speaking.
-Up!
-*waving arms furiously*
-
-

Поскольку способ fly принимает свойство self, если у нас было два вида оба выполняющих один особенность, то Ржавчина может понять, какую выполнение особенности использовать в зависимости от вида self.

-

Однако, сопряженные функции, не являющиеся способами, не имеют свойства self. Когда существует несколько видов или особенностей, определяющих функции, не являющиеся способами, с одним и тем же именем функции, Ржавчина не всегда знает, какой вид вы имеете в виду, если только вы не используете полный правила написания. Например, в приложении 19-19 мы создаём особенность для приюта животных, который хочет назвать всех маленьких собак Spot. Мы создаём особенность Animal со связанной с ним функцией baby_name, не являющейся способом. Особенность Animal выполнен для устройства Dog, для которой мы также напрямую предоставляем связанную функцию baby_name, не являющуюся способом.

-

Файл: src/main.rs

-
trait Animal {
-    fn baby_name() -> String;
-}
-
-struct Dog;
-
-impl Dog {
-    fn baby_name() -> String {
-        String::from("Spot")
-    }
-}
-
-impl Animal for Dog {
-    fn baby_name() -> String {
-        String::from("puppy")
-    }
-}
-
-fn main() {
-    println!("A baby dog is called a {}", Dog::baby_name());
-}
-

Приложение 19-19: Особенность с сопряженной функцией и вид с сопряженной функцией с тем же именем, которая тоже выполняет особенность

-

Мы выполнили код для приюта для животных, который хочет назвать всех щенков именем Spot, в сопряженной функции baby_name, которая определена для Dog. Вид Dog также выполняет особенность Animal, который описывает свойства, которые есть у всех животных. Маленьких собак называют щенками, и это выражается в выполнения Animal у Dog в функции baby_name сопряженной с особенностью Animal.

-

В main мы вызываем функцию Dog::baby_name, которая вызывает сопряженную функцию определённую напрямую у Dog. Этот код печатает следующее:

-
$ cargo run
-   Compiling traits-example v0.1.0 (file:///projects/traits-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.54s
-     Running `target/debug/traits-example`
-A baby dog is called a Spot
-
-

Этот вывод не является тем, что мы хотели бы получить. Мы хотим вызвать функцию baby_name, которая является частью особенности Animal выполненного у Dog, так чтобы код печатал A baby dog is called a puppy. Техника указания имени особенности использованная в приложении 19-18 здесь не помогает; если мы изменим main код как в приложении 19-20, мы получим ошибку сборки.

-

Файл: src/main.rs

-
trait Animal {
-    fn baby_name() -> String;
-}
-
-struct Dog;
-
-impl Dog {
-    fn baby_name() -> String {
-        String::from("Spot")
-    }
-}
-
-impl Animal for Dog {
-    fn baby_name() -> String {
-        String::from("puppy")
-    }
-}
-
-fn main() {
-    println!("A baby dog is called a {}", Animal::baby_name());
-}
-

Приложение 19-20. Попытка вызвать функцию baby_name из особенности Animal, но Ржавчина не знает какую выполнение использовать

-

Поскольку Animal::baby_name не имеет свойства self, и могут быть другие виды, выполняющие особенность Animal, Ржавчина не может понять, какую выполнение Animal::baby_name мы хотим использовать. Мы получим эту ошибку сборщика:

-
$ cargo run
-   Compiling traits-example v0.1.0 (file:///projects/traits-example)
-error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type
-  --> src/main.rs:20:43
-   |
-2  |     fn baby_name() -> String;
-   |     ------------------------- `Animal::baby_name` defined here
-...
-20 |     println!("A baby dog is called a {}", Animal::baby_name());
-   |                                           ^^^^^^^^^^^^^^^^^^^ cannot call associated function of trait
-   |
-help: use the fully-qualified path to the only available implementation
-   |
-20 |     println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
-   |                                           +++++++       +
-
-For more information about this error, try `rustc --explain E0790`.
-error: could not compile `traits-example` (bin "traits-example") due to 1 previous error
-
-

Чтобы устранить неоднозначность и сказать Rust, что мы хотим использовать выполнение Animal для Dog, нужно использовать полный правила написания. Приложение 19-21 отображает, как использовать полный правила написания.

-

Файл: src/main.rs

-
trait Animal {
-    fn baby_name() -> String;
-}
-
-struct Dog;
-
-impl Dog {
-    fn baby_name() -> String {
-        String::from("Spot")
-    }
-}
-
-impl Animal for Dog {
-    fn baby_name() -> String {
-        String::from("puppy")
-    }
-}
-
-fn main() {
-    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
-}
-

Приложение 19-21: Использование полного правил написания для указания, что мы мы хотим вызвать функцию baby_name у особенности Animal выполненную в Dog

-

Мы указываем изложение вида в угловых скобках, которая указывает на то что мы хотим вызвать способ baby_name из особенности Animal выполненный в Dog, также указывая что мы хотим рассматривать вид Dog в качестве Animal для вызова этой функции. Этот код теперь напечатает то, что мы хотим:

-
$ cargo run
-   Compiling traits-example v0.1.0 (file:///projects/traits-example)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
-     Running `target/debug/traits-example`
-A baby dog is called a puppy
-
-

В общем, полный правила написания определяется следующим образом:

-
<Type as Trait>::function(receiver_if_method, next_arg, ...);
-

Для сопряженных функций, которые не являются способами, будет отсутствовать receiver (предмет приёмника): будет только список переменных. Вы можете использовать полный правила написания везде, где вызываете функции или способы. Тем не менее, разрешается опустить любую часть этого правил написания, которую Ржавчина может понять из другой сведений в программе. Вам нужно использовать более подробный правила написания только в тех случаях, когда существует несколько выполнений, использующих одно и то же название, и Ржавчина нужно помочь определить, какую выполнение вы хотите вызвать.

-

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

-

Иногда вы можете написать определение особенности, которое зависит от другого особенности: для вида, выполняющего первый особенность, вы хотите потребовать, чтобы этот вид также выполнил второй особенность. Вы должны сделать это, чтобы ваше определение особенности могло использовать связанные элементы второго особенности. Особенность, на который опирается ваше определение особенности, называется supertrait вашего особенности.

-

Например, мы хотим создать особенность OutlinePrint с способом outline_print, который будет печатать значение обрамлённое звёздочками. Мы хотим чтобы устройства Point, выполняющая особенность встроенной библиотеки Display, вывела на печать (x, y) при вызове outline_print у образца Point, который имеет значение 1 для x и значение 3 для y. Она должна напечатать следующее:

-
**********
-*        *
-* (1, 3) *
-*        *
-**********
-
-

В выполнения outline_print мы хотим использовать возможность особенности Display. Поэтому нам нужно указать, что особенность OutlinePrint будет работать только для видов, которые также выполняют Display и предоставляют возможность, которая нужна в OutlinePrint. Мы можем сделать это в объявлении особенности, указав OutlinePrint: Display. Этот способ похож на добавление ограничения в особенность. В приложении 19-22 показана выполнение особенности OutlinePrint.

-

Файл: src/main.rs

-
use std::fmt;
-
-trait OutlinePrint: fmt::Display {
-    fn outline_print(&self) {
-        let output = self.to_string();
-        let len = output.len();
-        println!("{}", "*".repeat(len + 4));
-        println!("*{}*", " ".repeat(len + 2));
-        println!("* {output} *");
-        println!("*{}*", " ".repeat(len + 2));
-        println!("{}", "*".repeat(len + 4));
-    }
-}
-
-fn main() {}
-

Приложение 19-22: Выполнение особенности OutlinePrint которая требует возможности особенности Display

-

Поскольку мы указали, что особенность OutlinePrint требует особенности Display, мы можем использовать функцию to_string, которая самостоятельно выполнена для любого вида выполняющего Display. Если бы мы попытались использовать to_string не добавляя двоеточие и не указывая особенность Display после имени особенности, мы получили бы сообщение о том, что способ с именем to_string не был найден у вида &Self в текущей области видимости.

-

Давайте посмотрим что происходит, если мы пытаемся выполнить особенность OutlinePrint для вида, который не выполняет Display, например устройства Point:

-

Файл: src/main.rs

-
use std::fmt;
-
-trait OutlinePrint: fmt::Display {
-    fn outline_print(&self) {
-        let output = self.to_string();
-        let len = output.len();
-        println!("{}", "*".repeat(len + 4));
-        println!("*{}*", " ".repeat(len + 2));
-        println!("* {output} *");
-        println!("*{}*", " ".repeat(len + 2));
-        println!("{}", "*".repeat(len + 4));
-    }
-}
-
-struct Point {
-    x: i32,
-    y: i32,
-}
-
-impl OutlinePrint for Point {}
-
-fn main() {
-    let p = Point { x: 1, y: 3 };
-    p.outline_print();
-}
-

Мы получаем сообщение о том, что требуется выполнение Display, но её нет:

-
$ cargo run
-   Compiling traits-example v0.1.0 (file:///projects/traits-example)
-error[E0277]: `Point` doesn't implement `std::fmt::Display`
-  --> src/main.rs:20:23
-   |
-20 | impl OutlinePrint for Point {}
-   |                       ^^^^^ `Point` cannot be formatted with the default formatter
-   |
-   = help: the trait `std::fmt::Display` is not implemented for `Point`
-   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
-note: required by a bound in `OutlinePrint`
-  --> src/main.rs:3:21
-   |
-3  | trait OutlinePrint: fmt::Display {
-   |                     ^^^^^^^^^^^^ required by this bound in `OutlinePrint`
-
-error[E0277]: `Point` doesn't implement `std::fmt::Display`
-  --> src/main.rs:24:7
-   |
-24 |     p.outline_print();
-   |       ^^^^^^^^^^^^^ `Point` cannot be formatted with the default formatter
-   |
-   = help: the trait `std::fmt::Display` is not implemented for `Point`
-   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
-note: required by a bound in `OutlinePrint::outline_print`
-  --> src/main.rs:3:21
-   |
-3  | trait OutlinePrint: fmt::Display {
-   |                     ^^^^^^^^^^^^ required by this bound in `OutlinePrint::outline_print`
-4  |     fn outline_print(&self) {
-   |        ------------- required by a bound in this associated function
-
-For more information about this error, try `rustc --explain E0277`.
-error: could not compile `traits-example` (bin "traits-example") due to 2 previous errors
-
-

Чтобы исправить, мы выполняем Display у устройства Point и выполняем требуемое ограничение OutlinePrint, вот так:

-

Файл: src/main.rs

-
trait OutlinePrint: fmt::Display {
-    fn outline_print(&self) {
-        let output = self.to_string();
-        let len = output.len();
-        println!("{}", "*".repeat(len + 4));
-        println!("*{}*", " ".repeat(len + 2));
-        println!("* {output} *");
-        println!("*{}*", " ".repeat(len + 2));
-        println!("{}", "*".repeat(len + 4));
-    }
-}
-
-struct Point {
-    x: i32,
-    y: i32,
-}
-
-impl OutlinePrint for Point {}
-
-use std::fmt;
-
-impl fmt::Display for Point {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "({}, {})", self.x, self.y)
-    }
-}
-
-fn main() {
-    let p = Point { x: 1, y: 3 };
-    p.outline_print();
-}
-

Тогда выполнение особенности OutlinePrint для устройства Point будет собрана успешно и мы можем вызвать outline_print у образца Point для отображения значения обрамлённое звёздочками.

-

Образец Newtype для выполнение внешних особенностей у внешних видов

-

В разделе "Выполнение особенности у типа" главы 10, мы упоминали "правило сироты" (orphan rule), которое гласит, что разрешается выполнить особенность у вида, если либо особенность, либо вид являются местными для нашего ящика. Можно обойти это ограничение, используя образец нового вида (newtype pattern), который включает в себя создание нового вида в упорядоченной в ряд устройстве. (Мы рассмотрели упорядоченные в ряд устройства в разделе "Использование устройств упорядоченных рядов без именованных полей для создания различных видов" главы 5.) Устройства упорядоченного ряда будет иметь одно поле и будет тонкой оболочкой для вида которому мы хотим выполнить особенность. Тогда вид оболочки является местным для нашего ящика и мы можем выполнить особенность для местной обёртки. Newtype это понятие, который происходит от языка программирования Haskell. В нем нет ухудшения производительности времени выполнения при использовании этого образца и вид оболочки исключается во время сборки.

-

В качестве примера, мы хотим выполнить особенность Display для вида Vec<T>, где "правило сироты" (orphan rule) не позволяет нам этого делать напрямую, потому что особенность Display и вид Vec<T> объявлены вне нашего ящика. Мы можем сделать устройство Wrapper, которая содержит образец Vec<T>; тогда мы можем выполнить Display у устройства Wrapper и использовать значение Vec<T> как показано в приложении 19-23.

-

Файл: src/main.rs

-
use std::fmt;
-
-struct Wrapper(Vec<String>);
-
-impl fmt::Display for Wrapper {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "[{}]", self.0.join(", "))
-    }
-}
-
-fn main() {
-    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
-    println!("w = {w}");
-}
-

Приложение 19-23. Создание вида Wrapper Vec<String> для выполнения Display

-

Выполнение Display использует self.0 для доступа к внутреннему Vec<T>, потому что Wrapper это устройства упорядоченного ряда, а Vec<T> это элемент с порядковым указателем 0 в упорядоченном ряде. Затем мы можем использовать полезные возможности вида Display у Wrapper.

-

Недостатком использования этой техники является то, что Wrapper является новым видом, поэтому он не имеет способов для значения, которое он держит в себе. Мы должны были бы выполнить все способы для Vec<T> непосредственно во Wrapper, так чтобы эти способы делегировались внутреннему self.0, что позволило бы нам обращаться с Wrapper точно так же, как с Vec<T>. Если бы мы хотели, чтобы новый вид имел каждый способ имеющийся у внутреннего вида, выполняя особенность Deref (обсуждается в разделе "Работа с умными указателями как с обычными ссылками с помощью Deref особенности" главы 15) у Wrapper для возвращения внутреннего вида, то это было бы решением. Если мы не хотим, чтобы вид Wrapper имел все способы внутреннего вида, например, для ограничения поведения вида Wrapper, то пришлось бы вручную выполнить только те способы, которые нам нужны.

-

Этот образец newtype также полезен, даже когда особенности не задействованы. Давайте переключим внимание и рассмотрим некоторые продвинутые способы взаимодействия с системой видов Rust.

-

Продвинутые виды

-

Система видов Ржавчина имеет некоторые особенности, о которых мы уже упоминали, но ещё не обсуждали. Мы начнём с общего обзора newtypes, а затем разберёмся, чем они могут пригодиться в качестве видов. Далее мы перейдём к псевдонимам видов - возможности, похожей на newtypes, но с несколько иной смыслом. Мы также обсудим вид ! и виды с изменяемым размером.

-

Использование образца Newtype для обеспечения безопасности видов и создания абстракций

-
-

Примечание: В этом разделе предполагается, что вы прочитали предыдущий раздел "Использование образца Newtype для выполнения внешних особенностей для внешних видов."

-
-

Образец newtype полезен и для других задач, помимо тех, которые мы обсуждали до сих пор, в частности, для постоянного обеспечения того, чтобы значения никогда не путались, а также для указания единиц измерения значения. Пример использования newtypes для указания единиц измерения вы видели в приложении 19-15: вспомните, как устройства Millimeters и Meters обернули значения u32 в newtype. Если бы мы написали функцию с свойствоом вида Millimeters, мы не смогли бы собрать программу, которая случайно попыталась бы вызвать эту функцию со значением вида Meters или обычным u32.

-

Мы также можем использовать образец newtype для абстрагирования от некоторых подробностей выполнения вида: новый вид может предоставлять открытый API, который отличается от API скрытого внутри вида.

-

Newtypes также позволяют скрыть внутреннюю выполнение. Например, мы можем создать вид People, который обернёт HashMap<i32, String>, хранящий ID человека, связанный с его именем. Код, использующий People, будет взаимодействовать только с открытым API, который мы предоставляем, например, способ добавления имени в собрание People; этому коду не нужно будет знать, что внутри мы присваиваем i32 ID именам. Образец newtype - это лёгкий способ достижения инкапсуляции для скрытия подробностей выполнения, который мы обсуждали в разделе "Инкапсуляция, скрывающая подробности выполнения" главы 17.

-

Создание родственных вида с помощью псевдонимов вида

-

Rust предоставляет возможность объявить псевдоним вида чтобы дать существующему виду другое имя. Для этого мы используем ключевое слово type. Например, мы можем создать псевдоним вида Kilometers для i32 следующим образом:

-
fn main() {
-    type Kilometers = i32;
-
-    let x: i32 = 5;
-    let y: Kilometers = 5;
-
-    println!("x + y = {}", x + y);
-}
-

Теперь псевдоним Kilometers является родственным для i32; в отличие от видов Millimeters и Meters, которые мы создали в приложении 19-15, Kilometers не является отдельным, новым видом. Значения, имеющие вид Kilometers, будут обрабатываться так же, как и значения вида i32:

-
fn main() {
-    type Kilometers = i32;
-
-    let x: i32 = 5;
-    let y: Kilometers = 5;
-
-    println!("x + y = {}", x + y);
-}
-

Поскольку Kilometers и i32 являются одним и тем же видом, мы можем добавлять значения обоих видов и передавать значения Kilometers функциям, принимающим свойства i32. Однако, используя этот способ, мы не получаем тех преимуществ проверки видов, которые мы получаем от образца newtype, рассмотренного ранее. Другими словами, если мы где-то перепутаем значения Kilometers и i32, сборщик не выдаст нам ошибку.

-

Родственные в основном используются для сокращения повторений. Например, у нас может быть такой многословный тип:

-
Box<dyn Fn() + Send + 'static>
-

Написание таких длинных видов в ярлыках функций и в виде наставлений видов по всему коду может быть утомительным и чреватым ошибками. Представьте себе дело, наполненный таким кодом, как в приложении 19-24.

-
fn main() {
-    let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));
-
-    fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {
-        // --snip--
-    }
-
-    fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {
-        // --snip--
-        Box::new(|| ())
-    }
-}
-

Приложение 19-24: Использование длинного вида во многих местах

-

Псевдоним вида делает этот код более удобным для работы, сокращая количество повторений. В приложении 19-25 мы ввели псевдоним Thunk для вида verbose и можем заменить все использования этого вида более коротким псевдонимом Thunk.

-
fn main() {
-    type Thunk = Box<dyn Fn() + Send + 'static>;
-
-    let f: Thunk = Box::new(|| println!("hi"));
-
-    fn takes_long_type(f: Thunk) {
-        // --snip--
-    }
-
-    fn returns_long_type() -> Thunk {
-        // --snip--
-        Box::new(|| ())
-    }
-}
-

Приложение 19-25: Представление псевдонима Thunk для уменьшения количества повторений

-

Такой код гораздо легче читать и писать! Выбор осмысленного имени для псевдонима вида также может помочь прояснить ваши намерения (thunk - название для кода, который будет вычисляться позднее, поэтому это подходящее имя для сохраняемого замыкания).

-

Псевдонимы видов также часто используются с видом Result<T, E> для сокращения повторений. Рассмотрим звено std::io в встроенной библиотеке. Действия ввода-вывода часто возвращают Result<T, E> для обработки случаев, когда эти действия не удаются. В данной библиотеке есть устройства std::io::Error, которая отражает все возможные ошибки ввода/вывода. Многие функции в std::io будут возвращать Result<T, E>, где E - это std::io::Error, например, эти функции в особенности Write:

-
use std::fmt;
-use std::io::Error;
-
-pub trait Write {
-    fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
-    fn flush(&mut self) -> Result<(), Error>;
-
-    fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
-    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
-}
-

Result<..., Error> часто повторяется. Поэтому std::io содержит такое объявление псевдонима вида:

-
use std::fmt;
-
-type Result<T> = std::result::Result<T, std::io::Error>;
-
-pub trait Write {
-    fn write(&mut self, buf: &[u8]) -> Result<usize>;
-    fn flush(&mut self) -> Result<()>;
-
-    fn write_all(&mut self, buf: &[u8]) -> Result<()>;
-    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
-}
-

Поскольку это объявление находится в звене std::io, мы можем использовать полный псевдоним std::io::Result<T>; это и есть Result<T, E>, где в качестве E выступает std::io::Error. Ярлыки функций особенности Write в итоге выглядят следующим образом:

-
use std::fmt;
-
-type Result<T> = std::result::Result<T, std::io::Error>;
-
-pub trait Write {
-    fn write(&mut self, buf: &[u8]) -> Result<usize>;
-    fn flush(&mut self) -> Result<()>;
-
-    fn write_all(&mut self, buf: &[u8]) -> Result<()>;
-    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
-}
-

Псевдоним вида помогает двумя способами: он облегчает написание кода и даёт нам согласованный внешняя оболочка для всего из std::io. Поскольку это псевдоним, то это просто ещё один вид Result<T, E>, что означает, что с ним мы можем использовать любые способы, которые работают с Result<T, E>, а также особый правила написания вроде ? оператора.

-

Вид Never, который никогда не возвращается

-

В Ржавчина есть особый вид !, который на жаргоне теории видов известен как empty type (пустой вид), потому что он не содержит никаких значений. Мы предпочитаем называть его never type (никакой вид), потому что он используется в качестве возвращаемого вида, когда функция ничего не возвращает. Вот пример:

-
fn bar() -> ! {
-    // --snip--
-    panic!();
-}
-

Этот код читается как "функция bar ничего не возвращает". Функции, которые ничего не возвращают, называются рассеивающими функциями (diverging functions). Мы не можем производить значения вида !, поэтому bar никогда ничего не вернёт.

-

Но для чего нужен вид, для которого вы никогда не сможете создать значения? Напомним код из приложения 2-5, отрывка "игры в загадки"; мы воспроизвели его часть здесь в приложении 19-26.

-
use rand::Rng;
-use std::cmp::Ordering;
-use std::io;
-
-fn main() {
-    println!("Guess the number!");
-
-    let secret_number = rand::thread_rng().gen_range(1..=100);
-
-    println!("The secret number is: {secret_number}");
-
-    loop {
-        println!("Please input your guess.");
-
-        let mut guess = String::new();
-
-        // --snip--
-
-        io::stdin()
-            .read_line(&mut guess)
-            .expect("Failed to read line");
-
-        let guess: u32 = match guess.trim().parse() {
-            Ok(num) => num,
-            Err(_) => continue,
-        };
-
-        println!("You guessed: {guess}");
-
-        // --snip--
-
-        match guess.cmp(&secret_number) {
-            Ordering::Less => println!("Too small!"),
-            Ordering::Greater => println!("Too big!"),
-            Ordering::Equal => {
-                println!("You win!");
-                break;
-            }
-        }
-    }
-}
-

Приложение 19-26: Сопоставление match с веткой, которая заканчивается continue

-

В то время мы опуисполнения некоторые подробности в этом коде. В главе 6 раздела "Оператор управления потоком match" мы обсуждали, что все ветви match должны возвращать одинаковый вид. Например, следующий код не работает:

-
fn main() {
-    let guess = "3";
-    let guess = match guess.trim().parse() {
-        Ok(_) => 5,
-        Err(_) => "hello",
-    };
-}
-

Вид guess в этом коде должен быть целым и строкой, а Ржавчина требует, чтобы guess имел только один вид. Так что же возвращает continue? Как нам позволили вернуть u32 из одной ветви и при этом иметь другую ветвь, которая оканчивается continue в приложении 19-26?

-

Как вы уже возможно догадались, continue имеет значение !. То есть, когда Ржавчина вычисляет вид guess, он смотрит на обе сопоставляемые ветки, первая со значением u32 и последняя со значением !. Так как ! никогда не может иметь значение, то Ржавчина решает что видом guess является вид u32.

-

Условный подход к описанию такого поведения заключается в том, что выражения вида ! могут быть преобразованы в любой другой вид. Нам позволяется завершить этот match с помощью continue, потому что continue не возвращает никакого значения; вместо этого он передаёт управление обратно в начало цикла, поэтому в случае Err мы никогда не присваиваем значение guess.

-

Вид never полезен также для макроса panic!. Вспомните функцию unwrap, которую мы вызываем для значений Option<T>, чтобы создать значение или вызвать панику с этим определением:

-
enum Option<T> {
-    Some(T),
-    None,
-}
-
-use crate::Option::*;
-
-impl<T> Option<T> {
-    pub fn unwrap(self) -> T {
-        match self {
-            Some(val) => val,
-            None => panic!("called `Option::unwrap()` on a `None` value"),
-        }
-    }
-}
-

В этом коде происходит то же самое, что и в match в приложении 19-26: Ржавчина видит, что val имеет вид T, а panic! имеет вид !, поэтому итогом общего выражения match является T. Этот код работает, потому что panic! не производит никакого значения; он завершает программу. В случае None мы не будем возвращать значение из unwrap, поэтому этот код работает.

-

Последнее выражение, которое имеет вид ! это loop:

-
fn main() {
-    print!("forever ");
-
-    loop {
-        print!("and ever ");
-    }
-}
-

В данном случае цикл никогда не завершится, поэтому ! является значением выражения. Но это не будет так, если мы добавим break, так как цикл завершит свою работу, когда дойдёт до break.

-

Виды с изменяемым размером и особенность Sized

-

Rust необходимо знать некоторые подробности о видах, например, сколько места нужно выделить для значения определённого вида. Из-за этого один из особенностей системы видов поначалу вызывает некоторое недоумение: подход видов с изменяемым размером. Иногда называемые DST или безразмерные виды, эти виды позволяют нам писать код, используя значения, размер которых мы можем узнать только во время выполнения.

-

Давайте углубимся в подробности изменяемого вида str, который мы использовали на протяжении всей книги. Все верно, не вида &str, а вида str самого по себе, который является DST. Мы не можем знать, какой длины строка до особенности времени выполнения, то есть мы не можем создать переменную вида str и не можем принять переменная вида str. Рассмотрим следующий код, который не работает:

-
fn main() {
-    let s1: str = "Hello there!";
-    let s2: str = "How's it going?";
-}
-

Rust должен знать, сколько памяти выделить для любого значения определенного вида и все значения вида должны использовать одинаковый размер памяти. Если Ржавчина позволил бы нам написать такой код, то эти два значения str должны были бы занимать одинаковое количество памяти. Но они имеют разную длину: s1 нужно 12 байтов памяти, а для s2 нужно 15. Вот почему невозможно создать переменную имеющую вид изменяемого размера.

-

Так что же нам делать? В этом случае вы уже знаете ответ: мы преобразуем виды s1 и s2 в &str, а не в str. Вспомните из раздела "Строковые срезы" главы 4, что устройства данных среза просто хранит начальную положение и длину среза. Так, в отличие от &T, который содержит только одно значение - адрес памяти, где находится T, в &str хранятся два значения - адрес str и его длина. Таким образом, мы можем узнать размер значения &str во время сборки: он вдвое больше длины usize. То есть, мы всегда знаем размер &str, независимо от длины строки, на которую оно ссылается. В целом, именно так в Ржавчина используются виды изменяемого размера: они содержат дополнительный бит метаданных, который хранит размер изменяемой сведений. Золотое правило изменяемых размерных видов заключается в том, что мы всегда должны помещать значения таких видов за каким-либо указателем.

-

Мы можем соединенять str со всеми видами указателей: например, Box<str> или Rc<str>. На самом деле, вы уже видели это раньше, но с другим изменяемым размерным видом: особенностями. Каждый особенность - это изменяемый размерный вид, на который мы можем ссылаться, используя имя особенности. В главе 17 в разделе "Использование особенность-предметов, допускающих значения разных видов" мы упоминали, что для использования особенностей в качестве особенность-предметов мы должны поместить их за указателем, например &dyn Trait или Box<dyn Trait> (Rc<dyn Trait> тоже подойдёт).

-

Для работы с DST Ржавчина использует особенность Sized чтобы решить, будет ли размер вида известен на стадии сборки. Этот особенность самостоятельно выполняется для всего, чей размер известен к времени сборки. Кроме того, Ржавчина неявно добавляет ограничение на Sized к каждой гибкой функции. То есть, определение гибкой функции, такое как:

-
fn generic<T>(t: T) {
-    // --snip--
-}
-

на самом деле рассматривается как если бы мы написали её в виде:

-
fn generic<T: Sized>(t: T) {
-    // --snip--
-}
-

По умолчанию обобщённые функции будут работать только с видами чей размер известен во время сборки. Тем не менее, можно использовать следующий особый правила написания, чтобы ослабить это ограничение:

-
fn generic<T: ?Sized>(t: &T) {
-    // --snip--
-}
-

Ограничение особенности ?Sized означает «T может или не может быть Sized», эта наставление отменяет обычное правило, согласно которому гибкие виды должны иметь известный размер во время сборки. Использовать правила написания ?Trait в таком качестве можно только для Sized, и ни для каких других особенностей.

-

Также обратите внимание, что мы поменяли вид свойства t с T на &T. Поскольку вид мог бы не быть Sized, мы должны использовать его за каким-либо указателем. В данном случае мы выбрали ссылку.

-

Далее мы поговорим о функциях и замыканиях!

-

Продвинутые функции и замыкания

-

В этом разделе рассматриваются некоторые продвинутые возможности, относящиеся к функциям и замыканиям, такие как указатели функций и возвращаемые замыкания.

-

Указатели функций

-

Мы уже обсуждали, как передавать замыкания в функции; но также можно передавать обычные функции в функции! Эта техника полезна, когда вы хотите передать ранее созданную функцию, а не определять новое замыкание. Функции соответствуют виду fn (со строчной буквой f), не путать с особенностью замыкания Fn. Вид fn называется указателем функции. Передача функций с помощью указателей функций позволяет использовать функции в качестве переменных других функций.

-

Для указания того, что свойство является указателем на функцию, используется правила написания, такой же, как и для замыканий, что отображается в приложении 19-27, где мы определили функцию add_one, которая добавляет единицу к переданному ей свойству. Функция do_twice принимает два свойства: указатель на любую функцию, принимающую свойство i32 и возвращающую i32, и число вида i32. Функция do_twice дважды вызывает функцию f, передавая ей значение arg, а затем складывает полученные итоги. Функция main вызывает функцию do_twice с переменнойми add_one и 5.

-

Файл: src/main.rs

-
fn add_one(x: i32) -> i32 {
-    x + 1
-}
-
-fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
-    f(arg) + f(arg)
-}
-
-fn main() {
-    let answer = do_twice(add_one, 5);
-
-    println!("The answer is: {answer}");
-}
-

Приложение 19-27: Использование вида fn для получения указателя на функцию в качестве переменной

-

Этот код выводит Ответ: 12. Мы указали, что свойство f в do_twice является fn, которая принимает на вход единственный свойство вида i32 и возвращает i32. Затем мы можем вызвать f в теле do_twice. В main мы можем передать имя функции add_one в качестве первого переменной в do_twice.

-

В отличие от замыканий, fn является видом, а не особенностью, поэтому мы указываем fn непосредственно в качестве вида свойства, а не объявляем свойство гибкого вида с одним из особенностей Fn в качестве связанного.

-

Указатели функций выполняют все три особенности замыканий (Fn, FnMut и FnOnce), то есть вы всегда можете передать указатель функции в качестве переменной функции, которая ожидает замыкание. Лучше всего для описания функции использовать гибкий вид и один из особенностей замыканий, чтобы ваши функции могли принимать как функции, так и замыкания.

-

Однако, одним из примеров, когда вы бы хотели принимать только fn, но не замыкания, является взаимодействие с внешним кодом, который не имеет замыканий: функции языка C могут принимать функции в качестве переменных, однако замыканий в языке C нет.

-

В качестве примера того, где можно использовать либо замыкание, определяемое непосредственно в месте передачи, либо именованную функцию, рассмотрим использование способа map, предоставляемого особенностью Iterator в встроенной библиотеке. Чтобы использовать функцию map для преобразования вектора чисел в вектор строк, мы можем использовать замыкание, например, так:

-
fn main() {
-    let list_of_numbers = vec![1, 2, 3];
-    let list_of_strings: Vec<String> =
-        list_of_numbers.iter().map(|i| i.to_string()).collect();
-}
-

Или мы можем использовать функцию в качестве переменной map вместо замыкания, например, так:

-
fn main() {
-    let list_of_numbers = vec![1, 2, 3];
-    let list_of_strings: Vec<String> =
-        list_of_numbers.iter().map(ToString::to_string).collect();
-}
-

Обратите внимание, что мы должны использовать полный правила написания, о котором мы говорили ранее в разделе "Продвинутые особенности", потому что доступно несколько функций с именем to_string. Здесь мы используем функцию to_string определённую в особенности ToString, который выполнен в встроенной библиотеке для любого вида выполняющего особенность Display.

-

Вспомните из раздела "Значения перечислений" главы 6, что имя каждого определённого нами исхода перечисления также становится функцией-объявителем. Мы можем использовать эти объявители в качестве указателей на функции, выполняющих особенности замыканий, что означает, что мы можем использовать объявители в качестве переменных для способов, принимающих замыкания, например, так:

-
fn main() {
-    enum Status {
-        Value(u32),
-        Stop,
-    }
-
-    let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
-}
-

Здесь мы создаём образцы Status::Value, используя каждое значение u32 в ряде (0..20), с которым вызывается map с помощью функции объявителя Status::Value. Некоторые люди предпочитают этот исполнение, а некоторые предпочитают использовать замыкания. Оба исхода собирается в один и тот же код, поэтому используйте любой исполнение, который вам понятнее.

-

Возврат замыканий

-

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

-

Следующий код пытается напрямую вернуть замыкание, но он не собирается:

-
fn returns_closure() -> dyn Fn(i32) -> i32 {
-    |x| x + 1
-}
-

Ошибка сборщика выглядит следующим образом:

-
$ cargo build
-   Compiling functions-example v0.1.0 (file:///projects/functions-example)
-error[E0746]: return type cannot have an unboxed trait object
- --> src/lib.rs:1:25
-  |
-1 | fn returns_closure() -> dyn Fn(i32) -> i32 {
-  |                         ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
-  |
-help: return an `impl Trait` instead of a `dyn Trait`, if all returned values are the same type
-  |
-1 | fn returns_closure() -> impl Fn(i32) -> i32 {
-  |                         ~~~~
-help: box the return type, and wrap all of the returned values in `Box::new`
-  |
-1 ~ fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
-2 ~     Box::new(|x| x + 1)
-  |
-
-For more information about this error, try `rustc --explain E0746`.
-error: could not compile `functions-example` (lib) due to 1 previous error
-
-

Ошибка снова ссылается на особенность Sized ! Ржавчина не знает, сколько памяти нужно будет выделить для замыкания. Мы видели решение этой сбоев ранее. Мы можем использовать особенность-предмет:

-
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
-    Box::new(|x| x + 1)
-}
-

Этот код просто отлично собирается. Для получения дополнительной сведений об особенность-предмета. обратитесь к разделу "Использование особенность-предметов которые допускают значения разных видов" главы 17.

-

Далее давайте посмотрим на макросы!

-

Макросы

-

Мы использовали макросы, такие как println! на протяжении всей этой книги, но мы не изучили полностью, что такое макрос и как он работает. Понятие макрос относится к семейству возможностей в Rust. Это декларативные (declarative) макросы с помощью macro_rules! и три вида процедурных (procedural) макросов:

-
    -
  • Пользовательские (выводимые) #[derive] макросы, которые указывают код, добавленный с помощью свойства derive, используемые для устройств и перечислений
  • -
  • Макросы подобные свойствам (attribute-like), которые определяют настраиваемые свойства, используемые для любого элемента языка
  • -
  • Похожие на функции (function-like) макросы, которые выглядят как вызовы функций, но работают с TokenStream
  • -
-

Мы поговорим о каждом из них по очереди, но сначала давайте рассмотрим, зачем вообще нужны макросы, если есть функции.

-

Разница между макросами и функциями

-

По сути, макросы являются способом написания кода, который записывает другой код, что известно как мета программирование. В Приложении C мы обсуждаем свойство derive, который порождает за вас выполнение различных особенностей. Мы также использовали макросы println! и vec! на протяжении книги. Все эти макросы раскрываются для создания большего количества кода, чем исходный код написанный вами вручную.

-

Мета программирование полезно для уменьшения объёма кода, который вы должны написать и поддерживать, что также является одним из предназначений функций. Однако макросы имеют некоторые дополнительные возможности, которых функции не имеют.

-

Ярлык функции должна объявлять некоторое количество и вид этих свойств имеющихся у функции. Макросы, с другой стороны, могут принимать переменное число свойств: мы можем вызвать println!("hello") с одним переменнаяом или println!("hello {}", name) с двумя переменнойми. Также макросы раскрываются до того как сборщик преобразует смысл кода, поэтому макрос может, например, выполнить особенность заданного вида. Функция этого не может, потому что она вызывается во время выполнения и особенность должен быть выполнен во время сборки.

-

Обратной стороной выполнения макроса вместо функции является то, что определения макросов являются более сложными, чем определения функций, потому что вы создаёте Ржавчина код, который записывает другой Ржавчина код. Из-за этой косвенности, объявления макросов, как правило, труднее читать, понимать и поддерживать, чем объявления функций.

-

Другое важное различие между макросами и функциями заключается в том, что вы должны объявить макросы или добавить их в область видимости прежде чем можете вызывать их в файле, в отличии от функций, которые вы можете объявить где угодно и вызывать из любого места.

-

Декларативные макросы с macro_rules! для общего мета программирования

-

Наиболее широко используемой способом макросов в Ржавчина являются декларативные макросы. Они также иногда упоминаются как "макросы на примере", "macro_rules! макрос" или просто "макросы". По своей сути декларативные макросы позволяют писать нечто похожее на выражение match в Rust. Как обсуждалось в главе 6, match выражения являются управляющими устройствами, которые принимают некоторое выражение, итог значения выражения сопоставляют с образцами, а затем запускают код для сопоставляемой ветки. Макросы также сравнивают значение с образцами, которые связаны с определенным кодом: в этой случаи значение является записью исходного кода Rust, переданным в макрос. Образцы сравниваются со устройствами этого исходного кода и при совпадении код, связанный с каждым образцом, заменяет код переданный макросу. Все это происходит во время сборки.

-

Для определения макроса используется устройство macro_rules!. Давайте рассмотрим, как использовать macro_rules! глядя на то, как объявлен макрос vec!. В главе 8 рассказано, как можно использовать макрос vec! для создания нового вектора с определёнными значениями. Например, следующий макрос создаёт новый вектор, содержащий три целых числа:

-
#![allow(unused)]
-fn main() {
-let v: Vec<u32> = vec![1, 2, 3];
-}
-

Мы также могли использовать макрос vec! для создания вектора из двух целых чисел или вектора из пяти строковых срезов. Мы не смогли бы использовать функцию, чтобы сделать то же самое, потому что мы не знали бы заранее количество или вид значений.

-

В приложении 19-28 приведено несколько упрощённое определение макроса vec!.

-

Файл: src/lib.rs

-
#[macro_export]
-macro_rules! vec {
-    ( $( $x:expr ),* ) => {
-        {
-            let mut temp_vec = Vec::new();
-            $(
-                temp_vec.push($x);
-            )*
-            temp_vec
-        }
-    };
-}
-

Приложение 19-28: Упрощённая исполнение определения макроса vec!

-
-

Примечание: действительное определение макроса vec! в встроенной библиотеке содержит код для предварительного выделения правильного объёма памяти. Этот код является переработкой, которую мы здесь не используем, чтобы сделать пример проще.

-
-

Изложение #[macro_export] указывает, что данный макрос должен быть доступен всякий раз, когда ящик с объявленным макросом, добавлен в область видимости. Без этой изложении макрос нельзя добавить в область видимости.

-

Затем мы начинаем объявление макроса с помощью macro_rules! и имени макроса, который объявляется без восклицательного знака. Название, в данном случае vec, после которого следуют фигурные скобки, указывающие тело определения макроса.

-

Устройства в теле макроса vec! похожа на устройство match выражения. Здесь у нас есть одна ветвь с образцом ( $( $x:expr ),* ), затем следует ветвь => и раздел кода, связанный с этим образцом. Если образец сопоставлен успешно, то соответствующий раздел кода будет создан. Учитывая, что данный код является единственным образцом в этом макросе, существует только один действительный способ сопоставления, любой другой образец приведёт к ошибке. Более сложные макросы будут иметь более одной ветви.

-

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

-

Во-первых, мы используем набор скобок, чтобы охватить весь образец. Мы используем знак доллара ( $) для объявления переменной в системе макросов, которая будет содержать код на Rust, соответствующий образцу. Знак доллара показывает, что это макропеременная, а не обычная переменная Rust. Далее следует набор скобок, в котором определятся значения, соответствующие образцу в скобках, для использования в коде замены. Внутри $() находится $x:expr, которое соответствует любому выражению Ржавчина и даёт выражению имя $x.

-

Запятая, следующая за $() указывает на то, что буквенный символ-разделитель запятая может дополнительно появиться после кода, который соответствует коду в $(). Звёздочка * указывает, что образец соответствует ноль или больше раз тому, что предшествует *.

-

Когда вызывается этот макрос с помощью vec![1, 2, 3]; образец $x соответствует три раза всем трём выражениям 1, 2 и 3.

-

Теперь давайте посмотрим на образец в теле кода, связанного с этой ветвью: temp_vec.push() внутри $()* порождается для каждой части, которая соответствует символу $() в образце ноль или более раз в зависимости от того, сколько раз образец сопоставлен. Символ $x заменяется на каждое совпадающее выражение. Когда мы вызываем этот макрос с vec![1, 2, 3];, созданный код, заменяющий этот вызов макроса будет следующим:

-
{
-    let mut temp_vec = Vec::new();
-    temp_vec.push(1);
-    temp_vec.push(2);
-    temp_vec.push(3);
-    temp_vec
-}
-

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

-

Чтобы узнать больше о том, как писать макросы, обратитесь к онлайн-документации или другим ресурсам, таким как «Маленькая книга макросов Rust» , начатая Дэниелом Кипом и продолженная Лукасом Виртом.

-

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

-

Вторая разновидность макросов - это процедурные макросы (procedural macros), которые действуют как функции (и являются видом процедуры). Процедурные макросы принимают некоторый код в качестве входных данных, работают над этим кодом и создают некоторый код в качестве вывода, а не выполняют сопоставления с образцами и замену кода другим кодом, как это делают декларативные макросы. Процедурные макросы могут быть трёх видов: "пользовательского вывода" (custom-derive), "похожие на свойство" (attribute-like) и "похожие на функцию" (function-like), все они работают схожим образом.

-

При создании процедурных макросов объявления должны находиться в собственном ящике целенаправленного вида. Это из-за сложных технических причин, которые мы надеемся будут устранены в будущем. В приложении 19-29 показано, как задать процедурный макрос, где some_attribute является заполнителем для использования целенаправленного макроса.

-

Файл: src/lib.rs

-
use proc_macro;
-
-#[some_attribute]
-pub fn some_name(input: TokenStream) -> TokenStream {
-}
-

Приложение 19-29: Пример определения процедурного макроса

-

Функция, которая определяет процедурный макрос, принимает TokenStream в качестве входных данных и создаёт TokenStream в качестве вывода. Вид TokenStream объявлен ящиком proc_macro, включённым в Ржавчина и представляет собой последовательность токенов. Это ядро макроса: исходный код над которым работает макрос, является входным TokenStream, а код создаваемый макросом является выходным TokenStream. К функции имеет также прикреплённый свойство, определяющий какой вид процедурного макроса мы создаём. Можно иметь несколько видов процедурных макросов в одном и том же ящике.

-

Давайте посмотрим на различные виды процедурных макросов. Начнём с пользовательского, выводимого (derive) макроса и затем объясним небольшие различия, делающие другие разновидности отличающимися.

-

Как написать пользовательский derive макрос

-

Давайте создадим ящик с именем hello_macro, который определяет особенность с именем HelloMacro и имеет одну с ним сопряженную функцию с именем hello_macro. Вместо того, чтобы пользователи нашего ящика самостоятельно выполнили особенность HelloMacro для каждого из своих видов, мы предоставим им процедурный макрос, чтобы они могли определять свой вид с помощью свойства #[derive(HelloMacro)] и получили выполнение по умолчанию для функции hello_macro. Выполнение по умолчанию выведет Hello, Macro! My name is TypeName!, где TypeName - это имя вида, для которого был определён этот особенность. Другими словами, мы напишем ящик, использование которого позволит другому программисту писать код показанный в приложении 19-30.

-

Файл: src/main.rs

-
use hello_macro::HelloMacro;
-use hello_macro_derive::HelloMacro;
-
-#[derive(HelloMacro)]
-struct Pancakes;
-
-fn main() {
-    Pancakes::hello_macro();
-}
-

Приложение 19-30: Код, который сможет писать пользователь нашего ящика при использовании нашего процедурного макроса

-

Этот код напечатает Hello, Macro! My name is Pancakes!, когда мы закончим. Первый шаг - создать новый, библиотечный ящик так:

-
$ cargo new hello_macro --lib
-
-

Далее, мы определим особенность HelloMacro и сопряженную с ним функцию:

-

Файл: src/lib.rs

-
pub trait HelloMacro {
-    fn hello_macro();
-}
-

У нас есть особенность и его функция. На этом этапе пользователь ящика может выполнить особенность для достижения желаемой возможности, так:

-
use hello_macro::HelloMacro;
-
-struct Pancakes;
-
-impl HelloMacro for Pancakes {
-    fn hello_macro() {
-        println!("Hello, Macro! My name is Pancakes!");
-    }
-}
-
-fn main() {
-    Pancakes::hello_macro();
-}
-

Тем не менее, ему придётся написать разделвыполнения для каждого вида, который он хотел использовать вместе с hello_macro; а мы хотим избавить их от необходимости делать эту работу.

-

Кроме того, мы пока не можем предоставить функцию hello_macro с выполнением по умолчанию, которая будет печатать имя вида, для которого выполнен особенность: Ржавчина не имеет возможностей рефлексии (reflection), поэтому он не может выполнить поиск имени вида во время выполнения кода. Нам нужен макрос для создания кода во время сборки.

-

Следующим шагом является определение процедурного макроса. На мгновение написания этой статьи процедурные макросы должны быть в собственном ящике. Со временем это ограничение может быть отменено. Соглашение о внутреннем выстраивании

-

ящиков и макросов является следующим: для ящика с именем foo, его пользовательский, ящик с выводимым процедурным макросом называется foo_derive. Давайте начнём с создания нового ящика с именем hello_macro_derive внутри дела hello_macro:

-
$ cargo new hello_macro_derive --lib
-
-

Наши два ящика тесно связаны, поэтому мы создаём процедурный макрос-ящик в папке ящика hello_macro. Если мы изменим определение особенности в hello_macro, то нам придётся также изменить выполнение процедурного макроса в hello_macro_derive. Два ящика нужно будет обнародовать отдельно и программисты, использующие эти ящики, должны будут добавить их как зависимости, а затем добавить их в область видимости. Мы могли вместо этого сделать так, что ящик hello_macro использует hello_macro_derive как зависимость и реэкспортирует код процедурного макроса. Однако то, как мы внутренне выстраивали

-

дело, делает возможным программистам использовать hello_macro даже если они не хотят derive возможность.

-

Нам нужно объявить ящик hello_macro_derive как процедурный макрос-ящик. Также понадобятся возможности из ящиков syn и quote, как вы увидите через мгновение, поэтому нам нужно добавить их как зависимости. Добавьте следующее в файл Cargo.toml для hello_macro_derive:

-

Файл: hello_macro_derive/Cargo.toml

-
[lib]
-proc-macro = true
-
-[dependencies]
-syn = "2.0"
-quote = "1.0"
-
-

Чтобы начать определение процедурного макроса, поместите код приложения 19-31 в ваш файл src/lib.rs ящика hello_macro_derive. Обратите внимание, что этот код не собирается пока мы не добавим определение для функции impl_hello_macro.

-

Файл: hello_macro_derive/src/lib.rs

-
use proc_macro::TokenStream;
-use quote::quote;
-
-#[proc_macro_derive(HelloMacro)]
-pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
-    // Construct a representation of Ржавчина code as a syntax tree
-    // that we can manipulate
-    let ast = syn::parse(input).unwrap();
-
-    // Build the trait implementation
-    impl_hello_macro(&ast)
-}
-

Приложение 19-31: Код, который потребуется в большинстве процедурных макро ящиков для обработки Ржавчина кода

-

Обратите внимание, что мы разделили код на функцию hello_macro_derive, которая отвечает за синтаксический анализ TokenStream и функцию impl_hello_macro, которая отвечает за преобразование синтаксического дерева: это делает написание процедурного макроса удобнее. Код во внешней функции ( hello_macro_derive в данном случае) будет одинаковым для почти любого процедурного макрос ящика, который вы видите или создаёте. Код, который вы указываете в теле внутренней функции (в данном случае impl_hello_macro ) будет отличаться в зависимости от цели вашего процедурного макроса.

-

Мы представили три новых ящика: proc_macro syn и quote. Макрос proc_macro поставляется с Rust, поэтому нам не нужно было добавлять его в зависимости внутри Cargo.toml. Макрос proc_macro - это API сборщика, который позволяет нам читать и управлять Ржавчина кодом из нашего кода.

-

Ящик syn разбирает Ржавчина код из строки в устройство данных над которой мы может выполнять действия. Ящик quote превращает устройства данных syn обратно в код Rust. Эти ящики упрощают разбор любого вида Ржавчина кода, который мы хотели бы обрабатывать: написание полного синтаксического анализатора для кода Ржавчина не является простой задачей.

-

Функция hello_macro_derive будет вызываться, когда пользователь нашей библиотеки указывает своему виду #[derive(HelloMacro)]. Это возможно, потому что мы определяли функцию hello_macro_derive с помощью proc_macro_derive и указали имя HelloMacro, которое соответствует имени нашего особенности; это соглашение, которому следует большинство процедурных макросов.

-

Функция hello_macro_derive сначала преобразует input из TokenStream в устройство данных, которую мы можем затем преобразовать и над которой выполнять действия. Здесь ящик syn вступает в игру. Функция parse в syn принимает TokenStream и возвращает устройство DeriveInput, представляющую разобранный код Rust. Приложение 19-32 показывает соответствующие части устройства DeriveInput, которые мы получаем при разборе строки struct Pancakes;:

-
DeriveInput {
-    // --snip--
-
-    ident: Ident {
-        ident: "Pancakes",
-        span: #0 bytes(95..103)
-    },
-    data: Struct(
-        DataStruct {
-            struct_token: Struct,
-            fields: Unit,
-            semi_token: Some(
-                Semi
-            )
-        }
-    )
-}
-

Приложение 19-32: Образец DeriveInput получаемый, когда разбирается код имеющий свойство макроса из приложения 19-30

-

Поля этой устройства показывают, что код Rust, который мы разобрали, является разделустройства с ident (определителем, означающим имя) Pancakes. В этой устройстве есть больше полей для описания всех видов кода Rust; проверьте документацию syn о устройстве DeriveInput для получения дополнительной сведений.

-

Вскоре мы определим функцию impl_hello_macro, в которой построим новый, дополнительный код Rust. Но прежде чем мы это сделаем, обратите внимание, что выводом для нашего выводимого (derive) макроса также является TokenStream. Возвращаемый TokenStream добавляется в код, написанный пользователями макроса, поэтому, когда они соберут свой ящик, они получат дополнительную возможность, которую мы предоставляем в изменённом TokenStream.

-

Возможно, вы заметили, что мы вызываем unwrap чтобы выполнить панику в функции hello_macro_derive, если вызов функции syn::parse потерпит неудачу. Наш процедурный макрос должен паниковать при ошибках, потому что функции proc_macro_derive должны возвращать TokenStream, а не вид Result для соответствия API процедурного макроса. Мы упроисполнения этот пример с помощью unwrap, но в рабочем коде вы должны предоставить более определенные сообщения об ошибках, если что-то пошло не правильно, используя panic! или expect.

-

Теперь, когда у нас есть код для преобразования определеного Ржавчина кода из TokenStream в образец DeriveInput, давайте создадим код выполняющий особенность HelloMacro у определеного вида, как показано в приложении 19-33.

-

Файл: hello_macro_derive/src/lib.rs

-
use proc_macro::TokenStream;
-use quote::quote;
-
-#[proc_macro_derive(HelloMacro)]
-pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
-    // Construct a representation of Ржавчина code as a syntax tree
-    // that we can manipulate
-    let ast = syn::parse(input).unwrap();
-
-    // Build the trait implementation
-    impl_hello_macro(&ast)
-}
-
-fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
-    let name = &ast.ident;
-    let gen = quote! {
-        impl HelloMacro for #name {
-            fn hello_macro() {
-                println!("Hello, Macro! My name is {}!", stringify!(#name));
-            }
-        }
-    };
-    gen.into()
-}
-

Приложение 19-33: Выполнение особенности HelloMacro с использованием проанализированного кода Rust.

-

Мы получаем образец устройства Ident содержащий имя (определитель) определеного вида с использованием ast.ident. Устройства в приложении 19-32 показывает, что когда мы запускаем функцию impl_hello_macro для кода из приложения 19-30, то получаемый ident будет иметь поле ident со значением "Pancakes". Таким образом, переменная name в приложении 19-33 будет содержать образец устройства Ident, что при печати выдаст строку "Pancakes", что является именем устройства в приложении 19-30.

-

Макрос quote! позволяет определить код Rust, который мы хотим вернуть. Сборщик ожидает что-то отличное от прямого итога выполнения макроса quote!, поэтому нужно преобразовать его в TokenStream. Мы делаем это путём вызова способа into, который использует промежуточное представление и возвращает значение требуемого вида TokenStream.

-

Макрос quote! также предоставляет очень полезную механику образцов: мы можем ввести #name и quote! заменит его значением из переменной name. Вы можете даже сделать некоторое повторение, подобное тому, как работают обычные макросы. Проверьте документацию ящика quote для подробного введения.

-

Мы хотим, чтобы наш процедурный макрос порождал выполнение нашего особенности HelloMacro для вида, который определял пользователь, который мы можем получить, используя #name. Выполнение особенности имеет одну функцию hello_macro, тело которой содержит возможность, которую мы хотим предоставить: напечатать Hello, Macro! My name is с именем определеного вида.

-

Макрос stringify! используемый здесь, встроен в Rust. Он принимает Ржавчина выражение, такое как 1 + 2 и во время сборки сборщик превращает выражение в строковый запись, такой как "1 + 2". Он отличается от макросов format! или println!, которые вычисляют выражение, а затем превращают итог в виде вида String. Существует возможность того, что введённый #name может оказаться выражением для печати буквально как есть, поэтому здесь мы используем stringify!. Использование stringify! также уменьшает выделение памяти путём преобразования #name в строковый запись во время сборки.

-

На этом этапе приказ cargo build должна завершиться успешно для обоих hello_macro и hello_macro_derive. Давайте подключим эти ящики к коду в приложении 19-30, чтобы увидеть процедурный макрос в действии! Создайте новый двоичный дело в папке ваших дел с использованием приказы cargo new pancakes. Нам нужно добавить hello_macro и hello_macro_derive в качестве зависимостей для ящика pancakes в файл Cargo.toml. Если вы размещаете свои исполнения hello_macro и hello_macro_derive на сайт crates.io, они будут обычными зависимостями; если нет, вы можете указать их как path зависимости следующим образом:

-
hello_macro = { path = "../hello_macro" }
-hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }
-
-

Поместите код в приложении 19-30 в src/main.rs и выполните cargo run: он должен вывести Hello, Macro! My name is Pancakes!. Выполнение особенности HelloMacro из процедурного макроса была включена без необходимости его выполнения ящиком pancakes; #[derive(HelloMacro)] добавил выполнение особенности.

-

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

-

Макросы, похожие на свойство

-

Подобные свойствам макросы похожи на пользовательские выводимые макросы, но вместо создания кода для derive свойства, они позволяют создавать новые свойства. Они являются также более гибкими: derive работает только для устройств и перечислений; свойство-подобные могут применяться и к другим элементам, таким как функции. Вот пример использования имеющего свойство макроса: допустим, у вас есть свойство именованный route который определяет функции при использовании фреймворка для веб-приложений:

-
#[route(GET, "/")]
-fn index() {
-

Данный свойство #[route] будет определён платспособом как процедурный макрос. Ярлык функции определения макроса будет выглядеть так:

-
#[proc_macro_attribute]
-pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
-

Здесь есть два свойства вида TokenStream. Первый для содержимого свойства: часть GET, "/" . Второй это тело элемента, к которому прикреплён свойство: в данном случае fn index() {} и остальная часть тела функции.

-

Кроме того, имеющие свойства макросы работают так же как и пользовательские выводимые макросы: вы создаёте ящик с видом proc-macro и выполняете функцию, которая порождает код, который хотите!

-

Макросы, похожие на функции

-

Макросы, похожие на функции, выглядят подобно вызову функций. Подобно макросам macro_rules! они являются более гибкими, чем функции; например, они могут принимать неизвестное количество переменных. Тем не менее, макросы macro_rules! можно объявлять только с использованием правил написания подобного сопоставлению, который мы обсуждали ранее в разделе "Декларативные макросы macro_rules! для общего мета программирования". Макросы, похожие на функции, принимают свойство TokenStream и их определение управляет этим TokenStream, используя код Rust, как это делают два других вида процедурных макроса. Примером подобного возможностей макроса является макрос sql!, который можно вызвать так:

-
let sql = sql!(SELECT * FROM posts WHERE id=1);
-

Этот макрос будет разбирать SQL указанию внутри него и проверять, что она синтаксически правильная, что является гораздо более сложной обработкой, чем то что может сделать макрос macro_rules!. Макрос sql! мог бы быть определён так:

-
#[proc_macro]
-pub fn sql(input: TokenStream) -> TokenStream {
-

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

-

Итоги

-

Фух! Теперь у вас в распоряжении есть некоторые возможности Rust, которые вы не будете часто использовать, но вы будете знать, что они доступны в особых обстоятельствах. Мы представили несколько сложных тем, чтобы при появлении сообщения с предложением исправить ошибку или в коде других людей, вы могли бы распознать эти подходы и правила написания. Используйте эту главу как справочник, который поможет вам найти решение.

-

Далее мы применим в действительностивсе, что обсуждали на протяжении всей книги, и выполним ещё один дело!

-

Конечный дело: создание многопоточного веб-сервера

-

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

-

В качестве нашего конечного дела мы напишем веб-сервер, который выводит надпись “hello” в веб-браузере, как на рисунке 20-1.

-

hello from rust

-

Рисунок 20-1: Наш последний совместный дело

-

Для создания веб-сервера нам понадобится:

-
    -
  1. Узнать немного о протоколах TCP и HTTP.
  2. -
  3. Сделать прослушивание TCP соединения у сокета.
  4. -
  5. Создать возможность для парсинга небольшого количества HTTP-запросов.
  6. -
  7. Научить сервер отдавать правильный HTTP-ответ.
  8. -
  9. Улучшить пропускную способность нашего сервера с помощью объединения потоков.
  10. -
-

Прежде чем мы начнём, заметим: способ, который мы будем использовать - не лучшим способ создания веб-сервера на Rust. Члены сообщества уже обнародовали на crates.io несколько готовых к использованию ящиков, которые предоставляют более полные выполнения веб-сервера и объединения потоков, чем те, которые мы создадим. Однако наша цель в этой главе — научиться новому, а не идти по лёгкому пути. Поскольку Ржавчина — это язык системного программирования, мы можем выбирать тот уровень абстракции, который нам подходит, и можем переходить на более низкий уровень, что может быть невозможно или неприменимо в других языках. Поэтому мы напишем основной HTTP-сервер и объединениепотоков вручную, чтобы вы могли изучить общие мысли и способы, лежащие в основе ящиков, которые, возможно, вы будете использовать в будущем.

-

Создание однопоточного веб-сервера

-

Начнём с однопоточного веб-сервера. Перед тем, как начать, давайте сделаем краткий обзор протоколов, задействованных при создании веб-серверов. Детальное описание этих протоколов выходит за рамки этой книги, но краткий обзор даст вам необходимую сведения.

-

Двумя основными протоколами, используемыми в веб-серверах, являются протокол передачи гипертекста (HTTP - Hypertext Transfer Protocol) и Протокол управления передачей (TCP - Transmission Control Protocol). Оба протокола являются протоколами вида запрос-ответ (request-response), то есть клиент объявляет запросы, а сервер слушает эти запросы и предоставляет ответ клиенту. Содержимое этих запросов и ответов определяется протоколами.

-

TCP - это протокол нижнего уровня, который описывает подробности того, как сведения передаётся от одного сервера к другому, но не определяет, что это за сведения. HTTP строится поверх TCP, определяя содержимое запросов и ответов. Технически возможно использовать HTTP с другими протоколами, но в подавляющем большинстве случаев HTTP отправляет свои данные поверх TCP. Мы будем работать с необработанными байтами в TCP и запросами и ответами в HTTP.

-

Прослушивание TCP соединения

-

Нашему веб-серверу необходимо прослушивать TCP-соединение, так что это первая часть, над которой мы будем работать. Обычная библиотека предлагает для этого звено std::net. Сделаем новый дело обычным способом:

-
$ cargo new hello
-      Created binary (application) `hello` project
-$ cd hello
-
-

Дл начала добавьте код из приложения 20-1 в файл src/main.rs. Этот код будет прослушивать входящие TCP потоки по адресу 127.0.0.1:7878. Когда сервер примет входящий поток, он напечатает Connection established! ("Соединение установлено!").

-

Файл: src/main.rs

-
use std::net::TcpListener;
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        println!("Connection established!");
-    }
-}
-

Приложение 20-1: Прослушивание входящих потоков и печать сообщения при получении потока

-

Используя TcpListener мы можем слушать TCP соединения к адресу 127.0.0.1:7878. В адресе, в его части перед двоеточием, сначала идёт IP-адрес, относящийся к вашему компьютеру (он одинаковый на каждом компьютере и не представляет определенный компьютер автора), а часть 7878 является портом. Мы выбрали этот порт по двум причинам: HTTP обычно не используется на этом порту, поэтому маловероятно, что наш сервер будет враждовать с каким-нибудь другим сервером, который может выполняться на вашей машине, и ещё 7878 - это слово rust, набранное на телефоне.

-

Функция bind в этом сценарии работает так же, как функция new, поскольку она возвращает новый образец TcpListener . Причина, по которой функция называется bind заключается в том, что в сетевой совокупности понятий подключение к порту для прослушивания называется «привязка к порту» (“binding to a port”).

-

Функция bind возвращает Result<T, E>, а это значит, что привязка может не состояться. Так, например, подключение к порту 80 предполагает наличие привилегий администратора (прочие пользователи могут прослушивать порты только от 1023-го и выше), поэтому если мы попытаемся подключиться к порту 80, не будучи администратором, привязка не сработает. Привязка также не выполнится, например, если мы запустим два образца нашей программы, прослушивающие один и тот же порт. Поскольку мы пишем простейший сервер в учебных целях, мы не будем беспокоиться об обработке подобных ошибок; вместо этого мы используем unwrap для прекращения работы программы в случае возникновения ошибок.

-

Способ incoming в TcpListener возвращает повторитель , который даёт нам последовательность потоков (определеннее, потоков вида TcpStream ). Один поток представляет собой открытое соединение между клиентом и сервером. Соединением называется полный этап запроса и ответа, в котором клиент подключается к серверу, сервер порождает ответ, и сервер закрывает соединение. Таким образом, мы будем читать из потока TcpStream то, что отправил клиент, а затем записывать наш ответ в поток, для отправки его обратно клиенту. В целом, цикл for будет обрабатывать каждое соединение по очереди и создавать серию потоков, которые мы будем обрабатывать.

-

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

-

Попробуем запустить этот код! Вызовите cargo run в окне вызова, а затем загрузите 127.0.0.1:7878 в веб-браузере. В браузере должно отображаться сообщение об ошибке, например «Connection reset», поскольку сервер в настоящее время не отправляет обратно никаких данных. Но когда вы посмотрите на свой окно вызова, вы должны увидеть несколько сообщений, которые были напечатаны, когда браузер подключался к серверу!

-
     Running `target/debug/hello`
- Connection established!
- Connection established!
- Connection established!
-
-

Иногда вы видите несколько сообщений, напечатанных для одного запроса браузера; Причина может заключаться в том, что браузер выполняет запрос страницы, а также других ресурсов, таких как значок favicon.ico, который отображается на вкладке браузера.

-

Также может быть, что браузер пытается подключиться к серверу несколько раз, потому что сервер не отвечает. Когда stream выходит из области видимости и отбрасывается в конце цикла, соединение закрывается как часть выполнения drop. Браузеры иногда обрабатывают закрытые соединения, повторяя попытки, потому что неполадка может быть временной. Важным обстоятельством является то, что мы успешно получили указатель TCP-соединения!

-

Не забудьте остановить программу, нажав ctrl-c, когда вы закончите выполнение определённой исполнения кода. Затем перезапустите программу, вызвав приказ cargo run, после того, как вы внесли какой-либо набор изменений, чтобы убедиться, что выполняется самая свежая исполнение кода.

-

Чтение запросов

-

Выполняем возможности чтения запроса из браузера! Чтобы разделить части, связанные с получением соединения и последующим действием с ним, мы запустим новую функцию для обработки соединения. В этой новой функции handle_connection мы будем читать данные из потока TCP и распечатывать их, чтобы мы могли видеть данные, отправленные из браузера. Измените код, чтобы он выглядел как в приложении 20-2.

-

Файл: src/main.rs

-
use std::{
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let http_request: Vec<_> = buf_reader
-        .lines()
-        .map(|result| result.unwrap())
-        .take_while(|line| !line.is_empty())
-        .collect();
-
-    println!("Request: {http_request:#?}");
-}
-

Приложение 20-2: Чтение из TcpStream и печать данных

-

Мы добавляем std::io::prelude и std::io::BufReader в область видимости, чтобы получить доступ к особенностям и видам, которые позволяют нам читать и писать в поток. В цикле for функции main вместо вывода сообщения о том, что мы установили соединение, мы теперь вызываем новую функцию handle_connection и передаём ей stream.

-

В функции handle_connection мы создаём новый образец BufReader, который оборачивает изменяемую ссылку на stream. BufReader добавляет буферизацию, управляя вызовами способов особенности std::io::Read за нас.

-

Мы создаём переменную http_request для сбора строк запроса, который браузер отправляет на наш сервер. Мы указываем, что хотим собрать эти строки в вектор, добавляя изложение вида Vec<_>.

-

BufReader выполняет особенность std::io::BufRead, который выполняет способ lines. Способ lines возвращает повторитель Result<String, std::io::Error>, разделяющий поток данных на части всякий раз, когда ему попадается байт новой строки. Чтобы получить все строки String, мы с помощью map вызываем unwrap у каждого Result. Значение Result может быть ошибкой, если данные не соответствуют исполнению UTF-8 или если возникли сбоев с чтением из потока. Опять же, программа в промышленном исполнении должна обрабатывать эти ошибки более изящно, но мы для простоты решили прекращать работу программы в случае ошибки.

-

Браузер указывает об окончании HTTP-запроса, отправляя два символа перевода строки подряд, поэтому, чтобы получить один запрос из потока, мы забираем строки, пока не получим строку, которая является пустой строкой. После того, как мы собрали строки в вектор, мы распечатываем их, используя красивое отладочное изменение -, чтобы мы могли взглянуть на указания, которые веб-браузер отправляет на наш сервер.

-

Попробуем этот код! Запустите программу и снова сделайте запрос в веб-браузере. Обратите внимание, что мы по-прежнему будем получать в браузере страницу с ошибкой, но вывод нашей программы в окне вызова теперь будет выглядеть примерно так:

-
$ cargo run
-   Compiling hello v0.1.0 (file:///projects/hello)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.42s
-     Running `target/debug/hello`
-Request: [
-    "GET / HTTP/1.1",
-    "Host: 127.0.0.1:7878",
-    "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0) Gecko/20100101 Firefox/99.0",
-    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
-    "Accept-Language: en-US,en;q=0.5",
-    "Accept-Encoding: gzip, deflate, br",
-    "DNT: 1",
-    "Connection: keep-alive",
-    "Upgrade-Insecure-Requests: 1",
-    "Sec-Fetch-Dest: document",
-    "Sec-Fetch-Mode: navigate",
-    "Sec-Fetch-Site: none",
-    "Sec-Fetch-User: ?1",
-    "Cache-Control: max-age=0",
-]
-
-

В зависимости от вашего браузера итог может немного отличаться. Теперь, когда мы печатаем данные запроса, мы можем понять, почему мы получаем несколько подключений из одного запроса браузера, посмотрев на путь после GET в первой строке запроса. Если все повторяющиеся соединения запрашивают / , мы знаем, что браузер пытается получить / повторно, потому что он не получает ответа от нашей программы.

-

Давайте разберём эти данные запроса, чтобы понять, что браузер запрашивает у нашей программы.

-

Пристальный взгляд на HTTP запрос

-

HTTP - это текстовый протокол и запрос имеет следующий вид:

-
Method Request-URI HTTP-Version CRLF
-headers CRLF
-message-body
-
-

Первая строка - это строка запроса , содержащая сведения о том, что запрашивает клиент. Первая часть строки запроса указывает используемый способ , например GET или POST , который описывает, как клиент выполняет этот запрос. Наш клиент использовал запрос GET, что означает, что он просит нас предоставить сведения.

-

Следующая часть строки запроса - это /, которая указывает унифицированный определитель ресурса (URI), который запрашивает клиент: URI почти, но не совсем то же самое, что и унифицированный указатель ресурса (URL). Разница между URI и URL-адресами не важна для наших целей в этой главе, но согласно принятых требований HTTP использует понятие URI, поэтому мы можем просто мысленно заменить URL-адрес здесь.

-

Последняя часть - это исполнение HTTP, которую использует клиент, а затем строка запроса заканчивается последовательностью CRLF . (CRLF обозначает возврат каретки и перевод строки , что является понятием из дней пишущих машинок!) Последовательность CRLF также может быть записана как \r\n , где \r - возврат каретки, а \n - перевод строки. Последовательность CRLF отделяет строку запроса от остальных данных запроса. Обратите внимание, что при печати CRLF мы видим начало новой строки, а не \r\n .

-

Глядя на данные строки запроса, которые мы получили от запуска нашей программы, мы видим, что GET - это способ, / - это URI запроса, а HTTP/1.1 - это исполнение.

-

После строки запроса оставшиеся строки, начиная с Host: далее, являются заголовками. GET запросы не имеют тела.

-

Попробуйте сделать запрос из другого браузера или запросить другой адрес, например 127.0.0.1:7878/test , чтобы увидеть, как изменяются данные запроса.

-

Теперь, когда мы знаем, что запрашивает браузер, давайте отправим обратно в ответ некоторые данные!

-

Написание ответа

-

Теперь выполняем отправку данных в ответ на запрос клиента. Ответы имеют следующий вид:

-
HTTP-Version Status-Code Reason-Phrase CRLF
-headers CRLF
-message-body
-
-

Первая строка - это строка состояния, которая содержит исполнение HTTP, используемую в ответе, числовой код состояния, который суммирует итог запроса, и фразу причины, которая предоставляет текстовое описание кода состояния. После последовательности CRLF идут любые заголовки, другая последовательность CRLF и тело ответа.

-

Вот пример ответа, который использует HTTP исполнения 1.1, имеет код состояния 200, фразу причины OK, без заголовков и без тела:

-
HTTP/1.1 200 OK\r\n\r\n
-
-

Код состояния 200 - это обычный успешный ответ. Текст представляет собой крошечный успешный HTTP-ответ. Давайте запишем это в поток как наш ответ на успешный запрос! Из функции handle_connection удалите println! который печатал данные запроса и заменял их кодом из Приложения 20-3.

-

Файл: src/main.rs

-
use std::{
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let http_request: Vec<_> = buf_reader
-        .lines()
-        .map(|result| result.unwrap())
-        .take_while(|line| !line.is_empty())
-        .collect();
-
-    let response = "HTTP/1.1 200 OK\r\n\r\n";
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-3: Запись крошечного успешного HTTP-ответа в поток

-

Первый перевод строки определяет переменную response, которая содержит данные сообщения об успешном выполнении. Затем мы вызываем as_bytes в нашем response, чтобы преобразовать строковые данные в байты. Способ write_all в stream принимает вид &[u8] и отправляет эти байты непосредственно получателю. Поскольку действие write_all может завершиться с ошибкой, мы, как и ранее, используем unwrap на любом возможно ошибочном итоге. И опять, в существующем приложении здесь вам нужно было бы добавить обработку ошибок.

-

После этих изменений давайте запустим наш код и сделаем запрос. Мы больше не печатаем никаких данных в окно вызова, поэтому мы не увидим никакого вывода, кроме сообщений от Cargo. Когда вы загрузите 127.0.0.1:7878 в веб-браузере, вы должны получить пустую страницу вместо ошибки. Вы только что вручную написали код получения HTTP-запроса и отправки ответа на него!

-

Возвращение существующего HTML

-

Давайте выполняем возможности чего-нибудь большего, чем просто пустой страницы. Создайте новый файл hello.html в корне папки вашего дела, а не в папке src . Вы можете ввести любой HTML-код, который вам заблагорассудится; В приложении 20-4 показан один из исходов.

-

Файл: hello.html

-
<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8">
-    <title>Hello!</title>
-  </head>
-  <body>
-    <h1>Hello!</h1>
-    <p>Hi from Rust</p>
-  </body>
-</html>
-
-

Приложение 20-4: Пример HTML-файла для ответа на запрос

-

Это простейший HTML5-документ с заголовком и каким-то текстом. Чтобы сервер возвращал его в ответ на полученный запрос, мы изменим handle_connection, как показано в приложении 20-5, чтобы считать HTML-файл, добавить его в ответ в качестве тела и отправить.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-// --snip--
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let http_request: Vec<_> = buf_reader
-        .lines()
-        .map(|result| result.unwrap())
-        .take_while(|line| !line.is_empty())
-        .collect();
-
-    let status_line = "HTTP/1.1 200 OK";
-    let contents = fs::read_to_string("hello.html").unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-5. Отправка содержимого hello.html в качестве тела ответа

-

Мы добавили элемент fs в указанию use, чтобы включить в область видимости звено файловой системы встроенной библиотеки. Код для чтения содержимого файла в строку должен выглядеть знакомым для вас; мы использовали его в главе 12, когда читали содержимое файла для нашего дела ввода-вывода в приложении 12-4.

-

Далее мы используем format! чтобы добавить содержимое файла в качестве тела ответа об успешном завершении. Чтобы обеспечить действительный HTTP-ответ, мы добавляем заголовок Content-Length который имеет размер тела нашего ответа, в данном случае размер hello.html .

-

Запустите этот код приказом cargo run и загрузите 127.0.0.1:7878 в браузере; вы должны увидеть выведенный HTML в браузере!

-

В настоящее время мы пренебрегаем данные запроса в переменной http_request и в любом случае просто отправляем обратно содержимое HTML-файла. Это означает, что если вы попытаетесь запросить адрес 127.0.0.1:7878/something-else в своём браузере, вы все равно получите тот же самый HTML-ответ. Пока что наш сервер очень ограничен, и не умеет делать то, что делает большинство веб-серверов. Мы хотим настроить наши ответы в зависимости от запроса и отправлять обратно HTML-файл только для правильно созданного запроса к пути / .

-

Проверка запроса и выборочное возвращение ответа

-

Сейчас наш веб-сервер возвращает HTML из файла независимо от того, что определенно запросил клиент. Давайте добавим проверку того, что браузер запрашивает /, прежде чем вернуть HTML-файл, и будем возвращать ошибку, если браузер запрашивает что-то постороннее. Для этого нам нужно изменять handle_connection, как показано в приложении 20-6. Новый код проверяет соответствует ли требуемый запросом ресурс с определителем /, и содержит разделы if и else, чтобы иначе обрабатывать другие запросы.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-// --snip--
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    if request_line == "GET / HTTP/1.1" {
-        let status_line = "HTTP/1.1 200 OK";
-        let contents = fs::read_to_string("hello.html").unwrap();
-        let length = contents.len();
-
-        let response = format!(
-            "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
-        );
-
-        stream.write_all(response.as_bytes()).unwrap();
-    } else {
-        // some other request
-    }
-}
-

Приложение 20-6: Обрабатываем запросы для корневого ресурса / не так, как запросы для других ресурсов

-

Мы будем рассматривать только первую строку HTTP-запроса, поэтому вместо того, чтобы читать весь запрос в вектор, мы вызываем next , чтобы получить первый элемент из повторителя. Первый вызов unwrap заботится об обработке Option и останавливает программу, если в повторителе нет элементов. Второй unwrap обрабатывает Result и имеет тот же эффект, что и unwrap, который был в map, добавленном в приложении 20-2.

-

Затем мы проверяем переменную request_line, чтобы увидеть, равна ли она строке запроса, соответствующей запросу GET для пути / . Если это так, разделif возвращает содержимое нашего HTML-файла.

-

Если request_line не равна запросу GET для пути /, это означает, что мы получили какой-то другой запрос. Мы скоро добавим код в разделelse, чтобы ответить на все остальные запросы.

-

Запустите этот код сейчас и запросите 127.0.0.1:7878 ; вы должны получить HTML в hello.html . Если вы сделаете любой другой запрос, например 127.0.0.1:7878/something-else , вы получите ошибку соединения, подобную той, которую вы видели при запуске кода из Приложения 20-1 и Приложения 20-2.

-

Теперь давайте добавим код из приложения 20-7 в разделelse чтобы вернуть ответ с кодом состояния 404, который указывает о том, что содержание для запроса не найден. Мы также вернём HTML-код для страницы, отображаемой в браузере, с указанием ответа конечному пользователю.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    if request_line == "GET / HTTP/1.1" {
-        let status_line = "HTTP/1.1 200 OK";
-        let contents = fs::read_to_string("hello.html").unwrap();
-        let length = contents.len();
-
-        let response = format!(
-            "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
-        );
-
-        stream.write_all(response.as_bytes()).unwrap();
-    // --snip--
-    } else {
-        let status_line = "HTTP/1.1 404 NOT FOUND";
-        let contents = fs::read_to_string("404.html").unwrap();
-        let length = contents.len();
-
-        let response = format!(
-            "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
-        );
-
-        stream.write_all(response.as_bytes()).unwrap();
-    }
-}
-

Приложение 20-7: Отвечаем кодом состояния 404 и страницей ошибки, если было запрошено что-то, отличающееся от ресурса /

-

Здесь ответ имеет строку состояния с кодом 404 и фразу причины NOT FOUND. Тело ответа будет HTML из файла 404.html. Вам нужно создать файл 404.html рядом с hello.html для этой страницы ошибки; снова не стесняйтесь использовать любой HTML код или пример HTML кода в приложении 20-8.

-

Файл: 404.html

-
<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8">
-    <title>Hello!</title>
-  </head>
-  <body>
-    <h1>Oops!</h1>
-    <p>Sorry, I don't know what you're asking for.</p>
-  </body>
-</html>
-
-

Приложение 20-8. Пример содержимого страницы для отправки с любым ответом 404

-

С этими изменениями снова запустите сервер. Запрос на 127.0.0.1:7878 должен возвращать содержимое hello.html, и любой другой запрос, как 127.0.0.1:7878/foo, должен возвращать сообщение об ошибке HTML от 404.html.

-

Переработка кода

-

На текущий мгновение разделы if и else во многом повторяются: они оба читают файлы и записывают содержимое файлов в поток. Разница лишь в строке состояния и имени файла. Давайте сделаем код более кратким, вынеся эти отличия в отдельные разделы if и else, в которых переменным будут присвоены значения строки состояния и имени файла; далее эти переменные мы сможем использовать в коде для чтения файла и создания ответа. В приложении 20-9 показан код после изменения объёмных разделов if и else.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-// --snip--
-
-fn handle_connection(mut stream: TcpStream) {
-    // --snip--
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = if request_line == "GET / HTTP/1.1" {
-        ("HTTP/1.1 200 OK", "hello.html")
-    } else {
-        ("HTTP/1.1 404 NOT FOUND", "404.html")
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-9: Переработка кода разделов if и else, чтобы они содержали только код, который отличается для каждого из случаев

-

Теперь разделы if и else возвращают только соответствующие значения для строки состояния и имени файла в упорядоченном ряде. Затем мы используем разъединение, чтобы присвоить эти два значения status_line и filename используя образец в указания let, как обсуждалось в главе 18.

-

Ранее повторяющийся код теперь находится вне разделов if и else и использует переменные status_line и filename. Это позволяет легче увидеть разницу между этими двумя случаями и означает, что у нас есть только одно место для обновления кода, если захотим изменить работу чтения файлов и записи ответов. Поведение кода в приложении 20-9 будет таким же, как и в 20-8.

-

Потрясающие! Теперь у нас есть простой веб-сервер примерно на 40 строках кода Rust, который отвечает на один запрос страницей с содержанием и отвечает на все остальные запросы ответом 404.

-

В настоящее время наш сервер работает в одном потоке, что означает, что он может обслуживать только один запрос за раз. Давайте разберёмся, почему это может быть неполадкой, сымитировав несколько медленных запросов. Затем мы исправим случай так, чтобы наш сервер мог обрабатывать несколько запросов одновременно.

-

Превращение однопоточного сервера в многопоточный сервер

-

В текущей выполнения сервер обрабатывает каждый запрос по очереди, то есть, он не начнёт обрабатывать второе соединение, пока не завершит обработку первого. При росте числа запросов к серверу, такое последовательное выполнение было бы все менее и менее разумным. Если сервер получает какой-то запрос, обработка которого занимает достаточно много времени, последующим запросам придётся ждать завершения обработки длительного запроса, даже если эти новые запросы сами по себе могут быть обработаны быстро. Нам нужно это исправить, но сначала рассмотрим неполадку в действии.

-

Подражание медленного запроса в текущей выполнения сервера

-

Мы посмотрим, как запрос с медленной обработкой может повлиять на другие запросы, сделанные к серверу в текущей выполнения. В приложении 20-10 выполнена обработка запроса к ресурсу /sleep с эмуляцией медленного ответа, при которой сервер будет ждать 5 секунд перед тем, как ответить.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-// --snip--
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        handle_connection(stream);
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    // --snip--
-
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    // --snip--
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-10: Подражание медленного запроса с помощью 5-секундной задержки

-

Мы переключились с if на match, так как теперь у нас есть три случая. Нам придётся явно сопоставить срез от request_line для проверки совпадения образца со строковыми записями; match не делает самостоятельно е ссылки и разыменования, как это делает способ равенства.

-

Первая ветка совпадает с разделом if из приложения 20-9. Вторая ветка соответствует запросу /sleep . Когда этот запрос получен, сервер заснёт на 5 секунд, прежде чем отдать успешную HTML-страницу. Третья ветка совпадает с разделом else из приложения 20-9.

-

Можно увидеть, насколько прост наш сервер: в существующих библиотеках распознавание разных запросов осуществлялось бы гораздо менее многословно!

-

Запустите сервер приказом cargo run. Затем откройте два окна браузера: одно с адресом http://127.0.0.1:7878/, другое с http://127.0.0.1:7878/sleep. Если вы несколько раз обратитесь к URI /, то как и раньше увидите, что сервер быстро ответит. Но если вы введёте URI /sleep, а затем загрузите URI /, то увидите что / ждёт, пока /sleep не отработает полные 5 секунд перед загрузкой страницы.

-

Есть несколько способов, которые можно использовать, чтобы избавиться от подтормаживания запросов после одного медленного запроса; способ, который мы выполняем, называется объединением потоков.

-

Улучшение пропускной способности с помощью объединения потоков

-

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

-

Мы ограничим число потоков в объединении небольшим числом, чтобы защитить нас от атак вида «отказ в обслуживании» (DoS - Denial of Service); если бы наша программа создавала новый поток в мгновение поступления каждого запроса, то кто-то сделавший 10 миллионов запросов к серверу, мог бы создать хаос, использовать все ресурсы нашего сервера и остановить обработку запросов.

-

Вместо порождения неограниченного количества потоков, у нас будет определенное количество потоков, ожидающих в объединении. Поступающие запросы будут отправляться в объединениедля обработки. Объединение будет иметь очередь входящих запросов. Каждый из потоков в объединении будет извлекать запрос из этой очереди, обрабатывать запрос и затем запрашивать в очереди следующий запрос. При таком внешнем виде мы можем обрабатывать N запросов одновременно, где N - количество потоков. Если каждый поток отвечает на длительный запрос, последующие запросы могут по-прежнему задержаться в очереди, но теперь мы увеличили количество "длинных" запросов, которые мы можем обработать, перед тем, как эта случаей снова возникнет.

-

Этот подход - лишь один из многих способов улучшить пропускную способность веб-сервера. Другими исходами, на которые возможно стоило бы обратить внимание, являются: прообраз fork/join, прообраз однопоточного не согласованного ввода-вывода или прообраз многопоточного не согласованного ввода-вывода. Если вам важна эта тема, вы можете почитать больше сведений о других решениях и попробовать выполнить их самостоятельно. С таким низкоуровневым языком как Rust, любой из этих исходов осуществим.

-

Прежде чем приступить к выполнения объединения потоков, давайте поговорим о том, как должно выглядеть использование объединения . Когда вы пытаетесь создать код, сначала необходимо написать клиентский внешнюю оболочку. Напишите API кода, чтобы он был внутренне выстроен так, как вы хотите его вызывать, затем выполните возможность данной устройства, вместо подхода выполнить возможности. а затем разрабатывать общедоступный API.

-

Подобно тому, как мы использовали разработку через проверка (test-driven) в деле главы 12, мы будем использовать здесь разработку, управляемую сборщиком (compiler-driven). Мы напишем код, вызывающий нужные нам функции, а затем посмотрим на ошибки сборщика, чтобы определить, что мы должны изменить дальше, чтобы заставить код работать. Однако перед этим, в качестве отправной точки, мы рассмотрим технику, которую мы не будем применять в дальнейшем.

- -

-

Порождение потока для каждого запроса

-

Сначала давайте рассмотрим, как мог бы выглядеть код, если бы он создавал бы новый поток для каждого соединения. Как упоминалось ранее, мы не собираемся использовать этот способ в окончательной выполнения, из-за возможных неполадок при возможно неограниченном числе порождённых потоков. Это лишь отправная точка, с которой начнёт работу наш многопоточный сервер. Затем мы улучшим код, добавив объединениепотоков, и тогда разницу между этими двумя решениями будет легче заметить. В приложении 20-11 показаны изменения, которые нужно внести в код main, чтобы порождать новый поток для обработки каждого входящего соединения внутри цикла for.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        thread::spawn(|| {
-            handle_connection(stream);
-        });
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-11: Порождение нового потока для каждого соединения

-

Как вы изучили в главе 16, функция thread::spawn создаст новый поток и затем запустит код замыкания в этом новом потоке. Если вы запустите этот код и загрузите /sleep в своём браузере, а затем загрузите / в двух других вкладках браузера, вы действительно увидите, что запросам к / не приходится ждать завершения /sleep. Но, как мы уже упоминали, это в какой-то мгновение приведёт к сильному снижению производительности системы, так как вы будете создавать новые потоки без каких-либо ограничений.

- -

-

Создание конечного числа потоков

-

Мы хотим, чтобы наш объединениепотоков работал подобным, знакомым образом, чтобы переключение с потоков на объединениепотоков не требовало больших изменений в коде использующем наш API. В приложении 20-12 показан гипотетический внешняя оболочка для устройства ThreadPool, который мы хотим использовать вместо thread::spawn.

-

Файл: src/main.rs

-
use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-    let pool = ThreadPool::new(4);
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        pool.execute(|| {
-            handle_connection(stream);
-        });
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Приложение 20-12: Наш наилучший внешняя оболочка ThreadPool

-

Мы используем ThreadPool::new, чтобы создать новый объединениепотоков с конфигурируемым числом потоков, в данном случае четырьмя. Затем в цикле for функция pool.execute имеет внешнюю оболочку, похожий на thread::spawn, в том смысле, что он так же принимает замыкание, код которого объединениедолжен выполнить для каждого соединения. Нам нужно выполнить pool.execute, чтобы он принимал замыкание и передавал его потоку из объединения для выполнения. Этот код пока не собирается, но мы постараемся, чтобы сборщик помог нам это исправить.

- -

-

Создание ThreadPool с помощью разработки, управляемой сборщиком

-

Внесите изменения приложения 20-12 в файл src/main.rs, а затем давайте воспользуемся ошибками сборщика из приказы cargo check для управления нашей разработкой. Вот первая ошибка, которую мы получаем:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0433]: failed to resolve: use of undeclared type `ThreadPool`
-  --> src/main.rs:11:16
-   |
-11 |     let pool = ThreadPool::new(4);
-   |                ^^^^^^^^^^ use of undeclared type `ThreadPool`
-
-For more information about this error, try `rustc --explain E0433`.
-error: could not compile `hello` (bin "hello") due to 1 previous error
-
-

Замечательно! Ошибка говорит о том, что нам нужен вид или звено ThreadPool, поэтому мы сейчас его создадим. Наша выполнение ThreadPool не будет зависеть от того, что делает наш веб-сервер. Итак, давайте переделаем ящик hello из двоичного в библиотечный, чтобы хранить там нашу выполнение ThreadPool. После того, как мы переключимся в библиотечный ящик, мы также сможем использовать отдельную библиотеку объединения потоков для любой подходящей работы, а не только для обслуживания веб-запросов.

-

Создайте файл src/lib.rs, который содержит следующий код, который является простейшим определением устройства ThreadPool, которое мы можем иметь на данный мгновение:

-

Файл: src/lib.rs

-
pub struct ThreadPool;
-

Затем изменените файл main.rs, чтобы внести ThreadPool из библиотечного ящика в текущую область видимости, добавив следующий код в начало src/main.rs:

-

Файл: src/main.rs

-
use hello::ThreadPool;
-use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-    let pool = ThreadPool::new(4);
-
-    for stream in listener.incoming() {
-        let stream = stream.unwrap();
-
-        pool.execute(|| {
-            handle_connection(stream);
-        });
-    }
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Этот код по-прежнему не будет работать, но давайте проверим его ещё раз, чтобы получить следующую ошибку, которую нам нужно устранить:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0599]: no function or associated item named `new` found for struct `ThreadPool` in the current scope
-  --> src/main.rs:12:28
-   |
-12 |     let pool = ThreadPool::new(4);
-   |                            ^^^ function or associated item not found in `ThreadPool`
-
-For more information about this error, try `rustc --explain E0599`.
-error: could not compile `hello` (bin "hello") due to 1 previous error
-
-

Эта ошибка указывает, что далее нам нужно создать сопряженную функцию с именем new для ThreadPool. Мы также знаем, что new должна иметь один свойство, который может принимать 4 в качестве переменной и должен возвращать образец ThreadPool. Давайте выполняем простейшую функцию new, которая будет иметь эти свойства:

-

Файл: src/lib.rs

-
pub struct ThreadPool;
-
-impl ThreadPool {
-    pub fn new(size: usize) -> ThreadPool {
-        ThreadPool
-    }
-}
-

Мы выбираем usize в качестве вида свойства size, потому что мы знаем, что отрицательное число потоков не имеет никакого смысла. Мы также знаем, что мы будем использовать число 4 в качестве количества элементов в собрания потоков, для чего предназначен вид usize, как обсуждалось в разделе "Целочисленные виды" главы 3.

-

Давайте проверим код ещё раз:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0599]: no method named `execute` found for struct `ThreadPool` in the current scope
-  --> src/main.rs:17:14
-   |
-17 |         pool.execute(|| {
-   |         -----^^^^^^^ method not found in `ThreadPool`
-
-For more information about this error, try `rustc --explain E0599`.
-error: could not compile `hello` (bin "hello") due to 1 previous error
-
-

Теперь мы ошибка возникает из-за того, что у нас нет способа execute в устройстве ThreadPool. Вспомните раздел "Создание конечного числа потоков", в котором мы решили, что наш объединениепотоков должен иметь внешнюю оболочку, похожий на thread::spawn. Кроме того, мы выполняем функцию execute, чтобы она принимала замыкание и передавала его свободному потоку из объединения для запуска.

-

Мы определим способ execute у ThreadPool, принимающий замыкание в качестве свойства. Вспомните из раздела "Перемещение захваченных значений из замыканий и особенности Fn" главы 13 сведения о том, что мы можем принимать замыкания в качестве свойств тремя различными особенностями: Fn , FnMut и FnOnce. Нам нужно решить, какой вид замыкания использовать здесь. Мы знаем, что в конечном счёте мы сделаем что-то похожее на выполнение встроенной библиотеки thread::spawn, поэтому мы можем посмотреть, какие ограничения накладывает на свой свойство ярлык функции thread::spawn. Документация показывает следующее:

-
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
-    where
-        F: FnOnce() -> T,
-        F: Send + 'static,
-        T: Send + 'static,
-

Свойство вида F - это как раз то, что нас важно; свойство вида T относится к возвращаемому значению и нам он не важен. Можно увидеть, что spawn использует FnOnce в качестве ограничения особенности у F. Возможно это как раз то, чего мы хотим, так как в конечном итоге мы передадим полученный в execute переменная в функцию spawn. Дополнительную уверенность в том, что FnOnce - это именно тот особенность, который мы хотим использовать, нам даётобстоятельство, что поток для выполнения запроса будет выполнять замыкание этого запроса только один раз, что соответствует части Once ("единожды") в названии особенности FnOnce.

-

Свойство вида F также имеет ограничение особенности Send и ограничение времени жизни 'static, которые полезны в нашей случаи: нам нужен Send для передачи замыкания из одного потока в другой и 'static, потому что мы не знаем, сколько времени поток будет выполняться. Давайте создадим способ execute для ThreadPool, который будет принимать обобщённый свойство вида F со следующими ограничениями:

-

Файл: src/lib.rs

-
pub struct ThreadPool;
-
-impl ThreadPool {
-    // --snip--
-    pub fn new(size: usize) -> ThreadPool {
-        ThreadPool
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-

Мы по-прежнему используем () после FnOnce потому что особенность FnOnce представляет замыкание, которое не принимает свойств и возвращает единичный вид (). Также как и при определении функций, вид возвращаемого значения в ярлыке может быть опущен, но даже если у нас нет свойств, нам все равно нужны скобки.

-

Опять же, это самая простая выполнение способа execute: она ничего не делает, мы просто пытаемся сделать код собираемым. Давайте проверим снова:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.24s
-
-

Сейчас мы получаем только предупреждения, что означает, что код собирается! Но обратите внимание, если вы попробуете cargo run и сделаете запрос в браузере, вы увидите ошибки в браузере, которые мы видели в начале главы. Наша библиотека на самом деле ещё не вызывает замыкание, переданное в execute!

-
-

Примечание: вы возможно слышали высказывание о языках со строгими сборщиками, таких как Haskell и Rust, которое звучит так: «Если код собирается, то он работает». Но это высказывание не всегда верно. Наш дело собирается, но абсолютно ничего не делает! Если бы мы создавали существующий, законченный дело, это был бы хороший мгновение начать писать состоящие из звеньев проверки, чтобы проверять, что код собирается и имеет желаемое поведение.

-
-

Проверка количества потоков в new

-

Мы ничего не делаем с свойствами new и execute. Давайте выполняем тела этих функций с нужным нам поведением. Для начала давайте подумаем о new. Ранее мы выбрали беззнаковый вид для свойства size, потому что объединениес отрицательным числом потоков не имеет смысла. Объединение с нулём потоков также не имеет смысла, однако ноль - это вполне допустимое значение usize. Мы добавим код для проверки того, что size больше нуля, прежде чем вернуть образец ThreadPool, и заставим программу паниковать, если она получит ноль, используя макрос assert!, как показано в приложении 20-13.

-

Файл: src/lib.rs

-
pub struct ThreadPool;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        ThreadPool
-    }
-
-    // --snip--
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-

Приложение 20-13: Выполнение ThreadPool::new с со сбоем завершениям работы, если size равен нулю

-

Мы добавили немного документации для нашей устройства ThreadPool с помощью примечаниев. Обратите внимание, что мы следовали хорошим применением документирования, добавив раздел, в котором указывается случаей, при которой функция может со сбоем завершаться, как это обсуждалось в главе 14. Попробуйте запустить cargo doc --open и кликнуть на устройство ThreadPool, чтобы увидеть как выглядит созданная документация для new!

-

Вместо добавления макроса assert!, как мы здесь сделали, мы могли бы преобразовать функцию new в функцию build таким образом, чтобы она возвращала Result , подобно тому, как мы делали в функции Config::new дела ввода/вывода в приложении 12-9. Но в данном случае мы решили, что попытка создания объединения потоков без указания хотя бы одного потока должна быть непоправимой ошибкой. Если вы чувствуете такое стремление, попробуйте написать функцию build с ярлыком ниже, для сравнения с функцией new:

-
pub fn build(size: usize) -> Result<ThreadPool, PoolCreationError> {
-

Создание места для хранения потоков

-

Теперь, имея возможность удостовериться, что количество потоков для хранения в объединении соответствует требованиям, мы можем создавать эти потоки и сохранять их в устройстве ThreadPool перед тем как возвратить её. Но как мы "сохраним" поток? Давайте ещё раз посмотрим на ярлык thread::spawn:

-
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
-    where
-        F: FnOnce() -> T,
-        F: Send + 'static,
-        T: Send + 'static,
-

Функция spawn возвращает вид JoinHandle<T>, где T является видом, который возвращает замыкание. Давайте попробуем использовать JoinHandle и посмотрим, что произойдёт. В нашем случае замыкания, которые мы передаём объединению потоков, будут обрабатывать соединение и не будут возвращать ничего, поэтому T будет единичным (unit) видом ().

-

Код в приложении 20-14 собирается, но пока не создаст ни одного потока. Мы изменили определение ThreadPool так, чтобы он содержал вектор образцов thread::JoinHandle<()>, объявляли вектор ёмкостью size, установили цикл for, который будет выполнять некоторый код для создания потоков, и вернули образец ThreadPool, содержащий их.

-

Файл: src/lib.rs

-
use std::thread;
-
-pub struct ThreadPool {
-    threads: Vec<thread::JoinHandle<()>>,
-}
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let mut threads = Vec::with_capacity(size);
-
-        for _ in 0..size {
-            // create some threads and store them in the vector
-        }
-
-        ThreadPool { threads }
-    }
-    // --snip--
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-

Приложение 20-14: Создание вектора в ThreadPool для хранения потоков

-

Мы включили std::thread в область видимости библиотечного ящика, потому что мы используем thread::JoinHandle в качестве вида элементов вектора в ThreadPool.

-

После получения правильного значения size, наш ThreadPool создаёт новый вектор, который может содержать size элементов. Функция with_capacity выполняет ту же задачу, что и Vec::new, но с важным отличием: она заранее выделяет необходимый объём памяти в векторе. Поскольку мы знаем, что нам нужно хранить size элементов в векторе, предварительное выделение памяти для этих элементов будет немного более эффективным, чем использование Vec::new, при котором размер вектора будет увеличиваться по мере вставки элементов.

-

Если вы снова запустите приказ cargo check, она должна завершиться успешно.

-

Устройства Worker, ответственная за отправку кода из ThreadPool в поток

-

Мы целенаправленно оставили примечание в цикле for в Приложении 20-14 по поводу создания потоков. Сейчас мы разберёмся, как на самом деле создаются потоки. Обычная библиотека предоставляет thread::spawn для создания потоков, причём thread::spawn ожидает получить некоторый код, который поток должен выполнить, как только он будет создан. Однако в нашем случае мы хотим создавать потоки и заставлять их ожидать код, который мы будем передавать им позже. Выполнение потоков в встроенной библиотеке не предоставляет никакого способа сделать это, мы должны выполнить это вручную.

-

Мы будем выполнить это поведение, добавив новую устройство данных между ThreadPool и потоками, которая будет управлять этим новым поведением. Мы назовём эту устройство Worker ("работник"), это общепринятое имя в выполнения объединений. Работник берёт код, который нужно выполнить, и запускает этот код внутри рабочего потока. Представьте людей, работающих на кухне ресторана: работники ожидают, пока не поступят заказы от клиентов, а затем они несут ответственность за принятие этих заказов и их выполнение.

-

Вместо того чтобы хранить вектор образцов JoinHandle<()> в объединении потоков, мы будем хранить образцы устройства Worker. Каждый Worker будет хранить один образец JoinHandle<()>. Затем мы выполняем способ у Worker, который будет принимать замыкание и отправлять его в существующий поток для выполнения. Для того чтобы мы могли различать работники в объединении при логировании или отладке, мы также присвоим каждому работнику id.

-

Вот как выглядит новая последовательность действий, которые будут происходить при создании ThreadPool. Мы выполняем код, который будет отправлять замыкание в поток, после того, как у нас будет Worker , заданный следующим образом:

-
    -
  1. Определим устройство Worker, которая содержит id и JoinHandle<()>.
  2. -
  3. Изменим ThreadPool, чтобы он содержал вектор образцов Worker.
  4. -
  5. Определим функцию Worker::new, которая принимает номер id и возвращает образец Worker, который содержит id и поток, порождённый с пустым замыканием.
  6. -
  7. В ThreadPool::new используем счётчик цикла for для создания id, создаём новый Worker с этим id и сохраняем образец "работника" в вектор.
  8. -
-

Если вы готовы принять вызов, попробуйте выполнить эти изменения самостоятельно, не глядя на код в приложении 20-15.

-

Готовы? Вот приложение 20-15 с одним из способов сделать указанные ранее изменения.

-

Файл: src/lib.rs

-
use std::thread;
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-}
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id));
-        }
-
-        ThreadPool { workers }
-    }
-    // --snip--
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize) -> Worker {
-        let thread = thread::spawn(|| {});
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-15: Изменение ThreadPool для хранения образцов Worker вместо непосредственного хранения потоков

-

Мы изменили название поля в ThreadPool с threads на workers, поскольку теперь оно содержит образцы Worker вместо образцов JoinHandle<()>. Мы используем счётчик в цикле for для передачи цифрового определителя в качестве переменной Worker::new, и сохраняем каждый новый Worker в векторе с именем workers.

-

Внешний код (вроде нашего сервера в src/bin/main.rs) не обязательно должен знать подробности выполнения, касающиеся использования устройства Worker внутри ThreadPool, поэтому мы делаем устройство Worker и её функцию new закрытыми. Функция Worker::new использует заданный нами id и сохраняет образец JoinHandle<()>, который создаётся при порождении нового потока с пустым замыканием.

-
-

Примечание: Если операционная система не может создать поток из-за нехватки системных ресурсов, thread::spawn со сбоем завершится. Это приведёт к со сбоемму завершению нашего сервера целиком, даже если некоторые потоки были созданы успешно. Для простоты будем считать, что нас устраивает такое поведение, но в существующей выполнения объединения потоков вы, вероятно, захотите использовать std::thread::Builder и его способ spawn, который вместо этого возвращает Result .

-
-

Этот код собирается и будет хранить количество образцов Worker, которое мы указали в качестве переменной функции ThreadPool::new. Но мы всё ещё не обрабатываем замыкание, которое мы получаем в способе execute. Давайте посмотрим, как это сделать далее.

-

Отправка запросов в потоки через потоки

-

Следующая неполадка, с которой мы будем бороться, заключается в том, что замыкания, переданные в thread::spawn абсолютно ничего не делают. Сейчас мы получаем замыкание, которое хотим выполнить, в способе execute. Но мы должны передать какое-то замыкание в способ thread::spawn, при создании каждого Worker во время создания ThreadPool.

-

Мы хотим, чтобы вновь созданные устройства Worker извлекали код для запуска из очереди, хранящейся в ThreadPool и отправляли этот код в свой поток для выполнения.

-

потоки (channels), простой способ связи между двумя потоками, с которыми мы познакомились в главе 16, кажется наилучше подойдут для этого сценария. Мы будем использовать поток в качестве очереди заданий, а приказ execute отправит задание из ThreadPool образцам Worker, которые будут отправлять задание в свой поток. Расчет таков:

-
    -
  1. ThreadPool создаст поток и будет хранить отправитель.
  2. -
  3. Каждый Worker будет хранить приёмник.
  4. -
  5. Мы создадим новую устройство Job, которая будет хранить замыкания, которые мы хотим отправить в поток.
  6. -
  7. Способ execute отправит задание, которое он хочет выполнить, в отправляющую сторону потока.
  8. -
  9. В своём потоке Worker будет замкнуто опрашивать принимающую сторону потока и выполнять замыкание любого задания, которое он получит.
  10. -
-

Давайте начнём с создания потока в ThreadPool::new и удержания отправляющей стороны в образце ThreadPool, как показано в приложении 20-16. В устройстве Job сейчас ничего не содержится, но это будет вид элемента, который мы отправляем в поток.

-

Файл: src/lib.rs

-
use std::{sync::mpsc, thread};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-struct Job;
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id));
-        }
-
-        ThreadPool { workers, sender }
-    }
-    // --snip--
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize) -> Worker {
-        let thread = thread::spawn(|| {});
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-16: Изменение ThreadPool для хранения отправляющей части потока, который отправляет образцы Job

-

В ThreadPool::new мы создаём наш новый поток и сохраняем в объединении его отправляющую сторону. Код успешно собирается.

-

Давайте попробуем передавать принимающую сторону потока каждому "работнику" (устройстве Worker), когда объединениепотоков создаёт поток. Мы знаем, что хотим использовать получающую часть потока в потоке, порождаемым "работником", поэтому мы будем ссылаться на свойство receiver в замыкании. Код 20-17 пока не собирается.

-

Файл: src/lib.rs

-
use std::{sync::mpsc, thread};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-struct Job;
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, receiver));
-        }
-
-        ThreadPool { workers, sender }
-    }
-    // --snip--
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-
-// --snip--
-
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: mpsc::Receiver<Job>) -> Worker {
-        let thread = thread::spawn(|| {
-            receiver;
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-17: Передача принимающей части потока "работникам"

-

Мы внесли несколько небольших и простых изменений: мы передаём принимающую часть потока в Worker::new, а затем используем его внутри замыкания.

-

При попытке проверить код, мы получаем ошибку:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0382]: use of moved value: `receiver`
-  --> src/lib.rs:26:42
-   |
-21 |         let (sender, receiver) = mpsc::channel();
-   |                      -------- move occurs because `receiver` has type `std::sync::mpsc::Receiver<Job>`, which does not implement the `Copy` trait
-...
-25 |         for id in 0..size {
-   |         ----------------- inside of this loop
-26 |             workers.push(Worker::new(id, receiver));
-   |                                          ^^^^^^^^ value moved here, in previous iteration of loop
-   |
-note: consider changing this parameter type in method `new` to borrow instead if owning the value isn't necessary
-  --> src/lib.rs:47:33
-   |
-47 |     fn new(id: usize, receiver: mpsc::Receiver<Job>) -> Worker {
-   |        --- in this method       ^^^^^^^^^^^^^^^^^^^ this parameter takes ownership of the value
-help: consider moving the expression out of the loop so it is only moved once
-   |
-25 ~         let mut value = Worker::new(id, receiver);
-26 ~         for id in 0..size {
-27 ~             workers.push(value);
-   |
-
-For more information about this error, try `rustc --explain E0382`.
-error: could not compile `hello` (lib) due to 1 previous error
-
-

Код пытается передать receiver нескольким образцам Worker. Это не сработает, поскольку, как вы можете помнить из главы 16: выполнение потока, которую предоставляет Ржавчина - несколько производителей, один потребитель. Это означает, что мы не можем просто клонировать принимающую сторону потока, чтобы исправить этот код. Кроме этого, мы не хотим отправлять одно и то же сообщение нескольким потребителям, поэтому нам нужен единый список сообщений для множества обработчиков, чтобы каждое сообщение обрабатывалось лишь один раз.

-

Кроме того, удаление задачи из очереди потока включает изменение receiver, поэтому потокам необходим безопасный способ делиться и изменять receiver, в противном случае мы можем получить условия гонки (как описано в главе 16).

-

Вспомните умные указатели, которые обсуждались в главе 16: чтобы делиться владением между несколькими потоками и разрешать потокам изменять значение, нам нужно использовать вид Arc<Mutex<T>>. Вид Arc позволит нескольким "работникам" владеть получателем (receiver), а Mutex заверяет что только один "работник" сможет получить задание (job) от получателя за раз. Приложение 20-18 показывает изменения, которые мы должны сделать.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-// --snip--
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-struct Job;
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    // --snip--
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-    }
-}
-
-// --snip--
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        // --snip--
-        let thread = thread::spawn(|| {
-            receiver;
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-18. Совместное использование приёмника в "работниках" с применением Arc и Mutex

-

В ThreadPool::new мы помещаем принимающую сторону потока внутрь Arc и Mutex. Для каждого нового "работника" мы клонируем Arc, чтобы увеличить счётчик ссылок так, что "работники" могут разделять владение принимающей стороной потока.

-

С этими изменениями код собирается! Мы подбираемся к цели!

-

Выполнение способа execute

-

Давайте выполняем наконец способ execute у устройства ThreadPool. Мы также изменим вид Job со устройства на псевдоним вида для особенность-предмета. который будет содержать вид замыкания, принимаемый способом execute. Как описано в разделе "Создание родственных вида с помощью псевдонимов типа" главы 19, псевдонимы видов позволяют делать длинные виды короче, облегчая их использование. Посмотрите на приложение 20-19.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-// --snip--
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    // --snip--
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-// --snip--
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(|| {
-            receiver;
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-19: Создание псевдонима вида Job для указателя Box, содержащего каждое замыкание и затем отправляющее задание (job) в поток

-

После создания нового образца Job с замыканием, полученным в execute, мы посылаем его через отправляющий конец потока. На тот случай, если отправка не удастся, вызываем unwrap у send. Это может произойти, например, если мы остановим выполнение всех наших потоков, что означает, что принимающая сторона прекратила получать новые сообщения. На данный мгновение мы не можем остановить выполнение наших потоков: наши потоки будут исполняться до тех пор, пока существует объединение Причина, по которой мы используем unwrap, заключается в том, что, хотя мы знаем, что сбой не произойдёт, сборщик этого не знает.

-

Но мы ещё не закончили! В "работнике" (worker) наше замыкание, переданное в thread::spawn все ещё ссылается только на принимающую сторону потока. Вместо этого нам нужно, чтобы замыкание работало в бесконечном цикле, запрашивая задание у принимающей части потока и выполняя задание, когда оно принято. Давайте внесём изменения, показанные в приложении 20-20 внутри Worker::new.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-// --snip--
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-20: Получение и выполнение заданий в потоке "работника"

-

Здесь мы сначала вызываем lock у receiver, чтобы получить мьютекс, а затем вызываем unwrap, чтобы со сбоем завершить работу при любых ошибках. Захват блокировки может завершиться неудачей, если мьютекс находится в отравленном состоянии (poisoned state), что может произойти, если какой-то другой поток завершился со сбоем, удерживая блокировку, вместо снятия блокировки. В этой случаи вызвать unwrap для со сбоемго завершения потока вполне оправдано. Не стесняйтесь заменить unwrap на expect с сообщением об ошибке, которое имеет для вас значение.

-

Если мы получили блокировку мьютекса, мы вызываем recv, чтобы получить Job из потока. Последний вызов unwrap позволяет миновать любые ошибки, которые могут возникнуть, если поток, управляющий отправитель, прекратил исполняться, подобно тому, как способ send возвращает Err, если получатель не принимает сообщение.

-

Вызов recv - блокирующий, поэтому пока задач нет, текущий поток будет ждать, пока задача не появится. Mutex<T> заверяет, что только один поток Worker за раз попытается запросить задачу.

-

Наш объединениепотоков теперь находится в рабочем состоянии! Выполните cargo run и сделайте несколько запросов:

- -
$ cargo run
-   Compiling hello v0.1.0 (file:///projects/hello)
-warning: field is never read: `workers`
- --> src/lib.rs:7:5
-  |
-7 |     workers: Vec<Worker>,
-  |     ^^^^^^^^^^^^^^^^^^^^
-  |
-  = note: `#[warn(dead_code)]` on by default
-
-warning: field is never read: `id`
-  --> src/lib.rs:48:5
-   |
-48 |     id: usize,
-   |     ^^^^^^^^^
-
-warning: field is never read: `thread`
-  --> src/lib.rs:49:5
-   |
-49 |     thread: thread::JoinHandle<()>,
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-warning: `hello` (lib) generated 3 warnings
-    Finished dev [unoptimized + debuginfo] target(s) in 1.40s
-     Running `target/debug/hello`
-Worker 0 got a job; executing.
-Worker 2 got a job; executing.
-Worker 1 got a job; executing.
-Worker 3 got a job; executing.
-Worker 0 got a job; executing.
-Worker 2 got a job; executing.
-Worker 1 got a job; executing.
-Worker 3 got a job; executing.
-Worker 0 got a job; executing.
-Worker 2 got a job; executing.
-
-

Успех! Теперь у нас есть объединениепотоков, который обрабатывает соединения не согласованно. Никогда не создаётся более четырёх потоков, поэтому наша система не будет перегружена, если сервер получит много запросов. Если мы отправим запрос ресурса /sleep, сервер сможет обслуживать другие запросы, обрабатывая их в другом потоке.

-
-

Примечание: если вы запросите /sleep в нескольких окнах браузера одновременно, они могут загружаться по одному, с интервалами в 5 секунд. Некоторые веб-браузеры выполняют несколько образцов одного и того же запроса последовательно из-за кэширования. Такое ограничение не связано с работой нашего веб-сервера.

-
-

После изучения цикла while let в главе 18 вы можете удивиться, почему мы не написали код рабочего потока (worker thread), как показано в приложении 20-22.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-// --snip--
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || {
-            while let Ok(job) = receiver.lock().unwrap().recv() {
-                println!("Worker {id} got a job; executing.");
-
-                job();
-            }
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-22: Иная выполнение Worker::new с использованием while let

-

Этот код собирается и запускается, но не даёт желаемого поведения: медленный запрос всё равно приведёт к тому, что другие запросы будут ждать обработки. Причина здесь несколько тоньше: устройства Mutex не имеет открытого способа unlock, так как владение блокировкой основано на времени жизни MutexGuard<T> внутри LockResult<MutexGuard<T>>, которое возвращает способ lock. Во время сборки анализатор заимствований может проследить за выполнением правила, согласно которому к ресурсу, охраняемому Mutex, нельзя получить доступ пока мы удерживаем блокировку. Однако в этой выполнение мы также можем получить случай, когда блокировка будет удерживаться дольше, чем предполагалось, если мы не будем внимательно учитывать время жизни MutexGuard<T>.

-

Код в приложении 20-20, использующий let job = receiver.lock().unwrap().recv().unwrap(); работает, потому что при использовании let любые промежуточные значения, используемые в выражении справа от знака равенства, немедленно уничтожаются после завершения указания let. Однако while letif let и match) не удаляет временные значения до конца связанного раздела. Таким образом, в приложении 20-21 блокировка не снимается в течение всего времени вызова job(), что означает, что другие работники не могут получать задания.

-

Мягкое завершение работы и очистка

-

Приложение 20-20 не согласованно отвечает на запросы с помощью использования объединения потоков, как мы и хотели. Мы получаем некоторые предупреждения про workers, id и поля thread, которые мы не используем напрямую, что напоминает нам о том, что мы не освобождаем все ресурсы. Когда мы используем менее элегантный способ остановки основного потока клавишной сочетанием ctrl-c, все остальные потоки также немедленно останавливаются, даже если они находятся в середине обработки запроса.

-

Далее, выполняем особенность Drop для вызова join у каждого потока в объединении, чтобы они могли завершить запросы, над которыми они работают, перед закрытием. Затем мы выполняем способ сообщить потокам, что они должны перестать принимать новые запросы и завершить работу. Чтобы увидеть этот код в действии, мы изменим наш сервер так, чтобы он принимал только два запроса, после чего правильно завершал работу объединения потоков.

-

Выполнение особенности Drop для ThreadPool

-

Давайте начнём с выполнения Drop у нашего объединения потоков. Когда объединениеудаляется, все наши потоки должны объединиться (join), чтобы убедиться, что они завершают свою работу. В приложении 20-22 показана первая попытка выполнения Drop, код пока не будет работать.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            worker.thread.join().unwrap();
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: thread::JoinHandle<()>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker { id, thread }
-    }
-}
-

Приложение 20-22: Присоединение (Joining) каждого потока, когда объединениепотоков выходит из области видимости

-

Сначала мы пройдёмся по каждому worker из объединения потоков. Для этого мы используем &mut с self, потому что нам нужно иметь возможность изменять worker. Для каждого обработчика мы выводим сообщение о том, что он завершает работу, а затем вызываем join у потока этого обработчика. Для случаев, когда вызов join не удался, мы используем unwrap, чтобы заставить Ржавчина запаниковать и перейти в режим грубого завершения работы.

-

Ошибка получаемая при сборки этого кода:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0507]: cannot move out of `worker.thread` which is behind a mutable reference
-  --> src/lib.rs:52:13
-   |
-52 |             worker.thread.join().unwrap();
-   |             ^^^^^^^^^^^^^ ------ `worker.thread` moved due to this method call
-   |             |
-   |             move occurs because `worker.thread` has type `JoinHandle<()>`, which does not implement the `Copy` trait
-   |
-note: `JoinHandle::<T>::join` takes ownership of the receiver `self`, which moves `worker.thread`
-  --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/thread/mod.rs:1718:17
-
-For more information about this error, try `rustc --explain E0507`.
-error: could not compile `hello` (lib) due to 1 previous error
-
-

Ошибка говорит нам, что мы не можем вызвать join, потому что у нас есть только изменяемое заимствование каждого worker, а join забирает во владение свой переменная. Чтобы решить эту неполадку, нам нужно извлечь поток из образца Worker, который владеет thread, чтобы join мог его использовать. Мы сделали это в приложении 17-15: теперь, когда Worker хранит в себе Option<thread::JoinHandle<()>>, мы можем воспользоваться способом take у Option, чтобы извлечь значение из исхода Some, тем самым оставляя на его месте None. Другими словами, в рабочем состоянии Worker будет использовать исход Some содержащий thread, а когда мы захотим завершить Worker, мы заменим Some на None, чтобы у Worker не было потока для работы.

-

Итак, мы хотим обновить объявление Worker следующим образом:

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            worker.thread.join().unwrap();
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker { id, thread }
-    }
-}
-

Теперь давайте опираться на сборщик, чтобы найти другие места, которые нужно изменить. Проверяя код, мы получаем две ошибки:

-
$ cargo check
-    Checking hello v0.1.0 (file:///projects/hello)
-error[E0599]: no method named `join` found for enum `Option` in the current scope
-  --> src/lib.rs:52:27
-   |
-52 |             worker.thread.join().unwrap();
-   |                           ^^^^ method not found in `Option<JoinHandle<()>>`
-   |
-note: the method `join` exists on the type `JoinHandle<()>`
-  --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/thread/mod.rs:1718:5
-help: consider using `Option::expect` to unwrap the `JoinHandle<()>` value, panicking if the value is an `Option::None`
-   |
-52 |             worker.thread.expect("REASON").join().unwrap();
-   |                          +++++++++++++++++
-
-error[E0308]: mismatched types
-  --> src/lib.rs:72:22
-   |
-72 |         Worker { id, thread }
-   |                      ^^^^^^ expected `Option<JoinHandle<()>>`, found `JoinHandle<_>`
-   |
-   = note: expected enum `Option<JoinHandle<()>>`
-            found struct `JoinHandle<_>`
-help: try wrapping the expression in `Some`
-   |
-72 |         Worker { id, thread: Some(thread) }
-   |                      +++++++++++++      +
-
-Some errors have detailed explanations: E0308, E0599.
-For more information about an error, try `rustc --explain E0308`.
-error: could not compile `hello` (lib) due to 2 previous errors
-
-

Давайте обратимся ко второй ошибке, которая указывает на код в конце Worker::new; нам нужно обернуть значение thread в исход Some при создании нового Worker. Внесите следующие изменения, чтобы исправить эту ошибку:

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            worker.thread.join().unwrap();
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        // --snip--
-
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker {
-            id,
-            thread: Some(thread),
-        }
-    }
-}
-

Первая ошибка находится в нашей выполнения Drop. Ранее мы упоминали, что намеревались вызвать take для свойства Option, чтобы забрать thread из этапа worker. Следующие изменения делают это:

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: mpsc::Sender<Job>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool { workers, sender }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            if let Some(thread) = worker.thread.take() {
-                thread.join().unwrap();
-            }
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker {
-            id,
-            thread: Some(thread),
-        }
-    }
-}
-

Как уже говорилось в главе 17, способ take у вида Option забирает значение из исхода Some и оставляет исход None в этом месте. Мы используем if let, чтобы разъединять Some и получить поток; затем вызываем join у потока. Если поток "работника" уже None, мы знаем, что этот "работник" уже очистил свой поток, поэтому в этом случае ничего не происходит.

-

Тревожное оповещение потокам прекратить прослушивание получения задач

-

Теперь, после всех внесённых нами изменений, код собирается без каких-либо предупреждений. Но плохая новость в том, что этот код всё ещё не работает так, как мы этого хотим. Причина заключается в логике замыканий, запускаемых потоками образцов Worker: в данный мгновение мы вызываем join, но это не приводит к завершению потоков, так как они находятся в бесконечном цикле, ожидая новую задачу. Если мы попытаемся удалить ThreadPool в текущей выполнения drop, основной поток навсегда заблокируется в ожидании завершения первого потока из объединения .

-

Чтобы решить эту неполадку, нам нужно будет изменить выполнение drop в ThreadPool, а затем внести изменения в цикл Worker .

-

Во-первых, изменим выполнение drop ThreadPool таким образом, чтобы явно удалять sender перед тем, как начнём ожидать завершения потоков. В приложении 20-23 показаны изменения в ThreadPool для явного удаления sender . Мы используем ту же технику Option и take, что и с потоком, чтобы переместить sender из ThreadPool:

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: Option<mpsc::Sender<Job>>,
-}
-// --snip--
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        // --snip--
-
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool {
-            workers,
-            sender: Some(sender),
-        }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.as_ref().unwrap().send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        drop(self.sender.take());
-
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            if let Some(thread) = worker.thread.take() {
-                thread.join().unwrap();
-            }
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let job = receiver.lock().unwrap().recv().unwrap();
-
-            println!("Worker {id} got a job; executing.");
-
-            job();
-        });
-
-        Worker {
-            id,
-            thread: Some(thread),
-        }
-    }
-}
-

Приложение 20-23. Явное удаление sender перед ожиданием завершения рабочих потоков

-

Удаление sender закрывает поток, что указывает на то, что сообщения больше не будут отправляться. Когда это произойдёт, все вызовы recv, выполняемые рабочими этапами в бесконечном цикле, вернут ошибку. В приложении 20-24 мы меняем цикл Worker для правильного выхода из него в этом случае, что означает, что потоки завершатся, когда выполнение drop ThreadPool вызовет для них join.

-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: Option<mpsc::Sender<Job>>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool {
-            workers,
-            sender: Some(sender),
-        }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.as_ref().unwrap().send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        drop(self.sender.take());
-
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            if let Some(thread) = worker.thread.take() {
-                thread.join().unwrap();
-            }
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let message = receiver.lock().unwrap().recv();
-
-            match message {
-                Ok(job) => {
-                    println!("Worker {id} got a job; executing.");
-
-                    job();
-                }
-                Err(_) => {
-                    println!("Worker {id} disconnected; shutting down.");
-                    break;
-                }
-            }
-        });
-
-        Worker {
-            id,
-            thread: Some(thread),
-        }
-    }
-}
-

Приложение 20-24: Явный выход из цикла, когда recv возвращает ошибку

-

Чтобы увидеть этот код в действии, давайте изменим main, чтобы принимать только два запроса, прежде чем правильно завершить работу сервера как показано в приложении 20-25.

-

Файл: src/main.rs

-
use hello::ThreadPool;
-use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-    let pool = ThreadPool::new(4);
-
-    for stream in listener.incoming().take(2) {
-        let stream = stream.unwrap();
-
-        pool.execute(|| {
-            handle_connection(stream);
-        });
-    }
-
-    println!("Shutting down.");
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Код 20-25. Выключение сервера после обслуживания двух запросов с помощью выхода из цикла

-

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

-

Способ take определён в особенности Iterator и ограничивает повторение самое большее первыми двумя элементами. ThreadPool выйдет из области видимости в конце main и будет запущена его выполнение drop.

-

Запустите сервер с cargo run и сделайте три запроса. Третий запрос должен выдать ошибку и в окне вызова вы должны увидеть вывод, подобный следующему:

- -
$ cargo run
-   Compiling hello v0.1.0 (file:///projects/hello)
-    Finished dev [unoptimized + debuginfo] target(s) in 1.0s
-     Running `target/debug/hello`
-Worker 0 got a job; executing.
-Shutting down.
-Shutting down worker 0
-Worker 3 got a job; executing.
-Worker 1 disconnected; shutting down.
-Worker 2 disconnected; shutting down.
-Worker 3 disconnected; shutting down.
-Worker 0 disconnected; shutting down.
-Shutting down worker 1
-Shutting down worker 2
-Shutting down worker 3
-
-

Вы возможно увидите другой порядок рабочих потоков и напечатанных сообщений. Мы можем увидеть, как этот код работает по сообщениям: "работники" номер 0 и 3 получили первые два запроса. Сервер прекратил принимать соединения после второго подключения, а выполнение Drop для ThreadPool начинает выполняется ещё тогда, когда как работник 3 даже не приступил к выполнению своей работы. Удаление sender отключает все рабочие потоки от потока и просит их завершить работу. Каждый рабочий поток при отключении печатает сообщение, а затем объединениепотоков вызывает join, чтобы дождаться, пока каждый из рабочих потоков завершится.

-

Обратите внимание на один важная особенность этого определенного запуска: ThreadPool удалил sender, и прежде чем какой-либо из работников получил ошибку, мы попытались присоединить (join) рабочий поток с номером 0. Рабочий поток 0 ещё не получил ошибку от recv, поэтому основной поток заблокировался, ожидания завершения потока работника 0. Тем временем, работник 3 получил задание, а затем каждый из рабочих потоков получил ошибку. Когда рабочий поток 0 завершился, основной поток ждал окончания завершения выполнения остальных рабочих потоков. В этот мгновение все они вышли из своих циклов и остановились.

-

Примите поздравления! Теперь мы завершили дело; у нас есть основной веб-сервер, использующий объединениепотоков для не согласованных ответов. Мы можем выполнить правильное завершение работы сервера, очистив все потоки в объединении.

-

Вот полный код для справки:

-

Файл: src/main.rs

-
use hello::ThreadPool;
-use std::{
-    fs,
-    io::{prelude::*, BufReader},
-    net::{TcpListener, TcpStream},
-    thread,
-    time::Duration,
-};
-
-fn main() {
-    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
-    let pool = ThreadPool::new(4);
-
-    for stream in listener.incoming().take(2) {
-        let stream = stream.unwrap();
-
-        pool.execute(|| {
-            handle_connection(stream);
-        });
-    }
-
-    println!("Shutting down.");
-}
-
-fn handle_connection(mut stream: TcpStream) {
-    let buf_reader = BufReader::new(&mut stream);
-    let request_line = buf_reader.lines().next().unwrap().unwrap();
-
-    let (status_line, filename) = match &request_line[..] {
-        "GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
-        "GET /sleep HTTP/1.1" => {
-            thread::sleep(Duration::from_secs(5));
-            ("HTTP/1.1 200 OK", "hello.html")
-        }
-        _ => ("HTTP/1.1 404 NOT FOUND", "404.html"),
-    };
-
-    let contents = fs::read_to_string(filename).unwrap();
-    let length = contents.len();
-
-    let response =
-        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
-
-    stream.write_all(response.as_bytes()).unwrap();
-}
-

Файл: src/lib.rs

-
use std::{
-    sync::{mpsc, Arc, Mutex},
-    thread,
-};
-
-pub struct ThreadPool {
-    workers: Vec<Worker>,
-    sender: Option<mpsc::Sender<Job>>,
-}
-
-type Job = Box<dyn FnOnce() + Send + 'static>;
-
-impl ThreadPool {
-    /// Create a new ThreadPool.
-    ///
-    /// The size is the number of threads in the pool.
-    ///
-    /// # Panics
-    ///
-    /// The `new` function will panic if the size is zero.
-    pub fn new(size: usize) -> ThreadPool {
-        assert!(size > 0);
-
-        let (sender, receiver) = mpsc::channel();
-
-        let receiver = Arc::new(Mutex::new(receiver));
-
-        let mut workers = Vec::with_capacity(size);
-
-        for id in 0..size {
-            workers.push(Worker::new(id, Arc::clone(&receiver)));
-        }
-
-        ThreadPool {
-            workers,
-            sender: Some(sender),
-        }
-    }
-
-    pub fn execute<F>(&self, f: F)
-    where
-        F: FnOnce() + Send + 'static,
-    {
-        let job = Box::new(f);
-
-        self.sender.as_ref().unwrap().send(job).unwrap();
-    }
-}
-
-impl Drop for ThreadPool {
-    fn drop(&mut self) {
-        drop(self.sender.take());
-
-        for worker in &mut self.workers {
-            println!("Shutting down worker {}", worker.id);
-
-            if let Some(thread) = worker.thread.take() {
-                thread.join().unwrap();
-            }
-        }
-    }
-}
-
-struct Worker {
-    id: usize,
-    thread: Option<thread::JoinHandle<()>>,
-}
-
-impl Worker {
-    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
-        let thread = thread::spawn(move || loop {
-            let message = receiver.lock().unwrap().recv();
-
-            match message {
-                Ok(job) => {
-                    println!("Worker {id} got a job; executing.");
-
-                    job();
-                }
-                Err(_) => {
-                    println!("Worker {id} disconnected; shutting down.");
-                    break;
-                }
-            }
-        });
-
-        Worker {
-            id,
-            thread: Some(thread),
-        }
-    }
-}
-

Мы могли бы сделать ещё больше! Если вы хотите продолжить совершенствование этого дела, вот несколько мыслей:

-
    -
  • Добавьте больше документации в ThreadPool и его открытые способы.
  • -
  • Добавьте проверки для возможностей, исполняемого библиотекой.
  • -
  • Замените вызовы unwrap на более устойчивую обработку ошибок.
  • -
  • Используйте ThreadPool для выполнения некоторых других задач, помимо обслуживания веб-запросов.
  • -
  • На crates.io найдите ящик для работы с объединениями потоков и на его основе выполните подобный веб-сервер. Затем сравните его API и надёжность с выполненным нами объединением потоков.
  • -
-

Итоги

-

Отличная работа! Вы сделали это к концу книги! Мы хотим поблагодарить вас за то, что присоединились к нам в этом путешествии по языку Rust. Теперь вы готовы выполнить свои собственные дела на Ржавчина и помочь с делами другим людям. Имейте в виду, что сообщество Ржавчина разработчиков довольно гостеприимно, они с удовольствием постараются помочь вам с любыми трудностями, с которыми вы можете столкнуться в своём путешествии по Rust.

-

Дополнительная сведения

-

Следующие разделы содержат справочные источники, которые могут оказаться полезными в вашем путешествии по Rust.

-

Приложение A: Ключевые слова

-

Следующий список содержит ключевые слова, зарезервированные для текущего или будущего использования в языке Rust. Как таковые их нельзя использовать в качестве определителей (за исключением сырых определителей, которые мы обсудим в разделе «Сырые определители»). определительы — это имена функций, переменных, свойств, полей устройств, звеньев, ящиков, постоянных значений, макросов, постоянных значений, свойств, видов, свойств или времён жизни.

-

Используемые в настоящее время ключевые слова

-

Ниже приведён список используемых в настоящее время ключевых слов с их описанием.

-
    -
  • as — выполнить простое преобразование, уточнить определенную свойство, которую содержит предмет, или переименовать элемент в выражении use
  • -
  • async — возврат Future вместо блокировки текущего потока
  • -
  • await — остановка выполнения до готовности итога Future
  • -
  • break — немедленный выход из цикла
  • -
  • const — определение постоянного элемента или неизменяемого сырого указателя
  • -
  • continue — досрочный переход к следующей повторения цикла
  • -
  • crate — ссылка на корень дополнения в пути к звену
  • -
  • dyn — изменяемая отсылка к особенности предмета
  • -
  • else — иные ветви для устройств управления потока if и if let
  • -
  • enum — определение перечислений
  • -
  • extern — связывание внешней функции или переменной
  • -
  • false — логический ложный запись
  • -
  • fn — определение функции или вида указателя на функцию
  • -
  • for — замкнуто перебирать элементы из повторителя, выполнить признак или указывать время жизни с более высоким рейтингом.
  • -
  • if — ветвление на основе итога условного выражения
  • -
  • impl — выполнение встроенной возможности или возможности особенности
  • -
  • in — часть правил написания цикла for
  • -
  • let — объявление (связывание) переменной
  • -
  • loop — безусловный цикл
  • -
  • match — сопоставление значения с образцами
  • -
  • mod — определение звена
  • -
  • move — перекладывание владения на замыкание всеми захваченными элементами
  • -
  • mut — обозначение изменчивости в ссылках, сырах указателей и привязках к образцу
  • -
  • pub — изменитель открытой доступность полей устройств, разделов impl и звеньев
  • -
  • ref — привязка по ссылке
  • -
  • return — возвращает итог из функции
  • -
  • Self — псевдоним для определяемого или исполняемого вида
  • -
  • self — предмет текущего способа или звена
  • -
  • static — вездесущая переменная или время жизни, продолжающееся на протяжении всего выполнения программы
  • -
  • struct — определение устройства
  • -
  • super — родительский звено текущего звена
  • -
  • trait — определение особенности
  • -
  • true — логический истинный запись
  • -
  • type — определение псевдонима вида или связанного вида
  • -
  • union - определить объединение; является ключевым словом только при использовании в объявлении объединения
  • -
  • unsafe — обозначение небезопасного кода, функций, особенностей и их выполнений
  • -
  • use — ввод имён в область видимости
  • -
  • where — ограничение вида
  • -
  • while — условный цикл, основанный на итоге выражения
  • -
-

Ключевые слова, зарезервированные для будущего использования

-

Следующие ключевые слова ещё не имеют никакой возможности, но зарезервированы Ржавчина для возможного использования в будущем.

-
    -
  • abstract
  • -
  • become
  • -
  • box
  • -
  • do
  • -
  • final
  • -
  • macro
  • -
  • override
  • -
  • priv
  • -
  • try
  • -
  • typeof
  • -
  • unsized
  • -
  • virtual
  • -
  • yield
  • -
-

Сырые определители

-

Сырые определители — это правила написания, позволяющий использовать ключевые слова там, где обычно они не могут быть. Для создания и использования сырого определителя к ключевому слову добавляется приставка r#.

-

Например, ключевое слово match. Если вы попытаетесь собрать следующую функцию, использующую в качестве имени match:

-

Файл: src/main.rs

-
fn match(needle: &str, haystack: &str) -> bool {
-    haystack.contains(needle)
-}
-

вы получите ошибку:

-
error: expected identifier, found keyword `match`
- --> src/main.rs:4:4
-  |
-4 | fn match(needle: &str, haystack: &str) -> bool {
-  |    ^^^^^ expected identifier, found keyword
-
-

Ошибка говорит о том, что вы не можете использовать ключевое слово match в качестве определителя функции. Чтобы получить возможность использования слова match в качестве имени функции, нужно использовать правила написания «сырых определителей», например так:

-

Файл: src/main.rs

-
fn r#match(needle: &str, haystack: &str) -> bool {
-    haystack.contains(needle)
-}
-
-fn main() {
-    assert!(r#match("foo", "foobar"));
-}
-

Этот код собирается без ошибок. Обратите внимание, что приставка r# в определении имени функции указан так же, как он указан в месте её вызова в main.

-

Сырые определители позволяют вам использовать любое слово, которое вы выберете, в качестве определителя, даже если это слово окажется зарезервированным ключевым словом. Это даёт нам больше свободы в выборе имён определителей, а также позволяет нам встраиваться с программами, написанными на языке, где эти слова не являются ключевыми. Кроме того, необработанные определители позволяют вам использовать библиотеки, написанные в исполнения Rust, отличной от используемой в вашем ящике. Например, try не является ключевым словом в выпуске 2015 года, но является в выпуске 2018 года. Если вы зависите от библиотеки, написанной с использованием исполнения 2015 года и имеющей функцию try, вам потребуется использовать правила написания сырого определителя, в данном случае r#try, для вызова этой функции из кода исполнения 2018 года. См. Приложение E для получения дополнительной сведений о изданиех Rust.

-

Дополнение Б: Операторы и обозначения

-

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

-

Операторы

-

Таблица Б-1 содержит операторы языка Rust, пример появления оператора, короткое объяснение, возможность перегрузки оператора. Если оператор можно перегрузить, то показан особенность, с помощью которого его можно перегрузить.

-

Таблица Б-1: Операторы

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ОператорПримерОбъяснениеПерегружаемость
!ident!(...), ident!{...}, ident![...]Вызов макроса
!!exprПобитовое или логическое отрицаниеNot
!=expr != exprСравнение "не равно"PartialEq
%expr % exprОстаток от деленияRem
%=var %= exprОстаток от деления и присваиваниеRemAssign
&&expr, &mut exprЗаимствование
&&type, &mut type, &'a type, &'a mut typeУказывает что данный вид заимствуется
&expr & exprПобитовое ИBitAnd
&=var &= exprПобитовое И и присваиваниеBitAndAssign
&&expr && exprЛогическое И
*expr * exprАрифметическое умножениеMul
*=var *= exprАрифметическое умножение и присваиваниеMulAssign
**exprРазыменование ссылкиDeref
**const type, *mut typeУказывает, что данный вид является сырым указателем
+trait + trait, 'a + traitСоединение ограничений вида
+expr + exprАрифметическое сложениеAdd
+=var += exprАрифметическое сложение и присваиваниеAddAssign
,expr, exprРазделитель переменных и элементов
-- exprАрифметическое отрицаниеNeg
-expr - exprАрифметическое вычитаниеSub
-var -= exprАрифметическое вычитание и присваиваниеSubAssign
->fn(...) -> type, &vert;...&vert; -> type...
.expr.identДоступ к элементу
...., expr.., ..expr, expr..exprУказывает на рядчисел, исключая правыйPartialOrd
..=..=expr, expr..=exprУказывает на рядчисел, включая правыйPartialOrd
....exprправила написания обновления устройства
..variant(x, ..), struct_type { x, .. }Привязка «И все остальное»
...expr...expr(Устарело, используйте новый правила написания ..=) Используется при определении инклюзивного ряда
/expr / exprАрифметическое делениеDiv
/=var /= exprАрифметическое деление и присваиваниеDivAssign
:pat: type, ident: typeОграничения видов
:ident: exprОбъявление поля устройства
:'a: loop {...}Метка цикла
;expr;Признак конца указания и элемента
;[...; len]Часть правил написания массива конечного размера
<<expr << exprБитовый сдвиг влевоShl
<<=var <<= exprБитовый сдвиг влево и присваиваниеShlAssign
<expr < exprСравнение "меньше чем"PartialOrd
<=expr <= exprСравнение "меньше или равно"PartialOrd
=var = expr, ident = typeПрисваивание/эквивалентность
==expr == exprСравнение "равно"PartialEq
=>pat => exprЧасть правил написания устройства match
>expr > exprСравнение "больше чем"PartialOrd
>=expr >= exprСравнение "больше или равно"PartialOrd
>>expr >> exprБитовый сдвиг вправоShr
>>=var >>= exprБитовый сдвиг вправо и присваиваниеShrAssign
@ident @ patPattern binding
^expr ^ exprПобитовое исключающее ИЛИBitXor
^=var ^= exprПобитовое исключающее ИЛИ и присваиваниеBitXorAssign
&vert;pat &vert; patИные образцы
&vert;expr &vert; exprПобитовое ИЛИBitOr
&vert;=var &vert;= exprПобитовое ИЛИ и присваиваниеBitOrAssign
&vert;&vert;expr &vert;&vert; exprКороткое логическое ИЛИ
?expr?Возврат ошибки
-
-

Обозначения не-операторы

-

Следующий список содержит все символы, которые не работают как операторы; то есть они не ведут себя как вызов функции или способа.

-

Таблица Б-2 показывает символы, которые появляются сами по себе и допустимы в различных местах.

-

Таблица Б-2: Автономный правила написания

-
- - - - - - - - - - - -
ОбозначениеОбъяснение
'identИменованное время жизни или метка цикла
...u8, ...i32, ...f64, ...usize, etc.Числовой запись определённого вида
"..."Строковый запись
r"...", r#"..."#, r##"..."##, etc.Необработанный строковый запись, в котором не обрабатываются escape-символы
b"..."Строковый запись байтов; создаёт массив байтов вместо строки
br"...", br#"..."#, br##"..."##, etc.Необработанный строковый байтовый запись, сочетание необработанного и байтового записи
'...'Символьный запись
b'...'ASCII байтовый запись
&vert;...&vert; exprЗамыкание
!Всегда пустой вид для расходящихся функций
_«Пренебрегаемое» связывание образцов; также используется для читабельности целочисленных записей
-
-

Таблица Б-3 показывает обозначения которые появляются в среде путей упорядочевания звеньев

-

Таблица Б-3. Правила написания, связанный с путями

-
- - - - - - - - - -
ОбозначениеОбъяснение
ident::identПуть к пространству имён
::pathПуть относительно корня ящика (т. е. явный абсолютный путь)
self::pathПуть относительно текущего звена (т. е. явный относительный путь).
super::pathПуть относительно родительского звена текущего звена
type::ident, <type as trait>::identСопряженные постоянные значения, функции и виды
<type>::...Сопряженный элемент для вида, который не может быть назван прямо (например <&T>::..., <[T]>::..., etc.)
trait::method(...)Устранение неоднозначности вызова способа путём именования особенности, который определяет его
type::method(...)Устранение неоднозначности путём вызова способа через имя вида, для которого он определён
<type as trait>::method(...)Устранение неоднозначности вызова способа путём именования особенности и вида
-
-

Таблица Б-4 показывает обозначения которые появляются в среде использования обобщённых видов свойств

-

Таблица Б-4: Обобщения

-
- - - - - - - - -
ОбозначениеОбъяснение
path<...>Определяет свойства для обобщённых свойств в виде (e.g., Vec<u8>)
path::<...>, method::<...>Определяет свойства для обобщённых свойств, функций, или способов в выражении. Часто называют turbofish (например "42".parse::<i32>())
fn ident<...> ...Определение обобщённой функции
struct ident<...> ...Определение обобщённой устройства
enum ident<...> ...Объявление обобщённого перечисления
impl<...> ...Определение обобщённой выполнения
for<...> typeВысокоуровневое связывание времени жизни
type<ident=type>Обобщённый вид где один или более сопряженных видов имеют определённое присваивание (например Iterator<Item=T>)
-
-

Таблица Б-5 показывает обозначения которые появляются в среде использования обобщённых видов свойств с ограничениями видов

-

Таблица Б-5: Ограничения видов

-
- - - - - - -
ОбозначениеОбъяснение
T: UОбобщённый свойство T ограничивается до видов которые выполняют особенность U
T: 'aОбобщённый вид T должен существовать не меньше чем 'a (то есть вид не может иметь ссылки с временем жизни меньше чем 'a)
T: 'staticОбобщённый вид T не имеет заимствованных ссылок кроме имеющих время жизни 'static
'b: 'aОбобщённое время жизни 'b должно быть не меньше чем 'a
T: ?SizedПозволяет обобщённым видам свойства иметь изменяемый размер
'a + trait, trait + traitСоединение ограничений видов
-
-

Таблица Б-6 показывает обозначения, которые появляются в среде вызова или определения макросов и указания свойств элемента.

-

Таблица Б-6: Макросы и свойства

-
- - - - - - -
ОбозначениеОбъяснение
#[meta]Внешний свойство
#![meta]Внутренний свойство
$identПодстановка в макросе
$ident:kindЗахват макроса
$(…)…Повторение макроса
ident!(...), ident!{...}, ident![...]Вызов макроса
-
-

Таблица Б-7 показывает обозначения, которые создают примечания.

-

Таблица Б-7: Примечания

-
- - - - - - -
ОбозначениеОбъяснение
//Однострочный примечание
//!Внутренний однострочный примечание документации
///Внешний однострочный примечание документации
/*...*/Многострочный примечание
/*!...*/Внутренний многострочный примечание документации
/**...*/Внешний многострочный примечание документации
-
-

Таблица Б-8 показывает обозначения, которые появляются в среде использования упорядоченных рядов.

-

Таблица Б-8: Упорядоченные ряды

-
- - - - - - - - -
ОбозначениеОбъяснение
()Пустой упорядоченный ряд, он же пустой вид. И запись и вид.
(expr)Выражение в скобках
(expr,)Упорядоченный ряд с одним элементом выражения
(type,)Упорядоченный ряд с одним элементом вида
(expr, ...)Выражение упорядоченного ряда
(type, ...)Вид упорядоченного ряда
(type, ...)Выражение вызова функции; также используется для объявления устройств-упорядоченных рядов и исходов-упорядоченных рядов перечисления
expr.0, expr.1, etc.Взятие элемента по порядковому указателю в упорядоченном ряде
-
-

Таблица Б-9 показывает среды, в которых используются фигурные скобки.

-

Таблица Б-9: Фигурные скобки

-
- - -
СредаОбъяснение
{...}Выражение раздела
Type {...}struct запись
-
-

Таблица Б-10 показывает среды, в которых используются квадратные скобки.

-

Таблица Б-10: Квадратные скобки

-
- - - - - -
СредаОбъяснение
[...]Запись массива
[expr; len]Запись массива, содержащий len повторов expr
[type; len]Массив, содержащий len образцов вида type
expr[expr]Взятие по порядковому указателю в собрания. Возможна перегрузка (Index, IndexMut)
expr[..], expr[a..], expr[..b], expr[a..b]Взятие среза собрания по порядковому указателю, используется Range, RangeFrom, RangeTo, или RangeFull как "порядковый указатель"
-

Дополнение В: Выводимые особенности

-

Во многих частях книги мы обсуждали свойство derive, которые Вы могли применить к объявлению устройства или перечисления. Свойство derive порождает код по умолчанию для выполнения особенности, который вы указали в derive.

-

В этом дополнении, мы расскажем про все особенности, которые вы можете использовать в свойстве derive. Каждая раздел содержит:

-
    -
  • Действия и способы, добавляемые особенностью
  • -
  • Как представлена выполнение особенности через derive
  • -
  • Что выполнение особенности рассказывает про вид
  • -
  • Условия, в которых разрешено или запрещено выполнить особенность
  • -
  • Примеры случаев, которые требуют наличие особенности
  • -
-

Если Вам понадобилось поведение отличное от поведения при выполнения через derive, обратитесь к документации по встроенной библиотеке чтобы узнать как вручную выполнить особенность.

-

Перечисленные здесь особенности являются единственными, определёнными встроенной библиотекой, которые могут быть выполнены в ваших видах с помощью derive. Другие особенности, определённые в встроенной библиотеке, не имеют ощутимого поведения по умолчанию, поэтому вам решать, как выполнить их для достижения ваших целей.

-

Пример особенности, который нельзя выполнить через derive - Display, который обрабатывает изменение -для конечных пользователей. Вы всегда должны сами рассмотреть лучший способ для отображения вида конечному пользователю. Какие части вида должны быть разрешены для просмотра конечному пользователю? Какие части они найдут подходящими? Какой вид вывода для них будет самым подходящим? Сборщик Ржавчина не знает ответы на эти вопросы, поэтому он не может подобрать подходящее обычное поведение.

-

Список видов, выполняемых через derive, в этом дополнении не является исчерпывающим: библиотеки могут выполнить derive для их собственных особенностей, составляя свои списки особенностей, которые Вы можете использовать с помощью derive. Выполнение derive включает в себя использование процедурных макросов, которые были рассмотрены в разделе "Макросы" главы 19.

-

Debug для отладочного вывода

-

Особенность Debug включает отладочное изменение -в изменяемых строках, которые вы можете указать с помощью :? внутри {} фигурных скобок.

-

Особенность Debug позволяет Вам напечатать предметы вида с целью отладки, поэтому Вы и другие программисты, использующие Ваш вид, смогут проверить предмет в определённой точке выполнения программы.

-

Особенность Debug обязателен в некоторых случаях. Например, при использовании макроса assert_eq!. Этот макрос печатает значения входных переменных, если они не совпадают. Это позволяет программистам увидеть, почему эти предметы не равны.

-

PartialEq и Eq для сравнения равенства

-

Особенность PartialEq позволяет Вам сравнить предметы одного вида на эквивалентность, и включает для них использование операторов == и !=.

-

Использование PartialEq выполняет способ eq. Когда PartialEq используют для устройства, два предмета равны если равны все поля предметов, и предметы не равны, если хотя бы одно поле отлично. Когда используется для перечислений, каждый исход равен себе, и не равен другим исходам.

-

Особенность PartialEq обязателен в некоторых случаях. Например для макроса assert_eq!, где необходимо сравнивать два предмета одного вида на эквивалентность.

-

Особенность Eq не имеет способов. Он указывает что каждое значение определеного вида равно самому себе. Особенность Eq может быть применён только для видов выполняющих особенность PartialEq, хотя не все виды, которые выполняют PartialEq могут выполнить Eq. Примером являются числа с плавающей запятой: выполнение чисел с плавающей запятой говорит, что два образца со значениями не-число (NaN) не равны друг другу.

-

Особенность Eqнеобходим в некоторых случаях. Например, для ключей в HashMap<K, V>. Поэтому HashMap<K, V> может сказать, что два ключа являются одним и тем же.

-

PartialOrd и Ord для сравнения порядка

-

Особенность PartialOrd позволяет Вам сравнить предметы одного вида с помощью сортировки. Вид, выполняющий PartialOrd может использоваться с операторами <, >, <=, и >=. Вы можете выполнить особенность PartialOrd только для видов, выполняющих PartialEq.

-

Использование PartialOrd выполняет способ partial_cmp, который возвращает Option<Ordering> который является None когда значения не выстраивают порядок. Примером значения, которое не может быть упорядочено, не являются числом (NaN) значение с плавающей запятой. Вызов partial_cmp с любым числом с плавающей запятой и значением NaN вернёт None.

-

Когда используется для устройств, PartialOrd сравнивает два предмета путём сравнения значений каждого поля в порядке, в котором поля объявлены в устройстве. Когда используется для перечислений, то исходы перечисления объявленные ранее будут меньше чем исходы объявленные позже.

-

Например, особенность PartialOrd может потребоваться для способа gen_range из rand ящика который порождает случайные значения в заданном ряде (который определён выражением ряда).

-

Особенность Ord позволяет знать, для двух значений определеного вида всегда будет существовать валидный порядок. Особенность Ord выполняет способ cmp, который возвращает Ordering а не Option<Ordering> потому что валидный порядок всегда будет существовать. Вы можете применить особенность Ord только для видов, выполняющих особенность PartialOrd и Eq (Eq также требует PartialEq). При использовании на устройствах или перечислениях, cmp имеет такое же поведение, как и partial_cmp вPartialOrd.

-

Особенность Ord необходим в некоторых случаях. Например, сохранение значений в BTreeSet<T>, виде данных, который хранит сведения на основе порядка отсортированных данных.

-

Clone и Copy для повторения значений

-

Особенность Clone позволяет вам явно создать глубокую повтор значения, а также этап повторения может вызывать особый код и воспроизводить данные с кучи. Более подробно про Clone смотрите в разделы "Способы взаимодействия переменных и данных: клонирование" в разделе 4.

-

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

-

Особенность Clone необходим в некоторых случаях. Например, для вызова способа to_vec для среза. Срез не владеет данными, содержащимися в нем, но вектор значений, возвращённый из to_vec должен владеть этими предметами, поэтому to_vec вызывает clone для всех данных. Таким образом, вид хранящийся в срезе, должен выполнить Clone.

-

Особенность Copy позволяет повторять значения повторяя только данные, которые хранятся на обойме, произвольный код не требуется. Смотрите раздел "Из обоймы данные: Повторение" в разделе 4 для большей сведений о Copy.

-

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

-

Вы можете вывести Copy для любого вида все части которого выполняют Copy. Вид который выполняет Copy должен также выполнить Clone, потому что вид выполняющий Copy имеет обыкновенную выполнение Clone который выполняет ту же задачу, что и Copy.

-

Особенность Copy нужен очень редко; виды, выполняющие Copy имеют небольшую переработку, то есть для него не нужно вызывать способ clone, который делает код более кратким.

-

Все, что вы делаете с Copy можно также делать и с Clone, но код может быть медленнее и требовать вызов способа clone в некоторых местах.

-

Hash для превращения значения в значение конечного размера

-

Особенность Hash позволяет превратить значение произвольного размера в значение конечного размера с использованием хеш-функции. Использование Hash выполняет способ hash. При выполнения через derive, способ hash сочетает итоги вызова hash на каждой части данных вида, то есть все поля или значения должны выполнить Hash для использования Hash с помощью derive.

-

Особенность Hash необходим в некоторых случаях. Например, для хранения ключей в HashMap<K, V>, для их более эффективного хранения.

-

Default для значений по умолчанию

-

Особенность Default позволяет создавать значение по умолчанию для вида. Использование Default выполняет функцию default. Обычная выполнение способа default вызовет функцию default на каждой части данных вида, то есть для использования Default через derive, все поля и значения вида данных должны также выполнить Default.

-

Функция Default::defaultчасто используется в сочетания с правилами написания обновления устройства, который мы обсуждали в разделы "Создание образца устройства из образца другой устройства с помощью правил написания обновления устройства" главы 5. Вы можете настроить несколько полей для устройства, а для остальных полей установить значения с помощью ..Default::default().

-

Особенность Default необходим в некоторых случаях. Например, для способа unwrap_or_default у вида Option<T>. Если значение Option<T> будет None, способ unwrap_or_default вернёт итог вызова функции Default::default для вида T, хранящегося в Option<T>.

-

Дополнение Г - Средства разработки

-

В этом дополнении мы расскажем про часто используемые средства разработки, предоставляемые Rust. Мы рассмотрим самостоятельное изменение -, быстрый путь исправления предупреждений, линтер, и встраивание с IDE.

-

Самостоятельное изменение

-

с rustfmt

-

Средство rustfmt переделает ваш код в соответствии со исполнением кода сообщества. Многие совместные дела используют rustfmt, чтобы предотвратить споры о том, какой исполнение использовать при написании Rust: все изменяют свой код с помощью этого средства.

-

Для установки rustfmt, введите следующее:

-
$ rustup component add rustfmt
-
-

Этот приказ установит rustfmt и cargo-fmt, также как Ржавчина даёт Вам одновременно rustc и cargo. Для изменения дела, использующего Cargo, введите следующее:

-
$ cargo fmt
-
-

Этот приказ изменит весь код на языке Ржавчина в текущем ящике. Будет изменён только исполнение кода, смысл останется прежней. Для большей сведений о rustfmt, смотрите документацию.

-

Исправление кода с rustfix

-

Средство rustfix включён в установку Ржавчина и может самостоятельно исправлять предупреждения сборщика с очевидным способом исправления сбоев, скорее всего, подходящим вам. Вероятно, вы уже видели предупреждения сборщика. Например, рассмотрим этот код:

-

Файл: src/main.rs

-
fn do_something() {}
-
-fn main() {
-    for i in 0..100 {
-        do_something();
-    }
-}
-

Мы вызываем функцию do_something 100 раз, но никогда не используем переменную i в теле цикла for. Ржавчина предупреждает нас об этом:

-
$ cargo build
-   Compiling myprogram v0.1.0 (file:///projects/myprogram)
-warning: unused variable: `i`
- --> src/main.rs:4:9
-  |
-4 |     for i in 0..100 {
-  |         ^ help: consider using `_i` instead
-  |
-  = note: #[warn(unused_variables)] on by default
-
-    Finished dev [unoptimized + debuginfo] target(s) in 0.50s
-
-

Предупреждение предлагает нам использовать _i как имя переменной: нижнее подчёркивание в начале определителя предполагает, что мы его не используем. Мы можем самостоятельно применить это предположение с помощью rustfix, запустив приказ cargo fix:

-
$ cargo fix
-    Checking myprogram v0.1.0 (file:///projects/myprogram)
-      Fixing src/main.rs (1 fix)
-    Finished dev [unoptimized + debuginfo] target(s) in 0.59s
-
-

Когда посмотрим в src/main.rs снова, мы увидим что cargo fix изменил наш код:

-

Файл: src/main.rs

-
fn do_something() {}
-
-fn main() {
-    for _i in 0..100 {
-        do_something();
-    }
-}
-

Переменная цикла for теперь носит имя _i, и предупреждение больше не появляется.

-

Также Вы можете использовать приказ cargo fix для перемещения вашего кода между различными изданиеми Rust. Издания будут рассмотрены в дополнении Д.

-

Больше проверок с Clippy

-

Средство Clippy является собранием проверок (lints) для анализа Вашего кода, поэтому Вы можете найти простые ошибки и улучшить ваш Ржавчина код.

-

Для установки Clippy, введите следующее:

-
$ rustup component add clippy
-
-

Для запуска проверок Clippy’s для дела Cargo, введите следующее:

-
$ cargo clippy
-
-

Например, скажем что Вы хотите написать программу, в которой будет использоваться приближенная математическая постоянное значение, такая как число Пи, как в следующей программе:

-

Файл: src/main.rs

-
fn main() {
-    let x = 3.1415;
-    let r = 8.0;
-    println!("the area of the circle is {}", x * r * r);
-}
-

Запуск cargo clippy для этого дела вызовет следующую ошибку:

-
error: approximate value of `f{32, 64}::consts::PI` found
- --> src/main.rs:2:13
-  |
-2 |     let x = 3.1415;
-  |             ^^^^^^
-  |
-  = note: `#[deny(clippy::approx_constant)]` on by default
-  = help: consider using the constant directly
-  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
-
-

Эта ошибка сообщает вам, что в Ржавчина уже определена более точная постоянное значение PI, и что ваша программа будет более правильной, если вы вместо неё будете использовать эту постоянное значение. Затем вы должны изменить свой код, чтобы использовать постоянное значение PI. Следующий код не приводит к ошибкам или предупреждениям от Clippy:

-

Файл: src/main.rs

-
fn main() {
-    let x = std::f64::consts::PI;
-    let r = 8.0;
-    println!("the area of the circle is {}", x * r * r);
-}
-

Для большей сведений о Clippy смотрите документацию.

-

Встраивание с IDE с помощью rust-analyzer

-

Чтобы облегчить встраивание с IDE, сообщество Ржавчина советует использовать rust-analyzer. Этот средство представляет собой набор направленных на сборщик утилит, которые используют Language Server Protocol, который является сводом требований для взаимодействия IDE и языков программирования друг с другом. Разные клиенты могут использовать rust-analyzer, например подключаемый звено анализатора Ржавчина для Visual Studio Code.

-

Посетите домашнюю страницу дела rust-analyzer для получения указаний по установке, затем установите поддержку языкового сервера в именно среде IDE. Ваша IDE получит такие возможности, как автозаполнение, переход к определению и встроенные ошибки.

-

Приложение E - Издания языка

-

В главе 1, можно увидеть, что приказ cargo new добавляет некоторые мета-данные о издания языка в файл Cargo.toml. Данное приложение рассказывает, что они означают.

-

Язык Ржавчина и его сборщик имеют шестинедельный цикл выпуска, означающий, что пользователи постоянно получают новые функции. В других языках обычно выпускают большие обновления, но редко. Объединение Ржавчина выпускает меньшие обновления, но более часто. Через некоторое время все эти небольшие изменения накапливаются. Между исполнениями обычно сложно оглянуться назад и сказать "Ого, язык сильно изменился между исполнениями Ржавчина 1.10 и Ржавчина 1.31!"

-

Каждые два или три года, объединение Ржавчина выпускает новую издание языка (Rust edition). Каждая издание объединяет все новые особенности, которые попали в язык с новыми дополнениями, с полной, обновлённой документацией и набором средств. Новые издания поставляются как часть шестинедельного этапа исполнений.

-

Для разных людей издания служат разным целям:

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

На мгновение написания доступны две издания Rust: Ржавчина 2015 и Ржавчина 2018. Данная книга написана с использованием идиом издания Ржавчина 2018.

-

Ключ edition в настроечном файле Cargo.toml отображает, какую издание сборщик должен использовать для вашего кода. Если ключа нет, то для обратной совместимости сборщик Ржавчина использует издание 2015.

-

Любой дело может выбрать издание отличную от издания по умолчанию, которая равна 2015. Издания могут содержать несовместимые изменения, включая новые ключевые слова, которые могут враждовать с определителями в коде. Однако, пока вы не переключитесь на новую издание, ваш код будет продолжать собираться даже после обновления используемой исполнения сборщика.

-

Все исполнения сборщика Ржавчина поддерживают любую издание, которая предшествовала выпуску текущей, и они могут линковать дополнения любой поддерживаемой издания. Изменения изданий действуют только на способ начального разбора сборщиком исходного кода. Поэтому, если вы используете 2015 издание, а одна из ваших зависимостей использует 2018, ваш дело будет собран и сможет пользоваться этой зависимостью. Обратная случаей, когда ваш дело использует Ржавчина 2018, а зависимость использует Ржавчина 2015, работает таким же образом.

-

Внесём ясность: большая часть возможностей будет доступна во всех изданиях. Разработчики, использующие любую издание Rust, будут продолжать получать улучшения по мере выпуска новых исполнений. Однако в некоторых случаях, в основном, когда добавляются новые ключевые слова, некоторые новые возможности могут быть доступны только в последних изданиях. Нужно переключить издание, чтобы воспользоваться новыми возможностями.

-

Для получения больше подробностей, есть полная книга Edition Guide про издания, в которой перечисляются различия между изданиями и объясняется, как самостоятельно обновить свой код на новую издание с помощью приказы cargo fix.

-

Приложение Е: Переводы книги

-

Для ресурсов на языках, отличных от английского. Большинство из них все ещё в разработке; см. ярлык «Переводы», чтобы помочь или сообщить нам о новом переводе!

- -

Дополнение Ё - Как создаётся Ржавчина и “Nightly Rust”

-

Это дополнение рассказывает как создаётся Rust, и как это влияет на Вас как на разработчика.

-

Безотказность без стагнации

-

Как язык, Ржавчина много заботиться о безотказности Вашего кода. Мы хотим чтобы Ржавчина был прочным фундаментом, вашей опорой, и если бы все постоянно менялось, это было бы невозможно. В то же время, если мы не можем экспериментировать с различными возможностями, мы не можем обнаружить важные сбоев до исполнения, когда мы не можем их изменить.

-

Нашим решением сбоев является “безотказность без стагнации”, и наш руководящий принцип: Вы никогда не должны бояться перехода на новую безотказную исполнение Rust. Каждое обновление должно быть безболезненным, но также должно добавлять новые функции, меньше дефектов и более быструю скорость сборки.

-

Ту-ту! потоки выпуска и поездка на поезде

-

Разработка языка Ржавчина работает по принципу расписания поездов. То есть, вся разработка совершается в ветке master Ржавчина хранилища. Выпуски следуют подходы последовательного выпуска продукта (software release train), которая была использована Cisco IOS и другими программными продуктами. Есть три потока выпуска Rust:

-
    -
  • Ночной (Nightly)
  • -
  • Бета (Beta)
  • -
  • Безотказный (Stable)
  • -
-

Большинство Ржавчина разработчиков используют безотказную исполнение, но те кто хотят попробовать экспериментальные новые функции, должны использовать Nightly или Beta.

-

Приведём пример, как работает этап разработки и выпуска новых исполнений. Давайте предположим, что объединение Ржавчина работает над исполнением Ржавчина 1.5. Его исполнение состоялся в декабре 2015 года, но это даст существующегостичность номера исполнения. Была добавлена новая возможность в Rust: новые изменения в ветку master. Каждую ночь выпускается новая ночная исполнение Rust. Каждый день является днём выпуска ночной исполнения и эти выпуски создаются нашей устройством самостоятельно . По мере того как идёт время, наши выпуски выглядят так:

-
nightly: * - - * - - *
-
-

Каждые шесть недель наступает время подготовки новой Beta исполнения! Ветка beta Ржавчина хранилища ответвляется от ветки master, используемой исполнением Nightly. Теперь мы имеем два выпуска:

-
nightly: * - - * - - *
-                     |
-beta:                *
-
-

Многие пользователи Ржавчина не используют активно бета-исполнение, но проверяют бета-исполнение в их системе CI для помощи Ржавчина обнаружить сбоев обратной совместимости. В это время каждую ночь выпускается новая исполнение Nightly:

-
nightly: * - - * - - * - - * - - *
-                     |
-beta:                *
-
-

Предположим, что была найдена отступление. Хорошо, что мы можем проверять бета-исполнение перед тем как отступление попала в безотказную исполнение! Исправление отправляется в ветку master, поэтому исполнение nightly исправлена и затем исправление также направляется в ветку beta, и происходит новый выпуск бета-исполнения:

-
nightly: * - - * - - * - - * - - * - - *
-                     |
-beta:                * - - - - - - - - *
-
-

Через шесть недель после выпуска бета-исполнения, наступает время для выпуска безотказной исполнения! Ветка stable создаётся из ветки beta:

-
nightly: * - - * - - * - - * - - * - - * - * - *
-                     |
-beta:                * - - - - - - - - *
-                                       |
-stable:                                *
-
-

Ура! Ржавчина 1.5 выпущена! Но мы также забыли про одну вещь: так как прошло шесть недель, мы должны выпустить бета-исполнение следующей исполнения Ржавчина 1.6. Поэтому после ответвления ветки stable из ветки beta, следующая исполнение beta ответвляется снова от nightly:

-
nightly: * - - * - - * - - * - - * - - * - * - *
-                     |                         |
-beta:                * - - - - - - - - *       *
-                                       |
-stable:                                *
-
-

Это называется “прообраз поезда” (train model), потому что каждые шесть недель выпуск “покидает станцию”, но ему все ещё нужно пройти поток beta, чтобы попасть в безотказную исполнение.

-

Rust выпускается каждые шесть недель, как часы. Если вы знаете дату одного выпуска Rust, вы знаете дату выпуска следующего: это шесть недель позднее. Хорошим особенностью выпуска исполнений каждые шесть недель является то, что следующий поезд прибывает скоро. Если какая-то функция не попадает в исполнение, не надо волноваться: ещё один выпуск произойдёт очень скоро! Это помогает снизить давление в случае если функция возможно не отполирована к дате выпуска.

-

Благодаря этому этапу, вы всегда можете посмотреть следующую исполнение Ржавчина и убедиться, что на неё легко будет перейти: если бета-выпуск будет работать не так как ожидалось, вы можете сообщить об этом разработчикам и он будет исправлен перед выпуском безотказной исполнения! Поломки в бета-исполнения случаются относительно редко, но rustc все ещё является частью программного обеспечения, поэтому дефекты все ещё существуют.

-

Ненадежные функции

-

У этой подходы выпуска есть ещё один плюс: ненадежные функции. Ржавчина использует технику называемую “флаги возможностей” (feature flags) для определения функций, которые были включены в выпуске. Если новая функция находится в активной разработке, она попадает в ветку master, и поэтому попадает в ночную исполнение, но с флагом функции (feature flag). Если как пользователь, вы хотите попробовать работу такой функции, находящейся в разработке, вы должны использовать ночную исполнение Ржавчина и указать в вашем исходном коде определённый флаг.

-

Если вы используете бета или безотказную исполнение Rust, Вы не можете использовать флаги функций. Этот ключевой мгновение позволяет использовать в действительностиновые возможности перед их отладкой. Это может использоваться желающими идти в ногу со временем, а другие могут использовать безотказную исполнение и быть уверенными что их код не сломается. Безотказность без стагнации.

-

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

-

Rustup и значение ночной исполнения Rust

-

Rustup делает лёгким изменение между различными потоками Rust, на вездесущем или местном для дела уровне. По умолчанию устанавливается безотказная исполнение Rust. Для установки ночной исполнения выполните приказ:

-
$ rustup toolchain install nightly
-
-

Вы можете также увидеть все установленные средства разработчика (toolchains) (исполнения Ржавчина и сопряженные составляющие) с помощью rustup. Это пример вывода у одного из авторов Ржавчина с компьютером на Windows:

-
> rustup toolchain list
-stable-x86_64-pc-windows-msvc (default)
-beta-x86_64-pc-windows-msvc
-nightly-x86_64-pc-windows-msvc
-
-

Как видите, включенный набор средств (toolchain) используется по умолчанию. Большинство пользователей Ржавчина используют безотказные исполнения большую часть времени. Возможно, вы захотите использовать безотказную большую часть времени, но использовать каждую ночную исполнение в определенном деле, потому что заботитесь о передовых возможностях. Для этого вы можете использовать приказ rustup override в папке этого дела, чтобы установить ночной набор средств, должна использоваться приказ rustup, когда вы находитесь в этом папке:

-
$ cd ~/projects/needs-nightly
-$ rustup override set nightly
-
-

Теперь каждый раз, когда вы вызываете rustc или cargo внутри ~/projects/needs-nightly, rustup будет следить за тем, чтобы вы используете ночную исполнение Rust, а не безотказную по умолчанию. Это очень удобно, когда у вас есть множество Ржавчина дел!

-

Этап RFC и приказы

-

Итак, как вы узнаете об этих новых возможностях? Прообраз разработки Ржавчина следует этапу запроса примечаниев (RFC - Request For Comments). Если хотите улучшить Rust, вы можете написать предложение, которое называется RFC.

-

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

-

Если новая возможность принята и кто-то может выполнить её, то задача открывается в хранилища Rust. Человек выполняющий её, вполне может не быть тем, кто предложил эту возможность! Когда выполнение готова, она попадает в master ветвь с флагом функции, как мы обсуждали в разделе "Небезотказных функциях".

-

Через некоторое время, разработчики Ржавчина использующие ночные выпуски, смогут опробовать новую возможность, члены приказы обсудят её, как она работает в ночной исполнения и решат, должна ли она попасть в безотказную исполнение Ржавчина или нет. Если принимается решение двигать её вперёд, ограничение функции с помощью флага убирается и функция теперь считается безотказной! Она едет в новую безотказную исполнение Rust.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - -
- - diff --git a/rustbook-ru/book/searcher.js b/rustbook-ru/book/searcher.js deleted file mode 100644 index dc03e0a02..000000000 --- a/rustbook-ru/book/searcher.js +++ /dev/null @@ -1,483 +0,0 @@ -"use strict"; -window.search = window.search || {}; -(function search(search) { - // Search functionality - // - // You can use !hasFocus() to prevent keyhandling in your key - // event handlers while the user is typing their search. - - if (!Mark || !elasticlunr) { - return; - } - - //IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith - if (!String.prototype.startsWith) { - String.prototype.startsWith = function(search, pos) { - return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; - }; - } - - var search_wrap = document.getElementById('search-wrapper'), - searchbar = document.getElementById('searchbar'), - searchbar_outer = document.getElementById('searchbar-outer'), - searchresults = document.getElementById('searchresults'), - searchresults_outer = document.getElementById('searchresults-outer'), - searchresults_header = document.getElementById('searchresults-header'), - searchicon = document.getElementById('search-toggle'), - content = document.getElementById('content'), - - searchindex = null, - doc_urls = [], - results_options = { - teaser_word_count: 30, - limit_results: 30, - }, - search_options = { - bool: "AND", - expand: true, - fields: { - title: {boost: 1}, - body: {boost: 1}, - breadcrumbs: {boost: 0} - } - }, - mark_exclude = [], - marker = new Mark(content), - current_searchterm = "", - URL_SEARCH_PARAM = 'search', - URL_MARK_PARAM = 'highlight', - teaser_count = 0, - - SEARCH_HOTKEY_KEYCODE = 83, - ESCAPE_KEYCODE = 27, - DOWN_KEYCODE = 40, - UP_KEYCODE = 38, - SELECT_KEYCODE = 13; - - function hasFocus() { - return searchbar === document.activeElement; - } - - function removeChildren(elem) { - while (elem.firstChild) { - elem.removeChild(elem.firstChild); - } - } - - // Helper to parse a url into its building blocks. - function parseURL(url) { - var a = document.createElement('a'); - a.href = url; - return { - source: url, - protocol: a.protocol.replace(':',''), - host: a.hostname, - port: a.port, - params: (function(){ - var ret = {}; - var seg = a.search.replace(/^\?/,'').split('&'); - var len = seg.length, i = 0, s; - for (;i': '>', - '"': '"', - "'": ''' - }; - var repl = function(c) { return MAP[c]; }; - return function(s) { - return s.replace(/[&<>'"]/g, repl); - }; - })(); - - function formatSearchMetric(count, searchterm) { - if (count == 1) { - return count + " search result for '" + searchterm + "':"; - } else if (count == 0) { - return "No search results for '" + searchterm + "'."; - } else { - return count + " search results for '" + searchterm + "':"; - } - } - - function formatSearchResult(result, searchterms) { - var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms); - teaser_count++; - - // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor - var url = doc_urls[result.ref].split("#"); - if (url.length == 1) { // no anchor found - url.push(""); - } - - // encodeURIComponent escapes all chars that could allow an XSS except - // for '. Due to that we also manually replace ' with its url-encoded - // representation (%27). - var searchterms = encodeURIComponent(searchterms.join(" ")).replace(/\'/g, "%27"); - - return '' + result.doc.breadcrumbs + '' - + '' - + teaser + ''; - } - - function makeTeaser(body, searchterms) { - // The strategy is as follows: - // First, assign a value to each word in the document: - // Words that correspond to search terms (stemmer aware): 40 - // Normal words: 2 - // First word in a sentence: 8 - // Then use a sliding window with a constant number of words and count the - // sum of the values of the words within the window. Then use the window that got the - // maximum sum. If there are multiple maximas, then get the last one. - // Enclose the terms in . - var stemmed_searchterms = searchterms.map(function(w) { - return elasticlunr.stemmer(w.toLowerCase()); - }); - var searchterm_weight = 40; - var weighted = []; // contains elements of ["word", weight, index_in_document] - // split in sentences, then words - var sentences = body.toLowerCase().split('. '); - var index = 0; - var value = 0; - var searchterm_found = false; - for (var sentenceindex in sentences) { - var words = sentences[sentenceindex].split(' '); - value = 8; - for (var wordindex in words) { - var word = words[wordindex]; - if (word.length > 0) { - for (var searchtermindex in stemmed_searchterms) { - if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) { - value = searchterm_weight; - searchterm_found = true; - } - }; - weighted.push([word, value, index]); - value = 2; - } - index += word.length; - index += 1; // ' ' or '.' if last word in sentence - }; - index += 1; // because we split at a two-char boundary '. ' - }; - - if (weighted.length == 0) { - return body; - } - - var window_weight = []; - var window_size = Math.min(weighted.length, results_options.teaser_word_count); - - var cur_sum = 0; - for (var wordindex = 0; wordindex < window_size; wordindex++) { - cur_sum += weighted[wordindex][1]; - }; - window_weight.push(cur_sum); - for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) { - cur_sum -= weighted[wordindex][1]; - cur_sum += weighted[wordindex + window_size][1]; - window_weight.push(cur_sum); - }; - - if (searchterm_found) { - var max_sum = 0; - var max_sum_window_index = 0; - // backwards - for (var i = window_weight.length - 1; i >= 0; i--) { - if (window_weight[i] > max_sum) { - max_sum = window_weight[i]; - max_sum_window_index = i; - } - }; - } else { - max_sum_window_index = 0; - } - - // add around searchterms - var teaser_split = []; - var index = weighted[max_sum_window_index][2]; - for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) { - var word = weighted[i]; - if (index < word[2]) { - // missing text from index to start of `word` - teaser_split.push(body.substring(index, word[2])); - index = word[2]; - } - if (word[1] == searchterm_weight) { - teaser_split.push("") - } - index = word[2] + word[0].length; - teaser_split.push(body.substring(word[2], index)); - if (word[1] == searchterm_weight) { - teaser_split.push("") - } - }; - - return teaser_split.join(''); - } - - function init(config) { - results_options = config.results_options; - search_options = config.search_options; - searchbar_outer = config.searchbar_outer; - doc_urls = config.doc_urls; - searchindex = elasticlunr.Index.load(config.index); - - // Set up events - searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false); - searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false); - document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false); - // If the user uses the browser buttons, do the same as if a reload happened - window.onpopstate = function(e) { doSearchOrMarkFromUrl(); }; - // Suppress "submit" events so the page doesn't reload when the user presses Enter - document.addEventListener('submit', function(e) { e.preventDefault(); }, false); - - // If reloaded, do the search or mark again, depending on the current url parameters - doSearchOrMarkFromUrl(); - } - - function unfocusSearchbar() { - // hacky, but just focusing a div only works once - var tmp = document.createElement('input'); - tmp.setAttribute('style', 'position: absolute; opacity: 0;'); - searchicon.appendChild(tmp); - tmp.focus(); - tmp.remove(); - } - - // On reload or browser history backwards/forwards events, parse the url and do search or mark - function doSearchOrMarkFromUrl() { - // Check current URL for search request - var url = parseURL(window.location.href); - if (url.params.hasOwnProperty(URL_SEARCH_PARAM) - && url.params[URL_SEARCH_PARAM] != "") { - showSearch(true); - searchbar.value = decodeURIComponent( - (url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20')); - searchbarKeyUpHandler(); // -> doSearch() - } else { - showSearch(false); - } - - if (url.params.hasOwnProperty(URL_MARK_PARAM)) { - var words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' '); - marker.mark(words, { - exclude: mark_exclude - }); - - var markers = document.querySelectorAll("mark"); - function hide() { - for (var i = 0; i < markers.length; i++) { - markers[i].classList.add("fade-out"); - window.setTimeout(function(e) { marker.unmark(); }, 300); - } - } - for (var i = 0; i < markers.length; i++) { - markers[i].addEventListener('click', hide); - } - } - } - - // Eventhandler for keyevents on `document` - function globalKeyHandler(e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text' || !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName)) { return; } - - if (e.keyCode === ESCAPE_KEYCODE) { - e.preventDefault(); - searchbar.classList.remove("active"); - setSearchUrlParameters("", - (searchbar.value.trim() !== "") ? "push" : "replace"); - if (hasFocus()) { - unfocusSearchbar(); - } - showSearch(false); - marker.unmark(); - } else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) { - e.preventDefault(); - showSearch(true); - window.scrollTo(0, 0); - searchbar.select(); - } else if (hasFocus() && e.keyCode === DOWN_KEYCODE) { - e.preventDefault(); - unfocusSearchbar(); - searchresults.firstElementChild.classList.add("focus"); - } else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE - || e.keyCode === UP_KEYCODE - || e.keyCode === SELECT_KEYCODE)) { - // not `:focus` because browser does annoying scrolling - var focused = searchresults.querySelector("li.focus"); - if (!focused) return; - e.preventDefault(); - if (e.keyCode === DOWN_KEYCODE) { - var next = focused.nextElementSibling; - if (next) { - focused.classList.remove("focus"); - next.classList.add("focus"); - } - } else if (e.keyCode === UP_KEYCODE) { - focused.classList.remove("focus"); - var prev = focused.previousElementSibling; - if (prev) { - prev.classList.add("focus"); - } else { - searchbar.select(); - } - } else { // SELECT_KEYCODE - window.location.assign(focused.querySelector('a')); - } - } - } - - function showSearch(yes) { - if (yes) { - search_wrap.classList.remove('hidden'); - searchicon.setAttribute('aria-expanded', 'true'); - } else { - search_wrap.classList.add('hidden'); - searchicon.setAttribute('aria-expanded', 'false'); - var results = searchresults.children; - for (var i = 0; i < results.length; i++) { - results[i].classList.remove("focus"); - } - } - } - - function showResults(yes) { - if (yes) { - searchresults_outer.classList.remove('hidden'); - } else { - searchresults_outer.classList.add('hidden'); - } - } - - // Eventhandler for search icon - function searchIconClickHandler() { - if (search_wrap.classList.contains('hidden')) { - showSearch(true); - window.scrollTo(0, 0); - searchbar.select(); - } else { - showSearch(false); - } - } - - // Eventhandler for keyevents while the searchbar is focused - function searchbarKeyUpHandler() { - var searchterm = searchbar.value.trim(); - if (searchterm != "") { - searchbar.classList.add("active"); - doSearch(searchterm); - } else { - searchbar.classList.remove("active"); - showResults(false); - removeChildren(searchresults); - } - - setSearchUrlParameters(searchterm, "push_if_new_search_else_replace"); - - // Remove marks - marker.unmark(); - } - - // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor . - // `action` can be one of "push", "replace", "push_if_new_search_else_replace" - // and replaces or pushes a new browser history item. - // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet. - function setSearchUrlParameters(searchterm, action) { - var url = parseURL(window.location.href); - var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM); - if (searchterm != "" || action == "push_if_new_search_else_replace") { - url.params[URL_SEARCH_PARAM] = searchterm; - delete url.params[URL_MARK_PARAM]; - url.hash = ""; - } else { - delete url.params[URL_MARK_PARAM]; - delete url.params[URL_SEARCH_PARAM]; - } - // A new search will also add a new history item, so the user can go back - // to the page prior to searching. A updated search term will only replace - // the url. - if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) { - history.pushState({}, document.title, renderURL(url)); - } else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) { - history.replaceState({}, document.title, renderURL(url)); - } - } - - function doSearch(searchterm) { - - // Don't search the same twice - if (current_searchterm == searchterm) { return; } - else { current_searchterm = searchterm; } - - if (searchindex == null) { return; } - - // Do the actual search - var results = searchindex.search(searchterm, search_options); - var resultcount = Math.min(results.length, results_options.limit_results); - - // Display search metrics - searchresults_header.innerText = formatSearchMetric(resultcount, searchterm); - - // Clear and insert results - var searchterms = searchterm.split(' '); - removeChildren(searchresults); - for(var i = 0; i < resultcount ; i++){ - var resultElem = document.createElement('li'); - resultElem.innerHTML = formatSearchResult(results[i], searchterms); - searchresults.appendChild(resultElem); - } - - // Display results - showResults(true); - } - - fetch(path_to_root + 'searchindex.json') - .then(response => response.json()) - .then(json => init(json)) - .catch(error => { // Try to load searchindex.js if fetch failed - var script = document.createElement('script'); - script.src = path_to_root + 'searchindex.js'; - script.onload = () => init(window.search); - document.head.appendChild(script); - }); - - // Exported functions - search.hasFocus = hasFocus; -})(window.search); diff --git a/rustbook-ru/book/searchindex.js b/rustbook-ru/book/searchindex.js deleted file mode 100644 index 0ea2008f3..000000000 --- a/rustbook-ru/book/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Object.assign(window.search, {"doc_urls":["title-page.html#Язык-программирования-rust","foreword.html#Предисловие","ch00-00-introduction.html#Введение","ch00-00-introduction.html#Кому-подходит-rust","ch00-00-introduction.html#Объединения-разработчиков","ch00-00-introduction.html#Студенты","ch00-00-introduction.html#Предприятия","ch00-00-introduction.html#Разработчики-open-source","ch00-00-introduction.html#Люди-ценящие-скорость-и-безотказность","ch00-00-introduction.html#Для-кого-эта-книга","ch00-00-introduction.html#Как-использовать-эту-книгу","ch00-00-introduction.html#Исходные-коды","ch01-00-getting-started.html#Начало-работы","ch01-01-installation.html#Установка","ch01-01-installation.html#Условные-обозначения-приказной-строки","ch01-01-installation.html#Установка-rustup-на-linux-или-macos","ch01-01-installation.html#Установка-rustup-на-windows","ch01-01-installation.html#Устранение-возможных-ошибок","ch01-01-installation.html#Обновление-и-удаление","ch01-01-installation.html#Местная-документация","ch01-02-hello-world.html#Привет-мир","ch01-02-hello-world.html#Создание-папки-дела","ch01-02-hello-world.html#Написание-и-запуск-первой-Ржавчина-программы","ch01-02-hello-world.html#Анатомия-программы-на-rust","ch01-02-hello-world.html#Сборка-и-запуск---это-отдельные-шаги","ch01-03-hello-cargo.html#Привет-cargo","ch01-03-hello-cargo.html#Создание-дела-с-помощью-cargo","ch01-03-hello-cargo.html#Сборка-и-запуск-cargo-дела","ch01-03-hello-cargo.html#Сборка-конечной-исполнения-release","ch01-03-hello-cargo.html#cargo-как-Условие","ch01-03-hello-cargo.html#Итоги","ch02-00-guessing-game-tutorial.html#Программируем-игру-в-загадки","ch02-00-guessing-game-tutorial.html#Настройка-нового-дела","ch02-00-guessing-game-tutorial.html#Обработка-догадки","ch02-00-guessing-game-tutorial.html#Хранение-значений-с-помощью-переменных","ch02-00-guessing-game-tutorial.html#Получение-пользовательского-ввода","ch02-00-guessing-game-tutorial.html#Обработка-возможного-сбоя-с-помощью-вида-result","ch02-00-guessing-game-tutorial.html#Вывод-значений-с-помощью-заполнителей-println","ch02-00-guessing-game-tutorial.html#Проверка-первой-части","ch02-00-guessing-game-tutorial.html#Создание-тайного-числа","ch02-00-guessing-game-tutorial.html#Использование-ящика-для-получения-дополнительного-возможностей","ch02-00-guessing-game-tutorial.html#Создание-случайного-числа","ch02-00-guessing-game-tutorial.html#Сравнение-догадки-с-тайным-числом","ch02-00-guessing-game-tutorial.html#Возможность-нескольких-догадок-с-помощью-циклов","ch02-00-guessing-game-tutorial.html#Выход-после-правильной-догадки","ch02-00-guessing-game-tutorial.html#Обработка-недопустимого-ввода","ch02-00-guessing-game-tutorial.html#Заключение","ch03-00-common-programming-concepts.html#Общие-подходы-программирования","ch03-01-variables-and-mutability.html#Переменные-и-изменяемость","ch03-01-variables-and-mutability.html#Постоянного-значения","ch03-01-variables-and-mutability.html#Затенение-переменных","ch03-02-data-types.html#Виды-Данных","ch03-02-data-types.html#Одиночные-виды-данных","ch03-02-data-types.html#Составные-виды-данных","ch03-03-how-functions-work.html#Функции","ch03-03-how-functions-work.html#Свойства-функции","ch03-03-how-functions-work.html#Указания-и-выражения","ch03-03-how-functions-work.html#Функции-с-возвращаемыми-значениями","ch03-04-comments.html#Примечания","ch03-05-control-flow.html#Управляющие-устройства","ch03-05-control-flow.html#Выражения-if","ch03-05-control-flow.html#Повторное-выполнение-кода-с-помощью-циклов","ch03-05-control-flow.html#Итоги","ch04-00-understanding-ownership.html#Понимание-Владения","ch04-01-what-is-ownership.html#Что-такое-владение","ch04-01-what-is-ownership.html#Обойма-и-куча","ch04-01-what-is-ownership.html#Правила-владения","ch04-01-what-is-ownership.html#Область-видимости-переменной","ch04-01-what-is-ownership.html#Вид-данных-string","ch04-01-what-is-ownership.html#Память-и-способы-её-выделения","ch04-01-what-is-ownership.html#Владение-и-функции","ch04-01-what-is-ownership.html#Возвращение-значений-и-область-видимости","ch04-02-references-and-borrowing.html#Ссылки-и-заимствование","ch04-02-references-and-borrowing.html#Изменяемые-ссылки","ch04-02-references-and-borrowing.html#Висячие-ссылки","ch04-02-references-and-borrowing.html#Правила-работы-с-ссылками","ch04-03-slices.html#Вид-срезы","ch04-03-slices.html#Строковые-срезы","ch04-03-slices.html#Другие-срезы","ch04-03-slices.html#Итоги","ch05-00-structs.html#Использование-устройств-для-внутреннего-выстраивания","ch05-01-defining-structs.html#Определение-и-объявление-устройств","ch05-01-defining-structs.html#Использование-сокращённой-объявления-поля","ch05-01-defining-structs.html#Создание-образца-устройства-из-образца-другой-устройства-с-помощью-правил-написания-обновления-устройства","ch05-01-defining-structs.html#Упорядоченные-в-ряд-устройства-устройства-без-именованных-полей-для-создания-разных-видов","ch05-01-defining-structs.html#Единично-подобные-устройства-устройства-без-полей","ch05-01-defining-structs.html#Владение-данными-устройства","ch05-02-example-structs.html#Пример-использования-устройств","ch05-02-example-structs.html#Переработка-кода-при-помощи-упорядоченных-рядов","ch05-02-example-structs.html#Переработка-кода-при-помощи-устройств-добавим-больше-смысла","ch05-02-example-structs.html#Добавление-полезной-возможности-при-помощи-выводимых-особенностей","ch05-03-method-syntax.html#правила-написания-способа","ch05-03-method-syntax.html#Определение-способов","ch05-03-method-syntax.html#Где-используется-оператор--","ch05-03-method-syntax.html#Способы-с-несколькими-свойствами","ch05-03-method-syntax.html#Сопряженные-функции","ch05-03-method-syntax.html#Несколько-разделов-impl","ch05-03-method-syntax.html#Итоги","ch06-00-enums.html#Перечисления-и-сопоставление-с-образцом","ch06-01-defining-an-enum.html#Определение-перечисления","ch06-01-defining-an-enum.html#Значения-перечислений","ch06-01-defining-an-enum.html#Перечисление-option-и-его-преимущества-перед-null-значениями","ch06-02-match.html#Управляющая-устройство-match","ch06-02-match.html#Образцы-привязывающие-значения","ch06-02-match.html#Сопоставление-образца-для-option","ch06-02-match.html#match-охватывает-все-исходы-значения","ch06-02-match.html#Гибкие-образцы-и-заполнитель-_","ch06-03-if-let.html#Краткое-управление-потоком-выполнения-с-if-let","ch06-03-if-let.html#Итоги","ch07-00-managing-growing-projects-with-packages-crates-and-modules.html#Управление-растущими-делами-с-помощью-дополнений-ящиков-и-звеньев","ch07-01-packages-and-crates.html#Дополнения-и-ящики","ch07-02-defining-modules-to-control-scope-and-privacy.html#Определение-звеньев-для-управления-видимости-и-закрытости","ch07-02-defining-modules-to-control-scope-and-privacy.html#Шпаргалка-по-звенам","ch07-02-defining-modules-to-control-scope-and-privacy.html#Объединение-связанного-кода-в-звенах","ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#Пути-для-ссылки-на-элемент-в-дереве-звеньев","ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#Раскрываем-закрытые-пути-с-помощью-ключевого-слова-pub","ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#Начинаем-относительный-путь-с-помощью-super","ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#Делаем-общедоступными-устройства-и-перечисления","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#Подключение-путей-в-область-видимости-с-помощью-ключевого-слова-use","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#Создание-идиоматических-путей-с-use","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#Предоставление-новых-имён-с-помощью-ключевого-слова-as","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#Реэкспорт-имён-с-pub-use","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#Использование-внешних-дополнений","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#Использование-вложенных-путей-для-уменьшения-длинных-списков-use","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#Оператор--glob","ch07-05-separating-modules-into-different-files.html#Разделение-звеньев-на-разные-файлы","ch07-05-separating-modules-into-different-files.html#Иные-пути-к-файлам","ch07-05-separating-modules-into-different-files.html#Итог","ch08-00-common-collections.html#Общие-собрания","ch08-01-vectors.html#Хранение-списков-значений-в-векторах","ch08-01-vectors.html#Создание-нового-вектора","ch08-01-vectors.html#Изменение-вектора","ch08-01-vectors.html#Чтение-данных-вектора","ch08-01-vectors.html#Перебор-значений-в-векторе","ch08-01-vectors.html#Использование-перечислений-для-хранения-множества-разных-видов","ch08-01-vectors.html#Удаление-элементов-из-вектора","ch08-02-strings.html#Хранение-закодированного-текста-utf-8-в-строках","ch08-02-strings.html#Что-же-такое-строка","ch08-02-strings.html#Создание-новых-строк","ch08-02-strings.html#Обновление-строковых-данных","ch08-02-strings.html#Упорядочевание-в-строках","ch08-02-strings.html#Срезы-строк","ch08-02-strings.html#Способы-для-перебора-строк","ch08-02-strings.html#Строки-не-так-просты","ch08-03-hash-maps.html#Хранение-ключей-со-связанными-значениями-в-hashmap","ch08-03-hash-maps.html#Создание-новой-хеш-карты","ch08-03-hash-maps.html#Доступ-к-данным-в-hashmap","ch08-03-hash-maps.html#Хеш-карты-и-владение","ch08-03-hash-maps.html#Обновление-данных-в-hashmap","ch08-03-hash-maps.html#Функция-хеширования","ch08-03-hash-maps.html#Итоги","ch09-00-error-handling.html#Обработка-ошибок","ch09-01-unrecoverable-errors-with-panic.html#Неустранимые-ошибки-с-макросом-panic","ch09-01-unrecoverable-errors-with-panic.html#Раскручивать-обойма-или-прерывать-выполнение-программы-в-ответ-на-панику","ch09-01-unrecoverable-errors-with-panic.html#Использование-обратной-трассировки-panic","ch09-02-recoverable-errors-with-result.html#Исправимые-ошибки-с-result","ch09-02-recoverable-errors-with-result.html#Обработка-различных-ошибок-с-помощью-match","ch09-02-recoverable-errors-with-result.html#Иные-использованию-match-с-result","ch09-02-recoverable-errors-with-result.html#Краткие-способы-обработки-ошибок---unwrap-и-expect","ch09-02-recoverable-errors-with-result.html#Проброс-ошибок","ch09-03-to-panic-or-not-to-panic.html#panic-или-не-panic","ch09-03-to-panic-or-not-to-panic.html#Примеры-прототипирование-и-проверки","ch09-03-to-panic-or-not-to-panic.html#Случаи-в-которых-у-вас-больше-сведений-чем-у-сборщика","ch09-03-to-panic-or-not-to-panic.html#Руководство-по-обработке-ошибок","ch09-03-to-panic-or-not-to-panic.html#Создание-пользовательских-видов-для-проверки","ch09-03-to-panic-or-not-to-panic.html#Итоги","ch10-00-generics.html#Обобщённые-виды-особенности-и-время-жизни","ch10-00-generics.html#Удаление-повторения-кода-с-помощью-выделения-общей-возможности","ch10-01-syntax.html#Обобщённые-виды-данных","ch10-01-syntax.html#В-объявлении-функций","ch10-01-syntax.html#В-определении-устройств","ch10-01-syntax.html#В-определениях-перечислений","ch10-01-syntax.html#В-определении-способов","ch10-01-syntax.html#Производительность-кода-использующего-обобщённые-виды","ch10-02-traits.html#Особенности-определение-общего-поведения","ch10-02-traits.html#Определение-особенности","ch10-02-traits.html#Выполнение-особенности-у-вида","ch10-02-traits.html#Выполнение-поведения-по-умолчанию","ch10-02-traits.html#Особенности-как-свойства","ch10-02-traits.html#Возврат-значений-вида-выполняющего-определённый-особенность","ch10-02-traits.html#Использование-ограничений-особенности-для-условной-выполнения-способов","ch10-03-lifetime-syntax.html#Валидация-ссылок-при-помощи-времён-жизни","ch10-03-lifetime-syntax.html#Времена-жизни-предотвращают-появление-повисших-ссылок","ch10-03-lifetime-syntax.html#Анализатор-заимствований","ch10-03-lifetime-syntax.html#Обобщённые-времена-жизни-в-функциях","ch10-03-lifetime-syntax.html#правила-написания-изложении-времени-жизни","ch10-03-lifetime-syntax.html#Изложения-времени-жизни-в-ярлыках-функций","ch10-03-lifetime-syntax.html#Мышление-в-понятиях-времён-жизни","ch10-03-lifetime-syntax.html#Определение-времён-жизни-при-объявлении-устройств","ch10-03-lifetime-syntax.html#Правила-неявного-выведения-времени-жизни","ch10-03-lifetime-syntax.html#Изложение-времён-жизни-в-определении-способов","ch10-03-lifetime-syntax.html#Постоянное-время-жизни","ch10-03-lifetime-syntax.html#Обобщённые-виды-свойств-ограничения-особенностей-и-времена-жизни-вместе","ch10-03-lifetime-syntax.html#Итоги","ch11-00-testing.html#Написание-автоматизированных-проверок","ch11-01-writing-tests.html#Как-писать-проверки","ch11-01-writing-tests.html#Устройства-проверяющей-функции","ch11-01-writing-tests.html#Проверка-итогов-с-помощью-макроса-assert","ch11-01-writing-tests.html#Проверка-на-равенство-с-помощью-макросов-assert_eq-и-assert_ne","ch11-01-writing-tests.html#Создание-сообщений-об-ошибках","ch11-01-writing-tests.html#Проверка-с-помощью-макроса-should_panic","ch11-01-writing-tests.html#Использование-result-в-проверках","ch11-02-running-tests.html#Управление-хода-выполнения-проверок","ch11-02-running-tests.html#Выполнение-проверок-одновременно-или-последовательно","ch11-02-running-tests.html#Отображение-итогов-работы-функции","ch11-02-running-tests.html#Запуск-подмножества-проверок-по-имени","ch11-02-running-tests.html#Пренебрежение-проверок","ch11-03-test-organization.html#Создание-проверок","ch11-03-test-organization.html#Состоящие-из-звеньев-проверки","ch11-03-test-organization.html#Встраиваемые-проверки","ch11-03-test-organization.html#Итоги","ch12-00-an-io-project.html#Дело-с-вводомвыводом-io-создание-с-окном-вывода-приложения","ch12-01-accepting-command-line-arguments.html#Принятие-переменных-приказной-строки","ch12-01-accepting-command-line-arguments.html#Чтение-значений-переменных","ch12-01-accepting-command-line-arguments.html#Функция-args-и-недействительный-Юникод-символ-unicode","ch12-01-accepting-command-line-arguments.html#Сохранения-значений-переменных-в-переменные","ch12-02-reading-a-file.html#Чтение-файла","ch12-03-improving-error-handling-and-modularity.html#Переработка-кода-для-улучшения-выделения-на-звенья-и-обработки-ошибок","ch12-03-improving-error-handling-and-modularity.html#Разделение-ответственности-для-двоичных-дел","ch12-03-improving-error-handling-and-modularity.html#Исправление-ошибок-обработки","ch12-03-improving-error-handling-and-modularity.html#Извлечение-логики-из-main","ch12-03-improving-error-handling-and-modularity.html#Разделение-кода-на-библиотечный-ящик","ch12-04-testing-the-librarys-functionality.html#Развитие-возможности-библиотеки-разработкой-на-основе-проверок","ch12-04-testing-the-librarys-functionality.html#Написание-проверки-с-ошибкой","ch12-04-testing-the-librarys-functionality.html#Написание-кода-для-прохождения-проверки","ch12-05-working-with-environment-variables.html#Работа-с-переменными-окружения","ch12-05-working-with-environment-variables.html#Написание-ошибочного-проверки-для-функции-search-с-учётом-регистра","ch12-05-working-with-environment-variables.html#Выполнение-функции-search_case_insensitive","ch12-06-writing-to-stderr-instead-of-stdout.html#Запись-сообщений-ошибок-в-поток-ошибок-вместо-принятого-потока-вывода","ch12-06-writing-to-stderr-instead-of-stdout.html#Проверка-куда-записываются-ошибки","ch12-06-writing-to-stderr-instead-of-stdout.html#Печать-ошибок-в-поток-ошибок","ch12-06-writing-to-stderr-instead-of-stdout.html#Итоги","ch13-00-functional-features.html#Полезные--возможности-языка-повторители-и-замыкания","ch13-01-closures.html#Замыкания-анонимные-функции-которые-запечатлевают-захватывают-своё-окружение","ch13-01-closures.html#Захват-переменных-окружения-с-помощью-замыкания","ch13-01-closures.html#Выведение-и-изложение-видов-замыкания","ch13-01-closures.html#Захват-ссылок-или-передача-владения","ch13-01-closures.html#Перемещение-захваченных-значений-из-замыканий-и-особенности-fn","ch13-02-iterators.html#Обработка-последовательности-элементов-с-помощью-повторителей","ch13-02-iterators.html#Особенность-iterator-и-способ-next","ch13-02-iterators.html#Способы-которые-потребляют-повторитель","ch13-02-iterators.html#Способы-которые-создают-другие-повторители","ch13-02-iterators.html#Использование-замыканий-которые-захватывают-переменные-окружения","ch13-03-improving-our-io-project.html#Улучшение-нашего-дела-с-вводомвыводом","ch13-03-improving-our-io-project.html#Удаляем-clone-используем-повторитель","ch13-03-improving-our-io-project.html#Делаем-код-понятнее-с-помощью-переходников-повторителей","ch13-03-improving-our-io-project.html#Выбор-между-циклами-или-повторителями","ch13-04-performance.html#Сравнение-производительности-циклов-и-повторителей","ch13-04-performance.html#Итоги","ch14-00-more-about-cargo.html#Больше-о-cargo-и-cratesio","ch14-01-release-profiles.html#Настройка-сборок-с-профилями-исполнений","ch14-02-publishing-to-crates-io.html#Обнародование-библиотеки-в-cratesio","ch14-02-publishing-to-crates-io.html#Создание-полезных-примечаниев-к-документации","ch14-02-publishing-to-crates-io.html#Экспорт-удобного-общедоступного-api-с-pub-use","ch14-02-publishing-to-crates-io.html#Настройка-учётной-записи-cratesio","ch14-02-publishing-to-crates-io.html#Добавление-метаданных-в-новую-библиотеку","ch14-02-publishing-to-crates-io.html#Обнародование-на-cratesio","ch14-02-publishing-to-crates-io.html#Обнародование-новой-исполнения-существующей-библиотеки","ch14-02-publishing-to-crates-io.html#Устранение-устаревших-исполнений-с-cratesio-с-помощью-cargo-yank","ch14-03-cargo-workspaces.html#Рабочие-пространства-cargo","ch14-03-cargo-workspaces.html#Создание-рабочего-пространства","ch14-03-cargo-workspaces.html#Добавление-второго-ящика-в-рабочее-пространство","ch14-04-installing-binaries.html#Установка-двоичных-файлов-с-помощью-cargo-install","ch14-05-extending-cargo.html#Расширение-cargo-пользовательскими-приказми","ch14-05-extending-cargo.html#Итоги","ch15-00-smart-pointers.html#Умные-указатели","ch15-01-box.html#Использование-box-для-ссылки-на-данные-в-куче","ch15-01-box.html#Использование-box-для-хранения-данных-в-куче","ch15-01-box.html#Включение-рекурсивных-видов-с-помощью-boxes","ch15-02-deref.html#Обращение-с-умными-указателями-как-с-обычными-ссылками-с-помощью-deref-особенности","ch15-02-deref.html#Следуя-за-указателем-на-значение","ch15-02-deref.html#Использование-box-как-ссылку","ch15-02-deref.html#Определение-собственного-умного-указателя","ch15-02-deref.html#Трактование-вида-как-ссылки-выполняя-особенность-deref","ch15-02-deref.html#Неявные-разыменованные-приведения-с-функциями-и-способами","ch15-02-deref.html#Как-разыменованное-приведение-взаимодействует-с-изменяемостью","ch15-03-drop.html#Запуск-кода-при-очистке-с-помощью-особенности-drop","ch15-03-drop.html#Раннее-удаление-значения-с-помощью-stdmemdrop","ch15-04-rc.html#rc-умный-указатель-с-подсчётом-ссылок","ch15-04-rc.html#Использование-rc-для-совместного-использования-данных","ch15-04-rc.html#Клонирование-rc-увеличивает-количество-ссылок","ch15-05-interior-mutability.html#refcell-и-образец-внутренней-изменяемости","ch15-05-interior-mutability.html#Применение-правил-заимствования-во-время-выполнения-с-помощью-refcell","ch15-05-interior-mutability.html#Внутренняя-изменяемость-изменяемое-заимствование-неизменяемого-значения","ch15-05-interior-mutability.html#Наличие-нескольких-владельцев-изменяемых-данных-путём-объединения-видов-rc-и-refcell","ch15-06-reference-cycles.html#Ссылочные-замыкания-могут-приводить-к-утечке-памяти","ch15-06-reference-cycles.html#Создание-ссылочного-замыкания","ch15-06-reference-cycles.html#Предотвращение-ссылочной-зацикленности-замена-умного-указателя-rc-на-weak","ch15-06-reference-cycles.html#Итоги","ch16-00-concurrency.html#Многопоточность-без-страха","ch16-01-threads.html#Использование-потоков-для-одновременного-выполнения-кода","ch16-01-threads.html#Создание-нового-потока-с-помощью--spawn","ch16-01-threads.html#Ожидание-завершения-работы-всех-потоков-используя-join","ch16-01-threads.html#Использование-move-замыканий-в-потоках","ch16-02-message-passing.html#Передача-данных-с-помощью-сообщений-между-потоками","ch16-02-message-passing.html#потоки-и-передача-владения","ch16-02-message-passing.html#Отправка-нескольких-значений-и-ожидание-получателем","ch16-02-message-passing.html#Создание-нескольких-отправителей-путём-клонирования-передатчика","ch16-03-shared-state.html#Многопоточное-разделяемое-состояние","ch16-03-shared-state.html#Мьютексы-предоставляют-доступ-к-данным-из-одного-потока-за-раз","ch16-03-shared-state.html#Сходства-refcell--rc-и-mutex--arc","ch16-04-extensible-concurrency-sync-and-send.html#Расширенная-многопоточность-с-помощью-особенностей-sync-и-send","ch16-04-extensible-concurrency-sync-and-send.html#Разрешение-передачи-во-владение-между-потоками-с-помощью-send","ch16-04-extensible-concurrency-sync-and-send.html#Разрешение-доступа-из-нескольких-потоков-с-sync","ch16-04-extensible-concurrency-sync-and-send.html#Выполнение-send-и-sync-вручную-небезопасна","ch16-04-extensible-concurrency-sync-and-send.html#Итоги","ch17-00-oop.html#Возможности-предметно-направленного-программирования-в-rust","ch17-01-what-is-oo.html#Свойства-предметно-направленных-языков","ch17-01-what-is-oo.html#Предметы-содержат-данные-и-поведение","ch17-01-what-is-oo.html#Инкапсуляция-скрывающая-подробности-выполнения","ch17-01-what-is-oo.html#Наследование-как-система-видов-и-способ-совместного-использования-кода","ch17-01-what-is-oo.html#Полиморфизм","ch17-02-trait-objects.html#Использование-особенность-предметов-допускающих-значения-разных-видов","ch17-02-trait-objects.html#Определение-особенности-для-общего-поведения","ch17-02-trait-objects.html#Выполнения-особенности","ch17-02-trait-objects.html#Особенность-предметы-выполняют-изменяемую-управление-связывание","ch17-03-oo-design-patterns.html#Выполнение-предметно-направленного-образца-разработки","ch17-03-oo-design-patterns.html#Определение-post-и-создание-нового-образца-в-состоянии-черновика","ch17-03-oo-design-patterns.html#Хранение-текста-содержимого-записи","ch17-03-oo-design-patterns.html#Убедимся-что-содержание-черновика-будет-пустым","ch17-03-oo-design-patterns.html#Запрос-на-проверку-записи-меняет-её-состояние","ch17-03-oo-design-patterns.html#Добавление-approve-для-изменения-поведения-content","ch17-03-oo-design-patterns.html#Соглашенияы-образца-Состояние","ch17-03-oo-design-patterns.html#Итоги","ch18-00-patterns.html#Образцы-и-сопоставление","ch18-01-all-the-places-for-patterns.html#Все-случаи-где-могут-быть-использованы-образцы","ch18-01-all-the-places-for-patterns.html#Ветки-match","ch18-01-all-the-places-for-patterns.html#Условные-выражения-if-let","ch18-01-all-the-places-for-patterns.html#Условные-циклы-while-let","ch18-01-all-the-places-for-patterns.html#Цикл-for","ch18-01-all-the-places-for-patterns.html#Указание-let","ch18-01-all-the-places-for-patterns.html#Свойства-функции","ch18-02-refutability.html#Возможность-опровержения-может-ли-образец-не-совпадать","ch18-03-pattern-syntax.html#правила-написания-образцов","ch18-03-pattern-syntax.html#Сопоставление-с-записью","ch18-03-pattern-syntax.html#Сопоставление-именованных-переменных","ch18-03-pattern-syntax.html#объединение-образцов","ch18-03-pattern-syntax.html#Сопоставление-рядов-с-помощью-","ch18-03-pattern-syntax.html#Разъединение-для-получения-значений","ch18-03-pattern-syntax.html#Пренебрежение-значений-в-образце","ch18-03-pattern-syntax.html#Дополнительные-условия-оператора-сопоставления-match-guards","ch18-03-pattern-syntax.html#Связывание-","ch18-03-pattern-syntax.html#Итоги","ch19-00-advanced-features.html#Расширенные-возможности","ch19-01-unsafe-rust.html#unsafe-rust","ch19-01-unsafe-rust.html#Небезопасные-сверхспособности","ch19-01-unsafe-rust.html#Разыменование-сырых-указателей","ch19-01-unsafe-rust.html#Вызов-небезопасной-функции-или-способа","ch19-01-unsafe-rust.html#Получение-доступа-и-внесение-изменений-в-изменяемую-постоянную-переменную","ch19-01-unsafe-rust.html#Выполнение-небезопасных-особенностей","ch19-01-unsafe-rust.html#Доступ-к-полям-объединений-union","ch19-01-unsafe-rust.html#Когда-использовать-небезопасный-код","ch19-03-advanced-traits.html#Продвинутые-особенности","ch19-03-advanced-traits.html#Указание-видов-заполнителей-в-определениях-особенностей-с-сопряженными-видами","ch19-03-advanced-traits.html#Свойства-обобщённого-вида-по-умолчанию-и-перегрузка-операторов","ch19-03-advanced-traits.html#Полностью-квалифицированный-правила-написания-для-устранения-неоднозначности-вызов-способов-с-одинаковым-именем","ch19-03-advanced-traits.html#Использование-супер-особенностей-для-требования-возможности-одного-особенности-в-рамках-другого-особенности","ch19-03-advanced-traits.html#Образец-newtype-для-выполнение-внешних-особенностей-у-внешних-видов","ch19-04-advanced-types.html#Продвинутые-виды","ch19-04-advanced-types.html#Использование-образца-newtype-для-обеспечения-безопасности-видов-и-создания-абстракций","ch19-04-advanced-types.html#Создание-родственных-вида-с-помощью-псевдонимов-вида","ch19-04-advanced-types.html#Вид-never-который-никогда-не-возвращается","ch19-04-advanced-types.html#Виды-с-изменяемым-размером-и-особенность-sized","ch19-05-advanced-functions-and-closures.html#Продвинутые-функции-и-замыкания","ch19-05-advanced-functions-and-closures.html#Указатели-функций","ch19-05-advanced-functions-and-closures.html#Возврат-замыканий","ch19-06-macros.html#Макросы","ch19-06-macros.html#Разница-между-макросами-и-функциями","ch19-06-macros.html#Декларативные-макросы-с-macro_rules-для-общего-мета-программирования","ch19-06-macros.html#Процедурные-макросы-для-создания-кода-из-свойств","ch19-06-macros.html#Как-написать-пользовательский-derive-макрос","ch19-06-macros.html#Макросы-похожие-на-свойство","ch19-06-macros.html#Макросы-похожие-на-функции","ch19-06-macros.html#Итоги","ch20-00-final-project-a-web-server.html#Конечный-дело-создание-многопоточного-веб-сервера","ch20-01-single-threaded.html#Создание-однопоточного-веб-сервера","ch20-01-single-threaded.html#Прослушивание-tcp-соединения","ch20-01-single-threaded.html#Чтение-запросов","ch20-01-single-threaded.html#Пристальный-взгляд-на-http-запрос","ch20-01-single-threaded.html#Написание-ответа","ch20-01-single-threaded.html#Возвращение-существующего-html","ch20-01-single-threaded.html#Проверка-запроса-и-выборочное-возвращение-ответа","ch20-01-single-threaded.html#Переработка-кода","ch20-02-multithreaded.html#Превращение-однопоточного-сервера-в-многопоточный-сервер","ch20-02-multithreaded.html#Подражание-медленного-запроса-в-текущей-выполнения-сервера","ch20-02-multithreaded.html#Улучшение-пропускной-способности-с-помощью-объединения-потоков","ch20-03-graceful-shutdown-and-cleanup.html#Мягкое-завершение-работы-и-очистка","ch20-03-graceful-shutdown-and-cleanup.html#Выполнение-особенности-drop-для-threadpool","ch20-03-graceful-shutdown-and-cleanup.html#Тревожное-оповещение-потокам-прекратить-прослушивание-получения-задач","ch20-03-graceful-shutdown-and-cleanup.html#Итоги","appendix-00.html#Дополнительная-сведения","appendix-01-keywords.html#Приложение-a-Ключевые-слова","appendix-01-keywords.html#Используемые-в-настоящее-время-ключевые-слова","appendix-01-keywords.html#Ключевые-слова-зарезервированные-для-будущего-использования","appendix-01-keywords.html#Сырые-определители","appendix-02-operators.html#Дополнение-Б-Операторы-и-обозначения","appendix-02-operators.html#Операторы","appendix-02-operators.html#Обозначения-не-операторы","appendix-03-derivable-traits.html#Дополнение-В-Выводимые-особенности","appendix-03-derivable-traits.html#debug-для-отладочного-вывода","appendix-03-derivable-traits.html#partialeq-и-eq-для-сравнения-равенства","appendix-03-derivable-traits.html#partialord-и-ord-для-сравнения-порядка","appendix-03-derivable-traits.html#clone-и-copy-для-повторения-значений","appendix-03-derivable-traits.html#hash-для-превращения-значения-в-значение-конечного-размера","appendix-03-derivable-traits.html#default-для-значений-по-умолчанию","appendix-04-useful-development-tools.html#Дополнение-Г---Средства-разработки","appendix-04-useful-development-tools.html#Самостоятельное-изменение","appendix-04-useful-development-tools.html#Исправление-кода-с-rustfix","appendix-04-useful-development-tools.html#Больше-проверок-с-clippy","appendix-04-useful-development-tools.html#Встраивание-с-ide-с-помощью-rust-analyzer","appendix-05-editions.html#Приложение-e---Издания-языка","appendix-06-translation.html#Приложение-Е-Переводы-книги","appendix-07-nightly-rust.html#Дополнение-Ё---Как-создаётся-Ржавчина-и-nightly-rust","appendix-07-nightly-rust.html#Безотказность-без-стагнации","appendix-07-nightly-rust.html#Ту-ту-потоки-выпуска-и-поездка-на-поезде","appendix-07-nightly-rust.html#Ненадежные-функции","appendix-07-nightly-rust.html#rustup-и-значение-ночной-исполнения-rust","appendix-07-nightly-rust.html#Этап-rfc-и-приказы"],"index":{"documentStore":{"docInfo":{"0":{"body":19,"breadcrumbs":2,"title":1},"1":{"body":15,"breadcrumbs":0,"title":0},"10":{"body":60,"breadcrumbs":0,"title":0},"100":{"body":246,"breadcrumbs":1,"title":0},"101":{"body":172,"breadcrumbs":3,"title":2},"102":{"body":78,"breadcrumbs":2,"title":1},"103":{"body":80,"breadcrumbs":1,"title":0},"104":{"body":116,"breadcrumbs":2,"title":1},"105":{"body":93,"breadcrumbs":2,"title":1},"106":{"body":69,"breadcrumbs":2,"title":1},"107":{"body":108,"breadcrumbs":0,"title":0},"108":{"body":5,"breadcrumbs":0,"title":0},"109":{"body":8,"breadcrumbs":0,"title":0},"11":{"body":1,"breadcrumbs":0,"title":0},"110":{"body":52,"breadcrumbs":0,"title":0},"111":{"body":4,"breadcrumbs":0,"title":0},"112":{"body":72,"breadcrumbs":0,"title":0},"113":{"body":66,"breadcrumbs":0,"title":0},"114":{"body":138,"breadcrumbs":0,"title":0},"115":{"body":170,"breadcrumbs":1,"title":1},"116":{"body":31,"breadcrumbs":1,"title":1},"117":{"body":117,"breadcrumbs":0,"title":0},"118":{"body":139,"breadcrumbs":2,"title":1},"119":{"body":85,"breadcrumbs":2,"title":1},"12":{"body":8,"breadcrumbs":0,"title":0},"120":{"body":33,"breadcrumbs":1,"title":0},"121":{"body":46,"breadcrumbs":3,"title":2},"122":{"body":60,"breadcrumbs":1,"title":0},"123":{"body":133,"breadcrumbs":2,"title":1},"124":{"body":7,"breadcrumbs":2,"title":1},"125":{"body":70,"breadcrumbs":4,"title":0},"126":{"body":17,"breadcrumbs":4,"title":0},"127":{"body":3,"breadcrumbs":4,"title":0},"128":{"body":6,"breadcrumbs":0,"title":0},"129":{"body":2,"breadcrumbs":0,"title":0},"13":{"body":8,"breadcrumbs":0,"title":0},"130":{"body":40,"breadcrumbs":0,"title":0},"131":{"body":19,"breadcrumbs":0,"title":0},"132":{"body":141,"breadcrumbs":0,"title":0},"133":{"body":35,"breadcrumbs":0,"title":0},"134":{"body":25,"breadcrumbs":0,"title":0},"135":{"body":21,"breadcrumbs":0,"title":0},"136":{"body":7,"breadcrumbs":4,"title":2},"137":{"body":20,"breadcrumbs":2,"title":0},"138":{"body":92,"breadcrumbs":2,"title":0},"139":{"body":174,"breadcrumbs":2,"title":0},"14":{"body":1,"breadcrumbs":0,"title":0},"140":{"body":207,"breadcrumbs":2,"title":0},"141":{"body":43,"breadcrumbs":2,"title":0},"142":{"body":16,"breadcrumbs":2,"title":0},"143":{"body":10,"breadcrumbs":2,"title":0},"144":{"body":15,"breadcrumbs":2,"title":1},"145":{"body":28,"breadcrumbs":1,"title":0},"146":{"body":55,"breadcrumbs":2,"title":1},"147":{"body":39,"breadcrumbs":1,"title":0},"148":{"body":107,"breadcrumbs":2,"title":1},"149":{"body":12,"breadcrumbs":1,"title":0},"15":{"body":31,"breadcrumbs":3,"title":3},"150":{"body":23,"breadcrumbs":1,"title":0},"151":{"body":8,"breadcrumbs":0,"title":0},"152":{"body":2,"breadcrumbs":2,"title":1},"153":{"body":50,"breadcrumbs":1,"title":0},"154":{"body":133,"breadcrumbs":2,"title":1},"155":{"body":138,"breadcrumbs":2,"title":1},"156":{"body":65,"breadcrumbs":2,"title":1},"157":{"body":38,"breadcrumbs":4,"title":3},"158":{"body":82,"breadcrumbs":3,"title":2},"159":{"body":420,"breadcrumbs":1,"title":0},"16":{"body":20,"breadcrumbs":2,"title":2},"160":{"body":8,"breadcrumbs":4,"title":2},"161":{"body":6,"breadcrumbs":2,"title":0},"162":{"body":35,"breadcrumbs":2,"title":0},"163":{"body":12,"breadcrumbs":2,"title":0},"164":{"body":128,"breadcrumbs":2,"title":0},"165":{"body":8,"breadcrumbs":2,"title":0},"166":{"body":15,"breadcrumbs":0,"title":0},"167":{"body":150,"breadcrumbs":0,"title":0},"168":{"body":0,"breadcrumbs":0,"title":0},"169":{"body":187,"breadcrumbs":0,"title":0},"17":{"body":23,"breadcrumbs":0,"title":0},"170":{"body":153,"breadcrumbs":0,"title":0},"171":{"body":35,"breadcrumbs":0,"title":0},"172":{"body":197,"breadcrumbs":0,"title":0},"173":{"body":30,"breadcrumbs":0,"title":0},"174":{"body":2,"breadcrumbs":0,"title":0},"175":{"body":29,"breadcrumbs":0,"title":0},"176":{"body":133,"breadcrumbs":0,"title":0},"177":{"body":230,"breadcrumbs":0,"title":0},"178":{"body":175,"breadcrumbs":0,"title":0},"179":{"body":195,"breadcrumbs":0,"title":0},"18":{"body":7,"breadcrumbs":0,"title":0},"180":{"body":73,"breadcrumbs":0,"title":0},"181":{"body":2,"breadcrumbs":0,"title":0},"182":{"body":96,"breadcrumbs":0,"title":0},"183":{"body":45,"breadcrumbs":0,"title":0},"184":{"body":131,"breadcrumbs":0,"title":0},"185":{"body":16,"breadcrumbs":0,"title":0},"186":{"body":200,"breadcrumbs":0,"title":0},"187":{"body":98,"breadcrumbs":0,"title":0},"188":{"body":34,"breadcrumbs":0,"title":0},"189":{"body":119,"breadcrumbs":0,"title":0},"19":{"body":3,"breadcrumbs":0,"title":0},"190":{"body":82,"breadcrumbs":0,"title":0},"191":{"body":12,"breadcrumbs":0,"title":0},"192":{"body":45,"breadcrumbs":0,"title":0},"193":{"body":4,"breadcrumbs":0,"title":0},"194":{"body":16,"breadcrumbs":0,"title":0},"195":{"body":3,"breadcrumbs":0,"title":0},"196":{"body":375,"breadcrumbs":0,"title":0},"197":{"body":425,"breadcrumbs":1,"title":1},"198":{"body":238,"breadcrumbs":2,"title":2},"199":{"body":203,"breadcrumbs":0,"title":0},"2":{"body":7,"breadcrumbs":0,"title":0},"20":{"body":11,"breadcrumbs":0,"title":0},"200":{"body":431,"breadcrumbs":1,"title":1},"201":{"body":60,"breadcrumbs":2,"title":2},"202":{"body":18,"breadcrumbs":0,"title":0},"203":{"body":9,"breadcrumbs":0,"title":0},"204":{"body":215,"breadcrumbs":0,"title":0},"205":{"body":210,"breadcrumbs":0,"title":0},"206":{"body":185,"breadcrumbs":0,"title":0},"207":{"body":0,"breadcrumbs":0,"title":0},"208":{"body":86,"breadcrumbs":0,"title":0},"209":{"body":377,"breadcrumbs":0,"title":0},"21":{"body":27,"breadcrumbs":0,"title":0},"210":{"body":1,"breadcrumbs":0,"title":0},"211":{"body":30,"breadcrumbs":1,"title":1},"212":{"body":26,"breadcrumbs":0,"title":0},"213":{"body":30,"breadcrumbs":0,"title":0},"214":{"body":58,"breadcrumbs":2,"title":2},"215":{"body":51,"breadcrumbs":0,"title":0},"216":{"body":112,"breadcrumbs":0,"title":0},"217":{"body":15,"breadcrumbs":0,"title":0},"218":{"body":242,"breadcrumbs":0,"title":0},"219":{"body":353,"breadcrumbs":0,"title":0},"22":{"body":24,"breadcrumbs":0,"title":0},"220":{"body":346,"breadcrumbs":1,"title":1},"221":{"body":124,"breadcrumbs":0,"title":0},"222":{"body":7,"breadcrumbs":0,"title":0},"223":{"body":322,"breadcrumbs":0,"title":0},"224":{"body":527,"breadcrumbs":0,"title":0},"225":{"body":1,"breadcrumbs":0,"title":0},"226":{"body":132,"breadcrumbs":1,"title":1},"227":{"body":761,"breadcrumbs":1,"title":1},"228":{"body":4,"breadcrumbs":2,"title":0},"229":{"body":12,"breadcrumbs":2,"title":0},"23":{"body":19,"breadcrumbs":1,"title":1},"230":{"body":54,"breadcrumbs":2,"title":0},"231":{"body":3,"breadcrumbs":2,"title":0},"232":{"body":4,"breadcrumbs":0,"title":0},"233":{"body":0,"breadcrumbs":0,"title":0},"234":{"body":142,"breadcrumbs":0,"title":0},"235":{"body":176,"breadcrumbs":0,"title":0},"236":{"body":201,"breadcrumbs":0,"title":0},"237":{"body":309,"breadcrumbs":1,"title":1},"238":{"body":38,"breadcrumbs":0,"title":0},"239":{"body":70,"breadcrumbs":2,"title":2},"24":{"body":59,"breadcrumbs":0,"title":0},"240":{"body":36,"breadcrumbs":0,"title":0},"241":{"body":105,"breadcrumbs":0,"title":0},"242":{"body":81,"breadcrumbs":0,"title":0},"243":{"body":3,"breadcrumbs":0,"title":0},"244":{"body":586,"breadcrumbs":1,"title":1},"245":{"body":264,"breadcrumbs":0,"title":0},"246":{"body":4,"breadcrumbs":0,"title":0},"247":{"body":75,"breadcrumbs":0,"title":0},"248":{"body":8,"breadcrumbs":0,"title":0},"249":{"body":5,"breadcrumbs":4,"title":2},"25":{"body":20,"breadcrumbs":3,"title":1},"250":{"body":79,"breadcrumbs":2,"title":0},"251":{"body":3,"breadcrumbs":4,"title":1},"252":{"body":150,"breadcrumbs":3,"title":0},"253":{"body":252,"breadcrumbs":6,"title":3},"254":{"body":18,"breadcrumbs":4,"title":1},"255":{"body":103,"breadcrumbs":3,"title":0},"256":{"body":35,"breadcrumbs":4,"title":1},"257":{"body":7,"breadcrumbs":3,"title":0},"258":{"body":29,"breadcrumbs":6,"title":3},"259":{"body":3,"breadcrumbs":4,"title":1},"26":{"body":97,"breadcrumbs":3,"title":1},"260":{"body":54,"breadcrumbs":3,"title":0},"261":{"body":415,"breadcrumbs":3,"title":0},"262":{"body":61,"breadcrumbs":7,"title":2},"263":{"body":14,"breadcrumbs":4,"title":1},"264":{"body":3,"breadcrumbs":3,"title":0},"265":{"body":27,"breadcrumbs":0,"title":0},"266":{"body":10,"breadcrumbs":2,"title":1},"267":{"body":30,"breadcrumbs":2,"title":1},"268":{"body":333,"breadcrumbs":2,"title":1},"269":{"body":9,"breadcrumbs":2,"title":1},"27":{"body":120,"breadcrumbs":3,"title":1},"270":{"body":87,"breadcrumbs":1,"title":0},"271":{"body":33,"breadcrumbs":2,"title":1},"272":{"body":100,"breadcrumbs":1,"title":0},"273":{"body":90,"breadcrumbs":2,"title":1},"274":{"body":142,"breadcrumbs":1,"title":0},"275":{"body":23,"breadcrumbs":1,"title":0},"276":{"body":100,"breadcrumbs":2,"title":1},"277":{"body":192,"breadcrumbs":2,"title":1},"278":{"body":6,"breadcrumbs":2,"title":1},"279":{"body":172,"breadcrumbs":2,"title":1},"28":{"body":9,"breadcrumbs":3,"title":1},"280":{"body":113,"breadcrumbs":2,"title":1},"281":{"body":7,"breadcrumbs":2,"title":1},"282":{"body":29,"breadcrumbs":2,"title":1},"283":{"body":746,"breadcrumbs":1,"title":0},"284":{"body":129,"breadcrumbs":3,"title":2},"285":{"body":5,"breadcrumbs":0,"title":0},"286":{"body":247,"breadcrumbs":0,"title":0},"287":{"body":434,"breadcrumbs":2,"title":2},"288":{"body":8,"breadcrumbs":0,"title":0},"289":{"body":5,"breadcrumbs":0,"title":0},"29":{"body":14,"breadcrumbs":3,"title":1},"290":{"body":4,"breadcrumbs":0,"title":0},"291":{"body":74,"breadcrumbs":1,"title":1},"292":{"body":200,"breadcrumbs":1,"title":1},"293":{"body":268,"breadcrumbs":1,"title":1},"294":{"body":97,"breadcrumbs":0,"title":0},"295":{"body":103,"breadcrumbs":0,"title":0},"296":{"body":45,"breadcrumbs":0,"title":0},"297":{"body":54,"breadcrumbs":0,"title":0},"298":{"body":2,"breadcrumbs":0,"title":0},"299":{"body":396,"breadcrumbs":0,"title":0},"3":{"body":1,"breadcrumbs":1,"title":1},"30":{"body":11,"breadcrumbs":2,"title":0},"300":{"body":21,"breadcrumbs":4,"title":4},"301":{"body":3,"breadcrumbs":4,"title":2},"302":{"body":20,"breadcrumbs":3,"title":1},"303":{"body":22,"breadcrumbs":3,"title":1},"304":{"body":9,"breadcrumbs":4,"title":2},"305":{"body":4,"breadcrumbs":2,"title":0},"306":{"body":6,"breadcrumbs":2,"title":1},"307":{"body":2,"breadcrumbs":1,"title":0},"308":{"body":13,"breadcrumbs":1,"title":0},"309":{"body":107,"breadcrumbs":1,"title":0},"31":{"body":5,"breadcrumbs":0,"title":0},"310":{"body":9,"breadcrumbs":1,"title":0},"311":{"body":3,"breadcrumbs":1,"title":0},"312":{"body":29,"breadcrumbs":1,"title":0},"313":{"body":115,"breadcrumbs":1,"title":0},"314":{"body":258,"breadcrumbs":1,"title":0},"315":{"body":5,"breadcrumbs":1,"title":0},"316":{"body":43,"breadcrumbs":1,"title":0},"317":{"body":65,"breadcrumbs":2,"title":1},"318":{"body":62,"breadcrumbs":1,"title":0},"319":{"body":55,"breadcrumbs":1,"title":0},"32":{"body":61,"breadcrumbs":0,"title":0},"320":{"body":133,"breadcrumbs":1,"title":0},"321":{"body":448,"breadcrumbs":3,"title":2},"322":{"body":264,"breadcrumbs":1,"title":0},"323":{"body":3,"breadcrumbs":1,"title":0},"324":{"body":6,"breadcrumbs":0,"title":0},"325":{"body":0,"breadcrumbs":0,"title":0},"326":{"body":33,"breadcrumbs":1,"title":1},"327":{"body":67,"breadcrumbs":0,"title":0},"328":{"body":26,"breadcrumbs":0,"title":0},"329":{"body":51,"breadcrumbs":0,"title":0},"33":{"body":100,"breadcrumbs":0,"title":0},"330":{"body":105,"breadcrumbs":0,"title":0},"331":{"body":49,"breadcrumbs":0,"title":0},"332":{"body":165,"breadcrumbs":0,"title":0},"333":{"body":0,"breadcrumbs":0,"title":0},"334":{"body":18,"breadcrumbs":0,"title":0},"335":{"body":82,"breadcrumbs":0,"title":0},"336":{"body":20,"breadcrumbs":0,"title":0},"337":{"body":50,"breadcrumbs":0,"title":0},"338":{"body":325,"breadcrumbs":0,"title":0},"339":{"body":287,"breadcrumbs":0,"title":0},"34":{"body":53,"breadcrumbs":0,"title":0},"340":{"body":156,"breadcrumbs":2,"title":2},"341":{"body":57,"breadcrumbs":0,"title":0},"342":{"body":2,"breadcrumbs":0,"title":0},"343":{"body":7,"breadcrumbs":0,"title":0},"344":{"body":5,"breadcrumbs":3,"title":2},"345":{"body":15,"breadcrumbs":1,"title":0},"346":{"body":76,"breadcrumbs":1,"title":0},"347":{"body":400,"breadcrumbs":1,"title":0},"348":{"body":50,"breadcrumbs":1,"title":0},"349":{"body":40,"breadcrumbs":1,"title":0},"35":{"body":42,"breadcrumbs":0,"title":0},"350":{"body":5,"breadcrumbs":2,"title":1},"351":{"body":4,"breadcrumbs":1,"title":0},"352":{"body":2,"breadcrumbs":0,"title":0},"353":{"body":92,"breadcrumbs":0,"title":0},"354":{"body":151,"breadcrumbs":0,"title":0},"355":{"body":438,"breadcrumbs":0,"title":0},"356":{"body":283,"breadcrumbs":0,"title":0},"357":{"body":71,"breadcrumbs":1,"title":1},"358":{"body":2,"breadcrumbs":0,"title":0},"359":{"body":27,"breadcrumbs":1,"title":1},"36":{"body":103,"breadcrumbs":1,"title":1},"360":{"body":234,"breadcrumbs":0,"title":0},"361":{"body":149,"breadcrumbs":1,"title":1},"362":{"body":71,"breadcrumbs":1,"title":1},"363":{"body":0,"breadcrumbs":0,"title":0},"364":{"body":107,"breadcrumbs":0,"title":0},"365":{"body":96,"breadcrumbs":0,"title":0},"366":{"body":10,"breadcrumbs":0,"title":0},"367":{"body":7,"breadcrumbs":0,"title":0},"368":{"body":69,"breadcrumbs":1,"title":1},"369":{"body":27,"breadcrumbs":0,"title":0},"37":{"body":37,"breadcrumbs":1,"title":1},"370":{"body":332,"breadcrumbs":1,"title":1},"371":{"body":20,"breadcrumbs":0,"title":0},"372":{"body":20,"breadcrumbs":0,"title":0},"373":{"body":1,"breadcrumbs":0,"title":0},"374":{"body":15,"breadcrumbs":0,"title":0},"375":{"body":18,"breadcrumbs":0,"title":0},"376":{"body":82,"breadcrumbs":1,"title":1},"377":{"body":136,"breadcrumbs":0,"title":0},"378":{"body":34,"breadcrumbs":1,"title":1},"379":{"body":78,"breadcrumbs":0,"title":0},"38":{"body":24,"breadcrumbs":0,"title":0},"380":{"body":99,"breadcrumbs":1,"title":1},"381":{"body":175,"breadcrumbs":0,"title":0},"382":{"body":65,"breadcrumbs":0,"title":0},"383":{"body":0,"breadcrumbs":0,"title":0},"384":{"body":88,"breadcrumbs":0,"title":0},"385":{"body":1601,"breadcrumbs":0,"title":0},"386":{"body":9,"breadcrumbs":0,"title":0},"387":{"body":632,"breadcrumbs":2,"title":2},"388":{"body":622,"breadcrumbs":0,"title":0},"389":{"body":2,"breadcrumbs":0,"title":0},"39":{"body":3,"breadcrumbs":0,"title":0},"390":{"body":1,"breadcrumbs":0,"title":0},"391":{"body":1,"breadcrumbs":0,"title":0},"392":{"body":35,"breadcrumbs":0,"title":0},"393":{"body":12,"breadcrumbs":0,"title":0},"394":{"body":55,"breadcrumbs":0,"title":0},"395":{"body":1,"breadcrumbs":1,"title":0},"396":{"body":159,"breadcrumbs":1,"title":0},"397":{"body":125,"breadcrumbs":1,"title":0},"398":{"body":14,"breadcrumbs":1,"title":0},"399":{"body":4,"breadcrumbs":2,"title":1},"4":{"body":8,"breadcrumbs":0,"title":0},"40":{"body":192,"breadcrumbs":0,"title":0},"400":{"body":17,"breadcrumbs":3,"title":2},"401":{"body":31,"breadcrumbs":3,"title":2},"402":{"body":31,"breadcrumbs":3,"title":2},"403":{"body":12,"breadcrumbs":2,"title":1},"404":{"body":20,"breadcrumbs":2,"title":1},"405":{"body":2,"breadcrumbs":1,"title":0},"406":{"body":18,"breadcrumbs":1,"title":0},"407":{"body":71,"breadcrumbs":2,"title":1},"408":{"body":68,"breadcrumbs":2,"title":1},"409":{"body":16,"breadcrumbs":4,"title":3},"41":{"body":107,"breadcrumbs":0,"title":0},"410":{"body":26,"breadcrumbs":2,"title":1},"411":{"body":16,"breadcrumbs":1,"title":0},"412":{"body":1,"breadcrumbs":5,"title":2},"413":{"body":1,"breadcrumbs":3,"title":0},"414":{"body":53,"breadcrumbs":3,"title":0},"415":{"body":6,"breadcrumbs":3,"title":0},"416":{"body":46,"breadcrumbs":5,"title":2},"417":{"body":12,"breadcrumbs":4,"title":1},"42":{"body":317,"breadcrumbs":0,"title":0},"43":{"body":116,"breadcrumbs":0,"title":0},"44":{"body":55,"breadcrumbs":0,"title":0},"45":{"body":189,"breadcrumbs":0,"title":0},"46":{"body":7,"breadcrumbs":0,"title":0},"47":{"body":4,"breadcrumbs":0,"title":0},"48":{"body":104,"breadcrumbs":0,"title":0},"49":{"body":14,"breadcrumbs":0,"title":0},"5":{"body":2,"breadcrumbs":0,"title":0},"50":{"body":117,"breadcrumbs":0,"title":0},"51":{"body":60,"breadcrumbs":0,"title":0},"52":{"body":197,"breadcrumbs":0,"title":0},"53":{"body":181,"breadcrumbs":0,"title":0},"54":{"body":46,"breadcrumbs":0,"title":0},"55":{"body":85,"breadcrumbs":0,"title":0},"56":{"body":124,"breadcrumbs":0,"title":0},"57":{"body":132,"breadcrumbs":0,"title":0},"58":{"body":22,"breadcrumbs":0,"title":0},"59":{"body":1,"breadcrumbs":0,"title":0},"6":{"body":2,"breadcrumbs":0,"title":0},"60":{"body":276,"breadcrumbs":0,"title":0},"61":{"body":269,"breadcrumbs":0,"title":0},"62":{"body":2,"breadcrumbs":0,"title":0},"63":{"body":1,"breadcrumbs":0,"title":0},"64":{"body":0,"breadcrumbs":0,"title":0},"65":{"body":1,"breadcrumbs":0,"title":0},"66":{"body":0,"breadcrumbs":0,"title":0},"67":{"body":35,"breadcrumbs":0,"title":0},"68":{"body":29,"breadcrumbs":1,"title":1},"69":{"body":265,"breadcrumbs":0,"title":0},"7":{"body":3,"breadcrumbs":2,"title":2},"70":{"body":86,"breadcrumbs":0,"title":0},"71":{"body":116,"breadcrumbs":0,"title":0},"72":{"body":149,"breadcrumbs":0,"title":0},"73":{"body":236,"breadcrumbs":0,"title":0},"74":{"body":167,"breadcrumbs":0,"title":0},"75":{"body":0,"breadcrumbs":0,"title":0},"76":{"body":159,"breadcrumbs":0,"title":0},"77":{"body":343,"breadcrumbs":0,"title":0},"78":{"body":17,"breadcrumbs":0,"title":0},"79":{"body":3,"breadcrumbs":0,"title":0},"8":{"body":2,"breadcrumbs":0,"title":0},"80":{"body":3,"breadcrumbs":0,"title":0},"81":{"body":131,"breadcrumbs":0,"title":0},"82":{"body":52,"breadcrumbs":0,"title":0},"83":{"body":110,"breadcrumbs":0,"title":0},"84":{"body":28,"breadcrumbs":0,"title":0},"85":{"body":15,"breadcrumbs":0,"title":0},"86":{"body":109,"breadcrumbs":0,"title":0},"87":{"body":76,"breadcrumbs":0,"title":0},"88":{"body":27,"breadcrumbs":0,"title":0},"89":{"body":56,"breadcrumbs":0,"title":0},"9":{"body":0,"breadcrumbs":0,"title":0},"90":{"body":234,"breadcrumbs":0,"title":0},"91":{"body":4,"breadcrumbs":0,"title":0},"92":{"body":125,"breadcrumbs":0,"title":0},"93":{"body":55,"breadcrumbs":0,"title":0},"94":{"body":140,"breadcrumbs":0,"title":0},"95":{"body":38,"breadcrumbs":0,"title":0},"96":{"body":68,"breadcrumbs":1,"title":1},"97":{"body":1,"breadcrumbs":0,"title":0},"98":{"body":4,"breadcrumbs":0,"title":0},"99":{"body":35,"breadcrumbs":1,"title":0}},"docs":{"0":{"body":"От Стива Клабника и Кэрол Николс, при поддержке других участников сообщества Rust В этой исполнения учебника предполагается, что вы используете Ржавчина 1.67.1 (выпущен 09.02.2023) или новее. См. раздел «Установка» главы 1 для установки или обновления Rust. HTML-исполнение книги доступна онлайн по адресам https://doc.rust-lang.org/stable/book/ (англ.) и https://doc.rust-lang.ru/book (рус.) и офлайн. При установке Ржавчина с помощью rustup: просто запустите rustup docs --book, чтобы её открыть. Также доступны несколько переводов от сообщества. Этот источник доступен в виде печатной книги в мягкой обложке и в виде электронной книги от No Starch Press . 🚨 Предпочитаете более увлекательный этап обучения? Попробуйте другую исполнение Ржавчина Book, в которой есть: проверочные вопросы, цветовое выделение, наглядные визуализации и многое другое : https://rust-book.cs.brown.edu","breadcrumbs":"Язык программирования Rust » Язык программирования Rust","id":"0","title":"Язык программирования Rust"},"1":{"body":"Не всегда было ясно, но язык программирования Ржавчина в основном посвящён расширению возможностей : независимо от того, какой код вы пишете сейчас, Ржавчина позволяет вам достичь большего, чтобы программировать уверенно в более широком ряде областей, чем вы делали раньше. Возьмём, к примеру, работу «системного уровня», которая касается низкоуровневых подробностей управления памятью, представления данных и многопоточности. Привычно эта область программирования считается загадочной, доступной лишь немногим избранным, посвятившим долгие годы изучению всех её печально известных подводных камней. И даже те, кто опытют это, делают всё с осторожностью, чтобы их код не был уязвим для уязвимостей, сбоев или повреждений. Rust разрушает эти преграды, устраняя старые подводные камни и предоставляя дружелюбный, отполированный набор средств, который поможет вам на этом пути. Программисты, которым необходимо «погрузиться» в низкоуровневое управление, могут сделать это с помощью Rust, не беря на себя привычный риск сбоев или дыр в безопасности и не изучая тонкости изменчивых наборов средств. Более того, язык предназначен для того, чтобы легко вести вас к надёжному коду, который эффективен с точки зрения скорости и использования памяти. Программисты, которые уже работают с низкоуровневым кодом, могут использовать Ржавчина для повышения своих чувства собственной значимости. Например, внедрение одновременности в Ржавчина является действием с относительно низким риском: сборщик поймает для вас привычные ошибки. И вы можете заняться более враждебной переработкой в своём коде с уверенностью, что не будете случайно добавлять в код сбои или уязвимости. Но Ржавчина не ограничивается низкоуровневым системным программированием. Он достаточно выразителен и удобен, чтобы приложения CLI (Command Line Interface – окно выводаные программы), веб-серверы и многие другие виды кода были довольно приятными для написания — позже вы найдёте простые примеры того и другого в книге. Работа с Ржавчина позволяет вырабатывать навыки, которые переносятся из одной предметной области в другую; вы можете изучить Rust, написав веб-приложение, а затем применить те же навыки для Raspberry Pi. Эта книга полностью раскрывает возможности Ржавчина для расширения возможностей его пользователей. Это дружелюбный и доступный источник, призванный помочь вам повысить уровень не только ваших знаний о Rust, но и ваших возможностей и уверенности как программиста в целом. Так что погружайтесь, готовьтесь учиться и добро пожаловать в сообщество Rust! — Nicholas Matsakis и Aaron Turon","breadcrumbs":"Предисловие » Предисловие","id":"1","title":"Предисловие"},"10":{"body":"В целом, книга предполагает, что вы будете читать последовательно от начала до конца. Более поздние главы опираются на подходы, изложенные в предыдущих главах, а предыдущие главы могут не углубляться в подробности именно темы, так как в последующих главах они будут рассматриваться более подробно. В этой книге вы найдёте два вида глав: главы о подходах и главы с делом. В главах о подходах вы узнаете о каком-либо особенности Rust. В главах дела мы будем вместе создавать небольшие программы, применяя то, что вы уже узнали. Главы 2, 12 и 20 - это главы дела; остальные - главы о подходах. Глава 1 объясняет, как установить Rust, как написать программу \"Hello, world!\" и как использовать Cargo, управленец дополнений и средство сборки Rust. Глава 2 - это опытное введение в написание программы на Rust, в которой вам предлагается создать игру для угадывания чисел. Здесь мы рассмотрим подходы на высоком уровне, а в последующих главах будет предоставлена дополнительная сведения. Если вы хотите сразу же приступить к работе, глава 2 - самое подходящее место для этого. В главе 3 рассматриваются возможности Rust, схожие с возможностями других языков программирования, а в главе 4 вы узнаете о системе владения Rust. Если вы особенно дотошный ученик и предпочитаете изучить каждую подробность, прежде чем переходить к следующей, возможно, вы захотите пропустить главу 2 и сразу перейти к главе 3, вернувшись к главе 2, когда захотите поработать над делом, применяя изученные подробности. Глава 5 описывает устройства и способы, а глава 6 охватывает перечисления, выражения match и устройства управления потоком if let. Вы будете использовать устройства и перечисления для создания пользовательских видов в Rust. В главе 7 вы узнаете о системе звеньев Rust, о правилах согласования закрытости вашего кода и его открытом внешней оболочке прикладного программирования (API). В главе 8 обсуждаются некоторые распространённые устройства данных - собрания, которые предоставляет обычная библиотека, такие как векторы, строки и HashMaps. В главе 9 рассматриваются философия и способы обработки ошибок в Rust. В главе 10 рассматриваются образцовые виды данных, особенности и времена жизни, позволяющие написать код, который может использоваться разными видами. Глава 11 посвящена проверке, которое даже с заверениями безопасности в Ржавчина необходимо для обеспечения правильной логики вашей программы. В главе 12 мы создадим собственную выполнение подмножества возможности средства приказной строки grep, предназначенного для поиска текста в файлах. Для этого мы будем использовать многие подходы, которые обсуждались в предыдущих главах. В главе 13 рассматриваются замыкания и повторители: особенности Rust, пришедшие из полезных языков программирования. В главе 14 мы более подробно рассмотрим Cargo и поговорим о лучших способах распространения ваших библиотек среди других разработчиков. В главе 15 обсуждаются умные указатели, которые предоставляет обычная библиотека, и особенности, обеспечивающие их возможность. В главе 16 мы рассмотрим различные подходы одновременного программирования и поговорим о возможности Ржавчина для безбоязненного многопоточно программирования. В главе 17 рассматривается сравнение идиом Ржавчина с принципами предметно-направленного программирования, которые наверняка вам знакомы. Глава 18 - это справочник по образцам и сопоставлению с образцами, которые являются мощными способами выражения мыслей в программах на Rust. Глава 19 содержит множество важных дополнительных тем, включая небезопасный Rust, макросы и многое другое о времени жизни, особенностях, видах, функциях и замыканиях. В главе 20 мы завершим дело, в котором выполняем низкоуровневый многопоточный веб-сервер! Наконец, некоторые приложения содержат полезную сведения о языке в более справочном виде. В приложении A рассматриваются ключевые слова Rust, в приложении B — операторы и символы Rust, в приложении C — производные особенности, предоставляемые встроенной библиотекой, в приложении D — некоторые полезные средства разработки, а в приложении E — издания Rust. В приложении F вы найдёте переводы книги, а в приложении G мы расскажем о том, как создаётся Ржавчина и что такое nightly Rust. Нет неправильного способа читать эту книгу: если вы хотите пропустить главу - сделайте это! Возможно, вам придётся вернуться к предыдущим главам, если возникнет недопонимание. Делайте все, как вам удобно. Важной частью этапа обучения Ржавчина является изучение того, как читать сообщения об ошибках, которые отображает сборщик: они приведут вас к работающему коду. Мы изучим много примеров, которые не собираются и отображают ошибки в сообщениях сборщика в разных случаейх. Знайте, что если вы введёте и запустите случайный пример, он может не собраться! Убедитесь, что вы прочитали окружающий текст, чтобы понять, не предназначен ли пример, который вы пытаетесь запустить, для отображения ошибки. Ferris также поможет вам различить код, который не предназначен для работы: Ferris Пояснения Этот код не собирается! Этот код вызывает панику! Этот код не приводит к желаемому поведению. В большинстве случаев мы приведём вас к правильной исполнения любого кода, который не собирается.","breadcrumbs":"Введение » Как использовать эту книгу","id":"10","title":"Как использовать эту книгу"},"100":{"body":"Образцы каждого исхода перечисления IpAddrKind можно создать следующим образом: # enum IpAddrKind {\n# V4,\n# V6,\n# }\n# # fn main() { let four = IpAddrKind::V4; let six = IpAddrKind::V6;\n# # route(IpAddrKind::V4);\n# route(IpAddrKind::V6);\n# }\n# # fn route(ip_kind: IpAddrKind) {} Обратите внимание, что исходы перечисления находятся в пространстве имён вместе с его определителем, а для их обособления мы используем двойное двоеточие. Это удобно тем, что теперь оба значения IpAddrKind::V4 и IpAddrKind::V6 относятся к одному виду: IpAddrKind. Затем мы можем, например, определить функцию, которая принимает любой из исходов IpAddrKind: # enum IpAddrKind {\n# V4,\n# V6,\n# }\n# # fn main() {\n# let four = IpAddrKind::V4;\n# let six = IpAddrKind::V6;\n# # route(IpAddrKind::V4);\n# route(IpAddrKind::V6);\n# }\n# fn route(ip_kind: IpAddrKind) {} Можно вызвать эту функцию с любым из исходов: # enum IpAddrKind {\n# V4,\n# V6,\n# }\n# # fn main() {\n# let four = IpAddrKind::V4;\n# let six = IpAddrKind::V6;\n# route(IpAddrKind::V4); route(IpAddrKind::V6);\n# }\n# # fn route(ip_kind: IpAddrKind) {} Использование перечислений позволяет получить ещё больше преимуществ. Если подумать о нашем виде для IP-адреса, то выяснится, что на данный мгновение у нас нет возможности хранить собственно сам IP-адрес ; мы будем знать только его вид . Учитывая, что недавно в главе 5 вы узнали о устройствах, у вас может возникнуть соблазн решить эту неполадку с помощью устройств, как показано в приложении 6-1. # fn main() { enum IpAddrKind { V4, V6, } struct IpAddr { kind: IpAddrKind, address: String, } let home = IpAddr { kind: IpAddrKind::V4, address: String::from(\"127.0.0.1\"), }; let loopback = IpAddr { kind: IpAddrKind::V6, address: String::from(\"::1\"), };\n# } Приложение 6-1. Сохранение данных и IpAddrKind IP-адреса с использованием struct Здесь мы определили устройство IpAddr, у которой есть два поля: kind вида IpAddrKind (перечисление, которое мы определили ранее) и address вида String. У нас есть два образца этой устройства. Первый - home, который является IpAddrKind::V4 в качестве значения kind с соответствующим адресом 127.0.0.1. Второй образец - loopback. Он в качестве значения kind имеет другой исход IpAddrKind, V6, и с ним сопряжен адрес ::1. Мы использовали устройство для объединения значений kind и address вместе, таким образом вид вида адреса теперь сопряжен со значением. Однако представление этой же подходы с помощью перечисления более кратко: вместо того, чтобы помещать перечисление в устройство, мы можем поместить данные непосредственно в любой из исходов перечисления. Это новое определение перечисления IpAddr гласит, что оба исхода V4 и V6 будут иметь соответствующие значения String: # fn main() { enum IpAddr { V4(String), V6(String), } let home = IpAddr::V4(String::from(\"127.0.0.1\")); let loopback = IpAddr::V6(String::from(\"::1\"));\n# } Мы прикрепляем данные к каждому исходу перечисления напрямую, поэтому нет необходимости в дополнительной устройстве. Здесь также легче увидеть ещё одну подробность того, как работают перечисления: имя каждого исхода перечисления, который мы определяем, также становится функцией, которая создаёт образец перечисления. То есть IpAddr::V4() - это вызов функции, который принимает String и возвращает образец вида IpAddr. Мы самостоятельно получаем эту функцию-строитель, определяемую в итоге определения перечисления. Ещё одно преимущество использования перечисления вместо устройства заключается в том, что каждый исход перечисления может иметь разное количество сопряженных данных представленных в разных видах. Исполнение 4 для IP адресов всегда будет содержать четыре цифровых составляющих, которые будут иметь значения между 0 и 255. При необходимости сохранить адреса вида V4 как четыре значения вида u8, а также описать адреса вида V6 как единственное значение вида String, мы не смогли бы с помощью устройства. Перечисления решают эту задачу легко: # fn main() { enum IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from(\"::1\"));\n# } Мы показали несколько различных способов определения устройств данных для хранения IP-адресов четвёртой и шестой исполнений. Однако, как оказалось, желание хранить IP-адреса и указывать их вид настолько распространено, что в встроенной библиотеке есть определение, которое мы можем использовать! Давайте посмотрим, как обычная библиотека определяет IpAddr: в ней есть точно такое же перечисление с исходами, которое мы определили и использовали, но она помещает данные об адресе внутрь этих исходов в виде двух различных устройств, которые имеют различные определения для каждого из исходов: struct Ipv4Addr { // --snip--\n} struct Ipv6Addr { // --snip--\n} enum IpAddr { V4(Ipv4Addr), V6(Ipv6Addr),\n} Этот код отображает что мы можем добавлять любой вид данных в значение перечисления: строку, число, устройство и пр. Вы даже можете включить в перечисление другие перечисления! Обычные виды данных не очень сложны, хотя, возможно, могут быть очень сложными (вложенность данных может быть очень глубокой). Обратите внимание, что хотя определение перечисления IpAddr есть в встроенной библиотеке, мы смогли объявлять и использовать свою собственную выполнение с подобным названием без каких-либо несоответствий, потому что мы не добавили определение встроенной библиотеки в область видимости кода. Подробнее об этом поговорим в Главе 7. Рассмотрим другой пример перечисления в приложении 6-2: в этом примере каждый элемент перечисления имеет свой особый вид данных внутри: enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32),\n}\n# # fn main() {} Приложение 6-2. Перечисление Message, в каждом из исходов которого хранятся разные количества и виды значений. Это перечисление имеет 4 элемента: Quit - пустой элемент без сопряженных данных, Move имеет именованные поля, как и устройства. Write - элемент с единственной строкой вида String, ChangeColor - упорядоченный ряд из трёх значений вида i32. Определение перечисления с исходами, такими как в приложении 6-2, похоже на определение значений различных видов внутри устройств, за исключением того, что перечисление не использует ключевое слово struct и все исходы объединены внутри вида Message. Следующие устройства могут содержать те же данные, что и предыдущие исходы перечислений: struct QuitMessage; // unit struct\nstruct MoveMessage { x: i32, y: i32,\n}\nstruct WriteMessage(String); // tuple struct\nstruct ChangeColorMessage(i32, i32, i32); // tuple struct\n# # fn main() {} Но когда мы использовали различные устройства, каждая из которых имеет свои собственные виды, мы не могли легко определять функции, которые принимают любые виды сообщений, как это можно сделать с помощью перечисления вида Message, объявленного в приложении 6-2, который является единым видом. Есть ещё одно сходство между перечислениями и устройствами: так же, как мы можем определять способы для устройств с помощью impl раздела, мы можем определять и способы для перечисления. Вот пример способа с именем call, который мы могли бы определить в нашем перечислении Message: # fn main() {\n# enum Message {\n# Quit,\n# Move { x: i32, y: i32 },\n# Write(String),\n# ChangeColor(i32, i32, i32),\n# }\n# impl Message { fn call(&self) { // method body would be defined here } } let m = Message::Write(String::from(\"hello\")); m.call();\n# } В теле способа будет использоваться self для получения значение того предмета. у которого мы вызвали этот способ. В этом примере мы создали переменную m, содержащую значение Message::Write(String::from(\"hello\")), и именно это значение будет представлять self в теле способа call при выполнении m.call(). Теперь посмотрим на другое наиболее часто используемое перечисление из встроенной библиотеки, которое является очень распространённым и полезным: Option.","breadcrumbs":"Перечисления и сопоставление с образцом » Определение Enum » Значения перечислений","id":"100","title":"Значения перечислений"},"101":{"body":"В этом разделе рассматривается пример использования Option, ещё одного перечисления, определённого в встроенной библиотеке. Вид Option кодирует очень распространённый сценарий, в котором значение может быть чем-то, а может быть ничем. Например, если вы запросите первый элемент из непустого списка, вы получите значение. Если вы запросите первый элемент пустого списка, вы ничего не получите. Выражение этой подходы в понятиях системы видов означает, что сборщик может проверить, обработали ли вы все случаи, которые должны были обработать; эта возможность может предотвратить ошибки, которые чрезвычайно распространены в других языках программирования. Внешний вид языка программирования часто рассматривается с точки зрения того, какие функции вы включаете в него, но те функции, которые вы исключаете, также важны. Например в Ржавчина нет такого возможностей как null значения, однако он есть во многих других языках. Null значение - это значение, которое означает, что значения нет. В языках с null значением переменные всегда могут находиться в одном из двух состояний: нет значения (null) или есть значение (not-null) . В своей презентации 2009 года «Null ссылки: ошибка в миллиард долларов» Тони Хоар (Tony Hoare), изобретатель null, сказал следующее: Я называю это своей ошибкой на миллиард долларов. В то время я разрабатывал первую комплексную систему видов для ссылок на предметно-направленном языке. Моя цель состояла в том, чтобы обеспечить, что любое использование ссылок должно быть абсолютно безопасным, с самостоятельной проверкой сборщиком. Но я не мог устоять перед соблазном вставить пустую ссылку просто потому, что это было так легко выполнить. Это привело к бесчисленным ошибкам, уязвимостям и системным сбоям, которые, вероятно, причинили боль и ущерб на миллиард долларов за последние сорок лет. Неполадкас null значениями заключается в том, что если вы попытаетесь использовать null значение в качестве not-null значения, вы получите ошибку определённого рода. Поскольку свойство null или not-null распространено повсеместно, сделать такую ошибку очень просто. Тем не менее, подход, которую null пытается выразить, является полезной: null - это значение, которое в настоящее время по какой-то причине недействительно или отсутствует. Неполадкана самом деле не в подходы, а в именно выполнения. Таким образом, в Ржавчина нет значений null, но есть перечисление, которое может закодировать подход присутствия или отсутствия значения. Это перечисление Option , и оно определено встроенной библиотекой следующим образом: enum Option { None, Some(T),\n} Перечисление Option настолько полезно, что оно даже включено в прелюдию; вам не нужно явно вводить его в область видимости. Его исходы также включены в прелюдию: вы можете использовать Some и None напрямую, без приставки Option::. При всём при этом, Option является обычным перечислением, а Some(T) и None представляют собой его исходы. - это особенность Rust, о которой мы ещё не говорили. Это свойство обобщённого вида, и мы рассмотрим его более подробно в главе 10. На данный мгновение всё, что вам нужно знать, это то, что означает, что исход Some Option может содержать один отрывок данных любого вида, и что каждый определенный вид, который используется вместо T делает общий Option другим видом. Вот несколько примеров использования Option для хранения числовых и строковых видов: # fn main() { let some_number = Some(5); let some_char = Some('e'); let absent_number: Option = None;\n# } Вид some_number - Option. Вид some_char - Option, это другой вид. Ржавчина может вывести эти виды, потому что мы указали значение внутри исхода Some. Для absent_number Ржавчина требует, чтобы мы определяли общий вид для Option: сборщик не может вывести вид, который будет в Some, глядя только на значение None. Здесь мы сообщаем Rust, что absent_number должен иметь вид Option. Когда есть значение Some, мы знаем, что значение присутствует и содержится внутри Some. Когда есть значение None, это означает то же самое, что и null в некотором смысле: у нас нет действительного значения. Так почему наличие Option лучше, чем null? Вкратце, поскольку Option и T (где T может быть любым видом) относятся к разным видам, сборщик не позволит нам использовать значение Option даже если бы оно было определённо допустимым значением. Например, этот код не будет собираться, потому что он пытается добавить i8 к значению вида Option: # fn main() { let x: i8 = 5; let y: Option = Some(5); let sum = x + y;\n# } Если мы запустим этот код, то получим такое сообщение об ошибке: $ cargo run Compiling enums v0.1.0 (file:///projects/enums)\nerror[E0277]: cannot add `Option` to `i8` --> src/main.rs:5:17 |\n5 | let sum = x + y; | ^ no implementation for `i8 + Option` | = help: the trait `Add>` is not implemented for `i8` = help: the following other types implement trait `Add`: <&'a i8 as Add> <&i8 as Add<&i8>> > For more information about this error, try `rustc --explain E0277`.\nerror: could not compile `enums` (bin \"enums\") due to 1 previous error Сильно! В действительности, это сообщение об ошибке означает, что Ржавчина не понимает, как сложить i8 и Option, потому что это разные виды. Когда у нас есть значение вида наподобие i8, сборщик заверяет, что у нас всегда есть допустимое значение вида. Мы можем уверенно продолжать работу, не проверяя его на null перед использованием. Однако, когда у нас есть значение вида Option (где T - это любое значение любого вида T, упакованное в Option, например значение вида i8 или String), мы должны беспокоиться о том, что значение вида T возможно не имеет значения (является исходом None), и сборщик позаботится о том, чтобы мы обработали такой случай, прежде чем мы бы попытались использовать None значение. Другими словами, вы должны преобразовать Option в T прежде чем вы сможете выполнять действия с этим T. Как правило, это помогает выявить одну из наиболее распространённых неполадок с null: предполагая, что что-то не равно null, когда оно на самом деле равно null. Устранение риска ошибочного предположения касательно не-null значения помогает вам быть более уверенным в своём коде. Чтобы иметь значение, которое может быть null, вы должны явно описать вид этого значения с помощью Option. Затем, когда вы используете это значение, вы обязаны явно обрабатывать случай, когда значение равно null. Везде, где значение имеет вид, отличный от Option, вы можете смело рассчитывать на то, что значение не равно null. Это продуманное расчетное решение в Rust, ограничивающее распространение null и увеличивающее безопасность кода на Rust. Итак, как же получить значение T из исхода Some, если у вас на руках есть только предмет Option, и как можно его, вообще, использовать? Перечисление Option имеет большое количество способов, полезных в различных случаейх; вы можете ознакомиться с ними в его документации . Знакомство с способами перечисления Option будет чрезвычайно полезным в вашем путешествии с Rust. В общем случае, чтобы использовать значение Option, нужен код, который будет обрабатывать все исходы перечисления Option. Вам понадобится некоторый код, который будет работать только тогда, когда у вас есть значение Some(T), и этому коду разрешено использовать внутри T. Также вам понадобится другой код, который будет работать, если у вас есть значение None, и у этого кода не будет доступного значения T. Выражение match — это устройство управления потоком выполнения программы, которая делает именно это при работе с перечислениями: она запускает разный код в зависимости от того, какой исход перечисления имеется, и этот код может использовать данные, находящиеся внутри совпавшего исхода.","breadcrumbs":"Перечисления и сопоставление с образцом » Определение Enum » Перечисление Option и его преимущества перед Null-значениями","id":"101","title":"Перечисление Option и его преимущества перед Null-значениями"},"102":{"body":"В Ржавчина есть чрезвычайно мощный рычаг управления потоком, именуемый match, который позволяет сравнивать значение с различными образцами и затем выполнять код в зависимости от того, какой из образцов совпал. Образцы могут состоять из записанных значений, имён переменных, подстановочных знаков и многого другого; в главе 18 рассматриваются все различные виды образцов и то, что они делают. Сила match заключается в выразительности образцов и в том, что сборщик проверяет, что все возможные случаи обработаны. Думайте о выражении match как о машине для сортировки монет: монеты скользят по дорожке с различными по размеру отверстиями, и каждая монета падает через первое попавшееся отверстие, в которое она поместилась. Таким же образом значения проходят через каждый образец в match, и при первом же \"подходящем\" образце значение попадает в соответствующий раздел кода, который будет использоваться во время выполнения. Говоря о монетах, давайте используем их в качестве примера, используя match! Для этого мы напишем функцию, которая будет получать на вход неизвестную монету Соединённых Штатов и, подобно счётной машине, определять, какая это монета, и возвращать её стоимость в центах, как показано в приложении 6-3. enum Coin { Penny, Nickel, Dime, Quarter,\n} fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, }\n}\n# # fn main() {} Приложение 6-3: Перечисление и выражение match, использующее в качестве образцов его исходы Давайте разберём match в функции value_in_cents. Сначала пишется ключевое слово match, затем следует выражение, которое в данном случае является значением coin. Это выглядит очень похоже на условное выражение, используемое в if, но есть большая разница: с if выражение должно возвращать булево значение, а здесь это может быть любой вид. Вид coin в этом примере — перечисление вида Coin, объявленное в строке 1. Далее идут ветки match. Ветки состоят из двух частей: образец и некоторый код. Здесь первая ветка имеет образец, который является значением Coin::Penny, затем идёт оператор =>, который разделяет образец и код для выполнения. Код в этом случае - это просто значение 1. Каждая ветка отделяется от последующей при помощи запятой. Когда выполняется выражение match, оно сравнивает полученное значение с образцом каждого ответвления по порядку. Если образец совпадает со значением, то выполняется код, связанный с этим образцом. Если этот образец не соответствует значению, то выполнение продолжается со следующей ветки, так же, как в автомате по сортировке монет. У нас может быть столько ответвлений, сколько нужно: в приложении 6-3 наш match состоит из четырёх ответвлений. Код, связанный с каждым ответвлением, является выражением, а полученное значение выражения в соответствующем ответвлении — это значение, которое возвращается для всего выражения match. Обычно фигурные скобки не используются, если код совпадающей ветви невелик, как в приложении 6-3, где каждая ветвь просто возвращает значение. Если вы хотите выполнить несколько строк кода в одной ветви, вы должны использовать фигурные скобки, а запятая после этой ветви необязательна. Например, следующий код печатает \"Lucky penny!\" каждый раз, когда способ вызывается с Coin::Penny, но при этом он возвращает последнее значение раздела - 1: # enum Coin {\n# Penny,\n# Nickel,\n# Dime,\n# Quarter,\n# }\n# fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => { println!(\"Lucky penny!\"); 1 } Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, }\n}\n# # fn main() {}","breadcrumbs":"Перечисления и сопоставление с образцом » Устройство потока управления match » Управляющая устройство match","id":"102","title":"Управляющая устройство match"},"103":{"body":"Есть ещё одно полезное качество у веток в выражении match: они могут привязываться к частям тех значений, которые совпали с образцом. Благодаря этому можно извлекать значения из исходов перечисления. В качестве примера, давайте изменим один из исходов перечисления так, чтобы он хранил в себе данные. С 1999 по 2008 год Соединённые Штаты чеканили 25 центов с различным внешнем видом на одной стороне для каждого из 50 штатов. Ни одна другая монета не получила внешнего видаштата, только четверть доллара имела эту дополнительную особенность. Мы можем добавить эту сведения в наш enum путём изменения исхода Quarter и включить в него значение UsState, как сделано в приложении 6-4. #[derive(Debug)] // so we can inspect the state in a minute\nenum UsState { Alabama, Alaska, // --snip--\n} enum Coin { Penny, Nickel, Dime, Quarter(UsState),\n}\n# # fn main() {} Приложение 6-4: Перечисление Coin, в котором исход Quarter также сохраняет значение UsState Представьте, что ваш друг пытается собрать четвертаки всех 50 штатов. Сортируя монеты по виду, мы также будем сообщать название штата, к которому относится каждый четвертак, чтобы, если у нашего друга нет такой монеты, он мог добавить её в свою собрание. В выражении match для этого кода мы добавляем переменную с именем state в образец, который соответствует значениям исхода Coin::Quarter. Когда Coin::Quarter совпадёт с образцом, переменная state будет привязана к значению штата этого четвертака. Затем мы сможем использовать state в коде этой ветки, вот так: # #[derive(Debug)]\n# enum UsState {\n# Alabama,\n# Alaska,\n# // --snip--\n# }\n# # enum Coin {\n# Penny,\n# Nickel,\n# Dime,\n# Quarter(UsState),\n# }\n# fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter(state) => { println!(\"State quarter from {state:?}!\"); 25 } }\n}\n# # fn main() {\n# value_in_cents(Coin::Quarter(UsState::Alaska));\n# } Если мы сделаем вызов функции value_in_cents(Coin::Quarter(UsState::Alaska)), то coin будет иметь значение Coin::Quarter(UsState::Alaska). Когда мы будем сравнивать это значение с каждой из веток, ни одна из них не будет совпадать, пока мы не достигнем исхода Coin::Quarter(state). В этот мгновение state привяжется к значению UsState::Alaska. Затем мы сможем использовать эту привязку в выражении println!, получив таким образом внутреннее значение исхода Quarter перечисления Coin.","breadcrumbs":"Перечисления и сопоставление с образцом » Устройство потока управления match » Образцы, привязывающие значения","id":"103","title":"Образцы, привязывающие значения"},"104":{"body":"В предыдущем разделе мы хотели получить внутреннее значение T для случая Some при использовании Option; мы можем обработать вид Option используя match, как уже делали с перечислением Coin! Вместо сравнения монет мы будем сравнивать исходы Option, независимо от этого изменения рычаг работы выражения match останется прежним. Допустим, мы хотим написать функцию, которая принимает Option и если есть значение внутри, то добавляет 1 к существующему значению. Если значения нет, то функция должна возвращать значение None и не пытаться выполнить какие-либо действия. Такую функцию довольно легко написать благодаря выражению match, код будет выглядеть как в приложении 6-5. # fn main() { fn plus_one(x: Option) -> Option { match x { None => None, Some(i) => Some(i + 1), } } let five = Some(5); let six = plus_one(five); let none = plus_one(None);\n# } Приложение 6-5: Функция, использующая выражение match для Option Давайте более подробно рассмотрим первое выполнение plus_one. Когда мы вызываем plus_one(five), переменная x в теле plus_one будет иметь значение Some(5). Затем мы сравниваем это значение с каждой ветвью сопоставления: # fn main() {\n# fn plus_one(x: Option) -> Option {\n# match x { None => None,\n# Some(i) => Some(i + 1),\n# }\n# }\n# # let five = Some(5);\n# let six = plus_one(five);\n# let none = plus_one(None);\n# } Значение Some(5) не соответствует образцу None, поэтому мы продолжаем со следующим ответвлением: # fn main() {\n# fn plus_one(x: Option) -> Option {\n# match x {\n# None => None, Some(i) => Some(i + 1),\n# }\n# }\n# # let five = Some(5);\n# let six = plus_one(five);\n# let none = plus_one(None);\n# } Совпадает ли Some(5) с образцом Some(i)? Да, это так! У нас такой же исход. Тогда переменная i привязывается к значению, содержащемуся внутри Some, поэтому i получает значение 5. Затем выполняется код сопряженный для данного ответвления, поэтому мы добавляем 1 к значению i и создаём новое значение Some со значением 6 внутри. Теперь давайте рассмотрим второй вызов plus_one в приложении 6-5, где x является None. Мы входим в выражение match и сравниваем значение с первым ответвлением: # fn main() {\n# fn plus_one(x: Option) -> Option {\n# match x { None => None,\n# Some(i) => Some(i + 1),\n# }\n# }\n# # let five = Some(5);\n# let six = plus_one(five);\n# let none = plus_one(None);\n# } Оно совпадает! Для данной ветки образец (None) не подразумевает наличие какого-то значения к которому можно было бы что-то добавить, поэтому программа останавливается и возвращает значение которое находится справа от => - т.е. None. Так как образец первой ветки совпал, то никакие другие образцы веток не сравниваются. Соединение match и перечислений полезно во многих случаейх. Вы часто будете видеть подобную сочетание в коде на Rust: сделать сопоставление значений перечисления используя match, привязать переменную к данным внутри значения, выполнить код на основе привязанных данных. Сначала это может показаться немного сложным, но как только вы привыкнете, то захотите чтобы такая возможность была бы во всех языках. Это неизменно любимый пользователями приём.","breadcrumbs":"Перечисления и сопоставление с образцом » Устройство потока управления match » Сопоставление образца для Option","id":"104","title":"Сопоставление образца для Option"},"105":{"body":"Есть ещё один особенность match, который мы должны обсудить: образцы должны покрывать все возможные исходы. Рассмотрим эту исполнение нашей функции plus_one, которая содержит ошибку и не собирается: # fn main() { fn plus_one(x: Option) -> Option { match x { Some(i) => Some(i + 1), } }\n# # let five = Some(5);\n# let six = plus_one(five);\n# let none = plus_one(None);\n# } Мы не обработали исход None, поэтому этот код вызовет изъян в программе. К счастью, Ржавчина знает и умеет ловить такой случай. Если мы попытаемся собрать такой код, мы получим ошибку сборки: $ cargo run Compiling enums v0.1.0 (file:///projects/enums)\nerror[E0004]: non-exhaustive patterns: `None` not covered --> src/main.rs:3:15 |\n3 | match x { | ^ pattern `None` not covered |\nnote: `Option` defined here --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/option.rs:571:1 ::: /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/core/src/option.rs:575:5 | = note: not covered = note: the matched value is of type `Option`\nhelp: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown |\n4 ~ Some(i) => Some(i + 1),\n5 ~ None => todo!(), | For more information about this error, try `rustc --explain E0004`.\nerror: could not compile `enums` (bin \"enums\") due to 1 previous error Rust знает, что мы не описали все возможные случаи, и даже знает, какой именно из образцов мы упуисполнения! Сопоставления в Ржавчина являются исчерпывающими : мы должны покрыть все возможные исходы, чтобы код был правильным. Особенно в случае Option, когда Ржавчина не даёт нам забыть обработать явным образом значение None, тем самым он защищает нас от предположения, что у нас есть значение, в то время как у нас может быть и null, что делает невозможным совершить ошибку на миллиард долларов, о которой говорилось ранее.","breadcrumbs":"Перечисления и сопоставление с образцом » Устройство потока управления match » Match охватывает все исходы значения","id":"105","title":"Match охватывает все исходы значения"},"106":{"body":"Используя перечисления, мы также можем выполнять особые действия для нескольких определённых значений, а для всех остальных значений выполнять одно действие по умолчанию. Представьте, что мы выполняем игру, в которой при выпадении 3 игрок не двигается, а получает новую нового образца шляпу. Если выпадает 7, игрок теряет шляпу. При всех остальных значениях ваш игрок перемещается на столько-то мест на игровом поле. Вот match, выполняющий эту логику, в котором итог броска костей жёстко закодирован, а не является случайным значением, а вся остальная логика представлена функциями без тел, поскольку их выполнение не входит в рамки данного примера: # fn main() { let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), other => move_player(other), } fn add_fancy_hat() {} fn remove_fancy_hat() {} fn move_player(num_spaces: u8) {}\n# } Для первых двух веток образцами являются записанные значения 3 и 7. Для последней ветки, которая охватывает все остальные возможные значения, образцом является переменная, которую мы решили назвать other. Код, выполняемый для ветки other, использует эту переменную, передавая её в функцию move_player. Этот код собирается, даже если мы не перечислили все возможные значения u8, потому что последний образец будет соответствовать всем значениям, не указанным в определенном списке. Этот гибкий образец удовлетворяет требованию, что соответствие должно быть исчерпывающим. Обратите внимание, что мы должны поместить ветку с гибким образцом последней, потому что образцы оцениваются по порядку. Ржавчина предупредит нас, если мы добавим ветки после гибкого образца, потому что эти последующие ветки никогда не будут выполняться! В Ржавчина также есть образец, который можно использовать, когда мы не хотим использовать значение в гибком образце: _, который является особым образцом, который соответствует любому значению и не привязывается к этому значению. Это говорит Rust, что мы не собираемся использовать это значение, поэтому Ржавчина не будет предупреждать нас о неиспользуемой переменной. Давайте изменим правила игры так: если выпадает что-то, кроме 3 или 7, нужно бросить ещё раз. Нам не нужно использовать значение в этом случае, поэтому мы можем изменить наш код, чтобы использовать _ вместо переменной с именем other: # fn main() { let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => reroll(), } fn add_fancy_hat() {} fn remove_fancy_hat() {} fn reroll() {}\n# } Этот пример также удовлетворяет требованию исчерпывающей полноты, поскольку мы явно пренебрегаем все остальные значения в последней ветке; мы ничего не забыли. Если мы изменим правила игры ещё раз, чтобы в ваш ход не происходило ничего другого, если вы бросаете не 3 или 7, мы можем выразить это, используя единичное значение (пустой вид упорядоченного ряда, о котором мы упоминали в разделе \"Упорядоченные ряды\" ) в качестве кода, который идёт вместе с веткой _: # fn main() { let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => (), } fn add_fancy_hat() {} fn remove_fancy_hat() {}\n# } Здесь мы явно говорим Rust, что не собираемся использовать никакое другое значение, которое не соответствует образцам в предыдущих ветках, и не хотим запускать никакой код в этом случае. Подробнее о образцах и совпадениях мы поговорим в Главе 18 . Пока же мы перейдём к правилам написания if let, который может быть полезен в случаейх, когда выражение match слишком многословно.","breadcrumbs":"Перечисления и сопоставление с образцом » Устройство потока управления match » Гибкие образцы и заполнитель _","id":"106","title":"Гибкие образцы и заполнитель _"},"107":{"body":"правила написания if let позволяет ссоединенять if и let в менее многословную устройство, и затем обработать значения соответствующе только одному образцу, одновременно пренебрегая все остальные. Рассмотрим программу в приложении 6-6, которая обрабатывает сопоставление значения Option в переменной config_max, но хочет выполнить код только в том случае, если значение является исходом Some. # fn main() { let config_max = Some(3u8); match config_max { Some(max) => println!(\"The maximum is configured to be {max}\"), _ => (), }\n# } Приложение 6-6. Выражение match, которое выполнит код только при значении равном Some Если значение равно Some, мы распечатываем значение в исходе Some, привязывая значение к переменной max в образце. Мы не хотим ничего делать со значением None. Чтобы удовлетворить выражение match, мы должны добавить _ => () после обработки первой и единственной ветки, и добавление образцового кода раздражает. Вместо этого, мы могли бы написать это более коротким способом, используя if let. Следующий код ведёт себя так же, как выражение match в приложении 6-6: # fn main() { let config_max = Some(3u8); if let Some(max) = config_max { println!(\"The maximum is configured to be {max}\"); }\n# } правила написания if let принимает образец и выражение, разделённые знаком равенства. Он работает так же, как match, когда в него на вход передадут выражение и подходящим образцом для этого выражения окажется первая ветка. В данном случае образцом является Some(max), где max привязывается к значению внутри Some. Затем мы можем использовать max в теле раздела if let так же, как мы использовали max в соответствующей ветке match. Код в разделе if let не запускается, если значение не соответствует образцу. Используя if let мы меньше печатаем, меньше делаем отступов и меньше получаем образцового кода. Тем не менее, мы теряем полную проверку всех исходов, предоставляемую выражением match. Выбор между match и if let зависит от того, что вы делаете в вашем определенном случае и является ли получение краткости при потере полноты проверки подходящим соглашением. Другими словами, вы можете думать о устройства if let как о синтаксическом сахаре для match, который выполнит код если входное значение будет соответствовать единственному образцу, и пренебрегает все остальные значения. Можно добавлять else к if let. Разделкода, который находится внутри else подобен по смыслу блоку кода ветки связанной с образцом _ выражения match (которое эквивалентно сборной устройства if let и else). Вспомним объявление перечисления Coin в приложении 6-4, где исход Quarter также содержит внутри значение штата вида UsState. Если бы мы хотели посчитать все монеты не являющиеся четвертями, а для четвертей печатать название штата, то мы могли бы сделать это с помощью выражения match таким образом: # #[derive(Debug)]\n# enum UsState {\n# Alabama,\n# Alaska,\n# // --snip--\n# }\n# # enum Coin {\n# Penny,\n# Nickel,\n# Dime,\n# Quarter(UsState),\n# }\n# # fn main() {\n# let coin = Coin::Penny; let mut count = 0; match coin { Coin::Quarter(state) => println!(\"State quarter from {state:?}!\"), _ => count += 1, }\n# } Или мы могли бы использовать выражение if let и else так: # #[derive(Debug)]\n# enum UsState {\n# Alabama,\n# Alaska,\n# // --snip--\n# }\n# # enum Coin {\n# Penny,\n# Nickel,\n# Dime,\n# Quarter(UsState),\n# }\n# # fn main() {\n# let coin = Coin::Penny; let mut count = 0; if let Coin::Quarter(state) = coin { println!(\"State quarter from {state:?}!\"); } else { count += 1; }\n# } Если у вас есть случаей в которой ваша программа имеет логику которая слишком многословна для того чтобы её выражать используя match, помните, о том, что также в вашем наборе средств Ржавчина есть if let.","breadcrumbs":"Перечисления и сопоставление с образцом » Краткий поток управления с if let » Краткое управление потоком выполнения с if let","id":"107","title":"Краткое управление потоком выполнения с if let"},"108":{"body":"Мы рассмотрели как использовать перечисления для создания пользовательских видов, которые могут быть одним из наборов перечисляемых значений. Мы показали, как вид Option из встроенной библиотеки помогает использовать систему видов для предотвращения ошибок. А когда значения перечисления имеют данные внутри них, можно использовать match или if let, чтобы извлечь и пользоваться значением, в зависимости от того, сколько случаев нужно обработать. Теперь ваши программы на Ржавчина могут выражать подходы вашей предметной области, используя устройства и перечисления. Создание и использование пользовательских видов в API обеспечивает типобезопасность: сборщик позаботится о том, чтобы функции получали значения только того вида, который они ожидают. Чтобы предоставить вашим пользователям хорошо согласованный API, который прост в использовании и предоставляет только то, что нужно вашим пользователям, надо поговорить о звенах в Rust.","breadcrumbs":"Перечисления и сопоставление с образцом » Краткий поток управления с if let » Итоги","id":"108","title":"Итоги"},"109":{"body":"По мере роста кодовой хранилища ваших программ, создание дела будет иметь большое значение, ведь отслеживание всей программы в голове будет становиться всё более сложным. Объединенияя связанные функции и разделяя код по основным возможностям (фичам, feature), вы делаете более прозрачным понимание о том, где искать код выполняющий определённую функцию и где стоит вносить изменения для того чтобы изменить её поведение. Программы, которые мы писали до сих пор, были в одном файле одного звена. По мере роста дела, мы можем создавать код иначе, разделив его на несколько звеньев и несколько файлов. Дополнение может содержать несколько двоичных ящиков и дополнительно один ящик библиотеки. Дополнение может включать в себя много двоичных ящиков и дополнительно один библиотечный ящик. По мере роста дополнения вы можете извлекать части программы в отдельные ящики, которые затем станут внешними зависимостями для основного кода нашей программы. Эта глава охватывает все эти техники. В свою очередь для очень крупных дел, состоящих из набора взаимосвязанных дополнений развивающихся вместе, Cargo предоставляет рабочие пространства, workspaces , их мы рассмотрим за пределами данной главы, в разделе \"Рабочие пространства Cargo\" Главы 14. Мы также обсудим инкапсуляцию подробностей, которая позволяет использовать код снова на более высоком уровне: единожды выполнив какую-то действие, другой код может вызывать этот код через открытый внешняя оболочка, не зная как работает выполнение. То, как вы пишете код, определяет какие части общедоступны для использования другим кодом и какие части являются закрытыми деталями выполнения для которых вы оставляете право на изменения только за собой. Это ещё один способ ограничить количество подробностей, которые вы должны держать в голове. Связанное понятие - это область видимости: вложенный среда в котором написан код имеющий набор имён, которые определены «в текущей области видимости». При чтении, письме и сборки кода, программистам и сборщикам необходимо знать, относится ли определенное имя в определённом месте к переменной, к функции, к устройстве, к перечислению, к звену, к постоянных значенийе или другому элементу и что означает этот элемент. Можно создавать области видимости и изменять какие имена входят или выходят за их рамки. Нельзя иметь два элемента с тем же именем в одной области; есть доступные средства для разрешения несоответствий имён. Rust имеет ряд функций, которые позволяют управлять согласованием кода, в том числе управлять тем какие подробности открыты, какие подробности являются частными, какие имена есть в каждой области вашей программы. Эти функции иногда вместе именуемые состоящей из звеньев системой включают в себя: Дополнения: Возможности Cargo позволяющий собирать, проверять и делиться ящиками Ящики: Дерево звеньев, которое создаёт библиотечный или исполняемый файл Звенья и use: Позволяют вместе управлять устройство, область видимости и скрытие путей Пути: способ именования элемента, такого как устройства, функция или звено В этой главе мы рассмотрим все эти функции, обсудим как они взаимодействуют и объясним, как использовать их для управления областью видимости. К концу у вас должно появиться солидное понимание состоящей из звеньев системы и умение работать с областями видимости на уровне искуссника!","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Управление растущими делами с помощью дополнений, ящиков и звеньев","id":"109","title":"Управление растущими делами с помощью дополнений, ящиков и звеньев"},"11":{"body":"Файлы с исходным кодом, используемым в этой книге, можно найти на GitHub .","breadcrumbs":"Введение » Исходные коды","id":"11","title":"Исходные коды"},"110":{"body":"Первые части состоящей из звеньев системы, которые мы рассмотрим — это дополнения и ящики. Ящик — это наименьший размер кода, который сборщик Ржавчина рассматривает за раз. Даже если вы запустите rustc вместо cargo и передадите один файл с исходным кодом (как мы уже делали в разделе «Написание и запуск программы на Rust» Главы 1), сборщик считает этот файл ящиком. Ящики могут содержать звенья, и звенья могут быть определены в других файлах, которые собираются вместе с ящиком, как мы увидим в следующих разделах. Ящик может быть одним из двух видов: двоичный ящик или библиотечный ящик. Бинарные ящики — это программы, которые вы можете собрать в исполняемые файлы, которые вы можете запускать, например программу приказной строки или сервер. У каждого двоичного ящика должна быть функция с именем main, которая определяет, что происходит при запуске исполняемого файла. Все ящики, которые мы создали до сих пор, были двоичными ящиками. Библиотечные ящики не имеют функции main и не собираются в исполняемый файл. Вместо этого они определяют возможность, предназначенную для совместного использования другими делами. Например, ящик rand, который мы использовали в Главе 2 обеспечивает возможность, которая порождает случайные числа. В большинстве случаев, когда Rustaceans говорят «ящик», они имеют в виду библиотечный ящик, и они используют «ящик» взаимозаменяемо с общей подходом программирования «библиотека». Корневой звено ящика — это исходный файл, из которого сборщик Ржавчина начинает собирать корневой звено вашего ящика (мы подробно объясним звенья в разделе «Определение звеньев для управления видимости и закрытости» ). Дополнение — это набор из одного или нескольких ящиков, предоставляющий набор возможности. Дополнение содержит файл Cargo.toml , в котором описывается, как собирать эти ящики. На самом деле Cargo — это дополнение, содержащий двоичный ящик для средства приказной строки, который вы использовали для создания своего кода. Дополнение Cargo также содержит библиотечный ящик, от которого зависит двоичный ящик. Другие дела тоже могут зависеть от библиотечного ящика Cargo, чтобы использовать ту же логику, что и средство приказной строки Cargo. Дополнение может содержать сколько угодно двоичных ящиков, но не более одного библиотечного ящика. Дополнение должен содержать хотя бы один ящик, библиотечный или двоичный. Давайте пройдёмся по тому, что происходит, когда мы создаём дополнение. Сначала введём приказ cargo new: $ cargo new my-project Created binary (application) `my-project` package\n$ ls my-project\nCargo.toml\nsrc\n$ ls my-project/src\nmain.rs После того, как мы запустили 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 : каждый файл будет отдельным двоичным ящиком.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Дополнения и ящики » Дополнения и ящики","id":"110","title":"Дополнения и ящики"},"111":{"body":"В этом разделе мы поговорим о звенах и других частях системы звеньев, а именно: путях (paths), которые позволяют именовать элементы; ключевом слове use, которое приносит путь в область видимости; ключевом слове pub, которое делает элементы общедоступными. Мы также обсудим ключевое слово as, внешние дополнения и оператор glob. А пока давайте сосредоточимся на звенах! Во-первых, мы начнём со списка правил, чтобы вам было легче понять при согласования кода в будущем. Затем мы подробно объясним каждое из правил.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Определение звеньев для управления областью действия и тайностью » Определение звеньев для управления видимости и закрытости","id":"111","title":"Определение звеньев для управления видимости и закрытости"},"112":{"body":"Здесь мы даём краткий обзор того, как звенья, пути, ключевое слово 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, содержит следующие файлы и папки: backyard\n├── Cargo.lock\n├── Cargo.toml\n└── src ├── garden │ └── vegetables.rs ├── garden.rs └── main.rs Файл корневого звена ящика в нашем случае src/main.rs , и его содержимое: Файл: src/main.rs use crate::garden::vegetables::Asparagus; pub mod garden; fn main() { let plant = Asparagus {}; println!(\"I'm growing {plant:?}!\");\n} Строка pub mod garden; говорит сборщику о подключении кода, найденном в src/garden.rs : Файл: src/garden.rs pub mod vegetables; А здесь pub mod vegetables; указывает на подключаемый код в src/garden/vegetables.rs . Этот код: #[derive(Debug)]\npub struct Asparagus {} Теперь давайте рассмотрим подробности этих правил и отобразим их в действии!","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Определение звеньев для управления областью действия и тайностью » Шпаргалка по звенам","id":"112","title":"Шпаргалка по звенам"},"113":{"body":"Звенья позволяют упорядочивать код внутри ящика для удобочитаемости и лёгкого повторного использования. Звенья также позволяют нам управлять закрытостью элементов, поскольку код внутри звена по умолчанию является закрытым. Частные элементы — это внутренние подробности выполнения, недоступные для внешнего использования. Мы можем сделать звенья и элементы внутри них общедоступными, что позволит внешнему коду использовать их и зависеть от них. В качестве примера, давайте напишем библиотечный ящик предоставляющий возможность ресторана. Мы определим ярлыки функций, но оставим их тела пустыми, чтобы сосредоточиться на согласования кода, вместо выполнения кода для ресторана. В ресторанной индустрии некоторые части ресторана называются фронтом дома , а другие задней частью дома . Фронт дома это там где находятся клиенты; здесь размещаются места клиентов, официанты принимают заказы и оплаты, а бармены делают напитки. Задняя часть дома это где шеф-повара и повара работают на кухне, работают посудомоечные машины, а управленцы занимаются административной деятельностью. Чтобы внутренне выстроить ящик подобно тому, как работает настоящий ресторан, можно согласовать размещение функций во вложенных звенах. Создадим новую библиотеку (библиотечный ящик) с именем restaurant выполнив приказ cargo new restaurant --lib; затем вставим код из приложения 7-1 в 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() {} }\n} Приложение 7-1: Звено front_of_house , содержащий другие звенья, которые в свою очередь содержат функции Мы определяем звено, начиная с ключевого слова mod, затем определяем название звена (в данном случае front_of_house) и размещаем фигурные скобки вокруг тела звена. Внутри звеньев, можно иметь другие звенья, как в случае с звенами hosting и serving. Звенья также могут содержать определения для других элементов, таких как устройства, перечисления, постоянные значения, особенности или — как в приложении 7-1 — функции. Используя звенья, мы можем собъединять связанные определения вместе и сказать почему они являются связанными. Программистам будет легче найти необходимую возможность в объединенном коде, вместо того чтобы искать её в одном общем списке. Программисты, добавляющие новые функции в этот код, будут знать, где разместить код для поддержания порядка в программе. Как мы упоминали ранее, файлы src/main.rs и src/lib.rs называются корневыми звенами ящика . Причина такого именования в том, что содержимое любого из этих двух файлов образует звено с именем crate в корне устройства звеньев ящика, известной как дерево звеньев . В приложении 7-2 показано дерево звеньев для устройства звеньев, приведённой в коде приложения 7-1. crate └── front_of_house ├── hosting │ ├── add_to_waitlist │ └── seat_at_table └── serving ├── take_order ├── serve_order └── take_payment Приложение 7-2: Древо звеньев для программы из Приложения 7-1 Это дерево показывает, как некоторые из звеньев вкладываются друг в друга; например, hosting находится внутри front_of_house. Дерево также показывает, что некоторые звенья являются братьями (siblings) друг для друга, то есть они определены в одном звене; hosting и serving это братья которые определены внутри front_of_house. Если звено A содержится внутри звена B, мы говорим, что звено A является потомком (child) звена B, а звено B является родителем (parent) звена A. Обратите внимание, что родителем всего дерева звеньев является неявный звено с именем crate. Дерево звеньев может напомнить вам дерево папок файловой системы на компьютере; это очень удачное сравнение! По подобию с папкими в файловой системе, мы используется звенья для согласования кода. И так же, как нам надо искать файлы в папких на компьютере, нам требуется способ поиска нужных звеньев.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Определение звеньев для управления областью действия и тайностью » Объединение связанного кода в звенах","id":"113","title":"Объединение связанного кода в звенах"},"114":{"body":"Чтобы показать 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 mod front_of_house { mod hosting { fn add_to_waitlist() {} }\n} pub fn eat_at_restaurant() { // Absolute path crate::front_of_house::hosting::add_to_waitlist(); // Relative path front_of_house::hosting::add_to_waitlist();\n} Приложение 7-3. Вызов функции 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. $ cargo build Compiling restaurant v0.1.0 (file:///projects/restaurant)\nerror[E0603]: module `hosting` is private --> src/lib.rs:9:28 |\n9 | crate::front_of_house::hosting::add_to_waitlist(); | ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported | | | private module |\nnote: the module `hosting` is defined here --> src/lib.rs:2:5 |\n2 | mod hosting { | ^^^^^^^^^^^ error[E0603]: module `hosting` is private --> src/lib.rs:12:21 |\n12 | front_of_house::hosting::add_to_waitlist(); | ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported | | | private module |\nnote: the module `hosting` is defined here --> src/lib.rs:2:5 |\n2 | mod hosting { | ^^^^^^^^^^^ For more information about this error, try `rustc --explain E0603`.\nerror: could not compile `restaurant` (lib) due to 2 previous errors Приложение 7-4. Ошибки сборки при сборке кода из приложения 7-3 Сообщения об ошибках говорят о том, что звено hosting является закрытым. Другими словами, у нас есть правильные пути к звену hosting и функции add_to_waitlist, но Ржавчина не позволяет нам использовать их, потому что у него нет доступа к закрытым разделам. В Ржавчина все элементы (функции, способы, устройства, перечисления, звенья и постоянные значения) по умолчанию являются закрытыми для родительских звеньев. Если вы хотите сделать элемент, например функцию или устройство, закрытым, вы помещаете его в звено. Элементы в родительском звене не могут использовать закрытые элементы внутри дочерних звеньев, но элементы в дочерних звенах могут использовать элементы у своих звенах-предках. Это связано с тем, что дочерние звенья оборачивают и скрывают подробности своей выполнения, но дочерние звенья могут видеть среда, в котором они определены. Продолжая нашу метафору, подумайте о правилах закрытости как о задней части ресторана: то, что там происходит, скрыто от клиентов ресторана, но офис-управленцы могут видеть и делать всё в ресторане, которым они управляют. В Ржавчина решили, что система звеньев должна исполняться таким образом, чтобы по умолчанию скрывать подробности выполнения. Таким образом, вы знаете, какие части внутреннего кода вы можете изменять не нарушая работы внешнего кода. Тем не менее, Ржавчина даёт нам возможность открывать внутренние части кода дочерних звеньев для внешних звеньев-предков, используя ключевое слово pub, чтобы сделать элемент общедоступным.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Пути для ссылки на элемент в дереве звеньев » Пути для ссылки на элемент в дереве звеньев","id":"114","title":"Пути для ссылки на элемент в дереве звеньев"},"115":{"body":"Давайте вернёмся к ошибке в приложении 7-4, которая говорит, что звено hosting является закрытым. Мы хотим, чтобы функция eat_at_restaurant из родительского звена имела доступ к функции add_to_waitlist в дочернем звене, поэтому мы помечаем звено hosting ключевым словом pub, как показано в приложении 7-5. Файл: src/lib.rs mod front_of_house { pub mod hosting { fn add_to_waitlist() {} }\n} pub fn eat_at_restaurant() { // Absolute path crate::front_of_house::hosting::add_to_waitlist(); // Relative path front_of_house::hosting::add_to_waitlist();\n} Приложение 7-5. Объявление звена hosting как pub для его использования из eat_at_restaurant К сожалению, код в приложении 7-5 всё ещё приводит к ошибке, как показано в приложении 7-6. $ cargo build Compiling restaurant v0.1.0 (file:///projects/restaurant)\nerror[E0603]: function `add_to_waitlist` is private --> src/lib.rs:9:37 |\n9 | crate::front_of_house::hosting::add_to_waitlist(); | ^^^^^^^^^^^^^^^ private function |\nnote: the function `add_to_waitlist` is defined here --> src/lib.rs:3:9 |\n3 | fn add_to_waitlist() {} | ^^^^^^^^^^^^^^^^^^^^ error[E0603]: function `add_to_waitlist` is private --> src/lib.rs:12:30 |\n12 | front_of_house::hosting::add_to_waitlist(); | ^^^^^^^^^^^^^^^ private function |\nnote: the function `add_to_waitlist` is defined here --> src/lib.rs:3:9 |\n3 | fn add_to_waitlist() {} | ^^^^^^^^^^^^^^^^^^^^ For more information about this error, try `rustc --explain E0603`.\nerror: could not compile `restaurant` (lib) due to 2 previous errors Приложение 7-6: Ошибки сборки при сборке кода в приложении 7-5 Что произошло? Добавление ключевого слова pub перед mod hosting сделало звено общедоступным. После этого изменения, если мы можем получить доступ к звену front_of_house, то мы можем получить доступ к звену hosting. Но содержимое звена hosting всё ещё является закрытым: превращение звена в общедоступный звено не делает его содержимое общедоступным. Ключевое слово pub позволяет внешнему коду в звенах-предках обращаться только к звену, без доступа ко внутреннему коду. Поскольку звенья являются дополнениями, мы мало что можем сделать, просто сделав звено общедоступным; нам нужно пойти дальше и сделать один или несколько элементов в звене общедоступными. Ошибки в приложении 7-6 говорят, что функция add_to_waitlist является закрытой. Правила закрытости применяются к устройствам, перечислениям, функциям и способам, также как и к звенам. Давайте также сделаем функцию add_to_waitlist общедоступной, добавив ключевое слово pub перед её определением, как показано в приложении 7-7. Файл: src/lib.rs mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }\n} pub fn eat_at_restaurant() { // Absolute path crate::front_of_house::hosting::add_to_waitlist(); // Relative path front_of_house::hosting::add_to_waitlist();\n} Приложение 7-7. Добавление ключевого слова pub к mod hosting и к fn add_to_waitlist позволяет нам вызывать функцию из eat_at_restaurant Теперь код собирается! Чтобы понять, почему добавление ключевого слова pub позволяет нам использовать эти пути для add_to_waitlist в соответствии с правилами закрытости, давайте рассмотрим абсолютный и относительный пути. В случае абсолютного пути мы начинаем с crate, корня дерева звеньев нашего ящика. Звено front_of_house определён в корневом звене ящика. Хотя front_of_house не является общедоступным, но поскольку функция eat_at_restaurant определена в том же звене, что и front_of_house (то есть, eat_at_restaurant и front_of_house являются потомками одного родителя), мы можем ссылаться на front_of_house из eat_at_restaurant. Далее идёт звено hosting, помеченный как pub. Мы можем получить доступ к родительскому звену звена hosting, поэтому мы можем получить доступ и к hosting. Наконец, функция add_to_waitlist помечена как pub, и так как мы можем получить доступ к её родительскому звену, то вызов этой функции разрешён! В случае относительного пути логика такая же как для абсолютного пути, за исключением первого шага: вместо того, чтобы начинать с корневого звена ящика, путь начинается с front_of_house. Звено front_of_house определён в том же звене, что и eat_at_restaurant, поэтому относительный путь, начинающийся с звена, в котором определена eat_at_restaurant тоже работает. Тогда, по причине того, что hosting и add_to_waitlist помечены как pub, остальная часть пути работает и вызов этой функции разрешён! Если вы собираетесь предоставить общий доступ к своему библиотечному ящику, чтобы другие дела могли использовать ваш код, ваш общедоступный API — это ваш договор с пользователями вашего ящика, определяющий, как они могут взаимодействовать с вашим кодом. Есть много соображений по поводу управления изменениями в вашем общедоступном API, чтобы сделать необременительным для людей зависимость от вашего ящика. Эти соображения выходят за рамки этой книги; если вам важна эта тема, см. The Ржавчина API Guidelines . Лучшие опытов для дополнений с двоичным и библиотечным ящиками Мы упоминали, что дополнение может содержать как корневой звено двоичного ящика src/main.rs , так и корневой звено библиотечного ящика src/lib.rs , и оба ящика будут по умолчанию иметь имя дополнения. Как правило, дополнения с таким образцом, содержащим как библиотечный, так и двоичный ящик, будут иметь достаточно кода в двоичном ящике, чтобы запустить исполняемый файл, который вызывает код из библиотечного ящика. Это позволяет другим делам извлечь выгоду из большей части возможности, предоставляемой дополнением, поскольку код библиотечного ящика можно использовать совместно. Дерево звеньев должно быть определено в src/lib.rs . Затем любые общедоступные элементы можно использовать в двоичном ящике, начав пути с имени дополнения. Двоичный ящик становится пользователем библиотечного ящика точно так же, как полностью внешний ящик использует библиотечный ящик: он может использовать только общедоступный API. Это поможет вам разработать хороший API; вы не только автор, но и пользователь! В Главе 12 мы эту опыт согласования кода с помощью окно выводаной программы, которая будет содержать как двоичный, так и библиотечный ящики.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Пути для ссылки на элемент в дереве звеньев » Раскрываем закрытые пути с помощью ключевого слова pub","id":"115","title":"Раскрываем закрытые пути с помощью ключевого слова pub"},"116":{"body":"Также можно построить относительные пути, которые начинаются в родительском звене, используя ключевое слово super в начале пути. Это похоже на правила написания начала пути файловой системы ... Использование super позволяет нам сослаться на элемент, который, как мы знаем, находится в родительском звене, что может упростить переупорядочение дерева звеньев, чем когда звено тесно связан с родителем, но родитель может когда-нибудь быть перемещён в другое место в дереве звеньев. Рассмотрим код в приложении 7-8, где расчитывается случаей, в которой повар исправляет неправильный заказ и лично приносит его клиенту. Функция fix_incorrect_order вызывает функцию deliver_order, определённую в родительском звене, указывая путь к deliver_order, начинающийся с super: Файл: src/lib.rs fn deliver_order() {} mod back_of_house { fn fix_incorrect_order() { cook_order(); super::deliver_order(); } fn cook_order() {}\n} Приложение 7-8: Вызов функции с использованием относительного пути, начинающегося с super Функция fix_incorrect_order находится в звене back_of_house, поэтому мы можем использовать super для перехода к родительскому звену звена back_of_house, который в этом случае является crate, корневым звеном. В этом звене мы ищем deliver_order и находим его. Успех! Мы думаем, что звено back_of_house и функция deliver_order, скорее всего, останутся в тех же родственных отношениях друг с другом, и должны будут перемещены вместе, если мы решим ресогласовать дерево звеньев ящика. Поэтому мы использовали super, чтобы в будущем у нас было меньше мест для обновления кода, если этот код будет перемещён в другой звено.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Пути для ссылки на элемент в дереве звеньев » Начинаем относительный путь с помощью super","id":"116","title":"Начинаем относительный путь с помощью super"},"117":{"body":"Мы также можем использовать pub для обозначения устройств и перечислений как общедоступных, но есть несколько дополнительных подробностей использования pub со устройствами и перечислениями. Если мы используем pub перед определением устройства, мы делаем устройство общедоступной, но поля устройства по-прежнему остаются закрытыми. Мы можем сделать каждое поле общедоступным или нет в каждом определенном случае. В приложении 7-9 мы определили общедоступную устройство back_of_house::Breakfast с общедоступным полем toast и с закрытым полем seasonal_fruit. Это расчитывает случай в ресторане, когда клиент может выбрать вид хлеба, который подаётся с едой, а шеф-повар решает какие фрукты сопровождают еду, исходя из того, что сезонно и что есть в наличии. Доступные фрукты быстро меняются, поэтому клиенты не могут выбирать фрукты или даже увидеть, какие фрукты они получат. Файл: src/lib.rs mod back_of_house { pub struct Breakfast { pub toast: String, seasonal_fruit: String, } impl Breakfast { pub fn summer(toast: &str) -> Breakfast { Breakfast { toast: String::from(toast), seasonal_fruit: String::from(\"peaches\"), } } }\n} pub fn eat_at_restaurant() { // Order a breakfast in the summer with Rye toast let mut meal = back_of_house::Breakfast::summer(\"Rye\"); // Change our mind about what bread we'd like meal.toast = String::from(\"Wheat\"); println!(\"I'd like {} toast please\", meal.toast); // The next line won't compile if we uncomment it; we're not allowed // to see or modify the seasonal fruit that comes with the meal // meal.seasonal_fruit = String::from(\"blueberries\");\n} Приложение 7-9: Устройства с общедоступными и закрытыми полями Поскольку поле toast в устройстве back_of_house::Breakfast является открытым, то в функции eat_at_restaurant можно писать и читать поле toast, используя точечную наставление. Обратите внимание, что мы не можем использовать поле seasonal_fruit в eat_at_restaurant, потому что seasonal_fruit является закрытым. Попробуйте убрать примечания с последней строки для значения поля seasonal_fruit, чтобы увидеть какую ошибку вы получите! Также обратите внимание, что поскольку back_of_house::Breakfast имеет закрытое поле, то устройства должна предоставить открытую сопряженную функцию, которая создаёт образец Breakfast (мы назвали её summer). Если Breakfast не имел бы такой функции, мы бы не могли создать образец Breakfast внутри eat_at_restaurant, потому что мы не смогли бы установить значение закрытого поля seasonal_fruit в функции eat_at_restaurant. В отличии от устройства, если мы сделаем общедоступным перечисление, то все его исходы будут общедоступными. Нужно только указать pub перед ключевым словом enum, как показано в приложении 7-10. Файл: src/lib.rs mod back_of_house { pub enum Appetizer { Soup, Salad, }\n} pub fn eat_at_restaurant() { let order1 = back_of_house::Appetizer::Soup; let order2 = back_of_house::Appetizer::Salad;\n} Приложение 7-10. Определяя перечисление общедоступным мы делаем все его исходы общедоступными Поскольку мы сделали общедоступным перечисление Appetizer, то можно использовать исходы Soup и Salad в функции eat_at_restaurant. Перечисления не очень полезны, если их исходы не являются общедоступными: было бы досадно каждый раз определять все исходы перечисления как pub. По этой причине по умолчанию исходы перечислений являются общедоступными. Устройства часто полезны, если их поля не являются общедоступными, поэтому поля устройства следуют общему правилу, согласно которому, всё по умолчанию является закрытым, если не указано pub. Есть ещё одна случаей с pub, которую мы не освещали, и это последняя особенность состоящей из звеньев системы: ключевое слово use. Мы сначала опишем use само по себе, а затем покажем как сочетать pub и use вместе.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Пути для ссылки на элемент в дереве звеньев » Делаем общедоступными устройства и перечисления","id":"117","title":"Делаем общедоступными устройства и перечисления"},"118":{"body":"Необходимость записывать пути к функциям вызова может показаться неудобной и повторяющейся. В приложении 7-7 независимо от того, выбирали ли мы абсолютный или относительный путь к функции add_to_waitlist , каждый раз, когда мы хотели вызвать add_to_waitlist , нам приходилось также указывать front_of_house и hosting . К счастью, есть способ упростить этот этап: мы можем один раз создать псевдоним на путь при помощи ключевого слова use, а затем использовать более короткое имя везде в области видимости. В приложении 7-11 мы подключили звено crate::front_of_house::hosting в область действия функции eat_at_restaurant, поэтому нам достаточно только указать hosting::add_to_waitlist для вызова функции add_to_waitlist внутри eat_at_restaurant. Файл: src/lib.rs mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }\n} use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist();\n} Приложение 7-11. Добавление звена в область видимости при помощи use Добавление use и пути в область видимости подобно созданию символической ссылки в файловой системе. С добавлением use crate::front_of_house::hosting в корневой звено ящика, hosting становится допустимым именем в этой области, как если бы звено hosting был определён в корневом звене ящика. Пути, подключённые в область видимости с помощью use, также проверяются на доступность, как и любые другие пути. Обратите внимание, что use создаёт псевдоним только для той именно области, в которой это объявление use и находится. В приложении 7-12 функция eat_at_restaurant перемещается в новый дочерний звено с именем customer, область действия которого отличается от области действия указания use, поэтому тело функции не будет собираться: Файл: src/lib.rs mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }\n} use crate::front_of_house::hosting; mod customer { pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }\n} Приложение 7-12. Указание use применяется только в её собственной области видимости Ошибка сборщика показывает, что данный псевдоним не может использоваться в звене customer: $ cargo build Compiling restaurant v0.1.0 (file:///projects/restaurant)\nerror[E0433]: failed to resolve: use of undeclared crate or module `hosting` --> src/lib.rs:11:9 |\n11 | hosting::add_to_waitlist(); | ^^^^^^^ use of undeclared crate or module `hosting` |\nhelp: consider importing this module through its public re-export |\n10 + use crate::hosting; | warning: unused import: `crate::front_of_house::hosting` --> src/lib.rs:7:5 |\n7 | use crate::front_of_house::hosting; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default For more information about this error, try `rustc --explain E0433`.\nwarning: `restaurant` (lib) generated 1 warning\nerror: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted Обратите внимание, что есть также предупреждение о том, что use не используется в своей области! Чтобы решить эту неполадку, можно переместить use в звено customer, или же можно сослаться на псевдоним в родительском звене с помощью super::hosting в дочернем звене customer.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Введение путей в область видимости с помощью ключевого слова use » Подключение путей в область видимости с помощью ключевого слова use","id":"118","title":"Подключение путей в область видимости с помощью ключевого слова use"},"119":{"body":"В приложении 7-11 вы могли бы задаться вопросом, почему мы указали use crate::front_of_house::hosting, а затем вызвали hosting::add_to_waitlist внутри eat_at_restaurant вместо указания в use полного пути прямо до функции add_to_waitlist для получения того же итога, что в приложении 7-13. Файл: src/lib.rs mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }\n} use crate::front_of_house::hosting::add_to_waitlist; pub fn eat_at_restaurant() { add_to_waitlist();\n} Приложение 7-13: Добавление функции add_to_waitlist в область видимости с use неидиоматическим способом Хотя приложениеи 7-11 и 7-13 выполняют одну и ту же задачу, приложение 7-11 является идиоматическим способом подключения функции в область видимости с помощью use. Подключение родительского звена функции в область видимости при помощи use означает, что мы должны указывать родительский звено при вызове функции. Указание родительского звена при вызове функции даёт понять, что функция не определена местно, но в то же время сводя к уменьшению повторение полного пути. В коде приложения 7-13 не ясно, где именно определена add_to_waitlist. С другой стороны, при подключении устройств, перечислений и других элементов используя use, идиоматически правильным будет указывать полный путь. Приложение 7-14 показывает идиоматический способ подключения устройства встроенной библиотеки HashMap в область видимости двоичного ящика. Файл: src/main.rs use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2);\n} Приложение 7-14. Включение HashMap в область видимости идиоматическим способом За этой идиомой нет веской причины: это просто соглашение, которое появилось само собой. Люди привыкли читать и писать код на Ржавчина таким образом. Исключением из этой идиомы является случай, когда мы подключаем два элемента с одинаковыми именами в область видимости используя указанию use — Ржавчина просто не позволяет этого сделать. Приложение 7-15 показывает, как подключить в область действия два вида с одинаковыми именами Result, но из разных родительских звеньев и как на них ссылаться. Файл: src/lib.rs use std::fmt;\nuse std::io; fn function1() -> fmt::Result { // --snip--\n# Ok(())\n} fn function2() -> io::Result<()> { // --snip--\n# Ok(())\n} Приложение 7-15. Для включения двух видов с одинаковыми именами в одну область видимости необходимо использовать их родительские звенья. Как видите, использование имени родительских звеньев позволяет различать два вида Result. Если бы вместо этого мы указали use std::fmt::Result и use std::io::Result, мы бы имели два вида Result в одной области видимости, и Ржавчина не смог бы понять какой из двух Result мы имели в виду, когда нашёл бы их употребление в коде.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Введение путей в область видимости с помощью ключевого слова use » Создание идиоматических путей с use","id":"119","title":"Создание идиоматических путей с use"},"12":{"body":"Начнём наше путешествие в Rust! Нужно много всего изучить, но каждое путешествие с чего-то начинается. В этой главе мы обсудим: установку Ржавчина на Linux, macOS и Windows, написание программы, печатающей Hello, world!, использование cargo, управленца дополнений и системы сборки в одном лице для Rust.","breadcrumbs":"С чего начать » Начало работы","id":"12","title":"Начало работы"},"120":{"body":"Есть другое решение сбоев добавления двух видов с одинаковыми именами в одну и ту же область видимости используя use: после пути можно указать as и новое местное имя (псевдоним) для вида. Приложение 7-16 показывает как по-другому написать код из приложения 7-15, путём переименования одного из двух видов Result используя as. Файл: src/lib.rs use std::fmt::Result;\nuse std::io::Result as IoResult; fn function1() -> Result { // --snip--\n# Ok(())\n} fn function2() -> IoResult<()> { // --snip--\n# Ok(())\n} Приложение 7-16: Переименование вида, когда он включён в область видимости с помощью ключевого слова as Во второй указания use мы выбрали новое имя IoResult для вида std::io::Result, которое теперь не будет враждовать с видом Result из std::fmt, который также подключён в область видимости. Приложения 7-15 и 7-16 считаются идиоматичными, поэтому выбор за вами!","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Введение путей в область видимости с помощью ключевого слова use » Предоставление новых имён с помощью ключевого слова as","id":"120","title":"Предоставление новых имён с помощью ключевого слова as"},"121":{"body":"Когда мы подключаем имя в область видимости, используя ключевое слово use, то имя, доступное в новой области видимости, является закрытым. Чтобы позволить коду, который вызывает наш код, ссылаться на это имя, как если бы оно было определено в области видимости данного кода, можно объединить pub и use. Этот способ называется реэкспортом (re-exporting) , потому что мы подключаем элемент в область видимости, но также делаем этот элемент доступным для подключения в других областях видимости. Приложение 7-17 показывает код из приложения 7-11, где use в корневом звене заменено на pub use. Файл: src/lib.rs mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }\n} pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist();\n} Приложение 7-17. Предоставление имени для использования любым кодом из новой области при помощи pub use До этого изменения внешний код должен был вызывать функцию add_to_waitlist , используя путь restaurant::front_of_house::hosting::add_to_waitlist() . Теперь, когда это объявление pub use повторно экспортировало звено hosting из корневого звена, внешний код теперь может использовать вместо него путь restaurant::hosting::add_to_waitlist() . Реэкспорт полезен, когда внутренняя устройства вашего кода отличается от того, как программисты, вызывающие ваш код, думают о предметной области. Например, по подобию с рестораном люди, управляющие им, думают о «передней части дома» и «задней части дома». Но клиенты, посещающие ресторан, вероятно, не будут думать о частях ресторана в таких понятиях. Используя pub use , мы можем написать наш код с одной устройством, но сделать общедоступной другую устройство. Благодаря этому наша библиотека хорошо согласована для программистов, работающих над библиотекой, и для программистов, вызывающих библиотеку. Мы рассмотрим ещё один пример pub use и его влияние на документацию вашего ящика в разделе «Экспорт удобного общедоступного API с pub use» Главы 14.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Введение путей в область видимости с помощью ключевого слова use » Реэкспорт имён с pub use","id":"121","title":"Реэкспорт имён с pub use"},"122":{"body":"В Главе 2 мы запрограммировали игру угадывания числа, где использовался внешний дополнение с именем rand для создания случайного числа. Чтобы использовать rand в нашем деле, мы добавили эту строку в Cargo.toml : Файл: Cargo.toml rand = \"0.8.5\" Добавление rand в качестве зависимости в Cargo.toml указывает Cargo загрузить дополнение rand и все его зависимости из crates.io и сделать rand доступным для нашего дела. Затем, чтобы подключить определения rand в область видимости нашего дополнения, мы добавили строку use начинающуюся с названия дополнения rand и списка элементов, которые мы хотим подключить в область видимости. Напомним, что в разделе \"Создание случайного числа\" Главы 2, мы подключили особенность Rng в область видимости и вызвали функцию rand::thread_rng: # use std::io;\nuse rand::Rng; fn main() {\n# println!(\"Guess the number!\");\n# let secret_number = rand::thread_rng().gen_range(1..=100);\n# # println!(\"The secret number is: {secret_number}\");\n# # println!(\"Please input your guess.\");\n# # let mut guess = String::new();\n# # io::stdin()\n# .read_line(&mut guess)\n# .expect(\"Failed to read line\");\n# # println!(\"You guessed: {guess}\");\n} Члены сообщества Ржавчина сделали много дополнений доступными на ресурсе crates.io , и добавление любого из них в ваш дополнение включает в себя одни и те же шаги: добавить внешние дополнения в файл Cargo.toml вашего дополнения, использовать use для подключения элементов внешних дополнений в область видимости. Обратите внимание, что обычная библиотека std также является ящиком, внешним по отношению к нашему дополнению. Поскольку обычная библиотека поставляется с языком Rust, нам не нужно изменять Cargo.toml для подключения std. Но нам нужно ссылаться на неё при помощи use, чтобы добавить элементы оттуда в область видимости нашего дополнения. Например, с HashMap мы использовали бы эту строку: use std::collections::HashMap; Это абсолютный путь, начинающийся с std, имени ящика встроенной библиотеки.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Введение путей в область видимости с помощью ключевого слова use » Использование внешних дополнений","id":"122","title":"Использование внешних дополнений"},"123":{"body":"Если мы используем несколько элементов, определённых в одном ящике или в том же звене, то перечисление каждого элемента в отдельной строке может занимать много вертикального пространства в файле. Например, эти две указания use используются в программе угадывания числа (приложение 2-4) для подключения элементов из std в область видимости: Файл: src/main.rs # use rand::Rng;\n// --snip--\nuse std::cmp::Ordering;\nuse std::io;\n// --snip--\n# # fn main() {\n# println!(\"Guess the number!\");\n# # let secret_number = rand::thread_rng().gen_range(1..=100);\n# # println!(\"The secret number is: {secret_number}\");\n# # println!(\"Please input your guess.\");\n# # let mut guess = String::new();\n# # io::stdin()\n# .read_line(&mut guess)\n# .expect(\"Failed to read line\");\n# # println!(\"You guessed: {guess}\");\n# # match guess.cmp(&secret_number) {\n# Ordering::Less => println!(\"Too small!\"),\n# Ordering::Greater => println!(\"Too big!\"),\n# Ordering::Equal => println!(\"You win!\"),\n# }\n# } Вместо этого, мы можем использовать вложенные пути, чтобы добавить эти элементы в область видимости одной строкой. Мы делаем это, как показано в приложении 7-18, указывая общую часть пути, за которой следуют два двоеточия, а затем фигурные скобки вокруг списка тех частей продолжения пути, которые отличаются. Файл: src/main.rs # use rand::Rng;\n// --snip--\nuse std::{cmp::Ordering, io};\n// --snip--\n# # fn main() {\n# println!(\"Guess the number!\");\n# # let secret_number = rand::thread_rng().gen_range(1..=100);\n# # println!(\"The secret number is: {secret_number}\");\n# # println!(\"Please input your guess.\");\n# # let mut guess = String::new();\n# # io::stdin()\n# .read_line(&mut guess)\n# .expect(\"Failed to read line\");\n# # let guess: u32 = guess.trim().parse().expect(\"Please type a number!\");\n# # println!(\"You guessed: {guess}\");\n# # match guess.cmp(&secret_number) {\n# Ordering::Less => println!(\"Too small!\"),\n# Ordering::Greater => println!(\"Too big!\"),\n# Ordering::Equal => println!(\"You win!\"),\n# }\n# } Приложение 7-18. Указание вложенного пути для добавления нескольких элементов с одинаковым приставкой в область видимости В больших программах, подключение множества элементов из одного дополнения или звена с использованием вложенных путей может значительно сократить количество необходимых отдельных указаний use! Можно использовать вложенный путь на любом уровне, что полезно при объединении двух указаний use, которые имеют общую часть пути. Например, в приложении 7-19 показаны две указания use: одна подключает std::io, а другая подключает std::io::Write в область видимости. Файл: src/lib.rs use std::io;\nuse std::io::Write; Приложение 7-19: Две указания use, в которых один путь является частью другого Общей частью этих двух путей является std::io, и это полный первый путь. Чтобы объединить эти два пути в одной указания use, мы можем использовать ключевое слово self во вложенном пути, как показано в приложении 7-20. Файл: src/lib.rs use std::io::{self, Write}; Приложение 7-20: Объединение путей из Приложения 7-19 в одну указанию use Эта строка подключает std::io и std::io::Write в область видимости.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Введение путей в область видимости с помощью ключевого слова use » Использование вложенных путей для уменьшения длинных списков use","id":"123","title":"Использование вложенных путей для уменьшения длинных списков use"},"124":{"body":"Если мы хотим включить в область видимости все общедоступные элементы, определённые в пути, мы можем указать этот путь, за которым следует оператор *: use std::collections::*; Эта указание use подключает все открытые элементы из звена std::collections в текущую область видимости. Будьте осторожны при использовании оператора *! Он может усложнить понимание, какие имена находятся в области видимости и где были определены имена, используемые в вашей программе. Оператор * часто используется при проверке для подключения всего что есть в звене tests; мы поговорим об этом в разделе \"Как писать проверки\" Главы 11. Оператор * также иногда используется как часть образца самостоятельного подключения (prelude) : смотрите документацию по встроенной библиотеке для получения дополнительной сведений об этом образце.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Введение путей в область видимости с помощью ключевого слова use » Оператор * (glob)","id":"124","title":"Оператор * (glob)"},"125":{"body":"До сих пор все примеры в этой главе определяли несколько звеньев в одном файле. Когда звенья становятся большими, вы можете захотеть переместить их определения в отдельные файлы, чтобы упростить навигацию по коду. Например, давайте начнём с кода из приложения 7-17, в котором было несколько звеньев ресторана. Мы будем извлекать звенья в файлы вместо того, чтобы определять все звенья в корневом звене ящика. В нашем случае корневой звено ящика - src/lib.rs , но это разделение также работает и с двоичными ящиками, у которых корневой звено ящика — src/main.rs . Сначала мы извлечём звено front_of_house в свой собственный файл. Удалите код внутри фигурных скобок для звена front_of_house, оставив только объявление mod front_of_house;, так что теперь src/lib.rs содержит код, показанный в приложении 7-21. Обратите внимание, что этот исход не собирается, пока мы не создадим файл src/front_of_house.rs из приложении 7-22. Файл: src/lib.rs mod front_of_house; pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist();\n} Приложение 7-21. Объявление звена front_of_house, чьё содержимое будет в src/front_of_house.rs Затем поместим код, который был в фигурных скобках, в новый файл с именем src/front_of_house.rs , как показано в приложении 7-22. Сборщик знает, что нужно искать в этом файле, потому что он наткнулся в корневом звене ящика на объявление звена с именем front_of_house. Файл: src/front_of_house.rs pub mod hosting { pub fn add_to_waitlist() {}\n} Приложение 7-22. Определение содержимого звена front_of_house в файле src/front_of_house.rs Обратите внимание, что вам нужно только один раз загрузить файл с помощью объявления mod в вашем дереве звеньев. Как только сборщик узнает, что файл является частью дела (и узнает, где в дереве звеньев находится код из-за того, куда вы помеисполнения указанию mod), другие файлы в вашем деле должны ссылаться на код загруженного файла, используя путь к месту, где он был объявлен, как описано в разделе «Пути для ссылки на элемент в дереве звеньев» . Другими словами, mod — это не действие «включения», которую вы могли видеть в других языках программирования. Далее мы извлечём звено hosting в его собственный файл. Этап немного отличается, потому что hosting является дочерним звеном для front_of_house, а не корневого звена. Мы поместим файл для hosting в новый папка, который будет назван по имени его предка в дереве звеньев, в данном случае это src/front_of_house/ . Чтобы начать перенос hosting, мы меняем src/front_of_house.rs так, чтобы он содержал только объявление звена hosting: Файл: src/front_of_house.rs pub mod hosting; Затем мы создаём папка src/front_of_house и файл hosting.rs , в котором будут определения, сделанные в звене hosting: Файл: src/front_of_house/hosting.rs pub fn add_to_waitlist() {} Если вместо этого мы поместим hosting.rs в папка src , сборщик будет думать, что код в hosting.rs это звено hosting, объявленный в корне ящика, а не объявленный как дочерний звено front_of_house. Правила сборщика для проверки какие файлы содержат код каких звеньев предполагают, что папки и файлы точно соответствуют дереву звеньев.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Separating Modules into Different Files » Разделение звеньев на разные файлы","id":"125","title":"Разделение звеньев на разные файлы"},"126":{"body":"До сих пор мы рассматривали наиболее идиоматические пути к файлам, используемые сборщиком Rust, но Ржавчина также поддерживает и старый исполнение пути к файлу. Для звена с именем front_of_house, объявленного в корневом звене ящика, сборщик будет искать код звена в: src/front_of_house.rs (что мы рассматривали) src/front_of_house/mod.rs (старый исполнение, всё ещё поддерживаемый путь) Для звена с именем hosting, который является подзвеном front_of_house, сборщик будет искать код звена в: src/front_of_house/hosting.rs (что мы рассматривали) src/front_of_house/hosting/mod.rs (старый исполнение, всё ещё поддерживаемый путь) Если вы используете оба исполнения для одного и того же звена, вы получите ошибку сборщика. Использование сочетания обоих исполнениий для разных звеньев в одном деле разрешено, но это может сбивать с толку людей, перемещающихся по вашему делу. Основным недостатком исполнения, в котором используются файлы с именами mod.rs , является то, что в вашем деле может оказаться много файлов с именами mod.rs , что может привести к путанице, если вы одновременно откроете их в редакторе. Мы перенесли код каждого звена в отдельный файл, а дерево звеньев осталось прежним. Вызовы функций в eat_at_restaurant будут работать без каких-либо изменений, несмотря на то, что определения находятся в разных файлах. Этот способ позволяет перемещать звенья в новые файлы по мере увеличения их размеров. Обратите внимание, что указание pub use crate::front_of_house::hosting в src/lib.rs также не изменилась, и use не влияет на то, какие файлы собираются как часть ящика. Ключевое слово mod объявляет звенья, и Ржавчина ищет в файле с тем же именем, что и у звена, код, который входит в этот звено.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Separating Modules into Different Files » Иные пути к файлам","id":"126","title":"Иные пути к файлам"},"127":{"body":"Rust позволяет разбить дополнение на несколько ящиков и ящик - на звенья, так что вы можете ссылаться на элементы, определённые в одном звене, из другого звена. Это можно делать при помощи указания абсолютных или относительных путей. Эти пути можно добавить в область видимости указанием use, поэтому вы можете пользоваться более короткими путями для многократного использования элементов в этой области видимости. Код звена по умолчанию является закрытым, но можно сделать определения общедоступными, добавив ключевое слово pub. В следующей главе мы рассмотрим некоторые собрания устройств данных из встроенной библиотеки, которые вы можете использовать в своём правильноно согласованном коде.","breadcrumbs":"Управление растущими делами с помощью дополнений, ящиков и звеньев » Separating Modules into Different Files » Итог","id":"127","title":"Итог"},"128":{"body":"Обычная библиотека содержит несколько полезных устройств данных, которые называются собраниями . Большая часть других видов данных представляют собой хранение определенного значения, но особенностью собраний является хранение множества однотипных значений. В отличии от массива или упорядоченного ряда данные собраний хранятся в куче, а это значит, что размер собрания может быть неизвестен в мгновение сборки программы. Он может изменяться (увеличиваться, уменьшаться) во время работы программы. Каждый вид собраний имеет свои возможности и отличается по производительности, так что выбор именно собрания зависит от случаи и является умением разработчика, вырабатываемым со временем. В этой главе будет рассмотрено несколько собраний: Вектор (vector) - позволяет нам сохранять различное количество последовательно хранящихся значений, Строка (string) - это последовательность символов. Мы же упоминали вид String ранее, но в данной главе мы поговорим о нем подробнее. Хеш-таблица (hash map) - собрание которая позволяет хранить перечень ассоциаций значения с ключом (перечень пар ключ:значение). Является именно выполнением более общей устройства данных называемой map . Для того, чтобы узнать о других видах собраний предоставляемых встроенной библиотекой смотрите документацию . Мы обсудим как создавать и обновлять векторы, строки и хеш-таблицы, а также объясним что делает каждую из них особенной.","breadcrumbs":"Общие собрания » Общие собрания","id":"128","title":"Общие собрания"},"129":{"body":"Первым видом собрания, который мы разберём, будет Vec, также известный как вектор (vector). Векторы позволяют хранить более одного значения в единой устройстве данных, хранящей элементы в памяти один за другим. Векторы могут хранить данные только одного вида. Их удобно использовать, когда нужно хранить список элементов, например, список текстовых строк из файла, или список цен товаров в корзине покупок.","breadcrumbs":"Общие собрания » Хранение списков значений с векторами » Хранение списков значений в векторах","id":"129","title":"Хранение списков значений в векторах"},"13":{"body":"Первым шагом является установка Rust. Мы загрузим Rust, используя средство приказной строки rustup, предназначенный для управлениями исполнениями Ржавчина и другими связанными с ним средствами. Вам понадобится интернет-соединение для его загрузки. Примечание: если вы по каким-то причинам предпочитаете не использовать rustup, пожалуйста, посетите страницу «Другие способы установки Rust» для получения дополнительных возможностей. Следующие шаги устанавливают последнюю безотказную исполнение сборщика Rust. Благодаря заверениям безотказности Ржавчина все примеры в книге, которые собираются, будут собираться и в новых исполнениях Rust. Вывод может немного отличаться в разных исполнениях, поскольку Ржавчина часто улучшает сообщения об ошибках и предупреждения. Другими словами, любая новая, безотказная исполнение Rust, которую вы установите с помощью этих шагов, должна работать с содержимым этой книги так, как ожидается.","breadcrumbs":"С чего начать » Установка » Установка","id":"13","title":"Установка"},"130":{"body":"Чтобы создать новый пустой вектор, мы вызываем функцию Vec::new, как показано в приложении 8-1. # fn main() { let v: Vec = Vec::new();\n# } Приложение 8-1: Создание нового пустого вектора для хранения значений вида i32 Обратите внимание, что здесь мы добавили изложение вида. Поскольку мы не вставляем никаких значений в этот вектор, Ржавчина не знает, какие элементы мы собираемся хранить. Это важный мгновение. Векторы выполнены с использованием обобщённых видов; мы рассмотрим, как использовать обобщённые виды с вашими собственными видами в Главе 10. А пока знайте, что вид Vec, предоставляемый встроенной библиотекой, может хранить любой вид. Когда мы создаём новый вектор для хранения определенного вида, мы можем указать этот вид в угловых скобках. В приложении 8-1 мы сообщили Rust, что Vec в v будет хранить элементы вида i32. Чаще всего вы будете создавать Vec с начальными значениями и Ржавчина может определить вид сохраняемых вами значений, но иногда вам всё же придётся указывать изложение вида. Для удобства Ржавчина предоставляет макрос vec!, который создаст новый вектор, содержащий заданные вами значения. В приложении 8-2 создаётся новый Vec, который будет хранить значения 1, 2 и 3. Числовым видом является i32, потому что это вид по умолчанию для целочисленных значений, о чём упоминалось в разделе “Виды данных” Главы 3. # fn main() { let v = vec![1, 2, 3];\n# } Приложение 8-2: Создание нового вектора, содержащего значения Поскольку мы указали начальные значения вида i32, Ржавчина может сделать вывод, что вид переменной v это Vec и изложение вида здесь не нужна. Далее мы посмотрим как изменять вектор.","breadcrumbs":"Общие собрания » Хранение списков значений с векторами » Создание нового вектора","id":"130","title":"Создание нового вектора"},"131":{"body":"Чтобы создать вектор и затем добавить к нему элементы, можно использовать способ push показанный в приложении 8-3. # fn main() { let mut v = Vec::new(); v.push(5); v.push(6); v.push(7); v.push(8);\n# } Приложение 8-3: Использование способа push для добавления значений в вектор Как и с любой переменной, если мы хотим изменить её значение, нам нужно сделать её изменяемой с помощью ключевого слова mut, что обсуждалось в Главе 3. Все числа которые мы помещаем в вектор имеют вид i32 по этому Ржавчина с лёгкостью выводит вид вектора, по этой причине нам не нужна здесь изложение вида вектора Vec.","breadcrumbs":"Общие собрания » Хранение списков значений с векторами » Изменение вектора","id":"131","title":"Изменение вектора"},"132":{"body":"Есть два способа сослаться на значение, хранящееся в векторе: с помощью порядкового указателя или способа get . В следующих примерах для большей ясности мы указали виды значений, возвращаемых этими функциями. В приложении 8-4 показаны оба способа доступа к значению в векторе: либо с помощью правил написания упорядочевания и с помощью способа get. # fn main() { let v = vec![1, 2, 3, 4, 5]; let third: &i32 = &v[2]; println!(\"The third element is {third}\"); let third: Option<&i32> = v.get(2); match third { Some(third) => println!(\"The third element is {third}\"), None => println!(\"There is no third element.\"), }\n# } Приложение 8-4. Использование правил написания упорядочевания и способа get для доступа к элементу в векторе Обратите внимание здесь на пару подробностей. Мы используем значение порядкового указателя 2 для получения третьего элемента: векторы упорядочеваются начиная с нуля. Указывая & и [] мы получаем ссылку на элемент по указанному порядковому указателю. Когда мы используем способ get содержащего порядковый указатель, переданный в качестве переменной, мы получаем вид Option<&T>, который мы можем проверить с помощью match. Причина, по которой Ржавчина предоставляет два способа ссылки на элемент, заключается в том, что вы можете выбрать, как программа будет себя вести, когда вы попытаетесь использовать значение порядкового указателя за пределами ряда существующих элементов. В качестве примера давайте посмотрим, что происходит, когда у нас есть вектор из пяти элементов, а затем мы пытаемся получить доступ к элементу с порядковым указателем 100 с помощью каждого способа, как показано в приложении 8-5. # fn main() { let v = vec![1, 2, 3, 4, 5]; let does_not_exist = &v[100]; let does_not_exist = v.get(100);\n# } Приложение 8-5. Попытка доступа к элементу с порядковым указателем 100 в векторе, содержащем пять элементов Когда мы запускаем этот код, первая строка с &v[100] вызовет панику программы, потому что происходит попытка получить ссылку на несуществующий элемент. Такой подход лучше всего использовать, когда вы хотите, чтобы ваша программа со сбоем завершила работу при попытке доступа к элементу за пределами вектора. Когда способу get передаётся порядковый указатель, который находится за пределами вектора, он без паники возвращает None. Вы могли бы использовать такой подход, если доступ к элементу за пределами рядавектора происходит время от времени при обычных обстоятельствах. Тогда ваш код будет иметь логику для обработки наличия Some(&element) или None, как обсуждалось в Главе 6. Например, порядковый указательможет исходить от человека, вводящего число. Если пользователь случайно введёт слишком большое число, то программа получит значение None и у вас будет возможность сообщить пользователю, сколько элементов находится в текущем векторе, и дать ему возможность ввести допустимое значение. Такое поведение было бы более дружелюбным для пользователя, чем внезапный сбой программы из-за опечатки! Когда у программы есть действительная ссылка, borrow checker (средство проверки заимствований), обеспечивает соблюдение правил владения и заимствования (описанные в Главе 4), чтобы обеспечить, что эта ссылка и любые другие ссылки на содержимое вектора остаются действительными. Вспомните правило, которое гласит, что у вас не может быть изменяемых и неизменяемых ссылок в одной и той же области. Это правило применяется в приложении 8-6, где мы храним неизменяемую ссылку на первый элемент вектора и затем пытаемся добавить элемент в конец вектора. Данная программа не будет работать, если мы также попробуем сослаться на данный элемент позже в функции: # fn main() { let mut v = vec![1, 2, 3, 4, 5]; let first = &v[0]; v.push(6); println!(\"The first element is: {first}\");\n# } Приложение 8-6. Попытка добавить некоторый элемент в вектор, в то время когда есть ссылка на элемент вектора Сборка этого кода приведёт к ошибке: $ cargo run Compiling collections v0.1.0 (file:///projects/collections)\nerror[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> src/main.rs:6:5 |\n4 | let first = &v[0]; | - immutable borrow occurs here\n5 |\n6 | v.push(6); | ^^^^^^^^^ mutable borrow occurs here\n7 |\n8 | println!(\"The first element is: {first}\"); | ------- immutable borrow later used here For more information about this error, try `rustc --explain E0502`.\nerror: could not compile `collections` (bin \"collections\") due to 1 previous error Код в приложении 8-6 может выглядеть так, как будто он должен работать. Почему ссылка на первый элемент должна заботиться об изменениях в конце вектора? Эта ошибка возникает из-за особенности того, как работают векторы: поскольку векторы размещают значения в памяти друг за другом, добавление нового элемента в конец вектора может потребовать выделения новой памяти и повторения старых элементов в новое пространство, если нет достаточного места, чтобы разместить все элементы друг за другом там, где в данный мгновение хранится вектор. В этом случае ссылка на первый элемент будет указывать на освобождённую память. Правила заимствования предотвращают попадание программ в такую случай. Примечание: Дополнительные сведения о выполнения вида Vec смотрите в разделе \"The Rustonomicon\" .","breadcrumbs":"Общие собрания » Хранение списков значений с векторами » Чтение данных вектора","id":"132","title":"Чтение данных вектора"},"133":{"body":"Для доступа к каждому элементу вектора по очереди, мы повторяем все элементы, вместо использования порядковых указателей для доступа к одному за раз. В приложении 8-7 показано, как использовать цикл for для получения неизменяемых ссылок на каждый элемент в векторе значений вида i32 и их вывода. # fn main() { let v = vec![100, 32, 57]; for i in &v { println!(\"{i}\"); }\n# } Приложение 8-7. Печать каждого элемента векторе, при помощи повторения по элементам вектора с помощью цикла for Мы также можем повторять изменяемые ссылки на каждый элемент изменяемого вектора, чтобы вносить изменения во все элементы. Цикл for в приложении 8-8 добавит 50 к каждому элементу. # fn main() { let mut v = vec![100, 32, 57]; for i in &mut v { *i += 50; }\n# } Приложение 8-8. Повторение и изменение элементов вектора по изменяемым ссылкам Чтобы изменить значение на которое ссылается изменяемая ссылка, мы должны использовать оператор разыменования ссылки * для получения значения по ссылке в переменной i прежде чем использовать оператор +=. Мы поговорим подробнее об операторе разыменования в разделе “Следование по указателю к значению с помощью оператора разыменования” Главы 15. Перебор вектора, будь то неизменяемый или изменяемый, безопасен из-за правил проверки заимствования. Если бы мы попытались вставить или удалить элементы в телах цикла for в приложениях 8-7 и 8-8, мы бы получили ошибку сборщика, подобную той, которую мы получили с кодом в приложении 8-6. Ссылка на вектор, содержащийся в цикле for, предотвращает одновременную изменение всего вектора.","breadcrumbs":"Общие собрания » Хранение списков значений с векторами » Перебор значений в векторе","id":"133","title":"Перебор значений в векторе"},"134":{"body":"Векторы могут хранить значения только одинакового вида. Это может быть неудобно; определённо могут быть случаи когда надо хранить список элементов разных видов. К счастью, исходы перечисления определены для одного и того же вида перечисления, поэтому, когда нам нужен один вид для представления элементов разных видов, мы можем определить и использовать перечисление! Например, мы хотим получить значения из строки в электронной таблице где некоторые столбцы строки содержат целые числа, некоторые числа с плавающей точкой, а другие - строковые значения. Можно определить перечисление, исходы которого будут содержать разные виды значений и тогда все исходы перечисления будут считаться одним и тем же видом: видом самого перечисления. Затем мы можем создать вектор для хранения этого перечисления и, в конечном счёте, для хранения различных видов. Мы покажем это в приложении 8-9. # fn main() { enum SpreadsheetCell { Int(i32), Float(f64), Text(String), } let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from(\"blue\")), SpreadsheetCell::Float(10.12), ];\n# } Приложение 8-9: Определение enum для хранения значений разных видов в одном векторе Rust должен знать, какие виды будут в векторе во время сборки, чтобы точно знать сколько памяти в куче потребуется для хранения каждого элемента. Мы также должны чётко указать, какие виды разрешены в этом векторе. Если бы Ржавчина позволял вектору содержать любой вид, то был бы шанс что один или несколько видов вызовут ошибки при выполнении действий над элементами вектора. Использование перечисления вместе с выражением match означает, что во время сборки Ржавчина заверяет, что все возможные случаи будут обработаны, как обсуждалось в главе 6. Если вы не знаете исчерпывающий набор видов, которые программа получит во время выполнения для хранения в векторе, то техника использования перечисления не сработает. Вместо этого вы можете использовать особенность-предмет, который мы рассмотрим в главе 17. Теперь, когда мы обсудили некоторые из наиболее распространённых способов использования векторов, обязательно ознакомьтесь с документацией по API вектора , чтобы узнать о множестве полезных способов, определённых в Vec встроенной библиотеки. Например, в дополнение к способу push, существует способ pop, который удаляет и возвращает последний элемент.","breadcrumbs":"Общие собрания » Хранение списков значений с векторами » Использование перечислений для хранения множества разных видов","id":"134","title":"Использование перечислений для хранения множества разных видов"},"135":{"body":"Подобно устройствам struct, вектор высвобождает свою память когда выходит из области видимости, что показано в приложении 8-10. # fn main() { { let v = vec![1, 2, 3, 4]; // do stuff with v } // <- v goes out of scope and is freed here\n# } Приложение 8-10. Показано как удаляется вектор и его элементы Когда вектор удаляется, всё его содержимое также удаляется: удаление вектора означает и удаление значений, которые он содержит. Средство проверки заимствования заверяет, что любые ссылки на содержимое вектора используются только тогда, когда сам вектор действителен. Давайте перейдём к следующему виду собрания: String!","breadcrumbs":"Общие собрания » Хранение списков значений с векторами » Удаление элементов из вектора","id":"135","title":"Удаление элементов из вектора"},"136":{"body":"Мы говорили о строках в главе 4, но сейчас мы рассмотрим их более подробно. Новички в Ржавчина обычно застревают на строках из-за сочетания трёх причин: склонность Ржавчина сборщика к выявлению возможных ошибок, более сложная устройства данных чем считают многие программисты и UTF-8. Эти обстоятельства объединяются таким образом, что направление может показаться сложной, если вы пришли из других языков программирования. Полезно обсуждать строки в среде собраний, потому что строки выполнены в виде набора байтов, плюс некоторые способы для обеспечения полезной возможности, когда эти байты преобразуются как текст. В этом разделе мы поговорим об действиех над String таких как создание, обновление и чтение, которые есть у каждого вида собраний. Мы также обсудим какими особенностями String отличается от других собраний, а именно каким образом упорядочевание в String осложняется различием между тем как люди и компьютеры преобразуют данные заключённые в String.","breadcrumbs":"Общие собрания » Хранение закодированного текста UTF-8 со строками » Хранение закодированного текста UTF-8 в строках","id":"136","title":"Хранение закодированного текста UTF-8 в строках"},"137":{"body":"Сначала мы определим, что мы подразумеваем под понятием строка (string). В Ржавчина есть только один строковый вид в ядре языка - срез строки str, обычно используемый в заимствованном виде как &str. В Главе 4 мы говорили о срезах строк, string slices , которые являются ссылками на некоторые строковые данные в кодировке UTF-8. Например, строковые записи хранятся в двоичном файле программы и поэтому являются срезами строк. Вид String предоставляемый встроенной библиотекой Rust, не встроен в ядро языка и является расширяемым, изменяемым, владеющим, строковым видом в UTF-8 кодировке. Когда Rustaceans говорят о \"строках\" то, они обычно имеют в виду виды String или строковые срезы &str, а не просто один из них. Хотя этот раздел в основном посвящён String, оба вида усиленно используются в встроенной библиотеке Rust, оба, и String и строковые срезы, кодируются в UTF-8.","breadcrumbs":"Общие собрания » Хранение закодированного текста UTF-8 со строками » Что же такое строка?","id":"137","title":"Что же такое строка?"},"138":{"body":"Многие из тех же действий, которые доступны Vec , доступны также в String, потому что String в действительности выполнен как обёртка вокруг вектора байтов с некоторыми дополнительными заверениями, ограничениями и возможностями. Примером функции, которая одинаково работает с Vec и String, является функция new, создающая новый образец вида, и показана в Приложении 8-11. # fn main() { let mut s = String::new();\n# } Приложение 8-11. Создание новой пустой String строки Эта строка создаёт новую пустую строковую переменную с именем s, в которую мы можем затем загрузить данные. Часто у нас есть некоторые начальные данные, которые мы хотим назначить строке. Для этого мы используем способ to_string доступный для любого вида, который выполняет особенность Display, как у строковых записей. Приложение 8-12 показывает два примера. # fn main() { let data = \"initial contents\"; let s = data.to_string(); // the method also works on a literal directly: let s = \"initial contents\".to_string();\n# } Приложение 8-12: Использование способа to_string для создания образца вида String из строкового записи Эти выражения создают строку с initial contents. Мы также можем использовать функцию String::from для создания String из строкового записи. Код приложения 8-13 является эквивалентным коду из приложения 8-12, который использует функцию to_string: # fn main() { let s = String::from(\"initial contents\");\n# } Приложение 8-13: Использование функции String::from для создания образца вида String из строкового записи Поскольку строки используются для очень многих вещей, можно использовать множество API для строк, предоставляющих множество возможностей. Некоторые из них могут показаться избыточными, но все они занимаются своим делом! В данном случае String::from и to_string делают одно и тоже, поэтому выбор зависит от исполнения который вам больше импонирует. Запомните, что строки хранятся в кодировке UTF-8, поэтому можно использовать любые правильно кодированные данные в них, как показано в приложении 8-14: # fn main() { let hello = String::from(\"السلام عليكم\"); let hello = String::from(\"Dobrý den\"); let hello = String::from(\"Hello\"); let hello = String::from(\"שלום\"); let hello = String::from(\"नमस्ते\"); let hello = String::from(\"こんにちは\"); let hello = String::from(\"안녕하세요\"); let hello = String::from(\"你好\"); let hello = String::from(\"Olá\"); let hello = String::from(\"Здравствуйте\"); let hello = String::from(\"Hola\");\n# } Приложение 8-14: Хранение приветствий в строках на разных языках Все это допустимые String значения.","breadcrumbs":"Общие собрания » Хранение закодированного текста UTF-8 со строками » Создание новых строк","id":"138","title":"Создание новых строк"},"139":{"body":"Строка String может увеличиваться в размере, а её содержимое может меняться, по подобию как содержимое Vec при вставке в него большего количества данных. Кроме того, можно использовать оператор + или макрос format! для объединения значений String. Присоединение к строке с помощью push_str и push Мы можем нарастить String используя способ push_str который добавит в исходное значение новый строковый срез, как показано в приложении 8-15. # fn main() { let mut s = String::from(\"foo\"); s.push_str(\"bar\");\n# } Приложение 8-15. Добавление среза строки к String с помощью способа push_str После этих двух строк кода s будет содержать foobar. Способ push_str принимает строковый срез, потому что мы не всегда хотим владеть входным свойствоом. Например, код в приложении 8-16 показывает исход, когда будет не желательно поведение, при котором мы не сможем использовать s2 после его добавления к содержимому значения переменной s1. # fn main() { let mut s1 = String::from(\"foo\"); let s2 = \"bar\"; s1.push_str(s2); println!(\"s2 is {s2}\");\n# } Приложение 8-16: Использование среза строки после добавления её содержимого к другой String Если способ push_str стал бы владельцем переменнойs2, мы не смогли бы напечатать его значение в последней строке. Однако этот код работает так, как мы ожидали! Способ push принимает один символ в качестве свойства и добавляет его к String. В приложении 8-17 показан код, добавляющий букву “l” к String используя способ push. # fn main() { let mut s = String::from(\"lo\"); s.push('l');\n# } Приложение 8-17: Добавление одного символа в String значение используя push В итоге s будет содержать lol. Объединение строк с помощью оператора + или макроса format! Часто хочется объединять две существующие строки. Один из возможных способов — это использование оператора + из приложения 8-18: # fn main() { let s1 = String::from(\"Hello, \"); let s2 = String::from(\"world!\"); let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used\n# } Приложение 8-18: Использование оператора + для объединения двух значений String в новое String значение Строка s3 будет содержать Hello, world!. Причина того, что s1 после добавления больше недействительна и причина, по которой мы использовали ссылку на s2 имеют отношение к ярлыке вызываемого способа при использовании оператора +. Оператор + использует способ add, чья ярлык выглядит примерно так: fn add(self, s: &str) -> String { В встроенной библиотеке вы увидите способ add определённым с использованием обобщённых и связанных видов. Здесь мы видим ярлык с определенными видами, заменяющими обобщённый, что происходит когда вызывается данный способ со значениями String. Мы обсудим обобщённые виды в Главе 10. Эта ярлык даёт нам ключ для понимания особенностей оператора +. Во-первых, перед s2 мы видим &, что означает что мы складываем ссылку на вторую строку с первой строкой. Это происходит из-за свойства s в функции add: мы можем добавить только &str к String; мы не можем сложить два значения String. Но подождите — вид &s2 это &String, а не &str, как определён второй свойство в add. Так почему код в приложении 8-18 собирается? Причина, по которой мы можем использовать &s2 в вызове add заключается в том, что сборщик может принудительно привести (coerce) переменная вида &String к виду &str. Когда мы вызываем способ add в Ржавчина используется принудительное приведение (deref coercion), которое превращает &s2 в &s2[..]. Мы подробно обсудим принудительное приведение в Главе 15. Так как add не забирает во владение свойство s, s2 по прежнему будет действительной строкой String после применения действия. Во-вторых, как можно видеть в ярлыке, add забирает во владение self, потому что self не имеет &. Это означает, что s1 в приложении 8-18 будет перемещён в вызов add и больше не будет действителен после этого вызова. Не смотря на то, что код let s3 = s1 + &s2; выглядит как будто он воспроизведет обе строки и создаёт новую, эта указание в действительности забирает во владение переменную s1, присоединяет к ней повтор содержимого s2, а затем возвращает владение итогом. Другими словами, это выглядит как будто код создаёт множество повторов, но это не так; данная выполнение более эффективна, чем повторение. Если нужно объединить несколько строк, поведение оператора + становится громоздким: # fn main() { let s1 = String::from(\"tic\"); let s2 = String::from(\"tac\"); let s3 = String::from(\"toe\"); let s = s1 + \"-\" + &s2 + \"-\" + &s3;\n# } Здесь переменная s будет содержать tic-tac-toe. С множеством символов + и \" становится трудно понять, что происходит. Для более сложного соединения строк можно использовать макрос format!: # fn main() { let s1 = String::from(\"tic\"); let s2 = String::from(\"tac\"); let s3 = String::from(\"toe\"); let s = format!(\"{s1}-{s2}-{s3}\");\n# } Этот код также устанавливает переменную s в значение tic-tac-toe. Макрос format! работает тем же способом что макрос println!, но вместо вывода на экран возвращает вид String с содержимым. Исполнение кода с использованием format! значительно легче читается, а также код, созданный макросом format!, использует ссылки, а значит не забирает во владение ни один из его свойств.","breadcrumbs":"Общие собрания » Хранение закодированного текста UTF-8 со строками » Обновление строковых данных","id":"139","title":"Обновление строковых данных"},"14":{"body":"В этой главе и во всей книге мы будем выполнять некоторые приказы, используемые в окне вызова. Строки, которые вы должны вводить в окне вызова, начинаются с $. Вам не нужно вводить символ $; это подсказка приказной строки, отображаемая для обозначения начала каждой приказы. Строки, которые не начинаются с $, обычно показывают вывод предыдущей приказы. Кроме того, в примерах, своеобразных для PowerShell, будет использоваться >, а не $.","breadcrumbs":"С чего начать » Установка » Условные обозначения приказной строки","id":"14","title":"Условные обозначения приказной строки"},"140":{"body":"Доступ к отдельным символам в строке, при помощи ссылки на них по порядковому указателю, является допустимой и распространённой действием во многих других языках программирования. Тем не менее, если вы попытаетесь получить доступ к частям String, используя правила написания упорядочевания в Rust, то вы получите ошибку. Рассмотрим неверный код в приложении 8-19. # fn main() { let s1 = String::from(\"hello\"); let h = s1[0];\n# } Приложение 8-19: Попытка использовать правила написания порядкового указателя со строкой Этот код приведёт к следующей ошибке: $ cargo run Compiling collections v0.1.0 (file:///projects/collections)\nerror[E0277]: the type `str` cannot be indexed by `{integer}` --> src/main.rs:3:16 |\n3 | let h = s1[0]; | ^ string indices are ranges of `usize` | = help: the trait `SliceIndex` is not implemented for `{integer}`, which is required by `String: Index<_>` = note: you can use `.chars().nth()` or `.bytes().nth()` for more information, see chapter 8 in The Book: = help: the trait `SliceIndex<[_]>` is implemented for `usize` = help: for that trait implementation, expected `[_]`, found `str` = note: required for `String` to implement `Index<{integer}>` For more information about this error, try `rustc --explain E0277`.\nerror: could not compile `collections` (bin \"collections\") due to 1 previous error Ошибка и примечание говорит, что в Ржавчина строки не поддерживают упорядочевание. Но почему так? Чтобы ответить на этот вопрос, нужно обсудить то, как Ржавчина хранит строки в памяти. Внутреннее представление Вид String является оболочкой над видом Vec. Давайте посмотрим на несколько закодированных правильным образом в UTF-8 строк из примера приложения 8-14. Начнём с этой: # fn main() {\n# let hello = String::from(\"السلام عليكم\");\n# let hello = String::from(\"Dobrý den\");\n# let hello = String::from(\"Hello\");\n# let hello = String::from(\"שלום\");\n# let hello = String::from(\"नमस्ते\");\n# let hello = String::from(\"こんにちは\");\n# let hello = String::from(\"안녕하세요\");\n# let hello = String::from(\"你好\");\n# let hello = String::from(\"Olá\");\n# let hello = String::from(\"Здравствуйте\"); let hello = String::from(\"Hola\");\n# } В этом случае len будет 4, что означает вектор, хранит строку \"Hola\" длиной 4 байта. Каждая из этих букв занимает 1 байт при кодировании в UTF-8. Но как насчёт следующей строки? (Обратите внимание, что эта строка начинается с заглавной кириллической \"З\", а не цифры 3.) # fn main() {\n# let hello = String::from(\"السلام عليكم\");\n# let hello = String::from(\"Dobrý den\");\n# let hello = String::from(\"Hello\");\n# let hello = String::from(\"שלום\");\n# let hello = String::from(\"नमस्ते\");\n# let hello = String::from(\"こんにちは\");\n# let hello = String::from(\"안녕하세요\");\n# let hello = String::from(\"你好\");\n# let hello = String::from(\"Olá\"); let hello = String::from(\"Здравствуйте\");\n# let hello = String::from(\"Hola\");\n# } Отвечая на вопрос, какова длина строки, вы можете ответить 12. Однако ответ Ржавчина - 24, что равно числу байт, необходимых для кодирования «Здравствуйте» в UTF-8, так происходит, потому что каждое одиночное значение Unicode символа в этой строке занимает 2 байта памяти. Следовательно, порядковый указательпо байтам строки не всегда бы соответствовал действительному одиночному Unicode значению. Для отображения рассмотрим этот недопустимый код Rust: let hello = \"Здравствуйте\";\nlet answer = &hello[0]; Каким должно быть значение переменной answer? Должно ли оно быть значением первой буквы З? При кодировке в UTF-8, первый байт значения З равен 208, а второй - 151, поэтому значение в answer на самом деле должно быть 208, но само по себе 208 не является действительным символом. Возвращение 208, скорее всего не то, что хотел бы получить пользователь: ведь он ожидает первую букву этой строки; тем не менее, это единственный байт данных, который в Ржавчина доступен по порядковому указателю 0. Пользователи обычно не хотят получить значение байта, даже если строка содержит только латинские буквы: если &\"hello\"[0] было бы допустимым кодом, который вернул значение байта, то он вернул бы 104, а не h. Таким образом, чтобы предотвратить возврат непредвиденного значения, вызывающего ошибки которые не могут быть сразу обнаружены, Ржавчина просто не собирает такой код и предотвращает недопонимание на ранних этапах этапа разработки. Байты, одиночные значения и кластеры графем! Боже мой! Ещё один мгновение, касающийся UTF-8, заключается в том, что на самом деле существует три способа рассмотрения строк с точки зрения Rust: как байты, как одиночные значения и как кластеры графем (самая близкая вещь к тому, что мы назвали бы буквами ). Если посмотреть на слово языка хинди «नमस्ते», написанное в транскрипции Devanagari, то оно хранится как вектор значений u8 который выглядит следующим образом: [224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,\n224, 165, 135] Эти 18 байт являются именно тем, как компьютеры в конечном итоге сохранят в памяти эту строку. Если мы посмотрим на 18 байт как на одиночные Unicode значения, которые являются Ржавчина видом char, то байты будут выглядеть так: ['न', 'म', 'स', '्', 'त', 'े'] Здесь есть шесть значений вида char, но четвёртый и шестой являются не буквами: они диакритики, особые обозначения которые не имеют смысла сами по себе. Наконец, если мы посмотрим на байты как на кластеры графем, то получим то, что человек назвал бы словом на хинди состоящем из четырёх букв: [\"न\", \"म\", \"स्\", \"ते\"] Rust предоставляет различные способы преобразования необработанных строковых данных, которые компьютеры хранят так, чтобы каждой программе можно было выбрать необходимую преобразование, независимо от того, на каком человеческом языке представлены эти данные. Последняя причина, по которой Ржавчина не позволяет нам упорядочивать String для получения символов является то, что программисты ожидают, что действия упорядочевания всегда имеют постоянное время (O(1)) выполнения. Но невозможно обеспечить такую производительность для String, потому что Ржавчина понадобилось бы пройтись по содержимому от начала до порядкового указателя, чтобы определить, сколько было действительных символов.","breadcrumbs":"Общие собрания » Хранение закодированного текста UTF-8 со строками » Упорядочевание в строках","id":"140","title":"Упорядочевание в строках"},"141":{"body":"Упорядочевание строк часто является плохой мыслью, потому что не ясно каким должен быть возвращаемый вид такой действия: байтовым значением, символом, кластером графем или срезом строки. Поэтому Ржавчина просит вас быть более определенным, если действительно требуется использовать порядковые указатели для создания срезов строк. Вместо упорядочевания с помощью числового порядкового указателя [], вы можете использовать оператор ряда[] при создании среза строки в котором содержится указание на то, срез каких байтов надо делать: let hello = \"Здравствуйте\"; let s = &hello[0..4]; Здесь переменная s будет вида &str который содержит первые 4 байта строки. Ранее мы упоминали, что каждый из этих символов был по 2 байта, что означает, что s будет содержать \"Зд\". Что бы произошло, если бы мы использовали &hello[0..1]? Ответ: Ржавчина бы запаниковал во время выполнения точно так же, как если бы обращались к недействительному порядковому указателю в векторе: $ cargo run Compiling collections v0.1.0 (file:///projects/collections) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s Running `target/debug/collections`\nthread 'main' panicked at src/main.rs:4:19:\nbyte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Вы должны использовать ряды для создания срезов строк с осторожностью, потому что это может привести к сбою вашей программы.","breadcrumbs":"Общие собрания » Хранение закодированного текста UTF-8 со строками » Срезы строк","id":"141","title":"Срезы строк"},"142":{"body":"Лучший способ работать с отрывками строк — чётко указать, нужны ли вам символы или байты. Для отдельных одиночных значений в Юникоде используйте способ chars. Вызов chars у \"Зд\" выделяет и возвращает два значения вида char, и вы можете выполнить повторение по итогу для доступа к каждому элементу: for c in \"Зд\".chars() { println!(\"{c}\");\n} Код напечатает следующее: З\nд Способ bytes возвращает каждый байт, который может быть подходящим в другой предметной области: for b in \"Зд\".bytes() { println!(\"{b}\");\n} Этот код выведет четыре байта, составляющих эту строку: 208\n151\n208\n180 Но делая так, обязательно помните, что валидные одиночные Unicode значения могут состоять более чем из одного байта. Извлечение кластеров графем из строк, как в случае с языком хинди, является сложным, поэтому эта возможность не предусмотрена встроенной библиотекой. На crates.io есть доступные библиотеки, если Вам нужен данный возможности.","breadcrumbs":"Общие собрания » Хранение закодированного текста UTF-8 со строками » Способы для перебора строк","id":"142","title":"Способы для перебора строк"},"143":{"body":"Подводя итог, становится ясно, что строки сложны. Различные языки программирования выполняют различные исходы того, как представить эту сложность для программиста. В Ржавчина решили сделать правильную обработку данных String поведением по умолчанию для всех программ Rust, что означает, что программисты должны заранее продумать обработку UTF-8 данных. Этот соглашение раскрывает большую сложность строк, чем в других языках программирования, но это предотвращает от необходимости обрабатывать ошибки, связанные с не-ASCII символами которые могут появиться в ходе разработки позже. Хорошая новость состоит в том что обычная библиотека предлагает множество полезных возможностей, построенных на основе видов String и &str, чтобы помочь правильно обрабатывать эти сложные случаи. Обязательно ознакомьтесь с документацией для полезных способов, таких как contains для поиска в строке и replace для замены частей строки другой строкой. Давайте переключимся на что-то немного менее сложное: HashMap!","breadcrumbs":"Общие собрания » Хранение закодированного текста UTF-8 со строками » Строки не так просты","id":"143","title":"Строки не так просты"},"144":{"body":"Последняя собрание, которую мы рассмотрим, будет hash map (хеш-карта). Вид HashMap хранит ключи вида K на значения вида V. Данная устройства согласует и хранит данные с помощью функции хеширования . Во множестве языков программирования выполнена данная устройства, но часто с разными наименованиями: такими как hash, map, object, hash table, dictionary или ассоциативный массив. Хеш-карты полезны, когда нужно искать данные не используя порядковый указатель, как это например делается в векторах, а с помощью ключа, который может быть любого вида. Например, в игре вы можете отслеживать счёт каждой приказы в хеш-карте, в которой каждый ключ - это название приказы, а значение - счёт приказы. Имея имя приказы, вы можете получить её счёт из хеш-карты. В этом разделе мы рассмотрим основной API хеш-карт. Остальной набор полезных функций скрывается в объявлении вида HashMap. Как и прежде, советуем обратиться к документации по встроенной библиотеке для получения дополнительной сведений.","breadcrumbs":"Общие собрания » Хранение ключей со связанными значениями в HashMap » Хранение ключей со связанными значениями в HashMap","id":"144","title":"Хранение ключей со связанными значениями в HashMap"},"145":{"body":"Создать пустую хеш-карту можно с помощью new, а добавить в неё элементы - с помощью insert. В приложении 8-20 мы отслеживаем счёт двух приказов, синей Blue и жёлтой Yellow . Синяя приказ набрала 10 очков, а жёлтая приказ - 50. # fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from(\"Blue\"), 10); scores.insert(String::from(\"Yellow\"), 50);\n# } Приложение 8-20: Создание новой хеш-карты и вставка в неё пары ключей и значений Обратите внимание, что нужно сначала указать строку use std::collections::HashMap; для её подключения из собраний встроенной библиотеки. Из трёх собраний данная является наименее используемой, поэтому она не подключается в область видимости функцией самостоятельного подключения (prelude). Хеш-карты также имеют меньшую поддержку со стороны встроенной библиотеки; например, нет встроенного макроса для их разработки. Подобно векторам, хеш-карты хранят свои данные в куче. Здесь вид HashMap имеет в качестве вида ключей String, а в качестве вида значений вид i32. Как и векторы, HashMap однородны: все ключи должны иметь одинаковый вид и все значения должны иметь тоже одинаковый вид.","breadcrumbs":"Общие собрания » Хранение ключей со связанными значениями в HashMap » Создание новой хеш-карты","id":"145","title":"Создание новой хеш-карты"},"146":{"body":"Мы можем получить значение из HashMap по ключу, с помощью способа get, как показано в приложении 8-21. # fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from(\"Blue\"), 10); scores.insert(String::from(\"Yellow\"), 50); let team_name = String::from(\"Blue\"); let score = scores.get(&team_name).copied().unwrap_or(0);\n# } Приложение 8-21: Доступ к очкам приказы \"Blue\", которые хранятся в хеш-карте Здесь score будет иметь количество очков, связанное с приказом \"Blue\", итог будет 10. Способ get возвращает Option<&V>; если для какого-то ключа нет значения в HashMap, get вернёт None. Из-за такого подхода программе следует обрабатывать Option, вызывая copied для получения Option вместо Option<&i32>, затем unwrap_or для установки score в ноль, если scores не содержит данных по этому ключу. Мы можем перебирать каждую пару ключ/значение в HashMap таким же образом, как мы делали с векторами, используя цикл for: # fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from(\"Blue\"), 10); scores.insert(String::from(\"Yellow\"), 50); for (key, value) in &scores { println!(\"{key}: {value}\"); }\n# } Этот код будет печатать каждую пару в произвольном порядке: Yellow: 50\nBlue: 10","breadcrumbs":"Общие собрания » Хранение ключей со связанными значениями в HashMap » Доступ к данным в HashMap","id":"146","title":"Доступ к данным в HashMap"},"147":{"body":"Для видов, которые выполняют особенность Copy, например i32, значения повторяются в HashMap. Для значений со владением, таких как String, значения будут перемещены в хеш-карту и она станет владельцем этих значений, как показано в приложении 8-22. # fn main() { use std::collections::HashMap; let field_name = String::from(\"Favorite color\"); let field_value = String::from(\"Blue\"); let mut map = HashMap::new(); map.insert(field_name, field_value); // field_name and field_value are invalid at this point, try using them and // see what compiler error you get!\n# } Приложение 8-22: Показывает, что ключи и значения находятся во владении HashMap, как только они были вставлены Мы не можем использовать переменные field_name и field_value после того, как их значения были перемещены в HashMap вызовом способа insert. Если мы вставим в HashMap ссылки на значения, то они не будут перемещены в HashMap. Значения, на которые указывают ссылки, должны быть действительными хотя бы до тех пор, пока хеш-карта действительна. Мы поговорим подробнее об этих вопросах в разделе \"Валидация ссылок при помощи времён жизни\" главы 10.","breadcrumbs":"Общие собрания » Хранение ключей со связанными значениями в HashMap » Хеш-карты и владение","id":"147","title":"Хеш-карты и владение"},"148":{"body":"Хотя количество ключей и значений может увеличиваться в HashMap, каждый ключ может иметь только одно значение, связанное с ним в один мгновение времени (обратное утверждение неверно: приказы \"Blue\" и \"Yellow\" могут хранить в хеш-карте scores одинаковое количество очков, например 10). Когда вы хотите изменить данные в хеш-карте, необходимо решить, как обрабатывать случай, когда ключ уже имеет назначенное значение. Можно заменить старое значение новым, полностью пренебрегая старое. Можно сохранить старое значение и пренебрегать новое, или добавлять новое значение, если только ключ ещё не имел значения. Или можно было бы объединить старое значение и новое значение. Давайте посмотрим, как сделать каждый из исходов! Перезапись старых значений Если мы вставим ключ и значение в HashMap, а затем вставим тот же ключ с новым значением, то старое значение связанное с этим ключом, будет заменено на новое. Даже несмотря на то, что код в приложении 8-23 вызывает insert дважды, хеш-карта будет содержать только одну пару ключ/значение, потому что мы вставляем значения для одного и того же ключа - ключа приказы \"Blue\". # fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from(\"Blue\"), 10); scores.insert(String::from(\"Blue\"), 25); println!(\"{scores:?}\");\n# } Приложение 8-23: Замена значения, хранимого в определенном ключе Код напечатает {\"Blue\": 25}. Начальное значение 10 было перезаписано. Вставка значения только в том случае, когда ключ не имеет значения Обычно проверяют, существует ли определенный ключ в хеш-карте со значением, а затем предпринимаются следующие действия: если ключ существует в хеш-карте, существующее значение должно оставаться таким, какое оно есть. Если ключ не существует, то вставляют его и значение для него. Хеш-карты имеют для этого особый API, называемый entry , который принимает ключ для проверки в качестве входного свойства. Возвращаемое значение способа entry - это перечисление Entry, с двумя исходами: первый представляет значение, которое может существовать, а второй говорит о том, что значение отсутствует. Допустим, мы хотим проверить, имеется ли ключ и связанное с ним значение для приказы \"Yellow\". Если хеш-карта не имеет значения для такого ключа, то мы хотим вставить значение 50. То же самое мы хотим проделать и для приказы \"Blue\". Используем API entry в коде приложения 8-24. # fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from(\"Blue\"), 10); scores.entry(String::from(\"Yellow\")).or_insert(50); scores.entry(String::from(\"Blue\")).or_insert(50); println!(\"{scores:?}\");\n# } Приложение 8-24: Использование способа entry для вставки значения только в том случае, когда ключ не имеет значения Способ or_insert определён в Entry так, чтобы возвращать изменяемую ссылку на соответствующее значение ключа внутри исхода перечисления Entry, когда этот ключ существует, а если его нет, то вставлять свойство в качестве нового значения этого ключа и возвращать изменяемую ссылку на новое значение. Эта техника намного чище, чем самостоятельное написание логики и, кроме того, она более безопасна и согласуется с правилами заимствования. При выполнении кода приложения 8-24 будет напечатано {\"Yellow\": 50, \"Blue\": 10}. Первый вызов способа entry вставит ключ для приказы \"Yellow\" со значением 50, потому что для жёлтой приказы ещё не имеется значения в HashMap. Второй вызов entry не изменит хеш-карту, потому что для ключа приказы \"Blue\" уже имеется значение 10. Создание нового значения на основе старого значения Другим распространённым исходом использования хеш-карт является поиск значения по ключу, а затем обновление этого значения на основе старого значения. Например, в приложении 8-25 показан код, который подсчитывает, сколько раз определённое слово встречается в некотором тексте. Мы используем HashMap со словами в качестве ключей и увеличиваем соответствующее слову значение, чтобы отслеживать, сколько раз мы встретили это слово. Если мы впервые встретили слово, то сначала вставляем значение 0. # fn main() { use std::collections::HashMap; let text = \"hello world wonderful world\"; let mut map = HashMap::new(); for word in text.split_whitespace() { let count = map.entry(word).or_insert(0); *count += 1; } println!(\"{map:?}\");\n# } Приложение 8-25: Подсчёт количества вхождений слов с использованием хеш-карты, которая хранит слова и счётчики Этот код напечатает {\"world\": 2, \"hello\": 1, \"wonderful\": 1}. Если вы увидите, что пары ключ/значение печатаются в другом порядке, то вспомните, что мы писали в разделы \"Доступ к данным в HashMap\" , что повторение по хеш-карте происходит в произвольном порядке. Способ split_whitespace возвращает повторитель по срезам строки, разделённых пробелам, для строки text. Способ or_insert возвращает изменяемую ссылку (&mut V) на значение ключа. Мы сохраняем изменяемую ссылку в переменной count, для этого, чтобы присвоить переменной значение, необходимо произвести разыменование с помощью звёздочки (*). Изменяемая ссылка удаляется сразу же после выхода из области видимости цикла for, поэтому все эти изменения безопасны и согласуются с правилами заимствования.","breadcrumbs":"Общие собрания » Хранение ключей со связанными значениями в HashMap » Обновление данных в HashMap","id":"148","title":"Обновление данных в HashMap"},"149":{"body":"По умолчанию HashMap использует функцию хеширования SipHash , которая может противостоять атакам класса отказ в обслуживании, Denial of Service (DoS) с использованием хеш-таблиц siphash . Это не самый быстрый из возможных алгоритмов хеширования, в данном случае производительность идёт на соглашение с обеспечением лучшей безопасности. Если после профилирования вашего кода окажется, что хеш-функция, используемая по умолчанию, очень медленная, вы можете заменить её используя другой hasher. Hasher - это вид, выполняющий особенность BuildHasher. Подробнее о особенностях мы поговорим в Главе 10. Вам совсем не обязательно выполнить свою собственную функцию хеширования; crates.io имеет достаточное количество библиотек, предоставляющих разные выполнения hasher с множеством общих алгоритмов хеширования.","breadcrumbs":"Общие собрания » Хранение ключей со связанными значениями в HashMap » Функция хеширования","id":"149","title":"Функция хеширования"},"15":{"body":"Если вы используете Linux или macOS, пожалуйста, выполните следующую приказ: $ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh Приказ загружает сценарий и запускает установку средства rustup, который устанавливает последнюю безотказную исполнение Rust. Вам может быть предложено ввести пазначение. Если установка прошла успешно, появится следующая строка: Rust is installed now. Great! Вам также понадобится составитель (linker) — программа, которую Ржавчина использует для объединения своих собранных выходных данных в один файл. Скорее всего, он у вас уже есть. При возникновении ошибок объединения, вам следует установить сборщик C, который обычно будет включать в себя и составитель. Сборщик C также полезен, потому что некоторые распространённые дополнения Ржавчина зависят от кода C и нуждаются в сборщике C. На macOS вы можете получить сборщик C, выполнив приказ: $ xcode-select --install Пользователи Linux, как правило, должны устанавливать GCC или Clang в соответствии с документацией их установочного набора. Например, при использовании Ubuntu можно установить дополнение build-essential.","breadcrumbs":"С чего начать » Установка » Установка rustup на Linux или macOS","id":"15","title":"Установка rustup на Linux или macOS"},"150":{"body":"Векторы, строки и хеш-карты предоставят большое количество возможностей для программ, когда необходимо сохранять, получать доступ и изменять данные. Теперь вы готовы решить следующие учебные задания: Есть список целых чисел. Создайте функцию, используйте вектор и верните из списка: среднее значение; медиану (значение элемента из середины списка после его сортировки); режиму списка (mode of list, то значение которое встречается в списке наибольшее количество раз; HashMap будет полезна в данном случае). Преобразуйте строку в кодировку \"поросячьей латыни\" (Pig Latin). Первая согласная каждого слова перемещается в конец и к ней добавляется окончание \"ay\", так \"first\" станет \"irst-fay\". Слову, начинающемуся на гласную, в конец добавляется \"hay\" (\"apple\" становится \"apple-hay\"). Помните о подробностях работы с кодировкой UTF-8! Используя хеш-карту и векторы, создайте текстовый внешняя оболочка позволяющий пользователю добавлять имена сотрудников к названию отдела предприятия. Например, \"Add Sally to Engineering\" или \"Add Amir to Sales\". Затем позвольте пользователю получить список всех людей из отдела или всех людей в предприятия, отсортированных по отделам в алфавитном порядке. Документация API встроенной библиотеки описывает способы у векторов, строк и HashMap. Советуем воспользоваться ей при решении упражнений. Потихоньку мы переходим к более сложным программам, в которых действия могут потерпеть неудачу. Наступило наилучшее время для обсуждения обработки ошибок.","breadcrumbs":"Общие собрания » Хранение ключей со связанными значениями в HashMap » Итоги","id":"150","title":"Итоги"},"151":{"body":"Возникновение ошибок в ходе выполнения программ — это суровая действительность в жизни программного обеспечения, поэтому Ржавчина имеет ряд функций для обработки случаев, в которых что-то идёт не так. Во многих случаях Ржавчина требует, чтобы вы признали возможность ошибки и предприняли некоторые действия, прежде чем ваш код будет собран. Это требование делает вашу программу более надёжной, обеспечивая, что вы обнаружите ошибки и обработаете их надлежащим образом, прежде чем развернёте свой код в производственной среде! В Ржавчина ошибки объединяются на две основные разряды: исправимые (recoverable) и неисправимые (unrecoverable). В случае исправимой ошибки, такой как файл не найден , мы, скорее всего, просто хотим сообщить о неполадке пользователю и повторить действие. Неисправимые ошибки всегда являются симптомами изъянов в коде, например, попытка доступа к ячейке за пределами границ массива, и поэтому мы хотим немедленно остановить программу. Большинство языков не различают эти два вида ошибок и обрабатывают оба вида одинаково, используя такие рычаги, как исключения. В Ржавчина нет исключений. Вместо этого он имеет вид Result для обрабатываемых (исправимых) ошибок и макрос panic!, который останавливает выполнение, когда программа встречает необрабатываемую (неисправимую) ошибку. Сначала эта глава расскажет про вызов panic!, а потом расскажет о возврате значений Result. Кроме того, мы рассмотрим, что нужно учитывать при принятии решения о том, следует ли попытаться исправить ошибку или остановить выполнение.","breadcrumbs":"Обработка ошибок » Обработка ошибок","id":"151","title":"Обработка ошибок"},"152":{"body":"Иногда в коде происходят плохие вещи, и вы ничего не можете с этим поделать. В этих случаях у Ржавчина есть макрос panic! В действительностисуществует два способа вызвать панику: путём выполнения действия, которое вызывает панику в нашем коде (например, обращение к массиву за пределами его размера) или путём явного вызова макроса panic!. В обоих случаях мы вызываем панику в нашей программе. По умолчанию паника выводит сообщение об ошибке, раскручивает и очищает обойма вызовов, и завершают работу. С помощью переменной окружения вы также можете заставить Ржавчина отображать обойма вызовов при возникновении паники, чтобы было легче отследить источник паники.","breadcrumbs":"Обработка ошибок » Неустранимые ошибки с panic! » Неустранимые ошибки с макросом panic!","id":"152","title":"Неустранимые ошибки с макросом panic!"},"153":{"body":"По умолчанию, когда происходит паника, программа начинает этап раскрутки обоймы , означающий в Ржавчина проход обратно по обойме вызовов и очистку данных для каждой обнаруженной функции. Тем не менее, этот обратный проход по обойме и очистка порождают много работы. Ржавчина как иное решение предоставляет вам возможность немедленного прерывания (aborting), которое завершает работу программы без очистки. Память, которую использовала программа, должна быть очищена операционной системой. Если в вашем деле нужно насколько это возможно сделать маленьким исполняемый файл, вы можете переключиться с исхода раскрутки обоймы на исход прерывания при панике, добавьте panic = 'abort' в раздел [profile] вашего Cargo.toml файла. Например, если вы хотите прервать панику в режиме исполнения, добавьте это: [profile.release]\npanic = 'abort' Давайте попробуем вызвать panic! в простой программе: Файл: src/main.rs fn main() { panic!(\"crash and burn\");\n} При запуске программы, вы увидите что-то вроде этого: $ cargo run Compiling panic v0.1.0 (file:///projects/panic) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s Running `target/debug/panic`\nthread 'main' panicked at src/main.rs:2:5:\ncrash and burn\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Выполнение макроса panic! вызывает сообщение об ошибке, содержащееся в двух последних строках. Первая строка показывает сообщение паники и место в исходном коде, где возникла паника: src/main.rs:2:5 указывает, что это вторая строка, пятый символ внутри нашего файла src/main.rs В этом случае указанная строка является частью нашего кода, и если мы перейдём к этой строке, мы увидим вызов макроса panic!. В других случаях вызов panic! мог бы произойти в стороннем коде, который вызывает наш код, тогда имя файла и номер строки для сообщения об ошибке будет из чужого кода, где макрос panic! выполнен, а не из строк нашего кода, которые в конечном итоге привели к выполнению panic!. Мы можем использовать обратную трассировку вызовов функций которые вызвали panic! чтобы выяснить, какая часть нашего кода вызывает неполадку. Мы обсудим обратную трассировку более подробно далее.","breadcrumbs":"Обработка ошибок » Неустранимые ошибки с panic! » Раскручивать обойма или прерывать выполнение программы в ответ на панику?","id":"153","title":"Раскручивать обойма или прерывать выполнение программы в ответ на панику?"},"154":{"body":"Давайте посмотрим на другой пример, где, вызов panic! происходит в сторонней библиотеке из-за ошибки в нашем коде (а не как в примере ранее, из-за вызова макроса нашим кодом напрямую). В приложении 9-1 приведён код, который пытается получить доступ по порядковому указателю в векторе за пределами допустимого рядазначений порядкового указателя. Файл: src/main.rs fn main() { let v = vec![1, 2, 3]; v[99];\n} Приложение 9-1: Попытка доступа к элементу за пределами вектора, которая вызовет panic! Здесь мы пытаемся получить доступ к 100-му элементу вектора (который находится по порядковому указателю 99, потому что упорядочевание начинается с нуля), но вектор имеет только 3 элемента. В этой случаи, Ржавчина будет вызывать панику. Использование [] должно возвращать элемент, но вы передаёте неверный порядковый указатель: не существует элемента, который Ржавчина мог бы вернуть. В языке C, например, попытка прочесть за пределами конца устройства данных (в нашем случае векторе) приведёт к неопределённому поведению, undefined behavior, UB . Вы всё равно получите значение, которое находится в том месте памяти компьютера, которое соответствовало бы этому элементу в векторе, несмотря на то, что память по тому адресу совсем не принадлежит вектору (всё просто: C рассчитал бы место хранения элемента с порядковым указателем 99 и считал бы то, что там хранится, упс). Это называется чтением за пределом буфера, buffer overread, и может привести к уязвимостям безопасности. Если злоумышленник может управлять порядковым указателем таким образом, то у него появляется возможность читать данные, которые он не должен иметь возможности читать. Чтобы защитить вашу программу от такого рода уязвимостей при попытке прочитать элемент с порядковым указателем, которого не существует, Ржавчина остановит выполнение и откажется продолжить работу программы. Давайте попробуем так сделать и посмотрим на поведение Rust: $ cargo run Compiling panic v0.1.0 (file:///projects/panic) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s Running `target/debug/panic`\nthread 'main' panicked at src/main.rs:4:6:\nindex out of bounds: the len is 3 but the index is 99\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Следующая строка говорит, что мы можем установить переменную среды RUST_BACKTRACE, чтобы получить обратную трассировку того, что именно стало причиной ошибки. Обратная трассировка создаёт список всех функций, которые были вызваны до какой-то определённой точки выполнения программы. Обратная трассировка в Ржавчина работает так же, как и в других языках. По этому предлагаем вам читать данные обратной трассировки как и везде - читать сверху вниз, пока не увидите сведения о файлах написанных вами. Это место, где возникла неполадка. Другие строки, которые выше над строками с упоминанием наших файлов, - это код, который вызывается нашим кодом; строки ниже являются кодом, который вызывает наш код. Эти строки могут включать основной код Rust, код встроенной библиотеки или используемые ящики. Давайте попробуем получить обратную трассировку с помощью установки переменной среды RUST_BACKTRACE в любое значение, кроме 0. Приложение 9-2 показывает вывод, подобный тому, что вы увидите. $ RUST_BACKTRACE=1 cargo run\nthread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5\nstack backtrace: 0: rust_begin_unwind at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/panicking.rs:584:5 1: core::panicking::panic_fmt at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:142:14 2: core::panicking::panic_bounds_check at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:84:5 3: >::index at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:242:10 4: core::slice::index:: for [T]>::index at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:18:9 5: as core::ops::index::Index>::index at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/alloc/src/vec/mod.rs:2591:9 6: panic::main at ./src/main.rs:4:5 7: core::ops::function::FnOnce::call_once at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/ops/function.rs:248:5\nnote: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. Приложение 9-2: Обратная трассировка, созданная вызовом panic!, когда установлена переменная окружения RUST_BACKTRACE Тут много вывода! Вывод, который вы увидите, может отличаться от представленного, в зависимости от вашей операционной системы и исполнения Rust. Для того, чтобы получить обратную трассировку с этой сведениями, должны быть включены символы отладки, debug symbols. Символы отладки включены по умолчанию при использовании cargo build или cargo run без флага --release, как у нас в примере. В выводе обратной трассировки приложения 9-2, строка #6 указывает на строку в нашем деле, которая вызывала неполадку: строка 4 из файла src/main.rs. Если мы не хотим, чтобы наша программа запаниковала, мы должны начать исследование с места, на которое указывает первая строка с упоминанием нашего файла. В приложении 9-1, где мы для отображения обратной трассировки сознательно написали код, который паникует, способ исправления паники состоит в том, чтобы не запрашивать элемент за пределами ряда значений порядковых указателей вектора. Когда ваш код запаникует в будущем, вам нужно будет выяснить, какое выполняющееся кодом действие, с какими значениями вызывает панику и что этот код должен делать вместо этого. Мы вернёмся к обсуждению макроса panic!, и того когда нам следует и не следует использовать panic! для обработки ошибок в разделе \"panic! или НЕ panic!\" этой главы. Далее мы рассмотрим, как восстановить выполнение программы после исправляемых ошибок, использующих вид Result.","breadcrumbs":"Обработка ошибок » Неустранимые ошибки с panic! » Использование обратной трассировки panic!","id":"154","title":"Использование обратной трассировки panic!"},"155":{"body":"Многие ошибки являются не настолько критичными, чтобы останавливать выполнение программы. Иногда, когда в функции происходит сбой, необходима просто правильная преобразование и обработка ошибки. К примеру, при попытке открыть файл может произойти ошибка из-за отсутствия файла. Вы, возможно, захотите исправить случай и создать новый файл вместо остановки программы. Вспомните раздел [\"Обработка возможного сбоя с помощью Result\"] главы 2: мы использовали там перечисление Result, имеющее два исхода. Ok и Err для обработки сбоев. Само перечисление определено следующим образом: enum Result { Ok(T), Err(E),\n} Виды T и E являются свойствами обобщённого вида: мы обсудим обобщённые виды более подробно в Главе 10. Все что вам нужно знать прямо сейчас - это то, что T представляет вид значения, которое будет возвращено в случае успеха внутри исхода Ok, а E представляет вид ошибки, которая будет возвращена при сбое внутри исхода Err. Так как вид Result имеет эти обобщённые свойства (generic type parameters), мы можем использовать вид Result и функции, которые определены для него, в разных случаейх, когда вид успешного значение и значения ошибки, которые мы хотим вернуть, отличаются. Давайте вызовем функцию, которая возвращает значение Result, потому что может потерпеть неудачу. В приложении 9-3 мы пытаемся открыть файл. Файл: src/main.rs use std::fs::File; fn main() { let greeting_file_result = File::open(\"hello.txt\");\n} Приложение 9-3: Открытие файла File::open возвращает значения вида Result. Гибкий вид T в выполнения File::open соответствует виду успешно полученного значения, std::fs::File, а именно указателю файла. Вид E, используемый для значения в случае возникновения ошибки, - std::io::Error. Такой возвращаемый вид означает, что вызов File::open может быть успешным и вернуть указатель файла, из которого мы можем читать или в который можем писать. Также вызов функции может завершиться неудачей: например, файл может не существовать, или у нас может не быть разрешения на доступ к файлу. Функция File::open должна иметь способ сообщить нам об успехе или неудаче и в то же время дать нам либо указатель файла, либо сведения об ошибке. Эту возможность как раз и предоставляет перечисление Result. В случае успеха File::open значением переменной greeting_file_result будет образец Ok, содержащий указатель файла. В случае неудачи значение в переменной greeting_file_result будет образцом Err, содержащим дополнительную сведения о том, какая именно ошибка произошла. Необходимо дописать в код приложения 9-3 выполнение разных действий в зависимости от значения, которое вернёт вызов File::open. Приложение 9-4 показывает один из способов обработки Result - пользуясь основным средством языка, таким как выражение match, рассмотренным в Главе 6. Файл: src/main.rs use std::fs::File; fn main() { let greeting_file_result = File::open(\"hello.txt\"); let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => panic!(\"Problem opening the file: {error:?}\"), };\n} Приложение 9-4: Использование выражения match для обработки возвращаемых исходов вида Result Обратите внимание, что также как перечисление Option, перечисление Result и его исходы, входят в область видимости благодаря авто-подключения (prelude), поэтому не нужно указывать Result:: перед использованием исходов Ok и Err в ветках выражения match. Если итогом будет Ok, этот код вернёт значение file из исхода Ok, а мы затем присвоим это значение файлового указателя переменной greeting_file. После match мы можем использовать указатель файла для чтения или записи. Другая ветвь match обрабатывает случай, где мы получаем значение Err после вызова File::open. В этом примере мы решили вызвать макрос panic!. Если в нашей текущей папки нет файла с именем hello.txt и мы выполним этот код, то мы увидим следующее сообщение от макроса panic!: $ cargo run Compiling error-handling v0.1.0 (file:///projects/error-handling) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s Running `target/debug/error-handling`\nthread 'main' panicked at src/main.rs:8:23:\nProblem opening the file: Os { code: 2, kind: NotFound, message: \"No such file or directory\" }\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Как обычно, данное сообщение точно говорит, что пошло не так.","breadcrumbs":"Обработка ошибок » Устранимые ошибки с Result » Исправимые ошибки с Result","id":"155","title":"Исправимые ошибки с Result"},"156":{"body":"Код в приложении 9-4 будет вызывать panic! независимо от того, почему вызов File::open не удался. Однако мы хотим предпринять различные действия для разных причин сбоя. Если открытие File::open не удалось из-за отсутствия файла, мы хотим создать файл и вернуть его указатель. Если вызов File::open не удался по любой другой причине - например, потому что у нас не было прав на открытие файла, то все равно мы хотим вызвать panic! как у нас сделано в приложении 9-4. Для этого мы добавляем выражение внутреннего match, показанное в приложении 9-5. Файл: src/main.rs use std::fs::File;\nuse std::io::ErrorKind; fn main() { let greeting_file_result = File::open(\"hello.txt\"); let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create(\"hello.txt\") { Ok(fc) => fc, Err(e) => panic!(\"Problem creating the file: {e:?}\"), }, other_error => { panic!(\"Problem opening the file: {other_error:?}\"); } }, };\n} Приложение 9-5: Обработка различных ошибок разными способами Видом значения возвращаемого функцией File::open внутри Err исхода является io::Error, устройства из встроенной библиотеки. Данная устройства имеет способ kind, который можно вызвать для получения значения io::ErrorKind. Перечисление io::ErrorKind из встроенной библиотеки имеет исходы, представляющие различные виды ошибок, которые могут появиться при выполнении действий в io. Исход, который мы хотим использовать, это ErrorKind::NotFound, который даёт сведения, о том, что файл который мы пытаемся открыть ещё не существует. Итак, во второй строке мы вызываем сопоставление образца с переменной greeting_file_result и попадаем в ветку с обработкой ошибки, но также у нас есть внутренняя проверка для сопоставления error.kind() ошибки. Условие, которое мы хотим проверить во внутреннем match, заключается в том, является ли значение, возвращаемое error.kind(), исходом NotFound перечисления ErrorKind. Если это так, мы пытаемся создать файл с помощью функции File::create. Однако, поскольку вызов File::create тоже может завершиться ошибкой, нам нужна обработка ещё одной ошибки, теперь уже во внутреннем выражении match. Заметьте: если файл не может быть создан, выводится другое, особое сообщение об ошибке. Вторая же ветка внешнего match (который обрабатывает вызов error.kind()), остаётся той же самой - в итоге программа паникует при любой ошибке, кроме ошибки отсутствия файла.","breadcrumbs":"Обработка ошибок » Устранимые ошибки с Result » Обработка различных ошибок с помощью match","id":"156","title":"Обработка различных ошибок с помощью match"},"157":{"body":"Как много match! Выражение match является очень полезным, но в то же время довольно простым. В главе 13 вы узнаете о замыканиях (closures), которые используются во многих способах вида Result. Эти способы помогают быть более кратким, чем использование match при работе со значениями Result в вашем коде. Например, вот другой способ написать ту же логику, что показана в Приложении 9-5, но с использованием замыканий и способа unwrap_or_else: use std::fs::File;\nuse std::io::ErrorKind; fn main() { let greeting_file = File::open(\"hello.txt\").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create(\"hello.txt\").unwrap_or_else(|error| { panic!(\"Problem creating the file: {:?}\", error); }) } else { panic!(\"Problem opening the file: {:?}\", error); } });\n} Несмотря на то, что данный код имеет такое же поведение как в приложении 9-5, он не содержит ни одного выражения match и проще для чтения. Советуем вам вернуться к примеру этого раздела после того как вы прочитаете Главу 13 и изучите способ unwrap_or_else по документации встроенной библиотеки. Многие из способов о которых вы узнаете в документации и Главе 13 могут очистить код от больших, вложенных выражений match при обработке ошибок.","breadcrumbs":"Обработка ошибок » Устранимые ошибки с Result » Иные использованию match с Result","id":"157","title":"Иные использованию match с Result"},"158":{"body":"Использование match работает достаточно хорошо, но может быть довольно многословным и не всегда хорошо передаёт смысл. Вид Result имеет множество вспомогательных способов для выполнения различных, более отличительных задач. Способ unwrap - это способ быстрого доступа к значениям, выполненный так же, как и выражение match, которое мы написали в Приложении 9-4. Если значение Result является исходом Ok, unwrap возвращает значение внутри Ok. Если Result - исход Err, то unwrap вызовет для нас макрос panic!. Вот пример unwrap в действии: Файл: src/main.rs use std::fs::File; fn main() { let greeting_file = File::open(\"hello.txt\").unwrap();\n} Если мы запустим этот код при отсутствии файла hello.txt , то увидим сообщение об ошибке из вызова panic! способа unwrap: thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os {\ncode: 2, kind: NotFound, message: \"No such file or directory\" }',\nsrc/main.rs:4:49 Другой способ, похожий на unwrap, это expect, позволяющий указать сообщение об ошибке для макроса panic!. Использование expect вместо unwrap с предоставлением хорошего сообщения об ошибке выражает ваше намерение и делает более простым отслеживание источника паники. правила написания способа expect выглядит так: Файл: src/main.rs use std::fs::File; fn main() { let greeting_file = File::open(\"hello.txt\") .expect(\"hello.txt should be included in this project\");\n} expect используется так же как и unwrap: либо возвращается указатель файла либо вызывается макрос panic!.Наше сообщение об ошибке в expect будет передано в panic! и заменит обычное используемое сообщение.Вот как это выглядит: thread 'main' panicked at 'hello.txt should be included in this project: Os {\ncode: 2, kind: NotFound, message: \"No such file or directory\" }',\nsrc/main.rs:5:10 В рабочем коде, большинство выбирает expect в угоду unwrap и добавляет описание, почему действие должна закончиться успешно. Но даже если предположение оказалось неверным, сведений для отладки будет больше.","breadcrumbs":"Обработка ошибок » Устранимые ошибки с Result » Краткие способы обработки ошибок - unwrap и expect","id":"158","title":"Краткие способы обработки ошибок - unwrap и expect"},"159":{"body":"Когда вы пишете функцию, выполнение которой вызывает что-то, что может завершиться ошибкой, вместо обработки ошибки в этой функции, вы можете вернуть ошибку в вызывающий код, чтобы он мог решить, что с ней делать. Такой приём известен как распространение ошибки ( propagating the error ). Благодаря нему мы даём больше управления вызывающему коду, где может быть больше сведений или логики, которая диктует, как ошибка должна обрабатываться, чем было бы в месте появления этой ошибки. Например, код программы 9-6 читает имя пользователя из файла. Если файл не существует или не может быть прочтён, то функция возвращает ошибку в код, который вызвал данную функцию. Файл: src/main.rs use std::fs::File;\nuse std::io::{self, Read}; fn read_username_from_file() -> Result { let username_file_result = File::open(\"hello.txt\"); let mut username_file = match username_file_result { Ok(file) => file, Err(e) => return Err(e), }; let mut username = String::new(); match username_file.read_to_string(&mut username) { Ok(_) => Ok(username), Err(e) => Err(e), }\n} Приложение 9-6: Функция, которая возвращает ошибки в вызывающий код, используя оператор match Эта функция может быть написана гораздо более коротким способом, но мы начнём с того, что многое сделаем вручную, чтобы изучить обработку ошибок; а в конце покажем более короткий способ. Давайте сначала рассмотрим вид возвращаемого значения: Result. Здесь есть возвращаемое значение функции вида Result где образцовый свойство T был заполнен определенным видом String и образцовый свойство E был заполнен определенным видом io::Error. Если эта функция выполнится без неполадок. то код, вызывающий эту функцию, получит значение Ok, содержащее String - имя пользователя, которое эта функция прочитала из файла. Если функция столкнётся с какими-либо неполадками, вызывающий код получит значение Err, содержащее образец io::Error, который включает дополнительную сведения о том, какие сбоев возникли. Мы выбрали io::Error в качестве возвращаемого вида этой функции, потому что это вид значения ошибки, возвращаемого из обеих действий, которые мы вызываем в теле этой функции и которые могут завершиться неудачей: функция File::open и способ read_to_string. Тело функции начинается с вызова File::open. Затем мы обрабатываем значение Result с помощью match, подобно match из приложения 9-4. Если File::open завершается успешно, то указатель файла в переменной образца file становится значением в изменяемой переменной username_file и функция продолжит свою работу. В случае Err, вместо вызова panic!, мы используем ключевое слово return для досрочного возврата из функции и передаём значение ошибки из File::open, которое теперь находится в переменной образца e, обратно в вызывающий код как значение ошибки этой функции. Таким образом, если у нас есть файловый указатель в username_file, функция создаёт новую String в переменной username и вызывает способ read_to_string для файлового указателя в username_file, чтобы прочитать содержимое файла в username. Способ read_to_string также возвращает Result, потому что он может потерпеть неудачу, даже если File::open завершился успешно. Поэтому нам нужен ещё один match для обработки этого Result: если read_to_string завершится успешно, то наша функция сработала, и мы возвращаем имя пользователя из файла, которое теперь находится в username, обёрнутое в Ok. Если read_to_string потерпит неудачу, мы возвращаем значение ошибки таким же образом, как мы возвращали значение ошибки в match, который обрабатывал возвращаемое значение File::open. Однако нам не нужно явно указывать return, потому что это последнее выражение в функции. Затем код, вызывающий этот, будет обрабатывать получение либо значения Ok, содержащего имя пользователя, либо значения Err, содержащего io::Error. Вызывающий код должен решить, что делать с этими значениями. Если вызывающий код получает значение Err, он может вызвать panic! и завершить работу программы, использовать имя пользователя по умолчанию или найти имя пользователя, например, не в файле. У нас недостаточно сведений о том, что на самом деле пытается сделать вызывающий код, поэтому мы распространяем всю сведения об успехах или ошибках вверх, чтобы она могла обрабатываться соответствующим образом. Эта схема передачи ошибок настолько распространена в Rust, что Ржавчина предоставляет оператор вопросительного знака ?, чтобы облегчить эту задачу. Сокращение для проброса ошибок: оператор ? В приложении 9-7 показана выполнение read_username_from_file, которая имеет ту же возможность, что и в приложении 9-6, но в этой выполнения используется оператор ?. Файл: src/main.rs use std::fs::File;\nuse std::io::{self, Read}; fn read_username_from_file() -> Result { let mut username_file = File::open(\"hello.txt\")?; let mut username = String::new(); username_file.read_to_string(&mut username)?; Ok(username)\n} Приложение 9-7: Функция, возвращающая ошибки в вызывающий код с помощью оператора ? Выражение ?, расположенное после Result, работает почти так же, как и те выражения match, которые мы использовали для обработки значений Result в приложении 9-6. Если в качестве значения Result будет Ok, то значение внутри Ok будет возвращено из этого выражения, и программа продолжит работу. Если же значение представляет собой Err, то Err будет возвращено из всей функции, как если бы мы использовали ключевое слово return, так что значение ошибки будет передано в вызывающий код. Существует разница между тем, что делает выражение match из приложения 9-6 и тем, что делает оператор ?: значения ошибок, для которых вызван оператор ?, проходят через функцию from, определённую в особенности From встроенной библиотеки, которая используется для преобразования значений из одного вида в другой. Когда оператор ? вызывает функцию from, полученный вид ошибки преобразуется в вид ошибки, определённый в возвращаемом виде текущей функции. Это полезно, когда функция возвращает только один вид ошибки, для описания всех возможных исходов сбоев, даже если её отдельные составляющие могут выходить из строя по разным причинам. Например, мы могли бы изменить функцию read_username_from_file в приложении 9-7, чтобы возвращать пользовательский вид ошибки с именем OurError, который мы определим. Если мы также определим impl From for OurError для создания образца OurError из io::Error, то оператор ?, вызываемый в теле read_username_from_file, вызовет from и преобразует виды ошибок без необходимости добавления дополнительного кода в функцию. В случае приложения 9-7 оператор ? в конце вызова File::open вернёт значение внутри Ok в переменную username_file. Если произойдёт ошибка, оператор ? выполнит ранний возврат значения Err вызывающему коду. То же самое относится к оператору ? в конце вызова read_to_string. Оператор ? позволяет избавиться от большого количества образцового кода и упростить выполнение этой функции. Мы могли бы даже ещё больше сократить этот код, если бы использовали цепочку вызовов способов сразу после ?, как показано в приложении 9-8. Файл: src/main.rs use std::fs::File;\nuse std::io::{self, Read}; fn read_username_from_file() -> Result { let mut username = String::new(); File::open(\"hello.txt\")?.read_to_string(&mut username)?; Ok(username)\n} Приложение 9-8: Цепочка вызовов способов после оператора ? Мы перенесли создание новой String в username в начало функции; эта часть не изменилась. Вместо создания переменной username_file мы соединили вызов read_to_string непосредственно с итогом File::open(\"hello.txt\")?. У нас по-прежнему есть ? в конце вызова read_to_string, и мы по-прежнему возвращаем значение Ok, содержащее username, когда и File::open и read_to_string завершаются успешно, а не возвращают ошибки. Возможность снова такая же, как в Приложении 9-6 и Приложении 9-7; это просто другой, более удобный способ её написания. Продолжая рассматривать разные способы записи данной функции, приложение 9-9 отображает способ сделать её ещё короче с помощью fs::read_to_string. Файл: src/main.rs use std::fs;\nuse std::io; fn read_username_from_file() -> Result { fs::read_to_string(\"hello.txt\")\n} Приложение 9-9: Использование fs::read_to_string вместо открытия и последующего чтения файла Чтение файла в строку довольно распространённая действие, так что обычная библиотека предоставляет удобную функцию fs::read_to_string, которая открывает файл, создаёт новую String, читает содержимое файла, размещает его в String и возвращает её. Конечно, использование функции fs::read_to_string не даёт возможности объяснить обработку всех ошибок, поэтому мы сначала изучили длинный способ. Где можно использовать оператор ? Оператор ? может использоваться только в функциях, вид возвращаемого значения которых совместим со значением, для которого используется ?. Это потому, что оператор ? определён для выполнения раннего возврата значения из функции таким же образом, как и выражение match, которое мы определили в приложении 9-6. В приложении 9-6 match использовало значение Result, а ответвление с ранним возвратом вернуло значение Err(e). Вид возвращаемого значения функции должен быть Result, чтобы он был совместим с этим return. В приложении 9-10 давайте посмотрим на ошибку, которую мы получим, если воспользуемся оператором ? в функции main с видом возвращаемого значения, несовместимым с видом значения, для которого мы используем ?: Файл: src/main.rs use std::fs::File; fn main() { let greeting_file = File::open(\"hello.txt\")?;\n} Приложение 9-10: Попытка использовать ? в main функции, которая возвращает () , не будет собираться Этот код открывает файл, что может привести к сбою. ? оператор следует за значением Result , возвращаемым File::open , но эта main функция имеет возвращаемый вид () , а не Result . Когда мы собираем этот код, мы получаем следующее сообщение об ошибке: $ cargo run Compiling error-handling v0.1.0 (file:///projects/error-handling)\nerror[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`) --> src/main.rs:4:48 |\n3 | fn main() { | --------- this function should return `Result` or `Option` to accept `?`\n4 | let greeting_file = File::open(\"hello.txt\")?; | ^ cannot use the `?` operator in a function that returns `()` | = help: the trait `FromResidual>` is not implemented for `()` For more information about this error, try `rustc --explain E0277`.\nerror: could not compile `error-handling` (bin \"error-handling\") due to 1 previous error Эта ошибка указывает на то, что оператор ? разрешено использовать только в функции, которая возвращает Result, Option или другой вид, выполняющий FromResidual. Для исправления ошибки есть два исхода. Первый - изменить возвращаемый вид вашей функции так, чтобы он был совместим со значением, для которого вы используете оператор ?, если у вас нет ограничений, препятствующих этому. Другой способ - использовать match или один из способов Result для обработки Result любым подходящим способом. В сообщении об ошибке также упоминалось, что ? можно использовать и со значениями Option. Как и при использовании ? для Result, вы можете использовать ? только для Option в функции, которая возвращает Option. Поведение оператора ? при вызове Option похоже на его поведение при вызове Result: если значение равно None, то None будет возвращено раньше из функции в этот мгновение. Если значение Some, значение внутри Some является результирующим значением выражения, и функция продолжает исполняться. В приложении 9-11 приведён пример функции, которая находит последний символ первой строки заданного текста: fn last_char_of_first_line(text: &str) -> Option { text.lines().next()?.chars().last()\n}\n# # fn main() {\n# assert_eq!(\n# last_char_of_first_line(\"Hello, world\\nHow are you today?\"),\n# Some('d')\n# );\n# # assert_eq!(last_char_of_first_line(\"\"), None);\n# assert_eq!(last_char_of_first_line(\"\\nhi\"), None);\n# } Приложение 9-11: Использование оператора ? для значения Option Эта функция возвращает Option, потому что возможно, что там есть символ, но также возможно, что его нет. Этот код принимает переменная среза text строки и вызывает для него способ lines, который возвращает повторитель для строк в строке. Поскольку эта функция хочет проверить первую строку, она вызывает next у повторителя, чтобы получить первое значение от повторителя. Если text является пустой строкой, этот вызов next вернёт None, и в этом случае мы используем ? чтобы остановить и вернуть None из last_char_of_first_line. Если text не является пустой строкой, next вернёт значение Some, содержащее отрывок строки первой строки в text. Символ ? извлекает отрывок строки, и мы можем вызвать chars для этого отрывка строки. чтобы получить повторитель символов. Нас важно последний символ в первой строке, поэтому мы вызываем last, чтобы вернуть последний элемент в повторителе. Вернётся Option, потому что возможно, что первая строка пустая - например, если text начинается с пустой строки, но имеет символы в других строках, как в \"\\nhi\". Однако, если в первой строке есть последний символ, он будет возвращён в исходе Some. Оператор ? в середине даёт нам краткий способ выразить эту логику, позволяя выполнить функцию в одной строке. Если бы мы не могли использовать оператор ? в Option, нам пришлось бы выполнить эту логику, используя больше вызовов способов или выражение match. Обратите внимание, что вы можете использовать оператор ? Result в функции, которая возвращает Result , и вы можете использовать оператор ? для Option в функции, которая возвращает Option , но вы не можете смешивать и сопоставлять. Оператор ? не будет самостоятельно преобразовывать Result в Option или наоборот; в этих случаях вы можете использовать такие способы, как способ ok для Result или способ ok_or для Option, чтобы выполнить преобразование явно. До сих пор все функции main, которые мы использовали, возвращали (). Функция main - особенная, потому что это точка входа и выхода исполняемых программ, и существуют ограничения на вид возвращаемого значения, чтобы программы вели себя так, как ожидается. К счастью, main также может возвращать Result<(), E> . В приложении 9-12 используется код из приложения 9-10, но мы изменили возвращаемый вид main на Result<(), Box> и добавили возвращаемое значение Ok(()) в конец. Теперь этот код будет собран: use std::error::Error;\nuse std::fs::File; fn main() -> Result<(), Box> { let greeting_file = File::open(\"hello.txt\")?; Ok(())\n} Приложение 9-12: Замена main на return Result<(), E> позволяет использовать оператор ? оператор над значениями Result Вид Box является особенность-предметом , о котором мы поговорим в разделе \"Использование особенность-предметов, допускающих значения разных видов\" в главе 17. Пока что вы можете считать, что Box означает \"любой вид ошибки\". Использование ? для значения Result в функции main с видом ошибки Box разрешено, так как позволяет вернуть любое значение Err раньше времени. Даже если тело этой функции main будет возвращать только ошибки вида std::io::Error, указав Box, эта ярлык останется правильной, даже если в тело main будет добавлен код, возвращающий другие ошибки. Когда main функция возвращает Result<(), E>, исполняемый файл завершится со значением 0, если main вернёт Ok(()), и выйдет с ненулевым значением, если main вернёт значение Err. Исполняемые файлы, написанные на C, при выходе возвращают целые числа: успешно завершённые программы возвращают целое число 0, а программы с ошибкой возвращают целое число, отличное от 0. Ржавчина также возвращает целые числа из исполняемых файлов, чтобы быть совместимым с этим соглашением. Функция main может возвращать любые виды, выполняющие особенность std::process::Termination , в которых имеется функция report, возвращающая ExitCode. Обратитесь к документации встроенной библиотеки за дополнительной сведениями о порядке выполнения особенности Termination для ваших собственных видов. Теперь, когда мы обсудили подробности вызова panic! или возврата Result, давайте вернёмся к тому, как решить, какой из случаев подходит для какой случаи.","breadcrumbs":"Обработка ошибок » Устранимые ошибки с Result » Проброс ошибок","id":"159","title":"Проброс ошибок"},"16":{"body":"На Windows перейдите по адресу https://www.rust-lang.org/tools/install и следуйте указаниям по установке Rust. На определённом этапе установки вы получите сообщение, предупреждающее, что вам также понадобятся средства сборки MSVC для Visual Studio 2013 или более поздней исполнения. Чтобы получить средства сборки, вам потребуется установить Visual Studio 2022 . На вопрос о том, какие составляющие необходимо установить, выберите: “Desktop Development with C++” The Windows 10 or 11 SDK Английский языковой дополнение вместе с любым другим языковым дополнением по вашему выбору. В остальной части этой книги используются приказы, которые работают как в cmd.exe , так и в PowerShell. При наличии отличительных различий мы объясним, что необходимо сделать в таких случаях.","breadcrumbs":"С чего начать » Установка » Установка rustup на Windows","id":"16","title":"Установка rustup на Windows"},"160":{"body":"Итак, как принимается решение о том, когда следует вызывать panic!, а когда вернуть Result? При панике код не имеет возможности восстановить своё выполнение. Можно было бы вызывать panic! для любой ошибочной случаи, независимо от того, имеется ли способ восстановления или нет, но с другой стороны, вы принимаете решение от имени вызывающего вас кода, что случаей необратима. Когда вы возвращаете значение Result, вы делегируете принятие решения вызывающему коду. Вызывающий код может попытаться выполнить восстановление способом, который подходит в данной случаи, или же он может решить, что из ошибки в Err нельзя восстановиться и вызовет panic!, превратив вашу исправимую ошибку в неисправимую. Поэтому возвращение Result является хорошим выбором по умолчанию для функции, которая может дать сбой. В таких случаей как примеры, протовиды и проверки, более уместно писать код, который паникует вместо возвращения Result. Давайте рассмотрим почему, а затем мы обсудим случаи, в которых сборщик не может доказать, что ошибка невозможна, но вы, как человек, можете это сделать. Глава будет заканчиваться некоторыми общими руководящими принципами о том, как решить, стоит ли паниковать в коде библиотеки.","breadcrumbs":"Обработка ошибок » panic! или Не panic! » panic! или не panic!","id":"160","title":"panic! или не panic!"},"161":{"body":"Когда вы пишете пример, отображающий некоторую подход, наличие хорошего кода обработки ошибок может сделать пример менее понятным. Понятно, что в примерах вызов способа unwrap, который может привести к панике, является лишь обозначением способа обработки ошибок в приложении, который может отличаться в зависимости от того, что делает остальная часть кода. Точно так же способы unwrap и expect являются очень удобными при создании протовида, прежде чем вы будете готовы решить, как обрабатывать ошибки. Они оставляют чёткие отступыв коде до особенности, когда вы будете готовы сделать программу более надёжной. Если в проверке происходит сбой при вызове способа, то вы бы хотели, чтобы весь проверка не прошёл, даже если этот способ не является проверяемой возможностью. Поскольку вызов panic! это способ, которым проверка помечается как провалившийся, использование unwrap или expect - именно то, что нужно.","breadcrumbs":"Обработка ошибок » panic! или Не panic! » Примеры, прототипирование и проверки","id":"161","title":"Примеры, прототипирование и проверки"},"162":{"body":"Также было бы целесообразно вызывать unwrap или expect когда у вас есть какая-то другая логика, которая заверяет, что Result будет иметь значение Ok, но вашу логику не понимает сборщик. У вас по-прежнему будет значение Result которое нужно обработать: любая действие, которую вы вызываете, все ещё имеет возможность неудачи в целом, хотя это логически невозможно в вашей именно случаи. Если, проверяя код вручную, вы можете убедиться, что никогда не будет исход с Err, то вполне допустимо вызывать unwrap, а ещё лучше задокументировать причину, по которой, по вашему мнению, у вас никогда не будет исхода Err в тексте expect. Вот пример: # fn main() { use std::net::IpAddr; let home: IpAddr = \"127.0.0.1\" .parse() .expect(\"Hardcoded IP address should be valid\");\n# } Мы создаём образец IpAddr, анализируя жёстко закодированную строку. Можно увидеть, что 127.0.0.1 является действительным IP-адресом, поэтому здесь допустимо использование expect. Однако наличие жёстко закодированной допустимой строки не меняет вид возвращаемого значения способа parse: мы все ещё получаем значение Result и сборщик все также заставляет нас обращаться с Resultтак, будто возможен исход Err, потому что сборщик недостаточно умён, чтобы увидеть, что эта строка всегда действительный IP-адрес. Если строка IP-адреса пришла от пользователя, то она не является жёстко запрограммированной в программе и, следовательно, может привести к ошибке, мы определённо хотели бы обработать Result более надёжным способом. Упоминание предположения о том, что этот IP-адрес жёстко закодирован, побудит нас изменить expect для лучшей обработки ошибок, если в будущем нам потребуется вместо этого получить IP-адрес из какого-либо другого источника.","breadcrumbs":"Обработка ошибок » panic! или Не panic! » Случаи, в которых у вас больше сведений, чем у сборщика","id":"162","title":"Случаи, в которых у вас больше сведений, чем у сборщика"},"163":{"body":"Желательно, чтобы код паниковал, если он может оказаться в неправильном состоянии. В этом среде неправильное состояние это когда некоторое допущение, заверение, договор или неизменная величина были нарушены. Например, когда недопустимые, противоречивые или пропущенные значения передаются в ваш код - плюс один или несколько пунктов из следующего перечисленного в списке: Неправильное состояние — это что-то неожиданное, отличается от того, что может происходить время от времени, например, когда пользователь вводит данные в неправильном виде. Ваш код после этой точки должен полагаться на то, что он не находится в неправильном состоянии, вместо проверок наличия сбоев на каждом этапе. Нет хорошего способа закодировать данную сведения в видах, которые вы используете. Мы рассмотрим пример того, что мы имеем в виду в разделе “Кодирование состояний и поведения на основе видов” главы 17. Если кто-то вызывает ваш код и передаёт значения, которые не имеют смысла, лучше всего вернуть ошибку, если вы это можете, чтобы пользователь библиотеки мог решить, что он хочет делать в этом случае. Однако в тех случаях, когда продолжение выполнения программы может быть небезопасным или вредным, лучшим выбором будет вызов panic! и оповещение пользователя, использующего вашу библиотеку, об ошибке в его коде, чтобы он мог исправить её во время разработки. Подобно panic! подходит, если вы вызываете внешний, неподуправлениеный вам код, и он возвращает недопустимое состояние, которое вы не можете исправить. Однако, когда ожидается сбой, лучше вернуть Result, чем выполнить вызов panic!. В качестве примера можно привести синтаксический анализатор, которому передали неправильно созданные данные, или HTTP-запрос, возвращающий значение указывающий на то, что вы достигли ограничения на частоту запросов. В этих случаях возврат Result означает, что ошибка является ожидаемой и вызывающий код должен решить, как её обрабатывать. Когда ваш код выполняет действие, которая может подвергнуть пользователя риску, если она вызывается с использованием недопустимых значений, ваш код должен сначала проверить допустимость значений и паниковать, если значения недопустимы. Так советуется делать в основном из соображений безопасности: попытка оперировать неправильными данными может привести к уязвимостям. Это основная причина, по которой обычная библиотека будет вызывать panic!, если попытаться получить доступ к памяти вне границ массива: доступ к памяти, не относящейся к текущей устройстве данных, является известной неполадкой безопасности. Функции часто имеют договоры: их поведение обеспечивается, только если входные данные отвечают определённым требованиям. Паника при нарушении договора имеет смысл, потому что это всегда указывает на изъян со стороны вызывающего кода, и это не ошибка, которую вы хотели бы, чтобы вызывающий код явно обрабатывал. На самом деле, нет разумного способа для восстановления вызывающего кода; программисты, вызывающие ваш код, должны исправить свой. Договоры для функции, особенно когда нарушение вызывает панику, следует описать в документации по API функции. Тем не менее, наличие множества проверок ошибок во всех ваших функциях было бы многословным и раздражительным. К счастью, можно использовать систему видов Ржавчина (следовательно и проверку видов сборщиком), чтобы она сделала множество проверок вместо вас. Если ваша функция имеет определённый вид в качестве свойства, вы можете продолжить работу с логикой кода зная, что сборщик уже обеспечил правильное значение. Например, если используется обычный вид, а не вид Option, то ваша программа ожидает наличие чего-то вместо ничего . Ваш код не должен будет обрабатывать оба исхода Some и None: он будет иметь только один исход для определённого значения. Код, пытающийся ничего не передавать в функцию, не будет даже собираться, поэтому ваша функция не должна проверять такой случай во время выполнения. Другой пример - это использование целого вида без знака, такого как u32, который заверяет, что свойство никогда не будет отрицательным.","breadcrumbs":"Обработка ошибок » panic! или Не panic! » Руководство по обработке ошибок","id":"163","title":"Руководство по обработке ошибок"},"164":{"body":"Давайте разовьём мысль использования системы видов Ржавчина чтобы убедиться, что у нас есть правильное значение, и рассмотрим создание пользовательского вида для валидации. Вспомним игру угадывания числа из Главы 2, в которой наш код просил пользователя угадать число между 1 и 100. Мы никогда не проверяли, что предположение пользователя лежит между этими числами, перед сравнением предположения с загаданным нами числом; мы только проверяли, что оно положительно. В этом случае последствия были не очень страшными: наши сообщения «Слишком много» или «Слишком мало», выводимые в окно вывода, все равно были правильными. Но было бы лучше подталкивать пользователя к правильным догадкам и иметь различное поведение для случаев, когда пользователь предлагает число за пределами ряда, и когда пользователь вводит, например, буквы вместо цифр. Один из способов добиться этого - пытаться разобрать введённое значение как i32, а не как u32, чтобы разрешить возможно отрицательные числа, а затем добавить проверку для нахождение числа в ряде, например, так: # use rand::Rng;\n# use std::cmp::Ordering;\n# use std::io;\n# # fn main() {\n# println!(\"Guess the number!\");\n# # let secret_number = rand::thread_rng().gen_range(1..=100);\n# loop { // --snip-- # println!(\"Please input your guess.\");\n# # let mut guess = String::new();\n# # io::stdin()\n# .read_line(&mut guess)\n# .expect(\"Failed to read line\");\n# let guess: i32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; if guess < 1 || guess > 100 { println!(\"The secret number will be between 1 and 100.\"); continue; } match guess.cmp(&secret_number) { // --snip--\n# Ordering::Less => println!(\"Too small!\"),\n# Ordering::Greater => println!(\"Too big!\"),\n# Ordering::Equal => {\n# println!(\"You win!\");\n# break;\n# }\n# } }\n# } Выражение if проверяет, находится ли наше значение вне ряда, сообщает пользователю о неполадке и вызывает continue, чтобы начать следующую повторение цикла и попросить ввести другое число. После выражения if мы можем продолжить сравнение значения guess с загаданным числом, зная, что guess лежит в ряде от 1 до 100. Однако это не наилучшее решение: если бы было чрезвычайно важно, чтобы программа работала только со значениями от 1 до 100, существовало бы много функций, требующих этого, то такая проверка в каждой функции была бы утомительной (и могла бы отрицательно повлиять на производительность). Вместо этого можно создать новый вид и поместить проверки в функцию создания образца этого вида, не повторяя их везде. Таким образом, функции могут использовать новый вид в своих ярлыках и быть уверены в значениях, которые им передают. Приложение 9-13 показывает один из способов, как определить вид Guess, чтобы образец Guess создавался только при условии, что функция new получает значение от 1 до 100. Приложение 9-13. Вид Guess, который будет создавать образцы только для значений от 1 до 100 Сначала мы определяем устройство с именем Guess, которая имеет поле с именем value вида i32, в котором будет храниться число. Затем мы выполняем сопряженную функцию new, создающую образцы значений вида Guess. Функция new имеет один свойство value вида i32, и возвращает Guess. Код в теле функции new проверяет, что значение value находится между 1 и 100. Если value не проходит эту проверку, мы вызываем panic!, которая оповестит программиста, написавшего вызывающий код, что в его коде есть ошибка, которую необходимо исправить, поскольку попытка создания Guess со значением value вне заданного ряда нарушает договор, на который полагается Guess::new. Условия, в которых Guess::new паникует, должны быть описаны в документации к API; мы рассмотрим соглашения о документации, указывающие на возможность появления panic! в документации API, которую вы создадите в Главе 14. Если value проходит проверку, мы создаём новый образец Guess, у которого значение поля value равно значению свойства value, и возвращаем Guess. Затем мы выполняем способ с названием value, который заимствует self, не имеет других свойств, и возвращает значение вида i32. Этот способ иногда называют извлекатель (getter), потому что его цель состоит в том, чтобы извлечь данные из полей устройства и вернуть их. Этот открытый способ является необходимым, поскольку поле value устройства Guess является закрытым. Важно, чтобы поле value было закрытым, чтобы код, использующий устройство Guess, не мог устанавливать value напрямую: код снаружи звена должен использовать функцию Guess::new для создания образца Guess, таким образом обеспечивая, что у Guess нет возможности получить value, не проверенное условиями в функции Guess::new. Функция, которая принимает или возвращает только числа от 1 до 100, может объявить в своей ярлыке, что она принимает или возвращает Guess, вместо i32, таким образом не будет необходимости делать дополнительные проверки в теле такой функции.","breadcrumbs":"Обработка ошибок » panic! или Не panic! » Создание пользовательских видов для проверки","id":"164","title":"Создание пользовательских видов для проверки"},"165":{"body":"Функции обработки ошибок в Ржавчина призваны помочь написанию более надёжного кода. Макрос panic! указывает , что ваша программа находится в состоянии, которое она не может обработать, и позволяет сказать этапу чтобы он прекратил своё выполнение, вместо попытки продолжить выполнение с неправильными или неверными значениями. Перечисление Result использует систему видов Rust, чтобы сообщить, что действия могут завершиться неудачей, и ваш код мог восстановиться. Можно использовать Result, чтобы сообщить вызывающему коду, что он должен обрабатывать вероятный успех или вероятную неудачу. Использование panic! и Result правильным образом сделает ваш код более надёжным перед лицом неизбежных неполадок. Теперь, когда вы увидели полезные способы использования обобщённых видов Option и Result в встроенной библиотеке, мы поговорим о том, как работают обобщённые виды и как вы можете использовать их в своём коде.","breadcrumbs":"Обработка ошибок » panic! или Не panic! » Итоги","id":"165","title":"Итоги"},"166":{"body":"Каждый язык программирования имеет в своём арсенале эффективные средства борьбы с повторением кода. В Ржавчина одним из таких средств являются обобщённые виды данных - generics . Это абстрактные подставные виды на место которых возможно поставить какой-либо определенный вид или другое свойство. Когда мы пишем код, мы можем выразить поведение обобщённых видов или их связь с другими обобщёнными видами, не зная какой вид будет использован на их месте при сборки и запуске кода. Функции могут принимать свойства некоторого \"обобщённого\" вида вместо привычных \"определенных\" видов, вроде i32 или String. Подобно, функция принимает свойства с неизвестными заранее значениями, чтобы выполнять одинаковые действия над несколькими определенными значениями. На самом деле мы уже использовали обобщённые виды данных в Главе 6 (Option), в Главе 8 (Vec и HashMap) и в Главе 9 (Result). В этой главе вы узнаете, как определить собственные виды данных, функции и способы, используя возможности обобщённых видов. Прежде всего, мы рассмотрим как для уменьшения повторения извлечь из кода некоторую общую возможность. Далее, мы будем использовать тот же рычаг для создания обобщённой функции из двух функций, которые отличаются только видом их свойств. Мы также объясним, как использовать обобщённые виды данных при определении устройств и перечислений. После этого мы изучим как использовать особенности (traits) для определения поведения в обобщённом виде. Можно соединенять особенности с обобщёнными видами, чтобы обобщённый вид мог принимать только такие виды, которые имеют определённое поведение, а не все подряд. В конце мы обсудим времена жизни (lifetimes) , вариации обобщённых видов, которые дают сборщику сведения о том, как сроки жизни ссылок относятся друг к другу. Времена жизни позволяют нам указать дополнительную сведения об \"одолженных\" (borrowed) значениях, которая позволит сборщику удостовериться в соблюдения правил используемых ссылок в тех случаейх, когда сборщик не может сделать это самостоятельно .","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Обобщённые виды, особенности и время жизни","id":"166","title":"Обобщённые виды, особенности и время жизни"},"167":{"body":"В обобщениях мы можем заменить определенный вид на \"заполнитель\" (placeholder), обозначающую несколько видов, что позволяет удалить повторяющийся код. Прежде чем углубляться в правила написания обобщённых видов, давайте сначала посмотрим, как удалить повторение, не задействуя гибкие виды, путём извлечения функции, которая заменяет определённые значения заполнителем, представляющим несколько значений. Затем мы применим ту же технику для извлечения гибкой функции! Изучив, как распознать повторяющийся код, который можно извлечь в функцию, вы начнёте распознавать повторяющийся код, который может использовать обобщённые виды. Начнём с короткой программы в приложении 10-1, которая находит наибольшее число в списке. Файл: src/main.rs fn main() { let number_list = vec![34, 50, 25, 100, 65]; let mut largest = &number_list[0]; for number in &number_list { if number > largest { largest = number; } } println!(\"The largest number is {largest}\");\n# assert_eq!(*largest, 100);\n} Приложение 10-1: Поиск наибольшего числа в списке чисел Сохраним список целых чисел в переменной number_list и поместим первое значение из списка в переменную largest. Далее, переберём все элементы списка, и, если текущий элемент больше числа сохранённого в переменной largest, заменим значение в этой переменной. Если текущий элемент меньше или равен \"наибольшему\", найденному ранее, значение переменной оставим прежним и перейдём к следующему элементу списка. После перебора всех элементов списка переменная largest должна содержать наибольшее значение, которое в нашем случае будет равно 100. Теперь перед нами стоит задача найти наибольшее число в двух разных списках. Для этого мы можем повторять код из приложения 10-1 и использовать ту же логику в двух разных местах программы, как показано в приложении 10-2. Файл: src/main.rs fn main() { let number_list = vec![34, 50, 25, 100, 65]; let mut largest = &number_list[0]; for number in &number_list { if number > largest { largest = number; } } println!(\"The largest number is {largest}\"); let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8]; let mut largest = &number_list[0]; for number in &number_list { if number > largest { largest = number; } } println!(\"The largest number is {largest}\");\n} Приложение 10-2: Код для поиска наибольшего числа в двух списках чисел Несмотря на то, что код программы работает, повторение кода утомительно и подвержено ошибкам. При внесении изменений мы должны не забыть обновить каждое место, где код повторяется. Для устранения повторения мы можем создать дополнительную абстракцию с помощью функции которая сможет работать с любым списком целых чисел переданным ей в качестве входного свойства и находить для этого списка наибольшее число. Данное решение делает код более ясным и позволяет абстрактным образом выполнить алгоритм поиска наибольшего числа в списке. В приложении 10-3 мы извлекаем код, который находит наибольшее число, в функцию с именем largest. Затем мы вызываем функцию, чтобы найти наибольшее число в двух списках из приложения 10-2. Мы также можем использовать эту функцию для любого другого списка значений i32 , который может встретиться позже. Файл: src/main.rs fn largest(list: &[i32]) -> &i32 { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest\n} fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!(\"The largest number is {result}\");\n# assert_eq!(*result, 100); let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8]; let result = largest(&number_list); println!(\"The largest number is {result}\");\n# assert_eq!(*result, 6000);\n} Приложение 10-3: Абстрактный код для поиска наибольшего числа в двух списках Функция largest имеет свойство с именем list, который представляет любой срез значений вида i32, которые мы можем передать в неё. В итоге вызова функции, код выполнится с определенными, переданными в неё значениями. Итак, вот шаги выполненные для изменения кода из приложения 10-2 в приложение 10-3: Определить повторяющийся код. Извлечь повторяющийся код и поместить его в тело функции, определив входные и выходные значения этого кода в ярлыке функции. Обновить и заменить два участка повторяющегося кода вызовом одной функции. Далее, чтобы уменьшить повторение кода, мы воспользуемся теми же шагами для обобщённых видов. Обобщённые виды позволяют работать над абстрактными видами таким же образом, как тело функции может работать над абстрактным списком list вместо определенных значений. Например, у нас есть две функции: одна ищет наибольший элемент внутри среза значений вида i32, а другая внутри среза значений вида char. Как уменьшить такое повторение? Давайте выяснять!","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Удаление повторения кода с помощью выделения общей возможности","id":"167","title":"Удаление повторения кода с помощью выделения общей возможности"},"168":{"body":"Мы используем обобщённые виды данных для объявления функций или устройств, которые затем можно использовать с различными определенными видами данных. Давайте сначала посмотрим, как объявлять функции, устройства, перечисления и способы, используя обобщённые виды данных. Затем мы обсудим, как обобщённые виды данных влияют на производительность кода.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Обобщённые виды данных » Обобщённые виды данных","id":"168","title":"Обобщённые виды данных"},"169":{"body":"Когда мы объявляем функцию с обобщёнными видами, мы размещаем обобщённые виды в ярлыке функции, где мы обычно указываем виды данных переменных и возвращаемого значения. Используя обобщённые виды, мы делаем код более гибким и предоставляем большую возможность при вызове нашей функции, предотвращая повторение кода. Рассмотрим пример с функцией largest. Приложение 10-4 показывает две функции, каждая из которых находит самое большое значение в срезе своего вида. Позже мы объединим их в одну функцию, использующую обобщённые виды данных. Файл: src/main.rs fn largest_i32(list: &[i32]) -> &i32 { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest\n} fn largest_char(list: &[char]) -> &char { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest\n} fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest_i32(&number_list); println!(\"The largest number is {result}\");\n# assert_eq!(*result, 100); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest_char(&char_list); println!(\"The largest char is {result}\");\n# assert_eq!(*result, 'y');\n} Приложение 10-4: две функции, отличающиеся только именем и видом обрабатываемых данных Функция largest_i32 уже встречалась нам: мы извлекли её в приложении 10-3, когда боролись с повторением кода — она находит наибольшее значение вида i32 в срезе. Функция largest_char находит самое большое значение вида char в срезе. Тело у этих функций одинаковое, поэтому давайте избавимся от повторяемлшл кода, используя свойство обобщённого вида в одной функции. Для свойствоизации видов данных в новой объявляемой функции нам нужно дать имя обобщённому виду — так же, как мы это делаем для переменных функций. Можно использовать любой определитель для имени свойства вида, но мы будем использовать T, потому что по соглашению имена свойств в Ржавчина должны быть короткими (обычно длиной в один символ), а именование видов в Ржавчина делается в наставлении UpperCamelCase. Сокращение слова «type» до одной буквы T является обычным выбором большинства программистов, использующих язык Rust. Когда мы используем свойство в теле функции, мы должны объявить имя свойства в ярлыке, чтобы сборщик знал, что означает это имя. Подобно когда мы используем имя вида свойства в ярлыке функции, мы должны объявить это имя раньше, чем мы его используем. Чтобы определить обобщённую функцию largest, поместим объявление имён свойств в треугольные скобки <> между именем функции и списком свойств, как здесь: fn largest(list: &[T]) -> &T { Объявление читается так: функция largest является обобщённой по виду T. Эта функция имеет один свойство с именем list, который является срезом значений с видом данных T. Функция largest возвращает значение этого же вида T. Приложение 10-5 показывает определение функции largest с использованием обобщённых видов данных в её ярлыке. Приложение также показывает, как мы можем вызвать функцию со срезом данных вида i32 или char. Данный код пока не будет собираться, но мы исправим это к концу раздела. Файл: src/main.rs fn largest(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest\n} fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!(\"The largest number is {result}\"); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest(&char_list); println!(\"The largest char is {result}\");\n} Приложение 10-5: функция largest, использующая свойства обобщённого типа; пока ещё не собирается Если мы соберем программу сейчас, мы получим следующую ошибку: $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10)\nerror[E0369]: binary operation `>` cannot be applied to type `&T` --> src/main.rs:5:17 |\n5 | if item > largest { | ---- ^ ------- &T | | | &T |\nhelp: consider restricting type parameter `T` |\n1 | fn largest(list: &[T]) -> &T { | ++++++++++++++++++++++ For more information about this error, try `rustc --explain E0369`.\nerror: could not compile `chapter10` (bin \"chapter10\") due to 1 previous error В подсказке упоминается std::cmp::PartialOrd, который является особенностью . Мы поговорим про особенности в следующем разделе. Сейчас ошибка в функции largest указывает, что функция не будет работать для всех возможных видов T. Так как мы хотим сравнивать значения вида T в теле функции, мы можем использовать только те виды, данные которых можно упорядочить: можем упорядочить — значит, можем и сравнить. Чтобы можно было задействовать сравнения, обычная библиотека имеет особенность std::cmp::PartialOrd, который вы можете выполнить для видов (смотрите дополнение С для большей сведений про данный особенность). Следуя совету в сообщении сборщика, ограничим вид T теми исходами, которые поддерживают особенность PartialOrd, и тогда пример успешно собирается, так как обычная библиотека выполняет PartialOrd как для вида i32, так и для вида char.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Обобщённые виды данных » В объявлении функций","id":"169","title":"В объявлении функций"},"17":{"body":"Чтобы проверить, правильно ли у вас установлен Rust, откройте оболочку и введите эту строку: $ rustc --version Вы должны увидеть номер исполнения, хэш определения и дату определения для последней безотказной исполнения, которая была выпущена, в следующем виде: rustc x.y.z (abcabcabc yyyy-mm-dd) Если вы видите эту сведения, вы успешно установили Rust! Если вы не видите эту сведения, убедитесь, что Ржавчина находится в вашей системной переменной %PATH% следующим образом: В Windows CMD: > echo %PATH% В PowerShell: > echo $env:Path В Linux и macOS: $ echo $PATH Если все было сделано правильно, но Ржавчина все ещё не работает, есть несколько мест, где вам могут помочь. Узнайте, как связаться с другими Rustaceans (так мы себя называем) на странице сообщества .","breadcrumbs":"С чего начать » Установка » Устранение возможных ошибок","id":"17","title":"Устранение возможных ошибок"},"170":{"body":"Мы также можем определить устройства, использующие обобщённые виды в одном или нескольких своих полях, с помощью правил написания <>. Приложение 10-6 показывает, как определить устройство Point, чтобы хранить поля координат x и y любого вида данных. Файл: src/main.rs struct Point { x: T, y: T,\n} fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 };\n} Приложение 10-6: устройства Point, содержащая поля x и y вида T правила написания использования обобщённых видов в определении устройства очень похож на правила написания в определении функции. Сначала мы объявляем имена видов свойств внутри треугольных скобок сразу после названия устройства. Затем мы можем использовать обобщённые виды в определении устройства в тех местах, где ранее мы указывали бы определенные виды. Так как мы используем только один обобщённый вид данных для определения устройства Point, это определение означает, что устройства Point является обобщённой с видом T, и оба поля x и y имеют одинаковый вид, каким бы он не являлся. Если мы создадим образец устройства Point со значениями разных видов, как показано в приложении 10-7, наш код не собирается. Файл: src/main.rs struct Point { x: T, y: T,\n} fn main() { let wont_work = Point { x: 5, y: 4.0 };\n} Приложение 10-7: поля x и y должны быть одного вида, так как они имеют один и тот же обобщённый вид T В этом примере, когда мы присваиваем целочисленное значение 5 переменной x , мы сообщаем сборщику, что обобщённый вид T будет целым числом для этого образца Point. Затем, когда мы указываем значение 4.0 (имеющее вид, отличный от целого числа) для y, который по нашему определению должен иметь тот же вид, что и x, мы получим ошибку несоответствия видов: $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10)\nerror[E0308]: mismatched types --> src/main.rs:7:38 |\n7 | let wont_work = Point { x: 5, y: 4.0 }; | ^^^ expected integer, found floating-point number For more information about this error, try `rustc --explain E0308`.\nerror: could not compile `chapter10` (bin \"chapter10\") due to 1 previous error Чтобы определить устройство Point, где оба значения x и y являются обобщёнными, но различными видами, можно использовать несколько свойств обобщённого вида. Например, в приложении 10-8 мы изменим определение Point таким образом, чтобы оно использовало обобщённые виды T и U, где x имеет вид T а y имеет вид U. Файл: src/main.rs struct Point { x: T, y: U,\n} fn main() { let both_integer = Point { x: 5, y: 10 }; let both_float = Point { x: 1.0, y: 4.0 }; let integer_and_float = Point { x: 5, y: 4.0 };\n} Приложение 10-8: устройства Point обобщена для двух видов, так что x и y могут быть значениями разных видов Теперь разрешены все показанные образцы вида Point! В объявлении можно использовать сколь угодно много свойств обобщённого вида, но если делать это в большом количестве, код будет тяжело читать. Если в вашем коде требуется много обобщённых видов, возможно, стоит разбить его на более мелкие части.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Обобщённые виды данных » В определении устройств","id":"170","title":"В определении устройств"},"171":{"body":"Как и устройства, перечисления также могут хранить обобщённые виды в своих исхода.. Давайте ещё раз посмотрим на перечисление Option, предоставленное встроенной библиотекой, которое мы использовали в главе 6: enum Option { Some(T), None,\n} Это определение теперь должно быть вам более понятно. Как видите, перечисление Option является обобщённым по виду T и имеет два исхода: исход Some, который содержит одно значение вида T, и исход None, который не содержит никакого значения. Используя перечисление Option, можно выразить абстрактную подход необязательного значения — и так как Option является обобщённым, можно использовать эту абстракцию независимо от того, каким будет вид необязательного значения. Перечисления также могут использовать несколько обобщённых видов. Определение перечисления Result, которое мы упоминали в главе 9, является примером такого использования: enum Result { Ok(T), Err(E),\n} Перечисление Result имеет два обобщённых вида: T и E — и два исхода: Ok, который содержит вид T, и Err, содержащий вид E. С таким определением удобно использовать перечисление Result везде, где действия могут быть выполнены успешно (возвращая значение вида T) или неуспешно (возвращая ошибку вида E). Это то, что мы делали при открытии файла в приложении 9-3, где T заполнялось видом std::fs::File, если файл был открыт успешно, либо E заполнялось видом std::io::Error, если при открытии файла возникали какие-либо сбоев. Если вы встречаете в коде случаи, когда несколько определений устройств или перечислений отличаются только видами содержащихся в них значений, вы можете устранить повторение, используя обобщённые виды.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Обобщённые виды данных » В определениях перечислений","id":"171","title":"В определениях перечислений"},"172":{"body":"Мы можем выполнить способы для устройств и перечислений (как мы делали в главе 5) и в определениях этих способов также использовать обобщённые виды. В приложении 10-9 показана устройства Point, которую мы определили в приложении 10-6, с добавленным для неё способом x. Файл: src/main.rs struct Point { x: T, y: T,\n} impl Point { fn x(&self) -> &T { &self.x }\n} fn main() { let p = Point { x: 5, y: 10 }; println!(\"p.x = {}\", p.x());\n} Приложение 10-9: Выполнение способа с именем x у устройства Point, которая будет возвращать ссылку на поле x вида T Здесь мы определили способ с именем x у устройства Point, который возвращает ссылку на данные в поле x. Обратите внимание, что мы должны объявить T сразу после impl . В этом случае мы можем использовать T для указания на то, что выполняем способ для вида Point. Объявив T гибким видом сразу после impl , Ржавчина может определить, что вид в угловых скобках в Point является гибким, а не определенным видом. Мы могли бы выбрать другое имя для этого обобщённого свойства, отличное от имени, использованного в определении устройства, но обычно используют одно и то же имя. Способы, написанные внутри раздела impl , который использует обобщённый вид, будут определены для любого образца вида, независимо от того, какой определенный вид в конечном итоге будет подставлен вместо этого обобщённого. Мы можем также указать ограничения, какие обобщённые виды разрешено использовать при определении способов. Например, мы могли бы выполнить способы только для образцов вида Point, а не для образцов Point, в которых используется произвольный обобщённый вид. В приложении 10-10 мы используем определенный вид f32, что означает, что мы не определяем никакие виды после impl. Файл: src/main.rs # struct Point {\n# x: T,\n# y: T,\n# }\n# # impl Point {\n# fn x(&self) -> &T {\n# &self.x\n# }\n# }\n# impl Point { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() }\n}\n# # fn main() {\n# let p = Point { x: 5, y: 10 };\n# # println!(\"p.x = {}\", p.x());\n# } Приложение 10-10: разделimpl, который применяется только к устройстве, имеющей определенный вид для свойства обобщённого вида T Этот код означает, что вид Point будет иметь способ с именем distance_from_origin, а другие образцы Point, где T имеет вид, отличный от f32, не будут иметь этого способа. Способ вычисляет, насколько далеко наша точка находится от точки с координатами (0.0, 0.0), и использует математические действия, доступные только для видов с плавающей точкой. Свойства обобщённого вида, которые мы используем в определении устройства, не всегда совпадают с подобиями, использующимися в ярлыках способов этой устройства. Чтобы пример был более очевидным, в приложении 10-11 используются обобщённые виды X1 и Y1 для определения устройства Point и виды X2 Y2 для ярлыки способа mixup. Способ создаёт новый образец устройства Point, где значение x берётся из self Point (имеющей вид X1), а значение y - из переданной устройства Point (где эта переменная имеет вид Y2). Файл: src/main.rs struct Point { x: X1, y: Y1,\n} impl Point { fn mixup(self, other: Point) -> Point { Point { x: self.x, y: other.y, } }\n} fn main() { let p1 = Point { x: 5, y: 10.4 }; let p2 = Point { x: \"Hello\", y: 'c' }; let p3 = p1.mixup(p2); println!(\"p3.x = {}, p3.y = {}\", p3.x, p3.y);\n} Приложение 10-11: способ, использующий обобщённые виды, отличающиеся от видов, используемых в определении устройства В функции main мы определили вид Point, который имеет вид i32 для x (со значением 5 ) и вид f64 для y (со значением 10.4). Переменная p2 является устройством Point, которая имеет строковый срез для x (со значением «Hello») и char для y (со значением c). Вызов mixup на p1 с переменнаяом p2 создаст для нас образец устройства p3, который будет иметь вид i32 для x (потому что x взят из p1). Переменная p3 будет иметь вид char для y (потому что y взят из p2). Вызов макроса println! выведет p3.x = 5, p3.y = c. Цель этого примера — отобразить случай, в которой некоторые обобщённые свойства объявлены с помощью impl, а некоторые объявлены в определении способа. Здесь обобщённые свойства X1 и Y1 объявляются после impl, потому что они относятся к определению устройства. Обобщённые свойства X2 и Y2 объявляются после fn mixup, так как они относятся только к способу.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Обобщённые виды данных » В определении способов","id":"172","title":"В определении способов"},"173":{"body":"Вы могли бы задаться вопросом, возникают ли какие-нибудь дополнительные издержки при использовании свойств обобщённого вида. Хорошая новость в том, что при использовании обобщённых видов ваша программа работает ничуть ни медленнее, чем если бы она работала с использованием определенных видов. В Ржавчина это достигается во время сборки при помощи мономорфизации кода, использующего обобщённые виды. Мономорфизация — это этап превращения обобщённого кода в определенный код путём подстановки определенных видов, использующихся при сборки. В этом этапе сборщик выполняет шаги, противоположные тем, которые мы использовали для создания обобщённой функции в приложении 10-5: он просматривает все места, где вызывается обобщённый код, и порождает код для определенных видов, использовавшихся для вызова в обобщённом. Давайте посмотрим, как это работает при использовании перечисления Option из встроенной библиотеки: let integer = Some(5);\nlet float = Some(5.0); Когда Ржавчина собирает этот код, он выполняет мономорфизацию. Во время этого этапа сборщик считывает значения, которые были использованы в образцах Option, и определяет два вида Option: один для вида i32, а другой — для f64. Таким образом, он разворачивает обобщённое определение Option в два определения, именно для i32 и f64, тем самым заменяя обобщённое определение определенными. Мономорфизированная исполнение кода выглядит примерно так (сборщик использует имена, отличные от тех, которые мы используем здесь для отображения): Файл: src/main.rs enum Option_i32 { Some(i32), None,\n} enum Option_f64 { Some(f64), None,\n} fn main() { let integer = Option_i32::Some(5); let float = Option_f64::Some(5.0);\n} Обобщённое Option заменяется определенными определениями, созданными сборщиком. Поскольку Ржавчина собирает обобщённый код в код, определяющий вид в каждом образце, мы не платим за использование обобщённых видов во время выполнения. Когда код запускается, он работает точно так же, как если бы мы сделали повторение каждое определение вручную. Этап мономорфизации делает обобщённые виды Ржавчина чрезвычайно эффективными во время выполнения.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Обобщённые виды данных » Производительность кода, использующего обобщённые виды","id":"173","title":"Производительность кода, использующего обобщённые виды"},"174":{"body":"Особенность сообщает сборщику Ржавчина о возможности, которой обладает определённый вид и которой он может поделиться с другими видами. Можно использовать особенности, чтобы определять общее поведение абстрактным способом. Мы можем использовать ограничение особенности (trait bounds) чтобы указать, что общим видом может быть любой вид, который имеет определённое поведение. Примечание: Особенности похожи на возможность часто называемую внешней оболочкими в других языках программирования, хотя и с некоторыми отличиями.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Особенности (свойства): определение разделяемого поведения » Особенности: определение общего поведения","id":"174","title":"Особенности: определение общего поведения"},"175":{"body":"Поведение вида определяется теми способами, которые мы можем вызвать у данного вида. Различные виды разделяют одинаковое поведение, если мы можем вызвать одни и те же способы у этих видов. Определение особенностей - это способ собъединять ярлыки способов вместе для того, чтобы описать общее поведение, необходимое для достижения определённой цели. Например, пусть есть несколько устройств, которые имеют различный вид и различный размер текста: устройства NewsArticle, которая содержит новость, напечатанную в каком-то месте мира; устройства Tweet, которая содержит 280 символьную строку твита и мета-данные, обозначающие является ли твит новым или ответом на другой твит. Мы хотим создать ящик библиотеки медиа-агрегатора aggregator, которая может отображать сводку данных сохранённых в образцах устройств NewsArticle или Tweet. Чтобы этого достичь, нам необходимо иметь возможность для каждой устройства получить короткую сводку на основе имеющихся данных, и для этого мы запросим сводку вызвав способ summarize. Приложение 10-12 показывает определение особенности Summary, который выражает это поведение. Файл: src/lib.rs pub trait Summary { fn summarize(&self) -> String;\n} Приложение 10-12: Определение особенности Summary, который содержит поведение предоставленное способом summarize Здесь мы объявляем особенность с использованием ключевого слова trait, а затем его название, которым в нашем случае является Summary. Также мы объявляем ящик как pub что позволяет ящикам, зависящим от нашего ящика, тоже использовать наш ящик, что мы увидим в последующих примерах. Внутри фигурных скобок объявляются ярлыки способов, которые описывают поведения видов, выполняющих данный особенность, в данном случае поведение определяется только одной ярлыком способа fn summarize(&self) -> String. После ярлыки способа, вместо предоставления выполнения в фигурных в скобках, мы используем точку с запятой. Каждый вид, выполняющий данный особенность, должен предоставить своё собственное поведение для данного способа. Сборщик обеспечит, что любой вид содержащий особенность Summary, будет также иметь и способ summarize объявленный с точно такой же ярлыком. Особенность может иметь несколько способов в описании его тела: ярлыки способов перечисляются по одной на каждой строке и должны закачиваться символом ;.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Особенности (свойства): определение разделяемого поведения » Определение особенности","id":"175","title":"Определение особенности"},"176":{"body":"Теперь, после того как мы определили желаемое поведение используя особенность Summary, можно выполнить его у видов в нашем медиа-агрегаторе. Приложение 10-13 показывает выполнение особенности Summary у устройства NewsArticle, которая использует для создания сводки в способе summarize заголовок, автора и место обнародования статьи. Для устройства Tweet мы определяем выполнение summarize используя имя пользователя и следующий за ним полный текст твита, полагая что содержание твита уже ограничено 280 символами. Файл: src/lib.rs # pub trait Summary {\n# fn summarize(&self) -> String;\n# }\n# pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String,\n} impl Summary for NewsArticle { fn summarize(&self) -> String { format!(\"{}, by {} ({})\", self.headline, self.author, self.location) }\n} pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool,\n} impl Summary for Tweet { fn summarize(&self) -> String { format!(\"{}: {}\", self.username, self.content) }\n} Приложение 10-13: Выполнение особенности Summary для устройств NewsArticle и Tweet Выполнение особенности у вида подобна выполнения обычных способов. Разница в том что после impl мы ставим имя особенности, который мы хотим выполнить, затем используем ключевое слово for, а затем указываем имя вида, для которого мы хотим сделать выполнение особенности. Внутри раздела impl мы помещаем ярлык способа объявленную в особенности. Вместо добавления точки с запятой в конце, после каждой ярлыки используются фигурные скобки и тело способа заполняется определенным поведением, которое мы хотим получить у способов особенности для определенного вида. Теперь когда библиотека выполнила особенность Summary для NewsArticle и Tweet, программисты использующие ящик могут вызывать способы особенности у образцов видов NewsArticle и Tweet точно так же как если бы это были обычные способы. Единственное отличие состоит в том, что программист должен ввести особенность в область видимости точно так же как и виды. Здесь пример того как двоичный ящик может использовать наш aggregator: use aggregator::{Summary, Tweet}; fn main() { let tweet = Tweet { username: String::from(\"horse_ebooks\"), content: String::from( \"of course, as you probably already know, people\", ), reply: false, retweet: false, }; println!(\"1 new tweet: {}\", tweet.summarize());\n} Данный код напечатает: 1 new tweet: horse_ebooks: of course, as you probably already know, people. Другие ящики, которые зависят от aggregator, тоже могу включить особенность Summary в область видимости для выполнения Summary в их собственных видах. Одно ограничение, на которое следует обратить внимание, заключается в том, что мы можем выполнить особенность для вида только в том случае, если хотя бы один из особенностей вида является местным для нашего ящика. Например, мы можем выполнить обычный библиотечный особенность Display на собственном виде Tweet как часть возможности нашего ящика aggregator потому что вид Tweet является местным для ящика aggregator. Также мы можем выполнить Summary для Vec в нашем ящике aggregator, потому что особенность Summary является местным для нашего ящика aggregator. Но мы не можем выполнить внешние особенности для внешних видов. Например, мы не можем выполнить особенность Display для Vec внутри нашего ящика aggregator, потому что Display и Vec оба определены в встроенной библиотеке а не местно в нашем ящике aggregator. Это ограничение является частью свойства называемого согласованность (coherence), а ещё точнее сиротское правило (orphan rule), которое называется так потому что не представлен родительский вид. Это правило заверяет, что код других людей не может сломать ваш код и наоборот. Без этого правила два ящика могли бы выполнить один особенность для одинакового вида и Ржавчина не сможет понять, какой выполнением нужно пользоваться.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Особенности (свойства): определение разделяемого поведения » Выполнение особенности у вида","id":"176","title":"Выполнение особенности у вида"},"177":{"body":"Иногда полезно иметь поведение по умолчанию для некоторых или всех способов в особенности вместо того, чтобы требовать выполнения всех способов в каждом виде, выполняющим данный особенность. Затем, когда мы выполняем особенность для определённого вида, можно сохранить или переопределить поведение каждого способа по умолчанию уже внутри видов. В примере 10-14 показано, как указать строку по умолчанию для способа summarize из особенности Summary вместо определения только ярлыки способа, как мы сделали в примере 10-12. Файл: src/lib.rs pub trait Summary { fn summarize(&self) -> String { String::from(\"(Read more...)\") }\n}\n# # pub struct NewsArticle {\n# pub headline: String,\n# pub location: String,\n# pub author: String,\n# pub content: String,\n# }\n# # impl Summary for NewsArticle {}\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# # impl Summary for Tweet {\n# fn summarize(&self) -> String {\n# format!(\"{}: {}\", self.username, self.content)\n# }\n# } Приложение 10-14: Определение особенности Summary с выполнением способа summarize по умолчанию Для использования выполнения по умолчанию при создании сводки у образцов NewsArticle вместо определения пользовательской выполнения, мы указываем пустой разделimpl с impl Summary for NewsArticle {}. Хотя мы больше не определяем способ summarize непосредственно в NewsArticle, мы предоставили выполнение по умолчанию и указали, что NewsArticle выполняет особенность Summary. В итоге мы всё ещё можем вызвать способ summarize у образца NewsArticle, например так: # use aggregator::{self, NewsArticle, Summary};\n# # fn main() { let article = NewsArticle { headline: String::from(\"Penguins win the Stanley Cup Championship!\"), location: String::from(\"Pittsburgh, PA, USA\"), author: String::from(\"Iceburgh\"), content: String::from( \"The Pittsburgh Penguins once again are the best \\ hockey team in the NHL.\", ), }; println!(\"New article available! {}\", article.summarize());\n# } Этот код печатает New article available! (Read more...) . Создание выполнения по умолчанию не требует от нас изменений чего-либо в выполнения Summary для Tweet в приложении 10-13. Причина заключается в том, что правила написания для переопределения выполнения по умолчанию является таким же, как правила написания для выполнения способа особенности, который не имеет выполнения по умолчанию. Выполнения по умолчанию могут вызывать другие способы в том же особенности, даже если эти другие способы не имеют выполнения по умолчанию. Таким образом, особенность может предоставить много полезной возможности и только требует от разработчиков указывать небольшую его часть. Например, мы могли бы определить особенность Summary имеющий способ summarize_author, выполнение которого требуется, а затем определить способ summarize который имеет выполнение по умолчанию, которая внутри вызывает способ summarize_author: pub trait Summary { fn summarize_author(&self) -> String; fn summarize(&self) -> String { format!(\"(Read more from {}...)\", self.summarize_author()) }\n}\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# # impl Summary for Tweet {\n# fn summarize_author(&self) -> String {\n# format!(\"@{}\", self.username)\n# }\n# } Чтобы использовать такую исполнение особенности Summary, нужно только определить способ summarize_author, при выполнения особенности для вида: # pub trait Summary {\n# fn summarize_author(&self) -> String;\n# # fn summarize(&self) -> String {\n# format!(\"(Read more from {}...)\", self.summarize_author())\n# }\n# }\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# impl Summary for Tweet { fn summarize_author(&self) -> String { format!(\"@{}\", self.username) }\n} После того, как мы определим summarize_author, можно вызвать summarize для образцов устройства Tweet и выполнение по умолчанию способа summarize будет вызывать определение summarize_author которое мы уже предоставили. Так как мы выполнили способ summarize_author особенности Summary, то особенность даёт нам поведение способа summarize без необходимости писать код. # use aggregator::{self, Summary, Tweet};\n# # fn main() { let tweet = Tweet { username: String::from(\"horse_ebooks\"), content: String::from( \"of course, as you probably already know, people\", ), reply: false, retweet: false, }; println!(\"1 new tweet: {}\", tweet.summarize());\n# } Этот код печатает 1 new tweet: (Read more from @horse_ebooks...) . Обратите внимание, что невозможно вызвать выполнение по умолчанию из переопределённой выполнения того же способа.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Особенности (свойства): определение разделяемого поведения » Выполнение поведения по умолчанию","id":"177","title":"Выполнение поведения по умолчанию"},"178":{"body":"Теперь, когда вы знаете, как определять и выполнить особенности, можно изучить, как использовать особенности, чтобы определить функции, которые принимают много различных видов. Мы будем использовать особенность Summary, выполненный для видов NewsArticle и Tweet в приложении 10-13, чтобы определить функцию notify, которая вызывает способ summarize для его свойства item, который имеет некоторый вид, выполняющий особенность Summary. Для этого мы используем правила написания impl Trait примерно так: # pub trait Summary {\n# fn summarize(&self) -> String;\n# }\n# # pub struct NewsArticle {\n# pub headline: String,\n# pub location: String,\n# pub author: String,\n# pub content: String,\n# }\n# # impl Summary for NewsArticle {\n# fn summarize(&self) -> String {\n# format!(\"{}, by {} ({})\", self.headline, self.author, self.location)\n# }\n# }\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# # impl Summary for Tweet {\n# fn summarize(&self) -> String {\n# format!(\"{}: {}\", self.username, self.content)\n# }\n# }\n# pub fn notify(item: &impl Summary) { println!(\"Breaking news! {}\", item.summarize());\n} Вместо определенного вида у свойства item указывается ключевое слово impl и имя особенности. Этот свойство принимает любой вид, который выполняет указанный особенность. В теле notify мы можем вызывать любые способы у образца item , которые приходят с особенностью Summary, такие как способ summarize. Мы можем вызвать notify и передать в него любой образец NewsArticle или Tweet. Код, который вызывает данную функцию с любым другим видом, таким как String или i32, не будет собираться, потому что эти виды не выполняют особенность Summary. правила написания ограничения особенности правила написания impl Trait работает для простых случаев, но на самом деле является синтаксическим сахаром для более длинной видовы, которая называется ограничением особенности (trait bound); это выглядит так: pub fn notify(item: &T) { println!(\"Breaking news! {}\", item.summarize());\n} Эта более длинная разновидность эквивалентна примеру в предыдущем разделе, но она более многословна. Мы помещаем объявление свойства обобщённого вида с ограничением особенности после двоеточия внутри угловых скобок. правила написания impl Trait удобен и делает код более сжатым в простых случаях, в то время как более полный правила написания с ограничением особенности в других случаях может выразить большую сложность. Например, у нас может быть два свойства, которые выполняют особенность Summary. Использование правил написания impl Trait выглядит так: pub fn notify(item1: &impl Summary, item2: &impl Summary) { Использовать impl Trait удобнее если мы хотим разрешить функции иметь разные виды для item1 и item2 (но оба вида должны выполнить Summary). Если же мы хотим заставить оба свойства иметь один и тот же вид, то мы должны использовать ограничение особенности так: pub fn notify(item1: &T, item2: &T) { Обобщённый вид T указан для видов свойств item1 и item2 и ограничивает функцию так, что определенные значения видов переданные переменнойми для item1 и item2 должны быть одинаковыми. Задание нескольких границ особенностей с помощью правил написания + Также можно указать более одного ограничения особенности. Допустим, мы хотели бы чтобы notify использовал как изменение вывода так и summarize для свойства item: тогда мы указываем что в notify свойство item должен выполнить оба особенности Display и Summary. Мы можем сделать это используя правила написания +: pub fn notify(item: &(impl Summary + Display)) { правила написания + также допустим с ограничениями особенности для обобщённых видов: pub fn notify(item: &T) { При наличии двух ограничений особенности, тело способа notify может вызывать summarize и использовать {} для изменения item при его печати. Более ясные границы особенности с помощью where Использование слишком большого количества ограничений особенности имеет свои недостатки. Каждый обобщённый вид имеет свои границы особенности, поэтому функции с несколькими свойствами обобщённого вида могут содержать много сведений об ограничениях между названием функции и списком её свойств затрудняющих чтение ярлыки. По этой причине в Ржавчина есть иной правила написания для определения ограничений особенности внутри предложения where после ярлыки функции. Поэтому вместо того, чтобы писать так: fn some_function(t: &T, u: &U) -> i32 { можно использовать where таким образом: fn some_function(t: &T, u: &U) -> i32\nwhere T: Display + Clone, U: Clone + Debug,\n{\n# unimplemented!()\n# } Ярлык этой функции менее загромождена: название функции, список свойств, и возвращаемый вид находятся рядом, а ярлык не содержит в себе множество ограничений особенности.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Особенности (свойства): определение разделяемого поведения » Особенности как свойства","id":"178","title":"Особенности как свойства"},"179":{"body":"Также можно использовать правила написания impl Trait в возвращаемой позиции, чтобы вернуть значение некоторого вида выполняющего особенность, как показано здесь: # pub trait Summary {\n# fn summarize(&self) -> String;\n# }\n# # pub struct NewsArticle {\n# pub headline: String,\n# pub location: String,\n# pub author: String,\n# pub content: String,\n# }\n# # impl Summary for NewsArticle {\n# fn summarize(&self) -> String {\n# format!(\"{}, by {} ({})\", self.headline, self.author, self.location)\n# }\n# }\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# # impl Summary for Tweet {\n# fn summarize(&self) -> String {\n# format!(\"{}: {}\", self.username, self.content)\n# }\n# }\n# fn returns_summarizable() -> impl Summary { Tweet { username: String::from(\"horse_ebooks\"), content: String::from( \"of course, as you probably already know, people\", ), reply: false, retweet: false, }\n} Используя impl Summary для возвращаемого вида, мы указываем, что функция returns_summarizable возвращает некоторый вид, который выполняет особенность Summary без обозначения определенного вида. В этом случае returns_summarizable возвращает Tweet, но код, вызывающий эту функцию, этого не знает. Возможность возвращать вид, который определяется только выполняемым им признаком, особенно полезна в среде замыканий и повторителей, которые мы рассмотрим в Главе 13. Замыкания и повторители создают виды, которые знает только сборщик или виды, которые очень долго указывать. правила написания impl Trait позволяет кратко указать, что функция возвращает некоторый вид, который выполняет особенность Iterator без необходимости писать очень длинный вид. Однако, impl Trait возможно использовать, если возвращаете только один вид. Например, данный код, который возвращает значения или вида NewsArticle или вида Tweet, но в качестве возвращаемого вида объявляет impl Summary , не будет работать: # pub trait Summary {\n# fn summarize(&self) -> String;\n# }\n# # pub struct NewsArticle {\n# pub headline: String,\n# pub location: String,\n# pub author: String,\n# pub content: String,\n# }\n# # impl Summary for NewsArticle {\n# fn summarize(&self) -> String {\n# format!(\"{}, by {} ({})\", self.headline, self.author, self.location)\n# }\n# }\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# # impl Summary for Tweet {\n# fn summarize(&self) -> String {\n# format!(\"{}: {}\", self.username, self.content)\n# }\n# }\n# fn returns_summarizable(switch: bool) -> impl Summary { if switch { NewsArticle { headline: String::from( \"Penguins win the Stanley Cup Championship!\", ), location: String::from(\"Pittsburgh, PA, USA\"), author: String::from(\"Iceburgh\"), content: String::from( \"The Pittsburgh Penguins once again are the best \\ hockey team in the NHL.\", ), } } else { Tweet { username: String::from(\"horse_ebooks\"), content: String::from( \"of course, as you probably already know, people\", ), reply: false, retweet: false, } }\n} Возврат либо NewsArticle либо Tweet не допускается из-за ограничений того, как выполнен правила написания impl Trait в сборщике. Мы рассмотрим, как написать функцию с таким поведением в разделе \"Использование предметов особенностей, которые разрешены для значений или разных видов\" Главы 17.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Особенности (свойства): определение разделяемого поведения » Возврат значений вида выполняющего определённый особенность","id":"179","title":"Возврат значений вида выполняющего определённый особенность"},"18":{"body":"После установки Ржавчина с помощью rustup обновление до новой исполнения не составит труда. В приказной оболочке запустите следующий скрипт обновления: $ rustup update Чтобы удалить Ржавчина и rustup, выполните следующую приказ: $ rustup self uninstall","breadcrumbs":"С чего начать » Установка » Обновление и удаление","id":"18","title":"Обновление и удаление"},"180":{"body":"Используя ограничение особенности с разделом impl, который использует свойства обобщённого вида, можно выполнить способы условно, для тех видов, которые выполняют указанный особенность. Например, вид Pair в приложении 10-15 всегда выполняет функцию new для возврата нового образца Pair (вспомните раздел “Определение способов” Главы 5 где Self является псевдонимом вида для вида раздела impl, который в данном случае является Pair). Но в следующем разделе impl вид Pair выполняет способ cmp_display только если его внутренний вид T выполняет особенность PartialOrd (позволяющий сравнивать) и особенность Display (позволяющий выводить на печать). Файл: src/lib.rs use std::fmt::Display; struct Pair { x: T, y: T,\n} impl Pair { fn new(x: T, y: T) -> Self { Self { x, y } }\n} impl Pair { fn cmp_display(&self) { if self.x >= self.y { println!(\"The largest member is x = {}\", self.x); } else { println!(\"The largest member is y = {}\", self.y); } }\n} Приложение 10-15: Условная выполнение способов у обобщённых видов в зависимости от ограничений особенности Мы также можем условно выполнить особенность для любого вида, который выполняет другой особенность. Выполнения особенности для любого вида, который удовлетворяет ограничениям особенности, называются общими выполнениеми и широко используются в встроенной библиотеке Rust. Например, обычная библиотека выполняет особенность ToString для любого вида, который выполняет особенность Display. Разделimpl в встроенной библиотеке выглядит примерно так: impl ToString for T { // --snip--\n} Поскольку обычная библиотека имеет эту общую выполнение, то можно вызвать способ to_string определённый особенностью ToString для любого вида, который выполняет особенность Display. Например, мы можем превратить целые числа в их соответствующие String значения, потому что целые числа выполняют особенность Display: let s = 3.to_string(); Общие выполнения приведены в документации к особенности в разделе \"Implementors\". Особенности и ограничения особенностей позволяют писать код, который использует свойства обобщённого вида для уменьшения повторения кода, а также указывая сборщику, что мы хотим обобщённый вид, чтобы иметь определённое поведение. Затем сборщик может использовать сведения про ограничения особенности, чтобы проверить, что все определенные виды, используемые с нашим кодом, обеспечивают правильное поведение. В изменяемых строго определенных языках мы получили бы ошибку во время выполнения, если бы вызвали способ для вида, который не выполняет вид определяемый способом. Но Ржавчина перемещает эти ошибки на время сборки, поэтому мы вынуждены исправить сбоев, прежде чем наш код начнёт работать. Кроме того, мы не должны писать код, который проверяет своё поведение во время выполнения, потому что это уже проверено во время сборки. Это повышает производительность без необходимости отказываться от гибкости обобщённых видов.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Особенности (свойства): определение разделяемого поведения » Использование ограничений особенности для условной выполнения способов","id":"180","title":"Использование ограничений особенности для условной выполнения способов"},"181":{"body":"Сроки (времена) жизни - ещё один вид обобщений, с которыми мы уже встречались. Если раньше мы использовали обобщения, чтобы убедиться, что вид обладает нужным нам поведением, теперь мы будем использовать сроки жизни для того, чтобы быть уверенными, что ссылки действительны как самое меньшее столько времени в этапе исполнения программы, сколько нам требуется. В разделе \"Ссылки и заимствование\" главы 4, мы кое о чём умолчали: у каждой ссылки в Ржавчина есть своё время жизни — область кода, на протяжении которого данная ссылка действительна (valid). В большинстве случаев сроки жизни выводятся неявно — так же, как у видов (нам требуется явно объявлять виды лишь в тех случаях, когда при самостоятельном выведении вида возможны исходы). Точно так же мы должны явно объявлять сроки жизни тех ссылок, для которых времена жизни могут быть определены сборщиком по-разному. Ржавчина требует от нас объявлять взаимосвязи посредством обобщённых свойств сроков жизни, чтобы убедиться в том, что во время исполнения все действующие ссылки будут правильными. Определение времени жизни — это подход, отсутствующая в большинстве других языков программирования, так что она может показаться незнакомой. Хотя в этой главе мы не будем рассматривать времена жизни во всех подробностях, тем не менее, мы обсудим основные случаи, в которых вы можете столкнуться с правилами написания времени жизни, что позволит вам получше ознакомиться с этой подходом.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Валидация ссылок при помощи времён жизни","id":"181","title":"Валидация ссылок при помощи времён жизни"},"182":{"body":"Основное предназначение сроков жизни — предотвращать появление так называемых \"повисших ссылок\" (dangling references), из-за которых программа обращается не к тем данным, к которым она собиралась обратиться. Рассмотрим программу из приложения 10-16, имеющую внешнюю и внутреннюю области видимости. fn main() { let r; { let x = 5; r = &x; } println!(\"r: {r}\");\n} Приложение 10-16: Попытка использования ссылки, значение которой вышло из области видимости Примечание: примеры в приложениях 10-16, 10-17 и 10-23 объявляют переменные без указания их начального значения, поэтому имя переменной существует во внешней области видимости. На первый взгляд может показаться, что это противоречит отсутствию в Ржавчина нулевых (null) значений. Однако, если мы попытаемся использовать переменную, прежде чем присвоить ей значение, мы получим ошибку сборки, которая показывает, что Ржавчина действительно не разрешает нулевые (null) значения. Внешняя область видимости объявляет переменную с именем r без начального значения, а внутренняя область объявляет переменную с именем x с начальным значением 5. Во внутренней области мы пытаемся установить значение r как ссылку на x. Затем внутренняя область видимости заканчивается и мы пытаемся напечатать значение из r. Этот код не будет собран, потому что значение на которое ссылается r исчезает из области видимости, прежде чем мы попробуем использовать его. Вот сообщение об ошибке: $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10)\nerror[E0597]: `x` does not live long enough --> src/main.rs:6:13 |\n5 | let x = 5; | - binding `x` declared here\n6 | r = &x; | ^^ borrowed value does not live long enough\n7 | } | - `x` dropped here while still borrowed\n8 |\n9 | println!(\"r: {r}\"); | --- borrow later used here For more information about this error, try `rustc --explain E0597`.\nerror: could not compile `chapter10` (bin \"chapter10\") due to 1 previous error Переменная x «не живёт достаточно долго». Причина в том, что x выйдет из области видимости, когда эта внутренняя область закончится в строке 7. Но r все ещё является действительной во внешней области видимости; поскольку её охват больше, мы говорим, что она «живёт дольше». Если бы Ржавчина позволил такому коду работать, то переменная r смогла бы ссылаться на память, которая уже была освобождена (в тот мгновение, когда x вышла из внутренней области видимости), и всё что мы попытались бы сделать с r работало бы неправильно. Как же Ржавчина определяет, что этот код неправилен? Он использует для этого анализатор заимствований (borrow checker).","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Времена жизни предотвращают появление \"повисших\" ссылок","id":"182","title":"Времена жизни предотвращают появление \"повисших\" ссылок"},"183":{"body":"Сборщик Ржавчина имеет в своём составе анализатор заимствований , который сравнивает области видимости для определения, являются ли все заимствования действительными. В приложении 10-17 показан тот же код, что и в приложении 10-16, но с изложениями, показывающими времена жизни переменных. fn main() { let r; // ---------+-- 'a // | { // | let x = 5; // -+-- 'b | r = &x; // | | } // -+ | // | println!(\"r: {r}\"); // |\n} // ---------+ Пример 10-17: Изложение времён жизни переменных r и x, с помощью определителей времени жизни 'a и 'b, соответственно Здесь мы описали время жизни для r с помощью 'a и время жизни x с помощью 'b . Как видите, время жизни 'b внутреннего раздела гораздо меньше, чем время жизни 'a внешнего раздела. Во время сборки Ржавчина сравнивает продолжительность двух времён жизни и видит, что r имеет время жизни 'a, но ссылается на память со временем жизни 'b. Программа отклоняется, потому что 'b короче, чем 'a: предмет ссылки не живёт так же долго, как сама ссылка. Приложение 10-18 исправляет код, чтобы в нём не было повисшей ссылки, и собирается без ошибок. fn main() { let x = 5; // ----------+-- 'b // | let r = &x; // --+-- 'a | // | | println!(\"r: {r}\"); // | | // --+ |\n} // ----------+ Приложение 10-18: Ссылка правильна, так как данные имеют более продолжительное время жизни, чем ссылка на эти данные Здесь переменная x имеет время жизни 'b, которое больше, чем время жизни 'a. Это означает, что переменная r может ссылаться на переменную x потому что Ржавчина знает, что ссылка в переменной r будет всегда действительной до тех пор, пока переменная x является валидной. После того, как мы на примерах рассмотрели времена жизни ссылок и обсудили как Ржавчина их анализирует, давайте поговорим об обобщённых временах жизни входных свойств и возвращаемых значений функций.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Анализатор заимствований","id":"183","title":"Анализатор заимствований"},"184":{"body":"Напишем функцию, которая возвращает более длинный из двух срезов строки. Эта функция принимает два среза строки и возвращает один срез строки. После того как мы выполнили функцию longest, код в приложении 10-19 должен вывести The longest string is abcd. Файл: src/main.rs fn main() { let string1 = String::from(\"abcd\"); let string2 = \"xyz\"; let result = longest(string1.as_str(), string2); println!(\"The longest string is {result}\");\n} Приложение 10-19: Функция main вызывает функцию longest для поиска наибольшего из двух срезов строки Обратите внимание, что мы хотим чтобы функция принимала строковые срезы, которые являются ссылками, а не строки, потому что мы не хотим, чтобы функция longest забирала во владение свои свойства. Обратитесь к разделу \"Строковые срезы как свойства\" Главы 4 для более подробного обсуждения того, почему свойства используемые в приложении 10-19 выбраны именно таким образом. Если мы попробуем выполнить функцию longest так, как это показано в приложении 10-20, программа не собирается: Файл: src/main.rs # fn main() {\n# let string1 = String::from(\"abcd\");\n# let string2 = \"xyz\";\n# # let result = longest(string1.as_str(), string2);\n# println!(\"The longest string is {result}\");\n# }\n# fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y }\n} Приложение 10-20: Выполнение функции longest, которая возвращает наибольший срез строки, но пока не собирается Вместо этого мы получим следующую ошибку, говорящую о временах жизни: $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10)\nerror[E0106]: missing lifetime specifier --> src/main.rs:9:33 |\n9 | fn longest(x: &str, y: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`\nhelp: consider introducing a named lifetime parameter |\n9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { | ++++ ++ ++ ++ For more information about this error, try `rustc --explain E0106`.\nerror: could not compile `chapter10` (bin \"chapter10\") due to 1 previous error Текст ошибки показывает, что возвращаемому виду нужен обобщённый свойство времени жизни, потому что Ржавчина не может определить, относится ли возвращаемая ссылка к x или к y. На самом деле, мы тоже не знаем, потому что разделif в теле функции возвращает ссылку на x, а разделelse возвращает ссылку на y! Когда мы определяем эту функцию, мы не знаем определенных значений, которые будут в неё передаваться. Поэтому мы не знаем какая из ветвей оператора if или else будет выполнена. Мы также не знаем определенных времён жизни ссылок, которые будут переданы в функцию, поэтому мы не можем посмотреть на их области видимости, как мы делали в примерах 10-17 и 10-18, чтобы определить, будет ли возвращаемая нами ссылка правильной во всех случаях. Анализатор заимствований также не может этого определить, потому что он не знает как времена жизни переменных x и y соотносятся с временем жизни возвращаемого значения. Чтобы исправить эту ошибку, мы добавим обобщённый свойство времени жизни, который определит отношения между ссылками таким образом, чтобы анализатор заимствований мог провести свой анализ.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Обобщённые времена жизни в функциях","id":"184","title":"Обобщённые времена жизни в функциях"},"185":{"body":"Изложения времени жизни не меняют срок, как долго живёт та или иная ссылка. Они скорее описывают, как соотносятся между собой времена жизни нескольких ссылок, не влияя на само время жизни. Точно так же, как функции могут принимать любой вид, когда в ярлыке указан свойство обобщённого вида, функции могут принимать ссылки с любым временем жизни, указанным с помощью свойства обобщённого времени жизни. Изложения времени жизни имеют немного необычный правила написания: имена свойств времени жизни должны начинаться с апострофа ('), пишутся маленькими буквами, и обычно очень короткие, как и имена обобщённых видов. Большинство людей использует имя 'a в качестве первой изложении времени жизни. Изложения свойств времени жизни следуют после символа & и отделяются пробелом от названия ссылочного вида. Приведём несколько примеров: у нас есть ссылка на i32 без указания времени жизни, ссылка на i32, с временем жизни имеющим имя 'a и изменяемая ссылка на i32, которая также имеет время жизни 'a. &i32 // a reference\n&'a i32 // a reference with an explicit lifetime\n&'a mut i32 // a mutable reference with an explicit lifetime Одна изложение времени жизни сама по себе не имеет большого значения, поскольку изложении предназначены для того, чтобы уведомить Ржавчина о том, как времена жизни нескольких ссылок соотносятся между собой. Давайте рассмотрим, как изложении времени жизни связаны друг с другом в среде функции longest.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » правила написания изложении времени жизни","id":"185","title":"правила написания изложении времени жизни"},"186":{"body":"Чтобы использовать изложении времени жизни в ярлыках функций, нам нужно объявить свойства обобщённого времени жизни внутри угловых скобок между именем функции и списком свойств, как мы это делали с свойствами обобщённого вида . Мы хотим, чтобы ярлык отражала следующее ограничение: возвращаемая ссылка будет действительна до тех пор, пока валидны оба свойства. Это связь между временами жизни свойств и возвращаемого значения. Мы назовём это время жизни 'a, а затем добавим его к каждой ссылке, как показано в приложении 10-21. Файл: src/main.rs # fn main() {\n# let string1 = String::from(\"abcd\");\n# let string2 = \"xyz\";\n# # let result = longest(string1.as_str(), string2);\n# println!(\"The longest string is {result}\");\n# }\n# fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }\n} Приложение 10-21: В определении функции longest указано, что все ссылки должны иметь одинаковое время жизни, обозначенное как 'a Этот код должен собираться и давать желаемый итог, когда мы вызовем его в функции main приложения 10-19. Ярлык функции теперь сообщает Rust, что для некоторого времени жизни 'a функция принимает два свойства, оба из которых являются срезами строк, которые живут не меньше, чем время жизни 'a. Ярлык функции также сообщает Rust, что срез строки, возвращаемый функцией, будет жить как самое меньшее столько, сколько длится время жизни 'a. В действительностиэто означает, что время жизни ссылки, возвращаемой функцией longest, равно меньшему времени жизни передаваемых в неё ссылок. Мы хотим, чтобы Ржавчина использовал именно такие отношения при анализе этого кода. Помните, когда мы указываем свойства времени жизни в этой ярлыке функции, мы не меняем время жизни каких-либо переданных или возвращённых значений. Скорее, мы указываем, что анализатор заимствований должен отклонять любые значения, которые не соответствуют этим ограничениям. Обратите внимание, что самой функции longest не нужно точно знать, как долго будут жить x и y, достаточно того, что некоторая область может быть заменена на 'a, которая будет удовлетворять этой ярлыке. При определении времён жизни функций, изложении помещаются в ярлык функции, а не в тело функции. Изложения времени жизни становятся частью договора функции, как и виды в ярлыке. Наличие ярлыков функций, содержащих договор времени жизни, означает, что анализ который выполняет сборщик Rust, может быть проще. Если есть неполадка с тем, как функция определяется или как она вызывается, ошибки сборщика могут указать на часть нашего кода и ограничения более точно. Если бы вместо этого сборщик Ржавчина сделал больше предположений о том, какие отношения времён жизни мы хотели получить, сборщик смог бы указать только на использование нашего кода за много шагов от источника сбоев. Когда мы передаём определенные ссылки в функцию longest, определенным временем жизни, которое будет заменено на 'a, является часть области видимости x, которая пересекается с областью видимости y. Другими словами, обобщённое время жизни 'a получит определенное время жизни, равное меньшему из времён жизни x и y. Так как мы определяли возвращаемую ссылку тем же свойствоом времени жизни 'a, то возвращённая ссылка также будет действительна на протяжении меньшего из времён жизни x и y. Давайте посмотрим, как изложении времени жизни ограничивают функцию longest путём передачи в неё ссылок, которые имеют разные определенные времена жизни. Приложение 10-22 является очевидным примером. Файл: src/main.rs fn main() { let string1 = String::from(\"long string is long\"); { let string2 = String::from(\"xyz\"); let result = longest(string1.as_str(), string2.as_str()); println!(\"The longest string is {result}\"); }\n}\n# # fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {\n# if x.len() > y.len() {\n# x\n# } else {\n# y\n# }\n# } Приложение 10-22: Использование функции longest со ссылками на значения вида String, имеющими разное время жизни В этом примере переменная string1 действительна до конца внешней области, string2 действует до конца внутренней области видимости и result ссылается на что-то, что является действительным до конца внутренней области видимости. Запустите этот код, и вы увидите что анализатор заимствований разрешает такой код; он собирает и напечатает The longest string is long string is long. Теперь, давайте попробуем пример, который показывает, что время жизни ссылки result должно быть меньшим временем жизни одного из двух переменных. Мы переместим объявление переменной result за пределы внутренней области видимости, но оставим присвоение значения переменной result в области видимости string2. Затем мы переместим println!, который использует result за пределы внутренней области видимости, после того как внутренняя область видимости закончилась. Код в приложении 10-23 не собирается. Файл: src/main.rs fn main() { let string1 = String::from(\"long string is long\"); let result; { let string2 = String::from(\"xyz\"); result = longest(string1.as_str(), string2.as_str()); } println!(\"The longest string is {result}\");\n}\n# # fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {\n# if x.len() > y.len() {\n# x\n# } else {\n# y\n# }\n# } Приложение 10-23: Попытка использования result, после того как string2 вышла из области видимости При попытке собрать этот код, мы получим такую ошибку: $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10)\nerror[E0597]: `string2` does not live long enough --> src/main.rs:6:44 |\n5 | let string2 = String::from(\"xyz\"); | ------- binding `string2` declared here\n6 | result = longest(string1.as_str(), string2.as_str()); | ^^^^^^^ borrowed value does not live long enough\n7 | } | - `string2` dropped here while still borrowed\n8 | println!(\"The longest string is {result}\"); | -------- borrow later used here For more information about this error, try `rustc --explain E0597`.\nerror: could not compile `chapter10` (bin \"chapter10\") due to 1 previous error Эта ошибка говорит о том, что если мы хотим использовать result в указания println!, переменная string2 должна бы быть действительной до конца внешней области видимости. Ржавчина знает об этом, потому что мы определяли свойства функции и её возвращаемое значение одинаковым временем жизни 'a. Будучи людьми, мы можем посмотреть на этот код и увидеть, что string1 длиннее, чем string2 и, следовательно, result будет содержать ссылку на string1. Поскольку string1 ещё не вышла из области видимости, ссылка на string1 будет все ещё действительной в указания println!. Однако сборщик не видит, что ссылка в этом случае валидна. Мы сказали Rust, что время жизни ссылки, возвращаемой из функции longest, равняется меньшему из времён жизни переданных в неё ссылок. Таким образом, анализатор заимствований запрещает код в приложении 10-23, как возможно имеющий недействительную ссылку. Попробуйте провести больше экспериментов с различными значениями и временами жизни ссылок, передаваемых в функцию longest, а также с тем, как используется возвращаемое значение Перед сборкой делайте предположения о том, пройдёт ли ваш код анализ заимствований, а затем проверяйте, насколько вы были правы.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Изложения времени жизни в ярлыках функций","id":"186","title":"Изложения времени жизни в ярлыках функций"},"187":{"body":"В зависимости от того, что делает ваша функция, следует использовать разные способы указания свойств времени жизни. Например, если мы изменим выполнение функции longest таким образом, чтобы она всегда возвращала свой первый переменная вместо самого длинного среза строки, то время жизни для свойства y можно совсем не указывать. Этот код собирается: Файл: src/main.rs # fn main() {\n# let string1 = String::from(\"abcd\");\n# let string2 = \"efghijklmnopqrstuvwxyz\";\n# # let result = longest(string1.as_str(), string2);\n# println!(\"The longest string is {result}\");\n# }\n# fn longest<'a>(x: &'a str, y: &str) -> &'a str { x\n} Мы указали свойство времени жизни 'a для свойства x и возвращаемого значения, но не для свойства y, поскольку время жизни свойства y никак не соотносится с временем жизни свойства x или возвращаемого значения. При возврате ссылки из функции, свойство времени жизни для возвращаемого вида должен соответствовать свойству времени жизни одного из переменных. Если возвращаемая ссылка не ссылается на один из свойств, она должна ссылаться на значение, созданное внутри функции. Однако, это приведёт к недействительной ссылке, поскольку значение, на которое она ссылается, выйдет из области видимости в конце функции. Посмотрите на попытку выполнения функции longest, которая не собирается: Файл: src/main.rs # fn main() {\n# let string1 = String::from(\"abcd\");\n# let string2 = \"xyz\";\n# # let result = longest(string1.as_str(), string2);\n# println!(\"The longest string is {result}\");\n# }\n# fn longest<'a>(x: &str, y: &str) -> &'a str { let result = String::from(\"really long string\"); result.as_str()\n} Здесь, несмотря на то, что мы указали свойство времени жизни 'a для возвращаемого вида, выполнение не будет собрана, потому что время жизни возвращаемого значения никак не связано с временем жизни свойств. Получаем сообщение об ошибке: $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10)\nerror[E0515]: cannot return value referencing local variable `result` --> src/main.rs:11:5 |\n11 | result.as_str() | ------^^^^^^^^^ | | | returns a value referencing data owned by the current function | `result` is borrowed here For more information about this error, try `rustc --explain E0515`.\nerror: could not compile `chapter10` (bin \"chapter10\") due to 1 previous error Неполадказаключается в том, что result выходит за область видимости и очищается в конце функции longest. Мы также пытаемся вернуть ссылку на result из функции. Мы не можем указать свойства времени жизни, которые могли бы изменить недействительную ссылку, а Ржавчина не позволит нам создать недействительную ссылку. В этом случае лучшим решением будет вернуть владеющий вид данных, а не ссылку: в этом случае вызывающая функция будет нести ответственность за очистку полученного ею значения. В конечном итоге, правила написания времён жизни выполняет связывание времён жизни различных переменных и возвращаемых значений функций. Описывая времена жизни, мы даём Ржавчина достаточно сведений, чтобы разрешить безопасные действия с памятью и запретить действия, которые могли бы создать недействительные ссылки или иным способом нарушить безопасность памяти.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Мышление в понятиях времён жизни","id":"187","title":"Мышление в понятиях времён жизни"},"188":{"body":"До сих пор мы объявляли устройства, которые всегда содержали владеющие виды данных. Устройства могут содержать и ссылки, но при этом необходимо добавить изложение времени жизни для каждой ссылки в определении устройства. Приложение 10-24 описывает устройство ImportantExcerpt, содержащую срез строки: Файл: src/main.rs struct ImportantExcerpt<'a> { part: &'a str,\n} fn main() { let novel = String::from(\"Call me Ishmael. Some years ago...\"); let first_sentence = novel.split('.').next().unwrap(); let i = ImportantExcerpt { part: first_sentence, };\n} Приложение 10-25. Устройства, содержащая ссылку, требует изложении времени жизни У устройства имеется одно поле part, хранящее срез строки, который сам по себе является ссылкой. Как и в случае с обобщёнными видами данных, мы объявляем имя обобщённого свойства времени жизни внутри угловых скобок после имени устройства, чтобы иметь возможность использовать его внутри определения устройства. Данная изложение означает, что образец ImportantExcerpt не может пережить ссылку, которую он содержит в своём поле part. Функция main здесь создаёт образец устройства ImportantExcerpt, который содержит ссылку на первое предложение вида String принадлежащее переменной novel. Данные в novel существуют до создания образца ImportantExcerpt. Кроме того, novel не выходит из области видимости до тех пор, пока ImportantExcerpt не выйдет за область видимости, поэтому ссылка в внутри образца ImportantExcerpt является действительной.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Определение времён жизни при объявлении устройств","id":"188","title":"Определение времён жизни при объявлении устройств"},"189":{"body":"Вы изучили, что у каждой ссылки есть время жизни и что нужно указывать свойства времени жизни для функций или устройств, которые используют ссылки. Однако в Главе 4 у нас была функция в приложении 4-9, которая затем снова показана в приложении 10-25, в которой код собрался без наставлений времени жизни. Файл: src/lib.rs fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..]\n}\n# # fn main() {\n# let my_string = String::from(\"hello world\");\n# # // first_word works on slices of `String`s\n# let word = first_word(&my_string[..]);\n# # let my_string_literal = \"hello world\";\n# # // first_word works on slices of string literals\n# let word = first_word(&my_string_literal[..]);\n# # // Because string literals *are* string slices already,\n# // this works too, without the slice syntax!\n# let word = first_word(my_string_literal);\n# } Приложение 10-25: Функция, которую мы определили в приложении 4-9 собирается без наставлений времени жизни, несмотря на то, что входной и возвращаемый вид свойств являются ссылками Причина, по которой этот код собирается — историческая. В ранних (до-1.0) исполнениях Ржавчина этот код не собрался бы, поскольку каждой ссылке нужно было явно назначать время жизни. В те времена, ярлык функции была бы написана примерно так: fn first_word<'a>(s: &'a str) -> &'a str { После написания большого количества кода на Ржавчина разработчики языка обнаружили, что в определённых случаейх программисты описывают одни и те же изложении времён жизни снова и снова. Эти случаи были предсказуемы и следовали нескольким определенным образцовым моделям. Объединение Ржавчина решила запрограммировать эти образцы в код сборщика Rust, чтобы анализатор заимствований мог вывести времена жизни в таких случаейх без необходимости явного указания наставлений программистами. Мы упоминаем этот отрывок истории Rust, потому что возможно, что в будущем появится больше образцов для самостоятельного выведения времён жизни, которые будут добавлены в сборщик. Таким образом, в будущем может понадобится ещё меньшее количество наставлений. Образцы, запрограммированные в анализаторе ссылок языка Rust, называются правилами неявного выведения времени жизни . Это не правила, которым должны следовать программисты; а набор частных случаев, которые рассмотрит сборщик, и, если ваш код попадает в эти случаи, вам не нужно будет указывать время жизни явно. Правила выведения не предоставляют полного заключения. Если Ржавчина определенно применяет правила, но некоторая неясность относительно времён жизни ссылок все ещё остаётся, сборщик не будет догадываться, какими должны быть времена жизни оставшихся ссылок. В этом случае, вместо угадывания сборщик выдаст ошибку, которую вы можете устранить, добавив изложении времени жизни. Времена жизни свойств функции или способа называются временем жизни ввода , а времена жизни возвращаемых значений называются временем жизни вывода . Сборщик использует три правила, чтобы выяснить времена жизни ссылок при отсутствии явных наставлений. Первое правило относится ко времени жизни ввода, второе и третье правила применяются ко временам жизни вывода. Если сборщик доходит до конца проверки трёх правил и всё ещё есть ссылки, для которых он не может выяснить время жизни, сборщик остановится с ошибкой. Эти правила применяются к объявлениям fn, а также к разделам impl. Первое правило заключается в том, что каждый свойство являющийся ссылкой, получает свой собственный свойство времени жизни. Другими словами, функция с одним свойствоом получит один свойство времени жизни: fn foo<'a>(x: &'a i32); функция с двумя переменнойми получит два отдельных свойства времени жизни: fn foo<'a, 'b>(x: &'a i32, y: &'b i32), и так далее. Второе правило говорит, что если есть ровно один входной свойство времени жизни, то его время жизни назначается всем выходным свойствам: fn foo<'a>(x: &'a i32) -> &'a i32. Третье правило о том, что если есть множество входных свойств времени жизни, но один из них является ссылкой &self или &mut self, так как эта функция является способом, то время жизни self назначается временем жизни всем выходным свойствам. Это третье правило делает способы намного приятнее для чтения и записи, потому что требуется меньше символов. Представим, что мы сборщик и применим эти правила, чтобы вывести времена жизни ссылок в ярлыке функции first_word приложения 10-25. Ярлык этой функции начинается без объявления времён жизни ссылок: fn first_word(s: &str) -> &str { Теперь мы (в качестве сборщика) применим первое правило, утверждающее, что каждый свойство функции получает своё собственное время жизни. Как обычно, назовём его 'a и теперь ярлык выглядит так: fn first_word<'a>(s: &'a str) -> &str { Далее применяем второе правило, поскольку в функции указан только один входной свойство времени жизни. Второе правило гласит, что время жизни единственного входного свойства назначается выходным свойствам, поэтому ярлык теперь преобразуется таким образом: fn first_word<'a>(s: &'a str) -> &'a str { Теперь все ссылки в этой функции имеют свойства времени жизни и сборщик может продолжить свой анализ без необходимости просить у программиста указать изложении времён жизни в ярлыке этой функции. Давайте рассмотрим ещё один пример: на этот раз функцию longest, в которой не было свойств времени жизни, когда мы начали с ней работать в приложении 10-20: fn longest(x: &str, y: &str) -> &str { Применим первое правило: каждому свойству назначается собственное время жизни. На этот раз у функции есть два свойства, поэтому есть два времени жизни: fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str { Можно заметить, что второе правило здесь не применимо, так как в ярлыке указано больше одного входного свойства времени жизни. Третье правило также не применимо, так как longest — функция, а не способ, следовательно, в ней нет свойства self. Итак, мы прошли все три правила, но так и не смогли вычислить время жизни выходного свойства. Поэтому мы и получили ошибку при попытке собрать код приложения 10-20: сборщик работал по правилам неявного выведения времён жизни, но не мог выяснить все времена жизни ссылок в ярлыке. Так как третье правило применяется только к способам, далее мы рассмотрим времена жизни в этом среде, чтобы понять, почему нам часто не требуется определять времена жизни в ярлыках способов.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Правила неявного выведения времени жизни","id":"189","title":"Правила неявного выведения времени жизни"},"19":{"body":"Установка Ржавчина также включает местную повтор документации, чтобы вы могли читать её в без доступа к мировой сети режиме. Выполните rustup doc, чтобы открыть местную документацию в браузере. Если обычная библиотека предоставляет вид или функцию, а вы не знаете, что она делает или как её использовать, воспользуйтесь документацией внешней оболочки прикладного программирования (API), чтобы это узнать!","breadcrumbs":"С чего начать » Установка » Местная документация","id":"19","title":"Местная документация"},"190":{"body":"Когда мы выполняем способы для устройств с временами жизни, мы используем тот же правила написания, который применялся для наставлений обобщённых видов данных на приложении 10-11. Место, где мы объявляем и используем времена жизни, зависит от того, с чем они связаны — с полями устройства, либо с переменнойми способов и возвращаемыми значениями. Имена свойств времени жизни для полей устройств всегда описываются после ключевого слова impl и затем используются после имени устройства, поскольку эти времена жизни являются частью вида устройства. В ярлыках способов внутри раздела impl ссылки могут быть привязаны ко времени жизни ссылок в полях устройства, либо могут быть независимыми. Вдобавок, правила неявного выведения времён жизни часто делают так, что изложении переменных времён жизни являются необязательными в ярлыках способов. Рассмотрим несколько примеров, использующих устройство с названием ImportantExcerpt, которую мы определили в приложении 10-24. Сначала, воспользуемся способом level, чей единственный свойство является ссылкой на self, а возвращаемое значение i32, не является ссылкой ни на что: # struct ImportantExcerpt<'a> {\n# part: &'a str,\n# }\n# impl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 }\n}\n# # impl<'a> ImportantExcerpt<'a> {\n# fn announce_and_return_part(&self, announcement: &str) -> &str {\n# println!(\"Attention please: {announcement}\");\n# self.part\n# }\n# }\n# # fn main() {\n# let novel = String::from(\"Call me Ishmael. Some years ago...\");\n# let first_sentence = novel.split('.').next().unwrap();\n# let i = ImportantExcerpt {\n# part: first_sentence,\n# };\n# } Объявление свойства времени жизни после impl и его использование после имени вида является обязательным, но нам не нужно определять время жизни ссылки на self, благодаря первому правилу неявного выведения времён жизни. Вот пример, где применяется третье правило неявного выведения времён жизни: # struct ImportantExcerpt<'a> {\n# part: &'a str,\n# }\n# # impl<'a> ImportantExcerpt<'a> {\n# fn level(&self) -> i32 {\n# 3\n# }\n# }\n# impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part(&self, announcement: &str) -> &str { println!(\"Attention please: {announcement}\"); self.part }\n}\n# # fn main() {\n# let novel = String::from(\"Call me Ishmael. Some years ago...\");\n# let first_sentence = novel.split('.').next().unwrap();\n# let i = ImportantExcerpt {\n# part: first_sentence,\n# };\n# } В этом способе имеется два входных свойства, поэтому Ржавчина применит первое правило и назначит обоим свойствам &self и announcement собственные времена жизни. Далее, поскольку один из свойств является &self, то возвращаемое значение получает время жизни переменой &self и все времена жизни теперь выведены.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Изложение времён жизни в определении способов","id":"190","title":"Изложение времён жизни в определении способов"},"191":{"body":"Одно особенное время жизни, которое мы должны обсудить, называется 'static. Оно означает, что данная ссылка может жить всю продолжительность работы программы. Все строковые записи по умолчанию имеют время жизни 'static, но мы можем указать его явным образом: let s: &'static str = \"I have a static lifetime.\"; Содержание этой строки сохраняется внутри двоичного файл программы и всегда доступно для использования. Следовательно, время жизни всех строковых записей равно 'static. Сообщения сборщика об ошибках в качестве решения сбоев могут предлагать вам использовать время жизни 'static. Но прежде чем указывать 'static как время жизни для ссылки, подумайте, на самом ли деле данная ссылка будет доступна во всё время работы программы. В большинстве случаев, сообщения об ошибках, предлагающие использовать время жизни 'static появляются при попытках создания недействительных ссылок или несовпадения имеющихся времён жизни. В таких случаях, решение заключается в исправлении таких неполадок. а не в указании постоянного времени жизни 'static.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Постоянное время жизни","id":"191","title":"Постоянное время жизни"},"192":{"body":"Давайте кратко рассмотрим правила написания задания свойств обобщённых видов, ограничений особенности и времён жизни совместно в одной функции: # fn main() {\n# let string1 = String::from(\"abcd\");\n# let string2 = \"xyz\";\n# # let result = longest_with_an_announcement(\n# string1.as_str(),\n# string2,\n# \"Today is someone's birthday!\",\n# );\n# println!(\"The longest string is {result}\");\n# }\n# use std::fmt::Display; fn longest_with_an_announcement<'a, T>( x: &'a str, y: &'a str, ann: T,\n) -> &'a str\nwhere T: Display,\n{ println!(\"Announcement! {ann}\"); if x.len() > y.len() { x } else { y }\n} Это функция longest из приложения 10-21, которая возвращает наибольший из двух срезов строки. Но теперь у неё есть дополнительный свойство с именем ann обобщённого вида T, который может быть представлен любым видом, выполняющим особенность Display, как указано в предложении where. Этот дополнительный свойство будет напечатан с использованием {} , поэтому ограничение особенности Display необходимо. Поскольку время жизни является обобщённым видом, то объявления свойства времени жизни 'a и свойства обобщённого вида T помещаются в один список внутри угловых скобок после имени функции.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Обобщённые виды свойств, ограничения особенностей и времена жизни вместе","id":"192","title":"Обобщённые виды свойств, ограничения особенностей и времена жизни вместе"},"193":{"body":"В этой главе мы рассмотрели много всего! Теперь вы знакомы с свойствами обобщённого вида, особенностями и ограничениями особенности, обобщёнными свойствами времени жизни, вы готовы писать код без повторений, который будет работать во множестве различных случаев. Свойства обобщённого вида позволяют использовать код для различных видов данных. Особенности и ограничения особенности помогают убедиться, что, хотя виды и обобщённые, они будут вести себя, как этого требует ваш код. Вы изучили, как использовать изложении времени жизни чтобы убедиться, что этот гибкий код не будет порождать никаких повисших ссылок. И весь этот анализ происходит в мгновение сборки и не влияет на производительность программы во время работы! Верите или нет, но в рамках этой темы всё есть ещё чему поучиться: в Главе 17 обсуждаются особенности-предметы, которые являются ещё одним способом использования особенностей. Существуют также более сложные сценарии с изложениями времени жизни, которые вам понадобятся только в очень сложных случаях; для этого вам следует прочитать Rust Reference . Далее вы узнаете, как писать проверки на Rust, чтобы убедиться, что ваш код работает так, как задумано.","breadcrumbs":"Общие виды, особенности (свойства) и время жизни » Валидация ссылок посредством сроков жизни » Итоги","id":"193","title":"Итоги"},"194":{"body":"В своём эссе 1972 года “The Humble Programmer,” Edsger W. Dijkstra сказал, что «Проверка программы может быть очень эффективным способом показать наличие ошибок, но это безнадёжно неадекватно для показа их отсутствия». Это не значит, что мы не должны пытаться проверять столько, сколько мы можем! Соблюдение правил программы считается то, в какой степени наш код выполняет именно то, что мы задумывали. Ржавчина разработан с учётом большой озабоченности соблюдением правил программ, но соблюдение правил сложна и нелегко доказуема. Система определения Ржавчина берет на себя огромную часть этого бремени, но она не может уловить абсолютно все сбоев. Поэтому в Ржавчина предусмотрена возможность написания автопроверок. Допустим, мы пишем функцию add_two, которая прибавляет 2 к любому переданному ей числу. Ярлык этой функции принимает целое число в качестве свойства и возвращает целое число в качестве итога. Когда мы выполняем и собираем эту функцию, Ржавчина выполняет всю проверку видов и проверку заимствований, которую вы уже изучили, чтобы убедиться, что, например, мы не передаём значение String или недопустимую ссылку в эту функцию. Но Ржавчина не способен проверить, что эта функция сделает именно то, что мы задумали, то есть вернёт свойство плюс 2, а не, скажем, свойство плюс 10 или свойство - 50! Вот тут-то и приходят на помощь проверки. Мы можем написать проверки, которые утверждают, например, что когда мы передаём 3 в функцию add_two, возвращаемое значение будет 5. Мы можем запускать эти проверки всякий раз, когда мы вносим изменения в наш код, чтобы убедиться, что любое существующее правильное поведение не изменилось. Проверка - сложный навык: мы не сможем охватить все подробности написания хороших проверок в одной главе, но мы обсудим основные подходы к проверке в Rust. Мы поговорим об изложениех и макросах, доступных вам для написания проверок, о поведении по умолчанию и свойствах, предусмотренных для запуска проверок, а также о том, как согласовать проверки в состоящие из звеньев проверки и встроенные проверки.","breadcrumbs":"Написание самостоятельно х проверок » Написание автоматизированных проверок","id":"194","title":"Написание автоматизированных проверок"},"195":{"body":"Проверки - это функции Rust, которые проверяют, что не проверочный код работает ожидаемым образом. Содержимое проверочных функций обычно выполняет следующие три действия: Установка любых необходимых данных или состояния. Запуск кода, который вы хотите проверить. Утверждение, что итоги являются теми, которые вы ожидаете. Давайте рассмотрим функции предоставляемые в Ржавчина целенаправленно для написания проверок, которые выполнят все эти действия, включая свойство test, несколько макросов и свойство should_panic.","breadcrumbs":"Написание самостоятельно х проверок » Как писать проверки » Как писать проверки","id":"195","title":"Как писать проверки"},"196":{"body":"В простейшем случае в Ржавчина проверка - это функция, определенная свойством test. Свойства представляют собой метаданные о отрывках кода Rust; один из примеров свойство derive, который мы использовали со устройствами в главе 5. Чтобы превратить функцию в проверяющую функцию добавьте #[test] в строку перед fn. Когда вы запускаете проверки приказом cargo test, Ржавчина создаёт двоичный звено выполняющий функции определеные свойством test и сообщающий о том, успешно или нет прошла каждая проверяющая функция. Когда мы создаём новый дело библиотеки с помощью Cargo, то в нём самостоятельно порождается проверочный звено с проверку-функцией для нас. Этот звено даст вам образец для написания ваших проверок, так что вам не нужно искать точную устройство и правила написания проверочных функций каждый раз, когда вы начинаете новый дело. Вы можете добавить столько дополнительных проверочных функций и столько проверочных звеньев, сколько захотите! Мы исследуем некоторые особенности работы проверок, экспериментируя с образцовым проверкой созданным для нас, без существующего проверки любого кода. Затем мы напишем некоторые существующие проверки, которые вызывают некоторый написанный код и убедимся в его правильном поведении. Мы рассмотрим некоторые особенности работы проверок, поэкспериментируем с образцовым проверкой, прежде чем приступать к действительному проверке любого кода. Затем мы напишем несколько существующих проверок, которые вызывают некоторый написанный нами код и проверяют, что его поведение правильное. Давайте создадим новый библиотечный дело под названием adder, который складывает два числа: $ cargo new adder --lib Created library `adder` project\n$ cd adder Содержимое файла src/lib.rs вашей библиотеки adder должно выглядеть как в приложении 11-1. Файл: src/lib.rs pub fn add(left: usize, right: usize) -> usize { left + right\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); }\n} Приложение 11-1: Проверочный звено и функция, созданные самостоятельно с помощью cargo new Сейчас давайте пренебрегаем первые две строчки кода и сосредоточимся на функции. Обратите внимание на правила написания изложении #[test]: этот свойство указывает, что это проверочная функция, поэтому запускающий проверка знает, что эту функцию следует рассматривать как проверочную. У нас также могут быть не проверяемые функции в звене tests, которые помогут настроить общие сценарии или выполнить общие действия, поэтому нам всегда нужно указывать, какие функции являются проверкими. В теле функции проверки используется макрос assert_eq!, чтобы утверждать, что result, который содержит итог сложения 2 и 2, равен 4. Это утверждение служит примером вида для типичного проверки. Давайте запустим его, чтобы убедиться, что этот проверка пройден. Приказ cargo test выполнит все проверки в выбранном деле и сообщит о итогах как в приложении 11-2: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.57s Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 1 test\ntest tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests adder running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Приложение 11-2: Вывод сведений о работе самостоятельно созданных проверок Cargo собрал и выполнил проверку. Мы видим строку running 1 test. Следующая строка показывает имя созданной проверочной функции, называемой it_works, и итог запуска этого проверки равный ok. Текст test result: ok. означает, что все проверки пройдены успешно и часть вывода 1 passed; 0 failed сообщает общее количество проверок, которые прошли или были ошибочными. Можно пометить проверка как пренебрегаемый, чтобы он не выполнялся в определенном случае; мы рассмотрим это в разделе “Пренебрежение некоторых проверок, если их целенаправленно не запрашивать” позже в этой главе. Поскольку в данный мгновение мы этого не сделали, в сводке показано, что 0 ignored. Мы также можем передать переменная приказу cargo test для запуска только тех проверок, имя которых соответствует строке; это называется выборкой , и мы рассмотрим это в разделе “Запуск подмножества проверок по имени” . Мы также не фильтровали выполняемые проверки, поэтому в конце сводки показано, что 0 filtered out. Исчисление 0 measured предназначена для проверок производительности. На мгновение написания этой статьи такие проверки доступны только в ночной сборке Rust. Посмотрите документацию о проверках производительности , чтобы узнать больше. Следующая часть вывода проверок начинается с Doc-tests adder - это сведения о проверках в документации. У нас пока нет проверок документации, но Ржавчина может собирать любые примеры кода, которые находятся в API документации. Такая возможность помогает поддерживать документацию и код в согласованном состоянии. Мы поговорим о написании проверок документации в разделы \"Примечания документации как проверки\" Главы 14. Пока просто пренебрегаем часть Doc-tests вывода. Давайте начнём настраивать проверка в соответствии с нашими собственными потребностями. Сначала поменяем название нашего проверки it_works на exploration, вот так: Файл: src/lib.rs pub fn add(left: usize, right: usize) -> usize { left + right\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn exploration() { let result = add(2, 2); assert_eq!(result, 4); }\n} Снова выполним приказ cargo test. Вывод показывает наименование нашей проверку-функции - exploration вместо it_works: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.59s Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 1 test\ntest tests::exploration ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests adder running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Добавим ещё один проверка, но в этот раз целенаправленно сделаем так, чтобы этот новый проверка не отработал! Проверка терпит неудачу, когда что-то паникует в проверяемой функции. Каждый проверка запускается в новом потоке и когда главный поток видит, что проверочный поток упал, то помечает проверка как завершившийся со сбоем. Мы говорили о простейшем способе вызвать панику в главе 9, используя для этого известный макрос panic!. Введём код проверку-функции another, как в файле src/lib.rs из приложения 11-3. Файл: src/lib.rs # pub fn add(left: usize, right: usize) -> usize {\n# left + right\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn exploration() {\n# let result = add(2, 2);\n# assert_eq!(result, 4);\n# }\n# # #[test]\n# fn another() {\n# panic!(\"Make this test fail\");\n# }\n# } Приложение 11-3: Добавление второго проверки, который завершится ошибкой, потому что мы вызываем panic! макрос Запустим приказ cargo test. Вывод итогов показан в приложении 11-4, который сообщает, что проверка exploration пройден, а another нет: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.72s Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 2 tests\ntest tests::another ... FAILED\ntest tests::exploration ... ok failures: ---- tests::another stdout ----\nthread 'tests::another' panicked at src/lib.rs:17:9:\nMake this test fail\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::another test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib` Приложение 11-4. Итоги проверки, когда один проверка пройден, а другой нет Вместо ok, строка test tests::another сообщает FAILED. Две новые разделы появились между отдельными итогами и сводкой: в первом отображается подробная причина каждого сбоя проверки. В данном случае проверка another не сработал, потому что panicked at 'Make this test fail', произошло в строке 10 файла src/lib.rs . В следующем разделе перечисляют имена всех не пройденных проверок, что удобно, когда есть много проверок и много подробных итогов неудачных проверок. Мы можем использовать имя не пройденного проверки для его дальнейшей отладки; мы больше поговорим о способах запуска проверок в разделе \"Управление хода выполнения проверок\" . Итоговая строка отображается в конце: общий итог нашего проверки FAILED. У нас один проверка пройден и один проверка завершён со сбоем. Теперь, когда вы увидели, как выглядят итоги проверки при разных сценариях, давайте рассмотрим другие макросы полезные в проверках, кроме panic!.","breadcrumbs":"Написание самостоятельно х проверок » Как писать проверки » Устройства проверяющей функции","id":"196","title":"Устройства проверяющей функции"},"197":{"body":"Макрос assert! доступен из встроенной библиотеки и является удобным, когда вы хотите проверить что некоторое условие в проверке вычисляется в значение true. Мы передаём в макрос assert! переменная, который вычисляется в логическое значение. Если оно true, то ничего не происходит и проверка считается пройденным. Если же значение вычисляется в false, то макрос assert! вызывает макрос panic!, чтобы вызвать сбой проверки. Использование макроса assert! помогает проверить, что код исполняется как ожидалось. В главе 5, приложении 5-15, мы использовали устройство Rectangle и способ can_hold, который повторён в приложении 11-5. Давайте поместим этот код в файл src/lib.rs и напишем несколько проверок для него используя макрос assert!. Файл: src/lib.rs # #[derive(Debug)]\n# struct Rectangle {\n# width: u32,\n# height: u32,\n# }\n# # impl Rectangle {\n# fn can_hold(&self, other: &Rectangle) -> bool {\n# self.width > other.width && self.height > other.height\n# }\n# } Приложение 11-5: Использование устройства Rectangle и её способа can_hold из главы 5 Способ can_hold возвращает логическое значение, что означает, что он является наилучшим исходом использования в макросе assert!. В приложении 11-6 мы пишем проверка, который выполняет способ can_hold путём создания образца Rectangle шириной 8 и высотой 7 и убеждаемся, что он может содержать другой образец Rectangle имеющий ширину 5 и высоту 1. Файл: src/lib.rs # #[derive(Debug)]\n# struct Rectangle {\n# width: u32,\n# height: u32,\n# }\n# # impl Rectangle {\n# fn can_hold(&self, other: &Rectangle) -> bool {\n# self.width > other.width && self.height > other.height\n# }\n# }\n# #[cfg(test)]\nmod tests { use super::*; #[test] fn larger_can_hold_smaller() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(larger.can_hold(&smaller)); }\n} Приложение 11-6: Проверка для способа can_hold, который проверяет что больший прямоугольник действительно может содержать меньший Также, в звене tests обратите внимание на новую добавленную строку use super::*;. Звено tests является обычным и подчиняется тем же правилам видимости, которые мы обсуждали в главе 7 \"Пути для ссылки на элементы внутри дерева звена\" . Так как этот звено tests является внутренним, нужно подключить проверяемый код из внешнего звена в область видимости внутреннего звена с проверкими. Для этого используется вездесущеее подключение, так что все что определено во внешнем звене становится доступным внутри tests звена. Мы назвали наш проверка larger_can_hold_smaller и создали два нужных образца Rectangle. Затем вызвали макрос assert! и передали итог вызова larger.can_hold(&smaller) в него. Это выражение должно возвращать true, поэтому наш проверка должен пройти. Давайте выясним! $ cargo test Compiling rectangle v0.1.0 (file:///projects/rectangle) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e) running 1 test\ntest tests::larger_can_hold_smaller ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests rectangle running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Проверка проходит. Теперь добавим другой проверка, в этот раз мы попытаемся убедиться, что меньший прямоугольник не может содержать больший прямоугольник: Файл: src/lib.rs # #[derive(Debug)]\n# struct Rectangle {\n# width: u32,\n# height: u32,\n# }\n# # impl Rectangle {\n# fn can_hold(&self, other: &Rectangle) -> bool {\n# self.width > other.width && self.height > other.height\n# }\n# }\n# #[cfg(test)]\nmod tests { use super::*; #[test] fn larger_can_hold_smaller() { // --snip--\n# let larger = Rectangle {\n# width: 8,\n# height: 7,\n# };\n# let smaller = Rectangle {\n# width: 5,\n# height: 1,\n# };\n# # assert!(larger.can_hold(&smaller)); } #[test] fn smaller_cannot_hold_larger() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(!smaller.can_hold(&larger)); }\n} Поскольку правильный итог функции can_hold в этом случае false, то мы должны инвертировать этот итог, прежде чем передадим его в assert! макро. Как итог, наш проверка пройдёт, если can_hold вернёт false: $ cargo test Compiling rectangle v0.1.0 (file:///projects/rectangle) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e) running 2 tests\ntest tests::larger_can_hold_smaller ... ok\ntest tests::smaller_cannot_hold_larger ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests rectangle running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Два проверки работают. Теперь проверим, как отреагируют проверки, если мы добавим ошибку в код. Давайте изменим выполнение способа can_hold заменив одно из логических выражений знак сравнения с \"больше чем\" на противоположный \"меньше чем\" при сравнении ширины: # #[derive(Debug)]\n# struct Rectangle {\n# width: u32,\n# height: u32,\n# }\n# // --snip--\nimpl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width < other.width && self.height > other.height }\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn larger_can_hold_smaller() {\n# let larger = Rectangle {\n# width: 8,\n# height: 7,\n# };\n# let smaller = Rectangle {\n# width: 5,\n# height: 1,\n# };\n# # assert!(larger.can_hold(&smaller));\n# }\n# # #[test]\n# fn smaller_cannot_hold_larger() {\n# let larger = Rectangle {\n# width: 8,\n# height: 7,\n# };\n# let smaller = Rectangle {\n# width: 5,\n# height: 1,\n# };\n# # assert!(!smaller.can_hold(&larger));\n# }\n# } Запуск проверок теперь производит следующее: $ cargo test Compiling rectangle v0.1.0 (file:///projects/rectangle) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s Running unittests src/lib.rs (target/debug/deps/rectangle-6584c4561e48942e) running 2 tests\ntest tests::larger_can_hold_smaller ... FAILED\ntest tests::smaller_cannot_hold_larger ... ok failures: ---- tests::larger_can_hold_smaller stdout ----\nthread 'tests::larger_can_hold_smaller' panicked at src/lib.rs:28:9:\nassertion failed: larger.can_hold(&smaller)\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::larger_can_hold_smaller test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib` Наши проверки нашли ошибку! Так как в проверке larger.width равно 8 и smaller.width равно 5 сравнение ширины в способе can_hold возвращает итог false, поскольку число 8 не меньше чем 5.","breadcrumbs":"Написание самостоятельно х проверок » Как писать проверки » Проверка итогов с помощью макроса assert!","id":"197","title":"Проверка итогов с помощью макроса assert!"},"198":{"body":"Общим способом проверки возможности является использование сравнения итога проверяемого кода и ожидаемого значения, чтобы убедиться в их равенстве. Для этого можно использовать макрос assert!, передавая ему выражение с использованием оператора ==. Важно также знать, что кроме этого обычная библиотека предлагает пару макросов assert_eq! и assert_ne!, чтобы сделать проверка более удобным. Эти макросы сравнивают два переменной на равенство или неравенство соответственно. Макросы также печатают два значения входных свойств, если проверка завершился ошибкой, что позволяет легче увидеть почему проверка ошибочен. Противоположно этому, макрос assert! может только отобразить, что он вычислил значение false для выражения ==, но не значения, которые привели к итогу false. В приложении 11-7, мы напишем функцию add_two, которая прибавляет к входному свойству 2 и возвращает значение. Затем, проверим эту функцию с помощью макроса assert_eq!: Файл: src/lib.rs pub fn add_two(a: usize) -> usize { a + 2\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn it_adds_two() { let result = add_two(2); assert_eq!(result, 4); }\n} Приложение 11-7: Проверка функции add_two с помощью макроса assert_eq! Проверим, что проверки проходят! $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 1 test\ntest tests::it_adds_two ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests adder running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Первый переменная, который мы передаём в макрос assert_eq! число 4 чей итог вызова равен add_two(2) . Строка для этого проверки - test tests::it_adds_two ... ok , а текст ok означает, что наш проверка пройден! Давайте введём ошибку в код, чтобы увидеть, как она выглядит, когда проверка, который использует assert_eq! завершается ошибкой. Измените выполнение функции add_two, чтобы добавлять 3: pub fn add_two(a: usize) -> usize { a + 3\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn it_adds_two() {\n# let result = add_two(2);\n# assert_eq!(result, 4);\n# }\n# } Попробуем выполнить данный проверка ещё раз: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 1 test\ntest tests::it_adds_two ... FAILED failures: ---- tests::it_adds_two stdout ----\nthread 'tests::it_adds_two' panicked at src/lib.rs:12:9:\nassertion `left == right` failed left: 5 right: 4\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::it_adds_two test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib` Наш проверка нашёл ошибку! Проверка it_adds_two не выполнился, отображается сообщение assertion failed: (left == right)`` и показывает, что left было 4, а right было 5. Это сообщение полезно и помогает начать отладку: это означает left переменная assert_eq! имел значение 4, но right переменная для вызова add_two(2) был со значением 5. Обратите внимание, что в некоторых языках (таких как Java) в библиотеках кода для проверки принято именовать входные свойства проверочных функций как \"ожидаемое\" (expected) и \"действительное\" (actual). В Ржавчина приняты следующие обозначения left и right соответственно, а порядок в котором определяются ожидаемое значение и производимое проверяемым кодом значение не имеют значения. Мы могли бы написать выражение в проверке как assert_eq!(add_two(2), 4), что приведёт к отображаемому сообщению об ошибке assertion failed: (left == right)``, слева left было бы 5, а справа right было бы 4. Макрос assert_ne! сработает успешно, если входные свойства не равны друг другу и завершится с ошибкой, если значения равны. Этот макрос наиболее полезен в тех случаях, когда мы не знаем заранее, каким значение будет , но знаем точно, каким оно не может быть. К примеру, если проверяется функция, которая обязательно изменяет входные данные определённым образом, но способ изменения входного свойства зависит от дня недели, в который запускаются проверки, что лучший способ проверить правильность работы такой функции - это сравнить и убедиться, что выходное значение функции не должно быть равным входному значению. В своей работе макросы assert_eq! и assert_ne! неявным образом используют операторы == и != соответственно. Когда проверка не срабатывает, макросы печатают значения переменных с помощью отладочного изменения и это означает, что значения сравниваемых переменных должны выполнить особенности PartialEq и Debug. Все простые и большая часть видов встроенной библиотеки Ржавчина выполняют эти особенности. Для устройств и перечислений, которые вы выполняете сами будет необходимо выполнить особенность PartialEq для сравнения значений на равенство или неравенство. Для печати отладочной сведений в виде сообщений в строку вывода окне вывода необходимо выполнить особенность Debug. Так как оба особенности являются выводимыми особенностями, как упоминалось в приложении 5-12 главы 5, то эти особенности можно выполнить добавив изложение #[derive(PartialEq, Debug)] к определению устройства или перечисления. Смотрите больше подробностей в Appendix C \"Выводимые особенности\" про эти и другие выводимые особенности.","breadcrumbs":"Написание самостоятельно х проверок » Как писать проверки » Проверка на равенство с помощью макросов assert_eq! и assert_ne!","id":"198","title":"Проверка на равенство с помощью макросов assert_eq! и assert_ne!"},"199":{"body":"Также можно добавить пользовательское сообщение как дополнительный переменная макросов для печати в сообщении об ошибке проверки assert!, assert_eq!, и assert_ne!. Любые переменные, указанные после обязательных переменных, далее передаются в макрос format! (он обсуждается в разделе \"Сцепление с помощью оператора + или макроса format!\" ), так что вы можете передать измененную строку, которая содержит {} для заполнителей и значения, заменяющие эти заполнители. Пользовательские сообщения полезны для пояснения того, что означает утверждение (assertion); когда проверка завершается неудачей, у вас будет лучшее представление о том, в чем неполадка с кодом. Например, есть функция, которая приветствует человека по имени и мы хотим проверять эту функцию. Мы хотим чтобы передаваемое ей имя выводилось в окно вывода: Файл: src/lib.rs pub fn greeting(name: &str) -> String { format!(\"Hello {name}!\")\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn greeting_contains_name() { let result = greeting(\"Carol\"); assert!(result.contains(\"Carol\")); }\n} Требования к этой программе ещё не были согласованы и мы вполне уверены, что текст Hello в начале приветствия ещё изменится. Мы решили, что не хотим обновлять проверка при изменении требований, поэтому вместо проверки на точное равенство со значением возвращённым из greeting, мы просто будем проверять, что вывод содержит текст из входного свойства. Давайте внесём ошибку в этот код, изменив greeting так, чтобы оно не включало name и увидим, как выглядит сбой этого проверки: pub fn greeting(name: &str) -> String { String::from(\"Hello!\")\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn greeting_contains_name() {\n# let result = greeting(\"Carol\");\n# assert!(result.contains(\"Carol\"));\n# }\n# } Запуск этого проверки выводит следующее: $ cargo test Compiling greeter v0.1.0 (file:///projects/greeter) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a) running 1 test\ntest tests::greeting_contains_name ... FAILED failures: ---- tests::greeting_contains_name stdout ----\nthread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:\nassertion failed: result.contains(\"Carol\")\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::greeting_contains_name test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib` Сообщение содержит лишь сведения о том что сравнение не было успешным и в какой строке это произошло. В данном случае, более полезный текст сообщения был бы, если бы также выводилось значение из функции greeting. Изменим проверяющую функцию так, чтобы выводились пользовательское сообщение измененное строкой с заменителем и действительными данными из кода greeting: # pub fn greeting(name: &str) -> String {\n# String::from(\"Hello!\")\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# #[test] fn greeting_contains_name() { let result = greeting(\"Carol\"); assert!( result.contains(\"Carol\"), \"Greeting did not contain name, value was `{result}`\" ); }\n# } После того, как выполним проверка ещё раз мы получим подробное сообщение об ошибке: $ cargo test Compiling greeter v0.1.0 (file:///projects/greeter) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.93s Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a) running 1 test\ntest tests::greeting_contains_name ... FAILED failures: ---- tests::greeting_contains_name stdout ----\nthread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:\nGreeting did not contain name, value was `Hello!`\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::greeting_contains_name test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib` Мы можем увидеть значение, которое мы на самом деле получили в проверочном выводе, что поможет нам отлаживать произошедшее, а не то, что мы ожидали.","breadcrumbs":"Написание самостоятельно х проверок » Как писать проверки » Создание сообщений об ошибках","id":"199","title":"Создание сообщений об ошибках"},"2":{"body":"Примечание. Это издание книги такое же, как и Язык программирования Rust , доступное в печатном и электронном виде от No Starch Press . Добро пожаловать в The Ржавчина Programming Language , вводную книгу о Rust. Язык программирования Ржавчина помогает создавать быстрые, более надёжные приложения. Хорошая удобство и низкоуровневый управление часто являются противоречивыми требованиями для внешнего видаязыков программирования; Ржавчина бросает вызов этому вражде. Благодаря уравновешенности мощных технических возможностей c большим удобством разработки, Ржавчина предоставляет возможности управления низкоуровневыми элементами (например, использование памяти) без трудностей, привычно связанных с таким управлением.","breadcrumbs":"Введение » Введение","id":"2","title":"Введение"},"20":{"body":"Теперь, когда вы установили Rust, пришло время написать свою первую программу на Rust. Привычно при изучении нового языка принято писать небольшую программу, которая печатает на экране текст Привет, мир!, поэтому мы сделаем то же самое! Примечание: Эта книга предполагает наличие достаточного навыка работы с приказной строкой. Ржавчина не предъявляет особых требований к тому, каким набором средств вы пользуетесь для изменения или хранения вашего кода, поэтому если вы предпочитаете использовать встроенную среду разработки (IDE) вместо приказной строки, смело используйте вашу любимую IDE. Многие IDE сейчас в той или иной степени поддерживают Rust; подробности можно узнать из документации к IDE. Объединение Ржавчина сосредоточилась на обеспечении отличной поддержки IDE с помощью rust-analyzer. Более подробную сведения смотрите в Приложении D .","breadcrumbs":"С чего начать » Привет, Мир! » Привет, мир!","id":"20","title":"Привет, мир!"},"200":{"body":"В дополнение к проверке того, что наш код возвращает правильные, ожидаемые значения, важным также является проверить, что наш код обрабатывает ошибки, которые мы ожидаем. Например, рассмотрим вид Guess который мы создали в главе 9, приложения 9-10. Другой код, который использует Guess зависит от заверения того, что Guess образцы будут содержать значения только от 1 до 100. Мы можем написать проверка, который заверяет, что попытка создать образец Guess со значением вне этого ряда вызывает панику. Выполняем это с помощью другого свойства проверку-функции #[should_panic]. Этот свойство сообщает системе проверки, что проверка проходит, когда способ порождает ошибку. Если ошибка не порождается - проверка считается не пройденным. Приложение 11-8 показывает проверка, который проверяет, что условия ошибки Guess::new произойдут, когда мы их ожидаем их. Файл: src/lib.rs pub struct Guess { value: i32,\n} impl Guess { pub fn new(value: i32) -> Guess { if value < 1 || value > 100 { panic!(\"Guess value must be between 1 and 100, got {value}.\"); } Guess { value } }\n} #[cfg(test)]\nmod tests { use super::*; #[test] #[should_panic] fn greater_than_100() { Guess::new(200); }\n} Приложение 11-8: Проверка того, что условие вызовет макрос panic! Свойство #[should_panic] следует после #[test] и до объявления проверочной функции. Посмотрим на вывод итога, когда проверка проходит: $ cargo test Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d) running 1 test\ntest tests::greater_than_100 - should panic ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests guessing_game running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Выглядит хорошо! Теперь давайте внесём ошибку в наш код, убрав условие о том, что функция new будет паниковать если значение больше 100: # pub struct Guess {\n# value: i32,\n# }\n# // --snip--\nimpl Guess { pub fn new(value: i32) -> Guess { if value < 1 { panic!(\"Guess value must be between 1 and 100, got {value}.\"); } Guess { value } }\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# #[should_panic]\n# fn greater_than_100() {\n# Guess::new(200);\n# }\n# } Когда мы запустим проверка в приложении 11-8, он потерпит неудачу: $ cargo test Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d) running 1 test\ntest tests::greater_than_100 - should panic ... FAILED failures: ---- tests::greater_than_100 stdout ----\nnote: test did not panic as expected failures: tests::greater_than_100 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib` Мы получаем не очень полезное сообщение в этом случае, но когда мы смотрим на проверяющую функцию, мы видим, что она #[should_panic]. Со сбоеме выполнение, которое мы получили означает, что код в проверяющей функции не вызвал паники. Проверки, которые используют should_panic могут быть неточными, потому что они только указывают, что код вызвал панику. Проверка с свойством should_panic пройдёт, даже если проверка паникует по причине, отличной от той, которую мы ожидали. Чтобы сделать проверки с should_panic более точными, мы можем добавить необязательный свойство expected для свойства should_panic. Такая подробностизация проверки позволит удостовериться, что сообщение об ошибке содержит предоставленный текст. Например, рассмотрим измененный код для Guess в приложении 11-9, где new функция паникует с различными сообщениями в зависимости от того, является ли значение слишком маленьким или слишком большим. Файл: src/lib.rs # pub struct Guess {\n# value: i32,\n# }\n# // --snip-- impl Guess { pub fn new(value: i32) -> Guess { if value < 1 { panic!( \"Guess value must be greater than or equal to 1, got {value}.\" ); } else if value > 100 { panic!( \"Guess value must be less than or equal to 100, got {value}.\" ); } Guess { value } }\n} #[cfg(test)]\nmod tests { use super::*; #[test] #[should_panic(expected = \"less than or equal to 100\")] fn greater_than_100() { Guess::new(200); }\n} Приложение 11-9: Проверка panic! на наличие в его сообщении указанной подстроки Этот проверка пройдёт, потому что значение, которое мы помеисполнения для should_panic в свойство свойства expected является подстрокой сообщения, с которым функция Guess::new вызывает панику. Мы могли бы указать полное, ожидаемое сообщение для паники, в этом случае это будет Guess value must be less than or equal to 100, got 200. То что вы выберите для указания как ожидаемого свойства у should_panic зависит от того, какая часть сообщения о панике неповторима или динамична, насколько вы хотите, чтобы ваш проверка был точным. В этом случае достаточно подстроки из сообщения паники, чтобы обеспечить выполнение кода в проверочной функции else if value > 100 . Чтобы увидеть, что происходит, когда проверка should_panic неуспешно завершается с сообщением expected, давайте снова внесём ошибку в наш код, поменяв местами if value < 1 и else if value > 100 блоки: # pub struct Guess {\n# value: i32,\n# }\n# # impl Guess {\n# pub fn new(value: i32) -> Guess { if value < 1 { panic!( \"Guess value must be less than or equal to 100, got {value}.\" ); } else if value > 100 { panic!( \"Guess value must be greater than or equal to 1, got {value}.\" ); }\n# # Guess { value }\n# }\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# #[should_panic(expected = \"less than or equal to 100\")]\n# fn greater_than_100() {\n# Guess::new(200);\n# }\n# } На этот раз, когда мы выполним should_panic проверка, он потерпит неудачу: $ cargo test Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.66s Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d) running 1 test\ntest tests::greater_than_100 - should panic ... FAILED failures: ---- tests::greater_than_100 stdout ----\nthread 'tests::greater_than_100' panicked at src/lib.rs:12:13:\nGuess value must be greater than or equal to 1, got 200.\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\nnote: panic did not contain expected string panic message: `\"Guess value must be greater than or equal to 1, got 200.\"`, expected substring: `\"less than or equal to 100\"` failures: tests::greater_than_100 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib` Сообщение об ошибке указывает, что этот проверка действительно вызвал панику, как мы и ожидали, но сообщение о панике не включено ожидаемую строку 'Guess value must be less than or equal to 100'. Сообщение о панике, которое мы получили в этом случае, было Guess value must be greater than or equal to 1, got 200. Теперь мы можем начать выяснение, где ошибка!","breadcrumbs":"Написание самостоятельно х проверок » Как писать проверки » Проверка с помощью макроса should_panic","id":"200","title":"Проверка с помощью макроса should_panic"},"201":{"body":"Пока что мы написали проверки, которые паникуют, когда терпят неудачу. Мы также можем написать проверки которые используют Result! Вот проверка из приложения 11-1, переписанный с использованием Result и возвращающий Err вместо паники: pub fn add(left: usize, right: usize) -> usize { left + right\n} #[cfg(test)]\nmod tests { use super::*; // ANCHOR: here #[test] fn it_works() -> Result<(), String> { let result = add(2, 2); if result == 4 { Ok(()) } else { Err(String::from(\"two plus two does not equal four\")) } } // ANCHOR_END: here\n} Функция it_works теперь имеет возвращаемый вид Result<(), String>. В теле функции, вместо вызова макроса assert_eq!, мы возвращаем Ok(()) когда проверка успешно выполнен и Err со String внутри, когда проверка не проходит. Написание проверок так, чтобы они возвращали Result позволяет использовать оператор \"вопросительный знак\" в теле проверок, который может быть удобным способом писать проверки, которые должны выполниться не успешно, если какая-либо действие внутри них возвращает исход ошибки Err. Вы не можете использовать изложение #[should_panic] в проверках, использующих Result. Чтобы утверждать, что действие возвращает исход Err, не используйте оператор вопросительного знака для значения Result. Вместо этого используйте assert!(value.is_err()). Теперь, когда вы знаете несколько способов написания проверок, давайте взглянем на то, что происходит при запуске проверок и исследуем разные возможности используемые с приказом cargo test.","breadcrumbs":"Написание самостоятельно х проверок » Как писать проверки » Использование Result в проверках","id":"201","title":"Использование Result в проверках"},"202":{"body":"Подобно тому, как cargo run выполняет сборку вашего кода, а затем запускает полученный двоичный файл, cargo test собирает ваш код в режиме проверки и запускает полученный двоичный файл с проверкими. Двоичный файл, создаваемый cargo test, по умолчанию запускает все проверки одновременно и перехватывает вывод, порождаемый во время выполнения проверок, предотвращая их вывод на экран для облегчения чтения вывода, относящегося к итогам проверки. Однако вы можете указать свойства приказной строки, чтобы изменить это поведение по умолчанию. Часть свойств приказной строки передаётся в cargo test, а часть - в итоговый двоичный файл с проверкими. Чтобы разделить эти два вида переменных, нужно сначала указать переменные, которые идут в cargo test, затем использовать разделитель --, а потом те, которые попадут в двоичный файл проверки. Выполнение cargo test --help выводит возможности, которые вы можете использовать с cargo test, а выполнение cargo test -- --help выводит возможности, которые вы можете использовать за разделителем.","breadcrumbs":"Написание самостоятельно х проверок » Управление выполнением проверок » Управление хода выполнения проверок","id":"202","title":"Управление хода выполнения проверок"},"203":{"body":"Когда вы запускаете несколько проверок, по умолчанию они выполняются одновременно с использованием потоков, что означает, что они завершатся быстрее, и вы быстрее получите итоги. Поскольку проверки выполняются одновременно, вы должны убедиться, что ваши проверки не зависят друг от друга или от какого-либо общего состояния, включая общее окружение, например, текущий рабочий папка или переменные окружения. Например, допустим, каждый из ваших проверок запускает код, который создаёт файл на диске с именем test-output.txt и записывает некоторые данные в этот файл. Затем каждый проверка считывает данные из этого файла и утверждает, что файл содержит определённое значение, которое в каждом проверке разное. Поскольку все проверки выполняются одновременно, один из проверок может перезаписать файл в промежутке между записью и чтением файла другим проверкой. Тогда второй проверка потерпит неудачу, но не потому, что код неверен, а потому, что эти проверки мешали друг другу при одновременном выполнении. Одно из решений - убедиться, что каждый проверка пишет в свой отдельный файл; другое решение - запускать проверки по одному. Если вы не хотите запускать проверки одновременно или хотите более подробный управление над количеством используемых потоков, можно установить флаг --test-threads и то количество потоков, которое вы хотите использовать для проверки. Взгляните на следующий пример: $ cargo test -- --test-threads=1 Мы устанавливаем количество проверочных потоков равным 1 , указывая программе не использовать одновременность. Выполнение проверок с использованием одного потока займёт больше времени, чем их одновременное выполнение, но проверки не будут мешать друг другу, если они совместно используют состояние.","breadcrumbs":"Написание самостоятельно х проверок » Управление выполнением проверок » Выполнение проверок одновременно или последовательно","id":"203","title":"Выполнение проверок одновременно или последовательно"},"204":{"body":"По умолчанию, если проверка пройден, система управления запуска проверок блокирует вывод на печать, т.е. если вы вызовете макрос println! внутри кода проверки и проверка будет пройден, вы не увидите вывода на окно вывода итогов вызова println!. Если же проверка не был пройден, все несущие сведения сообщения, а также описание ошибки будут выведены на окно вывода. Например, в коде (11-10) функция выводит значение свойства с поясняющим текстовым сообщением, а также возвращает целочисленное постоянных значенийное значение 10. Далее следует проверка, который имеет правильный входной свойство и проверка, который имеет ошибочный входной свойство: Файл: src/lib.rs fn prints_and_returns_10(a: i32) -> i32 { println!(\"I got the value {a}\"); 10\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn this_test_will_pass() { let value = prints_and_returns_10(4); assert_eq!(value, 10); } #[test] fn this_test_will_fail() { let value = prints_and_returns_10(8); assert_eq!(value, 5); }\n} Приложение 11-10: Проверка функции, которая использует макрос println! Итог вывода на окно вывода приказы cargo test: $ cargo test Compiling silly-function v0.1.0 (file:///projects/silly-function) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166) running 2 tests\ntest tests::this_test_will_fail ... FAILED\ntest tests::this_test_will_pass ... ok failures: ---- tests::this_test_will_fail stdout ----\nI got the value 8\nthread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:\nassertion `left == right` failed left: 10 right: 5\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::this_test_will_fail test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib` Обратите внимание, что нигде в этом выводе мы не видим сообщения I got the value 4 , которое печатается при выполнении пройденного проверки. Этот вывод был записан. Итог неудачного проверки, I got the value 8 , появляется в разделе итоговых итогов проверки, который также показывает причину неудачного проверки. Если мы хотим видеть напечатанные итоги прохождения проверок, мы можем сказать Rust, чтобы он также показывал итоги успешных проверок с помощью --show-output. $ cargo test -- --show-output Когда мы снова запускаем проверки из Приложения 11-10 с флагом --show-output , мы видим следующий итог: $ cargo test -- --show-output Compiling silly-function v0.1.0 (file:///projects/silly-function) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166) running 2 tests\ntest tests::this_test_will_fail ... FAILED\ntest tests::this_test_will_pass ... ok successes: ---- tests::this_test_will_pass stdout ----\nI got the value 4 successes: tests::this_test_will_pass failures: ---- tests::this_test_will_fail stdout ----\nI got the value 8\nthread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:\nassertion `left == right` failed left: 5 right: 10\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::this_test_will_fail test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib`","breadcrumbs":"Написание самостоятельно х проверок » Управление выполнением проверок » Отображение итогов работы функции","id":"204","title":"Отображение итогов работы функции"},"205":{"body":"Бывают случаи, когда в запуске всех проверок нет необходимости и нужно запустить только несколько проверок. Если вы работаете над функцией и хотите запустить проверки, которые исследуют её работу - это было бы удобно. Вы можете это сделать, используя приказ cargo test, передав в качестве переменной имена проверок. Для отображения, как запустить объединение проверок, мы создадим объединение проверок для функции add_two function, как показано в Приложении 11-11, и постараемся выбрать какие из них запускать. Файл: src/lib.rs pub fn add_two(a: usize) -> usize { a + 2\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn add_two_and_two() { let result = add_two(2); assert_eq!(result, 4); } #[test] fn add_three_and_two() { let result = add_two(3); assert_eq!(result, 5); } #[test] fn one_hundred() { let result = add_two(100); assert_eq!(result, 102); }\n} Приложение 11-11: Три проверки с различными именами Если вы выполните приказ cargo test без уточняющих переменных, все проверки выполнятся одновременно: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 3 tests\ntest tests::add_three_and_two ... ok\ntest tests::add_two_and_two ... ok\ntest tests::one_hundred ... ok test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests adder running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Запуск одного проверки Мы можем запустить один проверка с помощью указания его имени в приказу cargo test: $ cargo test one_hundred Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.69s Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 1 test\ntest tests::one_hundred ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s Был запущен только проверка с названием one_hundred; два других проверки не соответствовали этому названию. Итоги проверки с помощью вывода 2 filtered out дают нам понять, что у нас было больше проверок, но они не были запущены. Таким образом мы не можем указать имена нескольких проверок; будет использоваться только первое значение, указанное для cargo test . Но есть способ запустить несколько проверок. Использование фильтров для запуска нескольких проверок Мы можем указать часть имени проверки, и будет запущен любой проверка, имя которого соответствует этому значению. Например, поскольку имена двух наших проверок содержат add, мы можем запустить эти два, запустив cargo test add: $ cargo test add Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 2 tests\ntest tests::add_three_and_two ... ok\ntest tests::add_two_and_two ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s Этот приказ запускала все проверки с add в имени и отфильтровывала проверка с именем one_hundred . Также обратите внимание, что звено, в котором появляется проверка, становится частью имени проверки, поэтому мы можем запускать все проверки в звене, фильтруя имя звена.","breadcrumbs":"Написание самостоятельно х проверок » Управление выполнением проверок » Запуск подмножества проверок по имени","id":"205","title":"Запуск подмножества проверок по имени"},"206":{"body":"Бывает, что некоторые проверки требуют продолжительного времени для своего исполнения, и вы хотите исключить их из исполнения при запуске cargo test. Вместо перечисления в приказной строке всех проверок, которые вы хотите запускать, вы можете определять проверки, требующие много времени для прогона, свойством ignore, чтобы исключить их, как показано здесь: Файл: src/lib.rs pub fn add(left: usize, right: usize) -> usize { left + right\n} // ANCHOR: here\n#[cfg(test)]\nmod tests { use super::*; #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); } #[test] #[ignore] fn expensive_test() { // code that takes an hour to run }\n}\n// ANCHOR_END: here После #[test] мы добавляем строку #[ignore] в проверка, который хотим исключить. Теперь, когда мы запускаем наши проверки, it_works запускается, а expensive_test пренебрегается: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 2 tests\ntest tests::expensive_test ... ignored\ntest tests::it_works ... ok test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests adder running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Функция expensive_test помечена как ignored. Если вы хотите выполнить только пренебреженные проверки, вы можете воспользоваться приказом cargo test -- --ignored: $ cargo test -- --ignored Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 1 test\ntest expensive_test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s Doc-tests adder running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Управляя тем, какие проверки запускать, вы можете быть уверены, что итоги вашего cargo test будут быстрыми. Когда вы дойдёте до особенности, где имеет смысл проверить итоги проверок ignored, и у вас есть время дождаться их итогов, вы можете запустить их с помощью cargo test -- --ignored. Если вы хотите запустить все проверки независимо от того, пренебрегаются они или нет, выполните cargo test -- --include-ignored.","breadcrumbs":"Написание самостоятельно х проверок » Управление выполнением проверок » Пренебрежение проверок","id":"206","title":"Пренебрежение проверок"},"207":{"body":"Как упоминалось в начале главы, проверка является сложной пунктом и разные люди используют разную совокупность понятий и устройство. Сообщество Ржавчина думает о проверках с точки зрения двух основных разрядов: состоящие из звеньев проверки и встроенные проверки . Состоящие из звеньев проверки это небольшие и более сосредоточенные на проверке одного звена в отдельности или могут проверяться закрытые внешние оболочки. Встраиваемые проверки являются полностью внешними по отношению к вашей библиотеке и используют код библиотеки так же, как любой другой внешний код, используя только общедоступные внешние оболочки и возможно выполняя проверка нескольких звеньев в одном проверке. Написание обоих видов проверок важно для обеспечения того, чтобы кусочки вашей библиотеки по отдельности и вместе делали то, что вы ожидаете.","breadcrumbs":"Написание самостоятельно х проверок » Создание проверок » Создание проверок","id":"207","title":"Создание проверок"},"208":{"body":"Целью состоящих из звеньев проверок является проверка каждого раздела кода, изолированное от остального возможностей, чтобы можно было быстро понять, что работает неправильно или не так как ожидается. Мы разместим состоящие из звеньев проверки в папке src , в каждый проверяемый файл. Но в Ржавчина принято создавать проверяемый звено tests и код проверки сохранять в файлы с таким же именем, как составляющие которые предстоит проверять. Также необходимо добавить изложение cfg(test) к этому звену. Звено проверок и изложение #[cfg(test)] Изложение #[cfg(test)] у звена с проверкими указывает Ржавчина собирать и запускать только код проверок, когда выполняется приказ cargo test, а не когда запускается cargo build. Это уменьшает время сборки, если вы только хотите собрать библиотеку и уменьшить место для результирующих собранных артефактов, потому что проверки не будут включены. Вы увидите что, по причине того, что встроенные проверки помещаются в другой папка им не нужна изложение #[cfg(test)]. Тем не менее, так как состоящие из звеньев проверки идут в тех же файлах что и основной код, вы будете использовать #[cfg(test)] чтобы указать, что они не должны быть включены в собранный итог. Напомним, что когда мы порождали новый дело adder в первом разделе этой главы, то Cargo создал для нас код ниже: Файл: src/lib.rs pub fn add(left: usize, right: usize) -> usize { left + right\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); }\n} Этот код является самостоятельно созданным проверочным звеном. Свойство cfg предназначен для настройке и говорит Rust, что следующий элемент должен быть включён только учитывая определённую возможность настройке. В этом случае возможностью настройке является test, который предоставлен в Ржавчина для сборки и запуска текущих проверок. Используя свойство cfg, Cargo собирает только проверочный код при активном запуске проверок приказом cargo test. Это включает в себя любые вспомогательные функции, которые могут быть в этом звене в дополнение к функциям помеченным #[test]. Проверка закрытых функций (private) Сообщество программистов не имеет однозначного мнения по поводу проверять или нет закрытые функции. В некоторых языках весьма сложно или даже невозможно проверять такие функции. Независимо от того, какой технологии проверки вы придерживаетесь, в Ржавчина закрытые функции можно проверять. Рассмотрим приложение 11-12 с закрытой функцией internal_adder. Файл: src/lib.rs pub fn add_two(a: usize) -> usize { internal_adder(a, 2)\n} fn internal_adder(left: usize, right: usize) -> usize { left + right\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn internal() { let result = internal_adder(2, 2); assert_eq!(result, 4); }\n} Приложение 11-12: Проверка закрытых функций Обратите внимание, что функция internal_adder не помечена как pub. Проверки — это просто Ржавчина код, а звено tests — это ещё один звено. Как мы обсуждали в разделе “Пути для ссылки на элемент в дереве звеньев“ , элементы в дочерних звенах могут использовать элементы из своих родительских звеньев. В этом проверке мы помещаем все элементы родительского звена test в область видимости с помощью use super::* и затем проверка может вызывать internal_adder. Если вы считаете, что закрытые функции не нужно проверять, то Ржавчина не заставит вас это сделать.","breadcrumbs":"Написание самостоятельно х проверок » Создание проверок » Состоящие из звеньев проверки","id":"208","title":"Состоящие из звеньев проверки"},"209":{"body":"В Ржавчина встроенные проверки являются полностью внешними по отношению к вашей библиотеке. Они используют вашу библиотеку так же, как любой другой код, что означает, что они могут вызывать только функции, которые являются частью открытого API библиотеки. Их целью является проверка, много ли частей вашей библиотеки работают вместе правильно. У звеньев кода правильно работающих самостоятельно, могут возникнуть сбоев при встраивани, поэтому проверочное покрытие встроенного кода также важно. Для создания встроенных проверок сначала нужен папка tests . Папка tests Мы создаём папку tests в корневой папке вашего дела, рядом с папкой src . Cargo знает, что искать файлы с встроенными проверкими нужно в этой папки. После этого мы можем создать столько проверочных файлов, сколько захотим, и Cargo собирает каждый из файлов в отдельный ящик. Давайте создадим встроенный проверку. Рядом с кодом из приложения 11-12, который всё ещё в файле src/lib.rs , создайте папка tests , создайте новый файл с именем tests/integration_test.rs . Устройства папок должна выглядеть так: adder\n├── Cargo.lock\n├── Cargo.toml\n├── src\n│ └── lib.rs\n└── tests └── integration_test.rs Введите код из приложения 11-13 в файл tests/integration_test.rs file: Файл: tests/integration_test.rs use adder::add_two; #[test]\nfn it_adds_two() { let result = add_two(2); assert_eq!(result, 4);\n} Приложение 11-13: Встраиваемая проверка функция из ящика adder Каждый файл в папке tests представляет собой отдельный ящик, поэтому нам нужно подключить нашу библиотеку в область видимости каждого проверочного ящика. По этой причине мы добавляем use adder в верхней части кода, что не нужно нам делать в состоящих из звеньев проверках. Нам не нужно вносить примечания в код в tests/integration_test.rs с помощью #[cfg(test)]. Cargo особым образом обрабатывает папка tests и собирает файлы в этом папке только тогда, когда мы запускаем приказ cargo test. Запустите cargo test сейчас: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 1.31s Running unittests src/lib.rs (target/debug/deps/adder-1082c4b063a8fbe6) running 1 test\ntest tests::internal ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running tests/integration_test.rs (target/debug/deps/integration_test-1082c4b063a8fbe6) running 1 test\ntest it_adds_two ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests adder running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Выходные данные представлены тремя разделами: состоящие из звеньев проверки, встроенные проверки и проверки документации. Обратите внимание, что если какой-нибудь проверка в одной из разделов не пройдёт, последующие разделы выполняться не будут. Например, если состоящий из звеньев проверка провалился, не будет выведено итогов встроенных и документационных проверок, потому что эти проверки будут выполняться только в том случае, если все состоящие из звеньев проверки завершатся успешно. Первый раздел для состоящих из звеньев проверок такой же, как мы видели: одна строка для каждого состоящего из звеньев проверки (один с именем internal, который мы добавили в приложении 11-12), а затем сводная строка для состоящих из звеньев проверок. Раздел встроенных проверок начинается со строки Running tests/integration_test.rs. Далее идёт строка для каждой проверочной функции в этом встроенном проверке и итоговая строка для итогов встроенного проверки непосредственно перед началом раздела Doc-tests adder. Каждый файл встроенного проверки имеет свой собственный раздел, поэтому, если мы добавим больше файлов в папка tests , то здесь будет больше разделов встроенного проверки. Мы всё ещё можем запустить определённую функцию в встроенных проверках, указав имя проверка функции в качестве переменной в cargo test. Чтобы запустить все проверки в определенном файле встроенных проверок, используйте переменная --test сопровождаемый именем файла у приказы cargo test: $ cargo test --test integration_test Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.64s Running tests/integration_test.rs (target/debug/deps/integration_test-82e7799c1bc62298) running 1 test\ntest it_adds_two ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Этот приказ запускает только проверки в файле tests/integration_test.rs . Подзвенья в встроенных проверках По мере добавления большего количества встроенных проверок, можно создать более одного файла в папке tests , чтобы легче создавать их; например, вы можете собъединять функции проверки по возможности, которую они проверяют. Как упоминалось ранее, каждый файл в папке tests собран как отдельный ящик, что полезно для создания отдельных областей видимости, чтобы более точно создавать видимость то, как конечные пользователи будут использовать ваш ящик. Однако это означает, что файлы в папке tests ведут себя не так, как файлы в src , как вы узнали в Главе 7 относительно того как разделить код на звенья и файлы. Различное поведение файлов в папке tests наиболее заметно, когда у вас есть набор вспомогательных функций, которые будут полезны в нескольких встроенных проверочных файлах. Представим, что вы пытаетесь выполнить действия, описанные в разделе «Разделение звеньев в разные файлы» главы 7, чтобы извлечь их в общий звено. Например, вы создали файл tests/common.rs и помеисполнения в него функцию setup, содержащую некоторый код, который вы будете вызывать из разных проверочных функций в нескольких проверочных файлах Файл: tests/common.rs pub fn setup() { // setup code specific to your library's tests would go here\n} Когда мы снова запустим проверки, мы увидим новый раздел в итогах проверок для файла common.rs , хотя этот файл не содержит никаких проверочных функций, более того, мы даже не вызывали функцию setup откуда либо: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.89s Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4) running 1 test\ntest tests::internal ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running tests/common.rs (target/debug/deps/common-92948b65e88960b4) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running tests/integration_test.rs (target/debug/deps/integration_test-92948b65e88960b4) running 1 test\ntest it_adds_two ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests adder running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Упоминание файла common и появление в итогах выполнения проверок сообщения вида running 0 tests - это не то, чего мы хотели. Мы только хотели выделить некоторый общий код, который будет использоваться другими файлами встроенных проверок. Чтобы звено common больше не появлялся в итогах выполнения проверок, вместо файла tests/common.rs мы создадим файл tests/common/mod.rs . Директория дела теперь выглядит следующим образом: ├── Cargo.lock\n├── Cargo.toml\n├── src\n│ └── lib.rs\n└── tests ├── common │ └── mod.rs └── integration_test.rs Здесь используется более раннее соглашение об именовании файлов, которое Ржавчина также понимает. Мы говорили об этом в разделе “Иные пути к файлам” главы 7. Именование файла таким образом говорит, что Ржавчина не должен рассматривать звено common как файл встроенных проверок. Когда мы перемещаем код функции setup в файл tests/common/mod.rs и удаляем файл tests/common.rs , дополнительный раздел больше не будет отображаться в итогах проверок. Файлы в подпапких папки tests не собираются как отдельные ящики или не появляются в итогах выполнения проверок. После того, как мы создали файл tests/common/mod.rs , мы можем использовать его в любых файлах встроенных проверок как обычный звено. Вот пример вызова функции setup из проверки it_adds_two в файле tests/integration_test.rs : Файл: tests/integration_test.rs use adder::add_two; mod common; #[test]\nfn it_adds_two() { common::setup(); let result = add_two(2); assert_eq!(result, 4);\n} Обратите внимание, что объявление mod common; совпадает с объявлением звена, которое отображено в приложении 7-21. Затем в проверочной функции мы можем вызвать функцию common::setup(). Встраиваемые проверки для двоичных ящиков Если наш дело является двоичным ящиком, который содержит только src/main.rs и не содержит src/lib.rs , мы не сможем создать встроенные проверки в папке tests и подключить функции определённые в файле src/main.rs в область видимости с помощью указания use. Только библиотечные ящики могут предоставлять функции, которые можно использовать в других ящиках; двоичные ящики предназначены только для самостоятельного запуска. Это одна из причин, почему дела на Rust, которые порождают исполняемые звенья, обычно имеют простой файл src/main.rs , который в свою очередь вызывает логику, которая находится в файле src/lib.rs . Используя такую устройство, встроенные проверки могут проверить библиотечный ящик, используя оператор use для подключения важного возможностей. Если этот важный возможности работает, то и небольшое количество кода в файле src/main.rs также будет работать, а значит этот небольшой объём кода не нуждается в проверке.","breadcrumbs":"Написание самостоятельно х проверок » Создание проверок » Встраиваемые проверки","id":"209","title":"Встраиваемые проверки"},"21":{"body":"Прежде всего начнём с создания папки, в которой будем сохранять наш код на языке Rust. На самом деле не важно, где сохранять наш код. Однако, для упражнений и дел, обсуждаемых в данной книге, мы советуем создать папку projects в вашем домашнем папке, там же и хранить в будущем код программ из книги. Откройте окно вызова и введите следующие приказы для того, чтобы создать папку projects для хранения кода разных дел, и, внутри неё, папку hello_world для дела “Привет, мир!”. Для Linux, macOS и PowerShell на Windows, введите: $ mkdir ~/projects\n$ cd ~/projects\n$ mkdir hello_world\n$ cd hello_world Для Windows в CMD, введите: > mkdir \"%USERPROFILE%\\projects\"\n> cd /d \"%USERPROFILE%\\projects\"\n> mkdir hello_world\n> cd hello_world","breadcrumbs":"С чего начать » Привет, Мир! » Создание папки дела","id":"21","title":"Создание папки дела"},"210":{"body":"Средства проверки языка Ржавчина предоставляют способ задать ожидаемое поведение кода, чтобы убедиться, что он всё ещё соответствует вашим ожиданиям даже после внесения изменений. Состоящие из звеньев проверки проверяют различные части библиотеки по отдельности и могут проверять закрытые подробности выполнения. Встраиваемые проверки проверяют, что части библиотеки работают правильно сообща. Эти проверки используют для проверки кода открытый API библиотеки, таким же образом, как его будет использовать внешний код. Хотя система видов Ржавчина и правила владения помогают предотвратить некоторые виды ошибок, проверки по-прежнему важны для уменьшения количества логических ошибок, связанных с поведением вашего кода. Давайте объединим знания, полученные в этой и предыдущей главах, чтобы поработать над делом!","breadcrumbs":"Написание самостоятельно х проверок » Создание проверок » Итоги","id":"210","title":"Итоги"},"211":{"body":"В этой главе вы примените многие знания, полученные ранее, а также познакомитесь с ещё неизученными API встроенной библиотеки. Мы создадим окно выводаное приложение, которое будет взаимодействовать с файлом и с окно выводаным вводом / выводом, чтобы применить в некоторых подходах Rust, с которыми вы уже знакомы. Скорость, безопасность, сборка в один исполняемый файл и кроссплатформенность делают Ржавчина наилучшим языком для создания окно выводаных средств, так что в нашем деле мы создадим свою собственную исполнение обычной утилиты поиска grep, что расшифровывается, как \"вездесущеее средство поиска и печати\" ( g lobally search a r egular e xpression and p rint). В простейшем случае grep используется для поиска в выбранном файле указанного текста. Для этого утилита grep получает имя файла и текст в качестве переменных. Далее она читает файл, находит и выводит строки, содержащие искомый текст. Попутно мы покажем, как сделать так, чтобы наше окно выводаное приложение использовало возможности окна вызова, которые используются многими другими окно выводаными средствами. Мы будем читать значение переменной окружения, чтобы позволить пользователю настроить поведение нашего средства. Мы также будем печатать сообщения об ошибках в обычный окно выводаный поток ошибок ( stderr ) вместо принятого вывода ( stdout ), чтобы, к примеру, пользователь мог перенаправить успешный вывод в файл, в то время, как сообщения об ошибках останутся на экране. Один из участников Rust-сообщества, Andrew Gallant, уже выполнил полновозможный, очень быстрый подобие программы grep и назвал его ripgrep. По сравнению с ним, наша исполнение будет довольно простой, но эта глава даст вам знания, которые нужны для понимания существующих дел, таких как ripgrep. Наш дело grep будет использовать ранее изученные подходы: Создание кода (используя то, что вы узнали о звенах в главе 7 ) Использование векторов и строк (собрания, глава 8 ) Обработка ошибок ( Глава 9 ) Использование особенностей и времени жизни там, где это необходимо ( глава 10 ) Написание проверок ( Глава 11 ) Мы также кратко представим замыкания, повторители и предметы особенности, которые будут объяснены подробно в главах 13 и 17 .","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Дело с вводом/выводом (I/O): создание с окном вывода приложения","id":"211","title":"Дело с вводом/выводом (I/O): создание с окном вывода приложения"},"212":{"body":"Создадим новый дело с окном вывода приложения как обычно с помощью приказы cargo new. Мы назовём дело minigrep, чтобы различать наше приложение от grep, которое возможно уже есть в вашей системе. $ cargo new minigrep Created binary (application) `minigrep` project\n$ cd minigrep Первая задача - заставить minigrep принимать два переменной приказной строки: путь к файлу и строку для поиска. То есть мы хотим иметь возможность запускать нашу программу через cargo run, с использованием двойного дефиса, чтобы указать, что следующие переменные предназначены для нашей программы, а не для cargo, строки для поиска и пути к файлу в котором нужно искать, как описано ниже: $ cargo run -- searchstring example-filename.txt В данный мгновение программа созданная cargo new не может обрабатывать переменные, которые мы ей передаём. Некоторые существующие библиотеки на crates.io могут помочь с написанием программы, которая принимает переменные приказной строки, но так как вы просто изучаете эту подход, давайте выполняем эту возможность сами.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Получение переменных приказной строки » Принятие переменных приказной строки","id":"212","title":"Принятие переменных приказной строки"},"213":{"body":"Чтобы minigrep мог воспринимать значения переменных приказной строки, которые мы ему передаём, нам понадобится функция std::env::args, входящая в обычную библиотеку Rust. Эта функция возвращает повторитель переменных приказной строки, переданных в minigrep. Мы подробно рассмотрим повторители в главе 13 . Пока вам достаточно знать две вещи об повторителях: повторители порождают серию значений, и мы можем вызвать способ collect у повторителя, чтобы создать из него собрание, например вектор, который будет содержать все элементы, произведённые повторителем. Код представленный в Приложении 12-1 позволяет вашей программе minigrep читать любые переданные ей переменные приказной строки, а затем собирать значения в вектор. Файл: src/main.rs use std::env; fn main() { let args: Vec = env::args().collect(); dbg!(args);\n} Приложение 12-1: Собираем переменные приказной строки в вектор и выводим их на печать Сначала мы вводим звено std::env в область видимости с помощью указания use, чтобы мы могли использовать его функцию args. Обратите внимание, что функция std::env::args вложена в два уровня звеньев. Как мы обсуждали в главе 7 , в случаях, когда нужная функция оказывается вложенной в более чем один звено, советуется выносить в область видимости родительский звено, а не функцию. Таким образом, мы можем легко использовать другие функции из std::env. Это менее двусмысленно, чем добавление use std::env::args и последующий вызов функции только с args, потому что args может быть легко принят за функцию, определённую в текущем звене.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Получение переменных приказной строки » Чтение значений переменных","id":"213","title":"Чтение значений переменных"},"214":{"body":"Обратите внимание, что std::env::args вызовет панику, если какой-либо переменная содержит недопустимый символ Юникода. Если вашей программе необходимо принимать переменные, содержащие недопустимые символы Unicode, используйте вместо этого std::env::args_os. Эта функция возвращает повторитель , который выдаёт значения OsString вместо значений String. Мы решили использовать std::env::args здесь для простоты, потому что значения OsString отличаются для каждой площадки и с ними сложнее работать, чем со значениями String. В первой строке кода функции main мы вызываем env::args и сразу используем способ collect, чтобы превратить повторитель в вектор содержащий все полученные значения. Мы можем использовать функцию collect для создания многих видов собраний, поэтому мы явно определяем вид args чтобы указать, что мы хотим вектор строк. Хотя нам очень редко нужно определять виды в Rust, collect - это одна из функций, с которой вам часто нужна изложение вида, потому что Ржавчина не может сам вывести какую собрание вы хотите. И в заключение мы печатаем вектор с помощью отладочного макроса. Попробуем запустить код сначала без переменных, а затем с двумя переменнойми: $ cargo run Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s Running `target/debug/minigrep`\n[src/main.rs:5:5] args = [ \"target/debug/minigrep\",\n] $ cargo run -- needle haystack Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.57s Running `target/debug/minigrep needle haystack`\n[src/main.rs:5:5] args = [ \"target/debug/minigrep\", \"needle\", \"haystack\",\n] Обратите внимание, что первое значение в векторе \"target/debug/minigrep\" является названием нашего двоичного файла. Это соответствует поведению списка переменных в Си, позволяя программам использовать название с которым они были вызваны при выполнении. Часто бывает удобно иметь доступ к имени программы, если вы хотите распечатать его в сообщениях или изменить поведение программы в зависимости от того, какой псевдоним приказной строки был использован для вызова программы. Но для целей этой главы, мы пренебрегаем его и сохраним только два переменной, которые нам нужны.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Получение переменных приказной строки » Функция args и недействительный Юникод символ (Unicode)","id":"214","title":"Функция args и недействительный Юникод символ (Unicode)"},"215":{"body":"На текущий мгновение программа может получить доступ к значениям, указанным в качестве переменных приказной строки. Теперь нам требуется сохранять значения этих двух переменных в переменных, чтобы мы могли использовать их в остальных частях программы. Мы сделаем это в приложении 12-2. Файл: src/main.rs use std::env; fn main() { let args: Vec = env::args().collect(); let query = &args[1]; let file_path = &args[2]; println!(\"Searching for {query}\"); println!(\"In file {file_path}\");\n} Приложение 12-2: Создание переменных для хранения значений переменных искомой подстроки и пути к файлу Как видно из распечатки вектора, имя программы занимает первое значение в векторе по адресу args[0], значит, переменные начинаются с порядкового указателя 1. Первый переменная minigrep - это строка, которую мы ищем, поэтому мы помещаем ссылку на первый переменная в переменную query. Вторым переменнаяом является путь к файлу, поэтому мы помещаем ссылку на второй переменная в переменную file_path. Для проверки соблюдения правил работы нашей программы, значения переменных выводятся в окно вывода. Далее, запустим нашу программу со следующими переменнойми: test и sample.txt: $ cargo run -- test sample.txt Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s Running `target/debug/minigrep test sample.txt`\nSearching for test\nIn file sample.txt Отлично, программа работает! Нам нужно чтобы значения переменных были сохранены в правильных переменных. Позже мы добавим обработку ошибок с некоторыми вероятными ошибочными случаейми, например, когда пользователь не предоставляет переменные; сейчас мы пренебрегаем эту случай и поработаем над добавлением возможности чтения файла.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Получение переменных приказной строки » Сохранения значений переменных в переменные","id":"215","title":"Сохранения значений переменных в переменные"},"216":{"body":"Теперь добавим возможность чтения файла, указанного как переменная приказной строки file_path. Во-первых, нам нужен пример файла для проверки: мы будем использовать файл с небольшим объёмом текста в несколько строк с несколькими повторяющимися словами. В приложении 12-3 представлено стихотворение Эмили Дикинсон, которое будет хорошо работать! Создайте файл с именем poem.txt в корне вашего дела и введите стихотворение \"I’m nobody! Who are you?\" Файл: poem.txt I'm nobody! Who are you?\nAre you nobody, too?\nThen there's a pair of us - don't tell!\nThey'd banish us, you know. How dreary to be somebody!\nHow public, like a frog\nTo tell your name the livelong day\nTo an admiring bog! Приложение 12-3: Стихотворение Эмили Дикинсон - хороший пример для проверки Текст на месте, изменените src/main.rs и добавьте код для чтения файла, как показано в приложении 12-4. Файл: src/main.rs use std::env;\nuse std::fs; fn main() { // --snip--\n# let args: Vec = env::args().collect();\n# # let query = &args[1];\n# let file_path = &args[2];\n# # println!(\"Searching for {query}\"); println!(\"In file {file_path}\"); let contents = fs::read_to_string(file_path) .expect(\"Should have been able to read the file\"); println!(\"With text:\\n{contents}\");\n} Приложение 12-4: Чтение содержимого файла указанного во втором переменной Во-первых, мы добавляем ещё одну указанию use чтобы подключить соответствующую часть встроенной библиотеки: нам нужен std::fs для обработки файлов. В main мы добавили новую указанию: функция fs::read_to_string принимает file_path, открывает этот файл и возвращает содержимое файла как std::io::Result. После этого, мы снова добавили временную указанию println! для печати значения contents после чтения файла, таким образом мы можем проверить, что программа отрабатывает до этого места. Давайте запустим этот код с любой строкой в качестве первого переменной приказной строки (потому что мы ещё не выполнили поисковую часть) и файл poem.txt как второй переменная: $ cargo run -- the poem.txt Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s Running `target/debug/minigrep the poem.txt`\nSearching for the\nIn file poem.txt\nWith text:\nI'm nobody! Who are you?\nAre you nobody, too?\nThen there's a pair of us - don't tell!\nThey'd banish us, you know. How dreary to be somebody!\nHow public, like a frog\nTo tell your name the livelong day\nTo an admiring bog! Отлично! Этот код прочитал и затем напечатал содержимое файла. Но у программы есть несколько недостатков. Прежде всего, функция main решает слишком много задач: как правило функция понятнее и проще в обслуживании если она воплощает только одну мысль. Другая неполадка заключается в том, что мы не обрабатываем ошибки так хорошо, как могли бы. Пока наша программа небольшая, то эти недостатки не являются большой неполадкой, но по мере роста программы эти недостатки будет всё труднее исправлять. Хорошей опытом является начинать переработка кода на ранней стадии разработки программы, потому что гораздо проще перерабатывать код меньшие объёмы кода. Мы сделаем это далее.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Чтение файла » Чтение файла","id":"216","title":"Чтение файла"},"217":{"body":"Для улучшения программы мы исправим 4 имеющихся сбоев, связанных со устройством программы и тем как обрабатываются вероятные ошибки. Во-первых, функция main на данный мгновение решает две задачи: анализирует переменные приказной строки и читает файлы. По мере роста программы количество отдельных задач, которые обрабатывает функция main, будет увеличиваться. Поскольку эта функция получает больше обязанностей, то становится все труднее понимать её, труднее проверять и труднее изменять, не сломав одну из её частей. Лучше всего разделить возможность, чтобы каждая функция отвечала за одну задачу. Эта неполадка также связана со второй неполадкой: хотя переменные query и file_path являются переменными настройке нашей программы, переменные вида contents используются для выполнения логики программы. Чем длиннее становится main, тем больше переменных нам нужно будет добавить в область видимости; чем больше у нас переменных в области видимости, тем сложнее будет отслеживать назначение каждой переменной. Лучше всего собъединять переменные настройке в одну устройство, чтобы сделать их назначение понятным. Третья неполадка заключается в том, что мы используем expect для вывода сведений об ошибке при неполадке с чтением файла, но сообщение об ошибке просто выведет текстShould have been able to read the file. Чтение файла может не сработать по разным причинам, например: файл не найден или у нас может не быть разрешения на его чтение. Сейчас же, независимо от случаи, мы напечатаем одно и то же сообщение об ошибке, что не даст пользователю никакой сведений! В-четвёртых, мы используем expect неоднократно для обработки различных ошибок и если пользователь запускает нашу программу без указания достаточного количества переменных он получит ошибку index out of bounds из Rust, что не совсем понятно описывает неполадку. Было бы лучше, если бы весь код обработки ошибок находился в одном месте, чтобы тем, кто будет поддерживать наш код в дальнейшем, нужно было бы вносить изменения только здесь, если потребуется изменить логику обработки ошибок. Наличие всего кода обработки ошибок в одном месте заверяет, что мы напечатаем сообщения, которые будут иметь смысл для наших конечных пользователей. Давайте решим эти четыре сбоев путём переработки кода нашего дела.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Переработка кода для обеспечения выделения на звенья и улучшения обработки ошибок » Переработка кода для улучшения выделения на звенья и обработки ошибок","id":"217","title":"Переработка кода для улучшения выделения на звенья и обработки ошибок"},"218":{"body":"Внутренняя неполадка распределения ответственности за выполнение нескольких задач функции main является общей для многих двоичных дел. В итоге Ржавчина сообщество разработало этап для использования в качестве руководства по разделению ответственности двоичной программы, когда код в main начинает увеличиваться. Этап имеет следующие шаги: Разделите код программы на два файла main.rs и lib.rs . Перенесите всю логику работы программы в файл lib.rs . Пока ваша логика синтаксического анализа приказной строки мала, она может оставаться в файле main.rs . Когда логика синтаксического анализа приказной строки становится сложной, извлеките её из main.rs и переместите в lib.rs. Полезные обязанности, которые остаются в функции main после этого этапа должно быть ограничено следующим: Вызов логики разбора приказной строки со значениями переменных Настройка любой другой настройке Вызов функции run в lib.rs Обработка ошибки, если run возвращает ошибку Этот образец о разделении ответственности: main.rs занимается запуском программы, а lib.rs обрабатывает всю логику задачи. Поскольку нельзя проверить функцию main напрямую, то такая устройства позволяет проверить всю логику программы путём перемещения её в функции внутри lib.rs . Единственный код, который остаётся в main.rs будет достаточно маленьким, чтобы проверить его соблюдение правил прочитав код. Давайте переработаем нашу программу, следуя этому этапу. Извлечение обработчика переменных Мы извлечём возможность для разбора переменных в функцию, которую вызовет main для подготовки к перемещению логики разбора приказной строки в файл src/lib.rs . Приложение 12-5 показывает новый запуск main, который вызывает новую функцию parse_config, которую мы определим сначала в src/main.rs. Файл: src/main.rs # use std::env;\n# use std::fs;\n# fn main() { let args: Vec = env::args().collect(); let (query, file_path) = parse_config(&args); // --snip--\n# # println!(\"Searching for {query}\");\n# println!(\"In file {file_path}\");\n# # let contents = fs::read_to_string(file_path)\n# .expect(\"Should have been able to read the file\");\n# # println!(\"With text:\\n{contents}\");\n} fn parse_config(args: &[String]) -> (&str, &str) { let query = &args[1]; let file_path = &args[2]; (query, file_path)\n} Приложение 12-5: Выделение функции parse_config из main Мы все ещё собираем переменные приказной строки в вектор, но вместо присваивания значение переменной с порядковым указателем 1 переменной query и значение переменной с порядковым указателем 2 переменной с именем file_path в функции main, мы передаём весь вектор в функцию parse_config. Функция parse_config затем содержит логику, которая определяет, какой переменная идёт в какую переменную и передаёт значения обратно в main. Мы все ещё создаём переменные query и file_path в main, но main больше не несёт ответственности за определение соответствия переменной приказной строки и соответствующей переменной. Эта доработка может показаться излишней для нашей маленькой программы, но мы проводим переработка кода небольшими, постепенными шагами. После внесения этого изменения снова запустите программу и убедитесь, что анализ переменных все ещё работает. Также хорошо часто проверять все этапы, чтобы помочь определить причину неполадок. когда они возникают. Объединение настроечных переменных Мы можем сделать ещё один маленький шаг для улучшения функции parse_config. На данный мгновение мы возвращаем упорядоченный ряд, но затем мы немедленно разделяем его снова на отдельные части. Это признак того, что, возможно, пока у нас нет правильной абстракции. Ещё один индикатор, который показывает, что есть место для улучшения, это часть config из parse_config, что подразумевает, что два значения, которые мы возвращаем, связаны друг с другом и оба являются частью одного настроечного значения. В настоящее время мы не отражаем этого смысла в устройстве данных, кроме объединения двух значений в упорядоченный ряд; мы могли бы поместить оба значения в одну устройство и дать каждому из полей устройства понятное имя. Это облегчит будущую поддержку этого кода, чтобы понять, как различные значения относятся друг к другу и какое их назначение. В приложении 12-6 показаны улучшения функции parse_config . Файл: src/main.rs # use std::env;\n# use std::fs;\n# fn main() { let args: Vec = env::args().collect(); let config = parse_config(&args); println!(\"Searching for {}\", config.query); println!(\"In file {}\", config.file_path); let contents = fs::read_to_string(config.file_path) .expect(\"Should have been able to read the file\"); // --snip--\n# # println!(\"With text:\\n{contents}\");\n} struct Config { query: String, file_path: String,\n} fn parse_config(args: &[String]) -> Config { let query = args[1].clone(); let file_path = args[2].clone(); Config { query, file_path }\n} Приложение 12-6: Переработка кода функции parse_config, чтобы возвращать образец устройства Config Мы добавили устройство с именем Config объявленную с полями назваными как query и file_path. Ярлык parse_config теперь указывает, что она возвращает значение Config. В теле parse_config, где мы возвращали срезы строк, которые ссылаются на значения String в args, теперь мы определяем Config как содержащие собственные String значения. Переменная args в main является владельцем значений переменной и позволяют функции parse_config только одалживать их, что означает, что мы бы нарушили правила заимствования Rust, если бы Config попытался бы взять во владение значения в args . Мы можем управлять данными String разным количеством способов, но самый простой, хотя и отчасти неэффективный это вызвать способ clone у значений. Он сделает полную повтор данных для образца Config для владения, что занимает больше времени и памяти, чем сохранение ссылки на строку данных. Однако клонирование данных также делает наш код очень простым, потому что нам не нужно управлять временем жизни ссылок; в этом обстоятельстве, отказ от небольшой производительности, чтобы получить простоту, стоит небольших соглашениеа. К при использовании способа cloneСуществует тенденция в среде программистов Ржавчина избегать использования clone, т.к. это понижает эффективность работы кода. В Главе 13 , вы изучите более эффективные способы, которые могут подойти в подобной случаи. Но сейчас можно воспроизводить несколько строк, чтобы продолжить работу, потому что вы сделаете эти повторы только один раз, а ваше имя файла и строка запроса будут очень маленькими. Лучше иметь работающую программу, которая немного неэффективна, чем пытаться заранее перерабатывать код при первом написании. По мере приобретения опыта работы с Ржавчина вам будет проще начать с наиболее эффективного решения, но сейчас вполне приемлемо вызвать clone. Мы обновили код в main поэтому он помещает образец Config возвращённый из parse_config в переменную с именем config, и мы обновили код, в котором ранее использовались отдельные переменные query и file_path, так что теперь он использует вместо этого поля в устройстве Config. Теперь наш код более чётко передаёт то, что query и file_path связаны и что цель из использования состоит в том, чтобы настроить, как программа будет работать. Любой код, который использует эти значения знает, что может найти их в именованных полях образца config по их назначению. Создание строителя для устройства Config Пока что мы извлекли логику, отвечающую за синтаксический анализ переменных приказной строки из main и помеисполнения его в функцию parse_config. Это помогло нам увидеть, что значения query и file_path были связаны и что их отношения должны быть отражены в нашем коде. Затем мы добавили устройство Config в качестве названия связанных общей целью query и file_path и чтобы иметь возможность вернуть именованные значения как имена полей устройства из функции parse_config. Итак, теперь целью функции parse_config является создание образца Config, мы можем изменить parse_config из простой функции на функцию названную new, которая связана со устройством Config. Выполняя это изменение мы сделаем код более идиоматичным. Можно создавать образцы видов в встроенной библиотеке, такие как String с помощью вызова String::new. Точно так же изменив название parse_config на название функции new, связанную с Config, мы будем уметь создавать образцы Config, вызывая Config::new. Приложение 12-7 показывает изменения, которые мы должны сделать. Файл: src/main.rs # use std::env;\n# use std::fs;\n# fn main() { let args: Vec = env::args().collect(); let config = Config::new(&args);\n# # println!(\"Searching for {}\", config.query);\n# println!(\"In file {}\", config.file_path);\n# # let contents = fs::read_to_string(config.file_path)\n# .expect(\"Should have been able to read the file\");\n# # println!(\"With text:\\n{contents}\"); // --snip--\n} // --snip-- # struct Config {\n# query: String,\n# file_path: String,\n# }\n# impl Config { fn new(args: &[String]) -> Config { let query = args[1].clone(); let file_path = args[2].clone(); Config { query, file_path } }\n} Приложение 12-7: Переименование parse_config в Config::new Мы обновили main где вызывали parse_config, чтобы вместо этого вызывалась Config::new. Мы изменили имя parse_config на new и перенесли его внутрь раздела impl, который связывает функцию new с Config. Попробуйте снова собрать код, чтобы убедиться, что он работает.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Переработка кода для обеспечения выделения на звенья и улучшения обработки ошибок » Разделение ответственности для двоичных дел","id":"218","title":"Разделение ответственности для двоичных дел"},"219":{"body":"Теперь мы поработаем над исправлением обработки ошибок. Напомним, что попытки получить доступ к значениям в векторе args с порядковым указателем 1 или порядковым указателем 2 приведут к панике, если вектор содержит менее трёх элементов. Попробуйте запустить программу без каких-либо переменных; это будет выглядеть так: $ cargo run Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s Running `target/debug/minigrep`\nthread 'main' panicked at src/main.rs:27:21:\nindex out of bounds: the len is 1 but the index is 1\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Строка index out of bounds: the len is 1 but the index is 1 является сообщением об ошибке предназначенной для программистов. Она не поможет нашим конечным пользователям понять, что случилось и что они должны сделать вместо этого. Давайте исправим это сейчас. Улучшение сообщения об ошибке В приложении 12-8 мы добавляем проверку в функцию new, которая будет проверять, что срез достаточно длинный, перед попыткой доступа по порядковым указателям 1 и 2. Если срез не достаточно длинный, программа паникует и отображает улучшенное сообщение об ошибке. Файл: src/main.rs # use std::env;\n# use std::fs;\n# # fn main() {\n# let args: Vec = env::args().collect();\n# # let config = Config::new(&args);\n# # println!(\"Searching for {}\", config.query);\n# println!(\"In file {}\", config.file_path);\n# # let contents = fs::read_to_string(config.file_path)\n# .expect(\"Should have been able to read the file\");\n# # println!(\"With text:\\n{contents}\");\n# }\n# # struct Config {\n# query: String,\n# file_path: String,\n# }\n# # impl Config { // --snip-- fn new(args: &[String]) -> Config { if args.len() < 3 { panic!(\"not enough arguments\"); } // --snip--\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Config { query, file_path }\n# }\n# } Приложение 12-8: Добавление проверки количества переменных Этот код похож на функцию Guess::new написанную в приложении 9-13 , где мы вызывали panic!, когда value переменной вышло за пределы допустимых значений. Здесь вместо проверки на рядзначений, мы проверяем, что длина args не менее 3 и остальная часть функции может работать при условии, что это условие было выполнено. Если в args меньше трёх элементов, это условие будет истинным и мы вызываем макрос panic! для немедленного завершения программы. Имея нескольких лишних строк кода в new, давайте запустим программу снова без переменных, чтобы увидеть, как выглядит ошибка: $ cargo run Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s Running `target/debug/minigrep`\nthread 'main' panicked at src/main.rs:26:13:\nnot enough arguments\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Этот вывод лучше: у нас теперь есть разумное сообщение об ошибке. Тем не менее, мы также имеем постороннюю сведения, которую мы не хотим предоставлять нашим пользователям. Возможно, использованная техника, которую мы использовали в приложении 9-13, не является лучшей для использования: вызов panic! больше подходит для программирования сбоев, чем решения сбоев, как обсуждалось в главе 9 . Вместо этого мы можем использовать другую технику, о которой вы узнали в главе 9 [возвращая Result], которая указывает либо на успех, либо на ошибку. Возвращение Result вместо вызова panic! Мы можем вернуть значение Result, которое будет содержать образец Config в успешном случае и опишет неполадку в случае ошибки. Мы так же изменим функцию new на build потому что многие программисты ожидают что new никогда не завершится неудачей. Когда Config::build взаимодействует с main, мы можем использовать вид Result как сигнал возникновения сбоев. Затем мы можем изменить main, чтобы преобразовать исход Err в более применимую ошибку для наших пользователей без окружающего текста вроде thread 'main' и RUST_BACKTRACE, что происходит при вызове panic!. Приложение 12-9 показывает изменения, которые нужно внести в возвращаемое значения функции Config::build, и в тело функции, необходимые для возврата вида Result. Заметьте, что этот код не собирается, пока мы не обновим main, что мы и сделаем в следующем приложении. Файл: src/main.rs # use std::env;\n# use std::fs;\n# # fn main() {\n# let args: Vec = env::args().collect();\n# # let config = Config::new(&args);\n# # println!(\"Searching for {}\", config.query);\n# println!(\"In file {}\", config.file_path);\n# # let contents = fs::read_to_string(config.file_path)\n# .expect(\"Should have been able to read the file\");\n# # println!(\"With text:\\n{contents}\");\n# }\n# # struct Config {\n# query: String,\n# file_path: String,\n# }\n# impl Config { fn build(args: &[String]) -> Result { if args.len() < 3 { return Err(\"not enough arguments\"); } let query = args[1].clone(); let file_path = args[2].clone(); Ok(Config { query, file_path }) }\n} Приложение 12-9. Возвращение вида Result из Config::build Наша функция build теперь возвращает Result с образцом Config в случае успеха и &'static str в случае ошибки. Значения ошибок всегда будут строковыми записями, которые имеют время жизни 'static. Мы внесли два изменения в тело функции build: вместо вызова panic!, когда пользователь не передаёт достаточно переменных, мы теперь возвращаем Err значение и мы завернули возвращаемое значение Config в Ok . Эти изменения заставят функцию соответствовать своей новой ярлыке вида. Возвращение значения Err из Config::build позволяет функции main обработать значение Result возвращённое из функции build и выйти из этапа более чисто в случае ошибки. Вызов Config::build и обработка ошибок Чтобы обработать ошибку и вывести более дружественное сообщение об ошибке, нам нужно обновить код main для обработки Result, возвращаемого из Config::build как показано в приложении 12-10. Мы также возьмём на себя ответственность за выход из программы приказной строки с ненулевым кодом ошибки panic! и выполняем это вручную. Не нулевой значение выхода - это соглашение, которое указывает этапу, который вызывает нашу программу, что программа завершилась с ошибкой. Файл: src/main.rs # use std::env;\n# use std::fs;\nuse std::process; fn main() { let args: Vec = env::args().collect(); let config = Config::build(&args).unwrap_or_else(|err| { println!(\"Problem parsing arguments: {err}\"); process::exit(1); }); // --snip--\n# # println!(\"Searching for {}\", config.query);\n# println!(\"In file {}\", config.file_path);\n# # let contents = fs::read_to_string(config.file_path)\n# .expect(\"Should have been able to read the file\");\n# # println!(\"With text:\\n{contents}\");\n# }\n# # struct Config {\n# query: String,\n# file_path: String,\n# }\n# # impl Config {\n# fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# } Приложение 12-10. Выход с кодом ошибки если создание новой Config терпит неудачу В этом приложении мы использовали способ, который мы ещё не рассматривали подробно: unwrap_or_else, который в встроенной библиотеке определён как Result. Использование unwrap_or_else позволяет нам определить некоторые пользовательские ошибки обработки, не содержащие panic!. Если Result является значением Ok, поведение этого способа подобно unwrap: возвращает внутреннее значение из обёртки Ok. Однако, если значение является значением Err, то этот способ вызывает код замыкания , которое является анонимной функцией, определённой заранее и передаваемую в качестве переменной в unwrap_or_else. Мы рассмотрим замыкания более подробно в главе 13 . В данный мгновение, вам просто нужно знать, что unwrap_or_else передаст внутреннее значение Err, которое в этом случае является постоянной строкой not enough arguments, которое мы добавили в приложении 12-9, в наше замыкание как переменная err указанное между вертикальными линиями. Код в замыкании может затем использовать значение err при выполнении. Мы добавили новую строку use, чтобы подключить process из встроенной библиотеки в область видимости. Код в замыкании, который будет запущен в случае ошибки содержит только две строчки: мы печатаем значение err и затем вызываем process::exit. Функция process::exit немедленно остановит программу и вернёт номер, который был передан в качестве кода состояния выхода. Это похоже на обработку с помощью макроса panic!, которую мы использовали в приложении 12-8, но мы больше не получаем весь дополнительный вывод. Давай попробуем: $ cargo run Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s Running `target/debug/minigrep`\nProblem parsing arguments: not enough arguments Замечательно! Этот вывод намного дружелюбнее для наших пользователей.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Переработка кода для обеспечения выделения на звенья и улучшения обработки ошибок » Исправление ошибок обработки","id":"219","title":"Исправление ошибок обработки"},"22":{"body":"Затем создайте новый исходный файл и назовите его main.rs . Файлы Ржавчина всегда заканчиваются расширением .rs . Если вы используете более одного слова в имени файла, принято разделять их символом подчёркивания. Например, используйте hello_world.rs вместо helloworld.rs . Теперь откроем файл main.rs для изменения и введём следующие строки кода: Название файла: main.rs fn main() { println!(\"Привет, мир!\");\n} Приложение 1-1: Программа, которая печатает Привет, мир! Сохраните файл и вернитесь в окно окна вызова в папка ~/projects/hello_world . В Linux или macOS введите следующие приказы для сборки и запуска файла: $ rustc main.rs\n$ ./main\nПривет, мир! В Windows, введите приказ .\\main.exe вместо ./main: > rustc main.rs\n> .\\main.exe\nПривет, мир! Независимо от вашей операционной системы, строка Привет, мир! должна быть выведена на окно вызова. Если вы не видите такого вывода, обратитесь к разделу \"Устранение неполадок \" , чтобы узнать, как получить помощь. Если напечаталось Привет, мир!, то примите наши поздравления! Вы написали программу на Rust, что делает вас Ржавчина программистом — добро пожаловать!","breadcrumbs":"С чего начать » Привет, Мир! » Написание и запуск первой Ржавчина программы","id":"22","title":"Написание и запуск первой Ржавчина программы"},"220":{"body":"Теперь, когда мы закончили переработка кода разбора настройке, давайте обратимся к логике программы. Как мы указали в разделе «Разделение ответственности в двоичных делах» , мы извлечём функцию с именем run, которая будет содержать всю логику, присутствующую в настоящее время в функции main и которая не связана с настройкой настройке или обработкой ошибок. Когда мы закончим, то main будет краткой, легко проверяемой и мы сможем написать проверки для всей остальной логики. Код 12-11 отображает извлечённую логику в функцию run. Мы делаем маленькое, инкрементальное приближение к извлечению функции. Код всё ещё сосредоточен в файле src/main.rs : Файл: src/main.rs # use std::env;\n# use std::fs;\n# use std::process;\n# fn main() { // --snip-- # let args: Vec = env::args().collect();\n# # let config = Config::build(&args).unwrap_or_else(|err| {\n# println!(\"Problem parsing arguments: {err}\");\n# process::exit(1);\n# });\n# println!(\"Searching for {}\", config.query); println!(\"In file {}\", config.file_path); run(config);\n} fn run(config: Config) { let contents = fs::read_to_string(config.file_path) .expect(\"Should have been able to read the file\"); println!(\"With text:\\n{contents}\");\n} // --snip--\n# # struct Config {\n# query: String,\n# file_path: String,\n# }\n# # impl Config {\n# fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# } Приложение 12-11. Извлечение функции run, содержащей остальную логику программы Функция run теперь содержит всю оставшуюся логику из main, начиная от чтения файла. Функция run принимает образец Config как переменная. Возврат ошибок из функции run Оставшаяся логика программы выделена в функцию run, где мы можем улучшить обработку ошибок как мы уже делали с Config::build в приложении 12-9. Вместо того, чтобы позволить программе паниковать с помощью вызова expect, функция run вернёт Result, если что-то пойдёт не так. Это позволит далее окне выводадировать логику обработки ошибок в main удобным способом. Приложение 12-12 показывает изменения, которые мы должны внести в ярлык и тело run. Файл: src/main.rs # use std::env;\n# use std::fs;\n# use std::process;\nuse std::error::Error; // --snip-- # # fn main() {\n# let args: Vec = env::args().collect();\n# # let config = Config::build(&args).unwrap_or_else(|err| {\n# println!(\"Problem parsing arguments: {err}\");\n# process::exit(1);\n# });\n# # println!(\"Searching for {}\", config.query);\n# println!(\"In file {}\", config.file_path);\n# # run(config);\n# }\n# fn run(config: Config) -> Result<(), Box> { let contents = fs::read_to_string(config.file_path)?; println!(\"With text:\\n{contents}\"); Ok(())\n}\n# # struct Config {\n# query: String,\n# file_path: String,\n# }\n# # impl Config {\n# fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# } Приложение 12-12. Изменение функции run для возврата Result Здесь мы сделали три значительных изменения. Во-первых, мы изменили вид возвращаемого значения функции run на Result<(), Box> . Эта функция ранее возвращала вид () и мы сохраняли его как значение, возвращаемое в случае Ok. Для вида ошибки мы использовали предмет особенность Box (и вверху мы подключили вид std::error::Error в область видимости с помощью указания use). Мы рассмотрим особенности предметов в главе 17 . Сейчас просто знайте, что Box означает, что функция будет возвращать вид выполняющий особенность Error, но не нужно указывать, какой именно будет вид возвращаемого значения. Это даёт возможность возвращать значения ошибок, которые могут быть разных видов в разных случаях. Ключевое слово dyn сокращение для слова «изменяемый». Во-вторых, мы убрали вызов expect в пользу использования оператора ?, как мы обсудили в главе 9 . Скорее, чем вызывать panic! в случае ошибки, оператор ? вернёт значение ошибки из текущей функции для вызывающего, чтобы он её обработал. В-третьих, функция run теперь возвращает значение Ok в случае успеха. В ярлыке функции run успешный вид объявлен как (), который означает, что нам нужно обернуть значение единичного вида в значение Ok. Данный правила написания Ok(()) поначалу может показаться немного странным, но использование () выглядит как идиоматический способ указать, что мы вызываем run для его побочных эффектов; он не возвращает значение, которое нам нужно. Когда вы запустите этот код, он собирается, но отобразит предупреждение: $ cargo run -- the poem.txt Compiling minigrep v0.1.0 (file:///projects/minigrep)\nwarning: unused `Result` that must be used --> src/main.rs:19:5 |\n19 | run(config); | ^^^^^^^^^^^ | = note: this `Result` may be an `Err` variant, which should be handled = note: `#[warn(unused_must_use)]` on by default\nhelp: use `let _ = ...` to ignore the resulting value |\n19 | let _ = run(config); | +++++++ warning: `minigrep` (bin \"minigrep\") generated 1 warning Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.71s Running `target/debug/minigrep the poem.txt`\nSearching for the\nIn file poem.txt\nWith text:\nI'm nobody! Who are you?\nAre you nobody, too?\nThen there's a pair of us - don't tell!\nThey'd banish us, you know. How dreary to be somebody!\nHow public, like a frog\nTo tell your name the livelong day\nTo an admiring bog! Rust говорит, что наш код пренебрег Result значение и значение Result может указывать на то, что произошла ошибка. Но мы не проверяем, была ли ошибка и сборщик напоминает нам, что мы, вероятно, хотели здесь выполнить некоторый код обработки ошибок! Давайте исправим эту неполадку сейчас. Обработка ошибок, возвращённых из run в main Мы будем проверять и обрабатывать ошибки используя способику, подобную той, которую мы использовали для Config::build в приложении 12-10, но с небольшой разницей: Файл: src/main.rs # use std::env;\n# use std::error::Error;\n# use std::fs;\n# use std::process;\n# fn main() { // --snip-- # let args: Vec = env::args().collect();\n# # let config = Config::build(&args).unwrap_or_else(|err| {\n# println!(\"Problem parsing arguments: {err}\");\n# process::exit(1);\n# });\n# println!(\"Searching for {}\", config.query); println!(\"In file {}\", config.file_path); if let Err(e) = run(config) { println!(\"Application error: {e}\"); process::exit(1); }\n}\n# # fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # println!(\"With text:\\n{contents}\");\n# # Ok(())\n# }\n# # struct Config {\n# query: String,\n# file_path: String,\n# }\n# # impl Config {\n# fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# } Мы используем if let вместо unwrap_or_else чтобы проверить, возвращает ли run значение Err и вызывается process::exit(1), если это так. Функция run не возвращает значение, которое мы хотим развернуть способом unwrap, таким же образом как Config::build возвращает образец Config. Так как run возвращает () в случае успеха и мы заботимся только об обнаружении ошибки, то нам не нужно вызывать unwrap_or_else, чтобы вернуть развёрнутое значение, потому что оно будет только (). Тело функций if let и unwrap_or_else одинаковы в обоих случаях: мы печатаем ошибку и выходим.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Переработка кода для обеспечения выделения на звенья и улучшения обработки ошибок » Извлечение логики из main","id":"220","title":"Извлечение логики из main"},"221":{"body":"Наш дело minigrep пока выглядит хорошо! Теперь мы разделим файл src/main.rs и поместим некоторый код в файл src/lib.rs . Таким образом мы сможем его проверять и чтобы в файле src/main.rs было меньшее количество полезных обязанностей. Давайте перенесём весь код не относящийся к функции main из файла src/main.rs в новый файл src/lib.rs : Определение функции run Соответствующие указания use Определение устройства Config Определение функции Config::build Содержимое src/lib.rs должно иметь ярлыки, показанные в приложении 12-13 (мы опуисполнения тела функций для краткости). Обратите внимание, что код не будет собираться пока мы не изменим src/main.rs в приложении 12-14. Файл: src/lib.rs use std::error::Error;\nuse std::fs; pub struct Config { pub query: String, pub file_path: String,\n} impl Config { pub fn build(args: &[String]) -> Result { // --snip--\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path }) }\n} pub fn run(config: Config) -> Result<(), Box> { // --snip--\n# let contents = fs::read_to_string(config.file_path)?;\n# # println!(\"With text:\\n{contents}\");\n# # Ok(())\n} Приложение 12-13. Перемещение Config и run в src/lib.rs Мы добавили определетель доступа pub к устройстве Config, а также её полям, к способу build и функции run. Теперь у нас есть библиотечный ящик, который содержит открытый API, который мы можем проверять! Теперь нам нужно подключить код, который мы перемеисполнения в src/lib.rs, в область видимости двоичного ящика внутри src/main.rs , как показано в приложении 12-14. Файл: src/main.rs use std::env;\nuse std::process; use minigrep::Config; fn main() { // --snip--\n# let args: Vec = env::args().collect();\n# # let config = Config::build(&args).unwrap_or_else(|err| {\n# println!(\"Problem parsing arguments: {err}\");\n# process::exit(1);\n# });\n# # println!(\"Searching for {}\", config.query);\n# println!(\"In file {}\", config.file_path);\n# if let Err(e) = minigrep::run(config) { // --snip--\n# println!(\"Application error: {e}\");\n# process::exit(1); }\n} Приложение 12-14. Использование ящика библиотеки minigrep внутри src/main.rs Мы добавляем use minigrep::Config для подключения вида Config из ящика библиотеки в область видимости двоичного ящика и добавляем к имени функции run приставка нашего ящика. Теперь все функции должны быть подключены и должны работать. Запустите программу с cargo run и убедитесь, что все работает правильно. Уф! Было много работы, но мы настроены на будущий успех. Теперь проще обрабатывать ошибки и мы сделали код более состоящим из звеньев. С этого особенности почти вся наша работа будет выполняться внутри src/lib.rs . Давайте воспользуемся этой новой выделения на звенья, сделав что-то, что было бы трудно со старым кодом, но легко с новым кодом: мы напишем несколько проверок!","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Переработка кода для обеспечения выделения на звенья и улучшения обработки ошибок » Разделение кода на библиотечный ящик","id":"221","title":"Разделение кода на библиотечный ящик"},"222":{"body":"Теперь, когда мы извлекли логику в src/lib.rs и оставили разбор переменных приказной строки и обработку ошибок в src/main.rs , стало гораздо проще писать проверки для основной возможности нашего кода. Мы можем вызывать функции напрямую с различными переменнойми и проверить возвращаемые значения без необходимости вызова нашего двоичного файла из приказной строки. В этом разделе в программу minigrep мы добавим логику поиска с использованием этапа разработки через проверка (TDD), который следует этим шагам: Напишите проверка, который завершается неудачей, и запустите его, чтобы убедиться, что он не сработал именно по той причине, которую вы ожидаете. Пишите или изменяйте ровно столько кода, чтобы успешно выполнился новый проверку. Выполните переработка кода кода, который вы только что добавили или изменили, и убедитесь, что проверки продолжают проходить. Повторите с шага 1! Хотя это всего лишь один из многих способов написания программного обеспечения, TDD может помочь в разработке кода. Написание проверки перед написанием кода, обеспечивающего прохождение проверки, помогает поддерживать высокое покрытие проверкими на протяжении всего этапа разработки. Мы проверим выполнение возможности, которая делает поиск строки запроса в содержимом файла и создание списка строк, соответствующих запросу. Мы добавим эту возможность в функцию под названием search.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Разработка возможности библиотеки с помощью разработки через проверка » Развитие возможности библиотеки разработкой на основе проверок","id":"222","title":"Развитие возможности библиотеки разработкой на основе проверок"},"223":{"body":"Поскольку они нам больше не нужны, давайте удалим указания с println!, которые мы использовали для проверки поведения программы в src/lib.rs и src/main.rs . Затем в src/lib.rs мы добавим звено tests с проверочной функцией, как делали это в главе 11 . Проверочная функция определяет поведение, которое мы хотим проверить в функции search: она должна принимать запрос и текст для поиска, а возвращать только те строки из текста, которые содержат запрос. В приложении 12-15 показан этот проверка, который пока не собирается. Файл: src/lib.rs # use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# }\n# # impl Config {\n# pub fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# }\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # Ok(())\n# }\n# #[cfg(test)]\nmod tests { use super::*; #[test] fn one_result() { let query = \"duct\"; let contents = \"\\\nRust:\nsafe, fast, productive.\nPick three.\"; assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents)); }\n} Приложение 12-15: Создание безуспешного проверки для функции search, которую мы хотим создать Этот проверка ищет строку \"duct\". Текст, в котором мы ищем, состоит из трёх строк, только одна из которых содержит \"duct\" (обратите внимание, что обратная косая черта после открывающей двойной кавычки говорит Ржавчина не помещать символ новой строки в начало содержимого этого строкового записи). Мы проверяем, что значение, возвращаемое функцией search, содержит только ожидаемую нами строку. Мы не можем запустить этот проверка и увидеть сбой, потому что проверка даже не собирается: функции search ещё не существует! В соответствии с принципами TDD мы добавим ровно столько кода, чтобы проверка собирался и запускался, добавив определение функции search, которая всегда возвращает пустой вектор, как показано в приложении 12-16. Потом проверка должен собраться и потерпеть неудачу при запуске, потому что пустой вектор не равен вектору, содержащему строку \"safe, fast, productive.\" Файл: src/lib.rs # use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# }\n# # impl Config {\n# pub fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# }\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # Ok(())\n# }\n# pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { vec![]\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn one_result() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# } Приложение 12-16. Определение функции search, достаточное, чтобы проверка собрался Заметьте, что в ярлыке search нужно явно указать время жизни 'a для переменной contents и возвращаемого значения. Напомним из Главы 10 , что свойства времени жизни указывают с временем жизни какого переменной связано время жизни возвращаемого значения. В данном случае мы говорим, что возвращаемый вектор должен содержать срезы строк, ссылающиеся на содержимое переменной contents (а не переменной query). Другими словами, мы говорим Rust, что данные, возвращаемые функцией search, будут жить до тех пор, пока живут данные, переданные в функцию search через переменная contents. Это важно! Чтобы ссылки были действительными, данные, на которые ссылаются с помощью срезов тоже должны быть действительными; если сборщик предполагает, что мы делаем строковые срезы переменной query, а не переменной contents, он неправильно выполнит проверку безопасности. Если мы забудем изложении времени жизни и попробуем собрать эту функцию, то получим следующую ошибку: $ cargo build Compiling minigrep v0.1.0 (file:///projects/minigrep)\nerror[E0106]: missing lifetime specifier --> src/lib.rs:28:51 |\n28 | pub fn search(query: &str, contents: &str) -> Vec<&str> { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `query` or `contents`\nhelp: consider introducing a named lifetime parameter |\n28 | pub fn search<'a>(query: &'a str, contents: &'a str) -> Vec<&'a str> { | ++++ ++ ++ ++ For more information about this error, try `rustc --explain E0106`.\nerror: could not compile `minigrep` (lib) due to 1 previous error Rust не может понять, какой из двух переменных нам нужен, поэтому нужно сказать ему об этом. Так как contents является тем переменнаяом, который содержит весь наш текст, и мы хотим вернуть части этого текста, которые совпали при поиске, мы понимаем, что contents является переменнаяом, который должен быть связан с возвращаемым значением временем жизни. Другие языки программирования не требуют от вас связывания в ярлыке переменных с возвращаемыми значениями, но после определённой опытов вам станет проще. Можете сравнить этот пример с разделом «Проверка ссылок с временами жизни» главы 10. Запустим проверку: $ cargo test Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.97s Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94) running 1 test\ntest tests::one_result ... FAILED failures: ---- tests::one_result stdout ----\nthread 'tests::one_result' panicked at src/lib.rs:44:9:\nassertion `left == right` failed left: [\"safe, fast, productive.\"] right: []\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::one_result test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib` Отлично. Наш проверка не сработал, как мы и ожидали. Давайте сделаем так, чтобы он срабатывал!","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Разработка возможности библиотеки с помощью разработки через проверка » Написание проверки с ошибкой","id":"223","title":"Написание проверки с ошибкой"},"224":{"body":"Сейчас наш проверка не проходит, потому что мы всегда возвращаем пустой вектор. Чтобы исправить это и выполнить search, наша программа должна выполнить следующие шаги: Повторение по каждой строке содержимого. Проверить, содержит ли данная строка искомую. Если это так, добавить её в список значений, которые мы возвращаем. Если это не так, ничего не делать. Вернуть список итогов. Давайте проработаем каждый шаг, начиная с перебора строк. Перебор строк с помощью способа lines В Ржавчина есть полезный способ для построчной повторения строк, удобно названный lines, как показано в приложении 12-17. Обратите внимание, код пока не собирается. Файл: src/lib.rs # use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# }\n# # impl Config {\n# pub fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# }\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # Ok(())\n# }\n# pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { for line in contents.lines() { // do something with line }\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn one_result() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# } Приложение 12-17: Повторение по каждой строке из contents Способ lines возвращает повторитель . Мы подробно поговорим об повторителях в Главе 13 , но вспомните, что вы видели этот способ использования повторителя в Приложении 3-5 , где мы использовали цикл for с повторителем, чтобы выполнить некоторый код для каждого элемента в собрания. Поиск в каждой строке текста запроса Далее мы проверяем, содержит ли текущая строка нашу искомую строку. К счастью, у строк есть полезный способ contains, который именно это и делает! Добавьте вызов способа contains в функции search, как показано в приложении 12-18. Обратите внимание, что это все ещё не собирается. Файл: src/lib.rs # use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# }\n# # impl Config {\n# pub fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# }\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # Ok(())\n# }\n# pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { for line in contents.lines() { if line.contains(query) { // do something with line } }\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn one_result() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# } Приложение 12-18. Добавление проверки, содержится ли query в строке На данный мгновение мы наращиваем возможность. Чтобы заставить это собираться, нам нужно вернуть значение из тела функции, как мы указали в ярлыке функции. Сохранение совпавшей строки Чтобы завершить эту функцию, нам нужен способ сохранить совпадающие строки, которые мы хотим вернуть. Для этого мы можем создать изменяемый вектор перед циклом for и вызывать способ push для сохранения line в векторе. После цикла for мы возвращаем вектор, как показано в приложении 12-19. Файл: src/lib.rs # use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# }\n# # impl Config {\n# pub fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# }\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # Ok(())\n# }\n# pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn one_result() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# } Приложение 12-19: Сохраняем совпавшие строки, чтобы впоследствии их можно было вернуть Теперь функция search должна возвратить только строки, содержащие query, и проверка должен пройти. Запустим его: $ cargo test Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `test` profile [unoptimized + debuginfo] target(s) in 1.22s Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94) running 1 test\ntest tests::one_result ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests minigrep running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Наш проверка пройден, значит он работает! На этом этапе мы могли бы рассмотреть возможности изменения выполнения функции поиска, сохраняя прохождение проверок и поддерживая имеющуюся возможность. Код в функции поиска не так уж плох, но он не использует некоторые полезные функции повторителей. Вернёмся к этому примеру в главе 13 , где будем исследовать повторители подробно, и посмотрим как его улучшить. Использование функции search в функции run Теперь, когда функция search работает и проверена, нужно вызвать search из нашей функции run. Нам нужно передать значение config.query и contents, которые run читает из файла, в функцию search. Тогда run напечатает каждую строку, возвращаемую из search: Файл: src/lib.rs # use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# }\n# # impl Config {\n# pub fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# }\n# pub fn run(config: Config) -> Result<(), Box> { let contents = fs::read_to_string(config.file_path)?; for line in search(&config.query, &contents) { println!(\"{line}\"); } Ok(())\n}\n# # pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.contains(query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn one_result() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# } Мы по-прежнему используем цикл for для возврата каждой строки из функции search и её печати. Теперь вся программа должна работать! Давайте попробуем сначала запустить её со словом «frog», которое должно вернуть только одну строчку из стихотворения Эмили Дикинсон: $ cargo run -- frog poem.txt Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.38s Running `target/debug/minigrep frog poem.txt`\nHow public, like a frog Здорово! Теперь давайте попробуем слово, которое будет соответствовать нескольким строкам, например «body»: $ cargo run -- body poem.txt Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s Running `target/debug/minigrep body poem.txt`\nI'm nobody! Who are you?\nAre you nobody, too?\nHow dreary to be somebody! И наконец, давайте удостоверимся, что мы не получаем никаких строк, когда ищем слово, отсутствующее в стихотворении, например «monomorphization»: $ cargo run -- monomorphization poem.txt Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s Running `target/debug/minigrep monomorphization poem.txt` Отлично! Мы создали собственную простое-исполнение обычного средства и научились тому, как внутренне выстроить приложения. Мы также немного узнали о файловом вводе и выводе, временах жизни, проверке и разборе переменных приказной строки. Чтобы завершить этот дело, мы кратко выполним пару вещей: как работать с переменными окружения и как печатать в обычный поток ошибок, обе из которых полезны при написании окно выводаных программ.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Разработка возможности библиотеки с помощью разработки через проверка » Написание кода для прохождения проверки","id":"224","title":"Написание кода для прохождения проверки"},"225":{"body":"Мы улучшим minigrep, добавив дополнительную функцию: возможность для поиска без учёта регистра, которую пользователь может включить с помощью переменной среды окружения. Мы могли бы сделать эту функцию свойствоом приказной строки и потребовать, чтобы пользователи вводили бы её каждый раз при её применении, но вместо этого мы будем использовать переменную среды окружения, что позволит нашим пользователям устанавливать переменную среды один раз и все поиски будут не чувствительны к регистру в этом окно вызоваьном сеансе.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Работа с переменными среды » Работа с переменными окружения","id":"225","title":"Работа с переменными окружения"},"226":{"body":"Мы, во-первых, добавим новую функцию search_case_insensitive, которую мы будем вызывать, когда переменная окружения содержит значение. Мы продолжим следовать этапу TDD, поэтому первый шаг - это снова написать не проходящий проверку. Мы добавим новый проверка для новой функции search_case_insensitive и переименуем наш старый проверка из one_result в case_sensitive, чтобы прояснить различия между двумя проверкими, как показано в приложении 12-20. Файл: src/lib.rs # use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# }\n# # impl Config {\n# pub fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# }\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # for line in search(&config.query, &contents) {\n# println!(\"{line}\");\n# }\n# # Ok(())\n# }\n# # pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.contains(query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# #[cfg(test)]\nmod tests { use super::*; #[test] fn case_sensitive() { let query = \"duct\"; let contents = \"\\\nRust:\nsafe, fast, productive.\nPick three.\nDuct tape.\"; assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents)); } #[test] fn case_insensitive() { let query = \"rUsT\"; let contents = \"\\\nRust:\nsafe, fast, productive.\nPick three.\nTrust me.\"; assert_eq!( vec![\"Rust:\", \"Trust me.\"], search_case_insensitive(query, contents) ); }\n} Приложение 12-20. Добавление нового не проходящего проверки для функции поиска нечувствительной к регистру, которую мы собираемся добавить Обратите внимание, что мы также отредактировали содержимое переменной contents из старого проверки. Мы добавили новую строку с текстом \"Duct tape.\", используя заглавную D, которая не должна соответствовать запросу \"duct\" при поиске с учётом регистра. Такое изменение старого проверки помогает избежать случайного нарушения возможности поиска чувствительного к регистру, который мы уже выполнили. Этот проверка должен пройти сейчас и должен продолжать выполняться успешно, пока мы работаем над поиском без учёта регистра. Новый проверка для поиска нечувствительного к регистру использует \"rUsT\" качестве строки запроса. В функции search_case_insensitive, которую мы собираемся выполнить, запрос \"rUsT\" должен соответствовать строке содержащей \"Rust:\" с большой буквы R и соответствовать строке \"Trust me.\", хотя обе имеют разные регистры из запроса. Это наш не проходящий проверка, он не собирается, потому что мы ещё не определили функцию search_case_insensitive. Не стесняйтесь добавлять скелет выполнение, которая всегда возвращает пустой вектор, подобно тому, как мы это делали для функции search в приложении 12-16, чтобы увидеть сборку проверки и его сбой.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Работа с переменными среды » Написание ошибочного проверки для функции search с учётом регистра","id":"226","title":"Написание ошибочного проверки для функции search с учётом регистра"},"227":{"body":"Функция search_case_insensitive, показанная в приложении 12-21, будет почти такая же, как функция search. Разница лишь в том, что текст будет в нижнем регистре для query и для каждой line, так что для любого регистра входных переменных это будет тот же случай, когда мы проверяем, содержит ли строка запрос. Файл: src/lib.rs # use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# }\n# # impl Config {\n# pub fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# }\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # for line in search(&config.query, &contents) {\n# println!(\"{line}\");\n# }\n# # Ok(())\n# }\n# # pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.contains(query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# pub fn search_case_insensitive<'a>( query: &str, contents: &'a str,\n) -> Vec<&'a str> { let query = query.to_lowercase(); let mut results = Vec::new(); for line in contents.lines() { if line.to_lowercase().contains(&query) { results.push(line); } } results\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn case_sensitive() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Duct tape.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# # #[test]\n# fn case_insensitive() {\n# let query = \"rUsT\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Trust me.\";\n# # assert_eq!(\n# vec![\"Rust:\", \"Trust me.\"],\n# search_case_insensitive(query, contents)\n# );\n# }\n# } Приложение 12-21. Определение функции search_case_insensitive с уменьшением регистра строки запроса и строки содержимого перед их сравнением Сначала преобразуем в нижний регистр строку query и сохраняем её в затенённой переменной с тем же именем. Вызов to_lowercase для строки запроса необходим, так что независимо от того, будет ли пользовательский запрос \"rust\" , \"RUST\", \"Rust\" или \"rUsT\", мы будем преобразовывать запрос к \"rust\" и делать значение нечувствительным к регистру. Хотя to_lowercase будет обрабатывать Unicode, он не будет точным на 100%. Если бы мы писали существующее приложение, мы бы хотели проделать здесь немного больше работы, но этот раздел посвящён переменным среды, а не Unicode, поэтому мы оставим это здесь. Обратите внимание, что query теперь имеет вид String, а не срез строки, потому что вызов to_lowercase создаёт новые данные, а не ссылается на существующие. К примеру, запрос: \"rUsT\" это срез строки не содержащий строчных букв u или t, которые мы можем использовать, поэтому мы должны выделить новую String, содержащую «rust». Когда мы передаём запрос query в качестве переменной способа contains, нам нужно добавить знак, поскольку ярлык contains, определена для приёмы среза строки. Затем мы добавляем вызов to_lowercase для каждой строки line для преобразования к нижнему регистру всех символов. Теперь, когда мы преобразовали line и query в нижний регистр, мы найдём совпадения независимо от того, в каком регистре находится переменная с запросом. Давайте посмотрим, проходит ли эта выполнение проверки: $ cargo test Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `test` profile [unoptimized + debuginfo] target(s) in 1.33s Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94) running 2 tests\ntest tests::case_insensitive ... ok\ntest tests::case_sensitive ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Doc-tests minigrep running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Отлично! Проверки прошли. Теперь давайте вызовем новую функцию search_case_insensitive из функции run. Во-первых, мы добавим свойство настройке в устройство Config для переключения между поиском с учётом регистра и без учёта регистра. Добавление этого поля приведёт к ошибкам сборщика, потому что мы ещё нигде не объявим это поле: Файл: src/lib.rs # use std::error::Error;\n# use std::fs;\n# pub struct Config { pub query: String, pub file_path: String, pub ignore_case: bool,\n}\n# # impl Config {\n# pub fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# }\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # let results = if config.ignore_case {\n# search_case_insensitive(&config.query, &contents)\n# } else {\n# search(&config.query, &contents)\n# };\n# # for line in results {\n# println!(\"{line}\");\n# }\n# # Ok(())\n# }\n# # pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.contains(query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # pub fn search_case_insensitive<'a>(\n# query: &str,\n# contents: &'a str,\n# ) -> Vec<&'a str> {\n# let query = query.to_lowercase();\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.to_lowercase().contains(&query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn case_sensitive() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Duct tape.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# # #[test]\n# fn case_insensitive() {\n# let query = \"rUsT\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Trust me.\";\n# # assert_eq!(\n# vec![\"Rust:\", \"Trust me.\"],\n# search_case_insensitive(query, contents)\n# );\n# }\n# } Мы добавили поле ignore_case, которое содержит логическое значение. Далее нам нужна функция run, чтобы проверить значение поля ignore_case и использовать его, чтобы решить, вызывать ли функцию search или функцию search_case_insensitive, как показано в приложении 12-22. Этот код все ещё не собирается. Файл: src/lib.rs # use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# pub ignore_case: bool,\n# }\n# # impl Config {\n# pub fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# }\n# pub fn run(config: Config) -> Result<(), Box> { let contents = fs::read_to_string(config.file_path)?; let results = if config.ignore_case { search_case_insensitive(&config.query, &contents) } else { search(&config.query, &contents) }; for line in results { println!(\"{line}\"); } Ok(())\n}\n# # pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.contains(query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # pub fn search_case_insensitive<'a>(\n# query: &str,\n# contents: &'a str,\n# ) -> Vec<&'a str> {\n# let query = query.to_lowercase();\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.to_lowercase().contains(&query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn case_sensitive() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Duct tape.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# # #[test]\n# fn case_insensitive() {\n# let query = \"rUsT\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Trust me.\";\n# # assert_eq!(\n# vec![\"Rust:\", \"Trust me.\"],\n# search_case_insensitive(query, contents)\n# );\n# }\n# } Приложение 12-22. Вызов либо search, либо search_case_insensitive на основе значения в config.ignore_case Наконец, нам нужно проверить переменную среды. Функции для работы с переменными среды находятся в звене env встроенной библиотеки, поэтому мы хотим подключить этот звено в область видимости в верхней части src/lib.rs. Затем мы будем использовать функцию var из звена env для проверки установлено ли любое значение в переменной среды с именем IGNORE_CASE, как показано в приложении 12-23. Файл: src/lib.rs use std::env;\n// --snip-- # use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# pub ignore_case: bool,\n# }\n# impl Config { pub fn build(args: &[String]) -> Result { if args.len() < 3 { return Err(\"not enough arguments\"); } let query = args[1].clone(); let file_path = args[2].clone(); let ignore_case = env::var(\"IGNORE_CASE\").is_ok(); Ok(Config { query, file_path, ignore_case, }) }\n}\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # let results = if config.ignore_case {\n# search_case_insensitive(&config.query, &contents)\n# } else {\n# search(&config.query, &contents)\n# };\n# # for line in results {\n# println!(\"{line}\");\n# }\n# # Ok(())\n# }\n# # pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.contains(query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # pub fn search_case_insensitive<'a>(\n# query: &str,\n# contents: &'a str,\n# ) -> Vec<&'a str> {\n# let query = query.to_lowercase();\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.to_lowercase().contains(&query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn case_sensitive() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Duct tape.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# # #[test]\n# fn case_insensitive() {\n# let query = \"rUsT\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Trust me.\";\n# # assert_eq!(\n# vec![\"Rust:\", \"Trust me.\"],\n# search_case_insensitive(query, contents)\n# );\n# }\n# } Приложение 12-23. Проверка переменной среды с именем IGNORE_CASE Здесь мы создаём новую переменную ignore_case. Чтобы установить её значение, мы вызываем функцию env::var и передаём ей имя переменной окружения IGNORE_CASE. Функция env::var возвращает Result, который будет успешным исходом Ok содержащий значение переменной среды, если переменная среды установлена. Он вернёт исход Err, если переменная окружения не установлена. Мы используем способ is_ok у Result, чтобы проверить установлена ли переменная окружения, что будет означать, что программа должна выполнить поиск без учёта регистра. Если переменная среды IGNORE_CASE не содержит любого значения, то is_ok вернёт значение false и программа выполнит поиск c учётом регистра. Мы не заботимся о значении переменной среды, нас важно только установлена она или нет, поэтому мы проверяем is_ok, а не используем unwrap, expect или любой другой способ, который мы видели у Result. Мы передаём значение переменной ignore_case образцу Config, чтобы функция run могла прочитать это значение и решить, следует ли вызывать search или search_case_insensitive, как мы выполнили в приложении 12-22. Давайте попробуем! Во-первых, мы запустим нашу программу без установленной переменной среды и с помощью значения запроса to, который должен соответствовать любой строке, содержащей слово «to» в нижнем регистре: $ cargo run -- to poem.txt Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s Running `target/debug/minigrep to poem.txt`\nAre you nobody, too?\nHow dreary to be somebody! Похоже, все ещё работает! Теперь давайте запустим программу с IGNORE_CASE, установленным в 1, но с тем же значением запроса to. $ IGNORE_CASE=1 cargo run -- to poem.txt Если вы используете PowerShell, вам нужно установить переменную среды и запустить программу двумя приказми, а не одной: PS> $Env:IGNORE_CASE=1; cargo run -- to poem.txt Это заставит переменную окружения IGNORE_CASE сохраниться до конца сеанса работы окне вывода. Переменную можно отключить с помощью приказы Remove-Item: PS> Remove-Item Env:IGNORE_CASE Мы должны получить строки, содержащие «to», которые могут иметь заглавные буквы: Are you nobody, too?\nHow dreary to be somebody!\nTo tell your name the livelong day\nTo an admiring bog! Отлично, мы также получили строки, содержащие «To»! Наша программа minigrep теперь может выполнять поиск без учёта регистра, управляемая переменной среды. Теперь вы знаете, как управлять свойствами, заданными с помощью переменных приказной строки или переменных среды. Некоторые программы допускают использование переменных и переменных среды для одной и той же настройке. В таких случаях программы решают, что из них имеет больший приоритет. Для другого самостоятельного упражнения попробуйте управлять чувствительностью к регистру с помощью переменной приказной строки или переменной окружения. Решите, переменная приказной строки или переменная среды будет иметь приоритет, если программа выполняется со значениями \"учитывать регистр\" в одном случае, и \"пренебрегать регистр\" в другом. Звено std::env содержит много других полезных функций для работы с переменными среды: ознакомьтесь с его документацией, чтобы узнать доступные.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Работа с переменными среды » Выполнение функции search_case_insensitive","id":"227","title":"Выполнение функции search_case_insensitive"},"228":{"body":"В данный мгновение мы записываем весь наш вывод в окно вызова, используя функцию println!. В большинстве окно вызоваов предоставлено два вида вывода: обычный поток вывода ( stdout ) для общей сведений и обычный поток ошибок ( stderr ) для сообщений об ошибках. Это различие позволяет пользователям выбирать, направлять ли успешный вывод программы в файл, но при этом выводить сообщения об ошибках на экран. Функция println! может печатать только в обычный вывод, поэтому мы должны использовать что-то ещё для печати в обычный поток ошибок.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Запись сообщений об ошибках в stderr вместо stdout » Запись сообщений ошибок в поток ошибок вместо принятого потока вывода","id":"228","title":"Запись сообщений ошибок в поток ошибок вместо принятого потока вывода"},"229":{"body":"Во-первых, давайте посмотрим, как содержимое, напечатанное из minigrep в настоящее время записывается в обычный вывод, включая любые сообщения об ошибках, которые мы хотим вместо этого записать в обычный поток ошибок. Мы сделаем это, перенаправив обычный поток вывода в файл и намеренно вызовем ошибку. Мы не будем перенаправлять обычный поток ошибок, поэтому любой содержание, отправленный в поток принятых ошибок будет продолжать отображаться на экране. Ожидается, что программы приказной строки будут отправлять сообщения об ошибках в обычный поток ошибок, поэтому мы все равно можем видеть сообщения об ошибках на экране, даже если мы перенаправляем обычный поток вывода в файл. Наша программа в настоящее время не ведёт себя правильно: мы увидим, что она сохраняет вывод сообщения об ошибке в файл! Чтобы отобразить это поведение, мы запустим программу с помощью > и именем файла output.txt в который мы хотим перенаправить обычный поток вывода. Мы не будем передавать никаких переменных, что должно вызвать ошибку: $ cargo run > output.txt правила написания > указывает оболочке записывать содержимое принятого вывода в output.txt вместо экрана. Мы не увидели сообщение об ошибке, которое мы ожидали увидеть на экране, так что это означает, что оно должно быть в файле. Вот что содержит output.txt : Problem parsing arguments: not enough arguments Да, наше сообщение об ошибке выводится в обычный вывод. Гораздо более полезнее, чтобы подобные сообщения об ошибках печатались в встроенной поток ошибок, поэтому в файл попадают только данные из успешного запуска. Мы поменяем это.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Запись сообщений об ошибках в stderr вместо stdout » Проверка, куда записываются ошибки","id":"229","title":"Проверка, куда записываются ошибки"},"23":{"body":"Давайте рассмотрим «Привет, мир!» программу в подробностях. Вот первая часть головоломки: fn main() { } Эти строки определяют функцию с именем main. Функция main особенная: это всегда первый код, который запускается в каждой исполняемой программе Rust. Первая строка объявляет функцию с именем main, которая не имеет свойств и ничего не возвращает. Если бы были свойства, они бы заключались в круглые скобки (). Тело функции заключено в {}. Ржавчина требует фигурных скобок вокруг всех тел функций. Хороший исполнение — поместить открывающую фигурную скобку на ту же строку, что и объявление функции, добавив между ними один пробел. Примечание: Если хотите придерживаться принятого исполнения во всех делах Rust, вы можете использовать средство самостоятельного изменения под названием rustfmt для изменения кода в определённом исполнении (подробнее о rustfmt в Приложении D . Объединение Ржавчина включила этот средство в обычный установочный набор Rust, как rustc, поэтому он уже должен быть установлен на вашем компьютере! Тело функции main содержит следующий код: println!(\"Привет, мир!\"); Эта строка делает всю работу в этой маленькой программе: печатает текст на экран. Можно заметить четыре важных подробности. Во-первых, исполнение Ржавчина предполагает отступ в четыре пробела, а не табуляцию. Во-вторых, println! вызывается макрос Rust. Если бы вместо него была вызвана функция, она была бы набрана как println (без !). Более подробно мы обсудим макросы Ржавчина в главе 19. Пока достаточно знать, что использование ! подразумевает вызов макроса вместо обычной функции, и что макросы не всегда подчиняются тем же правилам как функции. В-третьих, вы видите строку \"Привет, мир!\". Мы передаём её в качестве переменной макросу println!, и она выводится на экран. В-четвёртых, мы завершаем строку точкой с запятой (;), которая указывает на окончание этого выражения и возможность начала следующего. Большинство строк кода Ржавчина заканчиваются точкой с запятой.","breadcrumbs":"С чего начать » Привет, Мир! » Анатомия программы на Rust","id":"23","title":"Анатомия программы на Rust"},"230":{"body":"Мы будем использовать код в приложении 12-24, чтобы изменить способ вывода сообщений об ошибках. Из-за переработки кода, который мы делали ранее в этой главе, весь код, который печатает сообщения об ошибках, находится в одной функции: main. Обычная библиотека предоставляет макрос eprintln!который печатает в обычный поток ошибок, поэтому давайте изменим два места, где мы вызывали println! для печати ошибок, чтобы использовать eprintln! вместо этого. Файл: src/main.rs # use std::env;\n# use std::process;\n# # use minigrep::Config;\n# fn main() { let args: Vec = env::args().collect(); let config = Config::build(&args).unwrap_or_else(|err| { eprintln!(\"Problem parsing arguments: {err}\"); process::exit(1); }); if let Err(e) = minigrep::run(config) { eprintln!(\"Application error: {e}\"); process::exit(1); }\n} Запись сообщений об ошибках в Standard Error вместо Standard Output используя eprintln! Давайте снова запустим программу таким же образом, без каких-либо переменных и перенаправим обычный вывод с помощью >: $ cargo run > output.txt\nProblem parsing arguments: not enough arguments Теперь мы видим ошибку на экране и output.txt не содержит ничего, что мы ожидаем от программы приказной строки. Давайте снова запустим программу с переменнойми, которые не вызывают ошибку, но все же перенаправляют обычный вывод в файл, например так: $ cargo run -- to poem.txt > output.txt Мы не увидим никакого вывода в окно вызова, а output.txt будет содержать наши итоги: Файл: output.txt Are you nobody, too?\nHow dreary to be somebody! Это отображает, что в зависимости от случаи мы теперь используем обычный поток вывода для успешного текста и обычный поток ошибок для вывода ошибок.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Запись сообщений об ошибках в stderr вместо stdout » Печать ошибок в поток ошибок","id":"230","title":"Печать ошибок в поток ошибок"},"231":{"body":"В этой главе были повторены некоторые основные подходы, которые вы изучили до сих пор и было рассказано, как выполнять обычные действия ввода-вывода в Rust. Используя переменные приказной строки, файлы, переменные среды и макросeprintln! для печати ошибок и вы теперь готовы писать приложения приказной строки. В сочетании с подходами из предыдущих главах, ваш код будет хорошо согласован, будет эффективно хранить данные в соответствующих устройствах, хорошо обрабатывать ошибки и хорошо проверяться. Далее мы рассмотрим некоторые возможности Rust, на которые повлияли полезные языки: замыкания и повторители.","breadcrumbs":"Дело с вводом-выводом: создание программы приказной строки » Запись сообщений об ошибках в stderr вместо stdout » Итоги","id":"231","title":"Итоги"},"232":{"body":"Внешний вид языка Ржавчина черпал вдохновение из многих других языков и техник, среди которых значительное влияние оказало функциональное программирование . Программирование в функциональном исполнении подразумевает использование функций взначении предметов, передавая их в качестве переменных, возвращая их из других функций, присваивая их переменным для последующего выполнения и так далее. В этой главе мы не будем рассуждать о том, что из себя представляет функциональное программирование, а обсудим возможности Rust, присущие многим языкам, которые принято называть функциональными. Более подробно мы поговорим про: Замыкания - устройства, подобные функциям, которые можно помещать в переменные Повторители — способ обработки последовательности элементов, То, как, используя замыкания и повторители, улучшить работу с действиеми ввода-вывода в деле из главы 12 Производительность замыканий и повторителей (спойлер: они быстрее, чем вы думаете!) Мы уже рассмотрели другие возможности Rust, такие как сопоставление с образцом и перечисления, которые также появились под влиянием функционального исполнения. Поскольку освоение замыканий и повторителей — важная часть написания идиоматичного, быстрого кода на Rust, мы посвятим им всю эту главу.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Полезные возможности языка: повторители и замыкания","id":"232","title":"Полезные возможности языка: повторители и замыкания"},"233":{"body":"Замыкания в Ржавчина - это анонимные функции, которые можно сохранять в переменных или передавать в качестве переменных другим функциям. Вы можете создать замыкание в одном месте, а затем вызвать его в каком-нибудь другом, чтобы выполнить обработку в ином среде. В отличие от функций, замыкания могут использовать значения из области видимости в которой они были определены. Мы выполним, как эти функции замыканий открывают возможности для повторного использования кода и изменения его поведения.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Замыкания: анонимные функции, которые захватывают своё окружение » Замыкания: анонимные функции, которые запечатлевают (\"захватывают\") своё окружение","id":"233","title":"Замыкания: анонимные функции, которые запечатлевают (\"захватывают\") своё окружение"},"234":{"body":"Сначала мы рассмотрим, как с помощью замыканий можно использовать предметы из области, в которой они вместе были определены, для их последующего использования. Вот сценарий: Время от времени наша предприятие по производству футболок в качестве акции дарит эксклюзивные футболки, выпущенные ограниченным тиражом, каким-нибудь пользователям из нашего списка рассылки. Люди из списка рассылки при желании могут выбрать любимый цвет в своём профиле. Если человек, выбранный для получения бесплатной футболки, указал свой любимый цвет, он получает футболку этого цвета. Если человек не указал свой любимый цвет, он получит рубашку того цвета, которых у предприятия на данный мгновение больше всего. Существует множество способов выполнить это. В данном примере мы будем использовать перечисление ShirtColor, которое может быть двух исходов Red и Blue (для простоты ограничим количество доступных цветов этими двумя). Запасы предприятия мы представим устройством Inventory, которая состоит из поля shirts, содержащего Vec, в котором перечислены рубашки тех цветов, которые есть в наличии. Способ giveaway, определённый в Inventory, принимает необязательный свойство - цвет, предпочитаемый пользователем, выбранным для получения бесплатной рубашки, и возвращает тот цвет рубашки, который он получит в действительности. Эта схема показана в приложении 13-1: Имя файла: src/main.rs #[derive(Debug, PartialEq, Copy, Clone)]\nenum ShirtColor { Red, Blue,\n} struct Inventory { shirts: Vec,\n} impl Inventory { fn giveaway(&self, user_preference: Option) -> ShirtColor { user_preference.unwrap_or_else(|| self.most_stocked()) } fn most_stocked(&self) -> ShirtColor { let mut num_red = 0; let mut num_blue = 0; for color in &self.shirts { match color { ShirtColor::Red => num_red += 1, ShirtColor::Blue => num_blue += 1, } } if num_red > num_blue { ShirtColor::Red } else { ShirtColor::Blue } }\n} fn main() { let store = Inventory { shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue], }; let user_pref1 = Some(ShirtColor::Red); let giveaway1 = store.giveaway(user_pref1); println!( \"The user with preference {:?} gets {:?}\", user_pref1, giveaway1 ); let user_pref2 = None; let giveaway2 = store.giveaway(user_pref2); println!( \"The user with preference {:?} gets {:?}\", user_pref2, giveaway2 );\n} Приложение 13-1: Случаей с раздачей рубашек предприятием В магазине store, определённом в main, осталось две синие и одна красная рубашки для этой ограниченной акции. Мы вызываем способ giveaway для пользователя предпочитающего красную рубашку и для пользователя без каких-либо предпочтений. Опять же, этот код мог быть выполнен множеством способов, но в данном случае, чтобы сосредоточиться на замыканиях, мы придерживались изученных ранее подходов, за исключением тела способа giveaway, в котором используется замыкание. В способе giveaway мы получаем пользовательское предпочтение цвета как свойство вида Option и вызываем способ unwrap_or_else на user_preference. Способ unwrap_or_else перечисления Option определён встроенной библиотекой. Он принимает один переменная: замыкание без переменных, которое возвращает значение T (преобразуется в вид значения, которое окажется в исходе Some перечисления Option, в нашем случае ShirtColor). Если Option окажется исходом Some, unwrap_or_else вернёт значение из Some. А если Option будет является исходом None, unwrap_or_else вызовет замыкание и вернёт значение, возвращённое замыканием. В качестве переменной unwrap_or_else мы передаём замыкание || self.most_stocked(). Это замыкание, которое не принимает никаких свойств (если бы у замыкания были свойства, они были бы перечислены между двумя вертикальными полосами). В теле замыкания вызывается self.most_stocked(). Здесь мы определили замыкание, а выполнение unwrap_or_else такова, что выполнится оно позднее, когда потребуется получить итог. Выполнение этого кода выводит: $ cargo run Compiling shirt-company v0.1.0 (file:///projects/shirt-company) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s Running `target/debug/shirt-company`\nThe user with preference Some(Red) gets Red\nThe user with preference None gets Blue Важной особенностью здесь является то, что мы передали замыкание, которое вызывает self.most_stocked() текущего образца Inventory. Обычной библиотеке не нужно знать ничего о видах Inventory или ShirtColor, которые мы определили, или о логике, которую мы хотим использовать в этом сценарии. Замыкание определяет неизменяемую ссылку на self Inventory и передаёт её с указанным нами кодом в способ unwrap_or_else. А вот функции не могут определять своё окружение таким образом.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Замыкания: анонимные функции, которые захватывают своё окружение » Захват переменных окружения с помощью замыкания","id":"234","title":"Захват переменных окружения с помощью замыкания"},"235":{"body":"Есть и другие различия между функциями и замыканиями. Замыкания обычно не требуют определенния видов входных свойств или возвращаемого значения, как это делается в функциях fn. Изложения видов требуются для функций, потому что виды являются частью явного внешней оболочки, предоставляемого пользователям. Жёсткое определение таких внешних оболочек важно для того, чтобы все были согласованы в том, какие виды значений использует и возвращает функция. А вот замыкания, напротив, не употребляются взначении подобных открытых внешних оболочек: они хранятся в переменных, используются не имея имени и незримо для пользователей нашей библиотеки. Замыкания, как правило, небольшие и уместны в каком-то узконаправленном среде, а не в произвольных случаях. В этих ограниченных средах сборщик может вывести виды свойств и возвращаемого вида, подобно тому, как он может вывести виды большинства переменных (есть редкие случаи, когда сборщику также нужны изложении видов замыканий). Как и в случае с переменными, мы можем добавить изложении видов, если хотим повысить ясность и чёткость описания ценой увеличения многословности, большей чем это необходимо. Определение видов для замыкания будет выглядеть как определение, показанное в приложении 13-2. В этом примере мы определяем замыкание и храним его в переменной, а не определяем замыкание в том месте, куда мы передаём его в качестве переменной, как это было в приложении 13-1. Имя файла: src/main.rs # use std::thread;\n# use std::time::Duration;\n# # fn generate_workout(intensity: u32, random_number: u32) { let expensive_closure = |num: u32| -> u32 { println!(\"calculating slowly...\"); thread::sleep(Duration::from_secs(2)); num };\n# # if intensity < 25 {\n# println!(\"Today, do {} pushups!\", expensive_closure(intensity));\n# println!(\"Next, do {} situps!\", expensive_closure(intensity));\n# } else {\n# if random_number == 3 {\n# println!(\"Take a break today! Remember to stay hydrated!\");\n# } else {\n# println!(\n# \"Today, run for {} minutes!\",\n# expensive_closure(intensity)\n# );\n# }\n# }\n# }\n# # fn main() {\n# let simulated_user_specified_value = 10;\n# let simulated_random_number = 7;\n# # generate_workout(simulated_user_specified_value, simulated_random_number);\n# } Приложение 13-2: Добавление необязательных наставлений видов свойств и возвращаемых значений в замыкании С добавлением наставлений видов правила написания замыканий выглядит более похожим на правила написания функций. Здесь мы, для сравнения, определяем функцию, которая добавляет 1 к своему свойству, и замыкание, которое имеет такое же поведение. Мы добавили несколько пробелов, чтобы выровнять соответствующие части. Это показывает, что правила написания замыкания похож на правила написания функции, за исключением использования труб (вертикальная черта) и количества необязательного правил написания: fn add_one_v1 (x: u32) -> u32 { x + 1 }\nlet add_one_v2 = |x: u32| -> u32 { x + 1 };\nlet add_one_v3 = |x| { x + 1 };\nlet add_one_v4 = |x| x + 1 ; В первой строке показано определение функции, а во второй - полностью определенное определение замыкания. В третьей строке мы удаляем изложении видов из определения замыкания. В четвёртой строке мы убираем скобки, которые являются необязательными, поскольку тело замыкания содержит только одну действие. Это всё правильные определения, которые будут иметь одинаковое поведение при вызове. Строки add_one_v3 и add_one_v4 требуют, чтобы замыкания были вычислены до сборки, поскольку виды будут выведены из их использования. Это похоже на let v = Vec::new();, когда в Vec необходимо вставить либо изложении видов, либо значения некоторого вида, чтобы Ржавчина смог вывести вид. Для определений замыкания сборщик выводит определенные виды для каждого из свойств и возвращаемого значения. Например, в приложении 13-3 показано определение короткого замыкания, которое просто возвращает значение, полученное в качестве свойства. Это замыкание не очень полезно, кроме как для целей данного примера. Обратите внимание, что мы не добавили в определение никаких наставлений видов. Поскольку наставлений видов нет, мы можем вызвать замыкание для любого вида, что мы и сделали в первый раз с String. Если затем мы попытаемся вызвать example_closure для целого числа, мы получим ошибку. Имя файла: src/main.rs # fn main() { let example_closure = |x| x; let s = example_closure(String::from(\"hello\")); let n = example_closure(5);\n# } Приложение 13-3: Попытка вызова замыкания, виды которого выводятся из двух разных видов Сборщик вернёт нам вот такую ошибку: $ cargo run Compiling closure-example v0.1.0 (file:///projects/closure-example)\nerror[E0308]: mismatched types --> src/main.rs:5:29 |\n5 | let n = example_closure(5); | --------------- ^- help: try using a conversion method: `.to_string()` | | | | | expected `String`, found integer | arguments to this function are incorrect |\nnote: expected because the closure was earlier called with an argument of type `String` --> src/main.rs:4:29 |\n4 | let s = example_closure(String::from(\"hello\")); | --------------- ^^^^^^^^^^^^^^^^^^^^^ expected because this argument is of type `String` | | | in this closure call\nnote: closure parameter defined here --> src/main.rs:2:28 |\n2 | let example_closure = |x| x; | ^ For more information about this error, try `rustc --explain E0308`.\nerror: could not compile `closure-example` (bin \"closure-example\") due to 1 previous error При первом вызове example_closure со значением String сборщик определяет вид x и возвращаемый вид замыкания как String. Эти виды затем определятся в замыкании в example_closure, и мы получаем ошибку вида при следующей попытке использовать другой вид с тем же замыканием.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Замыкания: анонимные функции, которые захватывают своё окружение » Выведение и изложение видов замыкания","id":"235","title":"Выведение и изложение видов замыкания"},"236":{"body":"Замыкания могут захватывать значения из своего окружения тремя способами, которые соответствуют тем же трём способам, которыми функция может принимать свойства: заимствование неизменяемых, заимствование изменяемых и получение владения. Замыкание самостоятельно определяет, какой из этих способов использовать, исходя из того, что тело функции делает с полученными значениями. В приложении 13-4 мы определяем замыкание, которое захватывает неизменяемую ссылку на вектор с именем list, поскольку неизменяемой ссылки достаточно для печати значения: Имя файла: src/main.rs fn main() { let list = vec![1, 2, 3]; println!(\"Before defining closure: {list:?}\"); let only_borrows = || println!(\"From closure: {list:?}\"); println!(\"Before calling closure: {list:?}\"); only_borrows(); println!(\"After calling closure: {list:?}\");\n} Приложение 13-4: Определение и вызов замыкания, которое захватывает неизменяемую ссылку Этот пример также отображает, то что переменная может быть привязана к определению замыкания, и в дальнейшем мы можем вызвать замыкание, используя имя переменной и круглые скобки, как если бы имя переменной было именем функции. Поскольку мы можем иметь несколько неизменяемых ссылок на list одновременно, list остаётся доступным из кода до определения замыкания, после определения замыкания, а также до вызова замыкания и после. Этот код собирается, выполняется и печатает: $ cargo run Locking 1 package to latest compatible version Adding closure-example v0.1.0 (/Users/carolnichols/rust/book/tmp/listings/ch13-functional-features/listing-13-04) Compiling closure-example v0.1.0 (file:///projects/closure-example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s Running `target/debug/closure-example`\nBefore defining closure: [1, 2, 3]\nBefore calling closure: [1, 2, 3]\nFrom closure: [1, 2, 3]\nAfter calling closure: [1, 2, 3] В следующем приложении 13-5 мы изменили тело замыкания так, чтобы оно добавляло элемент в вектор list. Теперь замыкание захватывает изменяемую ссылку: Имя файла: src/main.rs fn main() { let mut list = vec![1, 2, 3]; println!(\"Before defining closure: {list:?}\"); let mut borrows_mutably = || list.push(7); borrows_mutably(); println!(\"After calling closure: {list:?}\");\n} Приложение 13-5. Определение и вызов замыкания, захватывающего изменяемую ссылку Этот код собирается, запускается и печатает: $ cargo run Locking 1 package to latest compatible version Adding closure-example v0.1.0 (/Users/carolnichols/rust/book/tmp/listings/ch13-functional-features/listing-13-05) Compiling closure-example v0.1.0 (file:///projects/closure-example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s Running `target/debug/closure-example`\nBefore defining closure: [1, 2, 3]\nAfter calling closure: [1, 2, 3, 7] Обратите внимание, что между определением и вызовом замыкания borrows_mutably больше нет println!: когда определяется borrows_mutably, оно захватывает изменяемую ссылку на list. После вызова замыкания мы больше не используем его, поэтому изменяемое заимствование заканчивается. Между определением замыкания и вызовом замыкания неизменяемое заимствование для печати недоступно, потому что при наличии изменяемого заимствования никакие другие заимствования недопустимы. Попробуйте добавить туда println! и посмотрите, какое сообщение об ошибке вы получите! Если вы хотите заставить замыкание принять владение значениями, которые оно использует в окружении, даже если в теле замыкания нет кода, требующего владения, вы можете использовать ключевое слово move перед списком свойств. Эта техника в основном полезна при передаче замыкания новому потоку, чтобы переместить данные так, чтобы они принадлежали новому потоку. Мы подробно обсудим потоки и то, зачем их использовать, в главе 16, когда будем говорить о одновременности, а пока давайте вкратце рассмотрим порождение нового потока с помощью замыкания, в котором используется ключевое слово move. В приложении 13-6 показан код из приложения 13-4, измененный для печати вектора в новом потоке, а не в основном потоке: Файл: src/main.rs use std::thread; fn main() { let list = vec![1, 2, 3]; println!(\"Before defining closure: {list:?}\"); thread::spawn(move || println!(\"From thread: {list:?}\")) .join() .unwrap();\n} Приложение 13-6: Использование move для принуждения замыкания потока принять на себя владение list Мы порождаем новый поток, передавая ему в качестве переменной замыкание для выполнения. Тело замыкания распечатывает список. В приложении 13-4 замыкание захватило list только с помощью неизменяемой ссылки, потому что это наименьше необходимый доступ к list для его печати. В этом примере, несмотря на то, что тело замыкания по-прежнему требует только неизменяемой ссылки, нам нужно указать, что list должен быть перемещён в замыкание, поместив ключевое слово move в начало определения замыкания. Новый поток может завершиться раньше, чем завершится основной поток, или основной поток может завершиться первым. Если основной поток сохранил владение list, но завершился раньше нового потока и удалил list, то неизменяемая ссылка в потоке будет недействительной. Поэтому сборщик требует, чтобы list был перемещён в замыкание, переданное новому потоку, чтобы ссылка была действительной. Попробуйте убрать ключевое слово move или использовать list в основном потоке после определения замыкания и посмотрите, какие ошибки сборщика вы получите!","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Замыкания: анонимные функции, которые захватывают своё окружение » Захват ссылок или передача владения","id":"236","title":"Захват ссылок или передача владения"},"237":{"body":"После того, как замыкание захватило ссылку или владение значением из среды, в которой оно определено (тем самым влияя на то, что перемещается в замыкание), код в теле замыкания определяет, что происходит со ссылками или значениями, в мгновение последующего выполнения замыкания (тем самым влияя на то, что перемещается из замыкания). Тело замыкания может делать любое из следующих действий: перемещать захваченное значение из замыкания, изменять захваченное значение, не перемещать и не изменять значение или вообще ничего не захватывать из среды. То, как замыкание получает и обрабатывает значения из своего окружения, указывает на то, какие особенности выполняет замыкание, а с помощью особенностей функции и устройства могут определять, какие виды замыканий они могут использовать. Замыканиям самостоятельно присваивается выполнение одного, двух или всех трёх из нижеперечисленных особенностей Fn, аддитивным образом, в зависимости от того, как тело замыкания обрабатывает значения: FnOnce применяется к замыканиям, которые могут быть вызваны один раз. Все замыкания выполняют по крайней мере этот особенность, потому что все замыкания могут быть вызваны. Замыкание, которое перемещает захваченные значения из своего тела, выполняет только FnOnce и ни один из других признаков Fn, потому что оно может быть вызвано только один раз. FnMut применяется к замыканиям, которые не перемещают захваченные значения из своего тела, но могут изменять захваченные значения. Такие замыкания могут вызываться более одного раза. Fn применяется к замыканиям, которые не перемещают захваченные значения из своего тела и не изменяют захваченные значения, а также к замыканиям, которые ничего не захватывают из своего окружения. Такие замыкания могут выполняться более одного раза и не меняют ничего в своём окружении, что важно в таких случаях, как одновременный вызов замыкания несколько раз. Давайте рассмотрим определение способа unwrap_or_else у Option, который мы использовали в приложении 13-1: impl Option { pub fn unwrap_or_else(self, f: F) -> T where F: FnOnce() -> T { match self { Some(x) => x, None => f(), } }\n} Напомним, что T - это гибкий вид, отображающий вид значения в Some исходе Option. Этот вид T также является возвращаемым видом функции unwrap_or_else: например, код, вызывающий unwrap_or_else у Option, получит String. Далее, обратите внимание, что функция unwrap_or_else имеет дополнительный свойство гибкого вида F. Здесь F - это вид входного свойства f, который является замыканием, заданным нами при вызове unwrap_or_else. Ограничением особенности, заданным для обобщённого вида F, является FnOnce() -> T, что означает, что F должен вызываться один раз, не принимать никаких переменных и возвращать T. Использование FnOnce в ограничении особенности говорит о том, что unwrap_or_else должен вызывать f не более одного раза. В теле unwrap_or_else мы видим, что если Option будет равен Some, то f не будет вызван. Если же значение Option будет равным None, то f будет вызван один раз. Поскольку все замыкания выполняют FnOnce, unwrap_or_else принимает самые разные виды замыканий и является настолько гибким, насколько это возможно. Примечание: Функции также могут выполнить все три особенности Fn. Если то, что мы хотим сделать, не требует захвата значения из среды, мы можем передавать имя какой-либо функции, а не замыкания, когда нам нужно что-то, выполняющее один из особенностей Fn. Например, для значения Option> мы можем вызвать unwrap_or_else(Vec::new), чтобы получить новый пустой вектор, если значение окажется None. Теперь рассмотрим способ встроенной библиотеки sort_by_key, определённый у срезов, чтобы увидеть, чем он отличается от unwrap_or_else и почему sort_by_key использует FnMut вместо FnOnce для ограничения особенности. Замыкание принимает единственный переменная в виде ссылки на текущий элемент в рассматриваемом срезе и возвращает значение вида K, к которому применима сортировка. Эта функция полезна, когда вы хотите отсортировать срез по определённому свойству каждого элемента. В приложении 13-7 у нас есть список образцов Rectangle, и мы используем sort_by_key, чтобы упорядочить их по свойству width от меньшего к большему: Файл: src/main.rs #[derive(Debug)]\nstruct Rectangle { width: u32, height: u32,\n} fn main() { let mut list = [ Rectangle { width: 10, height: 1 }, Rectangle { width: 3, height: 5 }, Rectangle { width: 7, height: 12 }, ]; list.sort_by_key(|r| r.width); println!(\"{list:#?}\");\n} Приложение 13-7: Использование sort_by_key для сортировки прямоугольников по ширине Этот код печатает: $ cargo run Compiling rectangles v0.1.0 (file:///projects/rectangles) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s Running `target/debug/rectangles`\n[ Rectangle { width: 3, height: 5, }, Rectangle { width: 7, height: 12, }, Rectangle { width: 10, height: 1, },\n] Причина, по которой sort_by_key определена как принимающая замыкание FnMut, заключается в том, что она вызывает замыкание несколько раз: по одному разу для каждого элемента в срезе. Замыкание |r| r.width не захватывает, не изменяет и не перемещает ничего из своего окружения, поэтому оно удовлетворяет требованиям связанности признаков. И наоборот, в приложении 13-8 показан пример замыкания, которое выполняет только признак FnOnce, потому что оно перемещает значение из среды. Сборщик не позволит нам использовать это замыкание с sort_by_key: Файл: src/main.rs #[derive(Debug)]\nstruct Rectangle { width: u32, height: u32,\n} fn main() { let mut list = [ Rectangle { width: 10, height: 1 }, Rectangle { width: 3, height: 5 }, Rectangle { width: 7, height: 12 }, ]; let mut sort_operations = vec![]; let value = String::from(\"closure called\"); list.sort_by_key(|r| { sort_operations.push(value); r.width }); println!(\"{list:#?}\");\n} Приложение 13-8: Попытка использовать замыкание FnOnce с sort_by_key Это надуманный, замысловатый способ (который не работает) подсчёта количества вызовов sort_by_key при сортировке list. Этот код пытается выполнить подсчёт, перемещая value - String из окружения замыкания - в вектор sort_operations. Замыкание захватывает value, затем перемещает value из замыкания, передавая владение на value вектору sort_operations. Это замыкание можно вызвать один раз; попытка вызвать его второй раз не сработает, потому что value уже не будет находиться в той среде, из которой его можно будет снова поместить в sort_operations! Поэтому это замыкание выполняет только FnOnce. Когда мы попытаемся собрать этот код, мы получим ошибку сообщающую о том что value не может быть перемещено из замыкания, потому что замыкание должно выполнить FnMut: $ cargo run Compiling rectangles v0.1.0 (file:///projects/rectangles)\nerror[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure --> src/main.rs:18:30 |\n15 | let value = String::from(\"closure called\"); | ----- captured outer variable\n16 |\n17 | list.sort_by_key(|r| { | --- captured by this `FnMut` closure\n18 | sort_operations.push(value); | ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait |\nhelp: consider cloning the value if the performance cost is acceptable |\n18 | sort_operations.push(value.clone()); | ++++++++ For more information about this error, try `rustc --explain E0507`.\nerror: could not compile `rectangles` (bin \"rectangles\") due to 1 previous error Ошибка указывает на строку в теле замыкания, которая перемещает value из окружения. Чтобы исправить это, нужно изменить тело замыкания так, чтобы оно не перемещало значения из окружения. Для подсчёта количества вызовов sort_by_key более простым способом является хранение счётчика в окружении и увеличение его значения в теле замыкания. Замыкание в приложении 13-9 работает с sort_by_key, поскольку оно определяет только изменяемую ссылку на счётчик num_sort_operations и поэтому может быть вызвано более одного раза: Файл: src/main.rs #[derive(Debug)]\nstruct Rectangle { width: u32, height: u32,\n} fn main() { let mut list = [ Rectangle { width: 10, height: 1 }, Rectangle { width: 3, height: 5 }, Rectangle { width: 7, height: 12 }, ]; let mut num_sort_operations = 0; list.sort_by_key(|r| { num_sort_operations += 1; r.width }); println!(\"{list:#?}, sorted in {num_sort_operations} operations\");\n} Приложение 13-9: Использование замыкания FnMut с sort_by_key разрешено Особенности Fn важны при определении или использовании функций или видов, использующих замыкания. В следующем разделе мы обсудим повторители. Многие способы повторителей принимают переменные в виде замыканий, поэтому не забывайте об этих подробностях, пока мы продвигаемся дальше!","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Замыкания: анонимные функции, которые захватывают своё окружение » Перемещение захваченных значений из замыканий и особенности Fn","id":"237","title":"Перемещение захваченных значений из замыканий и особенности Fn"},"238":{"body":"Использование образца Повторитель помогает при необходимости поочерёдного выполнения какой-либо действия над элементами последовательности. Повторитель отвечает за логику перебора элементов и определение особенности завершения последовательности. Используя повторители, вам не нужно самостоятельно выполнить всю эту логику. В Ржавчина повторители ленивые (lazy) , то есть они не делают ничего, пока вы не вызовете особые способы, потребляющие повторитель , чтобы задействовать его. Например, код в приложении 13-10 создаёт повторитель элементов вектора v1, вызывая способ iter, определённый у Vec. Сам по себе этот код не делает ничего полезного. # fn main() { let v1 = vec![1, 2, 3]; let v1_iter = v1.iter();\n# } Приложение 13-10: Создание повторителя Повторитель хранится в переменной v1_iter. Создав повторитель , мы можем использовать его различными способами. В приложении 3-5 главы 3 мы совершали обход элементов массива используя цикл for для выполнения какого-то кода над каждым из его элементов. Под капотом это неявно создавало, а затем потребляло повторитель , но до сих пор мы не касались того, как именно это работает. В примере из приложения 13-11 мы отделили создание повторителя от его использования в цикле for. В цикле for, использующем повторитель в v1_iter, каждый элемент повторителя участвует только в одной повторения цикла, в ходе которой выводится на экран его значение. # fn main() { let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter { println!(\"Got: {val}\"); }\n# } Приложение 13-11: Использование повторителя в цикле for В языках, обычные библиотеки которых не предоставляют повторители, вы, скорее всего, напишите эту же возможность так: создадите переменную со значением 0 затем, в цикле, использовав её для получения элемента вектора по порядковому указателю, будете увеличивать её значение, и так, пока оно не достигнет числа равного количеству элементов в векторе. Повторители выполняют всю эту логику за вас, сокращая количество повторяющегося кода, который возможно может быть написан неправильно. Повторители дают вам гибкость, позволяя использовать одинаковые принципы работы с различными видами последовательностей, а не только со устройствами данных, которые можно упорядочивать, например, векторами. Давайте рассмотрим, как повторители это делают.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Обработка последовательности элементов с помощью повторителей » Обработка последовательности элементов с помощью повторителей","id":"238","title":"Обработка последовательности элементов с помощью повторителей"},"239":{"body":"Все повторители выполняют особенность Iterator, который определён в встроенной библиотеке. Его определение выглядит так: pub trait Iterator { type Item; fn next(&mut self) -> Option; // methods with default implementations elided\n} Обратите внимание данное объявление использует новый правила написания: type Item и Self::Item, которые определяют сопряженный вид (associated type) с этим особенностью. Мы подробнее поговорим о сопряженных видах в главе 19. Сейчас вам нужно знать, что этот код требует от выполнений особенности Iterator определить требуемый им вид Item и данный вид Item используется в способе next. Другими словами, вид Item будет являться видом элемента, который возвращает повторитель . Особенность Iterator требует, чтобы разработчики определяли только один способ: способ next, который возвращает один элемент повторителя за раз обёрнутый в исход Some и когда повторение завершена, возвращает None. Мы можем вызывать способ next у повторителей напрямую; в приложении 13-12 показано, какие значения возвращаются при повторных вызовах next у повторителя, созданного из вектора. Файл: src/lib.rs # #[cfg(test)]\n# mod tests { #[test] fn iterator_demonstration() { let v1 = vec![1, 2, 3]; let mut v1_iter = v1.iter(); assert_eq!(v1_iter.next(), Some(&1)); assert_eq!(v1_iter.next(), Some(&2)); assert_eq!(v1_iter.next(), Some(&3)); assert_eq!(v1_iter.next(), None); }\n# } Приложение 13-12: Вызов способа next повторителя Обратите внимание, что нам нужно сделать переменную v1_iter изменяемой: вызов способа next повторителя изменяет внутреннее состояние повторителя, которое повторитель использует для отслеживания того, где он находится в последовательности. Другими словами, этот код потребляет (consume) или использует повторитель . Каждый вызов next потребляет элемент из повторителя. Нам не нужно было делать изменяемой v1_iter при использовании цикла for, потому что цикл забрал во владение v1_iter и сделал её изменяемой неявно для нас. Заметьте также, что значения, которые мы получаем при вызовах next являются неизменяемыми ссылками на значения в векторе. Способ iter создаёт повторитель по неизменяемым ссылкам. Если мы хотим создать повторитель , который становится владельцем v1 и возвращает принадлежащие ему значения, мы можем вызвать into_iter вместо iter. Точно так же, если мы хотим перебирать изменяемые ссылки, мы можем вызвать iter_mut вместо iter.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Обработка последовательности элементов с помощью повторителей » Особенность Iterator и способ next","id":"239","title":"Особенность Iterator и способ next"},"24":{"body":"Вы только что запустили впервые созданную программу, поэтому давайте рассмотрим каждый шаг этого этапа. Перед запуском программы на Ржавчина вы должны собрать её с помощью сборщика Rust, введя приказ rustc и передав ей имя вашего исходного файла, например: $ rustc main.rs Если у вас есть опыт работы с C или C++, вы заметите, что это похоже на gcc или clang. После успешной сборки Ржавчина выводит двоичный исполняемый файл. В Linux, macOS и PowerShell в Windows вы можете увидеть исполняемый файл, введя приказ ls в оболочке: $ ls\nmain main.rs В Linux и macOS вы увидите два файла. При использовании PowerShell в Windows вы увидите такие же три файла, как и при использовании CMD. Используя CMD в Windows, введите следующее: > dir /B %= the /B option says to only show the file names =%\nmain.exe\nmain.pdb\nmain.rs Это показывает исходный код файла с расширением .rs , исполняемый файл ( main.exe на Windows, но main на всех других площадках) и, при использовании Windows, файл, содержащий отладочную сведения с расширением .pdb . Отсюда вы запускаете файлы main или main.exe , например: $ ./main # для Linux\n> .\\main.exe # для Windows Если ваш main.rs — это ваша программа «Привет, мир!», эта строка выведет в окно вызова Привет, мир!. Если вы лучше знакомы с изменяемыми языками, такими как Ruby, Python или JavaScript, возможно, вы не привыкли собирать и запускать программу как отдельные шаги. Ржавчина — это предварительно собранный язык, то есть вы можете собрать программу и передать исполняемый файл кому-то другому, и он сможет запустить его даже без установленного Rust. Если вы даёте кому-то файл .rb , .py или .js , у него должна быть установлена выполнение Ruby, Python или JavaScript (соответственно). Но в этих языках вам нужна только одна приказ для сборки и запуска вашей программы. В внешнем виде языков программирования всё — соглашение. Сборка с помощью rustc подходит для простых программ, но по мере роста вашего дела вы захотите управлять всеми свойствами и упростить передачу кода. Далее мы познакомим вас с средством Cargo, который поможет вам писать программы из существующего мира на Rust.","breadcrumbs":"С чего начать » Привет, Мир! » Сборка и запуск - это отдельные шаги","id":"24","title":"Сборка и запуск - это отдельные шаги"},"240":{"body":"У особенности Iterator есть несколько способов, выполнение которых по умолчанию предоставляется встроенной библиотекой; вы можете узнать об этих способах, просмотрев документацию API встроенной библиотеки для Iterator. Некоторые из этих способов вызывают next в своём определении, поэтому вам необходимо выполнить способ next при выполнения особенности Iterator. Способы, вызывающие next, называются потребляющими переходниками , поскольку их вызов потребляет повторитель . Примером может служить способ sum, который забирает во владение повторитель и перебирает элементы, многократно вызывая next, тем самым потребляя повторитель . В этапе повторения он добавляет каждый элемент к текущей сумме и возвращает итоговое значение по завершении повторения. В приложении 13-13 приведён проверка, отображающий использование способа sum: Файл: src/lib.rs # #[cfg(test)]\n# mod tests { #[test] fn iterator_sum() { let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); let total: i32 = v1_iter.sum(); assert_eq!(total, 6); }\n# } Приложение 13-13: Вызов способа sum для получения суммы всех элементов в повторителе Мы не можем использовать v1_iter после вызова способа sum, потому что sum забирает во владение повторитель у которого вызван способ.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Обработка последовательности элементов с помощью повторителей » Способы, которые потребляют повторитель","id":"240","title":"Способы, которые потребляют повторитель"},"241":{"body":"Переходники повторителей - это способы, определённые для особенности Iterator, которые не потребляют повторитель . Вместо этого они создают различные повторители, изменяя некоторые особенности исходного повторителя. В приложении 13-14 показан пример вызова способа переходника повторителя map, который принимает замыкание и вызывает его для каждого элемента по мере повторения элементов. Способ map возвращает новый повторитель , который создаёт изменённые элементы. Замыкание здесь создаёт новый повторитель , в котором каждый элемент из вектора будет увеличен на 1: Файл: src/main.rs # fn main() { let v1: Vec = vec![1, 2, 3]; v1.iter().map(|x| x + 1);\n# } Приложение 13-14: Вызов переходника повторителя map для создания нового повторителя Однако этот код выдаёт предупреждение: $ cargo run Compiling iterators v0.1.0 (file:///projects/iterators)\nwarning: unused `Map` that must be used --> src/main.rs:4:5 |\n4 | v1.iter().map(|x| x + 1); | ^^^^^^^^^^^^^^^^^^^^^^^^ | = note: iterators are lazy and do nothing unless consumed = note: `#[warn(unused_must_use)]` on by default\nhelp: use `let _ = ...` to ignore the resulting value |\n4 | let _ = v1.iter().map(|x| x + 1); | +++++++ warning: `iterators` (bin \"iterators\") generated 1 warning Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s Running `target/debug/iterators` Код в приложении 13-14 ничего не делает; указанное нами замыкание никогда не вызывается. Предупреждение напоминает нам, почему: переходники повторителей ленивы, и здесь нам нужно потребить повторитель . Чтобы устранить это предупреждение и потребить повторитель , мы воспользуемся способом collect, который мы использовали в главе 12 с env::args в приложении 12-1. Этот способ потребляет повторитель и собирает полученные значения в собрание указанного вида. В приложении 13-15 мы собираем в вектор итоги перебора повторителя, который возвращается в итоге вызова map. Этот вектор в итоге будет содержать каждый элемент исходного вектора, увеличенный на 1. Файл: src/main.rs # fn main() { let v1: Vec = vec![1, 2, 3]; let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); assert_eq!(v2, vec![2, 3, 4]);\n# } Приложение 13-15: Вызов способа map для создания нового повторителя, а затем вызов способа collect для потребления нового повторителя и создания вектора Поскольку map принимает замыкание, мы можем указать любую действие, которую хотим выполнить над каждым элементом. Это отличный пример того, как замыкания позволяют задавать желаемое поведение, используя при этом особенности повторения, которые обеспечивает особенность Iterator. Вы можете выстроить цепочку из нескольких вызовов переходников повторителя для выполнения сложных действий в удобочитаемом виде. Но поскольку все повторители являются \"ленивыми\", для получения итогов вызовов переходников повторителя необходимо вызвать один из способов потребляющего переходника.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Обработка последовательности элементов с помощью повторителей » Способы, которые создают другие повторители","id":"241","title":"Способы, которые создают другие повторители"},"242":{"body":"Многие переходники повторителей принимают замыкания в качестве переменных, и обычно замыкания, которые мы будем указывать в качестве переменных переходникам повторителей, это замыкания, которые определяют (захватывают) своё окружение. В этом примере мы будем использовать способ filter, который принимает замыкание. Замыкание получает элемент из повторителя и возвращает bool. Если замыкание возвращает true, значение будет включено в повторение, создаваемую filter. Если замыкание возвращает false, значение не будет включено. В приложении 13-16 мы используем filter с замыканием, которое захватывает переменную shoe_size из своего окружения для повторения по собрания образцов устройства Shoe. Он будет возвращать обувь только указанного размера. Файл: src/lib.rs #[derive(PartialEq, Debug)]\nstruct Shoe { size: u32, style: String,\n} fn shoes_in_size(shoes: Vec, shoe_size: u32) -> Vec { shoes.into_iter().filter(|s| s.size == shoe_size).collect()\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn filters_by_size() { let shoes = vec![ Shoe { size: 10, style: String::from(\"sneaker\"), }, Shoe { size: 13, style: String::from(\"sandal\"), }, Shoe { size: 10, style: String::from(\"boot\"), }, ]; let in_my_size = shoes_in_size(shoes, 10); assert_eq!( in_my_size, vec![ Shoe { size: 10, style: String::from(\"sneaker\") }, Shoe { size: 10, style: String::from(\"boot\") }, ] ); }\n} Приложение 13-16. Использование способа filter с замыканием, определяющим shoe_size Функция shoes_in_size принимает в качестве свойств вектор с образцами обуви и размер обуви, а возвращает вектор, содержащий только обувь указанного размера. В теле shoes_in_my_size мы вызываем into_iter чтобы создать повторитель , который становится владельцем вектора. Затем мы вызываем filter, чтобы превратить этот повторитель в другой, который содержит только элементы, для которых замыкание возвращает true. Замыкание захватывает свойство shoe_size из окружения и сравнивает его с размером каждой пары обуви, оставляя только обувь указанного размера. Наконец, вызов collect собирает значения, возвращаемые приспособленным повторителем, в вектор, возвращаемый функцией. Проверка показывает, что когда мы вызываем shoes_in_my_size, мы возвращаем только туфли, размер которых совпадает с указанным нами значением.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Обработка последовательности элементов с помощью повторителей » Использование замыканий, которые захватывают переменные окружения","id":"242","title":"Использование замыканий, которые захватывают переменные окружения"},"243":{"body":"Вооружившись полученными знаниями об повторителях, мы можем улучшить выполнение работы с вводом/выводом в деле главы 12, применяя повторители для того, чтобы сделать некоторые места в коде более понятными и краткими. Давайте рассмотрим, как повторители могут улучшить нашу выполнение функции Config::build и функции search.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Улучшение нашего дела с вводом/выводом » Улучшение нашего дела с вводом/выводом","id":"243","title":"Улучшение нашего дела с вводом/выводом"},"244":{"body":"В приложении 12-6 мы добавили код, который принимает срез значений String и создаёт образец устройства Config путём упорядочевания среза и клонирования значений, позволяя устройстве Config владеть этими значениями. В приложении 13-17 мы воспроизвели выполнение функции Config::build, как это было в приложении 12-23: Файл: src/lib.rs # use std::env;\n# use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# pub ignore_case: bool,\n# }\n# impl Config { pub fn build(args: &[String]) -> Result { if args.len() < 3 { return Err(\"not enough arguments\"); } let query = args[1].clone(); let file_path = args[2].clone(); let ignore_case = env::var(\"IGNORE_CASE\").is_ok(); Ok(Config { query, file_path, ignore_case, }) }\n}\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # let results = if config.ignore_case {\n# search_case_insensitive(&config.query, &contents)\n# } else {\n# search(&config.query, &contents)\n# };\n# # for line in results {\n# println!(\"{line}\");\n# }\n# # Ok(())\n# }\n# # pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.contains(query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # pub fn search_case_insensitive<'a>(\n# query: &str,\n# contents: &'a str,\n# ) -> Vec<&'a str> {\n# let query = query.to_lowercase();\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.to_lowercase().contains(&query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn case_sensitive() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Duct tape.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# # #[test]\n# fn case_insensitive() {\n# let query = \"rUsT\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Trust me.\";\n# # assert_eq!(\n# vec![\"Rust:\", \"Trust me.\"],\n# search_case_insensitive(query, contents)\n# );\n# }\n# } Приложение 13-17: Репродукция функции Config::build из приложения 12-23 Ранее мы говорили, что не стоит беспокоиться о неэффективных вызовах clone, потому что мы удалим их в будущем. Ну что же, время пришло! Нам понадобился здесь clone, потому что в свойстве args у нас срез с элементами String, но функция build не владеет args. Чтобы образец Config владел значениями, нам пришлось клонировать их из args в переменные query и file_path. Благодаря нашим новым знаниям об повторителях мы можем изменить функцию build, чтобы вместо заимствования среза она принимала в качестве переменной повторитель . Мы будем использовать возможность повторителя вместо кода, который проверяет длину среза и обращается по порядковому указателю к определённым значениям. Это позволит лучше понять, что делает функция Config::build, поскольку повторитель будет обращаться к значениям. Как только Config::build получит в своё распоряжение повторитель и перестанет использовать действия упорядочевания с заимствованием, мы сможем переместить значения String из повторителя в Config вместо того, чтобы вызывать clone и создавать новое выделение памяти. Использование возвращённого повторителя напрямую Откройте файл src/main.rs дела ввода-вывода, который должен выглядеть следующим образом: Файл: src/main.rs # use std::env;\n# use std::process;\n# # use minigrep::Config;\n# fn main() { let args: Vec = env::args().collect(); let config = Config::build(&args).unwrap_or_else(|err| { eprintln!(\"Problem parsing arguments: {err}\"); process::exit(1); }); // --snip--\n# # if let Err(e) = minigrep::run(config) {\n# eprintln!(\"Application error: {e}\");\n# process::exit(1);\n# }\n} Сначала мы изменим начало функции main, которая была в приложении 12-24, на код в приложении 13-18, который теперь использует повторитель . Это не будет собираться, пока мы не обновим Config::build. Файл: src/main.rs # use std::env;\n# use std::process;\n# # use minigrep::Config;\n# fn main() { let config = Config::build(env::args()).unwrap_or_else(|err| { eprintln!(\"Problem parsing arguments: {err}\"); process::exit(1); }); // --snip--\n# # if let Err(e) = minigrep::run(config) {\n# eprintln!(\"Application error: {e}\");\n# process::exit(1);\n# }\n} Приложение 13-18: Передача возвращаемого значения из env::args в Config::build Функция env::args возвращает повторитель ! Вместо того чтобы собирать значения повторителя в вектор и затем передавать срез в Config::build, теперь мы передаём владение повторителем, возвращённым из env::args в Config::build напрямую. Далее нам нужно обновить определение Config::build. В файле src/lib.rs вашего дела ввода-вывода изменим ярлык Config::build так, чтобы она выглядела как в приложении 13-19. Это все ещё не собирается, потому что нам нужно обновить тело функции. Файл: src/lib.rs # use std::env;\n# use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# pub ignore_case: bool,\n# }\n# impl Config { pub fn build( mut args: impl Iterator, ) -> Result { // --snip--\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # let ignore_case = env::var(\"IGNORE_CASE\").is_ok();\n# # Ok(Config {\n# query,\n# file_path,\n# ignore_case,\n# })\n# }\n# }\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # let results = if config.ignore_case {\n# search_case_insensitive(&config.query, &contents)\n# } else {\n# search(&config.query, &contents)\n# };\n# # for line in results {\n# println!(\"{line}\");\n# }\n# # Ok(())\n# }\n# # pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.contains(query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # pub fn search_case_insensitive<'a>(\n# query: &str,\n# contents: &'a str,\n# ) -> Vec<&'a str> {\n# let query = query.to_lowercase();\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.to_lowercase().contains(&query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn case_sensitive() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Duct tape.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# # #[test]\n# fn case_insensitive() {\n# let query = \"rUsT\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Trust me.\";\n# # assert_eq!(\n# vec![\"Rust:\", \"Trust me.\"],\n# search_case_insensitive(query, contents)\n# );\n# }\n# } Приложение 13-19: Обновление ярлыки Config::build для определения повторителя как ожидаемого свойства Документация встроенной библиотеки для функции env::args показывает, что вид возвращаемого ею повторителя - std::env::Args, и этот вид выполняет признак Iterator и возвращает значения String. Мы обновили ярлык функции Config::build, чтобы свойство args имел гибкий вид ограниченный особенностью impl Iterator вместо &[String]. Такое использование правил написания impl Trait, который мы обсуждали в разделе \" Особенности как свойства\" главы 10, означает, что args может быть любым видом, выполняющим вид Iterator и возвращающим элементы String. Поскольку мы владеем args и будем изменять args в этапе повторения над ним, мы можем добавить ключевое слово mut в свод требований свойства args, чтобы сделать его изменяемым. Использование способов особенности Iterator вместо порядковых указателей Далее мы подправим содержимое Config::build. Поскольку args выполняет признак Iterator, мы знаем, что можем вызвать у него способ next! В приложении 13-20 код из приложения 12-23 обновлён для использования способа next: Файл: src/lib.rs # use std::env;\n# use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# pub ignore_case: bool,\n# }\n# impl Config { pub fn build( mut args: impl Iterator, ) -> Result { args.next(); let query = match args.next() { Some(arg) => arg, None => return Err(\"Didn't get a query string\"), }; let file_path = match args.next() { Some(arg) => arg, None => return Err(\"Didn't get a file path\"), }; let ignore_case = env::var(\"IGNORE_CASE\").is_ok(); Ok(Config { query, file_path, ignore_case, }) }\n}\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # let results = if config.ignore_case {\n# search_case_insensitive(&config.query, &contents)\n# } else {\n# search(&config.query, &contents)\n# };\n# # for line in results {\n# println!(\"{line}\");\n# }\n# # Ok(())\n# }\n# # pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.contains(query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # pub fn search_case_insensitive<'a>(\n# query: &str,\n# contents: &'a str,\n# ) -> Vec<&'a str> {\n# let query = query.to_lowercase();\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.to_lowercase().contains(&query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn case_sensitive() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Duct tape.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# # #[test]\n# fn case_insensitive() {\n# let query = \"rUsT\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Trust me.\";\n# # assert_eq!(\n# vec![\"Rust:\", \"Trust me.\"],\n# search_case_insensitive(query, contents)\n# );\n# }\n# } Приложение 13-20: Изменяем тело Config::build так, чтобы использовать способы повторителя Помните, что первое значение в возвращаемых данных env::args - это имя программы. Мы хотим пренебрегать его и перейти к следующему значению, поэтому сперва мы вызываем next и ничего не делаем с возвращаемым значением. Затем мы вызываем next, чтобы получить значение, которое мы хотим поместить в поле query в Config. Если next возвращает Some, мы используем match для извлечения значения. Если возвращается None, это означает, что было задано недостаточно переменных, и мы досрочно возвращаем значение Err. То же самое мы делаем для значения file_path.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Улучшение нашего дела с вводом/выводом » Удаляем clone, используем повторитель","id":"244","title":"Удаляем clone, используем повторитель"},"245":{"body":"Мы также можем воспользоваться преимуществами повторителей в функции search в нашем деле с действиеми ввода-вывода, которая воспроизведена здесь в приложении 13-21 так же, как и в приложении 12-19: Файл: src/lib.rs # use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# }\n# # impl Config {\n# pub fn build(args: &[String]) -> Result {\n# if args.len() < 3 {\n# return Err(\"not enough arguments\");\n# }\n# # let query = args[1].clone();\n# let file_path = args[2].clone();\n# # Ok(Config { query, file_path })\n# }\n# }\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # Ok(())\n# }\n# pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn one_result() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# } Приложение 13-21: Выполнение функции search из приложения 12-19 Мы можем написать этот код в более сжатом виде, используя способы переходника повторителя. Это также позволит нам избежать наличия изменяемого временного вектора results. Функциональный исполнение программирования предпочитает уменьшить количество изменяемого состояния, чтобы сделать код более понятным. Удаление изменяемого состояния может позволить в будущем сделать поиск одновременным, поскольку нам не придётся управлять одновременным доступом к вектору results. В приложении 13-22 показано это изменение: Файл: src/lib.rs # use std::env;\n# use std::error::Error;\n# use std::fs;\n# # pub struct Config {\n# pub query: String,\n# pub file_path: String,\n# pub ignore_case: bool,\n# }\n# # impl Config {\n# pub fn build(\n# mut args: impl Iterator,\n# ) -> Result {\n# args.next();\n# # let query = match args.next() {\n# Some(arg) => arg,\n# None => return Err(\"Didn't get a query string\"),\n# };\n# # let file_path = match args.next() {\n# Some(arg) => arg,\n# None => return Err(\"Didn't get a file path\"),\n# };\n# # let ignore_case = env::var(\"IGNORE_CASE\").is_ok();\n# # Ok(Config {\n# query,\n# file_path,\n# ignore_case,\n# })\n# }\n# }\n# # pub fn run(config: Config) -> Result<(), Box> {\n# let contents = fs::read_to_string(config.file_path)?;\n# # let results = if config.ignore_case {\n# search_case_insensitive(&config.query, &contents)\n# } else {\n# search(&config.query, &contents)\n# };\n# # for line in results {\n# println!(\"{line}\");\n# }\n# # Ok(())\n# }\n# pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { contents .lines() .filter(|line| line.contains(query)) .collect()\n}\n# # pub fn search_case_insensitive<'a>(\n# query: &str,\n# contents: &'a str,\n# ) -> Vec<&'a str> {\n# let query = query.to_lowercase();\n# let mut results = Vec::new();\n# # for line in contents.lines() {\n# if line.to_lowercase().contains(&query) {\n# results.push(line);\n# }\n# }\n# # results\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn case_sensitive() {\n# let query = \"duct\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Duct tape.\";\n# # assert_eq!(vec![\"safe, fast, productive.\"], search(query, contents));\n# }\n# # #[test]\n# fn case_insensitive() {\n# let query = \"rUsT\";\n# let contents = \"\\\n# Rust:\n# safe, fast, productive.\n# Pick three.\n# Trust me.\";\n# # assert_eq!(\n# vec![\"Rust:\", \"Trust me.\"],\n# search_case_insensitive(query, contents)\n# );\n# }\n# } Приложение 13-22: Использование способов переходника повторителя в выполнения функции search Напомним, что назначение функции search - вернуть все строки в contents, которые содержат query. Подобно примеру filter в приложении 13-16, этот код использует переходник filter, чтобы сохранить только те строки, для которых line.contains(query) возвращает true. Затем мы собираем совпадающие строки в другой вектор с помощью collect. Так гораздо проще! Не стесняйтесь сделать такое же изменение для использования способов повторителя в функции search_case_insensitive.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Улучшение нашего дела с вводом/выводом » Делаем код понятнее с помощью переходников повторителей","id":"245","title":"Делаем код понятнее с помощью переходников повторителей"},"246":{"body":"Следующий логичный вопрос - какой исполнение вы должны выбрать в своём коде и почему: подлинную выполнение в приложении 13-21 или исполнение с использованием повторителей в приложении 13-22. Большинство программистов на языке Ржавчина предпочитают использовать исполнение повторителей. Сначала разобраться с ним немного сложно, но как только вы почувствуете, что такое различные переходники повторителей и что они делают, понять повторители станет проще. Вместо того чтобы возиться с различными элементами цикла и создавать новые векторы, код сосредотачивается на высокоуровневой цели цикла. Это абстрагирует часть обычного кода, поэтому легче увидеть подходы, единственные для этого кода, такие как условие выборки, которое должен пройти каждый элемент в повторителе. Но действительно ли эти две выполнения эквивалентны? Интуитивно можно предположить, что более низкоуровневый цикл будет быстрее. Давайте поговорим о производительности.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Улучшение нашего дела с вводом/выводом » Выбор между циклами или повторителями","id":"246","title":"Выбор между циклами или повторителями"},"247":{"body":"Чтобы определить, что лучше использовать циклы или повторители, нужно знать, какая выполнение быстрее: исполнение функции search с явным циклом for или исполнение с повторителями. Мы выполнили проверка производительности, разместив всё содержимое книги (“The Adventures of Sherlock Holmes” by Sir Arthur Conan Doyle) в строку вида String и поискали слово the в её содержимом. Вот итоги проверки функции search с использованием цикла for и с использованием повторителей: test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)\ntest bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200) Исполнение с использованием повторителей была немного быстрее! Мы не будем приводить здесь непосредственно код проверки, поскольку мысль не в том, чтобы доказать, что решения в точности эквивалентны, а в том, чтобы получить общее представление о том, как эти две выполнения близки по производительности. Для более исчерпывающего проверки, вам нужно проверить различные тексты разных размеров в качестве содержимого для contents, разные слова и слова различной длины в качестве query и всевозможные другие исходы. Дело в том, что повторители, будучи высокоуровневой абстракцией, собираются примерно в тот же код, как если бы вы написали его низкоуровневый исход самостоятельно. Повторители - это одна из абстракций с нулевой стоимостью ( zero-cost abstractions ) в Rust, под которой мы подразумеваем, что использование абстракции не накладывает дополнительных расходов во время выполнения. Подобно тому, как Бьёрн Страуструп, внешнем видер и разработчик C++, определяет нулевые накладные расходы ( zero-overhead ) в книге “Foundations of C++” (2012): В целом, выполнение C++ подчиняется принципу отсутствия накладных расходов: за то, чем вы не пользуетесь, платить не нужно. И далее: тот код, что вы используете, нельзя сделать ещё лучше. В качестве другого примера приведём код, взятый из аудио декодера. Алгоритм декодирования использует математическую действие линейного предсказания для оценки будущих значений на основе линейной функции предыдущих выборок. Код использует соединение вызовов повторителя для выполнения математических вычислений для трёх переменных в области видимости: срез данных buffer, массив из 12 коэффициентов coefficients и число для сдвига данных в переменной qlp_shift. Переменные определены в примере, но не имеют начальных значений. Хотя этот код не имеет большого значения вне среды, он является кратким, существующим примером того, как Ржавчина переводит мысли высокого уровня в код низкого уровня. let buffer: &mut [i32];\nlet coefficients: [i64; 12];\nlet qlp_shift: i16; for i in 12..buffer.len() { let prediction = coefficients.iter() .zip(&buffer[i - 12..i]) .map(|(&c, &s)| c * s as i64) .sum::() >> qlp_shift; let delta = buffer[i]; buffer[i] = prediction as i32 + delta;\n} Чтобы вычислить значение переменной prediction, этот код перебирает каждое из 12 значений в переменной coefficients и использует способ zip для объединения значений коэффициентов с предыдущими 12 значениями в переменной buffer. Затем, для каждой пары мы перемножаем значения, суммируем все итоги и у суммы сдвигаем биты вправо в переменную qlp_shift. Для вычислений в таких приложениях, как аудио декодеры, часто требуется производительность. Здесь мы создаём повторитель , используя два переходника, впоследствии потребляющих значение. В какой ассемблерный код будет собираться этот код на Rust? На мгновение написания этой главы он собирается в то же самое, что вы написали бы руками. Не существует цикла, соответствующего повторения по значениям в «коэффициентах»coefficients: Ржавчина знает, что существует двенадцать повторений, поэтому он «разворачивает» цикл. Разворачивание - это улучшение, которая устраняет издержки кода управления циклом и вместо этого порождает повторяющийся код для каждой повторения цикла. Все коэффициенты сохраняются в регистрах, что означает очень быстрый доступ к значениям. Нет никаких проверок границ доступа к массиву во время выполнения. Все эти переработки, которые может применить Rust, делают полученный код чрезвычайно эффективным. Теперь, когда вы это знаете, используйте повторители и замыкания без страха! Они представляют код в более высокоуровневом виде, но без потери производительности во время выполнения.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Сравнение производительности: циклы и повторители » Сравнение производительности циклов и повторителей","id":"247","title":"Сравнение производительности циклов и повторителей"},"248":{"body":"Замыкания (closures) и повторители (iterators) это возможности Rust, вдохновлённые мыслями полезных языков. Они позволяют Ржавчина ясно выражать мысли высокого уровня с производительностью низкоуровневого кода. Выполнения замыканий и повторителей таковы, что нет влияния на производительность выполнения кода. Это одна из целей Rust, направленных на обеспечение абстракций с нулевой стоимостью (zero-cost abstractions). Теперь, когда мы улучшили представление кода в нашем деле, рассмотрим некоторые возможности, которые нам предоставляет cargo для обнародования нашего кода в хранилища.","breadcrumbs":"Полезные возможности языка: повторители и замыкания » Сравнение производительности: циклы и повторители » Итоги","id":"248","title":"Итоги"},"249":{"body":"До сих пор мы использовали только самые основные возможности Cargo для сборки, запуска и проверки нашего кода, но он может гораздо больше. В этой главе мы обсудим некоторые другие, более продвинутые возможности, чтобы показать вам, как делать следующее: Настройка сборки с помощью готовых профилей Обнародование библиотеки на crates.io Управление крупными делами с помощью рабочих пространств Установка двоичных файлов с crates.io Расширение возможностей Cargo с помощью возможности добавления собственных приказов Cargo может делать значительно больше того, что мы рассмотрим в этой главе, полное описание всех его функций см. в документации .","breadcrumbs":"Подробнее о Cargo и Crates.io » Больше о Cargo и Crates.io","id":"249","title":"Больше о Cargo и Crates.io"},"25":{"body":"Cargo - это система сборки и управленец дополнений Rust. Большая часть разработчиков используют данный средство для управления делами, потому что Cargo выполняет за вас множество задач, таких как сборка кода, загрузка библиотек, от которых зависит ваш код, и создание этих библиотек. (Мы называем библиотеки, которые нужны вашему коду, зависимостями .) Самые простые программы на Rust, подобные той, которую мы написали, не имеют никаких зависимостей. Если бы мы сделали дело «Hello, world!» с Cargo, он бы использовал только ту часть Cargo, которая отвечает за сборку вашего кода. По мере написания более сложных программ на Ржавчина вы будете добавлять зависимости, а если вы начнёте дело с использованием Cargo, добавлять зависимости станет намного проще. Поскольку значительное число дел Ржавчина используют Cargo, оставшаяся часть книги подразумевает, что вы тоже используете Cargo. Cargo входит в состав поставки Rust, если вы использовали напрямую от разрабочиков программы установки, рассмотренные в разделе \"Установка\" . Если вы установили Ржавчина другим способом, проверьте, установлен ли Cargo, введя в окне вызова следующее: $ cargo --version Если приказ выдал номер исполнения, то значит Cargo установлен. Если вы видите ошибку, вроде command not found (\"приказ не найдена\"), загляните в документацию для использованного вами способа установки, чтобы выполнить установку Cargo отдельно.","breadcrumbs":"С чего начать » Hello, Cargo! » Привет, Cargo!","id":"25","title":"Привет, Cargo!"},"250":{"body":"В Ржавчина профили выпуска — это предопределённые и настраиваемые профили с различными настройками, которые позволяют программисту лучше управлять различные свойства сборки кода. Каждый профиль настраивается независимо от других. Cargo имеет два основных профиля: профиль dev, используемый Cargo при запуске cargo build, и профиль release, используемый Cargo при запуске cargo build --release. Профиль dev определён со значениями по умолчанию для разработки, а профиль release имеет значения по умолчанию для сборок в исполнение. Эти имена профилей могут быть знакомы по итогам ваших сборок: $ cargo build Finished dev [unoptimized + debuginfo] target(s) in 0.0s\n$ cargo build --release Finished release [optimized] target(s) in 0.0s dev и release — это разные профили, используемые сборщиком. Cargo содержит настройки по умолчанию для каждого профиля, которые применяются, если вы явно не указали разделы [profile.*] в файле дела Cargo.toml . Добавляя разделы [profile.*] для любого профиля, который вы хотите настроить, вы переопределяете любое подмножество свойств по умолчанию. Например, вот значения по умолчанию для свойства opt-level для профилей dev и release: Файл: Cargo.toml [profile.dev]\nopt-level = 0 [profile.release]\nopt-level = 3 Свойство opt-level управляет количеством переработок, которые Ржавчина будет применять к вашему коду, в ряде от 0 до 3. Использование большего количества переработок увеличивает время сборки, поэтому если вы находитесь в этапе разработки и часто собираете свой код, целесообразно использовать меньшее количество переработок, чтобы сборка происходила быстрее, даже если в итоге код будет работать медленнее. Поэтому opt-level по умолчанию для dev установлен в 0. Когда вы готовы обнародовать свой код, то лучше потратить больше времени на сборку. Вы собираете программу в режиме исполнения только один раз, но выполняться она будет многократно, так что использование режима исполнения позволяет увеличить скорость выполнения кода за счёт времени сборки. Вот почему по умолчанию opt-level для профиля release равен 3. Вы можете переопределить настройки по умолчанию, добавив другое значение для них в Cargo.toml . Например, если мы хотим использовать уровень переработки 1 в профиле разработки, мы можем добавить эти две строки в файл Cargo.toml нашего дела: Файл: Cargo.toml [profile.dev]\nopt-level = 1 Этот код переопределяет настройку по умолчанию 0. Теперь, когда мы запустим cargo build, Cargo будет использовать значения по умолчанию для профиля dev плюс нашу настройку для opt-level. Поскольку мы установили для opt-level значение 1, Cargo будет применять больше переработок, чем было задано по умолчанию, но не так много, как при сборке исполнения. Полный список свойств настройке и значений по умолчанию для каждого профиля вы можете найти в документации Cargo .","breadcrumbs":"Подробнее о Cargo и Crates.io » Настройка билдов с помощью профилей выпуска » Настройка сборок с профилями исполнений","id":"250","title":"Настройка сборок с профилями исполнений"},"251":{"body":"Мы использовали дополнения из crates.io в качестве зависимостей нашего дела, но вы также можете поделиться своим кодом с другими людьми, обнародовав свои собственные дополнения. Реестр библиотек по адресу crates.io распространяет исходный код ваших дополнений, поэтому он в основном размещает код с открытым исходным кодом. В Ржавчина и Cargo есть функции, которые облегчают поиск и использование обнародованного дополнения. Далее мы поговорим о некоторых из этих функций, а затем объясним, как обнародовать дополнение.","breadcrumbs":"Подробнее о Cargo и Crates.io » Обнародование ящика на Crates.io » Обнародование библиотеки в Crates.io","id":"251","title":"Обнародование библиотеки в Crates.io"},"252":{"body":"Правильноное документирование ваших дополнений поможет другим пользователям знать, как и когда их использовать, поэтому стоит потратить время на написание документации. В главе 3 мы обсуждали, как вносить примечания в код Rust, используя две косые черты, //. В Ржавчина также есть особый вид примечаниев к документации, который обычно называется примечанием к документации , который порождает документацию HTML. HTML-код отображает содержимое примечаниев к документации для открытых элементов API, предназначенных для программистов, увлеченных в знании того, как использовать вашу библиотеку, в отличие от того, как она выполнена . Примечания к документации используют три слеша, /// вместо двух и поддерживают наставление Markdown для изменения текста. Размещайте примечания к документации непосредственно перед элементом, который они документируют. В приложении 14-1 показаны примечания к документации для функции add_one в библиотеке с именем my_crate: Файл: src/lib.rs /// Adds one to the number given.\n///\n/// # Examples\n///\n/// ```\n/// let arg = 5;\n/// let answer = my_crate::add_one(arg);\n///\n/// assert_eq!(6, answer);\n/// ```\npub fn add_one(x: i32) -> i32 { x + 1\n} Приложение 14-1: Примечание к документации для функции Здесь мы даём описание того, что делает функция add_one, начинаем раздел с заголовка Examples, а затем предоставляем код, который отображает, как использовать функцию add_one. Мы можем создать документацию HTML из этого примечания к документации, запустив cargo doc. Этот приказ запускает средство rustdoc, поставляемый с Rust, и помещает созданную HTML-документацию в папка target/doc . Для удобства, запустив cargo doc --open, мы создадим HTML для документации вашей текущей библиотеки (а также документацию для всех зависимостей вашей библиотеки) и откроем итог в веб-браузере. Перейдите к функции add_one и вы увидите, как отображается текст в примечаниях к документации, что показано на рисунке 14-1: Рисунок 14-1: HTML документация для функции add_one Часто используемые разделы Мы использовали Markdown заголовок # Examples в приложении 14-1 для создания раздела в HTML с заголовком \"Examples\". Вот некоторые другие разделы, которые авторы библиотек обычно используют в своей документации: Panics : Сценарии, в которых документированная функция может вызывать панику. Вызывающие функцию, которые не хотят, чтобы их программы паниковали, должны убедиться, что они не вызывают функцию в этих случаейх. Ошибки : Если функция возвращает Result, описание видов ошибок, которые могут произойти и какие условия могут привести к тому, что эти ошибки могут быть возвращены, может быть полезным для вызывающих, так что они могут написать код для обработки различных видов ошибок разными способами. Безопасность : Если функция является unsafe для вызова (мы обсуждаем безопасность в главе 19), должен быть раздел, объясняющий, почему функция небезопасна и охватывающий неизменные величины, которые функция ожидает от вызывающих сторон. В подавляющем большинстве случаев примечания к документации не нуждаются во всех этих разделах, но это хорошая подсказка, напоминающая вам о тех особенностях вашего кода, о которых пользователям будет важно узнать. Примечания к документации как проверки Добавление примеров кода в примечания к документации может помочь отобразить, как использовать вашу библиотеку, и это даёт дополнительный бонус: запуск cargo test запустит примеры кода в вашей документации как проверки! Нет ничего лучше, чем документация с примерами. Но нет ничего хуже, чем примеры, которые не работают, потому что код изменился с особенности написания документации. Если мы запустим cargo test с документацией для функции add_one из приложения 14-1, мы увидим раздел итогов проверки, подобный этому: Doc-tests my_crate running 1 test\ntest src/lib.rs - add_one (line 5) ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s Теперь, если мы изменим либо функцию, либо пример, так что assert_eq! в примере паникует, и снова запустим cargo test, мы увидим, что проверки документации обнаруживают, что пример и код не согласованы друг с другом! Указание примечаний содержащихся элементов Исполнение примечаниев к документам //! добавляет документацию к элементу, содержащему примечания, а не к элементам, следующим за примечаниями. Обычно мы используем эти примечания внутри корневого файла ящика (по соглашению src/lib.rs ) или внутри звена для документирования ящика или звена в целом. Например, чтобы добавить документацию, описывающую назначение my_crate , содержащего функцию add_one , мы добавляем примечания к документации, начинающиеся с //! в начало файла src/lib.rs , как показано в приложении 14-2: Файл: src/lib.rs //! # My Crate\n//!\n//! `my_crate` is a collection of utilities to make performing certain\n//! calculations more convenient. /// Adds one to the number given.\n// --snip--\n# ///\n# /// # Examples\n# ///\n# /// ```\n# /// let arg = 5;\n# /// let answer = my_crate::add_one(arg);\n# ///\n# /// assert_eq!(6, answer);\n# /// ```\n# pub fn add_one(x: i32) -> i32 {\n# x + 1\n# } Приложение 14-2: Документация для ящика my_crate в целом Обратите внимание, что после последней строки, начинающейся с //!, нет никакого кода. Поскольку мы начали примечания с //! вместо ///, мы документируем элемент, который содержит этот примечание, а не элемент, который следует за этим примечанием. В данном случае таким элементом является файл src/lib.rs , который является корнем crate. Эти примечания описывают весь ящик. Когда мы запускаем cargo doc --open, эти примечания будут отображаться на первой странице документации для my_crate над списком открытых элементов в библиотеке, как показано на рисунке 14-2: Рисунок 14-2: Предоставленная документация для my_crate, включая примечание, описывающие ящик в целом Примечания к документации внутри элементов полезны для описания ящиков и звеньев особенно. Используйте их, чтобы объяснить общую цель дополнения, чтобы помочь вашим пользователям понять устройство ящика.","breadcrumbs":"Подробнее о Cargo и Crates.io » Обнародование ящика на Crates.io » Создание полезных примечаниев к документации","id":"252","title":"Создание полезных примечаниев к документации"},"253":{"body":"Устройства вашего открытого API является основным обстоятельством при обнародования ящика. Люди, которые используют вашу библиотеку, менее знакомы со устройством, чем вы и могут столкнуться с трудностями при поиске частей, которые они хотят использовать, если ваша библиотека имеет большую упорядочевание звеньев. В главе 7 мы рассмотрели, как сделать элементы общедоступными с помощью ключевого слова pub и ввести элементы в область видимости с помощью ключевого слова use. Однако устройства, которая имеет смысл для вас при разработке ящика, может быть не очень удобной для пользователей. Вы можете согласовать устройство в виде упорядочевания с несколькими уровнями, но тогда люди, желающие использовать вид, который вы определили в глубине упорядочевания, могут столкнуться с неполадкой его поиска. Их также может раздражать необходимость вводить use my_crate::some_module::another_module::UsefulType; вместо use my_crate::UsefulType;. Хорошей новостью является то, что если устройства не удобна для использования другими из другой библиотеки, вам не нужно перестраивать внутреннюю устройство: вместо этого вы можете реэкспортировать элементы, чтобы сделать открытую устройство, отличную от вашей внутренней устройства, используя pub use. Реэкспорт берет открытый элемент в одном месте и делает его открытым в другом месте, как если бы он был определён в другом месте. Например, скажем, мы создали библиотеку с именем art для расчетов художественных подходов. Внутри этой библиотеки есть два звена: звено kinds содержащий два перечисления с именами PrimaryColor и SecondaryColor и звено utils, содержащий функцию с именем mix, как показано в приложении 14-3: Файл: src/lib.rs //! # Art\n//!\n//! A library for modeling artistic concepts. pub mod kinds { /// The primary colors according to the RYB color model. pub enum PrimaryColor { Red, Yellow, Blue, } /// The secondary colors according to the RYB color model. pub enum SecondaryColor { Orange, Green, Purple, }\n} pub mod utils { use crate::kinds::*; /// Combines two primary colors in equal amounts to create /// a secondary color. pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor { // --snip--\n# unimplemented!(); }\n} Приложение 14-3: Библиотека art с элементами, согласованными в звенья kinds и utils На рисунке 14-3 показано, как будет выглядеть титульная страница документации для этого ящика, созданный cargo doc: Рисунок 14-3: Первая страница документации для art, в которой перечислены звенья kinds и utils Обратите внимание, что виды PrimaryColor и SecondaryColor не указаны на главной странице, равно как и функция mix. Мы должны нажать kinds и utils, чтобы увидеть их. В другой библиотеке, которая зависит от этой библиотеки, потребуются операторы use, которые подключают элементы из art в область видимости, определяя устройство звена, которая определена в данный мгновение. В приложении 14-4 показан пример ящика, в котором используются элементы PrimaryColor и mix из ящика art: Файл: src/main.rs use art::kinds::PrimaryColor;\nuse art::utils::mix; fn main() { let red = PrimaryColor::Red; let yellow = PrimaryColor::Yellow; mix(red, yellow);\n} Приложение 14-4: Ящик использующий элементы из ящика art с экспортированной внутренней устройством Автору кода в приложении 14-4, который использует ящик art, пришлось выяснить, что PrimaryColor находится в звене kinds, а mix - в звене utils. Устройства звена art ящика больше подходит для разработчиков, работающих над art ящиком, чем для тех, кто его использует. Внутренняя устройства не содержит никакой полезной сведений для того, кто пытается понять, как использовать ящик art, а скорее вызывает путаницу, поскольку разработчики, использующие его, должны понять, где искать, и должны указывать имена звеньев в выражениях use. Чтобы удалить внутреннюю устройство из общедоступного API, мы можем изменить код ящика art в приложении 14-3, чтобы добавить операторы pub use для повторного реэкспорта элементов на верхнем уровне, как показано в приложении 14-5: Файл: src/lib.rs //! # Art\n//!\n//! A library for modeling artistic concepts. pub use self::kinds::PrimaryColor;\npub use self::kinds::SecondaryColor;\npub use self::utils::mix; pub mod kinds { // --snip--\n# /// The primary colors according to the RYB color model.\n# pub enum PrimaryColor {\n# Red,\n# Yellow,\n# Blue,\n# }\n# # /// The secondary colors according to the RYB color model.\n# pub enum SecondaryColor {\n# Orange,\n# Green,\n# Purple,\n# }\n} pub mod utils { // --snip--\n# use crate::kinds::*;\n# # /// Combines two primary colors in equal amounts to create\n# /// a secondary color.\n# pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {\n# SecondaryColor::Orange\n# }\n} Приложение 14-5: Добавление операторов pub use для реэкспорта элементов Документация API, которую cargo doc порождает для этой библиотеки, теперь будет перечислять и связывать реэкспорты на главной странице, как показано на рисунке 14-4, упрощая поиск видов PrimaryColor, SecondaryColor и функции mix. Рисунок 14-4: Первая страница документации для art, которая перечисляет реэкспорт Пользователи ящика art могут по-прежнему видеть и использовать внутреннюю устройство из приложения 14-3, как показано в приложении 14-4, или они могут использовать более удобную устройство в приложении 14-5, как показано в приложении 14-6: Файл: src/main.rs use art::mix;\nuse art::PrimaryColor; fn main() { // --snip--\n# let red = PrimaryColor::Red;\n# let yellow = PrimaryColor::Yellow;\n# mix(red, yellow);\n} Приложение 14-6: Программа, использующая реэкспортированные элементы из ящика art В случаях, когда имеется много вложенных звеньев, реэкспорт видов на верхнем уровне с помощью pub use может существенно повысить удобство работы для людей, использующих ящик. Ещё одно распространённое использование pub use - это реэкспорт определений зависимого звена в текущем ящике, чтобы сделать определения этого ящика частью открытого API вашего ящика. Создание полезной открытой устройства API - это больше искусство чем наука, и вы можете повторять, чтобы найти API, который лучше всего подойдёт вашим пользователям. Использование pub use даёт вам гибкость в том, как вы внутренне выстраиваете свою библиотеку внутри и отделяете эту внутреннюю устройство от того, что вы предоставляете пользователям. Посмотрите на код некоторых установленных ящиков, чтобы увидеть отличается ли их внутренняя устройства от их открытого API.","breadcrumbs":"Подробнее о Cargo и Crates.io » Обнародование ящика на Crates.io » Экспорт удобного общедоступного API с pub use","id":"253","title":"Экспорт удобного общедоступного API с pub use"},"254":{"body":"Прежде чем вы сможете обнародовать любые библиотеки, вам необходимо создать учётную запись на crates.io и получить API токен. Для этого зайдите на домашнюю страницу crates.io и войдите в систему через учётную запись GitHub. (В настоящее время требуется наличие учётной записи GitHub, но сайт может поддерживать другие способы создания учётной записи в будущем.) Сразу после входа в систему перейдите в настройки своей учётной записи по адресу https://crates.io/me/ и получите свой ключ API. Затем выполните приказ cargo login с вашим ключом API, например: $ cargo login abcdefghijklmnopqrstuvwxyz012345 Этот приказ сообщит Cargo о вашем API token и сохранит его местно в ~/.cargo/credentials . Обратите внимание, что этот токен является тайным : не делитесь им ни с кем другим. Если вы по какой-либо причине поделитесь им с кем-либо, вы должны отозвать его и создать новый токен на crates.io .","breadcrumbs":"Подробнее о Cargo и Crates.io » Обнародование ящика на Crates.io » Настройка учётной записи Crates.io","id":"254","title":"Настройка учётной записи Crates.io"},"255":{"body":"Допустим, у вас есть ящик, который вы хотите обнародовать. Перед обнародованием вам нужно добавить некоторые метаданные в раздел [package] файла Cargo.toml ящика. Вашему ящику понадобится не повторяющееся имя. Пока вы работаете над ящиком местно, вы можете назвать его как угодно. Однако названия ящиков на crates.io определятся в мгновение первой обнародования. Как только ящику присвоено название, никто другой не сможет обнародовать ящик с таким же именем. Перед тем как обнародовать ящик, поищите название, которое вы хотите использовать. Если такое имя уже используется, вам придётся подобрать другое и отредактировать поле name в файле Cargo.toml в разделе [package], чтобы использовать новое имя в качестве размещаяемого, например, так: Файл: Cargo.toml [package]\nname = \"guessing_game\" Даже если вы выбрали не повторяющееся имя, когда вы запустите cargo publish чтобы обнародовать ящик, вы получите предупреждение, а затем ошибку: $ cargo publish Updating crates.io index\nwarning: manifest has no description, license, license-file, documentation, homepage or repository.\nSee https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.\n--snip--\nerror: failed to publish to registry at https://crates.io Caused by: the remote server responded with an error: missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata Это ошибка, потому что вам не хватает важной сведений: необходимы описание и лицензия, чтобы люди знали, что делает ваш ящик и на каких условиях они могут его использовать. В поле Cargo.toml добавьте описание, состоящее из одного-двух предложений, поскольку оно будет появляться вместе с вашим ящиком в итогах поиска. Для поля license нужно указать значение определителя лицензии . В Linux Foundation's Software Package Data Exchange (SPDX) перечислены определители, которые можно использовать для этого значения. Например, чтобы указать, что вы лицензировали свой crate, используя лицензию MIT, добавьте определитель MIT: Файл: Cargo.toml [package]\nname = \"guessing_game\"\nlicense = \"MIT\" Если вы хотите использовать лицензию, которая отсутствует в SPDX, вам нужно поместить текст этой лицензии в файл, включите файл в свой дело, а затем используйте license-file, чтобы указать имя этого файла вместо использования ключа license. Руководство по выбору лицензии для вашего дела выходит за рамки этой книги. Многие люди в сообществе Ржавчина лицензируют свои дела так же, как и Rust, используя двойную лицензию MIT OR Apache 2.0. Эта применение отображает, что вы также можете указать несколько определителей лицензий, разделённых OR, чтобы иметь несколько лицензий для вашего дела. С добавлением единственного имени, исполнения, вашего описания и лицензии, файл Cargo.toml для дела, который готов к обнародования может выглядеть следующим образом: Файл: Cargo.toml [package]\nname = \"guessing_game\"\nversion = \"0.1.0\"\nedition = \"2021\"\ndescription = \"A fun game where you guess what number the computer has chosen.\"\nlicense = \"MIT OR Apache-2.0\" [dependencies] Документация Cargo описывает другие метаданные, которые вы можете указать, чтобы другие могли легче находить и использовать ваш ящик.","breadcrumbs":"Подробнее о Cargo и Crates.io » Обнародование ящика на Crates.io » Добавление метаданных в новую библиотеку","id":"255","title":"Добавление метаданных в новую библиотеку"},"256":{"body":"Теперь, когда вы создали учётную запись, сохранили свой токен API, выбрали имя для своего ящика и указали необходимые метаданные, вы готовы к обнародования! Обнародование библиотеки загружает определённую исполнение в crates.io для использования другими. Будьте осторожны, потому что обнародование является перманентной действием. Исполнение никогда не сможет быть перезаписана, а код не подлежит удалению. Одна из основных целей crates.io - служить постоянным архивом кода, чтобы сборки всех дел, зависящих от crates из crates.io продолжали работать. Предоставление возможности удаления исполнений сделало бы выполнение этой цели невозможным. При этом количество исполнений ящиков, которые вы можете обнародовать, не ограничено. Запустите приказ cargo publish ещё раз. Сейчас эта приказ должна выполниться успешно: $ cargo publish Updating crates.io index Packaging guessing_game v0.1.0 (file:///projects/guessing_game) Verifying guessing_game v0.1.0 (file:///projects/guessing_game) Compiling guessing_game v0.1.0\n(file:///projects/guessing_game/target/package/guessing_game-0.1.0) Finished dev [unoptimized + debuginfo] target(s) in 0.19s Uploading guessing_game v0.1.0 (file:///projects/guessing_game) Поздравляем! Теперь вы поделились своим кодом с сообществом Ржавчина и любой может легко добавить вашу библиотеку в качестве зависимости их дела.","breadcrumbs":"Подробнее о Cargo и Crates.io » Обнародование ящика на Crates.io » Обнародование на Crates.io","id":"256","title":"Обнародование на Crates.io"},"257":{"body":"Когда вы внесли изменения в свой ящик и готовы выпустить новую исполнение, измените значение version, указанное в вашем файле Cargo.toml и повторите размещение. Воспользуйтесь Semantic Versioning rules , чтобы решить, какой номер следующей исполнения подходит для ваших изменений. Затем запустите cargo publish, чтобы загрузить новую исполнение.","breadcrumbs":"Подробнее о Cargo и Crates.io » Обнародование ящика на Crates.io » Обнародование новой исполнения существующей библиотеки","id":"257","title":"Обнародование новой исполнения существующей библиотеки"},"258":{"body":"Хотя вы не можете удалить предыдущие исполнения ящика, вы можете помешать любым будущим делам добавлять его в качестве новой зависимости. Это полезно, когда исполнение ящика сломана по той или иной причине. В таких случаейх Cargo поддерживает выламывание (yanking) исполнения ящика. Вычёркивание исполнения не позволяет новым делам зависеть от этой исполнения, но при этом позволяет всем существующим делам, зависящим от неё, продолжать работу. По сути, исключение означает, что все дела с Cargo.lock не сломаются, а любые файлы Cargo.lock , которые будут порождаться в будущем, не смогут использовать исключённую исполнение. Чтобы вычеркнуть исполнение ящика, в папки ящика, который вы обнародовали ранее, выполните приказ cargo yank и укажите, какую исполнение вы хотите вычеркнуть. Например, если мы обнародовали ящик под названием guessing_game исполнения 1.0.1 и хотим вычеркнуть её, в папке дела для guessing_game мы выполним: $ cargo yank --vers 1.0.1 Updating crates.io index Yank guessing_game@1.0.1 Добавив в приказ --undo, вы также можете отменить выламывание и разрешить делам начать зависеть от исполнения снова: $ cargo yank --vers 1.0.1 --undo Updating crates.io index Unyank guessing_game@1.0.1 Вычёркивание не удаляет код. Оно не может, например, удалить случайно загруженные пароли. Если это произойдёт, вы должны немедленно сбросить эти пароли.","breadcrumbs":"Подробнее о Cargo и Crates.io » Обнародование ящика на Crates.io » Устранение устаревших исполнений с Crates.io с помощью cargo yank","id":"258","title":"Устранение устаревших исполнений с Crates.io с помощью cargo yank"},"259":{"body":"В главе 12 мы создали дополнение, который включал в себя двоичный и библиотечный ящики. По мере развития вашего дела может возникнуть случаей, когда библиотечный ящик будет становиться все больше, и вы захотите разделить ваш дополнение на несколько библиотечных ящиков. Cargo предоставляет возможность под названием workspaces , которая помогает управлять несколькими взаимосвязанными дополнениями, которые разрабатываются в тандеме.","breadcrumbs":"Подробнее о Cargo и Crates.io » Рабочие области Cargo » Рабочие пространства Cargo","id":"259","title":"Рабочие пространства Cargo"},"26":{"body":"Давайте создадим новый дело с помощью Cargo и посмотрим, как он отличается от нашего начального дела \"Hello, world!\". Перейдите обратно в папку projects (или любую другую, где вы решили сохранять код). Затем, в любой операционной системе, запустите приказ: $ cargo new hello_cargo\n$ cd hello_cargo Первая приказ создаёт новый папка и дело с именем hello_cargo . Мы назвали наш дело hello_cargo , и Cargo создаёт свои файлы в папке с тем же именем. Перейдём в папка hello_cargo и посмотрим файлы. Увидим, что Cargo создал два файла и одну папку: файл Cargo.toml и папка src с файлом main.rs внутри. Кроме того, cargo объявлял новый хранилище Git вместе с файлом .gitignore . Файлы Git не будут созданы, если вы запустите cargo new в существующем хранилища Git; вы можете изменить это поведение, используя cargo new --vcs=git. Примечание. Git — это распространённая система управления исполнений. Вы можете изменить cargo new, чтобы использовать другую систему управления исполнений или не использовать систему управления исполнений, используя флаг --vcs. Запустите cargo new --help, чтобы увидеть доступные свойства. Откройте файл Cargo.toml в любом текстовом редакторе. Он должен выглядеть как код в приложении 1-2. Файл: Cargo.toml [package]\nname = \"hello_cargo\"\nversion = \"0.1.0\"\nedition = \"2021\" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] Приложение 1-2: Содержимое файла Cargo.toml, созданное приказом cargo new Это файл в виде TOML ( Tom’s Obvious, Minimal Language ), который является видом настроек Cargo. Первая строка, [package], является заголовочной разделом, которая указывает что следующие указания настраивают дополнение. По мере добавления больше сведений в данный файл, будет добавляться больше разделов и указаний (строк). Следующие три строки задают сведения о настройке, необходимую Cargo для сборки вашей программы: имя, исполнение и издание Rust, который будет использоваться. Мы поговорим о ключе edition в Приложении E . Последняя строка, [dependencies] является началом разделы для списка любых зависимостей вашего дела. В Rust, это внешние дополнения кода, на которые ссылаются ключевым словом crate . Нам не нужны никакие зависимости в данном деле, но мы будем использовать их в первом деле главы 2, так что нам пригодится данная раздел зависимостей потом. Откройте файл src/main.rs и загляните в него: Файл: src/main.rs fn main() { println!(\"Hello, world!\");\n} Cargo создал для вас программу \"Hello, world!\", подобную той, которую мы написали в Приложении 1-1! Пока что различия между нашим предыдущим делом и делом, созданным при помощи Cargo, заключаются в том, что Cargo поместил исходный код в папка src , и у нас есть настроечный файл Cargo.toml в верхнем папке дела. Cargo ожидает, что ваши исходные файлы находятся внутри папки src . Папка верхнего уровня дела предназначен только для файлов README, сведений о лицензии, файлы настройке и чего то ещё не относящего к вашему коду. Использование Cargo помогает создавать дело. Есть место для всего и все находится на своём месте. Если вы начали дело без использования Cargo, как мы делали для \"Hello, world!\" дела, то можно преобразовывать его в дело с использованием Cargo. Переместите код в подпапка src и создайте соответствующий файл Cargo.toml в папке.","breadcrumbs":"С чего начать » Hello, Cargo! » Создание дела с помощью Cargo","id":"26","title":"Создание дела с помощью Cargo"},"260":{"body":"Workspace - это набор дополнений, которые используют один и тот же Cargo.lock и папку для хранения итогов сборки. Давайте создадим дело с использованием workspace - мы будем использовать обыкновенный код, чтобы сосредоточиться на устройстве рабочего пространства. Существует несколько способов внутренне выстроить рабочую область, но мы покажем только один из них. У нас будет рабочая область, содержащая двоичный файл и две библиотеки. Двоичный файл, который обеспечивает основную возможность, будет зависеть от двух библиотек. Одна библиотека предоставит функцию add_one, а вторая - add_two. Эти три ящика будут частью одного workspace . Начнём с создания папки для рабочего окружения: $ mkdir add\n$ cd add Далее в папке add мы создадим файл Cargo.toml , который будет определять настройку всего рабочего окружения. В этом файле не будет разделы [package]. Вместо этого он будет начинаться с разделы [workspace], которая позволит нам добавить звенья в рабочее пространство, указав путь к дополнению с нашим двоичным ящиком; в данном случае этот путь - adder : Файл: Cargo.toml [workspace] members = [ \"adder\",\n] Затем мы создадим исполняемый ящик adder, запустив приказ cargo new в папке add : $ cargo new adder Created binary (application) `adder` package На этом этапе мы можем создать рабочее пространство, запустив приказ cargo build. Файлы в папке add должны выглядеть следующим образом: ├── Cargo.lock\n├── Cargo.toml\n├── adder\n│ ├── Cargo.toml\n│ └── src\n│ └── main.rs\n└── target Рабочая область содержит на верхнем уровне один папка target , в который будут помещены собранные артефакты; дополнение adder не имеет собственного папки target . Даже если мы запустим cargo build из папки adder , собранные артефакты все равно окажутся в add/target , а не в add/adder/target . Cargo так определил папку target в рабочем пространстве, потому что ящики в рабочем пространстве должны зависеть друг от друга. Если бы каждый ящик имел свой собственный папка target , каждому ящику пришлось бы пересобирать каждый из других ящиков в рабочем пространстве, чтобы поместить артефакты в свой собственный папка target . Благодаря совместному использованию единого папки target ящики могут избежать ненужной пересборки.","breadcrumbs":"Подробнее о Cargo и Crates.io » Рабочие области Cargo » Создание рабочего пространства","id":"260","title":"Создание рабочего пространства"},"261":{"body":"Далее давайте создадим ещё одного участника дополнения в рабочей области и назовём его add_one. Внесите изменения в Cargo.toml верхнего уровня так, чтобы указать путь add_one в списке members: Файл: Cargo.toml [workspace] members = [ \"adder\", \"add_one\",\n] Затем создайте новый ящик библиотеки с именем add_one: $ cargo new add_one --lib Created library `add_one` package Ваш папка add должен теперь иметь следующие папки и файлы: ├── Cargo.lock\n├── Cargo.toml\n├── add_one\n│ ├── Cargo.toml\n│ └── src\n│ └── lib.rs\n├── adder\n│ ├── Cargo.toml\n│ └── src\n│ └── main.rs\n└── target В файле add_one/src/lib.rs добавим функцию add_one: Файл: add_one/src/lib.rs pub fn add_one(x: i32) -> i32 { x + 1\n} Теперь мы можем сделать так, чтобы дополнение adder с нашим исполняемым файлом зависел от дополнения add_one, содержащего нашу библиотеку. Сначала нам нужно добавить зависимость пути от add_one в adder/Cargo.toml . Файл: adder/Cargo.toml [dependencies]\nadd_one = { path = \"../add_one\" } Cargo не исходит из того, что ящики в рабочем пространстве могут зависеть друг от друга, поэтому нам необходимо явно указать отношения зависимости. Далее, давайте используем функцию add_one (из ящика add_one) в ящике adder. Откройте файл adder/src/main.rs и добавьте строку use в верхней части, чтобы ввести в область видимости новый библиотечный ящик add_one. Затем измените функцию main для вызова функции add_one, как показано в приложении 14-7. Файл: adder/src/main.rs use add_one; fn main() { let num = 10; println!(\"Hello, world! {num} plus one is {}!\", add_one::add_one(num));\n} Приложение 14-7: Использование возможностей библиотечного ящика add-one в ящике adder Давайте соберём рабочее пространство, запустив приказ cargo build в папке верхнего уровня add ! $ 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 Чтобы запустить двоичный ящик из папки add , нам нужно указать какой дополнение из рабочей области мы хотим использовать с помощью переменной -p и названия дополнения в приказу cargo run: $ cargo run -p adder Finished dev [unoptimized + debuginfo] target(s) in 0.0s Running `target/debug/adder`\nHello, world! 10 plus one is 11! Запуск кода из 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 [dependencies]\nrand = \"0.8.5\" Теперь мы можем добавить use rand; в файл add_one/src/lib.rs и сделать сборку рабочего пространства, запустив cargo build в папке add , что загрузит и собирает rand ящик: $ cargo build Updating crates.io index Downloaded rand v0.8.5 --snip-- Compiling rand v0.8.5 Compiling add_one v0.1.0 (file:///projects/add/add_one)\nwarning: unused import: `rand` --> add_one/src/lib.rs:1:5 |\n1 | use rand; | ^^^^ | = note: `#[warn(unused_imports)]` on by default warning: `add_one` (lib) generated 1 warning Compiling adder v0.1.0 (file:///projects/add/adder) Finished dev [unoptimized + debuginfo] target(s) in 10.18s Файл Cargo.lock верхнего уровня теперь содержит сведения о зависимости add_one к ящику rand. Тем не менее, не смотря на то что rand использован где-то в рабочем пространстве, мы не можем использовать его в других ящиках рабочего пространства, пока не добавим ящик rand в отдельные Cargo.toml файлы. Например, если мы добавим use rand; в файл adder/src/main.rs ящика adder, то получим ошибку: $ cargo build --snip-- Compiling adder v0.1.0 (file:///projects/add/adder)\nerror[E0432]: unresolved import `rand` --> adder/src/main.rs:2:5 |\n2 | use rand; | ^^^^ no external crate `rand` Чтобы исправить это, изменените файл Cargo.toml для дополнения adder и укажите, что rand также является его зависимостью. При сборке дополнения adder rand будет добавлен в список зависимостей для adder в Cargo.lock , но никаких дополнительных повторов rand загружено не будет. Cargo позаботился о том, чтобы все ящики во всех дополнениях рабочей области, использующих дополнение rand, использовали одну и ту же исполнение, экономя нам место и обеспечивая, что все ящики в рабочей области будут совместимы друг с другом. Добавление проверки в рабочее пространство В качестве ещё одного улучшения давайте добавим проверка функции add_one::add_one в add_one: Файл: add_one/src/lib.rs pub fn add_one(x: i32) -> i32 { x + 1\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn it_works() { assert_eq!(3, add_one(2)); }\n} Теперь запустите cargo test в папке верхнего уровня add . Запуск cargo test в рабочем пространстве, внутренне выстроеном подобно этому, запустит проверки для всех ящиков в рабочем пространстве: $ 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 unittests src/lib.rs (target/debug/deps/add_one-f0253159197f7841) running 1 test\ntest tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running unittests src/main.rs (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 Первая раздел вывода показывает, что проверка it_works в ящике add_one прошёл. Следующая раздел показывает, что в ящике adder не было обнаружено ни одного проверки, а последняя раздел показывает, что в ящике add_one не было найдено ни одного проверки документации. Мы также можем запустить проверки для одного определенного ящика в рабочем пространстве из папка верхнего уровня с помощью флага -p и указанием имени ящика для которого мы хотим запустить проверки: $ cargo test -p add_one Finished test [unoptimized + debuginfo] target(s) in 0.00s Running unittests src/lib.rs (target/debug/deps/add_one-b3235fea9a156f74) running 1 test\ntest 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 test запускает только проверки для ящика add-one и не запускает проверки ящика adder. Если вы соберётесь обнародовать ящики из рабочего пространства на crates.io , каждый ящик будет необходимо будет обнародовать отдельно. Подобно cargo test, мы можем обнародовать определенный ящик из нашей рабочей области, используя флаг -p и указав имя ящика, который мы хотим обнародовать. Для дополнительной опытов добавьте ящик add_two в данное рабочее пространство подобным способом, как делали с ящик add_one ! По мере роста дела рассмотрите возможность использования рабочих областей: легче понять небольшие, отдельные составляющие, чем один большой кусок кода. Кроме того, хранение ящиков в рабочем пространстве может облегчить согласование между ящиками, если они часто изменяются одновременно.","breadcrumbs":"Подробнее о Cargo и Crates.io » Рабочие области Cargo » Добавление второго ящика в рабочее пространство","id":"261","title":"Добавление второго ящика в рабочее пространство"},"262":{"body":"Приказ cargo install позволяет местно устанавливать и использовать исполняемые ящики. Она не предназначена для замены системных дополнений; она используется как удобный способ Ржавчина разработчикам устанавливать средства, которыми другие разработчики поделились на сайте crates.io . Заметьте, можно устанавливать только дополнения, имеющие исполняемые целевые ящики. Исполняемой целью (binary target) является запускаемая программа, созданная и имеющая в составе ящика файл src/main.rs или другой файл, указанный как исполняемый, в отличии от библиотечных ящиков, которые не могут запускаться сами по себе, но подходят для включения в другие программы. Обычно ящик содержит сведения в файле README , является ли он библиотекой, исполняемым файлом или обоими вместе. Все исполняемые файлы установленные приказом cargo install сохранены в корневой установочной папке bin . Если вы установили Ржавчина с помощью rustup.rs и у вас его нет в пользовательских настройках, то этим папкой будет $HOME/.cargo/bin . Он заверяет, что папка находится в вашем окружении $PATH, чтобы вы имели возможность запускать программы, которые вы установили приказом cargo install. Так, например, в главе 12 мы упоминали, что для поиска файлов существует выполнение утилиты grep на Ржавчина под названием ripgrep. Чтобы установить ripgrep, мы можем выполнить следующее: $ cargo install ripgrep Updating crates.io index Downloaded ripgrep v13.0.0 Downloaded 1 crate (243.3 KB) in 0.88s Installing ripgrep v13.0.0\n--snip-- Compiling ripgrep v13.0.0 Finished release [optimized + debuginfo] target(s) in 3m 10s Installing ~/.cargo/bin/rg Installed package `ripgrep v13.0.0` (executable `rg`) Последняя строка вывода показывает местоположение и название установленного исполняемого файла, который в случае ripgrep называется rg. Если вашей установочной папкой является $PATH, как уже упоминалось ранее, вы можете запустить rg --help и начать использовать более быстрый и грубый средство для поиска файлов!","breadcrumbs":"Подробнее о Cargo и Crates.io » Установка двоичных файлов с Crates.io с помощью cargo install » Установка двоичных файлов с помощью cargo install","id":"262","title":"Установка двоичных файлов с помощью cargo install"},"263":{"body":"Cargo расчитан так, что вы можете расширять его новыми субприказми без необходимости изменения самого Cargo. Если исполняемый файл доступен через переменную окружения $PATH и назван по образцу cargo-something, то его можно запускать как субприказ Cargo cargo something. Пользовательские приказы подобные этой также перечисляются в списке доступных через cargo --list. Возможность использовать cargo install для установки расширений и затем запускать их так же, как встроенные в Cargo средства, это очень удобное следствие продуманного внешнего вида Cargo!","breadcrumbs":"Подробнее о Cargo и Crates.io » Расширение возможностей Cargo путём добавления пользовательских приказов » Расширение Cargo пользовательскими приказми","id":"263","title":"Расширение Cargo пользовательскими приказми"},"264":{"body":"Совместное использование кода с Cargo и crates.io является частью того, что делает внутреннее устройство Ржавчина полезной для множества различных задач. Обычная библиотека Ржавчина небольшая и безотказная, но ящики легко распространять, использовать и улучшать независимо от самого языка. Не стесняйтесь делиться кодом, который был вам полезен, через crates.io ; скорее всего, он будет полезен и кому-то ещё!","breadcrumbs":"Подробнее о Cargo и Crates.io » Расширение возможностей Cargo путём добавления пользовательских приказов » Итоги","id":"264","title":"Итоги"},"265":{"body":"Указатель — это общая подход для переменной, которая содержит адрес участка памяти. Этот адрес «относится к», или «указывает на» некоторые другие данные. Наиболее общая разновидность указателя в Ржавчина — это ссылка, о которой вы узнали из главы 4. Ссылки обозначаются символом & и заимствуют значение, на которое указывают. Они не имеют каких-либо особых возможностей, кроме как ссылаться на данные, и не имеют никаких накладных расходов. Умные указатели , с другой стороны, являются устройствами данных, которые не только действуют как указатель, но также имеют дополнительные метаданные и возможности. Подход умных указателей не неповторима для Rust: умные указатели возникли в C++ и существуют в других языках. В Ржавчина есть разные умные указатели, определённые в встроенной библиотеке, которые обеспечивают возможность, выходящую за рамки ссылок. Одним из примеров, который мы рассмотрим в этой главе, является вид умного указателя reference counting (подсчёт ссылок). Этот указатель позволяет иметь несколько владельцев с помощью отслеживания количества владельцев и, когда владельцев не остаётся, очищает данные. Rust с его подходом владения и заимствования имеет дополнительное различие между ссылками и умными указателями: в то время, как ссылки только заимствуют данные, умные указатели часто владеют данными, на которые указывают. Ранее мы уже сталкивались с умными указателями в этой книге, хотя и не называли их так, например String и Vec в главе 8. Оба этих вида считаются умными указателями, потому что они владеют некоторой областью памяти и позволяют ею управлять. У них также есть метаданные и дополнительные возможности или заверения. String, например, хранит свой размер в виде метаданных и заверяет, что содержимое строки всегда будет в кодировке UTF-8. Умные указатели обычно выполняются с помощью устройств. Присущей чертой, которая отличает умный указатель от обычной устройства, является то, что для умных указателей выполнены особенности Deref и Drop. Особенность Deref позволяет образцу умного указателя вести себя как ссылка, так что вы можете написать код, работающий с ним как со ссылкой, так и как с умным указателем. Особенность Drop позволяет написать код, который будет запускаться когда образец умного указателя выйдет из области видимости. В этой главе мы обсудим оба особенности и выясним, почему они важны для умных указателей. Учитывая, что образец умного указателя является общим образцом разработки, часто используемым в Rust, эта глава не описывает все существующие умные указатели. Множество библиотек имеют свои умные указатели, и вы также можете написать свои. Мы охватим наиболее распространённые умные указатели из встроенной библиотеки: Box для распределения значений в куче (памяти) Rc вид счётчика ссылок, который допускает множественное владение Виды Ref и RefMut, доступ к которым осуществляется через вид RefCell, который обеспечивает правила заимствования во время выполнения вместо времени сборки Дополнительно мы рассмотрим образец внутренней изменчивости (interior mutability) , где неизменяемый вид предоставляет API для изменения своего внутреннего значения. Мы также обсудим ссылочные зацикленности (reference cycles) : как они могут приводить к утечке памяти и как это предотвратить. Приступим!","breadcrumbs":"Умные указатели » Умные указатели","id":"265","title":"Умные указатели"},"266":{"body":"Наиболее простой умный указатель - это box , чей вид записывается как Box. Такие переменные позволяют хранить данные в куче, а не в обойме. То, что остаётся в обойме, является указателем на данные в куче. Обратитесь к Главе 4, чтобы рассмотреть разницу между обоймой и кучей. У Box нет неполадок с производительностью, кроме хранения данных в куче вместо обоймы. Но он также и не имеет множества дополнительных возможностей. Вы будете использовать его чаще всего в следующих случаейх: Когда у вас есть вид, размер которого невозможно определить во время сборки, а вы хотите использовать значение этого вида в среде, требующем точного размера. Когда у вас есть большой размер данных и вы хотите передать владение, но при этом быть уверенным, что данные не будут воспроизведены Когда вы хотите получить значение во владение и вас важно только то, что оно относится к виду, выполняющему определённый особенность, а не то, является ли оно значением какого-то определенного вида Мы выясним первую случай в разделе \"Выполнение рекурсивных видов с помощью Box\" . Во втором случае, передача владения на большой размер данных может занять много времени, потому что данные повторяются через обойма. Для повышения производительности в этой случаи, мы можем хранить большое количество данных в куче с помощью Box. Затем только небольшое количество данных указателя воспроизводится в обойме, в то время как данные, на которые он ссылается, остаются в одном месте кучи. Третий случай известен как особенность предмет (trait object) и глава 17 посвящает целый раздел \"Использование особенность предметов, которые допускают значения разных видов\" только этой теме. Итак, то, что вы узнаете здесь, вы примените снова в Главе 17!","breadcrumbs":"Умные указатели » Использование Box для указания на данные в куче » Использование Box для ссылки на данные в куче","id":"266","title":"Использование Box для ссылки на данные в куче"},"267":{"body":"Прежде чем мы обсудим этот исход использования Box, мы рассмотрим правила написания и то, как взаимодействовать со значениями, хранящимися в Box. В приложении 15-1 показано, как использовать поле для хранения значения i32 в куче: Файл: src/main.rs fn main() { let b = Box::new(5); println!(\"b = {b}\");\n} Приложение 15-1: Сохранение значения i32 в куче с использованием box Мы объявляем переменную b со значением Box, указывающим на число 5, размещённое в куче. Эта программа выведет b = 5; в этом случае мы получаем доступ к данным в box так же, как если бы эти данные находились в обойме. Как и любое другое значение, когда box выйдет из области видимости, как b в конце main, он будет удалён. Деаллокация происходит как для box ( хранящегося в обойме), так и для данных, на которые он указывает (хранящихся в куче). Размещать одиночные значения в куче не слишком целесообразно, поэтому вряд ли вы будете часто использовать box'ы таким образом. В большинстве случаев более уместно размещать такие значения, как i32, в обойме, где они и сохраняются по умолчанию. Давайте рассмотрим случай, когда box позволяет нам определить виды, которые мы не могли бы иметь, если бы у нас не было box.","breadcrumbs":"Умные указатели » Использование Box для указания на данные в куче » Использование Box для хранения данных в куче","id":"267","title":"Использование Box для хранения данных в куче"},"268":{"body":"Значение рекурсивного вида может иметь другое значение такого же вида как свой составляющая. Рекурсивные виды представляют собой неполадку, поскольку во время сборки Ржавчина должен знать, сколько места занимает вид. Однако вложенность значений рекурсивных видов предположительно может продолжаться бесконечно, поэтому Ржавчина не может определить, сколько места потребуется. Поскольку 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, которая является недействительным или отсутствующим значением. Cons list не является часто используемой устройством данных в Rust. В большинстве случаев, когда вам нужен список элементов при использовании Rust, лучше использовать Vec. Другие, более сложные рекурсивные виды данных полезны в определённых случаейх, но благодаря тому, что в этой главе мы начнём с cons list, мы сможем выяснить, как box позволяет нам определить рекурсивный вид данных без особого напряжения. Приложение 15-2 содержит объявление перечисления cons списка. Обратите внимание, что этот код не будет собираться, потому что вид List не имеет известного размера, что мы и выясним. Файл: src/main.rs enum List { Cons(i32, List), Nil,\n}\n# # fn main() {} Приложение 15-2: Первая попытка определить перечисление в качестве устройства данных cons list, состоящей из i32 значений. Примечание: В данном примере мы выполняем cons list, который содержит только значения i32. Мы могли бы выполнить его с помощью generics, о которых мы говорили в главе 10, чтобы определить вид cons list, который мог бы хранить значения любого вида. Использование вида List для хранения списка 1, 2, 3 будет выглядеть как код в приложении 15-3: Файл: src/main.rs # enum List {\n# Cons(i32, List),\n# Nil,\n# }\n# use crate::List::{Cons, Nil}; fn main() { let list = Cons(1, Cons(2, Cons(3, Nil)));\n} Приложение 15-3: Использование перечисления List для хранения списка 1, 2, 3 Первое значение Cons содержит 1 и другой List. Это значение List является следующим значением Cons, которое содержит 2 и другой List. Это значение List является ещё один значением Cons, которое содержит 3 и значение List, которое наконец является Nil, не рекурсивным исходом, сигнализирующим об окончании списка. Если мы попытаемся собрать код в приложении 15-3, мы получим ошибку, показанную в приложении 15-4: $ cargo run Compiling cons-list v0.1.0 (file:///projects/cons-list)\nerror[E0072]: recursive type `List` has infinite size --> src/main.rs:1:1 |\n1 | enum List { | ^^^^^^^^^\n2 | Cons(i32, List), | ---- recursive without indirection |\nhelp: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle |\n2 | Cons(i32, Box), | ++++ + error[E0391]: cycle detected when computing when `List` needs drop --> src/main.rs:1:1 |\n1 | enum List { | ^^^^^^^^^ | = note: ...which immediately requires computing when `List` needs drop again = note: cycle used when computing whether `List` needs drop = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information Some errors have detailed explanations: E0072, E0391.\nFor more information about an error, try `rustc --explain E0072`.\nerror: could not compile `cons-list` (bin \"cons-list\") due to 2 previous errors Приложение 15-4: Ошибка, которую мы получаем при попытке определить рекурсивное перечисление Ошибка говорит о том, что этот вид \"имеет бесконечный размер\". Причина в том, что мы определили List в виде, которая является рекурсивной: она непосредственно хранит другое значение своего собственного вида. В итоге Ржавчина не может определить, сколько места ему нужно для хранения значения List. Давайте разберёмся, почему мы получаем эту ошибку. Сначала мы рассмотрим, как Ржавчина решает, сколько места ему нужно для хранения значения нерекурсивного вида. Вычисление размера нерекурсивного вида Вспомните перечисление Message определённое в приложении 6-2, когда обсуждали объявление enum в главе 6: enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32),\n}\n# # fn main() {} Чтобы определить, сколько памяти выделять под значение Message, Ржавчина проходит каждый из исходов, чтобы увидеть, какой исход требует наибольшее количество памяти. Ржавчина видит, что для Message::Quit не требуется места, Message::Move хватает места для хранения двух значений i32 и т.д. Так как будет использоваться только один исход, то наибольшее пространство, которое потребуется для значения Message, это пространство, которое потребуется для хранения самого большого из исходов перечисления. Сравните это с тем, что происходит, когда Ржавчина пытается определить, сколько места необходимо рекурсивному виду, такому как перечисление List в приложении 15-2. Сборщик смотрит на исход Cons, который содержит значение вида i32 и значение вида List. Следовательно, Cons нужно пространство, равное размеру i32 плюс размер List. Чтобы выяснить, сколько памяти необходимо виду List, сборщик смотрит на исходы, начиная с Cons. Исход Cons содержит значение вида i32 и значение вида List, и этот этап продолжается бесконечно, как показано на рисунке 15-1. Рисунок 15-1: Бесконечный List, состоящий из нескончаемого числа исходов Cons Использование Box для получения рекурсивного вида с известным размером Поскольку Ржавчина не может определить, сколько места нужно выделить для видов с рекурсивным определением, сборщик выдаёт ошибку с этим полезным предложением: help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable |\n2 | Cons(i32, Box), | ++++ + В данном предложении \"перенаправление\" означает, что вместо того, чтобы непосредственно хранить само значение, мы должны изменить устройство данных, так чтобы хранить его косвенно - хранить указатель на это значение. Поскольку Box является указателем, Ржавчина всегда знает, сколько места нужно Box: размер указателя не меняется в зависимости от объёма данных, на которые он указывает. Это означает, что мы можем поместить Box внутрь образца Cons вместо значения List напрямую. Box будет указывать на значение очередного List, который будет находиться в куче, а не внутри образца Cons. Мировозренческо у нас все ещё есть список, созданный из списков, содержащих другие списки, но эта выполнение теперь больше похожа на размещение элементов рядом друг с другом, а не внутри друг друга. Мы можем изменить определение перечисления List в приложении 15-2 и использование List в приложении 15-3 на код из приложения 15-5, который будет собираться: Файл: src/main.rs enum List { Cons(i32, Box), Nil,\n} use crate::List::{Cons, Nil}; fn main() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));\n} Приложение 15-5: Определение List, которое использует Box для того, чтобы иметь вычисляемый размер Cons требуется объём i32 плюс место для хранения данных указателя box. Nil не хранит никаких значений, поэтому ему нужно меньше места, чем Cons. Теперь мы знаем, что любое значение List займёт размер i32 плюс размер данных указателя box. Используя box, мы разорвали бесконечную рекурсивную цепочку, поэтому сборщик может определить размер, необходимый для хранения значения List. На рисунке 15-2 показано, как теперь выглядит Cons. Рисунок 15-2: List, который не является бесконечно большим, потому что Cons хранит Box. Box-ы обеспечивают только перенаправление и выделение в куче; у них нет никаких других особых возможностей, подобных тем, которые мы увидим у других видов умных указателей. У них также нет накладных расходов на производительность, которые несут эти особые возможности, поэтому они могут быть полезны в таких случаях, как cons list, где перенаправление - единственная функция, которая нам нужна. В главе 17 мы также рассмотрим другие случаи использования box. Вид Box является умным указателем, поскольку он выполняет особенность Deref, который позволяет обрабатывать значения Box как ссылки. Когда значение Box выходит из области видимости, данные кучи, на которые указывает box, также очищаются благодаря выполнения особенности Drop. Эти два особенности будут ещё более значимыми для возможности, предоставляемой другими видами умных указателей, которые мы обсудим в оставшейся части этой главы. Давайте рассмотрим эти два особенности более подробно.","breadcrumbs":"Умные указатели » Использование Box для указания на данные в куче » Включение рекурсивных видов с помощью Boxes","id":"268","title":"Включение рекурсивных видов с помощью Boxes"},"269":{"body":"Используя особенность Deref, вы можете изменить поведение оператора разыменования * (не путать с операторами умножения или вездесущего подключения). Выполнив Deref таким образом, что умный указатель может рассматриваться как обычная ссылка, вы можете писать код, оперирующий ссылками, а также использовать этот код с умными указателями. Давайте сначала посмотрим, как работает оператор разыменования с обычными ссылками. Затем мы попытаемся определить пользовательский вид, который ведёт себя как Box и посмотрим, почему оператор разыменования не работает как ссылка для нового объявленного вида. Мы рассмотрим, как выполнение особенности Deref делает возможным работу умных указателей подобно ссылкам. Затем посмотрим на разыменованное приведение (deref coercion) в Ржавчина и как оно позволяет работать с любыми ссылками или умными указателями. Примечание: есть одна большая разница между видом MyBox, который мы собираемся создать и существующим Box: наша исполнение не будет хранить свои данные в куче. В примере мы сосредоточимся на особенности Deref, поэтому менее важно то, где данные хранятся, чем поведение подобное указателю.","breadcrumbs":"Умные указатели » Работа с умными указателями как с обычными ссылками с помощью особенности Deref » Обращение с умными указателями как с обычными ссылками с помощью Deref особенности","id":"269","title":"Обращение с умными указателями как с обычными ссылками с помощью Deref особенности"},"27":{"body":"Посмотрим, в чем разница при сборке и запуске программы \"Hello, world!\" с помощью Cargo. В папке hello_cargo соберите дело следующей приказом: $ cargo build Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo) Finished dev [unoptimized + debuginfo] target(s) in 2.85 secs Этот приказ создаёт исполняемый файл в target/debug/hello_cargo (или target\\debug\\hello_cargo.exe в Windows), а не в вашем текущем папке. Поскольку обычная сборка является отладочной, Cargo помещает двоичный файл в папка с именем debug . Вы можете запустить исполняемый файл с помощью этой приказы: $ ./target/debug/hello_cargo # or .\\target\\debug\\hello_cargo.exe on Windows\nHello, world! Если все хорошо, то Hello, world! печатается в окне вызова. Запуск приказы cargo build в первый раз также приводит к созданию нового файла Cargo.lock в папке верхнего уровня. Данный файл хранит точные исполнения зависимостей вашего дела. Так как у нас нет зависимостей, то файл пустой. Вы никогда не должны менять этот файл вручную: Cargo сам управляет его содержимым для вас. Только что мы собрали дело приказом cargo build и запустили его из ./target/debug/hello_cargo. Но мы также можем при помощи приказы cargo run сразу и собрать код, и затем запустить полученный исполняемый файл всего лишь одной приказом: $ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/hello_cargo`\nHello, world! Использование cargo run более удобно, чем необходимость помнить и запускать cargo build, а затем использовать весь путь к двоичному файлу, поэтому большинство разработчиков используют cargo run. Обратите внимание, что на этот раз мы не видели вывода, указывающего на то, что Cargo собирает hello_cargo. Cargo выяснил, что файлы не изменились, поэтому не стал пересобирать, а просто запустил двоичный файл. Если бы вы изменили свой исходный код, Cargo пересобрал бы дело перед его запуском, и вы бы увидели этот вывод: $ cargo run Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo) Finished dev [unoptimized + debuginfo] target(s) in 0.33 secs Running `target/debug/hello_cargo`\nHello, world! Cargo также предоставляет приказ, называемую cargo check. Этот приказ быстро проверяет ваш код, чтобы убедиться, что он собирается, но не создаёт исполняемый файл: $ cargo check Checking hello_cargo v0.1.0 (file:///projects/hello_cargo) Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs Почему вам не нужен исполняемый файл? Часто cargo check выполняется намного быстрее, чем cargo build, поскольку пропускает этап создания исполняемого файла. Если вы постоянно проверяете свою работу во время написания кода, использование cargo check ускорит этап уведомления вас о том, что ваш дело всё ещё собирается! Таким образом, многие Rustacean периодически запускают cargo check, когда пишут свои программы, чтобы убедиться, что она собирается. Затем они запускают cargo build, когда готовы использовать исполняемый файл. Давайте подытожим, что мы уже узнали о Cargo: Мы можем создать дело с помощью cargo new. можно собирать дело, используя приказ cargo build, можно одновременно собирать и запускать дело одной приказом cargo run, можно собрать дело для проверки ошибок с помощью cargo check, не тратя время на кодосоздание исполняемого файла, cargo сохраняет итоги сборки не в папку с исходным кодом, а в отдельный папка target/debug . Дополнительным преимуществом использования Cargo является то, что его приказы одинаковы для разных операционных систем. С этой точки зрения, мы больше не будем предоставлять отдельные указания для Linux, macOS или Windows.","breadcrumbs":"С чего начать » Hello, Cargo! » Сборка и запуск Cargo дела","id":"27","title":"Сборка и запуск Cargo дела"},"270":{"body":"Обычная ссылка - это разновидность указателя, а указатель можно рассматривать как своеобразную стрелочку направляющую к значению, хранящемуся в другом месте. В приложении 15-6 мы создаём ссылку на значение i32, а затем используем оператор разыменования для перехода от ссылки к значению: Файл: src/main.rs fn main() { let x = 5; let y = &x; assert_eq!(5, x); assert_eq!(5, *y);\n} Приложение 15-6: Использование оператора разыменования для следования по ссылке к значению i32 Переменной x присвоено значение5 вида i32. Мы установили в качестве значения y ссылку на x. Мы можем утверждать, что значение x равно 5. Однако, если мы хотим сделать утверждение о значении в y, мы должны использовать *y, чтобы перейти по ссылке к значению, на которое она указывает (таким образом, происходит разыменование ), для того чтобы сборщик при сравнении мог использовать действительное значение. Как только мы разыменуем y, мы получим доступ к целочисленному значению, на которое указывает y, которое и будем сравнивать с 5. Если бы мы попытались написать assert_eq!(5, y);, то получили ошибку сборки: $ cargo run Compiling deref-example v0.1.0 (file:///projects/deref-example)\nerror[E0277]: can't compare `{integer}` with `&{integer}` --> src/main.rs:6:5 |\n6 | assert_eq!(5, y); | ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}` | = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}` = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) For more information about this error, try `rustc --explain E0277`.\nerror: could not compile `deref-example` (bin \"deref-example\") due to 1 previous error Сравнение числа и ссылки на число не допускается, потому что они различных видов. Мы должны использовать оператор разыменования, чтобы перейти по ссылке на значение, на которое она указывает.","breadcrumbs":"Умные указатели » Работа с умными указателями как с обычными ссылками с помощью особенности Deref » Следуя за указателем на значение","id":"270","title":"Следуя за указателем на значение"},"271":{"body":"Мы можем переписать код в приложении 15-6, чтобы использовать Box вместо ссылки; оператор разыменования, используемый для Box в приложении 15-7, работает так же, как оператор разыменования, используемый для ссылки в приложении 15-6: Файл: src/main.rs fn main() { let x = 5; let y = Box::new(x); assert_eq!(5, x); assert_eq!(5, *y);\n} Приложение 15-7: Использование оператора разыменования с видом Box Основное различие между приложением 15-7 и приложением 15-6 заключается в том, что здесь мы устанавливаем y как образец Box, указывающий на воспроизведенное значение x, а не как ссылку, указывающую на значение x. В последнем утверждении мы можем использовать оператор разыменования, чтобы проследовать за указателем Box так же, как мы это делали, когда y был ссылкой. Далее мы рассмотрим, что особенного в Box, что позволяет нам использовать оператор разыменования, определяя наш собственный вид.","breadcrumbs":"Умные указатели » Работа с умными указателями как с обычными ссылками с помощью особенности Deref » Использование Box как ссылку","id":"271","title":"Использование Box как ссылку"},"272":{"body":"Давайте создадим умный указатель, похожий на вид Box предоставляемый встроенной библиотекой, чтобы понять как поведение умных указателей отличается от поведения обычной ссылки. Затем мы рассмотрим вопрос, как добавить возможность использовать оператор разыменования. Вид Box в конечном итоге определяется как устройства упорядоченного ряда с одним элементом, поэтому в приложении 15-8 подобным образом определяется MyBox. Мы также определим функцию new, чтобы она соответствовала функции new, определённой в Box. Файл: src/main.rs struct MyBox(T); impl MyBox { fn new(x: T) -> MyBox { MyBox(x) }\n}\n# # fn main() {} Приложение 15-8: Определение вида MyBox Мы определяем устройство с именем MyBox и объявляем обобщённый свойство T, потому что мы хотим, чтобы наш вид хранил значения любого вида. Вид MyBox является устройством упорядоченного ряда с одним элементом вида T. Функция MyBox::new принимает один свойство вида T и возвращает образец MyBox, который содержит переданное значение. Давайте попробуем добавить функцию main из приложения 15-7 в приложение 15-8 и изменим её на использование вида MyBox, который мы определили вместо Box. Код в приложении 15-9 не будет собираться, потому что Ржавчина не знает, как разыменовывать MyBox. Файл: src/main.rs # struct MyBox(T);\n# # impl MyBox {\n# fn new(x: T) -> MyBox {\n# MyBox(x)\n# }\n# }\n# fn main() { let x = 5; let y = MyBox::new(x); assert_eq!(5, x); assert_eq!(5, *y);\n} Приложение 15-9. Попытка использовать MyBox таким же образом, как мы использовали ссылки и Box Вот итог ошибки сборки: $ cargo run Compiling deref-example v0.1.0 (file:///projects/deref-example)\nerror[E0614]: type `MyBox<{integer}>` cannot be dereferenced --> src/main.rs:14:19 |\n14 | assert_eq!(5, *y); | ^^ For more information about this error, try `rustc --explain E0614`.\nerror: could not compile `deref-example` (bin \"deref-example\") due to 1 previous error Наш вид MyBox не может быть разыменован, потому что мы не выполнили эту возможность. Чтобы включить разыменование с помощью оператора *, мы выполняем особенность Deref.","breadcrumbs":"Умные указатели » Работа с умными указателями как с обычными ссылками с помощью особенности Deref » Определение собственного умного указателя","id":"272","title":"Определение собственного умного указателя"},"273":{"body":"Как обсуждалось в разделе “Выполнение особенности для типа” Главы 10, для выполнения особенности нужно предоставить выполнения требуемых способов особенности. Особенность Deref, предоставляемый встроенной библиотекой требует от нас выполнения одного способа с именем deref, который заимствует self и возвращает ссылку на внутренние данные. Приложение 15-10 содержит выполнение Deref добавленную к определению MyBox: Файл: src/main.rs use std::ops::Deref; impl Deref for MyBox { type Target = T; fn deref(&self) -> &Self::Target { &self.0 }\n}\n# # struct MyBox(T);\n# # impl MyBox {\n# fn new(x: T) -> MyBox {\n# MyBox(x)\n# }\n# }\n# # fn main() {\n# let x = 5;\n# let y = MyBox::new(x);\n# # assert_eq!(5, x);\n# assert_eq!(5, *y);\n# } Приложение 15-10: Выполнение Deref для вида MyBox правила написания type Target = T; определяет связанный вид для использования у особенности Deref. Связанные виды - это немного другой способ объявления обобщённого свойства, но пока вам не нужно о них беспокоиться; мы рассмотрим их более подробно в главе 19. Мы заполним тело способа deref оператором &self.0 , чтобы deref вернул ссылку на значение, к которому мы хотим получить доступ с помощью оператора *; вспомним из раздела \"Using Tuple Structs without Named Fields to Create Different Types\" главы 5, что .0 получает доступ к первому значению в упорядоченной в ряд устройстве. Функция main в приложении 15-9, которая вызывает * для значения MyBox, теперь собирается, и проверки проходят! Без особенности Deref сборщик может только разыменовывать & ссылки. Способ deref даёт сборщику возможность принимать значение любого вида, выполняющего Deref и вызывать способ deref чтобы получить ссылку &, которую он знает, как разыменовывать. Когда мы ввели *y в приложении 15-9, Ржавчина в действительности выполнил за кулисами такой код: *(y.deref()) Rust заменяет оператор * вызовом способа deref и затем простое разыменование, поэтому нам не нужно думать о том, нужно ли нам вызывать способ deref. Эта функция Ржавчина позволяет писать код, который исполняется одинаково, независимо от того, есть ли у нас обычная ссылка или вид, выполняющий особенность Deref. Причина, по которой способ deref возвращает ссылку на значение, и что простое разыменование вне круглых скобок в *(y.deref()) все ещё необходимо, связана с системой владения. Если бы способ deref возвращал значение напрямую, а не ссылку на него, значение переместилось бы из self. Мы не хотим передавать владение внутренним значением внутри MyBox в этом случае и в большинстве случаев, когда мы используем оператор разыменования. Обратите внимание, что оператор * заменён вызовом способа deref, а затем вызовом оператора * только один раз, каждый раз, когда мы используем * в коде. Поскольку замена оператора * не повторяется бесконечно, мы получаем данные вида i32, которые соответствуют 5 в assert_eq! приложения 15-9.","breadcrumbs":"Умные указатели » Работа с умными указателями как с обычными ссылками с помощью особенности Deref » Трактование вида как ссылки выполняя особенность Deref","id":"273","title":"Трактование вида как ссылки выполняя особенность Deref"},"274":{"body":"Разыменованное приведение преобразует ссылку на вид, который выполняет признак Deref, в ссылку на другой вид. Например, deref coercion может преобразовать &String в &str, потому что String выполняет признак Deref, который возвращает &str. Deref coercion - это удобный рычаг, который Ржавчина использует для переменных функций и способов, и работает только для видов, выполняющих признак Deref. Это происходит самостоятельно , когда мы передаём в качестве переменной функции или способа ссылку на значение определённого вида, которое не соответствует виду свойства в определении функции или способа. В итоге серии вызовов способа deref вид, который мы передали, преобразуется в вид, необходимый для свойства. Разыменованное приведение было добавлено в Rust, так что программистам, пишущим вызовы функций и способов, не нужно добавлять множество явных ссылок и разыменований с помощью использования & и *. Возможность разыменованного приведения также позволяет писать больше кода, который может работать как с ссылками, так и с умными указателями. Чтобы увидеть разыменованное приведение в действии, давайте воспользуемся видом MyBox определённым в приложении 15-8, а также выполнение Deref добавленную в приложении 15-10. Приложение 15-11 показывает определение функции, у которой есть свойство вида срез строки: Файл: src/main.rs fn hello(name: &str) { println!(\"Hello, {name}!\");\n}\n# # fn main() {} Приложение 15-11: Функция hello имеющая свойство name вида &str Можно вызвать функцию hello со срезом строки в качестве переменной, например hello(\"Rust\");. Разыменованное приведение делает возможным вызов hello со ссылкой на значение вида MyBox, как показано в приложении 15-12. Файл: src/main.rs # use std::ops::Deref;\n# # impl Deref for MyBox {\n# type Target = T;\n# # fn deref(&self) -> &T {\n# &self.0\n# }\n# }\n# # struct MyBox(T);\n# # impl MyBox {\n# fn new(x: T) -> MyBox {\n# MyBox(x)\n# }\n# }\n# # fn hello(name: &str) {\n# println!(\"Hello, {name}!\");\n# }\n# fn main() { let m = MyBox::new(String::from(\"Rust\")); hello(&m);\n} Приложение 15-12: Вызов hello со ссылкой на значение MyBox, которое работает из-за разыменованного приведения Здесь мы вызываем функцию hello с переменнаяом &m, который является ссылкой на значение MyBox. Поскольку мы выполнили особенность Deref для MyBox в приложении 15-10, то Ржавчина может преобразовать &MyBox в &String вызывая deref. Обычная библиотека предоставляет выполнение особенности Deref для вида String, которая возвращает срез строки, это описано в документации API особенности Deref. Ржавчина снова вызывает deref, чтобы превратить &String в &str, что соответствует определению функции hello. Если бы Ржавчина не выполнил разыменованное приведение, мы должны были бы написать код в приложении 15-13 вместо кода в приложении 15-12 для вызова способа hello со значением вида &MyBox. Файл: src/main.rs # use std::ops::Deref;\n# # impl Deref for MyBox {\n# type Target = T;\n# # fn deref(&self) -> &T {\n# &self.0\n# }\n# }\n# # struct MyBox(T);\n# # impl MyBox {\n# fn new(x: T) -> MyBox {\n# MyBox(x)\n# }\n# }\n# # fn hello(name: &str) {\n# println!(\"Hello, {name}!\");\n# }\n# fn main() { let m = MyBox::new(String::from(\"Rust\")); hello(&(*m)[..]);\n} Приложение 15-13: Код, который нам пришлось бы написать, если бы в Ржавчина не было разыменованного приведения ссылок Код (*m) разыменовывает MyBox в String. Затем & и [..] принимают строковый срез String, равный всей строке, чтобы соответствовать ярлыке hello. Код без разыменованного приведения сложнее читать, писать и понимать со всеми этими символами. Разыменованное приведение позволяет Ржавчина обрабатывать эти преобразования для нас самостоятельно . Когда особенность Deref определён для задействованных видов, Ржавчина проанализирует виды и будет использовать Deref::deref столько раз, сколько необходимо, чтобы получить ссылку, соответствующую виду свойства. Количество раз, которое нужно вставить Deref::deref определяется во время сборки, поэтому использование разыменованного приведения не имеет накладных расходов во время выполнения!","breadcrumbs":"Умные указатели » Работа с умными указателями как с обычными ссылками с помощью особенности Deref » Неявные разыменованные приведения с функциями и способами","id":"274","title":"Неявные разыменованные приведения с функциями и способами"},"275":{"body":"Подобно тому, как вы используете особенность Deref для переопределения оператора * у неизменяемых ссылок, вы можете использовать особенность DerefMut для переопределения оператора * у изменяемых ссылок. Rust выполняет разыменованное приведение, когда находит виды и выполнения особенностей в трёх случаях: Из вида &T в вид &U когда верно T: Deref Из вида &mut T в вид &mut U когда верно T: DerefMut Из вида &mut T в вид &U когда верно T: Deref Первые два случая равноценны друг другу, за исключением того, что второй выполняет изменяемость. В первом случае говорится, что если у вас есть &T, а T выполняет Deref для некоторого вида U, вы сможете прозрачно получить &U. Во втором случае говорится, что такое же разыменованное приведение происходит и для изменяемых ссылок. Третий случай хитрее: Ржавчина также приводит изменяемую ссылку к неизменяемой. Но обратное не представляется возможным: неизменяемые ссылки никогда не приводятся к изменяемым ссылкам. Из-за правил заимствования, если у вас есть изменяемая ссылка, эта изменяемая ссылка должна быть единственной ссылкой на данные (в противном случае программа не будет собираться). Преобразование одной изменяемой ссылки в неизменяемую ссылку никогда не нарушит правила заимствования. Преобразование неизменяемой ссылки в изменяемую ссылку потребует наличия только одной неизменяемой ссылки на эти данные, и правила заимствования не заверяют этого. Следовательно, Ржавчина не может сделать предположение, что преобразование неизменяемой ссылки в изменяемую ссылку возможно.","breadcrumbs":"Умные указатели » Работа с умными указателями как с обычными ссылками с помощью особенности Deref » Как разыменованное приведение взаимодействует с изменяемостью","id":"275","title":"Как разыменованное приведение взаимодействует с изменяемостью"},"276":{"body":"Вторым важным особенностью умного указателя является Drop, который позволяет управлять, что происходит, когда значение вот-вот выйдет из области видимости. Вы можете выполнить особенность Drop для любого вида, а также использовать этот код для высвобождения ресурсов, таких как файлы или сетевые соединения. Мы рассматриваем Drop в среде умных указателей, потому что возможность свойства Drop по сути всегда используется при выполнения умного указателя. Например, при сбросе Box происходит деаллокация пространства на куче, на которое указывает box. В некоторых языках для некоторых видов программист должен вызывать код для освобождения памяти или ресурсов каждый раз, когда он завершает использование образцов этих видов. Примерами могут служить указатели файлов, сокеты или блокировки. Если забыть об этом, система окажется перегруженной и может упасть. В Ржавчина вы можете указать, что определённый отрывок кода должен выполняться всякий раз, когда значение выходит из области видимости, и сборщик самостоятельно будет его вставлять. Как следствие, вам не нужно заботиться о размещении кода очистки везде в программе, где завершается работа образца определённого вида - утечки ресурсов все равно не будет! Вы можете задать определённую логику, которая будет выполняться, когда значение выходит за пределы области видимости, выполнив признак Drop. Особенность Drop требует от вас выполнения одного способа drop, который принимает изменяемую ссылку на self. Чтобы увидеть, когда Ржавчина вызывает drop, давайте выполняем drop с помощью указаний println!. В приложении 15-14 показана устройства CustomSmartPointer, единственной не имеющей себе подобных возможностью которой является печать Dropping CustomSmartPointer!, когда образец выходит из области видимости, чтобы показать, когда Ржавчина выполняет функцию drop. Файл: src/main.rs struct CustomSmartPointer { data: String,\n} impl Drop for CustomSmartPointer { fn drop(&mut self) { println!(\"Dropping CustomSmartPointer with data `{}`!\", self.data); }\n} fn main() { let c = CustomSmartPointer { data: String::from(\"my stuff\"), }; let d = CustomSmartPointer { data: String::from(\"other stuff\"), }; println!(\"CustomSmartPointers created.\");\n} Приложение 15-14: Устройства CustomSmartPointer, выполняющая особенность Drop, куда мы поместим наш код очистки Особенность Drop включён в прелюдию, поэтому нам не нужно вводить его в область видимости. Мы выполняем особенность Drop для CustomSmartPointer и выполняем способ drop, который будет вызывать println!. Тело функции drop - это место, где должна располагаться вся логика, которую вы захотите выполнять, когда образец вашего вида выйдет из области видимости. Мы печатаем здесь текст, чтобы наглядно отобразить, когда Ржавчина вызовет drop. В main мы создаём два образца CustomSmartPointer и затем печатаем CustomSmartPointers created . В конце main наши образцы CustomSmartPointer выйдут из области видимости и Ржавчина вызовет код, который мы добавили в способ drop, который и напечатает наше окончательное сообщение. Обратите внимание, что нам не нужно вызывать способ drop явно. Когда мы запустим эту программу, мы увидим следующий вывод: $ cargo run Compiling drop-example v0.1.0 (file:///projects/drop-example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s Running `target/debug/drop-example`\nCustomSmartPointers created.\nDropping CustomSmartPointer with data `other stuff`!\nDropping CustomSmartPointer with data `my stuff`! Rust самостоятельно вызывал drop в мгновение выхода наших образцов из области видимости, тем самым выполнив заданный нами код. Переменные удаляются в обратном порядке их создания, поэтому d была удалена до c. Цель этого примера — дать вам наглядное представление о том, как работает способ drop; в типичных случаях вы будете задавать код очистки, который должен выполнить ваш вид, а не печатать сообщение.","breadcrumbs":"Умные указатели » Выполнение кода при очистке с помощью особенности Drop » Запуск кода при очистке с помощью особенности Drop","id":"276","title":"Запуск кода при очистке с помощью особенности Drop"},"277":{"body":"К сожалению, отключение функции самостоятельного удаления с помощью drop является не простым. Отключение drop обычно не требуется; весь смысл особенности Drop в том, чтобы о функции позаботились самостоятельно . Иногда, однако, вы можете захотеть очистить значение рано. Одним из примеров является использование умных указателей, которые управляют блокировками: вы могли бы потребовать принудительный вызов способа drop который снимает блокировку, чтобы другой код в той же области видимости мог получить блокировку. Ржавчина не позволяет вызвать способ особенности Drop вручную; вместо этого вы должны вызвать функцию std::mem::drop предоставляемую встроенной библиотекой, если хотите принудительно удалить значение до конца области видимости. Если попытаться вызвать способ drop особенности Drop вручную, изменяя функцию main приложения 15-14 так, как показано в приложении 15-15, мы получим ошибку сборщика: Файл: src/main.rs # struct CustomSmartPointer {\n# data: String,\n# }\n# # impl Drop for CustomSmartPointer {\n# fn drop(&mut self) {\n# println!(\"Dropping CustomSmartPointer with data `{}`!\", self.data);\n# }\n# }\n# fn main() { let c = CustomSmartPointer { data: String::from(\"some data\"), }; println!(\"CustomSmartPointer created.\"); c.drop(); println!(\"CustomSmartPointer dropped before the end of main.\");\n} Приложение 15-15: Попытка вызвать способ drop из особенности Drop вручную для досрочной очистки Когда мы попытаемся собрать этот код, мы получим ошибку: $ cargo run Compiling drop-example v0.1.0 (file:///projects/drop-example)\nerror[E0040]: explicit use of destructor method --> src/main.rs:16:7 |\n16 | c.drop(); | ^^^^ explicit destructor calls not allowed |\nhelp: consider using `drop` function |\n16 | drop(c); | +++++ ~ For more information about this error, try `rustc --explain E0040`.\nerror: could not compile `drop-example` (bin \"drop-example\") due to 1 previous error Это сообщение об ошибке говорит, что мы не можем явно вызывать drop. В сообщении об ошибке используется понятие деструктор (destructor) , который является общим понятием программирования для функции, которая очищает образец. Деструктор подобен строителю , который создаёт образец. Функция drop в Ржавчина является определённым деструктором. Rust не позволяет обращаться к drop напрямую, потому что он все равно самостоятельно вызовет drop в конце main. Это вызвало бы ошибку double free , потому что в этом случае Ржавчина попытался бы дважды очистить одно и то же значение. Невозможно отключить самостоятельную подстановку вызова drop, когда значение выходит из области видимости, и нельзя вызвать способ drop напрямую. Поэтому, если нам нужно принудительно избавиться от значения раньше времени, следует использовать функцию std::mem::drop. Функция std::mem::drop отличается от способа drop особенности Drop. Мы вызываем её, передавая в качестве переменной значение, которое хотим принудительно уничтожить. Функция находится в прелюдии, поэтому мы можем изменить main в приложении 15-15 так, чтобы вызвать функцию drop, как показано в приложении 15-16: Файл: src/main.rs # struct CustomSmartPointer {\n# data: String,\n# }\n# # impl Drop for CustomSmartPointer {\n# fn drop(&mut self) {\n# println!(\"Dropping CustomSmartPointer with data `{}`!\", self.data);\n# }\n# }\n# fn main() { let c = CustomSmartPointer { data: String::from(\"some data\"), }; println!(\"CustomSmartPointer created.\"); drop(c); println!(\"CustomSmartPointer dropped before the end of main.\");\n} Приложение 15-16: Вызов std::mem::drop для принудительного удаления значения до того, как оно выйдет из области видимости Выполнение данного кода выведет следующий итог:: $ cargo run Compiling drop-example v0.1.0 (file:///projects/drop-example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s Running `target/debug/drop-example`\nCustomSmartPointer created.\nDropping CustomSmartPointer with data `some data`!\nCustomSmartPointer dropped before the end of main. Текст Dropping CustomSmartPointer with data some data!, напечатанный между CustomSmartPointer created. и текстом CustomSmartPointer dropped before the end of main., показывает, что код способа drop вызывается для удаления c в этой точке. Вы можете использовать код, указанный в выполнения особенности Drop, чтобы сделать очистку удобной и безопасной: например, вы можете использовать её для создания своего собственного управленца памяти! С помощью особенности Drop и системы владения Ржавчина не нужно целенаправленно заботиться о том, чтобы освобождать ресурсы, потому что Ржавчина делает это самостоятельно . Также не нужно беспокоиться о неполадках, возникающих в итоге случайной очистки значений, которые всё ещё используются: система владения, которая заверяет, что ссылки всегда действительны, также заверяет, что drop вызывается только один раз, когда значение больше не используется. После того, как мы познакомились с Box и свойствами умных указателей, познакомимся с другими умными указателями, определёнными в встроенной библиотеке.","breadcrumbs":"Умные указатели » Выполнение кода при очистке с помощью особенности Drop » Раннее удаление значения с помощью std::mem::drop","id":"277","title":"Раннее удаление значения с помощью std::mem::drop"},"278":{"body":"В большинстве случаев владение является однозначным: вы точно знаете, какая переменная владеет данным значением. Однако бывают случаи, когда у одного значения может быть несколько владельцев. Например, в Графовых устройствах может быть несколько рёбер, указывающих на один и тот же узел — таким образом, этот узел становится в действительности собственностью всех этих рёбер. Узел не подлежит удалению, за исключением тех случаев, когда на него не указывает ни одно ребро и, соответственно, у него нет владельцев. Вы должны включить множественное владение явно, используя вид Ржавчина Rc, который является аббревиатурой для подсчёта ссылок . Вид Rc отслеживает количество ссылок на значение, чтобы определить, используется ли оно ещё. Если ссылок на значение нет, значение может быть очищено и при этом ни одна ссылка не станет недействительной. Представьте себе Rc как телевизор в гостиной. Когда один человек входит, чтобы смотреть телевизор, он включает его. Другие могут войти в комнату и посмотреть телевизор. Когда последний человек покидает комнату, он выключает телевизор, потому что он больше не используется. Если кто-то выключит телевизор во время его просмотра другими, то оставшиеся телезрители устроят шум! Вид Rc используется, когда мы хотим разместить в куче некоторые данные для чтения несколькими частями нашей программы и не можем определить во время сборки, какая из частей завершит использование данных последней. Если бы мы знали, какая часть завершит использование последней то, мы могли бы сделать эту часть владельцем данных и вступили бы в силу обычные правила владения, применяемые во время сборки. Обратите внимание, что Rc используется только в однопоточных сценариях. Когда мы обсудим состязательность в главе 16, мы рассмотрим, как выполнять подсчёт ссылок во многопоточных программах.","breadcrumbs":"Умные указатели » Rc, умный указатель с подсчётом ссылок » Rc, умный указатель с подсчётом ссылок","id":"278","title":"Rc, умный указатель с подсчётом ссылок"},"279":{"body":"Давайте вернёмся к нашему примеру с cons списком в приложении 15-5. Напомним, что мы определили его с помощью вида Box. В этот раз мы создадим два списка, оба из которых будут владеть третьим списком. Мировозренческо это похоже на рисунок 15-3: Рисунок 15-3: Два списка, b и c, делят владение над третьим списком, a Мы создадим список a, содержащий 5 и затем 10. Затем мы создадим ещё два списка: b начинающийся с 3 и c начинающийся с 4. Оба списка b и c затем продолжать первый список a, содержащий 5 и 10. Другими словами, оба списка будут разделять первый список, содержащий 5 и 10. Попытка выполнить этот сценарий, используя определение List с видом Box не будет работать, как показано в приложении 15-17: Файл: src/main.rs enum List { Cons(i32, Box), Nil,\n} use crate::List::{Cons, Nil}; fn main() { let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); let b = Cons(3, Box::new(a)); let c = Cons(4, Box::new(a));\n} Приложение 15-17: Отображение того, что нельзя иметь два списка, использующих Box, которые пытаются совместно владеть третьим списком При сборки этого кода, мы получаем эту ошибку: $ cargo run Compiling cons-list v0.1.0 (file:///projects/cons-list)\nerror[E0382]: use of moved value: `a` --> src/main.rs:11:30 |\n9 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); | - move occurs because `a` has type `List`, which does not implement the `Copy` trait\n10 | let b = Cons(3, Box::new(a)); | - value moved here\n11 | let c = Cons(4, Box::new(a)); | ^ value used here after move For more information about this error, try `rustc --explain E0382`.\nerror: could not compile `cons-list` (bin \"cons-list\") due to 1 previous error Исходы Cons владеют данными, которые они содержат, поэтому, когда мы создаём список b, то a перемещается в b, а b становится владельцем a. Затем, мы пытаемся использовать a снова при создании c, но нам не разрешают, потому что a был перемещён. Мы могли бы изменить определение Cons, чтобы вместо этого хранить ссылки, но тогда нам пришлось бы указывать свойства времени жизни. Указывая свойства времени жизни, мы бы указали, что каждый элемент в списке будет жить как самое меньшее столько же, сколько и весь список. Это относится к элементам и спискам в приложении 15.17, но не во всех сценариях. Вместо этого мы изменим наше определение вида List так, чтобы использовать Rc вместо Box, как показано в приложении 15-18. Каждый исход Cons теперь будет содержать значение и вид Rc, указывающий на List. Когда мы создадим b то, вместо того чтобы стал владельцем a, мы будем клонировать Rc который содержит a, тем самым увеличивая количество ссылок с единицы до двойки и позволяя переменным a и b разделять владение на данные в виде Rc. Мы также клонируем a при создании c, увеличивая количество ссылок с двух до трёх. Каждый раз, когда мы вызываем Rc::clone, счётчик ссылок на данные внутри Rc будет увеличиваться и данные не будут очищены, если на них нет нулевых ссылок. Файл: src/main.rs enum List { Cons(i32, Rc), Nil,\n} use crate::List::{Cons, Nil};\nuse std::rc::Rc; fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); let b = Cons(3, Rc::clone(&a)); let c = Cons(4, Rc::clone(&a));\n} Приложение 15-18: Определение List, использующее Rc Нам нужно добавить указанию use, чтобы подключить вид Rc в область видимости, потому что он не входит в список самостоятельного подключения прелюдии. В main, мы создаём список владеющий 5 и 10, сохраняем его в новом Rc переменной a. Затем при создании b и c, мы называем функцию Rc::clone и передаём ей ссылку на Rc как переменная a. Мы могли бы вызвать a.clone(), а не Rc::clone(&a), но в Ржавчина принято использовать Rc::clone в таком случае. Внутренняя выполнение Rc::clone не делает глубокого повторения всех данных, как это происходит в видах большинства выполнений clone. Вызов Rc::clone только увеличивает счётчик ссылок, что не занимает много времени. Глубокое повторение данных может занимать много времени. Используя Rc::clone для подсчёта ссылок, можно визуально различать виды клонирования с глубоким повторением и клонирования, которые увеличивают количество ссылок. При поиске в коде неполадок с производительностью нужно рассмотреть только клонирование с глубоким повторением и пренебрегать вызовы Rc::clone .","breadcrumbs":"Умные указатели » Rc, умный указатель с подсчётом ссылок » Использование Rc для совместного использования данных","id":"279","title":"Использование Rc для совместного использования данных"},"28":{"body":"Когда дело, наконец, готов к исполнению, можно использовать приказ cargo build --release для его сборки с переработкой. Данная приказ создаёт исполняемый файл в папке target/release в отличии от папки target/debug . Переработки делают так, что Ржавчина код работает быстрее, но их включение увеличивает время сборки. По этой причине есть два отдельных профиля: один для разработки, когда нужно осуществлять сборку быстро и часто, и другой, для сборки конечной программы, которую будете отдавать пользователям, которая готова к работе и будет выполняться сверх быстро. Если вы замеряете время выполнения вашего кода, убедитесь, что собрали дело с переработкой cargo build --release и проверяете исполняемый файл из папки target/release .","breadcrumbs":"С чего начать » Hello, Cargo! » Сборка конечной исполнения (Release)","id":"28","title":"Сборка конечной исполнения (Release)"},"280":{"body":"Давайте изменим рабочий пример в приложении 15-18, чтобы увидеть как изменяется число ссылок при создании и удалении ссылок на Rc внутри переменной a. В приложении 15-19 мы изменим main так, чтобы она имела внутреннюю область видимости вокруг списка c; тогда мы сможем увидеть, как меняется счётчик ссылок при выходе c из внутренней области видимости. Файл: src/main.rs # enum List {\n# Cons(i32, Rc),\n# Nil,\n# }\n# # use crate::List::{Cons, Nil};\n# use std::rc::Rc;\n# fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); println!(\"count after creating a = {}\", Rc::strong_count(&a)); let b = Cons(3, Rc::clone(&a)); println!(\"count after creating b = {}\", Rc::strong_count(&a)); { let c = Cons(4, Rc::clone(&a)); println!(\"count after creating c = {}\", Rc::strong_count(&a)); } println!(\"count after c goes out of scope = {}\", Rc::strong_count(&a));\n} Приложение 15-19: Печать количества ссылок В каждой части программы, где количество ссылок меняется, мы выводим количество ссылок, которое получаем, вызывая функцию Rc::strong_count. Эта функция названа strong_count, а не count, потому что вид Rc также имеет weak_count; мы увидим, для чего используется weak_count в разделе \"Предотвращение замкнутых ссылок: Превращение Rc в Weak\". Код выводит в окно вывода: $ cargo run Compiling cons-list v0.1.0 (file:///projects/cons-list) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.45s Running `target/debug/cons-list`\ncount after creating a = 1\ncount after creating b = 2\ncount after creating c = 3\ncount after c goes out of scope = 2 Можно увидеть, что Rc в переменной a имеет начальный счётчик ссылок равный 1; затем каждый раз при вызове clone счётчик увеличивается на 1. Когда c выходит из области видимости, счётчик уменьшается на 1. Нам не нужно вызывать функцию уменьшения счётчика ссылок, как при вызове Rc::clone для увеличения счётчика ссылок: выполнение Drop самостоятельно уменьшает счётчик ссылок, когда значение Rc выходит из области видимости. В этом примере мы не наблюдаем того, что когда b, а затем a выходят из области видимости в конце main, счётчик становится равным 0, и Rc полностью очищается. Использование Rc позволяет одному значению иметь несколько владельцев, а счётчик заверяет, что значение остаётся действительным до тех пор, пока любой из владельцев ещё существует. С помощью неизменяемых ссылок, вид Rc позволяет обмениваться данными между несколькими частями вашей программы только для чтения данных. Если вид Rc позволял бы иметь несколько изменяемых ссылок, вы могли бы нарушить одно из правил заимствования, описанных в главе 4: множественные изменяемые заимствования в одном и том же месте могут вызвать гонки данных (data races) и несогласованность данных. Но возможность изменять данные очень полезна! В следующем разделе мы обсудим образец внутренней изменчивости и вид RefCell, который можно использовать вместе с Rc для работы с этим ограничением.","breadcrumbs":"Умные указатели » Rc, умный указатель с подсчётом ссылок » Клонирование Rc увеличивает количество ссылок","id":"280","title":"Клонирование Rc увеличивает количество ссылок"},"281":{"body":"Внутренняя изменяемость - это образец разработки Rust, который позволяет вам изменять данные даже при наличии неизменяемых ссылок на эти данные; обычно такое действие запрещено правилами заимствования. Для изменения данных образец использует unsafe код внутри устройства данных, чтобы обойти обычные правила Rust, управляющие изменяемость и заимствование. Небезопасный (unsafe) код даёт понять сборщику, что мы самостоятельно следим за соблюдением этих правил, а не полагаемся на то, что сборщик будет делать это для нас; подробнее о небезопасном коде мы поговорим в главе 19. Мы можем использовать виды, в которых применяется образец внутренней изменяемости, только если мы можем обеспечить, что правила заимствования будут соблюдаться во время выполнения, несмотря на то, что сборщик не сможет этого обеспечить. В этом случае небезопасный код оборачивается безопасным API, и внешне вид остаётся неизменяемым. Давайте изучим данную подход с помощью вида данных RefCell, который выполняет этот образец.","breadcrumbs":"Умные указатели » RefCell и внутренняя изменяемость » RefCell и образец внутренней изменяемости","id":"281","title":"RefCell и образец внутренней изменяемости"},"282":{"body":"В отличие от Rc вид RefCell предоставляет единоличное владение данными, которые он содержит. В чем же отличие вида RefCell от Box? Давайте вспомним правила заимствования из Главы 4: В любой мгновение времени вы можете иметь либо одну изменяемую ссылку либо сколько угодно неизменяемых ссылок (но не оба вида ссылок одновременно). Ссылки всегда должны быть действительными. С помощью ссылок и вида Box неизменные величины правил заимствования применяются на этапе сборки. С помощью RefCell они применяются во время работы программы . Если вы нарушите эти правила, работая с ссылками, то будет ошибка сборки. Если вы работаете с RefCell и нарушите эти правила, то программа вызовет панику и завершится. Преимущества проверки правил заимствования во время сборки заключаются в том, что ошибки будут обнаруживаться раньше - ещё в этапе разработки, а производительность во время выполнения не пострадает, поскольку весь анализ завершён заранее. По этим причинам проверка правил заимствования во время сборки является лучшим выбором в большинстве случаев, и именно поэтому она используется в Ржавчина по умолчанию. Преимущество проверки правил заимствования во время выполнения заключается в том, что определённые сценарии, безопасные для памяти, разрешаются там, где они были бы запрещены проверкой во время сборки. Постоянной анализ, как и сборщик Rust, по своей сути устоявшийся. Некоторые свойства кода невозможно обнаружить, анализируя код: самый известный пример - неполадка остановки, которая выходит за рамки этой книги, но является важной темой для исследования. Поскольку некоторый анализ невозможен, то если сборщик Ржавчина не может быть уверен, что код соответствует правилам владения, он может отклонить правильную программу; таким образом он является консервативным. Если Ржавчина принял неправильную программу, то пользователи не смогут доверять заверениям, которые даёт Rust. Однако, если Ржавчина отклонит правильную программу, то программист будет испытывать неудобства, но ничего катастрофического не произойдёт. Вид RefCell полезен, когда вы уверены, что ваш код соответствует правилам заимствования, но сборщик не может понять и обеспечить этого. Подобно виду Rc, вид RefCell предназначен только для использования в однопоточных сценариях и выдаст ошибку времени сборки, если вы попытаетесь использовать его в многопоточном среде. Мы поговорим о том, как получить возможность RefCell во многопоточной программе в главе 16. Вот список причин выбора видов Box, Rc или RefCell: Вид Rc разрешает множественное владение одними и теми же данными; виды Box и RefCell разрешают иметь единственных владельцев. Вид Box разрешает неизменяемые или изменяемые владения, проверенные при сборки; вид Rc разрешает только неизменяемые владения, проверенные при сборки; вид RefCell разрешает неизменяемые или изменяемые владения, проверенные во время выполнения. Поскольку RefCell разрешает изменяемые заимствования, проверенные во время выполнения, можно изменять значение внутри RefCell даже если RefCell является неизменным. Изменение значения внутри неизменного значения является образцом внутренней изменяемости (interior mutability). Давайте посмотрим на случай, в которой внутренняя изменяемость полезна и рассмотрим, как это возможно.","breadcrumbs":"Умные указатели » RefCell и внутренняя изменяемость » Применение правил заимствования во время выполнения с помощью RefCell","id":"282","title":"Применение правил заимствования во время выполнения с помощью RefCell"},"283":{"body":"Следствием правил заимствования является то, что когда у вас есть неизменяемое значение, вы не можете заимствовать его с изменением. Например, этот код не будет собираться: fn main() { let x = 5; let y = &mut x;\n} Если вы попытаетесь собрать этот код, вы получите следующую ошибку: $ cargo run Compiling borrowing v0.1.0 (file:///projects/borrowing)\nerror[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable --> src/main.rs:3:13 |\n3 | let y = &mut x; | ^^^^^^ cannot borrow as mutable |\nhelp: consider changing this to be mutable |\n2 | let mut x = 5; | +++ For more information about this error, try `rustc --explain E0596`.\nerror: could not compile `borrowing` (bin \"borrowing\") due to 1 previous error Однако бывают случаи, в которых было бы полезно, чтобы предмет мог изменять себя при помощи своих способов, но казался неизменным для прочего кода. Код вне способов этого предмета не должен иметь возможности изменять его содержимое. Использование RefCell - один из способов получить возможность внутренней изменяемости, но при этом RefCell не позволяет полностью обойти правила заимствования: средство проверки правил заимствования в сборщике позволяет эту внутреннюю изменяемость, однако правила заимствования проверяются во время выполнения. Если вы нарушите правила, то вместо ошибки сборки вы получите panic!. Давайте разберём опытный пример, в котором мы можем использовать RefCell для изменения неизменяемого значения и посмотрим, почему это полезно. Исход использования внутренней изменяемости: мок предметы Иногда во время проверки программист использует один вид вместо другого для того, чтобы проверить определённое поведение и убедиться, что оно выполнено правильно. Такой вид-заместитель называется проверочным повторителем . Воспринимайте его как «каскадёра» в кинематографе, когда повторитель заменяет актёра для выполнения определённой сложной сцены. Проверочные повторители заменяют другие виды при выполнении проверок. Инсценировочные (mock) предметы — это особый вид проверочных повторителей, которые сохраняют данные происходящих во время проверки действий тем самым позволяя вам убедиться впоследствии, что все действия были выполнены правильно. В Ржавчина нет предметов в том же смысле, в каком они есть в других языках и в Ржавчина нет возможности мок предметов, встроенных в обычную библиотеку, как в некоторых других языках. Однако вы определённо можете создать устройство, которая будет служить тем же целям, что и мок предмет. Вот сценарий, который мы будем проверять: мы создадим библиотеку, которая отслеживает значение по отношению к заранее определённому наивысшему значению и отправляет сообщения в зависимости от того, насколько текущее значение находится близко к такому наивысшему значению. Эта библиотека может использоваться, например, для отслеживания квоты количества вызовов API пользователя, которые ему разрешено делать. Наша библиотека будет предоставлять только функции отслеживания того, насколько близко к наивысшему значению находится значение и какие сообщения должны быть внутри в этот мгновение. Ожидается, что приложения, использующие нашу библиотеку, предоставят рычаг для отправки сообщений: приложение может поместить сообщение в приложение, отправить электронное письмо, отправить текстовое сообщение или что-то ещё. Библиотеке не нужно знать эту подробность. Все что ему нужно - это что-то, что выполняет особенность, который мы предоставим с названием Messenger. Приложение 15-20 показывает код библиотеки: Файл: src/lib.rs pub trait Messenger { fn send(&self, msg: &str);\n} pub struct LimitTracker<'a, T: Messenger> { messenger: &'a T, value: usize, max: usize,\n} impl<'a, T> LimitTracker<'a, T>\nwhere T: Messenger,\n{ pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> { LimitTracker { messenger, value: 0, max, } } pub fn set_value(&mut self, value: usize) { self.value = value; let percentage_of_max = self.value as f64 / self.max as f64; if percentage_of_max >= 1.0 { self.messenger.send(\"Error: You are over your quota!\"); } else if percentage_of_max >= 0.9 { self.messenger .send(\"Urgent warning: You've used up over 90% of your quota!\"); } else if percentage_of_max >= 0.75 { self.messenger .send(\"Warning: You've used up over 75% of your quota!\"); } }\n} Приложение 15-20: Библиотека для отслеживания степени приближения того или иного значения к наиболее допустимой величине и предупреждения, в случае если значение достигает определённого уровня Одна важная часть этого кода состоит в том, что особенность Messenger имеет один способ send, принимающий переменнойми неизменяемую ссылку на self и текст сообщения. Он является внешней оболочкой, который должен иметь наш мок предмет. Другой важной частью является то, что мы хотим проверить поведение способа set_value у вида LimitTracker. Мы можем изменить значение, которое передаём свойствоом value, но set_value ничего не возвращает и нет основания, чтобы мы могли бы проверить утверждения о выполнении способа. Мы хотим иметь возможность сказать, что если мы создаём LimitTracker с чем-то, что выполняет особенность Messenger и с определённым значением для max, то когда мы передаём разные числа в переменной value образец self.messenger отправляет соответствующие сообщения. Нам нужен мок предмет, который вместо отправки электронного письма или текстового сообщения будет отслеживать сообщения, которые были ему поручены для отправки через send. Мы можем создать новый образец мок предмета. создать LimitTracker с использованием мок предмет для него, вызвать способ set_value у образца LimitTracker, а затем проверить, что мок предмет имеет ожидаемое сообщение. В приложении 15-21 показана попытка выполнить мок предмет, чтобы сделать именно то что хотим, но анализатор заимствований не разрешит такой код: Файл: src/lib.rs # pub trait Messenger {\n# fn send(&self, msg: &str);\n# }\n# # pub struct LimitTracker<'a, T: Messenger> {\n# messenger: &'a T,\n# value: usize,\n# max: usize,\n# }\n# # impl<'a, T> LimitTracker<'a, T>\n# where\n# T: Messenger,\n# {\n# pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {\n# LimitTracker {\n# messenger,\n# value: 0,\n# max,\n# }\n# }\n# # pub fn set_value(&mut self, value: usize) {\n# self.value = value;\n# # let percentage_of_max = self.value as f64 / self.max as f64;\n# # if percentage_of_max >= 1.0 {\n# self.messenger.send(\"Error: You are over your quota!\");\n# } else if percentage_of_max >= 0.9 {\n# self.messenger\n# .send(\"Urgent warning: You've used up over 90% of your quota!\");\n# } else if percentage_of_max >= 0.75 {\n# self.messenger\n# .send(\"Warning: You've used up over 75% of your quota!\");\n# }\n# }\n# }\n# #[cfg(test)]\nmod tests { use super::*; struct MockMessenger { sent_messages: Vec, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: vec![], } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { let mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mock_messenger, 100); limit_tracker.set_value(80); assert_eq!(mock_messenger.sent_messages.len(), 1); }\n} Приложение 15-21: Попытка выполнить MockMessenger, которая не была принята рычагом проверки заимствований Этот проверочный код определяет устройство MockMessenger, в которой есть поле sent_messages со значениями вида Vec из String для отслеживания сообщений, которые поручены устройстве для отправки. Мы также определяем сопряженную функцию new, чтобы было удобно создавать новые образцы MockMessenger, которые создаются с пустым списком сообщений. Затем мы выполняем особенность Messenger для вида MockMessenger, чтобы передать MockMessenger в LimitTracker. В ярлыке способа send мы принимаем сообщение для передачи в качестве свойства и сохраняем его в MockMessenger внутри списка sent_messages. В этом проверке мы проверяем, что происходит, когда LimitTracker сказано установить value в значение, превышающее 75 процентов от значения max. Сначала мы создаём новый MockMessenger, который будет иметь пустой список сообщений. Затем мы создаём новый LimitTracker и передаём ему ссылку на новый MockMessenger и max значение равное 100. Мы вызываем способ set_value у LimitTracker со значением 80, что составляет более 75 процентов от 100. Затем мы с помощью утверждения проверяем, что MockMessenger должен содержать одно сообщение из списка внутренних сообщений. Однако с этим проверкой есть одна неполадка, показанная ниже: $ cargo test Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)\nerror[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference --> src/lib.rs:58:13 |\n58 | self.sent_messages.push(String::from(message)); | ^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable |\nhelp: consider changing this to be a mutable reference in the `impl` method and the `trait` definition |\n2 ~ fn send(&mut self, msg: &str);\n3 | } ...\n56 | impl Messenger for MockMessenger {\n57 ~ fn send(&mut self, message: &str) { | For more information about this error, try `rustc --explain E0596`.\nerror: could not compile `limit-tracker` (lib test) due to 1 previous error Мы не можем изменять MockMessenger для отслеживания сообщений, потому что способ send принимает неизменяемую ссылку на self. Мы также не можем принять предложение из текста ошибки, чтобы использовать &mut self, потому что тогда ярлык send не будет соответствовать ярлыке в определении особенности Messenger (не стесняйтесь попробовать и посмотреть, какое сообщение об ошибке получите вы). Это случаей, в которой внутренняя изменяемость может помочь! Мы сохраним sent_messages внутри вида RefCell, а затем в способе send сообщение сможет изменить список sent_messages для хранения сообщений, которые мы видели. Приложение 15-22 показывает, как это выглядит: Файл: src/lib.rs # pub trait Messenger {\n# fn send(&self, msg: &str);\n# }\n# # pub struct LimitTracker<'a, T: Messenger> {\n# messenger: &'a T,\n# value: usize,\n# max: usize,\n# }\n# # impl<'a, T> LimitTracker<'a, T>\n# where\n# T: Messenger,\n# {\n# pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {\n# LimitTracker {\n# messenger,\n# value: 0,\n# max,\n# }\n# }\n# # pub fn set_value(&mut self, value: usize) {\n# self.value = value;\n# # let percentage_of_max = self.value as f64 / self.max as f64;\n# # if percentage_of_max >= 1.0 {\n# self.messenger.send(\"Error: You are over your quota!\");\n# } else if percentage_of_max >= 0.9 {\n# self.messenger\n# .send(\"Urgent warning: You've used up over 90% of your quota!\");\n# } else if percentage_of_max >= 0.75 {\n# self.messenger\n# .send(\"Warning: You've used up over 75% of your quota!\");\n# }\n# }\n# }\n# #[cfg(test)]\nmod tests { use super::*; use std::cell::RefCell; struct MockMessenger { sent_messages: RefCell>, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: RefCell::new(vec![]), } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.borrow_mut().push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { // --snip--\n# let mock_messenger = MockMessenger::new();\n# let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);\n# # limit_tracker.set_value(80); assert_eq!(mock_messenger.sent_messages.borrow().len(), 1); }\n} Приложение 15-22: Использование RefCell для изменения внутреннего значения, в то время как внешнее значение считается неизменяемым Поле sent_messages теперь имеет вид RefCell> вместо Vec. В функции new мы создаём новый образец RefCell> для пустого вектора. Для выполнения способа send первый свойство по-прежнему является неизменяемым для заимствования self, которое соответствует определению особенности. Мы вызываем borrow_mut для RefCell> в self.sent_messages, чтобы получить изменяемую ссылку на значение внутри RefCell>, которое является вектором. Затем мы можем вызвать push у изменяемой ссылки на вектор, чтобы отслеживать сообщения, отправленные во время проверки. Последнее изменение, которое мы должны сделать, заключается в утверждении для проверки: чтобы увидеть, сколько элементов находится во внутреннем векторе, мы вызываем способ borrow у RefCell>, чтобы получить неизменяемую ссылку на внутренний вектор сообщений. Теперь, когда вы увидели как использовать RefCell, давайте изучим как он работает! Отслеживание заимствований во время выполнения с помощью RefCell При создании неизменных и изменяемых ссылок мы используем правила написания & и &mut соответственно. У вида RefCell, мы используем способы borrow и borrow_mut, которые являются частью безопасного API, который принадлежит RefCell. Способ borrow возвращает вид умного указателя Ref, способ borrow_mut возвращает вид умного указателя RefMut. Оба вида выполняют особенность Deref, поэтому мы можем рассматривать их как обычные ссылки. Вид RefCell отслеживает сколько умных указателей Ref и RefMut активны в данное время. Каждый раз, когда мы вызываем borrow, вид RefCell увеличивает количество активных заимствований. Когда значение Ref выходит из области видимости, то количество неизменяемых заимствований уменьшается на единицу. Как и с правилами заимствования во время сборки, RefCell позволяет иметь много неизменяемых заимствований или одно изменяемое заимствование в любой мгновение времени. Если попытаться нарушить эти правила, то вместо получения ошибки сборщика, как это было бы со ссылками, выполнение RefCell будет вызывать панику во время выполнения. В приложении 15-23 показана изменение выполнения send из приложения 15-22. Мы намеренно пытаемся создать два изменяемых заимствования активных для одной и той же области видимости, чтобы показать как RefCell не позволяет нам делать так во время выполнения. Файл: src/lib.rs # pub trait Messenger {\n# fn send(&self, msg: &str);\n# }\n# # pub struct LimitTracker<'a, T: Messenger> {\n# messenger: &'a T,\n# value: usize,\n# max: usize,\n# }\n# # impl<'a, T> LimitTracker<'a, T>\n# where\n# T: Messenger,\n# {\n# pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {\n# LimitTracker {\n# messenger,\n# value: 0,\n# max,\n# }\n# }\n# # pub fn set_value(&mut self, value: usize) {\n# self.value = value;\n# # let percentage_of_max = self.value as f64 / self.max as f64;\n# # if percentage_of_max >= 1.0 {\n# self.messenger.send(\"Error: You are over your quota!\");\n# } else if percentage_of_max >= 0.9 {\n# self.messenger\n# .send(\"Urgent warning: You've used up over 90% of your quota!\");\n# } else if percentage_of_max >= 0.75 {\n# self.messenger\n# .send(\"Warning: You've used up over 75% of your quota!\");\n# }\n# }\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# use std::cell::RefCell;\n# # struct MockMessenger {\n# sent_messages: RefCell>,\n# }\n# # impl MockMessenger {\n# fn new() -> MockMessenger {\n# MockMessenger {\n# sent_messages: RefCell::new(vec![]),\n# }\n# }\n# }\n# impl Messenger for MockMessenger { fn send(&self, message: &str) { let mut one_borrow = self.sent_messages.borrow_mut(); let mut two_borrow = self.sent_messages.borrow_mut(); one_borrow.push(String::from(message)); two_borrow.push(String::from(message)); } }\n# # #[test]\n# fn it_sends_an_over_75_percent_warning_message() {\n# let mock_messenger = MockMessenger::new();\n# let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);\n# # limit_tracker.set_value(80);\n# # assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);\n# }\n# } Приложение 15-23: Создание двух изменяемых ссылок в одной области видимости, чтобы убедиться, что RefCell вызовет панику Мы создаём переменную one_borrow для умного указателя RefMut возвращаемого из способа borrow_mut. Затем мы создаём другое изменяемое заимствование таким же образом в переменной two_borrow. Это создаёт две изменяемые ссылки в одной области видимости, что недопустимо. Когда мы запускаем проверки для нашей библиотеки, код в приложении 15-23 собирается без ошибок, но проверка завершится неудачно: $ cargo test Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s Running unittests src/lib.rs (target/debug/deps/limit_tracker-e599811fa246dbde) running 1 test\ntest tests::it_sends_an_over_75_percent_warning_message ... FAILED failures: ---- tests::it_sends_an_over_75_percent_warning_message stdout ----\nthread 'tests::it_sends_an_over_75_percent_warning_message' panicked at src/lib.rs:60:53:\nalready borrowed: BorrowMutError\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::it_sends_an_over_75_percent_warning_message test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib` Обратите внимание, что код вызвал панику с сообщением already borrowed: BorrowMutError. Вот так вид RefCell обрабатывает нарушения правил заимствования во время выполнения. Решение отлавливать ошибки заимствования во время выполнения, а не во время сборки, как мы сделали здесь, означает, что вы возможно будете находить ошибки в своём коде на более поздних этапах разработки: возможно, не раньше, чем ваш код будет развернут в рабочем окружении. Кроме того, ваш код будет иметь небольшие потери производительности в этапе работы, поскольку заимствования будут отслеживаться во время выполнения, а не во время сборки. Однако использование RefCell позволяет написать предмет-имитатор, который способен изменять себя, чтобы сохранять сведения о тех значениях, которые он получал, пока вы использовали его в среде, где разрешены только неизменяемые значения. Вы можете использовать RefCell, несмотря на его недостатки, чтобы получить больше возможности, чем дают обычные ссылки.","breadcrumbs":"Умные указатели » RefCell и внутренняя изменяемость » Внутренняя изменяемость: изменяемое заимствование неизменяемого значения","id":"283","title":"Внутренняя изменяемость: изменяемое заимствование неизменяемого значения"},"284":{"body":"Обычный способ использования RefCell заключается в его сочетании с видом Rc. Напомним, что вид Rc позволяет иметь нескольких владельцев некоторых данных, но даёт только неизменяемый доступ к этим данным. Если у вас есть Rc, который внутри содержит вид RefCell, вы можете получить значение, которое может иметь несколько владельцев и которое можно изменять! Например, вспомните пример cons списка приложения 15-18, где мы использовали Rc, чтобы несколько списков могли совместно владеть другим списком. Поскольку Rc содержит только неизменяемые значения, мы не можем изменить ни одно из значений в списке после того, как мы их создали. Давайте добавим вид RefCell, чтобы получить возможность изменять значения в списках. В приложении 15-24 показано использование RefCell в определении Cons так, что мы можем изменить значение хранящееся во всех списках: Файл: src/main.rs #[derive(Debug)]\nenum List { Cons(Rc>, Rc), Nil,\n} use crate::List::{Cons, Nil};\nuse std::cell::RefCell;\nuse std::rc::Rc; fn main() { let value = Rc::new(RefCell::new(5)); let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil))); let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a)); let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a)); *value.borrow_mut() += 10; println!(\"a after = {a:?}\"); println!(\"b after = {b:?}\"); println!(\"c after = {c:?}\");\n} Приложение 15-24: Использование Rc> для создания List, который мы можем изменять Мы создаём значение, которое является образцом Rc> и сохраняем его в переменной с именем value, чтобы получить к ней прямой доступ позже. Затем мы создаём List в переменной a с исходом Cons, который содержит value. Нам нужно вызвать клонирование value, так как обе переменные a и value владеют внутренним значением 5, а не передают владение из value в переменную a или не выполняют заимствование с помощью a переменной value. Мы оборачиваем список у переменной a в вид Rc, поэтому при создании списков в переменные b и c они оба могут ссылаться на a, что мы и сделали в приложении 15-18. После создания списков a, b и c мы хотим добавить 10 к значению в value. Для этого вызовем borrow_mut у value, который использует функцию самостоятельного разыменования, о которой мы говорили в главе 5 (см. раздел \"Где находится оператор ->?\" ) во внутреннее значение RefCell. Способ borrow_mut возвращает умный указатель RefMut, и мы используя оператор разыменования, изменяем внутреннее значение. Когда мы печатаем a, b и c то видим, что все они имеют изменённое значение равное 15, а не 5: $ cargo run Compiling cons-list v0.1.0 (file:///projects/cons-list) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.63s Running `target/debug/cons-list`\na after = Cons(RefCell { value: 15 }, Nil)\nb after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))\nc after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil)) Эта техника довольно изящна! Используя RefCell, мы получаем внешне неизменяемое значение List. Но мы можем использовать способы RefCell, которые предоставляют доступ к его внутренностям, чтобы мы могли изменять наши данные, когда это необходимо. Проверка правил заимствования во время выполнения защищает нас от гонок данных, и иногда стоит немного пожертвовать производительностью ради такой гибкости наших устройств данных. Обратите внимание, что RefCell не работает для многопоточного кода! Mutex - это thread-safe исполнение RefCell, а Mutex мы обсудим в главе 16.","breadcrumbs":"Умные указатели » RefCell и внутренняя изменяемость » Наличие нескольких владельцев изменяемых данных путём объединения видов Rc и RefCell","id":"284","title":"Наличие нескольких владельцев изменяемых данных путём объединения видов Rc и RefCell"},"285":{"body":"Заверения безопасности памяти в Ржавчина затрудняют, но не делают невозможным случайное выделение памяти, которое никогда не очищается (известное как утечка памяти ). Полное предотвращение утечек памяти не является одной из заверений Rust, а это означает, что утечки памяти безопасны в Rust. Мы видим, что Ржавчина допускает утечку памяти с помощью Rc и RefCell: можно создавать ссылки, в которых элементы ссылаются друг на друга в цикле. Это создаёт утечки памяти, потому что счётчик ссылок каждого элемента в цикле никогда не достигнет 0, а значения никогда не будут удалены.","breadcrumbs":"Умные указатели » Ссылочные циклы могут привести к утечке памяти » Ссылочные замыкания могут приводить к утечке памяти","id":"285","title":"Ссылочные замыкания могут приводить к утечке памяти"},"286":{"body":"Давайте посмотрим, как может произойти случаей ссылочного замыкания и как её предотвратить, начиная с определения перечисления List и способа tail в приложении 15-25: Файл: src/main.rs use crate::List::{Cons, Nil};\nuse std::cell::RefCell;\nuse std::rc::Rc; #[derive(Debug)]\nenum List { Cons(i32, RefCell>), Nil,\n} impl List { fn tail(&self) -> Option<&RefCell>> { match self { Cons(_, item) => Some(item), Nil => None, } }\n} fn main() {} Приложение 15-25: Объявление cons list, который содержит RefCell, чтобы мы могли изменять то, на что ссылается образец Cons Мы используем другую вариацию определения List из приложения 15-5. Второй элемент в исходе Cons теперь RefCell>, что означает, что вместо возможности менять значение i32, как мы делали в приложении 15-24, мы хотим менять значение List, на которое указывает исход Cons. Мы также добавляем способ tail, чтобы нам было удобно обращаться ко второму элементу, если у нас есть исход Cons. В приложении 15-26 мы добавляем main функцию, которая использует определения приложения 15-25. Этот код создаёт список в переменной a и список b, который указывает на список a. Затем он изменяет список внутри a так, чтобы он указывал на b, создавая ссылочное замыкание. В коде есть указания println!, чтобы показать значения счётчиков ссылок в различных точках этого этапа. Файл: src/main.rs # use crate::List::{Cons, Nil};\n# use std::cell::RefCell;\n# use std::rc::Rc;\n# # #[derive(Debug)]\n# enum List {\n# Cons(i32, RefCell>),\n# Nil,\n# }\n# # impl List {\n# fn tail(&self) -> Option<&RefCell>> {\n# match self {\n# Cons(_, item) => Some(item),\n# Nil => None,\n# }\n# }\n# }\n# fn main() { let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); println!(\"a initial rc count = {}\", Rc::strong_count(&a)); println!(\"a next item = {:?}\", a.tail()); let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a)))); println!(\"a rc count after b creation = {}\", Rc::strong_count(&a)); println!(\"b initial rc count = {}\", Rc::strong_count(&b)); println!(\"b next item = {:?}\", b.tail()); if let Some(link) = a.tail() { *link.borrow_mut() = Rc::clone(&b); } println!(\"b rc count after changing a = {}\", Rc::strong_count(&b)); println!(\"a rc count after changing a = {}\", Rc::strong_count(&a)); // Uncomment the next line to see that we have a cycle; // it will overflow the stack // println!(\"a next item = {:?}\", a.tail());\n} Приложение 15-26: Создание ссылочного цикла из двух значений List, указывающих друг на друга Мы создаём образец Rc содержащий значение List в переменной a с начальным списком 5, Nil. Затем мы создаём образец Rc содержащий другое значение List в переменной b, которое содержит значение 10 и указывает на список в a. Мы меняем a так, чтобы он указывал на b вместо Nil, создавая зацикленность. Мы делаем это с помощью способа tail, чтобы получить ссылку на RefCell> из переменной a, которую мы помещаем в переменную link. Затем мы используем способ borrow_mut из вида RefCell>, чтобы изменить внутреннее значение вида Rc, содержащего начальное значение Nil на значение вида Rc взятое из переменной b. Когда мы запускаем этот код, оставив последний println! с примечаниями в данный мгновение, мы получим вывод: $ cargo run Compiling cons-list v0.1.0 (file:///projects/cons-list) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.53s Running `target/debug/cons-list`\na initial rc count = 1\na next item = Some(RefCell { value: Nil })\na rc count after b creation = 2\nb initial rc count = 1\nb next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })\nb rc count after changing a = 2\na rc count after changing a = 2 Количество ссылок на образцы Rc как в a, так и в b равно 2 после того, как мы заменили список в a на ссылку на b. В конце main Ржавчина уничтожает переменную b, что уменьшает количество ссылок на Rc из b с 2 до 1. Память, которую Rc занимает в куче, не будет освобождена в этот мгновение, потому что количество ссылок на неё равно 1, а не 0. Затем Ржавчина удаляет a, что уменьшает количество ссылок образца Rc в a с 2 до 1. Память этого образца также не может быть освобождена, поскольку другой образец Rc по-прежнему ссылается на него. Таким образом, память, выделенная для списка не будет освобождена никогда. Чтобы наглядно представить этот цикл ссылок, мы создали диаграмму на рисунке 15-4. Рисунок 15-4: Ссылочный цикл списков a и b, указывающих друг на друга Если вы удалите последний примечание с println! и запустите программу, Ржавчина будет пытаться печатать зацикленность в a, указывающей на b, указывающей на a и так далее, пока не переполниться обойма. По сравнению с существующей программой, последствия создания цикла ссылок в этом примере не так страшны: сразу после создания цикла ссылок программа завершается. Однако если более сложная программа выделит много памяти в цикле и будет удерживать её в течение длительного времени, программа будет потреблять больше памяти, чем ей нужно, и может перенапрячь систему, что приведёт к исчерпанию доступной памяти. Вызвать образование ссылочной зацикленности не просто, но и не невозможно. Если у вас есть значения RefCell которые содержат значения Rc или подобные вложенные сочетания видов с внутренней изменчивостью и подсчётом ссылок, вы должны убедиться, что вы не создаёте зацикленность; Вы не можете полагаться на то, что Ржавчина их обнаружит. Создание ссылочной зацикленности являлось бы логической ошибкой в программе, для которой вы должны использовать самостоятельно е проверки, проверку кода и другие опытов разработки программного обеспечения для её уменьшения. Другое решение для избежания ссылочной зацикленности - это ресоздание ваших устройств данных, чтобы некоторые ссылки выражали владение, а другие - отсутствие владения. В итоге можно иметь циклы, построенные на некоторых отношениях владения и некоторые не основанные на отношениях владения, тогда только отношения владения влияют на то, можно ли удалить значение. В приложении 15-25 мы всегда хотим, чтобы исходы Cons владели своим списком, поэтому ресоздание устройства данных невозможна. Давайте рассмотрим пример с использованием графов, состоящих из родительских и дочерних узлов, чтобы увидеть, когда отношения владения не являются подходящим способом предотвращения ссылочной зацикленности.","breadcrumbs":"Умные указатели » Ссылочные циклы могут привести к утечке памяти » Создание ссылочного замыкания","id":"286","title":"Создание ссылочного замыкания"},"287":{"body":"До сих пор мы выясняли, что вызов Rc::clone увеличивает strong_count образца Rc, а образец Rc удаляется, только если его strong_count равен 0. Вы также можете создать слабую ссылку на значение внутри образца Rc, вызвав Rc::downgrade и передав ссылку на Rc. Сильные ссылки - это то с помощью чего вы можете поделиться владением образца Rc. Слабые ссылки не отражают связи владения, и их подсчёт не влияет на то, когда образец Rc будет очищен. Они не приведут к ссылочному циклу, потому что любой цикл, включающий несколько слабых ссылок, будет разорван, как только количество сильных ссылок для задействованных значений станет равным 0. Когда вы вызываете Rc::downgrade, вы получаете умный указатель вида Weak. Вместо того чтобы увеличить strong_count в образце Rc на 1, вызов Rc::downgrade увеличивает weak_count на 1. Вид Rc использует weak_count для отслеживания количества существующих ссылок Weak, подобно strong_count. Разница в том, что weak_count не должен быть равен 0, чтобы образец Rc мог быть удалён. Поскольку значение, на которое ссылается Weak могло быть удалено, то необходимо убедиться, что это значение все ещё существует, чтобы сделать что-либо со значением на которое указывает Weak. Делайте это вызывая способ upgrade у образца вида Weak, который вернёт Option>. Вы получите итог Some, если значение Rc ещё не было удалено и итог None, если значение Rc было удалено. Поскольку upgrade возвращает вид Option, Ржавчина обеспечит обработку обоих случаев Some и None и не будет неправильного указателя. В качестве примера, вместо того чтобы использовать список чей элемент знает только о следующем элементе, мы создадим дерево, чьи элементы знают о своих дочерних элементах и о своих родительских элементах. Создание древовидной устройства данных: Node с дочерними узлами Для начала мы построим дерево с узлами, которые знают о своих дочерних узлах. Мы создадим устройство с именем Node, которая будет содержать собственное значение i32, а также ссылки на его дочерние значения Node: Файл: src/main.rs use std::cell::RefCell;\nuse std::rc::Rc; #[derive(Debug)]\nstruct Node { value: i32, children: RefCell>>,\n}\n# # fn main() {\n# let leaf = Rc::new(Node {\n# value: 3,\n# children: RefCell::new(vec![]),\n# });\n# # let branch = Rc::new(Node {\n# value: 5,\n# children: RefCell::new(vec![Rc::clone(&leaf)]),\n# });\n# } Мы хотим, чтобы Node владел своими дочерними узлами и мы хотим поделиться этим владением с переменными так, чтобы мы могли напрямую обращаться к каждому Node в дереве. Для этого мы определяем внутренние элементы вида Vec как значения вида Rc. Мы также хотим изменять те узлы, которые являются дочерними по отношению к другому узлу, поэтому у нас есть вид RefCell в поле children оборачивающий вид Vec>. Далее мы будем использовать наше определение устройства и создадим один образец Node с именем leaf со значением 3 и без дочерних элементов, а другой образец с именем branch со значением 5 и leaf в качестве одного из его дочерних элементов, как показано в приложении 15-27: Файл: src/main.rs # use std::cell::RefCell;\n# use std::rc::Rc;\n# # #[derive(Debug)]\n# struct Node {\n# value: i32,\n# children: RefCell>>,\n# }\n# fn main() { let leaf = Rc::new(Node { value: 3, children: RefCell::new(vec![]), }); let branch = Rc::new(Node { value: 5, children: RefCell::new(vec![Rc::clone(&leaf)]), });\n} Приложение 15-27: Создание узла leaf без дочерних элементов и узла branch с leaf в качестве одного из дочерних элементов Мы клонируем содержимое Rc из переменной leaf и сохраняем его в переменной branch, что означает, что Node в leaf теперь имеет двух владельцев: leaf и branch. Мы можем получить доступ из branch к leaf через обращение branch.children, но нет способа добраться из leaf к branch. Причина в том, что leaf не имеет ссылки на branch и не знает, что они связаны. Мы хотим, чтобы leaf знал, что branch является его родителем. Мы сделаем это далее. Добавление ссылки от ребёнка к его родителю Для того, чтобы дочерний узел знал о своём родительском узле нужно добавить поле parent в наше определение устройства Node. Неполадкав том, чтобы решить, каким должен быть вид parent. Мы знаем, что он не может содержать Rc, потому что это создаст ссылочную зацикленность с leaf.parent указывающей на branch и branch.children, указывающей на leaf, что приведёт к тому, что их значения strong_count никогда не будут равны 0. Подумаем об этих отношениях по-другому, родительский узел должен владеть своими потомками: если родительский узел удаляется, его дочерние узлы также должны быть удалены. Однако дочерний элемент не должен владеть своим родителем: если мы удаляем дочерний узел то родительский элемент все равно должен существовать. Это случай для использования слабых ссылок! Поэтому вместо Rc мы сделаем так, чтобы поле parent использовало вид Weak, а именно RefCell>. Теперь наше определение устройства Node выглядит так: Файл: src/main.rs use std::cell::RefCell;\nuse std::rc::{Rc, Weak}; #[derive(Debug)]\nstruct Node { value: i32, parent: RefCell>, children: RefCell>>,\n}\n# # fn main() {\n# let leaf = Rc::new(Node {\n# value: 3,\n# parent: RefCell::new(Weak::new()),\n# children: RefCell::new(vec![]),\n# });\n# # println!(\"leaf parent = {:?}\", leaf.parent.borrow().upgrade());\n# # let branch = Rc::new(Node {\n# value: 5,\n# parent: RefCell::new(Weak::new()),\n# children: RefCell::new(vec![Rc::clone(&leaf)]),\n# });\n# # *leaf.parent.borrow_mut() = Rc::downgrade(&branch);\n# # println!(\"leaf parent = {:?}\", leaf.parent.borrow().upgrade());\n# } Узел сможет ссылаться на свой родительский узел, но не владеет своим родителем. В приложении 15-28 мы обновляем main на использование нового определения так, чтобы у узла leaf был бы способ ссылаться на его родительский узел branch: Файл: src/main.rs # use std::cell::RefCell;\n# use std::rc::{Rc, Weak};\n# # #[derive(Debug)]\n# struct Node {\n# value: i32,\n# parent: RefCell>,\n# children: RefCell>>,\n# }\n# fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); println!(\"leaf parent = {:?}\", leaf.parent.borrow().upgrade()); let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!(\"leaf parent = {:?}\", leaf.parent.borrow().upgrade());\n} Приложение 15-28: Узел leaf со слабой ссылкой на его родительский узел branch Создание узла leaf выглядит подобно примеру из Приложения 15-27, за исключением поля parent: leaf изначально не имеет родителя, поэтому мы создаём новый, пустой образец ссылки Weak. На этом этапе, когда мы пытаемся получить ссылку на родительский узел у узла leaf с помощью способа upgrade, мы получаем значение None. Мы видим это в выводе первой указания println!: leaf parent = None Когда мы создаём узел branch у него также будет новая ссылка вида Weak в поле parent, потому что узел branch не имеет своего родительского узла. У нас все ещё есть leaf как один из потомков узла branch. Когда мы получили образец Node в переменной branch, мы можем изменить переменную leaf чтобы дать ей Weak ссылку на её родителя. Мы используем способ borrow_mut у вида RefCell> поля parent у leaf, а затем используем функцию Rc::downgrade для создания Weak ссылки на branch из Rc в branch. Когда мы снова напечатаем родителя leaf то в этот раз мы получим исход Some содержащий branch, теперь leaf может получить доступ к своему родителю! Когда мы печатаем leaf, мы также избегаем цикла, который в конечном итоге заканчивался переполнением обоймы, как в приложении 15-26; ссылки вида Weak печатаются как (Weak): leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },\nchildren: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },\nchildren: RefCell { value: [] } }] } }) Отсутствие бесконечного вывода означает, что этот код не создал ссылочной зацикленности. Мы также можем сказать это, посмотрев на значения, которые мы получаем при вызове Rc::strong_count и Rc::weak_count. Визуализация изменений в strong_count и weak_count Давайте посмотрим, как изменяются значения strong_count и weak_count образцов вида Rc с помощью создания новой внутренней области видимости и перемещая создания образца branch в эту область. Таким образом можно увидеть, что происходит, когда branch создаётся и затем удаляется при выходе из области видимости. Изменения показаны в приложении 15-29: Файл: src/main.rs # use std::cell::RefCell;\n# use std::rc::{Rc, Weak};\n# # #[derive(Debug)]\n# struct Node {\n# value: i32,\n# parent: RefCell>,\n# children: RefCell>>,\n# }\n# fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); println!( \"leaf strong = {}, weak = {}\", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); { let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!( \"branch strong = {}, weak = {}\", Rc::strong_count(&branch), Rc::weak_count(&branch), ); println!( \"leaf strong = {}, weak = {}\", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); } println!(\"leaf parent = {:?}\", leaf.parent.borrow().upgrade()); println!( \"leaf strong = {}, weak = {}\", Rc::strong_count(&leaf), Rc::weak_count(&leaf), );\n} Приложение 15-29: Создание branch во внутренней области видимости и подсчёт сильных и слабых ссылок После того, как leaf создан его Rc имеет значения strong count равное 1 и weak count равное 0. Во внутренней области мы создаём branch и связываем её с leaf, после чего при печати значений счётчиков Rc в branch они будет иметь strong count 1 и weak count 1 (для leaf.parent указывающего на branch с Weak ). Когда мы распечатаем счётчики из leaf, мы увидим, что они будут иметь strong count 2, потому что branch теперь имеет клон Rc переменной leaf хранящийся в branch.children, но все равно будет иметь weak count 0. Когда заканчивается внутренняя область видимости, branch выходит из области видимости и strong count Rc уменьшается до 0, поэтому его Node удаляется. Weak count 1 из leaf.parent не имеет никакого отношения к тому, был ли Node удалён, поэтому не будет никаких утечек памяти! Если мы попытаемся получить доступ к родителю переменной leaf после окончания области видимости, мы снова получим значение None. В конце программы Rc внутри leaf имеет strong count 1 и weak count 0 потому что переменная leaf снова является единственной ссылкой на Rc. Вся логика, которая управляет счётчиками и сбросом их значений, встроена внутри Rc и Weak и их выполнений особенности Drop. Указав, что отношение из дочернего к родительскому элементу должно быть ссылкой вида Weak в определении Node, делает возможным иметь родительские узлы, указывающие на дочерние узлы и наоборот, не создавая ссылочной зацикленности и утечек памяти.","breadcrumbs":"Умные указатели » Ссылочные циклы могут привести к утечке памяти » Предотвращение ссылочной зацикленности: замена умного указателя Rc на Weak","id":"287","title":"Предотвращение ссылочной зацикленности: замена умного указателя Rc на Weak"},"288":{"body":"В этой главе рассказано как использовать умные указатели для обеспечения различных заверений и соглашений по сравнению с обычными ссылками, которые Ржавчина использует по умолчанию. Вид Box имеет известный размер и указывает на данные размещённые в куче. Вид Rc отслеживает количество ссылок на данные в куче, поэтому данные могут иметь несколько владельцев. Вид RefCell с его внутренней изменяемостью предоставляет вид, который можно использовать при необходимости неизменного вида, но необходимости изменить внутреннее значение этого типа; он также обеспечивает соблюдение правил заимствования во время выполнения, а не во время сборки. Мы обсудили также особенности Deref и Drop, которые обеспечивают большую возможность умных указателей. Мы исследовали ссылочную зацикленность, которая может вызывать утечки памяти и как это предотвратить с помощью вида Weak. Если эта глава вызвала у вас влечение и вы хотите выполнить свои собственные умные указатели, обратитесь к \"The Rustonomicon\" за более полезной сведениями. Далее мы поговорим о одновременности в Rust. Вы даже узнаете о нескольких новых умных указателях.","breadcrumbs":"Умные указатели » Ссылочные циклы могут привести к утечке памяти » Итоги","id":"288","title":"Итоги"},"289":{"body":"Безопасное и эффективное управление многопоточным программированием — ещё одна из основных целей Rust. Многопоточное программирование , когда разные части программы выполняются независимо, и одновременное программирование , когда разные части программы выполняются одновременно, становятся всё более важными, поскольку всё больше компьютеров используют преимущества нескольких процессоров. Исторически программирование в этих условиях было сложным и подверженным ошибкам: Ржавчина надеется изменить это. Первоначально приказ Ржавчина считала, что обеспечение безопасности памяти и предотвращение неполадок многопоточности — это две отдельные сбоев, которые необходимо решать различными способами. Со временем приказ обнаружила, что системы владения и система видов являются мощным набором средств, помогающих управлять безопасностью памяти и неполадками многопоточного одновременности! Используя владение и проверку видов, многие ошибки многопоточности являются ошибками времени сборки в Rust, а не ошибками времени выполнения. Поэтому вместо того, чтобы тратить много времени на попытки воспроизвести точные обстоятельства, при которых возникает ошибка многопоточности во время выполнения, неправильный код будет отклонён с ошибкой. В итоге вы можете исправить свой код во время работы над ним, а не после развёртывания на рабочем сервере. Мы назвали этот особенность Ржавчина бесстрашной многопоточностью . Бесстрашная многопоточность позволяет вам писать код, который не содержит скрытых ошибок и легко ресогласуется без внесения новых. Примечание: для простоты мы будем называть многие сбоев многопоточными , хотя более точный понятие здесь — многопоточные и/или одновременные . Если бы эта книга была о многопоточности и/или одновременности, мы были бы более определены. В этой главе, пожалуйста, всякий раз, когда мы используем понятие «многопоточный» , мысленно замените на понятие «многопоточный и/или одновременный» . Многие языки предлагают довольно устоявшиеся решения неполадок многопоточности. Например, Erlang обладает элегантной возможностью для многопоточности при передаче сообщений, но не определяет ясных способов совместного использования состояния между потоками. Поддержка только подмножества возможных решений является разумной подходом для языков более высокого уровня, поскольку язык более высокого уровня обещает выгоду при отказе от некоторого управления над получением абстракций. Однако ожидается, что языки низкого уровня обеспечат решение с наилучшей производительностью в любой именно случаи и будут иметь меньше абстракций по сравнению с аппаратным обеспечением. Поэтому Ржавчина предлагает множество средств для расчетов неполадок любым способом, который подходит для вашей случаи и требований. Вот темы, которые мы рассмотрим в этой главе: Как создать потоки для одновременного запуска нескольких отрывков кода Многопоточность передачи сообщений , где потоки передают сообщения между потоками Многопоточность для совместно используемого состояния , когда несколько потоков имеют доступ к некоторому отрывку данных Особенности Sync и Send, которые расширяют заверения многопоточности в Ржавчина для пользовательских видов, а также видов, предоставляемых встроенной библиотекой","breadcrumbs":"Безбоязненный одновременность » Многопоточность без страха","id":"289","title":"Многопоточность без страха"},"29":{"body":"В простых делах Cargo не даёт больших преимуществ по сравнению с использованием rustc, но он проявит себя, когда ваши программы станут более сложными. Когда программы вырастают до нескольких файлов или нуждаются в зависимостях, гораздо проще позволить Cargo согласовывать сборку. Не смотря на то, что дело hello_cargo простой, теперь он использует большую часть существующего набора средств, который вы будете повседневно использовать в вашей развитии, связанной с Rust. Когда потребуется работать над делами размещёнными в сети, вы сможете просто использовать следующую последовательность приказов для получения кода с помощью Git, перехода в папка дела, сборку дела: $ git clone example.org/someproject\n$ cd someproject\n$ cargo build Для получения дополнительной сведений о Cargo ознакомьтесь с его документацией .","breadcrumbs":"С чего начать » Hello, Cargo! » Cargo как Условие","id":"29","title":"Cargo как Условие"},"290":{"body":"В большинстве современных операционных систем программный код выполняется в виде этапа , причём операционная система способна управлять несколькими этапами сразу. Программа, в свою очередь, может состоять из нескольких независимых частей, выполняемых одновременно. Устройство, благодаря которой эти независимые части выполняются, называется потоком . Например, веб-сервер может иметь несколько потоков для того, чтобы он мог обрабатывать больше одного запроса за раз. Разбиение вычислений на несколько потоков может повысить производительность программы, поскольку программа выполняет несколько задач одновременно, но такое разбиение также добавляет сложности. Поскольку потоки могут работать одновременно, нет чёткой заверения, определяющей порядок выполнения частей вашего кода в разных потоках. Это может привести к таким неполадкам, как: Состояния гонки, когда потоки обращаются к данным, либо ресурсам, несогласованно. Взаимные блокировки, когда два потока ожидают друг друга, не позволяя тем самым продолжить работу каждому из потоков. Ошибки, которые случаются только в определённых случаейх, которые трудно воспроизвести и, соответственно, трудно надёжно исправить. Rust пытается смягчить отрицательные последствия использования потоков, но программирование в многопоточном среде все ещё требует тщательного обдумывания устройства кода, которая отличается от устройства кода программ, работающих в одном потоке. Языки программирования выполняют потоки несколькими различными способами, и многие операционные системы предоставляют API, который язык может вызывать для создания новых потоков. Обычная библиотека Ржавчина использует прообраз выполнения потоков 1:1 , при которой одному потоку операционной системы соответствует ровно один \"языковой\" поток. Существуют ящики, в которых выполнены другие подходы многопоточности, отличающиеся от подходы 1:1.","breadcrumbs":"Безбоязненный одновременность » Использование потоков для одновременного выполнения кода » Использование потоков для одновременного выполнения кода","id":"290","title":"Использование потоков для одновременного выполнения кода"},"291":{"body":"Чтобы создать новый поток, мы вызываем функцию thread::spawn и передаём ей замыкание (мы говорили о замыканиях в главе 13), содержащее код, который мы хотим запустить в новом потоке. Пример в приложении 16-1 печатает некоторый текст из основного потока, а также другой текст из нового потока: Файл: src/main.rs use std::thread;\nuse std::time::Duration; fn main() { thread::spawn(|| { for i in 1..10 { println!(\"hi number {i} from the spawned thread!\"); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!(\"hi number {i} from the main thread!\"); thread::sleep(Duration::from_millis(1)); }\n} Приложение 16-1: Создание нового потока для печати определённого текста, в то время как основной поток печатает что-то другое Обратите внимание, что когда основной поток программы на Ржавчина завершается, все порождённые потоки закрываются, независимо от того, завершили они работу или нет. Вывод этой программы может каждый раз немного отличаться, но он будет выглядеть примерно так: hi number 1 from the main thread!\nhi number 1 from the spawned thread!\nhi number 2 from the main thread!\nhi number 2 from the spawned thread!\nhi number 3 from the main thread!\nhi number 3 from the spawned thread!\nhi number 4 from the main thread!\nhi number 4 from the spawned thread!\nhi number 5 from the spawned thread! Вызовы thread::sleep заставляют поток на короткое время останавливать своё выполнение, позволяя выполняться другим потокам. Очерёдность выполнения потоков вероятно будет меняться, но это не обязательно: это зависит от того, как ваша операционная система расчитывает потоки. В этом цикле основной поток печатает первым, несмотря на то, что указание печати из порождённого потока появляется раньше в коде. И даже несмотря на то, что мы указали порождённый поток печатать до тех пор, пока значение i не достигнет числа 9, оно успело дойти только до 5, когда основной поток завершился. Если вы запустите этот код и увидите вывод только из основного потока или не увидите печати из других потоков, попробуйте увеличить числа в рядах, чтобы дать операционной системе больше возможностей для переключения между потоками.","breadcrumbs":"Безбоязненный одновременность » Использование потоков для одновременного выполнения кода » Создание нового потока с помощью spawn","id":"291","title":"Создание нового потока с помощью spawn"},"292":{"body":"Код в приложении 16-1 преждевременно останавливает порождённый поток в большинстве случаев, из-за завершения основного потока. Более того, так как порядок выполнения потоков чётко не определён, этот код не даёт заверения, что порождённый поток вообще начнёт исполняться! Мы можем исправить неполадку, когда созданный поток не запускается или завершается преждевременно, сохранив возвращаемое значение thread::spawn в какой-либо переменной. Вид возвращаемого значения thread::spawn — JoinHandle . JoinHandle — это владеющее значение, которое, при вызове способа join , будет ждать завершения своего потока. Приложение 16-2 отображает, как использовать JoinHandle потока, созданного в приложении 16-1, и вызывать функцию join , для того, чтобы убедиться, что порождённый поток завершится раньше, чем поток main: Файл: src/main.rs use std::thread;\nuse std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!(\"hi number {i} from the spawned thread!\"); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!(\"hi number {i} from the main thread!\"); thread::sleep(Duration::from_millis(1)); } handle.join().unwrap();\n} Приложение 16-2. Сохранение значения JoinHandle потока thread::spawn , обеспечивающее, что поток выполнит всю необходимую работу, перед тем, как завершится Вызов join у указателя блокирует текущий поток, пока поток, представленный указателем не завершится. Блокировка потока означает, что потоку запрещено выполнять работу или выходить из него. Поскольку мы помеисполнения вызов join после цикла for основного потока, выполнение приложения 16-2 должно привести к выводу, подобному следующему: hi number 1 from the main thread!\nhi number 2 from the main thread!\nhi number 1 from the spawned thread!\nhi number 3 from the main thread!\nhi number 2 from the spawned thread!\nhi number 4 from the main thread!\nhi number 3 from the spawned thread!\nhi number 4 from the spawned thread!\nhi number 5 from the spawned thread!\nhi number 6 from the spawned thread!\nhi number 7 from the spawned thread!\nhi number 8 from the spawned thread!\nhi number 9 from the spawned thread! Два потока продолжают чередоваться, но основной поток находится в ожидании из-за вызова handle.join() и не завершается до тех пор, пока не завершится запущенный поток. Но давайте посмотрим, что произойдёт, если мы вместо этого переместим handle.join() перед циклом for в main, например так: Файл: src/main.rs use std::thread;\nuse std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!(\"hi number {i} from the spawned thread!\"); thread::sleep(Duration::from_millis(1)); } }); handle.join().unwrap(); for i in 1..5 { println!(\"hi number {i} from the main thread!\"); thread::sleep(Duration::from_millis(1)); }\n} Основной поток будет ждать завершения порождённого потока, а затем запустит свой цикл for , поэтому выходные данные больше не будут чередоваться, как показано ниже: hi number 1 from the spawned thread!\nhi number 2 from the spawned thread!\nhi number 3 from the spawned thread!\nhi number 4 from the spawned thread!\nhi number 5 from the spawned thread!\nhi number 6 from the spawned thread!\nhi number 7 from the spawned thread!\nhi number 8 from the spawned thread!\nhi number 9 from the spawned thread!\nhi number 1 from the main thread!\nhi number 2 from the main thread!\nhi number 3 from the main thread!\nhi number 4 from the main thread! Небольшие подробности, такие как место вызова join, могут повлиять на то, выполняются ли ваши потоки одновременно.","breadcrumbs":"Безбоязненный одновременность » Использование потоков для одновременного выполнения кода » Ожидание завершения работы всех потоков используя join","id":"292","title":"Ожидание завершения работы всех потоков используя join"},"293":{"body":"Мы часто используем ключевое слово move с замыканиями, переданными в thread::spawn, потому что в этом случае замыкание получает из окружения права владения на используемые им значения, таким образом передавая права владения этими значениями от одного потока к другому. В разделе \"Захват ссылок или перемещение прав владения\" главы 13 мы обсудили move в среде замыканий. Теперь мы сосредоточимся на взаимодействии между move и thread::spawn. Обратите внимание, что в приложении 16-1 замыкание, которое мы передаём в thread::spawn не принимает переменных: мы не используем никаких данных из основного потока в коде порождённого потока. Чтобы использовать данные из основного потока в порождённом потоке, замыкание порождённого потока должно захватывать значения, которые ему необходимы. Приложение 16-3 показывает попытку создать вектор в главном потоке и использовать его в порождённом потоке. Тем не менее, это не будет работать, как вы увидите через мгновение. Файл: src/main.rs use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { println!(\"Here's a vector: {v:?}\"); }); handle.join().unwrap();\n} Приложение 16-3: Попытка использовать вектор, созданный основным потоком, в другом потоке Замыкание использует переменную v, поэтому оно захватит v и сделает его частью окружения замыкания. Поскольку thread::spawn запускает это замыкание в новом потоке, мы должны иметь доступ к v внутри этого нового потока. Но при сборки этого примера, мы получаем следующую ошибку: $ cargo run Compiling threads v0.1.0 (file:///projects/threads)\nerror[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function --> src/main.rs:6:32 |\n6 | let handle = thread::spawn(|| { | ^^ may outlive borrowed value `v`\n7 | println!(\"Here's a vector: {v:?}\"); | - `v` is borrowed here |\nnote: function requires argument type to outlive `'static` --> src/main.rs:6:18 |\n6 | let handle = thread::spawn(|| { | __________________^\n7 | | println!(\"Here's a vector: {v:?}\");\n8 | | }); | |______^\nhelp: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword |\n6 | let handle = thread::spawn(move || { | ++++ For more information about this error, try `rustc --explain E0373`.\nerror: could not compile `threads` (bin \"threads\") due to 1 previous error Rust выводит как захватить v и так как в println! нужна только ссылка на v, то замыкание пытается заимствовать v. Однако есть неполадка: Ржавчина не может определить, как долго будет работать порождённый поток, поэтому он не знает, будет ли всегда действительной ссылка на v. В приложении 16-4 приведён сценарий, который с большей вероятностью будет иметь ссылку на v, что будет недопустимо: Файл: src/main.rs use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { println!(\"Here's a vector: {v:?}\"); }); drop(v); // oh no! handle.join().unwrap();\n} Приложение 16-4. Поток с замыканием, который пытается захватить ссылку на v из основного потока, удаляющего v Если бы Ржавчина позволил нам запустить этот код, есть вероятность, что порождённый поток был бы немедленно переведён в фоновый режим, не выполнив ничего. Порождённый поток имеет ссылку на v, но основной поток немедленно удаляет v , используя функцию drop , которую мы обсуждали в главе 15. Затем, когда порождённый поток начинает выполняться, v уже не существует, поэтому ссылка на него также будет недействительной. О, нет! Чтобы исправить ошибку сборщика в приложении 16-3, мы можем использовать совет из сообщения об ошибке: help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword |\n6 | let handle = thread::spawn(move || { | ++++ Добавляя ключевое слово move перед замыканием, мы заставляем замыкание забирать используемые значения во владение, вместо того, чтобы позволить Ржавчина вывести необходимость заимствования значения. Изменение Приложения 16-3, показанная в Приложении 16-5, будет собрана и запущена так, как мы ожидаем: Файл: src/main.rs use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(move || { println!(\"Here's a vector: {v:?}\"); }); handle.join().unwrap();\n} Приложение 16-5. Использование ключевого слова move , чтобы замыкание стало владельцем используемых им значений. У нас может возникнуть соблазн попробовать то же самое, чтобы исправить код в приложении 16.4, где основной поток вызывал drop с помощью замыкания move . Однако это исправление не сработает, потому что то, что пытается сделать приложение 16.4, запрещено по другой причине. Если мы добавим move к замыканию, мы переместим v в окружение замыкания и больше не сможем вызывать для него drop в основном потоке. Вместо этого мы получим эту ошибку сборщика: $ cargo run Compiling threads v0.1.0 (file:///projects/threads)\nerror[E0382]: use of moved value: `v` --> src/main.rs:10:10 |\n4 | let v = vec![1, 2, 3]; | - move occurs because `v` has type `Vec`, which does not implement the `Copy` trait\n5 |\n6 | let handle = thread::spawn(move || { | ------- value moved into closure here\n7 | println!(\"Here's a vector: {v:?}\"); | - variable moved due to use in closure\n...\n10 | drop(v); // oh no! | ^ value used here after move For more information about this error, try `rustc --explain E0382`.\nerror: could not compile `threads` (bin \"threads\") due to 1 previous error Правила владения Ржавчина снова нас спасли! Мы получили ошибку кода из приложения 16-3, потому что Ржавчина был устоявшийся и заимствовал v только для потока, что означало, что основной поток предположительно может сделать недействительной ссылку на порождённый поток. Сообщив Ржавчина о передаче владения v в порождаемый поток, мы заверяем Rust, что основной поток больше не будет использовать v. Если мы изменим Приложение 16-4 таким же образом, то мы нарушаем правила владения при попытке использовать v в главном потоке. Ключевое слово move отменяет основное устоявшееся поведение Ржавчина по заимствованию, что не позволяет нам нарушать правила владения. Имея достаточное понимание потоков и API потоков, давайте посмотрим, что мы можем делать с помощью потоков.","breadcrumbs":"Безбоязненный одновременность » Использование потоков для одновременного выполнения кода » Использование move-замыканий в потоках","id":"293","title":"Использование move-замыканий в потоках"},"294":{"body":"Всё большую распространенность для обеспечения безопасной многопоточности набирает способ, называемый передача сообщений . В этом случае потоки или акторы взаимодействуют друг с другом путём отправки сообщений с данными. Мысль этого подхода выражена в слогане из документации языка Go таким образом: «Не стоит передавать сведения с помощью разделяемой памяти; лучше делитесь памятью, передавая сведения». Для обеспечения отправки многопоточных сообщений в встроенной библиотеке языка Ржавчина выполнены потоки . Поток в программировании - это общепринятый рычаг, с помощью которого данные из одного потока отправляются другому потоку. Вы можете представить поток в программировании как направленное движение воды, например как ручей или реку. Если вы поместите какую-нибудь вещь на воду, например резиновую уточку, она будет плыть вниз по течению до тех пор, пока это течение не кончится. Поток состоит из двух половин: передатчика и приёмника. Передатчик — это место вверх по течению, где вы опускаете резиновых уточек в реку, а приёмник — это место, где резиновые уточки оказываются в конце пути. Одна часть вашего кода вызывает способы передатчика с данными, которые вы хотите отправить, а другая часть проверяет принимающую сторону на наличие поступающих сообщений. Поток считается закрытым , если либо передающая, либо принимающая его половина уничтожена. Давайте создадим программу, в которой один поток будет порождать значения и отправлять их в поток, а другой поток будет получать значения и распечатывать их. Мы будем отправлять между потоками простые значения, используя поток, чтобы изобразить эту функцию. После того, как вы ознакомитесь с этим способом, вы сможете использовать потоки с любыми потоками, которым необходимо взаимодействовать друг с другом. Это может быть например система чата или система, в которой несколько вычислительных потоков выполняют свою часть расчёта, а затем отправляют эту часть в отдельный поток, который уже агрегирует полученные итоги. Сначала в приложении 16-6 мы создадим поток, но не будем ничего с ним делать. Обратите внимание, что этот код ещё не собирается, потому что Ржавчина не может сказать, какой вид значений мы хотим отправить через поток. Файл: src/main.rs use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel();\n} Приложение 16-6: Создание потока и присваивание двух значений переменным tx и rx Мы создаём новый поток, используя функцию mpsc::channel; mpsc означает несколько производителей, один потребитель (multiple producer, single consumer). Коротко, способ которым обычная библиотека Ржавчина выполняет потоки, означает, что поток может иметь несколько отправляющих источников порождающих значения, но только одну принимающую сторону, которая потребляет эти значения. Представьте, что несколько ручьёв втекают в одну большую реку: всё, что плывёт вниз по любому из ручьёв, в конце концов окажется в одной реке. Сейчас мы пока начнём с одного производителя, а когда пример заработает, добавим ещё несколько. Функция mpsc::channel возвращает упорядоченный ряд, первый элемент которого является отправляющей стороной (передатчиком), а вторым элементом является принимающая сторона (получатель). Аббревиатуры tx и rx привычно используются во многих полях для передатчика и приёмника соответственно, поэтому мы называем соответствующие переменные именно так. Мы используем указанию let с образцом, который разъединяет упорядоченные ряды; мы обсудим использование образцов в указаниях let и разъединение в главе 18. А пока знайте, что описанное использование указания let является удобным способом извлечения частей упорядоченного ряда, возвращаемых mpsc::channel . Давайте переместим передающую часть в порождённый поток так, чтобы он отправлял одну строку и чтобы таким образом, порождённый поток связывался с основным потоком, как показано в приложении 16-7. Это похоже на то, как если бы вы помеисполнения резиновую утку в реку вверх по течению или отправили сообщение чата из одного потока в другой. Файл: src/main.rs use std::sync::mpsc;\nuse std::thread; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from(\"hi\"); tx.send(val).unwrap(); });\n} Приложение 16-7: Перемещение tx в созданный поток и отправка сообщения «привет» Опять же, мы используем thread::spawn для создания нового потока, а затем используем move для перемещения tx в замыкание, чтобы порождённый поток владел tx . Порождённый поток должен владеть передатчиком, чтобы иметь возможность отправлять сообщения через поток. Передатчик имеет способ send , который принимает значение, которое мы хотим отправить. Способ send возвращает вид Result , поэтому, если получатель уже удалён и отправить значение некуда, действие отправки вернёт ошибку. В этом примере мы вызываем unwrap для паники в случае ошибки. В существующем приложении мы обработали бы эту случай более правильно: вернитесь к главе 9, если хотите ещё раз разобрать стратегии правильной обработки ошибок. В приложении 16-8 мы получим значение от приёмника в основном потоке. Это похоже на извлечение резиновой уточки из воды в конце реки или получение сообщения в чате. Файл: src/main.rs use std::sync::mpsc;\nuse std::thread; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from(\"hi\"); tx.send(val).unwrap(); }); let received = rx.recv().unwrap(); println!(\"Got: {received}\");\n} Приложение 16-8: В основном потоке получаем сообщение \"hi\" и печатаем его Получатель имеет два важных способа: recv и try_recv. Мы используем recv, что является сокращением от receive , который блокирует выполнение основного потока и ждёт, пока данные не будут переданы по потоку. Как только значение будет получено, recv вернёт его в виде Result. Когда поток закроется, recv вернёт ошибку, чтобы дать понять, что больше никаких сообщений не поступит. В свою очередь, способ try_recv не блокирует, а сразу возвращает итог Result: значение Ok, содержащее сообщение, если оно доступно или значение Err, если никаких сообщений не поступило. Использование try_recv полезно, если у этого потока есть и другая работа в то время, пока происходит ожидание сообщений: так, мы можем написать цикл, который вызывает try_recv время от времени, обрабатывает сообщение, если оно доступно, а в промежутке выполняет другую работу до того особенности, как вновь будет произведена проверка. Мы использовали recv в этом примере для простоты; у нас нет никакой другой работы для основного потока, кроме как ждать сообщений, поэтому блокировка основного потока уместна. При запуске кода приложения 16-8, мы увидим значение, напечатанное из основного потока: Got: hi Отлично!","breadcrumbs":"Безбоязненный одновременность » Пересылка сообщений для передачи данных между потоками » Передача данных с помощью сообщений между потоками","id":"294","title":"Передача данных с помощью сообщений между потоками"},"295":{"body":"Правила владения играют жизненно важную значение в отправке сообщений, потому что они помогают писать безопасный многопоточный код. Предотвращение ошибок в многопоточном программировании является преимуществом для размышлений о владении во всех ваших Ржавчина программах. Давайте проведём эксперимент, чтобы показать как потоки и владение действуют совместно для предотвращения неполадок. мы попытаемся использовать значение val в порождённом потоке после того как отправим его в поток. Попробуйте собрать код в приложении 16-9, чтобы понять, почему этот код не разрешён: Файл: src/main.rs use std::sync::mpsc;\nuse std::thread; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from(\"hi\"); tx.send(val).unwrap(); println!(\"val is {val}\"); }); let received = rx.recv().unwrap(); println!(\"Got: {received}\");\n} Приложение 16-9: Попытка использовать val после того, как мы отправили его по потоку Здесь мы пытаемся напечатать значение val после того, как отправили его в поток вызвав tx.send. Разрешить это было бы плохой мыслью: после того, как значение было отправлено в другой поток, текущий поток мог бы изменить или удалить значение, прежде чем мы попытались бы использовать значение снова. Вероятно изменения в другом потоке могут привести к ошибкам или не ожидаемым итогам из-за противоречивых или несуществующих данных. Однако Ржавчина выдаёт нам ошибку, если мы пытаемся собрать код в приложении 16-9: $ cargo run Compiling message-passing v0.1.0 (file:///projects/message-passing)\nerror[E0382]: borrow of moved value: `val` --> src/main.rs:10:26 |\n8 | let val = String::from(\"hi\"); | --- move occurs because `val` has type `String`, which does not implement the `Copy` trait\n9 | tx.send(val).unwrap(); | --- value moved here\n10 | println!(\"val is {val}\"); | ^^^^^ value borrowed here after move | = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) For more information about this error, try `rustc --explain E0382`.\nerror: could not compile `message-passing` (bin \"message-passing\") due to 1 previous error Наша ошибка для многопоточности привела к ошибке сборки. Функция send вступает во владение своим свойствоом и когда значение перемещается, получатель становится владельцем этого свойства. Это останавливает нас от случайного использования значения снова после его отправки; анализатор заимствования проверяет, что все в порядке.","breadcrumbs":"Безбоязненный одновременность » Пересылка сообщений для передачи данных между потоками » потоки и передача владения","id":"295","title":"потоки и передача владения"},"296":{"body":"Код в приложении 16-8 собирается и выполняется, но в нем неясно показано то, что два отдельных потока общаются друг с другом через поток. В приложении 16-10 мы внесли некоторые изменения, которые докажут, что код в приложении 16-8 работает одновременно: порождённый поток теперь будет отправлять несколько сообщений и делать паузу на секунду между каждым сообщением. Файл: src/main.rs use std::sync::mpsc;\nuse std::thread;\nuse std::time::Duration; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let vals = vec![ String::from(\"hi\"), String::from(\"from\"), String::from(\"the\"), String::from(\"thread\"), ]; for val in vals { tx.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); for received in rx { println!(\"Got: {received}\"); }\n} Приложение 16-10: Отправка нескольких сообщений и пауза между ними На этот раз порождённый поток имеет вектор строк, которые мы хотим отправить основному потоку. Мы перебираем их, отправляя каждую строку по отдельности и делаем паузу между ними, вызывая функцию thread::sleep со значением Duration равным 1 секунде. В основном потоке мы больше не вызываем функцию recv явно: вместо этого мы используем rx как повторитель . Для каждого полученного значения мы печатаем его. Когда поток будет закрыт, повторение закончится. При выполнении кода в приложении 16-10 вы должны увидеть следующий вывод с паузой в 1 секунду между каждой строкой: Got: hi\nGot: from\nGot: the\nGot: thread Поскольку у нас нет кода, который приостанавливает или задерживает цикл for в основном потоке, мы можем сказать, что основной поток ожидает получения значений из порождённого потока.","breadcrumbs":"Безбоязненный одновременность » Пересылка сообщений для передачи данных между потоками » Отправка нескольких значений и ожидание получателем","id":"296","title":"Отправка нескольких значений и ожидание получателем"},"297":{"body":"Ранее мы упоминали, что mpsc — это аббревиатура от множество поставщиков, один потребитель . Давайте используем mpsc в полной мере и расширим код в приложении 16.10, создав несколько потоков, которые отправляют значения одному и тому же получателю. Мы можем сделать это, клонировав передатчик, как показано в приложении 16.11: Файл: src/main.rs # use std::sync::mpsc;\n# use std::thread;\n# use std::time::Duration;\n# # fn main() { // --snip-- let (tx, rx) = mpsc::channel(); let tx1 = tx.clone(); thread::spawn(move || { let vals = vec![ String::from(\"hi\"), String::from(\"from\"), String::from(\"the\"), String::from(\"thread\"), ]; for val in vals { tx1.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); thread::spawn(move || { let vals = vec![ String::from(\"more\"), String::from(\"messages\"), String::from(\"for\"), String::from(\"you\"), ]; for val in vals { tx.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); for received in rx { println!(\"Got: {received}\"); } // --snip--\n# } Приложение 16-11: Отправка нескольких сообщений от нескольких производителей На этот раз, прежде чем мы создадим первый порождённый поток, мы вызовем функцию clone на передатчике. В итоге мы получим новый передатчик, который мы сможем передать первому порождённому потоку. Исходный передатчик мы передадим второму порождённому потоку. Это даст нам два потока, каждый из которых отправляет разные сообщения одному получателю. Когда вы запустите код, вывод должен выглядеть примерно так: Got: hi\nGot: more\nGot: from\nGot: messages\nGot: for\nGot: the\nGot: thread\nGot: you Вы можете увидеть значения в другом порядке, в зависимости от вашей системы. Именно такое поведение делает одновременность как важным, так и сложным. Если вы поэкспериментируете с thread::sleep , задавая различные значения переменной в разных потоках, каждый запуск будет более неопределенным и каждый раз будут выводиться разные данные. Теперь, когда мы посмотрели, как работают потоки, давайте рассмотрим другой способ многопоточности.","breadcrumbs":"Безбоязненный одновременность » Пересылка сообщений для передачи данных между потоками » Создание нескольких отправителей путём клонирования передатчика","id":"297","title":"Создание нескольких отправителей путём клонирования передатчика"},"298":{"body":"Передача сообщений — прекрасный способ обработки одновременности, но не единственный. Другим способом может быть доступ нескольких потоков к одним и тем же общим данным. Рассмотрим ещё раз часть слогана из документации по языку Go: «Не стоит передавать сведения с помощью разделяемой памяти». Как бы выглядело общение, используя разделяемую память? Кроме того, почему энтузиасты передачи сообщений предостерегают от его использования? В каком-то смысле потоки в любом языке программирования похожи на единоличное владение, потому что после передачи значения по потоку вам больше не следует использовать отправленное значение. Многопоточная, совместно используемая память подобна множественному владению: несколько потоков могут одновременно обращаться к одной и той же области памяти. Как вы видели в главе 15, где умные указатели сделали возможным множественное владение, множественное владение может добавить сложность, потому что нужно управлять этими разными владельцами. Система видов Ржавчина и правила владения очень помогают в их правильном управлении. Для примера давайте рассмотрим мьютексы, один из наиболее распространённых многопоточных простейших для разделяемой памяти.","breadcrumbs":"Безбоязненный одновременность » Одновременность с общим состоянием » Многопоточное разделяемое состояние","id":"298","title":"Многопоточное разделяемое состояние"},"299":{"body":"Mutex - это сокращение от взаимное исключение (mutual exclusion), так как мьютекс позволяет только одному потоку получать доступ к некоторым данным в любой мгновение времени. Для того, чтобы получить доступ к данным в мьютексе, поток должен сначала подать сигнал, что он хочет получить доступ запрашивая блокировку (lock) мьютекса. Блокировка - это устройства данных, являющаяся частью мьютекса, которая отслеживает кто в настоящее время имеет эксклюзивный доступ к данным. Поэтому мьютекс описывается как предмет защищающий данные, которые он хранит через систему блокировки. Мьютексы имеют репутацию трудных в использовании, потому что вы должны помнить два правила: Перед тем как попытаться получить доступ к данным необходимо получить блокировку. Когда вы закончили работу с данными, которые защищает мьютекс, вы должны разблокировать данные, чтобы другие потоки могли получить блокировку. Для понимания мьютекса, представьте пример из жизни как объединениевое обсуждение на конференции с одним микрофоном. Прежде чем участник дискуссии сможет говорить, он должен спросить или дать сигнал, что он хочет использовать микрофон. Когда он получает микрофон, то может говорить столько, сколько хочет, а затем передаёт микрофон следующему участнику, который попросит дать ему выступить. Если участник дискуссии забудет освободить микрофон, когда закончит с ним, то никто больше не сможет говорить. Если управление общим микрофоном идёт не правильно, то конференция не будет работать как было расчитано наперед! Правильное управление мьютексами может быть невероятно сложным и именно поэтому многие люди с энтузиазмом относятся к потокам. Однако, благодаря системе видов и правилам владения в Rust, вы не можете использовать блокировку и разблокировку неправильным образом. Mutex API Давайте рассмотрим пример использования мьютекса в приложении 16-12 без использования нескольких потоков: Файл: src/main.rs use std::sync::Mutex; fn main() { let m = Mutex::new(5); { let mut num = m.lock().unwrap(); *num = 6; } println!(\"m = {m:?}\");\n} Приложение 16-12: Изучение API Mutex для простоты в однопоточном среде Как и во многих других видах, мы создаём Mutex с помощью сопутствующей функции new. Чтобы получить доступ к данным внутри мьютекса, мы используем способ lock для получения блокировки. Этот вызов блокирует выполнение текущего потока, так что он не сможет выполнять никакие действия, до тех пор пока не наступит наша очередь получить блокировку. Вызов lock потерпит неудачу, если другой поток, удерживающий блокировку, запаникует. В таком случае никто не сможет получить блокировку, поэтому мы предпочли использовать unwrap и заставить этот поток паниковать, если мы окажемся в такой случаи. После получения блокировки мы можем воспринимать возвращённое значение, названное в данном случае num, как изменяемую ссылку на содержащиеся внутри данные. Система видов заверяет, что мы получим блокировку перед использованием значения в m. Вид m - Mutex, а не i32, поэтому мы должны вызвать lock, чтобы иметь возможность использовать значение i32. Мы не должны об этом забывать, тем более что в иных случаях система видов и не даст нам доступ к внутреннему значению i32. Как вы наверное подозреваете, Mutex является умным указателем. Точнее, вызов lock возвращает умный указатель, называемый MutexGuard, обёрнутый в LockResult, который мы обработали с помощью вызова unwrap. Умный указатель вида MutexGuard выполняет особенность Deref для указания на внутренние данные; умный указатель также имеет выполнение особенности Drop, самостоятельно снимающего блокировку, когда MutexGuard выходит из области видимости, что происходит в конце внутренней области видимости. В итоге у нас нет риска забыть снять блокировку и оставить мьютекс в заблокированном состоянии, препятствуя его использованию другими потоками (снятие блокировки происходит самостоятельно ). После снятия блокировки можно напечатать значение мьютекса и увидеть, что мы смогли изменить внутреннее i32 на 6. Разделение Mutex между множеством потоков Теперь давайте попробуем с помощью Mutex совместно использовать значение между несколькими потоками. Мы стартуем 10 потоков и каждый из них увеличивает значение счётчика на 1, поэтому счётчик изменяется от 0 до 10. Обратите внимание, что в следующих нескольких примерах будут ошибки сборщика и мы будем использовать эти ошибки, чтобы узнать больше об использовании вида Mutex и как Ржавчина помогает нам правильно его использовать. Приложение 16-13 содержит наш начальный пример: Файл: src/main.rs use std::sync::Mutex;\nuse std::thread; fn main() { let counter = Mutex::new(0); let mut handles = vec![]; for _ in 0..10 { let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!(\"Result: {}\", *counter.lock().unwrap());\n} Приложение 16-13. Десять потоков, увеличивающих счётчик, защищённый Mutex Мы создаём переменную-счётчик counter для хранения i32 значения внутри Mutex, как мы это делали в приложении 16-12. Затем мы создаём 10 потоков, перебирая рядчисел. Мы используем thread::spawn и передаём всем этим потокам одинаковое замыкание, которое перемещает счётчик в поток, запрашивает блокировку на Mutex, вызывая способ lock, а затем добавляет 1 к значению в мьютексе. Когда поток завершит выполнение своего замыкания, num выйдет из области видимости и освободит блокировку, чтобы её мог получить другой поток. В основном потоке мы собираем все указатели в переменную handles. Затем, как мы это делали в приложении 16-2, вызываем join для каждого указателя, чтобы убедиться в завершении всех потоков. В этот мгновение основной поток получит доступ к блокировке и тоже напечатает итог программы. Сборщик намекнул, что этот пример не собирается. Давайте выясним почему! $ cargo run Compiling shared-state v0.1.0 (file:///projects/shared-state)\nerror[E0382]: borrow of moved value: `counter` --> src/main.rs:21:29 |\n5 | let counter = Mutex::new(0); | ------- move occurs because `counter` has type `Mutex`, which does not implement the `Copy` trait\n...\n8 | for _ in 0..10 { | -------------- inside of this loop\n9 | let handle = thread::spawn(move || { | ------- value moved into closure here, in previous iteration of loop\n...\n21 | println!(\"Result: {}\", *counter.lock().unwrap()); | ^^^^^^^ value borrowed here after move |\nhelp: consider moving the expression out of the loop so it is only moved once |\n8 ~ let mut value = counter.lock();\n9 ~ for _ in 0..10 {\n10 | let handle = thread::spawn(move || {\n11 ~ let mut num = value.unwrap(); | For more information about this error, try `rustc --explain E0382`.\nerror: could not compile `shared-state` (bin \"shared-state\") due to 1 previous error Сообщение об ошибке указывает, что значение counter было перемещёно в замыкание на предыдущей повторения цикла. Ржавчина говорит нам, что мы не можем передать counter во владение нескольким потокам. Давайте исправим ошибку сборщика с помощью способа множественного владения, который мы обсуждали в главе 15. Множественное владение между множеством потоков В главе 15 мы давали значение нескольким владельцам, используя умный указатель Rc для создания значения подсчитанных ссылок. Давайте сделаем то же самое здесь и посмотрим, что произойдёт. Мы завернём Mutex в Rc в приложении 16-14 и клонируем Rc перед передачей владения в поток. Теперь, когда мы увидели ошибки, мы также вернёмся к использованию цикла for и сохраним ключевое слово move у замыкания. Файл: src/main.rs use std::rc::Rc;\nuse std::sync::Mutex;\nuse std::thread; fn main() { let counter = Rc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Rc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!(\"Result: {}\", *counter.lock().unwrap());\n} Приложение 16-14: Попытка использования Rc, чтобы позволить нескольким потокам владеть Mutex Ещё раз, мы собираем и получаем ... другие ошибки! Сборщик учит нас. $ cargo run Compiling shared-state v0.1.0 (file:///projects/shared-state)\nerror[E0277]: `Rc>` cannot be sent between threads safely --> src/main.rs:11:36 |\n11 | let handle = thread::spawn(move || { | ------------- ^------ | | | | ______________________|_____________within this `{closure@src/main.rs:11:36: 11:43}` | | | | | required by a bound introduced by this call\n12 | | let mut num = counter.lock().unwrap();\n13 | |\n14 | | *num += 1;\n15 | | }); | |_________^ `Rc>` cannot be sent between threads safely | = help: within `{closure@src/main.rs:11:36: 11:43}`, the trait `Send` is not implemented for `Rc>`, which is required by `{closure@src/main.rs:11:36: 11:43}: Send`\nnote: required because it's used within this closure --> src/main.rs:11:36 |\n11 | let handle = thread::spawn(move || { | ^^^^^^^\nnote: required by a bound in `spawn` --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/std/src/thread/mod.rs:691:1 For more information about this error, try `rustc --explain E0277`.\nerror: could not compile `shared-state` (bin \"shared-state\") due to 1 previous error Ничего себе, это сообщение об ошибке очень многословно! Вот важная часть, на которой следует сосредоточиться: ``Rc cannot be sent between threads safely. Сборщик также сообщает нам причину: the trait Sendis not implemented forRc . Мы поговорим о Send в следующем разделе: это один из особенностей, который заверяет, что виды которые мы используем с потоками, предназначены для использования в многопоточном коде. К сожалению, Rc небезопасен для совместного использования между потоками. Когда Rc управляет счётчиком ссылок, он добавляется значение к счётчику для каждого вызова clone и вычитается значение из счётчика, когда каждое клонированное значение удаляется при выходе из области видимости. Но он не использует простейшие многопоточности, чтобы обеспечить, что изменения в подсчёте не могут быть прерваны другим потоком. Это может привести к неправильным подсчётам - незначительным ошибкам, которые в свою очередь, могут привести к утечкам памяти или удалению значения до того, как мы отработали с ним. Нам нужен вид точно такой же как Rc, но который позволяет изменять счётчик ссылок безопасно из разных потоков. Атомарный счётчик ссылок Arc К счастью, Arc является видом подобным виду Rc, который безопасен для использования в случаейх многопоточности. Буква А означает атомарное , что означает вид ссылка подсчитываемая атомарно . Atomics - это дополнительный вид простейших для многопоточности, который мы не будем здесь подробно описывать: дополнительную сведения смотрите в документации встроенной библиотеки для std::sync::atomic. На данный мгновение вам просто нужно знать, что atomics работают как простые виды, но безопасны для совместного использования между потоками. Вы можете спросить, почему все простые виды не являются атомарными и почему обычные виды библиотек не выполнены для использования вместе с видом Arc по умолчанию. Причина в том, что безопасность потоков сопровождается снижением производительности, которое вы хотите платить только тогда, когда вам это действительно нужно. Если вы просто выполняете действия со значениями в одном потоке, то ваш код может работать быстрее, если он не должен обеспечивать заверения предоставляемые atomics. Давайте вернёмся к нашему примеру: виды Arc и Rc имеют одинаковый API, поэтому мы исправляем нашу программу, заменяя вид в строках use, вызове new и вызове clone. Код в приложении 16-15, наконец собирается и запустится: Файл: src/main.rs use std::sync::{Arc, Mutex};\nuse std::thread; fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!(\"Result: {}\", *counter.lock().unwrap());\n} Приложение 16-15: Использование вида Arc для обёртывания Mutex, теперь несколько потоков могут совместно владеть мьютексом Код напечатает следующее: Result: 10 Мы сделали это! Мы посчитали от 0 до 10, что может показаться не очень впечатляющим, но это позволило больше узнать про Mutex и безопасность потоков. Вы также можете использовать устройство этой программы для выполнения более сложных действий, чем просто увеличение счётчика. Используя эту стратегию, вы можете разделить вычисления на независимые части, разделить эти части на потоки, а затем использовать Mutex, чтобы каждый поток обновлял конечный итог своей частью кода. Обратите внимание, что если вы выполняете простые числовые действия, то существуют виды более простые, чем Mutex, которые предоставляет звено std::sync::atomic встроенной библиотеки . Эти виды обеспечивают безопасный, одновременный, атомарный доступ к простым видам. Мы решили использовать Mutex с простым видом в этом примере, чтобы подробнее рассмотреть, как работает Mutex.","breadcrumbs":"Безбоязненный одновременность » Одновременность с общим состоянием » Мьютексы предоставляют доступ к данным из одного потока (за раз)","id":"299","title":"Мьютексы предоставляют доступ к данным из одного потока (за раз)"},"3":{"body":"Rust наилучше подходит для многих людей по целому ряду причин. Давайте рассмотрим несколько наиболее важных объединений.","breadcrumbs":"Введение » Кому подходит Rust","id":"3","title":"Кому подходит Rust"},"30":{"body":"Теперь вы готовы начать своё Ржавчина путешествие! В данной главе вы изучили как: установить последнюю безотказную исполнение Rust, используя rustup, обновить Ржавчина до последней исполнения, открыть местно установленную документацию, написать и запустить программу вида \"Hello, world!\", используя напрямую сборщик rustc, создать и запустить новый дело, используя соглашения и приказы Cargo. Это отличное время для создания более существенной программы, чтобы привыкнуть читать и писать код на языке Rust. Итак, в главе 2 мы построим программу для игры в угадай число. Если вы предпочитаете начать с изучения того, как работают общие подходы программирования в Rust, обратитесь к главе 3, а затем вернитесь к главе 2.","breadcrumbs":"С чего начать » Hello, Cargo! » Итоги","id":"30","title":"Итоги"},"300":{"body":"Вы могли заметить, что counter сам по себе не изменяемый (immutable), но мы можем получить изменяемую ссылку на значение внутри него; это означает, что Mutex обеспечивает внутреннюю изменяемость, также как и семейство Cell видов. Мы использовали RefCell в главе 15, чтобы получить возможность изменять содержимое внутри Rc, теперь подобным образом мы используем Mutex для изменения содержимого внутри Arc . Ещё одна подробность, на которую стоит обратить внимание: Ржавчина не может защитить вас от всевозможных логических ошибок при использовании Mutex. Вспомните в главе 15, что использование Rc сопряжено с риском создания ссылочной зацикленности, где два значения Rc ссылаются друг на друга, что приводит к утечкам памяти. Подобным образом, Mutex сопряжён с риском создания взаимных блокировок (deadlocks). Это происходит, когда действия необходимо заблокировать два ресурса и каждый из двух потоков получил одну из блокировок, заставляя оба потока ждать друг друга вечно. Если вам важна направление взаимных блокировок, попробуйте создать программу Rust, которая её содержит; затем исследуйте стратегии устранения взаимных блокировок для мьютексов на любом языке и попробуйте выполнить их в Rust. Документация встроенной библиотеки для Mutex и MutexGuard предлагает полезную сведения. Мы завершим эту главу, рассказав о особенностях Send и Sync и о том, как мы можем использовать их с пользовательскими видами.","breadcrumbs":"Безбоязненный одновременность » Одновременность с общим состоянием » Сходства RefCell / Rc и Mutex / Arc","id":"300","title":"Сходства RefCell / Rc и Mutex / Arc"},"301":{"body":"Важно, что сам язык Ржавчина имеет очень мало возможностей для многопоточности. Почти все функции многопоточности о которых мы говорили в этой главе, были частью встроенной библиотеки, а не языка. Ваши исходы работы с многопоточностью не ограничиваются языком или встроенной библиотекой; Вы можете написать свой собственный многопоточный возможности или использовать возможности написанные другими. Тем не менее, в язык встроены две подходы многопоточности: std::marker особенности Sync и Send.","breadcrumbs":"Безбоязненный одновременность » Расширяемый одновременность с помощью особенностей Sync и Send » Расширенная многопоточность с помощью особенностей Sync и Send","id":"301","title":"Расширенная многопоточность с помощью особенностей Sync и Send"},"302":{"body":"Маркерный особенность Send указывает, что владение видом выполняющим Send, может передаваться между потоками. Почти каждый вид Ржавчина является видом Send, но есть некоторые исключения, вроде Rc: он не может быть Send, потому что если вы клонировали значение Rc и попытались передать владение клоном в другой поток, оба потока могут обновить счётчик ссылок одновременно. По этой причине Rc выполнен для использования в однопоточных случаейх, когда вы не хотите платить за снижение производительности. Следовательно, система видов Ржавчина и ограничений особенности заверяют, что вы никогда не сможете случайно небезопасно отправлять значение Rc между потоками. Когда мы попытались сделать это в приложении 16-14, мы получили ошибку, the trait Send is not implemented for Rc>. Когда мы переключились на Arc, который является видом Send, то код собрался. Любой вид полностью состоящий из видов Send самостоятельно помечается как Send. Почти все простые виды являются Send, кроме сырых указателей, которые мы обсудим в главе 19.","breadcrumbs":"Безбоязненный одновременность » Расширяемый одновременность с помощью особенностей Sync и Send » Разрешение передачи во владение между потоками с помощью Send","id":"302","title":"Разрешение передачи во владение между потоками с помощью Send"},"303":{"body":"Маркерный особенность Sync указывает, что на вид выполняющий Sync можно безопасно ссылаться из нескольких потоков. Другими словами, любой вид T является видом Sync, если &T (ссылка на T ) является видом Send, что означает что ссылку можно безопасно отправить в другой поток. Подобно Send, простые виды являются видом Sync, а виды полностью объединенные из видов Sync, также являются Sync видом. Умный указатель Rc не является Sync видом по тем же причинам, по которым он не является Send. Вид RefCell (о котором мы говорили в главе 15) и семейство связанных видов Cell не являются Sync. Выполнение проверки заимствования, которую делает вид RefCell во время выполнения программы не является поточно-безопасной. Умный указатель Mutex является видом Sync и может использоваться для совместного доступа из нескольких потоков, как вы уже видели в разделе «Совместное использование Mutex между несколькими потоками» .","breadcrumbs":"Безбоязненный одновременность » Расширяемый одновременность с помощью особенностей Sync и Send » Разрешение доступа из нескольких потоков с Sync","id":"303","title":"Разрешение доступа из нескольких потоков с Sync"},"304":{"body":"Поскольку виды созданные из особенностей Send и Sync самостоятельно также являются видами Send и Sync, мы не должны выполнить эти особенности вручную. Являясь маркерными особенностями у них нет никаких способов для выполнения. Они просто полезны для выполнения неизменных величин, связанных с многопоточностью. Ручная выполнение этих особенностей включает в себя выполнение небезопасного кода Rust. Мы поговорим об использовании небезопасного кода Ржавчина в главе 19; на данный мгновение важная сведения заключается в том, что для создания новых многопоточных видов, не состоящих из частей Send и Sync необходимо тщательно продумать заверения безопасности. В Rustonomicon есть больше сведений об этих заверениях и о том как их соблюдать.","breadcrumbs":"Безбоязненный одновременность » Расширяемый одновременность с помощью особенностей Sync и Send » Выполнение Send и Sync вручную небезопасна","id":"304","title":"Выполнение Send и Sync вручную небезопасна"},"305":{"body":"Это не последний случай, когда вы увидите многопоточность в этой книге: дело в главе 20 будет использовать подходы этой главы для более существующегостичного случая, чем небольшие примеры обсуждаемые здесь. Как упоминалось ранее, поскольку в языке Ржавчина очень мало того, с помощью чего можно управлять многопоточностью, многие решения выполнены в виде ящиков. Они развиваются быстрее, чем обычная библиотека, поэтому обязательно поищите в Интернете текущие современные ящики. Обычная библиотека Ржавчина предоставляет потоки для передачи сообщений и виды умных указателей, такие как Mutex и Arc, которые можно безопасно использовать в многопоточных средах. Система видов и анализатор заимствований заверяют, что код использующий эти решения не будет содержать гонки данных или недействительные ссылки. Получив собирающийся код, вы можете быть уверены, что он будет успешно работать в нескольких потоках без ошибок, которые трудно обнаружить в других языках. Многопоточное программирование больше не является подходом, которую стоит опасаться: иди вперёд и сделай свои программы многопоточными безбоязненно! Далее мы поговорим об идиоматичных способах расчетов неполадок и внутреннего выстраивания решений по мере усложнения ваших программ на Rust. Кроме того, мы обсудим как идиомы Ржавчина связаны с теми, с которыми вы, возможно, знакомы по предметно-направленному программированию.","breadcrumbs":"Безбоязненный одновременность » Расширяемый одновременность с помощью особенностей Sync и Send » Итоги","id":"305","title":"Итоги"},"306":{"body":"Предметно-направленное программирование (ООП) — это способ построения программ. Предметы, как программная подход, были введены в язык программирования Simula в 1960-х годах. Эти предметы повлияли на архитектуру программирования Алана Кея, в которой предметы передают сообщения друг другу. Чтобы описать эту архитектуру, он ввёл понятие предметно-направленное программирование в 1967 году. Есть много состязающихся определений ООП, и по некоторым из этих определений Ржавчина является предметно-направленным, а по другим — нет. В этой главе мы рассмотрим некоторые свойства, которые обычно считаются предметно-направленными, и то, как эти свойства транслируются в идиомы языка Rust. Затем мы покажем, как выполнить образец предметно-направленного разработки в Rust, и обсудим соглашения между этим исходом и решением, использующим вместо этого некоторые сильные стороны Rust.","breadcrumbs":"Возможности предметно-направленного программирования Rust » Возможности предметно-направленного программирования в Rust","id":"306","title":"Возможности предметно-направленного программирования в Rust"},"307":{"body":"В сообществе программистов нет единого мнения о том, какими свойствами должен обладать язык, чтобы считаться предметно-направленным. На Ржавчина повлияли многие парадигмы программирования, включая ООП - например, в главе 13 мы изучали особенности, пришедшие из функционального программирования. Однозначно можно утверждать, что ООП-языкам присущи следующие присущие особенности: предметы, инкапсуляция и наследование. Давайте рассмотрим, что каждая из них означает и поддерживает ли их Rust.","breadcrumbs":"Возможности предметно-направленного программирования Rust » Свойства предметно-направленных языков » Свойства предметно-направленных языков","id":"307","title":"Свойства предметно-направленных языков"},"308":{"body":"Книга Приёмы предметно-направленного разработки. Образцы разработки Erich Gamma, Richard Helm, Ralph Johnson, и John Vlissides (Addison-Wesley Professional, 1994), в просторечии называемая Книга банды четырёх , представляет собой сборник примеров предметно-направленного разработки. В ней даётся следующее определение ООП: Предметно-направленные программы состоят из предметов. Предмет представляет собой сущность, своего рода дополнение, с данными и процедурами, которые работают с этими данными. Процедуры обычно называются способами или действиеми . В соответствии с этим определением, Ржавчина является предметно-направленным языком - в устройствах и перечислениях содержатся данные, а в х impl определяются способы для них. Хотя устройства и перечисления, имеющие способы, не называются предметами, они обеспечивают возможность, соответствующую определению предметов в книге банды четырёх.","breadcrumbs":"Возможности предметно-направленного программирования Rust » Свойства предметно-направленных языков » Предметы содержат данные и поведение","id":"308","title":"Предметы содержат данные и поведение"},"309":{"body":"Другим особенностью, обычно связанным с предметно-направленным программированием, является мысль инкапсуляции : подробности выполнения предмета недоступны для кода, использующего этот предмет. Единственный способ взаимодействия с предметом — через его открытый внешняя оболочка; код, использующий этот предмет, не должен иметь возможности взаимодействовать с внутренними свойствами предметами напрямую изменять его данные или поведение. Инкапсуляция позволяет изменять и ресоздавать внутренние свойства предмета без необходимости изменять код, который использует предмет. В главе 7 мы уже говорили о том, как управлять инкапсуляцией: мы можем использовать ключевое слово pub, чтобы определить, какие звенья, виды, функции и способы в нашем коде будут открытыми, а всё остальное по умолчанию будет закрытыми. Например, мы можем определить устройство AveragedCollection, в которой есть поле, содержащее вектор значений i32. Также, устройства будет иметь поле, содержащее среднее арифметическое чисел этого вектора, таким образом, среднее не нужно будет вычислять каждый раз, когда оно кому-то понадобится. Другими словами, AveragedCollection будет кэшировать вычисленное среднее для нас. В приложении 17-1 приведено определение устройства AveragedCollection: Файл: src/lib.rs pub struct AveragedCollection { list: Vec, average: f64,\n} Приложение 17-1: устройства AveragedCollection содержит список целых чисел и их среднее арифметическое. Обратите внимание, что устройства помечена ключевым словом pub, что позволяет другому коду её использовать, однако, поля устройства остаются недоступными. Это важно, потому что мы хотим обеспечить обновление среднего значения при добавлении или удалении элемента из списка. Мы можем получить нужное поведение, определив в устройстве способы add, remove и average, как показано в примере 17-2: Файл: src/lib.rs # pub struct AveragedCollection {\n# list: Vec,\n# average: f64,\n# }\n# impl AveragedCollection { pub fn add(&mut self, value: i32) { self.list.push(value); self.update_average(); } pub fn remove(&mut self) -> Option { let result = self.list.pop(); match result { Some(value) => { self.update_average(); Some(value) } None => None, } } pub fn average(&self) -> f64 { self.average } fn update_average(&mut self) { let total: i32 = self.list.iter().sum(); self.average = total as f64 / self.list.len() as f64; }\n} Приложение 17-2: Выполнение открытых способов add,remove, и average для AveragedCollection Открытые способы add, remove и average являются единственным способом получить или изменить данные в образце AveragedCollection. Когда элемент добавляется в list способом add, или удаляется с помощью способа remove, код выполнения каждого из этих способов вызывает закрытый способ update_average, который позаботится об обновлении поля average. Мы оставляем поля list и average закрытыми, чтобы внешний код не мог добавлять или удалять элементы непосредственно в поле list; в противном случае поле average может оказаться не согласовано при подобном вмешательстве. Способ average возвращает значение в поле average, что позволяет внешнему коду читать значение average, но не изменять его. Поскольку мы инкапсулировали подробности выполнения устройства AveragedCollection, мы можем легко изменить такие особенности, как устройства данных, в будущем. Например, мы могли бы использовать HashSet вместо Vec для поля list. Благодаря тому, что ярлыки открытых способов add, remove и average остаются неизменными, код, использующий AveragedCollection, также не будет нуждаться в изменении. У нас бы не получилось этого достичь, если бы мы сделали поле list доступным внешнему коду: HashSet иVec имеют разные способы для добавления и удаления элементов, поэтому внешний код, вероятно, должен измениться, если он изменяет list напрямую. Если инкапсуляция является обязательным особенностью для определения языка как предметно-направленного, то Ржавчина соответствует этому требованию. Возможность использовать или не использовать изменитель доступа pub для различных частей кода позволяет скрыть подробности выполнения.","breadcrumbs":"Возможности предметно-направленного программирования Rust » Свойства предметно-направленных языков » Инкапсуляция, скрывающая подробности выполнения","id":"309","title":"Инкапсуляция, скрывающая подробности выполнения"},"31":{"body":"Давайте окунёмся в Rust, вместе поработав над опытным делом! В этой главе вы познакомитесь с несколькими общими подходами Rust, показав, как использовать их в существующей программе. Вы узнаете о let , match, способах, сопряженных функциях, внешних дополнениях и многом другом! В следующих главах мы рассмотрим эти мысли более подробно. В этой главе вы просто примените в основах. Мы выполняем привычную для начинающих программистов задачу — игру в загадки. Вот как это работает: программа порождает случайное целое число в ряде от 1 до 100. Затем она предлагает игроку его угадать. После ввода числа программа укажет, меньше или больше было загаданное число. Если догадка верна, игра напечатает поздравительное сообщение и завершится.","breadcrumbs":"Программирование игры в загадки » Программируем игру в загадки","id":"31","title":"Программируем игру в загадки"},"310":{"body":"Наследование — это рычаг, с помощью которого предмет может унаследовать элементы из определения другого предмета. то есть получить данные и поведение родительского предмета без необходимости повторно их определять. Если язык должен иметь наследование, чтобы быть предметно-направленным, то Ржавчина таким не является. Здесь нет способа определить устройство, наследующую поля и выполнения способов родительской устройства, без использования макроса. Однако, если вы привыкли иметь наследование в своём наборе средств для программирования, вы можете использовать другие решения в Rust, в зависимости от того, по какой причине вы изначально хотите использовать наследование. Вы могли бы выбрать наследование по двум основным причинам. Одна из них - возможность повторного использования кода: вы можете выполнить определённое поведение для одного вида, а наследование позволит вам повторно использовать эту выполнение для другого вида. В Ржавчина для этого есть ограниченный способ, использующий выполнение способа особенности по умолчанию, который вы видели в приложении 10-14, когда мы добавили выполнение по умолчанию в способе summarize особенности Summary. Любой вид, выполняющий свойство Summary будет иметь доступный способ summarize без дополнительного кода. Это похоже на то, как родительский класс имеет выполнение способа, и класс-наследник тоже имеет выполнение способа. Мы также можем переопределить выполнение по умолчанию для способа summarize, когда выполняем особенность Summary, что похоже на дочерний класс, переопределяющий выполнение способа, унаследованного от родительского класса. Вторая причина использования наследования относится к системе видов: чтобы иметь возможность использовать дочерний вид в тех же места, что и родительский. Эта возможность также называется полиморфизм и означает возможность подменять предметы во время исполнения, если они имеют одинаковые свойства.","breadcrumbs":"Возможности предметно-направленного программирования Rust » Свойства предметно-направленных языков » Наследование как система видов и способ совместного использования кода","id":"310","title":"Наследование как система видов и способ совместного использования кода"},"311":{"body":"Для многих людей полиморфизм является родственным наследования. Но на самом деле это более общая подход, относящаяся к коду, который может работать с данными нескольких видов. Обычно такими видами выступают подклассы при наследовании. Вместо этого Ржавчина использует обобщённые виды для абстрагирования от видов, и ограничения особенностей (trait bounds) для указания того, какие возможности эти виды должны предоставлять. Это иногда называют ограниченным свойствоическим полиморфизмом . Наследование, как подход к разработке, в последнее время утратило распространенность во многих языках программирования, поскольку часто существует риск, что мы будем наследовать код чаще, чем это необходимо. Подклассы не всегда должны обладать всеми свойствами родительского класса, но при использовании наследования другого исхода нет. Это может сделать внешний вид программы менее гибким. Кроме этого, появляется возможность вызова у подклассов способов, которые не имеют смысла или вызывают ошибки, потому что эти способы неприменимы к подклассу. Кроме того, в некоторых языках разрешается только одиночное наследование (т.е. подкласс может наследоваться только от одного класса), что ещё больше ограничивает гибкость разработки программы. По этим причинам в Ржавчина применяется иной подход, с использованием особенностей-предметов вместо наследования. Давайте посмотрим как особенности-предметы выполняют полиморфизм в Rust.","breadcrumbs":"Возможности предметно-направленного программирования Rust » Свойства предметно-направленных языков » Полиморфизм","id":"311","title":"Полиморфизм"},"312":{"body":"В главе 8 мы упоминали, что одним из ограничений векторов является то, что они могут хранить элементы только одного вида. Мы создали обходное решение в приложении 8-9, где мы определили перечисление SpreadsheetCell в котором были исходы для хранения целых чисел, чисел с плавающей точкой и текста. Это означало, что мы могли хранить разные виды данных в каждой ячейке и при этом иметь вектор, представляющий строку из ячеек. Это очень хорошее решение, когда наши взаимозаменяемые элементы вектора являются видами с конечным набором, известным при сборки кода. Однако иногда мы хотим, чтобы пользователь нашей библиотеки мог расширить набор видов, которые допустимы в именно случаи. Чтобы показать как этого добиться, мы создадим пример средства с графическим внешней оболочкой пользователя (GUI), который просматривает список элементов, вызывает способ draw для каждого из них, чтобы нарисовать его на экране - это обычная техника для средств GUI. Мы создадим библиотечный ящик с именем gui, содержащий устройство библиотеки GUI. Этот ящик мог бы включать некоторые готовые виды для использования, такие как Button или TextField. Кроме того, пользователи такого ящика gui захотят создавать свои собственные виды, которые могут быть нарисованы: например, кто-то мог бы добавить вид Image, а кто-то другой добавить вид SelectBox. Мы не будем выполнить полноценную библиотеку GUI для этого примера, но покажем, как её части будут подходить друг к другу. На мгновение написания библиотеки мы не можем знать и определить все виды, которые могут захотеть создать другие программисты. Но мы знаем, что gui должен отслеживать множество значений различных видов и ему нужно вызывать способ draw для каждого из этих значений различного вида. Ему не нужно точно знать, что произойдёт, когда вызывается способ draw, просто у значения будет доступен такой способ для вызова. Чтобы сделать это на языке с наследованием, можно определить класс с именем Component у которого есть способ с названием draw. Другие классы, такие как Button, Image и SelectBox наследуются от Component и следовательно, наследуют способ draw. Каждый из них может переопределить выполнение способа draw, чтобы определить своё пользовательское поведение, но площадка может обрабатывать все виды, как если бы они были образцами Component и вызывать draw у них. Но поскольку в Ржавчина нет наследования, нам нужен другой способ внутренне выстроить gui библиотеку, чтобы позволить пользователям расширять её новыми видами.","breadcrumbs":"Возможности предметно-направленного программирования Rust » Использование особенность-предметов, допускающих значения разных видов » Использование особенность-предметов, допускающих значения разных видов","id":"312","title":"Использование особенность-предметов, допускающих значения разных видов"},"313":{"body":"Чтобы выполнить поведение, которое мы хотим иметь в gui, определим особенность с именем Draw, который будет содержать один способ с названием draw. Затем мы можем определить вектор, который принимает особенность-предмет . Особенность-предмет указывает как на образец вида, выполняющего указанный особенность, так и на внутреннюю таблицу, используемую для поиска способов особенности указанного вида во время выполнения. Мы создаём особенность-предмет в таком порядке: используем какой-нибудь вид указателя, например ссылку & или умный указатель Box, затем ключевое слово dyn, а затем указываем соответствующий особенность. (Мы будем говорить о причине того, что особенность-предметы должны использовать указатель в разделе \"Виды изменяемого размера и особенность Sized \" главы 19). Мы можем использовать особенность-предметы вместо гибкого или определенного вида. Везде, где мы используем особенность-предмет, система видов Ржавчина проверит во время сборки, что любое значение, используемое в этом среде, будет выполнить нужный особенность у особенность-предмета. Следовательно, нам не нужно знать все возможные виды во время сборки. Мы упоминали, что в Ржавчина мы воздерживаемся называть устройства и перечисления «предметами», чтобы отличать их от предметов в других языках. В устройстве или перечислении данные в полях устройства и поведение в разделах impl разделены, тогда как в других языках данные и поведение объединены в одну подход, часто обозначающуюся как предмет. Тем не менее, особенность-предметы являются более похожими на предметы на других языках, в том смысле, что они сочетают в себе данные и поведение. Но особенность-предметы отличаются от привычных предметов тем, что не позволяют добавлять данные к особенность-предмету. Особенность-предметы обычно не настолько полезны, как предметы в других языках: их определенная цель - обеспечить абстракцию через общее поведение. В приложении 17.3 показано, как определить особенность с именем Draw с помощью одного способа с именем draw: Файл: src/lib.rs pub trait Draw { fn draw(&self);\n} Приложение 17-3: Определение особенности Draw Этот правила написания должен выглядеть знакомым из наших дискуссий о том, как определять особенности в главе 10. Далее следует новый правила написания: в приложении 17.4 определена устройства с именем Screen, которая содержит вектор с именем components. Этот вектор имеет вид Box, который и является особенность-предметом; это замена для любого вида внутри Box который выполняет особенность Draw. Файл: src/lib.rs # pub trait Draw {\n# fn draw(&self);\n# }\n# pub struct Screen { pub components: Vec>,\n} Приложение 17-4: Определение устройства Screen с полем components, которое является вектором особенность-предметов, которые выполняют особенность Draw В устройстве Screen, мы определим способ run, который будет вызывать способ draw каждого элемента вектора components, как показано в приложении 17-5: Файл: src/lib.rs # pub trait Draw {\n# fn draw(&self);\n# }\n# # pub struct Screen {\n# pub components: Vec>,\n# }\n# impl Screen { pub fn run(&self) { for component in self.components.iter() { component.draw(); } }\n} Приложение 17-5: Выполнение способа run у устройства Screen, который вызывает способ draw каждого составляющих из вектора Это работает иначе, чем определение устройства, которая использует свойство общего вида с ограничениями особенности. Обобщённый свойство вида может быть заменён только одним определенным видом, тогда как особенность-предметы позволяют нескольким определенным видам замещать особенность-предмет во время выполнения. Например, мы могли бы определить устройство Screen используя общий вид и ограничение особенности, как показано в приложении 17-6: Файл: src/lib.rs # pub trait Draw {\n# fn draw(&self);\n# }\n# pub struct Screen { pub components: Vec,\n} impl Screen\nwhere T: Draw,\n{ pub fn run(&self) { for component in self.components.iter() { component.draw(); } }\n} Приложение 17-6: Иная выполнение устройства Screen и способа run, используя обобщённый вид и ограничения особенности Это исход ограничивает нас образцом Screen, который имеет список составляющих всех видов Button или всех видов TextField. Если у вас когда-либо будут только однородные собрания, использование обобщений и ограничений особенности является предпочтительным, поскольку определения будут мономорфизированы во время сборки для использования с определенными видами. С другой стороны, с помощью способа, использующего особенность-предметы, один образец Screen может содержать Vec который содержит Box - - - - -

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

- -
- - - - -
- - - - - - - -
-
-

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

-

От Стива Клабника и Кэрол Николс, при поддержке других участников сообщества Rust

-

В этой исполнения учебника предполагается, что вы используете Ржавчина 1.67.1 (выпущен 09.02.2023) или новее. См. раздел «Установка» главы 1 для установки или обновления Rust.

-

HTML-исполнение книги доступна онлайн по адресам https://doc.rust-lang.org/stable/book/(англ.) и https://doc.rust-lang.ru/book(рус.) и офлайн. При установке Ржавчина с помощью rustup: просто запустите rustup docs --book, чтобы её открыть.

-

Также доступны несколько переводов от сообщества.

-

Этот источник доступен в виде печатной книги в мягкой обложке и в виде электронной книги от No Starch Press .

-
-

🚨 Предпочитаете более увлекательный этап обучения? Попробуйте другую исполнение Ржавчина Book, в которой есть: проверочные вопросы, цветовое выделение, наглядные визуализации и многое другое: https://rust-book.cs.brown.edu

-
- -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rustbook-ru/book/tomorrow-night.css b/rustbook-ru/book/tomorrow-night.css deleted file mode 100644 index 81fe276e7..000000000 --- a/rustbook-ru/book/tomorrow-night.css +++ /dev/null @@ -1,102 +0,0 @@ -/* Tomorrow Night Theme */ -/* https://github.com/jmblog/color-themes-for-highlightjs */ -/* Original theme - https://github.com/chriskempson/tomorrow-theme */ -/* https://github.com/jmblog/color-themes-for-highlightjs */ - -/* Tomorrow Comment */ -.hljs-comment { - color: #969896; -} - -/* Tomorrow Red */ -.hljs-variable, -.hljs-attribute, -.hljs-tag, -.hljs-regexp, -.ruby .hljs-constant, -.xml .hljs-tag .hljs-title, -.xml .hljs-pi, -.xml .hljs-doctype, -.html .hljs-doctype, -.css .hljs-id, -.css .hljs-class, -.css .hljs-pseudo { - color: #cc6666; -} - -/* Tomorrow Orange */ -.hljs-number, -.hljs-preprocessor, -.hljs-pragma, -.hljs-built_in, -.hljs-literal, -.hljs-params, -.hljs-constant { - color: #de935f; -} - -/* Tomorrow Yellow */ -.ruby .hljs-class .hljs-title, -.css .hljs-rule .hljs-attribute { - color: #f0c674; -} - -/* Tomorrow Green */ -.hljs-string, -.hljs-value, -.hljs-inheritance, -.hljs-header, -.hljs-name, -.ruby .hljs-symbol, -.xml .hljs-cdata { - color: #b5bd68; -} - -/* Tomorrow Aqua */ -.hljs-title, -.css .hljs-hexcolor { - color: #8abeb7; -} - -/* Tomorrow Blue */ -.hljs-function, -.python .hljs-decorator, -.python .hljs-title, -.ruby .hljs-function .hljs-title, -.ruby .hljs-title .hljs-keyword, -.perl .hljs-sub, -.javascript .hljs-title, -.coffeescript .hljs-title { - color: #81a2be; -} - -/* Tomorrow Purple */ -.hljs-keyword, -.javascript .hljs-function { - color: #b294bb; -} - -.hljs { - display: block; - overflow-x: auto; - background: #1d1f21; - color: #c5c8c6; -} - -.coffeescript .javascript, -.javascript .xml, -.tex .hljs-formula, -.xml .javascript, -.xml .vbscript, -.xml .css, -.xml .hljs-cdata { - opacity: 0.5; -} - -.hljs-addition { - color: #718c00; -} - -.hljs-deletion { - color: #c82829; -} diff --git a/rustbook-ru/index.md b/rustbook-ru/index.md index e75cbcea1..91f71bb15 100644 --- a/rustbook-ru/index.md +++ b/rustbook-ru/index.md @@ -6,6 +6,6 @@ There are two editions of "The Ржавчина Programming Language": * [Second edition](second-edition/index.html) The second edition is a complete re-write. It is still under construction, -though it is far enough along to learn most of Rust. We suggest reading the +though it is far enough along to learn most of Ржавчина. We suggest reading the second edition and then checking out the first edition later to pick up some of the more esoteric parts of the language. diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-05/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-05/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-05/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-05/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-06/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-06/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-06/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-06/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-07/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-07/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-07/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-07/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-09/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-09/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-09/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-09/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-10/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-10/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-10/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-10/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-11/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-11/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-11/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-11/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-12/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-12/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-12/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-12/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-13/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-13/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-13/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-13/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-14/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-14/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-14/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-14/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-15/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-15/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-15/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-15/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-16/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-16/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-16/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-16/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-17/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-17/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-17/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-17/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-18/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-18/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-18/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-18/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-19/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-19/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-19/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-19/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-20/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-20/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-20/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-20/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-21/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-21/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-21/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-21/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-22/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-22/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-22/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-22/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-23/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-23/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-23/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-23/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-24/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-24/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-24/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-24/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/listing-20-25/hello.html b/rustbook-ru/listings/ch20-web-server/listing-20-25/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/listing-20-25/hello.html +++ b/rustbook-ru/listings/ch20-web-server/listing-20-25/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/no-listing-01-define-threadpool-struct/hello.html b/rustbook-ru/listings/ch20-web-server/no-listing-01-define-threadpool-struct/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/no-listing-01-define-threadpool-struct/hello.html +++ b/rustbook-ru/listings/ch20-web-server/no-listing-01-define-threadpool-struct/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/no-listing-02-impl-threadpool-new/hello.html b/rustbook-ru/listings/ch20-web-server/no-listing-02-impl-threadpool-new/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/no-listing-02-impl-threadpool-new/hello.html +++ b/rustbook-ru/listings/ch20-web-server/no-listing-02-impl-threadpool-new/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/no-listing-03-define-execute/hello.html b/rustbook-ru/listings/ch20-web-server/no-listing-03-define-execute/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/no-listing-03-define-execute/hello.html +++ b/rustbook-ru/listings/ch20-web-server/no-listing-03-define-execute/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/no-listing-04-update-worker-definition/hello.html b/rustbook-ru/listings/ch20-web-server/no-listing-04-update-worker-definition/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/no-listing-04-update-worker-definition/hello.html +++ b/rustbook-ru/listings/ch20-web-server/no-listing-04-update-worker-definition/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/no-listing-05-fix-worker-new/hello.html b/rustbook-ru/listings/ch20-web-server/no-listing-05-fix-worker-new/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/no-listing-05-fix-worker-new/hello.html +++ b/rustbook-ru/listings/ch20-web-server/no-listing-05-fix-worker-new/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/no-listing-06-fix-threadpool-drop/hello.html b/rustbook-ru/listings/ch20-web-server/no-listing-06-fix-threadpool-drop/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/no-listing-06-fix-threadpool-drop/hello.html +++ b/rustbook-ru/listings/ch20-web-server/no-listing-06-fix-threadpool-drop/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/listings/ch20-web-server/no-listing-07-final-code/hello.html b/rustbook-ru/listings/ch20-web-server/no-listing-07-final-code/hello.html index fe442d6b9..f645657d7 100644 --- a/rustbook-ru/listings/ch20-web-server/no-listing-07-final-code/hello.html +++ b/rustbook-ru/listings/ch20-web-server/no-listing-07-final-code/hello.html @@ -6,6 +6,6 @@

Hello!

-

Hi from Rust

+

Hi from Ржавчина

diff --git a/rustbook-ru/src/SUMMARY.md b/rustbook-ru/src/SUMMARY.md index af26ce248..51bebcb0a 100644 --- a/rustbook-ru/src/SUMMARY.md +++ b/rustbook-ru/src/SUMMARY.md @@ -1,6 +1,6 @@ # The Ржавчина Programming Language -[Язык программирования Rust](title-page.md) [Предисловие](foreword.md) [Введение](ch00-00-introduction.md) +[Язык программирования Ржавчина](title-page.md) [Предисловие](foreword.md) [Введение](ch00-00-introduction.md) ## С чего начать @@ -38,7 +38,7 @@ - [Устройство потока управления `match`](ch06-02-match.md) - [Краткий поток управления с `if let`](ch06-03-if-let.md) -## Основы Rust +## Основы Ржавчины - [Управление растущими делами с помощью дополнений, ящиков и звеньев](ch07-00-managing-growing-projects-with-packages-crates-and-modules.md) @@ -76,19 +76,19 @@ - [Получение переменных приказной строки](ch12-01-accepting-command-line-arguments.md) - [Чтение файла](ch12-02-reading-a-file.md) - - [Переработка кода для обеспечения выделения на звенья и улучшения обработки ошибок](ch12-03-improving-error-handling-and-modularity.md) + - [Переработка рукописи для обеспечения выделения на звенья и улучшения обработки ошибок](ch12-03-improving-error-handling-and-modularity.md) - [Разработка возможности библиотеки с помощью разработки через проверка](ch12-04-testing-the-librarys-functionality.md) - [Работа с переменными среды](ch12-05-working-with-environment-variables.md) - [Запись сообщений об ошибках в stderr вместо stdout](ch12-06-writing-to-stderr-instead-of-stdout.md) -## Думать на Rust +## Думать на Ржавчины - [Полезные возможности языка: повторители и замыкания](ch13-00-functional-features.md) - [Замыкания: анонимные функции, которые захватывают своё окружение](ch13-01-closures.md) - [Обработка последовательности элементов с помощью повторителей](ch13-02-iterators.md) - [Улучшение нашего дела с вводом/выводом](ch13-03-improving-our-io-project.md) - - [Сравнение производительности: циклы и повторители](ch13-04-performance.md) + - [Сравнение производительности: круговороты и повторители](ch13-04-performance.md) - [Подробнее о Cargo и Crates.io](ch14-00-more-about-cargo.md) @@ -102,19 +102,19 @@ - [Использование `Box` для указания на данные в куче](ch15-01-box.md) - [Работа с умными указателями как с обычными ссылками с помощью особенности `Deref`](ch15-02-deref.md) - - [Выполнение кода при очистке с помощью особенности `Drop`](ch15-03-drop.md) + - [Выполнение рукописи при очистке с помощью особенности `Drop`](ch15-03-drop.md) - [`Rc`, умный указатель с подсчётом ссылок](ch15-04-rc.md) - [`RefCell` и внутренняя изменяемость](ch15-05-interior-mutability.md) - - [Ссылочные циклы могут привести к утечке памяти](ch15-06-reference-cycles.md) + - [Ссылочные круговороты могут привести к утечке памяти](ch15-06-reference-cycles.md) - [Безбоязненный одновременность](ch16-00-concurrency.md) - - [Использование потоков для одновременного выполнения кода](ch16-01-threads.md) + - [Использование потоков для одновременного выполнения рукописи](ch16-01-threads.md) - [Пересылка сообщений для передачи данных между потоками](ch16-02-message-passing.md) - [Одновременность с общим состоянием](ch16-03-shared-state.md) - [Расширяемый одновременность с помощью особенностей `Sync` и `Send`](ch16-04-extensible-concurrency-sync-and-send.md) -- [Возможности предметно-направленного программирования Rust](ch17-00-oop.md) +- [Возможности предметно-направленного программирования Ржавчина](ch17-00-oop.md) - [Свойства предметно-направленных языков](ch17-01-what-is-oo.md) - [Использование особенность-предметов, допускающих значения разных видов](ch17-02-trait-objects.md) @@ -130,24 +130,24 @@ - [Расширенные возможности](ch19-00-advanced-features.md) - - [Небезопасный код в Rust](ch19-01-unsafe-rust.md) + - [Небезопасную рукопись в Ржавчине](ch19-01-unsafe-rust.md) - [Продвинутые особенности](ch19-03-advanced-traits.md) - [Продвинутые виды](ch19-04-advanced-types.md) - [Продвинутые функции и замыкания](ch19-05-advanced-functions-and-closures.md) - [Макросы](ch19-06-macros.md) -- [Конечный дело: создание многопоточного веб-сервера](ch20-00-final-project-a-web-server.md) +- [Конечный дело: создание многопоточного сетевого-отделенного вычислителя](ch20-00-final-project-a-web-server.md) - - [Создание однопоточного веб-сервера](ch20-01-single-threaded.md) - - [Превращение нашего однопоточного сервера в многопоточный сервер](ch20-02-multithreaded.md) + - [Создание однопоточного сетевого-отделенного вычислителя](ch20-01-single-threaded.md) + - [Превращение нашего однопоточного отделенного вычислителя в многопоточный отделеный вычислитель](ch20-02-multithreaded.md) - [ Мягкое завершение работы и очистка](ch20-03-graceful-shutdown-and-cleanup.md) - [Приложения](appendix-00.md) - [А — Ключевые слова](appendix-01-keywords.md) - - [B — Операторы и символы](appendix-02-operators.md) + - [B — Приказчики и знаки](appendix-02-operators.md) - [C — Выводимые особенности](appendix-03-derivable-traits.md) - [D — Полезные средства разработки](appendix-04-useful-development-tools.md) - [E — Издания](appendix-05-editions.md) - [F — Переводы книги](appendix-06-translation.md) - - [G — Как создаётся Ржавчина и «Nightly Rust»](appendix-07-nightly-rust.md) + - [G — Как создаётся Ржавчина и «Ночное издание Ржавчины»](appendix-07-nightly-rust.md) diff --git a/rustbook-ru/src/appendix-00.md b/rustbook-ru/src/appendix-00.md index 1968cc2ba..b2b3ae3aa 100644 --- a/rustbook-ru/src/appendix-00.md +++ b/rustbook-ru/src/appendix-00.md @@ -1,3 +1,3 @@ -# Дополнительная сведения +# Дополнительные сведения -Следующие разделы содержат справочные источники, которые могут оказаться полезными в вашем путешествии по Rust. +Следующие разделы содержат справочные источники, которые могут оказаться полезными в вашем путешествии по Ржавчина. diff --git a/rustbook-ru/src/appendix-01-keywords.md b/rustbook-ru/src/appendix-01-keywords.md index 26f9a4602..ba01e2622 100644 --- a/rustbook-ru/src/appendix-01-keywords.md +++ b/rustbook-ru/src/appendix-01-keywords.md @@ -1,30 +1,30 @@ ## Приложение A: Ключевые слова -Следующий список содержит ключевые слова, зарезервированные для текущего или будущего использования в языке Rust. Как таковые их нельзя использовать в качестве определителей (за исключением сырых определителей, которые мы обсудим в разделе [«Сырые определители]»). определительы — это имена функций, переменных, свойств, полей устройств, звеньев, ящиков, постоянных значений, макросов, постоянных значений, свойств, видов, свойств или времён жизни. +Следующий список содержит ключевые слова, зарезервированные для текущего или будущего использования в языке Ржавчина. Как таковые их нельзя использовать в качестве определителей (за исключением сырых определителей, которые мы обсудим в разделе [«Сырые определители]»). определительы — это имена функций, переменных, свойств, полей устройств, звеньев, ящиков, постоянных значений, макросов, постоянных значений, свойств, видов, свойств или времён жизни. ### Используемые в настоящее время ключевые слова Ниже приведён список используемых в настоящее время ключевых слов с их описанием. - `as` — выполнить простое преобразование, уточнить определенную свойство, которую содержит предмет, или переименовать элемент в выражении `use` -- `async` — возврат `Future` вместо блокировки текущего потока +- `async` — возврат `Future` вместо запрета текущего потока - `await` — остановка выполнения до готовности итога `Future` -- `break` — немедленный выход из цикла +- `break` — немедленный выход из круговорота - `const` — определение постоянного элемента или неизменяемого сырого указателя -- `continue` — досрочный переход к следующей повторения цикла +- `continue` — досрочный переход к следующей повторения круговорота - `crate` — ссылка на корень дополнения в пути к звену - `dyn` — изменяемая отсылка к особенности предмета - `else` — иные ветви для устройств управления потока `if` и `if let` - `enum` — определение перечислений - `extern` — связывание внешней функции или переменной -- `false` — логический ложный запись +- `false` — разумный ложный запись - `fn` — определение функции или вида указателя на функцию - `for` — замкнуто перебирать элементы из повторителя, выполнить признак или указывать время жизни с более высоким рейтингом. - `if` — ветвление на основе итога условного выражения - `impl` — выполнение встроенной возможности или возможности особенности -- `in` — часть правил написания цикла `for` +- `in` — часть правил написания круговорота `for` - `let` — объявление (связывание) переменной -- `loop` — безусловный цикл +- `loop` — безусловный круговорот - `match` — сопоставление значения с образцами - `mod` — определение звена - `move` — перекладывание владения на замыкание всеми захваченными элементами @@ -38,13 +38,13 @@ - `struct` — определение устройства - `super` — родительский звено текущего звена - `trait` — определение особенности -- `true` — логический истинный запись +- `true` — разумный истинный запись - `type` — определение псевдонима вида или связанного вида - `union` - определить [объединение]; является ключевым словом только при использовании в объявлении объединения -- `unsafe` — обозначение небезопасного кода, функций, особенностей и их выполнений +- `unsafe` — обозначение небезопасного рукописи, функций, особенностей и их выполнений - `use` — ввод имён в область видимости - `where` — ограничение вида -- `while` — условный цикл, основанный на итоге выражения +- `while` — условный круговорот, основанный на итоге выражения ### Ключевые слова, зарезервированные для будущего использования @@ -102,9 +102,9 @@ fn main() { } ``` -Этот код собирается без ошибок. Обратите внимание, что приставка `r#` в определении имени функции указан так же, как он указан в месте её вызова в `main`. +Этот рукопись собирается без ошибок. Обратите внимание, что приставка `r#` в определении имени функции указан так же, как он указан в месте её вызова в `main`. -Сырые определители позволяют вам использовать любое слово, которое вы выберете, в качестве определителя, даже если это слово окажется зарезервированным ключевым словом. Это даёт нам больше свободы в выборе имён определителей, а также позволяет нам встраиваться с программами, написанными на языке, где эти слова не являются ключевыми. Кроме того, необработанные определители позволяют вам использовать библиотеки, написанные в исполнения Rust, отличной от используемой в вашем ящике. Например, `try` не является ключевым словом в выпуске 2015 года, но является в выпуске 2018 года. Если вы зависите от библиотеки, написанной с использованием исполнения 2015 года и имеющей функцию `try`, вам потребуется использовать правила написания сырого определителя, в данном случае `r#try`, для вызова этой функции из кода исполнения 2018 года. См. [Приложение E] для получения дополнительной сведений о изданиех Rust. +Сырые определители позволяют вам использовать любое слово, которое вы выберете, в качестве определителя, даже если это слово окажется зарезервированным ключевым словом. Это даёт нам больше свободы в выборе имён определителей, а также позволяет нам встраиваться с программами, написанными на языке, где эти слова не являются ключевыми. Кроме того, необработанные определители позволяют вам использовать библиотеки, написанные в исполнения Ржавчина, отличной от используемой в вашем ящике. Например, `try` не является ключевым словом в выпуске 2015 года, но является в выпуске 2018 года. Если вы зависите от библиотеки, написанной с использованием исполнения 2015 года и имеющей функцию `try`, вам потребуется использовать правила написания сырого определителя, в данном случае `r#try`, для вызова этой функции из рукописи исполнения 2018 года. См. [Приложение E] для получения дополнительной сведений о изданиех Ржавчина. [«Сырые определители]: #raw-identifiers diff --git a/rustbook-ru/src/appendix-02-operators.md b/rustbook-ru/src/appendix-02-operators.md index 5592ffe09..c47406a17 100644 --- a/rustbook-ru/src/appendix-02-operators.md +++ b/rustbook-ru/src/appendix-02-operators.md @@ -1,17 +1,17 @@ -## Дополнение Б: Операторы и обозначения +## Дополнение Б: Отделеный вычислительы и обозначения -Это дополнение содержит глоссарий правил написания Rust, включая операторы и другие обозначения, которые появляются сами по себе или в среде путей, обобщений, особенностей, макросов, свойств, примечаниев, упорядоченных рядов и скобок. +Это дополнение содержит глоссарий правил написания Ржавчина, включая приказчики и другие обозначения, которые появляются сами по себе или в среде путей, обобщений, особенностей, макросов, свойств, примечаниев, упорядоченных рядов и скобок. -### Операторы +### Приказчики -Таблица Б-1 содержит операторы языка Rust, пример появления оператора, короткое объяснение, возможность перегрузки оператора. Если оператор можно перегрузить, то показан особенность, с помощью которого его можно перегрузить. +Таблица Б-1 содержит приказчики языка Ржавчина, пример появления приказчика, короткое объяснение, возможность перегрузки приказчика. Если приказчик можно перегрузить, то показан особенность, с помощью которого его можно перегрузить. -Таблица Б-1: Операторы +Таблица Б-1: Приказчики -Оператор | Пример | Объяснение | Перегружаемость +Приказчик | Пример | Объяснение | Перегружаемость --- | --- | --- | --- `!` | `ident!(...)`, `ident!{...}`, `ident![...]` | Вызов макроса | -`!` | `!expr` | Побитовое или логическое отрицание | `Not` +`!` | `!expr` | Побитовое или разумное отрицание | `Not` `!=` | `expr != expr` | Сравнение "не равно" | `PartialEq` `%` | `expr % expr` | Остаток от деления | `Rem` `%=` | `var %= expr` | Остаток от деления и присваивание | `RemAssign` @@ -19,7 +19,7 @@ `&` | `&type`, `&mut type`, `&'a type`, `&'a mut type` | Указывает что данный вид заимствуется | `&` | `expr & expr` | Побитовое И | `BitAnd` `&=` | `var &= expr` | Побитовое И и присваивание | `BitAndAssign` -`&&` | `expr && expr` | Логическое И | +`&&` | `expr && expr` | Разумное И | `*` | `expr * expr` | Арифметическое умножение | `Mul` `*=` | `var *= expr` | Арифметическое умножение и присваивание | `MulAssign` `*` | `*expr` | Разыменование ссылки | `Deref` @@ -42,7 +42,7 @@ `/=` | `var /= expr` | Арифметическое деление и присваивание | `DivAssign` `:` | `pat: type`, `ident: type` | Ограничения видов | `:` | `ident: expr` | Объявление поля устройства | -`:` | `'a: loop {...}` | Метка цикла | +`:` | `'a: loop {...}` | Метка круговорота | `;` | `expr;` | Признак конца указания и элемента | `;` | `[...; len]` | Часть правил написания массива конечного размера | `<<` | `expr << expr` | Битовый сдвиг влево | `Shl` @@ -62,30 +62,30 @@ &vert; | pat &vert; pat | Иные образцы | &vert; | expr &vert; expr | Побитовое ИЛИ | `BitOr` &vert;= | var &vert;= expr | Побитовое ИЛИ и присваивание | `BitOrAssign` -&vert;&vert; | expr &vert;&vert; expr | Короткое логическое ИЛИ | +&vert;&vert; | expr &vert;&vert; expr | Короткое разумное ИЛИ | `?` | `expr?` | Возврат ошибки | -### Обозначения не-операторы +### Обозначения не-приказчики -Следующий список содержит все символы, которые не работают как операторы; то есть они не ведут себя как вызов функции или способа. +Следующий список содержит все знаки, которые не работают как приказчики; то есть они не ведут себя как вызов функции или способа. -Таблица Б-2 показывает символы, которые появляются сами по себе и допустимы в различных местах. +Таблица Б-2 показывает знаки, которые появляются сами по себе и допустимы в различных местах. -Таблица Б-2: Автономный правила написания +Таблица Б-2: Независимые правила написания Обозначение | Объяснение --- | --- -`'ident` | Именованное время жизни или метка цикла +`'ident` | Именованное время жизни или метка круговорота `...u8`, `...i32`, `...f64`, `...usize`, etc. | Числовой запись определённого вида `"..."` | Строковый запись -`r"..."`, `r#"..."#`, `r##"..."##`, etc. | Необработанный строковый запись, в котором не обрабатываются escape-символы +`r"..."`, `r#"..."#`, `r##"..."##`, etc. | Необработанный строковый запись, в котором не обрабатываются escape-знаки `b"..."` | Строковый запись байтов; создаёт массив байтов вместо строки `br"..."`, `br#"..."#`, `br##"..."##`, etc. | Необработанный строковый байтовый запись, сочетание необработанного и байтового записи -`'...'` | Символьный запись +`'...'` | Знаковый запись `b'...'` | ASCII байтовый запись &vert;...&vert; expr | Замыкание `!` | Всегда пустой вид для расходящихся функций -`_` | «Пренебрегаемое» связывание образцов; также используется для читабельности целочисленных записей +`_` | «Пренебрегаемое» связывание образцов; также используется для удобства чтения целочисленных записей Таблица Б-3 показывает обозначения которые появляются в среде путей упорядочевания звеньев @@ -94,7 +94,7 @@ Обозначение | Объяснение --- | --- `ident::ident` | Путь к пространству имён -`::path` | Путь относительно корня ящика (т. е. явный абсолютный путь) +`::path` | Путь относительно корня ящика (т. е. явный безусловный путь) `self::path` | Путь относительно текущего звена (т. е. явный относительный путь). `super::path` | Путь относительно родительского звена текущего звена `type::ident`, `::ident` | Сопряженные постоянные значения, функции и виды @@ -151,11 +151,11 @@ Обозначение | Объяснение --- | --- `//` | Однострочный примечание -`//!` | Внутренний однострочный примечание документации -`///` | Внешний однострочный примечание документации +`//!` | Внутренний однострочный примечание пособия +`///` | Внешний однострочный примечание пособия `/*...*/` | Многострочный примечание -`/*!...*/` | Внутренний многострочный примечание документации -`/**...*/` | Внешний многострочный примечание документации +`/*!...*/` | Внутренний многострочный примечание пособия +`/**...*/` | Внешний многострочный примечание пособия Таблица Б-8 показывает обозначения, которые появляются в среде использования упорядоченных рядов. diff --git a/rustbook-ru/src/appendix-03-derivable-traits.md b/rustbook-ru/src/appendix-03-derivable-traits.md index 0a54a7ebf..32d65cfa2 100644 --- a/rustbook-ru/src/appendix-03-derivable-traits.md +++ b/rustbook-ru/src/appendix-03-derivable-traits.md @@ -1,6 +1,6 @@ ## Дополнение В: Выводимые особенности -Во многих частях книги мы обсуждали свойство `derive`, которые Вы могли применить к объявлению устройства или перечисления. Свойство `derive` порождает код по умолчанию для выполнения особенности, который вы указали в `derive`. +Во многих частях книги мы обсуждали свойство `derive`, которые Вы могли применить к объявлению устройства или перечисления. Свойство `derive` порождает рукопись по умолчанию для выполнения особенности, который вы указали в `derive`. В этом дополнении, мы расскажем про все особенности, которые вы можете использовать в свойстве `derive`. Каждая раздел содержит: @@ -10,7 +10,7 @@ - Условия, в которых разрешено или запрещено выполнить особенность - Примеры случаев, которые требуют наличие особенности -Если Вам понадобилось поведение отличное от поведения при выполнения через `derive`, обратитесь к [документации по встроенной библиотеке](../std/index.html) чтобы узнать как вручную выполнить особенность. +Если Вам понадобилось поведение отличное от поведения при выполнения через `derive`, обратитесь к [пособия по встроенной библиотеке](../std/index.html) чтобы узнать как вручную выполнить особенность. Перечисленные здесь особенности являются единственными, определёнными встроенной библиотекой, которые могут быть выполнены в ваших видах с помощью `derive`. Другие особенности, определённые в встроенной библиотеке, не имеют ощутимого поведения по умолчанию, поэтому вам решать, как выполнить их для достижения ваших целей. @@ -30,7 +30,7 @@ ### `PartialEq` и `Eq` для сравнения равенства -Особенность `PartialEq` позволяет Вам сравнить предметы одного вида на эквивалентность, и включает для них использование операторов `==` и `!=`. +Особенность `PartialEq` позволяет Вам сравнить предметы одного вида на эквивалентность, и включает для них использование приказчиков `==` и `!=`. Использование `PartialEq` выполняет способ `eq`. Когда `PartialEq` используют для устройства, два предмета равны если равны *все* поля предметов, и предметы не равны, если хотя бы одно поле отлично. Когда используется для перечислений, каждый исход равен себе, и не равен другим исходам. @@ -42,7 +42,7 @@ ### `PartialOrd` и `Ord` для сравнения порядка -Особенность `PartialOrd` позволяет Вам сравнить предметы одного вида с помощью сортировки. Вид, выполняющий `PartialOrd` может использоваться с операторами `<`, `>`, `<=`, и `>=`. Вы можете выполнить особенность `PartialOrd` только для видов, выполняющих `PartialEq`. +Особенность `PartialOrd` позволяет Вам сравнить предметы одного вида с помощью сортировки. Вид, выполняющий `PartialOrd` может использоваться с приказчиками `<`, `>`, `<=`, и `>=`. Вы можете выполнить особенность `PartialOrd` только для видов, выполняющих `PartialEq`. Использование `PartialOrd` выполняет способ `partial_cmp`, который возвращает `Option` который является `None` когда значения не выстраивают порядок. Примером значения, которое не может быть упорядочено, не являются числом (`NaN`) значение с плавающей запятой. Вызов `partial_cmp` с любым числом с плавающей запятой и значением `NaN` вернёт `None`. @@ -56,27 +56,27 @@ ### `Clone` и `Copy` для повторения значений -Особенность `Clone` позволяет вам явно создать глубокую повтор значения, а также этап повторения может вызывать особый код и воспроизводить данные с кучи. Более подробно про `Clone` смотрите в разделы ["Способы взаимодействия переменных и данных: клонирование"](ch04-01-what-is-ownership.html#ways-variables-and-data-interact-clone) в разделе 4. +Особенность `Clone` позволяет вам явно создать глубокую повтор значения, а также этап повторения может вызывать особый рукопись и воспроизводить данные с кучи. Более подробно про `Clone` смотрите в разделы ["Способы взаимодействия переменных и данных: клонирование"](ch04-01-what-is-ownership.html#ways-variables-and-data-interact-clone) в разделе 4. Использование `Clone` выполняет способ `clone`, который в случае выполнения на всем виде, вызывает `clone`для каждой части данных вида. Это подразумевает, что все поля или значения в виде также должны выполнить `Clone` для использования `Clone`. Особенность `Clone` необходим в некоторых случаях. Например, для вызова способа `to_vec` для среза. Срез не владеет данными, содержащимися в нем, но вектор значений, возвращённый из `to_vec` должен владеть этими предметами, поэтому `to_vec` вызывает `clone` для всех данных. Таким образом, вид хранящийся в срезе, должен выполнить `Clone`. -Особенность `Copy` позволяет повторять значения повторяя только данные, которые хранятся на обойме, произвольный код не требуется. Смотрите раздел ["Из обоймы данные: Повторение"](ch04-01-what-is-ownership.html#stack-only-data-copy) в разделе 4 для большей сведений о `Copy`. +Особенность `Copy` позволяет повторять значения повторяя только данные, которые хранятся на обойме, произвольный рукопись не требуется. Смотрите раздел ["Из обоймы данные: Повторение"](ch04-01-what-is-ownership.html#stack-only-data-copy) в разделе 4 для большей сведений о `Copy`. -Особенность `Copy` не содержит способов для предотвращения перегрузки этих способов программистами, иначе бы это нарушило соглашение, что никакой произвольный код не запускается. Таким образом все программисты могут предполагать, что повторение значений будет происходить быстро. +Особенность `Copy` не содержит способов для предотвращения перегрузки этих способов программистами, иначе бы это нарушило соглашение, что никакой произвольный рукопись не запускается. Таким образом все программисты могут предполагать, что повторение значений будет происходить быстро. Вы можете вывести `Copy` для любого вида все части которого выполняют `Copy`. Вид который выполняет `Copy` должен также выполнить `Clone`, потому что вид выполняющий `Copy` имеет обыкновенную выполнение `Clone` который выполняет ту же задачу, что и `Copy`. -Особенность `Copy` нужен очень редко; виды, выполняющие `Copy` имеют небольшую переработку, то есть для него не нужно вызывать способ `clone`, который делает код более кратким. +Особенность `Copy` нужен очень редко; виды, выполняющие `Copy` имеют небольшую переработку, то есть для него не нужно вызывать способ `clone`, который делает рукопись более кратким. -Все, что вы делаете с `Copy` можно также делать и с `Clone`, но код может быть медленнее и требовать вызов способа `clone` в некоторых местах. +Все, что вы делаете с `Copy` можно также делать и с `Clone`, но рукопись может быть медленнее и требовать вызов способа `clone` в некоторых местах. ### `Hash` для превращения значения в значение конечного размера Особенность `Hash` позволяет превратить значение произвольного размера в значение конечного размера с использованием хеш-функции. Использование `Hash` выполняет способ `hash`. При выполнения через derive, способ `hash` сочетает итоги вызова `hash` на каждой части данных вида, то есть все поля или значения должны выполнить `Hash` для использования `Hash` с помощью derive. -Особенность `Hash` необходим в некоторых случаях. Например, для хранения ключей в `HashMap`, для их более эффективного хранения. +Особенность `Hash` необходим в некоторых случаях. Например, для хранения ключей в `HashMap`, для их более качественного хранения. ### `Default` для значений по умолчанию diff --git a/rustbook-ru/src/appendix-04-useful-development-tools.md b/rustbook-ru/src/appendix-04-useful-development-tools.md index e7c8a205b..dad6709ad 100644 --- a/rustbook-ru/src/appendix-04-useful-development-tools.md +++ b/rustbook-ru/src/appendix-04-useful-development-tools.md @@ -1,12 +1,12 @@ ## Дополнение Г - Средства разработки -В этом дополнении мы расскажем про часто используемые средства разработки, предоставляемые Rust. Мы рассмотрим самостоятельное изменение +В этом дополнении мы расскажем про часто используемые средства разработки, предоставляемые Ржавчина. Мы рассмотрим самостоятельное изменение , быстрый путь исправления предупреждений, линтер, и встраивание с IDE. ### Самостоятельное изменение с `rustfmt` -Средство `rustfmt` переделает ваш код в соответствии со исполнением кода сообщества. Многие совместные дела используют `rustfmt`, чтобы предотвратить споры о том, какой исполнение использовать при написании Rust: все изменяют свой код с помощью этого средства. +Средство `rustfmt` переделает ваша рукопись в соответствии со исполнением рукописи сообщества. Многие совместные дела используют `rustfmt`, чтобы предотвратить споры о том, какой исполнение использовать при написании Ржавчина: все изменяют свой рукопись с помощью этого средства. Для установки `rustfmt`, введите следующее: @@ -20,9 +20,9 @@ $ rustup component add rustfmt $ cargo fmt ``` -Этот приказ изменит весь код на языке Ржавчина в текущем ящике. Будет изменён только исполнение кода, смысл останется прежней. Для большей сведений о `rustfmt`, смотрите [документацию]. +Этот приказ изменит весь рукопись на языке Ржавчина в текущем ящике. Будет изменён только исполнение рукописи, смысл останется прежней. Для большей сведений о `rustfmt`, смотрите [пособие]. -### Исправление кода с `rustfix` +### Исправление рукописи с `rustfix` Средство rustfix включён в установку Ржавчина и может самостоятельно исправлять предупреждения сборщика с очевидным способом исправления сбоев, скорее всего, подходящим вам. Вероятно, вы уже видели предупреждения сборщика. Например, рассмотрим этот код: @@ -38,7 +38,7 @@ fn main() { } ``` -Мы вызываем функцию `do_something` 100 раз, но никогда не используем переменную `i` в теле цикла `for`. Ржавчина предупреждает нас об этом: +Мы вызываем функцию `do_something` 100 раз, но никогда не используем переменную `i` в теле круговорота `for`. Ржавчина предупреждает нас об этом: ```console $ cargo build @@ -77,13 +77,13 @@ fn main() { } ``` -Переменная цикла `for` теперь носит имя `_i`, и предупреждение больше не появляется. +Переменная круговорота `for` теперь носит имя `_i`, и предупреждение больше не появляется. -Также Вы можете использовать приказ `cargo fix` для перемещения вашего кода между различными изданиеми Rust. Издания будут рассмотрены в дополнении Д. +Также Вы можете использовать приказ `cargo fix` для перемещения вашей рукописи между различными изданиями Ржвчины. Издания будут рассмотрены в дополнении Д. ### Больше проверок с Clippy -Средство Clippy является собранием проверок (lints) для анализа Вашего кода, поэтому Вы можете найти простые ошибки и улучшить ваш Ржавчина код. +Средство Clippy является собранием проверок (lints) для оценки вашей рукописи, поэтому Вы можете найти простые ошибки и улучшить ваш Ржавчина рукопись. Для установки Clippy, введите следующее: @@ -123,7 +123,7 @@ error: approximate value of `f{32, 64}::consts::PI` found = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant ``` -Эта ошибка сообщает вам, что в Ржавчина уже определена более точная постоянное значение `PI`, и что ваша программа будет более правильной, если вы вместо неё будете использовать эту постоянное значение. Затем вы должны изменить свой код, чтобы использовать постоянное значение `PI`. Следующий код не приводит к ошибкам или предупреждениям от Clippy: +Эта ошибка сообщает вам, что в Ржавчине уже определена более точная постоянное значение `PI`, и что ваша программа будет более правильной, если вы вместо неё будете использовать эту постоянное значение. Затем вы должны изменить свой рукопись, чтобы использовать постоянное значение `PI`. Следующий рукопись не приводит к ошибкам или предупреждениям от Clippy: Файл: src/main.rs @@ -135,15 +135,15 @@ fn main() { } ``` -Для большей сведений о Clippy смотрите [документацию](https://github.com/rust-lang/rustfmt). +Для большей сведений о Clippy смотрите [пособие](https://github.com/rust-lang/rustfmt). ### Встраивание с IDE с помощью `rust-analyzer` -Чтобы облегчить встраивание с IDE, сообщество Ржавчина советует использовать [`rust-analyzer`]. Этот средство представляет собой набор направленных на сборщик утилит, которые используют [Language Server Protocol], который является сводом требований для взаимодействия IDE и языков программирования друг с другом. Разные клиенты могут использовать `rust-analyzer`, например [подключаемый звено анализатора Ржавчина для Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). +Чтобы облегчить встраивание с IDE, сообщество Ржавчина советует использовать [`rust-analyzer`]. Этот средство представляет собой набор направленных на сборщик утилит, которые используют [Language Server Protocol], который является сводом требований для взаимодействия IDE и языков программирования друг с другом. Разные конечные потребители могут использовать `rust-analyzer`, например [подключаемый звено оценщика Ржавчина для Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). -Посетите домашнюю страницу дела rust-analyzer для получения указаний по установке, затем установите поддержку языкового сервера в именно среде IDE. Ваша IDE получит такие возможности, как автозаполнение, переход к определению и встроенные ошибки. +Посетите домашнюю страницу дела rust-analyzer для получения указаний по установке, затем установите поддержку языкового отделенного вычислителя в именно среде IDE. Ваша IDE получит такие возможности, как самозаполнение, переход к определению и встроенные ошибки. -[документацию]: https://github.com/rust-lang/rustfmt +[пособие]: https://github.com/rust-lang/rustfmt [Language Server Protocol]: http://langserver.org/ [`rust-analyzer`]: https://rust-analyzer.github.io \ No newline at end of file diff --git a/rustbook-ru/src/appendix-05-editions.md b/rustbook-ru/src/appendix-05-editions.md index 5bff25907..f8cc9e6a8 100644 --- a/rustbook-ru/src/appendix-05-editions.md +++ b/rustbook-ru/src/appendix-05-editions.md @@ -2,24 +2,24 @@ В главе 1, можно увидеть, что приказ `cargo new` добавляет некоторые мета-данные о издания языка в файл *Cargo.toml*. Данное приложение рассказывает, что они означают. -Язык Ржавчина и его сборщик имеют шестинедельный цикл выпуска, означающий, что пользователи постоянно получают новые функции. В других языках обычно выпускают большие обновления, но редко. Объединение Ржавчина выпускает меньшие обновления, но более часто. Через некоторое время все эти небольшие изменения накапливаются. Между исполнениями обычно сложно оглянуться назад и сказать "Ого, язык сильно изменился между исполнениями Ржавчина 1.10 и Ржавчина 1.31!" +Язык Ржавчина и его сборщик имеют шестинедельный круговорот выпуска, означающий, что пользователи постоянно получают новые функции. В других языках обычно выпускают большие обновления, но редко. Объединение Ржавчина выпускает меньшие обновления, но более часто. Через некоторое время все эти небольшие изменения накапливаются. Между исполнениями обычно сложно оглянуться назад и сказать "Ого, язык сильно изменился между исполнениями Ржавчина 1.10 и Ржавчина 1.31!" -Каждые два или три года, объединение Ржавчина выпускает новую издание языка *(Rust edition)*. Каждая издание объединяет все новые особенности, которые попали в язык с новыми дополнениями, с полной, обновлённой документацией и набором средств. Новые издания поставляются как часть шестинедельного этапа исполнений. +Каждые два или три года, объединение Ржавчина выпускает новую издание языка *(Ржавчина edition)*. Каждая издание объединяет все новые особенности, которые попали в язык с новыми дополнениями, с полной, обновлённой пособием и набором средств. Новые издания поставляются как часть шестинедельного этапа исполнений. Для разных людей издания служат разным целям: -- Для активных пользователей новая издание приносит все инкрементальные изменения в удобный и понятный дополнение. +- Для неугомонных пользователей новая издание приносит все инкрементальные изменения в удобный и понятный дополнение. - Для тех, кто языком не пользуется, новая реакция является знаком, что некоторые важные улучшения, на которые возможно надо взглянуть ещё раз, попали в язык. -- Для тех кто разрабатывает на Rust, новая издание даёт некоторую точку отсчёта для дела в целом. +- Для тех кто разрабатывает на Ржавчине, новая издание даёт некоторую точку отсчёта для дела в целом. -На мгновение написания доступны две издания Rust: Ржавчина 2015 и Ржавчина 2018. Данная книга написана с использованием идиом издания Ржавчина 2018. +На мгновение написания доступны два издания: Ржавчина 2015 и Ржавчина 2018. Данная книга написана с использованием идиом издания Ржавчины 2018. -Ключ `edition` в настроечном файле *Cargo.toml* отображает, какую издание сборщик должен использовать для вашего кода. Если ключа нет, то для обратной совместимости сборщик Ржавчина использует издание `2015`. +Ключ `edition` в настроечном файле *Cargo.toml* отображает, какую издание сборщик должен использовать для вашей рукописи. Если ключа нет, то для обратной совместимости сборщик Ржавчина использует издание `2015`. -Любой дело может выбрать издание отличную от издания по умолчанию, которая равна 2015. Издания могут содержать несовместимые изменения, включая новые ключевые слова, которые могут враждовать с определителями в коде. Однако, пока вы не переключитесь на новую издание, ваш код будет продолжать собираться даже после обновления используемой исполнения сборщика. +Любой дело может выбрать издание отличную от издания по умолчанию, которая равна 2015. Издания могут содержать несовместимые изменения, включая новые ключевые слова, которые могут враждовать с определителями в рукописи. Однако, пока вы не переключитесь на новую издание, ваша рукопись будет продолжать собираться даже после обновления используемой исполнения сборщика. -Все исполнения сборщика Ржавчина поддерживают любую издание, которая предшествовала выпуску текущей, и они могут линковать дополнения любой поддерживаемой издания. Изменения изданий действуют только на способ начального разбора сборщиком исходного кода. Поэтому, если вы используете 2015 издание, а одна из ваших зависимостей использует 2018, ваш дело будет собран и сможет пользоваться этой зависимостью. Обратная случаей, когда ваш дело использует Ржавчина 2018, а зависимость использует Ржавчина 2015, работает таким же образом. +Все исполнения сборщика Ржавчины поддерживают любую издание, которая предшествовала выпуску текущей, и они могут линковать дополнения любой поддерживаемой издания. Изменения изданий действуют только на способ начального разбора сборщиком исходного рукописи. Поэтому, если вы используете 2015 издание, а одна из ваших зависимостей использует 2018, ваш дело будет собран и сможет пользоваться этой зависимостью. Обратная случай, когда ваш дело использует Ржавчина 2018, а зависимость использует Ржавчина 2015, работает таким же образом. -Внесём ясность: большая часть возможностей будет доступна во всех изданиях. Разработчики, использующие любую издание Rust, будут продолжать получать улучшения по мере выпуска новых исполнений. Однако в некоторых случаях, в основном, когда добавляются новые ключевые слова, некоторые новые возможности могут быть доступны только в последних изданиях. Нужно переключить издание, чтобы воспользоваться новыми возможностями. +Внесём ясность: большая часть возможностей будет доступна во всех изданиях. Разработчики, использующие любую издание Ржавчина, будут продолжать получать улучшения по мере выпуска новых исполнений. Однако в некоторых случаях, в основном, когда добавляются новые ключевые слова, некоторые новые возможности могут быть доступны только в последних изданиях. Нужно переключить издание, чтобы воспользоваться новыми возможностями. -Для получения больше подробностей, есть полная книга [*Edition Guide*](https://doc.rust-lang.org/stable/edition-guide/) про издания, в которой перечисляются различия между изданиями и объясняется, как самостоятельно обновить свой код на новую издание с помощью приказы `cargo fix`. +Для получения больше подробностей, есть полная книга [*Edition Guide*](https://doc.rust-lang.org/stable/edition-guide/) про издания, в которой перечисляются различия между изданиями и объясняется, как самостоятельно обновить свой рукопись на новую издание с помощью приказы `cargo fix`. diff --git a/rustbook-ru/src/appendix-06-translation.md b/rustbook-ru/src/appendix-06-translation.md index 64d50972a..73f1d7b33 100644 --- a/rustbook-ru/src/appendix-06-translation.md +++ b/rustbook-ru/src/appendix-06-translation.md @@ -1,6 +1,6 @@ ## Приложение Е: Переводы книги -Для ресурсов на языках, отличных от английского. Большинство из них все ещё в разработке; см. [ярлык «Переводы»](https://github.com/rust-lang/book/issues?q=is%3Aopen+is%3Aissue+label%3ATranslations), чтобы помочь или сообщить нам о новом переводе! +Для источниковов на языках, отличных от английского. Большинство из них все ещё в разработке; см. [ярлык «Переводы»](https://github.com/rust-lang/book/issues?q=is%3Aopen+is%3Aissue+label%3ATranslations), чтобы помочь или сообщить нам о новом переводе! - [Português](https://github.com/rust-br/rust-book-pt-br) (BR) - [Português](https://github.com/nunojesus/rust-book-pt-pt) (PT) diff --git a/rustbook-ru/src/appendix-07-newest-features.md b/rustbook-ru/src/appendix-07-newest-features.md index 0dbe1dd41..b9459bcf9 100644 --- a/rustbook-ru/src/appendix-07-newest-features.md +++ b/rustbook-ru/src/appendix-07-newest-features.md @@ -1,11 +1,11 @@ # Приложение Г- Новый возможности -Это приложение описывает возможности. которые был добавлен в безотказную исполнение Rust +Это приложение описывает возможности. которые был добавлен в безотказное исполнение Rust с особенности написания данной книги. ## Быстрая объявление поля устройства Мы можем объявить данные устройства (struct, enum, union) с помощью именованных -полей. Вид `fieldname: fieldname`. Это устройство уменьшает код объявления +полей. Вид `fieldname: fieldname`. Это устройство уменьшает рукопись объявления устройства. ```rust @@ -35,10 +35,10 @@ fn main() { ## Returning from loops -Одна из функций оператора цикла `loop` - это отслеживание логический действий, +Одна из функций приказчика круговорота `loop` - это отслеживание разумный действий, таких как проверка завершил ли поток свою работы или нет. Но, бывают также случаи, -когда Вам необходимо вернуть значение из цикла. Если вы добавите оператор `break`, -вы сможете использовать оператора цикла `loop`, как анонимную функцию, которая +когда Вам необходимо вернуть значение из круговорота. Если вы добавите приказчик `break`, +вы сможете использовать приказчика круговорота `loop`, как анонимную функцию, которая возвращает значение: ```rust diff --git a/rustbook-ru/src/appendix-07-nightly-rust.md b/rustbook-ru/src/appendix-07-nightly-rust.md index a443506f1..153395ace 100644 --- a/rustbook-ru/src/appendix-07-nightly-rust.md +++ b/rustbook-ru/src/appendix-07-nightly-rust.md @@ -1,24 +1,24 @@ -## Дополнение Ё - Как создаётся Ржавчина и “Nightly Rust” +## Дополнение Ё - Как создаётся Ржавчина и “Ночное издание Ржавчины” -Это дополнение рассказывает как создаётся Rust, и как это влияет на Вас как на разработчика. +Это дополнение рассказывает как создаётся Ржавчина, и как это влияет на Вас как на разработчика. -### Безотказность без стагнации +### Безотказность без застоя -Как язык, Ржавчина *много* заботиться о безотказности Вашего кода. Мы хотим чтобы Ржавчина был прочным фундаментом, вашей опорой, и если бы все постоянно менялось, это было бы невозможно. В то же время, если мы не можем экспериментировать с различными возможностями, мы не можем обнаружить важные сбоев до исполнения, когда мы не можем их изменить. +Как язык, Ржавчина *много* заботиться о безотказности вашей рукописи. Мы хотим чтобы Ржавчина был прочным фундаментом, вашей опорой, и если бы все постоянно менялось, это было бы невозможно. В то же время, если мы не можем экспериментировать с различными возможностями, мы не можем обнаружить важные сбоев до исполнения, когда мы не можем их изменить. -Нашим решением сбоев является “безотказность без стагнации”, и наш руководящий принцип: Вы никогда не должны бояться перехода на новую безотказную исполнение Rust. Каждое обновление должно быть безболезненным, но также должно добавлять новые функции, меньше дефектов и более быструю скорость сборки. +Нашим решением сбоев является “безотказность без застоя”, и наш руководящий принцип: Вы никогда не должны бояться перехода на новую безотказное исполнение Ржавчины. Каждое обновление должно быть безболезненным, но также должно добавлять новые функции, меньше дефектов и более быструю скорость сборки. ### Ту-ту! потоки выпуска и поездка на поезде -Разработка языка Ржавчина работает по принципу *расписания поездов*. То есть, вся разработка совершается в ветке `master` Ржавчина хранилища. Выпуски следуют подходы последовательного выпуска продукта (software release train), которая была использована Cisco IOS и другими программными продуктами. Есть три *потока выпуска* Rust: +Разработка языка Ржавчина работает по принципу *расписания поездов*. То есть, вся разработка совершается в ветке `master` Ржавчина хранилища. Выпуски следуют подходы последовательного выпуска продукта (software release train), которая была использована Cisco IOS и другими программными продуктами. Есть три *потока выпуска* Ржавчина: - Ночной (Nightly) - Бета (Beta) - Безотказный (Stable) -Большинство Ржавчина разработчиков используют безотказную исполнение, но те кто хотят попробовать экспериментальные новые функции, должны использовать Nightly или Beta. +Большинство Ржавчина разработчиков используют безотказное исполнение, но те кто хотят попробовать экспериментальные новые функции, должны использовать Nightly или Beta. -Приведём пример, как работает этап разработки и выпуска новых исполнений. Давайте предположим, что объединение Ржавчина работает над исполнением Ржавчина 1.5. Его исполнение состоялся в декабре 2015 года, но это даст существующегостичность номера исполнения. Была добавлена новая возможность в Rust: новые изменения в ветку `master`. Каждую ночь выпускается новая ночная исполнение Rust. Каждый день является днём выпуска ночной исполнения и эти выпуски создаются нашей устройством самостоятельно . По мере того как идёт время, наши выпуски выглядят так: +Приведём пример, как работает этап разработки и выпуска новых исполнений. Давайте предположим, что объединение Ржавчина работает над исполнением Ржавчина 1.5. Его исполнение состоялся в декабре 2015 года, но это даст существующегостичность номера исполнения. Была добавлена новая возможность в Ржавчине: новые изменения в ветку `master`. Каждую ночь выпускается новая ночная исполнение Ржавчины. Каждый день является днём выпуска ночной исполнения и эти выпуски создаются нашей устройством самостоятельно . По мере того как идёт время, наши выпуски выглядят так: ```text nightly: * - - * - - * @@ -32,7 +32,7 @@ nightly: * - - * - - * beta: * ``` -Многие пользователи Ржавчина не используют активно бета-исполнение, но проверяют бета-исполнение в их системе CI для помощи Ржавчина обнаружить сбоев обратной совместимости. В это время каждую ночь выпускается новая исполнение Nightly: +Многие пользователи Ржавчины не используют постоянно бета-исполнение, но проверяют бета-исполнение в их системе CI для помощи Ржавчина обнаружить сбоев обратной совместимости. В это время каждую ночь выпускается новая исполнение Nightly: ```text nightly: * - - * - - * - - * - - * @@ -40,7 +40,7 @@ nightly: * - - * - - * - - * - - * beta: * ``` -Предположим, что была найдена отступление. Хорошо, что мы можем проверять бета-исполнение перед тем как отступление попала в безотказную исполнение! Исправление отправляется в ветку `master`, поэтому исполнение nightly исправлена и затем исправление также направляется в ветку `beta`, и происходит новый выпуск бета-исполнения: +Предположим, что была найдена отступление. Хорошо, что мы можем проверять бета-исполнение перед тем как отступление попала в безотказное исполнение! Исправление отправляется в ветку `master`, поэтому исполнение nightly исправлена и затем исправление также направляется в ветку `beta`, и происходит новый выпуск бета-исполнения: ```text nightly: * - - * - - * - - * - - * - - * @@ -48,7 +48,7 @@ nightly: * - - * - - * - - * - - * - - * beta: * - - - - - - - - * ``` -Через шесть недель после выпуска бета-исполнения, наступает время для выпуска безотказной исполнения! Ветка `stable` создаётся из ветки `beta`: +Через шесть недель после выпуска бета-исполнения, наступает время для выпуска безотказного исполнения! Ветка `stable` создаётся из ветки `beta`: ```text nightly: * - - * - - * - - * - - * - - * - * - * @@ -68,29 +68,29 @@ beta: * - - - - - - - - * * stable: * ``` -Это называется “прообраз поезда” (train model), потому что каждые шесть недель выпуск “покидает станцию”, но ему все ещё нужно пройти поток beta, чтобы попасть в безотказную исполнение. +Это называется “прообраз поезда” (train model), потому что каждые шесть недель выпуск “покидает станцию”, но ему все ещё нужно пройти поток beta, чтобы попасть в безотказное исполнение. -Rust выпускается каждые шесть недель, как часы. Если вы знаете дату одного выпуска Rust, вы знаете дату выпуска следующего: это шесть недель позднее. Хорошим особенностью выпуска исполнений каждые шесть недель является то, что следующий поезд прибывает скоро. Если какая-то функция не попадает в исполнение, не надо волноваться: ещё один выпуск произойдёт очень скоро! Это помогает снизить давление в случае если функция возможно не отполирована к дате выпуска. +Ржавчина выпускается каждые шесть недель, как часы. Если вы знаете дату одного выпуска Ржавчина, вы знаете дату выпуска следующего: это шесть недель позднее. Хорошим особенностью выпуска исполнений каждые шесть недель является то, что следующий поезд прибывает скоро. Если какая-то функция не попадает в исполнение, не надо волноваться: ещё один выпуск произойдёт очень скоро! Это помогает снизить давление в случае если функция возможно не отполирована к дате выпуска. -Благодаря этому этапу, вы всегда можете посмотреть следующую исполнение Ржавчина и убедиться, что на неё легко будет перейти: если бета-выпуск будет работать не так как ожидалось, вы можете сообщить об этом разработчикам и он будет исправлен перед выпуском безотказной исполнения! Поломки в бета-исполнения случаются относительно редко, но `rustc` все ещё является частью программного обеспечения, поэтому дефекты все ещё существуют. +Благодаря этому этапу, вы всегда можете посмотреть следующую исполнение Ржавчины и убедиться, что на неё легко будет перейти: если бета-выпуск будет работать не так как ожидалось, вы можете сообщить об этом разработчикам и он будет исправлен перед выпуском безотказного исполнения! Поломки в бета-исполнения случаются относительно редко, но `rustc` все ещё является частью программного обеспечения, поэтому дефекты все ещё существуют. ### Ненадежные функции -У этой подходы выпуска есть ещё один плюс: ненадежные функции. Ржавчина использует технику называемую “флаги возможностей” (feature flags) для определения функций, которые были включены в выпуске. Если новая функция находится в активной разработке, она попадает в ветку `master`, и поэтому попадает в ночную исполнение, но с *флагом функции* (feature flag). Если как пользователь, вы хотите попробовать работу такой функции, находящейся в разработке, вы должны использовать ночную исполнение Ржавчина и указать в вашем исходном коде определённый флаг. +У этой подходы выпуска есть ещё один плюс: ненадежные функции. Ржавчина использует технику называемую “клейма возможностей” (feature flags) для определения функций, которые были включены в выпуске. Если новая функция находится в усиленной разработке, она попадает в ветку `master`, и поэтому попадает в ночную исполнение, но с *клеймом функции* (feature flag). Если как пользователь, вы хотите попробовать работу такой функции, находящейся в разработке, вы должны использовать ночную исполнение Ржавчины и указать в вашем исходном рукописи определённый клеймо. -Если вы используете бета или безотказную исполнение Rust, Вы не можете использовать флаги функций. Этот ключевой мгновение позволяет использовать в действительностиновые возможности перед их отладкой. Это может использоваться желающими идти в ногу со временем, а другие могут использовать безотказную исполнение и быть уверенными что их код не сломается. Безотказность без стагнации. +Если вы используете бета или безотказное исполнение Ржавчины, Вы не можете использовать клейма функций. Этот ключевое мгновение позволяет использовать в действительности новые возможности перед их отладкой. Это может использоваться желающими идти в ногу со временем, а другие могут использовать безотказное исполнение и быть уверенными что их рукопись не сломается. Безотказность без застоя. -Эта книга содержит сведения только о безотказных возможностях, так как разрабатываемые возможности продолжают меняться в этапе и несомненно они будут отличаться в зависимости от того, когда эта книга написана и когда эти возможности будут включены в безотказные сборки. Вы можете найти сведения о возможностях ночной исполнения в интернете. +Эта книга содержит сведения только о безотказных возможностях, так как разрабатываемые возможности продолжают меняться в этапе и несомненно они будут отличаться в зависимости от того, когда эта книга написана и когда эти возможности будут включены в безотказные сборки. Вы можете найти сведения о возможностях ночной исполнения в мировой сети. ### Rustup и значение ночной исполнения Rust -Rustup делает лёгким изменение между различными потоками Rust, на вездесущем или местном для дела уровне. По умолчанию устанавливается безотказная исполнение Rust. Для установки ночной исполнения выполните приказ: +Rustup делает лёгким изменение между различными потоками Ржавчина, на вездесущем или местном для дела уровне. По умолчанию устанавливается безотказное исполнение Ржавчины. Для установки ночной исполнения выполните приказ: ```console $ rustup toolchain install nightly ``` -Вы можете также увидеть все установленные *средства разработчика (toolchains)* (исполнения Ржавчина и сопряженные составляющие) с помощью `rustup`. Это пример вывода у одного из авторов Ржавчина с компьютером на Windows: +Вы можете также увидеть все установленные *средства разработчика (toolchains)* (исполнения Ржавчина и сопряженные составляющие) с помощью `rustup`. Это пример вывода у одного из составителей Ржавчина с компьютером на Windows: ```powershell > rustup toolchain list @@ -106,14 +106,14 @@ $ cd ~/projects/needs-nightly $ rustup override set nightly ``` -Теперь каждый раз, когда вы вызываете `rustc` или `cargo` внутри *~/projects/needs-nightly*, `rustup` будет следить за тем, чтобы вы используете ночную исполнение Rust, а не безотказную по умолчанию. Это очень удобно, когда у вас есть множество Ржавчина дел! +Теперь каждый раз, когда вы вызываете `rustc` или `cargo` внутри *~/projects/needs-nightly*, `rustup` будет следить за тем, чтобы вы используете ночную исполнение Ржавчины, а не безотказную по умолчанию. Это очень удобно, когда у вас есть множество Ржавчина дел! ### Этап RFC и приказы -Итак, как вы узнаете об этих новых возможностях? Прообраз разработки Ржавчина следует *этапу запроса примечаниев (RFC - Request For Comments)*. Если хотите улучшить Rust, вы можете написать предложение, которое называется RFC. +Итак, как вы узнаете об этих новых возможностях? Прообраз разработки Ржавчина следует *этапу запроса примечаниев (RFC - Request For Comments)*. Если хотите улучшить Ржавчина, вы можете написать предложение, которое называется RFC. -Любой может написать RFC для улучшения Rust, предложения рассматриваются и обсуждаются приказом Rust, которая состоит из множества тематических объединений и общин. На [веб-сайте Rust](https://www.rust-lang.org/governance) есть полный список приказов, который включает приказы для каждой области дела: внешний вид языка, выполнение сборщика, инфраустройства, документация и многое другое. Соответствующая приказ читает предложение и примечания, пишет некоторые собственные примечания и в конечном итоге, приходит к согласию принять или отклонить эту возможность. +Любой может написать RFC для улучшения Ржавчина предложения рассматриваются и обсуждаются приказом Ржавчина, которая состоит из множества тематических объединений и общин. На [сетевом-сайте Rust](https://www.rust-lang.org/governance) есть полный список приказов, который включает приказы для каждой области дела: внешний вид языка, выполнение сборщика, инфраустройства, пособие и многое другое. Соответствующая приказ читает предложение и примечания, пишет некоторые собственные примечания и в конечном итоге, приходит к согласию принять или отклонить эту возможность. -Если новая возможность принята и кто-то может выполнить её, то задача открывается в хранилища Rust. Человек выполняющий её, вполне может не быть тем, кто предложил эту возможность! Когда выполнение готова, она попадает в `master` ветвь с флагом функции, как мы обсуждали в разделе ["Небезотказных функциях"](#unstable-features). +Если новая возможность принята и кто-то может выполнить её, то задача открывается в хранилища Ржавчина. Человек выполняющий её, вполне может не быть тем, кто предложил эту возможность! Когда выполнение готова, она попадает в `master` ветвь с клеймом функции, как мы обсуждали в разделе ["Небезотказных функциях"](#unstable-features). -Через некоторое время, разработчики Ржавчина использующие ночные выпуски, смогут опробовать новую возможность, члены приказы обсудят её, как она работает в ночной исполнения и решат, должна ли она попасть в безотказную исполнение Ржавчина или нет. Если принимается решение двигать её вперёд, ограничение функции с помощью флага убирается и функция теперь считается безотказной! Она едет в новую безотказную исполнение Rust. +Через некоторое время, разработчики Ржавчина использующие ночные выпуски, смогут опробовать новую возможность, члены приказы обсудят её, как она работает в ночной исполнения и решат, должна ли она попасть в безотказное исполнение Ржавчины или нет. Если принимается решение двигать её вперёд, ограничение функции с помощью клейма убирается и функция теперь считается безотказной! Она едет в новую безотказное исполнение Ржавчины. diff --git a/rustbook-ru/src/ch00-00-introduction.md b/rustbook-ru/src/ch00-00-introduction.md index f45044460..51c5b7efe 100644 --- a/rustbook-ru/src/ch00-00-introduction.md +++ b/rustbook-ru/src/ch00-00-introduction.md @@ -1,90 +1,90 @@ # Введение -> Примечание. Это издание книги такое же, как и [Язык программирования Rust], доступное в печатном и электронном виде от [No Starch Press]. +> Примечание. Это издание книги такое же, как и [Язык программирования Ржавчина], доступное в печатном и электронном виде от [No Starch Press]. -Добро пожаловать в *The Ржавчина Programming Language*, вводную книгу о Rust. Язык программирования Ржавчина помогает создавать быстрые, более надёжные приложения. Хорошая удобство и низкоуровневый управление часто являются противоречивыми требованиями для внешнего видаязыков программирования; Ржавчина бросает вызов этому вражде. Благодаря уравновешенности мощных технических возможностей c большим удобством разработки, Ржавчина предоставляет возможности управления низкоуровневыми элементами (например, использование памяти) без трудностей, привычно связанных с таким управлением. +Добро пожаловать в *Язык программирования Ржавчина*, вводную книгу о Ржавчине. Язык программирования Ржавчина помогает создавать быстрые, более надёжные приложения. Хорошее удобство и низкоуровневое управление часто являются противоречивыми требованиями для внешнего вида языков программирования; Ржавчина бросает вызов этому вражде. Благодаря уравновешенности мощных технических возможностей c большим удобством разработки, Ржавчина предоставляет возможности управления низкоуровневыми элементами (например, использование памяти) без трудностей, привычно связанных с таким управлением. ## Кому подходит Rust -Rust наилучше подходит для многих людей по целому ряду причин. Давайте рассмотрим несколько наиболее важных объединений. +Ржавчина наилучше подходит для многих людей по целому ряду причин. Давайте рассмотрим несколько наиболее важных обстоятельств. ### Объединения разработчиков -Rust показал себя как производительный средство для совместной работы больших приказов разработчиков с разным уровнем знаний в области системного программирования. Низкоуровневый код подвержен различным трудноуловимым ошибкам, которые в большинстве других языков могут быть обнаружены только с помощью тщательного проверки и проверки кода опытными разработчиками. В Ржавчина сборщик играет значение привратника, отказываясь собирать код с этими неуловимыми ошибками, включая ошибки одновременности. Работая вместе с сборщиком, приказ может сосредоточиться на работе над логикой программы, а не над поиском ошибок. +Ржавчина показал себя как высокопроизводительное средство для совместной работы больших объединений разработчиков по количеству человек с разным уровнем знаний в области системного программирования. Низкоуровневую рукопись подвержена различным трудноуловимым ошибкам, которые в большинстве других языков могут быть обнаружены только с помощью тщательной самопроверки и проверки рукописи опытными разработчиками. В Ржавчине сборщик играет значение привратника, отказываясь собирать рукопись с этими неуловимыми ошибками, включая ошибки одновременности. Работая вместе с сборщиком, приказ может сосредоточиться на работе над ходом мыслей программы, а не над поиском ошибок. -Rust также привносит современные средства разработчика в мир системного программирования: +Ржавчина также привносит современные средства разработчика в мир системного программирования: -- Cargo, входящий в состав управленец зависимостей и средство сборки, делает добавление, сборку и управление зависимостями безболезненным и согласованным в рамках всей внутреннего устройства Rust. -- Средство изменения Rustfmt обеспечивает единый исполнение кодирования для всех разработчиков. -- Ржавчина Language Server обеспечивает встраивание с встроенной средой разработки (IDE) для автодополнения кода и встроенных сообщений об ошибках. +- Cargo, управленец зависимостей входящий в состав и дополнительно как средство сборки, выполняет добавление, сборку и управление зависимостями безболезненным и согласованным в рамках всего внутреннего устройства Ржавчина. +- Средство изменения Rustfmt обеспечивает единое исполнение внешнего вида рукописей для всех разработчиков. +- Ржавчина Language Server обеспечивает взаимодействие со встроенной средой разработки (IDE) для самодополнения рукописи и встроенных сообщений об ошибках. -Благодаря применению этих и других средств в внутреннем устройстве Ржавчина разработчики способны производительно работать при написании кода системного уровня. +Благодаря применению этих и других средств в внутреннем устройстве Ржавчины разработчики способны производительно работать при написании рукописи системного уровня. -### Студенты +### Учащиеся -Rust полезен для студентов и тех, кто увлечен в изучении системных подходов. Используя Rust, многие люди узнали о таких темах, как разработка операционных систем. Сообщество радушно и с удовольствием ответит на вопросы начинающих. Благодаря усилиям — таким, как эта книга — приказы Ржавчина хотят сделать подходы систем более доступными для большего числа людей, особенно для новичков в программировании. +Ржавчина полезен для учащихся и тех, кто увлечен в изучении системных подходов. Используя Ржавчина многие люди узнали о таких темах, как разработка операционных систем. Сообщество радушно и с удовольствием ответит на вопросы начинающих. Благодаря усилиям — таким, как эта книга — приказы Ржавчина хотят сделать подходы систем более доступными для большего числа людей, особенно для новичков в программировании. ### Предприятия -Сотни больших и малых предприятий используют Ржавчина в промышленных условиях для решения различных задач, включая средства приказной строки, веб-сервисы, средства DevOps, встраиваемые устройства, анализ и транскодирование аудио и видео, криптовалюты, биоинформатику, поисковые системы, приложения Интернета вещей, машинное обучение и даже основные части веб-браузера Firefox. +Сотни больших и малых предприятий используют Ржавчину в промышленных условиях для решения различных задач, включая средства приказной строки, сетевые-сервисы, средства DevOps, встраиваемые устройства, оценка и транскодирование аудио и видео, криптовалюты, биоинформатику, поисковые системы, приложения Интернета вещей, машинное обучение и даже основные части сетевого-обозревателя Firefox. ### Разработчики Open Source -Rust предназначен для людей, которые хотят развивать язык программирования Rust, сообщество, средства для разработчиков и библиотеки. Мы будем рады, если вы внесёте свой вклад в развитие языка Rust. +Ржавчина предназначена для людей, которые хотят развивать язык программирования Ржавчина, само сообщество, средства для разработчиков и библиотеки. Мы будем рады, если вы внесёте свой вклад в развитие языка Ржавчины. ### Люди, ценящие скорость и безотказность -Rust предназначен для любителей скорости и безотказности в языке. Под скоростью мы подразумеваем как быстродействие программы на Rust, так и быстроту, с которой Ржавчина позволяет писать программы. Проверки сборщика Ржавчина обеспечивают безотказность за счёт полезных дополнений и переработки кода. Это выгодно отличается от хрупкого унаследованного кода в языках без таких проверок, который разработчики часто боятся изменять. Благодаря обеспечению абстракций с нулевой стоимостью, высокоуровневых возможностей, собираемых в низкоуровневый код такой же быстрый, как и написанный вручную, Ржавчина стремится сделать безопасный код ещё и быстрым. +Ржавчина предназначена для любителей скорости и безотказности в языке. Под скоростью мы подразумеваем как быстродействие программы на Ржавчине, так и быстроту, с которой Ржавчина позволяет писать программы. Проверки сборщика Ржавчины обеспечивают безотказность за счёт большого количества полезных дополнений и переработки рукописи. Это выгодно отличается от хрупкого унаследованной рукописи в языках без таких проверок, которую разработчики часто боятся изменять. Благодаря обеспечению абстракций с нулевой стоимостью, высокоуровневых возможностей, собираемых в низкоуровневую рукопись - производительность такая же быстрая, как и написанная вручную, Ржавчина стремится сделать безопасную рукопись ещё и быстрой. -Язык Ржавчина надеется поддержать и многих других пользователей; перечисленные здесь - лишь самые значимые увлеченные лица. В целом, главная цель Ржавчина - избавиться от соглашений, на которые программисты шли десятилетиями, обеспечив безопасность *и* производительность, скорость *и* удобство. Попробуйте Ржавчина и убедитесь, подойдут ли вам его решения. +Язык Ржавчина надеется поддержать и многих других пользователей; перечисленные здесь - лишь самые значимые увлеченные лица. В целом, главная цель Ржавчина - избавиться от соглашений, на которые программисты шли десятилетиями, обеспечив безопасность *и* производительность, скорость *и* удобство. Попробуйте Ржавчину и убедитесь, подойдут ли вам его решения. ## Для кого эта книга -В этой книге предполагается, что вы писали код на другом языке программирования, но не оговаривается, на каком именно. Мы постарались сделать источник доступным для широкого круга людей с разным уровнем подготовки в области программирования. Мы не будем тратить время на обсуждение *сути понятия* программирования или как его понимать. Если вы совсем новичок в программировании, советуем прочитать книгу, посвящённую введению в программирование. +В этой книге предполагается, что вы писали рукописи на другом языке программирования, но не оговаривается, на каком именно. Мы постарались сделать пособие доступным для широкого круга людей с разным уровнем подготовки в области программирования. Мы не будем тратить время на обсуждение *сути понятия* программирования или как его понимать. Если вы совсем новичок в программировании, советуем прочитать книгу, посвящённую введению в программирование. ## Как использовать эту книгу В целом, книга предполагает, что вы будете читать последовательно от начала до конца. Более поздние главы опираются на подходы, изложенные в предыдущих главах, а предыдущие главы могут не углубляться в подробности именно темы, так как в последующих главах они будут рассматриваться более подробно. -В этой книге вы найдёте два вида глав: главы о подходах и главы с делом. В главах о подходах вы узнаете о каком-либо особенности Rust. В главах дела мы будем вместе создавать небольшие программы, применяя то, что вы уже узнали. Главы 2, 12 и 20 - это главы дела; остальные - главы о подходах. +В этой книге вы найдёте два вида глав: главы о самих используемых подходах и главы с применением этих знаний на деле. В главах о подходах вы узнаете о каком-либо особенности Ржавчины. В главах дела мы будем вместе создавать небольшие программы, применяя то, что вы уже узнали. Главы 2, 12 и 20 - это главы дела; остальные - главы о подходах. -Глава 1 объясняет, как установить Rust, как написать программу "Hello, world!" и как использовать Cargo, управленец дополнений и средство сборки Rust. Глава 2 - это опытное введение в написание программы на Rust, в которой вам предлагается создать игру для угадывания чисел. Здесь мы рассмотрим подходы на высоком уровне, а в последующих главах будет предоставлена дополнительная сведения. Если вы хотите сразу же приступить к работе, глава 2 - самое подходящее место для этого. В главе 3 рассматриваются возможности Rust, схожие с возможностями других языков программирования, а в главе 4 вы узнаете о системе владения Rust. Если вы особенно дотошный ученик и предпочитаете изучить каждую подробность, прежде чем переходить к следующей, возможно, вы захотите пропустить главу 2 и сразу перейти к главе 3, вернувшись к главе 2, когда захотите поработать над делом, применяя изученные подробности. +Глава 1 объясняет, как установить Ржавчину, как написать программу "Hello, world!" и как использовать Cargo, управленец дополнений и средство сборки Ржавчина. Глава 2 - это опытное введение в написание программы на Ржавчине в которой вам предлагается создать игру для угадывания чисел. Здесь мы рассмотрим подходы на высоком уровне, а в последующих главах будут предоставлены дополнительные сведения. Если вы хотите сразу же приступить к работе, глава 2 - самое подходящее место для этого. В главе 3 рассматриваются возможности Ржавчины схожие с возможностями других языков программирования, а в главе 4 вы узнаете о системе владения Ржавчины. Если вы особенно дотошный ученик и предпочитаете изучить каждую подробность, прежде чем переходить к следующей, возможно, вы захотите пропустить главу 2 и сразу перейти к главе 3, вернувшись к главе 2, когда захотите поработать над делом, применяя изученные подробности. -Глава 5 описывает устройства и способы, а глава 6 охватывает перечисления, выражения `match` и устройства управления потоком `if let`. Вы будете использовать устройства и перечисления для создания пользовательских видов в Rust. +Глава 5 описывает устройства и способы, а глава 6 охватывает перечисления, выражения `match` и устройства управления потоком `if let`. Вы будете использовать устройства и перечисления для создания пользовательских видов в Ржавчине. -В главе 7 вы узнаете о системе звеньев Rust, о правилах согласования закрытости вашего кода и его открытом внешней оболочке прикладного программирования (API). В главе 8 обсуждаются некоторые распространённые устройства данных - собрания, которые предоставляет обычная библиотека, такие как векторы, строки и HashMaps. В главе 9 рассматриваются философия и способы обработки ошибок в Rust. +В главе 7 вы узнаете о системе звеньев Ржавчины, о правилах согласования закрытости вашей рукописи и её открытой внешней оболочке прикладного программирования (API). В главе 8 обсуждаются некоторые распространённые устройства данных - собрания, которые предоставляет обычная библиотека, такие как векторы, строки и HashMaps. В главе 9 рассматриваются мировоззрение и способы обработки ошибок в Ржавчине. -В главе 10 рассматриваются образцовые виды данных, особенности и времена жизни, позволяющие написать код, который может использоваться разными видами. Глава 11 посвящена проверке, которое даже с заверениями безопасности в Ржавчина необходимо для обеспечения правильной логики вашей программы. В главе 12 мы создадим собственную выполнение подмножества возможности средства приказной строки `grep`, предназначенного для поиска текста в файлах. Для этого мы будем использовать многие подходы, которые обсуждались в предыдущих главах. +В главе 10 рассматриваются образцовые виды данных, особенности и времена жизни, позволяющие написать рукопись, которая может использоваться разными видами. Глава 11 посвящена проверке, которое даже с заверениями безопасности в Ржавчине необходимо для обеспечения правильной хода мыслей вашей программы. В главе 12 мы создадим собственное исполнене подмножества возможности средства приказной строки `grep`, предназначенного для поиска текста в файлах. Для этого мы будем использовать многие подходы, которые обсуждались в предыдущих главах. -В главе 13 рассматриваются замыкания и повторители: особенности Rust, пришедшие из полезных языков программирования. В главе 14 мы более подробно рассмотрим Cargo и поговорим о лучших способах распространения ваших библиотек среди других разработчиков. В главе 15 обсуждаются умные указатели, которые предоставляет обычная библиотека, и особенности, обеспечивающие их возможность. +В главе 13 рассматриваются замыкания и повторители: особенности Ржавчины, пришедшие из других полезных языков программирования. В главе 14 мы более подробно рассмотрим Cargo и поговорим о лучших способах распространения ваших библиотек среди других разработчиков. В главе 15 обсуждаются умные указатели, которые предоставляет обычная библиотека, и особенности, обеспечивающие их возможность. -В главе 16 мы рассмотрим различные подходы одновременного программирования и поговорим о возможности Ржавчина для безбоязненного многопоточно программирования. В главе 17 рассматривается сравнение идиом Ржавчина с принципами предметно-направленного программирования, которые наверняка вам знакомы. +В главе 16 мы рассмотрим различные подходы одновременного программирования и поговорим о возможности Ржавчины для безбоязненного многопоточно программирования. В главе 17 рассматривается сравнение идиом Ржавчины с принципами предметно-направленного программирования, которые наверняка вам знакомы. -Глава 18 - это справочник по образцам и сопоставлению с образцами, которые являются мощными способами выражения мыслей в программах на Rust. Глава 19 содержит множество важных дополнительных тем, включая небезопасный Rust, макросы и многое другое о времени жизни, особенностях, видах, функциях и замыканиях. +Глава 18 - это справочник по образцам и сопоставлению с образцами, которые являются мощными способами выражения мыслей в программах на Ржавчине. Глава 19 содержит множество важных дополнительных тем, включая небезопасный язык Ржавчина, макросы и многое другое о времени жизни, особенностях, видах, функциях и замыканиях. -В главе 20 мы завершим дело, в котором выполняем низкоуровневый многопоточный веб-сервер! +В главе 20 мы завершим дело, в котором выполняем низкоуровневый многопоточный сетевой-отделеный вычислитель! -Наконец, некоторые приложения содержат полезную сведения о языке в более справочном виде. В приложении A рассматриваются ключевые слова Rust, в приложении B — операторы и символы Rust, в приложении C — производные особенности, предоставляемые встроенной библиотекой, в приложении D — некоторые полезные средства разработки, а в приложении E — издания Rust. В приложении F вы найдёте переводы книги, а в приложении G мы расскажем о том, как создаётся Ржавчина и что такое nightly Rust. +Наконец, некоторые приложения содержат полезные сведения о языке в более справочном виде. В приложении A рассматриваются ключевые слова Ржавчины, в приложении B — приказчики и знаки Ржавчины в приложении C — производные особенности, предоставляемые встроенной библиотекой, в приложении D — некоторые полезные средства разработки, а в приложении E — издания Ржавчины. В приложении F вы найдёте переводы книги, а в приложении G мы расскажем о том, как создаётся Ржавчина и что такое ночное издание Ржавчины. Нет неправильного способа читать эту книгу: если вы хотите пропустить главу - сделайте это! Возможно, вам придётся вернуться к предыдущим главам, если возникнет недопонимание. Делайте все, как вам удобно. -Важной частью этапа обучения Ржавчина является изучение того, как читать сообщения об ошибках, которые отображает сборщик: они приведут вас к работающему коду. Мы изучим много примеров, которые не собираются и отображают ошибки в сообщениях сборщика в разных случаейх. Знайте, что если вы введёте и запустите случайный пример, он может не собраться! Убедитесь, что вы прочитали окружающий текст, чтобы понять, не предназначен ли пример, который вы пытаетесь запустить, для отображения ошибки. Ferris также поможет вам различить код, который не предназначен для работы: +Важной частью этапа обучения Ржавчины является изучение того, как читать сообщения об ошибках, которые отображает сборщик: они приведут вас к работающей рукописи. Мы изучим много примеров, которые не собираются и отображают ошибки в сообщениях сборщика в разных случаях. Знайте, что если вы введёте и запустите случайный пример, он может не собраться! Убедитесь, что вы прочитали окружающий текст, чтобы понять, не предназначен ли пример, который вы пытаетесь запустить, для отображения ошибки. Ferris также поможет вам различить рукопись, который не предназначен для работы: Ferris | Пояснения --- | --- -Ferris with a question mark | Этот код не собирается! -Феррис вскидывает руки | Этот код вызывает панику! -Феррис с одним когтем вверх, пожимая плечами | Этот код не приводит к желаемому поведению. +Ferris with a question mark | Этот рукопись не собирается! +Феррис вскидывает руки | Этот рукопись вызывает сбой! +Феррис с одним когтем вверх, пожимая плечами | Этот рукопись не приводит к желаемому поведению. -В большинстве случаев мы приведём вас к правильной исполнения любого кода, который не собирается. +В большинстве случаев мы приведём вас к правильному исполнению любой рукописи, который не собирается. -## Исходные коды +## Исходные рукописи -Файлы с исходным кодом, используемым в этой книге, можно найти на [GitHub]. +Файлы с исходной рукописью, используемым в этой книге, можно найти на [GitHub]. -[Язык программирования Rust]: https://nostarch.com/rust-programming-language-2nd-edition +[Язык программирования Ржавчина]: https://nostarch.com/rust-programming-language-2nd-edition [No Starch Press]: https://nostarch.com/ [GitHub]: https://github.com/rust-lang/book/tree/main/src \ No newline at end of file diff --git a/rustbook-ru/src/ch01-00-getting-started.md b/rustbook-ru/src/ch01-00-getting-started.md index 56cd467f9..f29736490 100644 --- a/rustbook-ru/src/ch01-00-getting-started.md +++ b/rustbook-ru/src/ch01-00-getting-started.md @@ -4,4 +4,4 @@ - установку Ржавчина на Linux, macOS и Windows, - написание программы, печатающей `Hello, world!`, -- использование `cargo`, управленца дополнений и системы сборки в одном лице для Rust. +- использование `cargo`, управленца дополнений и системы сборки в одном лице для Ржавчины. diff --git a/rustbook-ru/src/ch01-00-introduction.md b/rustbook-ru/src/ch01-00-introduction.md index 6857790a8..02247f536 100644 --- a/rustbook-ru/src/ch01-00-introduction.md +++ b/rustbook-ru/src/ch01-00-introduction.md @@ -1,22 +1,22 @@ # Введение -Добро пожаловать! Это книга о языке программирования Rust. -Rust - это язык программирования, с помощью которого можно создавать безопасные, +Добро пожаловать! Это книга о языке программирования Ржавчина. +Ржавчина - это язык программирования, с помощью которого можно создавать безопасные, быстрые и многопоточные приложения. Безопасность, скорость и многопоточность — это три кита вашего Rust-приложения. Смысловой внешний вид языка позволяет создавать программы, объединяющие воедино высокую производительность, управление над используемыми -ресурсами и абстракции высокого уровня. Ржавчина объединяет в себе черты низкоуровневых +мощностями и абстракции высокого уровня. Ржавчина объединяет в себе черты низкоуровневых и высокоуровневых языков программирования. Системные программисты по достоинству оценят безопасность языковых устройств, а прикладные программисты получат возможность -создавать производительные решения. Код программ читается свободно, поэтому писать +создавать производительные решения. Рукопись программ читается свободно, поэтому писать на нём удобно. -Сборщик производит работу по постоянному анализу кода, переработки использования -ресурсов. Это позволяет повысить производительность работы программ и заранее -перерабатывать объём используемых системных ресурсов. Благодаря этим особенностям -Rust — это удобный средство для создания решений в следующих прикладных областях: -приложения с предопределёнными жёсткими квотами ресурсов - кодеки, драйверы устройств, +Сборщик производит работу по постоянному оценке рукописи, переработки использования +мощностей. Это позволяет повысить производительность работы программ и заранее +перерабатывать объём используемых системных мощностей. Благодаря этим особенностям +Ржавчина — это удобный средство для создания решений в следующих прикладных областях: +приложения с предопределёнными жёсткими квотами мощностей - кодеки, драйверы устройств, драйверы хранилищ данных и даже встроенные системы. Ржавчина весьма удобен для создания -веб-приложений. Управленец дополнений [crates.io] позволяет создавать высоконагруженные +сетевых-приложений. Управленец дополнений [crates.io] позволяет создавать высоконагруженные решения без неизбежных расходов многослойных, высокоуровневых иных решений. Просто отпустите Вашу фантазию в полёт и создавайте приложения! @@ -24,9 +24,9 @@ Rust — это удобный средство для создания реше Эта книга будет полезна программистам, которые уже имеют достаточную подготовку и опытный опыт в программировании. Очень даже возможно, что после внимательного -прочтения этой книги, написания и отладки кода, понимания основных образцов разработки, +прочтения этой книги, написания и отладки рукописи, понимания основных образцов разработки, обретения навыков комфортной работы с языковыми устройствоми Вы полюбите писать -на Rust. Небольшие учебные примеры научат использовать возможности Rust. Также Вы +на Ржавчине Небольшие учебные примеры научат использовать возможности Ржавчины. Также Вы научитесь пользоваться имеющимися средствами — дополнительными программами и технологиями, благодаря которым производительность работы будет увеличиваться. diff --git a/rustbook-ru/src/ch01-01-installation.md b/rustbook-ru/src/ch01-01-installation.md index 374100d6f..72b7c5f6c 100644 --- a/rustbook-ru/src/ch01-01-installation.md +++ b/rustbook-ru/src/ch01-01-installation.md @@ -1,30 +1,30 @@ ## Установка -Первым шагом является установка Rust. Мы загрузим Rust, используя средство приказной строки `rustup`, предназначенный для управлениями исполнениями Ржавчина и другими связанными с ним средствами. Вам понадобится интернет-соединение для его загрузки. +Первым шагом является установка Ржавчины. Мы загрузим Ржавчину, используя средство приказной строки `rustup`, предназначенное для управлениями исполнениями Ржавчина и другими связанными с ним средствами. Вам понадобится соединение с мировой сетью для его загрузки. > Примечание: если вы по каким-то причинам предпочитаете не использовать rustup, пожалуйста, посетите [страницу «Другие способы установки Rust»] для получения дополнительных возможностей. -Следующие шаги устанавливают последнюю безотказную исполнение сборщика Rust. Благодаря заверениям безотказности Ржавчина все примеры в книге, которые собираются, будут собираться и в новых исполнениях Rust. Вывод может немного отличаться в разных исполнениях, поскольку Ржавчина часто улучшает сообщения об ошибках и предупреждения. Другими словами, любая новая, безотказная исполнение Rust, которую вы установите с помощью этих шагов, должна работать с содержимым этой книги так, как ожидается. +Следующие шаги устанавливают последнее безотказное исполнение сборщика Ржавчины. Благодаря заверениям безотказности Ржавчины, все примеры в книге, которые собираются, будут собираться и в новых исполнениях Ржавчины. Вывод может немного отличаться в разных исполнениях, поскольку Ржавчина часто улучшает сообщения об ошибках и предупреждения. Другими словами, любое новое, безотказное исполнение Ржавчины, которую вы установите с помощью этих шагов, должна работать с содержимым этой книги так, как ожидается. > ### Условные обозначения приказной строки > -> В этой главе и во всей книге мы будем выполнять некоторые приказы, используемые в окне вызова. Строки, которые вы должны вводить в окне вызова, начинаются с `$`. Вам не нужно вводить символ `$`; это подсказка приказной строки, отображаемая для обозначения начала каждой приказы. Строки, которые не начинаются с `$`, обычно показывают вывод предыдущей приказы. Кроме того, в примерах, своеобразных для PowerShell, будет использоваться `>`, а не `$`. +> В этой главе и во всей книге мы будем выполнять некоторые приказы, используемые в окне вызова. Строки, которые вы должны вводить в окне вызова, начинаются с `$`. Вам не нужно вводить знак `$`; это подсказка приказной строки, отображаемая для обозначения начала каждого приказа. Строки, которые не начинаются с `$`, обычно показывают вывод предыдущего приказа. Кроме того, в примерах, своеобразных для PowerShell, будет использоваться `>`, а не `$`. ### Установка `rustup` на Linux или macOS -Если вы используете Linux или macOS, пожалуйста, выполните следующую приказ: +Если вы используете Linux или macOS, пожалуйста, выполните следующий приказ: ```console $ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh ``` -Приказ загружает сценарий и запускает установку средства `rustup`, который устанавливает последнюю безотказную исполнение Rust. Вам может быть предложено ввести пазначение. Если установка прошла успешно, появится следующая строка: +Приказ загружает заранее расчитанный замысел и запускает установку средства `rustup`, которое устанавливает последнее безотказное исполнение Ржавчины. Вам может быть предложено ввести пароль. Если установка прошла успешно, появится следующая строка: ```text -Rust is installed now. Great! +Ржавчина is installed now. Great! ``` -Вам также понадобится *составитель (linker)* — программа, которую Ржавчина использует для объединения своих собранных выходных данных в один файл. Скорее всего, он у вас уже есть. При возникновении ошибок объединения, вам следует установить сборщик C, который обычно будет включать в себя и составитель. Сборщик C также полезен, потому что некоторые распространённые дополнения Ржавчина зависят от кода C и нуждаются в сборщике C. +Вам также понадобится *составитель (linker)* — программа, которую Ржавчина использует для объединения своих собранных выходных данных в один файл. Скорее всего, он у вас уже есть. При возникновении ошибок объединения, вам следует установить сборщик C, который обычно будет включать в себя и составитель. Сборщик C также полезен, потому что некоторые распространённые дополнения Ржавчина зависят от рукописи C и нуждаются в сборщике C. На macOS вы можете получить сборщик C, выполнив приказ: @@ -32,35 +32,35 @@ Rust is installed now. Great! $ xcode-select --install ``` -Пользователи Linux, как правило, должны устанавливать GCC или Clang в соответствии с документацией их установочного набора. Например, при использовании Ubuntu можно установить дополнение `build-essential`. +Пользователи Linux, как правило, должны устанавливать GCC или Clang в соответствии с пособием их установочного набора. Например, при использовании Ubuntu можно установить дополнение `build-essential`. ### Установка `rustup` на Windows -На Windows перейдите по адресу [https://www.rust-lang.org/tools/install] и следуйте указаниям по установке Rust. На определённом этапе установки вы получите сообщение, предупреждающее, что вам также понадобятся средства сборки MSVC для Visual Studio 2013 или более поздней исполнения. +На Windows перейдите по адресу [https://www.rust-lang.org/tools/install] и следуйте указаниям по установке Ржавчины. На определённом этапе установки вы получите сообщение, предупреждающее, что вам также понадобятся средства сборки MSVC для Visual Studio 2013 или более поздней исполнения. Чтобы получить средства сборки, вам потребуется установить [Visual Studio 2022]. На вопрос о том, какие составляющие необходимо установить, выберите: - “Desktop Development with C++” - The Windows 10 or 11 SDK -- Английский языковой дополнение вместе с любым другим языковым дополнением по вашему выбору. +- Английский языковое дополнение вместе с любым другим языковым дополнением по вашему выбору. В остальной части этой книги используются приказы, которые работают как в *cmd.exe*, так и в PowerShell. При наличии отличительных различий мы объясним, что необходимо сделать в таких случаях. ### Устранение возможных ошибок -Чтобы проверить, правильно ли у вас установлен Rust, откройте оболочку и введите эту строку: +Чтобы проверить, правильно ли у вас установлена Ржавчина, откройте оболочку и введите эту строку: ```console $ rustc --version ``` -Вы должны увидеть номер исполнения, хэш определения и дату определения для последней безотказной исполнения, которая была выпущена, в следующем виде: +Вы должны увидеть номер исполнения, хэш определения и дату определения для последней безотказного исполнения, которое было выпущено, в следующем виде: ```text rustc x.y.z (abcabcabc yyyy-mm-dd) ``` -Если вы видите эту сведения, вы успешно установили Rust! Если вы не видите эту сведения, убедитесь, что Ржавчина находится в вашей системной переменной `%PATH%` следующим образом: +Если вы видите эти сведения, вы успешно установили Rust! Если вы не видите эти сведения, убедитесь, что Ржавчина находится в вашей системной папке переменной `%PATH%` следующим образом: В Windows CMD: @@ -84,23 +84,23 @@ $ echo $PATH ### Обновление и удаление -После установки Ржавчина с помощью `rustup` обновление до новой исполнения не составит труда. В приказной оболочке запустите следующий скрипт обновления: +После установки Ржавчины с помощью `rustup` обновление до нового исполнения не составит труда. В приказной оболочке запустите следующий приказ для обновления: ```console $ rustup update ``` -Чтобы удалить Ржавчина и `rustup`, выполните следующую приказ: +Чтобы удалить Ржавчину и `rustup`, выполните следующий приказ: ```console $ rustup self uninstall ``` -### Местная документация +### Местное пособие -Установка Ржавчина также включает местную повтор документации, чтобы вы могли читать её в без доступа к мировой сети режиме. Выполните `rustup doc`, чтобы открыть местную документацию в браузере. +Установка Ржавчины также включает местное пособие, чтобы вы могли читать его в без доступа к мировой сети. Выполните `rustup doc`, чтобы открыть местное пособие в обозревателе. -Если обычная библиотека предоставляет вид или функцию, а вы не знаете, что она делает или как её использовать, воспользуйтесь документацией внешней оболочки прикладного программирования (API), чтобы это узнать! +Если обычная библиотека предоставляет вид или функцию, а вы не знаете, что она делает или как её использовать, воспользуйтесь пособием внешней оболочки прикладного программирования (API), чтобы это узнать! [страницу «Другие способы установки Rust»]: https://forge.rust-lang.org/infra/other-installation-methods.html diff --git a/rustbook-ru/src/ch01-02-hello-world.md b/rustbook-ru/src/ch01-02-hello-world.md index 440f7b1d2..59e4a9d35 100644 --- a/rustbook-ru/src/ch01-02-hello-world.md +++ b/rustbook-ru/src/ch01-02-hello-world.md @@ -1,15 +1,15 @@ ## Привет, мир! -Теперь, когда вы установили Rust, пришло время написать свою первую программу на Rust. Привычно при изучении нового языка принято писать небольшую программу, которая печатает на экране текст `Привет, мир!`, поэтому мы сделаем то же самое! +Теперь, когда вы установили Ржавчину пришло время написать свою первую программу на Ржавчине. Привычно при изучении нового языка принято писать небольшую программу, которая печатает на экране текст `Привет, мир!`, поэтому мы сделаем то же самое! -> Примечание: Эта книга предполагает наличие достаточного навыка работы с приказной строкой. Ржавчина не предъявляет особых требований к тому, каким набором средств вы пользуетесь для изменения или хранения вашего кода, поэтому если вы предпочитаете использовать встроенную среду разработки (IDE) вместо приказной строки, смело используйте вашу любимую IDE. Многие IDE сейчас в той или иной степени поддерживают Rust; подробности можно узнать из документации к IDE. Объединение Ржавчина сосредоточилась на обеспечении отличной поддержки IDE с помощью `rust-analyzer`. Более подробную сведения смотрите в [Приложении D](appendix-04-useful-development-tools.md). +> Примечание: Эта книга предполагает наличие достаточного навыка работы с приказной строкой. Ржавчина не предъявляет особых требований к тому, каким набором средств вы пользуетесь для изменения или хранения вашей рукописи, поэтому если вы предпочитаете использовать встроенную среду разработки (IDE) вместо приказной строки, смело используйте вашу любимую IDE. Многие IDE сейчас в той или иной степени поддерживают Rust; подробности можно узнать из пособия к IDE. Объединение Ржавчина сосредоточилось на обеспечении отличной поддержки IDE с помощью `rust-analyzer`. Более подробные сведения смотрите в [Приложении D](appendix-04-useful-development-tools.md). > ### Создание папки дела -Прежде всего начнём с создания папки, в которой будем сохранять наш код на языке Rust. На самом деле не важно, где сохранять наш код. Однако, для упражнений и дел, обсуждаемых в данной книге, мы советуем создать папку *projects* в вашем домашнем папке, там же и хранить в будущем код программ из книги. +Прежде всего начнём с создания папки, в которой будем сохранять нашу рукопись на языке Ржавчина. На самом деле не важно, где сохранять нашу рукопись. Однако, для упражнений и дел, обсуждаемых в данной книге, мы советуем создать папку *projects* в вашем домашней папке, там же и хранить в будущем рукопись программ из книги. -Откройте окно вызова и введите следующие приказы для того, чтобы создать папку projects для хранения кода разных дел, и, внутри неё, папку hello_world для дела “Привет, мир!”. +Откройте окно вызова и введите следующие приказы для того, чтобы создать папку projects для хранения рукописи разных дел, и, внутри неё, папку hello_world для дела “Привет, мир!”. Для Linux, macOS и PowerShell на Windows, введите: @@ -29,11 +29,11 @@ $ cd hello_world > cd hello_world ``` -### Написание и запуск первой Ржавчина программы +### Написание и запуск первой программы на Ржавчине -Затем создайте новый исходный файл и назовите его *main.rs*. Файлы Ржавчина всегда заканчиваются расширением *.rs*. Если вы используете более одного слова в имени файла, принято разделять их символом подчёркивания. Например, используйте *hello_world.rs* вместо *helloworld.rs*. +Затем создайте новый исходный файл и назовите его *main.rs*. Файлы Ржавчина всегда заканчиваются расширением *.rs*. Если вы используете более одного слова в имени файла, принято разделять их знаком подчёркивания. Например, используйте *hello_world.rs* вместо *helloworld.rs*. -Теперь откроем файл *main.rs* для изменения и введём следующие строки кода: +Теперь откроем файл *main.rs* для изменения и введём следующие строки рукописи: Название файла: main.rs @@ -63,9 +63,9 @@ $ ./main Независимо от вашей операционной системы, строка `Привет, мир!` должна быть выведена на окно вызова. Если вы не видите такого вывода, обратитесь к разделу ["Устранение неполадок "], чтобы узнать, как получить помощь. -Если напечаталось `Привет, мир!`, то примите наши поздравления! Вы написали программу на Rust, что делает вас Ржавчина программистом — добро пожаловать! +Если напечаталось `Привет, мир!`, то примите наши поздравления! Вы написали программу на Ржавчине, что делает вас "Ржавым программистом" — добро пожаловать! -### Анатомия программы на Rust +### Устройство программы на Rust Давайте рассмотрим «Привет, мир!» программу в подробностях. Вот первая часть головоломки: @@ -75,11 +75,11 @@ fn main() { } ``` -Эти строки определяют функцию с именем `main`. Функция `main` особенная: это всегда первый код, который запускается в каждой исполняемой программе Rust. Первая строка объявляет функцию с именем `main`, которая не имеет свойств и ничего не возвращает. Если бы были свойства, они бы заключались в круглые скобки `()`. +Эти строки определяют функцию с именем `main`. Функция `main` особенная: это всегда первое что указано в любом деле, которое создается в каждой вновьсозданном деле в языке Ржавчина. Первая строка объявляет функцию с именем `main`, которая не имеет свойств и ничего не возвращает. Если бы были свойства, они бы заключались в круглые скобки `()`. -Тело функции заключено в `{}`. Ржавчина требует фигурных скобок вокруг всех тел функций. Хороший исполнение — поместить открывающую фигурную скобку на ту же строку, что и объявление функции, добавив между ними один пробел. +Тело функции заключено в `{}`. Ржавчина требует фигурных скобок вокруг всех тел функций. Принятое внешнее исполнение — поместить открывающую фигурную скобку на ту же строку, что и объявление функции, добавив между ними один пробел. -> Примечание: Если хотите придерживаться принятого исполнения во всех делах Rust, вы можете использовать средство самостоятельного изменения под названием `rustfmt` для изменения кода в определённом исполнении (подробнее о `rustfmt` в [Приложении D](appendix-04-useful-development-tools.md). Объединение Ржавчина включила этот средство в обычный установочный набор Rust, как `rustc`, поэтому он уже должен быть установлен на вашем компьютере! +> Примечание: Если хотите придерживаться принятого внешнего исполнения во всех делах Ржавчина вы можете использовать средство самостоятельного изменения под названием `rustfmt` для изменения рукописи в определённом исполнении (подробнее о `rustfmt` в [Приложении D](appendix-04-useful-development-tools.md). Объединение Ржавчины включила этот средство в обычный установочный набор Ржавчина, как `rustc`, поэтому он уже должен быть установлен на вашем компьютере! > Тело функции `main` содержит следующий код: @@ -90,19 +90,19 @@ fn main() { Эта строка делает всю работу в этой маленькой программе: печатает текст на экран. Можно заметить четыре важных подробности. -Во-первых, исполнение Ржавчина предполагает отступ в четыре пробела, а не табуляцию. +Во-первых, исполнение Ржавчины предполагает отступ в четыре пробела, а не табуляцию. -Во-вторых, `println!` вызывается макрос Rust. Если бы вместо него была вызвана функция, она была бы набрана как `println` (без `!`). Более подробно мы обсудим макросы Ржавчина в главе 19. Пока достаточно знать, что использование `!` подразумевает вызов макроса вместо обычной функции, и что макросы не всегда подчиняются тем же правилам как функции. +Во-вторых, `println!` вызывается макрос Ржавчины. Если бы вместо него была вызвана функция, она была бы набрана как `println` (без `!`). Более подробно мы обсудим макросы Ржавчина в главе 19. Пока достаточно знать, что использование `!` подразумевает вызов макроса вместо обычной функции, и что макросы не всегда подчиняются тем же правилам как функции. В-третьих, вы видите строку `"Привет, мир!"`. Мы передаём её в качестве переменной макросу `println!`, и она выводится на экран. -В-четвёртых, мы завершаем строку точкой с запятой (`;`), которая указывает на окончание этого выражения и возможность начала следующего. Большинство строк кода Ржавчина заканчиваются точкой с запятой. +В-четвёртых, мы завершаем строку точкой с запятой (`;`), которая указывает на окончание этого выражения и возможность начала следующего. Большинство строк рукописи Ржавчина заканчиваются точкой с запятой. ### Сборка и запуск - это отдельные шаги Вы только что запустили впервые созданную программу, поэтому давайте рассмотрим каждый шаг этого этапа. -Перед запуском программы на Ржавчина вы должны собрать её с помощью сборщика Rust, введя приказ `rustc` и передав ей имя вашего исходного файла, например: +Перед запуском программы на Ржавчине вы должны собрать её с помощью сборщика Ржавчины, введя приказ `rustc` и передав ему имя вашего исходного файла, например: ```console $ rustc main.rs @@ -126,7 +126,7 @@ main.pdb main.rs ``` -Это показывает исходный код файла с расширением *.rs*, исполняемый файл (*main.exe* на Windows, но *main* на всех других площадках) и, при использовании Windows, файл, содержащий отладочную сведения с расширением *.pdb*. Отсюда вы запускаете файлы *main* или *main.exe*, например: +Это показывает исходную рукопись файла с расширением *.rs*, исполняемый файл (*main.exe* на Windows, но *main* на всех других площадках) и, при использовании Windows, файл, содержащий отладочные сведения с расширением *.pdb*. Отсюда вы запускаете файлы *main* или *main.exe*, например: ```console $ ./main # для Linux @@ -135,9 +135,9 @@ $ ./main # для Linux Если ваш *main.rs* — это ваша программа «Привет, мир!», эта строка выведет в окно вызова `Привет, мир!`. -Если вы лучше знакомы с изменяемыми языками, такими как Ruby, Python или JavaScript, возможно, вы не привыкли собирать и запускать программу как отдельные шаги. Ржавчина — это предварительно *собранный* язык, то есть вы можете собрать программу и передать исполняемый файл кому-то другому, и он сможет запустить его даже без установленного Rust. Если вы даёте кому-то файл *.rb* , *.py* или *.js*, у него должна быть установлена выполнение Ruby, Python или JavaScript (соответственно). Но в этих языках вам нужна только одна приказ для сборки и запуска вашей программы. В внешнем виде языков программирования всё — соглашение. +Если вы лучше знакомы с изменяемыми языками, такими как Ruby, Python или JavaScript, возможно, вы не привыкли собирать и запускать программу как отдельные шаги. Ржавчина — это предварительно *собранный* язык, то есть вы можете собрать программу и передать исполняемый файл кому-то другому, и он сможет запустить его даже без установленного на личном компьютере языка Ржавчина. Если вы даёте кому-то файл *.rb* , *.py* или *.js*, у него должна быть установлена выполнение Ruby, Python или JavaScript (соответственно). Но в этих языках вам нужен только один приказ для сборки и запуска вашей программы. Во внешнем виде всех языков программирования всё является неким условным соглашением. -Сборка с помощью `rustc` подходит для простых программ, но по мере роста вашего дела вы захотите управлять всеми свойствами и упростить передачу кода. Далее мы познакомим вас с средством Cargo, который поможет вам писать программы из существующего мира на Rust. +Сборка с помощью `rustc` подходит для простых программ, но по мере роста вашего дела вы захотите управлять всеми свойствами и упростить передачу рукописи. Далее мы познакомим вас с средством Cargo, который поможет вам писать программы из существующего мира на Ржавчине. ["Устранение неполадок "]: ch01-01-installation.html#troubleshooting \ No newline at end of file diff --git a/rustbook-ru/src/ch01-03-hello-cargo.md b/rustbook-ru/src/ch01-03-hello-cargo.md index 5904c016a..7d05b81a4 100644 --- a/rustbook-ru/src/ch01-03-hello-cargo.md +++ b/rustbook-ru/src/ch01-03-hello-cargo.md @@ -1,35 +1,35 @@ ## Привет, Cargo! -Cargo - это система сборки и управленец дополнений Rust. Большая часть разработчиков используют данный средство для управления делами, потому что Cargo выполняет за вас множество задач, таких как сборка кода, загрузка библиотек, от которых зависит ваш код, и создание этих библиотек. (Мы называем библиотеки, которые нужны вашему коду, *зависимостями*.) +Cargo - это система сборки и управленец дополнений Ржавчины. Большая часть разработчиков используют данное средство для управления делами, потому что Cargo выполняет за вас множество задач, таких как сборка рукописи, загрузка библиотек, от которых зависит ваша рукопись, и создание этих библиотек. (Мы называем библиотеки, которые нужны вашему рукописи, *зависимостями*.) -Самые простые программы на Rust, подобные той, которую мы написали, не имеют никаких зависимостей. Если бы мы сделали дело «Hello, world!» с Cargo, он бы использовал только ту часть Cargo, которая отвечает за сборку вашего кода. По мере написания более сложных программ на Ржавчина вы будете добавлять зависимости, а если вы начнёте дело с использованием Cargo, добавлять зависимости станет намного проще. +Самые простые программы на Ржавчине, подобные той, которую мы написали, не имеют никаких зависимостей. Если бы мы сделали дело «Hello, world!» с Cargo, он бы использовал только ту часть Cargo, которая отвечает за сборку вашей рукописи. По мере написания более сложных программ на Ржавчине вы будете добавлять зависимости, а если вы начнёте дело с использованием Cargo, добавлять зависимости станет намного проще. -Поскольку значительное число дел Ржавчина используют Cargo, оставшаяся часть книги подразумевает, что вы тоже используете Cargo. Cargo входит в состав поставки Rust, если вы использовали напрямую от разрабочиков программы установки, рассмотренные в разделе ["Установка"]. Если вы установили Ржавчина другим способом, проверьте, установлен ли Cargo, введя в окне вызова следующее: +Поскольку значительное число дел, написанных на языке Ржавчина используют Cargo, оставшаяся часть книги подразумевает, что вы тоже используете Cargo. Cargo входит в состав поставки Ржавчины если вы скачали установочный набор напрямую от разрабочиков, рассмотрен в разделе ["Установка"]. Если вы установили Ржавчину другим способом, проверьте, установлен ли Cargo, введя в окне вызова следующее: ```console $ cargo --version ``` -Если приказ выдал номер исполнения, то значит Cargo установлен. Если вы видите ошибку, вроде `command not found` ("приказ не найдена"), загляните в документацию для использованного вами способа установки, чтобы выполнить установку Cargo отдельно. +Если приказ выдал номер исполнения, то значит Cargo установлен. Если вы видите ошибку, вроде `command not found` ("приказ не найден"), загляните в пособие для использованного вами способа установки, чтобы выполнить установку Cargo отдельно. -### Создание дела с помощью Cargo +### Создание своего дела с помощью Cargo -Давайте создадим новый дело с помощью Cargo и посмотрим, как он отличается от нашего начального дела "Hello, world!". Перейдите обратно в папку *projects* (или любую другую, где вы решили сохранять код). Затем, в любой операционной системе, запустите приказ: +Давайте создадим новое дело с помощью Cargo и посмотрим, как оно отличается от нашего начального дела "Hello, world!". Перейдите обратно в папку *projects* (или любую другую, где вы решили сохранять код). Затем, в любой операционной системе, запустите приказ: ```console $ cargo new hello_cargo $ cd hello_cargo ``` -Первая приказ создаёт новый папка и дело с именем *hello_cargo*. Мы назвали наш дело *hello_cargo*, и Cargo создаёт свои файлы в папке с тем же именем. +Первый приказ создаёт новый папка и дело с именем *hello_cargo*. Мы назвали наш дело *hello_cargo*, и Cargo создаёт свои файлы в папке с тем же именем. Перейдём в папка *hello_cargo* и посмотрим файлы. Увидим, что Cargo создал два файла и одну папку: файл *Cargo.toml* и папка *src* с файлом *main.rs* внутри. -Кроме того, cargo объявлял новый хранилище Git вместе с файлом *.gitignore*. Файлы Git не будут созданы, если вы запустите `cargo new` в существующем хранилища Git; вы можете изменить это поведение, используя `cargo new --vcs=git`. +Кроме того, cargo объявил новое хранилище Git вместе с файлом *.gitignore*. Файлы Git не будут созданы, если вы запустите `cargo new` в существующем хранилище Git; вы можете изменить это поведение, используя `cargo new --vcs=git`. -> Примечание. Git — это распространённая система управления исполнений. Вы можете изменить `cargo new`, чтобы использовать другую систему управления исполнений или не использовать систему управления исполнений, используя флаг `--vcs`. Запустите `cargo new --help`, чтобы увидеть доступные свойства. +> Примечание. Git — это распространённая система управления исполнений. Вы можете изменить `cargo new`, чтобы использовать другую систему управления исполнений или не использовать систему управления исполнений, используя клеймо `--vcs`. Запустите `cargo new --help`, чтобы увидеть доступные свойства. -Откройте файл *Cargo.toml* в любом текстовом редакторе. Он должен выглядеть как код в приложении 1-2. +Откройте файл *Cargo.toml* в любом текстовом редакторе. Он должен выглядеть как рукопись в приложении 1-2. Файл: Cargo.toml @@ -48,11 +48,11 @@ edition = "2021" Это файл в виде[*TOML*](https://github.com/toml-lang/toml) (*Tom’s Obvious, Minimal Language*), который является видом настроек Cargo. -Первая строка, `[package]`, является заголовочной разделом, которая указывает что следующие указания настраивают дополнение. По мере добавления больше сведений в данный файл, будет добавляться больше разделов и указаний (строк). +Первая строка, `[package]`, является заголовочным разделом, которая указывает что следующие указания настраивают дополнение. По мере добавления больше сведений в данный файл, будет добавляться больше разделов и указаний (строк). -Следующие три строки задают сведения о настройке, необходимую Cargo для сборки вашей программы: имя, исполнение и издание Rust, который будет использоваться. Мы поговорим о ключе `edition` в [Приложении E]. +Следующие три строки задают сведения о настройке, необходимую Cargo для сборки вашей программы: имя, исполнение и издание Ржавчина, который будет использоваться. Мы поговорим о ключе `edition` в [Приложении E]. -Последняя строка, `[dependencies]` является началом разделы для списка любых зависимостей вашего дела. В Rust, это внешние дополнения кода, на которые ссылаются ключевым словом *crate*. Нам не нужны никакие зависимости в данном деле, но мы будем использовать их в первом деле главы 2, так что нам пригодится данная раздел зависимостей потом. +Последняя строка, `[dependencies]` является началом раздела для списка любых зависимостей вашего дела. В Ржавчине, это внешние дополнения рукописи, на которые ссылаются ключевым словом *crate*. Нам не нужны никакие зависимости в данном деле, но мы будем использовать их в первом деле главы 2, так что нам пригодится данный раздел зависимостей потом. Откройте файл *src/main.rs* и загляните в него: @@ -64,15 +64,15 @@ fn main() { } ``` -Cargo создал для вас программу "Hello, world!", подобную той, которую мы написали в Приложении 1-1! Пока что различия между нашим предыдущим делом и делом, созданным при помощи Cargo, заключаются в том, что Cargo поместил исходный код в папка *src*, и у нас есть настроечный файл *Cargo.toml* в верхнем папке дела. +Cargo создал для вас программу "Hello, world!", подобную той, которую мы написали в Приложении 1-1! Пока что различия между нашим предыдущим делом и делом, созданным при помощи Cargo, заключаются в том, что Cargo поместил исходную рукопись в папка *src*, и у нас есть настроечный файл *Cargo.toml* в верхней папке дела. -Cargo ожидает, что ваши исходные файлы находятся внутри папки *src*. Папка верхнего уровня дела предназначен только для файлов README, сведений о лицензии, файлы настройке и чего то ещё не относящего к вашему коду. Использование Cargo помогает создавать дело. Есть место для всего и все находится на своём месте. +Cargo ожидает, что ваши исходные файлы находятся внутри папки *src*. Папка верхнего уровня дела предназначена только для файлов README, сведений о лицензии, файлы настроек и чего то ещё не относящего к вашему рукописи. Использование Cargo помогает создавать дело. Есть место для всего и все находится на своём месте. -Если вы начали дело без использования Cargo, как мы делали для "Hello, world!" дела, то можно преобразовывать его в дело с использованием Cargo. Переместите код в подпапка *src* и создайте соответствующий файл *Cargo.toml* в папке. +Если вы создали дело без использования Cargo, как мы делали для "Hello, world!" дела, то можно преобразовывать его в дело с использованием Cargo. Переместите рукопись в подпапку *src* и создайте соответствующий файл *Cargo.toml* в папке. -### Сборка и запуск Cargo дела +### Сборка и запуск дела через Cargo -Посмотрим, в чем разница при сборке и запуске программы "Hello, world!" с помощью Cargo. В папке *hello_cargo* соберите дело следующей приказом: +Посмотрим, в чем разница при сборке и запуске программы "Hello, world!" с помощью Cargo. В папке *hello_cargo* соберите дело следующим приказом: ```console $ cargo build @@ -80,16 +80,16 @@ $ cargo build Finished dev [unoptimized + debuginfo] target(s) in 2.85 secs ``` -Этот приказ создаёт исполняемый файл в *target/debug/hello_cargo* (или *target\debug\hello_cargo.exe* в Windows), а не в вашем текущем папке. Поскольку обычная сборка является отладочной, Cargo помещает двоичный файл в папка с именем *debug*. Вы можете запустить исполняемый файл с помощью этой приказы: +Этот приказ создаёт исполняемый файл в *target/debug/hello_cargo* (или *target\debug\hello_cargo.exe* в Windows), а не в вашем текущем папке. Поскольку обычная сборка является отладочной, Cargo помещает двоичный файл в папка с именем *debug*. Вы можете запустить исполняемый файл с помощью этого приказа: ```console $ ./target/debug/hello_cargo # or .\target\debug\hello_cargo.exe on Windows Hello, world! ``` -Если все хорошо, то `Hello, world!` печатается в окне вызова. Запуск приказы `cargo build` в первый раз также приводит к созданию нового файла *Cargo.lock* в папке верхнего уровня. Данный файл хранит точные исполнения зависимостей вашего дела. Так как у нас нет зависимостей, то файл пустой. Вы никогда не должны менять этот файл вручную: Cargo сам управляет его содержимым для вас. +Если все хорошо, то `Hello, world!` выводится в окне вызова. Запуск приказа `cargo build` в первый раз также приводит к созданию нового файла *Cargo.lock* в папке верхнего уровня. Данный файл хранит сведения о используемых зависимостей вашего дела. Так как у нас нет зависимостей, то файл пустой. Вы никогда не должны менять этот файл вручную: Cargo сам управляет его содержимым за вас. -Только что мы собрали дело приказом `cargo build` и запустили его из `./target/debug/hello_cargo`. Но мы также можем при помощи приказы `cargo run` сразу и собрать код, и затем запустить полученный исполняемый файл всего лишь одной приказом: +Только что мы собрали дело приказом `cargo build` и запустили его из `./target/debug/hello_cargo`. Но мы также можем при помощи приказы `cargo run` сразу и собрать рукопись, и затем запустить полученный исполняемый файл всего лишь одной приказом: ```console $ cargo run @@ -100,7 +100,7 @@ Hello, world! Использование `cargo run` более удобно, чем необходимость помнить и запускать `cargo build`, а затем использовать весь путь к двоичному файлу, поэтому большинство разработчиков используют `cargo run`. -Обратите внимание, что на этот раз мы не видели вывода, указывающего на то, что Cargo собирает `hello_cargo`. Cargo выяснил, что файлы не изменились, поэтому не стал пересобирать, а просто запустил двоичный файл. Если бы вы изменили свой исходный код, Cargo пересобрал бы дело перед его запуском, и вы бы увидели этот вывод: +Обратите внимание, что на этот раз мы не видели вывода, указывающего на то, что Cargo собирает `hello_cargo`. Cargo выяснил, что файлы не изменились, поэтому не стал пересобирать, а просто запустил двоичный файл. Если бы вы изменили свой исходную рукопись, Cargo пересобрал бы дело перед его запуском, и вы бы увидели этот вывод: ```console $ cargo run @@ -110,7 +110,7 @@ $ cargo run Hello, world! ``` -Cargo также предоставляет приказ, называемую `cargo check`. Этот приказ быстро проверяет ваш код, чтобы убедиться, что он собирается, но не создаёт исполняемый файл: +Cargo также предоставляет приказ, называемую `cargo check`. Этот приказ быстро проверяет ваша рукопись, чтобы убедиться, что он собирается, но не создаёт исполняемый файл: ```console $ cargo check @@ -118,7 +118,7 @@ $ cargo check Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs ``` -Почему вам не нужен исполняемый файл? Часто `cargo check` выполняется намного быстрее, чем `cargo build`, поскольку пропускает этап создания исполняемого файла. Если вы постоянно проверяете свою работу во время написания кода, использование `cargo check` ускорит этап уведомления вас о том, что ваш дело всё ещё собирается! Таким образом, многие Rustacean периодически запускают `cargo check`, когда пишут свои программы, чтобы убедиться, что она собирается. Затем они запускают `cargo build`, когда готовы использовать исполняемый файл. +Почему вам не нужен исполняемый файл? Часто `cargo check` выполняется намного быстрее, чем `cargo build`, поскольку пропускает этап создания исполняемого файла. Если вы постоянно проверяете свою работу во время написания рукописи, использование `cargo check` ускорит этап уведомления вас о том, что ваш дело всё ещё собирается! Таким образом, многие Rustacean периодически запускают `cargo check`, когда пишут свои программы, чтобы убедиться, что она собирается. Затем они запускают `cargo build`, когда готовы использовать исполняемый файл. Давайте подытожим, что мы уже узнали о Cargo: @@ -126,19 +126,19 @@ $ cargo check - можно собирать дело, используя приказ `cargo build`, - можно одновременно собирать и запускать дело одной приказом `cargo run`, - можно собрать дело для проверки ошибок с помощью `cargo check`, не тратя время на кодосоздание исполняемого файла, -- cargo сохраняет итоги сборки не в папку с исходным кодом, а в отдельный папка *target/debug*. +- cargo сохраняет итоги сборки не в папку с исходной рукописью, а в отдельный папка *target/debug*. Дополнительным преимуществом использования Cargo является то, что его приказы одинаковы для разных операционных систем. С этой точки зрения, мы больше не будем предоставлять отдельные указания для Linux, macOS или Windows. ### Сборка конечной исполнения (Release) -Когда дело, наконец, готов к исполнению, можно использовать приказ `cargo build --release` для его сборки с переработкой. Данная приказ создаёт исполняемый файл в папке *target/release* в отличии от папки *target/debug*. Переработки делают так, что Ржавчина код работает быстрее, но их включение увеличивает время сборки. По этой причине есть два отдельных профиля: один для разработки, когда нужно осуществлять сборку быстро и часто, и другой, для сборки конечной программы, которую будете отдавать пользователям, которая готова к работе и будет выполняться сверх быстро. Если вы замеряете время выполнения вашего кода, убедитесь, что собрали дело с переработкой `cargo build --release` и проверяете исполняемый файл из папки *target/release*. +Когда дело, наконец, готов к исполнению, можно использовать приказ `cargo build --release` для его сборки с переработкой. Данная приказ создаёт исполняемый файл в папке *target/release* в отличии от папки *target/debug*. Переработки делают так, что Ржавчина рукопись работает быстрее, но их включение увеличивает время сборки. По этой причине есть два отдельных профиля: один для разработки, когда нужно осуществлять сборку быстро и часто, и другой, для сборки конечной программы, которую будете отдавать пользователям, которая готова к работе и будет выполняться сверх быстро. Если вы замеряете время выполнения вашей рукописи, убедитесь, что собрали дело с переработкой `cargo build --release` и проверяете исполняемый файл из папки *target/release*. ### Cargo как Условие В простых делах Cargo не даёт больших преимуществ по сравнению с использованием `rustc`, но он проявит себя, когда ваши программы станут более сложными. Когда программы вырастают до нескольких файлов или нуждаются в зависимостях, гораздо проще позволить Cargo согласовывать сборку. -Не смотря на то, что дело `hello_cargo` простой, теперь он использует большую часть существующего набора средств, который вы будете повседневно использовать в вашей развитии, связанной с Rust. Когда потребуется работать над делами размещёнными в сети, вы сможете просто использовать следующую последовательность приказов для получения кода с помощью Git, перехода в папка дела, сборку дела: +Не смотря на то, что дело `hello_cargo` простой, теперь он использует большую часть существующего набора средств, который вы будете повседневно использовать в вашей развитии, связанной с Ржавчина. Когда потребуется работать над делами размещёнными в сети, вы сможете просто использовать следующую последовательность приказов для получения рукописи с помощью Git, перехода в папка дела, сборку дела: ```console $ git clone example.org/someproject @@ -146,21 +146,21 @@ $ cd someproject $ cargo build ``` -Для получения дополнительной сведений о Cargo ознакомьтесь с [его документацией] . +Для получения дополнительной сведений о Cargo ознакомьтесь с [его пособием] . ## Итоги Теперь вы готовы начать своё Ржавчина путешествие! В данной главе вы изучили как: -- установить последнюю безотказную исполнение Rust, используя `rustup`, +- установить последнее безотказное исполнение Ржавчины, используя `rustup`, - обновить Ржавчина до последней исполнения, -- открыть местно установленную документацию, +- открыть местно установленную пособие, - написать и запустить программу вида "Hello, world!", используя напрямую сборщик `rustc`, -- создать и запустить новый дело, используя соглашения и приказы Cargo. +- создать и запустить новое дело, используя соглашения и приказы Cargo. -Это отличное время для создания более существенной программы, чтобы привыкнуть читать и писать код на языке Rust. Итак, в главе 2 мы построим программу для игры в угадай число. Если вы предпочитаете начать с изучения того, как работают общие подходы программирования в Rust, обратитесь к главе 3, а затем вернитесь к главе 2. +Это отличное время для создания более существенной программы, чтобы привыкнуть читать и писать рукопись на языке Ржавчина. Итак, в главе 2 мы построим программу для игры в угадай число. Если вы предпочитаете начать с изучения того, как работают общие подходы программирования в Ржавчине, обратитесь к главе 3, а затем вернитесь к главе 2. ["Установка"]: ch01-01-installation.html#installation [Приложении E]: appendix-05-editions.html -[его документацией]: https://doc.rust-lang.org/cargo/ \ No newline at end of file +[его пособием]: https://doc.rust-lang.org/cargo/ \ No newline at end of file diff --git a/rustbook-ru/src/ch02-00-guessing-game-tutorial.md b/rustbook-ru/src/ch02-00-guessing-game-tutorial.md index 2a02fc7ef..975dcc6bf 100644 --- a/rustbook-ru/src/ch02-00-guessing-game-tutorial.md +++ b/rustbook-ru/src/ch02-00-guessing-game-tutorial.md @@ -1,19 +1,19 @@ # Программируем игру в загадки -Давайте окунёмся в Rust, вместе поработав над опытным делом! В этой главе вы познакомитесь с несколькими общими подходами Rust, показав, как использовать их в существующей программе. Вы узнаете о `let` , `match`, способах, сопряженных функциях, внешних дополнениях и многом другом! В следующих главах мы рассмотрим эти мысли более подробно. В этой главе вы просто примените в основах. +Давайте окунёмся в Ржавчине, вместе поработав над опытным делом! В этой главе вы познакомитесь с несколькими общими подходами Ржавчина показав, как использовать их в существующей программе. Вы узнаете о `let` , `match`, способах, сопряженных функциях, внешних дополнениях и многом другом! В следующих главах мы рассмотрим эти мысли более подробно. В этой главе вы просто примените в основах. Мы выполняем привычную для начинающих программистов задачу — игру в загадки. Вот как это работает: программа порождает случайное целое число в ряде от 1 до 100. Затем она предлагает игроку его угадать. После ввода числа программа укажет, меньше или больше было загаданное число. Если догадка верна, игра напечатает поздравительное сообщение и завершится. ## Настройка нового дела -Для настройки нового дела перейдите в папка *projects*, который вы создали в главе 1, и создайте новый дело с использованием Cargo, как показано ниже: +Для настройки нового дела перейдите в папка *projects*, который вы создали в главе 1, и создайте новое дело с использованием Cargo, как показано ниже: ```console $ cargo new guessing_game $ cd guessing_game ``` -Первая приказ, `cargo new`, принимает в качестве первого переменной имя дела (`guessing_game`). Вторая приказ изменяет папка на новый папка дела. +Первый приказ, `cargo new`, принимает в качестве первого переменной имя дела (`guessing_game`). Вторая приказ изменяет папка на новый папка дела. Загляните в созданный файл *Cargo.toml*: @@ -48,11 +48,11 @@ cd ../../.. Приказ `run` пригодится, когда необходимо ускоренно выполнить повторение дела. Именно так мы собираемся делать в этом деле, быстро проверяя каждую повторение, прежде чем перейти к следующей. -Снова откройте файл *src/main.rs*. Весь код вы будете писать в нем. +Снова откройте файл *src/main.rs*. Весь рукопись вы будете писать в нем. ## Обработка догадки -Первая часть программы запрашивает ввод данных пользователем, обрабатывает их и проверяет, что они в ожидаемой виде. Начнём с того, что позволим игроку ввести догадку. Вставьте код из приложения 2-1 в *src/main.rs*. +Первая часть программы запрашивает ввод данных пользователем, обрабатывает их и проверяет, что они в ожидаемой виде. Начнём с того, что позволим игроку ввести догадку. Вставьте рукопись из приложения 2-1 в *src/main.rs*. Файл: src/main.rs @@ -60,17 +60,17 @@ cd ../../.. {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-01/src/main.rs:all}} ``` -Приложение 2-1: код, который получает догадку от пользователя и выводит её на экран +Приложение 2-1: рукопись, который получает догадку от пользователя и выводит её на экран -Этот код содержит много сведений, поэтому давайте рассмотрим его построчно. Чтобы получить пользовательский ввод и затем вывести итог, нам нужно включить в область видимости библиотеку ввода/вывода `io`. Библиотека `io` является частью встроенной библиотеки, известной как `std`: +Этот рукопись содержит много сведений, поэтому давайте рассмотрим его построчно. Чтобы получить пользовательский ввод и затем вывести итог, нам нужно включить в область видимости библиотеку ввода/вывода `io`. Библиотека `io` является частью встроенной библиотеки, известной как `std`: ```rust,ignore {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-01/src/main.rs:io}} ``` -По умолчанию в Ржавчина есть набор элементов, определённых в встроенной библиотеке, которые он добавляет в область видимости каждой программы. Этот набор называется *прелюдией*, и вы можете изучить его содержание [в документации встроенной библиотеки]. +По умолчанию в Ржавчине есть набор элементов, определённых в встроенной библиотеке, которые он добавляет в область видимости каждой программы. Этот набор называется *прелюдией*, и вы можете изучить его содержание [в пособия встроенной библиотеки]. -Если вид, который требуется использовать, отсутствует в прелюдии, его нужно явно ввести в область видимости с помощью оператора `use`. Использование библиотеки `std::io` предоставляет ряд полезных полезных возможностей, включая способность принимать пользовательский ввод. +Если вид, который требуется использовать, отсутствует в прелюдии, его нужно явно ввести в область видимости с помощью приказчика `use`. Использование библиотеки `std::io` предоставляет ряд полезных полезных возможностей, включая способность принимать пользовательский ввод. Как уже отмечалось в главе 1, функция `main` является точкой входа в программу: @@ -86,7 +86,7 @@ cd ../../.. {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-01/src/main.rs:print}} ``` -Этот код показывает сведения о ходе игры и запрашивает пользовательский ввод. +Этот рукопись показывает сведения о ходе игры и запрашивает пользовательский ввод. ### Хранение значений с помощью переменных @@ -96,13 +96,13 @@ cd ../../.. {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-01/src/main.rs:string}} ``` -Вот теперь программа становится важнее! В этой маленькой строке на самом деле происходит очень многое. Для создания переменной мы используем оператор `let`. Вот ещё один пример: +Вот теперь программа становится важнее! В этой маленькой строке на самом деле происходит очень многое. Для создания переменной мы используем приказчик `let`. Вот ещё один пример: ```rust,ignore let apples = 5; ``` -Эта строка создаёт новую переменную с именем `apples` и привязывает её к значению 5. В Ржавчина переменные неизменяемы по умолчанию, то есть как только мы присвоим переменной значение, оно не изменится. Мы подробно обсудим эту подход в разделе ["Переменные и изменчивость".] в главе 3. Чтобы сделать переменную изменяемой, мы добавляем `mut` перед её именем: +Эта строка создаёт новую переменную с именем `apples` и привязывает её к значению 5. В Ржавчине переменные неизменяемы по умолчанию, то есть как только мы присвоим переменной значение, оно не изменится. Мы подробно обсудим эту подход в разделе ["Переменные и изменчивость".] в главе 3. Чтобы сделать переменную изменяемой, мы добавляем `mut` перед её именем: ```rust,ignore let apples = 5; // неизменяемая @@ -112,7 +112,7 @@ let mut bananas = 5; // изменяемая > Примечание: сочетание знаков `//` начинает примечание, который продолжается до конца строки. Ржавчина пренебрегает всё, что находится в примечаниях. Мы обсудим примечания более подробно в [Главе 3]. > -Возвращаясь к программе игры "Угадайка" — теперь вы знаете, что `let mut guess` предоставит изменяемую переменную с именем `guess`. Знак равенства (`=`) сообщает Rust, что сейчас нужно связать что-то с этой переменной. Справа от знака равенства находится значение, связанное с `guess`, которое является итогом вызова функции `String::new`, возвращающей новый образец `String`. `String` — это вид строки, предоставляемый встроенной библиотекой, который является расширяемым отрывком текста в кодировке UTF-8. +Возвращаясь к программе игры "Угадайка" — теперь вы знаете, что `let mut guess` предоставит изменяемую переменную с именем `guess`. Знак равенства (`=`) сообщает Ржавчина что сейчас нужно связать что-то с этой переменной. Справа от знака равенства находится значение, связанное с `guess`, которое является итогом вызова функции `String::new`, возвращающей новый образец `String`. `String` — это вид строки, предоставляемый встроенной библиотекой, который является расширяемым отрывком текста в кодировке UTF-8. правила написания `::` в строке `::new` указывает, что `new` является сопряженной функцией вида `String`. *Сопряженная функция* — это функция, выполненная для вида, в данном случае `String`. Функция `new` создаёт новую пустую строку. Функцию `new` можно встретить во многих видах, это привычное название для функции, которая создаёт новое значение какого-либо вида. @@ -130,7 +130,7 @@ let mut bananas = 5; // изменяемая Далее строка `.read_line(&mut guess)` вызывает способ [`read_line`] на указателе принятого ввода для получения ввода от пользователя. Мы также передаём `&mut guess` в качестве переменной `read_line`, сообщая ему, в какой строке хранить пользовательский ввод. Главная задача `read_line` — принять все, что пользователь вводит в обычный ввод, и сложить это в строку (не переписывая её содержимое), поэтому мы передаём эту строку в качестве переменной. Строковый переменная должен быть изменяемым, чтобы способ мог изменить содержимое строки. -Символ `&` указывает, что этот переменная является *ссылкой*, которая предоставляет возможность нескольким частям вашего кода получить доступ к одному отрывку данных без необходимости воспроизводить эти данные в память несколько раз. Ссылки — это сложная полезная возможность, а одним из главных преимуществ Ржавчина является безопасность и простота использования ссылок. Чтобы дописать эту программу, вам не понадобится знать много таких подробностей. Пока вам достаточно знать, что ссылки, как и переменные, по умолчанию неизменяемы. Соответственно, чтобы сделать её изменяемой, нужно написать `&mut guess`, а не `&guess`. (В главе 4 ссылки будут описаны более подробно). +Знак `&` указывает, что этот переменная является *ссылкой*, которая предоставляет возможность нескольким частям вашей рукописи получить доступ к одному отрывку данных без необходимости воспроизводить эти данные в память несколько раз. Ссылки — это сложная полезная возможность, а одним из главных преимуществ Ржавчине является безопасность и простота использования ссылок. Чтобы дописать эту программу, вам не понадобится знать много таких подробностей. Пока вам достаточно знать, что ссылки, как и переменные, по умолчанию неизменяемы. Соответственно, чтобы сделать её изменяемой, нужно написать `&mut guess`, а не `&guess`. (В главе 4 ссылки будут описаны более подробно). @@ -138,19 +138,19 @@ let mut bananas = 5; // изменяемая ### Обработка возможного сбоя с помощью вида `Result` -Мы всё ещё работаем над этой строкой кода. Сейчас мы обсуждаем третью строку, но обратите внимание, что она по-прежнему является частью одной логической строки. Следующая часть — способ: +Мы всё ещё работаем над этой строкой рукописи. Сейчас мы обсуждаем третью строку, но обратите внимание, что она по-прежнему является частью одной разумной строки. Следующая часть — способ: ```rust,ignore {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-01/src/main.rs:expect}} ``` -Мы могли бы написать этот код так: +Мы могли бы написать этот рукопись так: ```rust,ignore io::stdin().read_line(&mut guess).expect("Failed to read line"); ``` -Однако одну длинную строку трудно читать, поэтому лучше разделить её. При вызове способа с помощью правил написания `.method_name()` часто целесообразно вводить новую строку и другие пробельные символы, чтобы разбить длинные строки. Теперь давайте обсудим, что делает эта строка. +Однако одну длинную строку трудно читать, поэтому лучше разделить её. При вызове способа с помощью правил написания `.method_name()` часто целесообразно вводить новую строку и другие пробельные знаки, чтобы разбить длинные строки. Теперь давайте обсудим, что делает эта строка. Как упоминалось ранее, `read_line` помещает всё, что вводит пользователь, в строку, которую мы ему передаём, но также возвращает значение `Result`. `Result` — это [*перечисление*], часто называемое *enum*, то есть вид, который может находиться в одном из нескольких возможных состояний. Мы называем каждое такое состояние *исходом*. @@ -166,19 +166,19 @@ io::stdin().read_line(&mut guess).expect("Failed to read line"); {{#include ../listings/ch02-guessing-game-tutorial/no-listing-02-without-expect/output.txt}} ``` -Rust предупреждает о неиспользованном значении `Result`, возвращаемого из `read_line`, показывая, что программа не учла возможность возникновения ошибки. +Ржавчина предупреждает о неиспользованном значении `Result`, возвращаемого из `read_line`, показывая, что программа не учла возможность возникновения ошибки. Правильный способ убрать предупреждение — это написать обработку ошибок, но в нашем случае мы просто хотим со сбоем завершить программу при возникновении сбоев, поэтому используем `expect`. О способах восстановления после ошибок вы узнаете в [главе 9]. ### Вывод значений с помощью заполнителей `println!` -Кроме закрывающей фигурной скобки, в коде на данный мгновение есть ещё только одно место для обсуждения: +Кроме закрывающей фигурной скобки, в рукописи на данный мгновение есть ещё только одно место для обсуждения: ```rust,ignore {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-01/src/main.rs:print_guess}} ``` -Этот код выводит строку, которая теперь содержит ввод пользователя. Набор фигурных скобок `{}` является заполнителем: думайте о `{}` как о маленьких клешнях краба, которые удерживают значение на месте. При печати значения переменной имя переменной может заключаться в фигурные скобки. При печати итога вычисления выражения поместите пустые фигурные скобки в строку вида, затем после строки вида укажите список выражений, разделённых запятыми, которые будут напечатаны в каждом заполнителе пустой фигурной скобки в том же порядке. Печать переменной и итога выражения одним вызовом `println!` будет выглядеть так: +Этот рукопись выводит строку, которая теперь содержит ввод пользователя. Набор фигурных скобок `{}` является заполнителем: думайте о `{}` как о маленьких клешнях краба, которые удерживают значение на месте. При печати значения переменной имя переменной может заключаться в фигурные скобки. При печати итога вычисления выражения поместите пустые фигурные скобки в строку вида, затем после строки вида укажите список выражений, разделённых запятыми, которые будут напечатаны в каждом заполнителе пустой фигурной скобки в том же порядке. Печать переменной и итога выражения одним вызовом `println!` будет выглядеть так: ```rust let x = 5; @@ -187,7 +187,7 @@ let y = 10; println!("x = {x} and y + 2 = {}", y + 2); ``` -Этот код выведет `x = 5 and y + 2 = 12`. +Этот рукопись выведет `x = 5 and y + 2 = 12`. ### Проверка первой части @@ -218,9 +218,9 @@ You guessed: 6 ### Использование ящика для получения дополнительного возможностей -Помните, что дополнение (crate) - это собрание файлов исходного кода Rust. Дело, создаваемый нами, представляет собой
двоичный дополнение (binary crate), который является исполняемым файлом. Дополнение rand - это библиотечный дополнение (library crate), содержащий код, который предназначен для использования в других программах и поэтому не может исполняться сам по себе. +Помните, что дополнение (crate) - это собрание файлов исходного рукописи Ржавчина. Дело, создаваемый нами, представляет собой
двоичный дополнение (binary crate), который является исполняемым файлом. Дополнение rand - это библиотечный дополнение (library crate), содержащий рукопись, который предназначен для использования в других программах и поэтому не может исполняться сам по себе. -Согласование работы внешних дополнений является тем местом, где Cargo на самом деле блистает. Чтобы начать писать код, использующий `rand`, необходимо изменить файл *Cargo.toml*, включив в него в качестве зависимости дополнение `rand`. Итак, откройте этот файл и добавьте следующую строку внизу под заголовком разделы `[dependencies]`, созданным для вас Cargo. Обязательно укажите `rand` в точности так же, как здесь, с таким же номером исполнения, иначе примеры кода из этого урока могут не заработать. +Согласование работы внешних дополнений является тем местом, где Cargo на самом деле блистает. Чтобы начать писать рукопись, использующий `rand`, необходимо изменить файл *Cargo.toml*, включив в него в качестве зависимости дополнение `rand`. Итак, откройте этот файл и добавьте следующую строку внизу под заголовком разделы `[dependencies]`, созданным для вас Cargo. Обязательно укажите `rand` в точности так же, как здесь, с таким же номером исполнения, иначе примеры рукописи из этого урока могут не заработать. преобразует строку в другой вид. Здесь мы используем его для преобразования строки в число. Нам нужно сообщить Ржавчина точный числовой вид, который мы хотим получить, используя `let guess: u32`. Двоеточие ( `:` ) после `guess` говорит Rust, что мы определяем вид переменной. В Ржавчина есть несколько встроенных числовых видов; `u32`, показанный здесь, представляет собой 32-битное целое число без знака. Это хороший выбор по умолчанию для небольшого положительного числа. Вы узнаете о других видах чисел в главе 3. +Способ [`parse` строк] преобразует строку в другой вид. Здесь мы используем его для преобразования строки в число. Нам нужно сообщить Ржавчина точный числовой вид, который мы хотим получить, используя `let guess: u32`. Двоеточие ( `:` ) после `guess` говорит Ржавчина, что мы определяем вид переменной. В Ржавчине есть несколько встроенных числовых видов; `u32`, показанный здесь, представляет собой 32-битное целое число без знака. Это хороший выбор по умолчанию для небольшого положительного числа. Вы узнаете о других видах чисел в главе 3. -Кроме того, изложение u32 в этом примере программы и сравнение с secret_number означает, что Ржавчина сделает вывод, что secret_number должен быть u32. Итак, теперь сравнение будет между двумя значениями одного типа! +Кроме того, изложение u32 в этом примере программы и сравнение с secret_number означает, что Ржавчина сделает вывод, что secret_number должен быть u32. Итак, теперь сравнение будет между двумя значениями одного вида! -Способ `parse` будет работать только с символами, которые логически могут быть преобразованы в числа, и поэтому легко может вызвать ошибки. Если, например, строка содержит `A👍%`, преобразовать её в число невозможно. Так как способ `parse` может потерпеть неудачу, он возвращает вид `Result` — так же как и способ `read_line` (обсуждалось ранее в разделе «Обработка возможной ошибки с помощью вида `Result`»). Мы будем точно так же обрабатывать данный Result, вновь используя способ `expect`. Если `parse` вернёт исход `Result` `Err`, так как не смог создать число из строки, вызов `expect` со сбоем завершит игру и отобразит переданное ему сообщение. Если `parse` сможет успешно преобразовать строку в число, он вернёт исход `Result` `Ok`, а `expect` вернёт число, полученное из значения `Ok`. +Способ `parse` будет работать только с знаками, которые разумно могут быть преобразованы в числа, и поэтому легко может вызвать ошибки. Если, например, строка содержит `A👍%`, преобразовать её в число невозможно. Так как способ `parse` может потерпеть неудачу, он возвращает вид `Result` — так же как и способ `read_line` (обсуждалось ранее в разделе «Обработка возможной ошибки с помощью вида `Result`»). Мы будем точно так же обрабатывать данный Result, вновь используя способ `expect`. Если `parse` вернёт исход `Result` `Err`, так как не смог создать число из строки, вызов `expect` со сбоем завершит игру и отобразит переданное ему сообщение. Если `parse` сможет успешно преобразовать строку в число, он вернёт исход `Result` `Ok`, а `expect` вернёт число, полученное из значения `Ok`. Давайте запустим программу теперь: @@ -460,11 +460,11 @@ Too big! Хорошо! Несмотря на то, что были добавлены пробелы в строке ввода, программа всё равно поняла, что пользователь имел в виду число 76. Запустите программу несколько раз, чтобы проверить разное поведение при различных видах ввода: задайте число правильно, задайте слишком большое число и задайте слишком маленькое число. -Сейчас у нас работает большая часть игры, но пользователь может сделать только одну догадку. Давайте изменим это, добавив цикл! +Сейчас у нас работает большая часть игры, но пользователь может сделать только одну догадку. Давайте изменим это, добавив круговорот! -## Возможность нескольких догадок с помощью циклов +## Возможность нескольких догадок с помощью круговоротов -Ключевое слово `loop` создаёт бесконечный цикл. Мы добавляем цикл, чтобы дать пользователям больше шансов угадать число: +Ключевое слово `loop` создаёт бесконечный круговорот. Мы добавляем круговорот, чтобы дать пользователям больше возможностей угадать число: Имя файла: src/main.rs @@ -472,7 +472,7 @@ Too big! {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/no-listing-04-looping/src/main.rs:here}} ``` -Как видите, мы перемеисполнения всё, начиная с подсказки ввода догадки, в цикл. Не забудьте добавить ещё по четыре пробела на отступы строк внутри цикла и запустите программу снова. Теперь программа будет бесконечно запрашивать ещё одну догадку, что в действительности создаёт новую неполадку. Похоже, пользователь не сможет выйти из игры! +Как видите, мы переместили всё, начиная с подсказки ввода догадки, в круговорот. Не забудьте добавить ещё по четыре пробела на отступы строк внутри круговорота и запустите программу снова. Теперь программа будет бесконечно запрашивать ещё одну догадку, что в действительности создаёт новую неполадку. Похоже, пользователь не сможет выйти из игры! Пользователь может прервать выполнение программы с помощью сочетания клавиш ctrl+c. Но есть и другой способ спастись от этого ненасытного монстра, о котором говорилось при обсуждении `parse` в [«Сравнение догадки с тайным числом»](#comparing-the-guess-to-the-secret-number): если пользователь введёт нечисловой ответ, программа завершится со сбоем. Мы можем воспользоваться этим, чтобы позволить пользователю выйти из игры, как показано здесь: @@ -514,7 +514,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ### Выход после правильной догадки -Давайте запрограммируем игру на выход при выигрыше пользователя, добавив оператор `break`: +Давайте запрограммируем игру на выход при выигрыше пользователя, добавив приказчик `break`: Файл: src/main.rs @@ -522,11 +522,11 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/no-listing-05-quitting/src/main.rs:here}} ``` -Добавление строки `break` после `You win!` заставляет программу выйти из цикла, когда пользователь правильно угадает тайное число. Выход из цикла также означает выход из программы, так как цикл является последней частью `main`. +Добавление строки `break` после `You win!` заставляет программу выйти из круговорота, когда пользователь правильно угадает тайное число. Выход из круговорота также означает выход из программы, так как круговорот является последней частью `main`. ### Обработка недопустимого ввода -Чтобы улучшить поведение игры, вместо со сбоемго завершения программы, когда пользователь вводит не число, давайте заставим игру пренебрегать этотобстоятельство, позволяя пользователю продолжить угадывание. Для этого необходимо изменить строку, в которой `guess` преобразуется из `String` в `u32`, как показано в приложении 2-5. +Чтобы улучшить поведение игры, вместо со сбоем завершения программы, когда пользователь вводит не число, давайте заставим игру пренебрегать это обстоятельство, позволяя пользователю продолжить угадывание. Для этого необходимо изменить строку, в которой `guess` преобразуется из `String` в `u32`, как показано в приложении 2-5. Файл: src/main.rs @@ -536,11 +536,11 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Приложение 2-5. Пренебрежение нечисловой догадки и запрос другой догадки вместо завершения программы -Мы заменяем вызов `expect` на выражение `match`, чтобы перейти от со сбоемго завершения при ошибке к обработке ошибки. Помните, что `parse` возвращает вид `Result`, а `Result` — это перечисление, которое имеет исходы `Ok` и `Err`. Здесь мы используем выражение `match`, как и в случае с итогом `Ordering` способа `cmp`. +Мы заменяем вызов `expect` на выражение `match`, чтобы перейти от со сбоем завершения при ошибке к обработке ошибки. Помните, что `parse` возвращает вид `Result`, а `Result` — это перечисление, которое имеет исходы `Ok` и `Err`. Здесь мы используем выражение `match`, как и в случае с итогом `Ordering` способа `cmp`. Если `parse` успешно преобразует строку в число, он вернёт значение `Ok`, содержащее полученное число. Это значение `Ok` будет соответствовать образцу первой ветки, а выражение `match` просто вернёт значение `num`, которое `parse` произвёл и поместил внутрь значения `Ok`. Это число окажется в нужной нам переменной `guess`, которую мы создали. -Если способ `parse` *не способен* превратить строку в число, он вернёт значение `Err`, которое содержит более подробную сведения об ошибке. Значение `Err` не совпадает с образцом `Ok(num)` в первой ветке `match`, но совпадает с образцом `Err(_)` второй ветки. Подчёркивание `_` является всеохватывающим выражением. В этой ветке мы говорим, что хотим обработать совпадение всех значений `Err`, независимо от того, какая сведения находится внутри. Поэтому программа выполнит код второй ветки, `continue`, который сообщает программе перейти к следующей повторения `loop` и запросить ещё одну догадку. В этом случае программа эффективно пренебрегает все ошибки, с которыми parse может столкнуться! +Если способ `parse` *не способен* превратить строку в число, он вернёт значение `Err`, которое содержит более подробные сведения об ошибке. Значение `Err` не совпадает с образцом `Ok(num)` в первой ветке `match`, но совпадает с образцом `Err(_)` второй ветки. Подчёркивание `_` является всеохватывающим выражением. В этой ветке мы говорим, что хотим обработать совпадение всех значений `Err`, независимо от того, какая сведения находится внутри. Поэтому программа выполнит рукопись второй ветки, `continue`, который сообщает программе перейти к следующей повторения `loop` и запросить ещё одну догадку. В этом случае программа правильно пренебрегает все ошибки, с которыми parse может столкнуться! Всё в программе теперь должно работать как положено. Давайте попробуем: @@ -576,7 +576,7 @@ You guessed: 61 You win! ``` -Потрясающе! С помощью одной маленькой последней правки мы закончим игру в угадывание. Напомним, что программа все ещё печатает тайное число. Это хорошо подходило для проверки, но это портит игру. Давайте удалим `println!`, который выводит тайное число. В Приложении 2-6 показан окончательный исход кода. +Потрясающе! С помощью одной маленькой последней правки мы закончим игру в угадывание. Напомним, что программа все ещё печатает тайное число. Это хорошо подходило для проверки, но это портит игру. Давайте удалим `println!`, который выводит тайное число. В Приложении 2-6 показан окончательный исход рукописи. Файл: src/main.rs @@ -584,16 +584,16 @@ You win! {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-06/src/main.rs}} ``` -Приложение 2-6: полный код игры +Приложение 2-6: полный рукопись игры На данный мгновение вы успешно создали игру в загадки. Поздравляем! ## Заключение -Этот дело — опытный способ познакомить вас со многими новыми подходами Rust: `let`, `match`, функции, использование внешних ящиков и многое другое. В следующих нескольких главах вы изучите эти подходы более подробно. Глава 3 охватывает понятия, которые есть в большинстве языков программирования, такие как переменные, виды данных и функции, и показывает, как использовать их в Rust. В главе 4 рассматривается владение — особенность, которая отличает Ржавчина от других языков. В главе 5 обсуждаются устройства и правила написания способов, а в главе 6 объясняется, как работают перечисления. +Это дело — отличный способ познакомить вас со многими новыми подходами Ржавчины: `let`, `match`, функции, использование внешних ящиков и многое другое. В следующих нескольких главах вы изучите эти подходы более подробно. Глава 3 охватывает понятия, которые есть в большинстве языков программирования, такие как переменные, виды данных и функции, и показывает, как использовать их в Ржавчине. В главе 4 рассматривается владение — особенность, которая отличает Ржавчина от других языков. В главе 5 обсуждаются устройства и правила написания способов, а в главе 6 объясняется, как работают перечисления. -[в документации встроенной библиотеки]: ../std/prelude/index.html +[в пособия встроенной библиотеки]: ../std/prelude/index.html ["Переменные и изменчивость".]: ch03-01-variables-and-mutability.html#variables-and-mutability [Главе 3]: ch03-04-comments.html [`std::io::Stdin`]: ../std/io/struct.Stdin.html diff --git a/rustbook-ru/src/ch03-00-common-programming-concepts.md b/rustbook-ru/src/ch03-00-common-programming-concepts.md index 322dc9efa..312712ed9 100644 --- a/rustbook-ru/src/ch03-00-common-programming-concepts.md +++ b/rustbook-ru/src/ch03-00-common-programming-concepts.md @@ -1,10 +1,10 @@ # Общие подходы программирования -В этой главе рассматриваются подходы, присутствующие почти в каждом языке программирования, и то, как они работают в Rust. В основе большинства языков программирования есть много общего. Все подходы, представленные в этой главе, не являются единственными для Rust, но мы обсудим их в среде Ржавчина и разъясним правила использования этих подходов. +В этой главе рассматриваются подходы, присутствующие почти в каждом языке программирования, и то, как они работают в Ржавчине. В основе большинства языков программирования есть много общего. Все подходы, представленные в этой главе, не являются единственными для Ржавчины, но мы обсудим их в среде Ржавчина и разъясним правила использования этих подходов. -В частности вы изучите переменные, основные виды, функции, примечания и поток управления. Эти фундаментальные понятия будут присутствовать в каждой программе на Rust, и их изучение на ранней стадии даст вам прочную основу для начала работы. +В частности вы изучите переменные, основные виды, функции, примечания и поток управления. Эти фундаментальные понятия будут присутствовать в каждой программе на Ржавчине, и их изучение на ранней этапе даст вам прочную основу для начала работы. >

Ключевые слова

->

В языке Ржавчина как и в других языках есть набор ключевых слов, зарезервированных только для использования в языке. Помните, что нельзя использовать эти слова в качестве имён переменных или функций. Большинство этих ключевых слов имеют особые назначения, и вы будете использовать их для выполнения различных задач в своих программах на Rust. Некоторые из них сейчас не имеют функционального назначения, но зарезервированы для возможности, которая может быть добавлена в Ржавчина в будущем. Список ключевых слов вы можете найти в Приложении А.

+>

В языке Ржавчина, как и в других языках есть набор ключевых слов, зарезервированных только для использования в языке. Помните, что нельзя использовать эти слова в качестве имён переменных или функций. Большинство этих ключевых слов имеют особые назначения, и вы будете использовать их для выполнения различных задач в своих программах на Ржавчине. Некоторые из них сейчас не имеют функционального назначения, но зарезервированы для возможности, которая может быть добавлена в Ржавчине в будущем. Список ключевых слов вы можете найти в Приложении А.

diff --git a/rustbook-ru/src/ch03-01-variables-and-mutability.md b/rustbook-ru/src/ch03-01-variables-and-mutability.md index 9a520c24e..909075abd 100644 --- a/rustbook-ru/src/ch03-01-variables-and-mutability.md +++ b/rustbook-ru/src/ch03-01-variables-and-mutability.md @@ -1,10 +1,10 @@ ## Переменные и изменяемость -Как упоминалось в разделе ["Хранение значений с помощью переменных"](ch02-00-guessing-game-tutorial.html#%D0%A5%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B9-%D1%81-%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D1%85), по умолчанию переменные неизменяемы. Это один из многих стимулов Rust, позволяющий писать код с использованием преимущества безопасности и удобной состязательности (concurrency), предоставляемых Rust. Тем не менее, существует возможность сделать переменные изменяемыми. Давайте рассмотрим, как и почему Ржавчина побуждает предпочесть неизменяемость и почему иногда можно отказаться от этого. +Как упоминалось в разделе ["Хранение значений с помощью переменных"](ch02-00-guessing-game-tutorial.html#%D0%A5%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B9-%D1%81-%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D1%85), по умолчанию переменные неизменяемы. Это один из многих преимуществ Ржавчине, позволяющий писать рукопись с использованием преимущества безопасности и удобной состязательности (concurrency), предоставляемых Ржавчина. Тем не менее, существует возможность сделать переменные изменяемыми. Давайте рассмотрим, как и почему Ржавчина побуждает предпочесть неизменяемость и почему иногда можно отказаться от этого. -Если переменная является неизменяемой, то после привязки значения к имени изменить его будет нельзя. Чтобы показать это, создайте новый дело под названием *variables* в папке *projects* с помощью приказы `cargo new variables`. +Если переменная является неизменяемой, то после привязки значения к имени изменить его будет нельзя. Чтобы показать это, создайте новое дело под названием *variables* в папке *projects* с помощью приказы `cargo new variables`. -Далее, в новом папке *variables* откройте *src/main.rs* и замените в нем код на ниже приведённый, который пока не будет собираться: +Далее, в новом папке *variables* откройте *src/main.rs* и замените в нем рукопись на ниже приведённый, который пока не будет собираться: Имя файла: src/main.rs @@ -27,9 +27,9 @@ error[E0384]: cannot assign twice to immutable variable `x` --> src/main.rs:4:5 Вы получили сообщение об ошибке `cannot assign twice to immutable variable `x``, потому что попытались присвоить новое значение неизменяемой переменной `x`. -Важно, чтобы при попытке изменить значение, объявленное неизменяемым, выдавались ошибки времени сборки, так как подобная случаей может привести к сбоям. Если одна часть нашего кода исполняется исходя из уверенности в неизменяемости значения, а другая часть изменяет это значение, то велика вероятность , что первая часть не выполнит своего предназначения. Причину такой ошибки бывает трудно отследить, особенно если вторая часть кода изменяет значение лишь *изредка*. Сборщик Ржавчина предоставляет заверение, что если объявить значение неизменяемым, то оно действительно не изменится, а значит, не нужно следить за этим самим. Таким образом, ваш код становится проще для понимания. +Важно, чтобы при попытке изменить значение, объявленное неизменяемым, выдавались ошибки времени сборки, так как подобная случай может привести к сбоям. Если одна часть нашего рукописи исполняется исходя из уверенности в неизменяемости значения, а другая часть изменяет это значение, то велика вероятность , что первая часть не выполнит своего предназначения. Причину такой ошибки бывает трудно отследить, особенно если вторая часть рукописи изменяет значение лишь *изредка*. Сборщик Ржавчина предоставляет заверение, что если объявить значение неизменяемым, то оно действительно не изменится, а значит, не нужно следить за этим самим. Таким образом, ваша рукопись становится проще для понимания. -Однако изменяемость может быть очень полезной и может сделать код более удобным для написания. Хотя переменные по умолчанию неизменяемы, их можно сделать изменяемыми, добавив `mut` перед именем переменной, как это было сделано в [Главе 2]. Добавление `mut` также передаёт будущим читателям кода намерение, обозначая, что другие части кода будут изменять значение этой переменной. +Однако изменяемость может быть очень полезной и может сделать рукопись более удобным для написания. Хотя переменные по умолчанию неизменяемы, их можно сделать изменяемыми, добавив `mut` перед именем переменной, как это было сделано в [Главе 2]. Добавление `mut` также передаёт будущим читателям рукописи намерение, обозначая, что другие части рукописи будут изменять значение этой переменной. Например, изменим *src/main.rs* на следующий код: @@ -53,7 +53,7 @@ error[E0384]: cannot assign twice to immutable variable `x` --> src/main.rs:4:5 Во-первых, нельзя использовать `mut` с постоянными значениями. Постоянного значения не просто неизменяемы по умолчанию — они неизменяемы всегда. Для объявления постоянных значенийиспользуется ключевое слово `const` вместо `let`, а также вид значения *должен быть* указан в изложении. Мы рассмотрим виды и изложении видов в следующем разделе [«Виды данных».], так что не беспокойтесь о подробностях прямо сейчас. Просто знайте, что вы всегда должны определять вид. -Постоянного значения можно объявлять в любой области видимости, включая вездесущую, благодаря этому они полезны для значений, которые нужны во многих частях кода. +Постоянного значения можно объявлять в любой области видимости, включая вездесущую, благодаря этому они полезны для значений, которые нужны во многих частях рукописи. Последнее отличие в том, что постоянные значения могут быть заданы только постоянным выражением, но не итогом вычисленного во время выполнения значения. @@ -67,7 +67,7 @@ const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; Постоянного значения существуют в течение всего времени работы программы в пределах области, в которой они были объявлены. Это свойство делает постоянные значения полезными для значений в домене вашего приложения, о которых могут знать несколько частей программы, например, наибольшее количество очков, которое может заработать любой игрок в игре, или скорость света. -Обозначение жёстко закодированных значений, используемых в программе, как постоянные значения полезно для передачи смысла этого значения будущим сопровождающим кода. Это также позволяет иметь единственное место в коде, которое нужно будет изменить, если в будущем потребуется обновить значение. +Обозначение жёстко закодированных значений, используемых в программе, как постоянные значения полезно для передачи смысла этого значения будущим сопровождающим рукописи. Это также позволяет иметь единственное место в рукописи, которое нужно будет изменить, если в будущем потребуется обновить значение. ### Затенение (переменных) @@ -79,7 +79,7 @@ const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; {{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-03-shadowing/src/main.rs}} ``` -Эта программа сначала привязывает `x` к значению `5`. Затем она создаёт новую переменную `x`, повторяя `let x =`, беря исходное значение и добавляя `1`, чтобы значение `x` стало равным `6`. Затем во внутренней области видимости, созданной с помощью фигурных скобок, третий оператор `let` также затеняет `x` и создаёт новую переменную, умножая предыдущее значение на `2`, чтобы дать `x` значение `12`. Когда эта область заканчивается, внутреннее затенение заканчивается, и `x` возвращается к значению `6`. Запустив эту программу, она выведет следующее: +Эта программа сначала привязывает `x` к значению `5`. Затем она создаёт новую переменную `x`, повторяя `let x =`, беря исходное значение и добавляя `1`, чтобы значение `x` стало равным `6`. Затем во внутренней области видимости, созданной с помощью фигурных скобок, третий приказчик `let` также затеняет `x` и создаёт новую переменную, умножая предыдущее значение на `2`, чтобы дать `x` значение `12`. Когда эта область заканчивается, внутреннее затенение заканчивается, и `x` возвращается к значению `6`. Запустив эту программу, она выведет следующее: ```console {{#include ../listings/ch03-common-programming-concepts/no-listing-03-shadowing/output.txt}} @@ -87,7 +87,7 @@ const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; Затенение отличается от объявления переменной с помощью `mut`, так как мы получим ошибку сборки, если случайно попробуем переназначить значение без использования ключевого слова `let`. Используя `let`, можно выполнить несколько превращений над значением, при этом оставляя переменную неизменяемой, после того как все эти превращения завершены. -Другой разницей между `mut` и затенением является то, что мы создаём совершенно новую переменную, когда снова используем слово `let` (ещё одну). Мы можем даже изменить вид значения, но снова использовать прежнее имя. К примеру, наша программа спрашивает пользователя, сколько пробелов он хочет разместить между некоторым текстом, запрашивая символы пробела, но мы на самом деле хотим сохранить данный ввод как число: +Другой разницей между `mut` и затенением является то, что мы создаём совершенно новую переменную, когда снова используем слово `let` (ещё одну). Мы можем даже изменить вид значения, но снова использовать прежнее имя. К примеру, наша программа спрашивает пользователя, сколько пробелов он хочет разместить между некоторым текстом, запрашивая знаки пробела, но мы на самом деле хотим сохранить данный ввод как число: ```rust {{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-04-shadowing-can-change-types/src/main.rs:here}} diff --git a/rustbook-ru/src/ch03-02-data-types.md b/rustbook-ru/src/ch03-02-data-types.md index a5bd722c3..82d3a0b7f 100644 --- a/rustbook-ru/src/ch03-02-data-types.md +++ b/rustbook-ru/src/ch03-02-data-types.md @@ -1,6 +1,6 @@ ## Виды Данных -Каждое значение в Ржавчина относится к определённому *виду данных*, который указывает на вид данных, что позволяет Ржавчина знать, как работать с этими данными. Мы рассмотрим два подмножества видов данных: одиночные и составные. +Каждое значение в Ржавчине относится к определённому *виду данных*, который указывает на вид данных, что позволяет Ржавчина знать, как работать с этими данными. Мы рассмотрим два подмножества видов данных: одиночные и составные. Не забывайте, что Ржавчина является *постоянно строго определенным* (statically typed) языком. Это означает, что он должен знать виды всех переменных во время сборки. Обычно сборщик может предположить, какой вид используется (вывести его), основываясь на значении и на том, как мы с ним работаем. В случаях, когда может быть выведено несколько видов, необходимо добавлять изложение вида вручную. Например, когда мы преобразовали `String` в число с помощью вызова `parse` в разделе [«Сравнение предположения с загаданным номером»](ch02-00-guessing-game-tutorial.html#comparing-the-guess-to-the-secret-number) главы 2, мы должны добавить такую изложение: @@ -8,7 +8,7 @@ let guess: u32 = "42".parse().expect("Not a number!"); ``` -Если мы не добавим изложение вида `: u32`, показанную в предыдущем коде, Ржавчина отобразит следующую ошибку, которая означает, что сборщику нужно от нас больше сведений, чтобы узнать, какой вид мы хотим использовать: +Если мы не добавим изложение вида `: u32`, показанную в предыдущем рукописи, Ржавчина отобразит следующую ошибку, которая означает, что сборщику нужно от нас больше сведений, чтобы узнать, какой вид мы хотим использовать: ```console {{#include ../listings/ch03-common-programming-concepts/output-only-01-no-type-annotations/output.txt}} @@ -18,13 +18,13 @@ let guess: u32 = "42".parse().expect("Not a number!"); ### Одиночные виды данных -*Одиночный* вид представляет собой единичное значение. В Ржавчина есть четыре основных одиночных вида: целочисленный, числа с плавающей точкой, логический и символы. Вы наверняка знакомы с этими видами по другим языкам программирования. Давайте разберёмся, как они работают в Rust. +*Одиночный* вид представляет собой единичное значение. В Ржавчине есть четыре основных одиночных вида: целочисленный, числа с плавающей точкой, разумный и знаки. Вы наверняка знакомы с этими видами по другим языкам программирования. Давайте разберёмся, как они работают в Ржавчине. #### Целочисленные виды -Целочисленный вид (*integer*) — это число без дробной части. В главе 2 мы использовали один целочисленный вид — вид `u32`. Такое объявление вида указывает, что значение, с которым оно связано, должно быть целым числом без знака (виды целых чисел со знаком начинаются с `i` вместо `u`), которое занимает 32 бита памяти. В Таблице 3-1 показаны встроенные целочисленные виды в Rust. Мы можем использовать любой из этих исходов для объявления вида целочисленного значения. +Целочисленный вид (*integer*) — это число без дробной части. В главе 2 мы использовали один целочисленный вид — вид `u32`. Такое объявление вида указывает, что значение, с которым оно связано, должно быть целым числом без знака (виды целых чисел со знаком начинаются с `i` вместо `u`), которое занимает 32 бита памяти. В Таблице 3-1 показаны встроенные целочисленные виды в ы можем использовать любой из этих исходов для объявления вида целочисленного значения. -Таблица 3-1: целочисленные виды в Rust +Таблица 3-1: целочисленные виды в Ржавчине Длина | Со знаком | Без знака --- | --- | --- @@ -35,7 +35,7 @@ let guess: u32 = "42".parse().expect("Not a number!"); 128 бит | `i128` | `u128` архитектурно-зависимая | `isize` | `usize` -Каждый исход может быть как со знаком, так и без знака и имеет явный размер. Такая свойство вида как *знаковый* и *беззнаковый* определяет возможность числа быть отрицательным. Другими словами, должно ли число иметь знак (знаковое) или оно всегда будет только положительным и, следовательно, может быть представлено без знака (беззнаковое). Это похоже на написание чисел на бумаге: когда знак имеет значение, число отображается со знаком плюс или со знаком -; однако, когда можно с уверенностью предположить, что число положительное, оно отображается без знака. Числа со знаком хранятся с использованием [дополнительного кода]. +Каждый исход может быть как со знаком, так и без знака и имеет явный размер. Такая свойство вида как *знаковый* и *беззнаковый* определяет возможность числа быть отрицательным. Другими словами, должно ли число иметь знак (знаковое) или оно всегда будет только положительным и, следовательно, может быть представлено без знака (беззнаковое). Это похоже на написание чисел на бумаге: когда знак имеет значение, число отображается со знаком плюс или со знаком -; однако, когда можно с уверенностью предположить, что число положительное, оно отображается без знака. Числа со знаком хранятся с использованием [дополнительного рукописи]. Каждый исход со знаком может хранить числа от -(2 n - 1 ) до 2 n - 1 - 1 включительно, где *n* — количество битов, которые использует этот исход. Таким образом, `i8` может хранить числа от -(2 7 ) до 2 7 - 1, что равно значениям от -128 до 127. Исходы без знака могут хранить числа от 0 до 2 n - 1, поэтому `u8` может хранить числа от 0 до 2 8 - 1, что равно значениям от 0 до 255. @@ -43,7 +43,7 @@ let guess: u32 = "42".parse().expect("Not a number!"); Вы можете записывать целочисленные записи в любой из разновидностей, показанных в таблице 3-2. Заметьте, что числовые записи, имеющие несколько числовых видов, допускают использование вставки вида, например `57u8`, для обозначения вида. Числовые записи также могут использовать `_` в качестве визуального разделителя для облегчения чтения числа, например `1_000`, который будет иметь такое же значение, как если бы было задано `1000`. -Таблица 3-2: Целочисленные записи в Rust +Таблица 3-2: Целочисленные записи в Ржавчине Числовой запись | Пример --- | --- @@ -53,18 +53,18 @@ let guess: u32 = "42".parse().expect("Not a number!"); Двоичный | `0b1111_0000` Байт (только `u8`) | `b'A'` -Как же узнать, какой вид целого числа использовать? Если вы не уверены, значения по умолчанию в Rust, как правило, подходят для начала: целочисленные виды по умолчанию `i32`. Основной случай, в котором вы должны использовать `isize` или `usize`, — это упорядочевание какой-либо собрания. +Как же узнать, какой вид целого числа использовать? Если вы не уверены, значения по умолчанию в Ржавчине, как правило, подходят для начала: целочисленные виды по умолчанию `i32`. Основной случай, в котором вы должны использовать `isize` или `usize`, — это упорядочевание какой-либо собрания. -> Целочисленное переполнение Допустим, имеется переменная вида `u8`, которая может хранить значения от 0 до 255. Если попытаться изменить переменную на значение вне этого ряда, например, 256, произойдёт *целочисленное переполнение*, что может привести к одному из двух исходов поведения. Если выполняется сборка в режиме отладки, Ржавчина включает проверку на целочисленное переполнение, приводящую вашу программу к *панике* во время выполнения, когда возникает такое поведение. Ржавчина использует понятие *паника(panicking)*, когда программа завершается с ошибкой. Мы обсудим панику более подробно в разделе ["Неустранимые ошибки с `panic!`"] в главе 9. . При сборки в режиме release с флагом `--release`, Ржавчина *не* включает проверки на целочисленное переполнение, которое вызывает панику. Вместо этого, в случае переполнения, Ржавчина выполняет *обёртывание второго дополнения*. Проще говоря, значения, превышающие наибольшее значение, которое может хранить вид, "оборачиваются" к наименьшему из значений, которые может хранить вид. В случае `u8` значение 256 становится 0, значение 257 становится 1, и так далее. Программа не запаникует, но переменная будет иметь значение, которое, вероятно, не будет соответствовать вашим ожиданиям. Полагаться на поведение обёртывания целочисленного переполнения считается ошибкой. Для явной обработки возможности переполнения существует семейство способов, предоставляемых встроенной библиотекой для простых числовых видов: +> Целочисленное переполнение Допустим, имеется переменная вида `u8`, которая может хранить значения от 0 до 255. Если попытаться изменить переменную на значение вне этого ряда, например, 256, произойдёт *целочисленное переполнение*, что может привести к одному из двух исходов поведения. Если выполняется сборка в режиме отладки, Ржавчина включает проверку на целочисленное переполнение, приводящую вашу программу к *сбое* во время выполнения, когда возникает такое поведение. Ржавчина использует понятие *сбой(panicking)*, когда программа завершается с ошибкой. Мы обсудим сбой более подробно в разделе ["Неустранимые ошибки с `panic!`"] в главе 9. . При сборки в режиме release с клеймом `--release`, Ржавчина *не* включает проверки на целочисленное переполнение, которое вызывает сбой. Вместо этого, в случае переполнения, Ржавчина выполняет *обёртывание второго дополнения*. Проще говоря, значения, превышающие наибольшее значение, которое может хранить вид, "оборачиваются" к наименьшему из значений, которые может хранить вид. В случае `u8` значение 256 становится 0, значение 257 становится 1, и так далее. Программа не запаникует, но переменная будет иметь значение, которое, вероятно, не будет соответствовать вашим ожиданиям. Полагаться на поведение обёртывания целочисленного переполнения считается ошибкой. Для явной обработки возможности переполнения существует семейство способов, предоставляемых встроенной библиотекой для простых числовых видов: > - Обёртывание во всех режимах с помощью способов `wrapping_*`, таких как `wrapping_add`. > - Возврат значения `None` при переполнении с помощью способов `checked_*`. -> - Возврат значения и логический индикатор, указывающий, произошло ли переполнение при использовании способов `overflowing_*`. +> - Возврат значения и разумный показатель, указывающий, произошло ли переполнение при использовании способов `overflowing_*`. > - Насыщение наименьшим или наибольшим значением с помощью способов `saturating_*`. > #### Числа с плавающей запятой -Также в Ржавчина есть два простых вида для чисел с плавающей запятой, представляющих собой числа с десятичной точкой. Виды с плавающей точкой в Ржавчина - это f32 и f64, размер которых составляет 32 бита и 64 бита соответственно. По умолчанию используется вид f64, поскольку на современных процессорах он работает примерно с той же скоростью, как и f32, но обладает большей точностью. Все виды с плавающей запятой являются знаковыми. +Также в Ржавчине есть два простых вида для чисел с плавающей запятой, представляющих собой числа с десятичной точкой. Виды с плавающей точкой в Ржавчине - это f32 и f64, размер которых составляет 32 бита и 64 бита соответственно. По умолчанию используется вид f64, поскольку на современных процессорах он работает примерно с той же скоростью, как и f32, но обладает большей точностью. Все виды с плавающей запятой являются знаковыми. Вот пример, отображающий числа с плавающей запятой в действии: @@ -78,7 +78,7 @@ let guess: u32 = "42".parse().expect("Not a number!"); #### Числовые действия -Rust поддерживает основные математические действия, привычные для всех видов чисел: сложение, вычитание, умножение, деление и остаток. Целочисленное деление обрезает значение в направлении нуля до ближайшего целого числа. Следующий код показывает, как можно использовать каждую числовую действие в указания `let`: +Ржавчина поддерживает основные математические действия, привычные для всех видов чисел: сложение, вычитание, умножение, деление и остаток. Целочисленное деление обрезает значение в направлении нуля до ближайшего целого числа. Следующий рукопись показывает, как можно использовать каждую числовую действие в указания `let`: Файл: src/main.rs @@ -86,11 +86,11 @@ Rust поддерживает основные математические де {{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-07-numeric-operations/src/main.rs}} ``` -Каждое выражение в этих указаниях использует математический оператор и вычисляется в одно значение, которое связывается с переменной. [Приложении B](appendix-02-operators.html) содержит список всех операторов, которые предоставляет Rust. +Каждое выражение в этих указаниях использует математический приказчик и вычисляется в одно значение, которое связывается с переменной. [Приложении B](appendix-02-operators.html) содержит список всех приказчиков, которые предоставляет Ржавчина. -#### Логический вид данных +#### Разумный вид данных -Как и в большинстве других языков программирования, логический вид в Ржавчина имеет два возможных значения: `true` и `false`. Значения логических видов имеют размер в один байт. Логический вид в Ржавчина задаётся с помощью `bool`. Например: +Как и в большинстве других языков программирования, разумный вид в Ржавчине имеет два возможных значения: `true` и `false`. Значения разумных видов имеют размер в один байт. Разумный вид в Ржавчине задаётся с помощью `bool`. Например: Файл: src/main.rs @@ -98,11 +98,11 @@ Rust поддерживает основные математические де {{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-08-boolean/src/main.rs}} ``` -Основной способ использования логических значений - это использование условий, таких как выражение `if`. Мы рассмотрим, как выражения `if` работают в Ржавчина в разделе ["Поток управления"]. +Основной способ использования разумных значений - это использование условий, таких как выражение `if`. Мы рассмотрим, как выражения `if` работают в Ржавчине в разделе ["Поток управления"]. -#### Символьный вид данных +#### Знаковый вид данных -Вид `char` в Ржавчина является самым простым алфавитным видом языка. Вот несколько примеров объявления значений `char`: +Вид `char` в Ржавчине является самым простым алфавитным видом языка. Вот несколько примеров объявления значений `char`: Файл: src/main.rs @@ -110,11 +110,11 @@ Rust поддерживает основные математические де {{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-09-char/src/main.rs}} ``` -Заметьте, мы указываем записи `char` с одинарными кавычками, в отличие от строковых записей, для которых используются двойные кавычки. Вид `char` в Ржавчина имеет размер четыре байта и представляет собой одиночное значение Unicode, а значит, может представлять собой не только ASCII. Акцентированные буквы, китайские, японские и корейские символы, эмодзи и пробелы нулевой ширины - все это допустимые значения вида `char` в Rust. Одиночные значения Unicode находятся в ряде от `U+0000` до `U+D7FF` и от `U+E000` до `U+10FFFF` включительно. Однако "символ" не является понятием в Unicode, поэтому ваше человеческое представление о том, что такое "символ", может не совпадать с тем, что такое `char` в Rust. Мы подробно обсудим эту тему в главе 8 "Хранение текста в кодировке UTF-8 с помощью строк". +Заметьте, мы указываем записи `char` с одинарными кавычками, в отличие от строковых записей, для которых используются двойные кавычки. Вид `char` в Ржавчине имеет размер четыре байта и представляет собой одиночное значение Unicode, а значит, может представлять собой не только ASCII. Акцентированные буквы, китайские, японские и корейские знаки, эмодзи и пробелы нулевой ширины - все это допустимые значения вида `char` в Ржавчине. Одиночные значения Unicode находятся в ряде от `U+0000` до `U+D7FF` и от `U+E000` до `U+10FFFF` включительно. Однако "знак" не является понятием в Unicode, поэтому ваше человеческое представление о том, что такое "знак", может не совпадать с тем, что такое `char` в Ржавчине. Мы подробно обсудим эту тему в главе 8 "Хранение текста в кодировке UTF-8 с помощью строк". ### Составные виды данных -*Составные виды* могут объединять различные значения в один вид. В Ржавчина есть два простых составных вида: упорядоченные ряды и массивы. +*Составные виды* могут объединять различные значения в один вид. В Ржавчине есть два простых составных вида: упорядоченные ряды и массивы. #### Упорядоченные ряды @@ -152,7 +152,7 @@ Rust поддерживает основные математические де #### Массивы -Другим способом создания собрания из нескольких значений является массив *array*. В отличие от упорядоченного ряда, каждый элемент массива должен иметь один и тот же вид. В отличие от массивов в некоторых других языках, массивы в Ржавчина имеют конечную длину. +Другим способом создания собрания из нескольких значений является массив *array*. В отличие от упорядоченного ряда, каждый элемент массива должен иметь один и тот же вид. В отличие от массивов в некоторых других языках, массивы в Ржавчине имеют конечную длину. Мы записываем значения в массиве в виде списка, разделённого запятыми, внутри квадратных скобок: @@ -185,7 +185,7 @@ let a: [i32; 5] = [1, 2, 3, 4, 5]; let a = [3; 5]; ``` -Массив в переменной `a` будет включать `5` элементов, значение которых будет равно `3`. Данная запись подобна коду `let a = [3, 3, 3, 3, 3];`, но является более краткой. +Массив в переменной `a` будет включать `5` элементов, значение которых будет равно `3`. Данная запись подобна рукописи `let a = [3, 3, 3, 3, 3];`, но является более краткой. ##### Доступ к элементам массива @@ -201,7 +201,7 @@ let a = [3; 5]; ##### Неправильный доступ к элементу массива -Давайте посмотрим, что произойдёт, если попытаться получить доступ к элементу массива, находящемуся за его пределами. Допустим, вы запускаете данный код, похожий на игру в угадывание из Главы 2, чтобы получить от пользователя порядковый указательмассива: +Давайте посмотрим, что произойдёт, если попытаться получить доступ к элементу массива, находящемуся за его пределами. Допустим, вы запускаете данный рукопись, похожий на игру в угадывание из Главы 2, чтобы получить от пользователя порядковый указательмассива: Файл: src/main.rs @@ -209,7 +209,7 @@ let a = [3; 5]; {{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-15-invalid-array-access/src/main.rs}} ``` -Этот код успешно собирается. Если запустить этот код с помощью `cargo run` и ввести `0`, `1`, `2`, `3` или `4`, программа напечатает соответствующее значение по данному порядковому указателю в массиве. Если вместо этого ввести число за пределами массива, например, `10`, то программа выведет следующее: +Этот рукопись успешно собирается. Если запустить этот рукопись с помощью `cargo run` и ввести `0`, `1`, `2`, `3` или `4`, программа напечатает соответствующее значение по данному порядковому указателю в массиве. Если вместо этого ввести число за пределами массива, например, `10`, то программа выведет следующее: Главы 2, чтобы выйти из программы, когда пользователь выиграл игру, угадав правильное число. +К счастью, Ржавчина также предоставляет способ выйти из круговорота с помощью рукописи. Ключевое слово `break` нужно поместить в круговорот, чтобы указать программе, когда следует прекратить выполнение круговорота. Напоминаем, мы делали так в игре "Угадайка" в разделе ["Выход после правильной догадки"](ch02-00-guessing-game-tutorial.html#quitting-after-a-correct-guess) Главы 2, чтобы выйти из программы, когда пользователь выиграл игру, угадав правильное число. -Мы также использовали `continue` в игре "Угадайка", которое указывает программе в цикле пропустить весь оставшийся код в данной повторения цикла и перейти к следующей повторения. +Мы также использовали `continue` в игре "Угадайка", которое указывает программе в круговороте пропустить весь оставшийся рукопись в данной повторения круговорота и перейти к следующей повторения. -#### Возвращение значений из циклов +#### Возвращение значений из круговоротов -Одно из применений `loop` - это повторение действия, которая может закончиться неудачей, например, проверка успешности выполнения потоком своего задания. Также может понадобиться передать из цикла итог этой действия в остальную часть кода. Для этого можно добавить возвращаемое значение после выражения `break`, которое используется для остановки цикла. Это значение будет возвращено из цикла, и его можно будет использовать, как показано здесь: +Одно из применений `loop` - это повторение действия, которая может закончиться неудачей, например, проверка успешности выполнения потоком своего задания. Также может понадобиться передать из круговорота итог этой действия в остальную часть рукописи. Для этого можно добавить возвращаемое значение после выражения `break`, которое используется для остановки круговорота. Это значение будет возвращено из круговорота, и его можно будет использовать, как показано здесь: ```rust {{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-33-return-value-from-loop/src/main.rs}} ``` -Перед циклом мы объявляем переменную с именем `counter` и объявим её значением `0`. Затем мы объявляем переменную с именем `result` для хранения значения, возвращаемого из цикла. На каждой повторения цикла мы добавляем `1` к переменной `counter`, а затем проверяем, равняется ли `10` переменная `counter`. Когда это происходит, мы используем ключевое слово `break` со значением `counter * 2`. После цикла мы ставим точку с запятой для завершения указания, присваивающей значение `result`. Наконец, мы выводим значение в `result`, равное в данном случае 20. +Перед круговоротом мы объявляем переменную с именем `counter` и объявим её значением `0`. Затем мы объявляем переменную с именем `result` для хранения значения, возвращаемого из круговорота. На каждой повторения круговорота мы добавляем `1` к переменной `counter`, а затем проверяем, равняется ли `10` переменная `counter`. Когда это происходит, мы используем ключевое слово `break` со значением `counter * 2`. После круговорота мы ставим точку с запятой для завершения указания, присваивающей значение `result`. Наконец, мы выводим значение в `result`, равное в данном случае 20. -#### Метки циклов для устранения неоднозначности между несколькими циклами +#### Метки круговоротов для устранения неоднозначности между несколькими круговоротами -Если у вас есть циклы внутри циклов, `break` и `continue` применяются к самому внутреннему циклу в этой цепочке. При желании вы можете создать *метку цикла*, которую вы затем сможете использовать с `break` или `continue` для указания, что эти ключевые слова применяются к помеченному циклу, а не к самому внутреннему циклу. Метки цикла должны начинаться с одинарной кавычки. Вот пример с двумя вложенными циклами: +Если у вас есть круговороты внутри круговоротов, `break` и `continue` применяются к самому внутреннему круговороту в этой цепочке. При желании вы можете создать *метку круговорота*, которую вы затем сможете использовать с `break` или `continue` для указания, что эти ключевые слова применяются к помеченному круговороту, а не к самому внутреннему круговороту. Метки круговорота должны начинаться с одинарной кавычки. Вот пример с двумя вложенными круговоротами: ```rust {{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-32-5-loop-labels/src/main.rs}} ``` -Внешний цикл имеет метку `'counting_up`, и он будет считать от 0 до 2. Внутренний цикл без метки ведёт обратный отсчёт от 10 до 9. Первый `break`, который не содержит метку, выйдет только из внутреннего цикла. Указание `break 'counting_up;` завершит внешний цикл. Этот код напечатает: +Внешний круговорот имеет метку `'counting_up`, и он будет считать от 0 до 2. Внутренний круговорот без метки ведёт обратный отсчёт от 10 до 9. Первый `break`, который не содержит метку, выйдет только из внутреннего круговорота. Указание `break 'counting_up;` завершит внешний круговорот. Этот рукопись напечатает: ```console {{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-32-5-loop-labels/output.txt}} ``` -#### Циклы с условием `while` +#### Круговороты с условием `while` -В программе часто требуется проверить состояние условия в цикле. Пока условие истинно, цикл выполняется. Когда условие перестаёт быть истинным, программа вызывает `break`, останавливая цикл. Такое поведение можно выполнить с помощью сочетания `loop`, `if`, `else` и `break`. При желании попробуйте сделать это в программе. Это настолько распространённый образец, что в Ржавчина выполнена встроенная языковая устройство для него, называемая цикл `while`. В приложении 3-3 мы используем `while`, чтобы выполнить три цикла программы, производя каждый раз обратный отсчёт, а затем, после завершения цикла, печатаем сообщение и выходим. +В программе часто требуется проверить состояние условия в круговороте. Пока условие истинно, круговорот выполняется. Когда условие перестаёт быть истинным, программа вызывает `break`, останавливая круговорот. Такое поведение можно выполнить с помощью сочетания `loop`, `if`, `else` и `break`. При желании попробуйте сделать это в программе. Это настолько распространённый образец, что в Ржавчине выполнена встроенная языковая устройство для него, называемая круговорот `while`. В приложении 3-3 мы используем `while`, чтобы выполнить три круговорота программы, производя каждый раз обратный отсчёт, а затем, после завершения круговорота, печатаем сообщение и выходим. Имя файла: src/main.rs @@ -192,13 +192,13 @@ again! {{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-03/src/main.rs}} ``` -Приложение 3-3: Использование цикла while для выполнения кода, пока условие истинно +Приложение 3-3: Использование круговорота while для выполнения рукописи, пока условие истинно -Эта устройство устраняет множество вложений, которые потребовались бы при использовании `loop`, `if`, `else` и `break`, и она более понятна. Пока условие вычисляется в `true`, код выполняется; в противном случае происходит выход из цикла. +Эта устройство устраняет множество вложений, которые потребовались бы при использовании `loop`, `if`, `else` и `break`, и она более понятна. Пока условие вычисляется в `true`, рукопись выполняется; в противном случае происходит выход из круговорота. -#### Цикл по элементам собрания с помощью `for` +#### Круговорот по элементам собрания с помощью `for` -Для перебора элементов собрания, например, массива, можно использовать устройство `while`. Например, цикл в приложении 3-4 печатает каждый элемент массива `a`. +Для перебора элементов собрания, например, массива, можно использовать устройство `while`. Например, круговорот в приложении 3-4 печатает каждый элемент массива `a`. Имя файла: src/main.rs @@ -206,19 +206,19 @@ again! {{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-04/src/main.rs}} ``` -Приложение 3-4: Перебор каждого элемента собрания с помощью цикла while +Приложение 3-4: Перебор каждого элемента собрания с помощью круговорота while -Этот код выполняет перебор элементов массива. Он начинается с порядкового указателя `0`, а затем замкнуто выполняется, пока не достигнет последнего порядкового указателя в массиве (то есть, когда `index < 5` уже не является истиной). Выполнение этого кода напечатает каждый элемент массива: +Этот рукопись выполняет перебор элементов массива. Он начинается с порядкового указателя `0`, а затем замкнуто выполняется, пока не достигнет последнего порядкового указателя в массиве (то есть, когда `index < 5` уже не является истиной). Выполнение этого рукописи напечатает каждый элемент массива: ```console {{#include ../listings/ch03-common-programming-concepts/listing-03-04/output.txt}} ``` -Все пять значений массива появляются в окне вызова, как и ожидалось. Поскольку `index` в какой-то мгновение достигнет значения `5`, цикл прекратит выполнение перед попыткой извлечь шестое значение из массива. +Все пять значений массива появляются в окне вызова, как и ожидалось. Поскольку `index` в какой-то мгновение достигнет значения `5`, круговорот прекратит выполнение перед попыткой извлечь шестое значение из массива. -Однако такой подход чреват ошибками; мы можем вызвать панику в программе, если значение порядкового указателя или условие проверки неверны. Например, если изменить определение массива `a` на четыре элемента, но забыть обновить условие на `while index < 4`, код вызовет панику. Также это медленно, поскольку сборщик добавляет код времени выполнения для обеспечения проверки нахождения порядкового указателя в границах массива на каждой повторения цикла. +Однако такой подход чреват ошибками; мы можем вызвать сбой в программе, если значение порядкового указателя или условие проверки неверны. Например, если изменить определение массива `a` на четыре элемента, но забыть обновить условие на `while index < 4`, рукопись вызовет сбой. Также это медленно, поскольку сборщик добавляет рукопись времени выполнения для обеспечения проверки нахождения порядкового указателя в границах массива на каждой повторения круговорота. -В качестве более краткой иного решения можно использовать цикл `for` и выполнять некоторый код для каждого элемента собрания. Цикл `for` может выглядеть как код в приложении 3-5. +В качестве более краткой иного решения можно использовать круговорот `for` и выполнять некоторый рукопись для каждого элемента собрания. Круговорот `for` может выглядеть как рукопись в приложении 3-5. Имя файла: src/main.rs @@ -226,15 +226,15 @@ again! {{#rustdoc_include ../listings/ch03-common-programming-concepts/listing-03-05/src/main.rs}} ``` -Приложение 3-5: Перебор каждого элемента собрания с помощью цикла for +Приложение 3-5: Перебор каждого элемента собрания с помощью круговорота for -При выполнении этого кода мы увидим тот же итог, что и в приложении 3-4. Что важнее, теперь мы повысили безопасность кода и устранили вероятность ошибок, которые могут возникнуть в итоге выхода за пределы массива или недостаточно далёкого перехода и пропуска некоторых элементов. +При выполнении этого рукописи мы увидим тот же итог, что и в приложении 3-4. Что важнее, теперь мы повысили безопасность рукописи и устранили вероятность ошибок, которые могут возникнуть в итоге выхода за пределы массива или недостаточно далёкого перехода и пропуска некоторых элементов. -При использовании цикла `for` не нужно помнить о внесении изменений в другой код, в случае изменения количества значений в массиве, как это было бы с способом, использованным в приложении 3-4. +При использовании круговорота `for` не нужно помнить о внесении изменений в другой рукопись, в случае изменения количества значений в массиве, как это было бы с способом, использованным в приложении 3-4. -Безопасность и краткость циклов `for` делают их наиболее часто используемой устройством цикла в Rust. Даже в случаейх необходимости выполнения некоторого кода определённое количество раз, как в примере обратного отсчёта, в котором использовался цикл `while` из Приложения 3-3, большинство Rustaceans использовали бы цикл `for`. Для этого можно использовать `Range`, предоставляемый встроенной библиотекой, который порождает последовательность всех чисел, начиная с первого числа и заканчивая вторым числом, но не включая его (т.е. `(1..4)` эквивалентно `[1, 2, 3]` или в общем случае `(start..end)` эквивалентно `[start, start+1, start+2, ... , end-2, end-1]` - прим.переводчика). +Безопасность и краткость круговоротов `for` делают их наиболее часто используемой устройством круговорота в Ржавчине. Даже в случаях необходимости выполнения некоторого рукописи определённое количество раз, как в примере обратного отсчёта, в котором использовался круговорот `while` из Приложения 3-3, большинство Rustaceans использовали бы круговорот `for`. Для этого можно использовать `Range`, предоставляемый встроенной библиотекой, который порождает последовательность всех чисел, начиная с первого числа и заканчивая вторым числом, но не включая его (т.е. `(1..4)` эквивалентно `[1, 2, 3]` или в общем случае `(start..end)` эквивалентно `[start, start+1, start+2, ... , end-2, end-1]` - прим.переводчика). -Вот как будет выглядеть обратный отсчёт с использованием цикла `for` и другого способа, о котором мы ещё не говорили, `rev`, для разворота ряда: +Вот как будет выглядеть обратный отсчёт с использованием круговорота `for` и другого способа, о котором мы ещё не говорили, `rev`, для разворота ряда: Имя файла: src/main.rs @@ -242,14 +242,14 @@ again! {{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-34-for-range/src/main.rs}} ``` -Данный код выглядит лучше, не так ли? +Данный рукопись выглядит лучше, не так ли? ## Итоги -Вы справились! Это была объёмная глава: вы узнали о переменных, одиночных и составных видах данных, функциях, примечаниях, выражениях `if` и циклах! Для опытов работы с подходами, обсуждаемыми в этой главе, попробуйте создать программы для выполнения следующих действий: +Вы справились! Это была объёмная глава: вы узнали о переменных, одиночных и составных видах данных, функциях, примечаниях, выражениях `if` и круговоротах! Для опытов работы с подходами, обсуждаемыми в этой главе, попробуйте создать программы для выполнения следующих действий: - Преобразование температур между значениями по Фаренгейту к Цельсию. - Порождение n-го числа Фибоначчи. - Распечатайте текст рождественской песни "Двенадцать дней Рождества", воспользовавшись повторами в песне. -Когда вы будете готовы двигаться дальше, мы поговорим о подходы в Rust, которая *не существует* обычно в других языках программирования: владение. +Когда вы будете готовы двигаться дальше, мы поговорим о подходы в Ржавчине, которая *не существует* обычно в других языках программирования: владение. diff --git a/rustbook-ru/src/ch04-00-understanding-ownership.md b/rustbook-ru/src/ch04-00-understanding-ownership.md index 274fc0719..bd1a043ac 100644 --- a/rustbook-ru/src/ch04-00-understanding-ownership.md +++ b/rustbook-ru/src/ch04-00-understanding-ownership.md @@ -1,3 +1,3 @@ # Понимание Владения -Владение - это самая не имеет себе подобных особенность Rust, которая имеет глубокие последствия для всего языка. Это позволяет Ржавчина обеспечивать безопасность памяти без использования сборщика мусора, поэтому важно понимать, как работает владение. В этой главе мы поговорим о владении, а также о нескольких связанных с ним возможностях: заимствовании, срезах и о том, как Ржавчина раскладывает данные в памяти. +Владение - это самая не имеет себе подобных особенность Ржавчина, которая имеет глубокие последствия для всего языка. Это позволяет Ржавчина обеспечивать безопасность памяти без использования сборщика мусора, поэтому важно понимать, как работает владение. В этой главе мы поговорим о владении, а также о нескольких связанных с ним возможностях: заимствовании, срезах и о том, как Ржавчина раскладывает данные в памяти. diff --git a/rustbook-ru/src/ch04-01-what-is-ownership.md b/rustbook-ru/src/ch04-01-what-is-ownership.md index aacdee92e..8582cfaaa 100644 --- a/rustbook-ru/src/ch04-01-what-is-ownership.md +++ b/rustbook-ru/src/ch04-01-what-is-ownership.md @@ -1,16 +1,16 @@ ## Что такое владение? -*Владение* — это набор правил, определяющих, как программа на языке Ржавчина управляет памятью. Все программы так или иначе должны управлять тем, как они используют память компьютера во время работы. Некоторые языки имеют сборщик мусора, постоянно отслеживающий неиспользуемую память во время работы программы; в других языках программист должен явно выделять и освобождать память. В Ржавчина используется третий подход: память управляется через систему владения с набором правил, которые проверяются сборщиком. При нарушении любого из правил программа не будет собрана. Ни одна из особенностей системы владения не замедлит работу вашей программы. +*Владение* — это набор правил, определяющих, как программа на языке Ржавчина управляет памятью. Все программы так или иначе должны управлять тем, как они используют память компьютера во время работы. Некоторые языки имеют сборщик мусора, постоянно отслеживающий неиспользуемую память во время работы программы; в других языках программист должен явно выделять и освобождать память. В Ржавчине используется третий подход: память управляется через систему владения с набором правил, которые проверяются сборщиком. При нарушении любого из правил программа не будет собрана. Ни одна из особенностей системы владения не замедлит работу вашей программы. -Поскольку владение является новой подходом для многих программистов, требуется некоторое время, чтобы привыкнуть к ней. Хорошая новость заключается в том, что чем больше у вас будет опыта с Ржавчина и с правилами системы владения, тем легче вам будет естественным образом разрабатывать безопасный и эффективный код. Держитесь! Не сдавайтесь! +Поскольку владение является новой подходом для многих программистов, требуется некоторое время, чтобы привыкнуть к ней. Хорошая новость заключается в том, что чем больше у вас будет опыта с Ржавчина и с правилами системы владения, тем легче вам будет естественным образом разрабатывать безопасный и производительный рукопись. Держитесь! Не сдавайтесь! Понимание подходы владения даст вам основу для понимания всех остальных особенностей, делающих Ржавчина единственным. В этой главе вы изучите владение на примерах, которые сосредоточены на наиболее часто используемой устройстве данных: строках. > ### Обойма и куча > -> Многие языки программирования не требуют, чтобы вы слишком часто думали о обойме и куче. Но в языках системного программирования, одним из которых является Rust, то, какое значение находится в обойме или в куче, влияет на поведение языка и на принятие вами определённых решений. Владение будет описано через призму обоймы и кучи позже в этой главе, а пока — краткое пояснение. +> Многие языки программирования не требуют, чтобы вы слишком часто думали о обойме и куче. Но в языках системного программирования, одним из которых является Ржавчина, то, какое значение находится в обойме или в куче, влияет на поведение языка и на принятие вами определённых решений. Владение будет описано через призму обоймы и кучи позже в этой главе, а пока — краткое пояснение. > -> И обойма, и куча — это части памяти, доступные вашему коду для использования во время выполнения. Однако они внутренне выстроенны +> И обойма, и куча — это части памяти, доступные вашему рукописи для использования во время выполнения. Однако они внутренне выстроенны по-разному. Обойма хранит значения в порядке их получения, а удаляет — в обратном. Это называется *«последним пришёл — первым ушёл»*. Подумайте о стопке тарелок: когда вы добавляете тарелки, вы кладёте их сверху стопки — когда вам нужна тарелка, вы берёте одну так же сверху. Добавление или удаление тарелок посередине или снизу не сработает! Добавление данных называется *помещением в обойма*, а удаление — извлечением *из обоймы*. Все данные, хранящиеся в обойме, должны иметь известный определенный размер. Данные, размер которых во время сборки неизвестен или может измениться, должны храниться в куче. > @@ -18,23 +18,23 @@ > > Помещение в обойма происходит более быстро, чем выделение памяти в куче, потому что операционная система не должна искать место для размещения сведений — это место всегда на верхушке обоймы. Для сравнения, выделение памяти в куче требует больше работы, потому что операционная система сначала должна найти участок памяти достаточного размера, а затем произвести некоторые действия для подготовки к следующему выделению памяти. > -> Доступ к данным в куче медленнее, чем доступ к данным в обойме, потому что вам нужно следовать по адресу указателя, чтобы добраться туда. Современные процессоры работают быстрее, если они меньше прыгают по памяти. Продолжая подобие, рассмотрим официанта в ресторане, принимающего заказы со многих столов. Наиболее эффективно будет получить все заказы за одним столом, прежде чем переходить к следующему столу. Получение заказа со стола А, затем со стола В, затем снова одного с А, а затем снова одного с В было бы гораздо более медленным делом. Точно так же процессор может выполнять свою работу лучше, если он работает с данными, которые находятся близко к другим данным (как в обойме), а не далеко (как это может быть в куче). +> Доступ к данным в куче медленнее, чем доступ к данным в обойме, потому что вам нужно следовать по адресу указателя, чтобы добраться туда. Современные процессоры работают быстрее, если они меньше прыгают по памяти. Продолжая подобие, рассмотрим официанта в ресторане, принимающего заказы со многих столов. Наиболее правильно будет получить все заказы за одним столом, прежде чем переходить к следующему столу. Получение заказа со стола А, затем со стола В, затем снова одного с А, а затем снова одного с В было бы гораздо более медленным делом. Точно так же процессор может выполнять свою работу лучше, если он работает с данными, которые находятся близко к другим данным (как в обойме), а не далеко (как это может быть в куче). > -> Когда ваш код вызывает функцию, значения, переданные в неё (возможно включающие указатели на данные в куче), и местные переменные помещаются в обойма. Когда функция завершается, эти значения извлекаются из обоймы. +> Когда ваша рукопись вызывает функцию, значения, переданные в неё (возможно включающие указатели на данные в куче), и местные переменные помещаются в обойма. Когда функция завершается, эти значения извлекаются из обоймы. > -> Отслеживание того, какие части кода используют какие данные, уменьшение количества повторяющихся данных и очистка неиспользуемых данных в куче, чтобы не исчерпать пространство, — все эти сбоев решает владение. Как только вы поймёте, что такое владение, вам не нужно будет слишком часто думать о обойме и куче. Однако знание того, что основная цель владения — управление данными кучи, может помочь объяснить, почему оно работает именно так. +> Отслеживание того, какие части рукописи используют какие данные, уменьшение количества повторяющихся данных и очистка неиспользуемых данных в куче, чтобы не исчерпать пространство, — все эти сбоев решает владение. Как только вы поймёте, что такое владение, вам не нужно будет слишком часто думать о обойме и куче. Однако знание того, что основная цель владения — управление данными кучи, может помочь объяснить, почему оно работает именно так. ### Правила владения Во-первых, давайте взглянем на правила владения. Помните об этих правилах, пока мы работаем с примерами, которые их отображают: -- У каждого значения в Ржавчина есть *владелец*, +- У каждого значения в Ржавчине есть *владелец*, - У значения может быть только один владелец в один мгновение времени, - Когда владелец покидает область видимости, значение удаляется. ### Область видимости переменной -Теперь, когда мы прошли основной правила написания Rust, мы не будем включать весь код `fn main() {` в примеры. Поэтому, если вы будете следовать этому курсу, убедитесь, что следующие примеры помещены в функцию `main` вручную. В итоге наши примеры будут более краткими, что позволит нам сосредоточиться на существующих подробностях, а не на образцовом коде. +Теперь, когда мы прошли основной правила написания Ржавчина, мы не будем включать весь рукопись `fn main() {` в примеры. Поэтому, если вы будете следовать этому курсу, убедитесь, что следующие примеры помещены в функцию `main` вручную. В итоге наши примеры будут более краткими, что позволит нам сосредоточиться на существующих подробностях, а не на образцовом рукописи. В качестве первого примера владения мы рассмотрим *область видимости* некоторых переменных. Область видимости — это рядвнутри программы, для которого допустим элемент. Возьмём следующую переменную: @@ -59,17 +59,17 @@ let s = "hello"; ### Вид данных `String` -Для отображения правил владения нам требуется более сложный вид данных чем те, что мы обсуждали в части ["Виды данных"] Главы 3. Виды, рассмотренные ранее, имеют определённый размер, а значит могут быть размещены на обойме и извлечены из него, когда их область видимости закончится, и могут быть быстро и обыкновенно воспроизведены для создания новой, независимой повторы, если другой части кода нужно использовать то же самое значение в другой области видимости. Но мы хотим посмотреть на данные, хранящиеся в куче, и выяснить, как Ржавчина узнаёт, когда нужно очистить эти данные, поэтому вид String — отличный пример. +Для отображения правил владения нам требуется более сложный вид данных чем те, что мы обсуждали в части ["Виды данных"] Главы 3. Виды, рассмотренные ранее, имеют определённый размер, а значит могут быть размещены на обойме и извлечены из него, когда их область видимости закончится, и могут быть быстро и обыкновенно воспроизведены для создания новой, независимой повторы, если другой части рукописи нужно использовать то же самое значение в другой области видимости. Но мы хотим посмотреть на данные, хранящиеся в куче, и выяснить, как Ржавчина узнаёт, когда нужно очистить эти данные, поэтому вид String — отличный пример. Мы сосредоточимся на тех частях `String`, которые связаны с владением. Эти особенности также применимы к другим сложным видам данных, независимо от того, предоставлены они встроенной библиотекой или созданы вами. Более подробно мы обсудим `String` в [главе 8]. -Мы уже видели строковые записи, где строковое значение жёстко прописано в нашей программе. Строковые записи удобны, но они подходят не для каждой случаи, где мы можем хотеть использовать текст. Одна из причин заключается в том, что они неизменны. Кроме того, не каждое строковое значение может быть известно во время написания кода: что, если мы захотим принять и сохранить пользовательский ввод? Для таких случаев в Ржавчина есть ещё один строковый вид — `String`. Этот вид управляет данными, выделенными в куче, и поэтому может хранить объём текста, который во время сборки неизвестен. Также вы можете создать `String` из строкового записи, используя функцию `from`, например: +Мы уже видели строковые записи, где строковое значение жёстко прописано в нашей программе. Строковые записи удобны, но они подходят не для каждой случаи, где мы можем хотеть использовать текст. Одна из причин заключается в том, что они неизменны. Кроме того, не каждое строковое значение может быть известно во время написания рукописи: что, если мы захотим принять и сохранить пользовательский ввод? Для таких случаев в Ржавчине есть ещё один строковый вид — `String`. Этот вид управляет данными, выделенными в куче, и поэтому может хранить объём текста, который во время сборки неизвестен. Также вы можете создать `String` из строкового записи, используя функцию `from`, например: ```rust let s = String::from("hello"); ``` -Оператор "Двойное двоеточие" `::` позволяет использовать пространство имён данной именно функции `from` с видом `String`, а не какое-то иное имя, такое как `string_from`. Мы обсудим этот правила написания более подробно в разделе [«Синтаксис способа»]. раздел Главы 5, и в ходе обсуждения пространств имён с звенами в [ «Пути для обращения к элементу в дереве звеньев»] в главе 7. +Приказчик "Двойное двоеточие" `::` позволяет использовать пространство имён данной именно функции `from` с видом `String`, а не какое-то иное имя, такое как `string_from`. Мы обсудим этот правила написания более подробно в разделе [«Правила написания способа»]. раздел Главы 5, и в ходе обсуждения пространств имён с звенами в [ «Пути для обращения к элементу в дереве звеньев»] в главе 7. Строка такого вида *может* быть изменяема: @@ -81,7 +81,7 @@ let s = String::from("hello"); ### Память и способы её выделения -В случае строкового записи мы знаем его содержимое во время сборки, и оно жёстко прописано в итоговом исполняемом файле. Причина того, что строковые записи более быстрые и эффективные, в их неизменяемости. К сожалению, нельзя поместить неопределённый кусок памяти в выполняемый файл для текста, размер которого неизвестен при сборки и может меняться во время выполнения программы. +В случае строкового записи мы знаем его содержимое во время сборки, и оно жёстко прописано в итоговом исполняемом файле. Причина того, что строковые записи более быстрые и производительные, в их неизменяемости. К сожалению, нельзя поместить неопределённый кусок памяти в выполняемый файл для текста, размер которого неизвестен при сборки и может меняться во время выполнения программы. Чтобы поддерживать изменяемый, увеличивающийся текст вида `String`, необходимо выделять память в куче для всего содержимого, размер которого неизвестен во время сборки. Это означает, что: @@ -90,19 +90,19 @@ let s = String::from("hello"); Первая часть выполняется нами: когда мы вызываем `String::from`, его выполнение запрашивает необходимую память. Это работает довольно похоже во всех языках программирования. -Однако вторая часть отличается. В языках со *сборщиком мусора (GC)*, память, которая больше не используется, отслеживается и очищается с его помощью — нам не нужно об этом думать. В большинстве языков без сборщика мусора мы обязаны сами определять, когда память больше не используется, и вызывать код для явного её освобождения, точно так же, как мы делали это для её запроса. Правильное выполнение этого этапа исторически было сложной неполадкой программирования. Если мы забудем освободить память, она будет потеряна. Если мы сделаем это слишком рано, у нас будет недопустимая переменная. Сделать это дважды — тоже будет ошибкой. Нам нужно соединить ровно один `allocate` ровно с одним `free`. +Однако вторая часть отличается. В языках со *сборщиком мусора (GC)*, память, которая больше не используется, отслеживается и очищается с его помощью — нам не нужно об этом думать. В большинстве языков без сборщика мусора мы обязаны сами определять, когда память больше не используется, и вызывать рукопись для явного её освобождения, точно так же, как мы делали это для её запроса. Правильное выполнение этого этапа исторически было сложной неполадкой программирования. Если мы забудем освободить память, она будет потеряна. Если мы сделаем это слишком рано, у нас будет недопустимая переменная. Сделать это дважды — тоже будет ошибкой. Нам нужно соединить ровно один `allocate` ровно с одним `free`. -Rust выбирает другой путь: память самостоятельно возвращается, как только владеющая памятью переменная выходит из области видимости. Вот исполнение примера с областью видимости из приложения 4-1, в котором используется вид `String` вместо строкового записи: +Ржавчина выбирает другой путь: память самостоятельно возвращается, как только владеющая памятью переменная выходит из области видимости. Вот исполнение примера с областью видимости из приложения 4-1, в котором используется вид `String` вместо строкового записи: ```rust {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-02-string-scope/src/main.rs:here}} ``` -Существует естественный мгновение, когда мы можем вернуть память, необходимую нашему `String`, обратно распределителю — когда `s` выходит за пределы области видимости. Когда переменная выходит за пределы области видимости, Ржавчина вызывает для нас особую функцию. Эта функция называется [`drop`], и именно здесь автор `String` может поместить код для возврата памяти. Ржавчина самостоятельно вызывает `drop` после закрывающей фигурной скобки. +Существует естественный мгновение, когда мы можем вернуть память, необходимую нашему `String`, обратно распределителю — когда `s` выходит за пределы области видимости. Когда переменная выходит за пределы области видимости, Ржавчина вызывает для нас особую функцию. Эта функция называется [`drop`], и именно здесь составитель`String` может поместить рукопись для возврата памяти. Ржавчина самостоятельно вызывает `drop` после закрывающей фигурной скобки. -> Примечание: в C++ этот образец освобождения ресурсов в конце времени жизни элемента иногда называется *«Получение ресурса есть объявление» (англ. Resource Acquisition Is Initialization (RAII))*. Функция `drop` в Ржавчина покажется вам знакомой, если вы использовали образцы RAII. +> Примечание: в C++ этот образец освобождения мощностей в конце времени жизни элемента иногда называется *«Получение ресурса есть объявление» (англ. Resource Acquisition Is Initialization (RAII))*. Функция `drop` в Ржавчине покажется вам знакомой, если вы использовали образцы RAII. -Этот образец оказывает глубокое влияние на способ написания кода в Rust. Сейчас это может казаться простым, но в более сложных случаейх поведение кода может быть неожиданным, например когда хочется иметь несколько переменных, использующих данные, выделенные в куче. Изучим несколько таких случаев. +Этот образец оказывает глубокое влияние на способ написания рукописи в Ржавчине. Сейчас это может казаться простым, но в более сложных случаях поведение рукописи может быть неожиданным, например когда хочется иметь несколько переменных, использующих данные, выделенные в куче. Изучим несколько таких случаев. @@ -110,7 +110,7 @@ Rust выбирает другой путь: память самостоятел #### Взаимодействие переменных и данных с помощью перемещения -Несколько переменных могут по-разному взаимодействовать с одними и теми же данными в Rust. Давайте рассмотрим пример использования целого числа в приложении 4-2. +Несколько переменных могут по-разному взаимодействовать с одними и теми же данными в Ржавчине. Давайте рассмотрим пример использования целого числа в приложении 4-2. ```rust {{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-02/src/main.rs:here}} @@ -188,30 +188,30 @@ Rust выбирает другой путь: память самостоятел Это отлично работает и очевидно приводит к поведению, представленному на рисунке 4-3, где данные кучи *были* воспроизведены. -Когда вы видите вызов `clone`, вы знаете о выполнении некоторого кода, который может быть дорогим. В то же время использование clone является визуальным индикатором того, что тут происходит что-то необычное. +Когда вы видите вызов `clone`, вы знаете о выполнении некоторого рукописи, который может быть дорогим. В то же время использование clone является визуальным показателем того, что тут происходит что-то необычное. #### Из обоймы данные: повторение -Это ещё одна особенность о которой мы ранее не говорили. Этот код, часть которого была показа ранее в приложении 4-2, использует целые числа. Он работает без ошибок: +Это ещё одна особенность о которой мы ранее не говорили. Этот рукопись, часть которого была показа ранее в приложении 4-2, использует целые числа. Он работает без ошибок: ```rust {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-06-copy/src/main.rs:here}} ``` -Но этот код, кажется, противоречит тому, что мы только что узнали: у нас нет вызова `clone`, но `x` всё ещё действителен и не был перемещён в `y`. +Но этот рукопись, кажется, противоречит тому, что мы только что узнали: у нас нет вызова `clone`, но `x` всё ещё действителен и не был перемещён в `y`. Причина в том, что такие виды, как целые числа, размер которых известен во время сборки, полностью хранятся в обойме, поэтому повторы действительных значений создаются быстро. Это означает, что нет причин, по которым мы хотели бы предотвратить доступность `x` после того, как создадим переменную `y`. Другими словами, здесь нет разницы между глубоким и поверхностным повторением, поэтому вызов `clone` ничем не отличается от обычного поверхностного повторения, и мы можем его опустить. -В Ржавчина есть особая изложение, называемая особенностью `Copy`, которую мы можем размещать на видах, хранящихся в обойме, как и целые числа (подробнее о видах мы поговорим в [главе 10]). Если вид выполняет особенность `Copy`, переменные, которые его используют, не перемещаются, а обыкновенно повторяются, что делает их действительными после присвоения другой переменной. +В Ржавчине есть особая изложение, называемая особенностью `Copy`, которую мы можем размещать на видах, хранящихся в обойме, как и целые числа (подробнее о видах мы поговорим в [главе 10]). Если вид выполняет особенность `Copy`, переменные, которые его используют, не перемещаются, а обыкновенно повторяются, что делает их действительными после присвоения другой переменной. -Rust не позволит нам определять вид с помощью `Copy`, если вид или любая из его частей выполняет `Drop`. Если для вида нужно, чтобы произошло что-то особенное, когда значение выходит за пределы области видимости, и мы добавляем изложение `Copy` к этому виду, мы получим ошибку времени сборки. Чтобы узнать, как добавить изложение `Copy` к вашему виду для выполнения особенности, смотрите [раздел «Производные особенности»] в приложении С. +Ржавчина не позволит нам определять вид с помощью `Copy`, если вид или любая из его частей выполняет `Drop`. Если для вида нужно, чтобы произошло что-то особенное, когда значение выходит за пределы области видимости, и мы добавляем изложение `Copy` к этому виду, мы получим ошибку времени сборки. Чтобы узнать, как добавить изложение `Copy` к вашему виду для выполнения особенности, смотрите [раздел «Производные особенности»] в приложении С. -Но какие же виды выполняют особенность `Copy`? Можно проверить документацию любого вида для уверенности, но как правило любая объединение простых одиночных значений может быть выполнить `Copy`, и никакие виды, которые требуют выделения памяти в куче или являются некоторой способом ресурсов, не выполняют особенности `Copy`. Вот некоторые виды, которые выполняют `Copy`: +Но какие же виды выполняют особенность `Copy`? Можно проверить пособие любого вида для уверенности, но как правило любая объединение простых одиночных значений может быть выполнить `Copy`, и никакие виды, которые требуют выделения памяти в куче или являются некоторой способом мощностей, не выполняют особенности `Copy`. Вот некоторые виды, которые выполняют `Copy`: - Все целочисленные виды, такие как `u32`, -- Логический вид данных `bool`, возможные значения которого `true` и `false`, +- Разумный вид данных `bool`, возможные значения которого `true` и `false`, - Все виды с плавающей запятой, такие как `f64`. -- Символьный вид `char`, +- Знаковый вид `char`, - Упорядоченные ряды, но только если они содержат виды, которые также выполняют `Copy`. Например, `(i32, i32)` будет с `Copy`, но упорядоченный ряд `(i32, String)` уже нет. ### Владение и функции @@ -226,7 +226,7 @@ Rust не позволит нам определять вид с помощью Приложение 4-3. Функции с определенными владельцами и областью действия -Если попытаться использовать `s` после вызова `takes_ownership`, Ржавчина выдаст ошибку времени сборки. Такие постоянные проверки защищают от ошибок. Попробуйте добавить код в `main`, который использует переменную `s` и `x`, чтобы увидеть где их можно использовать и где правила владения предотвращают их использование. +Если попытаться использовать `s` после вызова `takes_ownership`, Ржавчина выдаст ошибку времени сборки. Такие постоянные проверки защищают от ошибок. Попробуйте добавить рукопись в `main`, который использует переменную `s` и `x`, чтобы увидеть где их можно использовать и где правила владения предотвращают их использование. ### Возвращение значений и область видимости @@ -244,7 +244,7 @@ Rust не позволит нам определять вид с помощью Хотя это работает, получение права владения, а затем возвращение владения каждой функцией немного утомительно. Что, если мы хотим, чтобы функция использовала значение, но не становилась владельцем? Очень раздражает, что всё, что мы передаём, также должно быть передано обратно, если мы хотим использовать это снова, в дополнение к любым данным, полученным из тела функции, которые мы также можем захотеть вернуть. -Rust позволяет нам возвращать несколько значений с помощью упорядоченного ряда, как показано в приложении 4-5. +Ржавчина позволяет нам возвращать несколько значений с помощью упорядоченного ряда, как показано в приложении 4-5. Файл: src/main.rs @@ -254,13 +254,13 @@ Rust позволяет нам возвращать несколько знач Приложение 4-5: возврат права владения на свойства -Но это слишком высокопарно и многословно для подходы, которая должна быть общей. К счастью для нас, в Ржавчина есть возможность использовать значение без передачи права владения, называемая *ссылками*. +Но это слишком высокопарно и многословно для подходы, которая должна быть общей. К счастью для нас, в Ржавчине есть возможность использовать значение без передачи права владения, называемая *ссылками*. ["Виды данных"]: ch03-02-data-types.html#data-types [главе 8]: ch08-02-strings.html [главе 10]: ch10-02-traits.html [раздел «Производные особенности»]: appendix-03-derivable-traits.html -[«Синтаксис способа»]: ch05-03-method-syntax.html#method-syntax +[«Правила написания способа»]: ch05-03-method-syntax.html#method-syntax [ «Пути для обращения к элементу в дереве звеньев»]: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html [`drop`]: ../std/ops/trait.Drop.html#tymethod.drop \ No newline at end of file diff --git a/rustbook-ru/src/ch04-02-references-and-borrowing.md b/rustbook-ru/src/ch04-02-references-and-borrowing.md index 83bb442d4..614011331 100644 --- a/rustbook-ru/src/ch04-02-references-and-borrowing.md +++ b/rustbook-ru/src/ch04-02-references-and-borrowing.md @@ -1,6 +1,6 @@ ## Ссылки и заимствование -Неполадкас кодом упорядоченного ряда в приложении 4-5 заключается в том, что мы должны вернуть `String` из вызванной функции, чтобы использовать `String` после вызова `calculate_length`, потому что `String` была перемещена в `calculate_length`. Вместо этого мы можем предоставить ссылку на значение `String`. *Ссылка* похожа на указатель в том смысле, что это адрес, по которому мы можем проследовать, чтобы получить доступ к данным, хранящимся по этому адресу; эти данные принадлежат какой-то другой переменной. В отличие от указателя, ссылка обязательно указывает на допустимое значение определённого вида в течение всего срока существования этой ссылки. +Неполадкас рукописью упорядоченного ряда в приложении 4-5 заключается в том, что мы должны вернуть `String` из вызванной функции, чтобы использовать `String` после вызова `calculate_length`, потому что `String` была перемещена в `calculate_length`. Вместо этого мы можем предоставить ссылку на значение `String`. *Ссылка* похожа на указатель в том смысле, что это адрес, по которому мы можем проследовать, чтобы получить доступ к данным, хранящимся по этому адресу; эти данные принадлежат какой-то другой переменной. В отличие от указателя, ссылка обязательно указывает на допустимое значение определённого вида в течение всего срока существования этой ссылки. Вот как вы могли бы определить и использовать функцию `calculate_length`, имеющую ссылку на предмет в качестве свойства, вместо того, чтобы брать на себя ответственность за значение: @@ -10,13 +10,13 @@ {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-07-reference/src/main.rs:all}} ``` -Во-первых, обратите внимание, что весь код упорядоченного ряда в объявлении переменной и возвращаемое значение функции исчезли. Во-вторых, обратите внимание, что мы передаём `&s1` в `calculate_length` и в его определении используем `&String`, а не `String`. Эти знаки представляют собой *ссылки*, и они позволяют вам ссылаться на некоторое значение, не принимая владение над ним. Рисунок 4-5 изображает эту подход. +Во-первых, обратите внимание, что весь рукопись упорядоченного ряда в объявлении переменной и возвращаемое значение функции исчезли. Во-вторых, обратите внимание, что мы передаём `&s1` в `calculate_length` и в его определении используем `&String`, а не `String`. Эти знаки представляют собой *ссылки*, и они позволяют вам ссылаться на некоторое значение, не принимая владение над ним. Рисунок 4-5 изображает эту подход. &String s pointing at String s1 Рисунок 4-5: диаграмма для &String s, указывающей на String s1 -> Примечание: противоположностью ссылки с использованием `&` является *разыменование*, выполняемое с помощью оператора разыменования `*`. Мы увидим некоторые исходы использования оператора разыменования в главе 8 и обсудим подробности разыменования в главе 15. +> Примечание: противоположностью ссылки с использованием `&` является *разыменование*, выполняемое с помощью приказчика разыменования `*`. Мы увидим некоторые исходы использования приказчика разыменования в главе 8 и обсудим подробности разыменования в главе 15. Давайте подробнее рассмотрим рычаг вызова функции: @@ -26,7 +26,7 @@ `&s1` позволяет нам создать ссылку, которая *ссылается* на значение `s1`, но не владеет им. Поскольку она не владеет им, значение, на которое она указывает, не будет удалено, когда ссылка перестанет использоваться. -Ярлык функции использует `&` для индикации того, что вид свойства `s` является ссылкой. Добавим объясняющие примечания: +Ярлык функции использует `&` для отображения того, что вид свойства `s` является ссылкой. Добавим объясняющие примечания: ```rust {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-08-reference-with-annotations/src/main.rs:here}} @@ -36,7 +36,7 @@ Мы называем этап создания ссылки *заимствованием*. Как и в существующей жизни, если человек чем-то владеет, вы можете это у него позаимствовать. Когда вы закончите, вы должны вернуть это законному владельцу. -А что произойдёт, если попытаться изменить то, что было позаимствовано? Попробуйте код приложения 4-6 Спойлер: этот код не сработает! +А что произойдёт, если попытаться изменить то, что было позаимствовано? Попробуйте рукопись приложения 4-6 Спойлер: этот рукопись не сработает! Файл: src/main.rs @@ -56,7 +56,7 @@ ### Изменяемые ссылки -Мы можем исправить код из приложения 4-6, чтобы позволить себе изменять заимствованное значение, с помощью нескольких небольших настроек, которые используют *изменяемую ссылку*: +Мы можем исправить рукопись из приложения 4-6, чтобы позволить себе изменять заимствованное значение, с помощью нескольких небольших настроек, которые используют *изменяемую ссылку*: Файл: src/main.rs @@ -66,7 +66,7 @@ Сначала мы меняем `s` на `mut`. Затем мы создаём изменяемую ссылку с помощью `&mut s`, у которой вызываем `change` и обновляем ярлык функции, чтобы принять изменяемую ссылку с помощью `some_string: &mut String`. Это даёт понять, что `change` изменит значение, которое заимствует. -Изменяемые ссылки имеют одно большое ограничение: если у вас есть изменяемая ссылка на значение, у вас не может быть других ссылок на это же значение. Код, который пытается создать две изменяемые ссылки на `s`, завершится ошибкой: +Изменяемые ссылки имеют одно большое ограничение: если у вас есть изменяемая ссылка на значение, у вас не может быть других ссылок на это же значение. Рукопись, который пытается создать две изменяемые ссылки на `s`, завершится ошибкой: Файл: src/main.rs @@ -80,15 +80,15 @@ {{#include ../listings/ch04-understanding-ownership/no-listing-10-multiple-mut-not-allowed/output.txt}} ``` -Эта ошибка говорит о том, что код недействителен, потому что мы не можем заимствовать `s` как изменяемые более одного раза в один мгновение. Первое изменяемое заимствование находится в `r1` и должно длиться до тех пор, пока оно не будет использовано в `println!`, но между созданием этой изменяемой ссылки и её использованием мы попытались создать другую изменяемую ссылку в `r2`, которая заимствует те же данные, что и `r1`. +Эта ошибка говорит о том, что рукопись недействителен, потому что мы не можем заимствовать `s` как изменяемые более одного раза в один мгновение. Первое изменяемое заимствование находится в `r1` и должно длиться до тех пор, пока оно не будет использовано в `println!`, но между созданием этой изменяемой ссылки и её использованием мы попытались создать другую изменяемую ссылку в `r2`, которая заимствует те же данные, что и `r1`. -Ограничение, предотвращающее одновременное использование нескольких изменяемых ссылок на одни и те же данные, допускает изменение, но очень управляющим образом. Это то, с чем борются новые Rustaceans, потому что большинство языков позволяют изменять значение в любой мгновение. Преимущество этого ограничения заключается в том, что Ржавчина может предотвратить гонку данных во время сборки. *Гонка данных* похожа на состояние гонки и происходит, когда возникают следующие три сценария: +Ограничение, предотвращающее одновременное использование нескольких изменяемых ссылок на одни и те же данные, допускает изменение, но очень управляющим образом. Это то, с чем борются новые Rustaceans, потому что большинство языков позволяют изменять значение в любой мгновение. Преимущество этого ограничения заключается в том, что Ржавчина может предотвратить гонку данных во время сборки. *Гонка данных* похожа на состояние гонки и происходит, когда возникают следующие три задумки: - Два или больше указателей используют одни и те же данные в одно и то же время, - Самое наименьшее один указатель используется для записи данных, - Отсутствуют рычаги для согласования доступа к данным. -Гонки данных вызывают неопределённое поведение, и их может быть сложно диагностировать и исправить, когда вы пытаетесь отследить их во время выполнения. Ржавчина предотвращает такую неполадку, отказываясь собирать код с гонками данных! +Гонки данных вызывают неопределённое поведение, и их может быть сложно диагностировать и исправить, когда вы пытаетесь отследить их во время выполнения. Ржавчина предотвращает такую неполадку, отказываясь собирать рукопись с гонками данных! Как всегда, мы можем использовать фигурные скобки для создания новой области видимости, позволяющей использовать несколько изменяемых ссылок, но не *одновременно*: @@ -96,7 +96,7 @@ {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-11-muts-in-separate-scopes/src/main.rs:here}} ``` -Rust применяет подобное правило для соединения изменяемых и неизменяемых ссылок. Этот код приводит к ошибке: +Ржавчина применяет подобное правило для соединения изменяемых и неизменяемых ссылок. Этот рукопись приводит к ошибке: ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-12-immutable-and-mutable-not-allowed/src/main.rs:here}} @@ -112,19 +112,19 @@ Rust применяет подобное правило для соединен Пользователи неизменяемой ссылки не ожидают, что значение внезапно изменится из-под них! Однако разрешены множественные неизменяемые ссылки, потому что никто, кто просто читает данные, не может повлиять на чтение данных кем-либо ещё. -Обратите внимание, что область действия ссылки начинается с того места, где она была введена, и продолжается до последнего использования этой ссылки. Например, этот код будет собираться, потому что последнее использование неизменяемых ссылок `println!`, происходит до того, как вводится изменяемая ссылка: +Обратите внимание, что область действия ссылки начинается с того места, где она была введена, и продолжается до последнего использования этой ссылки. Например, этот рукопись будет собираться, потому что последнее использование неизменяемых ссылок `println!`, происходит до того, как вводится изменяемая ссылка: ```rust,edition2021 {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-13-reference-scope-ends/src/main.rs:here}} ``` -Области неизменяемых ссылок `r1` и `r2` заканчиваются после `println!` где они использовались в последний раз, то есть до создания изменяемой ссылки `r3`. Эти области не перекрываются, поэтому этот код разрешён: сборщик может сказать, что ссылка больше не используется в точке перед концом области. +Области неизменяемых ссылок `r1` и `r2` заканчиваются после `println!` где они использовались в последний раз, то есть до создания изменяемой ссылки `r3`. Эти области не перекрываются, поэтому этот рукопись разрешён: сборщик может сказать, что ссылка больше не используется в точке перед концом области. Несмотря на то, что ошибки заимствования могут иногда вызывать разочарование, помните, что сборщик Ржавчина заранее указывает на вероятную ошибку (во время сборки, а не во время выполнения) и точно показывает, в чем неполадка. Тогда вам не придётся выяснять, почему ваши данные оказались не такими, как вы ожидали. ### Висячие ссылки -В языках с указателями весьма легко ошибочно создать недействительную (висячую) *(dangling)* ссылку. Ссылку указывающую на участок памяти, который мог быть передан кому-то другому, путём освобождения некоторой памяти при сохранении указателя на эту память. Ржавчина сборщик заверяет, что ссылки никогда не станут недействительными: если у вас есть ссылка на какие-то данные, сборщик обеспечит что эти данные не выйдут из области видимости прежде, чем из области видимости исчезнет ссылка. +В языках с указателями весьма легко ошибочно создать недействительную (висячую) *(dangling)* ссылку. Ссылку указывающую на участок памяти, который мог быть передан кому-то другому, путём освобождения некоторой памяти при сохранении указателя на эту память. В Ржавчине сборщик заверяет, что ссылки никогда не станут недействительными: если у вас есть ссылка на какие-то данные, сборщик обеспечит что эти данные не выйдут из области видимости прежде, чем из области видимости исчезнет ссылка. Давайте попробуем создать висячую ссылку, чтобы увидеть, как Ржавчина предотвращает их появление с помощью ошибки во время сборки: @@ -140,14 +140,14 @@ Rust применяет подобное правило для соединен {{#include ../listings/ch04-understanding-ownership/no-listing-14-dangling-reference/output.txt}} ``` -Это сообщение об ошибке относится к особенности языка, которую мы ещё не рассмотрели: времени жизни. Мы подробно обсудим времена жизни в главе 10. Но если вы не обращаете внимания на части, касающиеся времени жизни, сообщение будет содержать ключ к тому, почему этот код является неполадкой: +Это сообщение об ошибке относится к особенности языка, которую мы ещё не рассмотрели: времени жизни. Мы подробно обсудим времена жизни в главе 10. Но если вы не обращаете внимания на части, касающиеся времени жизни, сообщение будет содержать ключ к тому, почему этот рукопись является неполадкой: ```text this function's return type contains a borrowed value, but there is no value for it to be borrowed from ``` -Давайте подробнее рассмотрим, что именно происходит на каждом этапе нашего кода `dangle`: +Давайте подробнее рассмотрим, что именно происходит на каждом этапе нашего рукописи `dangle`: Файл: src/main.rs @@ -155,7 +155,7 @@ for it to be borrowed from {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-15-dangling-reference-annotated/src/main.rs:here}} ``` -Поскольку `s` создаётся внутри `dangle`, когда код `dangle` будет завершён, `s` будет освобождена. Но мы попытались вернуть ссылку на неё. Это означает, что эта ссылка будет указывать на недопустимую `String`. Это нехорошо! Ржавчина не позволит нам сделать это. +Поскольку `s` создаётся внутри `dangle`, когда рукопись `dangle` будет завершён, `s` будет освобождена. Но мы попытались вернуть ссылку на неё. Это означает, что эта ссылка будет указывать на недопустимую `String`. Это нехорошо! Ржавчина не позволит нам сделать это. Решением будет вернуть непосредственно `String`: diff --git a/rustbook-ru/src/ch04-03-slices.md b/rustbook-ru/src/ch04-03-slices.md index 92526a34b..5663a0959 100644 --- a/rustbook-ru/src/ch04-03-slices.md +++ b/rustbook-ru/src/ch04-03-slices.md @@ -34,9 +34,9 @@ fn first_word(s: &String) -> ? Мы обсудим повторители более подробно в [Главе 13]. На данный мгновение знайте, что `iter` — это способ, который возвращает каждый элемент в собрания, а `enumerate` оборачивает итог `iter` и вместо этого возвращает каждый элемент как часть упорядоченного ряда. Первый элемент упорядоченного ряда, возвращаемый из `enumerate`, является порядковым указателем, а второй элемент — ссылкой на элемент. Это немного удобнее, чем вычислять порядковый указательсамостоятельно. -Поскольку способ `enumerate` возвращает упорядоченный ряд, мы можем использовать образцы для разъединения этого упорядоченного ряда. Мы подробнее обсудим образцы в [Главе 6.]. В цикле `for` мы указываем образец, имеющий `i` для порядкового указателя в упорядоченном ряде и `&item` для одного байта в упорядоченном ряде. Поскольку мы получаем ссылку на элемент из `.iter().enumerate()`, мы используем `&` в образце. +Поскольку способ `enumerate` возвращает упорядоченный ряд, мы можем использовать образцы для разъединения этого упорядоченного ряда. Мы подробнее обсудим образцы в [Главе 6.]. В круговороте `for` мы указываем образец, имеющий `i` для порядкового указателя в упорядоченном ряде и `&item` для одного байта в упорядоченном ряде. Поскольку мы получаем ссылку на элемент из `.iter().enumerate()`, мы используем `&` в образце. -Внутри цикла `for` мы ищем байт, представляющий пробел, используя правила написания байтового записи. Если мы находим пробел, мы возвращаем положение. В противном случае мы возвращаем длину строки с помощью `s.len()`. +Внутри круговорота `for` мы ищем байт, представляющий пробел, используя правила написания байтового записи. Если мы находим пробел, мы возвращаем положение. В противном случае мы возвращаем длину строки с помощью `s.len()`. ```rust,ignore {{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:inside_for}} @@ -62,7 +62,7 @@ fn second_word(s: &String) -> (usize, usize) { Теперь мы отслеживаем начальный *и* конечный порядковый указатель, и у нас есть ещё больше значений, которые были рассчитаны на основе данных в определённом состоянии, но вообще не привязаны к этому состоянию. У нас есть три несвязанные переменные, которые необходимо согласовать. -К счастью в Ржавчина есть решение данной сбоев: строковые срезы. +К счастью в Ржавчине есть решение данной сбоев: строковые срезы. ### Строковые срезы @@ -72,7 +72,7 @@ fn second_word(s: &String) -> (usize, usize) { {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-17-slice/src/main.rs:here}} ``` -Вместо ссылки на всю `String` `hello` является ссылкой на часть `String`, указанную в дополнительном куске кода `[0..5]`. Мы создаём срезы, используя рядв квадратных скобках, указав `[starting_index..ending_index]`, где `starting_index` — это первая позиция, а`ending_index` конечный_порядковый указатель— это на единицу больше, чем последняя позиция в срезе. Внутри устройства данных среза хранит начальную положение и длину среза, что соответствует `ending_index` - `starting_index`. Итак, в случае `let world = &s[6..11];`, `world` будет срезом, содержащим указатель на байт с порядковым указателем 6 `s` со значением длины `5`. +Вместо ссылки на всю `String` `hello` является ссылкой на часть `String`, указанную в дополнительном куске рукописи `[0..5]`. Мы создаём срезы, используя рядв квадратных скобках, указав `[starting_index..ending_index]`, где `starting_index` — это первая позиция, а`ending_index` конечный_порядковый указатель— это на единицу больше, чем последняя позиция в срезе. Внутри устройства данных среза хранит начальную положение и длину среза, что соответствует `ending_index` - `starting_index`. Итак, в случае `let world = &s[6..11];`, `world` будет срезом, содержащим указатель на байт с порядковым указателем 6 `s` со значением длины `5`. Рисунок 4-6 отображает это на диаграмме. @@ -111,7 +111,7 @@ let slice = &s[0..len]; let slice = &s[..]; ``` -> Примечание. Порядковые указатели ряда срезов строк должны располагаться на допустимых границах символов UTF-8. Если вы попытаетесь создать отрывок строки нарушая границы символа в котором больше одного байта, ваша программа завершится с ошибкой. В целях введения срезов строк мы предполагаем, что в этом разделе используется только ASCII; более подробное обсуждение обработки UTF-8 находится в разделе [«Сохранение закодированного текста UTF-8 со строками»]. раздел главы 8. +> Примечание. Порядковые указатели ряда срезов строк должны располагаться на допустимых границах знаков UTF-8. Если вы попытаетесь создать отрывок строки нарушая границы знака в котором больше одного байта, ваша программа завершится с ошибкой. В целях введения срезов строк мы предполагаем, что в этом разделе используется только ASCII; более подробное обсуждение обработки UTF-8 находится в разделе [«Сохранение закодированного текста UTF-8 со строками»]. раздел главы 8. > Давайте используем полученную сведения и перепишем способ `first_word` так, чтобы он возвращал срез. Для обозначения вида "срез строки" существует запись `&str`: @@ -132,7 +132,7 @@ let slice = &s[..]; fn second_word(s: &String) -> &str { ``` -Теперь у нас есть простой API, который гораздо сложнее испортить, потому что сборщик заверяет, что ссылки в `String` останутся действительными. Помните ошибку в программе в приложении 4-8, когда мы получили порядковый указательдо конца первого слова, но затем очиисполнения строку, так что наш порядковый указательстал недействительным? Этот код был логически неправильным, но не показывал немедленных ошибок. Неполадки проявятся позже, если мы попытаемся использовать порядковый указательпервого слова с пустой строкой. Срезы делают эту ошибку невозможной и сообщают нам о неполадке с нашим кодом гораздо раньше. Так, использование исполнения способа `first_word` со срезом вернёт ошибку сборки: +Теперь у нас есть простой API, который гораздо сложнее испортить, потому что сборщик заверяет, что ссылки в `String` останутся действительными. Помните ошибку в программе в приложении 4-8, когда мы получили порядковый указательдо конца первого слова, но затем очиисполнения строку, так что наш порядковый указательстал недействительным? Этот рукопись был разумно неправильным, но не показывал немедленных ошибок. Неполадки проявятся позже, если мы попытаемся использовать порядковый указательпервого слова с пустой строкой. Срезы делают эту ошибку невозможной и сообщают нам о неполадке с нашим рукописью гораздо раньше. Так, использование исполнения способа `first_word` со срезом вернёт ошибку сборки: Файл: src/main.rs @@ -146,7 +146,7 @@ fn second_word(s: &String) -> &str { {{#include ../listings/ch04-understanding-ownership/no-listing-19-slice-error/output.txt}} ``` -Напомним из правил заимствования, что если у нас есть неизменяемая ссылка на что-то, мы не можем также взять изменяемую ссылку. Поскольку для `clear` необходимо обрезать `String`, необходимо получить изменяемую ссылку. `println!` после вызова `clear` использует ссылку в `word`, поэтому неизменяемая ссылка в этот мгновение всё ещё должна быть активной. Ржавчина запрещает одновременное существование изменяемой ссылки в виде`clear` и неизменяемой ссылки в `word`, и сборка завершается ошибкой. Ржавчина не только упростил использование нашего API, но и устранил целый класс ошибок во время сборки! +Напомним из правил заимствования, что если у нас есть неизменяемая ссылка на что-то, мы не можем также взять изменяемую ссылку. Поскольку для `clear` необходимо обрезать `String`, необходимо получить изменяемую ссылку. `println!` после вызова `clear` использует ссылку в `word`, поэтому неизменяемая ссылка в этот мгновение всё ещё должна быть действительной. Ржавчина запрещает одновременное существование изменяемой ссылки в виде`clear` и неизменяемой ссылки в `word`, и сборка завершается ошибкой. Ржавчина не только упростил использование нашего API, но и устранил целый класс ошибок во время сборки! @@ -210,9 +210,9 @@ assert_eq!(slice, &[2, 3]); ## Итоги -Подходы владения, заимствования и срезов обеспечивают безопасность памяти в программах на Ржавчина во время сборки. Язык Ржавчина даёт вам управление над использованием памяти так же, как и другие языки системного программирования, но то, что владелец данных самостоятельно очищает эти данные, когда владелец выходит за рамки, означает, что вам не нужно писать и отлаживать дополнительный код, чтобы получить этот управление. +Подходы владения, заимствования и срезов обеспечивают безопасность памяти в программах на Ржавчине во время сборки. Язык Ржавчина даёт вам управление над использованием памяти так же, как и другие языки системного программирования, но то, что владелец данных самостоятельно очищает эти данные, когда владелец выходит за рамки, означает, что вам не нужно писать и отлаживать дополнительный рукопись, чтобы получить этот управление. -Владение влияет на множество других частей и подходов языка Rust. Мы будем говорить об этих подходах на протяжении оставшихся частей книги. Давайте перейдём к Главе 5 и рассмотрим объединение частей данных в устройства `struct`. +Владение влияет на множество других частей и подходов языка Ржавчина. Мы будем говорить об этих подходах на протяжении оставшихся частей книги. Давайте перейдём к Главе 5 и рассмотрим объединение частей данных в устройства `struct`. [Главе 13]: ch13-02-iterators.html diff --git a/rustbook-ru/src/ch05-00-structs.md b/rustbook-ru/src/ch05-00-structs.md index 6e9902d06..8ab072493 100644 --- a/rustbook-ru/src/ch05-00-structs.md +++ b/rustbook-ru/src/ch05-00-structs.md @@ -1,6 +1,6 @@ # Использование устройств для внутреннего выстраивания связанных данных -*Устройства (struct)* — это пользовательский вид данных, позволяющий назвать и упаковать вместе несколько связанных значений, составляющих значимую логическую объединение. Если вы знакомы с предметно-направленными языками, *устройства* похожа на свойства данных предмета. В этой главе мы сравним и сопоставим упорядоченные ряды со устройствами, чтобы опираться на то, что вы уже знаете, и отобразим, когда устройства являются лучшим способом объединения данных. +*Устройства (struct)* — это пользовательский вид данных, позволяющий назвать и упаковать вместе несколько связанных значений, составляющих значимую разумное объединение. Если вы знакомы с предметно-направленными языками, *устройства* похожа на свойства данных предмета. В этой главе мы сравним и сопоставим упорядоченные ряды со устройствами, чтобы опираться на то, что вы уже знаете, и отобразим, когда устройства являются лучшим способом объединения данных. -Мы отобразим, как определять устройства и создавать их образцы. Мы обсудим, как определить сопряженные функции, особенно сопряженные функции, называемые *способами*, для указания поведения, сопряженного с видом устройства. Устройства и перечисления (обсуждаемые в главе 6) являются строительными разделами для создания новых видов в предметной области вашей программы. Они дают возможность в полной мере воспользоваться преимуществами проверки видов во время сборки Rust. +Мы отобразим, как определять устройства и создавать их образцы. Мы обсудим, как определить сопряженные функции, особенно сопряженные функции, называемые *способами*, для указания поведения, сопряженного с видом устройства. Устройства и перечисления (обсуждаемые в главе 6) являются строительными разделами для создания новых видов в предметной области вашей программы. Они дают возможность в полной мере воспользоваться преимуществами проверки видов во время сборки Ржавчина. diff --git a/rustbook-ru/src/ch05-01-defining-structs.md b/rustbook-ru/src/ch05-01-defining-structs.md index 11c26ba4c..7b21f201c 100644 --- a/rustbook-ru/src/ch05-01-defining-structs.md +++ b/rustbook-ru/src/ch05-01-defining-structs.md @@ -62,7 +62,7 @@ Приложение 5-5: функция build_user использует сокращённую объявление полей, потому что её входные свойства username и email имеют имена подобные именам полей устройства -Здесь происходит создание нового образца устройства `User`, которая имеет поле с именем `email`. Мы хотим установить поле устройства `email` значением входного свойства `email` функции `build_user`. Так как поле `email` и входной свойство функции `email` имеют одинаковое название, можно писать просто `email` вместо кода `email: email`. +Здесь происходит создание нового образца устройства `User`, которая имеет поле с именем `email`. Мы хотим установить поле устройства `email` значением входного свойства `email` функции `build_user`. Так как поле `email` и входной свойство функции `email` имеют одинаковое название, можно писать просто `email` вместо рукописи `email: email`. ### Создание образца устройства из образца другой устройства с помощью правил написания обновления устройства @@ -78,7 +78,7 @@ Приложение 5-6: Создание нового образца User с использованием некоторых значений из образца user1 -Используя правила написания обновления устройства, можно получить тот же эффект, используя меньше кода как показано в приложении 5-7. правила написания `..` указывает, что оставшиеся поля устанавливаются неявно и должны иметь значения из указанного образца. +Используя правила написания обновления устройства, можно получить тот же итог, используя меньше рукописи как показано в приложении 5-7. правила написания `..` указывает, что оставшиеся поля устанавливаются неявно и должны иметь значения из указанного образца. Файл: src/main.rs @@ -88,13 +88,13 @@ Приложение 5-7: Использование правил написания обновления устройства для установки нового значения email для образца User, но использование остальных значений из образца user1 -Код в приложении 5-7 также создаёт образец в `user2`, который имеет другое значение для `email`, но с тем же значением для полей `username`, `active` и `sign_in_count` из `user1`. Оператор `..user1` должен стоять последним для указания на получение значений всех оставшихся полей из соответствующих полей в `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`: diff --git a/rustbook-ru/src/ch05-02-example-structs.md b/rustbook-ru/src/ch05-02-example-structs.md index 2a7a93eaf..3bd05ece6 100644 --- a/rustbook-ru/src/ch05-02-example-structs.md +++ b/rustbook-ru/src/ch05-02-example-structs.md @@ -2,7 +2,7 @@ Чтобы понять, когда нам может понадобиться использование устройств, давайте напишем программу, которая вычисляет площадь прямоугольника. Мы начнём с использования одиночных переменных, а затем будем улучшать программу до использования устройств. -Давайте создадим новый дело программы при помощи Cargo и назовём его *rectangles*. Наша программа будет получать на вход длину и ширину прямоугольника в пикселях и затем рассчитывать площадь прямоугольника. Приложение 5-8 показывает один из коротких исходов кода, который позволит нам сделать именно то, что надо, в файле дела *src/main.rs*. +Давайте создадим новое дело программы при помощи Cargo и назовём его *rectangles*. Наша программа будет получать на вход длину и ширину прямоугольника в пикселях и затем рассчитывать площадь прямоугольника. Приложение 5-8 показывает один из коротких исходов рукописи, который позволит нам сделать именно то, что надо, в файле дела *src/main.rs*. Файл: src/main.rs @@ -18,7 +18,7 @@ {{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/output.txt}} ``` -Этот код успешно вычисляет площадь прямоугольника, вызывая функцию `area` с каждым измерением, но мы можем улучшить его ясность и читабельность. +Этот рукопись успешно вычисляет площадь прямоугольника, вызывая функцию `area` с каждым измерением, но мы можем улучшить его ясность и удобство чтения. Неполадкаданного способа очевидна из ярлыки `area`: @@ -26,9 +26,9 @@ {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/src/main.rs:here}} ``` -Функция `area` должна вычислять площадь одного прямоугольника, но функция, которую мы написали, имеет два свойства, и нигде в нашей программе не ясно, что эти свойства взаимосвязаны. Было бы более читабельным и управляемым собъединять ширину и высоту вместе. В разделе [«Упорядоченные ряды»] главы 3 мы уже обсуждали один из способов сделать это — использовать упорядоченные ряды. +Функция `area` должна вычислять площадь одного прямоугольника, но функция, которую мы написали, имеет два свойства, и нигде в нашей программе не ясно, что эти свойства взаимосвязаны. Было бы более удобным для чтения и управляемым объединять ширину и высоту вместе. В разделе [«Упорядоченные ряды»] главы 3 мы уже обсуждали один из способов сделать это — использовать упорядоченные ряды. -### Переработка кода при помощи упорядоченных рядов +### Переработка рукописи при помощи упорядоченных рядов Приложение 5-9 — это другая исполнение программы, использующая упорядоченные ряды. @@ -42,9 +42,9 @@ С одной стороны, эта программа лучше. Упорядоченные ряды позволяют добавить немного устройства, и теперь мы передаём только один переменная. Но с другой стороны, эта исполнение менее понятна: упорядоченные ряды не называют свои элементы, поэтому нам приходится упорядочивать части упорядоченного ряда, что делает наше вычисление менее очевидным. -Если мы перепутаем местами ширину с высотой при расчёте площади, то это не имеет значения. Но если мы хотим нарисовать прямоугольник на экране, то это уже будет важно! Мы должны помнить, что ширина `width` находится в упорядоченном ряде с порядковым указателем `0`, а высота `height` — с порядковым указателем `1`. Если кто-то другой поработал бы с кодом, ему бы пришлось разобраться в этом и также помнить про порядок. Легко забыть и перепутать эти значения — и это вызовет ошибки, потому что данный код не передаёт наши намерения. +Если мы перепутаем местами ширину с высотой при расчёте площади, то это не имеет значения. Но если мы хотим нарисовать прямоугольник на экране, то это уже будет важно! Мы должны помнить, что ширина `width` находится в упорядоченном ряде с порядковым указателем `0`, а высота `height` — с порядковым указателем `1`. Если кто-то другой поработал бы с рукописью, ему бы пришлось разобраться в этом и также помнить про порядок. Легко забыть и перепутать эти значения — и это вызовет ошибки, потому что данный рукопись не передаёт наши намерения. -### Переработка кода при помощи устройств: добавим больше смысла +### Переработка рукописи при помощи устройств: добавим больше смысла Мы используем устройства, чтобы добавить смысл данным при помощи назначения им осмысленных имён . Мы можем переделать используемый упорядоченный ряд в устройство с единым именем для сущности и частными названиями её частей, как показано в приложении 5-10. @@ -74,7 +74,7 @@ Приложение 5-11: Попытка вывести значения образца Rectangle -При сборки этого кода мы получаем ошибку с сообщением: +При сборки этого рукописи мы получаем ошибку с сообщением: ```text {{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/output.txt:3}} @@ -90,9 +90,9 @@ {{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/output.txt:9:10}} ``` -Давайте попробуем! Вызов макроса `println!` теперь будет выглядеть так `println!("rect1 is {:?}", rect1);`. Ввод определетеля `:?` внутри фигурных скобок говорит макросу `println!`, что мы хотим использовать другой вид вывода, известный как `Debug`. Особенность `Debug` позволяет печатать устройство способом, удобным для разработчиков, чтобы видеть значение во время отладки кода. +Давайте попробуем! Вызов макроса `println!` теперь будет выглядеть так `println!("rect1 is {:?}", rect1);`. Ввод определетеля `:?` внутри фигурных скобок говорит макросу `println!`, что мы хотим использовать другой вид вывода, известный как `Debug`. Особенность `Debug` позволяет печатать устройство способом, удобным для разработчиков, чтобы видеть значение во время отладки рукописи. -Соберем код с этими изменениями. Упс! Мы всё ещё получаем ошибку: +Соберем рукопись с этими изменениями. Упс! Мы всё ещё получаем ошибку: ```text {{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-01-debug/output.txt:3}} @@ -104,7 +104,7 @@ {{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-01-debug/output.txt:9:10}} ``` -Rust *выполняет* возможность для печати отладочной сведений, но *не включает (не выводит) её по умолчанию*. Мы должны явно включить эту возможность для нашей устройства. Чтобы это сделать, добавляем внешний свойство `#[derive(Debug)]` сразу перед определением устройства, как показано в приложении 5-12. +Ржавчина *выполняет* возможность для печати отладочной сведений, но *не включает (не выводит) её по умолчанию*. Мы должны явно включить эту возможность для нашей устройства. Чтобы это сделать, добавляем внешний свойство `#[derive(Debug)]` сразу перед определением устройства, как показано в приложении 5-12. Файл: src/main.rs @@ -121,7 +121,7 @@ Rust *выполняет* возможность для печати отлад {{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-12/output.txt}} ``` -Отлично! Это не самый красивый вывод, но он показывает значения всех полей образца, которые определённо помогут при отладке. Когда у нас более крупные устройства, то полезно иметь более простой для чтения вывод; в таких случаях можно использовать код `{:#?}` вместо `{:?}` в строке макроса `println!`. В этом примере использование исполнения `{:#?}` приведёт к такому выводу: +Отлично! Это не самый красивый вывод, но он показывает значения всех полей образца, которые определённо помогут при отладке. Когда у нас более крупные устройства, то полезно иметь более простой для чтения вывод; в таких случаях можно использовать рукопись `{:#?}` вместо `{:?}` в строке макроса `println!`. В этом примере использование исполнения `{:#?}` приведёт к такому выводу: ```console {{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-02-pretty-debug/output.txt}} @@ -149,7 +149,7 @@ Rust *выполняет* возможность для печати отлад В дополнение к `Debug`, Ржавчина предоставил нам ряд особенностей, которые мы можем использовать с свойством `derive` для добавления полезного поведения к нашим пользовательским видам. Эти особенности и их поведение перечислены в [приложении C]. Мы расскажем, как выполнить эти особенности с пользовательским поведением, а также как создать свои собственные особенности в главе 10. Кроме того, есть много других свойств помимо `derive`; для получения дополнительной сведений смотрите [раздел “Свойства” справочника Rust](https://doc.rust-lang.org/reference/attributes.html). -Функция `area` является довольно отличительной: она считает только площадь прямоугольников. Было бы полезно привязать данное поведение как можно ближе к устройстве `Rectangle`, потому что наш отличительный код не будет работать с любым другим видом. Давайте рассмотрим, как можно улучшить наш код превращая функцию `area` в способ area, определённый для вида `Rectangle`. +Функция `area` является довольно отличительной: она считает только площадь прямоугольников. Было бы полезно привязать данное поведение как можно ближе к устройстве `Rectangle`, потому что наш отличительный рукопись не будет работать с любым другим видом. Давайте рассмотрим, как можно улучшить нашу рукопись превращая функцию `area` в способ area, определённый для вида `Rectangle`. [«Упорядоченные ряды»]: ch03-02-data-types.html#the-tuple-type diff --git a/rustbook-ru/src/ch05-03-method-syntax.md b/rustbook-ru/src/ch05-03-method-syntax.md index 97f0aa563..5ccff1e46 100644 --- a/rustbook-ru/src/ch05-03-method-syntax.md +++ b/rustbook-ru/src/ch05-03-method-syntax.md @@ -1,6 +1,6 @@ ## правила написания способа -*Способы* похожи на функции: мы объявляем их с помощью ключевого слова `fn` и имени, они могут иметь свойства и возвращаемое значение, и они содержат код, запускающийся в случае вызова способа. В отличие от функций, способы определяются в среде устройства (или предмета перечисления или особенности, которые мы рассмотрим в [главе 6)] и [главе 17] соответственно), а их первым свойствоом всегда является `self`, представляющий собой образец устройства, с которой вызывается этот способ. +*Способы* похожи на функции: мы объявляем их с помощью ключевого слова `fn` и имени, они могут иметь свойства и возвращаемое значение, и они содержат рукопись, запускающийся в случае вызова способа. В отличие от функций, способы определяются в среде устройства (или предмета перечисления или особенности, которые мы рассмотрим в [главе 6)] и [главе 17] соответственно), а их первым свойствоом всегда является `self`, представляющий собой образец устройства, с которой вызывается этот способ. ### Определение способов @@ -18,9 +18,9 @@ В ярлыке `area` мы используем `&self` вместо `rectangle: &Rectangle`. `&self` на самом деле является сокращением от `self: &Self`. Внутри раздела `impl` вид `Self` является псевдонимом вида, для которого выполнен раздел`impl`. Способы обязаны иметь свойство с именем `self` вида `Self`, поэтому Ржавчина позволяет вам сокращать его, используя только имя `self` на месте первого свойства. Обратите внимание, что нам по-прежнему нужно использовать `&` перед сокращением `self`, чтобы указать на то, что этот способ заимствует образец `Self`, точно так же, как мы делали это в `rectangle: &Rectangle`. Как и любой другой свойство, способы могут брать во владение `self`, заимствовать неизменяемый `self`, как мы поступили в данном случае, или заимствовать изменяемый `self`. -Мы выбрали `&self` здесь по той же причине, по которой использовали `&Rectangle` в исполнения кода с функцией: мы не хотим брать устройство во владение, мы просто хотим прочитать данные в устройстве, а не писать в неё. Если бы мы хотели изменить образец, на котором мы вызывали способ силами самого способа, то мы бы использовали `&mut self` в качестве первого свойства. Наличие способа, который берёт образец во владение, используя только `self` в качестве первого свойства, является редким; эта техника обычно используется, когда способ превращает `self` во что-то ещё, и вы хотите запретить вызывающей стороне использовать исходный образец после превращения. +Мы выбрали `&self` здесь по той же причине, по которой использовали `&Rectangle` в исполнения рукописи с функцией: мы не хотим брать устройство во владение, мы просто хотим прочитать данные в устройстве, а не писать в неё. Если бы мы хотели изменить образец, на котором мы вызывали способ силами самого способа, то мы бы использовали `&mut self` в качестве первого свойства. Наличие способа, который берёт образец во владение, используя только `self` в качестве первого свойства, является редким; эта техника обычно используется, когда способ превращает `self` во что-то ещё, и вы хотите запретить вызывающей стороне использовать исходный образец после превращения. -Основная причина использования способов вместо функций, помимо правил написания способа, где нет необходимости повторять вид `self` в ярлыке каждого способа, заключается в согласования кода. Мы помеисполнения все, что мы можем сделать с образцом вида, в один `impl` вместо того, чтобы заставлять будущих пользователей нашего кода искать доступный возможности `Rectangle` в разных местах предоставляемой нами библиотеки. +Основная причина использования способов вместо функций, помимо правил написания способа, где нет необходимости повторять вид `self` в ярлыке каждого способа, заключается в согласования рукописи. Мы помеисполнения все, что мы можем сделать с образцом вида, в один `impl` вместо того, чтобы заставлять будущих пользователей нашего рукописи искать доступный возможности `Rectangle` в разных местах предоставляемой нами библиотеки. Обратите внимание, что мы можем дать способу то же имя, что и одному из полей устройства. Например, для `Rectangle` мы можем определить способ, также названный `width`: @@ -34,11 +34,11 @@ Часто, но не всегда, когда мы создаём способы с тем же именем, что и у поля, мы хотим, чтобы он только возвращал значение одноимённого поля и больше ничего не делал. Подобные способы называются *геттерами*, и Ржавчина не выполняет их самостоятельно для полей устройства, как это делают некоторые другие языки. Геттеры полезны, поскольку вы можете сделать поле закрытым, а способ открытым и, таким образом, включить доступ только для чтения к этому полю как часть общедоступного API вида. Мы обсудим, что такое открытость и закрытость, и как обозначить поле или способ в качестве открытого или закрытого в [главе 7]. -> ### Где используется оператор `->`? +> ### Где используется приказчик `->`? > -> В языках C и C++, используются два различных оператора для вызова способов: используется `.`, если вызывается способ непосредственно у образца устройства и используется `->`, если вызывается способ для указателя на предмет. Другими словами, если `object` является указателем, то вызовы способа `object->something()` и ` (*object).something()` являются подобными. +> В языках C и C++, используются два различных приказчика для вызова способов: используется `.`, если вызывается способ непосредственно у образца устройства и используется `->`, если вызывается способ для указателя на предмет. Другими словами, если `object` является указателем, то вызовы способа `object->something()` и ` (*object).something()` являются подобными. > -> Ржавчина не имеет эквивалента оператора `->`, наоборот, в Ржавчина есть возможность называемая *самостоятельное обращение по ссылке и разыменование* (automatic referencing and dereferencing). Вызов способов является одним из немногих мест в Rust, в котором есть такое поведение. +> Ржавчина не имеет эквивалента приказчика `->`, наоборот, в Ржавчине есть возможность называемая *самостоятельное обращение по ссылке и разыменование* (automatic referencing and dereferencing). Вызов способов является одним из немногих мест в Ржавчине, в котором есть такое поведение. > > Вот как это работает: когда вы вызываете способ `object.something()`, Ржавчина самостоятельно добавляет `&`, `&mut` или `*`, таким образом, чтобы `object` соответствовал ярлыке способа. Другими словами, это то же самое: > @@ -65,7 +65,7 @@ > (&p1).distance(&p2); > ``` > -> Первый пример выглядит намного понятнее. Самостоятельный вывод ссылки работает потому, что способы имеют понятного получателя - вид `self`. Учитывая получателя и имя способа, Ржавчина может точно определить, что в данном случае делает код: читает ли способ (`&self`), делает ли изменение (`&mut self`) или поглощает (`self`). Тотобстоятельство, что Ржавчина делает заимствование неявным для принимающего способа, в значительной степени способствует тому, чтобы сделать владение удобным на опыте. +> Первый пример выглядит намного понятнее. Самостоятельный вывод ссылки работает потому, что способы имеют понятного получателя - вид `self`. Учитывая получателя и имя способа, Ржавчина может точно определить, что в данном случае делает код: читает ли способ (`&self`), делает ли изменение (`&mut self`) или поглощает (`self`). То обстоятельство, что Ржавчина делает заимствование неявным для принимающего способа, в значительной степени способствует тому, чтобы сделать владение удобным на опыте. ### Способы с несколькими свойствами @@ -86,7 +86,7 @@ Can rect1 hold rect2? true Can rect1 hold rect3? false ``` -Мы знаем, что хотим определить способ, поэтому он будет находится в `impl Rectangle` разделе. Имя способа будет `can_hold`, и оно будет принимать неизменяемое заимствование на другой `Rectangle` в качестве свойства. Мы можем сказать, какой это будет вид свойства, посмотрев на код вызывающего способа: способ `rect1.can_hold(&rect2)` передаёт в него `&rect2` , который является неизменяемым заимствованием образца `rect2` вида `Rectangle`. В этом есть смысл, потому что нам нужно только читать `rect2` (а не писать, что означало бы, что нужно изменяемое заимствование), и мы хотим, чтобы `main` сохранил право собственности на образец `rect2`, чтобы мы могли использовать его снова после вызов способа `can_hold`. Возвращаемое значение `can_hold` имеет булевый вид, а выполнение проверяет, являются ли ширина и высота `self` больше, чем ширина и высота другого `Rectangle` соответственно. Давайте добавим новый способ `can_hold` в `impl` разделиз приложения 5-13, как показано в приложении 5-15. +Мы знаем, что хотим определить способ, поэтому он будет находится в `impl Rectangle` разделе. Имя способа будет `can_hold`, и оно будет принимать неизменяемое заимствование на другой `Rectangle` в качестве свойства. Мы можем сказать, какой это будет вид свойства, посмотрев на рукопись вызывающего способа: способ `rect1.can_hold(&rect2)` передаёт в него `&rect2` , который является неизменяемым заимствованием образца `rect2` вида `Rectangle`. В этом есть смысл, потому что нам нужно только читать `rect2` (а не писать, что означало бы, что нужно изменяемое заимствование), и мы хотим, чтобы `main` сохранил право собственности на образец `rect2`, чтобы мы могли использовать его снова после вызов способа `can_hold`. Возвращаемое значение `can_hold` имеет булевый вид, а выполнение проверяет, являются ли ширина и высота `self` больше, чем ширина и высота другого `Rectangle` соответственно. Давайте добавим новый способ `can_hold` в `impl` разделиз приложения 5-13, как показано в приложении 5-15. Файл: src/main.rs @@ -96,7 +96,7 @@ Can rect1 hold rect3? false Приложение 5-15: Выполнение способа can_hold для Rectangle, принимающего другой образец Rectangle в качестве свойства -Когда мы запустим код с функцией `main` приложения 5-14, мы получим желаемый вывод. Способы могут принимать несколько свойств, которые мы добавляем в ярлык после первого свойства `self`, и эти свойства работают так же, как свойства в функциях. +Когда мы запустим рукопись с функцией `main` приложения 5-14, мы получим желаемый вывод. Способы могут принимать несколько свойств, которые мы добавляем в ярлык после первого свойства `self`, и эти свойства работают так же, как свойства в функциях. ### Сопряженные функции @@ -116,7 +116,7 @@ Can rect1 hold rect3? false ### Несколько разделов `impl` -Каждая устройства может иметь несколько `impl`. Например, Приложение 5-15 эквивалентен коду, показанному в приложении 5-16, в котором каждый способ находится в своём собственном разделе `impl`. +Каждая устройства может иметь несколько `impl`. Например, Приложение 5-15 эквивалентен рукописи, показанному в приложении 5-16, в котором каждый способ находится в своём собственном разделе `impl`. ```rust {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-16/src/main.rs:here}} @@ -128,9 +128,9 @@ Can rect1 hold rect3? false ## Итоги -Устройства позволяют создавать собственные виды, которые имеют смысл в вашей предметной области. Используя устройства, вы храните сопряженные друг с другом отрывки данных и даёте название частям данных, чтобы ваш код был более понятным. Способы позволяют определить поведение, которое имеют образцы ваших устройств, а сопряженные функции позволяют привязать возможность к вашей устройстве, не обращаясь к её образцу. +Устройства позволяют создавать собственные виды, которые имеют смысл в вашей предметной области. Используя устройства, вы храните сопряженные друг с другом отрывки данных и даёте название частям данных, чтобы ваша рукопись был более понятным. Способы позволяют определить поведение, которое имеют образцы ваших устройств, а сопряженные функции позволяют привязать возможность к вашей устройстве, не обращаясь к её образцу. -Но устройства — не единственный способ создавать собственные виды: давайте обратимся к перечислениям в Rust, чтобы добавить ещё один средство в свой арсенал. +Но устройства — не единственный способ создавать собственные виды: давайте обратимся к перечислениям в Ржавчине, чтобы добавить ещё один средство в свой арсенал. [главе 6)]: ch06-00-enums.html diff --git a/rustbook-ru/src/ch06-00-enums.md b/rustbook-ru/src/ch06-00-enums.md index 915a0bc0e..2c4833d25 100644 --- a/rustbook-ru/src/ch06-00-enums.md +++ b/rustbook-ru/src/ch06-00-enums.md @@ -1,3 +1,3 @@ # Перечисления и сопоставление с образцом -В этой главе мы рассмотрим *перечисления (enumerations)*, также называемые *enums*. Перечисления позволяют определить вид путём перечисления его возможных *исходов* . Сначала мы определим и используем перечисление, чтобы показать, как оно может объединить значения и данные. Далее мы рассмотрим особенно полезное перечисление под названием `Option`, которое выражает, что значение может быть либо чем-то, либо ничем. Затем мы рассмотрим, как сопоставление с образцом в выражении `match` позволяет легко запускать разный код для разных значений перечисления. Наконец, мы узнаем, насколько устройство `if let` удобна и кратка для обработки перечислений в вашем коде. +В этой главе мы рассмотрим *перечисления (enumerations)*, также называемые *enums*. Перечисления позволяют определить вид путём перечисления его возможных *исходов* . Сначала мы определим и используем перечисление, чтобы показать, как оно может объединить значения и данные. Далее мы рассмотрим особенно полезное перечисление под названием `Option`, которое выражает, что значение может быть либо чем-то, либо ничем. Затем мы рассмотрим, как сопоставление с образцом в выражении `match` позволяет легко запускать разный рукопись для разных значений перечисления. Наконец, мы узнаем, насколько устройство `if let` удобна и кратка для обработки перечислений в вашем рукописи. diff --git a/rustbook-ru/src/ch06-01-defining-an-enum.md b/rustbook-ru/src/ch06-01-defining-an-enum.md index 46d48ae48..00f12fa27 100644 --- a/rustbook-ru/src/ch06-01-defining-an-enum.md +++ b/rustbook-ru/src/ch06-01-defining-an-enum.md @@ -2,17 +2,17 @@ Там, где устройства дают вам возможность объединять связанные поля и данные, например `Rectangle` с его `width` и `height`, перечисления дают вам способ сказать, что значение является одним из возможных наборов значений. Например, мы можем захотеть сказать, что `Rectangle` — это одна из множества возможных фигур, в которую также входят `Circle` и `Triangle`. Для этого Ржавчина позволяет нам закодировать эти возможности в виде перечисления. -Давайте рассмотрим случай, которую мы могли бы захотеть отразить в коде, и поймём, почему перечисления полезны и более уместны, чем устройства в этом случае. Допустим, нам нужно работать с IP-адресами. В настоящее время для обозначения IP-адресов используются два основных исполнения: четвёртая и шестая исполнения. Поскольку это единственно возможные исходы IP-адресов, с которыми может столкнуться наша программа, мы можем *перечислить* все возможные исходы, откуда перечисление и получило своё название. +Давайте рассмотрим случай, которую мы могли бы захотеть отразить в рукописи, и поймём, почему перечисления полезны и более уместны, чем устройства в этом случае. Допустим, нам нужно работать с IP-адресами. В настоящее время для обозначения IP-адресов используются два основных исполнения: четвёртая и шестая исполнения. Поскольку это единственно возможные исходы IP-адресов, с которыми может столкнуться наша программа, мы можем *перечислить* все возможные исходы, откуда перечисление и получило своё название. -Любой IP-адрес может быть либо четвёртой, либо шестой исполнения, но не обеими одновременно. Эта особенность IP-адресов делает устройство данных enum подходящей, поскольку значение enum может представлять собой только один из его возможных исходов. Адреса как четвёртой, так и шестой исполнения по своей сути все равно являются IP-адресами, поэтому их следует рассматривать как один и тот же вид, когда в коде обрабатываются задачи, относящиеся к любому виду IP-адресов. +Любой IP-адрес может быть либо четвёртой, либо шестой исполнения, но не обеими одновременно. Эта особенность IP-адресов делает устройство данных enum подходящей, поскольку значение enum может представлять собой только один из его возможных исходов. Адреса как четвёртой, так и шестой исполнения по своей сути все равно являются IP-адресами, поэтому их следует рассматривать как один и тот же вид, когда в рукописи обрабатываются задачи, относящиеся к любому виду IP-адресов. -Можно выразить эту подход в коде, определив перечисление `IpAddrKind` и составив список возможных видов IP-адресов, `V4` и `V6`. Вот исходы перечислений: +Можно выразить эту подход в рукописи, определив перечисление `IpAddrKind` и составив список возможных видов IP-адресов, `V4` и `V6`. Вот исходы перечислений: ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-01-defining-enums/src/main.rs:def}} ``` -`IpAddrKind` теперь является пользовательским видом данных, который мы можем использовать в другом месте нашего кода. +`IpAddrKind` теперь является пользовательским видом данных, который мы можем использовать в другом месте нашего рукописи. ### Значения перечислений @@ -75,9 +75,9 @@ enum IpAddr { } ``` -Этот код отображает что мы можем добавлять любой вид данных в значение перечисления: строку, число, устройство и пр. Вы даже можете включить в перечисление другие перечисления! Обычные виды данных не очень сложны, хотя, возможно, могут быть очень сложными (вложенность данных может быть очень глубокой). +Этот рукопись отображает что мы можем добавлять любой вид данных в значение перечисления: строку, число, устройство и пр. Вы даже можете включить в перечисление другие перечисления! Обычные виды данных не очень сложны, хотя, возможно, могут быть очень сложными (вложенность данных может быть очень глубокой). -Обратите внимание, что хотя определение перечисления `IpAddr` есть в встроенной библиотеке, мы смогли объявлять и использовать свою собственную выполнение с подобным названием без каких-либо несоответствий, потому что мы не добавили определение встроенной библиотеки в область видимости кода. Подробнее об этом поговорим в Главе 7. +Обратите внимание, что хотя определение перечисления `IpAddr` есть в встроенной библиотеке, мы смогли объявлять и использовать свою собственную выполнение с подобным названием без каких-либо несоответствий, потому что мы не добавили определение встроенной библиотеки в область видимости рукописи. Подробнее об этом поговорим в Главе 7. Рассмотрим другой пример перечисления в приложении 6-2: в этом примере каждый элемент перечисления имеет свой особый вид данных внутри: @@ -114,21 +114,21 @@ enum IpAddr { ### Перечисление `Option` и его преимущества перед Null-значениями -В этом разделе рассматривается пример использования `Option`, ещё одного перечисления, определённого в встроенной библиотеке. Вид `Option` кодирует очень распространённый сценарий, в котором значение может быть чем-то, а может быть ничем. +В этом разделе рассматривается пример использования `Option`, ещё одного перечисления, определённого в встроенной библиотеке. Вид `Option` кодирует очень распространённый задумка, в котором значение может быть чем-то, а может быть ничем. Например, если вы запросите первый элемент из непустого списка, вы получите значение. Если вы запросите первый элемент пустого списка, вы ничего не получите. Выражение этой подходы в понятиях системы видов означает, что сборщик может проверить, обработали ли вы все случаи, которые должны были обработать; эта возможность может предотвратить ошибки, которые чрезвычайно распространены в других языках программирования. -Внешний вид языка программирования часто рассматривается с точки зрения того, какие функции вы включаете в него, но те функции, которые вы исключаете, также важны. Например в Ржавчина нет такого возможностей как null значения, однако он есть во многих других языках. *Null значение* - это значение, которое означает, что значения нет. В языках с null значением переменные всегда могут находиться в одном из двух состояний: *нет значения (null)* или *есть значение (not-null)*. +Внешний вид языка программирования часто рассматривается с точки зрения того, какие функции вы включаете в него, но те функции, которые вы исключаете, также важны. Например в Ржавчине нет такого возможностей как null значения, однако он есть во многих других языках. *Null значение* - это значение, которое означает, что значения нет. В языках с null значением переменные всегда могут находиться в одном из двух состояний: *нет значения (null)* или *есть значение (not-null)*. В своей презентации 2009 года «Null ссылки: ошибка в миллиард долларов» Тони Хоар (Tony Hoare), изобретатель null, сказал следующее: -> Я называю это своей ошибкой на миллиард долларов. В то время я разрабатывал первую комплексную систему видов для ссылок на предметно-направленном языке. Моя цель состояла в том, чтобы обеспечить, что любое использование ссылок должно быть абсолютно безопасным, с самостоятельной проверкой сборщиком. Но я не мог устоять перед соблазном вставить пустую ссылку просто потому, что это было так легко выполнить. Это привело к бесчисленным ошибкам, уязвимостям и системным сбоям, которые, вероятно, причинили боль и ущерб на миллиард долларов за последние сорок лет. +> Я называю это своей ошибкой на миллиард долларов. В то время я разрабатывал первую комплексную систему видов для ссылок на предметно-направленном языке. Моя цель состояла в том, чтобы обеспечить, что любое использование ссылок должно быть безусловно безопасным, с самостоятельной проверкой сборщиком. Но я не мог устоять перед соблазном вставить пустую ссылку просто потому, что это было так легко выполнить. Это привело к бесчисленным ошибкам, уязвимостям и системным сбоям, которые, вероятно, причинили боль и ущерб на миллиард долларов за последние сорок лет. Неполадкас null значениями заключается в том, что если вы попытаетесь использовать null значение в качестве not-null значения, вы получите ошибку определённого рода. Поскольку свойство null или not-null распространено повсеместно, сделать такую ошибку очень просто. Тем не менее, подход, которую null пытается выразить, является полезной: null - это значение, которое в настоящее время по какой-то причине недействительно или отсутствует. -Неполадкана самом деле не в подходы, а в именно выполнения. Таким образом, в Ржавчина нет значений null, но есть перечисление, которое может закодировать подход присутствия или отсутствия значения. Это перечисление `Option` , и оно [определено встроенной библиотекой следующим образом:] +Неполадкана самом деле не в подходы, а в именно выполнения. Таким образом, в Ржавчине нет значений null, но есть перечисление, которое может закодировать подход присутствия или отсутствия значения. Это перечисление `Option` , и оно [определено встроенной библиотекой следующим образом:] ```rust enum Option { @@ -139,23 +139,23 @@ enum Option { Перечисление `Option` настолько полезно, что оно даже включено в прелюдию; вам не нужно явно вводить его в область видимости. Его исходы также включены в прелюдию: вы можете использовать `Some` и `None` напрямую, без приставки `Option::`. При всём при этом, `Option` является обычным перечислением, а `Some(T)` и `None` представляют собой его исходы. -`` - это особенность Rust, о которой мы ещё не говорили. Это свойство обобщённого вида, и мы рассмотрим его более подробно в главе 10. На данный мгновение всё, что вам нужно знать, это то, что `` означает, что исход `Some` `Option` может содержать один отрывок данных любого вида, и что каждый определенный вид, который используется вместо `T` делает общий `Option` другим видом. Вот несколько примеров использования `Option` для хранения числовых и строковых видов: +`` - это особенность Ржавчина, о которой мы ещё не говорили. Это свойство обобщённого вида, и мы рассмотрим его более подробно в главе 10. На данный мгновение всё, что вам нужно знать, это то, что `` означает, что исход `Some` `Option` может содержать один отрывок данных любого вида, и что каждый определенный вид, который используется вместо `T` делает общий `Option` другим видом. Вот несколько примеров использования `Option` для хранения числовых и строковых видов: ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-06-option-examples/src/main.rs:here}} ``` -Вид `some_number` - `Option`. Вид `some_char` - `Option`, это другой вид. Ржавчина может вывести эти виды, потому что мы указали значение внутри исхода `Some`. Для `absent_number` Ржавчина требует, чтобы мы определяли общий вид для `Option`: сборщик не может вывести вид, который будет в `Some`, глядя только на значение `None`. Здесь мы сообщаем Rust, что `absent_number` должен иметь вид `Option`. +Вид `some_number` - `Option`. Вид `some_char` - `Option`, это другой вид. Ржавчина может вывести эти виды, потому что мы указали значение внутри исхода `Some`. Для `absent_number` Ржавчина требует, чтобы мы определяли общий вид для `Option`: сборщик не может вывести вид, который будет в `Some`, глядя только на значение `None`. Здесь мы сообщаем Ржавчина, что `absent_number` должен иметь вид `Option`. Когда есть значение `Some`, мы знаем, что значение присутствует и содержится внутри `Some`. Когда есть значение `None`, это означает то же самое, что и null в некотором смысле: у нас нет действительного значения. Так почему наличие `Option` лучше, чем null? -Вкратце, поскольку `Option` и `T` (где `T` может быть любым видом) относятся к разным видам, сборщик не позволит нам использовать значение `Option` даже если бы оно было определённо допустимым значением. Например, этот код не будет собираться, потому что он пытается добавить `i8` к значению вида `Option`: +Вкратце, поскольку `Option` и `T` (где `T` может быть любым видом) относятся к разным видам, сборщик не позволит нам использовать значение `Option` даже если бы оно было определённо допустимым значением. Например, этот рукопись не будет собираться, потому что он пытается добавить `i8` к значению вида `Option`: ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-07-cant-use-option-directly/src/main.rs:here}} ``` -Если мы запустим этот код, то получим такое сообщение об ошибке: +Если мы запустим этот рукопись, то получим такое сообщение об ошибке: ```console {{#include ../listings/ch06-enums-and-pattern-matching/no-listing-07-cant-use-option-directly/output.txt}} @@ -165,12 +165,12 @@ enum Option { Другими словами, вы должны преобразовать `Option` в `T` прежде чем вы сможете выполнять действия с этим `T`. Как правило, это помогает выявить одну из наиболее распространённых неполадок с null: предполагая, что что-то не равно null, когда оно на самом деле равно null. -Устранение риска ошибочного предположения касательно не-null значения помогает вам быть более уверенным в своём коде. Чтобы иметь значение, которое может быть null, вы должны явно описать вид этого значения с помощью `Option`. Затем, когда вы используете это значение, вы обязаны явно обрабатывать случай, когда значение равно null. Везде, где значение имеет вид, отличный от `Option`, вы *можете* смело рассчитывать на то, что значение не равно null. Это продуманное расчетное решение в Rust, ограничивающее распространение null и увеличивающее безопасность кода на Rust. +Устранение риска ошибочного предположения касательно не-null значения помогает вам быть более уверенным в своей рукописи. Чтобы иметь значение, которое может быть null, вы должны явно описать вид этого значения с помощью `Option`. Затем, когда вы используете это значение, вы обязаны явно обрабатывать случай, когда значение равно null. Везде, где значение имеет вид, отличный от `Option`, вы *можете* смело рассчитывать на то, что значение не равно null. Это продуманное расчетное решение в Ржавчине ограничивающее распространение null и увеличивающее безопасность рукописи на Ржавчине. -Итак, как же получить значение `T` из исхода `Some`, если у вас на руках есть только предмет `Option`, и как можно его, вообще, использовать? Перечисление `Option` имеет большое количество способов, полезных в различных случаейх; вы можете ознакомиться с ними в [его документации]. Знакомство с способами перечисления `Option` будет чрезвычайно полезным в вашем путешествии с Rust. +Итак, как же получить значение `T` из исхода `Some`, если у вас на руках есть только предмет `Option`, и как можно его, вообще, использовать? Перечисление `Option` имеет большое количество способов, полезных в различных случаях; вы можете ознакомиться с ними в [его пособия]. Знакомство с способами перечисления `Option` будет чрезвычайно полезным в вашем путешествии с Ржавчина. -В общем случае, чтобы использовать значение `Option`, нужен код, который будет обрабатывать все исходы перечисления `Option`. Вам понадобится некоторый код, который будет работать только тогда, когда у вас есть значение `Some(T)`, и этому коду разрешено использовать внутри `T`. Также вам понадобится другой код, который будет работать, если у вас есть значение `None`, и у этого кода не будет доступного значения `T`. Выражение match — это устройство управления потоком выполнения программы, которая делает именно это при работе с перечислениями: она запускает разный код в зависимости от того, какой исход перечисления имеется, и этот код может использовать данные, находящиеся внутри совпавшего исхода. +В общем случае, чтобы использовать значение `Option`, нужен рукопись, который будет обрабатывать все исходы перечисления `Option`. Вам понадобится некоторый рукопись, который будет работать только тогда, когда у вас есть значение `Some(T)`, и этому рукописи разрешено использовать внутри `T`. Также вам понадобится другой рукопись, который будет работать, если у вас есть значение `None`, и у этого рукописи не будет доступного значения `T`. Выражение match — это устройство управления потоком выполнения программы, которая делает именно это при работе с перечислениями: она запускает разный рукопись в зависимости от того, какой исход перечисления имеется, и этот рукопись может использовать данные, находящиеся внутри совпавшего исхода. [определено встроенной библиотекой следующим образом:]: ../std/option/enum.Option.html -[его документации]: ../std/option/enum.Option.html \ No newline at end of file +[его пособия]: ../std/option/enum.Option.html \ No newline at end of file diff --git a/rustbook-ru/src/ch06-02-match.md b/rustbook-ru/src/ch06-02-match.md index d7903d17e..cdaea14d7 100644 --- a/rustbook-ru/src/ch06-02-match.md +++ b/rustbook-ru/src/ch06-02-match.md @@ -4,9 +4,9 @@ ## Управляющая устройство `match` -В Ржавчина есть чрезвычайно мощный рычаг управления потоком, именуемый `match`, который позволяет сравнивать значение с различными образцами и затем выполнять код в зависимости от того, какой из образцов совпал. Образцы могут состоять из записанных значений, имён переменных, подстановочных знаков и многого другого; в главе 18 рассматриваются все различные виды образцов и то, что они делают. Сила match заключается в выразительности образцов и в том, что сборщик проверяет, что все возможные случаи обработаны. +В Ржавчине есть чрезвычайно мощный рычаг управления потоком, именуемый `match`, который позволяет сравнивать значение с различными образцами и затем выполнять рукопись в зависимости от того, какой из образцов совпал. Образцы могут состоять из записанных значений, имён переменных, подстановочных знаков и многого другого; в главе 18 рассматриваются все различные виды образцов и то, что они делают. Сила match заключается в выразительности образцов и в том, что сборщик проверяет, что все возможные случаи обработаны. -Думайте о выражении `match` как о машине для сортировки монет: монеты скользят по дорожке с различными по размеру отверстиями, и каждая монета падает через первое попавшееся отверстие, в которое она поместилась. Таким же образом значения проходят через каждый образец в `match`, и при первом же "подходящем" образце значение попадает в соответствующий раздел кода, который будет использоваться во время выполнения. +Думайте о выражении `match` как о машине для сортировки монет: монеты скользят по дорожке с различными по размеру отверстиями, и каждая монета падает через первое попавшееся отверстие, в которое она поместилась. Таким же образом значения проходят через каждый образец в `match`, и при первом же "подходящем" образце значение попадает в соответствующий раздел рукописи, который будет использоваться во время выполнения. Говоря о монетах, давайте используем их в качестве примера, используя `match`! Для этого мы напишем функцию, которая будет получать на вход неизвестную монету Соединённых Штатов и, подобно счётной машине, определять, какая это монета, и возвращать её стоимость в центах, как показано в приложении 6-3. @@ -18,13 +18,13 @@ Давайте разберём `match` в функции `value_in_cents`. Сначала пишется ключевое слово `match`, затем следует выражение, которое в данном случае является значением `coin`. Это выглядит очень похоже на условное выражение, используемое в `if`, но есть большая разница: с `if` выражение должно возвращать булево значение, а здесь это может быть любой вид. Вид `coin` в этом примере — перечисление вида Coin, объявленное в строке 1. -Далее идут ветки `match`. Ветки состоят из двух частей: образец и некоторый код. Здесь первая ветка имеет образец, который является значением `Coin::Penny`, затем идёт оператор `=>`, который разделяет образец и код для выполнения. Код в этом случае - это просто значение `1`. Каждая ветка отделяется от последующей при помощи запятой. +Далее идут ветки `match`. Ветки состоят из двух частей: образец и некоторый рукопись. Здесь первая ветка имеет образец, который является значением `Coin::Penny`, затем идёт приказчик `=>`, который разделяет образец и рукопись для выполнения. Рукопись в этом случае - это просто значение `1`. Каждая ветка отделяется от последующей при помощи запятой. -Когда выполняется выражение `match`, оно сравнивает полученное значение с образцом каждого ответвления по порядку. Если образец совпадает со значением, то выполняется код, связанный с этим образцом. Если этот образец не соответствует значению, то выполнение продолжается со следующей ветки, так же, как в автомате по сортировке монет. У нас может быть столько ответвлений, сколько нужно: в приложении 6-3 наш `match` состоит из четырёх ответвлений. +Когда выполняется выражение `match`, оно сравнивает полученное значение с образцом каждого ответвления по порядку. Если образец совпадает со значением, то выполняется рукопись, связанный с этим образцом. Если этот образец не соответствует значению, то выполнение продолжается со следующей ветки, так же, как в устройстве по сортировке монет. У нас может быть столько ответвлений, сколько нужно: в приложении 6-3 наш `match` состоит из четырёх ответвлений. -Код, связанный с каждым ответвлением, является выражением, а полученное значение выражения в соответствующем ответвлении — это значение, которое возвращается для всего выражения `match`. +Рукопись, связанный с каждым ответвлением, является выражением, а полученное значение выражения в соответствующем ответвлении — это значение, которое возвращается для всего выражения `match`. -Обычно фигурные скобки не используются, если код совпадающей ветви невелик, как в приложении 6-3, где каждая ветвь просто возвращает значение. Если вы хотите выполнить несколько строк кода в одной ветви, вы должны использовать фигурные скобки, а запятая после этой ветви необязательна. Например, следующий код печатает "Lucky penny!" каждый раз, когда способ вызывается с `Coin::Penny`, но при этом он возвращает последнее значение раздела - `1`: +Обычно фигурные скобки не используются, если рукопись совпадающей ветви невелик, как в приложении 6-3, где каждая ветвь просто возвращает значение. Если вы хотите выполнить несколько строк рукописи в одной ветви, вы должны использовать фигурные скобки, а запятая после этой ветви необязательна. Например, следующий рукопись печатает "Lucky penny!" каждый раз, когда способ вызывается с `Coin::Penny`, но при этом он возвращает последнее значение раздела - `1`: ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-08-match-arm-multiple-lines/src/main.rs:here}} @@ -34,7 +34,7 @@ Есть ещё одно полезное качество у веток в выражении match: они могут привязываться к частям тех значений, которые совпали с образцом. Благодаря этому можно извлекать значения из исходов перечисления. -В качестве примера, давайте изменим один из исходов перечисления так, чтобы он хранил в себе данные. С 1999 по 2008 год Соединённые Штаты чеканили 25 центов с различным внешнем видом на одной стороне для каждого из 50 штатов. Ни одна другая монета не получила внешнего видаштата, только четверть доллара имела эту дополнительную особенность. Мы можем добавить эту сведения в наш `enum` путём изменения исхода `Quarter` и включить в него значение `UsState`, как сделано в приложении 6-4. +В качестве примера, давайте изменим один из исходов перечисления так, чтобы он хранил в себе данные. С 1999 по 2008 год Соединённые Штаты чеканили 25 центов с различным внешнем видом на одной стороне для каждого из 50 штатов. Ни одна другая монета не получила внешнего вида штата, только четверть доллара имела эту дополнительную особенность. Мы можем добавить эти сведения в наш `enum` путём изменения исхода `Quarter` и включить в него значение `UsState`, как сделано в приложении 6-4. ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-04/src/main.rs:here}} @@ -44,7 +44,7 @@ Представьте, что ваш друг пытается собрать четвертаки всех 50 штатов. Сортируя монеты по виду, мы также будем сообщать название штата, к которому относится каждый четвертак, чтобы, если у нашего друга нет такой монеты, он мог добавить её в свою собрание. -В выражении match для этого кода мы добавляем переменную с именем `state` в образец, который соответствует значениям исхода `Coin::Quarter`. Когда `Coin::Quarter` совпадёт с образцом, переменная `state` будет привязана к значению штата этого четвертака. Затем мы сможем использовать `state` в коде этой ветки, вот так: +В выражении match для этого рукописи мы добавляем переменную с именем `state` в образец, который соответствует значениям исхода `Coin::Quarter`. Когда `Coin::Quarter` совпадёт с образцом, переменная `state` будет привязана к значению штата этого четвертака. Затем мы сможем использовать `state` в рукописи этой ветки, вот так: ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-09-variable-in-pattern/src/main.rs:here}} @@ -58,7 +58,7 @@ Допустим, мы хотим написать функцию, которая принимает `Option` и если есть значение внутри, то добавляет 1 к существующему значению. Если значения нет, то функция должна возвращать значение `None` и не пытаться выполнить какие-либо действия. -Такую функцию довольно легко написать благодаря выражению `match`, код будет выглядеть как в приложении 6-5. +Такую функцию довольно легко написать благодаря выражению `match`, рукопись будет выглядеть как в приложении 6-5. ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-05/src/main.rs:here}} @@ -78,7 +78,7 @@ {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-05/src/main.rs:second_arm}} ``` -Совпадает ли `Some(5)` с образцом `Some(i)`? Да, это так! У нас такой же исход. Тогда переменная `i` привязывается к значению, содержащемуся внутри `Some`, поэтому `i` получает значение `5`. Затем выполняется код сопряженный для данного ответвления, поэтому мы добавляем 1 к значению `i` и создаём новое значение `Some` со значением `6` внутри. +Совпадает ли `Some(5)` с образцом `Some(i)`? Да, это так! У нас такой же исход. Тогда переменная `i` привязывается к значению, содержащемуся внутри `Some`, поэтому `i` получает значение `5`. Затем выполняется рукопись сопряженный для данного ответвления, поэтому мы добавляем 1 к значению `i` и создаём новое значение `Some` со значением `6` внутри. Теперь давайте рассмотрим второй вызов `plus_one` в приложении 6-5, где `x` является `None`. Мы входим в выражение `match` и сравниваем значение с первым ответвлением: @@ -88,7 +88,7 @@ Оно совпадает! Для данной ветки образец (None) не подразумевает наличие какого-то значения к которому можно было бы что-то добавить, поэтому программа останавливается и возвращает значение которое находится справа от `=>` - т.е. `None`. Так как образец первой ветки совпал, то никакие другие образцы веток не сравниваются. -Соединение `match` и перечислений полезно во многих случаейх. Вы часто будете видеть подобную сочетание в коде на Rust: сделать сопоставление значений перечисления используя `match`, привязать переменную к данным внутри значения, выполнить код на основе привязанных данных. Сначала это может показаться немного сложным, но как только вы привыкнете, то захотите чтобы такая возможность была бы во всех языках. Это неизменно любимый пользователями приём. +Соединение `match` и перечислений полезно во многих случаях. Вы часто будете видеть подобную сочетание в рукописи на Ржавчине: сделать сопоставление значений перечисления используя `match`, привязать переменную к данным внутри значения, выполнить рукопись на основе привязанных данных. Сначала это может показаться немного сложным, но как только вы привыкнете, то захотите чтобы такая возможность была бы во всех языках. Это неизменно любимый пользователями приём. ### Match охватывает все исходы значения @@ -98,29 +98,29 @@ {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-10-non-exhaustive-match/src/main.rs:here}} ``` -Мы не обработали исход `None`, поэтому этот код вызовет изъян в программе. К счастью, Ржавчина знает и умеет ловить такой случай. Если мы попытаемся собрать такой код, мы получим ошибку сборки: +Мы не обработали исход `None`, поэтому этот рукопись вызовет изъян в программе. К счастью, Ржавчина знает и умеет ловить такой случай. Если мы попытаемся собрать такой рукопись, мы получим ошибку сборки: ```console {{#include ../listings/ch06-enums-and-pattern-matching/no-listing-10-non-exhaustive-match/output.txt}} ``` -Rust знает, что мы не описали все возможные случаи, и даже знает, какой именно из образцов мы упуисполнения! Сопоставления в Ржавчина являются *исчерпывающими*: мы должны покрыть все возможные исходы, чтобы код был правильным. Особенно в случае `Option`, когда Ржавчина не даёт нам забыть обработать явным образом значение `None`, тем самым он защищает нас от предположения, что у нас есть значение, в то время как у нас может быть и null, что делает невозможным совершить ошибку на миллиард долларов, о которой говорилось ранее. +Ржавчина знает, что мы не описали все возможные случаи, и даже знает, какой именно из образцов мы упуисполнения! Сопоставления в Ржавчине являются *исчерпывающими*: мы должны покрыть все возможные исходы, чтобы рукопись был правильным. Особенно в случае `Option`, когда Ржавчина не даёт нам забыть обработать явным образом значение `None`, тем самым он защищает нас от предположения, что у нас есть значение, в то время как у нас может быть и null, что делает невозможным совершить ошибку на миллиард долларов, о которой говорилось ранее. ### Гибкие образцы и заполнитель `_` -Используя перечисления, мы также можем выполнять особые действия для нескольких определённых значений, а для всех остальных значений выполнять одно действие по умолчанию. Представьте, что мы выполняем игру, в которой при выпадении 3 игрок не двигается, а получает новую нового образца шляпу. Если выпадает 7, игрок теряет шляпу. При всех остальных значениях ваш игрок перемещается на столько-то мест на игровом поле. Вот `match`, выполняющий эту логику, в котором итог броска костей жёстко закодирован, а не является случайным значением, а вся остальная логика представлена функциями без тел, поскольку их выполнение не входит в рамки данного примера: +Используя перечисления, мы также можем выполнять особые действия для нескольких определённых значений, а для всех остальных значений выполнять одно действие по умолчанию. Представьте, что мы выполняем игру, в которой при выпадении 3 игрок не двигается, а получает новую нового образца шляпу. Если выпадает 7, игрок теряет шляпу. При всех остальных значениях ваш игрок перемещается на столько-то мест на игровом поле. Вот `match`, выполняющий эту ход мыслей, в котором итог броска костей жёстко закодирован, а не является случайным значением, а вся остальная ход мыслей представлена функциями без тел, поскольку их выполнение не входит в рамки данного примера: ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-15-binding-catchall/src/main.rs:here}} ``` -Для первых двух веток образцами являются записанные значения 3 и 7. Для последней ветки, которая охватывает все остальные возможные значения, образцом является переменная, которую мы решили назвать `other`. Код, выполняемый для ветки `other`, использует эту переменную, передавая её в функцию `move_player`. +Для первых двух веток образцами являются записанные значения 3 и 7. Для последней ветки, которая охватывает все остальные возможные значения, образцом является переменная, которую мы решили назвать `other`. Рукопись, выполняемый для ветки `other`, использует эту переменную, передавая её в функцию `move_player`. -Этот код собирается, даже если мы не перечислили все возможные значения `u8`, потому что последний образец будет соответствовать всем значениям, не указанным в определенном списке. Этот гибкий образец удовлетворяет требованию, что соответствие должно быть исчерпывающим. Обратите внимание, что мы должны поместить ветку с гибким образцом последней, потому что образцы оцениваются по порядку. Ржавчина предупредит нас, если мы добавим ветки после гибкого образца, потому что эти последующие ветки никогда не будут выполняться! +Этот рукопись собирается, даже если мы не перечислили все возможные значения `u8`, потому что последний образец будет соответствовать всем значениям, не указанным в определенном списке. Этот гибкий образец удовлетворяет требованию, что соответствие должно быть исчерпывающим. Обратите внимание, что мы должны поместить ветку с гибким образцом последней, потому что образцы оцениваются по порядку. Ржавчина предупредит нас, если мы добавим ветки после гибкого образца, потому что эти последующие ветки никогда не будут выполняться! -В Ржавчина также есть образец, который можно использовать, когда мы не хотим использовать значение в гибком образце: `_`, который является особым образцом, который соответствует любому значению и не привязывается к этому значению. Это говорит Rust, что мы не собираемся использовать это значение, поэтому Ржавчина не будет предупреждать нас о неиспользуемой переменной. +В Ржавчине также есть образец, который можно использовать, когда мы не хотим использовать значение в гибком образце: `_`, который является особым образцом, который соответствует любому значению и не привязывается к этому значению. Это говорит Ржавчина, что мы не собираемся использовать это значение, поэтому Ржавчина не будет предупреждать нас о неиспользуемой переменной. -Давайте изменим правила игры так: если выпадает что-то, кроме 3 или 7, нужно бросить ещё раз. Нам не нужно использовать значение в этом случае, поэтому мы можем изменить наш код, чтобы использовать `_` вместо переменной с именем `other`: +Давайте изменим правила игры так: если выпадает что-то, кроме 3 или 7, нужно бросить ещё раз. Нам не нужно использовать значение в этом случае, поэтому мы можем изменить нашу рукопись, чтобы использовать `_` вместо переменной с именем `other`: ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-16-underscore-catchall/src/main.rs:here}} @@ -128,15 +128,15 @@ Rust знает, что мы не описали все возможные сл Этот пример также удовлетворяет требованию исчерпывающей полноты, поскольку мы явно пренебрегаем все остальные значения в последней ветке; мы ничего не забыли. -Если мы изменим правила игры ещё раз, чтобы в ваш ход не происходило ничего другого, если вы бросаете не 3 или 7, мы можем выразить это, используя единичное значение (пустой вид упорядоченного ряда, о котором мы упоминали в разделе ["Упорядоченные ряды"]) в качестве кода, который идёт вместе с веткой `_`: +Если мы изменим правила игры ещё раз, чтобы в ваш ход не происходило ничего другого, если вы бросаете не 3 или 7, мы можем выразить это, используя единичное значение (пустой вид упорядоченного ряда, о котором мы упоминали в разделе ["Упорядоченные ряды"]) в качестве рукописи, который идёт вместе с веткой `_`: ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-17-underscore-unit/src/main.rs:here}} ``` -Здесь мы явно говорим Rust, что не собираемся использовать никакое другое значение, которое не соответствует образцам в предыдущих ветках, и не хотим запускать никакой код в этом случае. +Здесь мы явно говорим Ржавчина, что не собираемся использовать никакое другое значение, которое не соответствует образцам в предыдущих ветках, и не хотим запускать никакую рукопись в этом случае. -Подробнее о образцах и совпадениях мы поговорим в [Главе 18]. Пока же мы перейдём к правилам написания `if let`, который может быть полезен в случаейх, когда выражение `match` слишком многословно. +Подробнее о образцах и совпадениях мы поговорим в [Главе 18]. Пока же мы перейдём к правилам написания `if let`, который может быть полезен в случаях, когда выражение `match` слишком многословно. ["Упорядоченные ряды"]: ch03-02-data-types.html#the-tuple-type diff --git a/rustbook-ru/src/ch06-03-if-let.md b/rustbook-ru/src/ch06-03-if-let.md index 0e5e78e01..cb45ae43a 100644 --- a/rustbook-ru/src/ch06-03-if-let.md +++ b/rustbook-ru/src/ch06-03-if-let.md @@ -1,28 +1,28 @@ ## Краткое управление потоком выполнения с `if let` -правила написания `if let` позволяет ссоединенять `if` и `let` в менее многословную устройство, и затем обработать значения соответствующе только одному образцу, одновременно пренебрегая все остальные. Рассмотрим программу в приложении 6-6, которая обрабатывает сопоставление значения `Option` в переменной `config_max`, но хочет выполнить код только в том случае, если значение является исходом `Some`. +правила написания `if let` позволяет ссоединенять `if` и `let` в менее многословную устройство, и затем обработать значения соответствующе только одному образцу, одновременно пренебрегая все остальные. Рассмотрим программу в приложении 6-6, которая обрабатывает сопоставление значения `Option` в переменной `config_max`, но хочет выполнить рукопись только в том случае, если значение является исходом `Some`. ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/listing-06-06/src/main.rs:here}} ``` -Приложение 6-6. Выражение match, которое выполнит код только при значении равном Some +Приложение 6-6. Выражение match, которое выполнит рукопись только при значении равном Some -Если значение равно `Some`, мы распечатываем значение в исходе `Some`, привязывая значение к переменной `max` в образце. Мы не хотим ничего делать со значением `None`. Чтобы удовлетворить выражение `match`, мы должны добавить `_ => ()` после обработки первой и единственной ветки, и добавление образцового кода раздражает. +Если значение равно `Some`, мы распечатываем значение в исходе `Some`, привязывая значение к переменной `max` в образце. Мы не хотим ничего делать со значением `None`. Чтобы удовлетворить выражение `match`, мы должны добавить `_ => ()` после обработки первой и единственной ветки, и добавление образцового рукописи раздражает. -Вместо этого, мы могли бы написать это более коротким способом, используя `if let`. Следующий код ведёт себя так же, как выражение `match` в приложении 6-6: +Вместо этого, мы могли бы написать это более коротким способом, используя `if let`. Следующий рукопись ведёт себя так же, как выражение `match` в приложении 6-6: ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-12-if-let/src/main.rs:here}} ``` -правила написания `if let` принимает образец и выражение, разделённые знаком равенства. Он работает так же, как `match`, когда в него на вход передадут выражение и подходящим образцом для этого выражения окажется первая ветка. В данном случае образцом является `Some(max)`, где `max` привязывается к значению внутри `Some`. Затем мы можем использовать `max` в теле раздела `if let` так же, как мы использовали `max` в соответствующей ветке `match`. Код в разделе `if let` не запускается, если значение не соответствует образцу. +правила написания `if let` принимает образец и выражение, разделённые знаком равенства. Он работает так же, как `match`, когда в него на вход передадут выражение и подходящим образцом для этого выражения окажется первая ветка. В данном случае образцом является `Some(max)`, где `max` привязывается к значению внутри `Some`. Затем мы можем использовать `max` в теле раздела `if let` так же, как мы использовали `max` в соответствующей ветке `match`. Рукопись в разделе `if let` не запускается, если значение не соответствует образцу. -Используя `if let` мы меньше печатаем, меньше делаем отступов и меньше получаем образцового кода. Тем не менее, мы теряем полную проверку всех исходов, предоставляемую выражением `match`. Выбор между `match` и `if let` зависит от того, что вы делаете в вашем определенном случае и является ли получение краткости при потере полноты проверки подходящим соглашением. +Используя `if let` мы меньше печатаем, меньше делаем отступов и меньше получаем образцового рукописи. Тем не менее, мы теряем полную проверку всех исходов, предоставляемую выражением `match`. Выбор между `match` и `if let` зависит от того, что вы делаете в вашем определенном случае и является ли получение краткости при потере полноты проверки подходящим соглашением. -Другими словами, вы можете думать о устройства `if let` как о синтаксическом сахаре для `match`, который выполнит код если входное значение будет соответствовать единственному образцу, и пренебрегает все остальные значения. +Другими словами, вы можете думать о устройства `if let` как о связанном сахаре для `match`, который выполнит рукопись если входное значение будет соответствовать единственному образцу, и пренебрегает все остальные значения. -Можно добавлять `else` к `if let`. Разделкода, который находится внутри `else` подобен по смыслу блоку кода ветки связанной с образцом `_` выражения `match` (которое эквивалентно сборной устройства `if let` и `else`). Вспомним объявление перечисления `Coin` в приложении 6-4, где исход `Quarter` также содержит внутри значение штата вида `UsState`. Если бы мы хотели посчитать все монеты не являющиеся четвертями, а для четвертей печатать название штата, то мы могли бы сделать это с помощью выражения `match` таким образом: +Можно добавлять `else` к `if let`. Разделрукописи, который находится внутри `else` подобен по смыслу разделу рукописи ветки связанной с образцом `_` выражения `match` (которое эквивалентно сборной устройства `if let` и `else`). Вспомним объявление перечисления `Coin` в приложении 6-4, где исход `Quarter` также содержит внутри значение штата вида `UsState`. Если бы мы хотели посчитать все монеты не являющиеся четвертями, а для четвертей печатать название штата, то мы могли бы сделать это с помощью выражения `match` таким образом: ```rust {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-13-count-and-announce-match/src/main.rs:here}} @@ -34,12 +34,12 @@ {{#rustdoc_include ../listings/ch06-enums-and-pattern-matching/no-listing-14-count-and-announce-if-let-else/src/main.rs:here}} ``` -Если у вас есть случаей в которой ваша программа имеет логику которая слишком многословна для того чтобы её выражать используя `match`, помните, о том, что также в вашем наборе средств Ржавчина есть `if let`. +Если у вас есть случай в которой ваша программа имеет ход мыслей которая слишком многословна для того чтобы её выражать используя `match`, помните, о том, что также в вашем наборе средств Ржавчине есть `if let`. ## Итоги Мы рассмотрели как использовать перечисления для создания пользовательских видов, которые могут быть одним из наборов перечисляемых значений. Мы показали, как вид `Option` из встроенной библиотеки помогает использовать систему видов для предотвращения ошибок. А когда значения перечисления имеют данные внутри них, можно использовать `match` или `if let`, чтобы извлечь и пользоваться значением, в зависимости от того, сколько случаев нужно обработать. -Теперь ваши программы на Ржавчина могут выражать подходы вашей предметной области, используя устройства и перечисления. Создание и использование пользовательских видов в API обеспечивает типобезопасность: сборщик позаботится о том, чтобы функции получали значения только того вида, который они ожидают. +Теперь ваши программы на Ржавчине могут выражать подходы вашей предметной области, используя устройства и перечисления. Создание и использование пользовательских видов в API обеспечивает типобезопасность: сборщик позаботится о том, чтобы функции получали значения только того вида, который они ожидают. -Чтобы предоставить вашим пользователям хорошо согласованный API, который прост в использовании и предоставляет только то, что нужно вашим пользователям, надо поговорить о звенах в Rust. +Чтобы предоставить вашим пользователям хорошо согласованный API, который прост в использовании и предоставляет только то, что нужно вашим пользователям, надо поговорить о звеньях в Ржавчине. diff --git a/rustbook-ru/src/ch07-00-managing-growing-projects-with-packages-crates-and-modules.md b/rustbook-ru/src/ch07-00-managing-growing-projects-with-packages-crates-and-modules.md index dffe41f90..2c6726ad6 100644 --- a/rustbook-ru/src/ch07-00-managing-growing-projects-with-packages-crates-and-modules.md +++ b/rustbook-ru/src/ch07-00-managing-growing-projects-with-packages-crates-and-modules.md @@ -1,14 +1,14 @@ # Управление растущими делами с помощью дополнений, ящиков и звеньев -По мере роста кодовой хранилища ваших программ, создание дела будет иметь большое значение, ведь отслеживание всей программы в голове будет становиться всё более сложным. Объединенияя связанные функции и разделяя код по основным возможностям (фичам, feature), вы делаете более прозрачным понимание о том, где искать код выполняющий определённую функцию и где стоит вносить изменения для того чтобы изменить её поведение. +По мере роста кодовой хранилища ваших программ, создание дела будет иметь большое значение, ведь отслеживание всей программы в голове будет становиться всё более сложным. Объединенияя связанные функции и разделяя рукопись по основным возможностям (фичам, feature), вы делаете более прозрачным понимание о том, где искать рукопись выполняющий определённую функцию и где стоит вносить изменения для того чтобы изменить её поведение. -Программы, которые мы писали до сих пор, были в одном файле одного звена. По мере роста дела, мы можем создавать код иначе, разделив его на несколько звеньев и несколько файлов. Дополнение может содержать несколько двоичных ящиков и дополнительно один ящик библиотеки. Дополнение может включать в себя много двоичных ящиков и дополнительно один библиотечный ящик. По мере роста дополнения вы можете извлекать части программы в отдельные ящики, которые затем станут внешними зависимостями для основного кода нашей программы. Эта глава охватывает все эти техники. В свою очередь для очень крупных дел, состоящих из набора взаимосвязанных дополнений развивающихся вместе, Cargo предоставляет рабочие пространства, *workspaces*, их мы рассмотрим за пределами данной главы, в разделе ["Рабочие пространства Cargo"] Главы 14. +Программы, которые мы писали до сих пор, были в одном файле одного звена. По мере роста дела, мы можем создавать рукопись иначе, разделив его на несколько звеньев и несколько файлов. Дополнение может содержать несколько двоичных ящиков и дополнительно один ящик библиотеки. Дополнение может включать в себя много двоичных ящиков и дополнительно один библиотечный ящик. По мере роста дополнения вы можете извлекать части программы в отдельные ящики, которые затем станут внешними зависимостями для основного рукописи нашей программы. Эта глава охватывает все эти техники. В свою очередь для очень крупных дел, состоящих из набора взаимосвязанных дополнений развивающихся вместе, Cargo предоставляет рабочие пространства, *workspaces*, их мы рассмотрим за пределами данной главы, в разделе ["Рабочие пространства Cargo"] Главы 14. -Мы также обсудим инкапсуляцию подробностей, которая позволяет использовать код снова на более высоком уровне: единожды выполнив какую-то действие, другой код может вызывать этот код через открытый внешняя оболочка, не зная как работает выполнение. То, как вы пишете код, определяет какие части общедоступны для использования другим кодом и какие части являются закрытыми деталями выполнения для которых вы оставляете право на изменения только за собой. Это ещё один способ ограничить количество подробностей, которые вы должны держать в голове. +Мы также обсудим инкапсуляцию подробностей, которая позволяет использовать рукопись снова на более высоком уровне: единожды выполнив какую-то действие, другой рукопись может вызывать этот рукопись через открытый внешняя оболочка, не зная как работает выполнение. То, как вы пишете рукопись, определяет какие части общедоступны для использования другим рукописью и какие части являются закрытыми деталями выполнения для которых вы оставляете право на изменения только за собой. Это ещё один способ ограничить количество подробностей, которые вы должны держать в голове. -Связанное понятие - это область видимости: вложенный среда в котором написан код имеющий набор имён, которые определены «в текущей области видимости». При чтении, письме и сборки кода, программистам и сборщикам необходимо знать, относится ли определенное имя в определённом месте к переменной, к функции, к устройстве, к перечислению, к звену, к постоянных значенийе или другому элементу и что означает этот элемент. Можно создавать области видимости и изменять какие имена входят или выходят за их рамки. Нельзя иметь два элемента с тем же именем в одной области; есть доступные средства для разрешения несоответствий имён. +Связанное понятие - это область видимости: вложенный среда в котором написан рукопись имеющий набор имён, которые определены «в текущей области видимости». При чтении, письме и сборки рукописи, программистам и сборщикам необходимо знать, относится ли определенное имя в определённом месте к переменной, к функции, к устройстве, к перечислению, к звену, к постоянных значенийе или другому элементу и что означает этот элемент. Можно создавать области видимости и изменять какие имена входят или выходят за их рамки. Нельзя иметь два элемента с тем же именем в одной области; есть доступные средства для разрешения несоответствий имён. -Rust имеет ряд функций, которые позволяют управлять согласованием кода, в том числе управлять тем какие подробности открыты, какие подробности являются частными, какие имена есть в каждой области вашей программы. Эти функции иногда вместе именуемые *состоящей из звеньев системой* включают в себя: +Ржавчина имеет ряд функций, которые позволяют управлять согласованием рукописи, в том числе управлять тем какие подробности открыты, какие подробности являются частными, какие имена есть в каждой области вашей программы. Эти функции иногда вместе именуемые *состоящей из звеньев системой* включают в себя: - **Дополнения:** Возможности Cargo позволяющий собирать, проверять и делиться ящиками - **Ящики:** Дерево звеньев, которое создаёт библиотечный или исполняемый файл diff --git a/rustbook-ru/src/ch07-00-modules.md b/rustbook-ru/src/ch07-00-modules.md index ffa368268..c7b8b0a7c 100644 --- a/rustbook-ru/src/ch07-00-modules.md +++ b/rustbook-ru/src/ch07-00-modules.md @@ -1,20 +1,20 @@ -# Использование звеньев для согласования и многократного использования кода +# Использование звеньев для согласования и многократного использования рукописи -Когда вы только начинаете писать программу, ваш код может совершенно свободно +Когда вы только начинаете писать программу, ваша рукопись может совершенно свободно поместиться в функции `main`. Но по мере создания полезных возможностей, добавления -все большего и большего количества функций вам понадобиться согласовать код в -удобные для чтения, объединения устройства. Для этого в Ржавчина есть система звеньев. +все большего и большего количества функций вам понадобиться согласовать рукопись в +удобные для чтения, объединения устройства. Для этого в Ржавчине есть система звеньев. -Также, как вы используете код программы в функциях, вы можете использовать функции в -звенах. Звено представляет собой пространство имён, в которое могут входить +Также, как вы используете рукопись программы в функциях, вы можете использовать функции в +звеньях. Звено представляет собой пространство имён, в которое могут входить различные функции и виды. Вы также можете управлять видимостью внутри звена. Общее описание возможностей звеньев: -* Ключевое слово `mod` объявляет звено. Код следующий после объявления звена считается +* Ключевое слово `mod` объявляет звено. Рукопись следующий после объявления звена считается включенным в него. * По умолчанию, звено закрыт и чтобы извне был доступ к его элементам это надо указать -в коде. Для этого используется ключевое слово `pub`. +в рукописи. Для этого используется ключевое слово `pub`. * Ключевое слов `use` даёт возможность использовать в тексте программы возможности звена. diff --git a/rustbook-ru/src/ch07-01-mod-and-the-filesystem.md b/rustbook-ru/src/ch07-01-mod-and-the-filesystem.md index ca64074a2..13b0d6d1f 100644 --- a/rustbook-ru/src/ch07-01-mod-and-the-filesystem.md +++ b/rustbook-ru/src/ch07-01-mod-and-the-filesystem.md @@ -1,12 +1,12 @@ ## `mod` и файловая система Мы начнём создавать наш пример использования звена. Создадим дело библиотеки -кода. +рукописи. Создадим основные разделы нашей библиотеки, которая будет предоставлять полезные возможности использования сетевых технологий. Назовём нашу библиотеку `communicator`. -По умолчанию Cargo создаёт библиотеки кода. Если при создании нового дела мы -не установим флаг `--bin`, то будет создана библиотека: +По умолчанию Cargo создаёт библиотеки рукописи. Если при создании нового дела мы +не установим клеймо `--bin`, то будет создана библиотека: ```text $ cargo new communicator @@ -27,16 +27,16 @@ mod tests { } ``` Cargo создаёт пустой проверка, чтобы показать как можно проверять возможности библиотеки. -Мы изучим использование синтаксических устройств `#[]` и `mod tests` в последующей +Мы изучим использование связанных устройств `#[]` и `mod tests` в последующей разделы "Использование `super` для доступа к родительскому звену" этой главы. -Сейчас же мы не будем использовать данный возможности. поэтому просто удалим этот код. +Сейчас же мы не будем использовать данный возможности. поэтому просто удалим этот рукопись. Т.к. у нас нет файла *src/main.rs*, нечего запускать на выполнение с помощью приказы `cargo run`. В тоже время мы можем воспользоваться приказом `cargo build` для сборки нашей библиотеки. -Мы рассмотрим различные возможности согласования кода нашей библиотеки. +Мы рассмотрим различные возможности согласования рукописи нашей библиотеки. ### Определение звена @@ -78,14 +78,14 @@ mod client { *src/lib.rs*
Теперь у нас есть описание двух функций, которые могут быть вызваны с помощью -синтаксических устройств `network::connect` и `client::connect`. +связанных устройств `network::connect` и `client::connect`. У каждой из этих функций могут быть различные полезные возможности, но у них нет между собой никакого несоответствия имён. В этом случае, если мы создаём библиотеку, файл *src/lib.rs* хранит точку доступа к библиотеке. Также мы можем создать звено в файле *src/main.rs* для какой-либо двоичной программы. Очень важная особенностью звеньев - они -могут быть вложенными. Это весьма удобно для логической согласования кода. +могут быть вложенными. Это весьма удобно для разумной согласования рукописи. Пример 7-2: Filename: src/lib.rs @@ -107,7 +107,7 @@ mod network { Теперь у нас есть две разные функции `network::connect` и `network::client::connect`. Каждая из которых находится в своём пространстве имён. -Теперь создание нашего кода имеет вот такую устройство: +Теперь создание нашего рукописи имеет вот такую устройство: ```text communicator @@ -123,12 +123,12 @@ communicator └── client ``` -Логическая создание кода зависит от ваших задач. +Разумная создание рукописи зависит от ваших задач. ### Размещение звеньев по нескольким файлам Состоящая из звеньев устройства похожа на файловую систему. Мы можем использовать состоящую из звеньев -систему для хранения кода в разных файлах. Рассмотрим пример 7-3: +систему для хранения рукописи в разных файлах. Рассмотрим пример 7-3: Filename: src/lib.rs @@ -162,9 +162,9 @@ communicator ``` Если звенья имеют множество функций и эти функции длинные, было бы удобно разделить -такой код на несколько файлов. +такой рукопись на несколько файлов. -Сначала заменим код звена `client` на декларацию звена: +Сначала заменим рукопись звена `client` на декларацию звена: Filename: src/lib.rs @@ -308,7 +308,7 @@ note: ... or maybe `use` the module `server` instead of possibly redeclaring it | ^^^^^^ ``` -Код 7-4: Ошибка при переносе кода вложенного звена `server` +Рукопись 7-4: Ошибка при переносе рукописи вложенного звена `server` в файл *src/server.rs* Сборщик предлагает решение: @@ -329,9 +329,9 @@ note: maybe move this module `network` to its own directory via Список правил: -* Если звено `foo` не имеет подчиненных звеньев, вы можете сохранить код звена в +* Если звено `foo` не имеет подчиненных звеньев, вы можете сохранить рукопись звена в файл *foo.rs*. -* Если звено `foo` имеет подзвено, вы должны перенести код звена в файл *foo/mod.rs* +* Если звено `foo` имеет подзвено, вы должны перенести рукопись звена в файл *foo/mod.rs* Это правило применяется рекурсивно. Если звено с именем `foo` имеет подзвено `bar` и `bar` не имеет подзвеньев, то у вас получится вот такая устройства @@ -346,4 +346,4 @@ note: maybe move this module `network` to its own directory via Звенья должны быть определены в своих файлах используя ключевое слово `mod`. Далее, мы поговорим о изменителье доступа `pub` и устраним сообщения о неполадках -в коде. +в рукописи. diff --git a/rustbook-ru/src/ch07-01-packages-and-crates.md b/rustbook-ru/src/ch07-01-packages-and-crates.md index c5bcd09a0..a9efff9ef 100644 --- a/rustbook-ru/src/ch07-01-packages-and-crates.md +++ b/rustbook-ru/src/ch07-01-packages-and-crates.md @@ -2,15 +2,15 @@ Первые части состоящей из звеньев системы, которые мы рассмотрим — это дополнения и ящики. -*Ящик* — это наименьший размер кода, который сборщик Ржавчина рассматривает за раз. Даже если вы запустите `rustc` вместо `cargo` и передадите один файл с исходным кодом (как мы уже делали в разделе «Написание и запуск программы на Rust» Главы 1), сборщик считает этот файл ящиком. Ящики могут содержать звенья, и звенья могут быть определены в других файлах, которые собираются вместе с ящиком, как мы увидим в следующих разделах. +*Ящик* — это наименьший размер рукописи, который сборщик Ржавчина рассматривает за раз. Даже если вы запустите `rustc` вместо `cargo` и передадите один файл с исходной рукописью (как мы уже делали в разделе «Написание и запуск программы на Rust» Главы 1), сборщик считает этот файл ящиком. Ящики могут содержать звенья, и звенья могут быть определены в других файлах, которые собираются вместе с ящиком, как мы увидим в следующих разделах. -Ящик может быть одним из двух видов: двоичный ящик или библиотечный ящик. *Бинарные ящики* — это программы, которые вы можете собрать в исполняемые файлы, которые вы можете запускать, например программу приказной строки или сервер. У каждого двоичного ящика должна быть функция с именем `main`, которая определяет, что происходит при запуске исполняемого файла. Все ящики, которые мы создали до сих пор, были двоичными ящиками. +Ящик может быть одним из двух видов: двоичный ящик или библиотечный ящик. *Бинарные ящики* — это программы, которые вы можете собрать в исполняемые файлы, которые вы можете запускать, например программу приказной строки или отделеный вычислитель. У каждого двоичного ящика должна быть функция с именем `main`, которая определяет, что происходит при запуске исполняемого файла. Все ящики, которые мы создали до сих пор, были двоичными ящиками. *Библиотечные ящики* не имеют функции `main` и не собираются в исполняемый файл. Вместо этого они определяют возможность, предназначенную для совместного использования другими делами. Например, ящик `rand`, который мы использовали в [Главе 2] обеспечивает возможность, которая порождает случайные числа. В большинстве случаев, когда Rustaceans говорят «ящик», они имеют в виду библиотечный ящик, и они используют «ящик» взаимозаменяемо с общей подходом программирования «библиотека». *Корневой звено ящика* — это исходный файл, из которого сборщик Ржавчина начинает собирать корневой звено вашего ящика (мы подробно объясним звенья в разделе [«Определение звеньев для управления видимости и закрытости»]). -*Дополнение* — это набор из одного или нескольких ящиков, предоставляющий набор возможности. Дополнение содержит файл *Cargo.toml*, в котором описывается, как собирать эти ящики. На самом деле Cargo — это дополнение, содержащий двоичный ящик для средства приказной строки, который вы использовали для создания своего кода. Дополнение Cargo также содержит библиотечный ящик, от которого зависит двоичный ящик. Другие дела тоже могут зависеть от библиотечного ящика Cargo, чтобы использовать ту же логику, что и средство приказной строки Cargo. +*Дополнение* — это набор из одного или нескольких ящиков, предоставляющий набор возможности. Дополнение содержит файл *Cargo.toml*, в котором описывается, как собирать эти ящики. На самом деле Cargo — это дополнение, содержащий двоичный ящик для средства приказной строки, который вы использовали для создания своего рукописи. Дополнение Cargo также содержит библиотечный ящик, от которого зависит двоичный ящик. Другие дела тоже могут зависеть от библиотечного ящика Cargo, чтобы использовать ту же ход мыслей, что и средство приказной строки Cargo. Дополнение может содержать сколько угодно двоичных ящиков, но не более одного библиотечного ящика. Дополнение должен содержать хотя бы один ящик, библиотечный или двоичный. diff --git a/rustbook-ru/src/ch07-02-controlling-visibility-with-pub.md b/rustbook-ru/src/ch07-02-controlling-visibility-with-pub.md index 27ceb2a2b..33e44c41c 100644 --- a/rustbook-ru/src/ch07-02-controlling-visibility-with-pub.md +++ b/rustbook-ru/src/ch07-02-controlling-visibility-with-pub.md @@ -1,7 +1,7 @@ ## Управление доступом с помощью ключевого слова `pub` -Мы исправили ошибки связанные с распределением кода. Но остались сбоев с использованием -кода (функции не используются): +Мы исправили ошибки связанные с распределением рукописи. Но остались сбоев с использованием +рукописи (функции не используются): ```text warning: function is never used: `connect`, #[warn(dead_code)] on by default @@ -27,7 +27,7 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default пользователи. Поэтому важно, чтобы сбоев с доступом к возможности. были решены. Для того, чтобы понять почему существуют такие ошибки, и как важно их устранить, -попробуем воспользоваться возможностями кода извне. Для этого создадим выполняемый +попробуем воспользоваться возможностями рукописи извне. Для этого создадим выполняемый дело в этой же папки. Создадим файл *src/main.rs*, который содержит: Filename: src/main.rs @@ -43,19 +43,19 @@ fn main() { Мы сейчас использовали приказ `extern crate` для того чтобы использовать возможности библиотеки `communicator` в нашей новой программе. Cargo использует файл *src/main.rs*, как точку доступа для двоичной программы, в то время, как *src/lib.rs* используется -для библиотеки. Такая создание кода довольно-таки обычна. Большинство кода +для библиотеки. Такая создание рукописи довольно-таки обычна. Большинство рукописи находится в библиотеках, а двоичный файл просто использует эту библиотеку. Итогом такой архитектуры является возможность другим программам также использовать возможности библиотеки. -Со стороны стороннего кода все что находится в библиотеке имеет пространство имён +Со стороны стороннего рукописи все что находится в библиотеке имеет пространство имён `communicator` (имя библиотеки) верхнего уровня. Всё остальное, это подчиненные звенья. -Также, обратите внимание, что когда мы используем внешние библиотеки с подзвенами, +Также, обратите внимание, что когда мы используем внешние библиотеки с внутренними вложениями, приказ `extern crate` начинает искать звенья с верхнего уровня. В нашей программе двоичный файл вызывает библиотечную функцию `connect` из -звена `client`. Но при сборки этого кода получим сообщении об ошибке: +звена `client`. Но при сборки этого рукописи получим сообщении об ошибке: ```text error: module `client` is private @@ -66,19 +66,19 @@ error: module `client` is private ``` Это сообщение говорит нам о том, что звено `client` закрытый. -Т.к. код в звене закрытый по умолчанию и не используется внутри библиотеки - на -это надо обратить внимание, т.к. это явная ошибка в согласования кода. +Т.к. рукопись в звене закрытый по умолчанию и не используется внутри библиотеки - на +это надо обратить внимание, т.к. это явная ошибка в согласования рукописи. После определения функции `client::connect`, как доступной (`pub`), не только сообщение -об ошибке исчезнет, но и пропадёт сообщение о том, что код не используется. -Создания доступного кода в Ржавчина даёт возможность его использования вне библиотеки. -Когда какой-либо кода помечается как `pub`, сборщик больше не сообщает об неиспользованном -коде, если даже он в действительности не используется. +об ошибке исчезнет, но и пропадёт сообщение о том, что рукопись не используется. +Создания доступного рукописи в Ржавчине даёт возможность его использования вне библиотеки. +Когда какой-либо рукописи помечается как `pub`, сборщик больше не сообщает об неиспользованном +рукописи, если даже он в действительности не используется. ### Сделать функции доступными Для того, чтобы сделать что-либо доступным извне, необходимо добавить ключевое слово -`pub` в начало декларирования элемента кода. Для исправления ошибки, мы добавим +`pub` в начало декларирования элемента рукописи. Для исправления ошибки, мы добавим этот определетель доступа перед определением имени звена: Filename: src/lib.rs @@ -124,9 +124,9 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default | ^ ``` -Код собрался и предостережения о функции `client::connect` уже нет! +Рукопись собрался и предостережения о функции `client::connect` уже нет! -Вам решать, что делать с неиспользованным кодом, то ли открыть к нему доступ, то ли +Вам решать, что делать с неиспользованным рукописью, то ли открыть к нему доступ, то ли удалить. В данном случае мы хотим, чтобы эти функции были доступны. Поэтому добавим `pub` @@ -141,7 +141,7 @@ pub fn connect() { mod server; ``` -Соберем и проанализируем ошибки: +Соберем и рассмотрим ошибки: ```text warning: function is never used: `connect`, #[warn(dead_code)] on by default @@ -187,7 +187,7 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default ### Примеры -Создадим новый дело библиотеки secret 7-5 *src/lib.rs*: +Создадим новое дело библиотеки secret 7-5 *src/lib.rs*: Filename: src/lib.rs @@ -212,9 +212,9 @@ fn try_me() { } ``` -Код 7-5: Примеры открытых и закрытый функций с ошибками +Рукопись 7-5: Примеры открытых и закрытый функций с ошибками -Перед сборкой кода, попробуйте догадаться, где будет ошибка. Убедитесь в этом +Перед сборкой рукописи, попробуйте догадаться, где будет ошибка. Убедитесь в этом с помощью сборки. Исправьте ошибки в коде! #### Рассмотрим ошибки diff --git a/rustbook-ru/src/ch07-02-defining-modules-to-control-scope-and-privacy.md b/rustbook-ru/src/ch07-02-defining-modules-to-control-scope-and-privacy.md index 4503d43f4..fc90002a1 100644 --- a/rustbook-ru/src/ch07-02-defining-modules-to-control-scope-and-privacy.md +++ b/rustbook-ru/src/ch07-02-defining-modules-to-control-scope-and-privacy.md @@ -1,24 +1,24 @@ ## Определение звеньев для управления видимости и закрытости -В этом разделе мы поговорим о звенах и других частях системы звеньев, а именно: *путях* (paths), которые позволяют именовать элементы; ключевом слове `use`, которое приносит путь в область видимости; ключевом слове `pub`, которое делает элементы общедоступными. Мы также обсудим ключевое слово `as`, внешние дополнения и оператор glob. А пока давайте сосредоточимся на звенах! +В этом разделе мы поговорим о звеньях и других частях системы звеньев, а именно: *путях* (paths), которые позволяют именовать элементы; ключевом слове `use`, которое приносит путь в область видимости; ключевом слове `pub`, которое делает элементы общедоступными. Мы также обсудим ключевое слово `as`, внешние дополнения и приказчик glob. А пока давайте сосредоточимся на звеньях! -Во-первых, мы начнём со списка правил, чтобы вам было легче понять при согласования кода в будущем. Затем мы подробно объясним каждое из правил. +Во-первых, мы начнём со списка правил, чтобы вам было легче понять при согласования рукописи в будущем. Затем мы подробно объясним каждое из правил. ### Шпаргалка по звенам -Здесь мы даём краткий обзор того, как звенья, пути, ключевое слово `use` и ключевое слово `pub` работают в сборщике и как большинство разработчиков согласуют свой код. В этой главе мы рассмотрим примеры каждого из этих правил, и это удобный мгновение чтобы напомнить о том, как работают звенья. +Здесь мы даём краткий обзор того, как звенья, пути, ключевое слово `use` и ключевое слово `pub` работают в сборщике и как большинство разработчиков согласуют свой рукопись. В этой главе мы рассмотрим примеры каждого из этих правил, и это удобный мгновение чтобы напомнить о том, как работают звенья. -- **Начнём с корня ящика**: при сборки сборщик сначала ищет корневой звено ящика (обычно это *src/lib.rs* для библиотечного ящика или *src/main.rs* для двоичного ящика) для сборки кода. -- **Объявление звеньев**: В файле корневого звена ящика вы можете объявить новые звенья; скажем, вы объявляете звено “garden” с помощью `mod garden;`. Сборщик будет искать код звена в следующих местах: +- **Начнём с корня ящика**: при сборки сборщик сначала ищет корневой звено ящика (обычно это *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.rs*. Сборщик будет искать рукопись подзвена в папке с именем родительского звена в следующих местах: - в этом же файле, сразу после `mod vegetables`, между фигурных скобок, которые заменяют точку с запятой - в файле *src/garden/vegetables.rs* - в файле *src/garden/vegetables/mod.rs* -- **Пути к коду в звенах**: После того, как звено станет частью вашего ящика и если допускают правила закрытости, вы можете ссылаться на код в этом звене из любого места вашего ящика, используя путь к коду. Например, вид `Asparagus`, в подзвене vegetables звена garden, будет найден по пути `crate::garden::vegetables::Asparagus`. -- **Скрытие или общедоступность**: Код в звене по умолчанию скрыт от родительского звена. Чтобы сделать звено общедоступным, объявите его как `pub mod` вместо `mod`. Чтобы сделать элементы общедоступного звена тоже общедоступными, используйте `pub` перед их объявлением. +- **Пути к рукописи в звеньях**: После того, как звено станет частью вашего ящика и если допускают правила закрытости, вы можете ссылаться на рукопись в этом звене из любого места вашего ящика, используя путь к рукописи. Например, вид `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`, содержит следующие файлы и папки: @@ -42,7 +42,7 @@ backyard {{#rustdoc_include ../listings/ch07-managing-growing-projects/quick-reference-example/src/main.rs}} ``` -Строка `pub mod garden;` говорит сборщику о подключении кода, найденном в *src/garden.rs*: +Строка `pub mod garden;` говорит сборщику о подключении рукописи, найденном в *src/garden.rs*: Файл: src/garden.rs @@ -50,7 +50,7 @@ backyard {{#rustdoc_include ../listings/ch07-managing-growing-projects/quick-reference-example/src/garden.rs}} ``` -А здесь `pub mod vegetables;` указывает на подключаемый код в *src/garden/vegetables.rs*. Этот код: +А здесь `pub mod vegetables;` указывает на подключаемый рукопись в *src/garden/vegetables.rs*. Этот код: ```rust,noplayground,ignore {{#rustdoc_include ../listings/ch07-managing-growing-projects/quick-reference-example/src/garden/vegetables.rs}} @@ -58,17 +58,15 @@ backyard Теперь давайте рассмотрим подробности этих правил и отобразим их в действии! -### Объединение связанного кода в звенах +### Объединение связанного рукописи в звеньях -*Звенья* позволяют упорядочивать код внутри ящика для удобочитаемости и лёгкого повторного использования. Звенья также позволяют нам управлять *закрытостью* элементов, поскольку код внутри звена по умолчанию является закрытым. Частные элементы — это внутренние подробности выполнения, недоступные для внешнего использования. Мы можем сделать звенья и элементы внутри них общедоступными, что позволит внешнему коду использовать их и зависеть от них. +*Звенья* позволяют упорядочивать рукопись внутри ящика для удобочитаемости и лёгкого повторного использования. Звенья также позволяют нам управлять *закрытостью* элементов, поскольку рукопись внутри звена по умолчанию является закрытым. Частные элементы — это внутренние подробности выполнения, недоступные для внешнего использования. Мы можем сделать звенья и элементы внутри них общедоступными, что позволит внешнему рукописи использовать их и зависеть от них. -В качестве примера, давайте напишем библиотечный ящик предоставляющий возможность ресторана. Мы определим ярлыки функций, но оставим их тела пустыми, чтобы сосредоточиться на согласования кода, вместо выполнения кода для ресторана. +В качестве примера, давайте напишем библиотечный ящик предоставляющий возможность ресторана. Мы определим ярлыки функций, но оставим их тела пустыми, чтобы сосредоточиться на согласования рукописи, вместо выполнения рукописи для ресторана. -В ресторанной индустрии некоторые части ресторана называются *фронтом дома*, а другие *задней частью дома*. Фронт дома это там где находятся клиенты; здесь размещаются места клиентов, официанты принимают заказы и оплаты, а бармены делают напитки. Задняя часть дома это где шеф-повара и повара работают на кухне, работают посудомоечные машины, а управленцы занимаются административной деятельностью. +В ресторанной индустрии некоторые части ресторана называются *фронтом дома*, а другие *задней частью дома*. Фронт дома это там где находятся конечные потребители; здесь размещаются места конечных потребителей, официанты принимают заказы и оплаты, а бармены делают напитки. Задняя часть дома это где шеф-повара и повара работают на кухне, работают посудомоечные машины, а управленцы занимаются управленческой деятельностью. -Чтобы внутренне выстроить - - ящик подобно тому, как работает настоящий ресторан, можно согласовать размещение функций во вложенных звенах. Создадим новую библиотеку (библиотечный ящик) с именем `restaurant` выполнив приказ `cargo new restaurant --lib`; затем вставим код из приложения 7-1 в *src/lib.rs* для определения некоторых звеньев и ярлыков функций. Это раздел фронта дома: +Чтобы внутренне выстроить ящик подобно тому, как работает настоящий ресторан, можно согласовать размещение функций во вложенных звеньях. Создадим новую библиотеку (библиотечный ящик) с именем `restaurant` выполнив приказ `cargo new restaurant --lib`; затем вставим рукопись из приложения 7-1 в *src/lib.rs* для определения некоторых звеньев и ярлыков функций. Это раздел фронта дома: Файл: src/lib.rs @@ -80,11 +78,11 @@ backyard Мы определяем звено, начиная с ключевого слова `mod`, затем определяем название звена (в данном случае `front_of_house`) и размещаем фигурные скобки вокруг тела звена. Внутри звеньев, можно иметь другие звенья, как в случае с звенами `hosting` и `serving`. Звенья также могут содержать определения для других элементов, таких как устройства, перечисления, постоянные значения, особенности или — как в приложении 7-1 — функции. -Используя звенья, мы можем собъединять связанные определения вместе и сказать почему они являются связанными. Программистам будет легче найти необходимую возможность в объединенном коде, вместо того чтобы искать её в одном общем списке. Программисты, добавляющие новые функции в этот код, будут знать, где разместить код для поддержания порядка в программе. +Используя звенья, мы можем объединять связанные определения вместе и сказать почему они являются связанными. Программистам будет легче найти необходимую возможность в объединенном рукописи, вместо того чтобы искать её в одном общем списке. Программисты, добавляющие новые функции в этот рукопись, будут знать, где разместить рукопись для поддержания порядка в программе. Как мы упоминали ранее, файлы *src/main.rs* и *src/lib.rs* называются *корневыми звенами ящика*. Причина такого именования в том, что содержимое любого из этих двух файлов образует звено с именем `crate` в корне устройства звеньев ящика, известной как *дерево звеньев*. -В приложении 7-2 показано дерево звеньев для устройства звеньев, приведённой в коде приложения 7-1. +В приложении 7-2 показано дерево звеньев для устройства звеньев, приведённой в рукописи приложения 7-1. ```text crate @@ -102,4 +100,4 @@ crate Это дерево показывает, как некоторые из звеньев вкладываются друг в друга; например, `hosting` находится внутри `front_of_house`. Дерево также показывает, что некоторые звенья являются *братьями* (siblings) друг для друга, то есть они определены в одном звене; `hosting` и `serving` это братья которые определены внутри `front_of_house`. Если звено A содержится внутри звена B, мы говорим, что звено A является *потомком* (child) звена B, а звено B является *родителем* (parent) звена A. Обратите внимание, что родителем всего дерева звеньев является неявный звено с именем `crate`. -Дерево звеньев может напомнить вам дерево папок файловой системы на компьютере; это очень удачное сравнение! По подобию с папкими в файловой системе, мы используется звенья для согласования кода. И так же, как нам надо искать файлы в папких на компьютере, нам требуется способ поиска нужных звеньев. +Дерево звеньев может напомнить вам дерево папок файловой системы на компьютере; это очень удачное сравнение! По подобию с папкими в файловой системе, мы используется звенья для согласования рукописи. И так же, как нам надо искать файлы в папких на компьютере, нам требуется способ поиска нужных звеньев. diff --git a/rustbook-ru/src/ch07-03-importing-names-with-use.md b/rustbook-ru/src/ch07-03-importing-names-with-use.md index 3e6f82d78..dc22f0769 100644 --- a/rustbook-ru/src/ch07-03-importing-names-with-use.md +++ b/rustbook-ru/src/ch07-03-importing-names-with-use.md @@ -128,10 +128,10 @@ fn main() { let green = Green; } ``` -Символ `*` называют *glob* и его функция - вставка всех элементов, видимых извне +Знак `*` называют *glob* и его функция - вставка всех элементов, видимых извне пространства имён. Обратите также внимание, что наряду с удобствами, существуют также недостатки использования полного подключения пространства имён, т.к. это может привести -к конфликтным или неожиданным случаейм, когда в разных пространствах имён существуют +к конфликтным или неожиданным случайм, когда в разных пространствах имён существуют одинаковые (по имени) функции, которые будут вставляться. Пример: @@ -182,7 +182,7 @@ note: `nested_modules` could also refer to the name imported here ### Доступ к возможности. родительского звена с помощью `super` Как вы помните, при создании библиотеки, Cargo предлагает использовать звено `tests`. -Сейчас разберёмся подробнее. Добавим код проверки в исходный код файла *src/lib.rs*: +Сейчас разберёмся подробнее. Добавим рукопись проверки в исходную рукопись файла *src/lib.rs*: Filename: src/lib.rs @@ -200,7 +200,7 @@ mod tests { ``` В главе 11 подробно рассказывается о проверке. Сейчас мы только немного расскажем. -Обратите внимание на особую изложение и то, что это отдельный звено в нашем коде. +Обратите внимание на особую изложение и то, что это отдельный звено в нашем рукописи. Состоящая из звеньев система нашего дела теперь имеет вид: ```text @@ -211,7 +211,7 @@ communicator └── tests ``` -Проверки помогают отлаживать код библиотеки. Напишем наш первый проверку. Он будет вызывать +Проверки помогают отлаживать рукопись библиотеки. Напишем наш первый проверку. Он будет вызывать функцию `client::connect`: Filename: src/lib.rs @@ -256,7 +256,7 @@ super::client::connect(); ``` Эти две возможности выглядят одинаковыми в этом примере. Если находитесь глубоко -внутри состоящей из звеньев упорядочевания, то начиная с корневого звена ваш код будет длинным. +внутри состоящей из звеньев упорядочевания, то начиная с корневого звена ваша рукопись будет длинным. Есть случаи, когда использование `super` более удобно. Это бывает утомительно печать `super::` в каждом проверке. Есть решение `use`. @@ -296,8 +296,8 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured ## Итоги -Теперь вы знаете ещё один способ, как можно согласовать ваш код. Его можно использовать -для объединения различных элементов вместе, при переработке кода большого количества -кода. +Теперь вы знаете ещё один способ, как можно согласовать ваша рукопись. Его можно использовать +для объединения различных элементов вместе, при переработке рукописи большого количества +рукописи. Далее, мы рассмотрим устройства данных встроенной библиотеки. diff --git a/rustbook-ru/src/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md b/rustbook-ru/src/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md index 439120d52..deb22ded8 100644 --- a/rustbook-ru/src/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md +++ b/rustbook-ru/src/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md @@ -1,15 +1,15 @@ ## Пути для ссылки на элемент в дереве звеньев -Чтобы показать Rust, где найти элемент в дереве звеньев, мы используем путь так же, как мы используем путь при навигации по файловой системе. Чтобы вызвать функцию, нам нужно знать её путь. +Чтобы показать Ржавчина, где найти элемент в дереве звеньев, мы используем путь так же, как мы используем путь при навигации по файловой системе. Чтобы вызвать функцию, нам нужно знать её путь. Пути бывают двух видов: -- *абсолютный путь* - это полный путь, начинающийся от корневого звена ящика; для кода из внешнего ящика абсолютный путь начинается с имени ящика, а для кода из текущего ящика он начинается с записи `crate`. +- *безусловный путь* - это полный путь, начинающийся от корневого звена ящика; для рукописи из внешнего ящика безусловный путь начинается с имени ящика, а для рукописи из текущего ящика он начинается с записи `crate`. - *относительный путь* начинается с текущего звена и использует ключевые слова `self`, `super` или определитель в текущем звене. -Как абсолютные, так и относительные, пути состоят из одного или нескольких определителей, разделённых двойными двоеточиями (`::`). +Как безусловные, так и относительные, пути состоят из одного или нескольких определителей, разделённых двойными двоеточиями (`::`). -Вернёмся к приложению 7-1, скажем, мы хотим вызвать функцию `add_to_waitlist`. Это то же самое, что спросить: какой путь у функции `add_to_waitlist`? В приложении 7-3 мы немного упроисполнения код приложения 7-1, удалив некоторые звенья и функции. +Вернёмся к приложению 7-1, скажем, мы хотим вызвать функцию `add_to_waitlist`. Это то же самое, что спросить: какой путь у функции `add_to_waitlist`? В приложении 7-3 мы немного упроисполнения рукопись приложения 7-1, удалив некоторые звенья и функции. Мы покажем два способа вызова функции `add_to_waitlist` из новой функции `eat_at_restaurant`, определённой в корневом звене ящика. Эти пути правильные, но остаётся ещё одна неполадка, которая не позволит этому примеру собраться как есть. Мы скоро объясним почему. @@ -21,27 +21,27 @@ {{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-03/src/lib.rs}} ``` -Приложение 7-3. Вызов функции add_to_waitlist с использованием абсолютного и относительного пути +Приложение 7-3. Вызов функции 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` мы используем безусловный путь. Функция `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` останется прежним, а относительный путь нужно будет обновить. Мы предпочитаем указывать абсолютные пути, потому что это позволяет проще перемещать определения кода и вызовы элементов независимо друг от друга. +Выбор, использовать относительный или безусловный путь, является решением, которое вы примете на основании вашего дела. Решение должно зависеть от того, с какой вероятностью вы переместите объявление элемента отдельно от или вместе с рукописью использующим этот элемент. Например, в случае перемещения звена `front_of_house` и его функции `eat_at_restaurant` в другой звено с именем `customer_experience`, будет необходимо обновить безусловный путь до `add_to_waitlist`, но относительный путь всё равно будет действителен. Однако, если мы переместим отдельно функцию `eat_at_restaurant` в звено с именем `dining`, то безусловный путь вызова `add_to_waitlist` останется прежним, а относительный путь нужно будет обновить. Мы предпочитаем указывать безусловные пути, потому что это позволяет проще перемещать определения рукописи и вызовы элементов независимо друг от друга. -Давайте попробуем собрать код из приложения 7-3 и выяснить, почему он ещё не собирается. Ошибка, которую мы получаем, показана в приложении 7-4. +Давайте попробуем собрать рукопись из приложения 7-3 и выяснить, почему он ещё не собирается. Ошибка, которую мы получаем, показана в приложении 7-4. ```console {{#include ../listings/ch07-managing-growing-projects/listing-07-03/output.txt}} ``` -Приложение 7-4. Ошибки сборки при сборке кода из приложения 7-3 +Приложение 7-4. Ошибки сборки при сборке рукописи из приложения 7-3 -Сообщения об ошибках говорят о том, что звено `hosting` является закрытым. Другими словами, у нас есть правильные пути к звену `hosting` и функции `add_to_waitlist`, но Ржавчина не позволяет нам использовать их, потому что у него нет доступа к закрытым разделам. В Ржавчина все элементы (функции, способы, устройства, перечисления, звенья и постоянные значения) по умолчанию являются закрытыми для родительских звеньев. Если вы хотите сделать элемент, например функцию или устройство, закрытым, вы помещаете его в звено. +Сообщения об ошибках говорят о том, что звено `hosting` является закрытым. Другими словами, у нас есть правильные пути к звену `hosting` и функции `add_to_waitlist`, но Ржавчина не позволяет нам использовать их, потому что у него нет доступа к закрытым разделам. В Ржавчине все элементы (функции, способы, устройства, перечисления, звенья и постоянные значения) по умолчанию являются закрытыми для родительских звеньев. Если вы хотите сделать элемент, например функцию или устройство, закрытым, вы помещаете его в звено. -Элементы в родительском звене не могут использовать закрытые элементы внутри дочерних звеньев, но элементы в дочерних звенах могут использовать элементы у своих звенах-предках. Это связано с тем, что дочерние звенья оборачивают и скрывают подробности своей выполнения, но дочерние звенья могут видеть среда, в котором они определены. Продолжая нашу метафору, подумайте о правилах закрытости как о задней части ресторана: то, что там происходит, скрыто от клиентов ресторана, но офис-управленцы могут видеть и делать всё в ресторане, которым они управляют. +Элементы в родительском звене не могут использовать закрытые элементы внутри дочерних звеньев, но элементы в дочерних звеньях могут использовать элементы у своих звеньях-предках. Это связано с тем, что дочерние звенья оборачивают и скрывают подробности своей выполнения, но дочерние звенья могут видеть среда, в котором они определены. Продолжая нашу метафору, подумайте о правилах закрытости как о задней части ресторана: то, что там происходит, скрыто от конечных потребителей ресторана, но офис-управленцы могут видеть и делать всё в ресторане, которым они управляют. -В Ржавчина решили, что система звеньев должна исполняться таким образом, чтобы по умолчанию скрывать подробности выполнения. Таким образом, вы знаете, какие части внутреннего кода вы можете изменять не нарушая работы внешнего кода. Тем не менее, Ржавчина даёт нам возможность открывать внутренние части кода дочерних звеньев для внешних звеньев-предков, используя ключевое слово `pub`, чтобы сделать элемент общедоступным. +В Ржавчине решили, что система звеньев должна исполняться таким образом, чтобы по умолчанию скрывать подробности выполнения. Таким образом, вы знаете, какие части внутреннего рукописи вы можете изменять не нарушая работы внешнего рукописи. Тем не менее, Ржавчина даёт нам возможность открывать внутренние части рукописи дочерних звеньев для внешних звеньев-предков, используя ключевое слово `pub`, чтобы сделать элемент общедоступным. ### Раскрываем закрытые пути с помощью ключевого слова `pub` @@ -55,15 +55,15 @@ Приложение 7-5. Объявление звена hosting как pub для его использования из eat_at_restaurant -К сожалению, код в приложении 7-5 всё ещё приводит к ошибке, как показано в приложении 7-6. +К сожалению, рукопись в приложении 7-5 всё ещё приводит к ошибке, как показано в приложении 7-6. ```console {{#include ../listings/ch07-managing-growing-projects/listing-07-05/output.txt}} ``` -Приложение 7-6: Ошибки сборки при сборке кода в приложении 7-5 +Приложение 7-6: Ошибки сборки при сборке рукописи в приложении 7-5 -Что произошло? Добавление ключевого слова `pub` перед `mod hosting` сделало звено общедоступным. После этого изменения, если мы можем получить доступ к звену `front_of_house`, то мы можем получить доступ к звену `hosting`. Но *содержимое* звена `hosting` всё ещё является закрытым: превращение звена в общедоступный звено не делает его содержимое общедоступным. Ключевое слово `pub` позволяет внешнему коду в звенах-предках обращаться только к звену, без доступа ко внутреннему коду. Поскольку звенья являются дополнениями, мы мало что можем сделать, просто сделав звено общедоступным; нам нужно пойти дальше и сделать один или несколько элементов в звене общедоступными. +Что произошло? Добавление ключевого слова `pub` перед `mod hosting` сделало звено общедоступным. После этого изменения, если мы можем получить доступ к звену `front_of_house`, то мы можем получить доступ к звену `hosting`. Но *содержимое* звена `hosting` всё ещё является закрытым: превращение звена в общедоступный звено не делает его содержимое общедоступным. Ключевое слово `pub` позволяет внешнему рукописи в звеньях-предках обращаться только к звену, без доступа ко внутреннему рукописи. Поскольку звенья являются дополнениями, мы мало что можем сделать, просто сделав звено общедоступным; нам нужно пойти дальше и сделать один или несколько элементов в звене общедоступными. Ошибки в приложении 7-6 говорят, что функция `add_to_waitlist` является закрытой. Правила закрытости применяются к устройствам, перечислениям, функциям и способам, также как и к звенам. @@ -77,27 +77,27 @@ Приложение 7-7. Добавление ключевого слова pub к mod hosting и к fn add_to_waitlist позволяет нам вызывать функцию из eat_at_restaurant -Теперь код собирается! Чтобы понять, почему добавление ключевого слова `pub` позволяет нам использовать эти пути для `add_to_waitlist` в соответствии с правилами закрытости, давайте рассмотрим абсолютный и относительный пути. +Теперь рукопись собирается! Чтобы понять, почему добавление ключевого слова `pub` позволяет нам использовать эти пути для `add_to_waitlist` в соответствии с правилами закрытости, давайте рассмотрим безусловный и относительный пути. -В случае абсолютного пути мы начинаем с `crate`, корня дерева звеньев нашего ящика. Звено `front_of_house` определён в корневом звене ящика. Хотя `front_of_house` не является общедоступным, но поскольку функция `eat_at_restaurant` определена в том же звене, что и `front_of_house` (то есть, `eat_at_restaurant` и `front_of_house` являются потомками одного родителя), мы можем ссылаться на `front_of_house` из `eat_at_restaurant`. Далее идёт звено `hosting`, помеченный как `pub`. Мы можем получить доступ к родительскому звену звена `hosting`, поэтому мы можем получить доступ и к `hosting`. Наконец, функция `add_to_waitlist` помечена как `pub`, и так как мы можем получить доступ к её родительскому звену, то вызов этой функции разрешён! +В случае безусловного пути мы начинаем с `crate`, корня дерева звеньев нашего ящика. Звено `front_of_house` определён в корневом звене ящика. Хотя `front_of_house` не является общедоступным, но поскольку функция `eat_at_restaurant` определена в том же звене, что и `front_of_house` (то есть, `eat_at_restaurant` и `front_of_house` являются потомками одного родителя), мы можем ссылаться на `front_of_house` из `eat_at_restaurant`. Далее идёт звено `hosting`, помеченный как `pub`. Мы можем получить доступ к родительскому звену звена `hosting`, поэтому мы можем получить доступ и к `hosting`. Наконец, функция `add_to_waitlist` помечена как `pub`, и так как мы можем получить доступ к её родительскому звену, то вызов этой функции разрешён! -В случае относительного пути логика такая же как для абсолютного пути, за исключением первого шага: вместо того, чтобы начинать с корневого звена ящика, путь начинается с `front_of_house`. Звено `front_of_house` определён в том же звене, что и `eat_at_restaurant`, поэтому относительный путь, начинающийся с звена, в котором определена `eat_at_restaurant` тоже работает. Тогда, по причине того, что `hosting` и `add_to_waitlist` помечены как `pub`, остальная часть пути работает и вызов этой функции разрешён! +В случае относительного пути ход мыслей такая же как для безусловного пути, за исключением первого шага: вместо того, чтобы начинать с корневого звена ящика, путь начинается с `front_of_house`. Звено `front_of_house` определён в том же звене, что и `eat_at_restaurant`, поэтому относительный путь, начинающийся с звена, в котором определена `eat_at_restaurant` тоже работает. Тогда, по причине того, что `hosting` и `add_to_waitlist` помечены как `pub`, остальная часть пути работает и вызов этой функции разрешён! -Если вы собираетесь предоставить общий доступ к своему библиотечному ящику, чтобы другие дела могли использовать ваш код, ваш общедоступный API — это ваш договор с пользователями вашего ящика, определяющий, как они могут взаимодействовать с вашим кодом. Есть много соображений по поводу управления изменениями в вашем общедоступном API, чтобы сделать необременительным для людей зависимость от вашего ящика. Эти соображения выходят за рамки этой книги; если вам важна эта тема, см. [The Ржавчина API Guidelines]. +Если вы собираетесь предоставить общий доступ к своему библиотечному ящику, чтобы другие дела могли использовать ваша рукопись, ваш общедоступный API — это ваш договор с пользователями вашего ящика, определяющий, как они могут взаимодействовать с вашим рукописью. Есть много соображений по поводу управления изменениями в вашем общедоступном API, чтобы сделать необременительным для людей зависимость от вашего ящика. Эти соображения выходят за рамки этой книги; если вам важна эта тема, см. [The Ржавчина API Guidelines]. > #### Лучшие опытов для дополнений с двоичным и библиотечным ящиками > -> Мы упоминали, что дополнение может содержать как корневой звено двоичного ящика *src/main.rs*, так и корневой звено библиотечного ящика *src/lib.rs*, и оба ящика будут по умолчанию иметь имя дополнения. Как правило, дополнения с таким образцом, содержащим как библиотечный, так и двоичный ящик, будут иметь достаточно кода в двоичном ящике, чтобы запустить исполняемый файл, который вызывает код из библиотечного ящика. Это позволяет другим делам извлечь выгоду из большей части возможности, предоставляемой дополнением, поскольку код библиотечного ящика можно использовать совместно. +> Мы упоминали, что дополнение может содержать как корневой звено двоичного ящика *src/main.rs*, так и корневой звено библиотечного ящика *src/lib.rs*, и оба ящика будут по умолчанию иметь имя дополнения. Как правило, дополнения с таким образцом, содержащим как библиотечный, так и двоичный ящик, будут иметь достаточно рукописи в двоичном ящике, чтобы запустить исполняемый файл, который вызывает рукопись из библиотечного ящика. Это позволяет другим делам извлечь выгоду из большей части возможности, предоставляемой дополнением, поскольку рукопись библиотечного ящика можно использовать совместно. > -> Дерево звеньев должно быть определено в *src/lib.rs*. Затем любые общедоступные элементы можно использовать в двоичном ящике, начав пути с имени дополнения. Двоичный ящик становится пользователем библиотечного ящика точно так же, как полностью внешний ящик использует библиотечный ящик: он может использовать только общедоступный API. Это поможет вам разработать хороший API; вы не только автор, но и пользователь! +> Дерево звеньев должно быть определено в *src/lib.rs*. Затем любые общедоступные элементы можно использовать в двоичном ящике, начав пути с имени дополнения. Двоичный ящик становится пользователем библиотечного ящика точно так же, как полностью внешний ящик использует библиотечный ящик: он может использовать только общедоступный API. Это поможет вам разработать хороший API; вы не только составитель, но и пользователь! > -> В [Главе 12] мы эту опыт согласования кода с помощью окно выводаной программы, которая будет содержать как двоичный, так и библиотечный ящики. +> В [Главе 12] мы эту опыт согласования рукописи с помощью окно выводаной программы, которая будет содержать как двоичный, так и библиотечный ящики. ### Начинаем относительный путь с помощью `super` Также можно построить относительные пути, которые начинаются в родительском звене, используя ключевое слово `super` в начале пути. Это похоже на правила написания начала пути файловой системы `..`. Использование `super` позволяет нам сослаться на элемент, который, как мы знаем, находится в родительском звене, что может упростить переупорядочение дерева звеньев, чем когда звено тесно связан с родителем, но родитель может когда-нибудь быть перемещён в другое место в дереве звеньев. -Рассмотрим код в приложении 7-8, где расчитывается случаей, в которой повар исправляет неправильный заказ и лично приносит его клиенту. Функция `fix_incorrect_order` вызывает функцию `deliver_order`, определённую в родительском звене, указывая путь к `deliver_order`, начинающийся с `super`: +Рассмотрим рукопись в приложении 7-8, где расчитывается случай, в которой повар исправляет неправильный заказ и лично приносит его потребителю. Функция `fix_incorrect_order` вызывает функцию `deliver_order`, определённую в родительском звене, указывая путь к `deliver_order`, начинающийся с `super`: Файл: src/lib.rs @@ -107,11 +107,11 @@ Приложение 7-8: Вызов функции с использованием относительного пути, начинающегося с super -Функция `fix_incorrect_order` находится в звене `back_of_house`, поэтому мы можем использовать `super` для перехода к родительскому звену звена `back_of_house`, который в этом случае является `crate`, корневым звеном. В этом звене мы ищем `deliver_order` и находим его. Успех! Мы думаем, что звено `back_of_house` и функция `deliver_order`, скорее всего, останутся в тех же родственных отношениях друг с другом, и должны будут перемещены вместе, если мы решим ресогласовать дерево звеньев ящика. Поэтому мы использовали `super`, чтобы в будущем у нас было меньше мест для обновления кода, если этот код будет перемещён в другой звено. +Функция `fix_incorrect_order` находится в звене `back_of_house`, поэтому мы можем использовать `super` для перехода к родительскому звену звена `back_of_house`, который в этом случае является `crate`, корневым звеном. В этом звене мы ищем `deliver_order` и находим его. Успех! Мы думаем, что звено `back_of_house` и функция `deliver_order`, скорее всего, останутся в тех же родственных отношениях друг с другом, и должны будут перемещены вместе, если мы решим ресогласовать дерево звеньев ящика. Поэтому мы использовали `super`, чтобы в будущем у нас было меньше мест для обновления рукописи, если этот рукопись будет перемещён в другой звено. ### Делаем общедоступными устройства и перечисления -Мы также можем использовать `pub` для обозначения устройств и перечислений как общедоступных, но есть несколько дополнительных подробностей использования `pub` со устройствами и перечислениями. Если мы используем `pub` перед определением устройства, мы делаем устройство общедоступной, но поля устройства по-прежнему остаются закрытыми. Мы можем сделать каждое поле общедоступным или нет в каждом определенном случае. В приложении 7-9 мы определили общедоступную устройство `back_of_house::Breakfast` с общедоступным полем `toast` и с закрытым полем `seasonal_fruit`. Это расчитывает случай в ресторане, когда клиент может выбрать вид хлеба, который подаётся с едой, а шеф-повар решает какие фрукты сопровождают еду, исходя из того, что сезонно и что есть в наличии. Доступные фрукты быстро меняются, поэтому клиенты не могут выбирать фрукты или даже увидеть, какие фрукты они получат. +Мы также можем использовать `pub` для обозначения устройств и перечислений как общедоступных, но есть несколько дополнительных подробностей использования `pub` со устройствами и перечислениями. Если мы используем `pub` перед определением устройства, мы делаем устройство общедоступной, но поля устройства по-прежнему остаются закрытыми. Мы можем сделать каждое поле общедоступным или нет в каждом определенном случае. В приложении 7-9 мы определили общедоступную устройство `back_of_house::Breakfast` с общедоступным полем `toast` и с закрытым полем `seasonal_fruit`. Это расчитывает случай в ресторане, когда потребитель может выбрать вид хлеба, который подаётся с едой, а шеф-повар решает какие фрукты сопровождают еду, исходя из того, что сезонно и что есть в наличии. Доступные фрукты быстро меняются, поэтому конечные потребители не могут выбирать фрукты или даже увидеть, какие фрукты они получат. Файл: src/lib.rs @@ -139,7 +139,7 @@ Перечисления не очень полезны, если их исходы не являются общедоступными: было бы досадно каждый раз определять все исходы перечисления как `pub`. По этой причине по умолчанию исходы перечислений являются общедоступными. Устройства часто полезны, если их поля не являются общедоступными, поэтому поля устройства следуют общему правилу, согласно которому, всё по умолчанию является закрытым, если не указано `pub`. -Есть ещё одна случаей с `pub`, которую мы не освещали, и это последняя особенность состоящей из звеньев системы: ключевое слово `use`. Мы сначала опишем `use` само по себе, а затем покажем как сочетать `pub` и `use` вместе. +Есть ещё одна случай с `pub`, которую мы не освещали, и это последняя особенность состоящей из звеньев системы: ключевое слово `use`. Мы сначала опишем `use` само по себе, а затем покажем как сочетать `pub` и `use` вместе. [The Ржавчина API Guidelines]: https://rust-lang.github.io/api-guidelines/ diff --git a/rustbook-ru/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md b/rustbook-ru/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md index d4f703865..a80ea2959 100644 --- a/rustbook-ru/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md +++ b/rustbook-ru/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md @@ -1,6 +1,6 @@ ## Подключение путей в область видимости с помощью ключевого слова `use` -Необходимость записывать пути к функциям вызова может показаться неудобной и повторяющейся. В приложении 7-7 независимо от того, выбирали ли мы абсолютный или относительный путь к функции `add_to_waitlist` , каждый раз, когда мы хотели вызвать `add_to_waitlist` , нам приходилось также указывать `front_of_house` и `hosting` . К счастью, есть способ упростить этот этап: мы можем один раз создать псевдоним на путь при помощи ключевого слова `use`, а затем использовать более короткое имя везде в области видимости. +Необходимость записывать пути к функциям вызова может показаться неудобной и повторяющейся. В приложении 7-7 независимо от того, выбирали ли мы безусловный или относительный путь к функции `add_to_waitlist` , каждый раз, когда мы хотели вызвать `add_to_waitlist` , нам приходилось также указывать `front_of_house` и `hosting` . К счастью, есть способ упростить этот этап: мы можем один раз создать псевдоним на путь при помощи ключевого слова `use`, а затем использовать более короткое имя везде в области видимости. В приложении 7-11 мы подключили звено `crate::front_of_house::hosting` в область действия функции `eat_at_restaurant`, поэтому нам достаточно только указать `hosting::add_to_waitlist` для вызова функции `add_to_waitlist` внутри `eat_at_restaurant`. @@ -12,7 +12,7 @@ Приложение 7-11. Добавление звена в область видимости при помощи use -Добавление `use` и пути в область видимости подобно созданию символической ссылки в файловой системе. С добавлением `use crate::front_of_house::hosting` в корневой звено ящика, `hosting` становится допустимым именем в этой области, как если бы звено `hosting` был определён в корневом звене ящика. Пути, подключённые в область видимости с помощью `use`, также проверяются на доступность, как и любые другие пути. +Добавление `use` и пути в область видимости подобно созданию условной ссылки в файловой системе. С добавлением `use crate::front_of_house::hosting` в корневой звено ящика, `hosting` становится допустимым именем в этой области, как если бы звено `hosting` был определён в корневом звене ящика. Пути, подключённые в область видимости с помощью `use`, также проверяются на доступность, как и любые другие пути. Обратите внимание, что `use` создаёт псевдоним только для той именно области, в которой это объявление `use` и находится. В приложении 7-12 функция `eat_at_restaurant` перемещается в новый дочерний звено с именем `customer`, область действия которого отличается от области действия указания `use`, поэтому тело функции не будет собираться: @@ -44,7 +44,7 @@ Приложение 7-13: Добавление функции add_to_waitlist в область видимости с use неидиоматическим способом -Хотя приложениеи 7-11 и 7-13 выполняют одну и ту же задачу, приложение 7-11 является идиоматическим способом подключения функции в область видимости с помощью `use`. Подключение родительского звена функции в область видимости при помощи `use` означает, что мы должны указывать родительский звено при вызове функции. Указание родительского звена при вызове функции даёт понять, что функция не определена местно, но в то же время сводя к уменьшению повторение полного пути. В коде приложения 7-13 не ясно, где именно определена `add_to_waitlist`. +Хотя приложениеи 7-11 и 7-13 выполняют одну и ту же задачу, приложение 7-11 является идиоматическим способом подключения функции в область видимости с помощью `use`. Подключение родительского звена функции в область видимости при помощи `use` означает, что мы должны указывать родительский звено при вызове функции. Указание родительского звена при вызове функции даёт понять, что функция не определена местно, но в то же время сводя к уменьшению повторение полного пути. В рукописи приложения 7-13 не ясно, где именно определена `add_to_waitlist`. С другой стороны, при подключении устройств, перечислений и других элементов используя `use`, идиоматически правильным будет указывать полный путь. Приложение 7-14 показывает идиоматический способ подключения устройства встроенной библиотеки `HashMap` в область видимости двоичного ящика. @@ -56,7 +56,7 @@ Приложение 7-14. Включение HashMap в область видимости идиоматическим способом -За этой идиомой нет веской причины: это просто соглашение, которое появилось само собой. Люди привыкли читать и писать код на Ржавчина таким образом. +За этой идиомой нет веской причины: это просто соглашение, которое появилось само собой. Люди привыкли читать и писать рукопись на Ржавчине таким образом. Исключением из этой идиомы является случай, когда мы подключаем два элемента с одинаковыми именами в область видимости используя указанию `use` — Ржавчина просто не позволяет этого сделать. Приложение 7-15 показывает, как подключить в область действия два вида с одинаковыми именами `Result`, но из разных родительских звеньев и как на них ссылаться. @@ -68,11 +68,11 @@ Приложение 7-15. Для включения двух видов с одинаковыми именами в одну область видимости необходимо использовать их родительские звенья. -Как видите, использование имени родительских звеньев позволяет различать два вида `Result`. Если бы вместо этого мы указали `use std::fmt::Result` и `use std::io::Result`, мы бы имели два вида `Result` в одной области видимости, и Ржавчина не смог бы понять какой из двух `Result` мы имели в виду, когда нашёл бы их употребление в коде. +Как видите, использование имени родительских звеньев позволяет различать два вида `Result`. Если бы вместо этого мы указали `use std::fmt::Result` и `use std::io::Result`, мы бы имели два вида `Result` в одной области видимости, и Ржавчина не смог бы понять какой из двух `Result` мы имели в виду, когда нашёл бы их употребление в рукописи. ### Предоставление новых имён с помощью ключевого слова `as` -Есть другое решение сбоев добавления двух видов с одинаковыми именами в одну и ту же область видимости используя `use`: после пути можно указать `as` и новое местное имя (псевдоним) для вида. Приложение 7-16 показывает как по-другому написать код из приложения 7-15, путём переименования одного из двух видов `Result` используя `as`. +Есть другое решение сбоев добавления двух видов с одинаковыми именами в одну и ту же область видимости используя `use`: после пути можно указать `as` и новое местное имя (псевдоним) для вида. Приложение 7-16 показывает как по-другому написать рукопись из приложения 7-15, путём переименования одного из двух видов `Result` используя `as`. Файл: src/lib.rs @@ -86,9 +86,9 @@ ### Реэкспорт имён с `pub use` -Когда мы подключаем имя в область видимости, используя ключевое слово `use`, то имя, доступное в новой области видимости, является закрытым. Чтобы позволить коду, который вызывает наш код, ссылаться на это имя, как если бы оно было определено в области видимости данного кода, можно объединить `pub` и `use`. Этот способ называется *реэкспортом (re-exporting)*, потому что мы подключаем элемент в область видимости, но также делаем этот элемент доступным для подключения в других областях видимости. +Когда мы подключаем имя в область видимости, используя ключевое слово `use`, то имя, доступное в новой области видимости, является закрытым. Чтобы позволить рукописи, который вызывает нашу рукопись, ссылаться на это имя, как если бы оно было определено в области видимости данного рукописи, можно объединить `pub` и `use`. Этот способ называется *реэкспортом (re-exporting)*, потому что мы подключаем элемент в область видимости, но также делаем этот элемент доступным для подключения в других областях видимости. -Приложение 7-17 показывает код из приложения 7-11, где `use` в корневом звене заменено на `pub use`. +Приложение 7-17 показывает рукопись из приложения 7-11, где `use` в корневом звене заменено на `pub use`. Файл: src/lib.rs @@ -96,11 +96,11 @@ {{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-17/src/lib.rs}} ``` -Приложение 7-17. Предоставление имени для использования любым кодом из новой области при помощи pub use +Приложение 7-17. Предоставление имени для использования любым рукописью из новой области при помощи pub use -До этого изменения внешний код должен был вызывать функцию `add_to_waitlist` , используя путь `restaurant::front_of_house::hosting::add_to_waitlist()` . Теперь, когда это объявление `pub use` повторно экспортировало звено `hosting` из корневого звена, внешний код теперь может использовать вместо него путь `restaurant::hosting::add_to_waitlist()` . +До этого изменения внешний рукопись должен был вызывать функцию `add_to_waitlist` , используя путь `restaurant::front_of_house::hosting::add_to_waitlist()` . Теперь, когда это объявление `pub use` повторно экспортировало звено `hosting` из корневого звена, внешний рукопись теперь может использовать вместо него путь `restaurant::hosting::add_to_waitlist()` . -Реэкспорт полезен, когда внутренняя устройства вашего кода отличается от того, как программисты, вызывающие ваш код, думают о предметной области. Например, по подобию с рестораном люди, управляющие им, думают о «передней части дома» и «задней части дома». Но клиенты, посещающие ресторан, вероятно, не будут думать о частях ресторана в таких понятиях. Используя `pub use` , мы можем написать наш код с одной устройством, но сделать общедоступной другую устройство. Благодаря этому наша библиотека хорошо согласована для программистов, работающих над библиотекой, и для программистов, вызывающих библиотеку. Мы рассмотрим ещё один пример `pub use` и его влияние на документацию вашего ящика в разделе «Экспорт удобного общедоступного API с `pub use`» Главы 14. +Реэкспорт полезен, когда внутренняя устройства вашей рукописи отличается от того, как программисты, вызывающие ваша рукопись, думают о предметной области. Например, по подобию с рестораном люди, управляющие им, думают о «передней части дома» и «задней части дома». Но конечные потребители, посещающие ресторан, вероятно, не будут думать о частях ресторана в таких понятиях. Используя `pub use` , мы можем написать нашу рукопись с одной устройством, но сделать общедоступной другую устройство. Благодаря этому наша библиотека хорошо согласована для программистов, работающих над библиотекой, и для программистов, вызывающих библиотеку. Мы рассмотрим ещё один пример `pub use` и его влияние на пособие вашего ящика в разделе «Экспорт удобного общедоступного API с `pub use`» Главы 14. ### Использование внешних дополнений @@ -126,15 +126,15 @@ {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-03/src/main.rs:ch07-04}} ``` -Члены сообщества Ржавчина сделали много дополнений доступными на ресурсе [crates.io](https://crates.io/), и добавление любого из них в ваш дополнение включает в себя одни и те же шаги: добавить внешние дополнения в файл *Cargo.toml* вашего дополнения, использовать `use` для подключения элементов внешних дополнений в область видимости. +Члены сообщества Ржавчина сделали много дополнений доступными на источнике [crates.io](https://crates.io/), и добавление любого из них в ваш дополнение включает в себя одни и те же шаги: добавить внешние дополнения в файл *Cargo.toml* вашего дополнения, использовать `use` для подключения элементов внешних дополнений в область видимости. -Обратите внимание, что обычная библиотека `std` также является ящиком, внешним по отношению к нашему дополнению. Поскольку обычная библиотека поставляется с языком Rust, нам не нужно изменять *Cargo.toml* для подключения `std`. Но нам нужно ссылаться на неё при помощи `use`, чтобы добавить элементы оттуда в область видимости нашего дополнения. Например, с `HashMap` мы использовали бы эту строку: +Обратите внимание, что обычная библиотека `std` также является ящиком, внешним по отношению к нашему дополнению. Поскольку обычная библиотека поставляется с языком Ржавчина, нам не нужно изменять *Cargo.toml* для подключения `std`. Но нам нужно ссылаться на неё при помощи `use`, чтобы добавить элементы оттуда в область видимости нашего дополнения. Например, с `HashMap` мы использовали бы эту строку: ```rust use std::collections::HashMap; ``` -Это абсолютный путь, начинающийся с `std`, имени ящика встроенной библиотеки. +Это безусловный путь, начинающийся с `std`, имени ящика встроенной библиотеки. ### Использование вложенных путей для уменьшения длинных списков `use` @@ -180,17 +180,17 @@ use std::collections::HashMap; Эта строка подключает `std::io` и `std::io::Write` в область видимости. -### Оператор * (glob) +### Приказчик * (glob) -Если мы хотим включить в область видимости *все* общедоступные элементы, определённые в пути, мы можем указать этот путь, за которым следует оператор `*`: +Если мы хотим включить в область видимости *все* общедоступные элементы, определённые в пути, мы можем указать этот путь, за которым следует приказчик `*`: ```rust use std::collections::*; ``` -Эта указание `use` подключает все открытые элементы из звена `std::collections` в текущую область видимости. Будьте осторожны при использовании оператора `*`! Он может усложнить понимание, какие имена находятся в области видимости и где были определены имена, используемые в вашей программе. +Эта указание `use` подключает все открытые элементы из звена `std::collections` в текущую область видимости. Будьте осторожны при использовании приказчика `*`! Он может усложнить понимание, какие имена находятся в области видимости и где были определены имена, используемые в вашей программе. -Оператор `*` часто используется при проверке для подключения всего что есть в звене `tests`; мы поговорим об этом в разделе ["Как писать проверки"] Главы 11. Оператор `*` также иногда используется как часть образца *самостоятельного подключения (prelude)*: смотрите [документацию по встроенной библиотеке](https://doc.rust-lang.org/std/prelude/index.html#other-preludes) для получения дополнительной сведений об этом образце. +Приказчик `*` часто используется при проверке для подключения всего что есть в звене `tests`; мы поговорим об этом в разделе ["Как писать проверки"] Главы 11. Приказчик `*` также иногда используется как часть образца *самостоятельного подключения (prelude)*: смотрите [пособие по встроенной библиотеке](https://doc.rust-lang.org/std/prelude/index.html#other-preludes) для получения дополнительной сведений об этом образце. ["Создание случайного числа"]: ch02-00-guessing-game-tutorial.html#generating-a-random-number diff --git a/rustbook-ru/src/ch07-05-separating-modules-into-different-files.md b/rustbook-ru/src/ch07-05-separating-modules-into-different-files.md index a095db85e..00faccf7c 100644 --- a/rustbook-ru/src/ch07-05-separating-modules-into-different-files.md +++ b/rustbook-ru/src/ch07-05-separating-modules-into-different-files.md @@ -1,10 +1,10 @@ ## Разделение звеньев на разные файлы -До сих пор все примеры в этой главе определяли несколько звеньев в одном файле. Когда звенья становятся большими, вы можете захотеть переместить их определения в отдельные файлы, чтобы упростить навигацию по коду. +До сих пор все примеры в этой главе определяли несколько звеньев в одном файле. Когда звенья становятся большими, вы можете захотеть переместить их определения в отдельные файлы, чтобы упростить навигацию по рукописи. -Например, давайте начнём с кода из приложения 7-17, в котором было несколько звеньев ресторана. Мы будем извлекать звенья в файлы вместо того, чтобы определять все звенья в корневом звене ящика. В нашем случае корневой звено ящика - *src/lib.rs*, но это разделение также работает и с двоичными ящиками, у которых корневой звено ящика — *src/main.rs*. +Например, давайте начнём с рукописи из приложения 7-17, в котором было несколько звеньев ресторана. Мы будем извлекать звенья в файлы вместо того, чтобы определять все звенья в корневом звене ящика. В нашем случае корневой звено ящика - *src/lib.rs*, но это разделение также работает и с двоичными ящиками, у которых корневой звено ящика — *src/main.rs*. -Сначала мы извлечём звено `front_of_house` в свой собственный файл. Удалите код внутри фигурных скобок для звена `front_of_house`, оставив только объявление `mod front_of_house;`, так что теперь *src/lib.rs* содержит код, показанный в приложении 7-21. Обратите внимание, что этот исход не собирается, пока мы не создадим файл *src/front_of_house.rs* из приложении 7-22. +Сначала мы извлечём звено `front_of_house` в свой собственный файл. Удалите рукопись внутри фигурных скобок для звена `front_of_house`, оставив только объявление `mod front_of_house;`, так что теперь *src/lib.rs* содержит рукопись, показанный в приложении 7-21. Обратите внимание, что этот исход не собирается, пока мы не создадим файл *src/front_of_house.rs* из приложении 7-22. Файл: src/lib.rs @@ -14,7 +14,7 @@ Приложение 7-21. Объявление звена front_of_house, чьё содержимое будет в src/front_of_house.rs -Затем поместим код, который был в фигурных скобках, в новый файл с именем *src/front_of_house.rs*, как показано в приложении 7-22. Сборщик знает, что нужно искать в этом файле, потому что он наткнулся в корневом звене ящика на объявление звена с именем `front_of_house`. +Затем поместим рукопись, который был в фигурных скобках, в новый файл с именем *src/front_of_house.rs*, как показано в приложении 7-22. Сборщик знает, что нужно искать в этом файле, потому что он наткнулся в корневом звене ящика на объявление звена с именем `front_of_house`. Файл: src/front_of_house.rs @@ -24,7 +24,7 @@ Приложение 7-22. Определение содержимого звена front_of_house в файле src/front_of_house.rs -Обратите внимание, что вам нужно только *один раз* загрузить файл с помощью объявления `mod` в вашем дереве звеньев. Как только сборщик узнает, что файл является частью дела (и узнает, где в дереве звеньев находится код из-за того, куда вы помеисполнения указанию `mod`), другие файлы в вашем деле должны ссылаться на код загруженного файла, используя путь к месту, где он был объявлен, как описано в разделе [«Пути для ссылки на элемент в дереве звеньев»]. Другими словами, `mod` — это *не* действие «включения», которую вы могли видеть в других языках программирования. +Обратите внимание, что вам нужно только *один раз* загрузить файл с помощью объявления `mod` в вашем дереве звеньев. Как только сборщик узнает, что файл является частью дела (и узнает, где в дереве звеньев находится рукопись из-за того, куда вы помеисполнения указанию `mod`), другие файлы в вашем деле должны ссылаться на рукопись загруженного файла, используя путь к месту, где он был объявлен, как описано в разделе [«Пути для ссылки на элемент в дереве звеньев»]. Другими словами, `mod` — это *не* действие «включения», которую вы могли видеть в других языках программирования. Далее мы извлечём звено `hosting` в его собственный файл. Этап немного отличается, потому что `hosting` является дочерним звеном для `front_of_house`, а не корневого звена. Мы поместим файл для `hosting` в новый папка, который будет назван по имени его предка в дереве звеньев, в данном случае это *src/front_of_house/*. @@ -44,16 +44,16 @@ {{#rustdoc_include ../listings/ch07-managing-growing-projects/no-listing-02-extracting-hosting/src/front_of_house/hosting.rs}} ``` -Если вместо этого мы поместим *hosting.rs* в папка *src*, сборщик будет думать, что код в *hosting.rs* это звено `hosting`, объявленный в корне ящика, а не объявленный как дочерний звено `front_of_house`. Правила сборщика для проверки какие файлы содержат код каких звеньев предполагают, что папки и файлы точно соответствуют дереву звеньев. +Если вместо этого мы поместим *hosting.rs* в папка *src*, сборщик будет думать, что рукопись в *hosting.rs* это звено `hosting`, объявленный в корне ящика, а не объявленный как дочерний звено `front_of_house`. Правила сборщика для проверки какие файлы содержат рукопись каких звеньев предполагают, что папки и файлы точно соответствуют дереву звеньев. > ### Иные пути к файлам > -> До сих пор мы рассматривали наиболее идиоматические пути к файлам, используемые сборщиком Rust, но Ржавчина также поддерживает и старый исполнение пути к файлу. Для звена с именем `front_of_house`, объявленного в корневом звене ящика, сборщик будет искать код звена в: +> До сих пор мы рассматривали наиболее идиоматические пути к файлам, используемые сборщиком Ржавчина, но Ржавчина также поддерживает и старый исполнение пути к файлу. Для звена с именем `front_of_house`, объявленного в корневом звене ящика, сборщик будет искать рукопись звена в: > > - *src/front_of_house.rs* (что мы рассматривали) > - *src/front_of_house/mod.rs* (старый исполнение, всё ещё поддерживаемый путь) > -> Для звена с именем `hosting`, который является подзвеном `front_of_house`, сборщик будет искать код звена в: +> Для звена с именем `hosting`, который является подзвеном `front_of_house`, сборщик будет искать рукопись звена в: > > - *src/front_of_house/hosting.rs* (что мы рассматривали) > - *src/front_of_house/hosting/mod.rs* (старый исполнение, всё ещё поддерживаемый путь) @@ -62,15 +62,15 @@ > > Основным недостатком исполнения, в котором используются файлы с именами *mod.rs*, является то, что в вашем деле может оказаться много файлов с именами *mod.rs*, что может привести к путанице, если вы одновременно откроете их в редакторе. -Мы перенесли код каждого звена в отдельный файл, а дерево звеньев осталось прежним. Вызовы функций в `eat_at_restaurant` будут работать без каких-либо изменений, несмотря на то, что определения находятся в разных файлах. Этот способ позволяет перемещать звенья в новые файлы по мере увеличения их размеров. +Мы перенесли рукопись каждого звена в отдельный файл, а дерево звеньев осталось прежним. Вызовы функций в `eat_at_restaurant` будут работать без каких-либо изменений, несмотря на то, что определения находятся в разных файлах. Этот способ позволяет перемещать звенья в новые файлы по мере увеличения их размеров. -Обратите внимание, что указание `pub use crate::front_of_house::hosting` в *src/lib.rs* также не изменилась, и `use` не влияет на то, какие файлы собираются как часть ящика. Ключевое слово `mod` объявляет звенья, и Ржавчина ищет в файле с тем же именем, что и у звена, код, который входит в этот звено. +Обратите внимание, что указание `pub use crate::front_of_house::hosting` в *src/lib.rs* также не изменилась, и `use` не влияет на то, какие файлы собираются как часть ящика. Ключевое слово `mod` объявляет звенья, и Ржавчина ищет в файле с тем же именем, что и у звена, рукопись, который входит в этот звено. ## Итог -Rust позволяет разбить дополнение на несколько ящиков и ящик - на звенья, так что вы можете ссылаться на элементы, определённые в одном звене, из другого звена. Это можно делать при помощи указания абсолютных или относительных путей. Эти пути можно добавить в область видимости указанием `use`, поэтому вы можете пользоваться более короткими путями для многократного использования элементов в этой области видимости. Код звена по умолчанию является закрытым, но можно сделать определения общедоступными, добавив ключевое слово `pub`. +Ржавчина позволяет разбить дополнение на несколько ящиков и ящик - на звенья, так что вы можете ссылаться на элементы, определённые в одном звене, из другого звена. Это можно делать при помощи указания безусловных или относительных путей. Эти пути можно добавить в область видимости указанием `use`, поэтому вы можете пользоваться более короткими путями для многократного использования элементов в этой области видимости. Рукопись звена по умолчанию является закрытым, но можно сделать определения общедоступными, добавив ключевое слово `pub`. -В следующей главе мы рассмотрим некоторые собрания устройств данных из встроенной библиотеки, которые вы можете использовать в своём правильноно согласованном коде. +В следующей главе мы рассмотрим некоторые собрания устройств данных из встроенной библиотеки, которые вы можете использовать в своём правильноно согласованном рукописи. [«Пути для ссылки на элемент в дереве звеньев»]: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html \ No newline at end of file diff --git a/rustbook-ru/src/ch08-00-common-collections.md b/rustbook-ru/src/ch08-00-common-collections.md index a5a814b31..294e004af 100644 --- a/rustbook-ru/src/ch08-00-common-collections.md +++ b/rustbook-ru/src/ch08-00-common-collections.md @@ -3,10 +3,10 @@ Обычная библиотека содержит несколько полезных устройств данных, которые называются *собраниями*. Большая часть других видов данных представляют собой хранение определенного значения, но особенностью собраний является хранение множества однотипных значений. В отличии от массива или упорядоченного ряда данные собраний хранятся в куче, а это значит, что размер собрания может быть неизвестен в мгновение сборки программы. Он может изменяться (увеличиваться, уменьшаться) во время работы программы. Каждый вид собраний имеет свои возможности и отличается по производительности, так что выбор именно собрания зависит от случаи и является умением разработчика, вырабатываемым со временем. В этой главе будет рассмотрено несколько собраний: - *Вектор (vector)* - позволяет нам сохранять различное количество последовательно хранящихся значений, -- *Строка (string)* - это последовательность символов. Мы же упоминали вид `String` ранее, но в данной главе мы поговорим о нем подробнее. +- *Строка (string)* - это последовательность знаков. Мы же упоминали вид `String` ранее, но в данной главе мы поговорим о нем подробнее. - *Хеш-таблица (hash map)* - собрание которая позволяет хранить перечень ассоциаций значения с ключом (перечень пар ключ:значение). Является именно выполнением более общей устройства данных называемой *map*. -Для того, чтобы узнать о других видах собраний предоставляемых встроенной библиотекой смотрите [документацию](https://doc.rust-lang.org/std/collections/index.html). +Для того, чтобы узнать о других видах собраний предоставляемых встроенной библиотекой смотрите [пособие](https://doc.rust-lang.org/std/collections/index.html). Мы обсудим как создавать и обновлять векторы, строки и хеш-таблицы, а также объясним что делает каждую из них особенной. diff --git a/rustbook-ru/src/ch08-01-vectors.md b/rustbook-ru/src/ch08-01-vectors.md index 40de84e70..9eb25d113 100644 --- a/rustbook-ru/src/ch08-01-vectors.md +++ b/rustbook-ru/src/ch08-01-vectors.md @@ -12,7 +12,7 @@ Приложение 8-1: Создание нового пустого вектора для хранения значений вида i32 -Обратите внимание, что здесь мы добавили изложение вида. Поскольку мы не вставляем никаких значений в этот вектор, Ржавчина не знает, какие элементы мы собираемся хранить. Это важный мгновение. Векторы выполнены с использованием обобщённых видов; мы рассмотрим, как использовать обобщённые виды с вашими собственными видами в Главе 10. А пока знайте, что вид `Vec`, предоставляемый встроенной библиотекой, может хранить любой вид. Когда мы создаём новый вектор для хранения определенного вида, мы можем указать этот вид в угловых скобках. В приложении 8-1 мы сообщили Rust, что `Vec` в `v` будет хранить элементы вида `i32`. +Обратите внимание, что здесь мы добавили изложение вида. Поскольку мы не вставляем никаких значений в этот вектор, Ржавчина не знает, какие элементы мы собираемся хранить. Это важный мгновение. Векторы выполнены с использованием обобщённых видов; мы рассмотрим, как использовать обобщённые виды с вашими собственными видами в Главе 10. А пока знайте, что вид `Vec`, предоставляемый встроенной библиотекой, может хранить любой вид. Когда мы создаём новый вектор для хранения определенного вида, мы можем указать этот вид в угловых скобках. В приложении 8-1 мы сообщили Ржавчина, что `Vec` в `v` будет хранить элементы вида `i32`. Чаще всего вы будете создавать `Vec` с начальными значениями и Ржавчина может определить вид сохраняемых вами значений, но иногда вам всё же придётся указывать изложение вида. Для удобства Ржавчина предоставляет макрос `vec!`, который создаст новый вектор, содержащий заданные вами значения. В приложении 8-2 создаётся новый `Vec`, который будет хранить значения `1`, `2` и `3`. Числовым видом является `i32`, потому что это вид по умолчанию для целочисленных значений, о чём упоминалось в разделе [“Виды данных”] Главы 3. @@ -58,9 +58,9 @@ Приложение 8-5. Попытка доступа к элементу с порядковым указателем 100 в векторе, содержащем пять элементов -Когда мы запускаем этот код, первая строка с `&v[100]` вызовет панику программы, потому что происходит попытка получить ссылку на несуществующий элемент. Такой подход лучше всего использовать, когда вы хотите, чтобы ваша программа со сбоем завершила работу при попытке доступа к элементу за пределами вектора. +Когда мы запускаем этот рукопись, первая строка с `&v[100]` вызовет сбой программы, потому что происходит попытка получить ссылку на несуществующий элемент. Такой подход лучше всего использовать, когда вы хотите, чтобы ваша программа со сбоем завершила работу при попытке доступа к элементу за пределами вектора. -Когда способу `get` передаётся порядковый указатель, который находится за пределами вектора, он без паники возвращает `None`. Вы могли бы использовать такой подход, если доступ к элементу за пределами рядавектора происходит время от времени при обычных обстоятельствах. Тогда ваш код будет иметь логику для обработки наличия `Some(&element)` или `None`, как обсуждалось в Главе 6. Например, порядковый указательможет исходить от человека, вводящего число. Если пользователь случайно введёт слишком большое число, то программа получит значение `None` и у вас будет возможность сообщить пользователю, сколько элементов находится в текущем векторе, и дать ему возможность ввести допустимое значение. Такое поведение было бы более дружелюбным для пользователя, чем внезапный сбой программы из-за опечатки! +Когда способу `get` передаётся порядковый указатель, который находится за пределами вектора, он без сбоя возвращает `None`. Вы могли бы использовать такой подход, если доступ к элементу за пределами рядавектора происходит время от времени при обычных обстоятельствах. Тогда ваша рукопись будет иметь ход мыслей для обработки наличия `Some(&element)` или `None`, как обсуждалось в Главе 6. Например, порядковый указательможет исходить от человека, вводящего число. Если пользователь случайно введёт слишком большое число, то программа получит значение `None` и у вас будет возможность сообщить пользователю, сколько элементов находится в текущем векторе, и дать ему возможность ввести допустимое значение. Такое поведение было бы более дружелюбным для пользователя, чем внезапный сбой программы из-за опечатки! Когда у программы есть действительная ссылка, borrow checker (средство проверки заимствований), обеспечивает соблюдение правил владения и заимствования (описанные в Главе 4), чтобы обеспечить, что эта ссылка и любые другие ссылки на содержимое вектора остаются действительными. Вспомните правило, которое гласит, что у вас не может быть изменяемых и неизменяемых ссылок в одной и той же области. Это правило применяется в приложении 8-6, где мы храним неизменяемую ссылку на первый элемент вектора и затем пытаемся добавить элемент в конец вектора. Данная программа не будет работать, если мы также попробуем сослаться на данный элемент позже в функции: @@ -70,27 +70,27 @@ Приложение 8-6. Попытка добавить некоторый элемент в вектор, в то время когда есть ссылка на элемент вектора -Сборка этого кода приведёт к ошибке: +Сборка этого рукописи приведёт к ошибке: ```console {{#include ../listings/ch08-common-collections/listing-08-06/output.txt}} ``` -Код в приложении 8-6 может выглядеть так, как будто он должен работать. Почему ссылка на первый элемент должна заботиться об изменениях в конце вектора? Эта ошибка возникает из-за особенности того, как работают векторы: поскольку векторы размещают значения в памяти друг за другом, добавление нового элемента в конец вектора может потребовать выделения новой памяти и повторения старых элементов в новое пространство, если нет достаточного места, чтобы разместить все элементы друг за другом там, где в данный мгновение хранится вектор. В этом случае ссылка на первый элемент будет указывать на освобождённую память. Правила заимствования предотвращают попадание программ в такую случай. +Рукопись в приложении 8-6 может выглядеть так, как будто он должен работать. Почему ссылка на первый элемент должна заботиться об изменениях в конце вектора? Эта ошибка возникает из-за особенности того, как работают векторы: поскольку векторы размещают значения в памяти друг за другом, добавление нового элемента в конец вектора может потребовать выделения новой памяти и повторения старых элементов в новое пространство, если нет достаточного места, чтобы разместить все элементы друг за другом там, где в данный мгновение хранится вектор. В этом случае ссылка на первый элемент будет указывать на освобождённую память. Правила заимствования предотвращают попадание программ в такую случай. > Примечание: Дополнительные сведения о выполнения вида `Vec` смотрите в разделе ["The Rustonomicon"](https://doc.rust-lang.org/nomicon/vec/vec.html). ### Перебор значений в векторе -Для доступа к каждому элементу вектора по очереди, мы повторяем все элементы, вместо использования порядковых указателей для доступа к одному за раз. В приложении 8-7 показано, как использовать цикл `for` для получения неизменяемых ссылок на каждый элемент в векторе значений вида `i32` и их вывода. +Для доступа к каждому элементу вектора по очереди, мы повторяем все элементы, вместо использования порядковых указателей для доступа к одному за раз. В приложении 8-7 показано, как использовать круговорот `for` для получения неизменяемых ссылок на каждый элемент в векторе значений вида `i32` и их вывода. ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-07/src/main.rs:here}} ``` -Приложение 8-7. Печать каждого элемента векторе, при помощи повторения по элементам вектора с помощью цикла for +Приложение 8-7. Печать каждого элемента векторе, при помощи повторения по элементам вектора с помощью круговорота for -Мы также можем повторять изменяемые ссылки на каждый элемент изменяемого вектора, чтобы вносить изменения во все элементы. Цикл `for` в приложении 8-8 добавит `50` к каждому элементу. +Мы также можем повторять изменяемые ссылки на каждый элемент изменяемого вектора, чтобы вносить изменения во все элементы. Круговорот `for` в приложении 8-8 добавит `50` к каждому элементу. ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-08/src/main.rs:here}} @@ -98,9 +98,9 @@ Приложение 8-8. Повторение и изменение элементов вектора по изменяемым ссылкам -Чтобы изменить значение на которое ссылается изменяемая ссылка, мы должны использовать оператор разыменования ссылки `*` для получения значения по ссылке в переменной `i` прежде чем использовать оператор `+=`. Мы поговорим подробнее об операторе разыменования в разделе [“Следование по указателю к значению с помощью оператора разыменования”] Главы 15. +Чтобы изменить значение на которое ссылается изменяемая ссылка, мы должны использовать приказчик разыменования ссылки `*` для получения значения по ссылке в переменной `i` прежде чем использовать приказчик `+=`. Мы поговорим подробнее об приказчике разыменования в разделе [“Следование по указателю к значению с помощью приказчика разыменования”] Главы 15. -Перебор вектора, будь то неизменяемый или изменяемый, безопасен из-за правил проверки заимствования. Если бы мы попытались вставить или удалить элементы в телах цикла `for` в приложениях 8-7 и 8-8, мы бы получили ошибку сборщика, подобную той, которую мы получили с кодом в приложении 8-6. Ссылка на вектор, содержащийся в цикле for, предотвращает одновременную изменение всего вектора. +Перебор вектора, будь то неизменяемый или изменяемый, безопасен из-за правил проверки заимствования. Если бы мы попытались вставить или удалить элементы в телах круговорота `for` в приложениях 8-7 и 8-8, мы бы получили ошибку сборщика, подобную той, которую мы получили с рукописью в приложении 8-6. Ссылка на вектор, содержащийся в круговороте for, предотвращает одновременную изменение всего вектора. ### Использование перечислений для хранения множества разных видов @@ -114,11 +114,11 @@ Приложение 8-9: Определение enum для хранения значений разных видов в одном векторе -Rust должен знать, какие виды будут в векторе во время сборки, чтобы точно знать сколько памяти в куче потребуется для хранения каждого элемента. Мы также должны чётко указать, какие виды разрешены в этом векторе. Если бы Ржавчина позволял вектору содержать любой вид, то был бы шанс что один или несколько видов вызовут ошибки при выполнении действий над элементами вектора. Использование перечисления вместе с выражением `match` означает, что во время сборки Ржавчина заверяет, что все возможные случаи будут обработаны, как обсуждалось в главе 6. +Ржавчина должен знать, какие виды будут в векторе во время сборки, чтобы точно знать сколько памяти в куче потребуется для хранения каждого элемента. Мы также должны чётко указать, какие виды разрешены в этом векторе. Если бы Ржавчина позволял вектору содержать любой вид, то был бы возможность что один или несколько видов вызовут ошибки при выполнении действий над элементами вектора. Использование перечисления вместе с выражением `match` означает, что во время сборки Ржавчина заверяет, что все возможные случаи будут обработаны, как обсуждалось в главе 6. Если вы не знаете исчерпывающий набор видов, которые программа получит во время выполнения для хранения в векторе, то техника использования перечисления не сработает. Вместо этого вы можете использовать особенность-предмет, который мы рассмотрим в главе 17. -Теперь, когда мы обсудили некоторые из наиболее распространённых способов использования векторов, обязательно ознакомьтесь [с документацией по API вектора](https://doc.rust-lang.org/std/vec/struct.Vec.html), чтобы узнать о множестве полезных способов, определённых в `Vec` встроенной библиотеки. Например, в дополнение к способу `push`, существует способ `pop`, который удаляет и возвращает последний элемент. +Теперь, когда мы обсудили некоторые из наиболее распространённых способов использования векторов, обязательно ознакомьтесь [с пособием по API вектора](https://doc.rust-lang.org/std/vec/struct.Vec.html), чтобы узнать о множестве полезных способов, определённых в `Vec` встроенной библиотеки. Например, в дополнение к способу `push`, существует способ `pop`, который удаляет и возвращает последний элемент. ### Удаление элементов из вектора @@ -136,4 +136,4 @@ Rust должен знать, какие виды будут в векторе [“Виды данных”]: ch03-02-data-types.html#data-types -[“Следование по указателю к значению с помощью оператора разыменования”]: ch15-02-deref.html#following-the-pointer-to-the-value-with-the-dereference-operator \ No newline at end of file +[“Следование по указателю к значению с помощью приказчика разыменования”]: ch15-02-deref.html#following-the-pointer-to-the-value-with-the-dereference-operator \ No newline at end of file diff --git a/rustbook-ru/src/ch08-02-strings.md b/rustbook-ru/src/ch08-02-strings.md index 5fbdfb627..35e30c81d 100644 --- a/rustbook-ru/src/ch08-02-strings.md +++ b/rustbook-ru/src/ch08-02-strings.md @@ -1,14 +1,14 @@ ## Хранение закодированного текста UTF-8 в строках -Мы говорили о строках в главе 4, но сейчас мы рассмотрим их более подробно. Новички в Ржавчина обычно застревают на строках из-за сочетания трёх причин: склонность Ржавчина сборщика к выявлению возможных ошибок, более сложная устройства данных чем считают многие программисты и UTF-8. Эти обстоятельства объединяются таким образом, что направление может показаться сложной, если вы пришли из других языков программирования. +Мы говорили о строках в главе 4, но сейчас мы рассмотрим их более подробно. Новички в Ржавчине обычно застревают на строках из-за сочетания трёх причин: склонность сборщика Ржавчины к выявлению возможных ошибок, более сложная устройства данных чем считают многие программисты и UTF-8. Эти обстоятельства объединяются таким образом, что направление может показаться сложной, если вы пришли из других языков программирования. Полезно обсуждать строки в среде собраний, потому что строки выполнены в виде набора байтов, плюс некоторые способы для обеспечения полезной возможности, когда эти байты преобразуются как текст. В этом разделе мы поговорим об действиех над `String` таких как создание, обновление и чтение, которые есть у каждого вида собраний. Мы также обсудим какими особенностями `String` отличается от других собраний, а именно каким образом упорядочевание в `String` осложняется различием между тем как люди и компьютеры преобразуют данные заключённые в `String`. ### Что же такое строка? -Сначала мы определим, что мы подразумеваем под понятием *строка* (string). В Ржавчина есть только один строковый вид в ядре языка - срез строки `str`, обычно используемый в заимствованном виде как `&str`. В Главе 4 мы говорили о *срезах строк, string slices*, которые являются ссылками на некоторые строковые данные в кодировке UTF-8. Например, строковые записи хранятся в двоичном файле программы и поэтому являются срезами строк. +Сначала мы определим, что мы подразумеваем под понятием *строка* (string). В Ржавчине есть только один строковый вид в ядре языка - срез строки `str`, обычно используемый в заимствованном виде как `&str`. В Главе 4 мы говорили о *срезах строк, string slices*, которые являются ссылками на некоторые строковые данные в кодировке UTF-8. Например, строковые записи хранятся в двоичном файле программы и поэтому являются срезами строк. -Вид `String` предоставляемый встроенной библиотекой Rust, не встроен в ядро языка и является расширяемым, изменяемым, владеющим, строковым видом в UTF-8 кодировке. Когда Rustaceans говорят о "строках" то, они обычно имеют в виду виды `String` или строковые срезы `&str`, а не просто один из них. Хотя этот раздел в основном посвящён `String`, оба вида усиленно используются в встроенной библиотеке Rust, оба, и `String` и строковые срезы, кодируются в UTF-8. +Вид `String` предоставляемый встроенной библиотекой Ржавчина, не встроен в ядро языка и является расширяемым, изменяемым, владеющим, строковым видом в UTF-8 кодировке. Когда Rustaceans говорят о "строках" то, они обычно имеют в виду виды `String` или строковые срезы `&str`, а не просто один из них. Хотя этот раздел в основном посвящён `String`, оба вида усиленно используются в встроенной библиотеке Ржавчина оба, и `String` и строковые срезы, кодируются в UTF-8. ### Создание новых строк @@ -30,7 +30,7 @@ Эти выражения создают строку с `initial contents`. -Мы также можем использовать функцию `String::from` для создания `String` из строкового записи. Код приложения 8-13 является эквивалентным коду из приложения 8-12, который использует функцию `to_string`: +Мы также можем использовать функцию `String::from` для создания `String` из строкового записи. Рукопись приложения 8-13 является эквивалентным рукописи из приложения 8-12, который использует функцию `to_string`: ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-13/src/main.rs:here}} @@ -52,7 +52,7 @@ ### Обновление строковых данных -Строка `String` может увеличиваться в размере, а её содержимое может меняться, по подобию как содержимое `Vec` при вставке в него большего количества данных. Кроме того, можно использовать оператор `+` или макрос `format!` для объединения значений `String`. +Строка `String` может увеличиваться в размере, а её содержимое может меняться, по подобию как содержимое `Vec` при вставке в него большего количества данных. Кроме того, можно использовать приказчик `+` или макрос `format!` для объединения значений `String`. #### Присоединение к строке с помощью `push_str` и `push` @@ -64,7 +64,7 @@ Приложение 8-15. Добавление среза строки к String с помощью способа push_str -После этих двух строк кода `s` будет содержать `foobar`. Способ `push_str` принимает строковый срез, потому что мы не всегда хотим владеть входным свойствоом. Например, код в приложении 8-16 показывает исход, когда будет не желательно поведение, при котором мы не сможем использовать `s2` после его добавления к содержимому значения переменной `s1`. +После этих двух строк рукописи `s` будет содержать `foobar`. Способ `push_str` принимает строковый срез, потому что мы не всегда хотим владеть входным свойствоом. Например, рукопись в приложении 8-16 показывает исход, когда будет не желательно поведение, при котором мы не сможем использовать `s2` после его добавления к содержимому значения переменной `s1`. ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-16/src/main.rs:here}} @@ -72,59 +72,59 @@ Приложение 8-16: Использование среза строки после добавления её содержимого к другой String -Если способ `push_str` стал бы владельцем переменной`s2`, мы не смогли бы напечатать его значение в последней строке. Однако этот код работает так, как мы ожидали! +Если способ `push_str` стал бы владельцем переменной`s2`, мы не смогли бы напечатать его значение в последней строке. Однако этот рукопись работает так, как мы ожидали! -Способ `push` принимает один символ в качестве свойства и добавляет его к `String`. В приложении 8-17 показан код, добавляющий букву “l” к `String` используя способ `push`. +Способ `push` принимает один знак в качестве свойства и добавляет его к `String`. В приложении 8-17 показан рукопись, добавляющий букву “l” к `String` используя способ `push`. ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-17/src/main.rs:here}} ``` -Приложение 8-17: Добавление одного символа в String значение используя push +Приложение 8-17: Добавление одного знака в String значение используя push В итоге `s` будет содержать `lol`. -#### Объединение строк с помощью оператора `+` или макроса `format!` +#### Объединение строк с помощью приказчика `+` или макроса `format!` -Часто хочется объединять две существующие строки. Один из возможных способов — это использование оператора `+` из приложения 8-18: +Часто хочется объединять две существующие строки. Один из возможных способов — это использование приказчика `+` из приложения 8-18: ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-18/src/main.rs:here}} ``` -Приложение 8-18: Использование оператора + для объединения двух значений String в новое String значение +Приложение 8-18: Использование приказчика + для объединения двух значений String в новое String значение -Строка `s3` будет содержать `Hello, world!`. Причина того, что `s1` после добавления больше недействительна и причина, по которой мы использовали ссылку на `s2` имеют отношение к ярлыке вызываемого способа при использовании оператора `+`. Оператор `+` использует способ `add`, чья ярлык выглядит примерно так: +Строка `s3` будет содержать `Hello, world!`. Причина того, что `s1` после добавления больше недействительна и причина, по которой мы использовали ссылку на `s2` имеют отношение к ярлыке вызываемого способа при использовании приказчика `+`. Приказчик `+` использует способ `add`, чья ярлык выглядит примерно так: ```rust,ignore fn add(self, s: &str) -> String { ``` -В встроенной библиотеке вы увидите способ `add` определённым с использованием обобщённых и связанных видов. Здесь мы видим ярлык с определенными видами, заменяющими обобщённый, что происходит когда вызывается данный способ со значениями `String`. Мы обсудим обобщённые виды в Главе 10. Эта ярлык даёт нам ключ для понимания особенностей оператора `+`. +В встроенной библиотеке вы увидите способ `add` определённым с использованием обобщённых и связанных видов. Здесь мы видим ярлык с определенными видами, заменяющими обобщённый, что происходит когда вызывается данный способ со значениями `String`. Мы обсудим обобщённые виды в Главе 10. Эта ярлык даёт нам ключ для понимания особенностей приказчика `+`. -Во-первых, перед `s2` мы видим `&`, что означает что мы складываем *ссылку* на вторую строку с первой строкой. Это происходит из-за свойства `s` в функции `add`: мы можем добавить только `&str` к `String`; мы не можем сложить два значения `String`. Но подождите — вид `&s2` это `&String`, а не `&str`, как определён второй свойство в `add`. Так почему код в приложении 8-18 собирается? +Во-первых, перед `s2` мы видим `&`, что означает что мы складываем *ссылку* на вторую строку с первой строкой. Это происходит из-за свойства `s` в функции `add`: мы можем добавить только `&str` к `String`; мы не можем сложить два значения `String`. Но подождите — вид `&s2` это `&String`, а не `&str`, как определён второй свойство в `add`. Так почему рукопись в приложении 8-18 собирается? -Причина, по которой мы можем использовать `&s2` в вызове `add` заключается в том, что сборщик может *принудительно привести (coerce)* переменная вида `&String` к виду `&str`. Когда мы вызываем способ `add` в Ржавчина используется *принудительное приведение* (deref coercion), которое превращает `&s2` в `&s2[..]`. Мы подробно обсудим принудительное приведение в Главе 15. Так как `add` не забирает во владение свойство `s`, `s2` по прежнему будет действительной строкой `String` после применения действия. +Причина, по которой мы можем использовать `&s2` в вызове `add` заключается в том, что сборщик может *принудительно привести (coerce)* переменная вида `&String` к виду `&str`. Когда мы вызываем способ `add` в Ржавчине используется *принудительное приведение* (deref coercion), которое превращает `&s2` в `&s2[..]`. Мы подробно обсудим принудительное приведение в Главе 15. Так как `add` не забирает во владение свойство `s`, `s2` по прежнему будет действительной строкой `String` после применения действия. -Во-вторых, как можно видеть в ярлыке, `add` забирает во владение `self`, потому что `self` *не имеет* `&`. Это означает, что `s1` в приложении 8-18 будет перемещён в вызов `add` и больше не будет действителен после этого вызова. Не смотря на то, что код `let s3 = s1 + &s2;` выглядит как будто он воспроизведет обе строки и создаёт новую, эта указание в действительности забирает во владение переменную `s1`, присоединяет к ней повтор содержимого `s2`, а затем возвращает владение итогом. Другими словами, это выглядит как будто код создаёт множество повторов, но это не так; данная выполнение более эффективна, чем повторение. +Во-вторых, как можно видеть в ярлыке, `add` забирает во владение `self`, потому что `self` *не имеет* `&`. Это означает, что `s1` в приложении 8-18 будет перемещён в вызов `add` и больше не будет действителен после этого вызова. Не смотря на то, что рукопись `let s3 = s1 + &s2;` выглядит как будто он воспроизведет обе строки и создаёт новую, эта указание в действительности забирает во владение переменную `s1`, присоединяет к ней повтор содержимого `s2`, а затем возвращает владение итогом. Другими словами, это выглядит как будто рукопись создаёт множество повторов, но это не так; данная выполнение более производительна, чем повторение. -Если нужно объединить несколько строк, поведение оператора `+` становится громоздким: +Если нужно объединить несколько строк, поведение приказчика `+` становится громоздким: ```rust {{#rustdoc_include ../listings/ch08-common-collections/no-listing-01-concat-multiple-strings/src/main.rs:here}} ``` -Здесь переменная `s` будет содержать `tic-tac-toe`. С множеством символов `+` и `"` становится трудно понять, что происходит. Для более сложного соединения строк можно использовать макрос `format!`: +Здесь переменная `s` будет содержать `tic-tac-toe`. С множеством знаков `+` и `"` становится трудно понять, что происходит. Для более сложного соединения строк можно использовать макрос `format!`: ```rust {{#rustdoc_include ../listings/ch08-common-collections/no-listing-02-format/src/main.rs:here}} ``` -Этот код также устанавливает переменную `s` в значение `tic-tac-toe`. Макрос `format!` работает тем же способом что макрос `println!`, но вместо вывода на экран возвращает вид `String` с содержимым. Исполнение кода с использованием `format!` значительно легче читается, а также код, созданный макросом `format!`, использует ссылки, а значит не забирает во владение ни один из его свойств. +Этот рукопись также устанавливает переменную `s` в значение `tic-tac-toe`. Макрос `format!` работает тем же способом что макрос `println!`, но вместо вывода на экран возвращает вид `String` с содержимым. Исполнение рукописи с использованием `format!` значительно легче читается, а также рукопись, созданный макросом `format!`, использует ссылки, а значит не забирает во владение ни один из его свойств. ### Упорядочевание в строках -Доступ к отдельным символам в строке, при помощи ссылки на них по порядковому указателю, является допустимой и распространённой действием во многих других языках программирования. Тем не менее, если вы попытаетесь получить доступ к частям `String`, используя правила написания упорядочевания в Rust, то вы получите ошибку. Рассмотрим неверный код в приложении 8-19. +Доступ к отдельным знакам в строке, при помощи ссылки на них по порядковому указателю, является допустимой и распространённой действием во многих других языках программирования. Тем не менее, если вы попытаетесь получить доступ к частям `String`, используя правила написания упорядочевания в Ржавчине, то вы получите ошибку. Рассмотрим неверный рукопись в приложении 8-19. ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch08-common-collections/listing-08-19/src/main.rs:here}} @@ -132,13 +132,13 @@ fn add(self, s: &str) -> String { Приложение 8-19: Попытка использовать правила написания порядкового указателя со строкой -Этот код приведёт к следующей ошибке: +Этот рукопись приведёт к следующей ошибке: ```console {{#include ../listings/ch08-common-collections/listing-08-19/output.txt}} ``` -Ошибка и примечание говорит, что в Ржавчина строки не поддерживают упорядочевание. Но почему так? Чтобы ответить на этот вопрос, нужно обсудить то, как Ржавчина хранит строки в памяти. +Ошибка и примечание говорит, что в Ржавчине строки не поддерживают упорядочевание. Но почему так? Чтобы ответить на этот вопрос, нужно обсудить то, как Ржавчина хранит строки в памяти. #### Внутреннее представление @@ -154,20 +154,20 @@ fn add(self, s: &str) -> String { {{#rustdoc_include ../listings/ch08-common-collections/listing-08-14/src/main.rs:russian}} ``` -Отвечая на вопрос, какова длина строки, вы можете ответить 12. Однако ответ Ржавчина - 24, что равно числу байт, необходимых для кодирования «Здравствуйте» в UTF-8, так происходит, потому что каждое одиночное значение Unicode символа в этой строке занимает 2 байта памяти. Следовательно, порядковый указательпо байтам строки не всегда бы соответствовал действительному одиночному Unicode значению. Для отображения рассмотрим этот недопустимый код Rust: +Отвечая на вопрос, какова длина строки, вы можете ответить 12. Однако ответ Ржавчина - 24, что равно числу байт, необходимых для кодирования «Здравствуйте» в UTF-8, так происходит, потому что каждое одиночное значение Unicode знака в этой строке занимает 2 байта памяти. Следовательно, порядковый указательпо байтам строки не всегда бы соответствовал действительному одиночному Unicode значению. Для отображения рассмотрим этот недопустимый рукопись Ржавчина: ```rust,ignore,does_not_compile let hello = "Здравствуйте"; let answer = &hello[0]; ``` -Каким должно быть значение переменной `answer`? Должно ли оно быть значением первой буквы `З`? При кодировке в UTF-8, первый байт значения `З` равен `208`, а второй - `151`, поэтому значение в `answer` на самом деле должно быть `208`, но само по себе `208` не является действительным символом. Возвращение `208`, скорее всего не то, что хотел бы получить пользователь: ведь он ожидает первую букву этой строки; тем не менее, это единственный байт данных, который в Ржавчина доступен по порядковому указателю 0. Пользователи обычно не хотят получить значение байта, даже если строка содержит только латинские буквы: если `&"hello"[0] `было бы допустимым кодом, который вернул значение байта, то он вернул бы `104`, а не `h`. +Каким должно быть значение переменной `answer`? Должно ли оно быть значением первой буквы `З`? При кодировке в UTF-8, первый байт значения `З` равен `208`, а второй - `151`, поэтому значение в `answer` на самом деле должно быть `208`, но само по себе `208` не является действительным знаком. Возвращение `208`, скорее всего не то, что хотел бы получить пользователь: ведь он ожидает первую букву этой строки; тем не менее, это единственный байт данных, который в Ржавчине доступен по порядковому указателю 0. Пользователи обычно не хотят получить значение байта, даже если строка содержит только латинские буквы: если `&"hello"[0] `было бы допустимым рукописью, который вернул значение байта, то он вернул бы `104`, а не `h`. -Таким образом, чтобы предотвратить возврат непредвиденного значения, вызывающего ошибки которые не могут быть сразу обнаружены, Ржавчина просто не собирает такой код и предотвращает недопонимание на ранних этапах этапа разработки. +Таким образом, чтобы предотвратить возврат непредвиденного значения, вызывающего ошибки которые не могут быть сразу обнаружены, Ржавчина просто не собирает такой рукопись и предотвращает недопонимание на ранних этапах этапа разработки. #### Байты, одиночные значения и кластеры графем! Боже мой! -Ещё один мгновение, касающийся UTF-8, заключается в том, что на самом деле существует три способа рассмотрения строк с точки зрения Rust: как байты, как одиночные значения и как кластеры графем (самая близкая вещь к тому, что мы назвали бы *буквами*). +Ещё один мгновение, касающийся UTF-8, заключается в том, что на самом деле существует три способа рассмотрения строк с точки зрения Ржавчины: как байты, как одиночные значения и как кластеры графем (самая близкая вещь к тому, что мы назвали бы *буквами*). Если посмотреть на слово языка хинди «नमस्ते», написанное в транскрипции Devanagari, то оно хранится как вектор значений `u8` который выглядит следующим образом: @@ -188,15 +188,15 @@ let answer = &hello[0]; ["न", "म", "स्", "ते"] ``` -Rust предоставляет различные способы преобразования необработанных строковых данных, которые компьютеры хранят так, чтобы каждой программе можно было выбрать необходимую преобразование, независимо от того, на каком человеческом языке представлены эти данные. +Ржавчина предоставляет различные способы преобразования необработанных строковых данных, которые компьютеры хранят так, чтобы каждой программе можно было выбрать необходимую преобразование, независимо от того, на каком человеческом языке представлены эти данные. -Последняя причина, по которой Ржавчина не позволяет нам упорядочивать `String` для получения символов является то, что программисты ожидают, что действия упорядочевания всегда имеют постоянное время (O(1)) выполнения. Но невозможно обеспечить такую производительность для `String`, потому что Ржавчина понадобилось бы пройтись по содержимому от начала до порядкового указателя, чтобы определить, сколько было действительных символов. +Последняя причина, по которой Ржавчина не позволяет нам упорядочивать `String` для получения знаков является то, что программисты ожидают, что действия упорядочевания всегда имеют постоянное время (O(1)) выполнения. Но невозможно обеспечить такую производительность для `String`, потому что Ржавчина понадобилось бы пройтись по содержимому от начала до порядкового указателя, чтобы определить, сколько было действительных знаков. ### Срезы строк -Упорядочевание строк часто является плохой мыслью, потому что не ясно каким должен быть возвращаемый вид такой действия: байтовым значением, символом, кластером графем или срезом строки. Поэтому Ржавчина просит вас быть более определенным, если действительно требуется использовать порядковые указатели для создания срезов строк. +Упорядочевание строк часто является плохой мыслью, потому что не ясно каким должен быть возвращаемый вид такой действия: байтовым значением, знаком, кластером графем или срезом строки. Поэтому Ржавчина просит вас быть более определенным, если действительно требуется использовать порядковые указатели для создания срезов строк. -Вместо упорядочевания с помощью числового порядкового указателя `[]`, вы можете использовать оператор ряда`[]` при создании среза строки в котором содержится указание на то, срез каких байтов надо делать: +Вместо упорядочевания с помощью числового порядкового указателя `[]`, вы можете использовать приказчик ряда`[]` при создании среза строки в котором содержится указание на то, срез каких байтов надо делать: ```rust let hello = "Здравствуйте"; @@ -204,9 +204,9 @@ let hello = "Здравствуйте"; let s = &hello[0..4]; ``` -Здесь переменная `s` будет вида `&str` который содержит первые 4 байта строки. Ранее мы упоминали, что каждый из этих символов был по 2 байта, что означает, что `s` будет содержать "Зд". +Здесь переменная `s` будет вида `&str` который содержит первые 4 байта строки. Ранее мы упоминали, что каждый из этих знаков был по 2 байта, что означает, что `s` будет содержать "Зд". -Что бы произошло, если бы мы использовали `&hello[0..1]`? Ответ: Ржавчина бы запаниковал во время выполнения точно так же, как если бы обращались к недействительному порядковому указателю в векторе: +Что бы произошло, если бы мы использовали `&hello[0..1]`? Ответ: Ржавчина бы вызвал сбой во время выполнения точно так же, как если бы обращались к недействительному порядковому указателю в векторе: ```console {{#include ../listings/ch08-common-collections/output-only-01-not-char-boundary/output.txt}} @@ -216,7 +216,7 @@ let s = &hello[0..4]; ### Способы для перебора строк -Лучший способ работать с отрывками строк — чётко указать, нужны ли вам символы или байты. Для отдельных одиночных значений в Юникоде используйте способ `chars`. Вызов `chars` у "Зд" выделяет и возвращает два значения вида `char`, и вы можете выполнить повторение по итогу для доступа к каждому элементу: +Лучший способ работать с отрывками строк — чётко указать, нужны ли вам знаки или байты. Для отдельных одиночных значений в Юнирукописи используйте способ `chars`. Вызов `chars` у "Зд" выделяет и возвращает два значения вида `char`, и вы можете выполнить повторение по итогу для доступа к каждому элементу: ```rust for c in "Зд".chars() { @@ -224,7 +224,7 @@ for c in "Зд".chars() { } ``` -Код напечатает следующее: +Рукопись напечатает следующее: ```text З @@ -239,7 +239,7 @@ for b in "Зд".bytes() { } ``` -Этот код выведет четыре байта, составляющих эту строку: +Этот рукопись выведет четыре байта, составляющих эту строку: ```text 208 @@ -254,8 +254,8 @@ for b in "Зд".bytes() { ### Строки не так просты -Подводя итог, становится ясно, что строки сложны. Различные языки программирования выполняют различные исходы того, как представить эту сложность для программиста. В Ржавчина решили сделать правильную обработку данных `String` поведением по умолчанию для всех программ Rust, что означает, что программисты должны заранее продумать обработку UTF-8 данных. Этот соглашение раскрывает большую сложность строк, чем в других языках программирования, но это предотвращает от необходимости обрабатывать ошибки, связанные с не-ASCII символами которые могут появиться в ходе разработки позже. +Подводя итог, становится ясно, что строки сложны. Различные языки программирования выполняют различные исходы того, как представить эту сложность для программиста. В Ржавчине решили сделать правильную обработку данных `String` поведением по умолчанию для всех программ Ржавчина, что означает, что программисты должны заранее продумать обработку UTF-8 данных. Этот соглашение раскрывает большую сложность строк, чем в других языках программирования, но это предотвращает от необходимости обрабатывать ошибки, связанные с не-ASCII знаками которые могут появиться в ходе разработки позже. -Хорошая новость состоит в том что обычная библиотека предлагает множество полезных возможностей, построенных на основе видов `String` и `&str`, чтобы помочь правильно обрабатывать эти сложные случаи. Обязательно ознакомьтесь с документацией для полезных способов, таких как `contains` для поиска в строке и `replace` для замены частей строки другой строкой. +Хорошая новость состоит в том что обычная библиотека предлагает множество полезных возможностей, построенных на основе видов `String` и `&str`, чтобы помочь правильно обрабатывать эти сложные случаи. Обязательно ознакомьтесь с пособием для полезных способов, таких как `contains` для поиска в строке и `replace` для замены частей строки другой строкой. Давайте переключимся на что-то немного менее сложное: HashMap! diff --git a/rustbook-ru/src/ch08-03-hash-maps.md b/rustbook-ru/src/ch08-03-hash-maps.md index 645587216..e2c591c46 100644 --- a/rustbook-ru/src/ch08-03-hash-maps.md +++ b/rustbook-ru/src/ch08-03-hash-maps.md @@ -2,9 +2,9 @@ Последняя собрание, которую мы рассмотрим, будет *hash map* (хеш-карта). Вид `HashMap` хранит ключи вида `K` на значения вида `V`. Данная устройства согласует и хранит данные с помощью *функции хеширования*. Во множестве языков программирования выполнена данная устройства, но часто с разными наименованиями: такими как hash, map, object, hash table, dictionary или ассоциативный массив. -Хеш-карты полезны, когда нужно искать данные не используя порядковый указатель, как это например делается в векторах, а с помощью ключа, который может быть любого вида. Например, в игре вы можете отслеживать счёт каждой приказы в хеш-карте, в которой каждый ключ - это название приказы, а значение - счёт приказы. Имея имя приказы, вы можете получить её счёт из хеш-карты. +Хеш-карты полезны, когда нужно искать данные не используя порядковый указатель, как это например делается в векторах, а с помощью ключа, который может быть любого вида. Например, в игре вы можете отслеживать счёт каждого приказа в хеш-карте, в которой каждый ключ - это название приказы, а значение - счёт приказы. Имея имя приказы, вы можете получить её счёт из хеш-карты. -В этом разделе мы рассмотрим основной API хеш-карт. Остальной набор полезных функций скрывается в объявлении вида `HashMap`. Как и прежде, советуем обратиться к документации по встроенной библиотеке для получения дополнительной сведений. +В этом разделе мы рассмотрим основной API хеш-карт. Остальной набор полезных функций скрывается в объявлении вида `HashMap`. Как и прежде, советуем обратиться к пособия по встроенной библиотеке для получения дополнительной сведений. ### Создание новой хеш-карты @@ -32,13 +32,13 @@ Здесь `score` будет иметь количество очков, связанное с приказом "Blue", итог будет `10`. Способ `get` возвращает `Option<&V>`; если для какого-то ключа нет значения в HashMap, `get` вернёт `None`. Из-за такого подхода программе следует обрабатывать `Option`, вызывая `copied` для получения `Option` вместо `Option<&i32>`, затем `unwrap_or` для установки `score` в ноль, если scores не содержит данных по этому ключу. -Мы можем перебирать каждую пару ключ/значение в HashMap таким же образом, как мы делали с векторами, используя цикл `for`: +Мы можем перебирать каждую пару ключ/значение в HashMap таким же образом, как мы делали с векторами, используя круговорот `for`: ```rust {{#rustdoc_include ../listings/ch08-common-collections/no-listing-03-iterate-over-hashmap/src/main.rs:here}} ``` -Этот код будет печатать каждую пару в произвольном порядке: +Этот рукопись будет печатать каждую пару в произвольном порядке: ```text Yellow: 50 @@ -67,7 +67,7 @@ Blue: 10 #### Перезапись старых значений -Если мы вставим ключ и значение в HashMap, а затем вставим тот же ключ с новым значением, то старое значение связанное с этим ключом, будет заменено на новое. Даже несмотря на то, что код в приложении 8-23 вызывает `insert` дважды, хеш-карта будет содержать только одну пару ключ/значение, потому что мы вставляем значения для одного и того же ключа - ключа приказы "Blue". +Если мы вставим ключ и значение в HashMap, а затем вставим тот же ключ с новым значением, то старое значение связанное с этим ключом, будет заменено на новое. Даже несмотря на то, что рукопись в приложении 8-23 вызывает `insert` дважды, хеш-карта будет содержать только одну пару ключ/значение, потому что мы вставляем значения для одного и того же ключа - ключа приказы "Blue". ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-23/src/main.rs:here}} @@ -75,7 +75,7 @@ Blue: 10 Приложение 8-23: Замена значения, хранимого в определенном ключе -Код напечатает `{"Blue": 25}`. Начальное значение `10` было перезаписано. +Рукопись напечатает `{"Blue": 25}`. Начальное значение `10` было перезаписано. @@ -85,7 +85,7 @@ Blue: 10 Обычно проверяют, существует ли определенный ключ в хеш-карте со значением, а затем предпринимаются следующие действия: если ключ существует в хеш-карте, существующее значение должно оставаться таким, какое оно есть. Если ключ не существует, то вставляют его и значение для него. -Хеш-карты имеют для этого особый API, называемый `entry` , который принимает ключ для проверки в качестве входного свойства. Возвращаемое значение способа `entry` - это перечисление `Entry`, с двумя исходами: первый представляет значение, которое может существовать, а второй говорит о том, что значение отсутствует. Допустим, мы хотим проверить, имеется ли ключ и связанное с ним значение для приказы "Yellow". Если хеш-карта не имеет значения для такого ключа, то мы хотим вставить значение 50. То же самое мы хотим проделать и для приказы "Blue". Используем API `entry` в коде приложения 8-24. +Хеш-карты имеют для этого особый API, называемый `entry` , который принимает ключ для проверки в качестве входного свойства. Возвращаемое значение способа `entry` - это перечисление `Entry`, с двумя исходами: первый представляет значение, которое может существовать, а второй говорит о том, что значение отсутствует. Допустим, мы хотим проверить, имеется ли ключ и связанное с ним значение для приказы "Yellow". Если хеш-карта не имеет значения для такого ключа, то мы хотим вставить значение 50. То же самое мы хотим проделать и для приказы "Blue". Используем API `entry` в рукописи приложения 8-24. ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-24/src/main.rs:here}} @@ -93,13 +93,13 @@ Blue: 10 Приложение 8-24: Использование способа entry для вставки значения только в том случае, когда ключ не имеет значения -Способ `or_insert` определён в `Entry` так, чтобы возвращать изменяемую ссылку на соответствующее значение ключа внутри исхода перечисления `Entry`, когда этот ключ существует, а если его нет, то вставлять свойство в качестве нового значения этого ключа и возвращать изменяемую ссылку на новое значение. Эта техника намного чище, чем самостоятельное написание логики и, кроме того, она более безопасна и согласуется с правилами заимствования. +Способ `or_insert` определён в `Entry` так, чтобы возвращать изменяемую ссылку на соответствующее значение ключа внутри исхода перечисления `Entry`, когда этот ключ существует, а если его нет, то вставлять свойство в качестве нового значения этого ключа и возвращать изменяемую ссылку на новое значение. Эта техника намного чище, чем самостоятельное написание хода мыслей и, кроме того, она более безопасна и согласуется с правилами заимствования. -При выполнении кода приложения 8-24 будет напечатано `{"Yellow": 50, "Blue": 10}`. Первый вызов `способа entry` вставит ключ для приказы "Yellow" со значением 50, потому что для жёлтой приказы ещё не имеется значения в HashMap. Второй вызов `entry` не изменит хеш-карту, потому что для ключа приказы "Blue" уже имеется значение 10. +При выполнении рукописи приложения 8-24 будет напечатано `{"Yellow": 50, "Blue": 10}`. Первый вызов `способа entry` вставит ключ для приказы "Yellow" со значением 50, потому что для жёлтой приказы ещё не имеется значения в HashMap. Второй вызов `entry` не изменит хеш-карту, потому что для ключа приказы "Blue" уже имеется значение 10. #### Создание нового значения на основе старого значения -Другим распространённым исходом использования хеш-карт является поиск значения по ключу, а затем обновление этого значения на основе старого значения. Например, в приложении 8-25 показан код, который подсчитывает, сколько раз определённое слово встречается в некотором тексте. Мы используем HashMap со словами в качестве ключей и увеличиваем соответствующее слову значение, чтобы отслеживать, сколько раз мы встретили это слово. Если мы впервые встретили слово, то сначала вставляем значение 0. +Другим распространённым исходом использования хеш-карт является поиск значения по ключу, а затем обновление этого значения на основе старого значения. Например, в приложении 8-25 показан рукопись, который подсчитывает, сколько раз определённое слово встречается в некотором тексте. Мы используем HashMap со словами в качестве ключей и увеличиваем соответствующее слову значение, чтобы отслеживать, сколько раз мы встретили это слово. Если мы впервые встретили слово, то сначала вставляем значение 0. ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-25/src/main.rs:here}} @@ -107,13 +107,13 @@ Blue: 10 Приложение 8-25: Подсчёт количества вхождений слов с использованием хеш-карты, которая хранит слова и счётчики -Этот код напечатает `{"world": 2, "hello": 1, "wonderful": 1}`. Если вы увидите, что пары ключ/значение печатаются в другом порядке, то вспомните, что мы писали в разделы ["Доступ к данным в HashMap"], что повторение по хеш-карте происходит в произвольном порядке. +Этот рукопись напечатает `{"world": 2, "hello": 1, "wonderful": 1}`. Если вы увидите, что пары ключ/значение печатаются в другом порядке, то вспомните, что мы писали в разделы ["Доступ к данным в HashMap"], что повторение по хеш-карте происходит в произвольном порядке. -Способ `split_whitespace` возвращает повторитель по срезам строки, разделённых пробелам, для строки `text`. Способ `or_insert` возвращает изменяемую ссылку (`&mut V`) на значение ключа. Мы сохраняем изменяемую ссылку в переменной `count`, для этого, чтобы присвоить переменной значение, необходимо произвести разыменование с помощью звёздочки (*). Изменяемая ссылка удаляется сразу же после выхода из области видимости цикла `for`, поэтому все эти изменения безопасны и согласуются с правилами заимствования. +Способ `split_whitespace` возвращает повторитель по срезам строки, разделённых пробелам, для строки `text`. Способ `or_insert` возвращает изменяемую ссылку (`&mut V`) на значение ключа. Мы сохраняем изменяемую ссылку в переменной `count`, для этого, чтобы присвоить переменной значение, необходимо произвести разыменование с помощью звёздочки (*). Изменяемая ссылка удаляется сразу же после выхода из области видимости круговорота `for`, поэтому все эти изменения безопасны и согласуются с правилами заимствования. ### Функция хеширования -По умолчанию `HashMap` использует функцию хеширования *SipHash*, которая может противостоять атакам класса отказ в обслуживании, Denial of Service (DoS) с использованием хеш-таблиц [siphash](https://en.wikipedia.org/wiki/SipHash). Это не самый быстрый из возможных алгоритмов хеширования, в данном случае производительность идёт на соглашение с обеспечением лучшей безопасности. Если после профилирования вашего кода окажется, что хеш-функция, используемая по умолчанию, очень медленная, вы можете заменить её используя другой hasher. *Hasher* - это вид, выполняющий особенность `BuildHasher`. Подробнее о особенностях мы поговорим в Главе 10. Вам совсем не обязательно выполнить свою собственную функцию хеширования; [crates.io](https://crates.io/) имеет достаточное количество библиотек, предоставляющих разные выполнения hasher с множеством общих алгоритмов хеширования. +По умолчанию `HashMap` использует функцию хеширования *SipHash*, которая может противостоять атакам класса отказ в обслуживании, Denial of Service (DoS) с использованием хеш-таблиц [siphash](https://en.wikipedia.org/wiki/SipHash). Это не самый быстрый из возможных алгоритмов хеширования, в данном случае производительность идёт на соглашение с обеспечением лучшей безопасности. Если после профилирования вашей рукописи окажется, что хеш-функция, используемая по умолчанию, очень медленная, вы можете заменить её используя другой hasher. *Hasher* - это вид, выполняющий особенность `BuildHasher`. Подробнее о особенностях мы поговорим в Главе 10. Вам совсем не обязательно выполнить свою собственную функцию хеширования; [crates.io](https://crates.io/) имеет достаточное количество библиотек, предоставляющих разные выполнения hasher с множеством общих алгоритмов хеширования. ## Итоги @@ -123,7 +123,7 @@ Blue: 10 - Преобразуйте строку в кодировку "поросячьей латыни" (Pig Latin). Первая согласная каждого слова перемещается в конец и к ней добавляется окончание "ay", так "first" станет "irst-fay". Слову, начинающемуся на гласную, в конец добавляется "hay" ("apple" становится "apple-hay"). Помните о подробностях работы с кодировкой UTF-8! - Используя хеш-карту и векторы, создайте текстовый внешняя оболочка позволяющий пользователю добавлять имена сотрудников к названию отдела предприятия. Например, "Add Sally to Engineering" или "Add Amir to Sales". Затем позвольте пользователю получить список всех людей из отдела или всех людей в предприятия, отсортированных по отделам в алфавитном порядке. -Документация API встроенной библиотеки описывает способы у векторов, строк и HashMap. Советуем воспользоваться ей при решении упражнений. +Пособие API встроенной библиотеки описывает способы у векторов, строк и HashMap. Советуем воспользоваться ей при решении упражнений. Потихоньку мы переходим к более сложным программам, в которых действия могут потерпеть неудачу. Наступило наилучшее время для обсуждения обработки ошибок. diff --git a/rustbook-ru/src/ch09-00-error-handling.md b/rustbook-ru/src/ch09-00-error-handling.md index d61de2909..c4a7a97fb 100644 --- a/rustbook-ru/src/ch09-00-error-handling.md +++ b/rustbook-ru/src/ch09-00-error-handling.md @@ -1,7 +1,7 @@ # Обработка ошибок -Возникновение ошибок в ходе выполнения программ — это суровая действительность в жизни программного обеспечения, поэтому Ржавчина имеет ряд функций для обработки случаев, в которых что-то идёт не так. Во многих случаях Ржавчина требует, чтобы вы признали возможность ошибки и предприняли некоторые действия, прежде чем ваш код будет собран. Это требование делает вашу программу более надёжной, обеспечивая, что вы обнаружите ошибки и обработаете их надлежащим образом, прежде чем развернёте свой код в производственной среде! +Возникновение ошибок в ходе выполнения программ — это суровая действительность в жизни программного обеспечения, поэтому Ржавчина имеет ряд функций для обработки случаев, в которых что-то идёт не так. Во многих случаях Ржавчина требует, чтобы вы признали возможность ошибки и предприняли некоторые действия, прежде чем ваша рукопись будет собран. Это требование делает вашу программу более надёжной, обеспечивая, что вы обнаружите ошибки и обработаете их надлежащим образом, прежде чем развернёте свой рукопись в производственной среде! -В Ржавчина ошибки объединяются на две основные разряды: *исправимые* (recoverable) и *неисправимые* (unrecoverable). В случае исправимой ошибки, такой как *файл не найден*, мы, скорее всего, просто хотим сообщить о неполадке пользователю и повторить действие. Неисправимые ошибки всегда являются симптомами изъянов в коде, например, попытка доступа к ячейке за пределами границ массива, и поэтому мы хотим немедленно остановить программу. +В Ржавчине ошибки объединяются на две основные разряды: *исправимые* (recoverable) и *неисправимые* (unrecoverable). В случае исправимой ошибки, такой как *файл не найден*, мы, скорее всего, просто хотим сообщить о неполадке пользователю и повторить действие. Неисправимые ошибки всегда являются симптомами изъянов в рукописи, например, попытка доступа к ячейке за пределами границ массива, и поэтому мы хотим немедленно остановить программу. -Большинство языков не различают эти два вида ошибок и обрабатывают оба вида одинаково, используя такие рычаги, как исключения. В Ржавчина нет исключений. Вместо этого он имеет вид `Result` для обрабатываемых (исправимых) ошибок и макрос `panic!`, который останавливает выполнение, когда программа встречает необрабатываемую (неисправимую) ошибку. Сначала эта глава расскажет про вызов `panic!`, а потом расскажет о возврате значений `Result`. Кроме того, мы рассмотрим, что нужно учитывать при принятии решения о том, следует ли попытаться исправить ошибку или остановить выполнение. +Большинство языков не различают эти два вида ошибок и обрабатывают оба вида одинаково, используя такие рычаги, как исключения. В Ржавчине нет исключений. Вместо этого он имеет вид `Result` для обрабатываемых (исправимых) ошибок и макрос `panic!`, который останавливает выполнение, когда программа встречает необрабатываемую (неисправимую) ошибку. Сначала эта глава расскажет про вызов `panic!`, а потом расскажет о возврате значений `Result`. Кроме того, мы рассмотрим, что нужно учитывать при принятии решения о том, следует ли попытаться исправить ошибку или остановить выполнение. diff --git a/rustbook-ru/src/ch09-01-unrecoverable-errors-with-panic.md b/rustbook-ru/src/ch09-01-unrecoverable-errors-with-panic.md index 2556719ef..1312236a9 100644 --- a/rustbook-ru/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/rustbook-ru/src/ch09-01-unrecoverable-errors-with-panic.md @@ -1,12 +1,12 @@ ## Неустранимые ошибки с макросом `panic!` -Иногда в коде происходят плохие вещи, и вы ничего не можете с этим поделать. В этих случаях у Ржавчина есть макрос panic! В действительностисуществует два способа вызвать панику: путём выполнения действия, которое вызывает панику в нашем коде (например, обращение к массиву за пределами его размера) или путём явного вызова макроса `panic!`. В обоих случаях мы вызываем панику в нашей программе. По умолчанию паника выводит сообщение об ошибке, раскручивает и очищает обойма вызовов, и завершают работу. С помощью переменной окружения вы также можете заставить Ржавчина отображать обойма вызовов при возникновении паники, чтобы было легче отследить источник паники. +Иногда в рукописи происходят плохие вещи, и вы ничего не можете с этим поделать. В этих случаях у Ржавчина есть макрос panic! В действительности существует два способа вызвать сбой: путём выполнения действия, которое вызывает сбой в нашем рукописи (например, обращение к массиву за пределами его размера) или путём явного вызова макроса `panic!`. В обоих случаях мы вызываем сбой в нашей программе. По умолчанию сбой выводит сообщение об ошибке, раскручивает и очищает обойма вызовов, и завершают работу. С помощью переменной окружения вы также можете заставить Ржавчина отображать обойма вызовов при возникновении сбоя, чтобы было легче отследить источник сбоя. -> ### Раскручивать обойма или прерывать выполнение программы в ответ на панику? +> ### Раскручивать обойма или прерывать выполнение программы в ответ на сбой? > -> По умолчанию, когда происходит паника, программа начинает этап *раскрутки обоймы*, означающий в Ржавчина проход обратно по обойме вызовов и очистку данных для каждой обнаруженной функции. Тем не менее, этот обратный проход по обойме и очистка порождают много работы. Ржавчина как иное решение предоставляет вам возможность *немедленного прерывания* (aborting), которое завершает работу программы без очистки. +> По умолчанию, когда происходит сбой, программа начинает этап *раскрутки обоймы*, означающий в Ржавчине проход обратно по обойме вызовов и очистку данных для каждой обнаруженной функции. Тем не менее, этот обратный проход по обойме и очистка порождают много работы. Ржавчина, как иное решение предоставляет вам возможность *немедленного прерывания* (aborting), которое завершает работу программы без очистки. > -> Память, которую использовала программа, должна быть очищена операционной системой. Если в вашем деле нужно насколько это возможно сделать маленьким исполняемый файл, вы можете переключиться с исхода раскрутки обоймы на исход прерывания при панике, добавьте `panic = 'abort'` в раздел [profile] вашего Cargo.toml файла. Например, если вы хотите прервать панику в режиме исполнения, добавьте это: +> Память, которую использовала программа, должна быть очищена операционной системой. Если в вашем деле нужно насколько это возможно сделать маленьким исполняемый файл, вы можете переключиться с исхода раскрутки обоймы на исход прерывания при сбое, добавьте `panic = 'abort'` в раздел [profile] вашего Cargo.toml файла. Например, если вы хотите прервать сбой в режиме исполнения, добавьте это: > > ```toml > [profile.release] @@ -28,13 +28,13 @@ ``` -Выполнение макроса `panic!` вызывает сообщение об ошибке, содержащееся в двух последних строках. Первая строка показывает сообщение паники и место в исходном коде, где возникла паника: *src/main.rs:2:5* указывает, что это вторая строка, пятый символ внутри нашего файла *src/main.rs* +Выполнение макроса `panic!` вызывает сообщение об ошибке, содержащееся в двух последних строках. Первая строка показывает сообщение сбоя и место в исходном рукописи, где возникла сбой: *src/main.rs:2:5* указывает, что это вторая строка, пятый знак внутри нашего файла *src/main.rs* -В этом случае указанная строка является частью нашего кода, и если мы перейдём к этой строке, мы увидим вызов макроса `panic!`. В других случаях вызов `panic!` мог бы произойти в стороннем коде, который вызывает наш код, тогда имя файла и номер строки для сообщения об ошибке будет из чужого кода, где макрос `panic!` выполнен, а не из строк нашего кода, которые в конечном итоге привели к выполнению `panic!`. Мы можем использовать обратную трассировку вызовов функций которые вызвали `panic!` чтобы выяснить, какая часть нашего кода вызывает неполадку. Мы обсудим обратную трассировку более подробно далее. +В этом случае указанная строка является частью нашего рукописи, и если мы перейдём к этой строке, мы увидим вызов макроса `panic!`. В других случаях вызов `panic!` мог бы произойти в стороннем рукописи, который вызывает нашу рукопись, тогда имя файла и номер строки для сообщения об ошибке будет из чужого рукописи, где макрос `panic!` выполнен, а не из строк нашего рукописи, которые в конечном итоге привели к выполнению `panic!`. Мы можем использовать обратную трассировку вызовов функций которые вызвали `panic!` чтобы выяснить, какая часть нашего рукописи вызывает неполадку. Мы обсудим обратную трассировку более подробно далее. ### Использование обратной трассировки `panic!` -Давайте посмотрим на другой пример, где, вызов `panic!` происходит в сторонней библиотеке из-за ошибки в нашем коде (а не как в примере ранее, из-за вызова макроса нашим кодом напрямую). В приложении 9-1 приведён код, который пытается получить доступ по порядковому указателю в векторе за пределами допустимого рядазначений порядкового указателя. +Давайте посмотрим на другой пример, где, вызов `panic!` происходит в сторонней библиотеке из-за ошибки в нашем рукописи (а не как в примере ранее, из-за вызова макроса нашим рукописью напрямую). В приложении 9-1 приведён рукопись, который пытается получить доступ по порядковому указателю в векторе за пределами допустимого рядазначений порядкового указателя. Файл: src/main.rs @@ -44,17 +44,17 @@ Приложение 9-1: Попытка доступа к элементу за пределами вектора, которая вызовет panic! -Здесь мы пытаемся получить доступ к 100-му элементу вектора (который находится по порядковому указателю 99, потому что упорядочевание начинается с нуля), но вектор имеет только 3 элемента. В этой случаи, Ржавчина будет вызывать панику. Использование `[]` должно возвращать элемент, но вы передаёте неверный порядковый указатель: не существует элемента, который Ржавчина мог бы вернуть. +Здесь мы пытаемся получить доступ к 100-му элементу вектора (который находится по порядковому указателю 99, потому что упорядочевание начинается с нуля), но вектор имеет только 3 элемента. В этой случаи, Ржавчина будет вызывать сбой. Использование `[]` должно возвращать элемент, но вы передаёте неверный порядковый указатель: не существует элемента, который Ржавчина мог бы вернуть. В языке C, например, попытка прочесть за пределами конца устройства данных (в нашем случае векторе) приведёт к *неопределённому поведению, undefined behavior, UB*. Вы всё равно получите значение, которое находится в том месте памяти компьютера, которое соответствовало бы этому элементу в векторе, несмотря на то, что память по тому адресу совсем не принадлежит вектору (всё просто: C рассчитал бы место хранения элемента с порядковым указателем 99 и считал бы то, что там хранится, упс). Это называется *чтением за пределом буфера, buffer overread,* и может привести к уязвимостям безопасности. Если злоумышленник может управлять порядковым указателем таким образом, то у него появляется возможность читать данные, которые он не должен иметь возможности читать. -Чтобы защитить вашу программу от такого рода уязвимостей при попытке прочитать элемент с порядковым указателем, которого не существует, Ржавчина остановит выполнение и откажется продолжить работу программы. Давайте попробуем так сделать и посмотрим на поведение Rust: +Чтобы защитить вашу программу от такого рода уязвимостей при попытке прочитать элемент с порядковым указателем, которого не существует, Ржавчина остановит выполнение и откажется продолжить работу программы. Давайте попробуем так сделать и посмотрим на поведение Ржавчина: ```console {{#include ../listings/ch09-error-handling/listing-09-01/output.txt}} ``` -Следующая строка говорит, что мы можем установить переменную среды `RUST_BACKTRACE`, чтобы получить обратную трассировку того, что именно стало причиной ошибки. Обратная трассировка создаёт список всех функций, которые были вызваны до какой-то определённой точки выполнения программы. Обратная трассировка в Ржавчина работает так же, как и в других языках. По этому предлагаем вам читать данные обратной трассировки как и везде - читать сверху вниз, пока не увидите сведения о файлах написанных вами. Это место, где возникла неполадка. Другие строки, которые выше над строками с упоминанием наших файлов, - это код, который вызывается нашим кодом; строки ниже являются кодом, который вызывает наш код. Эти строки могут включать основной код Rust, код встроенной библиотеки или используемые ящики. Давайте попробуем получить обратную трассировку с помощью установки переменной среды RUST_BACKTRACE в любое значение, кроме 0. Приложение 9-2 показывает вывод, подобный тому, что вы увидите. +Следующая строка говорит, что мы можем установить переменную среды `RUST_BACKTRACE`, чтобы получить обратную трассировку того, что именно стало причиной ошибки. Обратная трассировка создаёт список всех функций, которые были вызваны до какой-то определённой точки выполнения программы. Обратная трассировка в Ржавчине работает так же, как и в других языках. По этому предлагаем вам читать данные обратной трассировки как и везде - читать сверху вниз, пока не увидите сведения о файлах написанных вами. Это место, где возникла неполадка. Другие строки, которые выше над строками с упоминанием наших файлов, - это рукопись, который вызывается нашим рукописью; строки ниже являются рукописью, который вызывает наш рукопись. Эти строки могут включать основной рукопись Ржавчина, рукопись встроенной библиотеки или используемые ящики. Давайте попробуем получить обратную трассировку с помощью установки переменной среды RUST_BACKTRACE в любое значение, кроме 0. Приложение 9-2 показывает вывод, подобный тому, что вы увидите. этой главы. Далее мы рассмотрим, как восстановить выполнение программы после исправляемых ошибок, использующих вид `Result`. diff --git a/rustbook-ru/src/ch09-02-recoverable-errors-with-result.md b/rustbook-ru/src/ch09-02-recoverable-errors-with-result.md index c6da9ddc6..f97686368 100644 --- a/rustbook-ru/src/ch09-02-recoverable-errors-with-result.md +++ b/rustbook-ru/src/ch09-02-recoverable-errors-with-result.md @@ -11,7 +11,7 @@ enum Result { } ``` -Виды `T` и `E` являются свойствами обобщённого вида: мы обсудим обобщённые виды более подробно в Главе 10. Все что вам нужно знать прямо сейчас - это то, что `T` представляет вид значения, которое будет возвращено в случае успеха внутри исхода `Ok`, а `E` представляет вид ошибки, которая будет возвращена при сбое внутри исхода `Err`. Так как вид `Result` имеет эти обобщённые свойства (generic type parameters), мы можем использовать вид `Result` и функции, которые определены для него, в разных случаейх, когда вид успешного значение и значения ошибки, которые мы хотим вернуть, отличаются. +Виды `T` и `E` являются свойствами обобщённого вида: мы обсудим обобщённые виды более подробно в Главе 10. Все что вам нужно знать прямо сейчас - это то, что `T` представляет вид значения, которое будет возвращено в случае успеха внутри исхода `Ok`, а `E` представляет вид ошибки, которая будет возвращена при сбое внутри исхода `Err`. Так как вид `Result` имеет эти обобщённые свойства (generic type parameters), мы можем использовать вид `Result` и функции, которые определены для него, в разных случаях, когда вид успешного значение и значения ошибки, которые мы хотим вернуть, отличаются. Давайте вызовем функцию, которая возвращает значение `Result`, потому что может потерпеть неудачу. В приложении 9-3 мы пытаемся открыть файл. @@ -27,7 +27,7 @@ enum Result { В случае успеха `File::open` значением переменной `greeting_file_result` будет образец `Ok`, содержащий указатель файла. В случае неудачи значение в переменной `greeting_file_result` будет образцом `Err`, содержащим дополнительную сведения о том, какая именно ошибка произошла. -Необходимо дописать в код приложения 9-3 выполнение разных действий в зависимости от значения, которое вернёт вызов `File::open`. Приложение 9-4 показывает один из способов обработки `Result` - пользуясь основным средством языка, таким как выражение `match`, рассмотренным в Главе 6. +Необходимо дописать в рукопись приложения 9-3 выполнение разных действий в зависимости от значения, которое вернёт вызов `File::open`. Приложение 9-4 показывает один из способов обработки `Result` - пользуясь основным средством языка, таким как выражение `match`, рассмотренным в Главе 6. Файл: src/main.rs @@ -37,11 +37,11 @@ enum Result { Приложение 9-4: Использование выражения match для обработки возвращаемых исходов вида Result -Обратите внимание, что также как перечисление `Option`, перечисление `Result` и его исходы, входят в область видимости благодаря авто-подключения (prelude), поэтому не нужно указывать `Result::` перед использованием исходов `Ok` и `Err` в ветках выражения `match`. +Обратите внимание, что также как перечисление `Option`, перечисление `Result` и его исходы, входят в область видимости благодаря само-подключения (prelude), поэтому не нужно указывать `Result::` перед использованием исходов `Ok` и `Err` в ветках выражения `match`. -Если итогом будет `Ok`, этот код вернёт значение `file` из исхода `Ok`, а мы затем присвоим это значение файлового указателя переменной `greeting_file`. После `match` мы можем использовать указатель файла для чтения или записи. +Если итогом будет `Ok`, этот рукопись вернёт значение `file` из исхода `Ok`, а мы затем присвоим это значение файлового указателя переменной `greeting_file`. После `match` мы можем использовать указатель файла для чтения или записи. -Другая ветвь `match` обрабатывает случай, где мы получаем значение `Err` после вызова `File::open`. В этом примере мы решили вызвать макрос `panic!`. Если в нашей текущей папки нет файла с именем *hello.txt* и мы выполним этот код, то мы увидим следующее сообщение от макроса `panic!`: +Другая ветвь `match` обрабатывает случай, где мы получаем значение `Err` после вызова `File::open`. В этом примере мы решили вызвать макрос `panic!`. Если в нашей текущей папки нет файла с именем *hello.txt* и мы выполним этот рукопись, то мы увидим следующее сообщение от макроса `panic!`: ```console {{#include ../listings/ch09-error-handling/listing-09-04/output.txt}} @@ -51,7 +51,7 @@ enum Result { ### Обработка различных ошибок с помощью match -Код в приложении 9-4 будет вызывать `panic!` независимо от того, почему вызов `File::open` не удался. Однако мы хотим предпринять различные действия для разных причин сбоя. Если открытие `File::open` не удалось из-за отсутствия файла, мы хотим создать файл и вернуть его указатель. Если вызов `File::open` не удался по любой другой причине - например, потому что у нас не было прав на открытие файла, то все равно мы хотим вызвать `panic!` как у нас сделано в приложении 9-4. Для этого мы добавляем выражение внутреннего `match`, показанное в приложении 9-5. +Рукопись в приложении 9-4 будет вызывать `panic!` независимо от того, почему вызов `File::open` не удался. Однако мы хотим предпринять различные действия для разных причин сбоя. Если открытие `File::open` не удалось из-за отсутствия файла, мы хотим создать файл и вернуть его указатель. Если вызов `File::open` не удался по любой другой причине - например, потому что у нас не было прав на открытие файла, то все равно мы хотим вызвать `panic!` как у нас сделано в приложении 9-4. Для этого мы добавляем выражение внутреннего `match`, показанное в приложении 9-5. Файл: src/main.rs @@ -66,13 +66,13 @@ tests to fail lol --> Видом значения возвращаемого функцией `File::open` внутри `Err` исхода является `io::Error`, устройства из встроенной библиотеки. Данная устройства имеет способ `kind`, который можно вызвать для получения значения `io::ErrorKind`. Перечисление `io::ErrorKind` из встроенной библиотеки имеет исходы, представляющие различные виды ошибок, которые могут появиться при выполнении действий в `io`. Исход, который мы хотим использовать, это `ErrorKind::NotFound`, который даёт сведения, о том, что файл который мы пытаемся открыть ещё не существует. Итак, во второй строке мы вызываем сопоставление образца с переменной `greeting_file_result` и попадаем в ветку с обработкой ошибки, но также у нас есть внутренняя проверка для сопоставления `error.kind()` ошибки. -Условие, которое мы хотим проверить во внутреннем `match`, заключается в том, является ли значение, возвращаемое `error.kind()`, исходом `NotFound` перечисления `ErrorKind`. Если это так, мы пытаемся создать файл с помощью функции `File::create`. Однако, поскольку вызов `File::create` тоже может завершиться ошибкой, нам нужна обработка ещё одной ошибки, теперь уже во внутреннем выражении `match`. Заметьте: если файл не может быть создан, выводится другое, особое сообщение об ошибке. Вторая же ветка внешнего `match` (который обрабатывает вызов `error.kind()`), остаётся той же самой - в итоге программа паникует при любой ошибке, кроме ошибки отсутствия файла. +Условие, которое мы хотим проверить во внутреннем `match`, заключается в том, является ли значение, возвращаемое `error.kind()`, исходом `NotFound` перечисления `ErrorKind`. Если это так, мы пытаемся создать файл с помощью функции `File::create`. Однако, поскольку вызов `File::create` тоже может завершиться ошибкой, нам нужна обработка ещё одной ошибки, теперь уже во внутреннем выражении `match`. Заметьте: если файл не может быть создан, выводится другое, особое сообщение об ошибке. Вторая же ветка внешнего `match` (который обрабатывает вызов `error.kind()`), остаётся той же самой - в итоге программа вызывает сбой при любой ошибке, кроме ошибки отсутствия файла. > ### Иные использованию `match` с `Result` > -> Как много `match`! Выражение `match` является очень полезным, но в то же время довольно простым. В главе 13 вы узнаете о замыканиях (closures), которые используются во многих способах вида `Result`. Эти способы помогают быть более кратким, чем использование `match` при работе со значениями `Result` в вашем коде. +> Как много `match`! Выражение `match` является очень полезным, но в то же время довольно простым. В главе 13 вы узнаете о замыканиях (closures), которые используются во многих способах вида `Result`. Эти способы помогают быть более кратким, чем использование `match` при работе со значениями `Result` в вашем рукописи. > -> Например, вот другой способ написать ту же логику, что показана в Приложении 9-5, но с использованием замыканий и способа `unwrap_or_else`: +> Например, вот другой способ написать ту же ход мыслей, что показана в Приложении 9-5, но с использованием замыканий и способа `unwrap_or_else`: > > > @@ -93,7 +93,7 @@ tests to fail lol --> > } > ``` > -> Несмотря на то, что данный код имеет такое же поведение как в приложении 9-5, он не содержит ни одного выражения `match` и проще для чтения. Советуем вам вернуться к примеру этого раздела после того как вы прочитаете Главу 13 и изучите способ `unwrap_or_else` по документации встроенной библиотеки. Многие из способов о которых вы узнаете в документации и Главе 13 могут очистить код от больших, вложенных выражений `match` при обработке ошибок. +> Несмотря на то, что данный рукопись имеет такое же поведение как в приложении 9-5, он не содержит ни одного выражения `match` и проще для чтения. Советуем вам вернуться к примеру этого раздела после того как вы прочитаете Главу 13 и изучите способ `unwrap_or_else` по пособия встроенной библиотеки. Многие из способов о которых вы узнаете в пособия и Главе 13 могут очистить рукопись от больших, вложенных выражений `match` при обработке ошибок. ### Краткие способы обработки ошибок - `unwrap` и `expect` @@ -105,7 +105,7 @@ tests to fail lol --> {{#rustdoc_include ../listings/ch09-error-handling/no-listing-04-unwrap/src/main.rs}} ``` -Если мы запустим этот код при отсутствии файла *hello.txt*, то увидим сообщение об ошибке из вызова `panic!` способа `unwrap`: +Если мы запустим этот рукопись при отсутствии файла *hello.txt*, то увидим сообщение об ошибке из вызова `panic!` способа `unwrap`: {{#include ../listings/ch09-error-handling/listing-09-06/src/main.rs:here}} ``` -Приложение 9-6: Функция, которая возвращает ошибки в вызывающий код, используя оператор match +Приложение 9-6: Функция, которая возвращает ошибки в вызывающий рукопись, используя приказчик match Эта функция может быть написана гораздо более коротким способом, но мы начнём с того, что многое сделаем вручную, чтобы изучить обработку ошибок; а в конце покажем более короткий способ. Давайте сначала рассмотрим вид возвращаемого значения: `Result`. Здесь есть возвращаемое значение функции вида `Result` где образцовый свойство `T` был заполнен определенным видом `String` и образцовый свойство `E` был заполнен определенным видом `io::Error`. -Если эта функция выполнится без неполадок. то код, вызывающий эту функцию, получит значение `Ok`, содержащее `String` - имя пользователя, которое эта функция прочитала из файла. Если функция столкнётся с какими-либо неполадками, вызывающий код получит значение `Err`, содержащее образец `io::Error`, который включает дополнительную сведения о том, какие сбоев возникли. Мы выбрали `io::Error` в качестве возвращаемого вида этой функции, потому что это вид значения ошибки, возвращаемого из обеих действий, которые мы вызываем в теле этой функции и которые могут завершиться неудачей: функция `File::open` и способ `read_to_string`. +Если эта функция выполнится без неполадок. то рукопись, вызывающий эту функцию, получит значение `Ok`, содержащее `String` - имя пользователя, которое эта функция прочитала из файла. Если функция столкнётся с какими-либо неполадками, вызывающий рукопись получит значение `Err`, содержащее образец `io::Error`, который включает дополнительную сведения о том, какие сбоев возникли. Мы выбрали `io::Error` в качестве возвращаемого вида этой функции, потому что это вид значения ошибки, возвращаемого из обеих действий, которые мы вызываем в теле этой функции и которые могут завершиться неудачей: функция `File::open` и способ `read_to_string`. -Тело функции начинается с вызова `File::open`. Затем мы обрабатываем значение `Result` с помощью `match`, подобно `match` из приложения 9-4. Если `File::open` завершается успешно, то указатель файла в переменной образца `file` становится значением в изменяемой переменной `username_file` и функция продолжит свою работу. В случае `Err`, вместо вызова `panic!`, мы используем ключевое слово `return` для досрочного возврата из функции и передаём значение ошибки из `File::open`, которое теперь находится в переменной образца `e`, обратно в вызывающий код как значение ошибки этой функции. +Тело функции начинается с вызова `File::open`. Затем мы обрабатываем значение `Result` с помощью `match`, подобно `match` из приложения 9-4. Если `File::open` завершается успешно, то указатель файла в переменной образца `file` становится значением в изменяемой переменной `username_file` и функция продолжит свою работу. В случае `Err`, вместо вызова `panic!`, мы используем ключевое слово `return` для досрочного возврата из функции и передаём значение ошибки из `File::open`, которое теперь находится в переменной образца `e`, обратно в вызывающий рукопись как значение ошибки этой функции. Таким образом, если у нас есть файловый указатель в `username_file`, функция создаёт новую `String` в переменной `username` и вызывает способ `read_to_string` для файлового указателя в `username_file`, чтобы прочитать содержимое файла в `username`. Способ `read_to_string` также возвращает `Result`, потому что он может потерпеть неудачу, даже если `File::open` завершился успешно. Поэтому нам нужен ещё один `match` для обработки этого `Result`: если `read_to_string` завершится успешно, то наша функция сработала, и мы возвращаем имя пользователя из файла, которое теперь находится в `username`, обёрнутое в `Ok`. Если `read_to_string` потерпит неудачу, мы возвращаем значение ошибки таким же образом, как мы возвращали значение ошибки в `match`, который обрабатывал возвращаемое значение `File::open`. Однако нам не нужно явно указывать `return`, потому что это последнее выражение в функции. -Затем код, вызывающий этот, будет обрабатывать получение либо значения `Ok`, содержащего имя пользователя, либо значения `Err`, содержащего `io::Error`. Вызывающий код должен решить, что делать с этими значениями. Если вызывающий код получает значение `Err`, он может вызвать `panic!` и завершить работу программы, использовать имя пользователя по умолчанию или найти имя пользователя, например, не в файле. У нас недостаточно сведений о том, что на самом деле пытается сделать вызывающий код, поэтому мы распространяем всю сведения об успехах или ошибках вверх, чтобы она могла обрабатываться соответствующим образом. +Затем рукопись, вызывающий этот, будет обрабатывать получение либо значения `Ok`, содержащего имя пользователя, либо значения `Err`, содержащего `io::Error`. Вызывающий рукопись должен решить, что делать с этими значениями. Если вызывающий рукопись получает значение `Err`, он может вызвать `panic!` и завершить работу программы, использовать имя пользователя по умолчанию или найти имя пользователя, например, не в файле. У нас недостаточно сведений о том, что на самом деле пытается сделать вызывающий рукопись, поэтому мы распространяем всю сведения об успехах или ошибках вверх, чтобы она могла обрабатываться соответствующим образом. -Эта схема передачи ошибок настолько распространена в Rust, что Ржавчина предоставляет оператор вопросительного знака `?`, чтобы облегчить эту задачу. +Эта схема передачи ошибок настолько распространена в языке Ржавчина, что язык предоставляет приказчик вопросительного знака `?`, чтобы облегчить эту задачу. -#### Сокращение для проброса ошибок: оператор `?` +#### Сокращение для проброса ошибок: приказчик `?` -В приложении 9-7 показана выполнение `read_username_from_file`, которая имеет ту же возможность, что и в приложении 9-6, но в этой выполнения используется оператор `?`. +В приложении 9-7 показана выполнение `read_username_from_file`, которая имеет ту же возможность, что и в приложении 9-6, но в этой выполнения используется приказчик `?`. Файл: src/main.rs @@ -187,17 +187,17 @@ don't want to include it for rustdoc testing purposes. --> {{#include ../listings/ch09-error-handling/listing-09-07/src/main.rs:here}} ``` -Приложение 9-7: Функция, возвращающая ошибки в вызывающий код с помощью оператора ? +Приложение 9-7: Функция, возвращающая ошибки в вызывающий рукопись с помощью приказчика ? -Выражение `?`, расположенное после `Result`, работает почти так же, как и те выражения `match`, которые мы использовали для обработки значений `Result` в приложении 9-6. Если в качестве значения `Result` будет `Ok`, то значение внутри `Ok` будет возвращено из этого выражения, и программа продолжит работу. Если же значение представляет собой `Err`, то `Err` будет возвращено из всей функции, как если бы мы использовали ключевое слово `return`, так что значение ошибки будет передано в вызывающий код. +Выражение `?`, расположенное после `Result`, работает почти так же, как и те выражения `match`, которые мы использовали для обработки значений `Result` в приложении 9-6. Если в качестве значения `Result` будет `Ok`, то значение внутри `Ok` будет возвращено из этого выражения, и программа продолжит работу. Если же значение представляет собой `Err`, то `Err` будет возвращено из всей функции, как если бы мы использовали ключевое слово `return`, так что значение ошибки будет передано в вызывающий рукопись. -Существует разница между тем, что делает выражение `match` из приложения 9-6 и тем, что делает оператор `?`: значения ошибок, для которых вызван оператор `?`, проходят через функцию `from`, определённую в особенности `From` встроенной библиотеки, которая используется для преобразования значений из одного вида в другой. Когда оператор `?` вызывает функцию `from`, полученный вид ошибки преобразуется в вид ошибки, определённый в возвращаемом виде текущей функции. Это полезно, когда функция возвращает только один вид ошибки, для описания всех возможных исходов сбоев, даже если её отдельные составляющие могут выходить из строя по разным причинам. +Существует разница между тем, что делает выражение `match` из приложения 9-6 и тем, что делает приказчик `?`: значения ошибок, для которых вызван приказчик `?`, проходят через функцию `from`, определённую в особенности `From` встроенной библиотеки, которая используется для преобразования значений из одного вида в другой. Когда приказчик `?` вызывает функцию `from`, полученный вид ошибки преобразуется в вид ошибки, определённый в возвращаемом виде текущей функции. Это полезно, когда функция возвращает только один вид ошибки, для описания всех возможных исходов сбоев, даже если её отдельные составляющие могут выходить из строя по разным причинам. -Например, мы могли бы изменить функцию `read_username_from_file` в приложении 9-7, чтобы возвращать пользовательский вид ошибки с именем `OurError`, который мы определим. Если мы также определим `impl From for OurError` для создания образца `OurError` из `io::Error`, то оператор `?`, вызываемый в теле `read_username_from_file`, вызовет `from` и преобразует виды ошибок без необходимости добавления дополнительного кода в функцию. +Например, мы могли бы изменить функцию `read_username_from_file` в приложении 9-7, чтобы возвращать пользовательский вид ошибки с именем `OurError`, который мы определим. Если мы также определим `impl From for OurError` для создания образца `OurError` из `io::Error`, то приказчик `?`, вызываемый в теле `read_username_from_file`, вызовет `from` и преобразует виды ошибок без необходимости добавления дополнительного рукописи в функцию. -В случае приложения 9-7 оператор `?` в конце вызова `File::open` вернёт значение внутри `Ok` в переменную `username_file`. Если произойдёт ошибка, оператор `?` выполнит ранний возврат значения `Err` вызывающему коду. То же самое относится к оператору `?` в конце вызова `read_to_string`. +В случае приложения 9-7 приказчик `?` в конце вызова `File::open` вернёт значение внутри `Ok` в переменную `username_file`. Если произойдёт ошибка, приказчик `?` выполнит ранний возврат значения `Err` вызывающему рукописи. То же самое относится к приказчику `?` в конце вызова `read_to_string`. -Оператор `?` позволяет избавиться от большого количества образцового кода и упростить выполнение этой функции. Мы могли бы даже ещё больше сократить этот код, если бы использовали цепочку вызовов способов сразу после `?`, как показано в приложении 9-8. +Приказчик `?` позволяет избавиться от большого количества образцового рукописи и упростить выполнение этой функции. Мы могли бы даже ещё больше сократить этот рукопись, если бы использовали цепочку вызовов способов сразу после `?`, как показано в приложении 9-8. Файл: src/main.rs @@ -209,7 +209,7 @@ don't want to include it for rustdoc testing purposes. --> {{#include ../listings/ch09-error-handling/listing-09-08/src/main.rs:here}} ``` -Приложение 9-8: Цепочка вызовов способов после оператора ? +Приложение 9-8: Цепочка вызовов способов после приказчика ? Мы перенесли создание новой `String` в `username` в начало функции; эта часть не изменилась. Вместо создания переменной `username_file` мы соединили вызов `read_to_string` непосредственно с итогом `File::open("hello.txt")?`. У нас по-прежнему есть `?` в конце вызова `read_to_string`, и мы по-прежнему возвращаем значение `Ok`, содержащее `username`, когда и `File::open` и `read_to_string` завершаются успешно, а не возвращают ошибки. Возможность снова такая же, как в Приложении 9-6 и Приложении 9-7; это просто другой, более удобный способ её написания. @@ -229,11 +229,11 @@ don't want to include it for rustdoc testing purposes. --> Чтение файла в строку довольно распространённая действие, так что обычная библиотека предоставляет удобную функцию `fs::read_to_string`, которая открывает файл, создаёт новую `String`, читает содержимое файла, размещает его в `String` и возвращает её. Конечно, использование функции `fs::read_to_string` не даёт возможности объяснить обработку всех ошибок, поэтому мы сначала изучили длинный способ. -#### Где можно использовать оператор `?` +#### Где можно использовать приказчик `?` -Оператор `?` может использоваться только в функциях, вид возвращаемого значения которых совместим со значением, для которого используется `?`. Это потому, что оператор `?` определён для выполнения раннего возврата значения из функции таким же образом, как и выражение `match`, которое мы определили в приложении 9-6. В приложении 9-6 `match` использовало значение `Result`, а ответвление с ранним возвратом вернуло значение `Err(e)`. Вид возвращаемого значения функции должен быть `Result`, чтобы он был совместим с этим `return`. +Приказчик `?` может использоваться только в функциях, вид возвращаемого значения которых совместим со значением, для которого используется `?`. Это потому, что приказчик `?` определён для выполнения раннего возврата значения из функции таким же образом, как и выражение `match`, которое мы определили в приложении 9-6. В приложении 9-6 `match` использовало значение `Result`, а ответвление с ранним возвратом вернуло значение `Err(e)`. Вид возвращаемого значения функции должен быть `Result`, чтобы он был совместим с этим `return`. -В приложении 9-10 давайте посмотрим на ошибку, которую мы получим, если воспользуемся оператором `?` в функции `main` с видом возвращаемого значения, несовместимым с видом значения, для которого мы используем `?`: +В приложении 9-10 давайте посмотрим на ошибку, которую мы получим, если воспользуемся приказчиком `?` в функции `main` с видом возвращаемого значения, несовместимым с видом значения, для которого мы используем `?`: Файл: src/main.rs @@ -243,45 +243,45 @@ don't want to include it for rustdoc testing purposes. --> Приложение 9-10: Попытка использовать ? в main функции, которая возвращает () , не будет собираться -Этот код открывает файл, что может привести к сбою. `?` оператор следует за значением `Result` , возвращаемым `File::open` , но эта `main` функция имеет возвращаемый вид `()` , а не `Result` . Когда мы собираем этот код, мы получаем следующее сообщение об ошибке: +Этот рукопись открывает файл, что может привести к сбою. `?` приказчик следует за значением `Result` , возвращаемым `File::open` , но эта `main` функция имеет возвращаемый вид `()` , а не `Result` . Когда мы собираем этот рукопись, мы получаем следующее сообщение об ошибке: ```console {{#include ../listings/ch09-error-handling/listing-09-10/output.txt}} ``` -Эта ошибка указывает на то, что оператор `?` разрешено использовать только в функции, которая возвращает `Result`, `Option` или другой вид, выполняющий `FromResidual`. +Эта ошибка указывает на то, что приказчик `?` разрешено использовать только в функции, которая возвращает `Result`, `Option` или другой вид, выполняющий `FromResidual`. -Для исправления ошибки есть два исхода. Первый - изменить возвращаемый вид вашей функции так, чтобы он был совместим со значением, для которого вы используете оператор `?`, если у вас нет ограничений, препятствующих этому. Другой способ - использовать `match` или один из способов `Result` для обработки `Result` любым подходящим способом. +Для исправления ошибки есть два исхода. Первый - изменить возвращаемый вид вашей функции так, чтобы он был совместим со значением, для которого вы используете приказчик `?`, если у вас нет ограничений, препятствующих этому. Другой способ - использовать `match` или один из способов `Result` для обработки `Result` любым подходящим способом. -В сообщении об ошибке также упоминалось, что `?` можно использовать и со значениями `Option`. Как и при использовании `?` для `Result`, вы можете использовать `?` только для `Option` в функции, которая возвращает `Option`. Поведение оператора `?` при вызове `Option` похоже на его поведение при вызове `Result`: если значение равно `None`, то `None` будет возвращено раньше из функции в этот мгновение. Если значение `Some`, значение внутри `Some` является результирующим значением выражения, и функция продолжает исполняться. В приложении 9-11 приведён пример функции, которая находит последний символ первой строки заданного текста: +В сообщении об ошибке также упоминалось, что `?` можно использовать и со значениями `Option`. Как и при использовании `?` для `Result`, вы можете использовать `?` только для `Option` в функции, которая возвращает `Option`. Поведение приказчика `?` при вызове `Option` похоже на его поведение при вызове `Result`: если значение равно `None`, то `None` будет возвращено раньше из функции в этот мгновение. Если значение `Some`, значение внутри `Some` является результирующим значением выражения, и функция продолжает исполняться. В приложении 9-11 приведён пример функции, которая находит последний знак первой строки заданного текста: ```rust {{#rustdoc_include ../listings/ch09-error-handling/listing-09-11/src/main.rs:here}} ``` -Приложение 9-11: Использование оператора ? для значения Option<T> +Приложение 9-11: Использование приказчика ? для значения Option<T> -Эта функция возвращает `Option`, потому что возможно, что там есть символ, но также возможно, что его нет. Этот код принимает переменная среза `text` строки и вызывает для него способ `lines`, который возвращает повторитель для строк в строке. Поскольку эта функция хочет проверить первую строку, она вызывает `next` у повторителя, чтобы получить первое значение от повторителя. Если `text` является пустой строкой, этот вызов `next` вернёт `None`, и в этом случае мы используем `?` чтобы остановить и вернуть `None` из `last_char_of_first_line`. Если `text` не является пустой строкой, `next` вернёт значение `Some`, содержащее отрывок строки первой строки в `text`. +Эта функция возвращает `Option`, потому что возможно, что там есть знак, но также возможно, что его нет. Этот рукопись принимает переменная среза `text` строки и вызывает для него способ `lines`, который возвращает повторитель для строк в строке. Поскольку эта функция хочет проверить первую строку, она вызывает `next` у повторителя, чтобы получить первое значение от повторителя. Если `text` является пустой строкой, этот вызов `next` вернёт `None`, и в этом случае мы используем `?` чтобы остановить и вернуть `None` из `last_char_of_first_line`. Если `text` не является пустой строкой, `next` вернёт значение `Some`, содержащее отрывок строки первой строки в `text`. -Символ `?` извлекает отрывок строки, и мы можем вызвать `chars` для этого отрывка строки. чтобы получить повторитель символов. Нас важно последний символ в первой строке, поэтому мы вызываем `last`, чтобы вернуть последний элемент в повторителе. Вернётся `Option`, потому что возможно, что первая строка пустая - например, если `text` начинается с пустой строки, но имеет символы в других строках, как в `"\nhi"`. Однако, если в первой строке есть последний символ, он будет возвращён в исходе `Some`. Оператор `?` в середине даёт нам краткий способ выразить эту логику, позволяя выполнить функцию в одной строке. Если бы мы не могли использовать оператор `?` в `Option`, нам пришлось бы выполнить эту логику, используя больше вызовов способов или выражение `match`. +Знак `?` извлекает отрывок строки, и мы можем вызвать `chars` для этого отрывка строки. чтобы получить повторитель знаков. Нас важно последний знак в первой строке, поэтому мы вызываем `last`, чтобы вернуть последний элемент в повторителе. Вернётся `Option`, потому что возможно, что первая строка пустая - например, если `text` начинается с пустой строки, но имеет знаки в других строках, как в `"\nhi"`. Однако, если в первой строке есть последний знак, он будет возвращён в исходе `Some`. Приказчик `?` в середине даёт нам краткий способ выразить эту ход мыслей, позволяя выполнить функцию в одной строке. Если бы мы не могли использовать приказчик `?` в `Option`, нам пришлось бы выполнить эту ход мыслей, используя больше вызовов способов или выражение `match`. -Обратите внимание, что вы можете использовать оператор `?` `Result` в функции, которая возвращает `Result` , и вы можете использовать оператор `?` для `Option` в функции, которая возвращает `Option` , но вы не можете смешивать и сопоставлять. Оператор `?` не будет самостоятельно преобразовывать `Result` в `Option` или наоборот; в этих случаях вы можете использовать такие способы, как способ `ok` для `Result` или способ `ok_or` для `Option`, чтобы выполнить преобразование явно. +Обратите внимание, что вы можете использовать приказчик `?` `Result` в функции, которая возвращает `Result` , и вы можете использовать приказчик `?` для `Option` в функции, которая возвращает `Option` , но вы не можете смешивать и сопоставлять. Приказчик `?` не будет самостоятельно преобразовывать `Result` в `Option` или наоборот; в этих случаях вы можете использовать такие способы, как способ `ok` для `Result` или способ `ok_or` для `Option`, чтобы выполнить преобразование явно. До сих пор все функции `main`, которые мы использовали, возвращали `()`. Функция `main` - особенная, потому что это точка входа и выхода исполняемых программ, и существуют ограничения на вид возвращаемого значения, чтобы программы вели себя так, как ожидается. -К счастью, `main` также может возвращать `Result<(), E>` . В приложении 9-12 используется код из приложения 9-10, но мы изменили возвращаемый вид `main` на `Result<(), Box>` и добавили возвращаемое значение `Ok(())` в конец. Теперь этот код будет собран: +К счастью, `main` также может возвращать `Result<(), E>` . В приложении 9-12 используется рукопись из приложения 9-10, но мы изменили возвращаемый вид `main` на `Result<(), Box>` и добавили возвращаемое значение `Ok(())` в конец. Теперь этот рукопись будет собран: ```rust,ignore {{#rustdoc_include ../listings/ch09-error-handling/listing-09-12/src/main.rs}} ``` -Приложение 9-12: Замена main на return Result<(), E> позволяет использовать оператор ? оператор над значениями Result +Приложение 9-12: Замена main на return Result<(), E> позволяет использовать приказчик ? приказчик над значениями Result -Вид `Box` является *особенность-предметом*, о котором мы поговорим в разделе ["Использование особенность-предметов, допускающих значения разных видов"] в главе 17. Пока что вы можете считать, что `Box` означает "любой вид ошибки". Использование `?` для значения `Result` в функции `main` с видом ошибки `Box` разрешено, так как позволяет вернуть любое значение `Err` раньше времени. Даже если тело этой функции `main` будет возвращать только ошибки вида `std::io::Error`, указав `Box`, эта ярлык останется правильной, даже если в тело `main` будет добавлен код, возвращающий другие ошибки. +Вид `Box` является *особенность-предметом*, о котором мы поговорим в разделе ["Использование особенность-предметов, допускающих значения разных видов"] в главе 17. Пока что вы можете считать, что `Box` означает "любой вид ошибки". Использование `?` для значения `Result` в функции `main` с видом ошибки `Box` разрешено, так как позволяет вернуть любое значение `Err` раньше времени. Даже если тело этой функции `main` будет возвращать только ошибки вида `std::io::Error`, указав `Box`, эта ярлык останется правильной, даже если в тело `main` будет добавлен рукопись, возвращающий другие ошибки. Когда `main` функция возвращает `Result<(), E>`, исполняемый файл завершится со значением `0`, если `main` вернёт `Ok(())`, и выйдет с ненулевым значением, если `main` вернёт значение `Err`. Исполняемые файлы, написанные на C, при выходе возвращают целые числа: успешно завершённые программы возвращают целое число `0`, а программы с ошибкой возвращают целое число, отличное от `0`. Ржавчина также возвращает целые числа из исполняемых файлов, чтобы быть совместимым с этим соглашением. -Функция `main` может возвращать любые виды, выполняющие [особенность `std::process::Termination`], в которых имеется функция `report`, возвращающая `ExitCode`. Обратитесь к документации встроенной библиотеки за дополнительной сведениями о порядке выполнения особенности `Termination` для ваших собственных видов. +Функция `main` может возвращать любые виды, выполняющие [особенность `std::process::Termination`], в которых имеется функция `report`, возвращающая `ExitCode`. Обратитесь к пособия встроенной библиотеки за дополнительной сведениями о порядке выполнения особенности `Termination` для ваших собственных видов. Теперь, когда мы обсудили подробности вызова `panic!` или возврата `Result`, давайте вернёмся к тому, как решить, какой из случаев подходит для какой случаи. diff --git a/rustbook-ru/src/ch09-03-to-panic-or-not-to-panic.md b/rustbook-ru/src/ch09-03-to-panic-or-not-to-panic.md index 64b92ab47..d034683e2 100644 --- a/rustbook-ru/src/ch09-03-to-panic-or-not-to-panic.md +++ b/rustbook-ru/src/ch09-03-to-panic-or-not-to-panic.md @@ -1,46 +1,46 @@ ## `panic!` или не `panic!` -Итак, как принимается решение о том, когда следует вызывать `panic!`, а когда вернуть `Result`? При панике код не имеет возможности восстановить своё выполнение. Можно было бы вызывать `panic!` для любой ошибочной случаи, независимо от того, имеется ли способ восстановления или нет, но с другой стороны, вы принимаете решение от имени вызывающего вас кода, что случаей необратима. Когда вы возвращаете значение `Result`, вы делегируете принятие решения вызывающему коду. Вызывающий код может попытаться выполнить восстановление способом, который подходит в данной случаи, или же он может решить, что из ошибки в `Err` нельзя восстановиться и вызовет `panic!`, превратив вашу исправимую ошибку в неисправимую. Поэтому возвращение `Result` является хорошим выбором по умолчанию для функции, которая может дать сбой. +Итак, как принимается решение о том, когда следует вызывать `panic!`, а когда вернуть `Result`? При сбое рукопись не имеет возможности восстановить своё выполнение. Можно было бы вызывать `panic!` для любой ошибочной случаи, независимо от того, имеется ли способ восстановления или нет, но с другой стороны, вы принимаете решение от имени вызывающего вас рукописи, что случай необратима. Когда вы возвращаете значение `Result`, вы делегируете принятие решения вызывающему рукописи. Вызывающий рукопись может попытаться выполнить восстановление способом, который подходит в данной случаи, или же он может решить, что из ошибки в `Err` нельзя восстановиться и вызовет `panic!`, превратив вашу исправимую ошибку в неисправимую. Поэтому возвращение `Result` является хорошим выбором по умолчанию для функции, которая может дать сбой. -В таких случаей как примеры, протовиды и проверки, более уместно писать код, который паникует вместо возвращения `Result`. Давайте рассмотрим почему, а затем мы обсудим случаи, в которых сборщик не может доказать, что ошибка невозможна, но вы, как человек, можете это сделать. Глава будет заканчиваться некоторыми общими руководящими принципами о том, как решить, стоит ли паниковать в коде библиотеки. +В таких случай как примеры, протовиды и проверки, более уместно писать рукопись, который вызывает сбой вместо возвращения `Result`. Давайте рассмотрим почему, а затем мы обсудим случаи, в которых сборщик не может доказать, что ошибка невозможна, но вы, как человек, можете это сделать. Глава будет заканчиваться некоторыми общими руководящими принципами о том, как решить, стоит ли вызвать сбой в рукописи библиотеки. -### Примеры, прототипирование и проверки +### Примеры, создание опытного образца и проверки -Когда вы пишете пример, отображающий некоторую подход, наличие хорошего кода обработки ошибок может сделать пример менее понятным. Понятно, что в примерах вызов способа `unwrap`, который может привести к панике, является лишь обозначением способа обработки ошибок в приложении, который может отличаться в зависимости от того, что делает остальная часть кода. +Когда вы пишете пример, отображающий некоторую подход, наличие хорошего рукописи обработки ошибок может сделать пример менее понятным. Понятно, что в примерах вызов способа `unwrap`, который может привести к сбою, является лишь обозначением способа обработки ошибок в приложении, который может отличаться в зависимости от того, что делает остальная часть рукописи. -Точно так же способы `unwrap` и `expect` являются очень удобными при создании протовида, прежде чем вы будете готовы решить, как обрабатывать ошибки. Они оставляют чёткие отступыв коде до особенности, когда вы будете готовы сделать программу более надёжной. +Точно так же способы `unwrap` и `expect` являются очень удобными при создании протовида, прежде чем вы будете готовы решить, как обрабатывать ошибки. Они оставляют чёткие отступыв рукописи до особенности, когда вы будете готовы сделать программу более надёжной. Если в проверке происходит сбой при вызове способа, то вы бы хотели, чтобы весь проверка не прошёл, даже если этот способ не является проверяемой возможностью. Поскольку вызов `panic!` это способ, которым проверка помечается как провалившийся, использование `unwrap` или `expect` - именно то, что нужно. ### Случаи, в которых у вас больше сведений, чем у сборщика -Также было бы целесообразно вызывать `unwrap` или `expect` когда у вас есть какая-то другая логика, которая заверяет, что `Result` будет иметь значение `Ok`, но вашу логику не понимает сборщик. У вас по-прежнему будет значение `Result` которое нужно обработать: любая действие, которую вы вызываете, все ещё имеет возможность неудачи в целом, хотя это логически невозможно в вашей именно случаи. Если, проверяя код вручную, вы можете убедиться, что никогда не будет исход с `Err`, то вполне допустимо вызывать `unwrap`, а ещё лучше задокументировать причину, по которой, по вашему мнению, у вас никогда не будет исхода `Err` в тексте `expect`. Вот пример: +Также было бы целесообразно вызывать `unwrap` или `expect` когда у вас есть какая-то другая ход мыслей, которая заверяет, что `Result` будет иметь значение `Ok`, но вашу ход мыслей не понимает сборщик. У вас по-прежнему будет значение `Result` которое нужно обработать: любая действие, которую вы вызываете, все ещё имеет возможность неудачи в целом, хотя это разумно невозможно в вашей именно случаи. Если, проверяя рукопись вручную, вы можете убедиться, что никогда не будет исход с `Err`, то вполне допустимо вызывать `unwrap`, а ещё лучше задокументировать причину, по которой, по вашему мнению, у вас никогда не будет исхода `Err` в тексте `expect`. Вот пример: ```rust {{#rustdoc_include ../listings/ch09-error-handling/no-listing-08-unwrap-that-cant-fail/src/main.rs:here}} ``` -Мы создаём образец `IpAddr`, анализируя жёстко закодированную строку. Можно увидеть, что `127.0.0.1` является действительным IP-адресом, поэтому здесь допустимо использование `expect`. Однако наличие жёстко закодированной допустимой строки не меняет вид возвращаемого значения способа `parse`: мы все ещё получаем значение `Result` и сборщик все также заставляет нас обращаться с `Result`так, будто возможен исход `Err`, потому что сборщик недостаточно умён, чтобы увидеть, что эта строка всегда действительный IP-адрес. Если строка IP-адреса пришла от пользователя, то она не является жёстко запрограммированной в программе и, следовательно, *может* привести к ошибке, мы определённо хотели бы обработать `Result` более надёжным способом. Упоминание предположения о том, что этот IP-адрес жёстко закодирован, побудит нас изменить `expect` для лучшей обработки ошибок, если в будущем нам потребуется вместо этого получить IP-адрес из какого-либо другого источника. +Мы создаём образец `IpAddr`, рассмотривая жёстко закодированную строку. Можно увидеть, что `127.0.0.1` является действительным IP-адресом, поэтому здесь допустимо использование `expect`. Однако наличие жёстко закодированной допустимой строки не меняет вид возвращаемого значения способа `parse`: мы все ещё получаем значение `Result` и сборщик все также заставляет нас обращаться с `Result`так, будто возможен исход `Err`, потому что сборщик недостаточно умён, чтобы увидеть, что эта строка всегда действительный IP-адрес. Если строка IP-адреса пришла от пользователя, то она не является жёстко запрограммированной в программе и, следовательно, *может* привести к ошибке, мы определённо хотели бы обработать `Result` более надёжным способом. Упоминание предположения о том, что этот IP-адрес жёстко закодирован, побудит нас изменить `expect` для лучшей обработки ошибок, если в будущем нам потребуется вместо этого получить IP-адрес из какого-либо другого источника. ### Руководство по обработке ошибок -Желательно, чтобы код паниковал, если он может оказаться в неправильном состоянии. В этом среде *неправильное состояние* это когда некоторое допущение, заверение, договор или неизменная величина были нарушены. Например, когда недопустимые, противоречивые или пропущенные значения передаются в ваш код - плюс один или несколько пунктов из следующего перечисленного в списке: +Желательно, чтобы рукопись вызвал сбой, если он может оказаться в неправильном состоянии. В этом среде *неправильное состояние* это когда некоторое допущение, заверение, договор или неизменная величина были нарушены. Например, когда недопустимые, противоречивые или пропущенные значения передаются в ваша рукопись - плюс один или несколько пунктов из следующего перечисленного в списке: - Неправильное состояние — это что-то неожиданное, отличается от того, что может происходить время от времени, например, когда пользователь вводит данные в неправильном виде. -- Ваш код после этой точки должен полагаться на то, что он не находится в неправильном состоянии, вместо проверок наличия сбоев на каждом этапе. +- Ваша рукопись после этой точки должен полагаться на то, что он не находится в неправильном состоянии, вместо проверок наличия сбоев на каждом этапе. - Нет хорошего способа закодировать данную сведения в видах, которые вы используете. Мы рассмотрим пример того, что мы имеем в виду в разделе [“Кодирование состояний и поведения на основе видов”] главы 17. -Если кто-то вызывает ваш код и передаёт значения, которые не имеют смысла, лучше всего вернуть ошибку, если вы это можете, чтобы пользователь библиотеки мог решить, что он хочет делать в этом случае. Однако в тех случаях, когда продолжение выполнения программы может быть небезопасным или вредным, лучшим выбором будет вызов `panic!` и оповещение пользователя, использующего вашу библиотеку, об ошибке в его коде, чтобы он мог исправить её во время разработки. Подобно `panic!` подходит, если вы вызываете внешний, неподуправлениеный вам код, и он возвращает недопустимое состояние, которое вы не можете исправить. +Если кто-то вызывает ваша рукопись и передаёт значения, которые не имеют смысла, лучше всего вернуть ошибку, если вы это можете, чтобы пользователь библиотеки мог решить, что он хочет делать в этом случае. Однако в тех случаях, когда продолжение выполнения программы может быть небезопасным или вредным, лучшим выбором будет вызов `panic!` и оповещение пользователя, использующего вашу библиотеку, об ошибке в его рукописи, чтобы он мог исправить её во время разработки. Подобно `panic!` подходит, если вы вызываете внешний, неподуправлениеный вам рукопись, и он возвращает недопустимое состояние, которое вы не можете исправить. -Однако, когда ожидается сбой, лучше вернуть `Result`, чем выполнить вызов `panic!`. В качестве примера можно привести синтаксический анализатор, которому передали неправильно созданные данные, или HTTP-запрос, возвращающий значение указывающий на то, что вы достигли ограничения на частоту запросов. В этих случаях возврат `Result` означает, что ошибка является ожидаемой и вызывающий код должен решить, как её обрабатывать. +Однако, когда ожидается сбой, лучше вернуть `Result`, чем выполнить вызов `panic!`. В качестве примера можно привести связанный оценщик, которому передали неправильно созданные данные, или HTTP-запрос, возвращающий значение указывающий на то, что вы достигли ограничения на частоту запросов. В этих случаях возврат `Result` означает, что ошибка является ожидаемой и вызывающий рукопись должен решить, как её обрабатывать. -Когда ваш код выполняет действие, которая может подвергнуть пользователя риску, если она вызывается с использованием недопустимых значений, ваш код должен сначала проверить допустимость значений и паниковать, если значения недопустимы. Так советуется делать в основном из соображений безопасности: попытка оперировать неправильными данными может привести к уязвимостям. Это основная причина, по которой обычная библиотека будет вызывать `panic!`, если попытаться получить доступ к памяти вне границ массива: доступ к памяти, не относящейся к текущей устройстве данных, является известной неполадкой безопасности. Функции часто имеют договоры: их поведение обеспечивается, только если входные данные отвечают определённым требованиям. Паника при нарушении договора имеет смысл, потому что это всегда указывает на изъян со стороны вызывающего кода, и это не ошибка, которую вы хотели бы, чтобы вызывающий код явно обрабатывал. На самом деле, нет разумного способа для восстановления вызывающего кода; программисты, вызывающие ваш код, должны исправить свой. Договоры для функции, особенно когда нарушение вызывает панику, следует описать в документации по API функции. +Когда ваша рукопись выполняет действие, которая может подвергнуть пользователя риску, если она вызывается с использованием недопустимых значений, ваша рукопись должен сначала проверить допустимость значений и вызвать сбой, если значения недопустимы. Так советуется делать в основном из соображений безопасности: попытка оперировать неправильными данными может привести к уязвимостям. Это основная причина, по которой обычная библиотека будет вызывать `panic!`, если попытаться получить доступ к памяти вне границ массива: доступ к памяти, не относящейся к текущей устройстве данных, является известной неполадкой безопасности. Функции часто имеют договоры: их поведение обеспечивается, только если входные данные отвечают определённым требованиям. Сбой при нарушении договора имеет смысл, потому что это всегда указывает на изъян со стороны вызывающего рукописи, и это не ошибка, которую вы хотели бы, чтобы вызывающий рукопись явно обрабатывал. На самом деле, нет разумного способа для восстановления вызывающего рукописи; программисты, вызывающие ваша рукопись, должны исправить свой. Договоры для функции, особенно когда нарушение вызывает сбой, следует описать в пособия по API функции. -Тем не менее, наличие множества проверок ошибок во всех ваших функциях было бы многословным и раздражительным. К счастью, можно использовать систему видов Ржавчина (следовательно и проверку видов сборщиком), чтобы она сделала множество проверок вместо вас. Если ваша функция имеет определённый вид в качестве свойства, вы можете продолжить работу с логикой кода зная, что сборщик уже обеспечил правильное значение. Например, если используется обычный вид, а не вид `Option`, то ваша программа ожидает наличие *чего-то* вместо *ничего*. Ваш код не должен будет обрабатывать оба исхода `Some` и `None`: он будет иметь только один исход для определённого значения. Код, пытающийся ничего не передавать в функцию, не будет даже собираться, поэтому ваша функция не должна проверять такой случай во время выполнения. Другой пример - это использование целого вида без знака, такого как `u32`, который заверяет, что свойство никогда не будет отрицательным. +Тем не менее, наличие множества проверок ошибок во всех ваших функциях было бы многословным и раздражительным. К счастью, можно использовать систему видов Ржавчине (следовательно и проверку видов сборщиком), чтобы она сделала множество проверок вместо вас. Если ваша функция имеет определённый вид в качестве свойства, вы можете продолжить работу с ходом мыслей рукописи зная, что сборщик уже обеспечил правильное значение. Например, если используется обычный вид, а не вид `Option`, то ваша программа ожидает наличие *чего-то* вместо *ничего*. Ваша рукопись не должен будет обрабатывать оба исхода `Some` и `None`: он будет иметь только один исход для определённого значения. Рукопись, пытающийся ничего не передавать в функцию, не будет даже собираться, поэтому ваша функция не должна проверять такой случай во время выполнения. Другой пример - это использование целого вида без знака, такого как `u32`, который заверяет, что свойство никогда не будет отрицательным. ### Создание пользовательских видов для проверки -Давайте разовьём мысль использования системы видов Ржавчина чтобы убедиться, что у нас есть правильное значение, и рассмотрим создание пользовательского вида для валидации. Вспомним игру угадывания числа из Главы 2, в которой наш код просил пользователя угадать число между 1 и 100. Мы никогда не проверяли, что предположение пользователя лежит между этими числами, перед сравнением предположения с загаданным нами числом; мы только проверяли, что оно положительно. В этом случае последствия были не очень страшными: наши сообщения «Слишком много» или «Слишком мало», выводимые в окно вывода, все равно были правильными. Но было бы лучше подталкивать пользователя к правильным догадкам и иметь различное поведение для случаев, когда пользователь предлагает число за пределами ряда, и когда пользователь вводит, например, буквы вместо цифр. +Давайте разовьём мысль использования системы видов Ржавчине чтобы убедиться, что у нас есть правильное значение, и рассмотрим создание пользовательского вида для валидации. Вспомним игру угадывания числа из Главы 2, в которой нашу рукопись просил пользователя угадать число между 1 и 100. Мы никогда не проверяли, что предположение пользователя лежит между этими числами, перед сравнением предположения с загаданным нами числом; мы только проверяли, что оно положительно. В этом случае последствия были не очень страшными: наши сообщения «Слишком много» или «Слишком мало», выводимые в окно вывода, все равно были правильными. Но было бы лучше подталкивать пользователя к правильным догадкам и иметь различное поведение для случаев, когда пользователь предлагает число за пределами ряда, и когда пользователь вводит, например, буквы вместо цифр. Один из способов добиться этого - пытаться разобрать введённое значение как `i32`, а не как `u32`, чтобы разрешить возможно отрицательные числа, а затем добавить проверку для нахождение числа в ряде, например, так: @@ -48,7 +48,7 @@ {{#rustdoc_include ../listings/ch09-error-handling/no-listing-09-guess-out-of-range/src/main.rs:here}} ``` -Выражение `if` проверяет, находится ли наше значение вне ряда, сообщает пользователю о неполадке и вызывает `continue`, чтобы начать следующую повторение цикла и попросить ввести другое число. После выражения `if` мы можем продолжить сравнение значения `guess` с загаданным числом, зная, что `guess` лежит в ряде от 1 до 100. +Выражение `if` проверяет, находится ли наше значение вне ряда, сообщает пользователю о неполадке и вызывает `continue`, чтобы начать следующую повторение круговорота и попросить ввести другое число. После выражения `if` мы можем продолжить сравнение значения `guess` с загаданным числом, зная, что `guess` лежит в ряде от 1 до 100. Однако это не наилучшее решение: если бы было чрезвычайно важно, чтобы программа работала только со значениями от 1 до 100, существовало бы много функций, требующих этого, то такая проверка в каждой функции была бы утомительной (и могла бы отрицательно повлиять на производительность). @@ -67,17 +67,17 @@ purposes. --> Сначала мы определяем устройство с именем `Guess`, которая имеет поле с именем `value` вида `i32`, в котором будет храниться число. -Затем мы выполняем сопряженную функцию `new`, создающую образцы значений вида `Guess`. Функция `new` имеет один свойство `value` вида `i32`, и возвращает `Guess`. Код в теле функции `new` проверяет, что значение `value` находится между 1 и 100. Если `value` не проходит эту проверку, мы вызываем `panic!`, которая оповестит программиста, написавшего вызывающий код, что в его коде есть ошибка, которую необходимо исправить, поскольку попытка создания `Guess` со значением `value` вне заданного ряда нарушает договор, на который полагается `Guess::new`. Условия, в которых `Guess::new` паникует, должны быть описаны в документации к API; мы рассмотрим соглашения о документации, указывающие на возможность появления `panic!` в документации API, которую вы создадите в Главе 14. Если `value` проходит проверку, мы создаём новый образец `Guess`, у которого значение поля `value` равно значению свойства `value`, и возвращаем `Guess`. +Затем мы выполняем сопряженную функцию `new`, создающую образцы значений вида `Guess`. Функция `new` имеет один свойство `value` вида `i32`, и возвращает `Guess`. Рукопись в теле функции `new` проверяет, что значение `value` находится между 1 и 100. Если `value` не проходит эту проверку, мы вызываем `panic!`, которая оповестит программиста, написавшего вызывающий рукопись, что в его рукописи есть ошибка, которую необходимо исправить, поскольку попытка создания `Guess` со значением `value` вне заданного ряда нарушает договор, на который полагается `Guess::new`. Условия, в которых `Guess::new` вызывает сбой, должны быть описаны в пособия к API; мы рассмотрим соглашения о пособия, указывающие на возможность появления `panic!` в пособия API, которую вы создадите в Главе 14. Если `value` проходит проверку, мы создаём новый образец `Guess`, у которого значение поля `value` равно значению свойства `value`, и возвращаем `Guess`. -Затем мы выполняем способ с названием `value`, который заимствует `self`, не имеет других свойств, и возвращает значение вида `i32`. Этот способ иногда называют *извлекатель* (getter), потому что его цель состоит в том, чтобы извлечь данные из полей устройства и вернуть их. Этот открытый способ является необходимым, поскольку поле `value` устройства `Guess` является закрытым. Важно, чтобы поле `value` было закрытым, чтобы код, использующий устройство `Guess`, не мог устанавливать `value` напрямую: код снаружи звена *должен* использовать функцию `Guess::new` для создания образца `Guess`, таким образом обеспечивая, что у `Guess` нет возможности получить `value`, не проверенное условиями в функции `Guess::new`. +Затем мы выполняем способ с названием `value`, который заимствует `self`, не имеет других свойств, и возвращает значение вида `i32`. Этот способ иногда называют *извлекатель* (getter), потому что его цель состоит в том, чтобы извлечь данные из полей устройства и вернуть их. Этот открытый способ является необходимым, поскольку поле `value` устройства `Guess` является закрытым. Важно, чтобы поле `value` было закрытым, чтобы рукопись, использующий устройство `Guess`, не мог устанавливать `value` напрямую: рукопись снаружи звена *должен* использовать функцию `Guess::new` для создания образца `Guess`, таким образом обеспечивая, что у `Guess` нет возможности получить `value`, не проверенное условиями в функции `Guess::new`. Функция, которая принимает или возвращает только числа от 1 до 100, может объявить в своей ярлыке, что она принимает или возвращает `Guess`, вместо `i32`, таким образом не будет необходимости делать дополнительные проверки в теле такой функции. ## Итоги -Функции обработки ошибок в Ржавчина призваны помочь написанию более надёжного кода. Макрос `panic!` указывает , что ваша программа находится в состоянии, которое она не может обработать, и позволяет сказать этапу чтобы он прекратил своё выполнение, вместо попытки продолжить выполнение с неправильными или неверными значениями. Перечисление `Result` использует систему видов Rust, чтобы сообщить, что действия могут завершиться неудачей, и ваш код мог восстановиться. Можно использовать `Result`, чтобы сообщить вызывающему коду, что он должен обрабатывать вероятный успех или вероятную неудачу. Использование `panic!` и `Result` правильным образом сделает ваш код более надёжным перед лицом неизбежных неполадок. +Функции обработки ошибок в Ржавчине призваны помочь написанию более надёжного рукописи. Макрос `panic!` указывает , что ваша программа находится в состоянии, которое она не может обработать, и позволяет сказать этапу чтобы он прекратил своё выполнение, вместо попытки продолжить выполнение с неправильными или неверными значениями. Перечисление `Result` использует систему видов Ржавчине, чтобы сообщить, что действия могут завершиться неудачей, и ваша рукопись мог восстановиться. Можно использовать `Result`, чтобы сообщить вызывающему рукописи, что он должен обрабатывать вероятный успех или вероятную неудачу. Использование `panic!` и `Result` правильным образом сделает ваша рукопись более надёжным перед лицом неизбежных неполадок. -Теперь, когда вы увидели полезные способы использования обобщённых видов `Option` и `Result` в встроенной библиотеке, мы поговорим о том, как работают обобщённые виды и как вы можете использовать их в своём коде. +Теперь, когда вы увидели полезные способы использования обобщённых видов `Option` и `Result` в встроенной библиотеке, мы поговорим о том, как работают обобщённые виды и как вы можете использовать их в своей рукописи. [“Кодирование состояний и поведения на основе видов”]: ch17-03-oo-design-patterns.html#encoding-states-and-behavior-as-types \ No newline at end of file diff --git a/rustbook-ru/src/ch10-00-generics.md b/rustbook-ru/src/ch10-00-generics.md index 215ca235c..2dec9530d 100644 --- a/rustbook-ru/src/ch10-00-generics.md +++ b/rustbook-ru/src/ch10-00-generics.md @@ -1,18 +1,18 @@ # Обобщённые виды, особенности и время жизни -Каждый язык программирования имеет в своём арсенале эффективные средства борьбы с повторением кода. В Ржавчина одним из таких средств являются обобщённые виды данных - *generics*. Это абстрактные подставные виды на место которых возможно поставить какой-либо определенный вид или другое свойство. Когда мы пишем код, мы можем выразить поведение обобщённых видов или их связь с другими обобщёнными видами, не зная какой вид будет использован на их месте при сборки и запуске кода. +Каждый язык программирования имеет в своём арсенале производительные средства борьбы с повторением рукописи. В Ржавчине одним из таких средств являются обобщённые виды данных - *generics*. Это абстрактные подставные виды на место которых возможно поставить какой-либо определенный вид или другое свойство. Когда мы пишем рукопись, мы можем выразить поведение обобщённых видов или их связь с другими обобщёнными видами, не зная какой вид будет использован на их месте при сборки и запуске рукописи. Функции могут принимать свойства некоторого "обобщённого" вида вместо привычных "определенных" видов, вроде `i32` или `String`. Подобно, функция принимает свойства с неизвестными заранее значениями, чтобы выполнять одинаковые действия над несколькими определенными значениями. На самом деле мы уже использовали обобщённые виды данных в Главе 6 (`Option`), в Главе 8 (`Vec` и `HashMap`) и в Главе 9 (`Result`). В этой главе вы узнаете, как определить собственные виды данных, функции и способы, используя возможности обобщённых видов. -Прежде всего, мы рассмотрим как для уменьшения повторения извлечь из кода некоторую общую возможность. Далее, мы будем использовать тот же рычаг для создания обобщённой функции из двух функций, которые отличаются только видом их свойств. Мы также объясним, как использовать обобщённые виды данных при определении устройств и перечислений. +Прежде всего, мы рассмотрим как для уменьшения повторения извлечь из рукописи некоторую общую возможность. Далее, мы будем использовать тот же рычаг для создания обобщённой функции из двух функций, которые отличаются только видом их свойств. Мы также объясним, как использовать обобщённые виды данных при определении устройств и перечислений. После этого мы изучим как использовать особенности (traits) для определения поведения в обобщённом виде. Можно соединенять особенности с обобщёнными видами, чтобы обобщённый вид мог принимать только такие виды, которые имеют определённое поведение, а не все подряд. -В конце мы обсудим *времена жизни (lifetimes)*, вариации обобщённых видов, которые дают сборщику сведения о том, как сроки жизни ссылок относятся друг к другу. Времена жизни позволяют нам указать дополнительную сведения об "одолженных" (borrowed) значениях, которая позволит сборщику удостовериться в соблюдения правил используемых ссылок в тех случаейх, когда сборщик не может сделать это самостоятельно . +В конце мы обсудим *времена жизни (lifetimes)*, вариации обобщённых видов, которые дают сборщику сведения о том, как сроки жизни ссылок относятся друг к другу. Времена жизни позволяют нам указать дополнительную сведения об "одолженных" (borrowed) значениях, которая позволит сборщику удостовериться в соблюдения правил используемых ссылок в тех случаях, когда сборщик не может сделать это самостоятельно . -## Удаление повторения кода с помощью выделения общей возможности +## Удаление повторения рукописи с помощью выделения общей возможности -В обобщениях мы можем заменить определенный вид на "заполнитель" (placeholder), обозначающую несколько видов, что позволяет удалить повторяющийся код. Прежде чем углубляться в правила написания обобщённых видов, давайте сначала посмотрим, как удалить повторение, не задействуя гибкие виды, путём извлечения функции, которая заменяет определённые значения заполнителем, представляющим несколько значений. Затем мы применим ту же технику для извлечения гибкой функции! Изучив, как распознать повторяющийся код, который можно извлечь в функцию, вы начнёте распознавать повторяющийся код, который может использовать обобщённые виды. +В обобщениях мы можем заменить определенный вид на "заполнитель" (placeholder), обозначающую несколько видов, что позволяет удалить повторяющийся рукопись. Прежде чем углубляться в правила написания обобщённых видов, давайте сначала посмотрим, как удалить повторение, не задействуя гибкие виды, путём извлечения функции, которая заменяет определённые значения заполнителем, представляющим несколько значений. Затем мы применим ту же технику для извлечения гибкой функции! Изучив, как распознать повторяющийся рукопись, который можно извлечь в функцию, вы начнёте распознавать повторяющийся рукопись, который может использовать обобщённые виды. Начнём с короткой программы в приложении 10-1, которая находит наибольшее число в списке. @@ -26,7 +26,7 @@ Сохраним список целых чисел в переменной `number_list` и поместим первое значение из списка в переменную `largest`. Далее, переберём все элементы списка, и, если текущий элемент больше числа сохранённого в переменной `largest`, заменим значение в этой переменной. Если текущий элемент меньше или равен "наибольшему", найденному ранее, значение переменной оставим прежним и перейдём к следующему элементу списка. После перебора всех элементов списка переменная `largest` должна содержать наибольшее значение, которое в нашем случае будет равно 100. -Теперь перед нами стоит задача найти наибольшее число в двух разных списках. Для этого мы можем повторять код из приложения 10-1 и использовать ту же логику в двух разных местах программы, как показано в приложении 10-2. +Теперь перед нами стоит задача найти наибольшее число в двух разных списках. Для этого мы можем повторять рукопись из приложения 10-1 и использовать ту же ход мыслей в двух разных местах программы, как показано в приложении 10-2. Файл: src/main.rs @@ -34,13 +34,13 @@ {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-02/src/main.rs}} ``` -Приложение 10-2: Код для поиска наибольшего числа в двух списках чисел +Приложение 10-2: Рукопись для поиска наибольшего числа в двух списках чисел -Несмотря на то, что код программы работает, повторение кода утомительно и подвержено ошибкам. При внесении изменений мы должны не забыть обновить каждое место, где код повторяется. +Несмотря на то, что рукопись программы работает, повторение рукописи утомительно и подвержено ошибкам. При внесении изменений мы должны не забыть обновить каждое место, где рукопись повторяется. -Для устранения повторения мы можем создать дополнительную абстракцию с помощью функции которая сможет работать с любым списком целых чисел переданным ей в качестве входного свойства и находить для этого списка наибольшее число. Данное решение делает код более ясным и позволяет абстрактным образом выполнить алгоритм поиска наибольшего числа в списке. +Для устранения повторения мы можем создать дополнительную абстракцию с помощью функции которая сможет работать с любым списком целых чисел переданным ей в качестве входного свойства и находить для этого списка наибольшее число. Данное решение делает рукопись более ясным и позволяет абстрактным образом выполнить алгоритм поиска наибольшего числа в списке. -В приложении 10-3 мы извлекаем код, который находит наибольшее число, в функцию с именем `largest`. Затем мы вызываем функцию, чтобы найти наибольшее число в двух списках из приложения 10-2. Мы также можем использовать эту функцию для любого другого списка значений `i32` , который может встретиться позже. +В приложении 10-3 мы извлекаем рукопись, который находит наибольшее число, в функцию с именем `largest`. Затем мы вызываем функцию, чтобы найти наибольшее число в двух списках из приложения 10-2. Мы также можем использовать эту функцию для любого другого списка значений `i32` , который может встретиться позже. Файл: src/main.rs @@ -48,17 +48,17 @@ {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-03/src/main.rs:here}} ``` -Приложение 10-3: Абстрактный код для поиска наибольшего числа в двух списках +Приложение 10-3: Абстрактный рукопись для поиска наибольшего числа в двух списках -Функция `largest` имеет свойство с именем `list`, который представляет любой срез значений вида `i32`, которые мы можем передать в неё. В итоге вызова функции, код выполнится с определенными, переданными в неё значениями. +Функция `largest` имеет свойство с именем `list`, который представляет любой срез значений вида `i32`, которые мы можем передать в неё. В итоге вызова функции, рукопись выполнится с определенными, переданными в неё значениями. -Итак, вот шаги выполненные для изменения кода из приложения 10-2 в приложение 10-3: +Итак, вот шаги выполненные для изменения рукописи из приложения 10-2 в приложение 10-3: -1. Определить повторяющийся код. -2. Извлечь повторяющийся код и поместить его в тело функции, определив входные и выходные значения этого кода в ярлыке функции. -3. Обновить и заменить два участка повторяющегося кода вызовом одной функции. +1. Определить повторяющийся рукопись. +2. Извлечь повторяющийся рукопись и поместить его в тело функции, определив входные и выходные значения этого рукописи в ярлыке функции. +3. Обновить и заменить два участка повторяющегося рукописи вызовом одной функции. -Далее, чтобы уменьшить повторение кода, мы воспользуемся теми же шагами для обобщённых видов. Обобщённые виды позволяют работать над абстрактными видами таким же образом, как тело функции может работать над абстрактным списком `list` вместо определенных значений. +Далее, чтобы уменьшить повторение рукописи, мы воспользуемся теми же шагами для обобщённых видов. Обобщённые виды позволяют работать над абстрактными видами таким же образом, как тело функции может работать над абстрактным списком `list` вместо определенных значений. Например, у нас есть две функции: одна ищет наибольший элемент внутри среза значений вида `i32`, а другая внутри среза значений вида `char`. Как уменьшить такое повторение? Давайте выяснять! diff --git a/rustbook-ru/src/ch10-01-syntax.md b/rustbook-ru/src/ch10-01-syntax.md index 770ce93be..433d00957 100644 --- a/rustbook-ru/src/ch10-01-syntax.md +++ b/rustbook-ru/src/ch10-01-syntax.md @@ -1,10 +1,10 @@ ## Обобщённые виды данных -Мы используем обобщённые виды данных для объявления функций или устройств, которые затем можно использовать с различными определенными видами данных. Давайте сначала посмотрим, как объявлять функции, устройства, перечисления и способы, используя обобщённые виды данных. Затем мы обсудим, как обобщённые виды данных влияют на производительность кода. +Мы используем обобщённые виды данных для объявления функций или устройств, которые затем можно использовать с различными определенными видами данных. Давайте сначала посмотрим, как объявлять функции, устройства, перечисления и способы, используя обобщённые виды данных. Затем мы обсудим, как обобщённые виды данных влияют на производительность рукописи. ### В объявлении функций -Когда мы объявляем функцию с обобщёнными видами, мы размещаем обобщённые виды в ярлыке функции, где мы обычно указываем виды данных переменных и возвращаемого значения. Используя обобщённые виды, мы делаем код более гибким и предоставляем большую возможность при вызове нашей функции, предотвращая повторение кода. +Когда мы объявляем функцию с обобщёнными видами, мы размещаем обобщённые виды в ярлыке функции, где мы обычно указываем виды данных переменных и возвращаемого значения. Используя обобщённые виды, мы делаем рукопись более гибким и предоставляем большую возможность при вызове нашей функции, предотвращая повторение рукописи. Рассмотрим пример с функцией `largest`. Приложение 10-4 показывает две функции, каждая из которых находит самое большое значение в срезе своего вида. Позже мы объединим их в одну функцию, использующую обобщённые виды данных. @@ -16,9 +16,9 @@ Приложение 10-4: две функции, отличающиеся только именем и видом обрабатываемых данных -Функция `largest_i32` уже встречалась нам: мы извлекли её в приложении 10-3, когда боролись с повторением кода — она находит наибольшее значение вида `i32` в срезе. Функция `largest_char` находит самое большое значение вида `char` в срезе. Тело у этих функций одинаковое, поэтому давайте избавимся от повторяемлшл кода, используя свойство обобщённого вида в одной функции. +Функция `largest_i32` уже встречалась нам: мы извлекли её в приложении 10-3, когда боролись с повторением рукописи — она находит наибольшее значение вида `i32` в срезе. Функция `largest_char` находит самое большое значение вида `char` в срезе. Тело у этих функций одинаковое, поэтому давайте избавимся от повторяемлшл рукописи, используя свойство обобщённого вида в одной функции. -Для свойствоизации видов данных в новой объявляемой функции нам нужно дать имя обобщённому виду — так же, как мы это делаем для переменных функций. Можно использовать любой определитель для имени свойства вида, но мы будем использовать `T`, потому что по соглашению имена свойств в Ржавчина должны быть короткими (обычно длиной в один символ), а именование видов в Ржавчина делается в наставлении UpperCamelCase. Сокращение слова «type» до одной буквы `T` является обычным выбором большинства программистов, использующих язык Rust. +Для свойствоизации видов данных в новой объявляемой функции нам нужно дать имя обобщённому виду — так же, как мы это делаем для переменных функций. Можно использовать любой определитель для имени свойства вида, но мы будем использовать `T`, потому что по соглашению имена свойств в Ржавчине должны быть короткими (обычно длиной в один знак), а именование видов в Ржавчине делается в наставлении UpperCamelCase. Сокращение слова «type» до одной буквы `T` является обычным выбором большинства программистов, использующих язык Ржавчина. Когда мы используем свойство в теле функции, мы должны объявить имя свойства в ярлыке, чтобы сборщик знал, что означает это имя. Подобно когда мы используем имя вида свойства в ярлыке функции, мы должны объявить это имя раньше, чем мы его используем. Чтобы определить обобщённую функцию `largest`, поместим объявление имён свойств в треугольные скобки `<>` между именем функции и списком свойств, как здесь: @@ -28,7 +28,7 @@ fn largest(list: &[T]) -> &T { Объявление читается так: функция `largest` является обобщённой по виду `T`. Эта функция имеет один свойство с именем `list`, который является срезом значений с видом данных `T`. Функция `largest` возвращает значение этого же вида `T`. -Приложение 10-5 показывает определение функции `largest` с использованием обобщённых видов данных в её ярлыке. Приложение также показывает, как мы можем вызвать функцию со срезом данных вида `i32` или `char`. Данный код пока не будет собираться, но мы исправим это к концу раздела. +Приложение 10-5 показывает определение функции `largest` с использованием обобщённых видов данных в её ярлыке. Приложение также показывает, как мы можем вызвать функцию со срезом данных вида `i32` или `char`. Данный рукопись пока не будет собираться, но мы исправим это к концу раздела. Файл: src/main.rs @@ -36,7 +36,7 @@ fn largest(list: &[T]) -> &T { {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/src/main.rs}} ``` -Приложение 10-5: функция largest, использующая свойства обобщённого типа; пока ещё не собирается +Приложение 10-5: функция largest, использующая свойства обобщённого вида; пока ещё не собирается Если мы соберем программу сейчас, мы получим следующую ошибку: @@ -60,7 +60,7 @@ fn largest(list: &[T]) -> &T { правила написания использования обобщённых видов в определении устройства очень похож на правила написания в определении функции. Сначала мы объявляем имена видов свойств внутри треугольных скобок сразу после названия устройства. Затем мы можем использовать обобщённые виды в определении устройства в тех местах, где ранее мы указывали бы определенные виды. -Так как мы используем только один обобщённый вид данных для определения устройства `Point`, это определение означает, что устройства `Point` является обобщённой с видом `T`, и оба поля `x` и y имеют одинаковый вид, каким бы он не являлся. Если мы создадим образец устройства `Point` со значениями разных видов, как показано в приложении 10-7, наш код не собирается. +Так как мы используем только один обобщённый вид данных для определения устройства `Point`, это определение означает, что устройства `Point` является обобщённой с видом `T`, и оба поля `x` и y имеют одинаковый вид, каким бы он не являлся. Если мы создадим образец устройства `Point` со значениями разных видов, как показано в приложении 10-7, нашу рукопись не собирается. Файл: src/main.rs @@ -86,7 +86,7 @@ fn largest(list: &[T]) -> &T { Приложение 10-8: устройства Point<T, U> обобщена для двух видов, так что x и y могут быть значениями разных видов -Теперь разрешены все показанные образцы вида `Point`! В объявлении можно использовать сколь угодно много свойств обобщённого вида, но если делать это в большом количестве, код будет тяжело читать. Если в вашем коде требуется много обобщённых видов, возможно, стоит разбить его на более мелкие части. +Теперь разрешены все показанные образцы вида `Point`! В объявлении можно использовать сколь угодно много свойств обобщённого вида, но если делать это в большом количестве, рукопись будет тяжело читать. Если в вашем рукописи требуется много обобщённых видов, возможно, стоит разбить его на более мелкие части. ### В определениях перечислений @@ -112,7 +112,7 @@ enum Result { Перечисление `Result` имеет два обобщённых вида: `T` и `E` — и два исхода: `Ok`, который содержит вид `T`, и `Err`, содержащий вид `E`. С таким определением удобно использовать перечисление `Result` везде, где действия могут быть выполнены успешно (возвращая значение вида `T`) или неуспешно (возвращая ошибку вида `E`). Это то, что мы делали при открытии файла в приложении 9-3, где `T` заполнялось видом `std::fs::File`, если файл был открыт успешно, либо `E` заполнялось видом `std::io::Error`, если при открытии файла возникали какие-либо сбоев. -Если вы встречаете в коде случаи, когда несколько определений устройств или перечислений отличаются только видами содержащихся в них значений, вы можете устранить повторение, используя обобщённые виды. +Если вы встречаете в рукописи случаи, когда несколько определений устройств или перечислений отличаются только видами содержащихся в них значений, вы можете устранить повторение, используя обобщённые виды. ### В определении способов @@ -140,7 +140,7 @@ enum Result { Приложение 10-10: разделimpl, который применяется только к устройстве, имеющей определенный вид для свойства обобщённого вида T -Этот код означает, что вид `Point` будет иметь способ с именем `distance_from_origin`, а другие образцы `Point`, где `T` имеет вид, отличный от `f32`, не будут иметь этого способа. Способ вычисляет, насколько далеко наша точка находится от точки с координатами (0.0, 0.0), и использует математические действия, доступные только для видов с плавающей точкой. +Этот рукопись означает, что вид `Point` будет иметь способ с именем `distance_from_origin`, а другие образцы `Point`, где `T` имеет вид, отличный от `f32`, не будут иметь этого способа. Способ вычисляет, насколько далеко наша точка находится от точки с координатами (0.0, 0.0), и использует математические действия, доступные только для видов с плавающей точкой. Свойства обобщённого вида, которые мы используем в определении устройства, не всегда совпадают с подобиями, использующимися в ярлыках способов этой устройства. Чтобы пример был более очевидным, в приложении 10-11 используются обобщённые виды `X1` и `Y1` для определения устройства `Point` и виды `X2` `Y2` для ярлыки способа `mixup`. Способ создаёт новый образец устройства `Point`, где значение `x` берётся из `self` `Point` (имеющей вид `X1`), а значение `y` - из переданной устройства `Point` (где эта переменная имеет вид `Y2`). @@ -156,11 +156,11 @@ enum Result { Цель этого примера — отобразить случай, в которой некоторые обобщённые свойства объявлены с помощью `impl`, а некоторые объявлены в определении способа. Здесь обобщённые свойства `X1` и `Y1` объявляются после `impl`, потому что они относятся к определению устройства. Обобщённые свойства `X2` и `Y2` объявляются после `fn mixup`, так как они относятся только к способу. -### Производительность кода, использующего обобщённые виды +### Производительность рукописи, использующего обобщённые виды Вы могли бы задаться вопросом, возникают ли какие-нибудь дополнительные издержки при использовании свойств обобщённого вида. Хорошая новость в том, что при использовании обобщённых видов ваша программа работает ничуть ни медленнее, чем если бы она работала с использованием определенных видов. -В Ржавчина это достигается во время сборки при помощи мономорфизации кода, использующего обобщённые виды. *Мономорфизация* — это этап превращения обобщённого кода в определенный код путём подстановки определенных видов, использующихся при сборки. В этом этапе сборщик выполняет шаги, противоположные тем, которые мы использовали для создания обобщённой функции в приложении 10-5: он просматривает все места, где вызывается обобщённый код, и порождает код для определенных видов, использовавшихся для вызова в обобщённом. +В Ржавчине это достигается во время сборки при помощи мономорфизации рукописи, использующего обобщённые виды. *Мономорфизация* — это этап превращения обобщённого рукописи в определенный рукопись путём подстановки определенных видов, использующихся при сборки. В этом этапе сборщик выполняет шаги, противоположные тем, которые мы использовали для создания обобщённой функции в приложении 10-5: он просматривает все места, где вызывается обобщённый рукопись, и порождает рукопись для определенных видов, использовавшихся для вызова в обобщённом. Давайте посмотрим, как это работает при использовании перечисления `Option` из встроенной библиотеки: @@ -169,9 +169,9 @@ let integer = Some(5); let float = Some(5.0); ``` -Когда Ржавчина собирает этот код, он выполняет мономорфизацию. Во время этого этапа сборщик считывает значения, которые были использованы в образцах `Option`, и определяет два вида `Option`: один для вида `i32`, а другой — для `f64`. Таким образом, он разворачивает обобщённое определение `Option` в два определения, именно для `i32` и `f64`, тем самым заменяя обобщённое определение определенными. +Когда Ржавчина собирает этот рукопись, он выполняет мономорфизацию. Во время этого этапа сборщик считывает значения, которые были использованы в образцах `Option`, и определяет два вида `Option`: один для вида `i32`, а другой — для `f64`. Таким образом, он разворачивает обобщённое определение `Option` в два определения, именно для `i32` и `f64`, тем самым заменяя обобщённое определение определенными. -Мономорфизированная исполнение кода выглядит примерно так (сборщик использует имена, отличные от тех, которые мы используем здесь для отображения): +Мономорфизированная исполнение рукописи выглядит примерно так (сборщик использует имена, отличные от тех, которые мы используем здесь для отображения): Файл: src/main.rs @@ -192,6 +192,6 @@ fn main() { } ``` -Обобщённое `Option` заменяется определенными определениями, созданными сборщиком. Поскольку Ржавчина собирает обобщённый код в код, определяющий вид в каждом образце, мы не платим за использование обобщённых видов во время выполнения. Когда код запускается, он работает точно так же, как если бы мы сделали повторение каждое определение вручную. Этап мономорфизации делает обобщённые виды Ржавчина чрезвычайно эффективными во время выполнения. +Обобщённое `Option` заменяется определенными определениями, созданными сборщиком. Поскольку Ржавчина собирает обобщённый рукопись в рукопись, определяющий вид в каждом образце, мы не платим за использование обобщённых видов во время выполнения. Когда рукопись запускается, он работает точно так же, как если бы мы сделали повторение каждое определение вручную. Этап мономорфизации делает обобщённые виды Ржавчина чрезвычайно производительными во время выполнения. diff --git a/rustbook-ru/src/ch10-02-traits.md b/rustbook-ru/src/ch10-02-traits.md index 32b2aaaf9..abb81690e 100644 --- a/rustbook-ru/src/ch10-02-traits.md +++ b/rustbook-ru/src/ch10-02-traits.md @@ -6,9 +6,9 @@ ### Определение особенности -Поведение вида определяется теми способами, которые мы можем вызвать у данного вида. Различные виды разделяют одинаковое поведение, если мы можем вызвать одни и те же способы у этих видов. Определение особенностей - это способ собъединять ярлыки способов вместе для того, чтобы описать общее поведение, необходимое для достижения определённой цели. +Поведение вида определяется теми способами, которые мы можем вызвать у данного вида. Различные виды разделяют одинаковое поведение, если мы можем вызвать одни и те же способы у этих видов. Определение особенностей - это способ объединять ярлыки способов вместе для того, чтобы описать общее поведение, необходимое для достижения определённой цели. -Например, пусть есть несколько устройств, которые имеют различный вид и различный размер текста: устройства `NewsArticle`, которая содержит новость, напечатанную в каком-то месте мира; устройства `Tweet`, которая содержит 280 символьную строку твита и мета-данные, обозначающие является ли твит новым или ответом на другой твит. +Например, пусть есть несколько устройств, которые имеют различный вид и различный размер текста: устройства `NewsArticle`, которая содержит новость, напечатанную в каком-то месте мира; устройства `Tweet`, которая содержит 280 знаковую строку твита и мета-данные, обозначающие является ли твит новым или ответом на другой твит. Мы хотим создать ящик библиотеки медиа-агрегатора `aggregator`, которая может отображать сводку данных сохранённых в образцах устройств `NewsArticle` или `Tweet`. Чтобы этого достичь, нам необходимо иметь возможность для каждой устройства получить короткую сводку на основе имеющихся данных, и для этого мы запросим сводку вызвав способ `summarize`. Приложение 10-12 показывает определение особенности `Summary`, который выражает это поведение. @@ -24,11 +24,11 @@ После ярлыки способа, вместо предоставления выполнения в фигурных в скобках, мы используем точку с запятой. Каждый вид, выполняющий данный особенность, должен предоставить своё собственное поведение для данного способа. Сборщик обеспечит, что любой вид содержащий особенность `Summary`, будет также иметь и способ `summarize` объявленный с точно такой же ярлыком. -Особенность может иметь несколько способов в описании его тела: ярлыки способов перечисляются по одной на каждой строке и должны закачиваться символом ;. +Особенность может иметь несколько способов в описании его тела: ярлыки способов перечисляются по одной на каждой строке и должны закачиваться знаком ;. ### Выполнение особенности у вида -Теперь, после того как мы определили желаемое поведение используя особенность `Summary`, можно выполнить его у видов в нашем медиа-агрегаторе. Приложение 10-13 показывает выполнение особенности `Summary` у устройства `NewsArticle`, которая использует для создания сводки в способе `summarize` заголовок, автора и место обнародования статьи. Для устройства `Tweet` мы определяем выполнение `summarize` используя имя пользователя и следующий за ним полный текст твита, полагая что содержание твита уже ограничено 280 символами. +Теперь, после того как мы определили желаемое поведение используя особенность `Summary`, можно выполнить его у видов в нашем медиа-агрегаторе. Приложение 10-13 показывает выполнение особенности `Summary` у устройства `NewsArticle`, которая использует для создания сводки в способе `summarize` заголовок, составителя и место обнародования статьи. Для устройства `Tweet` мы определяем выполнение `summarize` используя имя пользователя и следующий за ним полный текст твита, полагая что содержание твита уже ограничено 280 знаками. Файл: src/lib.rs @@ -46,11 +46,11 @@ {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-01-calling-trait-method/src/main.rs}} ``` -Данный код напечатает: `1 new tweet: horse_ebooks: of course, as you probably already know, people`. +Данный рукопись напечатает: `1 new tweet: horse_ebooks: of course, as you probably already know, people`. Другие ящики, которые зависят от `aggregator`, тоже могу включить особенность `Summary` в область видимости для выполнения `Summary` в их собственных видах. Одно ограничение, на которое следует обратить внимание, заключается в том, что мы можем выполнить особенность для вида только в том случае, если хотя бы один из особенностей вида является местным для нашего ящика. Например, мы можем выполнить обычный библиотечный особенность `Display` на собственном виде `Tweet` как часть возможности нашего ящика `aggregator` потому что вид `Tweet` является местным для ящика `aggregator`. Также мы можем выполнить `Summary` для `Vec` в нашем ящике `aggregator`, потому что особенность `Summary` является местным для нашего ящика `aggregator`. -Но мы не можем выполнить внешние особенности для внешних видов. Например, мы не можем выполнить особенность `Display` для `Vec` внутри нашего ящика `aggregator`, потому что `Display` и `Vec` оба определены в встроенной библиотеке а не местно в нашем ящике `aggregator`. Это ограничение является частью свойства называемого *согласованность* (coherence), а ещё точнее *сиротское правило* (orphan rule), которое называется так потому что не представлен родительский вид. Это правило заверяет, что код других людей не может сломать ваш код и наоборот. Без этого правила два ящика могли бы выполнить один особенность для одинакового вида и Ржавчина не сможет понять, какой выполнением нужно пользоваться. +Но мы не можем выполнить внешние особенности для внешних видов. Например, мы не можем выполнить особенность `Display` для `Vec` внутри нашего ящика `aggregator`, потому что `Display` и `Vec` оба определены в встроенной библиотеке а не местно в нашем ящике `aggregator`. Это ограничение является частью свойства называемого *согласованность* (coherence), а ещё точнее *сиротское правило* (orphan rule), которое называется так потому что не представлен родительский вид. Это правило заверяет, что рукопись других людей не может сломать ваша рукопись и наоборот. Без этого правила два ящика могли бы выполнить один особенность для одинакового вида и Ржавчина не сможет понять, какой выполнением нужно пользоваться. ### Выполнение поведения по умолчанию @@ -74,7 +74,7 @@ {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-02-calling-default-impl/src/main.rs:here}} ``` -Этот код печатает `New article available! (Read more...)` . +Этот рукопись печатает `New article available! (Read more...)` . Создание выполнения по умолчанию не требует от нас изменений чего-либо в выполнения `Summary` для `Tweet` в приложении 10-13. Причина заключается в том, что правила написания для переопределения выполнения по умолчанию является таким же, как правила написания для выполнения способа особенности, который не имеет выполнения по умолчанию. @@ -90,13 +90,13 @@ {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/lib.rs:impl}} ``` -После того, как мы определим `summarize_author`, можно вызвать `summarize` для образцов устройства `Tweet` и выполнение по умолчанию способа `summarize` будет вызывать определение `summarize_author` которое мы уже предоставили. Так как мы выполнили способ `summarize_author` особенности `Summary`, то особенность даёт нам поведение способа `summarize` без необходимости писать код. +После того, как мы определим `summarize_author`, можно вызвать `summarize` для образцов устройства `Tweet` и выполнение по умолчанию способа `summarize` будет вызывать определение `summarize_author` которое мы уже предоставили. Так как мы выполнили способ `summarize_author` особенности `Summary`, то особенность даёт нам поведение способа `summarize` без необходимости писать рукопись. ```rust,ignore {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/main.rs:here}} ``` -Этот код печатает `1 new tweet: (Read more from @horse_ebooks...)` . +Этот рукопись печатает `1 new tweet: (Read more from @horse_ebooks...)` . Обратите внимание, что невозможно вызвать выполнение по умолчанию из переопределённой выполнения того же способа. @@ -108,7 +108,7 @@ {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-04-traits-as-parameters/src/lib.rs:here}} ``` -Вместо определенного вида у свойства `item` указывается ключевое слово `impl` и имя особенности. Этот свойство принимает любой вид, который выполняет указанный особенность. В теле `notify` мы можем вызывать любые способы у образца `item` , которые приходят с особенностью `Summary`, такие как способ `summarize`. Мы можем вызвать `notify` и передать в него любой образец `NewsArticle` или `Tweet`. Код, который вызывает данную функцию с любым другим видом, таким как `String` или `i32`, не будет собираться, потому что эти виды не выполняют особенность `Summary`. +Вместо определенного вида у свойства `item` указывается ключевое слово `impl` и имя особенности. Этот свойство принимает любой вид, который выполняет указанный особенность. В теле `notify` мы можем вызывать любые способы у образца `item` , которые приходят с особенностью `Summary`, такие как способ `summarize`. Мы можем вызвать `notify` и передать в него любой образец `NewsArticle` или `Tweet`. Рукопись, который вызывает данную функцию с любым другим видом, таким как `String` или `i32`, не будет собираться, потому что эти виды не выполняют особенность `Summary`. @@ -116,7 +116,7 @@ #### правила написания ограничения особенности -правила написания `impl Trait` работает для простых случаев, но на самом деле является синтаксическим сахаром для более длинной видовы, которая называется *ограничением особенности* (trait bound); это выглядит так: +правила написания `impl Trait` работает для простых случаев, но на самом деле является связанным сахаром для более длинной вида, которая называется *ограничением особенности* (trait bound); это выглядит так: ```rust,ignore pub fn notify(item: &T) { @@ -126,7 +126,7 @@ pub fn notify(item: &T) { Эта более длинная разновидность эквивалентна примеру в предыдущем разделе, но она более многословна. Мы помещаем объявление свойства обобщённого вида с ограничением особенности после двоеточия внутри угловых скобок. -правила написания `impl Trait` удобен и делает код более сжатым в простых случаях, в то время как более полный правила написания с ограничением особенности в других случаях может выразить большую сложность. Например, у нас может быть два свойства, которые выполняют особенность `Summary`. Использование правил написания `impl Trait` выглядит так: +правила написания `impl Trait` удобен и делает рукопись более сжатым в простых случаях, в то время как более полный правила написания с ограничением особенности в других случаях может выразить большую сложность. Например, у нас может быть два свойства, которые выполняют особенность `Summary`. Использование правил написания `impl Trait` выглядит так: ```rust,ignore pub fn notify(item1: &impl Summary, item2: &impl Summary) { @@ -159,7 +159,7 @@ pub fn notify(item: &T) { #### Более ясные границы особенности с помощью `where` -Использование слишком большого количества ограничений особенности имеет свои недостатки. Каждый обобщённый вид имеет свои границы особенности, поэтому функции с несколькими свойствами обобщённого вида могут содержать много сведений об ограничениях между названием функции и списком её свойств затрудняющих чтение ярлыки. По этой причине в Ржавчина есть иной правила написания для определения ограничений особенности внутри предложения `where` после ярлыки функции. Поэтому вместо того, чтобы писать так: +Использование слишком большого количества ограничений особенности имеет свои недостатки. Каждый обобщённый вид имеет свои границы особенности, поэтому функции с несколькими свойствами обобщённого вида могут содержать много сведений об ограничениях между названием функции и списком её свойств затрудняющих чтение ярлыки. По этой причине в Ржавчине есть иной правила написания для определения ограничений особенности внутри предложения `where` после ярлыки функции. Поэтому вместо того, чтобы писать так: ```rust,ignore fn some_function(t: &T, u: &U) -> i32 { @@ -181,11 +181,11 @@ fn some_function(t: &T, u: &U) -> i32 { {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-05-returning-impl-trait/src/lib.rs:here}} ``` -Используя `impl Summary` для возвращаемого вида, мы указываем, что функция `returns_summarizable` возвращает некоторый вид, который выполняет особенность `Summary` без обозначения определенного вида. В этом случае `returns_summarizable` возвращает `Tweet`, но код, вызывающий эту функцию, этого не знает. +Используя `impl Summary` для возвращаемого вида, мы указываем, что функция `returns_summarizable` возвращает некоторый вид, который выполняет особенность `Summary` без обозначения определенного вида. В этом случае `returns_summarizable` возвращает `Tweet`, но рукопись, вызывающий эту функцию, этого не знает. Возможность возвращать вид, который определяется только выполняемым им признаком, особенно полезна в среде замыканий и повторителей, которые мы рассмотрим в Главе 13. Замыкания и повторители создают виды, которые знает только сборщик или виды, которые очень долго указывать. правила написания `impl Trait` позволяет кратко указать, что функция возвращает некоторый вид, который выполняет особенность `Iterator` без необходимости писать очень длинный вид. -Однако, `impl Trait` возможно использовать, если возвращаете только один вид. Например, данный код, который возвращает значения или вида `NewsArticle` или вида `Tweet`, но в качестве возвращаемого вида объявляет `impl Summary` , не будет работать: +Однако, `impl Trait` возможно использовать, если возвращаете только один вид. Например, данный рукопись, который возвращает значения или вида `NewsArticle` или вида `Tweet`, но в качестве возвращаемого вида объявляет `impl Summary` , не будет работать: ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-06-impl-trait-returns-one-type/src/lib.rs:here}} @@ -205,7 +205,7 @@ fn some_function(t: &T, u: &U) -> i32 { Приложение 10-15: Условная выполнение способов у обобщённых видов в зависимости от ограничений особенности -Мы также можем условно выполнить особенность для любого вида, который выполняет другой особенность. Выполнения особенности для любого вида, который удовлетворяет ограничениям особенности, называются *общими выполнениеми* и широко используются в встроенной библиотеке Rust. Например, обычная библиотека выполняет особенность `ToString` для любого вида, который выполняет особенность `Display`. Раздел`impl` в встроенной библиотеке выглядит примерно так: +Мы также можем условно выполнить особенность для любого вида, который выполняет другой особенность. Выполнения особенности для любого вида, который удовлетворяет ограничениям особенности, называются *общими выполнениеми* и широко используются в встроенной библиотеке Ржавчина. Например, обычная библиотека выполняет особенность `ToString` для любого вида, который выполняет особенность `Display`. Раздел`impl` в встроенной библиотеке выглядит примерно так: ```rust,ignore impl ToString for T { @@ -219,9 +219,9 @@ impl ToString for T { let s = 3.to_string(); ``` -Общие выполнения приведены в документации к особенности в разделе "Implementors". +Общие выполнения приведены в пособия к особенности в разделе "Implementors". -Особенности и ограничения особенностей позволяют писать код, который использует свойства обобщённого вида для уменьшения повторения кода, а также указывая сборщику, что мы хотим обобщённый вид, чтобы иметь определённое поведение. Затем сборщик может использовать сведения про ограничения особенности, чтобы проверить, что все определенные виды, используемые с нашим кодом, обеспечивают правильное поведение. В изменяемых строго определенных языках мы получили бы ошибку во время выполнения, если бы вызвали способ для вида, который не выполняет вид определяемый способом. Но Ржавчина перемещает эти ошибки на время сборки, поэтому мы вынуждены исправить сбоев, прежде чем наш код начнёт работать. Кроме того, мы не должны писать код, который проверяет своё поведение во время выполнения, потому что это уже проверено во время сборки. Это повышает производительность без необходимости отказываться от гибкости обобщённых видов. +Особенности и ограничения особенностей позволяют писать рукопись, который использует свойства обобщённого вида для уменьшения повторения рукописи, а также указывая сборщику, что мы хотим обобщённый вид, чтобы иметь определённое поведение. Затем сборщик может использовать сведения про ограничения особенности, чтобы проверить, что все определенные виды, используемые с нашим рукописью, обеспечивают правильное поведение. В изменяемых строго определенных языках мы получили бы ошибку во время выполнения, если бы вызвали способ для вида, который не выполняет вид определяемый способом. Но Ржавчина перемещает эти ошибки на время сборки, поэтому мы вынуждены исправить сбоев, прежде чем нашу рукопись начнёт работать. Кроме того, мы не должны писать рукопись, который проверяет своё поведение во время выполнения, потому что это уже проверено во время сборки. Это повышает производительность без необходимости отказываться от гибкости обобщённых видов. ["Использование предметов особенностей, которые разрешены для значений или разных видов"]: ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types diff --git a/rustbook-ru/src/ch10-03-lifetime-syntax.md b/rustbook-ru/src/ch10-03-lifetime-syntax.md index 280f1ac10..bd335cda5 100644 --- a/rustbook-ru/src/ch10-03-lifetime-syntax.md +++ b/rustbook-ru/src/ch10-03-lifetime-syntax.md @@ -2,7 +2,7 @@ Сроки (времена) жизни - ещё один вид обобщений, с которыми мы уже встречались. Если раньше мы использовали обобщения, чтобы убедиться, что вид обладает нужным нам поведением, теперь мы будем использовать сроки жизни для того, чтобы быть уверенными, что ссылки действительны как самое меньшее столько времени в этапе исполнения программы, сколько нам требуется. -В разделе ["Ссылки и заимствование"](ch04-02-references-and-borrowing.html#references-and-borrowing) главы 4, мы кое о чём умолчали: у каждой ссылки в Ржавчина есть своё время жизни — область кода, на протяжении которого данная ссылка действительна (valid). В большинстве случаев сроки жизни выводятся неявно — так же, как у видов (нам требуется явно объявлять виды лишь в тех случаях, когда при самостоятельном выведении вида возможны исходы). Точно так же мы должны явно объявлять сроки жизни тех ссылок, для которых времена жизни могут быть определены сборщиком по-разному. Ржавчина требует от нас объявлять взаимосвязи посредством обобщённых свойств сроков жизни, чтобы убедиться в том, что во время исполнения все действующие ссылки будут правильными. +В разделе ["Ссылки и заимствование"](ch04-02-references-and-borrowing.html#references-and-borrowing) главы 4, мы кое о чём умолчали: у каждой ссылки в Ржавчине есть своё время жизни — область рукописи, на протяжении которого данная ссылка действительна (valid). В большинстве случаев сроки жизни выводятся неявно — так же, как у видов (нам требуется явно объявлять виды лишь в тех случаях, когда при самостоятельном выведении вида возможны исходы). Точно так же мы должны явно объявлять сроки жизни тех ссылок, для которых времена жизни могут быть определены сборщиком по-разному. Ржавчина требует от нас объявлять взаимосвязи посредством обобщённых свойств сроков жизни, чтобы убедиться в том, что во время исполнения все действующие ссылки будут правильными. Определение времени жизни — это подход, отсутствующая в большинстве других языков программирования, так что она может показаться незнакомой. Хотя в этой главе мы не будем рассматривать времена жизни во всех подробностях, тем не менее, мы обсудим основные случаи, в которых вы можете столкнуться с правилами написания времени жизни, что позволит вам получше ознакомиться с этой подходом. @@ -16,19 +16,19 @@ Приложение 10-16: Попытка использования ссылки, значение которой вышло из области видимости -> Примечание: примеры в приложениях 10-16, 10-17 и 10-23 объявляют переменные без указания их начального значения, поэтому имя переменной существует во внешней области видимости. На первый взгляд может показаться, что это противоречит отсутствию в Ржавчина нулевых (null) значений. Однако, если мы попытаемся использовать переменную, прежде чем присвоить ей значение, мы получим ошибку сборки, которая показывает, что Ржавчина действительно не разрешает нулевые (null) значения. +> Примечание: примеры в приложениях 10-16, 10-17 и 10-23 объявляют переменные без указания их начального значения, поэтому имя переменной существует во внешней области видимости. На первый взгляд может показаться, что это противоречит отсутствию в Ржавчине нулевых (null) значений. Однако, если мы попытаемся использовать переменную, прежде чем присвоить ей значение, мы получим ошибку сборки, которая показывает, что Ржавчина действительно не разрешает нулевые (null) значения. -Внешняя область видимости объявляет переменную с именем `r` без начального значения, а внутренняя область объявляет переменную с именем `x` с начальным значением `5`. Во внутренней области мы пытаемся установить значение `r` как ссылку на `x`. Затем внутренняя область видимости заканчивается и мы пытаемся напечатать значение из `r`. Этот код не будет собран, потому что значение на которое ссылается r исчезает из области видимости, прежде чем мы попробуем использовать его. Вот сообщение об ошибке: +Внешняя область видимости объявляет переменную с именем `r` без начального значения, а внутренняя область объявляет переменную с именем `x` с начальным значением `5`. Во внутренней области мы пытаемся установить значение `r` как ссылку на `x`. Затем внутренняя область видимости заканчивается и мы пытаемся напечатать значение из `r`. Этот рукопись не будет собран, потому что значение на которое ссылается r исчезает из области видимости, прежде чем мы попробуем использовать его. Вот сообщение об ошибке: ```console {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-16/output.txt}} ``` -Переменная `x` «не живёт достаточно долго». Причина в том, что `x` выйдет из области видимости, когда эта внутренняя область закончится в строке 7. Но `r` все ещё является действительной во внешней области видимости; поскольку её охват больше, мы говорим, что она «живёт дольше». Если бы Ржавчина позволил такому коду работать, то переменная `r` смогла бы ссылаться на память, которая уже была освобождена (в тот мгновение, когда `x` вышла из внутренней области видимости), и всё что мы попытались бы сделать с `r` работало бы неправильно. Как же Ржавчина определяет, что этот код неправилен? Он использует для этого анализатор заимствований (borrow checker). +Переменная `x` «не живёт достаточно долго». Причина в том, что `x` выйдет из области видимости, когда эта внутренняя область закончится в строке 7. Но `r` все ещё является действительной во внешней области видимости; поскольку её охват больше, мы говорим, что она «живёт дольше». Если бы Ржавчина позволил такому рукописи работать, то переменная `r` смогла бы ссылаться на память, которая уже была освобождена (в тот мгновение, когда `x` вышла из внутренней области видимости), и всё что мы попытались бы сделать с `r` работало бы неправильно. Как же Ржавчина определяет, что этот рукопись неправилен? Он использует для этого оценщик заимствований (borrow checker). -### Анализатор заимствований +### Оценщик заимствований -Сборщик Ржавчина имеет в своём составе *анализатор заимствований*, который сравнивает области видимости для определения, являются ли все заимствования действительными. В приложении 10-17 показан тот же код, что и в приложении 10-16, но с изложениями, показывающими времена жизни переменных. +Сборщик Ржавчина имеет в своём составе *оценщик заимствований*, который сравнивает области видимости для определения, являются ли все заимствования действительными. В приложении 10-17 показан тот же рукопись, что и в приложении 10-16, но с изложениями, показывающими времена жизни переменных. ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-17/src/main.rs}} @@ -38,7 +38,7 @@ Здесь мы описали время жизни для `r` с помощью `'a` и время жизни `x` с помощью `'b` . Как видите, время жизни `'b` внутреннего раздела гораздо меньше, чем время жизни `'a` внешнего раздела. Во время сборки Ржавчина сравнивает продолжительность двух времён жизни и видит, что `r` имеет время жизни `'a`, но ссылается на память со временем жизни `'b`. Программа отклоняется, потому что `'b` короче, чем `'a`: предмет ссылки не живёт так же долго, как сама ссылка. -Приложение 10-18 исправляет код, чтобы в нём не было повисшей ссылки, и собирается без ошибок. +Приложение 10-18 исправляет рукопись, чтобы в нём не было повисшей ссылки, и собирается без ошибок. ```rust {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-18/src/main.rs}} @@ -48,11 +48,11 @@ Здесь переменная `x` имеет время жизни `'b`, которое больше, чем время жизни `'a`. Это означает, что переменная `r` может ссылаться на переменную `x` потому что Ржавчина знает, что ссылка в переменной `r` будет всегда действительной до тех пор, пока переменная `x` является валидной. -После того, как мы на примерах рассмотрели времена жизни ссылок и обсудили как Ржавчина их анализирует, давайте поговорим об обобщённых временах жизни входных свойств и возвращаемых значений функций. +После того, как мы на примерах рассмотрели времена жизни ссылок и обсудили как Ржавчина их рассмотривает, давайте поговорим об обобщённых временах жизни входных свойств и возвращаемых значений функций. ### Обобщённые времена жизни в функциях -Напишем функцию, которая возвращает более длинный из двух срезов строки. Эта функция принимает два среза строки и возвращает один срез строки. После того как мы выполнили функцию `longest`, код в приложении 10-19 должен вывести `The longest string is abcd`. +Напишем функцию, которая возвращает более длинный из двух срезов строки. Эта функция принимает два среза строки и возвращает один срез строки. После того как мы выполнили функцию `longest`, рукопись в приложении 10-19 должен вывести `The longest string is abcd`. Файл: src/main.rs @@ -82,13 +82,13 @@ Текст ошибки показывает, что возвращаемому виду нужен обобщённый свойство времени жизни, потому что Ржавчина не может определить, относится ли возвращаемая ссылка к `x` или к `y`. На самом деле, мы тоже не знаем, потому что раздел`if` в теле функции возвращает ссылку на `x`, а раздел`else` возвращает ссылку на `y`! -Когда мы определяем эту функцию, мы не знаем определенных значений, которые будут в неё передаваться. Поэтому мы не знаем какая из ветвей оператора `if` или `else` будет выполнена. Мы также не знаем определенных времён жизни ссылок, которые будут переданы в функцию, поэтому мы не можем посмотреть на их области видимости, как мы делали в примерах 10-17 и 10-18, чтобы определить, будет ли возвращаемая нами ссылка правильной во всех случаях. Анализатор заимствований также не может этого определить, потому что он не знает как времена жизни переменных `x` и `y` соотносятся с временем жизни возвращаемого значения. Чтобы исправить эту ошибку, мы добавим обобщённый свойство времени жизни, который определит отношения между ссылками таким образом, чтобы анализатор заимствований мог провести свой анализ. +Когда мы определяем эту функцию, мы не знаем определенных значений, которые будут в неё передаваться. Поэтому мы не знаем какая из ветвей приказчика `if` или `else` будет выполнена. Мы также не знаем определенных времён жизни ссылок, которые будут переданы в функцию, поэтому мы не можем посмотреть на их области видимости, как мы делали в примерах 10-17 и 10-18, чтобы определить, будет ли возвращаемая нами ссылка правильной во всех случаях. Оценщик заимствований также не может этого определить, потому что он не знает как времена жизни переменных `x` и `y` соотносятся с временем жизни возвращаемого значения. Чтобы исправить эту ошибку, мы добавим обобщённый свойство времени жизни, который определит отношения между ссылками таким образом, чтобы оценщик заимствований мог провести свой оценку. ### правила написания изложении времени жизни Изложения времени жизни не меняют срок, как долго живёт та или иная ссылка. Они скорее описывают, как соотносятся между собой времена жизни нескольких ссылок, не влияя на само время жизни. Точно так же, как функции могут принимать любой вид, когда в ярлыке указан свойство обобщённого вида, функции могут принимать ссылки с любым временем жизни, указанным с помощью свойства обобщённого времени жизни. -Изложения времени жизни имеют немного необычный правила написания: имена свойств времени жизни должны начинаться с апострофа (`'`), пишутся маленькими буквами, и обычно очень короткие, как и имена обобщённых видов. Большинство людей использует имя `'a` в качестве первой изложении времени жизни. Изложения свойств времени жизни следуют после символа `&` и отделяются пробелом от названия ссылочного вида. +Изложения времени жизни имеют немного необычный правила написания: имена свойств времени жизни должны начинаться с апострофа (`'`), пишутся маленькими буквами, и обычно очень короткие, как и имена обобщённых видов. Большинство людей использует имя `'a` в качестве первой изложении времени жизни. Изложения свойств времени жизни следуют после знака `&` и отделяются пробелом от названия ссылочного вида. Приведём несколько примеров: у нас есть ссылка на `i32` без указания времени жизни, ссылка на `i32`, с временем жизни имеющим имя `'a` и изменяемая ссылка на `i32`, которая также имеет время жизни `'a`. @@ -114,13 +114,13 @@ Приложение 10-21: В определении функции longest указано, что все ссылки должны иметь одинаковое время жизни, обозначенное как 'a -Этот код должен собираться и давать желаемый итог, когда мы вызовем его в функции `main` приложения 10-19. +Этот рукопись должен собираться и давать желаемый итог, когда мы вызовем его в функции `main` приложения 10-19. -Ярлык функции теперь сообщает Rust, что для некоторого времени жизни `'a` функция принимает два свойства, оба из которых являются срезами строк, которые живут не меньше, чем время жизни `'a`. Ярлык функции также сообщает Rust, что срез строки, возвращаемый функцией, будет жить как самое меньшее столько, сколько длится время жизни `'a`. В действительностиэто означает, что время жизни ссылки, возвращаемой функцией `longest`, равно меньшему времени жизни передаваемых в неё ссылок. Мы хотим, чтобы Ржавчина использовал именно такие отношения при анализе этого кода. +Ярлык функции теперь сообщает Ржавчина, что для некоторого времени жизни `'a` функция принимает два свойства, оба из которых являются срезами строк, которые живут не меньше, чем время жизни `'a`. Ярлык функции также сообщает Ржавчина что срез строки, возвращаемый функцией, будет жить как самое меньшее столько, сколько длится время жизни `'a`. В действительности это означает, что время жизни ссылки, возвращаемой функцией `longest`, равно меньшему времени жизни передаваемых в неё ссылок. Мы хотим, чтобы Ржавчина использовал именно такие отношения при оценке этого рукописи. -Помните, когда мы указываем свойства времени жизни в этой ярлыке функции, мы не меняем время жизни каких-либо переданных или возвращённых значений. Скорее, мы указываем, что анализатор заимствований должен отклонять любые значения, которые не соответствуют этим ограничениям. Обратите внимание, что самой функции `longest` не нужно точно знать, как долго будут жить `x` и `y`, достаточно того, что некоторая область может быть заменена на `'a`, которая будет удовлетворять этой ярлыке. +Помните, когда мы указываем свойства времени жизни в этой ярлыке функции, мы не меняем время жизни каких-либо переданных или возвращённых значений. Скорее, мы указываем, что оценщик заимствований должен отклонять любые значения, которые не соответствуют этим ограничениям. Обратите внимание, что самой функции `longest` не нужно точно знать, как долго будут жить `x` и `y`, достаточно того, что некоторая область может быть заменена на `'a`, которая будет удовлетворять этой ярлыке. -При определении времён жизни функций, изложении помещаются в ярлык функции, а не в тело функции. Изложения времени жизни становятся частью договора функции, как и виды в ярлыке. Наличие ярлыков функций, содержащих договор времени жизни, означает, что анализ который выполняет сборщик Rust, может быть проще. Если есть неполадка с тем, как функция определяется или как она вызывается, ошибки сборщика могут указать на часть нашего кода и ограничения более точно. Если бы вместо этого сборщик Ржавчина сделал больше предположений о том, какие отношения времён жизни мы хотели получить, сборщик смог бы указать только на использование нашего кода за много шагов от источника сбоев. +При определении времён жизни функций, изложении помещаются в ярлык функции, а не в тело функции. Изложения времени жизни становятся частью договора функции, как и виды в ярлыке. Наличие ярлыков функций, содержащих договор времени жизни, означает, что оценка который выполняет сборщик Ржавчина, может быть проще. Если есть неполадка с тем, как функция определяется или как она вызывается, ошибки сборщика могут указать на часть нашего рукописи и ограничения более точно. Если бы вместо этого сборщик Ржавчина сделал больше предположений о том, какие отношения времён жизни мы хотели получить, сборщик смог бы указать только на использование нашего рукописи за много шагов от источника сбоев. Когда мы передаём определенные ссылки в функцию `longest`, определенным временем жизни, которое будет заменено на `'a`, является часть области видимости `x`, которая пересекается с областью видимости `y`. Другими словами, обобщённое время жизни `'a` получит определенное время жизни, равное меньшему из времён жизни `x` и `y`. Так как мы определяли возвращаемую ссылку тем же свойствоом времени жизни `'a`, то возвращённая ссылка также будет действительна на протяжении меньшего из времён жизни `x` и `y`. @@ -134,9 +134,9 @@ Приложение 10-22: Использование функции longest со ссылками на значения вида String, имеющими разное время жизни -В этом примере переменная `string1` действительна до конца внешней области, `string2` действует до конца внутренней области видимости и `result` ссылается на что-то, что является действительным до конца внутренней области видимости. Запустите этот код, и вы увидите что анализатор заимствований разрешает такой код; он собирает и напечатает `The longest string is long string is long`. +В этом примере переменная `string1` действительна до конца внешней области, `string2` действует до конца внутренней области видимости и `result` ссылается на что-то, что является действительным до конца внутренней области видимости. Запустите этот рукопись, и вы увидите что оценщик заимствований разрешает такой код; он собирает и напечатает `The longest string is long string is long`. -Теперь, давайте попробуем пример, который показывает, что время жизни ссылки `result` должно быть меньшим временем жизни одного из двух переменных. Мы переместим объявление переменной `result` за пределы внутренней области видимости, но оставим присвоение значения переменной `result` в области видимости `string2`. Затем мы переместим `println!`, который использует `result` за пределы внутренней области видимости, после того как внутренняя область видимости закончилась. Код в приложении 10-23 не собирается. +Теперь, давайте попробуем пример, который показывает, что время жизни ссылки `result` должно быть меньшим временем жизни одного из двух переменных. Мы переместим объявление переменной `result` за пределы внутренней области видимости, но оставим присвоение значения переменной `result` в области видимости `string2`. Затем мы переместим `println!`, который использует `result` за пределы внутренней области видимости, после того как внутренняя область видимости закончилась. Рукопись в приложении 10-23 не собирается. Файл: src/main.rs @@ -146,7 +146,7 @@ Приложение 10-23: Попытка использования result, после того как string2 вышла из области видимости -При попытке собрать этот код, мы получим такую ошибку: +При попытке собрать этот рукопись, мы получим такую ошибку: ```console {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-23/output.txt}} @@ -154,13 +154,13 @@ Эта ошибка говорит о том, что если мы хотим использовать `result` в указания `println!`, переменная `string2` должна бы быть действительной до конца внешней области видимости. Ржавчина знает об этом, потому что мы определяли свойства функции и её возвращаемое значение одинаковым временем жизни `'a`. -Будучи людьми, мы можем посмотреть на этот код и увидеть, что `string1` длиннее, чем `string2` и, следовательно, `result` будет содержать ссылку на `string1`. Поскольку `string1` ещё не вышла из области видимости, ссылка на `string1` будет все ещё действительной в указания `println!`. Однако сборщик не видит, что ссылка в этом случае валидна. Мы сказали Rust, что время жизни ссылки, возвращаемой из функции `longest`, равняется меньшему из времён жизни переданных в неё ссылок. Таким образом, анализатор заимствований запрещает код в приложении 10-23, как возможно имеющий недействительную ссылку. +Будучи людьми, мы можем посмотреть на этот рукопись и увидеть, что `string1` длиннее, чем `string2` и, следовательно, `result` будет содержать ссылку на `string1`. Поскольку `string1` ещё не вышла из области видимости, ссылка на `string1` будет все ещё действительной в указания `println!`. Однако сборщик не видит, что ссылка в этом случае валидна. Мы сказали Ржавчина, что время жизни ссылки, возвращаемой из функции `longest`, равняется меньшему из времён жизни переданных в неё ссылок. Таким образом, оценщик заимствований запрещает рукопись в приложении 10-23, как возможно имеющий недействительную ссылку. -Попробуйте провести больше экспериментов с различными значениями и временами жизни ссылок, передаваемых в функцию `longest`, а также с тем, как используется возвращаемое значение Перед сборкой делайте предположения о том, пройдёт ли ваш код анализ заимствований, а затем проверяйте, насколько вы были правы. +Попробуйте провести больше экспериментов с различными значениями и временами жизни ссылок, передаваемых в функцию `longest`, а также с тем, как используется возвращаемое значение Перед сборкой делайте предположения о том, пройдёт ли ваша рукопись оценка заимствований, а затем проверяйте, насколько вы были правы. ### Мышление в понятиях времён жизни -В зависимости от того, что делает ваша функция, следует использовать разные способы указания свойств времени жизни. Например, если мы изменим выполнение функции `longest` таким образом, чтобы она всегда возвращала свой первый переменная вместо самого длинного среза строки, то время жизни для свойства `y` можно совсем не указывать. Этот код собирается: +В зависимости от того, что делает ваша функция, следует использовать разные способы указания свойств времени жизни. Например, если мы изменим выполнение функции `longest` таким образом, чтобы она всегда возвращала свой первый переменная вместо самого длинного среза строки, то время жизни для свойства `y` можно совсем не указывать. Этот рукопись собирается: Файл: src/main.rs @@ -206,7 +206,7 @@ ### Правила неявного выведения времени жизни -Вы изучили, что у каждой ссылки есть время жизни и что нужно указывать свойства времени жизни для функций или устройств, которые используют ссылки. Однако в Главе 4 у нас была функция в приложении 4-9, которая затем снова показана в приложении 10-25, в которой код собрался без наставлений времени жизни. +Вы изучили, что у каждой ссылки есть время жизни и что нужно указывать свойства времени жизни для функций или устройств, которые используют ссылки. Однако в Главе 4 у нас была функция в приложении 4-9, которая затем снова показана в приложении 10-25, в которой рукопись собрался без наставлений времени жизни. Файл: src/lib.rs @@ -216,17 +216,17 @@ Приложение 10-25: Функция, которую мы определили в приложении 4-9 собирается без наставлений времени жизни, несмотря на то, что входной и возвращаемый вид свойств являются ссылками -Причина, по которой этот код собирается — историческая. В ранних (до-1.0) исполнениях Ржавчина этот код не собрался бы, поскольку каждой ссылке нужно было явно назначать время жизни. В те времена, ярлык функции была бы написана примерно так: +Причина, по которой этот рукопись собирается — историческая. В ранних (до-1.0) исполнениях Ржавчины этот рукопись не собрался бы, поскольку каждой ссылке нужно было явно назначать время жизни. В те времена, ярлык функции была бы написана примерно так: ```rust,ignore fn first_word<'a>(s: &'a str) -> &'a str { ``` -После написания большого количества кода на Ржавчина разработчики языка обнаружили, что в определённых случаейх программисты описывают одни и те же изложении времён жизни снова и снова. Эти случаи были предсказуемы и следовали нескольким определенным образцовым моделям. Объединение Ржавчина решила запрограммировать эти образцы в код сборщика Rust, чтобы анализатор заимствований мог вывести времена жизни в таких случаейх без необходимости явного указания наставлений программистами. +После написания большого количества рукописи на Ржавчине разработчики языка обнаружили, что в определённых случаях программисты описывают одни и те же изложении времён жизни снова и снова. Эти случаи были предсказуемы и следовали нескольким определенным образцовым моделям. Объединение Ржавчина решила запрограммировать эти образцы в рукопись сборщика Ржавчины чтобы оценщик заимствований мог вывести времена жизни в таких случаях без необходимости явного указания наставлений программистами. -Мы упоминаем этот отрывок истории Rust, потому что возможно, что в будущем появится больше образцов для самостоятельного выведения времён жизни, которые будут добавлены в сборщик. Таким образом, в будущем может понадобится ещё меньшее количество наставлений. +Мы упоминаем этот отрывок истории Ржавчина, потому что возможно, что в будущем появится больше образцов для самостоятельного выведения времён жизни, которые будут добавлены в сборщик. Таким образом, в будущем может понадобится ещё меньшее количество наставлений. -Образцы, запрограммированные в анализаторе ссылок языка Rust, называются *правилами неявного выведения времени жизни*. Это не правила, которым должны следовать программисты; а набор частных случаев, которые рассмотрит сборщик, и, если ваш код попадает в эти случаи, вам не нужно будет указывать время жизни явно. +Образцы, запрограммированные в оценщике ссылок языка Ржавчина называются *правилами неявного выведения времени жизни*. Это не правила, которым должны следовать программисты; а набор частных случаев, которые рассмотрит сборщик, и, если ваша рукопись попадает в эти случаи, вам не нужно будет указывать время жизни явно. Правила выведения не предоставляют полного заключения. Если Ржавчина определенно применяет правила, но некоторая неясность относительно времён жизни ссылок все ещё остаётся, сборщик не будет догадываться, какими должны быть времена жизни оставшихся ссылок. В этом случае, вместо угадывания сборщик выдаст ошибку, которую вы можете устранить, добавив изложении времени жизни. @@ -238,7 +238,7 @@ fn first_word<'a>(s: &'a str) -> &'a str { Второе правило говорит, что если есть ровно один входной свойство времени жизни, то его время жизни назначается всем выходным свойствам: `fn foo<'a>(x: &'a i32) -> &'a i32`. -Третье правило о том, что если есть множество входных свойств времени жизни, но один из них является ссылкой `&self` или `&mut self`, так как эта функция является способом, то время жизни `self` назначается временем жизни всем выходным свойствам. Это третье правило делает способы намного приятнее для чтения и записи, потому что требуется меньше символов. +Третье правило о том, что если есть множество входных свойств времени жизни, но один из них является ссылкой `&self` или `&mut self`, так как эта функция является способом, то время жизни `self` назначается временем жизни всем выходным свойствам. Это третье правило делает способы намного приятнее для чтения и записи, потому что требуется меньше знаков. Представим, что мы сборщик и применим эти правила, чтобы вывести времена жизни ссылок в ярлыке функции `first_word` приложения 10-25. Ярлык этой функции начинается без объявления времён жизни ссылок: @@ -258,7 +258,7 @@ fn first_word<'a>(s: &'a str) -> &str { fn first_word<'a>(s: &'a str) -> &'a str { ``` -Теперь все ссылки в этой функции имеют свойства времени жизни и сборщик может продолжить свой анализ без необходимости просить у программиста указать изложении времён жизни в ярлыке этой функции. +Теперь все ссылки в этой функции имеют свойства времени жизни и сборщик может продолжить свой оценка без необходимости просить у программиста указать изложении времён жизни в ярлыке этой функции. Давайте рассмотрим ещё один пример: на этот раз функцию `longest`, в которой не было свойств времени жизни, когда мы начали с ней работать в приложении 10-20: @@ -272,7 +272,7 @@ fn longest(x: &str, y: &str) -> &str { fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str { ``` -Можно заметить, что второе правило здесь не применимо, так как в ярлыке указано больше одного входного свойства времени жизни. Третье правило также не применимо, так как `longest` — функция, а не способ, следовательно, в ней нет свойства `self`. Итак, мы прошли все три правила, но так и не смогли вычислить время жизни выходного свойства. Поэтому мы и получили ошибку при попытке собрать код приложения 10-20: сборщик работал по правилам неявного выведения времён жизни, но не мог выяснить все времена жизни ссылок в ярлыке. +Можно заметить, что второе правило здесь не применимо, так как в ярлыке указано больше одного входного свойства времени жизни. Третье правило также не применимо, так как `longest` — функция, а не способ, следовательно, в ней нет свойства `self`. Итак, мы прошли все три правила, но так и не смогли вычислить время жизни выходного свойства. Поэтому мы и получили ошибку при попытке собрать рукопись приложения 10-20: сборщик работал по правилам неявного выведения времён жизни, но не мог выяснить все времена жизни ссылок в ярлыке. Так как третье правило применяется только к способам, далее мы рассмотрим времена жизни в этом среде, чтобы понять, почему нам часто не требуется определять времена жизни в ярлыках способов. @@ -324,9 +324,9 @@ let s: &'static str = "I have a static lifetime."; ## Итоги -В этой главе мы рассмотрели много всего! Теперь вы знакомы с свойствами обобщённого вида, особенностями и ограничениями особенности, обобщёнными свойствами времени жизни, вы готовы писать код без повторений, который будет работать во множестве различных случаев. Свойства обобщённого вида позволяют использовать код для различных видов данных. Особенности и ограничения особенности помогают убедиться, что, хотя виды и обобщённые, они будут вести себя, как этого требует ваш код. Вы изучили, как использовать изложении времени жизни чтобы убедиться, что этот гибкий код не будет порождать никаких повисших ссылок. И весь этот анализ происходит в мгновение сборки и не влияет на производительность программы во время работы! +В этой главе мы рассмотрели много всего! Теперь вы знакомы с свойствами обобщённого вида, особенностями и ограничениями особенности, обобщёнными свойствами времени жизни, вы готовы писать рукопись без повторений, который будет работать во множестве различных случаев. Свойства обобщённого вида позволяют использовать рукопись для различных видов данных. Особенности и ограничения особенности помогают убедиться, что, хотя виды и обобщённые, они будут вести себя, как этого требует ваша рукопись. Вы изучили, как использовать изложении времени жизни чтобы убедиться, что этот гибкий рукопись не будет порождать никаких повисших ссылок. И весь этот оценка происходит в мгновение сборки и не влияет на производительность программы во время работы! -Верите или нет, но в рамках этой темы всё есть ещё чему поучиться: в Главе 17 обсуждаются особенности-предметы, которые являются ещё одним способом использования особенностей. Существуют также более сложные сценарии с изложениями времени жизни, которые вам понадобятся только в очень сложных случаях; для этого вам следует прочитать [Rust Reference]. Далее вы узнаете, как писать проверки на Rust, чтобы убедиться, что ваш код работает так, как задумано. +Верите или нет, но в рамках этой темы всё есть ещё чему поучиться: в Главе 17 обсуждаются особенности-предметы, которые являются ещё одним способом использования особенностей. Существуют также более сложные задумки с изложениями времени жизни, которые вам понадобятся только в очень сложных случаях; для этого вам следует прочитать [Ржавчина Reference]. Далее вы узнаете, как писать проверки на Ржавчине чтобы убедиться, что ваша рукопись работает так, как задумано. -[Rust Reference]: ../reference/index.html \ No newline at end of file +[Ржавчина Reference]: ../reference/index.html \ No newline at end of file diff --git a/rustbook-ru/src/ch11-00-testing.md b/rustbook-ru/src/ch11-00-testing.md index ea681cfc4..7899b6ac1 100644 --- a/rustbook-ru/src/ch11-00-testing.md +++ b/rustbook-ru/src/ch11-00-testing.md @@ -1,11 +1,11 @@ -# Написание автоматизированных проверок +# Написание самоматизированных проверок -В своём эссе 1972 года “The Humble Programmer,” Edsger W. Dijkstra сказал, что «Проверка программы может быть очень эффективным способом показать наличие ошибок, но это безнадёжно неадекватно для показа их отсутствия». Это не значит, что мы не должны пытаться проверять столько, сколько мы можем! +В своём эссе 1972 года “The Humble Programmer,” Edsger W. Dijkstra сказал, что «Проверка программы может быть очень производительным способом показать наличие ошибок, но это безнадёжно неадекватно для показа их отсутствия». Это не значит, что мы не должны пытаться проверять столько, сколько мы можем! -Соблюдение правил программы считается то, в какой степени наш код выполняет именно то, что мы задумывали. Ржавчина разработан с учётом большой озабоченности соблюдением правил программ, но соблюдение правил сложна и нелегко доказуема. Система определения Ржавчина берет на себя огромную часть этого бремени, но она не может уловить абсолютно все сбоев. Поэтому в Ржавчина предусмотрена возможность написания автопроверок. +Соблюдение правил программы считается то, в какой степени нашу рукопись выполняет именно то, что мы задумывали. Ржавчина разработан с учётом большой озабоченности соблюдением правил программ, но соблюдение правил сложна и нелегко доказуема. Система определения Ржавчина берет на себя огромную часть этого бремени, но она не может уловить безусловно все сбоев. Поэтому в Ржавчине предусмотрена возможность написания самопроверок. Допустим, мы пишем функцию `add_two`, которая прибавляет 2 к любому переданному ей числу. Ярлык этой функции принимает целое число в качестве свойства и возвращает целое число в качестве итога. Когда мы выполняем и собираем эту функцию, Ржавчина выполняет всю проверку видов и проверку заимствований, которую вы уже изучили, чтобы убедиться, что, например, мы не передаём значение `String` или недопустимую ссылку в эту функцию. Но Ржавчина *не способен* проверить, что эта функция сделает именно то, что мы задумали, то есть вернёт свойство плюс 2, а не, скажем, свойство плюс 10 или свойство - 50! Вот тут-то и приходят на помощь проверки. -Мы можем написать проверки, которые утверждают, например, что когда мы передаём `3` в функцию `add_two`, возвращаемое значение будет `5`. Мы можем запускать эти проверки всякий раз, когда мы вносим изменения в наш код, чтобы убедиться, что любое существующее правильное поведение не изменилось. +Мы можем написать проверки, которые утверждают, например, что когда мы передаём `3` в функцию `add_two`, возвращаемое значение будет `5`. Мы можем запускать эти проверки всякий раз, когда мы вносим изменения в нашу рукопись, чтобы убедиться, что любое существующее правильное поведение не изменилось. -Проверка - сложный навык: мы не сможем охватить все подробности написания хороших проверок в одной главе, но мы обсудим основные подходы к проверке в Rust. Мы поговорим об изложениех и макросах, доступных вам для написания проверок, о поведении по умолчанию и свойствах, предусмотренных для запуска проверок, а также о том, как согласовать проверки в состоящие из звеньев проверки и встроенные проверки. +Проверка - сложный навык: мы не сможем охватить все подробности написания хороших проверок в одной главе, но мы обсудим основные подходы к проверке в Ржавчине. Мы поговорим об изложениех и макросах, доступных вам для написания проверок, о поведении по умолчанию и свойствах, предусмотренных для запуска проверок, а также о том, как согласовать проверки в состоящие из звеньев проверки и встроенные проверки. diff --git a/rustbook-ru/src/ch11-01-writing-tests.md b/rustbook-ru/src/ch11-01-writing-tests.md index 318aa0677..5f9f4e331 100644 --- a/rustbook-ru/src/ch11-01-writing-tests.md +++ b/rustbook-ru/src/ch11-01-writing-tests.md @@ -1,20 +1,20 @@ ## Как писать проверки -Проверки - это функции Rust, которые проверяют, что не проверочный код работает ожидаемым образом. Содержимое проверочных функций обычно выполняет следующие три действия: +Проверки - это функции Ржавчина, которые проверяют, что не проверочный рукопись работает ожидаемым образом. Содержимое проверочных функций обычно выполняет следующие три действия: 1. Установка любых необходимых данных или состояния. -2. Запуск кода, который вы хотите проверить. +2. Запуск рукописи, который вы хотите проверить. 3. Утверждение, что итоги являются теми, которые вы ожидаете. -Давайте рассмотрим функции предоставляемые в Ржавчина целенаправленно для написания проверок, которые выполнят все эти действия, включая свойство `test`, несколько макросов и свойство `should_panic`. +Давайте рассмотрим функции предоставляемые в Ржавчине целенаправленно для написания проверок, которые выполнят все эти действия, включая свойство `test`, несколько макросов и свойство `should_panic`. ### Устройства проверяющей функции -В простейшем случае в Ржавчина проверка - это функция, определенная свойством `test`. Свойства представляют собой метаданные о отрывках кода Rust; один из примеров свойство `derive`, который мы использовали со устройствами в главе 5. Чтобы превратить функцию в проверяющую функцию добавьте `#[test]` в строку перед `fn`. Когда вы запускаете проверки приказом `cargo test`, Ржавчина создаёт двоичный звено выполняющий функции определеные свойством test и сообщающий о том, успешно или нет прошла каждая проверяющая функция. +В простейшем случае в Ржавчине проверка - это функция, определенная свойством `test`. Свойства представляют собой метаданные о отрывках рукописи Rust; один из примеров свойство `derive`, который мы использовали со устройствами в главе 5. Чтобы превратить функцию в проверяющую функцию добавьте `#[test]` в строку перед `fn`. Когда вы запускаете проверки приказом `cargo test`, Ржавчина создаёт двоичный звено выполняющий функции определеные свойством test и сообщающий о том, успешно или нет прошла каждая проверяющая функция. -Когда мы создаём новый дело библиотеки с помощью Cargo, то в нём самостоятельно порождается проверочный звено с проверку-функцией для нас. Этот звено даст вам образец для написания ваших проверок, так что вам не нужно искать точную устройство и правила написания проверочных функций каждый раз, когда вы начинаете новый дело. Вы можете добавить столько дополнительных проверочных функций и столько проверочных звеньев, сколько захотите! +Когда мы создаём новое дело библиотеки с помощью Cargo, то в нём самостоятельно порождается проверочный звено с проверку-функцией для нас. Этот звено даст вам образец для написания ваших проверок, так что вам не нужно искать точную устройство и правила написания проверочных функций каждый раз, когда вы начинаете новое дело. Вы можете добавить столько дополнительных проверочных функций и столько проверочных звеньев, сколько захотите! -Мы исследуем некоторые особенности работы проверок, экспериментируя с образцовым проверкой созданным для нас, без существующего проверки любого кода. Затем мы напишем некоторые существующие проверки, которые вызывают некоторый написанный код и убедимся в его правильном поведении. Мы рассмотрим некоторые особенности работы проверок, поэкспериментируем с образцовым проверкой, прежде чем приступать к действительному проверке любого кода. Затем мы напишем несколько существующих проверок, которые вызывают некоторый написанный нами код и проверяют, что его поведение правильное. +Мы исследуем некоторые особенности работы проверок, экспериментируя с образцовым проверкой созданным для нас, без существующего проверки любой рукописи. Затем мы напишем некоторые существующие проверки, которые вызывают некоторый написанный рукопись и убедимся в его правильном поведении. Мы рассмотрим некоторые особенности работы проверок, поэкспериментируем с образцовым проверкой, прежде чем приступать к действительному проверке любой рукописи. Затем мы напишем несколько существующих проверок, которые вызывают некоторый написанный нами рукопись и проверяют, что его поведение правильное. Давайте создадим новый библиотечный дело под названием `adder`, который складывает два числа: @@ -44,9 +44,9 @@ cd ../../.. Приложение 11-1: Проверочный звено и функция, созданные самостоятельно с помощью cargo new -Сейчас давайте пренебрегаем первые две строчки кода и сосредоточимся на функции. Обратите внимание на правила написания изложении `#[test]`: этот свойство указывает, что это проверочная функция, поэтому запускающий проверка знает, что эту функцию следует рассматривать как проверочную. У нас также могут быть не проверяемые функции в звене `tests`, которые помогут настроить общие сценарии или выполнить общие действия, поэтому нам всегда нужно указывать, какие функции являются проверкими. +Сейчас давайте пренебрегаем первые две строчки рукописи и сосредоточимся на функции. Обратите внимание на правила написания изложении `#[test]`: этот свойство указывает, что это проверочная функция, поэтому запускающая проверка знает, что эту функцию следует рассматривать как проверочную. У нас также могут быть не проверяемые функции в звене `tests`, которые помогут настроить общие задумки или выполнить общие действия, поэтому нам всегда нужно указывать, какие функции являются проверкими. -В теле функции проверки используется макрос `assert_eq!`, чтобы утверждать, что `result`, который содержит итог сложения 2 и 2, равен 4. Это утверждение служит примером вида для типичного проверки. Давайте запустим его, чтобы убедиться, что этот проверка пройден. +В теле функции проверки используется макрос `assert_eq!`, чтобы утверждать, что `result`, который содержит итог сложения 2 и 2, равен 4. Это утверждение служит примером вида для обычной проверки. Давайте запустим его, чтобы убедиться, что этот проверка пройден. Приказ `cargo test` выполнит все проверки в выбранном деле и сообщит о итогах как в приложении 11-2: @@ -60,9 +60,9 @@ Cargo собрал и выполнил проверку. Мы видим стр Можно пометить проверка как пренебрегаемый, чтобы он не выполнялся в определенном случае; мы рассмотрим это в разделе [“Пренебрежение некоторых проверок, если их целенаправленно не запрашивать”] позже в этой главе. Поскольку в данный мгновение мы этого не сделали, в сводке показано, что `0 ignored`. Мы также можем передать переменная приказу `cargo test` для запуска только тех проверок, имя которых соответствует строке; это называется *выборкой*, и мы рассмотрим это в разделе [“Запуск подмножества проверок по имени”]. Мы также не фильтровали выполняемые проверки, поэтому в конце сводки показано, что `0 filtered out`. -Исчисление `0 measured` предназначена для проверок производительности. На мгновение написания этой статьи такие проверки доступны только в ночной сборке Rust. Посмотрите [документацию о проверках производительности](https://doc.rust-lang.org/unstable-book/library-features/test.html), чтобы узнать больше. +Исчисление `0 measured` предназначена для проверок производительности. На мгновение написания этой статьи такие проверки доступны только в ночной сборке Ржавчина. Посмотрите [пособие о проверках производительности](https://doc.rust-lang.org/unstable-book/library-features/test.html), чтобы узнать больше. -Следующая часть вывода проверок начинается с `Doc-tests adder` - это сведения о проверках в документации. У нас пока нет проверок документации, но Ржавчина может собирать любые примеры кода, которые находятся в API документации. Такая возможность помогает поддерживать документацию и код в согласованном состоянии. Мы поговорим о написании проверок документации в разделы ["Примечания документации как проверки"] Главы 14. Пока просто пренебрегаем часть `Doc-tests` вывода. +Следующая часть вывода проверок начинается с `Doc-tests adder` - это сведения о проверках в пособия. У нас пока нет проверок пособия, но Ржавчина может собирать любые примеры рукописи, которые находятся в API пособия. Такая возможность помогает поддерживать пособие и рукопись в согласованном состоянии. Мы поговорим о написании проверок пособия в разделы ["Примечания пособия как проверки"] Главы 14. Пока просто пренебрегаем часть `Doc-tests` вывода. Давайте начнём настраивать проверка в соответствии с нашими собственными потребностями. Сначала поменяем название нашего проверки `it_works` на `exploration`, вот так: @@ -78,7 +78,7 @@ Cargo собрал и выполнил проверку. Мы видим стр {{#include ../listings/ch11-writing-automated-tests/no-listing-01-changing-test-name/output.txt}} ``` -Добавим ещё один проверка, но в этот раз целенаправленно сделаем так, чтобы этот новый проверка не отработал! Проверка терпит неудачу, когда что-то паникует в проверяемой функции. Каждый проверка запускается в новом потоке и когда главный поток видит, что проверочный поток упал, то помечает проверка как завершившийся со сбоем. Мы говорили о простейшем способе вызвать панику в главе 9, используя для этого известный макрос `panic!`. Введём код проверку-функции `another`, как в файле *src/lib.rs* из приложения 11-3. +Добавим ещё один проверка, но в этот раз целенаправленно сделаем так, чтобы этот новый проверка не отработал! Проверка терпит неудачу, когда что-то вызывает сбой в проверяемой функции. Каждый проверка запускается в новом потоке и когда главный поток видит, что проверочный поток упал, то помечает проверка как завершившийся со сбоем. Мы говорили о простейшем способе вызвать сбой в главе 9, используя для этого известный макрос `panic!`. Введём рукопись проверку-функции `another`, как в файле *src/lib.rs* из приложения 11-3. Файл: src/lib.rs @@ -100,13 +100,13 @@ Cargo собрал и выполнил проверку. Мы видим стр Итоговая строка отображается в конце: общий итог нашего проверки `FAILED`. У нас один проверка пройден и один проверка завершён со сбоем. -Теперь, когда вы увидели, как выглядят итоги проверки при разных сценариях, давайте рассмотрим другие макросы полезные в проверках, кроме `panic!`. +Теперь, когда вы увидели, как выглядят итоги проверки при разных задумках, давайте рассмотрим другие макросы полезные в проверках, кроме `panic!`. ### Проверка итогов с помощью макроса `assert!` -Макрос `assert!` доступен из встроенной библиотеки и является удобным, когда вы хотите проверить что некоторое условие в проверке вычисляется в значение `true`. Мы передаём в макрос `assert!` переменная, который вычисляется в логическое значение. Если оно `true`, то ничего не происходит и проверка считается пройденным. Если же значение вычисляется в `false`, то макрос `assert!` вызывает макрос `panic!`, чтобы вызвать сбой проверки. Использование макроса `assert!` помогает проверить, что код исполняется как ожидалось. +Макрос `assert!` доступен из встроенной библиотеки и является удобным, когда вы хотите проверить что некоторое условие в проверке вычисляется в значение `true`. Мы передаём в макрос `assert!` переменная, который вычисляется в разумное значение. Если оно `true`, то ничего не происходит и проверка считается пройденным. Если же значение вычисляется в `false`, то макрос `assert!` вызывает макрос `panic!`, чтобы вызвать сбой проверки. Использование макроса `assert!` помогает проверить, что рукопись исполняется как ожидалось. -В главе 5, приложении 5-15, мы использовали устройство `Rectangle` и способ `can_hold`, который повторён в приложении 11-5. Давайте поместим этот код в файл *src/lib.rs* и напишем несколько проверок для него используя макрос `assert!`. +В главе 5, приложении 5-15, мы использовали устройство `Rectangle` и способ `can_hold`, который повторён в приложении 11-5. Давайте поместим этот рукопись в файл *src/lib.rs* и напишем несколько проверок для него используя макрос `assert!`. Файл: src/lib.rs @@ -116,7 +116,7 @@ Cargo собрал и выполнил проверку. Мы видим стр Приложение 11-5: Использование устройства Rectangle и её способа can_hold из главы 5 -Способ `can_hold` возвращает логическое значение, что означает, что он является наилучшим исходом использования в макросе `assert!`. В приложении 11-6 мы пишем проверка, который выполняет способ `can_hold` путём создания образца `Rectangle` шириной 8 и высотой 7 и убеждаемся, что он может содержать другой образец `Rectangle` имеющий ширину 5 и высоту 1. +Способ `can_hold` возвращает разумное значение, что означает, что он является наилучшим исходом использования в макросе `assert!`. В приложении 11-6 мы пишем проверка, который выполняет способ `can_hold` путём создания образца `Rectangle` шириной 8 и высотой 7 и убеждаемся, что он может содержать другой образец `Rectangle` имеющий ширину 5 и высоту 1. Файл: src/lib.rs @@ -126,7 +126,7 @@ Cargo собрал и выполнил проверку. Мы видим стр Приложение 11-6: Проверка для способа can_hold, который проверяет что больший прямоугольник действительно может содержать меньший -Также, в звене `tests` обратите внимание на новую добавленную строку `use super::*;`. Звено `tests` является обычным и подчиняется тем же правилам видимости, которые мы обсуждали в главе 7 ["Пути для ссылки на элементы внутри дерева звена"]. Так как этот звено `tests` является внутренним, нужно подключить проверяемый код из внешнего звена в область видимости внутреннего звена с проверкими. Для этого используется вездесущеее подключение, так что все что определено во внешнем звене становится доступным внутри `tests` звена. +Также, в звене `tests` обратите внимание на новую добавленную строку `use super::*;`. Звено `tests` является обычным и подчиняется тем же правилам видимости, которые мы обсуждали в главе 7 ["Пути для ссылки на элементы внутри дерева звена"]. Так как этот звено `tests` является внутренним, нужно подключить проверяемый рукопись из внешнего звена в область видимости внутреннего звена с проверкими. Для этого используется вездесущеее подключение, так что все что определено во внешнем звене становится доступным внутри `tests` звена. Мы назвали наш проверка `larger_can_hold_smaller` и создали два нужных образца `Rectangle`. Затем вызвали макрос `assert!` и передали итог вызова `larger.can_hold(&smaller)` в него. Это выражение должно возвращать `true`, поэтому наш проверка должен пройти. Давайте выясним! @@ -148,7 +148,7 @@ Cargo собрал и выполнил проверку. Мы видим стр {{#include ../listings/ch11-writing-automated-tests/no-listing-02-adding-another-rectangle-test/output.txt}} ``` -Два проверки работают. Теперь проверим, как отреагируют проверки, если мы добавим ошибку в код. Давайте изменим выполнение способа `can_hold` заменив одно из логических выражений знак сравнения с "больше чем" на противоположный "меньше чем" при сравнении ширины: +Два проверки работают. Теперь проверим, как отреагируют проверки, если мы добавим ошибку в рукопись. Давайте изменим выполнение способа `can_hold` заменив одно из разумных выражений знак сравнения с "больше чем" на противоположный "меньше чем" при сравнении ширины: ```rust,not_desired_behavior,noplayground {{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-03-introducing-a-bug/src/lib.rs:here}} @@ -164,7 +164,7 @@ Cargo собрал и выполнил проверку. Мы видим стр ### Проверка на равенство с помощью макросов `assert_eq!` и `assert_ne!` -Общим способом проверки возможности является использование сравнения итога проверяемого кода и ожидаемого значения, чтобы убедиться в их равенстве. Для этого можно использовать макрос `assert!`, передавая ему выражение с использованием оператора `==`. Важно также знать, что кроме этого обычная библиотека предлагает пару макросов `assert_eq!` и `assert_ne!`, чтобы сделать проверка более удобным. Эти макросы сравнивают два переменной на равенство или неравенство соответственно. Макросы также печатают два значения входных свойств, если проверка завершился ошибкой, что позволяет легче увидеть *почему* проверка ошибочен. Противоположно этому, макрос `assert!` может только отобразить, что он вычислил значение `false` для выражения `==`, но не значения, которые привели к итогу `false`. +Общим способом проверки возможности является использование сравнения итога проверяемого рукописи и ожидаемого значения, чтобы убедиться в их равенстве. Для этого можно использовать макрос `assert!`, передавая ему выражение с использованием приказчика `==`. Важно также знать, что кроме этого обычная библиотека предлагает пару макросов `assert_eq!` и `assert_ne!`, чтобы сделать проверка более удобным. Эти макросы сравнивают два переменной на равенство или неравенство соответственно. Макросы также печатают два значения входных свойств, если проверка завершился ошибкой, что позволяет легче увидеть *почему* проверка ошибочен. Противоположно этому, макрос `assert!` может только отобразить, что он вычислил значение `false` для выражения `==`, но не значения, которые привели к итогу `false`. В приложении 11-7, мы напишем функцию `add_two`, которая прибавляет к входному свойству `2` и возвращает значение. Затем, проверим эту функцию с помощью макроса `assert_eq!`: @@ -184,7 +184,7 @@ Cargo собрал и выполнил проверку. Мы видим стр Первый переменная, который мы передаём в макрос `assert_eq!` число `4` чей итог вызова равен `add_two(2)` . Строка для этого проверки - `test tests::it_adds_two ... ok` , а текст `ok` означает, что наш проверка пройден! -Давайте введём ошибку в код, чтобы увидеть, как она выглядит, когда проверка, который использует `assert_eq!` завершается ошибкой. Измените выполнение функции `add_two`, чтобы добавлять `3`: +Давайте введём ошибку в рукопись, чтобы увидеть, как она выглядит, когда проверка, который использует `assert_eq!` завершается ошибкой. Измените выполнение функции `add_two`, чтобы добавлять `3`: ```rust,not_desired_behavior,noplayground {{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-04-bug-in-add-two/src/lib.rs:here}} @@ -198,15 +198,15 @@ Cargo собрал и выполнил проверку. Мы видим стр Наш проверка нашёл ошибку! Проверка `it_adds_two` не выполнился, отображается сообщение `assertion failed: `(left == right)`` и показывает, что `left` было `4`, а `right` было `5`. Это сообщение полезно и помогает начать отладку: это означает `left` переменная `assert_eq!` имел значение `4`, но right переменная для вызова add_two(2) был со значением 5. -Обратите внимание, что в некоторых языках (таких как Java) в библиотеках кода для проверки принято именовать входные свойства проверочных функций как "ожидаемое" (`expected`) и "действительное" (`actual`). В Ржавчина приняты следующие обозначения `left` и `right` соответственно, а порядок в котором определяются ожидаемое значение и производимое проверяемым кодом значение не имеют значения. Мы могли бы написать выражение в проверке как `assert_eq!(add_two(2), 4)`, что приведёт к отображаемому сообщению об ошибке `assertion failed: `(left == right)``, слева `left` было бы 5, а справа right было бы 4. +Обратите внимание, что в некоторых языках (таких как Java) в библиотеках рукописи для проверки принято именовать входные свойства проверочных функций как "ожидаемое" (`expected`) и "действительное" (`actual`). В Ржавчине приняты следующие обозначения `left` и `right` соответственно, а порядок в котором определяются ожидаемое значение и производимое проверяемым рукописью значение не имеют значения. Мы могли бы написать выражение в проверке как `assert_eq!(add_two(2), 4)`, что приведёт к отображаемому сообщению об ошибке `assertion failed: `(left == right)``, слева `left` было бы 5, а справа right было бы 4. Макрос `assert_ne!` сработает успешно, если входные свойства не равны друг другу и завершится с ошибкой, если значения равны. Этот макрос наиболее полезен в тех случаях, когда мы не знаем заранее, каким значение *будет*, но знаем точно, каким оно *не может* быть. К примеру, если проверяется функция, которая обязательно изменяет входные данные определённым образом, но способ изменения входного свойства зависит от дня недели, в который запускаются проверки, что лучший способ проверить правильность работы такой функции - это сравнить и убедиться, что выходное значение функции не должно быть равным входному значению. -В своей работе макросы `assert_eq!` и `assert_ne!` неявным образом используют операторы `==` и `!=` соответственно. Когда проверка не срабатывает, макросы печатают значения переменных с помощью отладочного изменения и это означает, что значения сравниваемых переменных должны выполнить особенности `PartialEq` и `Debug`. Все простые и большая часть видов встроенной библиотеки Ржавчина выполняют эти особенности. Для устройств и перечислений, которые вы выполняете сами будет необходимо выполнить особенность `PartialEq` для сравнения значений на равенство или неравенство. Для печати отладочной сведений в виде сообщений в строку вывода окне вывода необходимо выполнить особенность `Debug`. Так как оба особенности являются выводимыми особенностями, как упоминалось в приложении 5-12 главы 5, то эти особенности можно выполнить добавив изложение `#[derive(PartialEq, Debug)]` к определению устройства или перечисления. Смотрите больше подробностей в Appendix C ["Выводимые особенности"] про эти и другие выводимые особенности. +В своей работе макросы `assert_eq!` и `assert_ne!` неявным образом используют приказчики `==` и `!=` соответственно. Когда проверка не срабатывает, макросы печатают значения переменных с помощью отладочного изменения и это означает, что значения сравниваемых переменных должны выполнить особенности `PartialEq` и `Debug`. Все простые и большая часть видов встроенной библиотеки Ржавчина выполняют эти особенности. Для устройств и перечислений, которые вы выполняете сами будет необходимо выполнить особенность `PartialEq` для сравнения значений на равенство или неравенство. Для печати отладочной сведений в виде сообщений в строку вывода окне вывода необходимо выполнить особенность `Debug`. Так как оба особенности являются выводимыми особенностями, как упоминалось в приложении 5-12 главы 5, то эти особенности можно выполнить добавив изложение `#[derive(PartialEq, Debug)]` к определению устройства или перечисления. Смотрите больше подробностей в Appendix C ["Выводимые особенности"] про эти и другие выводимые особенности. ### Создание сообщений об ошибках -Также можно добавить пользовательское сообщение как дополнительный переменная макросов для печати в сообщении об ошибке проверки `assert!`, `assert_eq!`, и `assert_ne!`. Любые переменные, указанные после обязательных переменных, далее передаются в макрос `format!` (он обсуждается в разделе ["Сцепление с помощью оператора `+` или макроса format!"](ch08-02-strings.html#concatenation-with-the--operator-or-the-format-macro)), так что вы можете передать измененную строку, которая содержит `{}` для заполнителей и значения, заменяющие эти заполнители. Пользовательские сообщения полезны для пояснения того, что означает утверждение (assertion); когда проверка завершается неудачей, у вас будет лучшее представление о том, в чем неполадка с кодом. +Также можно добавить пользовательское сообщение как дополнительный переменная макросов для печати в сообщении об ошибке проверки `assert!`, `assert_eq!`, и `assert_ne!`. Любые переменные, указанные после обязательных переменных, далее передаются в макрос `format!` (он обсуждается в разделе ["Сцепление с помощью приказчика `+` или макроса format!"](ch08-02-strings.html#concatenation-with-the--operator-or-the-format-macro)), так что вы можете передать измененную строку, которая содержит `{}` для заполнителей и значения, заменяющие эти заполнители. Пользовательские сообщения полезны для пояснения того, что означает утверждение (assertion); когда проверка завершается неудачей, у вас будет лучшее представление о том, в чем неполадка с рукописью. Например, есть функция, которая приветствует человека по имени и мы хотим проверять эту функцию. Мы хотим чтобы передаваемое ей имя выводилось в окно вывода: @@ -218,7 +218,7 @@ Cargo собрал и выполнил проверку. Мы видим стр Требования к этой программе ещё не были согласованы и мы вполне уверены, что текст `Hello` в начале приветствия ещё изменится. Мы решили, что не хотим обновлять проверка при изменении требований, поэтому вместо проверки на точное равенство со значением возвращённым из `greeting`, мы просто будем проверять, что вывод содержит текст из входного свойства. -Давайте внесём ошибку в этот код, изменив `greeting` так, чтобы оно не включало `name` и увидим, как выглядит сбой этого проверки: +Давайте внесём ошибку в этот рукопись, изменив `greeting` так, чтобы оно не включало `name` и увидим, как выглядит сбой этого проверки: ```rust,not_desired_behavior,noplayground {{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-06-greeter-with-bug/src/lib.rs:here}} @@ -230,7 +230,7 @@ Cargo собрал и выполнил проверку. Мы видим стр {{#include ../listings/ch11-writing-automated-tests/no-listing-06-greeter-with-bug/output.txt}} ``` -Сообщение содержит лишь сведения о том что сравнение не было успешным и в какой строке это произошло. В данном случае, более полезный текст сообщения был бы, если бы также выводилось значение из функции `greeting`. Изменим проверяющую функцию так, чтобы выводились пользовательское сообщение измененное строкой с заменителем и действительными данными из кода `greeting`: +Сообщение содержит лишь сведения о том что сравнение не было успешным и в какой строке это произошло. В данном случае, более полезный текст сообщения был бы, если бы также выводилось значение из функции `greeting`. Изменим проверяющую функцию так, чтобы выводились пользовательское сообщение измененное строкой с заменителем и действительными данными из рукописи `greeting`: ```rust,ignore {{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-07-custom-failure-message/src/lib.rs:here}} @@ -246,7 +246,7 @@ Cargo собрал и выполнил проверку. Мы видим стр ### Проверка с помощью макроса `should_panic` -В дополнение к проверке того, что наш код возвращает правильные, ожидаемые значения, важным также является проверить, что наш код обрабатывает ошибки, которые мы ожидаем. Например, рассмотрим вид `Guess` который мы создали в главе 9, приложения 9-10. Другой код, который использует `Guess` зависит от заверения того, что `Guess` образцы будут содержать значения только от 1 до 100. Мы можем написать проверка, который заверяет, что попытка создать образец `Guess` со значением вне этого ряда вызывает панику. +В дополнение к проверке того, что нашу рукопись возвращает правильные, ожидаемые значения, важным также является проверить, что нашу рукопись обрабатывает ошибки, которые мы ожидаем. Например, рассмотрим вид `Guess` который мы создали в главе 9, приложения 9-10. Другой рукопись, который использует `Guess` зависит от заверения того, что `Guess` образцы будут содержать значения только от 1 до 100. Мы можем написать проверка, который заверяет, что попытка создать образец `Guess` со значением вне этого ряда вызывает сбой. Выполняем это с помощью другого свойства проверку-функции `#[should_panic]`. Этот свойство сообщает системе проверки, что проверка проходит, когда способ порождает ошибку. Если ошибка не порождается - проверка считается не пройденным. @@ -266,7 +266,7 @@ Cargo собрал и выполнил проверку. Мы видим стр {{#include ../listings/ch11-writing-automated-tests/listing-11-08/output.txt}} ``` -Выглядит хорошо! Теперь давайте внесём ошибку в наш код, убрав условие о том, что функция `new` будет паниковать если значение больше 100: +Выглядит хорошо! Теперь давайте внесём ошибку в нашу рукопись, убрав условие о том, что функция `new` будет вызвать сбой если значение больше 100: ```rust,not_desired_behavior,noplayground {{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-08-guess-with-bug/src/lib.rs:here}} @@ -278,9 +278,9 @@ Cargo собрал и выполнил проверку. Мы видим стр {{#include ../listings/ch11-writing-automated-tests/no-listing-08-guess-with-bug/output.txt}} ``` -Мы получаем не очень полезное сообщение в этом случае, но когда мы смотрим на проверяющую функцию, мы видим, что она `#[should_panic]`. Со сбоеме выполнение, которое мы получили означает, что код в проверяющей функции не вызвал паники. +Мы получаем не очень полезное сообщение в этом случае, но когда мы смотрим на проверяющую функцию, мы видим, что она `#[should_panic]`. Со сбоеме выполнение, которое мы получили означает, что рукопись в проверяющей функции не вызвал сбоя. -Проверки, которые используют `should_panic` могут быть неточными, потому что они только указывают, что код вызвал панику. Проверка с свойством `should_panic` пройдёт, даже если проверка паникует по причине, отличной от той, которую мы ожидали. Чтобы сделать проверки с `should_panic` более точными, мы можем добавить необязательный свойство `expected` для свойства `should_panic`. Такая подробностизация проверки позволит удостовериться, что сообщение об ошибке содержит предоставленный текст. Например, рассмотрим измененный код для `Guess` в приложении 11-9, где `new` функция паникует с различными сообщениями в зависимости от того, является ли значение слишком маленьким или слишком большим. +Проверки, которые используют `should_panic` могут быть неточными, потому что они только указывают, что рукопись вызвал сбой. Проверка с свойством `should_panic` пройдёт, даже если проверка вызывает сбой по причине, отличной от той, которую мы ожидали. Чтобы сделать проверки с `should_panic` более точными, мы можем добавить необязательный свойство `expected` для свойства `should_panic`. Такая подробностизация проверки позволит удостовериться, что сообщение об ошибке содержит предоставленный текст. Например, рассмотрим измененный рукопись для `Guess` в приложении 11-9, где `new` функция вызывает сбой с различными сообщениями в зависимости от того, является ли значение слишком маленьким или слишком большим. Файл: src/lib.rs @@ -290,9 +290,9 @@ Cargo собрал и выполнил проверку. Мы видим стр Приложение 11-9: Проверка panic! на наличие в его сообщении указанной подстроки -Этот проверка пройдёт, потому что значение, которое мы помеисполнения для `should_panic` в свойство свойства `expected` является подстрокой сообщения, с которым функция `Guess::new` вызывает панику. Мы могли бы указать полное, ожидаемое сообщение для паники, в этом случае это будет `Guess value must be less than or equal to 100, got 200`. То что вы выберите для указания как ожидаемого свойства у `should_panic` зависит от того, какая часть сообщения о панике неповторима или динамична, насколько вы хотите, чтобы ваш проверка был точным. В этом случае достаточно подстроки из сообщения паники, чтобы обеспечить выполнение кода в проверочной функции `else if value > 100` . +Этот проверка пройдёт, потому что значение, которое мы помеисполнения для `should_panic` в свойство свойства `expected` является подстрокой сообщения, с которым функция `Guess::new` вызывает сбой. Мы могли бы указать полное, ожидаемое сообщение для сбоя, в этом случае это будет `Guess value must be less than or equal to 100, got 200`. То что вы выберите для указания как ожидаемого свойства у `should_panic` зависит от того, какая часть сообщения о сбое неповторима или динамична, насколько вы хотите, чтобы ваш проверка был точным. В этом случае достаточно подстроки из сообщения сбоя, чтобы обеспечить выполнение рукописи в проверочной функции `else if value > 100` . -Чтобы увидеть, что происходит, когда проверка `should_panic` неуспешно завершается с сообщением `expected`, давайте снова внесём ошибку в наш код, поменяв местами `if value < 1` и `else if value > 100` блоки: +Чтобы увидеть, что происходит, когда проверка `should_panic` неуспешно завершается с сообщением `expected`, давайте снова внесём ошибку в нашу рукопись, поменяв местами `if value < 1` и `else if value > 100` разделы: ```rust,ignore,not_desired_behavior {{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-09-guess-with-panic-msg-bug/src/lib.rs:here}} @@ -304,11 +304,11 @@ Cargo собрал и выполнил проверку. Мы видим стр {{#include ../listings/ch11-writing-automated-tests/no-listing-09-guess-with-panic-msg-bug/output.txt}} ``` -Сообщение об ошибке указывает, что этот проверка действительно вызвал панику, как мы и ожидали, но сообщение о панике не включено ожидаемую строку `'Guess value must be less than or equal to 100'`. Сообщение о панике, которое мы получили в этом случае, было `Guess value must be greater than or equal to 1, got 200.` Теперь мы можем начать выяснение, где ошибка! +Сообщение об ошибке указывает, что этот проверка действительно вызвал сбой, как мы и ожидали, но сообщение о сбое не включено ожидаемую строку `'Guess value must be less than or equal to 100'`. Сообщение о сбое, которое мы получили в этом случае, было `Guess value must be greater than or equal to 1, got 200.` Теперь мы можем начать выяснение, где ошибка! ### Использование `Result` в проверках -Пока что мы написали проверки, которые паникуют, когда терпят неудачу. Мы также можем написать проверки которые используют `Result`! Вот проверка из приложения 11-1, переписанный с использованием `Result` и возвращающий `Err` вместо паники: +Пока что мы написали проверки, которые паникуют, когда терпят неудачу. Мы также можем написать проверки которые используют `Result`! Вот проверка из приложения 11-1, переписанный с использованием `Result` и возвращающий `Err` вместо сбоя: ```rust,noplayground {{#rustdoc_include ../listings/ch11-writing-automated-tests/no-listing-10-result-in-tests/src/lib.rs}} @@ -316,9 +316,9 @@ Cargo собрал и выполнил проверку. Мы видим стр Функция `it_works` теперь имеет возвращаемый вид `Result<(), String>`. В теле функции, вместо вызова макроса `assert_eq!`, мы возвращаем `Ok(())` когда проверка успешно выполнен и `Err` со `String` внутри, когда проверка не проходит. -Написание проверок так, чтобы они возвращали `Result` позволяет использовать оператор "вопросительный знак" в теле проверок, который может быть удобным способом писать проверки, которые должны выполниться не успешно, если какая-либо действие внутри них возвращает исход ошибки `Err`. +Написание проверок так, чтобы они возвращали `Result` позволяет использовать приказчик "вопросительный знак" в теле проверок, который может быть удобным способом писать проверки, которые должны выполниться не успешно, если какая-либо действие внутри них возвращает исход ошибки `Err`. -Вы не можете использовать изложение `#[should_panic]` в проверках, использующих `Result`. Чтобы утверждать, что действие возвращает исход `Err`, *не* используйте оператор вопросительного знака для значения `Result`. Вместо этого используйте `assert!(value.is_err())`. +Вы не можете использовать изложение `#[should_panic]` в проверках, использующих `Result`. Чтобы утверждать, что действие возвращает исход `Err`, *не* используйте приказчик вопросительного знака для значения `Result`. Вместо этого используйте `assert!(value.is_err())`. Теперь, когда вы знаете несколько способов написания проверок, давайте взглянем на то, что происходит при запуске проверок и исследуем разные возможности используемые с приказом `cargo test`. @@ -326,5 +326,5 @@ Cargo собрал и выполнил проверку. Мы видим стр [“Пренебрежение некоторых проверок, если их целенаправленно не запрашивать”]: ch11-02-running-tests.html#ignoring-some-tests-unless-specifically-requested [“Запуск подмножества проверок по имени”]: ch11-02-running-tests.html#running-a-subset-of-tests-by-name ["Выводимые особенности"]: appendix-03-derivable-traits.html -["Примечания документации как проверки"]: ch14-02-publishing-to-crates-io.html#documentation-comments-as-tests +["Примечания пособия как проверки"]: ch14-02-publishing-to-crates-io.html#documentation-comments-as-tests ["Пути для ссылки на элементы внутри дерева звена"]: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html \ No newline at end of file diff --git a/rustbook-ru/src/ch11-02-running-tests.md b/rustbook-ru/src/ch11-02-running-tests.md index d3e2fbcec..a56b40b4a 100644 --- a/rustbook-ru/src/ch11-02-running-tests.md +++ b/rustbook-ru/src/ch11-02-running-tests.md @@ -1,6 +1,6 @@ ## Управление хода выполнения проверок -Подобно тому, как `cargo run` выполняет сборку вашего кода, а затем запускает полученный двоичный файл, `cargo test` собирает ваш код в режиме проверки и запускает полученный двоичный файл с проверкими. Двоичный файл, создаваемый `cargo test`, по умолчанию запускает все проверки одновременно и перехватывает вывод, порождаемый во время выполнения проверок, предотвращая их вывод на экран для облегчения чтения вывода, относящегося к итогам проверки. Однако вы можете указать свойства приказной строки, чтобы изменить это поведение по умолчанию. +Подобно тому, как `cargo run` выполняет сборку вашей рукописи, а затем запускает полученный двоичный файл, `cargo test` собирает ваша рукопись в режиме проверки и запускает полученный двоичный файл с проверкими. Двоичный файл, создаваемый `cargo test`, по умолчанию запускает все проверки одновременно и перехватывает вывод, порождаемый во время выполнения проверок, предотвращая их вывод на экран для облегчения чтения вывода, относящегося к итогам проверки. Однако вы можете указать свойства приказной строки, чтобы изменить это поведение по умолчанию. Часть свойств приказной строки передаётся в `cargo test`, а часть - в итоговый двоичный файл с проверкими. Чтобы разделить эти два вида переменных, нужно сначала указать переменные, которые идут в `cargo test`, затем использовать разделитель `--`, а потом те, которые попадут в двоичный файл проверки. Выполнение `cargo test --help` выводит возможности, которые вы можете использовать с `cargo test`, а выполнение `cargo test -- --help` выводит возможности, которые вы можете использовать за разделителем. @@ -8,9 +8,9 @@ Когда вы запускаете несколько проверок, по умолчанию они выполняются одновременно с использованием потоков, что означает, что они завершатся быстрее, и вы быстрее получите итоги. Поскольку проверки выполняются одновременно, вы должны убедиться, что ваши проверки не зависят друг от друга или от какого-либо общего состояния, включая общее окружение, например, текущий рабочий папка или переменные окружения. -Например, допустим, каждый из ваших проверок запускает код, который создаёт файл на диске с именем *test-output.txt* и записывает некоторые данные в этот файл. Затем каждый проверка считывает данные из этого файла и утверждает, что файл содержит определённое значение, которое в каждом проверке разное. Поскольку все проверки выполняются одновременно, один из проверок может перезаписать файл в промежутке между записью и чтением файла другим проверкой. Тогда второй проверка потерпит неудачу, но не потому, что код неверен, а потому, что эти проверки мешали друг другу при одновременном выполнении. Одно из решений - убедиться, что каждый проверка пишет в свой отдельный файл; другое решение - запускать проверки по одному. +Например, допустим, каждый из ваших проверок запускает рукопись, который создаёт файл на диске с именем *test-output.txt* и записывает некоторые данные в этот файл. Затем каждый проверка считывает данные из этого файла и утверждает, что файл содержит определённое значение, которое в каждом проверке разное. Поскольку все проверки выполняются одновременно, один из проверок может перезаписать файл в промежутке между записью и чтением файла другим проверкой. Тогда второй проверка потерпит неудачу, но не потому, что рукопись неверен, а потому, что эти проверки мешали друг другу при одновременном выполнении. Одно из решений - убедиться, что каждый проверка пишет в свой отдельный файл; другое решение - запускать проверки по одному. -Если вы не хотите запускать проверки одновременно или хотите более подробный управление над количеством используемых потоков, можно установить флаг `--test-threads` и то количество потоков, которое вы хотите использовать для проверки. Взгляните на следующий пример: +Если вы не хотите запускать проверки одновременно или хотите более подробный управление над количеством используемых потоков, можно установить клеймо `--test-threads` и то количество потоков, которое вы хотите использовать для проверки. Взгляните на следующий пример: ```console $ cargo test -- --test-threads=1 @@ -20,9 +20,9 @@ $ cargo test -- --test-threads=1 ### Отображение итогов работы функции -По умолчанию, если проверка пройден, система управления запуска проверок блокирует вывод на печать, т.е. если вы вызовете макрос `println!` внутри кода проверки и проверка будет пройден, вы не увидите вывода на окно вывода итогов вызова `println!`. Если же проверка не был пройден, все несущие сведения сообщения, а также описание ошибки будут выведены на окно вывода. +По умолчанию, если проверка пройден, система управления запуска проверок запрещает вывод на печать, т.е. если вы вызовете макрос `println!` внутри рукописи проверки и проверка будет пройден, вы не увидите вывода на окно вывода итогов вызова `println!`. Если же проверка не был пройден, все несущие сведения сообщения, а также описание ошибки будут выведены на окно вывода. -Например, в коде (11-10) функция выводит значение свойства с поясняющим текстовым сообщением, а также возвращает целочисленное постоянных значенийное значение 10. Далее следует проверка, который имеет правильный входной свойство и проверка, который имеет ошибочный входной свойство: +Например, в рукописи (11-10) функция выводит значение свойства с поясняющим текстовым сообщением, а также возвращает целочисленное постоянных значенийное значение 10. Далее следует проверка, который имеет правильный входной свойство и проверка, который имеет ошибочный входной свойство: Файл: src/lib.rs @@ -40,13 +40,13 @@ $ cargo test -- --test-threads=1 Обратите внимание, что нигде в этом выводе мы не видим сообщения `I got the value 4` , которое печатается при выполнении пройденного проверки. Этот вывод был записан. Итог неудачного проверки, `I got the value 8` , появляется в разделе итоговых итогов проверки, который также показывает причину неудачного проверки. -Если мы хотим видеть напечатанные итоги прохождения проверок, мы можем сказать Rust, чтобы он также показывал итоги успешных проверок с помощью `--show-output`. +Если мы хотим видеть напечатанные итоги прохождения проверок, мы можем сказать Ржавчина, чтобы он также показывал итоги успешных проверок с помощью `--show-output`. ```console $ cargo test -- --show-output ``` -Когда мы снова запускаем проверки из Приложения 11-10 с флагом `--show-output` , мы видим следующий итог: +Когда мы снова запускаем проверки из Приложения 11-10 с клеймом `--show-output` , мы видим следующий итог: ```console {{#include ../listings/ch11-writing-automated-tests/output-only-01-show-output/output.txt}} diff --git a/rustbook-ru/src/ch11-03-test-organization.md b/rustbook-ru/src/ch11-03-test-organization.md index 29878713a..5f2657ef2 100644 --- a/rustbook-ru/src/ch11-03-test-organization.md +++ b/rustbook-ru/src/ch11-03-test-organization.md @@ -1,18 +1,18 @@ ## Создание проверок -Как упоминалось в начале главы, проверка является сложной пунктом и разные люди используют разную совокупность понятий и устройство. Сообщество Ржавчина думает о проверках с точки зрения двух основных разрядов: *состоящие из звеньев проверки* и *встроенные проверки*. Состоящие из звеньев проверки это небольшие и более сосредоточенные на проверке одного звена в отдельности или могут проверяться закрытые внешние оболочки. Встраиваемые проверки являются полностью внешними по отношению к вашей библиотеке и используют код библиотеки так же, как любой другой внешний код, используя только общедоступные внешние оболочки и возможно выполняя проверка нескольких звеньев в одном проверке. +Как упоминалось в начале главы, проверка является сложной пунктом и разные люди используют разную совокупность понятий и устройство. Сообщество Ржавчина думает о проверках с точки зрения двух основных разрядов: *состоящие из звеньев проверки* и *встроенные проверки*. Состоящие из звеньев проверки это небольшие и более сосредоточенные на проверке одного звена в отдельности или могут проверяться закрытые внешние оболочки. Встраиваемые проверки являются полностью внешними по отношению к вашей библиотеке и используют рукопись библиотеки так же, как любой другой внешний рукопись, используя только общедоступные внешние оболочки и возможно выполняя проверка нескольких звеньев в одном проверке. Написание обоих видов проверок важно для обеспечения того, чтобы кусочки вашей библиотеки по отдельности и вместе делали то, что вы ожидаете. ### Состоящие из звеньев проверки -Целью состоящих из звеньев проверок является проверка каждого раздела кода, изолированное от остального возможностей, чтобы можно было быстро понять, что работает неправильно или не так как ожидается. Мы разместим состоящие из звеньев проверки в папке *src*, в каждый проверяемый файл. Но в Ржавчина принято создавать проверяемый звено `tests` и код проверки сохранять в файлы с таким же именем, как составляющие которые предстоит проверять. Также необходимо добавить изложение `cfg(test)` к этому звену. +Целью состоящих из звеньев проверок является проверка каждого раздела рукописи, изолированное от остального возможностей, чтобы можно было быстро понять, что работает неправильно или не так как ожидается. Мы разместим состоящие из звеньев проверки в папке *src*, в каждый проверяемый файл. Но в Ржавчине принято создавать проверяемый звено `tests` и рукопись проверки сохранять в файлы с таким же именем, как составляющие которые предстоит проверять. Также необходимо добавить изложение `cfg(test)` к этому звену. #### Звено проверок и изложение `#[cfg(test)]` -Изложение `#[cfg(test)]` у звена с проверкими указывает Ржавчина собирать и запускать только код проверок, когда выполняется приказ `cargo test`, а не когда запускается `cargo build`. Это уменьшает время сборки, если вы только хотите собрать библиотеку и уменьшить место для результирующих собранных артефактов, потому что проверки не будут включены. Вы увидите что, по причине того, что встроенные проверки помещаются в другой папка им не нужна изложение `#[cfg(test)]`. Тем не менее, так как состоящие из звеньев проверки идут в тех же файлах что и основной код, вы будете использовать `#[cfg(test)]` чтобы указать, что они не должны быть включены в собранный итог. +Изложение `#[cfg(test)]` у звена с проверкими указывает Ржавчина собирать и запускать только рукопись проверок, когда выполняется приказ `cargo test`, а не когда запускается `cargo build`. Это уменьшает время сборки, если вы только хотите собрать библиотеку и уменьшить место для результирующих собранных артефактов, потому что проверки не будут включены. Вы увидите что, по причине того, что встроенные проверки помещаются в другой папка им не нужна изложение `#[cfg(test)]`. Тем не менее, так как состоящие из звеньев проверки идут в тех же файлах что и основной рукопись, вы будете использовать `#[cfg(test)]` чтобы указать, что они не должны быть включены в собранный итог. -Напомним, что когда мы порождали новый дело `adder` в первом разделе этой главы, то Cargo создал для нас код ниже: +Напомним, что когда мы порождали новое дело `adder` в первом разделе этой главы, то Cargo создал для нас рукопись ниже: Файл: src/lib.rs @@ -20,11 +20,11 @@ {{#rustdoc_include ../listings/ch11-writing-automated-tests/listing-11-01/src/lib.rs}} ``` -Этот код является самостоятельно созданным проверочным звеном. Свойство `cfg` предназначен для *настройке* и говорит Rust, что следующий элемент должен быть включён только учитывая определённую возможность настройке. В этом случае возможностью настройке является `test`, который предоставлен в Ржавчина для сборки и запуска текущих проверок. Используя свойство `cfg`, Cargo собирает только проверочный код при активном запуске проверок приказом `cargo test`. Это включает в себя любые вспомогательные функции, которые могут быть в этом звене в дополнение к функциям помеченным `#[test]`. +Этот рукопись является самостоятельно созданным проверочным звеном. Свойство `cfg` предназначен для *настройки* и говорит Ржавчина, что следующий элемент должен быть включён только учитывая определённую возможность настройки. В этом случае возможностью настройки является `test`, который предоставлен в Ржавчине для сборки и запуска текущих проверок. Используя свойство `cfg`, Cargo собирает только проверочный рукопись при постоянном запуске проверок приказом `cargo test`. Это включает в себя любые вспомогательные функции, которые могут быть в этом звене в дополнение к функциям помеченным `#[test]`. #### Проверка закрытых функций (private) -Сообщество программистов не имеет однозначного мнения по поводу проверять или нет закрытые функции. В некоторых языках весьма сложно или даже невозможно проверять такие функции. Независимо от того, какой технологии проверки вы придерживаетесь, в Ржавчина закрытые функции можно проверять. Рассмотрим приложение 11-12 с закрытой функцией `internal_adder`. +Сообщество программистов не имеет однозначного мнения по поводу проверять или нет закрытые функции. В некоторых языках весьма сложно или даже невозможно проверять такие функции. Независимо от того, какой технологии проверки вы придерживаетесь, в Ржавчине закрытые функции можно проверять. Рассмотрим приложение 11-12 с закрытой функцией `internal_adder`. Файл: src/lib.rs @@ -34,17 +34,17 @@ Приложение 11-12: Проверка закрытых функций -Обратите внимание, что функция `internal_adder` не помечена как `pub`. Проверки — это просто Ржавчина код, а звено `tests` — это ещё один звено. Как мы обсуждали в разделе [“Пути для ссылки на элемент в дереве звеньев“], элементы в дочерних звенах могут использовать элементы из своих родительских звеньев. В этом проверке мы помещаем все элементы родительского звена `test` в область видимости с помощью `use super::*` и затем проверка может вызывать `internal_adder`. Если вы считаете, что закрытые функции не нужно проверять, то Ржавчина не заставит вас это сделать. +Обратите внимание, что функция `internal_adder` не помечена как `pub`. Проверки — это просто Ржавчина рукопись, а звено `tests` — это ещё один звено. Как мы обсуждали в разделе [“Пути для ссылки на элемент в дереве звеньев“], элементы в дочерних звеньях могут использовать элементы из своих родительских звеньев. В этом проверке мы помещаем все элементы родительского звена `test` в область видимости с помощью `use super::*` и затем проверка может вызывать `internal_adder`. Если вы считаете, что закрытые функции не нужно проверять, то Ржавчина не заставит вас это сделать. ### Встраиваемые проверки -В Ржавчина встроенные проверки являются полностью внешними по отношению к вашей библиотеке. Они используют вашу библиотеку так же, как любой другой код, что означает, что они могут вызывать только функции, которые являются частью открытого API библиотеки. Их целью является проверка, много ли частей вашей библиотеки работают вместе правильно. У звеньев кода правильно работающих самостоятельно, могут возникнуть сбоев при встраивани, поэтому проверочное покрытие встроенного кода также важно. Для создания встроенных проверок сначала нужен папка *tests* . +В Ржавчине встроенные проверки являются полностью внешними по отношению к вашей библиотеке. Они используют вашу библиотеку так же, как любой другой рукопись, что означает, что они могут вызывать только функции, которые являются частью открытого API библиотеки. Их целью является проверка, много ли частей вашей библиотеки работают вместе правильно. У звеньев рукописи правильно работающих самостоятельно, могут возникнуть сбоев при встраивани, поэтому проверочное покрытие встроенного рукописи также важно. Для создания встроенных проверок сначала нужен папка *tests* . #### Папка *tests* Мы создаём папку *tests* в корневой папке вашего дела, рядом с папкой *src*. Cargo знает, что искать файлы с встроенными проверкими нужно в этой папки. После этого мы можем создать столько проверочных файлов, сколько захотим, и Cargo собирает каждый из файлов в отдельный ящик. -Давайте создадим встроенный проверку. Рядом с кодом из приложения 11-12, который всё ещё в файле *src/lib.rs*, создайте папка *tests*, создайте новый файл с именем *tests/integration_test.rs*. Устройства папок должна выглядеть так: +Давайте создадим встроенный проверку. Рядом с рукописью из приложения 11-12, который всё ещё в файле *src/lib.rs*, создайте папка *tests*, создайте новый файл с именем *tests/integration_test.rs*. Устройства папок должна выглядеть так: ```text adder @@ -56,7 +56,7 @@ adder └── integration_test.rs ``` -Введите код из приложения 11-13 в файл *tests/integration_test.rs* file: +Введите рукопись из приложения 11-13 в файл *tests/integration_test.rs* file: Файл: tests/integration_test.rs @@ -66,15 +66,15 @@ adder Приложение 11-13: Встраиваемая проверка функция из ящика adder -Каждый файл в папке `tests` представляет собой отдельный ящик, поэтому нам нужно подключить нашу библиотеку в область видимости каждого проверочного ящика. По этой причине мы добавляем `use adder` в верхней части кода, что не нужно нам делать в состоящих из звеньев проверках. +Каждый файл в папке `tests` представляет собой отдельный ящик, поэтому нам нужно подключить нашу библиотеку в область видимости каждого проверочного ящика. По этой причине мы добавляем `use adder` в верхней части рукописи, что не нужно нам делать в состоящих из звеньев проверках. -Нам не нужно вносить примечания в код в *tests/integration_test.rs* с помощью `#[cfg(test)]`. Cargo особым образом обрабатывает папка `tests` и собирает файлы в этом папке только тогда, когда мы запускаем приказ `cargo test`. Запустите `cargo test` сейчас: +Нам не нужно вносить примечания в рукопись в *tests/integration_test.rs* с помощью `#[cfg(test)]`. Cargo особым образом обрабатывает папка `tests` и собирает файлы в этом папке только тогда, когда мы запускаем приказ `cargo test`. Запустите `cargo test` сейчас: ```console {{#include ../listings/ch11-writing-automated-tests/listing-11-13/output.txt}} ``` -Выходные данные представлены тремя разделами: состоящие из звеньев проверки, встроенные проверки и проверки документации. Обратите внимание, что если какой-нибудь проверка в одной из разделов не пройдёт, последующие разделы выполняться не будут. Например, если состоящий из звеньев проверка провалился, не будет выведено итогов встроенных и документационных проверок, потому что эти проверки будут выполняться только в том случае, если все состоящие из звеньев проверки завершатся успешно. +Выходные данные представлены тремя разделами: состоящие из звеньев проверки, встроенные проверки и проверки пособия. Обратите внимание, что если какой-нибудь проверка в одной из разделов не пройдёт, последующие разделы выполняться не будут. Например, если состоящий из звеньев проверка провалился, не будет выведено итогов встроенных и документационных проверок, потому что эти проверки будут выполняться только в том случае, если все состоящие из звеньев проверки завершатся успешно. Первый раздел для состоящих из звеньев проверок такой же, как мы видели: одна строка для каждого состоящего из звеньев проверки (один с именем `internal`, который мы добавили в приложении 11-12), а затем сводная строка для состоящих из звеньев проверок. @@ -92,9 +92,9 @@ adder #### Подзвенья в встроенных проверках -По мере добавления большего количества встроенных проверок, можно создать более одного файла в папке *tests*, чтобы легче создавать их; например, вы можете собъединять функции проверки по возможности, которую они проверяют. Как упоминалось ранее, каждый файл в папке *tests* собран как отдельный ящик, что полезно для создания отдельных областей видимости, чтобы более точно создавать видимость то, как конечные пользователи будут использовать ваш ящик. Однако это означает, что файлы в папке *tests* ведут себя не так, как файлы в *src*, как вы узнали в Главе 7 относительно того как разделить код на звенья и файлы. +По мере добавления большего количества встроенных проверок, можно создать более одного файла в папке *tests*, чтобы легче создавать их; например, вы можете объединять функции проверки по возможности, которую они проверяют. Как упоминалось ранее, каждый файл в папке *tests* собран как отдельный ящик, что полезно для создания отдельных областей видимости, чтобы более точно создавать видимость то, как конечные пользователи будут использовать ваш ящик. Однако это означает, что файлы в папке *tests* ведут себя не так, как файлы в *src*, как вы узнали в Главе 7 относительно того как разделить рукопись на звенья и файлы. -Различное поведение файлов в папке *tests* наиболее заметно, когда у вас есть набор вспомогательных функций, которые будут полезны в нескольких встроенных проверочных файлах. Представим, что вы пытаетесь выполнить действия, описанные в разделе [«Разделение звеньев в разные файлы»](ch07-05-separating-modules-into-different-files.html) главы 7, чтобы извлечь их в общий звено. Например, вы создали файл *tests/common.rs* и помеисполнения в него функцию `setup`, содержащую некоторый код, который вы будете вызывать из разных проверочных функций в нескольких проверочных файлах +Различное поведение файлов в папке *tests* наиболее заметно, когда у вас есть набор вспомогательных функций, которые будут полезны в нескольких встроенных проверочных файлах. Представим, что вы пытаетесь выполнить действия, описанные в разделе [«Разделение звеньев в разные файлы»](ch07-05-separating-modules-into-different-files.html) главы 7, чтобы извлечь их в общий звено. Например, вы создали файл *tests/common.rs* и помеисполнения в него функцию `setup`, содержащую некоторый рукопись, который вы будете вызывать из разных проверочных функций в нескольких проверочных файлах Файл: tests/common.rs @@ -108,7 +108,7 @@ adder {{#include ../listings/ch11-writing-automated-tests/no-listing-12-shared-test-code-problem/output.txt}} ``` -Упоминание файла `common` и появление в итогах выполнения проверок сообщения вида `running 0 tests` - это не то, чего мы хотели. Мы только хотели выделить некоторый общий код, который будет использоваться другими файлами встроенных проверок. +Упоминание файла `common` и появление в итогах выполнения проверок сообщения вида `running 0 tests` - это не то, чего мы хотели. Мы только хотели выделить некоторый общий рукопись, который будет использоваться другими файлами встроенных проверок. Чтобы звено `common` больше не появлялся в итогах выполнения проверок, вместо файла *tests/common.rs* мы создадим файл *tests/common/mod.rs*. Директория дела теперь выглядит следующим образом: @@ -123,7 +123,7 @@ adder └── integration_test.rs ``` -Здесь используется более раннее соглашение об именовании файлов, которое Ржавчина также понимает. Мы говорили об этом в разделе [“Иные пути к файлам”] главы 7. Именование файла таким образом говорит, что Ржавчина не должен рассматривать звено `common` как файл встроенных проверок. Когда мы перемещаем код функции setup в файл *tests/common/mod.rs* и удаляем файл *tests/common.rs*, дополнительный раздел больше не будет отображаться в итогах проверок. Файлы в подпапких папки tests не собираются как отдельные ящики или не появляются в итогах выполнения проверок. +Здесь используется более раннее соглашение об именовании файлов, которое Ржавчина также понимает. Мы говорили об этом в разделе [“Иные пути к файлам”] главы 7. Именование файла таким образом говорит, что Ржавчина не должен рассматривать звено `common` как файл встроенных проверок. Когда мы перемещаем рукопись функции setup в файл *tests/common/mod.rs* и удаляем файл *tests/common.rs*, дополнительный раздел больше не будет отображаться в итогах проверок. Файлы в подпапких папки tests не собираются как отдельные ящики или не появляются в итогах выполнения проверок. После того, как мы создали файл *tests/common/mod.rs*, мы можем использовать его в любых файлах встроенных проверок как обычный звено. Вот пример вызова функции `setup` из проверки `it_adds_two` в файле *tests/integration_test.rs*: @@ -139,11 +139,11 @@ adder Если наш дело является двоичным ящиком, который содержит только *src/main.rs* и не содержит *src/lib.rs*, мы не сможем создать встроенные проверки в папке *tests* и подключить функции определённые в файле *src/main.rs* в область видимости с помощью указания `use`. Только библиотечные ящики могут предоставлять функции, которые можно использовать в других ящиках; двоичные ящики предназначены только для самостоятельного запуска. -Это одна из причин, почему дела на Rust, которые порождают исполняемые звенья, обычно имеют простой файл *src/main.rs*, который в свою очередь вызывает логику, которая находится в файле *src/lib.rs*. Используя такую устройство, встроенные проверки *могут* проверить библиотечный ящик, используя оператор `use` для подключения важного возможностей. Если этот важный возможности работает, то и небольшое количество кода в файле *src/main.rs* также будет работать, а значит этот небольшой объём кода не нуждается в проверке. +Это одна из причин, почему дела на Ржавчине, которые порождают исполняемые звенья, обычно имеют простой файл *src/main.rs*, который в свою очередь вызывает ход мыслей, которая находится в файле *src/lib.rs*. Используя такую устройство, встроенные проверки *могут* проверить библиотечный ящик, используя приказчик `use` для подключения важного возможностей. Если этот важный возможности работает, то и небольшое количество рукописи в файле *src/main.rs* также будет работать, а значит этот небольшой объём рукописи не нуждается в проверке. ## Итоги -Средства проверки языка Ржавчина предоставляют способ задать ожидаемое поведение кода, чтобы убедиться, что он всё ещё соответствует вашим ожиданиям даже после внесения изменений. Состоящие из звеньев проверки проверяют различные части библиотеки по отдельности и могут проверять закрытые подробности выполнения. Встраиваемые проверки проверяют, что части библиотеки работают правильно сообща. Эти проверки используют для проверки кода открытый API библиотеки, таким же образом, как его будет использовать внешний код. Хотя система видов Ржавчина и правила владения помогают предотвратить некоторые виды ошибок, проверки по-прежнему важны для уменьшения количества логических ошибок, связанных с поведением вашего кода. +Средства проверки языка Ржавчина предоставляют способ задать ожидаемое поведение рукописи, чтобы убедиться, что он всё ещё соответствует вашим ожиданиям даже после внесения изменений. Состоящие из звеньев проверки проверяют различные части библиотеки по отдельности и могут проверять закрытые подробности выполнения. Встраиваемые проверки проверяют, что части библиотеки работают правильно сообща. Эти проверки используют для проверки рукописи открытый API библиотеки, таким же образом, как его будет использовать внешний рукопись. Хотя система видов Ржавчине и правила владения помогают предотвратить некоторые виды ошибок, проверки по-прежнему важны для уменьшения количества разумных ошибок, связанных с поведением вашей рукописи. Давайте объединим знания, полученные в этой и предыдущей главах, чтобы поработать над делом! diff --git a/rustbook-ru/src/ch12-00-an-io-project.md b/rustbook-ru/src/ch12-00-an-io-project.md index 1f713b34c..30ba7d41c 100644 --- a/rustbook-ru/src/ch12-00-an-io-project.md +++ b/rustbook-ru/src/ch12-00-an-io-project.md @@ -1,6 +1,6 @@ # Дело с вводом/выводом (I/O): создание с окном вывода приложения -В этой главе вы примените многие знания, полученные ранее, а также познакомитесь с ещё неизученными API встроенной библиотеки. Мы создадим окно выводаное приложение, которое будет взаимодействовать с файлом и с окно выводаным вводом / выводом, чтобы применить в некоторых подходах Rust, с которыми вы уже знакомы. +В этой главе вы примените многие знания, полученные ранее, а также познакомитесь с ещё неизученными API встроенной библиотеки. Мы создадим окно выводаное приложение, которое будет взаимодействовать с файлом и с окно выводаным вводом / выводом, чтобы применить в некоторых подходах Ржавчина, с которыми вы уже знакомы. Скорость, безопасность, сборка в один исполняемый файл и кроссплатформенность делают Ржавчина наилучшим языком для создания окно выводаных средств, так что в нашем деле мы создадим свою собственную исполнение обычной утилиты поиска `grep`, что расшифровывается, как "вездесущеее средство поиска и печати" (**g**lobally search a **r**egular **e**xpression and **p**rint). В простейшем случае `grep` используется для поиска в выбранном файле указанного текста. Для этого утилита `grep` получает имя файла и текст в качестве переменных. Далее она читает файл, находит и выводит строки, содержащие искомый текст. @@ -10,7 +10,7 @@ Наш дело `grep` будет использовать ранее изученные подходы: -- Создание кода (используя то, что вы узнали о звенах в [ главе 7]) +- Создание рукописи (используя то, что вы узнали о звеньях в [ главе 7]) - Использование векторов и строк (собрания, [глава 8]) - Обработка ошибок ([Глава 9]) - Использование особенностей и времени жизни там, где это необходимо ([глава 10]) diff --git a/rustbook-ru/src/ch12-01-accepting-command-line-arguments.md b/rustbook-ru/src/ch12-01-accepting-command-line-arguments.md index 5773b41f8..e615c1152 100644 --- a/rustbook-ru/src/ch12-01-accepting-command-line-arguments.md +++ b/rustbook-ru/src/ch12-01-accepting-command-line-arguments.md @@ -1,6 +1,6 @@ ## Принятие переменных приказной строки -Создадим новый дело с окном вывода приложения как обычно с помощью приказы `cargo new`. Мы назовём дело `minigrep`, чтобы различать наше приложение от `grep`, которое возможно уже есть в вашей системе. +Создадим новое дело с окном вывода приложения как обычно с помощью приказы `cargo new`. Мы назовём дело `minigrep`, чтобы различать наше приложение от `grep`, которое возможно уже есть в вашей системе. ```console $ cargo new minigrep @@ -18,9 +18,9 @@ $ cargo run -- searchstring example-filename.txt ### Чтение значений переменных -Чтобы `minigrep` мог воспринимать значения переменных приказной строки, которые мы ему передаём, нам понадобится функция `std::env::args`, входящая в обычную библиотеку Rust. Эта функция возвращает повторитель переменных приказной строки, переданных в `minigrep`. Мы подробно рассмотрим повторители в [главе 13]. Пока вам достаточно знать две вещи об повторителях: повторители порождают серию значений, и мы можем вызвать способ `collect` у повторителя, чтобы создать из него собрание, например вектор, который будет содержать все элементы, произведённые повторителем. +Чтобы `minigrep` мог воспринимать значения переменных приказной строки, которые мы ему передаём, нам понадобится функция `std::env::args`, входящая в обычную библиотеку Ржавчина. Эта функция возвращает повторитель переменных приказной строки, переданных в `minigrep`. Мы подробно рассмотрим повторители в [главе 13]. Пока вам достаточно знать две вещи об повторителях: повторители порождают последовательность значений, и мы можем вызвать способ `collect` у повторителя, чтобы создать из него собрание, например вектор, который будет содержать все элементы, произведённые повторителем. -Код представленный в Приложении 12-1 позволяет вашей программе `minigrep` читать любые переданные ей переменные приказной строки, а затем собирать значения в вектор. +Рукопись представленный в Приложении 12-1 позволяет вашей программе `minigrep` читать любые переданные ей переменные приказной строки, а затем собирать значения в вектор. Файл: src/main.rs @@ -32,13 +32,13 @@ $ cargo run -- searchstring example-filename.txt Сначала мы вводим звено `std::env` в область видимости с помощью указания `use`, чтобы мы могли использовать его функцию `args`. Обратите внимание, что функция `std::env::args` вложена в два уровня звеньев. Как мы обсуждали в [главе 7], в случаях, когда нужная функция оказывается вложенной в более чем один звено, советуется выносить в область видимости родительский звено, а не функцию. Таким образом, мы можем легко использовать другие функции из `std::env`. Это менее двусмысленно, чем добавление `use std::env::args` и последующий вызов функции только с `args`, потому что `args` может быть легко принят за функцию, определённую в текущем звене. -> ### Функция `args` и недействительный Юникод символ (Unicode) +> ### Функция `args` и недействительный Юнирукопись знак (Unicode) > -> Обратите внимание, что `std::env::args` вызовет панику, если какой-либо переменная содержит недопустимый символ Юникода. Если вашей программе необходимо принимать переменные, содержащие недопустимые символы Unicode, используйте вместо этого `std::env::args_os`. Эта функция возвращает повторитель , который выдаёт значения `OsString` вместо значений `String`. Мы решили использовать `std::env::args` здесь для простоты, потому что значения `OsString` отличаются для каждой площадки и с ними сложнее работать, чем со значениями `String`. +> Обратите внимание, что `std::env::args` вызовет сбой, если какой-либо переменная содержит недопустимый знак Юнирукописи. Если вашей программе необходимо принимать переменные, содержащие недопустимые знаки Unicode, используйте вместо этого `std::env::args_os`. Эта функция возвращает повторитель , который выдаёт значения `OsString` вместо значений `String`. Мы решили использовать `std::env::args` здесь для простоты, потому что значения `OsString` отличаются для каждой площадки и с ними сложнее работать, чем со значениями `String`. -В первой строке кода функции `main` мы вызываем `env::args` и сразу используем способ `collect`, чтобы превратить повторитель в вектор содержащий все полученные значения. Мы можем использовать функцию `collect` для создания многих видов собраний, поэтому мы явно определяем вид `args` чтобы указать, что мы хотим вектор строк. Хотя нам очень редко нужно определять виды в Rust, `collect` - это одна из функций, с которой вам часто нужна изложение вида, потому что Ржавчина не может сам вывести какую собрание вы хотите. +В первой строке рукописи функции `main` мы вызываем `env::args` и сразу используем способ `collect`, чтобы превратить повторитель в вектор содержащий все полученные значения. Мы можем использовать функцию `collect` для создания многих видов собраний, поэтому мы явно определяем вид `args` чтобы указать, что мы хотим вектор строк. Хотя нам очень редко нужно определять виды в Ржавчине, `collect` - это одна из функций, с которой вам часто нужна изложение вида, потому что Ржавчина не может сам вывести какую собрание вы хотите. -И в заключение мы печатаем вектор с помощью отладочного макроса. Попробуем запустить код сначала без переменных, а затем с двумя переменнойми: +И в заключение мы печатаем вектор с помощью отладочного макроса. Попробуем запустить рукопись сначала без переменных, а затем с двумя переменнойми: ```console {{#include ../listings/ch12-an-io-project/listing-12-01/output.txt}} @@ -70,7 +70,7 @@ $ cargo run -- searchstring example-filename.txt {{#include ../listings/ch12-an-io-project/listing-12-02/output.txt}} ``` -Отлично, программа работает! Нам нужно чтобы значения переменных были сохранены в правильных переменных. Позже мы добавим обработку ошибок с некоторыми вероятными ошибочными случаейми, например, когда пользователь не предоставляет переменные; сейчас мы пренебрегаем эту случай и поработаем над добавлением возможности чтения файла. +Отлично, программа работает! Нам нужно чтобы значения переменных были сохранены в правильных переменных. Позже мы добавим обработку ошибок с некоторыми вероятными ошибочными случайми, например, когда пользователь не предоставляет переменные; сейчас мы пренебрегаем эту случай и поработаем над добавлением возможности чтения файла. [главе 13]: ch13-00-functional-features.html diff --git a/rustbook-ru/src/ch12-02-reading-a-file.md b/rustbook-ru/src/ch12-02-reading-a-file.md index 11eb97db4..d0621f69c 100644 --- a/rustbook-ru/src/ch12-02-reading-a-file.md +++ b/rustbook-ru/src/ch12-02-reading-a-file.md @@ -10,7 +10,7 @@ Приложение 12-3: Стихотворение Эмили Дикинсон - хороший пример для проверки -Текст на месте, изменените *src/main.rs* и добавьте код для чтения файла, как показано в приложении 12-4. +Текст на месте, изменените *src/main.rs* и добавьте рукопись для чтения файла, как показано в приложении 12-4. Файл: src/main.rs @@ -26,10 +26,10 @@ После этого, мы снова добавили временную указанию `println!` для печати значения `contents` после чтения файла, таким образом мы можем проверить, что программа отрабатывает до этого места. -Давайте запустим этот код с любой строкой в качестве первого переменной приказной строки (потому что мы ещё не выполнили поисковую часть) и файл *poem.txt* как второй переменная: +Давайте запустим этот рукопись с любой строкой в качестве первого переменной приказной строки (потому что мы ещё не выполнили поисковую часть) и файл *poem.txt* как второй переменная: ```console {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-04/output.txt}} ``` -Отлично! Этот код прочитал и затем напечатал содержимое файла. Но у программы есть несколько недостатков. Прежде всего, функция `main` решает слишком много задач: как правило функция понятнее и проще в обслуживании если она воплощает только одну мысль. Другая неполадка заключается в том, что мы не обрабатываем ошибки так хорошо, как могли бы. Пока наша программа небольшая, то эти недостатки не являются большой неполадкой, но по мере роста программы эти недостатки будет всё труднее исправлять. Хорошей опытом является начинать переработка кода на ранней стадии разработки программы, потому что гораздо проще перерабатывать код меньшие объёмы кода. Мы сделаем это далее. +Отлично! Этот рукопись прочитал и затем напечатал содержимое файла. Но у программы есть несколько недостатков. Прежде всего, функция `main` решает слишком много задач: как правило функция понятнее и проще в обслуживании если она воплощает только одну мысль. Другая неполадка заключается в том, что мы не обрабатываем ошибки так хорошо, как могли бы. Пока наша программа небольшая, то эти недостатки не являются большой неполадкой, но по мере роста программы эти недостатки будет всё труднее исправлять. Хорошей опытом является начинать переработка рукописи на ранней этапе разработки программы, потому что гораздо проще перерабатывать рукопись меньшие объёмы рукописи. Мы сделаем это далее. diff --git a/rustbook-ru/src/ch12-03-improving-error-handling-and-modularity.md b/rustbook-ru/src/ch12-03-improving-error-handling-and-modularity.md index c5bd3e62a..087012981 100644 --- a/rustbook-ru/src/ch12-03-improving-error-handling-and-modularity.md +++ b/rustbook-ru/src/ch12-03-improving-error-handling-and-modularity.md @@ -1,35 +1,35 @@ -## Переработка кода для улучшения выделения на звенья и обработки ошибок +## Переработка рукописи для улучшения выделения на звенья и обработки ошибок -Для улучшения программы мы исправим 4 имеющихся сбоев, связанных со устройством программы и тем как обрабатываются вероятные ошибки. Во-первых, функция `main` на данный мгновение решает две задачи: анализирует переменные приказной строки и читает файлы. По мере роста программы количество отдельных задач, которые обрабатывает функция `main`, будет увеличиваться. Поскольку эта функция получает больше обязанностей, то становится все труднее понимать её, труднее проверять и труднее изменять, не сломав одну из её частей. Лучше всего разделить возможность, чтобы каждая функция отвечала за одну задачу. +Для улучшения программы мы исправим 4 имеющихся сбоев, связанных со устройством программы и тем как обрабатываются вероятные ошибки. Во-первых, функция `main` на данный мгновение решает две задачи: рассмотривает переменные приказной строки и читает файлы. По мере роста программы количество отдельных задач, которые обрабатывает функция `main`, будет увеличиваться. Поскольку эта функция получает больше обязанностей, то становится все труднее понимать её, труднее проверять и труднее изменять, не сломав одну из её частей. Лучше всего разделить возможность, чтобы каждая функция отвечала за одну задачу. -Эта неполадка также связана со второй неполадкой: хотя переменные `query` и `file_path` являются переменными настройке нашей программы, переменные вида `contents` используются для выполнения логики программы. Чем длиннее становится `main`, тем больше переменных нам нужно будет добавить в область видимости; чем больше у нас переменных в области видимости, тем сложнее будет отслеживать назначение каждой переменной. Лучше всего собъединять переменные настройке в одну устройство, чтобы сделать их назначение понятным. +Эта неполадка также связана со второй неполадкой: хотя переменные `query` и `file_path` являются переменными настройки нашей программы, переменные вида `contents` используются для выполнения хода мыслей программы. Чем длиннее становится `main`, тем больше переменных нам нужно будет добавить в область видимости; чем больше у нас переменных в области видимости, тем сложнее будет отслеживать назначение каждой переменной. Лучше всего объединять переменные настройки в одну устройство, чтобы сделать их назначение понятным. Третья неполадка заключается в том, что мы используем `expect` для вывода сведений об ошибке при неполадке с чтением файла, но сообщение об ошибке просто выведет текст`Should have been able to read the file`. Чтение файла может не сработать по разным причинам, например: файл не найден или у нас может не быть разрешения на его чтение. Сейчас же, независимо от случаи, мы напечатаем одно и то же сообщение об ошибке, что не даст пользователю никакой сведений! -В-четвёртых, мы используем `expect` неоднократно для обработки различных ошибок и если пользователь запускает нашу программу без указания достаточного количества переменных он получит ошибку `index out of bounds` из Rust, что не совсем понятно описывает неполадку. Было бы лучше, если бы весь код обработки ошибок находился в одном месте, чтобы тем, кто будет поддерживать наш код в дальнейшем, нужно было бы вносить изменения только здесь, если потребуется изменить логику обработки ошибок. Наличие всего кода обработки ошибок в одном месте заверяет, что мы напечатаем сообщения, которые будут иметь смысл для наших конечных пользователей. +В-четвёртых, мы используем `expect` неоднократно для обработки различных ошибок и если пользователь запускает нашу программу без указания достаточного количества переменных он получит ошибку `index out of bounds` из Ржавчина, что не совсем понятно описывает неполадку. Было бы лучше, если бы весь рукопись обработки ошибок находился в одном месте, чтобы тем, кто будет поддерживать нашу рукопись в дальнейшем, нужно было бы вносить изменения только здесь, если потребуется изменить ход мыслей обработки ошибок. Наличие всего рукописи обработки ошибок в одном месте заверяет, что мы напечатаем сообщения, которые будут иметь смысл для наших конечных пользователей. -Давайте решим эти четыре сбоев путём переработки кода нашего дела. +Давайте решим эти четыре сбоев путём переработки рукописи нашего дела. ### Разделение ответственности для двоичных дел -Внутренняя неполадка распределения ответственности за выполнение нескольких задач функции `main` является общей для многих двоичных дел. В итоге Ржавчина сообщество разработало этап для использования в качестве руководства по разделению ответственности двоичной программы, когда код в `main` начинает увеличиваться. Этап имеет следующие шаги: +Внутренняя неполадка распределения ответственности за выполнение нескольких задач функции `main` является общей для многих двоичных дел. В итоге Ржавчина сообщество разработало этап для использования в качестве руководства по разделению ответственности двоичной программы, когда рукопись в `main` начинает увеличиваться. Этап имеет следующие шаги: -- Разделите код программы на два файла *main.rs* и *lib.rs*. Перенесите всю логику работы программы в файл *lib.rs*. -- Пока ваша логика синтаксического анализа приказной строки мала, она может оставаться в файле *main.rs*. -- Когда логика синтаксического анализа приказной строки становится сложной, извлеките её из *main.rs* и переместите в *lib.rs.* +- Разделите рукопись программы на два файла *main.rs* и *lib.rs*. Перенесите всю ход мыслей работы программы в файл *lib.rs*. +- Пока ваша ход мыслей согласно правил написания оценки приказной строки мала, она может оставаться в файле *main.rs*. +- Когда ход мыслей согласно правил написания оценки приказной строки становится сложной, извлеките её из *main.rs* и переместите в *lib.rs.* Полезные обязанности, которые остаются в функции `main` после этого этапа должно быть ограничено следующим: -- Вызов логики разбора приказной строки со значениями переменных -- Настройка любой другой настройке +- Вызов хода мыслей разбора приказной строки со значениями переменных +- Настройка любой другой настройки - Вызов функции `run` в *lib.rs* - Обработка ошибки, если `run` возвращает ошибку -Этот образец о разделении ответственности: *main.rs* занимается запуском программы, а *lib.rs* обрабатывает всю логику задачи. Поскольку нельзя проверить функцию `main` напрямую, то такая устройства позволяет проверить всю логику программы путём перемещения её в функции внутри *lib.rs*. Единственный код, который остаётся в *main.rs* будет достаточно маленьким, чтобы проверить его соблюдение правил прочитав код. Давайте переработаем нашу программу, следуя этому этапу. +Этот образец о разделении ответственности: *main.rs* занимается запуском программы, а *lib.rs* обрабатывает всю ход мыслей задачи. Поскольку нельзя проверить функцию `main` напрямую, то такая устройства позволяет проверить всю ход мыслей программы путём перемещения её в функции внутри *lib.rs*. Единственный рукопись, который остаётся в *main.rs* будет достаточно маленьким, чтобы проверить его соблюдение правил прочитав рукопись. Давайте переработаем нашу программу, следуя этому этапу. #### Извлечение обработчика переменных -Мы извлечём возможность для разбора переменных в функцию, которую вызовет `main` для подготовки к перемещению логики разбора приказной строки в файл *src/lib.rs*. Приложение 12-5 показывает новый запуск `main`, который вызывает новую функцию `parse_config`, которую мы определим сначала в *src/main.rs.* +Мы извлечём возможность для разбора переменных в функцию, которую вызовет `main` для подготовки к перемещению хода мыслей разбора приказной строки в файл *src/lib.rs*. Приложение 12-5 показывает новый запуск `main`, который вызывает новую функцию `parse_config`, которую мы определим сначала в *src/main.rs.* Файл: src/main.rs @@ -39,15 +39,15 @@ Приложение 12-5: Выделение функции parse_config из main -Мы все ещё собираем переменные приказной строки в вектор, но вместо присваивания значение переменной с порядковым указателем 1 переменной `query` и значение переменной с порядковым указателем 2 переменной с именем `file_path` в функции `main`, мы передаём весь вектор в функцию `parse_config`. Функция `parse_config` затем содержит логику, которая определяет, какой переменная идёт в какую переменную и передаёт значения обратно в `main`. Мы все ещё создаём переменные `query` и `file_path` в `main`, но `main` больше не несёт ответственности за определение соответствия переменной приказной строки и соответствующей переменной. +Мы все ещё собираем переменные приказной строки в вектор, но вместо присваивания значение переменной с порядковым указателем 1 переменной `query` и значение переменной с порядковым указателем 2 переменной с именем `file_path` в функции `main`, мы передаём весь вектор в функцию `parse_config`. Функция `parse_config` затем содержит ход мыслей, которая определяет, какой переменная идёт в какую переменную и передаёт значения обратно в `main`. Мы все ещё создаём переменные `query` и `file_path` в `main`, но `main` больше не несёт ответственности за определение соответствия переменной приказной строки и соответствующей переменной. -Эта доработка может показаться излишней для нашей маленькой программы, но мы проводим переработка кода небольшими, постепенными шагами. После внесения этого изменения снова запустите программу и убедитесь, что анализ переменных все ещё работает. Также хорошо часто проверять все этапы, чтобы помочь определить причину неполадок. когда они возникают. +Эта доработка может показаться излишней для нашей маленькой программы, но мы проводим переработка рукописи небольшими, постепенными шагами. После внесения этого изменения снова запустите программу и убедитесь, что оценка переменных все ещё работает. Также хорошо часто проверять все этапы, чтобы помочь определить причину неполадок. когда они возникают. #### Объединение настроечных переменных Мы можем сделать ещё один маленький шаг для улучшения функции `parse_config`. На данный мгновение мы возвращаем упорядоченный ряд, но затем мы немедленно разделяем его снова на отдельные части. Это признак того, что, возможно, пока у нас нет правильной абстракции. -Ещё один индикатор, который показывает, что есть место для улучшения, это часть `config` из `parse_config`, что подразумевает, что два значения, которые мы возвращаем, связаны друг с другом и оба являются частью одного настроечного значения. В настоящее время мы не отражаем этого смысла в устройстве данных, кроме объединения двух значений в упорядоченный ряд; мы могли бы поместить оба значения в одну устройство и дать каждому из полей устройства понятное имя. Это облегчит будущую поддержку этого кода, чтобы понять, как различные значения относятся друг к другу и какое их назначение. +Ещё один показатель, который показывает, что есть место для улучшения, это часть `config` из `parse_config`, что подразумевает, что два значения, которые мы возвращаем, связаны друг с другом и оба являются частью одного настроечного значения. В настоящее время мы не отражаем этого смысла в устройстве данных, кроме объединения двух значений в упорядоченный ряд; мы могли бы поместить оба значения в одну устройство и дать каждому из полей устройства понятное имя. Это облегчит будущую поддержку этого рукописи, чтобы понять, как различные значения относятся друг к другу и какое их назначение. В приложении 12-6 показаны улучшения функции `parse_config` . @@ -57,24 +57,24 @@ {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-06/src/main.rs:here}} ``` -Приложение 12-6: Переработка кода функции parse_config, чтобы возвращать образец устройства Config +Приложение 12-6: Переработка рукописи функции parse_config, чтобы возвращать образец устройства Config -Мы добавили устройство с именем `Config` объявленную с полями назваными как `query` и `file_path`. Ярлык `parse_config` теперь указывает, что она возвращает значение `Config`. В теле `parse_config`, где мы возвращали срезы строк, которые ссылаются на значения `String` в `args`, теперь мы определяем `Config` как содержащие собственные `String` значения. Переменная `args` в `main` является владельцем значений переменной и позволяют функции `parse_config` только одалживать их, что означает, что мы бы нарушили правила заимствования Rust, если бы `Config` попытался бы взять во владение значения в `args` . +Мы добавили устройство с именем `Config` объявленную с полями назваными как `query` и `file_path`. Ярлык `parse_config` теперь указывает, что она возвращает значение `Config`. В теле `parse_config`, где мы возвращали срезы строк, которые ссылаются на значения `String` в `args`, теперь мы определяем `Config` как содержащие собственные `String` значения. Переменная `args` в `main` является владельцем значений переменной и позволяют функции `parse_config` только одалживать их, что означает, что мы бы нарушили правила заимствования Ржавчина, если бы `Config` попытался бы взять во владение значения в `args` . -Мы можем управлять данными `String` разным количеством способов, но самый простой, хотя и отчасти неэффективный это вызвать способ `clone` у значений. Он сделает полную повтор данных для образца `Config` для владения, что занимает больше времени и памяти, чем сохранение ссылки на строку данных. Однако клонирование данных также делает наш код очень простым, потому что нам не нужно управлять временем жизни ссылок; в этом обстоятельстве, отказ от небольшой производительности, чтобы получить простоту, стоит небольших соглашениеа. +Мы можем управлять данными `String` разным количеством способов, но самый простой, хотя и отчасти неразумный это вызвать способ `clone` у значений. Он сделает полную повтор данных для образца `Config` для владения, что занимает больше времени и памяти, чем сохранение ссылки на строку данных. Однако клонирование данных также делает нашу рукопись очень простым, потому что нам не нужно управлять временем жизни ссылок; в этом обстоятельстве, отказ от небольшой производительности, чтобы получить простоту, стоит небольших соглашениеа. -> К при использовании способа cloneСуществует тенденция в среде программистов Ржавчина избегать использования `clone`, т.к. это понижает эффективность работы кода. В [Главе 13], вы изучите более эффективные способы, которые могут подойти в подобной случаи. Но сейчас можно воспроизводить несколько строк, чтобы продолжить работу, потому что вы сделаете эти повторы только один раз, а ваше имя файла и строка запроса будут очень маленькими. Лучше иметь работающую программу, которая немного неэффективна, чем пытаться заранее перерабатывать код при первом написании. По мере приобретения опыта работы с Ржавчина вам будет проще начать с наиболее эффективного решения, но сейчас вполне приемлемо вызвать `clone`. +> К при использовании способа cloneСуществует тенденция в среде программистов Ржавчине избегать использования `clone`, т.к. это понижает производительность работы рукописи. В [Главе 13], вы изучите более производительные способы, которые могут подойти в подобной случаи. Но сейчас можно воспроизводить несколько строк, чтобы продолжить работу, потому что вы сделаете эти повторы только один раз, а ваше имя файла и строка запроса будут очень маленькими. Лучше иметь работающую программу, которая немного неэффективна, чем пытаться заранее перерабатывать рукопись при первом написании. По мере приобретения опыта работы с Ржавчина вам будет проще начать с наиболее качественного решения, но сейчас вполне приемлемо вызвать `clone`. > -Мы обновили код в `main` поэтому он помещает образец `Config` возвращённый из `parse_config` в переменную с именем `config`, и мы обновили код, в котором ранее использовались отдельные переменные `query` и `file_path`, так что теперь он использует вместо этого поля в устройстве `Config`. +Мы обновили рукопись в `main` поэтому он помещает образец `Config` возвращённый из `parse_config` в переменную с именем `config`, и мы обновили рукопись, в котором ранее использовались отдельные переменные `query` и `file_path`, так что теперь он использует вместо этого поля в устройстве `Config`. -Теперь наш код более чётко передаёт то, что `query` и `file_path` связаны и что цель из использования состоит в том, чтобы настроить, как программа будет работать. Любой код, который использует эти значения знает, что может найти их в именованных полях образца `config` по их назначению. +Теперь нашу рукопись более чётко передаёт то, что `query` и `file_path` связаны и что цель из использования состоит в том, чтобы настроить, как программа будет работать. Любой рукопись, который использует эти значения знает, что может найти их в именованных полях образца `config` по их назначению. #### Создание строителя для устройства `Config` -Пока что мы извлекли логику, отвечающую за синтаксический анализ переменных приказной строки из `main` и помеисполнения его в функцию `parse_config`. Это помогло нам увидеть, что значения `query` и `file_path` были связаны и что их отношения должны быть отражены в нашем коде. Затем мы добавили устройство `Config` в качестве названия связанных общей целью `query` и `file_path` и чтобы иметь возможность вернуть именованные значения как имена полей устройства из функции `parse_config`. +Пока что мы извлекли ход мыслей, отвечающую за связанный оценка переменных приказной строки из `main` и помеисполнения его в функцию `parse_config`. Это помогло нам увидеть, что значения `query` и `file_path` были связаны и что их отношения должны быть отражены в нашем рукописи. Затем мы добавили устройство `Config` в качестве названия связанных общей целью `query` и `file_path` и чтобы иметь возможность вернуть именованные значения как имена полей устройства из функции `parse_config`. -Итак, теперь целью функции `parse_config` является создание образца `Config`, мы можем изменить `parse_config` из простой функции на функцию названную `new`, которая связана со устройством `Config`. Выполняя это изменение мы сделаем код более идиоматичным. Можно создавать образцы видов в встроенной библиотеке, такие как `String` с помощью вызова `String::new`. Точно так же изменив название `parse_config` на название функции `new`, связанную с `Config`, мы будем уметь создавать образцы `Config`, вызывая `Config::new`. Приложение 12-7 показывает изменения, которые мы должны сделать. +Итак, теперь целью функции `parse_config` является создание образца `Config`, мы можем изменить `parse_config` из простой функции на функцию названную `new`, которая связана со устройством `Config`. Выполняя это изменение мы сделаем рукопись более идиоматичным. Можно создавать образцы видов в встроенной библиотеке, такие как `String` с помощью вызова `String::new`. Точно так же изменив название `parse_config` на название функции `new`, связанную с `Config`, мы будем уметь создавать образцы `Config`, вызывая `Config::new`. Приложение 12-7 показывает изменения, которые мы должны сделать. Файл: src/main.rs @@ -84,11 +84,11 @@ Приложение 12-7: Переименование parse_config в Config::new -Мы обновили `main` где вызывали `parse_config`, чтобы вместо этого вызывалась `Config::new`. Мы изменили имя `parse_config` на `new` и перенесли его внутрь раздела `impl`, который связывает функцию `new` с `Config`. Попробуйте снова собрать код, чтобы убедиться, что он работает. +Мы обновили `main` где вызывали `parse_config`, чтобы вместо этого вызывалась `Config::new`. Мы изменили имя `parse_config` на `new` и перенесли его внутрь раздела `impl`, который связывает функцию `new` с `Config`. Попробуйте снова собрать рукопись, чтобы убедиться, что он работает. ### Исправление ошибок обработки -Теперь мы поработаем над исправлением обработки ошибок. Напомним, что попытки получить доступ к значениям в векторе `args` с порядковым указателем 1 или порядковым указателем 2 приведут к панике, если вектор содержит менее трёх элементов. Попробуйте запустить программу без каких-либо переменных; это будет выглядеть так: +Теперь мы поработаем над исправлением обработки ошибок. Напомним, что попытки получить доступ к значениям в векторе `args` с порядковым указателем 1 или порядковым указателем 2 приведут к сбою, если вектор содержит менее трёх элементов. Попробуйте запустить программу без каких-либо переменных; это будет выглядеть так: ```console {{#include ../listings/ch12-an-io-project/listing-12-07/output.txt}} @@ -98,7 +98,7 @@ #### Улучшение сообщения об ошибке -В приложении 12-8 мы добавляем проверку в функцию `new`, которая будет проверять, что срез достаточно длинный, перед попыткой доступа по порядковым указателям 1 и 2. Если срез не достаточно длинный, программа паникует и отображает улучшенное сообщение об ошибке. +В приложении 12-8 мы добавляем проверку в функцию `new`, которая будет проверять, что срез достаточно длинный, перед попыткой доступа по порядковым указателям 1 и 2. Если срез не достаточно длинный, программа вызывает сбой и отображает улучшенное сообщение об ошибке. Файл: src/main.rs @@ -108,9 +108,9 @@ Приложение 12-8: Добавление проверки количества переменных -Этот код похож на [функцию `Guess::new` написанную в приложении 9-13], где мы вызывали `panic!`, когда `value` переменной вышло за пределы допустимых значений. Здесь вместо проверки на рядзначений, мы проверяем, что длина `args` не менее 3 и остальная часть функции может работать при условии, что это условие было выполнено. Если в `args` меньше трёх элементов, это условие будет истинным и мы вызываем макрос `panic!` для немедленного завершения программы. +Этот рукопись похож на [функцию `Guess::new` написанную в приложении 9-13], где мы вызывали `panic!`, когда `value` переменной вышло за пределы допустимых значений. Здесь вместо проверки на рядзначений, мы проверяем, что длина `args` не менее 3 и остальная часть функции может работать при условии, что это условие было выполнено. Если в `args` меньше трёх элементов, это условие будет истинным и мы вызываем макрос `panic!` для немедленного завершения программы. -Имея нескольких лишних строк кода в `new`, давайте запустим программу снова без переменных, чтобы увидеть, как выглядит ошибка: +Имея нескольких лишних строк рукописи в `new`, давайте запустим программу снова без переменных, чтобы увидеть, как выглядит ошибка: ```console {{#include ../listings/ch12-an-io-project/listing-12-08/output.txt}} @@ -126,7 +126,7 @@ Мы можем вернуть значение `Result`, которое будет содержать образец `Config` в успешном случае и опишет неполадку в случае ошибки. Мы так же изменим функцию `new` на `build` потому что многие программисты ожидают что `new` никогда не завершится неудачей. Когда `Config::build` взаимодействует с `main`, мы можем использовать вид `Result` как сигнал возникновения сбоев. Затем мы можем изменить `main`, чтобы преобразовать исход `Err` в более применимую ошибку для наших пользователей без окружающего текста вроде `thread 'main'` и `RUST_BACKTRACE`, что происходит при вызове `panic!`. -Приложение 12-9 показывает изменения, которые нужно внести в возвращаемое значения функции `Config::build`, и в тело функции, необходимые для возврата вида `Result`. Заметьте, что этот код не собирается, пока мы не обновим `main`, что мы и сделаем в следующем приложении. +Приложение 12-9 показывает изменения, которые нужно внести в возвращаемое значения функции `Config::build`, и в тело функции, необходимые для возврата вида `Result`. Заметьте, что этот рукопись не собирается, пока мы не обновим `main`, что мы и сделаем в следующем приложении. Файл: src/main.rs @@ -148,7 +148,7 @@ #### Вызов `Config::build` и обработка ошибок -Чтобы обработать ошибку и вывести более дружественное сообщение об ошибке, нам нужно обновить код `main` для обработки `Result`, возвращаемого из `Config::build` как показано в приложении 12-10. Мы также возьмём на себя ответственность за выход из программы приказной строки с ненулевым кодом ошибки `panic!` и выполняем это вручную. Не нулевой значение выхода - это соглашение, которое указывает этапу, который вызывает нашу программу, что программа завершилась с ошибкой. +Чтобы обработать ошибку и вывести более дружественное сообщение об ошибке, нам нужно обновить рукопись `main` для обработки `Result`, возвращаемого из `Config::build` как показано в приложении 12-10. Мы также возьмём на себя ответственность за выход из программы приказной строки с ненулевым рукописью ошибки `panic!` и выполняем это вручную. Не нулевой значение выхода - это соглашение, которое указывает этапу, который вызывает нашу программу, что программа завершилась с ошибкой. Файл: src/main.rs @@ -156,11 +156,11 @@ {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-10/src/main.rs:here}} ``` -Приложение 12-10. Выход с кодом ошибки если создание новой Config терпит неудачу +Приложение 12-10. Выход с рукописью ошибки если создание новой Config терпит неудачу -В этом приложении мы использовали способ, который мы ещё не рассматривали подробно: `unwrap_or_else`, который в встроенной библиотеке определён как `Result`. Использование `unwrap_or_else` позволяет нам определить некоторые пользовательские ошибки обработки, не содержащие `panic!`. Если `Result` является значением `Ok`, поведение этого способа подобно `unwrap`: возвращает внутреннее значение из обёртки `Ok`. Однако, если значение является значением `Err`, то этот способ вызывает код *замыкания*, которое является анонимной функцией, определённой заранее и передаваемую в качестве переменной в `unwrap_or_else`. Мы рассмотрим замыкания более подробно в [главе 13](ch13-00-functional-features.html). В данный мгновение, вам просто нужно знать, что `unwrap_or_else` передаст внутреннее значение `Err`, которое в этом случае является постоянной строкой `not enough arguments`, которое мы добавили в приложении 12-9, в наше замыкание как переменная `err` указанное между вертикальными линиями. Код в замыкании может затем использовать значение `err` при выполнении. +В этом приложении мы использовали способ, который мы ещё не рассматривали подробно: `unwrap_or_else`, который в встроенной библиотеке определён как `Result`. Использование `unwrap_or_else` позволяет нам определить некоторые пользовательские ошибки обработки, не содержащие `panic!`. Если `Result` является значением `Ok`, поведение этого способа подобно `unwrap`: возвращает внутреннее значение из обёртки `Ok`. Однако, если значение является значением `Err`, то этот способ вызывает рукопись *замыкания*, которое является анонимной функцией, определённой заранее и передаваемую в качестве переменной в `unwrap_or_else`. Мы рассмотрим замыкания более подробно в [главе 13](ch13-00-functional-features.html). В данный мгновение, вам просто нужно знать, что `unwrap_or_else` передаст внутреннее значение `Err`, которое в этом случае является постоянной строкой `not enough arguments`, которое мы добавили в приложении 12-9, в наше замыкание как переменная `err` указанное между вертикальными линиями. Рукопись в замыкании может затем использовать значение `err` при выполнении. -Мы добавили новую строку `use`, чтобы подключить `process` из встроенной библиотеки в область видимости. Код в замыкании, который будет запущен в случае ошибки содержит только две строчки: мы печатаем значение `err` и затем вызываем `process::exit`. Функция `process::exit` немедленно остановит программу и вернёт номер, который был передан в качестве кода состояния выхода. Это похоже на обработку с помощью макроса `panic!`, которую мы использовали в приложении 12-8, но мы больше не получаем весь дополнительный вывод. Давай попробуем: +Мы добавили новую строку `use`, чтобы подключить `process` из встроенной библиотеки в область видимости. Рукопись в замыкании, который будет запущен в случае ошибки содержит только две строчки: мы печатаем значение `err` и затем вызываем `process::exit`. Функция `process::exit` немедленно остановит программу и вернёт номер, который был передан в качестве рукописи состояния выхода. Это похоже на обработку с помощью макроса `panic!`, которую мы использовали в приложении 12-8, но мы больше не получаем весь дополнительный вывод. Давай попробуем: ```console {{#include ../listings/ch12-an-io-project/listing-12-10/output.txt}} @@ -168,11 +168,11 @@ Замечательно! Этот вывод намного дружелюбнее для наших пользователей. -### Извлечение логики из `main` +### Извлечение хода мыслей из `main` -Теперь, когда мы закончили переработка кода разбора настройке, давайте обратимся к логике программы. Как мы указали в разделе [«Разделение ответственности в двоичных делах»](#separation-of-concerns-for-binary-projects), мы извлечём функцию с именем `run`, которая будет содержать всю логику, присутствующую в настоящее время в функции `main` и которая не связана с настройкой настройке или обработкой ошибок. Когда мы закончим, то `main` будет краткой, легко проверяемой и мы сможем написать проверки для всей остальной логики. +Теперь, когда мы закончили переработка рукописи разбора настройки, давайте обратимся к ходу мыслей программы. Как мы указали в разделе [«Разделение ответственности в двоичных делах»](#separation-of-concerns-for-binary-projects), мы извлечём функцию с именем `run`, которая будет содержать всю ход мыслей, присутствующую в настоящее время в функции `main` и которая не связана с настройкой настройки или обработкой ошибок. Когда мы закончим, то `main` будет краткой, легко проверяемой и мы сможем написать проверки для всей остальной хода мыслей. -Код 12-11 отображает извлечённую логику в функцию `run`. Мы делаем маленькое, инкрементальное приближение к извлечению функции. Код всё ещё сосредоточен в файле *src/main.rs*: +Рукопись 12-11 отображает извлечённую ход мыслей в функцию `run`. Мы делаем маленькое, инкрементальное приближение к извлечению функции. Рукопись всё ещё сосредоточен в файле *src/main.rs*: Файл: src/main.rs @@ -180,13 +180,13 @@ {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-11/src/main.rs:here}} ``` -Приложение 12-11. Извлечение функции run, содержащей остальную логику программы +Приложение 12-11. Извлечение функции run, содержащей остальную ход мыслей программы -Функция `run` теперь содержит всю оставшуюся логику из `main`, начиная от чтения файла. Функция `run` принимает образец `Config` как переменная. +Функция `run` теперь содержит всю оставшуюся ход мыслей из `main`, начиная от чтения файла. Функция `run` принимает образец `Config` как переменная. #### Возврат ошибок из функции `run` -Оставшаяся логика программы выделена в функцию `run`, где мы можем улучшить обработку ошибок как мы уже делали с `Config::build` в приложении 12-9. Вместо того, чтобы позволить программе паниковать с помощью вызова `expect`, функция `run` вернёт `Result`, если что-то пойдёт не так. Это позволит далее окне выводадировать логику обработки ошибок в `main` удобным способом. Приложение 12-12 показывает изменения, которые мы должны внести в ярлык и тело `run`. +Оставшаяся ход мыслей программы выделена в функцию `run`, где мы можем улучшить обработку ошибок как мы уже делали с `Config::build` в приложении 12-9. Вместо того, чтобы позволить программе вызвать сбой с помощью вызова `expect`, функция `run` вернёт `Result`, если что-то пойдёт не так. Это позволит далее окне выводадировать ход мыслей обработки ошибок в `main` удобным способом. Приложение 12-12 показывает изменения, которые мы должны внести в ярлык и тело `run`. Файл: src/main.rs @@ -200,17 +200,17 @@ Для вида ошибки мы использовали *предмет особенность* `Box` (и вверху мы подключили вид `std::error::Error` в область видимости с помощью указания `use`). Мы рассмотрим особенности предметов в [главе 17]. Сейчас просто знайте, что `Box` означает, что функция будет возвращать вид выполняющий особенность `Error`, но не нужно указывать, какой именно будет вид возвращаемого значения. Это даёт возможность возвращать значения ошибок, которые могут быть разных видов в разных случаях. Ключевое слово `dyn` сокращение для слова «изменяемый». -Во-вторых, мы убрали вызов `expect` в пользу использования оператора `?`, как мы обсудили в [главе 9]. Скорее, чем вызывать `panic!` в случае ошибки, оператор `?` вернёт значение ошибки из текущей функции для вызывающего, чтобы он её обработал. +Во-вторых, мы убрали вызов `expect` в пользу использования приказчика `?`, как мы обсудили в [главе 9]. Скорее, чем вызывать `panic!` в случае ошибки, приказчик `?` вернёт значение ошибки из текущей функции для вызывающего, чтобы он её обработал. -В-третьих, функция `run` теперь возвращает значение `Ok` в случае успеха. В ярлыке функции `run` успешный вид объявлен как `()`, который означает, что нам нужно обернуть значение единичного вида в значение `Ok`. Данный правила написания `Ok(())` поначалу может показаться немного странным, но использование `()` выглядит как идиоматический способ указать, что мы вызываем `run` для его побочных эффектов; он не возвращает значение, которое нам нужно. +В-третьих, функция `run` теперь возвращает значение `Ok` в случае успеха. В ярлыке функции `run` успешный вид объявлен как `()`, который означает, что нам нужно обернуть значение единичного вида в значение `Ok`. Данный правила написания `Ok(())` поначалу может показаться немного странным, но использование `()` выглядит как идиоматический способ указать, что мы вызываем `run` для его побочных последствий; он не возвращает значение, которое нам нужно. -Когда вы запустите этот код, он собирается, но отобразит предупреждение: +Когда вы запустите этот рукопись, он собирается, но отобразит предупреждение: ```console {{#include ../listings/ch12-an-io-project/listing-12-12/output.txt}} ``` -Rust говорит, что наш код пренебрег `Result` значение и значение `Result` может указывать на то, что произошла ошибка. Но мы не проверяем, была ли ошибка и сборщик напоминает нам, что мы, вероятно, хотели здесь выполнить некоторый код обработки ошибок! Давайте исправим эту неполадку сейчас. +Ржавчина говорит, что нашу рукопись пренебрег `Result` значение и значение `Result` может указывать на то, что произошла ошибка. Но мы не проверяем, была ли ошибка и сборщик напоминает нам, что мы, вероятно, хотели здесь выполнить некоторый рукопись обработки ошибок! Давайте исправим эту неполадку сейчас. #### Обработка ошибок, возвращённых из `run` в `main` @@ -226,18 +226,18 @@ Rust говорит, что наш код пренебрег `Result` значе Тело функций `if let` и `unwrap_or_else` одинаковы в обоих случаях: мы печатаем ошибку и выходим. -### Разделение кода на библиотечный ящик +### Разделение рукописи на библиотечный ящик -Наш дело `minigrep` пока выглядит хорошо! Теперь мы разделим файл *src/main.rs* и поместим некоторый код в файл *src/lib.rs*. Таким образом мы сможем его проверять и чтобы в файле *src/main.rs* было меньшее количество полезных обязанностей. +Наш дело `minigrep` пока выглядит хорошо! Теперь мы разделим файл *src/main.rs* и поместим некоторый рукопись в файл *src/lib.rs*. Таким образом мы сможем его проверять и чтобы в файле *src/main.rs* было меньшее количество полезных обязанностей. -Давайте перенесём весь код не относящийся к функции `main` из файла *src/main.rs* в новый файл *src/lib.rs*: +Давайте перенесём весь рукопись не относящийся к функции `main` из файла *src/main.rs* в новый файл *src/lib.rs*: - Определение функции `run` - Соответствующие указания `use` - Определение устройства `Config` - Определение функции `Config::build` -Содержимое *src/lib.rs* должно иметь ярлыки, показанные в приложении 12-13 (мы опуисполнения тела функций для краткости). Обратите внимание, что код не будет собираться пока мы не изменим *src/main.rs* в приложении 12-14. +Содержимое *src/lib.rs* должно иметь ярлыки, показанные в приложении 12-13 (мы опуисполнения тела функций для краткости). Обратите внимание, что рукопись не будет собираться пока мы не изменим *src/main.rs* в приложении 12-14. Файл: src/lib.rs @@ -249,7 +249,7 @@ Rust говорит, что наш код пренебрег `Result` значе Мы добавили определетель доступа `pub` к устройстве `Config`, а также её полям, к способу `build` и функции `run`. Теперь у нас есть библиотечный ящик, который содержит открытый API, который мы можем проверять! -Теперь нам нужно подключить код, который мы перемеисполнения в *src/lib.rs,* в область видимости двоичного ящика внутри *src/main.rs*, как показано в приложении 12-14. +Теперь нам нужно подключить рукопись, который мы переместили в *src/lib.rs,* в область видимости двоичного ящика внутри *src/main.rs*, как показано в приложении 12-14. Файл: src/main.rs @@ -261,9 +261,9 @@ Rust говорит, что наш код пренебрег `Result` значе Мы добавляем `use minigrep::Config` для подключения вида `Config` из ящика библиотеки в область видимости двоичного ящика и добавляем к имени функции `run` приставка нашего ящика. Теперь все функции должны быть подключены и должны работать. Запустите программу с `cargo run` и убедитесь, что все работает правильно. -Уф! Было много работы, но мы настроены на будущий успех. Теперь проще обрабатывать ошибки и мы сделали код более состоящим из звеньев. С этого особенности почти вся наша работа будет выполняться внутри *src/lib.rs*. +Уф! Было много работы, но мы настроены на будущий успех. Теперь проще обрабатывать ошибки и мы сделали рукопись более состоящим из звеньев. С этого особенности почти вся наша работа будет выполняться внутри *src/lib.rs*. -Давайте воспользуемся этой новой выделения на звенья, сделав что-то, что было бы трудно со старым кодом, но легко с новым кодом: мы напишем несколько проверок! +Давайте воспользуемся этой новой выделения на звенья, сделав что-то, что было бы трудно со старым рукописью, но легко с новым рукописью: мы напишем несколько проверок! [Главе 13]: ch13-00-functional-features.html diff --git a/rustbook-ru/src/ch12-04-testing-the-librarys-functionality.md b/rustbook-ru/src/ch12-04-testing-the-librarys-functionality.md index 06d4607f1..d1601db7b 100644 --- a/rustbook-ru/src/ch12-04-testing-the-librarys-functionality.md +++ b/rustbook-ru/src/ch12-04-testing-the-librarys-functionality.md @@ -1,15 +1,15 @@ ## Развитие возможности библиотеки разработкой на основе проверок -Теперь, когда мы извлекли логику в *src/lib.rs* и оставили разбор переменных приказной строки и обработку ошибок в *src/main.rs*, стало гораздо проще писать проверки для основной возможности нашего кода. Мы можем вызывать функции напрямую с различными переменнойми и проверить возвращаемые значения без необходимости вызова нашего двоичного файла из приказной строки. +Теперь, когда мы извлекли ход мыслей в *src/lib.rs* и оставили разбор переменных приказной строки и обработку ошибок в *src/main.rs*, стало гораздо проще писать проверки для основной возможности нашего рукописи. Мы можем вызывать функции напрямую с различными переменнойми и проверить возвращаемые значения без необходимости вызова нашего двоичного файла из приказной строки. -В этом разделе в программу `minigrep` мы добавим логику поиска с использованием этапа разработки через проверка (TDD), который следует этим шагам: +В этом разделе в программу `minigrep` мы добавим ход мыслей поиска с использованием этапа разработки через проверка (TDD), который следует этим шагам: 1. Напишите проверка, который завершается неудачей, и запустите его, чтобы убедиться, что он не сработал именно по той причине, которую вы ожидаете. -2. Пишите или изменяйте ровно столько кода, чтобы успешно выполнился новый проверку. -3. Выполните переработка кода кода, который вы только что добавили или изменили, и убедитесь, что проверки продолжают проходить. +2. Пишите или изменяйте ровно столько рукописи, чтобы успешно выполнился новый проверку. +3. Выполните переработка рукописи рукописи, который вы только что добавили или изменили, и убедитесь, что проверки продолжают проходить. 4. Повторите с шага 1! -Хотя это всего лишь один из многих способов написания программного обеспечения, TDD может помочь в разработке кода. Написание проверки перед написанием кода, обеспечивающего прохождение проверки, помогает поддерживать высокое покрытие проверкими на протяжении всего этапа разработки. +Хотя это всего лишь один из многих способов написания программного обеспечения, TDD может помочь в разработке рукописи. Написание проверки перед написанием рукописи, обеспечивающего прохождение проверки, помогает поддерживать высокое покрытие проверкими на протяжении всего этапа разработки. Мы проверим выполнение возможности, которая делает поиск строки запроса в содержимом файла и создание списка строк, соответствующих запросу. Мы добавим эту возможность в функцию под названием `search`. @@ -25,9 +25,9 @@ Приложение 12-15: Создание безуспешного проверки для функции search, которую мы хотим создать -Этот проверка ищет строку `"duct"`. Текст, в котором мы ищем, состоит из трёх строк, только одна из которых содержит `"duct"` (обратите внимание, что обратная косая черта после открывающей двойной кавычки говорит Ржавчина не помещать символ новой строки в начало содержимого этого строкового записи). Мы проверяем, что значение, возвращаемое функцией `search`, содержит только ожидаемую нами строку. +Этот проверка ищет строку `"duct"`. Текст, в котором мы ищем, состоит из трёх строк, только одна из которых содержит `"duct"` (обратите внимание, что обратная косая черта после открывающей двойной кавычки говорит Ржавчина не помещать знак новой строки в начало содержимого этого строкового записи). Мы проверяем, что значение, возвращаемое функцией `search`, содержит только ожидаемую нами строку. -Мы не можем запустить этот проверка и увидеть сбой, потому что проверка даже не собирается: функции `search` ещё не существует! В соответствии с принципами TDD мы добавим ровно столько кода, чтобы проверка собирался и запускался, добавив определение функции `search`, которая всегда возвращает пустой вектор, как показано в приложении 12-16. Потом проверка должен собраться и потерпеть неудачу при запуске, потому что пустой вектор не равен вектору, содержащему строку `"safe, fast, productive."` +Мы не можем запустить этот проверка и увидеть сбой, потому что проверка даже не собирается: функции `search` ещё не существует! В соответствии с принципами TDD мы добавим ровно столько рукописи, чтобы проверка собирался и запускался, добавив определение функции `search`, которая всегда возвращает пустой вектор, как показано в приложении 12-16. Потом проверка должен собраться и потерпеть неудачу при запуске, потому что пустой вектор не равен вектору, содержащему строку `"safe, fast, productive."` Файл: src/lib.rs @@ -39,7 +39,7 @@ Заметьте, что в ярлыке `search` нужно явно указать время жизни `'a` для переменной `contents` и возвращаемого значения. Напомним из [Главы 10], что свойства времени жизни указывают с временем жизни какого переменной связано время жизни возвращаемого значения. В данном случае мы говорим, что возвращаемый вектор должен содержать срезы строк, ссылающиеся на содержимое переменной `contents` (а не переменной `query`). -Другими словами, мы говорим Rust, что данные, возвращаемые функцией `search`, будут жить до тех пор, пока живут данные, переданные в функцию `search` через переменная `contents`. Это важно! Чтобы ссылки были действительными, данные, на которые ссылаются *с помощью* срезов тоже должны быть действительными; если сборщик предполагает, что мы делаем строковые срезы переменной `query`, а не переменной `contents`, он неправильно выполнит проверку безопасности. +Другими словами, мы говорим Ржавчина что данные, возвращаемые функцией `search`, будут жить до тех пор, пока живут данные, переданные в функцию `search` через переменная `contents`. Это важно! Чтобы ссылки были действительными, данные, на которые ссылаются *с помощью* срезов тоже должны быть действительными; если сборщик предполагает, что мы делаем строковые срезы переменной `query`, а не переменной `contents`, он неправильно выполнит проверку безопасности. Если мы забудем изложении времени жизни и попробуем собрать эту функцию, то получим следующую ошибку: @@ -47,7 +47,7 @@ {{#include ../listings/ch12-an-io-project/output-only-02-missing-lifetimes/output.txt}} ``` -Rust не может понять, какой из двух переменных нам нужен, поэтому нужно сказать ему об этом. Так как `contents` является тем переменнаяом, который содержит весь наш текст, и мы хотим вернуть части этого текста, которые совпали при поиске, мы понимаем, что `contents` является переменнаяом, который должен быть связан с возвращаемым значением временем жизни. +Ржавчина не может понять, какой из двух переменных нам нужен, поэтому нужно сказать ему об этом. Так как `contents` является тем переменнаяом, который содержит весь наш текст, и мы хотим вернуть части этого текста, которые совпали при поиске, мы понимаем, что `contents` является переменнаяом, который должен быть связан с возвращаемым значением временем жизни. Другие языки программирования не требуют от вас связывания в ярлыке переменных с возвращаемыми значениями, но после определённой опытов вам станет проще. Можете сравнить этот пример с разделом [«Проверка ссылок с временами жизни»](ch10-03-lifetime-syntax.html#validating-references-with-lifetimes) главы 10. @@ -59,7 +59,7 @@ Rust не может понять, какой из двух переменных Отлично. Наш проверка не сработал, как мы и ожидали. Давайте сделаем так, чтобы он срабатывал! -### Написание кода для прохождения проверки +### Написание рукописи для прохождения проверки Сейчас наш проверка не проходит, потому что мы всегда возвращаем пустой вектор. Чтобы исправить это и выполнить `search`, наша программа должна выполнить следующие шаги: @@ -73,7 +73,7 @@ Rust не может понять, какой из двух переменных #### Перебор строк с помощью способа `lines` -В Ржавчина есть полезный способ для построчной повторения строк, удобно названный `lines`, как показано в приложении 12-17. Обратите внимание, код пока не собирается. +В Ржавчине есть полезный способ для построчной повторения строк, удобно названный `lines`, как показано в приложении 12-17. Обратите внимание, рукопись пока не собирается. Файл: src/lib.rs @@ -83,7 +83,7 @@ Rust не может понять, какой из двух переменных Приложение 12-17: Повторение по каждой строке из contents -Способ `lines` возвращает повторитель . Мы подробно поговорим об повторителях в [Главе 13], но вспомните, что вы видели этот способ использования повторителя в [Приложении 3-5], где мы использовали цикл `for` с повторителем, чтобы выполнить некоторый код для каждого элемента в собрания. +Способ `lines` возвращает повторитель . Мы подробно поговорим об повторителях в [Главе 13], но вспомните, что вы видели этот способ использования повторителя в [Приложении 3-5], где мы использовали круговорот `for` с повторителем, чтобы выполнить некоторый рукопись для каждого элемента в собрания. #### Поиск в каждой строке текста запроса @@ -101,7 +101,7 @@ Rust не может понять, какой из двух переменных #### Сохранение совпавшей строки -Чтобы завершить эту функцию, нам нужен способ сохранить совпадающие строки, которые мы хотим вернуть. Для этого мы можем создать изменяемый вектор перед циклом `for` и вызывать способ `push` для сохранения `line` в векторе. После цикла `for` мы возвращаем вектор, как показано в приложении 12-19. +Чтобы завершить эту функцию, нам нужен способ сохранить совпадающие строки, которые мы хотим вернуть. Для этого мы можем создать изменяемый вектор перед круговоротом `for` и вызывать способ `push` для сохранения `line` в векторе. После круговорота `for` мы возвращаем вектор, как показано в приложении 12-19. Файл: src/lib.rs @@ -119,7 +119,7 @@ Rust не может понять, какой из двух переменных Наш проверка пройден, значит он работает! -На этом этапе мы могли бы рассмотреть возможности изменения выполнения функции поиска, сохраняя прохождение проверок и поддерживая имеющуюся возможность. Код в функции поиска не так уж плох, но он не использует некоторые полезные функции повторителей. Вернёмся к этому примеру в [главе 13](ch13-02-iterators.html), где будем исследовать повторители подробно, и посмотрим как его улучшить. +На этом этапе мы могли бы рассмотреть возможности изменения выполнения функции поиска, сохраняя прохождение проверок и поддерживая имеющуюся возможность. Рукопись в функции поиска не так уж плох, но он не использует некоторые полезные функции повторителей. Вернёмся к этому примеру в [главе 13](ch13-02-iterators.html), где будем исследовать повторители подробно, и посмотрим как его улучшить. #### Использование функции `search` в функции `run` @@ -131,7 +131,7 @@ Rust не может понять, какой из двух переменных {{#rustdoc_include ../listings/ch12-an-io-project/no-listing-02-using-search-in-run/src/lib.rs:here}} ``` -Мы по-прежнему используем цикл `for` для возврата каждой строки из функции `search` и её печати. +Мы по-прежнему используем круговорот `for` для возврата каждой строки из функции `search` и её печати. Теперь вся программа должна работать! Давайте попробуем сначала запустить её со словом «frog», которое должно вернуть только одну строчку из стихотворения Эмили Дикинсон: @@ -151,9 +151,7 @@ Rust не может понять, какой из двух переменных {{#include ../listings/ch12-an-io-project/output-only-04-no-matches/output.txt}} ``` -Отлично! Мы создали собственную простое-исполнение обычного средства и научились тому, как внутренне выстроить - - приложения. Мы также немного узнали о файловом вводе и выводе, временах жизни, проверке и разборе переменных приказной строки. +Отлично! Мы создали собственную простое-исполнение обычного средства и научились тому, как внутренне выстроить приложения. Мы также немного узнали о файловом вводе и выводе, временах жизни, проверке и разборе переменных приказной строки. Чтобы завершить этот дело, мы кратко выполним пару вещей: как работать с переменными окружения и как печатать в обычный поток ошибок, обе из которых полезны при написании окно выводаных программ. diff --git a/rustbook-ru/src/ch12-05-working-with-environment-variables.md b/rustbook-ru/src/ch12-05-working-with-environment-variables.md index 8783a10bc..db6385d12 100644 --- a/rustbook-ru/src/ch12-05-working-with-environment-variables.md +++ b/rustbook-ru/src/ch12-05-working-with-environment-variables.md @@ -34,7 +34,7 @@ Обратите внимание, что `query` теперь имеет вид `String`, а не срез строки, потому что вызов `to_lowercase` создаёт новые данные, а не ссылается на существующие. К примеру, запрос: `"rUsT"` это срез строки не содержащий строчных букв `u` или `t`, которые мы можем использовать, поэтому мы должны выделить новую `String`, содержащую `«rust»`. Когда мы передаём запрос `query` в качестве переменной способа `contains`, нам нужно добавить знак, поскольку ярлык `contains`, определена для приёмы среза строки. -Затем мы добавляем вызов `to_lowercase` для каждой строки `line` для преобразования к нижнему регистру всех символов. Теперь, когда мы преобразовали `line` и `query` в нижний регистр, мы найдём совпадения независимо от того, в каком регистре находится переменная с запросом. +Затем мы добавляем вызов `to_lowercase` для каждой строки `line` для преобразования к нижнему регистру всех знаков. Теперь, когда мы преобразовали `line` и `query` в нижний регистр, мы найдём совпадения независимо от того, в каком регистре находится переменная с запросом. Давайте посмотрим, проходит ли эта выполнение проверки: @@ -50,7 +50,7 @@ {{#rustdoc_include ../listings/ch12-an-io-project/listing-12-22/src/lib.rs:here}} ``` -Мы добавили поле `ignore_case`, которое содержит логическое значение. Далее нам нужна функция `run`, чтобы проверить значение поля `ignore_case` и использовать его, чтобы решить, вызывать ли функцию `search` или функцию `search_case_insensitive`, как показано в приложении 12-22. Этот код все ещё не собирается. +Мы добавили поле `ignore_case`, которое содержит разумное значение. Далее нам нужна функция `run`, чтобы проверить значение поля `ignore_case` и использовать его, чтобы решить, вызывать ли функцию `search` или функцию `search_case_insensitive`, как показано в приложении 12-22. Этот рукопись все ещё не собирается. Файл: src/lib.rs @@ -119,4 +119,4 @@ To an admiring bog! Некоторые программы допускают использование переменных *и* переменных среды для одной и той же настройке. В таких случаях программы решают, что из них имеет больший приоритет. Для другого самостоятельного упражнения попробуйте управлять чувствительностью к регистру с помощью переменной приказной строки или переменной окружения. Решите, переменная приказной строки или переменная среды будет иметь приоритет, если программа выполняется со значениями "учитывать регистр" в одном случае, и "пренебрегать регистр" в другом. -Звено `std::env` содержит много других полезных функций для работы с переменными среды: ознакомьтесь с его документацией, чтобы узнать доступные. +Звено `std::env` содержит много других полезных функций для работы с переменными среды: ознакомьтесь с его пособием, чтобы узнать доступные. diff --git a/rustbook-ru/src/ch12-06-writing-to-stderr-instead-of-stdout.md b/rustbook-ru/src/ch12-06-writing-to-stderr-instead-of-stdout.md index c6d4f7d82..1f6ee816f 100644 --- a/rustbook-ru/src/ch12-06-writing-to-stderr-instead-of-stdout.md +++ b/rustbook-ru/src/ch12-06-writing-to-stderr-instead-of-stdout.md @@ -26,7 +26,7 @@ Problem parsing arguments: not enough arguments ### Печать ошибок в поток ошибок -Мы будем использовать код в приложении 12-24, чтобы изменить способ вывода сообщений об ошибках. Из-за переработки кода, который мы делали ранее в этой главе, весь код, который печатает сообщения об ошибках, находится в одной функции: `main`. Обычная библиотека предоставляет макрос `eprintln!`который печатает в обычный поток ошибок, поэтому давайте изменим два места, где мы вызывали `println!` для печати ошибок, чтобы использовать `eprintln!` вместо этого. +Мы будем использовать рукопись в приложении 12-24, чтобы изменить способ вывода сообщений об ошибках. Из-за переработки рукописи, который мы делали ранее в этой главе, весь рукопись, который печатает сообщения об ошибках, находится в одной функции: `main`. Обычная библиотека предоставляет макрос `eprintln!`который печатает в обычный поток ошибок, поэтому давайте изменим два места, где мы вызывали `println!` для печати ошибок, чтобы использовать `eprintln!` вместо этого. Файл: src/main.rs @@ -64,6 +64,6 @@ How dreary to be somebody! ## Итоги -В этой главе были повторены некоторые основные подходы, которые вы изучили до сих пор и было рассказано, как выполнять обычные действия ввода-вывода в Rust. Используя переменные приказной строки, файлы, переменные среды и макрос`eprintln!` для печати ошибок и вы теперь готовы писать приложения приказной строки. В сочетании с подходами из предыдущих главах, ваш код будет хорошо согласован, будет эффективно хранить данные в соответствующих устройствах, хорошо обрабатывать ошибки и хорошо проверяться. +В этой главе были повторены некоторые основные подходы, которые вы изучили до сих пор и было рассказано, как выполнять обычные действия ввода-вывода в Ржавчине. Используя переменные приказной строки, файлы, переменные среды и макрос`eprintln!` для печати ошибок и вы теперь готовы писать приложения приказной строки. В сочетании с подходами из предыдущих главах, ваша рукопись будет хорошо согласован, будет правильно хранить данные в соответствующих устройствах, хорошо обрабатывать ошибки и хорошо проверяться. -Далее мы рассмотрим некоторые возможности Rust, на которые повлияли полезные языки: замыкания и повторители. +Далее мы рассмотрим некоторые возможности Ржавчины, на которые повлияли полезные языки: замыкания и повторители. diff --git a/rustbook-ru/src/ch13-00-functional-features.md b/rustbook-ru/src/ch13-00-functional-features.md index 533ad4353..d7932b29d 100644 --- a/rustbook-ru/src/ch13-00-functional-features.md +++ b/rustbook-ru/src/ch13-00-functional-features.md @@ -2,7 +2,7 @@ Внешний вид языка Ржавчина черпал вдохновение из многих других языков и техник, среди которых значительное влияние оказало *функциональное программирование*. Программирование в функциональном исполнении подразумевает использование функций взначении предметов, передавая их в качестве переменных, возвращая их из других функций, присваивая их переменным для последующего выполнения и так далее. -В этой главе мы не будем рассуждать о том, что из себя представляет функциональное программирование, а обсудим возможности Rust, присущие многим языкам, которые принято называть функциональными. +В этой главе мы не будем рассуждать о том, что из себя представляет функциональное программирование, а обсудим возможности Ржавчины, присущие многим языкам, которые принято называть функциональными. Более подробно мы поговорим про: @@ -11,4 +11,4 @@ - То, как, используя замыкания и повторители, улучшить работу с действиеми ввода-вывода в деле из главы 12 - Производительность замыканий и повторителей (спойлер: они быстрее, чем вы думаете!) -Мы уже рассмотрели другие возможности Rust, такие как сопоставление с образцом и перечисления, которые также появились под влиянием функционального исполнения. Поскольку освоение замыканий и повторителей — важная часть написания идиоматичного, быстрого кода на Rust, мы посвятим им всю эту главу. +Мы уже рассмотрели другие возможности Ржавчины, такие как сопоставление с образцом и перечисления, которые также появились под влиянием функционального исполнения. Поскольку освоение замыканий и повторителей — важная часть написания идиоматичного, быстрого рукописи на Ржавчине, мы посвятим им всю эту главу. diff --git a/rustbook-ru/src/ch13-01-closures.md b/rustbook-ru/src/ch13-01-closures.md index 90a825239..76622964b 100644 --- a/rustbook-ru/src/ch13-01-closures.md +++ b/rustbook-ru/src/ch13-01-closures.md @@ -4,7 +4,7 @@ ## Замыкания: анонимные функции, которые запечатлевают ("захватывают") своё окружение -Замыкания в Ржавчина - это анонимные функции, которые можно сохранять в переменных или передавать в качестве переменных другим функциям. Вы можете создать замыкание в одном месте, а затем вызвать его в каком-нибудь другом, чтобы выполнить обработку в ином среде. В отличие от функций, замыкания могут использовать значения из области видимости в которой они были определены. Мы выполним, как эти функции замыканий открывают возможности для повторного использования кода и изменения его поведения. +Замыкания в Ржавчине - это анонимные функции, которые можно сохранять в переменных или передавать в качестве переменных другим функциям. Вы можете создать замыкание в одном месте, а затем вызвать его в каком-нибудь другом, чтобы выполнить обработку в ином среде. В отличие от функций, замыкания могут использовать значения из области видимости в которой они были определены. Мы выполним, как эти функции замыканий открывают возможности для повторного использования рукописи и изменения его поведения. @@ -14,7 +14,7 @@ ### Захват переменных окружения с помощью замыкания -Сначала мы рассмотрим, как с помощью замыканий можно использовать предметы из области, в которой они вместе были определены, для их последующего использования. Вот сценарий: Время от времени наша предприятие по производству футболок в качестве акции дарит эксклюзивные футболки, выпущенные ограниченным тиражом, каким-нибудь пользователям из нашего списка рассылки. Люди из списка рассылки при желании могут выбрать любимый цвет в своём профиле. Если человек, выбранный для получения бесплатной футболки, указал свой любимый цвет, он получает футболку этого цвета. Если человек не указал свой любимый цвет, он получит рубашку того цвета, которых у предприятия на данный мгновение больше всего. +Сначала мы рассмотрим, как с помощью замыканий можно использовать предметы из области, в которой они вместе были определены, для их последующего использования. Вот задумка: Время от времени наша предприятие по производству футболок в качестве акции дарит эксклюзивные футболки, выпущенные ограниченным тиражом, каким-нибудь пользователям из нашего списка рассылки. Люди из списка рассылки при желании могут выбрать любимый цвет в своём профиле. Если человек, выбранный для получения бесплатной футболки, указал свой любимый цвет, он получает футболку этого цвета. Если человек не указал свой любимый цвет, он получит рубашку того цвета, которых у предприятия на данный мгновение больше всего. Существует множество способов выполнить это. В данном примере мы будем использовать перечисление `ShirtColor`, которое может быть двух исходов `Red` и `Blue` (для простоты ограничим количество доступных цветов этими двумя). Запасы предприятия мы представим устройством `Inventory`, которая состоит из поля `shirts`, содержащего `Vec`, в котором перечислены рубашки тех цветов, которые есть в наличии. Способ `giveaway`, определённый в `Inventory`, принимает необязательный свойство - цвет, предпочитаемый пользователем, выбранным для получения бесплатной рубашки, и возвращает тот цвет рубашки, который он получит в действительности. Эта схема показана в приложении 13-1: @@ -24,21 +24,21 @@ {{#rustdoc_include ../listings/ch13-functional-features/listing-13-01/src/main.rs}} ``` -Приложение 13-1: Случаей с раздачей рубашек предприятием +Приложение 13-1: Случай с раздачей рубашек предприятием В магазине `store`, определённом в `main`, осталось две синие и одна красная рубашки для этой ограниченной акции. Мы вызываем способ `giveaway` для пользователя предпочитающего красную рубашку и для пользователя без каких-либо предпочтений. -Опять же, этот код мог быть выполнен множеством способов, но в данном случае, чтобы сосредоточиться на замыканиях, мы придерживались изученных ранее подходов, за исключением тела способа `giveaway`, в котором используется замыкание. В способе `giveaway` мы получаем пользовательское предпочтение цвета как свойство вида `Option` и вызываем способ `unwrap_or_else` на `user_preference`. Способ `unwrap_or_else` перечисления `Option` определён встроенной библиотекой. Он принимает один переменная: замыкание без переменных, которое возвращает значение `T` (преобразуется в вид значения, которое окажется в исходе `Some` перечисления `Option`, в нашем случае `ShirtColor`). Если `Option` окажется исходом `Some`, `unwrap_or_else` вернёт значение из `Some`. А если `Option` будет является исходом `None`, `unwrap_or_else` вызовет замыкание и вернёт значение, возвращённое замыканием. +Опять же, этот рукопись мог быть выполнен множеством способов, но в данном случае, чтобы сосредоточиться на замыканиях, мы придерживались изученных ранее подходов, за исключением тела способа `giveaway`, в котором используется замыкание. В способе `giveaway` мы получаем пользовательское предпочтение цвета как свойство вида `Option` и вызываем способ `unwrap_or_else` на `user_preference`. Способ `unwrap_or_else` перечисления `Option` определён встроенной библиотекой. Он принимает один переменная: замыкание без переменных, которое возвращает значение `T` (преобразуется в вид значения, которое окажется в исходе `Some` перечисления `Option`, в нашем случае `ShirtColor`). Если `Option` окажется исходом `Some`, `unwrap_or_else` вернёт значение из `Some`. А если `Option` будет является исходом `None`, `unwrap_or_else` вызовет замыкание и вернёт значение, возвращённое замыканием. В качестве переменной `unwrap_or_else` мы передаём замыкание `|| self.most_stocked()`. Это замыкание, которое не принимает никаких свойств (если бы у замыкания были свойства, они были бы перечислены между двумя вертикальными полосами). В теле замыкания вызывается `self.most_stocked()`. Здесь мы определили замыкание, а выполнение `unwrap_or_else` такова, что выполнится оно позднее, когда потребуется получить итог. -Выполнение этого кода выводит: +Выполнение этого рукописи выводит: ```console {{#include ../listings/ch13-functional-features/listing-13-01/output.txt}} ``` -Важной особенностью здесь является то, что мы передали замыкание, которое вызывает `self.most_stocked()` текущего образца `Inventory`. Обычной библиотеке не нужно знать ничего о видах `Inventory` или `ShirtColor`, которые мы определили, или о логике, которую мы хотим использовать в этом сценарии. Замыкание определяет неизменяемую ссылку на `self` `Inventory` и передаёт её с указанным нами кодом в способ `unwrap_or_else`. А вот функции не могут определять своё окружение таким образом. +Важной особенностью здесь является то, что мы передали замыкание, которое вызывает `self.most_stocked()` текущего образца `Inventory`. Обычной библиотеке не нужно знать ничего о видах `Inventory` или `ShirtColor`, которые мы определили, или о ходу мыслей, которую мы хотим использовать в этом задумки. Замыкание определяет неизменяемую ссылку на `self` `Inventory` и передаёт её с указанным нами рукописью в способ `unwrap_or_else`. А вот функции не могут определять своё окружение таким образом. ### Выведение и изложение видов замыкания @@ -101,7 +101,7 @@ let add_one_v4 = |x| x + 1 ; Этот пример также отображает, то что переменная может быть привязана к определению замыкания, и в дальнейшем мы можем вызвать замыкание, используя имя переменной и круглые скобки, как если бы имя переменной было именем функции. -Поскольку мы можем иметь несколько неизменяемых ссылок на `list` одновременно, `list` остаётся доступным из кода до определения замыкания, после определения замыкания, а также до вызова замыкания и после. Этот код собирается, выполняется и печатает: +Поскольку мы можем иметь несколько неизменяемых ссылок на `list` одновременно, `list` остаётся доступным из рукописи до определения замыкания, после определения замыкания, а также до вызова замыкания и после. Этот рукопись собирается, выполняется и печатает: ```console {{#include ../listings/ch13-functional-features/listing-13-04/output.txt}} @@ -117,7 +117,7 @@ let add_one_v4 = |x| x + 1 ; Приложение 13-5. Определение и вызов замыкания, захватывающего изменяемую ссылку -Этот код собирается, запускается и печатает: +Этот рукопись собирается, запускается и печатает: ```console {{#include ../listings/ch13-functional-features/listing-13-05/output.txt}} @@ -125,9 +125,9 @@ let add_one_v4 = |x| x + 1 ; Обратите внимание, что между определением и вызовом замыкания `borrows_mutably` больше нет `println!`: когда определяется `borrows_mutably`, оно захватывает изменяемую ссылку на `list`. После вызова замыкания мы больше не используем его, поэтому изменяемое заимствование заканчивается. Между определением замыкания и вызовом замыкания неизменяемое заимствование для печати недоступно, потому что при наличии изменяемого заимствования никакие другие заимствования недопустимы. Попробуйте добавить туда `println!` и посмотрите, какое сообщение об ошибке вы получите! -Если вы хотите заставить замыкание принять владение значениями, которые оно использует в окружении, даже если в теле замыкания нет кода, требующего владения, вы можете использовать ключевое слово `move` перед списком свойств. +Если вы хотите заставить замыкание принять владение значениями, которые оно использует в окружении, даже если в теле замыкания нет рукописи, требующего владения, вы можете использовать ключевое слово `move` перед списком свойств. -Эта техника в основном полезна при передаче замыкания новому потоку, чтобы переместить данные так, чтобы они принадлежали новому потоку. Мы подробно обсудим потоки и то, зачем их использовать, в главе 16, когда будем говорить о одновременности, а пока давайте вкратце рассмотрим порождение нового потока с помощью замыкания, в котором используется ключевое слово `move`. В приложении 13-6 показан код из приложения 13-4, измененный для печати вектора в новом потоке, а не в основном потоке: +Эта техника в основном полезна при передаче замыкания новому потоку, чтобы переместить данные так, чтобы они принадлежали новому потоку. Мы подробно обсудим потоки и то, зачем их использовать, в главе 16, когда будем говорить о одновременности, а пока давайте вкратце рассмотрим порождение нового потока с помощью замыкания, в котором используется ключевое слово `move`. В приложении 13-6 показан рукопись из приложения 13-4, измененный для печати вектора в новом потоке, а не в основном потоке: Файл: src/main.rs @@ -147,7 +147,7 @@ let add_one_v4 = |x| x + 1 ; ### Перемещение захваченных значений из замыканий и особенности `Fn` -После того, как замыкание захватило ссылку или владение значением из среды, в которой оно определено (тем самым влияя на то, что перемещается *в* замыкание), код в теле замыкания определяет, что происходит со ссылками или значениями, в мгновение последующего выполнения замыкания (тем самым влияя на то, что перемещается *из* замыкания). Тело замыкания может делать любое из следующих действий: перемещать захваченное значение из замыкания, изменять захваченное значение, не перемещать и не изменять значение или вообще ничего не захватывать из среды. +После того, как замыкание захватило ссылку или владение значением из среды, в которой оно определено (тем самым влияя на то, что перемещается *в* замыкание), рукопись в теле замыкания определяет, что происходит со ссылками или значениями, в мгновение последующего выполнения замыкания (тем самым влияя на то, что перемещается *из* замыкания). Тело замыкания может делать любое из следующих действий: перемещать захваченное значение из замыкания, изменять захваченное значение, не перемещать и не изменять значение или вообще ничего не захватывать из среды. То, как замыкание получает и обрабатывает значения из своего окружения, указывает на то, какие особенности выполняет замыкание, а с помощью особенностей функции и устройства могут определять, какие виды замыканий они могут использовать. Замыканиям самостоятельно присваивается выполнение одного, двух или всех трёх из нижеперечисленных особенностей `Fn`, аддитивным образом, в зависимости от того, как тело замыкания обрабатывает значения: @@ -171,7 +171,7 @@ impl Option { } ``` -Напомним, что `T` - это гибкий вид, отображающий вид значения в `Some` исходе `Option`. Этот вид `T` также является возвращаемым видом функции `unwrap_or_else`: например, код, вызывающий `unwrap_or_else` у `Option`, получит `String`. +Напомним, что `T` - это гибкий вид, отображающий вид значения в `Some` исходе `Option`. Этот вид `T` также является возвращаемым видом функции `unwrap_or_else`: например, рукопись, вызывающий `unwrap_or_else` у `Option`, получит `String`. Далее, обратите внимание, что функция `unwrap_or_else` имеет дополнительный свойство гибкого вида `F`. Здесь `F` - это вид входного свойства `f`, который является замыканием, заданным нами при вызове `unwrap_or_else`. @@ -189,7 +189,7 @@ impl Option { Приложение 13-7: Использование sort_by_key для сортировки прямоугольников по ширине -Этот код печатает: +Этот рукопись печатает: ```console {{#include ../listings/ch13-functional-features/listing-13-07/output.txt}} @@ -207,7 +207,7 @@ impl Option { Приложение 13-8: Попытка использовать замыкание FnOnce с sort_by_key -Это надуманный, замысловатый способ (который не работает) подсчёта количества вызовов `sort_by_key` при сортировке `list`. Этот код пытается выполнить подсчёт, перемещая `value` - `String` из окружения замыкания - в вектор `sort_operations`. Замыкание захватывает `value`, затем перемещает `value` из замыкания, передавая владение на `value` вектору `sort_operations`. Это замыкание можно вызвать один раз; попытка вызвать его второй раз не сработает, потому что `value` уже не будет находиться в той среде, из которой его можно будет снова поместить в `sort_operations`! Поэтому это замыкание выполняет только `FnOnce`. Когда мы попытаемся собрать этот код, мы получим ошибку сообщающую о том что `value` не может быть перемещено из замыкания, потому что замыкание должно выполнить `FnMut`: +Это надуманный, замысловатый способ (который не работает) подсчёта количества вызовов `sort_by_key` при сортировке `list`. Этот рукопись пытается выполнить подсчёт, перемещая `value` - `String` из окружения замыкания - в вектор `sort_operations`. Замыкание захватывает `value`, затем перемещает `value` из замыкания, передавая владение на `value` вектору `sort_operations`. Это замыкание можно вызвать один раз; попытка вызвать его второй раз не сработает, потому что `value` уже не будет находиться в той среде, из которой его можно будет снова поместить в `sort_operations`! Поэтому это замыкание выполняет только `FnOnce`. Когда мы попытаемся собрать этот рукопись, мы получим ошибку сообщающую о том что `value` не может быть перемещено из замыкания, потому что замыкание должно выполнить `FnMut`: ```console {{#include ../listings/ch13-functional-features/listing-13-08/output.txt}} diff --git a/rustbook-ru/src/ch13-02-iterators.md b/rustbook-ru/src/ch13-02-iterators.md index 74107d09f..bd285c1c5 100755 --- a/rustbook-ru/src/ch13-02-iterators.md +++ b/rustbook-ru/src/ch13-02-iterators.md @@ -1,8 +1,8 @@ ## Обработка последовательности элементов с помощью повторителей -Использование образца Повторитель помогает при необходимости поочерёдного выполнения какой-либо действия над элементами последовательности. Повторитель отвечает за логику перебора элементов и определение особенности завершения последовательности. Используя повторители, вам не нужно самостоятельно выполнить всю эту логику. +Использование образца Повторитель помогает при необходимости поочерёдного выполнения какой-либо действия над элементами последовательности. Повторитель отвечает за ход мыслей перебора элементов и определение особенности завершения последовательности. Используя повторители, вам не нужно самостоятельно выполнить всю эту ход мыслей. -В Ржавчина повторители *ленивые (lazy)*, то есть они не делают ничего, пока вы не вызовете особые способы, потребляющие повторитель , чтобы задействовать его. Например, код в приложении 13-10 создаёт повторитель элементов вектора `v1`, вызывая способ `iter`, определённый у `Vec`. Сам по себе этот код не делает ничего полезного. +В Ржавчине повторители *ленивые (lazy)*, то есть они не делают ничего, пока вы не вызовете особые способы, потребляющие повторитель , чтобы задействовать его. Например, рукопись в приложении 13-10 создаёт повторитель элементов вектора `v1`, вызывая способ `iter`, определённый у `Vec`. Сам по себе этот рукопись не делает ничего полезного. ```rust {{#rustdoc_include ../listings/ch13-functional-features/listing-13-10/src/main.rs:here}} @@ -10,19 +10,19 @@ Приложение 13-10: Создание повторителя -Повторитель хранится в переменной `v1_iter`. Создав повторитель , мы можем использовать его различными способами. В приложении 3-5 главы 3 мы совершали обход элементов массива используя цикл `for` для выполнения какого-то кода над каждым из его элементов. Под капотом это неявно создавало, а затем потребляло повторитель , но до сих пор мы не касались того, как именно это работает. +Повторитель хранится в переменной `v1_iter`. Создав повторитель , мы можем использовать его различными способами. В приложении 3-5 главы 3 мы совершали обход элементов массива используя круговорот `for` для выполнения какого-то рукописи над каждым из его элементов. Под капотом это неявно создавало, а затем потребляло повторитель , но до сих пор мы не касались того, как именно это работает. -В примере из приложения 13-11 мы отделили создание повторителя от его использования в цикле for. В цикле for, использующем повторитель в v1_iter, каждый элемент повторителя участвует только в одной повторения цикла, в ходе которой выводится на экран его значение. +В примере из приложения 13-11 мы отделили создание повторителя от его использования в круговороте for. В круговороте for, использующем повторитель в v1_iter, каждый элемент повторителя участвует только в одной повторения круговорота, в ходе которой выводится на экран его значение. ```rust {{#rustdoc_include ../listings/ch13-functional-features/listing-13-11/src/main.rs:here}} ``` -Приложение 13-11: Использование повторителя в цикле for +Приложение 13-11: Использование повторителя в круговороте for -В языках, обычные библиотеки которых не предоставляют повторители, вы, скорее всего, напишите эту же возможность так: создадите переменную со значением 0 затем, в цикле, использовав её для получения элемента вектора по порядковому указателю, будете увеличивать её значение, и так, пока оно не достигнет числа равного количеству элементов в векторе. +В языках, обычные библиотеки которых не предоставляют повторители, вы, скорее всего, напишите эту же возможность так: создадите переменную со значением 0 затем, в круговороте, использовав её для получения элемента вектора по порядковому указателю, будете увеличивать её значение, и так, пока оно не достигнет числа равного количеству элементов в векторе. -Повторители выполняют всю эту логику за вас, сокращая количество повторяющегося кода, который возможно может быть написан неправильно. Повторители дают вам гибкость, позволяя использовать одинаковые принципы работы с различными видами последовательностей, а не только со устройствами данных, которые можно упорядочивать, например, векторами. Давайте рассмотрим, как повторители это делают. +Повторители выполняют всю эту ход мыслей за вас, сокращая количество повторяющегося рукописи, который возможно может быть написан неправильно. Повторители дают вам гибкость, позволяя использовать одинаковые принципы работы с различными видами последовательностей, а не только со устройствами данных, которые можно упорядочивать, например, векторами. Давайте рассмотрим, как повторители это делают. ### Особенность `Iterator` и способ `next` @@ -38,7 +38,7 @@ pub trait Iterator { } ``` -Обратите внимание данное объявление использует новый правила написания: `type Item` и `Self::Item`, которые определяют *сопряженный вид* (associated type) с этим особенностью. Мы подробнее поговорим о сопряженных видах в главе 19. Сейчас вам нужно знать, что этот код требует от выполнений особенности `Iterator` определить требуемый им вид `Item` и данный вид `Item` используется в способе `next`. Другими словами, вид `Item` будет являться видом элемента, который возвращает повторитель . +Обратите внимание данное объявление использует новый правила написания: `type Item` и `Self::Item`, которые определяют *сопряженный вид* (associated type) с этим особенностью. Мы подробнее поговорим о сопряженных видах в главе 19. Сейчас вам нужно знать, что этот рукопись требует от выполнений особенности `Iterator` определить требуемый им вид `Item` и данный вид `Item` используется в способе `next`. Другими словами, вид `Item` будет являться видом элемента, который возвращает повторитель . Особенность `Iterator` требует, чтобы разработчики определяли только один способ: способ `next`, который возвращает один элемент повторителя за раз обёрнутый в исход `Some` и когда повторение завершена, возвращает `None`. @@ -52,15 +52,15 @@ pub trait Iterator { Приложение 13-12: Вызов способа next повторителя -Обратите внимание, что нам нужно сделать переменную `v1_iter` изменяемой: вызов способа `next` повторителя изменяет внутреннее состояние повторителя, которое повторитель использует для отслеживания того, где он находится в последовательности. Другими словами, этот код *потребляет* (consume) или использует повторитель . Каждый вызов `next` потребляет элемент из повторителя. Нам не нужно было делать изменяемой `v1_iter` при использовании цикла `for`, потому что цикл забрал во владение `v1_iter` и сделал её изменяемой неявно для нас. +Обратите внимание, что нам нужно сделать переменную `v1_iter` изменяемой: вызов способа `next` повторителя изменяет внутреннее состояние повторителя, которое повторитель использует для отслеживания того, где он находится в последовательности. Другими словами, этот рукопись *потребляет* (consume) или использует повторитель . Каждый вызов `next` потребляет элемент из повторителя. Нам не нужно было делать изменяемой `v1_iter` при использовании круговорота `for`, потому что круговорот забрал во владение `v1_iter` и сделал её изменяемой неявно для нас. Заметьте также, что значения, которые мы получаем при вызовах `next` являются неизменяемыми ссылками на значения в векторе. Способ `iter` создаёт повторитель по неизменяемым ссылкам. Если мы хотим создать повторитель , который становится владельцем `v1` и возвращает принадлежащие ему значения, мы можем вызвать `into_iter` вместо `iter`. Точно так же, если мы хотим перебирать изменяемые ссылки, мы можем вызвать `iter_mut` вместо `iter`. ### Способы, которые потребляют повторитель -У особенности `Iterator` есть несколько способов, выполнение которых по умолчанию предоставляется встроенной библиотекой; вы можете узнать об этих способах, просмотрев документацию API встроенной библиотеки для `Iterator`. Некоторые из этих способов вызывают `next` в своём определении, поэтому вам необходимо выполнить способ `next` при выполнения особенности `Iterator`. +У особенности `Iterator` есть несколько способов, выполнение которых по умолчанию предоставляется встроенной библиотекой; вы можете узнать об этих способах, просмотрев пособие API встроенной библиотеки для `Iterator`. Некоторые из этих способов вызывают `next` в своём определении, поэтому вам необходимо выполнить способ `next` при выполнения особенности `Iterator`. -Способы, вызывающие `next`, называются *потребляющими переходниками*, поскольку их вызов потребляет повторитель . Примером может служить способ `sum`, который забирает во владение повторитель и перебирает элементы, многократно вызывая `next`, тем самым потребляя повторитель . В этапе повторения он добавляет каждый элемент к текущей сумме и возвращает итоговое значение по завершении повторения. В приложении 13-13 приведён проверка, отображающий использование способа `sum`: +Способы, вызывающие `next`, называются *потребляющими переходниками*, поскольку их вызов потребляет повторитель . Примером может служить способ `sum`, который забирает во владение повторитель и перебирает элементы, многократно вызывая `next`, тем самым потребляя повторитель . В этапе повторения он добавляет каждый элемент к текущей итогу сложения и возвращает итоговое значение по завершении повторения. В приложении 13-13 приведён проверка, отображающий использование способа `sum`: Файл: src/lib.rs @@ -68,7 +68,7 @@ pub trait Iterator { {{#rustdoc_include ../listings/ch13-functional-features/listing-13-13/src/lib.rs:here}} ``` -Приложение 13-13: Вызов способа sum для получения суммы всех элементов в повторителе +Приложение 13-13: Вызов способа sum для получения итога сложения всех элементов в повторителе Мы не можем использовать `v1_iter` после вызова способа `sum`, потому что `sum` забирает во владение повторитель у которого вызван способ. @@ -86,13 +86,13 @@ pub trait Iterator { Приложение 13-14: Вызов переходника повторителя map для создания нового повторителя -Однако этот код выдаёт предупреждение: +Однако этот рукопись выдаёт предупреждение: ```console {{#include ../listings/ch13-functional-features/listing-13-14/output.txt}} ``` -Код в приложении 13-14 ничего не делает; указанное нами замыкание никогда не вызывается. Предупреждение напоминает нам, почему: переходники повторителей ленивы, и здесь нам нужно потребить повторитель . +Рукопись в приложении 13-14 ничего не делает; указанное нами замыкание никогда не вызывается. Предупреждение напоминает нам, почему: переходники повторителей ленивы, и здесь нам нужно потребить повторитель . Чтобы устранить это предупреждение и потребить повторитель , мы воспользуемся способом `collect`, который мы использовали в главе 12 с `env::args` в приложении 12-1. Этот способ потребляет повторитель и собирает полученные значения в собрание указанного вида. diff --git a/rustbook-ru/src/ch13-03-improving-our-io-project.md b/rustbook-ru/src/ch13-03-improving-our-io-project.md index 5a332c6e8..0c3e3349b 100644 --- a/rustbook-ru/src/ch13-03-improving-our-io-project.md +++ b/rustbook-ru/src/ch13-03-improving-our-io-project.md @@ -1,10 +1,10 @@ ## Улучшение нашего дела с вводом/выводом -Вооружившись полученными знаниями об повторителях, мы можем улучшить выполнение работы с вводом/выводом в деле главы 12, применяя повторители для того, чтобы сделать некоторые места в коде более понятными и краткими. Давайте рассмотрим, как повторители могут улучшить нашу выполнение функции `Config::build` и функции `search`. +Вооружившись полученными знаниями об повторителях, мы можем улучшить выполнение работы с вводом/выводом в деле главы 12, применяя повторители для того, чтобы сделать некоторые места в рукописи более понятными и краткими. Давайте рассмотрим, как повторители могут улучшить нашу выполнение функции `Config::build` и функции `search`. ### Удаляем `clone`, используем повторитель -В приложении 12-6 мы добавили код, который принимает срез значений `String` и создаёт образец устройства `Config` путём упорядочевания среза и клонирования значений, позволяя устройстве `Config` владеть этими значениями. В приложении 13-17 мы воспроизвели выполнение функции `Config::build`, как это было в приложении 12-23: +В приложении 12-6 мы добавили рукопись, который принимает срез значений `String` и создаёт образец устройства `Config` путём упорядочевания среза и клонирования значений, позволяя устройстве `Config` владеть этими значениями. В приложении 13-17 мы воспроизвели выполнение функции `Config::build`, как это было в приложении 12-23: Файл: src/lib.rs @@ -14,11 +14,11 @@ Приложение 13-17: Репродукция функции Config::build из приложения 12-23 -Ранее мы говорили, что не стоит беспокоиться о неэффективных вызовах `clone`, потому что мы удалим их в будущем. Ну что же, время пришло! +Ранее мы говорили, что не стоит беспокоиться о бесполезных вызовах `clone`, потому что мы удалим их в будущем. Ну что же, время пришло! Нам понадобился здесь `clone`, потому что в свойстве `args` у нас срез с элементами `String`, но функция `build` не владеет `args`. Чтобы образец `Config` владел значениями, нам пришлось клонировать их из `args` в переменные `query` и `file_path`. -Благодаря нашим новым знаниям об повторителях мы можем изменить функцию `build`, чтобы вместо заимствования среза она принимала в качестве переменной повторитель . Мы будем использовать возможность повторителя вместо кода, который проверяет длину среза и обращается по порядковому указателю к определённым значениям. Это позволит лучше понять, что делает функция `Config::build`, поскольку повторитель будет обращаться к значениям. +Благодаря нашим новым знаниям об повторителях мы можем изменить функцию `build`, чтобы вместо заимствования среза она принимала в качестве переменной повторитель . Мы будем использовать возможность повторителя вместо рукописи, который проверяет длину среза и обращается по порядковому указателю к определённым значениям. Это позволит лучше понять, что делает функция `Config::build`, поскольку повторитель будет обращаться к значениям. Как только `Config::build` получит в своё распоряжение повторитель и перестанет использовать действия упорядочевания с заимствованием, мы сможем переместить значения `String` из повторителя в `Config` вместо того, чтобы вызывать `clone` и создавать новое выделение памяти. @@ -32,7 +32,7 @@ {{#rustdoc_include ../listings/ch13-functional-features/listing-12-24-reproduced/src/main.rs:ch13}} ``` -Сначала мы изменим начало функции `main`, которая была в приложении 12-24, на код в приложении 13-18, который теперь использует повторитель . Это не будет собираться, пока мы не обновим `Config::build`. +Сначала мы изменим начало функции `main`, которая была в приложении 12-24, на рукопись в приложении 13-18, который теперь использует повторитель . Это не будет собираться, пока мы не обновим `Config::build`. Файл: src/main.rs @@ -54,7 +54,7 @@ Приложение 13-19: Обновление ярлыки Config::build для определения повторителя как ожидаемого свойства -Документация встроенной библиотеки для функции `env::args` показывает, что вид возвращаемого ею повторителя - `std::env::Args`, и этот вид выполняет признак `Iterator` и возвращает значения `String`. +Пособие встроенной библиотеки для функции `env::args` показывает, что вид возвращаемого ею повторителя - `std::env::Args`, и этот вид выполняет признак `Iterator` и возвращает значения `String`. Мы обновили ярлык функции `Config::build`, чтобы свойство `args` имел гибкий вид ограниченный особенностью `impl Iterator` вместо `&[String]`. Такое использование правил написания `impl Trait`, который мы обсуждали в разделе [" Особенности как свойства"] главы 10, означает, что `args` может быть любым видом, выполняющим вид `Iterator` и возвращающим элементы `String`. @@ -62,7 +62,7 @@ #### Использование способов особенности `Iterator` вместо порядковых указателей -Далее мы подправим содержимое `Config::build`. Поскольку `args` выполняет признак `Iterator`, мы знаем, что можем вызвать у него способ `next`! В приложении 13-20 код из приложения 12-23 обновлён для использования способа `next`: +Далее мы подправим содержимое `Config::build`. Поскольку `args` выполняет признак `Iterator`, мы знаем, что можем вызвать у него способ `next`! В приложении 13-20 рукопись из приложения 12-23 обновлён для использования способа `next`: Файл: src/lib.rs @@ -74,7 +74,7 @@ Помните, что первое значение в возвращаемых данных `env::args` - это имя программы. Мы хотим пренебрегать его и перейти к следующему значению, поэтому сперва мы вызываем `next` и ничего не делаем с возвращаемым значением. Затем мы вызываем `next`, чтобы получить значение, которое мы хотим поместить в поле `query` в `Config`. Если `next` возвращает `Some`, мы используем `match` для извлечения значения. Если возвращается `None`, это означает, что было задано недостаточно переменных, и мы досрочно возвращаем значение `Err`. То же самое мы делаем для значения `file_path`. -### Делаем код понятнее с помощью переходников повторителей +### Делаем рукопись понятнее с помощью переходников повторителей Мы также можем воспользоваться преимуществами повторителей в функции `search` в нашем деле с действиеми ввода-вывода, которая воспроизведена здесь в приложении 13-21 так же, как и в приложении 12-19: @@ -86,7 +86,7 @@ Приложение 13-21: Выполнение функции search из приложения 12-19 -Мы можем написать этот код в более сжатом виде, используя способы переходника повторителя. Это также позволит нам избежать наличия изменяемого временного вектора `results`. Функциональный исполнение программирования предпочитает уменьшить количество изменяемого состояния, чтобы сделать код более понятным. Удаление изменяемого состояния может позволить в будущем сделать поиск одновременным, поскольку нам не придётся управлять одновременным доступом к вектору `results`. В приложении 13-22 показано это изменение: +Мы можем написать этот рукопись в более сжатом виде, используя способы переходника повторителя. Это также позволит нам избежать наличия изменяемого временного вектора `results`. Функциональный исполнение программирования предпочитает уменьшить количество изменяемого состояния, чтобы сделать рукопись более понятным. Удаление изменяемого состояния может позволить в будущем сделать поиск одновременным, поскольку нам не придётся управлять одновременным доступом к вектору `results`. В приложении 13-22 показано это изменение: Файл: src/lib.rs @@ -96,13 +96,13 @@ Приложение 13-22: Использование способов переходника повторителя в выполнения функции search -Напомним, что назначение функции `search` - вернуть все строки в `contents`, которые содержат `query`. Подобно примеру `filter` в приложении 13-16, этот код использует переходник `filter`, чтобы сохранить только те строки, для которых `line.contains(query)` возвращает `true`. Затем мы собираем совпадающие строки в другой вектор с помощью `collect`. Так гораздо проще! Не стесняйтесь сделать такое же изменение для использования способов повторителя в функции `search_case_insensitive`. +Напомним, что назначение функции `search` - вернуть все строки в `contents`, которые содержат `query`. Подобно примеру `filter` в приложении 13-16, этот рукопись использует переходник `filter`, чтобы сохранить только те строки, для которых `line.contains(query)` возвращает `true`. Затем мы собираем совпадающие строки в другой вектор с помощью `collect`. Так гораздо проще! Не стесняйтесь сделать такое же изменение для использования способов повторителя в функции `search_case_insensitive`. -### Выбор между циклами или повторителями +### Выбор между круговоротами или повторителями -Следующий логичный вопрос - какой исполнение вы должны выбрать в своём коде и почему: подлинную выполнение в приложении 13-21 или исполнение с использованием повторителей в приложении 13-22. Большинство программистов на языке Ржавчина предпочитают использовать исполнение повторителей. Сначала разобраться с ним немного сложно, но как только вы почувствуете, что такое различные переходники повторителей и что они делают, понять повторители станет проще. Вместо того чтобы возиться с различными элементами цикла и создавать новые векторы, код сосредотачивается на высокоуровневой цели цикла. Это абстрагирует часть обычного кода, поэтому легче увидеть подходы, единственные для этого кода, такие как условие выборки, которое должен пройти каждый элемент в повторителе. +Следующий здоровый вопрос - какой исполнение вы должны выбрать в своей рукописи и почему: подлинную выполнение в приложении 13-21 или исполнение с использованием повторителей в приложении 13-22. Большинство программистов на языке Ржавчина предпочитают использовать исполнение повторителей. Сначала разобраться с ним немного сложно, но как только вы почувствуете, что такое различные переходники повторителей и что они делают, понять повторители станет проще. Вместо того чтобы возиться с различными элементами круговорота и создавать новые векторы, рукопись сосредотачивается на высокоуровневой цели круговорота. Это абстрагирует часть обычного рукописи, поэтому легче увидеть подходы, единственные для этого рукописи, такие как условие выборки, которое должен пройти каждый элемент в повторителе. -Но действительно ли эти две выполнения эквивалентны? Интуитивно можно предположить, что более низкоуровневый цикл будет быстрее. Давайте поговорим о производительности. +Но действительно ли эти две выполнения эквивалентны? Интуитивно можно предположить, что более низкоуровневый круговорот будет быстрее. Давайте поговорим о производительности. [" Особенности как свойства"]: ch10-02-traits.html#traits-as-parameters \ No newline at end of file diff --git a/rustbook-ru/src/ch13-04-performance.md b/rustbook-ru/src/ch13-04-performance.md index f872a5f1a..bc43df478 100644 --- a/rustbook-ru/src/ch13-04-performance.md +++ b/rustbook-ru/src/ch13-04-performance.md @@ -1,21 +1,21 @@ -## Сравнение производительности циклов и повторителей +## Сравнение производительности круговоротов и повторителей -Чтобы определить, что лучше использовать циклы или повторители, нужно знать, какая выполнение быстрее: исполнение функции `search` с явным циклом `for` или исполнение с повторителями. +Чтобы определить, что лучше использовать круговороты или повторители, нужно знать, какая выполнение быстрее: исполнение функции `search` с явным круговоротом `for` или исполнение с повторителями. -Мы выполнили проверка производительности, разместив всё содержимое книги *(“The Adventures of Sherlock Holmes” by Sir Arthur Conan Doyle)* в строку вида `String` и поискали слово *the* в её содержимом. Вот итоги проверки функции `search` с использованием цикла `for` и с использованием повторителей: +Мы выполнили проверка производительности, разместив всё содержимое книги *(“The Adventures of Sherlock Holmes” by Sir Arthur Conan Doyle)* в строку вида `String` и поискали слово *the* в её содержимом. Вот итоги проверки функции `search` с использованием круговорота `for` и с использованием повторителей: ```text test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700) test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200) ``` -Исполнение с использованием повторителей была немного быстрее! Мы не будем приводить здесь непосредственно код проверки, поскольку мысль не в том, чтобы доказать, что решения в точности эквивалентны, а в том, чтобы получить общее представление о том, как эти две выполнения близки по производительности. +Исполнение с использованием повторителей была немного быстрее! Мы не будем приводить здесь непосредственно рукопись проверки, поскольку мысль не в том, чтобы доказать, что решения в точности эквивалентны, а в том, чтобы получить общее представление о том, как эти две выполнения близки по производительности. -Для более исчерпывающего проверки, вам нужно проверить различные тексты разных размеров в качестве содержимого для `contents`, разные слова и слова различной длины в качестве `query` и всевозможные другие исходы. Дело в том, что повторители, будучи высокоуровневой абстракцией, собираются примерно в тот же код, как если бы вы написали его низкоуровневый исход самостоятельно. Повторители - это одна из *абстракций с нулевой стоимостью* ( zero-cost abstractions ) в Rust, под которой мы подразумеваем, что использование абстракции не накладывает дополнительных расходов во время выполнения. Подобно тому, как Бьёрн Страуструп, внешнем видер и разработчик C++, определяет *нулевые накладные расходы* ( zero-overhead ) в книге “Foundations of C++” (2012): +Для более исчерпывающего проверки, вам нужно проверить различные тексты разных размеров в качестве содержимого для `contents`, разные слова и слова различной длины в качестве `query` и всевозможные другие исходы. Дело в том, что повторители, будучи высокоуровневой абстракцией, собираются примерно в тот же рукопись, как если бы вы написали его низкоуровневый исход самостоятельно. Повторители - это одна из *абстракций с нулевой стоимостью* ( zero-cost abstractions ) в Ржавчине под которой мы подразумеваем, что использование абстракции не накладывает дополнительных расходов во время выполнения. Подобно тому, как Бьёрн Страуструп, внешнем видер и разработчик C++, определяет *нулевые накладные расходы* ( zero-overhead ) в книге “Foundations of C++” (2012): -> В целом, выполнение C++ подчиняется принципу отсутствия накладных расходов: за то, чем вы не пользуетесь, платить не нужно. И далее: тот код, что вы используете, нельзя сделать ещё лучше. +> В целом, выполнение C++ подчиняется принципу отсутствия накладных расходов: за то, чем вы не пользуетесь, платить не нужно. И далее: тот рукопись, что вы используете, нельзя сделать ещё лучше. -В качестве другого примера приведём код, взятый из аудио декодера. Алгоритм декодирования использует математическую действие линейного предсказания для оценки будущих значений на основе линейной функции предыдущих выборок. Код использует соединение вызовов повторителя для выполнения математических вычислений для трёх переменных в области видимости: срез данных `buffer`, массив из 12 коэффициентов `coefficients` и число для сдвига данных в переменной `qlp_shift`. Переменные определены в примере, но не имеют начальных значений. Хотя этот код не имеет большого значения вне среды, он является кратким, существующим примером того, как Ржавчина переводит мысли высокого уровня в код низкого уровня. +В качестве другого примера приведём рукопись, взятый из аудио декодера. Алгоритм декодирования использует математическую действие линейного предсказания для оценки будущих значений на основе линейной функции предыдущих выборок. Рукопись использует соединение вызовов повторителя для выполнения математических вычислений для трёх переменных в области видимости: срез данных `buffer`, массив из 12 множителей `coefficients` и число для сдвига данных в переменной `qlp_shift`. Переменные определены в примере, но не имеют начальных значений. Хотя этот рукопись не имеет большого значения вне среды, он является кратким, существующим примером того, как Ржавчина переводит мысли высокого уровня в рукопись низкого уровня. ```rust,ignore let buffer: &mut [i32]; @@ -32,14 +32,14 @@ for i in 12..buffer.len() { } ``` -Чтобы вычислить значение переменной `prediction`, этот код перебирает каждое из 12 значений в переменной `coefficients` и использует способ `zip` для объединения значений коэффициентов с предыдущими 12 значениями в переменной `buffer`. Затем, для каждой пары мы перемножаем значения, суммируем все итоги и у суммы сдвигаем биты вправо в переменную `qlp_shift`. +Чтобы вычислить значение переменной `prediction`, этот рукопись перебирает каждое из 12 значений в переменной `coefficients` и использует способ `zip` для объединения значений множителей с предыдущими 12 значениями в переменной `buffer`. Затем, для каждой пары мы перемножаем значения, складываем все итоги и у итога сложения сдвигаем биты вправо в переменную `qlp_shift`. -Для вычислений в таких приложениях, как аудио декодеры, часто требуется производительность. Здесь мы создаём повторитель , используя два переходника, впоследствии потребляющих значение. В какой ассемблерный код будет собираться этот код на Rust? На мгновение написания этой главы он собирается в то же самое, что вы написали бы руками. Не существует цикла, соответствующего повторения по значениям в «коэффициентах»`coefficients`: Ржавчина знает, что существует двенадцать повторений, поэтому он «разворачивает» цикл. *Разворачивание* - это улучшение, которая устраняет издержки кода управления циклом и вместо этого порождает повторяющийся код для каждой повторения цикла. +Для вычислений в таких приложениях, как аудио декодеры, часто требуется производительность. Здесь мы создаём повторитель , используя два переходника, впоследствии потребляющих значение. В какой ассемблерный рукопись будет собираться этот рукопись на Rust? На мгновение написания этой главы он собирается в то же самое, что вы написали бы руками. Не существует круговорота, соответствующего повторения по значениям в «множителях»`coefficients`: Ржавчина знает, что существует двенадцать повторений, поэтому он «разворачивает» круговорот. *Разворачивание* - это улучшение, которая устраняет издержки рукописи управления круговоротом и вместо этого порождает повторяющийся рукопись для каждой повторения круговорота. -Все коэффициенты сохраняются в регистрах, что означает очень быстрый доступ к значениям. Нет никаких проверок границ доступа к массиву во время выполнения. Все эти переработки, которые может применить Rust, делают полученный код чрезвычайно эффективным. Теперь, когда вы это знаете, используйте повторители и замыкания без страха! Они представляют код в более высокоуровневом виде, но без потери производительности во время выполнения. +Все множители сохраняются в регистрах, что означает очень быстрый доступ к значениям. Нет никаких проверок границ доступа к массиву во время выполнения. Все эти переработки, которые может применить Ржавчина делают полученный рукопись чрезвычайно производительным. Теперь, когда вы это знаете, используйте повторители и замыкания без страха! Они представляют рукопись в более высокоуровневом виде, но без потери производительности во время выполнения. ## Итоги -Замыкания (closures) и повторители (iterators) это возможности Rust, вдохновлённые мыслями полезных языков. Они позволяют Ржавчина ясно выражать мысли высокого уровня с производительностью низкоуровневого кода. Выполнения замыканий и повторителей таковы, что нет влияния на производительность выполнения кода. Это одна из целей Rust, направленных на обеспечение абстракций с нулевой стоимостью (zero-cost abstractions). +Замыкания (closures) и повторители (iterators) это возможности Ржавчины, вдохновлённые мыслями полезных языков. Они позволяют Ржавчина ясно выражать мысли высокого уровня с производительностью низкоуровневого рукописи. Выполнения замыканий и повторителей таковы, что нет влияния на производительность выполнения рукописи. Это одна из целей Ржавчина направленных на обеспечение абстракций с нулевой стоимостью (zero-cost abstractions). -Теперь, когда мы улучшили представление кода в нашем деле, рассмотрим некоторые возможности, которые нам предоставляет `cargo` для обнародования нашего кода в хранилища. +Теперь, когда мы улучшили представление рукописи в нашем деле, рассмотрим некоторые возможности, которые нам предоставляет `cargo` для обнародования нашего рукописи в хранилища. diff --git a/rustbook-ru/src/ch14-00-more-about-cargo.md b/rustbook-ru/src/ch14-00-more-about-cargo.md index df687287b..92b63489b 100644 --- a/rustbook-ru/src/ch14-00-more-about-cargo.md +++ b/rustbook-ru/src/ch14-00-more-about-cargo.md @@ -1,6 +1,6 @@ # Больше о Cargo и Crates.io -До сих пор мы использовали только самые основные возможности Cargo для сборки, запуска и проверки нашего кода, но он может гораздо больше. В этой главе мы обсудим некоторые другие, более продвинутые возможности, чтобы показать вам, как делать следующее: +До сих пор мы использовали только самые основные возможности Cargo для сборки, запуска и проверки нашего рукописи, но он может гораздо больше. В этой главе мы обсудим некоторые другие, более продвинутые возможности, чтобы показать вам, как делать следующее: - Настройка сборки с помощью готовых профилей - Обнародование библиотеки на [crates.io](https://crates.io/) @@ -8,4 +8,4 @@ - Установка двоичных файлов с [crates.io](https://crates.io/) - Расширение возможностей Cargo с помощью возможности добавления собственных приказов -Cargo может делать значительно больше того, что мы рассмотрим в этой главе, полное описание всех его функций см. в [документации](https://doc.rust-lang.org/cargo/). +Cargo может делать значительно больше того, что мы рассмотрим в этой главе, полное описание всех его функций см. в [пособия](https://doc.rust-lang.org/cargo/). diff --git a/rustbook-ru/src/ch14-01-release-profiles.md b/rustbook-ru/src/ch14-01-release-profiles.md index 1c0c5ed1c..c6d43a360 100644 --- a/rustbook-ru/src/ch14-01-release-profiles.md +++ b/rustbook-ru/src/ch14-01-release-profiles.md @@ -1,6 +1,6 @@ ## Настройка сборок с профилями исполнений -В Ржавчина *профили выпуска* — это предопределённые и настраиваемые профили с различными настройками, которые позволяют программисту лучше управлять различные свойства сборки кода. Каждый профиль настраивается независимо от других. +В Ржавчине *профили выпуска* — это предопределённые и настраиваемые профили с различными настройками, которые позволяют программисту лучше управлять различные свойства сборки рукописи. Каждый профиль настраивается независимо от других. Cargo имеет два основных профиля: профиль `dev`, используемый Cargo при запуске `cargo build`, и профиль `release`, используемый Cargo при запуске `cargo build --release`. Профиль `dev` определён со значениями по умолчанию для разработки, а профиль `release` имеет значения по умолчанию для сборок в исполнение. @@ -34,7 +34,7 @@ opt-level = 0 opt-level = 3 ``` -Свойство `opt-level` управляет количеством переработок, которые Ржавчина будет применять к вашему коду, в ряде от 0 до 3. Использование большего количества переработок увеличивает время сборки, поэтому если вы находитесь в этапе разработки и часто собираете свой код, целесообразно использовать меньшее количество переработок, чтобы сборка происходила быстрее, даже если в итоге код будет работать медленнее. Поэтому `opt-level` по умолчанию для `dev` установлен в `0`. Когда вы готовы обнародовать свой код, то лучше потратить больше времени на сборку. Вы собираете программу в режиме исполнения только один раз, но выполняться она будет многократно, так что использование режима исполнения позволяет увеличить скорость выполнения кода за счёт времени сборки. Вот почему по умолчанию `opt-level` для профиля `release` равен `3`. +Свойство `opt-level` управляет количеством переработок, которые Ржавчина будет применять к вашему рукописи, в ряде от 0 до 3. Использование большего количества переработок увеличивает время сборки, поэтому если вы находитесь в этапе разработки и часто собираете свой рукопись, целесообразно использовать меньшее количество переработок, чтобы сборка происходила быстрее, даже если в итоге рукопись будет работать медленнее. Поэтому `opt-level` по умолчанию для `dev` установлен в `0`. Когда вы готовы обнародовать свой рукопись, то лучше потратить больше времени на сборку. Вы собираете программу в режиме исполнения только один раз, но выполняться она будет многократно, так что использование режима исполнения позволяет увеличить скорость выполнения рукописи за счёт времени сборки. Вот почему по умолчанию `opt-level` для профиля `release` равен `3`. Вы можете переопределить настройки по умолчанию, добавив другое значение для них в *Cargo.toml*. Например, если мы хотим использовать уровень переработки 1 в профиле разработки, мы можем добавить эти две строки в файл *Cargo.toml* нашего дела: @@ -45,6 +45,6 @@ opt-level = 3 opt-level = 1 ``` -Этот код переопределяет настройку по умолчанию `0`. Теперь, когда мы запустим `cargo build`, Cargo будет использовать значения по умолчанию для профиля `dev` плюс нашу настройку для `opt-level`. Поскольку мы установили для `opt-level` значение `1`, Cargo будет применять больше переработок, чем было задано по умолчанию, но не так много, как при сборке исполнения. +Этот рукопись переопределяет настройку по умолчанию `0`. Теперь, когда мы запустим `cargo build`, Cargo будет использовать значения по умолчанию для профиля `dev` плюс нашу настройку для `opt-level`. Поскольку мы установили для `opt-level` значение `1`, Cargo будет применять больше переработок, чем было задано по умолчанию, но не так много, как при сборке исполнения. -Полный список свойств настройке и значений по умолчанию для каждого профиля вы можете найти в [документации Cargo](https://doc.rust-lang.org/cargo/reference/profiles.html). +Полный список свойств настройки и значений по умолчанию для каждого профиля вы можете найти в [пособия Cargo](https://doc.rust-lang.org/cargo/reference/profiles.html). diff --git a/rustbook-ru/src/ch14-02-publishing-to-crates-io.md b/rustbook-ru/src/ch14-02-publishing-to-crates-io.md index f11f5c2c4..6e86607de 100644 --- a/rustbook-ru/src/ch14-02-publishing-to-crates-io.md +++ b/rustbook-ru/src/ch14-02-publishing-to-crates-io.md @@ -1,14 +1,14 @@ ## Обнародование библиотеки в Crates.io -Мы использовали дополнения из [crates.io](https://crates.io/) в качестве зависимостей нашего дела, но вы также можете поделиться своим кодом с другими людьми, обнародовав свои собственные дополнения. Реестр библиотек по адресу [crates.io](https://crates.io/) распространяет исходный код ваших дополнений, поэтому он в основном размещает код с открытым исходным кодом. +Мы использовали дополнения из [crates.io](https://crates.io/) в качестве зависимостей нашего дела, но вы также можете поделиться своим рукописью с другими людьми, обнародовав свои собственные дополнения. Реестр библиотек по адресу [crates.io](https://crates.io/) распространяет исходную рукопись ваших дополнений, поэтому он в основном размещает рукопись с открытым исходной рукописью. -В Ржавчина и Cargo есть функции, которые облегчают поиск и использование обнародованного дополнения. Далее мы поговорим о некоторых из этих функций, а затем объясним, как обнародовать дополнение. +В Ржавчине и Cargo есть функции, которые облегчают поиск и использование обнародованного дополнения. Далее мы поговорим о некоторых из этих функций, а затем объясним, как обнародовать дополнение. -### Создание полезных примечаниев к документации +### Создание полезных примечаниев к пособия -Правильноное документирование ваших дополнений поможет другим пользователям знать, как и когда их использовать, поэтому стоит потратить время на написание документации. В главе 3 мы обсуждали, как вносить примечания в код Rust, используя две косые черты, `//`. В Ржавчина также есть особый вид примечаниев к документации, который обычно называется *примечанием к документации*, который порождает документацию HTML. HTML-код отображает содержимое примечаниев к документации для открытых элементов API, предназначенных для программистов, увлеченных в знании того, как *использовать* вашу библиотеку, в отличие от того, как она *выполнена*. +Правильноное документирование ваших дополнений поможет другим пользователям знать, как и когда их использовать, поэтому стоит потратить время на написание пособия. В главе 3 мы обсуждали, как вносить примечания в рукопись Ржавчина, используя две косые черты, `//`. В Ржавчине также есть особый вид примечаниев к пособия, который обычно называется *примечанием к пособия*, который порождает пособие HTML. HTML-рукопись отображает содержимое примечаниев к пособия для открытых элементов API, предназначенных для программистов, увлеченных в знании того, как *использовать* вашу библиотеку, в отличие от того, как она *выполнена*. -Примечания к документации используют три слеша, `///` вместо двух и поддерживают наставление Markdown для изменения текста. Размещайте примечания к документации непосредственно перед элементом, который они документируют. В приложении 14-1 показаны примечания к документации для функции `add_one` в библиотеке с именем `my_crate`: +Примечания к пособия используют три слеша, `///` вместо двух и поддерживают наставление Markdown для изменения текста. Размещайте примечания к пособия непосредственно перед элементом, который они документируют. В приложении 14-1 показаны примечания к пособия для функции `add_one` в библиотеке с именем `my_crate`: Файл: src/lib.rs @@ -16,29 +16,29 @@ {{#rustdoc_include ../listings/ch14-more-about-cargo/listing-14-01/src/lib.rs}} ``` -Приложение 14-1: Примечание к документации для функции +Приложение 14-1: Примечание к пособия для функции -Здесь мы даём описание того, что делает функция `add_one`, начинаем раздел с заголовка `Examples`, а затем предоставляем код, который отображает, как использовать функцию `add_one`. Мы можем создать документацию HTML из этого примечания к документации, запустив `cargo doc`. Этот приказ запускает средство `rustdoc`, поставляемый с Rust, и помещает созданную HTML-документацию в папка *target/doc*. +Здесь мы даём описание того, что делает функция `add_one`, начинаем раздел с заголовка `Examples`, а затем предоставляем рукопись, который отображает, как использовать функцию `add_one`. Мы можем создать пособие HTML из этого примечания к пособия, запустив `cargo doc`. Этот приказ запускает средство `rustdoc`, поставляемый с Ржавчина, и помещает созданную HTML-пособие в папка *target/doc*. -Для удобства, запустив `cargo doc --open`, мы создадим HTML для документации вашей текущей библиотеки (а также документацию для всех зависимостей вашей библиотеки) и откроем итог в веб-браузере. Перейдите к функции `add_one` и вы увидите, как отображается текст в примечаниях к документации, что показано на рисунке 14-1: +Для удобства, запустив `cargo doc --open`, мы создадим HTML для пособия вашей текущей библиотеки (а также пособие для всех зависимостей вашей библиотеки) и откроем итог в сетевого-обозревателе. Перейдите к функции `add_one` и вы увидите, как отображается текст в примечаниях к пособия, что показано на рисунке 14-1: - HTML-документация для функции `add_one`` my_crate` + HTML-пособие для функции `add_one`` my_crate` -Рисунок 14-1: HTML документация для функции add_one +Рисунок 14-1: HTML пособие для функции add_one #### Часто используемые разделы -Мы использовали Markdown заголовок `# Examples` в приложении 14-1 для создания раздела в HTML с заголовком "Examples". Вот некоторые другие разделы, которые авторы библиотек обычно используют в своей документации: +Мы использовали Markdown заголовок `# Examples` в приложении 14-1 для создания раздела в HTML с заголовком "Examples". Вот некоторые другие разделы, которые составители библиотек обычно используют в своей пособия: -- **Panics**: Сценарии, в которых документированная функция может вызывать панику. Вызывающие функцию, которые не хотят, чтобы их программы паниковали, должны убедиться, что они не вызывают функцию в этих случаейх. -- **Ошибки**: Если функция возвращает `Result`, описание видов ошибок, которые могут произойти и какие условия могут привести к тому, что эти ошибки могут быть возвращены, может быть полезным для вызывающих, так что они могут написать код для обработки различных видов ошибок разными способами. +- **Panics**: Задумки, в которых документированная функция может вызывать сбой. Вызывающие функцию, которые не хотят, чтобы их программы вызвал сбойи, должны убедиться, что они не вызывают функцию в этих случаях. +- **Ошибки**: Если функция возвращает `Result`, описание видов ошибок, которые могут произойти и какие условия могут привести к тому, что эти ошибки могут быть возвращены, может быть полезным для вызывающих, так что они могут написать рукопись для обработки различных видов ошибок разными способами. - **Безопасность**: Если функция является `unsafe` для вызова (мы обсуждаем безопасность в главе 19), должен быть раздел, объясняющий, почему функция небезопасна и охватывающий неизменные величины, которые функция ожидает от вызывающих сторон. -В подавляющем большинстве случаев примечания к документации не нуждаются во всех этих разделах, но это хорошая подсказка, напоминающая вам о тех особенностях вашего кода, о которых пользователям будет важно узнать. +В подавляющем большинстве случаев примечания к пособия не нуждаются во всех этих разделах, но это хорошая подсказка, напоминающая вам о тех особенностях вашей рукописи, о которых пользователям будет важно узнать. -#### Примечания к документации как проверки +#### Примечания к пособия как проверки -Добавление примеров кода в примечания к документации может помочь отобразить, как использовать вашу библиотеку, и это даёт дополнительный бонус: запуск `cargo test` запустит примеры кода в вашей документации как проверки! Нет ничего лучше, чем документация с примерами. Но нет ничего хуже, чем примеры, которые не работают, потому что код изменился с особенности написания документации. Если мы запустим `cargo test` с документацией для функции `add_one` из приложения 14-1, мы увидим раздел итогов проверки, подобный этому: +Добавление примеров рукописи в примечания к пособия может помочь отобразить, как использовать вашу библиотеку, и это даёт дополнительный бонус: запуск `cargo test` запустит примеры рукописи в вашей пособия как проверки! Нет ничего лучше, чем пособие с примерами. Но нет ничего хуже, чем примеры, которые не работают, потому что рукопись изменился с особенности написания пособия. Если мы запустим `cargo test` с пособием для функции `add_one` из приложения 14-1, мы увидим раздел итогов проверки, подобный этому: для использования другими. -Будьте осторожны, потому что обнародование является *перманентной* действием. Исполнение никогда не сможет быть перезаписана, а код не подлежит удалению. Одна из основных целей [crates.io](https://crates.io/) - служить постоянным архивом кода, чтобы сборки всех дел, зависящих от crates из [crates.io](https://crates.io/) продолжали работать. Предоставление возможности удаления исполнений сделало бы выполнение этой цели невозможным. При этом количество исполнений ящиков, которые вы можете обнародовать, не ограничено. +Будьте осторожны, потому что обнародование является *перманентной* действием. Исполнение никогда не сможет быть перезаписана, а рукопись не подлежит удалению. Одна из основных целей [crates.io](https://crates.io/) - служить постоянным архивом рукописи, чтобы сборки всех дел, зависящих от crates из [crates.io](https://crates.io/) продолжали работать. Предоставление возможности удаления исполнений сделало бы выполнение этой цели невозможным. При этом количество исполнений ящиков, которые вы можете обнародовать, не ограничено. Запустите приказ `cargo publish` ещё раз. Сейчас эта приказ должна выполниться успешно: @@ -250,9 +250,9 @@ $ cargo publish Uploading guessing_game v0.1.0 (file:///projects/guessing_game) ``` -Поздравляем! Теперь вы поделились своим кодом с сообществом Ржавчина и любой может легко добавить вашу библиотеку в качестве зависимости их дела. +Поздравляем! Теперь вы поделились своим рукописью с сообществом Ржавчина и любой может легко добавить вашу библиотеку в качестве зависимости их дела. -### Обнародование новой исполнения существующей библиотеки +### Обнародование нового исполнения существующей библиотеки Когда вы внесли изменения в свой ящик и готовы выпустить новую исполнение, измените значение `version`, указанное в вашем файле *Cargo.toml* и повторите размещение. Воспользуйтесь [Semantic Versioning rules], чтобы решить, какой номер следующей исполнения подходит для ваших изменений. Затем запустите `cargo publish`, чтобы загрузить новую исполнение. @@ -262,7 +262,7 @@ $ cargo publish ### Устранение устаревших исполнений с Crates.io с помощью `cargo yank` -Хотя вы не можете удалить предыдущие исполнения ящика, вы можете помешать любым будущим делам добавлять его в качестве новой зависимости. Это полезно, когда исполнение ящика сломана по той или иной причине. В таких случаейх Cargo поддерживает *выламывание* (yanking) исполнения ящика. +Хотя вы не можете удалить предыдущие исполнения ящика, вы можете помешать любым будущим делам добавлять его в качестве новой зависимости. Это полезно, когда исполнение ящика сломана по той или иной причине. В таких случаях Cargo поддерживает *выламывание* (yanking) исполнения ящика. Вычёркивание исполнения не позволяет новым делам зависеть от этой исполнения, но при этом позволяет всем существующим делам, зависящим от неё, продолжать работу. По сути, исключение означает, что все дела с *Cargo.lock* не сломаются, а любые файлы *Cargo.lock*, которые будут порождаться в будущем, не смогут использовать исключённую исполнение. @@ -287,7 +287,7 @@ $ cargo yank --vers 1.0.1 --undo Unyank guessing_game@1.0.1 ``` -Вычёркивание *не удаляет* код. Оно не может, например, удалить случайно загруженные пароли. Если это произойдёт, вы должны немедленно сбросить эти пароли. +Вычёркивание *не удаляет* рукопись. Оно не может, например, удалить случайно загруженные пароли. Если это произойдёт, вы должны немедленно сбросить эти пароли. [Linux Foundation's Software Package Data Exchange (SPDX)]: http://spdx.org/licenses/ diff --git a/rustbook-ru/src/ch14-03-cargo-workspaces.md b/rustbook-ru/src/ch14-03-cargo-workspaces.md index 1e8cf6b2c..c5d02d88a 100644 --- a/rustbook-ru/src/ch14-03-cargo-workspaces.md +++ b/rustbook-ru/src/ch14-03-cargo-workspaces.md @@ -1,10 +1,10 @@ ## Рабочие пространства Cargo -В главе 12 мы создали дополнение, который включал в себя двоичный и библиотечный ящики. По мере развития вашего дела может возникнуть случаей, когда библиотечный ящик будет становиться все больше, и вы захотите разделить ваш дополнение на несколько библиотечных ящиков. Cargo предоставляет возможность под названием *workspaces*, которая помогает управлять несколькими взаимосвязанными дополнениями, которые разрабатываются в тандеме. +В главе 12 мы создали дополнение, который включал в себя двоичный и библиотечный ящики. По мере развития вашего дела может возникнуть случай, когда библиотечный ящик будет становиться все больше, и вы захотите разделить ваш дополнение на несколько библиотечных ящиков. Cargo предоставляет возможность под названием *workspaces*, которая помогает управлять несколькими взаимосвязанными дополнениями, которые разрабатываются в тандеме. ### Создание рабочего пространства -*Workspace* - это набор дополнений, которые используют один и тот же *Cargo.lock* и папку для хранения итогов сборки. Давайте создадим дело с использованием *workspace* - мы будем использовать обыкновенный код, чтобы сосредоточиться на устройстве рабочего пространства. Существует несколько способов внутренне выстроить +*Workspace* - это набор дополнений, которые используют один и тот же *Cargo.lock* и папку для хранения итогов сборки. Давайте создадим дело с использованием *workspace* - мы будем использовать обыкновенный рукопись, чтобы сосредоточиться на устройстве рабочего пространства. Существует несколько способов внутренне выстроить рабочую область, но мы покажем только один из них. У нас будет рабочая область, содержащая двоичный файл и две библиотеки. Двоичный файл, который обеспечивает основную возможность, будет зависеть от двух библиотек. Одна библиотека предоставит функцию `add_one`, а вторая - `add_two`. Эти три ящика будут частью одного *workspace*. Начнём с создания папки для рабочего окружения: @@ -147,7 +147,7 @@ $ cargo run -p adder Hello, world! 10 plus one is 11! ``` -Запуск кода из *adder/src/main.rs*, который зависит от `add_one`. +Запуск рукописи из *adder/src/main.rs*, который зависит от `add_one`. #### Зависимость от внешних ящиков в рабочем пространстве @@ -260,9 +260,9 @@ running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` -Первая раздел вывода показывает, что проверка `it_works` в ящике `add_one` прошёл. Следующая раздел показывает, что в ящике `adder` не было обнаружено ни одного проверки, а последняя раздел показывает, что в ящике `add_one` не было найдено ни одного проверки документации. +Первая раздел вывода показывает, что проверка `it_works` в ящике `add_one` прошёл. Следующая раздел показывает, что в ящике `adder` не было обнаружено ни одного проверки, а последняя раздел показывает, что в ящике `add_one` не было найдено ни одного проверки пособия. -Мы также можем запустить проверки для одного определенного ящика в рабочем пространстве из папка верхнего уровня с помощью флага `-p` и указанием имени ящика для которого мы хотим запустить проверки: +Мы также можем запустить проверки для одного определенного ящика в рабочем пространстве из папка верхнего уровня с помощью клейма `-p` и указанием имени ящика для которого мы хотим запустить проверки: является частью того, что делает внутреннее устройство Ржавчина полезной для множества различных задач. Обычная библиотека Ржавчина небольшая и безотказная, но ящики легко распространять, использовать и улучшать независимо от самого языка. Не стесняйтесь делиться кодом, который был вам полезен, через [crates.io](https://crates.io/); скорее всего, он будет полезен и кому-то ещё! +Совместное использование рукописи с Cargo и [crates.io](https://crates.io/) является частью того, что делает внутреннее устройство Ржавчина полезной для множества различных задач. Обычная библиотека Ржавчина небольшая и безотказная, но ящики легко распространять, использовать и улучшать независимо от самого языка. Не стесняйтесь делиться рукописью, который был вам полезен, через [crates.io](https://crates.io/); скорее всего, он будет полезен и кому-то ещё! diff --git a/rustbook-ru/src/ch15-00-smart-pointers.md b/rustbook-ru/src/ch15-00-smart-pointers.md index f4f960618..c0ecd0b29 100644 --- a/rustbook-ru/src/ch15-00-smart-pointers.md +++ b/rustbook-ru/src/ch15-00-smart-pointers.md @@ -1,21 +1,21 @@ # Умные указатели -*Указатель* — это общая подход для переменной, которая содержит адрес участка памяти. Этот адрес «относится к», или «указывает на» некоторые другие данные. Наиболее общая разновидность указателя в Ржавчина — это ссылка, о которой вы узнали из главы 4. Ссылки обозначаются символом `&` и заимствуют значение, на которое указывают. Они не имеют каких-либо особых возможностей, кроме как ссылаться на данные, и не имеют никаких накладных расходов. +*Указатель* — это общая подход для переменной, которая содержит адрес участка памяти. Этот адрес «относится к», или «указывает на» некоторые другие данные. Наиболее общая разновидность указателя в Ржавчине — это ссылка, о которой вы узнали из главы 4. Ссылки обозначаются знаком `&` и заимствуют значение, на которое указывают. Они не имеют каких-либо особых возможностей, кроме как ссылаться на данные, и не имеют никаких накладных расходов. -*Умные указатели*, с другой стороны, являются устройствами данных, которые не только действуют как указатель, но также имеют дополнительные метаданные и возможности. Подход умных указателей не неповторима для Rust: умные указатели возникли в C++ и существуют в других языках. В Ржавчина есть разные умные указатели, определённые в встроенной библиотеке, которые обеспечивают возможность, выходящую за рамки ссылок. Одним из примеров, который мы рассмотрим в этой главе, является вид умного указателя *reference counting* (подсчёт ссылок). Этот указатель позволяет иметь несколько владельцев с помощью отслеживания количества владельцев и, когда владельцев не остаётся, очищает данные. +*Умные указатели*, с другой стороны, являются устройствами данных, которые не только действуют как указатель, но также имеют дополнительные метаданные и возможности. Подход умных указателей не неповторима для Ржавчины: умные указатели возникли в C++ и существуют в других языках. В Ржавчине есть разные умные указатели, определённые в встроенной библиотеке, которые обеспечивают возможность, выходящую за рамки ссылок. Одним из примеров, который мы рассмотрим в этой главе, является вид умного указателя *reference counting* (подсчёт ссылок). Этот указатель позволяет иметь несколько владельцев с помощью отслеживания количества владельцев и, когда владельцев не остаётся, очищает данные. -Rust с его подходом владения и заимствования имеет дополнительное различие между ссылками и умными указателями: в то время, как ссылки только заимствуют данные, умные указатели часто *владеют* данными, на которые указывают. +Ржавчина с его подходом владения и заимствования имеет дополнительное различие между ссылками и умными указателями: в то время, как ссылки только заимствуют данные, умные указатели часто *владеют* данными, на которые указывают. Ранее мы уже сталкивались с умными указателями в этой книге, хотя и не называли их так, например `String` и `Vec` в главе 8. Оба этих вида считаются умными указателями, потому что они владеют некоторой областью памяти и позволяют ею управлять. У них также есть метаданные и дополнительные возможности или заверения. `String`, например, хранит свой размер в виде метаданных и заверяет, что содержимое строки всегда будет в кодировке UTF-8. -Умные указатели обычно выполняются с помощью устройств. Присущей чертой, которая отличает умный указатель от обычной устройства, является то, что для умных указателей выполнены особенности `Deref` и `Drop`. Особенность `Deref` позволяет образцу умного указателя вести себя как ссылка, так что вы можете написать код, работающий с ним как со ссылкой, так и как с умным указателем. Особенность `Drop` позволяет написать код, который будет запускаться когда образец умного указателя выйдет из области видимости. В этой главе мы обсудим оба особенности и выясним, почему они важны для умных указателей. +Умные указатели обычно выполняются с помощью устройств. Присущей чертой, которая отличает умный указатель от обычной устройства, является то, что для умных указателей выполнены особенности `Deref` и `Drop`. Особенность `Deref` позволяет образцу умного указателя вести себя как ссылка, так что вы можете написать рукопись, работающий с ним как со ссылкой, так и как с умным указателем. Особенность `Drop` позволяет написать рукопись, который будет запускаться когда образец умного указателя выйдет из области видимости. В этой главе мы обсудим оба особенности и выясним, почему они важны для умных указателей. -Учитывая, что образец умного указателя является общим образцом разработки, часто используемым в Rust, эта глава не описывает все существующие умные указатели. Множество библиотек имеют свои умные указатели, и вы также можете написать свои. Мы охватим наиболее распространённые умные указатели из встроенной библиотеки: +Учитывая, что образец умного указателя является общим образцом разработки, часто используемым в Ржавчине, эта глава не описывает все существующие умные указатели. Множество библиотек имеют свои умные указатели, и вы также можете написать свои. Мы охватим наиболее распространённые умные указатели из встроенной библиотеки: - `Box` для распределения значений в куче (памяти) - `Rc` вид счётчика ссылок, который допускает множественное владение - Виды `Ref` и `RefMut`, доступ к которым осуществляется через вид `RefCell`, который обеспечивает правила заимствования во время выполнения вместо времени сборки -Дополнительно мы рассмотрим образец *внутренней изменчивости (interior mutability)*, где неизменяемый вид предоставляет API для изменения своего внутреннего значения. Мы также обсудим *ссылочные зацикленности (reference cycles)*: как они могут приводить к утечке памяти и как это предотвратить. +Дополнительно мы рассмотрим образец *внутренней изменчивости (interior mutability)*, где неизменяемый вид предоставляет API для изменения своего внутреннего значения. Мы также обсудим *ссылочные замкнутости (reference cycles)*: как они могут приводить к утечке памяти и как это предотвратить. Приступим! diff --git a/rustbook-ru/src/ch15-01-box.md b/rustbook-ru/src/ch15-01-box.md index 1a2c2e251..2f3789d71 100644 --- a/rustbook-ru/src/ch15-01-box.md +++ b/rustbook-ru/src/ch15-01-box.md @@ -2,7 +2,7 @@ Наиболее простой умный указатель - это *box*, чей вид записывается как `Box`. Такие переменные позволяют хранить данные в куче, а не в обойме. То, что остаётся в обойме, является указателем на данные в куче. Обратитесь к Главе 4, чтобы рассмотреть разницу между обоймой и кучей. -У Box нет неполадок с производительностью, кроме хранения данных в куче вместо обоймы. Но он также и не имеет множества дополнительных возможностей. Вы будете использовать его чаще всего в следующих случаейх: +У Box нет неполадок с производительностью, кроме хранения данных в куче вместо обоймы. Но он также и не имеет множества дополнительных возможностей. Вы будете использовать его чаще всего в следующих случаях: - Когда у вас есть вид, размер которого невозможно определить во время сборки, а вы хотите использовать значение этого вида в среде, требующем точного размера. - Когда у вас есть большой размер данных и вы хотите передать владение, но при этом быть уверенным, что данные не будут воспроизведены @@ -38,7 +38,7 @@ *cons list* - это устройства данных из языка программирования Lisp и его диалектов, представляющая собой набор вложенных пар и являющаяся Lisp-исполнением связного списка. Его название происходит от функции `cons` (сокращение от "construct function") в Lisp, которая создает пару из двух своих переменных. Вызывая `cons` для пары, которая состоит из некоторого значения и другой пары, мы можем выстраивать списки cons, состоящие из рекурсивных пар. -Вот, пример cons list в виде псевдокода, содержащий список 1, 2, 3, где каждая пара заключена в круглые скобки: +Вот, пример cons list в виде псевдорукописи, содержащий список 1, 2, 3, где каждая пара заключена в круглые скобки: ```text (1, (2, (3, Nil))) @@ -46,9 +46,9 @@ Каждый элемент в cons списке содержит два элемента: значение текущего элемента и следующий элемент. Последний элемент в списке содержит только значение называемое `Nil` без следующего элемента. Cons список создаётся путём рекурсивного вызова функции `cons`. Каноничное имя для обозначения основного случая рекурсии - `Nil`. Обратите внимание, что это не то же самое, что понятие “null” или “nil” из главы 6, которая является недействительным или отсутствующим значением. -Cons list не является часто используемой устройством данных в Rust. В большинстве случаев, когда вам нужен список элементов при использовании Rust, лучше использовать `Vec`. Другие, более сложные рекурсивные виды данных *полезны* в определённых случаейх, но благодаря тому, что в этой главе мы начнём с cons list, мы сможем выяснить, как box позволяет нам определить рекурсивный вид данных без особого напряжения. +Cons list не является часто используемой устройством данных в Ржавчине. В большинстве случаев, когда вам нужен список элементов при использовании Ржавчина лучше использовать `Vec`. Другие, более сложные рекурсивные виды данных *полезны* в определённых случаях, но благодаря тому, что в этой главе мы начнём с cons list, мы сможем выяснить, как box позволяет нам определить рекурсивный вид данных без особого напряжения. -Приложение 15-2 содержит объявление перечисления cons списка. Обратите внимание, что этот код не будет собираться, потому что вид `List` не имеет известного размера, что мы и выясним. +Приложение 15-2 содержит объявление перечисления cons списка. Обратите внимание, что этот рукопись не будет собираться, потому что вид `List` не имеет известного размера, что мы и выясним. Файл: src/main.rs @@ -60,7 +60,7 @@ Cons list не является часто используемой устрой > Примечание: В данном примере мы выполняем cons list, который содержит только значения `i32`. Мы могли бы выполнить его с помощью generics, о которых мы говорили в главе 10, чтобы определить вид cons list, который мог бы хранить значения любого вида. -Использование вида `List` для хранения списка `1, 2, 3` будет выглядеть как код в приложении 15-3: +Использование вида `List` для хранения списка `1, 2, 3` будет выглядеть как рукопись в приложении 15-3: Файл: src/main.rs @@ -72,7 +72,7 @@ Cons list не является часто используемой устрой Первое значение `Cons` содержит `1` и другой `List`. Это значение `List` является следующим значением `Cons`, которое содержит `2` и другой `List`. Это значение `List` является ещё один значением `Cons`, которое содержит `3` и значение `List`, которое наконец является `Nil`, не рекурсивным исходом, сигнализирующим об окончании списка. -Если мы попытаемся собрать код в приложении 15-3, мы получим ошибку, показанную в приложении 15-4: +Если мы попытаемся собрать рукопись в приложении 15-3, мы получим ошибку, показанную в приложении 15-4: ```console {{#include ../listings/ch15-smart-pointers/listing-15-03/output.txt}} @@ -117,7 +117,7 @@ help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` repre Поскольку `Box` является указателем, Ржавчина всегда знает, сколько места нужно `Box`: размер указателя не меняется в зависимости от объёма данных, на которые он указывает. Это означает, что мы можем поместить `Box` внутрь образца `Cons` вместо значения `List` напрямую. `Box` будет указывать на значение очередного `List`, который будет находиться в куче, а не внутри образца `Cons`. Мировозренческо у нас все ещё есть список, созданный из списков, содержащих другие списки, но эта выполнение теперь больше похожа на размещение элементов рядом друг с другом, а не внутри друг друга. -Мы можем изменить определение перечисления `List` в приложении 15-2 и использование `List` в приложении 15-3 на код из приложения 15-5, который будет собираться: +Мы можем изменить определение перечисления `List` в приложении 15-2 и использование `List` в приложении 15-3 на рукопись из приложения 15-5, который будет собираться: Файл: src/main.rs diff --git a/rustbook-ru/src/ch15-02-deref.md b/rustbook-ru/src/ch15-02-deref.md index 0ca0a3274..c9c75a9ec 100644 --- a/rustbook-ru/src/ch15-02-deref.md +++ b/rustbook-ru/src/ch15-02-deref.md @@ -1,8 +1,8 @@ ## Обращение с умными указателями как с обычными ссылками с помощью `Deref` особенности -Используя особенность `Deref`, вы можете изменить поведение *оператора разыменования* `*` (не путать с операторами умножения или вездесущего подключения). Выполнив `Deref` таким образом, что умный указатель может рассматриваться как обычная ссылка, вы можете писать код, оперирующий ссылками, а также использовать этот код с умными указателями. +Используя особенность `Deref`, вы можете изменить поведение *приказчика разыменования* `*` (не путать с приказчиками умножения или вездесущего подключения). Выполнив `Deref` таким образом, что умный указатель может рассматриваться как обычная ссылка, вы можете писать рукопись, оперирующий ссылками, а также использовать этот рукопись с умными указателями. -Давайте сначала посмотрим, как работает оператор разыменования с обычными ссылками. Затем мы попытаемся определить пользовательский вид, который ведёт себя как `Box` и посмотрим, почему оператор разыменования не работает как ссылка для нового объявленного вида. Мы рассмотрим, как выполнение особенности `Deref` делает возможным работу умных указателей подобно ссылкам. Затем посмотрим на *разыменованное приведение* (deref coercion) в Ржавчина и как оно позволяет работать с любыми ссылками или умными указателями. +Давайте сначала посмотрим, как работает приказчик разыменования с обычными ссылками. Затем мы попытаемся определить пользовательский вид, который ведёт себя как `Box` и посмотрим, почему приказчик разыменования не работает как ссылка для нового объявленного вида. Мы рассмотрим, как выполнение особенности `Deref` делает возможным работу умных указателей подобно ссылкам. Затем посмотрим на *разыменованное приведение* (deref coercion) в Ржавчине и как оно позволяет работать с любыми ссылками или умными указателями. > Примечание: есть одна большая разница между видом `MyBox`, который мы собираемся создать и существующим `Box`: наша исполнение не будет хранить свои данные в куче. В примере мы сосредоточимся на особенности `Deref`, поэтому менее важно то, где данные хранятся, чем поведение подобное указателю. @@ -12,7 +12,7 @@ ### Следуя за указателем на значение -Обычная ссылка - это разновидность указателя, а указатель можно рассматривать как своеобразную стрелочку направляющую к значению, хранящемуся в другом месте. В приложении 15-6 мы создаём ссылку на значение `i32`, а затем используем оператор разыменования для перехода от ссылки к значению: +Обычная ссылка - это разновидность указателя, а указатель можно рассматривать как своеобразную стрелочку направляющую к значению, хранящемуся в другом месте. В приложении 15-6 мы создаём ссылку на значение `i32`, а затем используем приказчик разыменования для перехода от ссылки к значению: Файл: src/main.rs @@ -20,7 +20,7 @@ {{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-06/src/main.rs}} ``` -Приложение 15-6: Использование оператора разыменования для следования по ссылке к значению i32 +Приложение 15-6: Использование приказчика разыменования для следования по ссылке к значению i32 Переменной `x` присвоено значение`5` вида `i32`. Мы установили в качестве значения `y` ссылку на `x`. Мы можем утверждать, что значение `x` равно `5`. Однако, если мы хотим сделать утверждение о значении в `y`, мы должны использовать `*y`, чтобы перейти по ссылке к значению, на которое она указывает (таким образом, происходит *разыменование*), для того чтобы сборщик при сравнении мог использовать действительное значение. Как только мы разыменуем `y`, мы получим доступ к целочисленному значению, на которое указывает `y`, которое и будем сравнивать с `5`. @@ -30,11 +30,11 @@ {{#include ../listings/ch15-smart-pointers/output-only-01-comparing-to-reference/output.txt}} ``` -Сравнение числа и ссылки на число не допускается, потому что они различных видов. Мы должны использовать оператор разыменования, чтобы перейти по ссылке на значение, на которое она указывает. +Сравнение числа и ссылки на число не допускается, потому что они различных видов. Мы должны использовать приказчик разыменования, чтобы перейти по ссылке на значение, на которое она указывает. ### Использование `Box` как ссылку -Мы можем переписать код в приложении 15-6, чтобы использовать `Box` вместо ссылки; оператор разыменования, используемый для `Box` в приложении 15-7, работает так же, как оператор разыменования, используемый для ссылки в приложении 15-6: +Мы можем переписать рукопись в приложении 15-6, чтобы использовать `Box` вместо ссылки; приказчик разыменования, используемый для `Box` в приложении 15-7, работает так же, как приказчик разыменования, используемый для ссылки в приложении 15-6: Файл: src/main.rs @@ -42,13 +42,13 @@ {{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-07/src/main.rs}} ``` -Приложение 15-7: Использование оператора разыменования с видом Box<i32> +Приложение 15-7: Использование приказчика разыменования с видом Box<i32> -Основное различие между приложением 15-7 и приложением 15-6 заключается в том, что здесь мы устанавливаем `y` как образец `Box`, указывающий на воспроизведенное значение `x`, а не как ссылку, указывающую на значение `x`. В последнем утверждении мы можем использовать оператор разыменования, чтобы проследовать за указателем `Box` так же, как мы это делали, когда `y` был ссылкой. Далее мы рассмотрим, что особенного в `Box`, что позволяет нам использовать оператор разыменования, определяя наш собственный вид. +Основное различие между приложением 15-7 и приложением 15-6 заключается в том, что здесь мы устанавливаем `y` как образец `Box`, указывающий на воспроизведенное значение `x`, а не как ссылку, указывающую на значение `x`. В последнем утверждении мы можем использовать приказчик разыменования, чтобы проследовать за указателем `Box` так же, как мы это делали, когда `y` был ссылкой. Далее мы рассмотрим, что особенного в `Box`, что позволяет нам использовать приказчик разыменования, определяя наш собственный вид. ### Определение собственного умного указателя -Давайте создадим умный указатель, похожий на вид `Box` предоставляемый встроенной библиотекой, чтобы понять как поведение умных указателей отличается от поведения обычной ссылки. Затем мы рассмотрим вопрос, как добавить возможность использовать оператор разыменования. +Давайте создадим умный указатель, похожий на вид `Box` предоставляемый встроенной библиотекой, чтобы понять как поведение умных указателей отличается от поведения обычной ссылки. Затем мы рассмотрим вопрос, как добавить возможность использовать приказчик разыменования. Вид `Box` в конечном итоге определяется как устройства упорядоченного ряда с одним элементом, поэтому в приложении 15-8 подобным образом определяется `MyBox`. Мы также определим функцию `new`, чтобы она соответствовала функции `new`, определённой в `Box`. @@ -62,7 +62,7 @@ Мы определяем устройство с именем `MyBox` и объявляем обобщённый свойство `T`, потому что мы хотим, чтобы наш вид хранил значения любого вида. Вид `MyBox` является устройством упорядоченного ряда с одним элементом вида `T`. Функция `MyBox::new` принимает один свойство вида `T` и возвращает образец `MyBox`, который содержит переданное значение. -Давайте попробуем добавить функцию `main` из приложения 15-7 в приложение 15-8 и изменим её на использование вида `MyBox`, который мы определили вместо `Box`. Код в приложении 15-9 не будет собираться, потому что Ржавчина не знает, как разыменовывать `MyBox`. +Давайте попробуем добавить функцию `main` из приложения 15-7 в приложение 15-8 и изменим её на использование вида `MyBox`, который мы определили вместо `Box`. Рукопись в приложении 15-9 не будет собираться, потому что Ржавчина не знает, как разыменовывать `MyBox`. Файл: src/main.rs @@ -78,11 +78,11 @@ {{#include ../listings/ch15-smart-pointers/listing-15-09/output.txt}} ``` -Наш вид `MyBox` не может быть разыменован, потому что мы не выполнили эту возможность. Чтобы включить разыменование с помощью оператора `*`, мы выполняем особенность `Deref`. +Наш вид `MyBox` не может быть разыменован, потому что мы не выполнили эту возможность. Чтобы включить разыменование с помощью приказчика `*`, мы выполняем особенность `Deref`. ### Трактование вида как ссылки выполняя особенность `Deref` -Как обсуждалось в разделе [“Выполнение особенности для типа”] Главы 10, для выполнения особенности нужно предоставить выполнения требуемых способов особенности. Особенность `Deref`, предоставляемый встроенной библиотекой требует от нас выполнения одного способа с именем `deref`, который заимствует `self` и возвращает ссылку на внутренние данные. Приложение 15-10 содержит выполнение `Deref` добавленную к определению `MyBox`: +Как обсуждалось в разделе [“Выполнение особенности для вида”] Главы 10, для выполнения особенности нужно предоставить выполнения требуемых способов особенности. Особенность `Deref`, предоставляемый встроенной библиотекой требует от нас выполнения одного способа с именем `deref`, который заимствует `self` и возвращает ссылку на внутренние данные. Приложение 15-10 содержит выполнение `Deref` добавленную к определению `MyBox`: Файл: src/main.rs @@ -94,7 +94,7 @@ правила написания `type Target = T;` определяет связанный вид для использования у особенности `Deref`. Связанные виды - это немного другой способ объявления обобщённого свойства, но пока вам не нужно о них беспокоиться; мы рассмотрим их более подробно в главе 19. -Мы заполним тело способа `deref` оператором `&self.0 `, чтобы `deref` вернул ссылку на значение, к которому мы хотим получить доступ с помощью оператора `*`; вспомним из раздела ["Using Tuple Structs without Named Fields to Create Different Types"] главы 5, что `.0` получает доступ к первому значению в упорядоченной в ряд устройстве. Функция `main` в приложении 15-9, которая вызывает `*` для значения `MyBox`, теперь собирается, и проверки проходят! +Мы заполним тело способа `deref` приказчиком `&self.0 `, чтобы `deref` вернул ссылку на значение, к которому мы хотим получить доступ с помощью приказчика `*`; вспомним из раздела ["Using Tuple Structs without Named Fields to Create Different Types"] главы 5, что `.0` получает доступ к первому значению в упорядоченной в ряд устройстве. Функция `main` в приложении 15-9, которая вызывает `*` для значения `MyBox`, теперь собирается, и проверки проходят! Без особенности `Deref` сборщик может только разыменовывать `&` ссылки. Способ `deref` даёт сборщику возможность принимать значение любого вида, выполняющего `Deref` и вызывать способ `deref` чтобы получить ссылку `&`, которую он знает, как разыменовывать. @@ -104,17 +104,17 @@ *(y.deref()) ``` -Rust заменяет оператор `*` вызовом способа `deref` и затем простое разыменование, поэтому нам не нужно думать о том, нужно ли нам вызывать способ `deref`. Эта функция Ржавчина позволяет писать код, который исполняется одинаково, независимо от того, есть ли у нас обычная ссылка или вид, выполняющий особенность `Deref`. +Ржавчина заменяет приказчик `*` вызовом способа `deref` и затем простое разыменование, поэтому нам не нужно думать о том, нужно ли нам вызывать способ `deref`. Эта функция Ржавчина позволяет писать рукопись, который исполняется одинаково, независимо от того, есть ли у нас обычная ссылка или вид, выполняющий особенность `Deref`. -Причина, по которой способ `deref` возвращает ссылку на значение, и что простое разыменование вне круглых скобок в `*(y.deref())` все ещё необходимо, связана с системой владения. Если бы способ `deref` возвращал значение напрямую, а не ссылку на него, значение переместилось бы из `self`. Мы не хотим передавать владение внутренним значением внутри `MyBox` в этом случае и в большинстве случаев, когда мы используем оператор разыменования. +Причина, по которой способ `deref` возвращает ссылку на значение, и что простое разыменование вне круглых скобок в `*(y.deref())` все ещё необходимо, связана с системой владения. Если бы способ `deref` возвращал значение напрямую, а не ссылку на него, значение переместилось бы из `self`. Мы не хотим передавать владение внутренним значением внутри `MyBox` в этом случае и в большинстве случаев, когда мы используем приказчик разыменования. -Обратите внимание, что оператор `*` заменён вызовом способа `deref`, а затем вызовом оператора `*` только один раз, каждый раз, когда мы используем `*` в коде. Поскольку замена оператора `*` не повторяется бесконечно, мы получаем данные вида `i32`, которые соответствуют `5` в `assert_eq!` приложения 15-9. +Обратите внимание, что приказчик `*` заменён вызовом способа `deref`, а затем вызовом приказчика `*` только один раз, каждый раз, когда мы используем `*` в рукописи. Поскольку замена приказчика `*` не повторяется бесконечно, мы получаем данные вида `i32`, которые соответствуют `5` в `assert_eq!` приложения 15-9. ### Неявные разыменованные приведения с функциями и способами -*Разыменованное приведение* преобразует ссылку на вид, который выполняет признак `Deref`, в ссылку на другой вид. Например, deref coercion может преобразовать `&String` в `&str`, потому что `String` выполняет признак `Deref`, который возвращает `&str`. Deref coercion - это удобный рычаг, который Ржавчина использует для переменных функций и способов, и работает только для видов, выполняющих признак `Deref`. Это происходит самостоятельно , когда мы передаём в качестве переменной функции или способа ссылку на значение определённого вида, которое не соответствует виду свойства в определении функции или способа. В итоге серии вызовов способа `deref` вид, который мы передали, преобразуется в вид, необходимый для свойства. +*Разыменованное приведение* преобразует ссылку на вид, который выполняет признак `Deref`, в ссылку на другой вид. Например, deref coercion может преобразовать `&String` в `&str`, потому что `String` выполняет признак `Deref`, который возвращает `&str`. Deref coercion - это удобный рычаг, который Ржавчина использует для переменных функций и способов, и работает только для видов, выполняющих признак `Deref`. Это происходит самостоятельно , когда мы передаём в качестве переменной функции или способа ссылку на значение определённого вида, которое не соответствует виду свойства в определении функции или способа. В итоге последовательности вызовов способа `deref` вид, который мы передали, преобразуется в вид, необходимый для свойства. -Разыменованное приведение было добавлено в Rust, так что программистам, пишущим вызовы функций и способов, не нужно добавлять множество явных ссылок и разыменований с помощью использования `&` и `*`. Возможность разыменованного приведения также позволяет писать больше кода, который может работать как с ссылками, так и с умными указателями. +Разыменованное приведение было добавлено в Ржавчине так, что программистам, пишущим вызовы функций и способов, не нужно добавлять множество явных ссылок и разыменований с помощью использования `&` и `*`. Возможность разыменованного приведения также позволяет писать больше рукописи, который может работать как с ссылками, так и с умными указателями. Чтобы увидеть разыменованное приведение в действии, давайте воспользуемся видом `MyBox` определённым в приложении 15-8, а также выполнение `Deref` добавленную в приложении 15-10. Приложение 15-11 показывает определение функции, у которой есть свойство вида срез строки: @@ -136,9 +136,9 @@ Rust заменяет оператор `*` вызовом способа `deref` Приложение 15-12: Вызов hello со ссылкой на значение MyBox<String>, которое работает из-за разыменованного приведения -Здесь мы вызываем функцию `hello` с переменнаяом `&m`, который является ссылкой на значение `MyBox`. Поскольку мы выполнили особенность `Deref` для `MyBox` в приложении 15-10, то Ржавчина может преобразовать `&MyBox` в `&String` вызывая `deref`. Обычная библиотека предоставляет выполнение особенности `Deref` для вида `String`, которая возвращает срез строки, это описано в документации API особенности `Deref`. Ржавчина снова вызывает `deref`, чтобы превратить `&String` в `&str`, что соответствует определению функции `hello`. +Здесь мы вызываем функцию `hello` с переменнаяом `&m`, который является ссылкой на значение `MyBox`. Поскольку мы выполнили особенность `Deref` для `MyBox` в приложении 15-10, то Ржавчина может преобразовать `&MyBox` в `&String` вызывая `deref`. Обычная библиотека предоставляет выполнение особенности `Deref` для вида `String`, которая возвращает срез строки, это описано в пособия API особенности `Deref`. Ржавчина снова вызывает `deref`, чтобы превратить `&String` в `&str`, что соответствует определению функции `hello`. -Если бы Ржавчина не выполнил разыменованное приведение, мы должны были бы написать код в приложении 15-13 вместо кода в приложении 15-12 для вызова способа `hello` со значением вида `&MyBox`. +Если бы Ржавчина не выполнил разыменованное приведение, мы должны были бы написать рукопись в приложении 15-13 вместо рукописи в приложении 15-12 для вызова способа `hello` со значением вида `&MyBox`. Файл: src/main.rs @@ -146,17 +146,17 @@ Rust заменяет оператор `*` вызовом способа `deref` {{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-13/src/main.rs:here}} ``` -Приложение 15-13: Код, который нам пришлось бы написать, если бы в Ржавчина не было разыменованного приведения ссылок +Приложение 15-13: Рукопись, который нам пришлось бы написать, если бы в Ржавчине не было разыменованного приведения ссылок -Код `(*m)` разыменовывает `MyBox` в `String`. Затем `&` и `[..]` принимают строковый срез `String`, равный всей строке, чтобы соответствовать ярлыке `hello`. Код без разыменованного приведения сложнее читать, писать и понимать со всеми этими символами. Разыменованное приведение позволяет Ржавчина обрабатывать эти преобразования для нас самостоятельно . +Рукопись `(*m)` разыменовывает `MyBox` в `String`. Затем `&` и `[..]` принимают строковый срез `String`, равный всей строке, чтобы соответствовать ярлыке `hello`. Рукопись без разыменованного приведения сложнее читать, писать и понимать со всеми этими знаками. Разыменованное приведение позволяет Ржавчина обрабатывать эти преобразования для нас самостоятельно . -Когда особенность `Deref` определён для задействованных видов, Ржавчина проанализирует виды и будет использовать `Deref::deref` столько раз, сколько необходимо, чтобы получить ссылку, соответствующую виду свойства. Количество раз, которое нужно вставить `Deref::deref` определяется во время сборки, поэтому использование разыменованного приведения не имеет накладных расходов во время выполнения! +Когда особенность `Deref` определён для задействованных видов, Ржавчина прорассмотривает виды и будет использовать `Deref::deref` столько раз, сколько необходимо, чтобы получить ссылку, соответствующую виду свойства. Количество раз, которое нужно вставить `Deref::deref` определяется во время сборки, поэтому использование разыменованного приведения не имеет накладных расходов во время выполнения! ### Как разыменованное приведение взаимодействует с изменяемостью -Подобно тому, как вы используете особенность `Deref` для переопределения оператора `*` у неизменяемых ссылок, вы можете использовать особенность `DerefMut` для переопределения оператора `*` у изменяемых ссылок. +Подобно тому, как вы используете особенность `Deref` для переопределения приказчика `*` у неизменяемых ссылок, вы можете использовать особенность `DerefMut` для переопределения приказчика `*` у изменяемых ссылок. -Rust выполняет разыменованное приведение, когда находит виды и выполнения особенностей в трёх случаях: +Ржавчина выполняет разыменованное приведение, когда находит виды и выполнения особенностей в трёх случаях: - Из вида `&T` в вид `&U` когда верно `T: Deref` - Из вида `&mut T` в вид `&mut U` когда верно `T: DerefMut` @@ -167,5 +167,5 @@ Rust выполняет разыменованное приведение, ко Третий случай хитрее: Ржавчина также приводит изменяемую ссылку к неизменяемой. Но обратное *не* представляется возможным: неизменяемые ссылки никогда не приводятся к изменяемым ссылкам. Из-за правил заимствования, если у вас есть изменяемая ссылка, эта изменяемая ссылка должна быть единственной ссылкой на данные (в противном случае программа не будет собираться). Преобразование одной изменяемой ссылки в неизменяемую ссылку никогда не нарушит правила заимствования. Преобразование неизменяемой ссылки в изменяемую ссылку потребует наличия только одной неизменяемой ссылки на эти данные, и правила заимствования не заверяют этого. Следовательно, Ржавчина не может сделать предположение, что преобразование неизменяемой ссылки в изменяемую ссылку возможно. -[“Выполнение особенности для типа”]: ch10-02-traits.html#implementing-a-trait-on-a-type +[“Выполнение особенности для вида”]: ch10-02-traits.html#implementing-a-trait-on-a-type ["Using Tuple Structs without Named Fields to Create Different Types"]: ch05-01-defining-structs.html#using-tuple-structs-without-named-fields-to-create-different-types \ No newline at end of file diff --git a/rustbook-ru/src/ch15-03-drop.md b/rustbook-ru/src/ch15-03-drop.md index c99ce6f68..2371185f6 100644 --- a/rustbook-ru/src/ch15-03-drop.md +++ b/rustbook-ru/src/ch15-03-drop.md @@ -1,12 +1,12 @@ -## Запуск кода при очистке с помощью особенности `Drop` +## Запуск рукописи при очистке с помощью особенности `Drop` -Вторым важным особенностью умного указателя является Drop, который позволяет управлять, что происходит, когда значение вот-вот выйдет из области видимости. Вы можете выполнить особенность Drop для любого вида, а также использовать этот код для высвобождения ресурсов, таких как файлы или сетевые соединения. +Вторым важным особенностью умного указателя является Drop, который позволяет управлять, что происходит, когда значение вот-вот выйдет из области видимости. Вы можете выполнить особенность Drop для любого вида, а также использовать этот рукопись для высвобождения мощностей, таких как файлы или сетевые соединения. Мы рассматриваем `Drop` в среде умных указателей, потому что возможность свойства `Drop` по сути всегда используется при выполнения умного указателя. Например, при сбросе `Box` происходит деаллокация пространства на куче, на которое указывает box. -В некоторых языках для некоторых видов программист должен вызывать код для освобождения памяти или ресурсов каждый раз, когда он завершает использование образцов этих видов. Примерами могут служить указатели файлов, сокеты или блокировки. Если забыть об этом, система окажется перегруженной и может упасть. В Ржавчина вы можете указать, что определённый отрывок кода должен выполняться всякий раз, когда значение выходит из области видимости, и сборщик самостоятельно будет его вставлять. Как следствие, вам не нужно заботиться о размещении кода очистки везде в программе, где завершается работа образца определённого вида - утечки ресурсов все равно не будет! +В некоторых языках для некоторых видов программист должен вызывать рукопись для освобождения памяти или мощностей каждый раз, когда он завершает использование образцов этих видов. Примерами могут служить указатели файлов, сокеты или запрета. Если забыть об этом, система окажется перегруженной и может упасть. В Ржавчине вы можете указать, что определённый отрывок рукописи должен выполняться всякий раз, когда значение выходит из области видимости, и сборщик самостоятельно будет его вставлять. Как следствие, вам не нужно заботиться о размещении рукописи очистки везде в программе, где завершается работа образца определённого вида - утечки мощностей все равно не будет! -Вы можете задать определённую логику, которая будет выполняться, когда значение выходит за пределы области видимости, выполнив признак `Drop`. Особенность `Drop` требует от вас выполнения одного способа `drop`, который принимает изменяемую ссылку на `self`. Чтобы увидеть, когда Ржавчина вызывает `drop`, давайте выполняем `drop` с помощью указаний `println!`. +Вы можете задать определённую ход мыслей, которая будет выполняться, когда значение выходит за пределы области видимости, выполнив признак `Drop`. Особенность `Drop` требует от вас выполнения одного способа `drop`, который принимает изменяемую ссылку на `self`. Чтобы увидеть, когда Ржавчина вызывает `drop`, давайте выполняем `drop` с помощью указаний `println!`. В приложении 15-14 показана устройства `CustomSmartPointer`, единственной не имеющей себе подобных возможностью которой является печать `Dropping CustomSmartPointer!`, когда образец выходит из области видимости, чтобы показать, когда Ржавчина выполняет функцию `drop`. @@ -16,11 +16,11 @@ {{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-14/src/main.rs}} ``` -Приложение 15-14: Устройства CustomSmartPointer, выполняющая особенность Drop, куда мы поместим наш код очистки +Приложение 15-14: Устройства CustomSmartPointer, выполняющая особенность Drop, куда мы поместим нашу рукопись очистки -Особенность `Drop` включён в прелюдию, поэтому нам не нужно вводить его в область видимости. Мы выполняем особенность `Drop` для `CustomSmartPointer` и выполняем способ `drop`, который будет вызывать `println!`. Тело функции `drop` - это место, где должна располагаться вся логика, которую вы захотите выполнять, когда образец вашего вида выйдет из области видимости. Мы печатаем здесь текст, чтобы наглядно отобразить, когда Ржавчина вызовет `drop`. +Особенность `Drop` включён в прелюдию, поэтому нам не нужно вводить его в область видимости. Мы выполняем особенность `Drop` для `CustomSmartPointer` и выполняем способ `drop`, который будет вызывать `println!`. Тело функции `drop` - это место, где должна располагаться вся ход мыслей, которую вы захотите выполнять, когда образец вашего вида выйдет из области видимости. Мы печатаем здесь текст, чтобы наглядно отобразить, когда Ржавчина вызовет `drop`. -В `main` мы создаём два образца `CustomSmartPointer` и затем печатаем `CustomSmartPointers created` . В конце `main` наши образцы `CustomSmartPointer` выйдут из области видимости и Ржавчина вызовет код, который мы добавили в способ `drop`, который и напечатает наше окончательное сообщение. Обратите внимание, что нам не нужно вызывать способ `drop` явно. +В `main` мы создаём два образца `CustomSmartPointer` и затем печатаем `CustomSmartPointers created` . В конце `main` наши образцы `CustomSmartPointer` выйдут из области видимости и Ржавчина вызовет рукопись, который мы добавили в способ `drop`, который и напечатает наше окончательное сообщение. Обратите внимание, что нам не нужно вызывать способ `drop` явно. Когда мы запустим эту программу, мы увидим следующий вывод: @@ -28,11 +28,11 @@ {{#include ../listings/ch15-smart-pointers/listing-15-14/output.txt}} ``` -Rust самостоятельно вызывал `drop` в мгновение выхода наших образцов из области видимости, тем самым выполнив заданный нами код. Переменные удаляются в обратном порядке их создания, поэтому `d` была удалена до `c`. Цель этого примера — дать вам наглядное представление о том, как работает способ `drop`; в типичных случаях вы будете задавать код очистки, который должен выполнить ваш вид, а не печатать сообщение. +Ржавчина самостоятельно вызывал `drop` в мгновение выхода наших образцов из области видимости, тем самым выполнив заданный нами рукопись. Переменные удаляются в обратном порядке их создания, поэтому `d` была удалена до `c`. Цель этого примера — дать вам наглядное представление о том, как работает способ `drop`; в типичных случаях вы будете задавать рукопись очистки, который должен выполнить ваш вид, а не печатать сообщение. ### Раннее удаление значения с помощью `std::mem::drop` -К сожалению, отключение функции самостоятельного удаления с помощью `drop` является не простым. Отключение `drop` обычно не требуется; весь смысл особенности `Drop` в том, чтобы о функции позаботились самостоятельно . Иногда, однако, вы можете захотеть очистить значение рано. Одним из примеров является использование умных указателей, которые управляют блокировками: вы могли бы потребовать принудительный вызов способа `drop` который снимает блокировку, чтобы другой код в той же области видимости мог получить блокировку. Ржавчина не позволяет вызвать способ особенности `Drop` вручную; вместо этого вы должны вызвать функцию `std::mem::drop` предоставляемую встроенной библиотекой, если хотите принудительно удалить значение до конца области видимости. +К сожалению, отключение функции самостоятельного удаления с помощью `drop` является не простым. Отключение `drop` обычно не требуется; весь смысл особенности `Drop` в том, чтобы о функции позаботились самостоятельно . Иногда, однако, вы можете захотеть очистить значение рано. Одним из примеров является использование умных указателей, которые управляют запретами: вы могли бы потребовать принудительный вызов способа `drop` который снимает запрет, чтобы другой рукопись в той же области видимости мог получить запрет. Ржавчина не позволяет вызвать способ особенности `Drop` вручную; вместо этого вы должны вызвать функцию `std::mem::drop` предоставляемую встроенной библиотекой, если хотите принудительно удалить значение до конца области видимости. Если попытаться вызвать способ `drop` особенности `Drop` вручную, изменяя функцию `main` приложения 15-14 так, как показано в приложении 15-15, мы получим ошибку сборщика: @@ -44,15 +44,15 @@ Rust самостоятельно вызывал `drop` в мгновение в Приложение 15-15: Попытка вызвать способ drop из особенности Drop вручную для досрочной очистки -Когда мы попытаемся собрать этот код, мы получим ошибку: +Когда мы попытаемся собрать этот рукопись, мы получим ошибку: ```console {{#include ../listings/ch15-smart-pointers/listing-15-15/output.txt}} ``` -Это сообщение об ошибке говорит, что мы не можем явно вызывать `drop`. В сообщении об ошибке используется понятие *деструктор (destructor)*, который является общим понятием программирования для функции, которая очищает образец. *Деструктор* подобен *строителю*, который создаёт образец. Функция `drop` в Ржавчина является определённым деструктором. +Это сообщение об ошибке говорит, что мы не можем явно вызывать `drop`. В сообщении об ошибке используется понятие *деструктор (destructor)*, который является общим понятием программирования для функции, которая очищает образец. *Деструктор* подобен *строителю*, который создаёт образец. Функция `drop` в Ржавчине является определённым деструктором. -Rust не позволяет обращаться к `drop` напрямую, потому что он все равно самостоятельно вызовет `drop` в конце `main`. Это вызвало бы ошибку *double free*, потому что в этом случае Ржавчина попытался бы дважды очистить одно и то же значение. +Ржавчина не позволяет обращаться к `drop` напрямую, потому что он все равно самостоятельно вызовет `drop` в конце `main`. Это вызвало бы ошибку *double free*, потому что в этом случае Ржавчина попытался бы дважды очистить одно и то же значение. Невозможно отключить самостоятельную подстановку вызова `drop`, когда значение выходит из области видимости, и нельзя вызвать способ `drop` напрямую. Поэтому, если нам нужно принудительно избавиться от значения раньше времени, следует использовать функцию `std::mem::drop`. @@ -66,15 +66,15 @@ Rust не позволяет обращаться к `drop` напрямую, п Приложение 15-16: Вызов std::mem::drop для принудительного удаления значения до того, как оно выйдет из области видимости -Выполнение данного кода выведет следующий итог:: +Выполнение данного рукописи выведет следующий итог:: ```console {{#include ../listings/ch15-smart-pointers/listing-15-16/output.txt}} ``` -Текст `Dropping CustomSmartPointer with data `some data`!`, напечатанный между `CustomSmartPointer created.` и текстом `CustomSmartPointer dropped before the end of main.`, показывает, что код способа `drop` вызывается для удаления `c` в этой точке. +Текст `Dropping CustomSmartPointer with data `some data`!`, напечатанный между `CustomSmartPointer created.` и текстом `CustomSmartPointer dropped before the end of main.`, показывает, что рукопись способа `drop` вызывается для удаления `c` в этой точке. -Вы можете использовать код, указанный в выполнения особенности `Drop`, чтобы сделать очистку удобной и безопасной: например, вы можете использовать её для создания своего собственного управленца памяти! С помощью особенности `Drop` и системы владения Ржавчина не нужно целенаправленно заботиться о том, чтобы освобождать ресурсы, потому что Ржавчина делает это самостоятельно . +Вы можете использовать рукопись, указанный в выполнения особенности `Drop`, чтобы сделать очистку удобной и безопасной: например, вы можете использовать её для создания своего собственного управленца памяти! С помощью особенности `Drop` и системы владения Ржавчины не нужно целенаправленно заботиться о том, чтобы освобождать мощности, потому что Ржавчина делает это самостоятельно . Также не нужно беспокоиться о неполадках, возникающих в итоге случайной очистки значений, которые всё ещё используются: система владения, которая заверяет, что ссылки всегда действительны, также заверяет, что `drop` вызывается только один раз, когда значение больше не используется. diff --git a/rustbook-ru/src/ch15-04-rc.md b/rustbook-ru/src/ch15-04-rc.md index 111580d65..68ae3063d 100644 --- a/rustbook-ru/src/ch15-04-rc.md +++ b/rustbook-ru/src/ch15-04-rc.md @@ -8,7 +8,7 @@ Вид `Rc` используется, когда мы хотим разместить в куче некоторые данные для чтения несколькими частями нашей программы и не можем определить во время сборки, какая из частей завершит использование данных последней. Если бы мы знали, какая часть завершит использование последней то, мы могли бы сделать эту часть владельцем данных и вступили бы в силу обычные правила владения, применяемые во время сборки. -Обратите внимание, что `Rc` используется только в однопоточных сценариях. Когда мы обсудим состязательность в главе 16, мы рассмотрим, как выполнять подсчёт ссылок во многопоточных программах. +Обратите внимание, что `Rc` используется только в однопоточных задумках. Когда мы обсудим состязательность в главе 16, мы рассмотрим, как выполнять подсчёт ссылок во многопоточных программах. ### Использование `Rc` для совместного использования данных @@ -20,7 +20,7 @@ Мы создадим список `a`, содержащий 5 и затем 10. Затем мы создадим ещё два списка: `b` начинающийся с 3 и `c` начинающийся с 4. Оба списка `b` и `c` затем продолжать первый список `a`, содержащий 5 и 10. Другими словами, оба списка будут разделять первый список, содержащий 5 и 10. -Попытка выполнить этот сценарий, используя определение `List` с видом `Box` не будет работать, как показано в приложении 15-17: +Попытка выполнить этот задумка, используя определение `List` с видом `Box` не будет работать, как показано в приложении 15-17: Файл: src/main.rs @@ -30,7 +30,7 @@ Приложение 15-17: Отображение того, что нельзя иметь два списка, использующих Box<T>, которые пытаются совместно владеть третьим списком -При сборки этого кода, мы получаем эту ошибку: +При сборки этого рукописи, мы получаем эту ошибку: ```console {{#include ../listings/ch15-smart-pointers/listing-15-17/output.txt}} @@ -38,7 +38,7 @@ Исходы `Cons` владеют данными, которые они содержат, поэтому, когда мы создаём список `b`, то `a` перемещается в `b`, а `b` становится владельцем `a`. Затем, мы пытаемся использовать `a` снова при создании `c`, но нам не разрешают, потому что `a` был перемещён. -Мы могли бы изменить определение `Cons`, чтобы вместо этого хранить ссылки, но тогда нам пришлось бы указывать свойства времени жизни. Указывая свойства времени жизни, мы бы указали, что каждый элемент в списке будет жить как самое меньшее столько же, сколько и весь список. Это относится к элементам и спискам в приложении 15.17, но не во всех сценариях. +Мы могли бы изменить определение `Cons`, чтобы вместо этого хранить ссылки, но тогда нам пришлось бы указывать свойства времени жизни. Указывая свойства времени жизни, мы бы указали, что каждый элемент в списке будет жить как самое меньшее столько же, сколько и весь список. Это относится к элементам и спискам в приложении 15.17, но не во всех задумках. Вместо этого мы изменим наше определение вида `List` так, чтобы использовать `Rc` вместо `Box`, как показано в приложении 15-18. Каждый исход `Cons` теперь будет содержать значение и вид `Rc`, указывающий на `List`. Когда мы создадим `b` то, вместо того чтобы стал владельцем `a`, мы будем клонировать `Rc` который содержит `a`, тем самым увеличивая количество ссылок с единицы до двойки и позволяя переменным `a` и `b` разделять владение на данные в виде `Rc`. Мы также клонируем `a` при создании `c`, увеличивая количество ссылок с двух до трёх. Каждый раз, когда мы вызываем `Rc::clone`, счётчик ссылок на данные внутри `Rc` будет увеличиваться и данные не будут очищены, если на них нет нулевых ссылок. @@ -52,7 +52,7 @@ Нам нужно добавить указанию `use`, чтобы подключить вид `Rc` в область видимости, потому что он не входит в список самостоятельного подключения прелюдии. В `main`, мы создаём список владеющий 5 и 10, сохраняем его в новом `Rc` переменной `a`. Затем при создании `b` и `c`, мы называем функцию `Rc::clone` и передаём ей ссылку на `Rc` как переменная `a`. -Мы могли бы вызвать `a.clone()`, а не `Rc::clone(&a)`, но в Ржавчина принято использовать `Rc::clone` в таком случае. Внутренняя выполнение `Rc::clone` не делает глубокого повторения всех данных, как это происходит в видах большинства выполнений `clone`. Вызов `Rc::clone` только увеличивает счётчик ссылок, что не занимает много времени. Глубокое повторение данных может занимать много времени. Используя `Rc::clone` для подсчёта ссылок, можно визуально различать виды клонирования с глубоким повторением и клонирования, которые увеличивают количество ссылок. При поиске в коде неполадок с производительностью нужно рассмотреть только клонирование с глубоким повторением и пренебрегать вызовы `Rc::clone` . +Мы могли бы вызвать `a.clone()`, а не `Rc::clone(&a)`, но в Ржавчине принято использовать `Rc::clone` в таком случае. Внутренняя выполнение `Rc::clone` не делает глубокого повторения всех данных, как это происходит в видах большинства выполнений `clone`. Вызов `Rc::clone` только увеличивает счётчик ссылок, что не занимает много времени. Глубокое повторение данных может занимать много времени. Используя `Rc::clone` для подсчёта ссылок, можно визуально различать виды клонирования с глубоким повторением и клонирования, которые увеличивают количество ссылок. При поиске в рукописи неполадок с производительностью нужно рассмотреть только клонирование с глубоким повторением и пренебрегать вызовы `Rc::clone` . ### Клонирование `Rc` увеличивает количество ссылок @@ -70,7 +70,7 @@ В каждой части программы, где количество ссылок меняется, мы выводим количество ссылок, которое получаем, вызывая функцию `Rc::strong_count`. Эта функция названа `strong_count`, а не `count`, потому что вид `Rc` также имеет `weak_count`; мы увидим, для чего используется `weak_count` в разделе "Предотвращение замкнутых ссылок: Превращение `Rc` в Weak<T>". -Код выводит в окно вывода: +Рукопись выводит в окно вывода: ```console {{#include ../listings/ch15-smart-pointers/listing-15-19/output.txt}} diff --git a/rustbook-ru/src/ch15-05-interior-mutability.md b/rustbook-ru/src/ch15-05-interior-mutability.md index bc7be0336..23e8cf603 100644 --- a/rustbook-ru/src/ch15-05-interior-mutability.md +++ b/rustbook-ru/src/ch15-05-interior-mutability.md @@ -1,8 +1,8 @@ ## `RefCell` и образец внутренней изменяемости -*Внутренняя изменяемость* - это образец разработки Rust, который позволяет вам изменять данные даже при наличии неизменяемых ссылок на эти данные; обычно такое действие запрещено правилами заимствования. Для изменения данных образец использует `unsafe` код внутри устройства данных, чтобы обойти обычные правила Rust, управляющие изменяемость и заимствование. Небезопасный (unsafe) код даёт понять сборщику, что мы самостоятельно следим за соблюдением этих правил, а не полагаемся на то, что сборщик будет делать это для нас; подробнее о небезопасном коде мы поговорим в главе 19. +*Внутренняя изменяемость* - это образец разработки Ржавчина, который позволяет вам изменять данные даже при наличии неизменяемых ссылок на эти данные; обычно такое действие запрещено правилами заимствования. Для изменения данных образец использует `unsafe` рукопись внутри устройства данных, чтобы обойти обычные правила Ржавчина управляющие изменяемость и заимствование. Небезопасный (unsafe) рукопись даёт понять сборщику, что мы самостоятельно следим за соблюдением этих правил, а не полагаемся на то, что сборщик будет делать это для нас; подробнее о небезопасном рукописи мы поговорим в главе 19. -Мы можем использовать виды, в которых применяется образец внутренней изменяемости, только если мы можем обеспечить, что правила заимствования будут соблюдаться во время выполнения, несмотря на то, что сборщик не сможет этого обеспечить. В этом случае `небезопасный` код оборачивается безопасным API, и внешне вид остаётся неизменяемым. +Мы можем использовать виды, в которых применяется образец внутренней изменяемости, только если мы можем обеспечить, что правила заимствования будут соблюдаться во время выполнения, несмотря на то, что сборщик не сможет этого обеспечить. В этом случае `небезопасный` рукопись оборачивается безопасным API, и внешне вид остаётся неизменяемым. Давайте изучим данную подход с помощью вида данных `RefCell`, который выполняет этот образец. @@ -13,15 +13,15 @@ - В любой мгновение времени вы можете иметь *либо* одну изменяемую ссылку либо сколько угодно неизменяемых ссылок (но не оба вида ссылок одновременно). - Ссылки всегда должны быть действительными. -С помощью ссылок и вида `Box` неизменные величины правил заимствования применяются на этапе сборки. С помощью `RefCell` они применяются *во время работы программы*. Если вы нарушите эти правила, работая с ссылками, то будет ошибка сборки. Если вы работаете с `RefCell` и нарушите эти правила, то программа вызовет панику и завершится. +С помощью ссылок и вида `Box` неизменные величины правил заимствования применяются на этапе сборки. С помощью `RefCell` они применяются *во время работы программы*. Если вы нарушите эти правила, работая с ссылками, то будет ошибка сборки. Если вы работаете с `RefCell` и нарушите эти правила, то программа вызовет сбой и завершится. -Преимущества проверки правил заимствования во время сборки заключаются в том, что ошибки будут обнаруживаться раньше - ещё в этапе разработки, а производительность во время выполнения не пострадает, поскольку весь анализ завершён заранее. По этим причинам проверка правил заимствования во время сборки является лучшим выбором в большинстве случаев, и именно поэтому она используется в Ржавчина по умолчанию. +Преимущества проверки правил заимствования во время сборки заключаются в том, что ошибки будут обнаруживаться раньше - ещё в этапе разработки, а производительность во время выполнения не пострадает, поскольку весь оценка завершён заранее. По этим причинам проверка правил заимствования во время сборки является лучшим выбором в большинстве случаев, и именно поэтому она используется в Ржавчине по умолчанию. -Преимущество проверки правил заимствования во время выполнения заключается в том, что определённые сценарии, безопасные для памяти, разрешаются там, где они были бы запрещены проверкой во время сборки. Постоянной анализ, как и сборщик Rust, по своей сути устоявшийся. Некоторые свойства кода невозможно обнаружить, анализируя код: самый известный пример - неполадка остановки, которая выходит за рамки этой книги, но является важной темой для исследования. +Преимущество проверки правил заимствования во время выполнения заключается в том, что определённые задумки, безопасные для памяти, разрешаются там, где они были бы запрещены проверкой во время сборки. Постоянной оценка, как и сборщик Ржавчина по своей сути устоявшийся. Некоторые свойства рукописи невозможно обнаружить, рассмотривая код: самый известный пример - неполадка остановки, которая выходит за рамки этой книги, но является важной темой для исследования. -Поскольку некоторый анализ невозможен, то если сборщик Ржавчина не может быть уверен, что код соответствует правилам владения, он может отклонить правильную программу; таким образом он является консервативным. Если Ржавчина принял неправильную программу, то пользователи не смогут доверять заверениям, которые даёт Rust. Однако, если Ржавчина отклонит правильную программу, то программист будет испытывать неудобства, но ничего катастрофического не произойдёт. Вид `RefCell` полезен, когда вы уверены, что ваш код соответствует правилам заимствования, но сборщик не может понять и обеспечить этого. +Поскольку некоторый оценка невозможен, то если сборщик Ржавчина не может быть уверен, что рукопись соответствует правилам владения, он может отклонить правильную программу; таким образом он является консервативным. Если Ржавчина принял неправильную программу, то пользователи не смогут доверять заверениям, которые даёт Ржавчина. Однако, если Ржавчина отклонит правильную программу, то программист будет испытывать неудобства, но ничего катастрофического не произойдёт. Вид `RefCell` полезен, когда вы уверены, что ваша рукопись соответствует правилам заимствования, но сборщик не может понять и обеспечить этого. -Подобно виду `Rc`, вид `RefCell` предназначен только для использования в однопоточных сценариях и выдаст ошибку времени сборки, если вы попытаетесь использовать его в многопоточном среде. Мы поговорим о том, как получить возможность `RefCell` во многопоточной программе в главе 16. +Подобно виду `Rc`, вид `RefCell` предназначен только для использования в однопоточных задумках и выдаст ошибку времени сборки, если вы попытаетесь использовать его в многопоточном среде. Мы поговорим о том, как получить возможность `RefCell` во многопоточной программе в главе 16. Вот список причин выбора видов `Box`, `Rc` или `RefCell`: @@ -33,19 +33,19 @@ ### Внутренняя изменяемость: изменяемое заимствование неизменяемого значения -Следствием правил заимствования является то, что когда у вас есть неизменяемое значение, вы не можете заимствовать его с изменением. Например, этот код не будет собираться: +Следствием правил заимствования является то, что когда у вас есть неизменяемое значение, вы не можете заимствовать его с изменением. Например, этот рукопись не будет собираться: ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch15-smart-pointers/no-listing-01-cant-borrow-immutable-as-mutable/src/main.rs}} ``` -Если вы попытаетесь собрать этот код, вы получите следующую ошибку: +Если вы попытаетесь собрать этот рукопись, вы получите следующую ошибку: ```console {{#include ../listings/ch15-smart-pointers/no-listing-01-cant-borrow-immutable-as-mutable/output.txt}} ``` -Однако бывают случаи, в которых было бы полезно, чтобы предмет мог изменять себя при помощи своих способов, но казался неизменным для прочего кода. Код вне способов этого предмета не должен иметь возможности изменять его содержимое. Использование `RefCell` - один из способов получить возможность внутренней изменяемости, но при этом `RefCell` не позволяет полностью обойти правила заимствования: средство проверки правил заимствования в сборщике позволяет эту внутреннюю изменяемость, однако правила заимствования проверяются во время выполнения. Если вы нарушите правила, то вместо ошибки сборки вы получите `panic!`. +Однако бывают случаи, в которых было бы полезно, чтобы предмет мог изменять себя при помощи своих способов, но казался неизменным для прочего рукописи. Рукопись вне способов этого предмета не должен иметь возможности изменять его содержимое. Использование `RefCell` - один из способов получить возможность внутренней изменяемости, но при этом `RefCell` не позволяет полностью обойти правила заимствования: средство проверки правил заимствования в сборщике позволяет эту внутреннюю изменяемость, однако правила заимствования проверяются во время выполнения. Если вы нарушите правила, то вместо ошибки сборки вы получите `panic!`. Давайте разберём опытный пример, в котором мы можем использовать `RefCell` для изменения неизменяемого значения и посмотрим, почему это полезно. @@ -53,11 +53,11 @@ Иногда во время проверки программист использует один вид вместо другого для того, чтобы проверить определённое поведение и убедиться, что оно выполнено правильно. Такой вид-заместитель называется *проверочным повторителем*. Воспринимайте его как «каскадёра» в кинематографе, когда повторитель заменяет актёра для выполнения определённой сложной сцены. Проверочные повторители заменяют другие виды при выполнении проверок. *Инсценировочные (mock) предметы* — это особый вид проверочных повторителей, которые сохраняют данные происходящих во время проверки действий тем самым позволяя вам убедиться впоследствии, что все действия были выполнены правильно. -В Ржавчина нет предметов в том же смысле, в каком они есть в других языках и в Ржавчина нет возможности мок предметов, встроенных в обычную библиотеку, как в некоторых других языках. Однако вы определённо можете создать устройство, которая будет служить тем же целям, что и мок предмет. +В Ржавчине нет предметов в том же смысле, в каком они есть в других языках и в Ржавчине нет возможности мок предметов, встроенных в обычную библиотеку, как в некоторых других языках. Однако вы определённо можете создать устройство, которая будет служить тем же целям, что и мок предмет. -Вот сценарий, который мы будем проверять: мы создадим библиотеку, которая отслеживает значение по отношению к заранее определённому наивысшему значению и отправляет сообщения в зависимости от того, насколько текущее значение находится близко к такому наивысшему значению. Эта библиотека может использоваться, например, для отслеживания квоты количества вызовов API пользователя, которые ему разрешено делать. +Вот задумка, который мы будем проверять: мы создадим библиотеку, которая отслеживает значение по отношению к заранее определённому наивысшему значению и отправляет сообщения в зависимости от того, насколько текущее значение находится близко к такому наивысшему значению. Эта библиотека может использоваться, например, для отслеживания квоты количества вызовов API пользователя, которые ему разрешено делать. -Наша библиотека будет предоставлять только функции отслеживания того, насколько близко к наивысшему значению находится значение и какие сообщения должны быть внутри в этот мгновение. Ожидается, что приложения, использующие нашу библиотеку, предоставят рычаг для отправки сообщений: приложение может поместить сообщение в приложение, отправить электронное письмо, отправить текстовое сообщение или что-то ещё. Библиотеке не нужно знать эту подробность. Все что ему нужно - это что-то, что выполняет особенность, который мы предоставим с названием `Messenger`. Приложение 15-20 показывает код библиотеки: +Наша библиотека будет предоставлять только функции отслеживания того, насколько близко к наивысшему значению находится значение и какие сообщения должны быть внутри в этот мгновение. Ожидается, что приложения, использующие нашу библиотеку, предоставят рычаг для отправки сообщений: приложение может поместить сообщение в приложение, отправить электронное письмо, отправить текстовое сообщение или что-то ещё. Библиотеке не нужно знать эту подробность. Все что ему нужно - это что-то, что выполняет особенность, который мы предоставим с названием `Messenger`. Приложение 15-20 показывает рукопись библиотеки: Файл: src/lib.rs @@ -67,9 +67,9 @@ Приложение 15-20: Библиотека для отслеживания степени приближения того или иного значения к наиболее допустимой величине и предупреждения, в случае если значение достигает определённого уровня -Одна важная часть этого кода состоит в том, что особенность `Messenger` имеет один способ `send`, принимающий переменнойми неизменяемую ссылку на `self` и текст сообщения. Он является внешней оболочкой, который должен иметь наш мок предмет. Другой важной частью является то, что мы хотим проверить поведение способа `set_value` у вида `LimitTracker`. Мы можем изменить значение, которое передаём свойствоом `value`, но `set_value` ничего не возвращает и нет основания, чтобы мы могли бы проверить утверждения о выполнении способа. Мы хотим иметь возможность сказать, что если мы создаём `LimitTracker` с чем-то, что выполняет особенность `Messenger` и с определённым значением для `max`, то когда мы передаём разные числа в переменной `value` образец self.messenger отправляет соответствующие сообщения. +Одна важная часть этого рукописи состоит в том, что особенность `Messenger` имеет один способ `send`, принимающий переменнойми неизменяемую ссылку на `self` и текст сообщения. Он является внешней оболочкой, который должен иметь наш мок предмет. Другой важной частью является то, что мы хотим проверить поведение способа `set_value` у вида `LimitTracker`. Мы можем изменить значение, которое передаём свойствоом `value`, но `set_value` ничего не возвращает и нет основания, чтобы мы могли бы проверить утверждения о выполнении способа. Мы хотим иметь возможность сказать, что если мы создаём `LimitTracker` с чем-то, что выполняет особенность `Messenger` и с определённым значением для `max`, то когда мы передаём разные числа в переменной `value` образец self.messenger отправляет соответствующие сообщения. -Нам нужен мок предмет, который вместо отправки электронного письма или текстового сообщения будет отслеживать сообщения, которые были ему поручены для отправки через `send`. Мы можем создать новый образец мок предмета. создать `LimitTracker` с использованием мок предмет для него, вызвать способ `set_value` у образца `LimitTracker`, а затем проверить, что мок предмет имеет ожидаемое сообщение. В приложении 15-21 показана попытка выполнить мок предмет, чтобы сделать именно то что хотим, но анализатор заимствований не разрешит такой код: +Нам нужен мок предмет, который вместо отправки электронного письма или текстового сообщения будет отслеживать сообщения, которые были ему поручены для отправки через `send`. Мы можем создать новый образец мок предмета. создать `LimitTracker` с использованием мок предмет для него, вызвать способ `set_value` у образца `LimitTracker`, а затем проверить, что мок предмет имеет ожидаемое сообщение. В приложении 15-21 показана попытка выполнить мок предмет, чтобы сделать именно то что хотим, но оценщик заимствований не разрешит такой код: Файл: src/lib.rs @@ -79,7 +79,7 @@ Приложение 15-21: Попытка выполнить MockMessenger, которая не была принята рычагом проверки заимствований -Этот проверочный код определяет устройство `MockMessenger`, в которой есть поле `sent_messages` со значениями вида `Vec` из `String` для отслеживания сообщений, которые поручены устройстве для отправки. Мы также определяем сопряженную функцию `new`, чтобы было удобно создавать новые образцы `MockMessenger`, которые создаются с пустым списком сообщений. Затем мы выполняем особенность `Messenger` для вида `MockMessenger`, чтобы передать `MockMessenger` в `LimitTracker`. В ярлыке способа `send` мы принимаем сообщение для передачи в качестве свойства и сохраняем его в `MockMessenger` внутри списка `sent_messages`. +Этот проверочный рукопись определяет устройство `MockMessenger`, в которой есть поле `sent_messages` со значениями вида `Vec` из `String` для отслеживания сообщений, которые поручены устройстве для отправки. Мы также определяем сопряженную функцию `new`, чтобы было удобно создавать новые образцы `MockMessenger`, которые создаются с пустым списком сообщений. Затем мы выполняем особенность `Messenger` для вида `MockMessenger`, чтобы передать `MockMessenger` в `LimitTracker`. В ярлыке способа `send` мы принимаем сообщение для передачи в качестве свойства и сохраняем его в `MockMessenger` внутри списка `sent_messages`. В этом проверке мы проверяем, что происходит, когда `LimitTracker` сказано установить `value` в значение, превышающее 75 процентов от значения `max`. Сначала мы создаём новый `MockMessenger`, который будет иметь пустой список сообщений. Затем мы создаём новый `LimitTracker` и передаём ему ссылку на новый `MockMessenger` и `max` значение равное 100. Мы вызываем способ `set_value` у `LimitTracker` со значением 80, что составляет более 75 процентов от 100. Затем мы с помощью утверждения проверяем, что `MockMessenger` должен содержать одно сообщение из списка внутренних сообщений. @@ -91,7 +91,7 @@ Мы не можем изменять `MockMessenger` для отслеживания сообщений, потому что способ `send` принимает неизменяемую ссылку на `self`. Мы также не можем принять предложение из текста ошибки, чтобы использовать `&mut self`, потому что тогда ярлык `send` не будет соответствовать ярлыке в определении особенности `Messenger` (не стесняйтесь попробовать и посмотреть, какое сообщение об ошибке получите вы). -Это случаей, в которой внутренняя изменяемость может помочь! Мы сохраним `sent_messages` внутри вида `RefCell`, а затем в способе `send` сообщение сможет изменить список `sent_messages` для хранения сообщений, которые мы видели. Приложение 15-22 показывает, как это выглядит: +Это случай, в которой внутренняя изменяемость может помочь! Мы сохраним `sent_messages` внутри вида `RefCell`, а затем в способе `send` сообщение сможет изменить список `sent_messages` для хранения сообщений, которые мы видели. Приложение 15-22 показывает, как это выглядит: Файл: src/lib.rs @@ -113,9 +113,9 @@ При создании неизменных и изменяемых ссылок мы используем правила написания `&` и `&mut` соответственно. У вида `RefCell`, мы используем способы `borrow` и `borrow_mut`, которые являются частью безопасного API, который принадлежит `RefCell`. Способ `borrow` возвращает вид умного указателя `Ref`, способ `borrow_mut` возвращает вид умного указателя `RefMut`. Оба вида выполняют особенность `Deref`, поэтому мы можем рассматривать их как обычные ссылки. -Вид `RefCell` отслеживает сколько умных указателей `Ref` и `RefMut` активны в данное время. Каждый раз, когда мы вызываем `borrow`, вид `RefCell` увеличивает количество активных заимствований. Когда значение `Ref` выходит из области видимости, то количество неизменяемых заимствований уменьшается на единицу. Как и с правилами заимствования во время сборки, `RefCell` позволяет иметь много неизменяемых заимствований или одно изменяемое заимствование в любой мгновение времени. +Вид `RefCell` отслеживает сколько умных указателей `Ref` и `RefMut` действительо в данное время. Каждый раз, когда мы вызываем `borrow`, вид `RefCell` увеличивает количество действующих заимствований. Когда значение `Ref` выходит из области видимости, то количество неизменяемых заимствований уменьшается на единицу. Как и с правилами заимствования во время сборки, `RefCell` позволяет иметь много неизменяемых заимствований или одно изменяемое заимствование в любой мгновение времени. -Если попытаться нарушить эти правила, то вместо получения ошибки сборщика, как это было бы со ссылками, выполнение `RefCell` будет вызывать панику во время выполнения. В приложении 15-23 показана изменение выполнения `send` из приложения 15-22. Мы намеренно пытаемся создать два изменяемых заимствования активных для одной и той же области видимости, чтобы показать как `RefCell` не позволяет нам делать так во время выполнения. +Если попытаться нарушить эти правила, то вместо получения ошибки сборщика, как это было бы со ссылками, выполнение `RefCell` будет вызывать сбой во время выполнения. В приложении 15-23 показана изменение выполнения `send` из приложения 15-22. Мы намеренно пытаемся создать два изменяемых заимствования действующих для одной и той же области видимости, чтобы показать как `RefCell` не позволяет нам делать так во время выполнения. Файл: src/lib.rs @@ -123,17 +123,17 @@ {{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-23/src/lib.rs:here}} ``` -Приложение 15-23: Создание двух изменяемых ссылок в одной области видимости, чтобы убедиться, что RefCell<T> вызовет панику +Приложение 15-23: Создание двух изменяемых ссылок в одной области видимости, чтобы убедиться, что RefCell<T> вызовет сбой -Мы создаём переменную `one_borrow` для умного указателя `RefMut` возвращаемого из способа `borrow_mut`. Затем мы создаём другое изменяемое заимствование таким же образом в переменной `two_borrow`. Это создаёт две изменяемые ссылки в одной области видимости, что недопустимо. Когда мы запускаем проверки для нашей библиотеки, код в приложении 15-23 собирается без ошибок, но проверка завершится неудачно: +Мы создаём переменную `one_borrow` для умного указателя `RefMut` возвращаемого из способа `borrow_mut`. Затем мы создаём другое изменяемое заимствование таким же образом в переменной `two_borrow`. Это создаёт две изменяемые ссылки в одной области видимости, что недопустимо. Когда мы запускаем проверки для нашей библиотеки, рукопись в приложении 15-23 собирается без ошибок, но проверка завершится неудачно: ```console {{#include ../listings/ch15-smart-pointers/listing-15-23/output.txt}} ``` -Обратите внимание, что код вызвал панику с сообщением `already borrowed: BorrowMutError`. Вот так вид `RefCell` обрабатывает нарушения правил заимствования во время выполнения. +Обратите внимание, что рукопись вызвал сбой с сообщением `already borrowed: BorrowMutError`. Вот так вид `RefCell` обрабатывает нарушения правил заимствования во время выполнения. -Решение отлавливать ошибки заимствования во время выполнения, а не во время сборки, как мы сделали здесь, означает, что вы возможно будете находить ошибки в своём коде на более поздних этапах разработки: возможно, не раньше, чем ваш код будет развернут в рабочем окружении. Кроме того, ваш код будет иметь небольшие потери производительности в этапе работы, поскольку заимствования будут отслеживаться во время выполнения, а не во время сборки. Однако использование `RefCell` позволяет написать предмет-имитатор, который способен изменять себя, чтобы сохранять сведения о тех значениях, которые он получал, пока вы использовали его в среде, где разрешены только неизменяемые значения. Вы можете использовать `RefCell`, несмотря на его недостатки, чтобы получить больше возможности, чем дают обычные ссылки. +Решение отлавливать ошибки заимствования во время выполнения, а не во время сборки, как мы сделали здесь, означает, что вы возможно будете находить ошибки в своей рукописи на более поздних этапах разработки: возможно, не раньше, чем ваша рукопись будет развернут в рабочем окружении. Кроме того, ваша рукопись будет иметь небольшие потери производительности в этапе работы, поскольку заимствования будут отслеживаться во время выполнения, а не во время сборки. Однако использование `RefCell` позволяет написать предмет-имитатор, который способен изменять себя, чтобы сохранять сведения о тех значениях, которые он получал, пока вы использовали его в среде, где разрешены только неизменяемые значения. Вы можете использовать `RefCell`, несмотря на его недостатки, чтобы получить больше возможности, чем дают обычные ссылки. ### Наличие нескольких владельцев изменяемых данных путём объединения видов `Rc` и `RefCell` @@ -153,7 +153,7 @@ Мы оборачиваем список у переменной `a` в вид `Rc`, поэтому при создании списков в переменные `b` и `c` они оба могут ссылаться на `a`, что мы и сделали в приложении 15-18. -После создания списков `a`, `b` и `c` мы хотим добавить 10 к значению в `value`. Для этого вызовем `borrow_mut` у `value`, который использует функцию самостоятельного разыменования, о которой мы говорили в главе 5 (см. раздел ["Где находится оператор `->`?"]) во внутреннее значение `RefCell`. Способ `borrow_mut` возвращает умный указатель `RefMut`, и мы используя оператор разыменования, изменяем внутреннее значение. +После создания списков `a`, `b` и `c` мы хотим добавить 10 к значению в `value`. Для этого вызовем `borrow_mut` у `value`, который использует функцию самостоятельного разыменования, о которой мы говорили в главе 5 (см. раздел ["Где находится приказчик `->`?"]) во внутреннее значение `RefCell`. Способ `borrow_mut` возвращает умный указатель `RefMut`, и мы используя приказчик разыменования, изменяем внутреннее значение. Когда мы печатаем `a`, `b` и `c` то видим, что все они имеют изменённое значение равное 15, а не 5: @@ -161,7 +161,7 @@ {{#include ../listings/ch15-smart-pointers/listing-15-24/output.txt}} ``` -Эта техника довольно изящна! Используя `RefCell`, мы получаем внешне неизменяемое значение `List`. Но мы можем использовать способы `RefCell`, которые предоставляют доступ к его внутренностям, чтобы мы могли изменять наши данные, когда это необходимо. Проверка правил заимствования во время выполнения защищает нас от гонок данных, и иногда стоит немного пожертвовать производительностью ради такой гибкости наших устройств данных. Обратите внимание, что `RefCell` не работает для многопоточного кода! `Mutex` - это thread-safe исполнение `RefCell`, а `Mutex` мы обсудим в главе 16. +Эта техника довольно изящна! Используя `RefCell`, мы получаем внешне неизменяемое значение `List`. Но мы можем использовать способы `RefCell`, которые предоставляют доступ к его внутренностям, чтобы мы могли изменять наши данные, когда это необходимо. Проверка правил заимствования во время выполнения защищает нас от гонок данных, и иногда стоит немного пожертвовать производительностью ради такой гибкости наших устройств данных. Обратите внимание, что `RefCell` не работает для многопоточного рукописи! `Mutex` - это thread-safe исполнение `RefCell`, а `Mutex` мы обсудим в главе 16. -["Где находится оператор `->`?"]: ch05-03-method-syntax.html#wheres-the---operator \ No newline at end of file +["Где находится приказчик `->`?"]: ch05-03-method-syntax.html#wheres-the---operator \ No newline at end of file diff --git a/rustbook-ru/src/ch15-06-reference-cycles.md b/rustbook-ru/src/ch15-06-reference-cycles.md index 1630fc642..f17260ac0 100644 --- a/rustbook-ru/src/ch15-06-reference-cycles.md +++ b/rustbook-ru/src/ch15-06-reference-cycles.md @@ -1,10 +1,10 @@ ## Ссылочные замыкания могут приводить к утечке памяти -Заверения безопасности памяти в Ржавчина затрудняют, но не делают невозможным случайное выделение памяти, которое никогда не очищается (известное как *утечка памяти* ). Полное предотвращение утечек памяти не является одной из заверений Rust, а это означает, что утечки памяти безопасны в Rust. Мы видим, что Ржавчина допускает утечку памяти с помощью `Rc` и `RefCell`: можно создавать ссылки, в которых элементы ссылаются друг на друга в цикле. Это создаёт утечки памяти, потому что счётчик ссылок каждого элемента в цикле никогда не достигнет 0, а значения никогда не будут удалены. +Заверения безопасности памяти в Ржавчине затрудняют, но не делают невозможным случайное выделение памяти, которое никогда не очищается (известное как *утечка памяти* ). Полное предотвращение утечек памяти не является одной из заверений Ржавчина, а это означает, что утечки памяти безопасны в Ржавчине. Мы видим, что Ржавчина допускает утечку памяти с помощью `Rc` и `RefCell`: можно создавать ссылки, в которых элементы ссылаются друг на друга в круговороте. Это создаёт утечки памяти, потому что счётчик ссылок каждого элемента в круговороте никогда не достигнет 0, а значения никогда не будут удалены. ### Создание ссылочного замыкания -Давайте посмотрим, как может произойти случаей ссылочного замыкания и как её предотвратить, начиная с определения перечисления `List` и способа `tail` в приложении 15-25: +Давайте посмотрим, как может произойти случай ссылочного замыкания и как её предотвратить, начиная с определения перечисления `List` и способа `tail` в приложении 15-25: Файл: src/main.rs @@ -16,7 +16,7 @@ Мы используем другую вариацию определения `List` из приложения 15-5. Второй элемент в исходе `Cons` теперь `RefCell>`, что означает, что вместо возможности менять значение `i32`, как мы делали в приложении 15-24, мы хотим менять значение `List`, на которое указывает исход `Cons`. Мы также добавляем способ `tail`, чтобы нам было удобно обращаться ко второму элементу, если у нас есть исход `Cons`. -В приложении 15-26 мы добавляем `main` функцию, которая использует определения приложения 15-25. Этот код создаёт список в переменной `a` и список `b`, который указывает на список `a`. Затем он изменяет список внутри `a` так, чтобы он указывал на `b`, создавая ссылочное замыкание. В коде есть указания `println!`, чтобы показать значения счётчиков ссылок в различных точках этого этапа. +В приложении 15-26 мы добавляем `main` функцию, которая использует определения приложения 15-25. Этот рукопись создаёт список в переменной `a` и список `b`, который указывает на список `a`. Затем он изменяет список внутри `a` так, чтобы он указывал на `b`, создавая ссылочное замыкание. В рукописи есть указания `println!`, чтобы показать значения счётчиков ссылок в различных точках этого этапа. Файл: src/main.rs @@ -24,35 +24,35 @@ {{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-26/src/main.rs:here}} ``` -Приложение 15-26: Создание ссылочного цикла из двух значений List, указывающих друг на друга +Приложение 15-26: Создание ссылочного круговорота из двух значений List, указывающих друг на друга Мы создаём образец `Rc` содержащий значение `List` в переменной `a` с начальным списком `5, Nil`. Затем мы создаём образец `Rc` содержащий другое значение `List` в переменной `b`, которое содержит значение 10 и указывает на список в `a`. -Мы меняем `a` так, чтобы он указывал на `b` вместо `Nil`, создавая зацикленность. Мы делаем это с помощью способа `tail`, чтобы получить ссылку на `RefCell>` из переменной `a`, которую мы помещаем в переменную `link`. Затем мы используем способ `borrow_mut` из вида `RefCell>`, чтобы изменить внутреннее значение вида `Rc`, содержащего начальное значение `Nil` на значение вида `Rc` взятое из переменной `b`. +Мы меняем `a` так, чтобы он указывал на `b` вместо `Nil`, создавая замкнутость. Мы делаем это с помощью способа `tail`, чтобы получить ссылку на `RefCell>` из переменной `a`, которую мы помещаем в переменную `link`. Затем мы используем способ `borrow_mut` из вида `RefCell>`, чтобы изменить внутреннее значение вида `Rc`, содержащего начальное значение `Nil` на значение вида `Rc` взятое из переменной `b`. -Когда мы запускаем этот код, оставив последний `println!` с примечаниями в данный мгновение, мы получим вывод: +Когда мы запускаем этот рукопись, оставив последний `println!` с примечаниями в данный мгновение, мы получим вывод: ```console {{#include ../listings/ch15-smart-pointers/listing-15-26/output.txt}} ``` -Количество ссылок на образцы `Rc` как в `a`, так и в `b` равно 2 после того, как мы заменили список в `a` на ссылку на `b`. В конце `main` Ржавчина уничтожает переменную `b`, что уменьшает количество ссылок на `Rc` из `b` с 2 до 1. Память, которую `Rc` занимает в куче, не будет освобождена в этот мгновение, потому что количество ссылок на неё равно 1, а не 0. Затем Ржавчина удаляет `a`, что уменьшает количество ссылок образца `Rc` в `a` с 2 до 1. Память этого образца также не может быть освобождена, поскольку другой образец `Rc` по-прежнему ссылается на него. Таким образом, память, выделенная для списка не будет освобождена никогда. Чтобы наглядно представить этот цикл ссылок, мы создали диаграмму на рисунке 15-4. +Количество ссылок на образцы `Rc` как в `a`, так и в `b` равно 2 после того, как мы заменили список в `a` на ссылку на `b`. В конце `main` Ржавчина уничтожает переменную `b`, что уменьшает количество ссылок на `Rc` из `b` с 2 до 1. Память, которую `Rc` занимает в куче, не будет освобождена в этот мгновение, потому что количество ссылок на неё равно 1, а не 0. Затем Ржавчина удаляет `a`, что уменьшает количество ссылок образца `Rc` в `a` с 2 до 1. Память этого образца также не может быть освобождена, поскольку другой образец `Rc` по-прежнему ссылается на него. Таким образом, память, выделенная для списка не будет освобождена никогда. Чтобы наглядно представить этот круговорот ссылок, мы создали диаграмму на рисунке 15-4. Reference cycle of lists -Рисунок 15-4: Ссылочный цикл списков a и b, указывающих друг на друга +Рисунок 15-4: Ссылочный круговорот списков a и b, указывающих друг на друга -Если вы удалите последний примечание с `println!` и запустите программу, Ржавчина будет пытаться печатать зацикленность в `a`, указывающей на `b`, указывающей на `a` и так далее, пока не переполниться обойма. +Если вы удалите последний примечание с `println!` и запустите программу, Ржавчина будет пытаться печатать замкнутость в `a`, указывающей на `b`, указывающей на `a` и так далее, пока не переполниться обойма. -По сравнению с существующей программой, последствия создания цикла ссылок в этом примере не так страшны: сразу после создания цикла ссылок программа завершается. Однако если более сложная программа выделит много памяти в цикле и будет удерживать её в течение длительного времени, программа будет потреблять больше памяти, чем ей нужно, и может перенапрячь систему, что приведёт к исчерпанию доступной памяти. +По сравнению с существующей программой, последствия создания круговорота ссылок в этом примере не так страшны: сразу после создания круговорота ссылок программа завершается. Однако если более сложная программа выделит много памяти в круговороте и будет удерживать её в течение длительного времени, программа будет потреблять больше памяти, чем ей нужно, и может перенапрячь систему, что приведёт к исчерпанию доступной памяти. -Вызвать образование ссылочной зацикленности не просто, но и не невозможно. Если у вас есть значения `RefCell` которые содержат значения `Rc` или подобные вложенные сочетания видов с внутренней изменчивостью и подсчётом ссылок, вы должны убедиться, что вы не создаёте зацикленность; Вы не можете полагаться на то, что Ржавчина их обнаружит. Создание ссылочной зацикленности являлось бы логической ошибкой в программе, для которой вы должны использовать самостоятельно е проверки, проверку кода и другие опытов разработки программного обеспечения для её уменьшения. +Вызвать образование ссылочной замкнутости не просто, но и не невозможно. Если у вас есть значения `RefCell` которые содержат значения `Rc` или подобные вложенные сочетания видов с внутренней изменчивостью и подсчётом ссылок, вы должны убедиться, что вы не создаёте замкнутость; Вы не можете полагаться на то, что Ржавчина их обнаружит. Создание ссылочной замкнутости являлось бы разумной ошибкой в программе, для которой вы должны использовать самостоятельно е проверки, проверку рукописи и другие опытов разработки программного обеспечения для её уменьшения. -Другое решение для избежания ссылочной зацикленности - это ресоздание ваших устройств данных, чтобы некоторые ссылки выражали владение, а другие - отсутствие владения. В итоге можно иметь циклы, построенные на некоторых отношениях владения и некоторые не основанные на отношениях владения, тогда только отношения владения влияют на то, можно ли удалить значение. В приложении 15-25 мы всегда хотим, чтобы исходы `Cons` владели своим списком, поэтому ресоздание устройства данных невозможна. Давайте рассмотрим пример с использованием графов, состоящих из родительских и дочерних узлов, чтобы увидеть, когда отношения владения не являются подходящим способом предотвращения ссылочной зацикленности. +Другое решение для избежания ссылочной замкнутости - это ресоздание ваших устройств данных, чтобы некоторые ссылки выражали владение, а другие - отсутствие владения. В итоге можно иметь круговороты, построенные на некоторых отношениях владения и некоторые не основанные на отношениях владения, тогда только отношения владения влияют на то, можно ли удалить значение. В приложении 15-25 мы всегда хотим, чтобы исходы `Cons` владели своим списком, поэтому ресоздание устройства данных невозможна. Давайте рассмотрим пример с использованием графов, состоящих из родительских и дочерних узлов, чтобы увидеть, когда отношения владения не являются подходящим способом предотвращения ссылочной замкнутости. -### Предотвращение ссылочной зацикленности: замена умного указателя `Rc` на `Weak` +### Предотвращение ссылочной замкнутости: замена умного указателя `Rc` на `Weak` -До сих пор мы выясняли, что вызов `Rc::clone` увеличивает `strong_count` образца `Rc`, а образец `Rc` удаляется, только если его `strong_count` равен 0. Вы также можете создать *слабую ссылку* на значение внутри образца `Rc`, вызвав `Rc::downgrade` и передав ссылку на `Rc`. Сильные ссылки - это то с помощью чего вы можете поделиться владением образца `Rc`. Слабые ссылки не отражают связи владения, и их подсчёт не влияет на то, когда образец `Rc` будет очищен. Они не приведут к ссылочному циклу, потому что любой цикл, включающий несколько слабых ссылок, будет разорван, как только количество сильных ссылок для задействованных значений станет равным 0. +До сих пор мы выясняли, что вызов `Rc::clone` увеличивает `strong_count` образца `Rc`, а образец `Rc` удаляется, только если его `strong_count` равен 0. Вы также можете создать *слабую ссылку* на значение внутри образца `Rc`, вызвав `Rc::downgrade` и передав ссылку на `Rc`. Сильные ссылки - это то с помощью чего вы можете поделиться владением образца `Rc`. Слабые ссылки не отражают связи владения, и их подсчёт не влияет на то, когда образец `Rc` будет очищен. Они не приведут к ссылочному круговороту, потому что любой круговорот, включающий несколько слабых ссылок, будет разорван, как только количество сильных ссылок для задействованных значений станет равным 0. Когда вы вызываете `Rc::downgrade`, вы получаете умный указатель вида `Weak`. Вместо того чтобы увеличить `strong_count` в образце `Rc` на 1, вызов `Rc::downgrade` увеличивает `weak_count` на 1. Вид `Rc` использует `weak_count` для отслеживания количества существующих ссылок `Weak`, подобно `strong_count`. Разница в том, что `weak_count` не должен быть равен 0, чтобы образец `Rc` мог быть удалён. @@ -86,7 +86,7 @@ #### Добавление ссылки от ребёнка к его родителю -Для того, чтобы дочерний узел знал о своём родительском узле нужно добавить поле `parent` в наше определение устройства `Node`. Неполадкав том, чтобы решить, каким должен быть вид `parent`. Мы знаем, что он не может содержать `Rc`, потому что это создаст ссылочную зацикленность с `leaf.parent` указывающей на `branch` и `branch.children`, указывающей на `leaf`, что приведёт к тому, что их значения `strong_count` никогда не будут равны 0. +Для того, чтобы дочерний узел знал о своём родительском узле нужно добавить поле `parent` в наше определение устройства `Node`. Неполадкав том, чтобы решить, каким должен быть вид `parent`. Мы знаем, что он не может содержать `Rc`, потому что это создаст ссылочную замкнутость с `leaf.parent` указывающей на `branch` и `branch.children`, указывающей на `leaf`, что приведёт к тому, что их значения `strong_count` никогда не будут равны 0. Подумаем об этих отношениях по-другому, родительский узел должен владеть своими потомками: если родительский узел удаляется, его дочерние узлы также должны быть удалены. Однако дочерний элемент не должен владеть своим родителем: если мы удаляем дочерний узел то родительский элемент все равно должен существовать. Это случай для использования слабых ссылок! @@ -118,7 +118,7 @@ leaf parent = None Когда мы создаём узел `branch` у него также будет новая ссылка вида `Weak` в поле `parent`, потому что узел `branch` не имеет своего родительского узла. У нас все ещё есть `leaf` как один из потомков узла `branch`. Когда мы получили образец `Node` в переменной `branch`, мы можем изменить переменную `leaf` чтобы дать ей `Weak` ссылку на её родителя. Мы используем способ `borrow_mut` у вида `RefCell>` поля `parent` у `leaf`, а затем используем функцию `Rc::downgrade` для создания `Weak` ссылки на `branch` из `Rc` в `branch`. -Когда мы снова напечатаем родителя `leaf` то в этот раз мы получим исход `Some` содержащий `branch`, теперь `leaf` может получить доступ к своему родителю! Когда мы печатаем `leaf`, мы также избегаем цикла, который в конечном итоге заканчивался переполнением обоймы, как в приложении 15-26; ссылки вида `Weak` печатаются как `(Weak)`: +Когда мы снова напечатаем родителя `leaf` то в этот раз мы получим исход `Some` содержащий `branch`, теперь `leaf` может получить доступ к своему родителю! Когда мы печатаем `leaf`, мы также избегаем круговорота, который в конечном итоге заканчивался переполнением обоймы, как в приложении 15-26; ссылки вида `Weak` печатаются как `(Weak)`: ```text leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) }, @@ -126,7 +126,7 @@ children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) }, children: RefCell { value: [] } }] } }) ``` -Отсутствие бесконечного вывода означает, что этот код не создал ссылочной зацикленности. Мы также можем сказать это, посмотрев на значения, которые мы получаем при вызове `Rc::strong_count` и `Rc::weak_count`. +Отсутствие бесконечного вывода означает, что этот рукопись не создал ссылочной замкнутости. Мы также можем сказать это, посмотрев на значения, которые мы получаем при вызове `Rc::strong_count` и `Rc::weak_count`. #### Визуализация изменений в `strong_count` и `weak_count` @@ -146,16 +146,16 @@ children: RefCell { value: [] } }] } }) Если мы попытаемся получить доступ к родителю переменной `leaf` после окончания области видимости, мы снова получим значение `None`. В конце программы `Rc` внутри `leaf` имеет strong count 1 и weak count 0 потому что переменная `leaf` снова является единственной ссылкой на `Rc`. -Вся логика, которая управляет счётчиками и сбросом их значений, встроена внутри `Rc` и `Weak` и их выполнений особенности `Drop`. Указав, что отношение из дочернего к родительскому элементу должно быть ссылкой вида `Weak` в определении `Node`, делает возможным иметь родительские узлы, указывающие на дочерние узлы и наоборот, не создавая ссылочной зацикленности и утечек памяти. +Вся ход мыслей, которая управляет счётчиками и сбросом их значений, встроена внутри `Rc` и `Weak` и их выполнений особенности `Drop`. Указав, что отношение из дочернего к родительскому элементу должно быть ссылкой вида `Weak` в определении `Node`, делает возможным иметь родительские узлы, указывающие на дочерние узлы и наоборот, не создавая ссылочной замкнутости и утечек памяти. ## Итоги -В этой главе рассказано как использовать умные указатели для обеспечения различных заверений и соглашений по сравнению с обычными ссылками, которые Ржавчина использует по умолчанию. Вид `Box` имеет известный размер и указывает на данные размещённые в куче. Вид `Rc` отслеживает количество ссылок на данные в куче, поэтому данные могут иметь несколько владельцев. Вид `RefCell` с его внутренней изменяемостью предоставляет вид, который можно использовать при необходимости неизменного вида, но необходимости изменить внутреннее значение этого типа; он также обеспечивает соблюдение правил заимствования во время выполнения, а не во время сборки. +В этой главе рассказано как использовать умные указатели для обеспечения различных заверений и соглашений по сравнению с обычными ссылками, которые Ржавчина использует по умолчанию. Вид `Box` имеет известный размер и указывает на данные размещённые в куче. Вид `Rc` отслеживает количество ссылок на данные в куче, поэтому данные могут иметь несколько владельцев. Вид `RefCell` с его внутренней изменяемостью предоставляет вид, который можно использовать при необходимости неизменного вида, но необходимости изменить внутреннее значение этого вида; он также обеспечивает соблюдение правил заимствования во время выполнения, а не во время сборки. -Мы обсудили также особенности `Deref` и `Drop`, которые обеспечивают большую возможность умных указателей. Мы исследовали ссылочную зацикленность, которая может вызывать утечки памяти и как это предотвратить с помощью вида `Weak`. +Мы обсудили также особенности `Deref` и `Drop`, которые обеспечивают большую возможность умных указателей. Мы исследовали ссылочную замкнутость, которая может вызывать утечки памяти и как это предотвратить с помощью вида `Weak`. Если эта глава вызвала у вас влечение и вы хотите выполнить свои собственные умные указатели, обратитесь к ["The Rustonomicon"](https://doc.rust-lang.org/nomicon/index.html) за более полезной сведениями. -Далее мы поговорим о одновременности в Rust. Вы даже узнаете о нескольких новых умных указателях. +Далее мы поговорим о одновременности в Ржавчине Вы даже узнаете о нескольких новых умных указателях. diff --git a/rustbook-ru/src/ch16-00-concurrency.md b/rustbook-ru/src/ch16-00-concurrency.md index 76708605f..10d48f712 100644 --- a/rustbook-ru/src/ch16-00-concurrency.md +++ b/rustbook-ru/src/ch16-00-concurrency.md @@ -1,8 +1,8 @@ # Многопоточность без страха -Безопасное и эффективное управление многопоточным программированием — ещё одна из основных целей Rust. *Многопоточное программирование*, когда разные части программы выполняются независимо, и *одновременное программирование*, когда разные части программы выполняются одновременно, становятся всё более важными, поскольку всё больше компьютеров используют преимущества нескольких процессоров. Исторически программирование в этих условиях было сложным и подверженным ошибкам: Ржавчина надеется изменить это. +Безопасное и качественное управление многопоточным программированием — ещё одна из основных целей Ржавчина*Многопоточное программирование*, когда разные части программы выполняются независимо, и *одновременное программирование*, когда разные части программы выполняются одновременно, становятся всё более важными, поскольку всё больше компьютеров используют преимущества нескольких процессоров. Исторически программирование в этих условиях было сложным и подверженным ошибкам: Ржавчина надеется изменить это. -Первоначально приказ Ржавчина считала, что обеспечение безопасности памяти и предотвращение неполадок многопоточности — это две отдельные сбоев, которые необходимо решать различными способами. Со временем приказ обнаружила, что системы владения и система видов являются мощным набором средств, помогающих управлять безопасностью памяти *и* неполадками многопоточного одновременности! Используя владение и проверку видов, многие ошибки многопоточности являются ошибками времени сборки в Rust, а не ошибками времени выполнения. Поэтому вместо того, чтобы тратить много времени на попытки воспроизвести точные обстоятельства, при которых возникает ошибка многопоточности во время выполнения, неправильный код будет отклонён с ошибкой. В итоге вы можете исправить свой код во время работы над ним, а не после развёртывания на рабочем сервере. Мы назвали этот особенность Ржавчина *бесстрашной* *многопоточностью*. Бесстрашная многопоточность позволяет вам писать код, который не содержит скрытых ошибок и легко ресогласуется без внесения новых. +Первоначально приказ Ржавчина считала, что обеспечение безопасности памяти и предотвращение неполадок многопоточности — это две отдельные сбоев, которые необходимо решать различными способами. Со временем приказ обнаружила, что системы владения и система видов являются мощным набором средств, помогающих управлять безопасностью памяти *и* неполадками многопоточного одновременности! Используя владение и проверку видов, многие ошибки многопоточности являются ошибками времени сборки в Ржавчине, а не ошибками времени выполнения. Поэтому вместо того, чтобы тратить много времени на попытки воспроизвести точные обстоятельства, при которых возникает ошибка многопоточности во время выполнения, неправильный рукопись будет отклонён с ошибкой. В итоге вы можете исправить свой рукопись во время работы над ним, а не после развёртывания на рабочем отделеном вычислителе. Мы назвали этот особенность Ржавчина *бесстрашной* *многопоточностью*. Бесстрашная многопоточность позволяет вам писать рукопись, который не содержит скрытых ошибок и легко ресогласуется без внесения новых. > Примечание: для простоты мы будем называть многие сбоев *многопоточными*, хотя более точный понятие здесь * — многопоточные и/или одновременные*. Если бы эта книга была о многопоточности и/или одновременности, мы были бы более определены. В этой главе, пожалуйста, всякий раз, когда мы используем понятие *«многопоточный»*, мысленно замените на понятие *«многопоточный и/или одновременный»*. @@ -10,7 +10,7 @@ Вот темы, которые мы рассмотрим в этой главе: -- Как создать потоки для одновременного запуска нескольких отрывков кода +- Как создать потоки для одновременного запуска нескольких отрывков рукописи - Многопоточность *передачи сообщений*, где потоки передают сообщения между потоками - Многопоточность для *совместно используемого состояния*, когда несколько потоков имеют доступ к некоторому отрывку данных -- Особенности `Sync` и `Send`, которые расширяют заверения многопоточности в Ржавчина для пользовательских видов, а также видов, предоставляемых встроенной библиотекой +- Особенности `Sync` и `Send`, которые расширяют заверения многопоточности в Ржавчине для пользовательских видов, а также видов, предоставляемых встроенной библиотекой diff --git a/rustbook-ru/src/ch16-01-threads.md b/rustbook-ru/src/ch16-01-threads.md index d6cad7760..63a60659e 100644 --- a/rustbook-ru/src/ch16-01-threads.md +++ b/rustbook-ru/src/ch16-01-threads.md @@ -1,20 +1,20 @@ -## Использование потоков для одновременного выполнения кода +## Использование потоков для одновременного выполнения рукописи -В большинстве современных операционных систем программный код выполняется в виде *этапа*, причём операционная система способна управлять несколькими этапами сразу. Программа, в свою очередь, может состоять из нескольких независимых частей, выполняемых одновременно. Устройство, благодаря которой эти независимые части выполняются, называется *потоком*. Например, веб-сервер может иметь несколько потоков для того, чтобы он мог обрабатывать больше одного запроса за раз. +В большинстве современных операционных систем программный рукопись выполняется в виде *этапа*, причём операционная система способна управлять несколькими этапами сразу. Программа, в свою очередь, может состоять из нескольких независимых частей, выполняемых одновременно. Устройство, благодаря которой эти независимые части выполняются, называется *потоком*. Например, сетевой-отделеный вычислитель может иметь несколько потоков для того, чтобы он мог обрабатывать больше одного запроса за раз. -Разбиение вычислений на несколько потоков может повысить производительность программы, поскольку программа выполняет несколько задач одновременно, но такое разбиение также добавляет сложности. Поскольку потоки могут работать одновременно, нет чёткой заверения, определяющей порядок выполнения частей вашего кода в разных потоках. Это может привести к таким неполадкам, как: +Разбиение вычислений на несколько потоков может повысить производительность программы, поскольку программа выполняет несколько задач одновременно, но такое разбиение также добавляет сложности. Поскольку потоки могут работать одновременно, нет чёткой заверения, определяющей порядок выполнения частей вашей рукописи в разных потоках. Это может привести к таким неполадкам, как: -- Состояния гонки, когда потоки обращаются к данным, либо ресурсам, несогласованно. -- Взаимные блокировки, когда два потока ожидают друг друга, не позволяя тем самым продолжить работу каждому из потоков. -- Ошибки, которые случаются только в определённых случаейх, которые трудно воспроизвести и, соответственно, трудно надёжно исправить. +- Состояния гонки, когда потоки обращаются к данным, либо мощностямм, несогласованно. +- Взаимные запрета, когда два потока ожидают друг друга, не позволяя тем самым продолжить работу каждому из потоков. +- Ошибки, которые случаются только в определённых случаях, которые трудно воспроизвести и, соответственно, трудно надёжно исправить. -Rust пытается смягчить отрицательные последствия использования потоков, но программирование в многопоточном среде все ещё требует тщательного обдумывания устройства кода, которая отличается от устройства кода программ, работающих в одном потоке. +Ржавчина пытается смягчить отрицательные последствия использования потоков, но программирование в многопоточном среде все ещё требует тщательного обдумывания устройства рукописи, которая отличается от устройства рукописи программ, работающих в одном потоке. Языки программирования выполняют потоки несколькими различными способами, и многие операционные системы предоставляют API, который язык может вызывать для создания новых потоков. Обычная библиотека Ржавчина использует прообраз выполнения потоков *1:1*, при которой одному потоку операционной системы соответствует ровно один "языковой" поток. Существуют ящики, в которых выполнены другие подходы многопоточности, отличающиеся от подходы 1:1. ### Создание нового потока с помощью `spawn` -Чтобы создать новый поток, мы вызываем функцию `thread::spawn` и передаём ей замыкание (мы говорили о замыканиях в главе 13), содержащее код, который мы хотим запустить в новом потоке. Пример в приложении 16-1 печатает некоторый текст из основного потока, а также другой текст из нового потока: +Чтобы создать новый поток, мы вызываем функцию `thread::spawn` и передаём ей замыкание (мы говорили о замыканиях в главе 13), содержащее рукопись, который мы хотим запустить в новом потоке. Пример в приложении 16-1 печатает некоторый текст из основного потока, а также другой текст из нового потока: Файл: src/main.rs @@ -24,7 +24,7 @@ Rust пытается смягчить отрицательные последс Приложение 16-1: Создание нового потока для печати определённого текста, в то время как основной поток печатает что-то другое -Обратите внимание, что когда основной поток программы на Ржавчина завершается, все порождённые потоки закрываются, независимо от того, завершили они работу или нет. Вывод этой программы может каждый раз немного отличаться, но он будет выглядеть примерно так: +Обратите внимание, что когда основной поток программы на Ржавчине завершается, все порождённые потоки закрываются, независимо от того, завершили они работу или нет. Вывод этой программы может каждый раз немного отличаться, но он будет выглядеть примерно так: Result: 10 ``` -Мы сделали это! Мы посчитали от 0 до 10, что может показаться не очень впечатляющим, но это позволило больше узнать про `Mutex` и безопасность потоков. Вы также можете использовать устройство этой программы для выполнения более сложных действий, чем просто увеличение счётчика. Используя эту стратегию, вы можете разделить вычисления на независимые части, разделить эти части на потоки, а затем использовать `Mutex`, чтобы каждый поток обновлял конечный итог своей частью кода. +Мы сделали это! Мы посчитали от 0 до 10, что может показаться не очень впечатляющим, но это позволило больше узнать про `Mutex` и безопасность потоков. Вы также можете использовать устройство этой программы для выполнения более сложных действий, чем просто увеличение счётчика. Используя эту стратегию, вы можете разделить вычисления на независимые части, разделить эти части на потоки, а затем использовать `Mutex`, чтобы каждый поток обновлял конечный итог своей частью рукописи. Обратите внимание, что если вы выполняете простые числовые действия, то существуют виды более простые, чем `Mutex`, которые предоставляет звено [`std::sync::atomic` встроенной библиотеки]. Эти виды обеспечивают безопасный, одновременный, атомарный доступ к простым видам. Мы решили использовать `Mutex` с простым видом в этом примере, чтобы подробнее рассмотреть, как работает `Mutex`. @@ -121,7 +121,7 @@ Result: 10 Вы могли заметить, что `counter` сам по себе не изменяемый (immutable), но мы можем получить изменяемую ссылку на значение внутри него; это означает, что `Mutex` обеспечивает внутреннюю изменяемость, также как и семейство `Cell` видов. Мы использовали `RefCell` в главе 15, чтобы получить возможность изменять содержимое внутри `Rc`, теперь подобным образом мы используем `Mutex` для изменения содержимого внутри `Arc` . -Ещё одна подробность, на которую стоит обратить внимание: Ржавчина не может защитить вас от всевозможных логических ошибок при использовании `Mutex`. Вспомните в главе 15, что использование `Rc` сопряжено с риском создания ссылочной зацикленности, где два значения `Rc` ссылаются друг на друга, что приводит к утечкам памяти. Подобным образом, `Mutex` сопряжён с риском создания *взаимных блокировок* (deadlocks). Это происходит, когда действия необходимо заблокировать два ресурса и каждый из двух потоков получил одну из блокировок, заставляя оба потока ждать друг друга вечно. Если вам важна направление взаимных блокировок, попробуйте создать программу Rust, которая её содержит; затем исследуйте стратегии устранения взаимных блокировок для мьютексов на любом языке и попробуйте выполнить их в Rust. Документация встроенной библиотеки для `Mutex` и `MutexGuard` предлагает полезную сведения. +Ещё одна подробность, на которую стоит обратить внимание: Ржавчина не может защитить вас от всевозможных разумных ошибок при использовании `Mutex`. Вспомните в главе 15, что использование `Rc` сопряжено с риском создания ссылочной замкнутости, где два значения `Rc` ссылаются друг на друга, что приводит к утечкам памяти. Подобным образом, `Mutex` сопряжён с риском создания *взаимных запретов* (deadlocks). Это происходит, когда действия необходимо запретить два средства и каждый из двух потоков получил один из запретов, заставляя оба потока ждать друг друга вечно. Если вам важна направление взаимных запретов, попробуйте создать программу Ржавчина, которая её содержит; затем исследуйте стратегии устранения взаимных запретов для взаимных исключений на любом языке и попробуйте выполнить их в Ржавчине. Пособие встроенной библиотеки для `Mutex` и `MutexGuard` предлагает полезные сведения. Мы завершим эту главу, рассказав о особенностях `Send` и `Sync` и о том, как мы можем использовать их с пользовательскими видами. diff --git a/rustbook-ru/src/ch16-04-extensible-concurrency-sync-and-send.md b/rustbook-ru/src/ch16-04-extensible-concurrency-sync-and-send.md index cd13b9257..45d24966f 100644 --- a/rustbook-ru/src/ch16-04-extensible-concurrency-sync-and-send.md +++ b/rustbook-ru/src/ch16-04-extensible-concurrency-sync-and-send.md @@ -6,9 +6,9 @@ ### Разрешение передачи во владение между потоками с помощью `Send` -Маркерный особенность `Send` указывает, что владение видом выполняющим `Send`, может передаваться между потоками. Почти каждый вид Ржавчина является видом `Send`, но есть некоторые исключения, вроде `Rc`: он не может быть `Send`, потому что если вы клонировали значение `Rc` и попытались передать владение клоном в другой поток, оба потока могут обновить счётчик ссылок одновременно. По этой причине `Rc` выполнен для использования в однопоточных случаейх, когда вы не хотите платить за снижение производительности. +Маркерный особенность `Send` указывает, что владение видом выполняющим `Send`, может передаваться между потоками. Почти каждый вид Ржавчина является видом `Send`, но есть некоторые исключения, вроде `Rc`: он не может быть `Send`, потому что если вы клонировали значение `Rc` и попытались передать владение клоном в другой поток, оба потока могут обновить счётчик ссылок одновременно. По этой причине `Rc` выполнен для использования в однопоточных случаях, когда вы не хотите платить за снижение производительности. -Следовательно, система видов Ржавчина и ограничений особенности заверяют, что вы никогда не сможете случайно небезопасно отправлять значение `Rc` между потоками. Когда мы попытались сделать это в приложении 16-14, мы получили ошибку, `the trait Send is not implemented for Rc>`. Когда мы переключились на `Arc`, который является видом `Send`, то код собрался. +Следовательно, система видов Ржавчине и ограничений особенности заверяют, что вы никогда не сможете случайно небезопасно отправлять значение `Rc` между потоками. Когда мы попытались сделать это в приложении 16-14, мы получили ошибку, `the trait Send is not implemented for Rc>`. Когда мы переключились на `Arc`, который является видом `Send`, то рукопись собрался. Любой вид полностью состоящий из видов `Send` самостоятельно помечается как `Send`. Почти все простые виды являются `Send`, кроме сырых указателей, которые мы обсудим в главе 19. @@ -22,18 +22,18 @@ Поскольку виды созданные из особенностей `Send` и `Sync` самостоятельно также являются видами `Send` и `Sync`, мы не должны выполнить эти особенности вручную. Являясь маркерными особенностями у них нет никаких способов для выполнения. Они просто полезны для выполнения неизменных величин, связанных с многопоточностью. -Ручная выполнение этих особенностей включает в себя выполнение небезопасного кода Rust. Мы поговорим об использовании небезопасного кода Ржавчина в главе 19; на данный мгновение важная сведения заключается в том, что для создания новых многопоточных видов, не состоящих из частей `Send` и `Sync` необходимо тщательно продумать заверения безопасности. В [Rustonomicon] есть больше сведений об этих заверениях и о том как их соблюдать. +Ручная выполнение этих особенностей включает в себя выполнение небезопасного рукописи Ржавчина. Мы поговорим об использовании небезопасного рукописи Ржавчина в главе 19; на данный мгновение важная сведения заключается в том, что для создания новых многопоточных видов, не состоящих из частей `Send` и `Sync` необходимо тщательно продумать заверения безопасности. В [Rustonomicon] есть больше сведений об этих заверениях и о том как их соблюдать. ## Итоги Это не последний случай, когда вы увидите многопоточность в этой книге: дело в главе 20 будет использовать подходы этой главы для более существующегостичного случая, чем небольшие примеры обсуждаемые здесь. -Как упоминалось ранее, поскольку в языке Ржавчина очень мало того, с помощью чего можно управлять многопоточностью, многие решения выполнены в виде ящиков. Они развиваются быстрее, чем обычная библиотека, поэтому обязательно поищите в Интернете текущие современные ящики. +Как упоминалось ранее, поскольку в языке Ржавчина очень мало того, с помощью чего можно управлять многопоточностью, многие решения выполнены в виде ящиков. Они развиваются быстрее, чем обычная библиотека, поэтому обязательно поищите в Мировой сети текущие современные ящики. -Обычная библиотека Ржавчина предоставляет потоки для передачи сообщений и виды умных указателей, такие как `Mutex` и `Arc`, которые можно безопасно использовать в многопоточных средах. Система видов и анализатор заимствований заверяют, что код использующий эти решения не будет содержать гонки данных или недействительные ссылки. Получив собирающийся код, вы можете быть уверены, что он будет успешно работать в нескольких потоках без ошибок, которые трудно обнаружить в других языках. Многопоточное программирование больше не является подходом, которую стоит опасаться: иди вперёд и сделай свои программы многопоточными безбоязненно! +Обычная библиотека Ржавчина предоставляет потоки для передачи сообщений и виды умных указателей, такие как `Mutex` и `Arc`, которые можно безопасно использовать в многопоточных средах. Система видов и оценщик заимствований заверяют, что рукопись использующий эти решения не будет содержать гонки данных или недействительные ссылки. Получив собирающийся рукопись, вы можете быть уверены, что он будет успешно работать в нескольких потоках без ошибок, которые трудно обнаружить в других языках. Многопоточное программирование больше не является подходом, которую стоит опасаться: иди вперёд и сделай свои программы многопоточными безбоязненно! Далее мы поговорим об идиоматичных способах расчетов неполадок и внутреннего выстраивания - решений по мере усложнения ваших программ на Rust. Кроме того, мы обсудим как идиомы Ржавчина связаны с теми, с которыми вы, возможно, знакомы по предметно-направленному программированию. + решений по мере усложнения ваших программ на Ржавчине. Кроме того, мы обсудим как идиомы Ржавчина связаны с теми, с которыми вы, возможно, знакомы по предметно-направленному программированию. [Rustonomicon]: ../nomicon/index.html \ No newline at end of file diff --git a/rustbook-ru/src/ch17-00-oop.md b/rustbook-ru/src/ch17-00-oop.md index 5235d8160..65e07bf41 100644 --- a/rustbook-ru/src/ch17-00-oop.md +++ b/rustbook-ru/src/ch17-00-oop.md @@ -1,3 +1,3 @@ # Возможности предметно-направленного программирования в Rust -Предметно-направленное программирование (ООП) — это способ построения программ. Предметы, как программная подход, были введены в язык программирования Simula в 1960-х годах. Эти предметы повлияли на архитектуру программирования Алана Кея, в которой предметы передают сообщения друг другу. Чтобы описать эту архитектуру, он ввёл понятие *предметно-направленное программирование* в 1967 году. Есть много состязающихся определений ООП, и по некоторым из этих определений Ржавчина является предметно-направленным, а по другим — нет. В этой главе мы рассмотрим некоторые свойства, которые обычно считаются предметно-направленными, и то, как эти свойства транслируются в идиомы языка Rust. Затем мы покажем, как выполнить образец предметно-направленного разработки в Rust, и обсудим соглашения между этим исходом и решением, использующим вместо этого некоторые сильные стороны Rust. +Предметно-направленное программирование (ООП) — это способ построения программ. Предметы, как программная подход, были введены в язык программирования Simula в 1960-х годах. Эти предметы повлияли на архитектуру программирования Алана Кея, в которой предметы передают сообщения друг другу. Чтобы описать эту архитектуру, он ввёл понятие *предметно-направленное программирование* в 1967 году. Есть много состязающихся определений ООП, и по некоторым из этих определений Ржавчина является предметно-направленным, а по другим — нет. В этой главе мы рассмотрим некоторые свойства, которые обычно считаются предметно-направленными, и то, как эти свойства транслируются в идиомы языка Ржавчина. Затем мы покажем, как выполнить образец предметно-направленного разработки в Ржавчине и обсудим соглашения между этим исходом и решением, использующим вместо этого некоторые сильные стороны Ржавчина. diff --git a/rustbook-ru/src/ch17-01-what-is-oo.md b/rustbook-ru/src/ch17-01-what-is-oo.md index 7fb8f0dfc..d6f1d5855 100644 --- a/rustbook-ru/src/ch17-01-what-is-oo.md +++ b/rustbook-ru/src/ch17-01-what-is-oo.md @@ -1,6 +1,6 @@ ## Свойства предметно-направленных языков -В сообществе программистов нет единого мнения о том, какими свойствами должен обладать язык, чтобы считаться предметно-направленным. На Ржавчина повлияли многие парадигмы программирования, включая ООП - например, в главе 13 мы изучали особенности, пришедшие из функционального программирования. Однозначно можно утверждать, что ООП-языкам присущи следующие присущие особенности: предметы, инкапсуляция и наследование. Давайте рассмотрим, что каждая из них означает и поддерживает ли их Rust. +В сообществе программистов нет единого мнения о том, какими свойствами должен обладать язык, чтобы считаться предметно-направленным. На Ржавчине повлияли многие парадигмы программирования, включая ООП - например, в главе 13 мы изучали особенности, пришедшие из функционального программирования. Однозначно можно утверждать, что ООП-языкам присущи следующие присущие особенности: предметы, инкапсуляция и наследование. Давайте рассмотрим, что каждая из них означает и поддерживает ли их Ржавчина. ### Предметы содержат данные и поведение @@ -12,9 +12,9 @@ ### Инкапсуляция, скрывающая подробности выполнения -Другим особенностью, обычно связанным с предметно-направленным программированием, является мысль *инкапсуляции*: подробности выполнения предмета недоступны для кода, использующего этот предмет. Единственный способ взаимодействия с предметом — через его открытый внешняя оболочка; код, использующий этот предмет, не должен иметь возможности взаимодействовать с внутренними свойствами предметами напрямую изменять его данные или поведение. Инкапсуляция позволяет изменять и ресоздавать внутренние свойства предмета без необходимости изменять код, который использует предмет. +Другим особенностью, обычно связанным с предметно-направленным программированием, является мысль *инкапсуляции*: подробности выполнения предмета недоступны для рукописи, использующего этот предмет. Единственный способ взаимодействия с предметом — через его открытый внешняя оболочка; рукопись, использующий этот предмет, не должен иметь возможности взаимодействовать с внутренними свойствами предметами напрямую изменять его данные или поведение. Инкапсуляция позволяет изменять и ресоздавать внутренние свойства предмета без необходимости изменять рукопись, который использует предмет. -В главе 7 мы уже говорили о том, как управлять инкапсуляцией: мы можем использовать ключевое слово `pub`, чтобы определить, какие звенья, виды, функции и способы в нашем коде будут открытыми, а всё остальное по умолчанию будет закрытыми. Например, мы можем определить устройство `AveragedCollection`, в которой есть поле, содержащее вектор значений `i32`. Также, устройства будет иметь поле, содержащее среднее арифметическое чисел этого вектора, таким образом, среднее не нужно будет вычислять каждый раз, когда оно кому-то понадобится. Другими словами, `AveragedCollection` будет кэшировать вычисленное среднее для нас. В приложении 17-1 приведено определение устройства `AveragedCollection`: +В главе 7 мы уже говорили о том, как управлять инкапсуляцией: мы можем использовать ключевое слово `pub`, чтобы определить, какие звенья, виды, функции и способы в нашем рукописи будут открытыми, а всё остальное по умолчанию будет закрытыми. Например, мы можем определить устройство `AveragedCollection`, в которой есть поле, содержащее вектор значений `i32`. Также, устройства будет иметь поле, содержащее среднее арифметическое чисел этого вектора, таким образом, среднее не нужно будет вычислять каждый раз, когда оно кому-то понадобится. Другими словами, `AveragedCollection` будет кэшировать вычисленное среднее для нас. В приложении 17-1 приведено определение устройства `AveragedCollection`: Файл: src/lib.rs @@ -24,7 +24,7 @@ Приложение 17-1: устройства AveragedCollection содержит список целых чисел и их среднее арифметическое. -Обратите внимание, что устройства помечена ключевым словом `pub`, что позволяет другому коду её использовать, однако, поля устройства остаются недоступными. Это важно, потому что мы хотим обеспечить обновление среднего значения при добавлении или удалении элемента из списка. Мы можем получить нужное поведение, определив в устройстве способы `add`, `remove` и `average`, как показано в примере 17-2: +Обратите внимание, что устройства помечена ключевым словом `pub`, что позволяет другому рукописи её использовать, однако, поля устройства остаются недоступными. Это важно, потому что мы хотим обеспечить обновление среднего значения при добавлении или удалении элемента из списка. Мы можем получить нужное поведение, определив в устройстве способы `add`, `remove` и `average`, как показано в примере 17-2: Файл: src/lib.rs @@ -34,32 +34,32 @@ Приложение 17-2: Выполнение открытых способов add,remove, и average для AveragedCollection -Открытые способы `add`, `remove` и `average` являются единственным способом получить или изменить данные в образце `AveragedCollection`. Когда элемент добавляется в `list` способом `add`, или удаляется с помощью способа `remove`, код выполнения каждого из этих способов вызывает закрытый способ `update_average`, который позаботится об обновлении поля `average`. +Открытые способы `add`, `remove` и `average` являются единственным способом получить или изменить данные в образце `AveragedCollection`. Когда элемент добавляется в `list` способом `add`, или удаляется с помощью способа `remove`, рукопись выполнения каждого из этих способов вызывает закрытый способ `update_average`, который позаботится об обновлении поля `average`. -Мы оставляем поля `list` и `average` закрытыми, чтобы внешний код не мог добавлять или удалять элементы непосредственно в поле `list`; в противном случае поле `average` может оказаться не согласовано при подобном вмешательстве. Способ `average` возвращает значение в поле `average`, что позволяет внешнему коду читать значение `average`, но не изменять его. +Мы оставляем поля `list` и `average` закрытыми, чтобы внешний рукопись не мог добавлять или удалять элементы непосредственно в поле `list`; в противном случае поле `average` может оказаться не согласовано при подобном вмешательстве. Способ `average` возвращает значение в поле `average`, что позволяет внешнему рукописи читать значение `average`, но не изменять его. -Поскольку мы инкапсулировали подробности выполнения устройства `AveragedCollection`, мы можем легко изменить такие особенности, как устройства данных, в будущем. Например, мы могли бы использовать `HashSet` вместо `Vec` для поля `list`. Благодаря тому, что ярлыки открытых способов `add`, `remove` и `average` остаются неизменными, код, использующий `AveragedCollection`, также не будет нуждаться в изменении. У нас бы не получилось этого достичь, если бы мы сделали поле `list` доступным внешнему коду: `HashSet` и`Vec` имеют разные способы для добавления и удаления элементов, поэтому внешний код, вероятно, должен измениться, если он изменяет `list` напрямую. +Поскольку мы инкапсулировали подробности выполнения устройства `AveragedCollection`, мы можем легко изменить такие особенности, как устройства данных, в будущем. Например, мы могли бы использовать `HashSet` вместо `Vec` для поля `list`. Благодаря тому, что ярлыки открытых способов `add`, `remove` и `average` остаются неизменными, рукопись, использующий `AveragedCollection`, также не будет нуждаться в изменении. У нас бы не получилось этого достичь, если бы мы сделали поле `list` доступным внешнему рукописи: `HashSet` и`Vec` имеют разные способы для добавления и удаления элементов, поэтому внешний рукопись, вероятно, должен измениться, если он изменяет `list` напрямую. -Если инкапсуляция является обязательным особенностью для определения языка как предметно-направленного, то Ржавчина соответствует этому требованию. Возможность использовать или не использовать изменитель доступа `pub` для различных частей кода позволяет скрыть подробности выполнения. +Если инкапсуляция является обязательным особенностью для определения языка как предметно-направленного, то Ржавчина соответствует этому требованию. Возможность использовать или не использовать изменитель доступа `pub` для различных частей рукописи позволяет скрыть подробности выполнения. -### Наследование как система видов и способ совместного использования кода +### Наследование как система видов и способ совместного использования рукописи *Наследование* — это рычаг, с помощью которого предмет может унаследовать элементы из определения другого предмета. то есть получить данные и поведение родительского предмета без необходимости повторно их определять. Если язык должен иметь наследование, чтобы быть предметно-направленным, то Ржавчина таким не является. Здесь нет способа определить устройство, наследующую поля и выполнения способов родительской устройства, без использования макроса. -Однако, если вы привыкли иметь наследование в своём наборе средств для программирования, вы можете использовать другие решения в Rust, в зависимости от того, по какой причине вы изначально хотите использовать наследование. +Однако, если вы привыкли иметь наследование в своём наборе средств для программирования, вы можете использовать другие решения в Ржавчине, в зависимости от того, по какой причине вы изначально хотите использовать наследование. -Вы могли бы выбрать наследование по двум основным причинам. Одна из них - возможность повторного использования кода: вы можете выполнить определённое поведение для одного вида, а наследование позволит вам повторно использовать эту выполнение для другого вида. В Ржавчина для этого есть ограниченный способ, использующий выполнение способа особенности по умолчанию, который вы видели в приложении 10-14, когда мы добавили выполнение по умолчанию в способе `summarize` особенности `Summary`. Любой вид, выполняющий свойство `Summary` будет иметь доступный способ `summarize` без дополнительного кода. Это похоже на то, как родительский класс имеет выполнение способа, и класс-наследник тоже имеет выполнение способа. Мы также можем переопределить выполнение по умолчанию для способа `summarize`, когда выполняем особенность `Summary`, что похоже на дочерний класс, переопределяющий выполнение способа, унаследованного от родительского класса. +Вы могли бы выбрать наследование по двум основным причинам. Одна из них - возможность повторного использования рукописи: вы можете выполнить определённое поведение для одного вида, а наследование позволит вам повторно использовать эту выполнение для другого вида. В Ржавчине для этого есть ограниченный способ, использующий выполнение способа особенности по умолчанию, который вы видели в приложении 10-14, когда мы добавили выполнение по умолчанию в способе `summarize` особенности `Summary`. Любой вид, выполняющий свойство `Summary` будет иметь доступный способ `summarize` без дополнительного рукописи. Это похоже на то, как родительский класс имеет выполнение способа, и класс-наследник тоже имеет выполнение способа. Мы также можем переопределить выполнение по умолчанию для способа `summarize`, когда выполняем особенность `Summary`, что похоже на дочерний класс, переопределяющий выполнение способа, унаследованного от родительского класса. Вторая причина использования наследования относится к системе видов: чтобы иметь возможность использовать дочерний вид в тех же места, что и родительский. Эта возможность также называется *полиморфизм* и означает возможность подменять предметы во время исполнения, если они имеют одинаковые свойства. > ### Полиморфизм > -> Для многих людей полиморфизм является родственным наследования. Но на самом деле это более общая подход, относящаяся к коду, который может работать с данными нескольких видов. Обычно такими видами выступают подклассы при наследовании. +> Для многих людей полиморфизм является родственным наследования. Но на самом деле это более общая подход, относящаяся к рукописи, который может работать с данными нескольких видов. Обычно такими видами выступают подклассы при наследовании. > > Вместо этого Ржавчина использует обобщённые виды для абстрагирования от видов, и ограничения особенностей (trait bounds) для указания того, какие возможности эти виды должны предоставлять. Это иногда называют *ограниченным свойствоическим полиморфизмом*. -Наследование, как подход к разработке, в последнее время утратило распространенность во многих языках программирования, поскольку часто существует риск, что мы будем наследовать код чаще, чем это необходимо. Подклассы не всегда должны обладать всеми свойствами родительского класса, но при использовании наследования другого исхода нет. Это может сделать внешний вид программы менее гибким. Кроме этого, появляется возможность вызова у подклассов способов, которые не имеют смысла или вызывают ошибки, потому что эти способы неприменимы к подклассу. Кроме того, в некоторых языках разрешается только одиночное наследование (т.е. подкласс может наследоваться только от одного класса), что ещё больше ограничивает гибкость разработки программы. +Наследование, как подход к разработке, в последнее время утратило распространенность во многих языках программирования, поскольку часто существует риск, что мы будем наследовать рукопись чаще, чем это необходимо. Подклассы не всегда должны обладать всеми свойствами родительского класса, но при использовании наследования другого исхода нет. Это может сделать внешний вид программы менее гибким. Кроме этого, появляется возможность вызова у подклассов способов, которые не имеют смысла или вызывают ошибки, потому что эти способы неприменимы к подклассу. Кроме того, в некоторых языках разрешается только одиночное наследование (т.е. подкласс может наследоваться только от одного класса), что ещё больше ограничивает гибкость разработки программы. -По этим причинам в Ржавчина применяется иной подход, с использованием особенностей-предметов вместо наследования. Давайте посмотрим как особенности-предметы выполняют полиморфизм в Rust. +По этим причинам в Ржавчине применяется иной подход, с использованием особенностей-предметов вместо наследования. Давайте посмотрим как особенности-предметы выполняют полиморфизм в Ржавчине. diff --git a/rustbook-ru/src/ch17-02-trait-objects.md b/rustbook-ru/src/ch17-02-trait-objects.md index c28872bbe..2cd1595c3 100644 --- a/rustbook-ru/src/ch17-02-trait-objects.md +++ b/rustbook-ru/src/ch17-02-trait-objects.md @@ -1,20 +1,20 @@ ## Использование особенность-предметов, допускающих значения разных видов -В главе 8 мы упоминали, что одним из ограничений векторов является то, что они могут хранить элементы только одного вида. Мы создали обходное решение в приложении 8-9, где мы определили перечисление `SpreadsheetCell` в котором были исходы для хранения целых чисел, чисел с плавающей точкой и текста. Это означало, что мы могли хранить разные виды данных в каждой ячейке и при этом иметь вектор, представляющий строку из ячеек. Это очень хорошее решение, когда наши взаимозаменяемые элементы вектора являются видами с конечным набором, известным при сборки кода. +В главе 8 мы упоминали, что одним из ограничений векторов является то, что они могут хранить элементы только одного вида. Мы создали обходное решение в приложении 8-9, где мы определили перечисление `SpreadsheetCell` в котором были исходы для хранения целых чисел, чисел с плавающей точкой и текста. Это означало, что мы могли хранить разные виды данных в каждой ячейке и при этом иметь вектор, представляющий строку из ячеек. Это очень хорошее решение, когда наши взаимозаменяемые элементы вектора являются видами с конечным набором, известным при сборки рукописи. Однако иногда мы хотим, чтобы пользователь нашей библиотеки мог расширить набор видов, которые допустимы в именно случаи. Чтобы показать как этого добиться, мы создадим пример средства с графическим внешней оболочкой пользователя (GUI), который просматривает список элементов, вызывает способ `draw` для каждого из них, чтобы нарисовать его на экране - это обычная техника для средств GUI. Мы создадим библиотечный ящик с именем `gui`, содержащий устройство библиотеки GUI. Этот ящик мог бы включать некоторые готовые виды для использования, такие как `Button` или `TextField`. Кроме того, пользователи такого ящика `gui` захотят создавать свои собственные виды, которые могут быть нарисованы: например, кто-то мог бы добавить вид `Image`, а кто-то другой добавить вид `SelectBox`. Мы не будем выполнить полноценную библиотеку GUI для этого примера, но покажем, как её части будут подходить друг к другу. На мгновение написания библиотеки мы не можем знать и определить все виды, которые могут захотеть создать другие программисты. Но мы знаем, что `gui` должен отслеживать множество значений различных видов и ему нужно вызывать способ `draw` для каждого из этих значений различного вида. Ему не нужно точно знать, что произойдёт, когда вызывается способ `draw`, просто у значения будет доступен такой способ для вызова. -Чтобы сделать это на языке с наследованием, можно определить класс с именем `Component` у которого есть способ с названием `draw`. Другие классы, такие как `Button`, `Image` и `SelectBox` наследуются от `Component` и следовательно, наследуют способ `draw`. Каждый из них может переопределить выполнение способа `draw`, чтобы определить своё пользовательское поведение, но площадка может обрабатывать все виды, как если бы они были образцами `Component` и вызывать `draw` у них. Но поскольку в Ржавчина нет наследования, нам нужен другой способ внутренне выстроить +Чтобы сделать это на языке с наследованием, можно определить класс с именем `Component` у которого есть способ с названием `draw`. Другие классы, такие как `Button`, `Image` и `SelectBox` наследуются от `Component` и следовательно, наследуют способ `draw`. Каждый из них может переопределить выполнение способа `draw`, чтобы определить своё пользовательское поведение, но площадка может обрабатывать все виды, как если бы они были образцами `Component` и вызывать `draw` у них. Но поскольку в Ржавчине нет наследования, нам нужен другой способ внутренне выстроить `gui` библиотеку, чтобы позволить пользователям расширять её новыми видами. ### Определение особенности для общего поведения -Чтобы выполнить поведение, которое мы хотим иметь в `gui`, определим особенность с именем `Draw`, который будет содержать один способ с названием `draw`. Затем мы можем определить вектор, который принимает *особенность-предмет*. Особенность-предмет указывает как на образец вида, выполняющего указанный особенность, так и на внутреннюю таблицу, используемую для поиска способов особенности указанного вида во время выполнения. Мы создаём особенность-предмет в таком порядке: используем какой-нибудь вид указателя, например ссылку `&` или умный указатель `Box`, затем ключевое слово `dyn`, а затем указываем соответствующий особенность. (Мы будем говорить о причине того, что особенность-предметы должны использовать указатель в разделе ["Виды изменяемого размера и особенность `Sized` "] главы 19). Мы можем использовать особенность-предметы вместо гибкого или определенного вида. Везде, где мы используем особенность-предмет, система видов Ржавчина проверит во время сборки, что любое значение, используемое в этом среде, будет выполнить нужный особенность у особенность-предмета. Следовательно, нам не нужно знать все возможные виды во время сборки. +Чтобы выполнить поведение, которое мы хотим иметь в `gui`, определим особенность с именем `Draw`, который будет содержать один способ с названием `draw`. Затем мы можем определить вектор, который принимает *особенность-предмет*. Особенность-предмет указывает как на образец вида, выполняющего указанный особенность, так и на внутреннюю таблицу, используемую для поиска способов особенности указанного вида во время выполнения. Мы создаём особенность-предмет в таком порядке: используем какой-нибудь вид указателя, например ссылку `&` или умный указатель `Box`, затем ключевое слово `dyn`, а затем указываем соответствующий особенность. (Мы будем говорить о причине того, что особенность-предметы должны использовать указатель в разделе ["Виды изменяемого размера и особенность `Sized` "] главы 19). Мы можем использовать особенность-предметы вместо гибкого или определенного вида. Везде, где мы используем особенность-предмет, система видов Ржавчине проверит во время сборки, что любое значение, используемое в этом среде, будет выполнить нужный особенность у особенность-предмета. Следовательно, нам не нужно знать все возможные виды во время сборки. -Мы упоминали, что в Ржавчина мы воздерживаемся называть устройства и перечисления «предметами», чтобы отличать их от предметов в других языках. В устройстве или перечислении данные в полях устройства и поведение в разделах `impl` разделены, тогда как в других языках данные и поведение объединены в одну подход, часто обозначающуюся как предмет. Тем не менее, особенность-предметы *являются* более похожими на предметы на других языках, в том смысле, что они сочетают в себе данные и поведение. Но особенность-предметы отличаются от привычных предметов тем, что не позволяют добавлять данные к особенность-предмету. Особенность-предметы обычно не настолько полезны, как предметы в других языках: их определенная цель - обеспечить абстракцию через общее поведение. +Мы упоминали, что в Ржавчине мы воздерживаемся называть устройства и перечисления «предметами», чтобы отличать их от предметов в других языках. В устройстве или перечислении данные в полях устройства и поведение в разделах `impl` разделены, тогда как в других языках данные и поведение объединены в одну подход, часто обозначающуюся как предмет. Тем не менее, особенность-предметы *являются* более похожими на предметы на других языках, в том смысле, что они сочетают в себе данные и поведение. Но особенность-предметы отличаются от привычных предметов тем, что не позволяют добавлять данные к особенность-предмету. Особенность-предметы обычно не настолько полезны, как предметы в других языках: их определенная цель - обеспечить абстракцию через общее поведение. В приложении 17.3 показано, как определить особенность с именем `Draw` с помощью одного способа с именем `draw`: @@ -72,7 +72,7 @@ Приложение 17-7: Устройства Button выполняет особенность Draw -Поля `width`, `height` и `label` устройства `Button` будут отличаться от, например, полей других составляющих вроде вида `TextField`, которая могла бы иметь те же поля плюс поле `placeholder`. Каждый из видов, который мы хотим нарисовать на экране будет выполнить особенность `Draw`, но будет использовать отличающийся код способа `draw` для определения как именно рисовать определенный вид, например `Button` в этом примере (без действительного кода GUI, который выходит за рамки этой главы). Например, вид `Button` может иметь дополнительный раздел`impl`, содержащий способы, относящиеся к тому, что происходит, когда пользователь нажимает кнопку. Эти исходы способов не будут применяться к видам вроде `TextField`. +Поля `width`, `height` и `label` устройства `Button` будут отличаться от, например, полей других составляющих вроде вида `TextField`, которая могла бы иметь те же поля плюс поле `placeholder`. Каждый из видов, который мы хотим нарисовать на экране будет выполнить особенность `Draw`, но будет использовать отличающийся рукопись способа `draw` для определения как именно рисовать определенный вид, например `Button` в этом примере (без действительного рукописи GUI, который выходит за рамки этой главы). Например, вид `Button` может иметь дополнительный раздел`impl`, содержащий способы, относящиеся к тому, что происходит, когда пользователь нажимает кнопку. Эти исходы способов не будут применяться к видам вроде `TextField`. Если кто-то использующий нашу библиотеку решает выполнить устройство `SelectBox`, которая имеет `width`, `height` и поля `options`, он выполняет также и особенность `Draw` для вида `SelectBox`, как показано в приложении 17-8: @@ -98,7 +98,7 @@ Эта подход, касающаяся только сообщений, на которые значение отвечает, в отличие от определенного вида у значения, подобна подходы *duck typing* в изменяемых строго определенных языках: если что-то ходит как утка и крякает как утка, то она должна быть утка! В выполнения способа `run` у `Screen` в приложении 17-5, `run` не нужно знать каким будет определенный вид каждого составляющих. Он не проверяет, является ли составляющая образцом `Button` или `SelectBox`, он просто вызывает способ `draw` составляющих. Указав `Box` в качестве вида значений в векторе `components`, мы определили `Screen` для значений у которых мы можем вызвать способ `draw`. -Преимущество использования особенность-предметов и системы видов Ржавчина для написания кода, похожего на код с использованием подходы duck typing состоит в том, что нам не нужно во время выполнения проверять выполняет ли значение в векторе определенный способ или беспокоиться о получении ошибок, если значение не выполняет способ, мы все равно вызываем способ. Ржавчина не собирает наш код, если значения не выполняют особенность, который нужен особенность-предмета.. +Преимущество использования особенность-предметов и системы видов Ржавчине для написания рукописи, похожего на рукопись с использованием подходы duck typing состоит в том, что нам не нужно во время выполнения проверять выполняет ли значение в векторе определенный способ или беспокоиться о получении ошибок, если значение не выполняет способ, мы все равно вызываем способ. Ржавчина не собирает нашу рукопись, если значения не выполняют особенность, который нужен особенность-предмета.. Например, в приложении 17-10 показано, что произойдёт, если мы попытаемся создать `Screen` с `String` в качестве его составляющих: @@ -120,9 +120,9 @@ ### Особенность-предметы выполняют изменяемую управление (связывание) -Вспомните, в разделе [«Производительность кода, использующего обобщённые виды»](ch10-01-syntax.html#performance-of-code-using-generics) в главе 10 наше обсуждение этапа мономорфизации, выполняемого сборщиком, когда мы используем ограничения особенностей для обобщённых видов: сборщик порождает частные выполнения функций и способов для каждого определенного вида, который мы применяем для свойства обобщённого вида. Код, который получается в итоге мономорфизации, выполняет *постоянную управление* , то есть когда сборщик знает, какой способ вы вызываете во время сборки. Это противоположно *изменяемой управления*, когда сборщик не может определить во время сборки, какой способ вы вызываете. В случае изменяемой управления сборщик создает код, который во время выполнения определит, какой способ нужно вызвать. +Вспомните, в разделе [«Производительность рукописи, использующего обобщённые виды»](ch10-01-syntax.html#performance-of-code-using-generics) в главе 10 наше обсуждение этапа мономорфизации, выполняемого сборщиком, когда мы используем ограничения особенностей для обобщённых видов: сборщик порождает частные выполнения функций и способов для каждого определенного вида, который мы применяем для свойства обобщённого вида. Рукопись, который получается в итоге мономорфизации, выполняет *постоянную управление* , то есть когда сборщик знает, какой способ вы вызываете во время сборки. Это противоположно *изменяемой управления*, когда сборщик не может определить во время сборки, какой способ вы вызываете. В случае изменяемой управления сборщик создает рукопись, который во время выполнения определит, какой способ нужно вызвать. -Когда мы используем особенность-предметы, Ржавчина должен использовать изменяемую управление. Сборщик не знает всех видов, которые могут быть использованы с кодом, использующим особенность-предметы, поэтому он не знает, какой способ выполнен для какого вида при вызове. Вместо этого, во время выполнения, Ржавчина использует указатели внутри особенность-предмета. чтобы узнать какой способ вызвать. Такой поиск вызывает дополнительные затраты во время исполнения, которые не требуются при постоянной управления. Изменяемая управление также не позволяет сборщику выбрать встраивание кода способа, что в свою очередь делает невозможными некоторые переработки. Однако мы получили дополнительную гибкость в коде, который мы написали в приложении 17-5, и которую смогли поддержать в приложении 17-9, поэтому все "за" и "против" нужно рассматривать в совокупности. +Когда мы используем особенность-предметы, Ржавчина должен использовать изменяемую управление. Сборщик не знает всех видов, которые могут быть использованы с рукописью, использующим особенность-предметы, поэтому он не знает, какой способ выполнен для какого вида при вызове. Вместо этого, во время выполнения, Ржавчина использует указатели внутри особенность-предмета. чтобы узнать какой способ вызвать. Такой поиск вызывает дополнительные затраты во время исполнения, которые не требуются при постоянной управления. Изменяемая управление также не позволяет сборщику выбрать встраивание рукописи способа, что в свою очередь делает невозможными некоторые переработки. Однако мы получили дополнительную гибкость в рукописи, который мы написали в приложении 17-5, и которую смогли поддержать в приложении 17-9, поэтому все "за" и "против" нужно рассматривать в совокупности. ["Виды изменяемого размера и особенность `Sized` "]: ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait \ No newline at end of file diff --git a/rustbook-ru/src/ch17-03-oo-design-patterns.md b/rustbook-ru/src/ch17-03-oo-design-patterns.md index 4e877d5b8..461bb80aa 100644 --- a/rustbook-ru/src/ch17-03-oo-design-patterns.md +++ b/rustbook-ru/src/ch17-03-oo-design-patterns.md @@ -1,23 +1,23 @@ ## Выполнение предметно-направленного образца разработки -*Образец "Состояние"* — это предметно-направленный образец разработки. Суть образца заключается в том, что мы определяем набор состояний, которые может иметь внутреннее значение. Состояния представлены набором *предметов состояния*, а поведение элемента изменяется в зависимости от его состояния. Мы рассмотрим пример устройства записи в блоге, в которой есть поле для хранения состояния, которое будет предметом состояния из набора «черновик», «обзор» или «обнародовано». +*Образец "Состояние"* — это предметно-направленный образец разработки. Суть образца заключается в том, что мы определяем набор состояний, которые может иметь внутреннее значение. Состояния представлены набором *предметов состояния*, а поведение элемента изменяется в зависимости от его состояния. Мы рассмотрим пример устройства записи в дневнике, в которой есть поле для хранения состояния, которое будет предметом состояния из набора «черновик», «обзор» или «обнародовано». -Предметы состояния имеют общую возможность: конечно в Ржавчина мы используем устройства и особенности, а не предметы и наследование. Каждый предмет состояния отвечает за своё поведение и сам определяет, когда он должен перейти в другое состояние. Элемент, который содержит предмет состояния, ничего не знает о различиях в поведении состояний или о том, когда одно состояние должно перейти в другое. +Предметы состояния имеют общую возможность: конечно в Ржавчине мы используем устройства и особенности, а не предметы и наследование. Каждый предмет состояния отвечает за своё поведение и сам определяет, когда он должен перейти в другое состояние. Элемент, который содержит предмет состояния, ничего не знает о различиях в поведении состояний или о том, когда одно состояние должно перейти в другое. -Преимуществом образца "Состояние" является то, что при изменении требований заказчика программы не требуется изменять код элемента, содержащего состояние, или код, использующий такой элемент. Нам нужно только обновить код внутри одного из предметов состояния, чтобы изменить его порядок действий, либо, возможно, добавить больше предметов состояния. +Преимуществом образца "Состояние" является то, что при изменении требований заказчика программы не требуется изменять рукопись элемента, содержащего состояние, или рукопись, использующий такой элемент. Нам нужно только обновить рукопись внутри одного из предметов состояния, чтобы изменить его порядок действий, либо, возможно, добавить больше предметов состояния. -Для начала выполняем образец "Состояние" более привычным предметно-направленным способом, а затем воспользуемся подходом, более естественным для Rust. Давайте шаг за шагом выполняем поток действий для записи в блоге, использующий образец "Состояние". +Для начала выполняем образец "Состояние" более привычным предметно-направленным способом, а затем воспользуемся подходом, более естественным для Ржавчины. Давайте шаг за шагом выполняем поток действий для записи в дневнике, использующий образец "Состояние". Окончательный возможности будет выглядеть так: -1. Запись в блоге создаётся как пустой черновик. +1. Запись в дневнике создаётся как пустой черновик. 2. Когда черновик готов, запрашивается его проверка. 3. После проверки происходит обнародование записи. -4. Только обнародованные записи блога возвращают содержимое записи на печать, поэтому сообщения, не прошедшие проверку, не могут быть обнародованы случайно. +4. Только обнародованные записи дневника возвращают содержимое записи на печать, поэтому сообщения, не прошедшие проверку, не могут быть обнародованы случайно. -Любые другие изменения, сделанные в записи, не должны иметь никакого эффекта. Например, если мы попытаемся подтвердить черновик записи в блоге до того, как запросим проверку, запись должна остаться необнародованным черновиком. +Любые другие изменения, сделанные в записи, не должны иметь никакого последствий. Например, если мы попытаемся подтвердить черновик записи в дневнике до того, как запросим проверку, запись должна остаться необнародованным черновиком. -В приложении 17-11 показан этот поток действий в виде кода: это пример использования API, который мы собираемся выполнить в библиотеке (ящике) с именем `blog`. Он пока не собирается, потому что ящик `blog` ещё не создан. +В приложении 17-11 показан этот поток действий в виде рукописи: это пример использования API, который мы собираемся выполнить в библиотеке (ящике) с именем `blog`. Он пока не собирается, потому что ящик `blog` ещё не создан. Файл: src/main.rs @@ -25,13 +25,13 @@ {{#rustdoc_include ../listings/ch17-oop/listing-17-11/src/main.rs:all}} ``` -Приложение 17-11: Код, отображающий желаемое поведение, которое мы хотим получить в ящике blog +Приложение 17-11: Рукопись, отображающий желаемое поведение, которое мы хотим получить в ящике blog -Мы хотим, чтобы пользователь мог создать новый черновик записи в блоге с помощью `Post::new`. Затем мы хотим разрешить добавление текста в запись блога. Если мы попытаемся получить содержимое записи сразу, до её проверки, мы не должны получить никакого текста на выходе, потому что запись все ещё является черновиком. Мы добавили утверждение (`assert_eq!`) в коде для опытных целей. Утверждение (assertion), что черновик записи блога должен возвращать пустую строку из способа `content` было бы отличным состоящим из звеньев проверкой, но мы не собираемся писать проверки для этого примера. +Мы хотим, чтобы пользователь мог создать новый черновик записи в дневнике с помощью `Post::new`. Затем мы хотим разрешить добавление текста в запись дневника. Если мы попытаемся получить содержимое записи сразу, до её проверки, мы не должны получить никакого текста на выходе, потому что запись все ещё является черновиком. Мы добавили утверждение (`assert_eq!`) в рукописи для опытных целей. Утверждение (assertion), что черновик записи дневника должен возвращать пустую строку из способа `content` было бы отличным состоящим из звеньев проверкой, но мы не собираемся писать проверки для этого примера. Далее мы хотим разрешить сделать запрос на проверку записи и хотим, чтобы `content` возвращал пустую строку, пока проверки не завершена. Когда запись пройдёт проверку, она должна быть обнародована, то есть при вызове `content` будет возвращён текст записи. -Обратите внимание, что единственный вид из ящика, с которым мы взаимодействуем - это вид `Post`. Этот вид будет использовать образец "Состояние" и будет содержать значение, которое будет являться одним из трёх предметов состояний, представляющих различные состояния, в которых может находиться запись: "черновик", "ожидание проверки" или "обнародовано". Управление переходом из одного состояния в другое будет осуществляться внутренней логикой вида `Post`. Состояния будут переключаться в итоге реакции на вызов способов образца `Post` пользователями нашей библиотеки, но пользователи не должны управлять изменениями состояния напрямую. Кроме того, пользователи не должны иметь возможность ошибиться с состояниями, например, обнародовать сообщение до его проверки. +Обратите внимание, что единственный вид из ящика, с которым мы взаимодействуем - это вид `Post`. Этот вид будет использовать образец "Состояние" и будет содержать значение, которое будет являться одним из трёх предметов состояний, представляющих различные состояния, в которых может находиться запись: "черновик", "ожидание проверки" или "обнародовано". Управление переходом из одного состояния в другое будет осуществляться внутренней ходом мыслей вида `Post`. Состояния будут переключаться в итоге реакции на вызов способов образца `Post` пользователями нашей библиотеки, но пользователи не должны управлять изменениями состояния напрямую. Кроме того, пользователи не должны иметь возможность ошибиться с состояниями, например, обнародовать сообщение до его проверки. ### Определение `Post` и создание нового образца в состоянии черновика @@ -53,7 +53,7 @@ ### Хранение текста содержимого записи -В приложении 17-11 показано, что мы хотим иметь возможность вызывать способ `add_text` и передать ему `&str`, которое добавляется к текстовому содержимому записи блога. Мы выполняем эту возможность как способ, а не делаем поле `content` открыто доступным, используя `pub`. Это означает, что позже мы сможем написать способ, который будет управлять, как именно читаются данные из поля `content`. Способ `add_text` довольно прост, поэтому давайте добавим его выполнение в раздел`impl Post`приложения 17-13: +В приложении 17-11 показано, что мы хотим иметь возможность вызывать способ `add_text` и передать ему `&str`, которое добавляется к текстовому содержимому записи дневника. Мы выполняем эту возможность как способ, а не делаем поле `content` открыто доступным, используя `pub`. Это означает, что позже мы сможем написать способ, который будет управлять, как именно читаются данные из поля `content`. Способ `add_text` довольно прост, поэтому давайте добавим его выполнение в раздел`impl Post`приложения 17-13: Файл: src/lib.rs @@ -67,7 +67,7 @@ ### Убедимся, что содержание черновика будет пустым -Даже после того, как мы вызвали `add_text` и добавили некоторый содержание в нашу запись, мы хотим, чтобы способ `content` возвращал пустой отрывок строки, так как запись всё ещё находится в черновом состоянии, как это показано в строке 7 приложения 17-11. А пока давайте выполняем способ `content` наиболее простым способом, который будет удовлетворять этому требованию: будем всегда возвращать пустой отрывок строки. Мы изменим код позже, как только выполняем возможность изменить состояние записи, чтобы она могла бы быть обнародована. Пока что записи могут находиться только в черновом состоянии, поэтому содержимое записи всегда должно быть пустым. Приложение 17-14 показывает такую выполнение-заглушку: +Даже после того, как мы вызвали `add_text` и добавили некоторый содержание в нашу запись, мы хотим, чтобы способ `content` возвращал пустой отрывок строки, так как запись всё ещё находится в черновом состоянии, как это показано в строке 7 приложения 17-11. А пока давайте выполняем способ `content` наиболее простым способом, который будет удовлетворять этому требованию: будем всегда возвращать пустой отрывок строки. Мы изменим рукопись позже, как только выполняем возможность изменить состояние записи, чтобы она могла бы быть обнародована. Пока что записи могут находиться только в черновом состоянии, поэтому содержимое записи всегда должно быть пустым. Приложение 17-14 показывает такую выполнение-заглушку: Файл: src/lib.rs @@ -97,7 +97,7 @@ Чтобы поглотить старое состояние, способ `request_review` должен стать владельцем значения состояния. Это место, где приходит на помощь вид `Option` поля `state` записи `Post`: мы вызываем способ `take`, чтобы забрать значение `Some` из поля `state` и оставить вместо него значение `None`, потому что Ржавчина не позволяет иметь необъявленные поля в устройствах. Это позволяет перемещать значение `state` из `Post`, а не заимствовать его. Затем мы установим новое значение `state` как итог этой действия. -Нам нужно временно установить `state` в `None`, вместо того, чтобы установить его напрямую с помощью кода вроде `self.state = self.state.request_review();`. Нам нужно завладеть значением поля `state`. Это даст нам заверение, что `Post` не сможет использовать старое значение `state` после того, как мы преобразовали его в новое состояние. +Нам нужно временно установить `state` в `None`, вместо того, чтобы установить его напрямую с помощью рукописи вроде `self.state = self.state.request_review();`. Нам нужно завладеть значением поля `state`. Это даст нам заверение, что `Post` не сможет использовать старое значение `state` после того, как мы преобразовали его в новое состояние. Способ `request_review` в `Draft` должен вернуть новый образец новой устройства `PendingReview`, обёрнутый в Box. Эта устройства будет представлять состояние, в котором запись ожидает проверки. Устройства `PendingReview` также выполняет способ `request_review`, но не выполняет никаких преобразований. Она возвращает сама себя, потому что, когда мы запрашиваем проверку записи, уже находящейся в состоянии `PendingReview`, она всё так же должна продолжать оставаться в состоянии `PendingReview`. @@ -123,7 +123,7 @@ Мы добавляем способ `approve` в особенность `State`, добавляем новую устройство, которая выполняет этот особенность `State` и устройство для состояния `Published`. -Подобно тому, как работает `request_review` для `PendingReview`, если мы вызовем способ `approve` для `Draft`, он не будет иметь никакого эффекта, потому что `approve` вернёт `self`. Когда мы вызываем для `PendingReview` способ `approve`, то он возвращает новый упакованный образец устройства `Published`. Устройства `Published` выполняет особенность `State`, и как для способа `request_review`, так и для способа `approve` она возвращает себя, потому что в этих случаях запись должна оставаться в состоянии `Published`. +Подобно тому, как работает `request_review` для `PendingReview`, если мы вызовем способ `approve` для `Draft`, он не будет иметь никакого последствий, потому что `approve` вернёт `self`. Когда мы вызываем для `PendingReview` способ `approve`, то он возвращает новый упакованный образец устройства `Published`. Устройства `Published` выполняет особенность `State`, и как для способа `request_review`, так и для способа `approve` она возвращает себя, потому что в этих случаях запись должна оставаться в состоянии `Published`. Теперь нам нужно обновить способ `content` для `Post`. Мы хотим, чтобы значение, возвращаемое из `content`, зависело от текущего состояния `Post`, поэтому мы собираемся перенести часть возможности `Post` в способ `content`, заданный для `state`, как показано в приложении 17.17: @@ -141,7 +141,7 @@ Затем мы вызываем способ `unwrap`. Мы знаем, что этот способ здесь никогда не приведёт к со сбоемму завершению программы, так все способы `Post` устроены таким образом, что после их выполнения, в поле `state` всегда содержится значение `Some`. Это один из случаев, про которых мы говорили в разделе ["Случаи, когда у вас больше сведений, чем у сборщика"] главы 9 - случай, когда мы знаем, что значение `None` никогда не встретится, даже если сборщик не может этого понять. -Теперь, когда мы вызываем `content` у вида `&Box`, в действие вступает принудительное приведение (deref coercion) для `&` и `Box`, поэтому в конечном итоге способ `content` будет вызван для вида, который выполняет особенность `State`. Это означает, что нам нужно добавить способ `content` в определение особенности `State`, и именно там мы поместим логику для определения того, какое содержимое возвращать, в зависимости от текущего состояния, как показано в приложении 17-18: +Теперь, когда мы вызываем `content` у вида `&Box`, в действие вступает принудительное приведение (deref coercion) для `&` и `Box`, поэтому в конечном итоге способ `content` будет вызван для вида, который выполняет особенность `State`. Это означает, что нам нужно добавить способ `content` в определение особенности `State`, и именно там мы поместим ход мыслей для определения того, какое содержимое возвращать, в зависимости от текущего состояния, как показано в приложении 17-18: Файл: src/lib.rs @@ -155,37 +155,37 @@ Обратите внимание, что для этого способа нам нужны изложении времени жизни, как мы обсуждали в главе 10. Мы берём ссылку на `post` в качестве переменной и возвращаем ссылку на часть этого `post`, поэтому время жизни возвращённой ссылки связано с временем жизни переменной `post`. -И вот, мы закончили - теперь всё из приложения 17-11 работает! Мы выполнили образец "Состояние", определяющий правила этапа работы с записью в блоге. Логика, связанная с этими правилами, находится в предмета. состояний, а не разбросана по всей устройстве `Post`. +И вот, мы закончили - теперь всё из приложения 17-11 работает! Мы выполнили образец "Состояние", определяющий правила этапа работы с записью в дневнике. Ход мыслей, связанная с этими правилами, находится в предмета. состояний, а не разбросана по всей устройстве `Post`. > #### Почему не перечисление? > -> Возможно, вам было важно, почему мы не использовали `enum` с различными возможными состояниями записи в качестве исходов. Это, безусловно, одно из возможных решений. Попробуйте его выполнить и сравните конечные итоги, чтобы выбрать, какой из исходов вам больше нравится! Одним из недостатков использования перечисления является то, что в каждом месте, где проверяется значение перечисления, потребуется выражение `match` или что-то подобное для обработки всех возможных исходов. Возможно в этом случае нам придётся повторять больше кода, чем это было в решении с особенность-предметом. +> Возможно, вам было важно, почему мы не использовали `enum` с различными возможными состояниями записи в качестве исходов. Это, безусловно, одно из возможных решений. Попробуйте его выполнить и сравните конечные итоги, чтобы выбрать, какой из исходов вам больше нравится! Одним из недостатков использования перечисления является то, что в каждом месте, где проверяется значение перечисления, потребуется выражение `match` или что-то подобное для обработки всех возможных исходов. Возможно в этом случае нам придётся повторять больше рукописи, чем это было в решении с особенность-предметом. ### Соглашенияы образца "Состояние" -Мы показали, что Ржавчина способен выполнить предметно-направленный образец "Состояние" для инкапсуляции различных видов поведения, которые должна иметь запись в каждом состоянии. Способы в `Post` ничего не знают о различных видах поведения. При такой согласования кода, нам достаточно взглянуть только на один его участок, чтобы узнать отличия в поведении обнародованной обнародования: в выполнение особенности `State` у устройства `Published`. +Мы показали, что Ржавчина способен выполнить предметно-направленный образец "Состояние" для инкапсуляции различных видов поведения, которые должна иметь запись в каждом состоянии. Способы в `Post` ничего не знают о различных видах поведения. При такой согласования рукописи, нам достаточно взглянуть только на один его участок, чтобы узнать отличия в поведении обнародованной обнародования: в выполнение особенности `State` у устройства `Published`. Если бы мы захотели создать иную выполнение, не использующую образец состояния, мы могли бы вместо этого использовать выражения `match` в способах `Post` или даже в `main`, которые бы проверяли состояние записи и изменяли поведение в этих местах. Это приведёт к тому, что нам придётся в нескольких местах исследовать все следствия того, что пост перешёл в состояние "обнародовано"! И эта нагрузка будет только увеличиваться по мере добавления новых состояний: для каждого из этих выражений `match` потребуются дополнительные ответвления. С помощью образца "Состояние" способы `Post` и участки, где мы используем `Post`, не требуют использования выражений `match`, а для добавления нового состояния нужно только добавить новую устройство и выполнить способы особенности у одной этой устройства. -Выполнение с использованием образца "Состояние" легко расширить для добавления новой возможности. Чтобы увидеть, как легко поддерживать код, использующий данный образец, попробуйте выполнить некоторые из предложений ниже: +Выполнение с использованием образца "Состояние" легко расширить для добавления новой возможности. Чтобы увидеть, как легко поддерживать рукопись, использующий данный образец, попробуйте выполнить некоторые из предложений ниже: - Добавьте способ `reject`, который изменяет состояние обнародования с `PendingReview` обратно на `Draft`. - Потребуйте два вызова способа `approve`, прежде чем переводить состояние в `Published`. - Разрешите пользователям добавлять текстовое содержимое только тогда, когда обнародование находится в состоянии `Draft`. Подсказка: пусть предмет состояния решает, можно ли менять содержимое, но не отвечает за изменение `Post`. -Одним из недостатков образца "Состояние" является то, что поскольку состояния сами выполняют переходы между собой, некоторые из состояний получаются связанными друг с другом. Если мы добавим другое состояние между `PendingReview` и `Published`, например `Scheduled` ("расчитано наперед"), то придётся изменить код в `PendingReview`, чтобы оно теперь переходило в `Scheduled`. Если бы не нужно было менять `PendingReview` при добавлении нового состояния, было бы меньше работы, но это означало бы, что мы переходим на другой образец разработки. +Одним из недостатков образца "Состояние" является то, что поскольку состояния сами выполняют переходы между собой, некоторые из состояний получаются связанными друг с другом. Если мы добавим другое состояние между `PendingReview` и `Published`, например `Scheduled` ("расчитано наперед"), то придётся изменить рукопись в `PendingReview`, чтобы оно теперь переходило в `Scheduled`. Если бы не нужно было менять `PendingReview` при добавлении нового состояния, было бы меньше работы, но это означало бы, что мы переходим на другой образец разработки. -Другим недостатком является то, что мы сделали повторение некоторую логику. Чтобы устранить некоторое повторение, мы могли бы попытаться сделать выполнения по умолчанию для способов `request_review` и `approve` особенности `State`, которые возвращают `self`; однако это нарушило бы безопасность предмета. потому что особенность не знает, каким определенно будет `self`. Мы хотим иметь возможность использовать `State` в качестве особенность-предмета. поэтому нам нужно, чтобы его способы были предметно-безопасными. +Другим недостатком является то, что мы сделали повторение некоторую ход мыслей. Чтобы устранить некоторое повторение, мы могли бы попытаться сделать выполнения по умолчанию для способов `request_review` и `approve` особенности `State`, которые возвращают `self`; однако это нарушило бы безопасность предмета. потому что особенность не знает, каким определенно будет `self`. Мы хотим иметь возможность использовать `State` в качестве особенность-предмета. поэтому нам нужно, чтобы его способы были предметно-безопасными. Другое повторение включает в себя схожие выполнения способов `request_review` и `approve` у `Post`. Оба способа делегируют выполнения одного и того же способа значению поля `state` вида `Option` и устанавливают итогом новое значение поля `state`. Если бы у `Post` было много способов, которые следовали этому образцу, мы могли бы рассмотреть определение макроса для устранения повторения (смотри раздел ["Макросы"] в главе 19). -Выполняя образец "Состояние" точно так, как он определён для предметно-направленных языков, мы не настолько полно используем преимущества Rust, как могли бы. Давайте посмотрим на некоторые изменения, которые мы можем внести в ящик `blog`, чтобы недопустимые состояния и переходы превратить в ошибки времени сборки. +Выполняя образец "Состояние" точно так, как он определён для предметно-направленных языков, мы не настолько полно используем преимущества Ржавчина, как могли бы. Давайте посмотрим на некоторые изменения, которые мы можем внести в ящик `blog`, чтобы недопустимые состояния и переходы превратить в ошибки времени сборки. #### Кодирование состояний и поведения в виде видов -Мы покажем вам, как переосмыслить образец "Состояние", чтобы получить другой набор соглашений. Вместо того, чтобы полностью инкапсулировать состояния и переходы, так, чтобы внешний код не знал о них, мы будем кодировать состояния с помощью разных видов. Следовательно, система проверки видов Ржавчина предотвратит попытки использовать черновые обнародования, там где разрешены только обнародованные обнародования, вызывая ошибки сборки. +Мы покажем вам, как переосмыслить образец "Состояние", чтобы получить другой набор соглашений. Вместо того, чтобы полностью инкапсулировать состояния и переходы, так, чтобы внешний рукопись не знал о них, мы будем кодировать состояния с помощью разных видов. Следовательно, система проверки видов Ржавчине предотвратит попытки использовать черновые обнародования, там где разрешены только обнародованные обнародования, вызывая ошибки сборки. Давайте рассмотрим первую часть `main` в приложении 17-11: @@ -195,7 +195,7 @@ {{#rustdoc_include ../listings/ch17-oop/listing-17-11/src/main.rs:here}} ``` -Мы по-прежнему поддерживаем создание новых сообщений в состоянии "черновика" с помощью способа `Post::new` и возможность добавлять текст к содержимому обнародования. Но вместо способа `content` у чернового сообщения, возвращающего пустую строку, мы сделаем так, что у черновых сообщений вообще не будет способа `content`. Таким образом, если мы попытаемся получить содержимое черновика, мы получим ошибку сборщика, сообщающую, что способ не существует. В итоге мы не сможем случайно отобразить черновик содержимого записи в работающей программе, потому что этот код даже не собирается. В приложении 17-19 показано определение устройств `Post` и `DraftPost`, а также способов для каждой из них: +Мы по-прежнему поддерживаем создание новых сообщений в состоянии "черновика" с помощью способа `Post::new` и возможность добавлять текст к содержимому обнародования. Но вместо способа `content` у чернового сообщения, возвращающего пустую строку, мы сделаем так, что у черновых сообщений вообще не будет способа `content`. Таким образом, если мы попытаемся получить содержимое черновика, мы получим ошибку сборщика, сообщающую, что способ не существует. В итоге мы не сможем случайно отобразить черновик содержимого записи в работающей программе, потому что этот рукопись даже не собирается. В приложении 17-19 показано определение устройств `Post` и `DraftPost`, а также способов для каждой из них: Файл: src/lib.rs @@ -205,7 +205,7 @@ Приложение 17-19: Устройства Post с способом content и устройства DraftPost без способа content -Обе устройства, `Post` и `DraftPost`, имеют закрытое поле `content`, в котором хранится текст сообщения блога. Устройства больше не содержат поле `state`, потому что мы перемещаем кодирование состояния в виды устройств. Устройства `Post` будет представлять обнародованную размещение, и у неё есть способ `content`, который возвращает `content`. +Обе устройства, `Post` и `DraftPost`, имеют закрытое поле `content`, в котором хранится текст сообщения дневника. Устройства больше не содержат поле `state`, потому что мы перемещаем кодирование состояния в виды устройств. Устройства `Post` будет представлять обнародованную размещение, и у неё есть способ `content`, который возвращает `content`. У нас все ещё есть функция `Post::new`, но вместо возврата образца `Post` она возвращает образец `DraftPost`. Поскольку поле `content` является закрытым и нет никаких функций, которые возвращают `Post`, просто так создать образец `Post` уже невозможно. @@ -223,9 +223,9 @@ Приложение 17-20: Вид PendingReviewPost, который создаётся путём вызова request_review образца DraftPost и способ approve, который превращает PendingReviewPost в обнародованный Post. -Способы `request_review` и `approve` забирают во владение `self`, таким образом поглощая образцы `DraftPost` и `PendingReviewPost`, которые потом преобразуются в `PendingReviewPost` и обнародованную `Post`, соответственно. Таким образом, у нас не будет никаких долгоживущих образцов `DraftPost`, после того, как мы вызвали у них `request_review` и так далее. В устройстве `PendingReviewPost` не определён способ `content`, поэтому попытка прочитать его содержимое приводит к ошибке сборщика, также как и в случае с `DraftPost`. Так как единственным способом получить обнародованный образец `Post`, у которого действительно есть объявленный способ `content`, является вызов способа `approve` у образца `PendingReviewPost`, а единственный способ получить `PendingReviewPost` - это вызвать способ `request_review` у образца `DraftPost`, теперь мы закодировали этап смены состояний записи блога с помощью системы видов. +Способы `request_review` и `approve` забирают во владение `self`, таким образом поглощая образцы `DraftPost` и `PendingReviewPost`, которые потом преобразуются в `PendingReviewPost` и обнародованную `Post`, соответственно. Таким образом, у нас не будет никаких долгоживущих образцов `DraftPost`, после того, как мы вызвали у них `request_review` и так далее. В устройстве `PendingReviewPost` не определён способ `content`, поэтому попытка прочитать его содержимое приводит к ошибке сборщика, также как и в случае с `DraftPost`. Так как единственным способом получить обнародованный образец `Post`, у которого действительно есть объявленный способ `content`, является вызов способа `approve` у образца `PendingReviewPost`, а единственный способ получить `PendingReviewPost` - это вызвать способ `request_review` у образца `DraftPost`, теперь мы закодировали этап смены состояний записи дневника с помощью системы видов. -Кроме этого, нужно внести небольшие изменения в `main`. Так как способы `request_review` и `approve` теперь возвращают предметы, а не преобразуют устройство от которой были вызваны, нам нужно добавить больше затеняющих присваиваний `let post =`, чтобы сохранять возвращаемые предметы. Также, теперь мы не можем использовать утверждения (assertions) для проверки того является ли содержимое черновиков и записей, находящихся на рассмотрении, пустыми строками, да они нам и не нужны - теперь стало невозможным собрать код, который бы пытался использовать содержимое записей, находящихся в этих состояниях. Обновлённый код в `main` показан в приложении 17-21: +Кроме этого, нужно внести небольшие изменения в `main`. Так как способы `request_review` и `approve` теперь возвращают предметы, а не преобразуют устройство от которой были вызваны, нам нужно добавить больше затеняющих присваиваний `let post =`, чтобы сохранять возвращаемые предметы. Также, теперь мы не можем использовать утверждения (assertions) для проверки того является ли содержимое черновиков и записей, находящихся на рассмотрении, пустыми строками, да они нам и не нужны - теперь стало невозможным собрать рукопись, который бы пытался использовать содержимое записей, находящихся в этих состояниях. Обновлённый рукопись в `main` показан в приложении 17-21: Файл: src/main.rs @@ -233,19 +233,19 @@ {{#rustdoc_include ../listings/ch17-oop/listing-17-21/src/main.rs}} ``` -Приложение 17-21: Изменения в main, использующие новую выполнение этапа подготовки записи блога +Приложение 17-21: Изменения в main, использующие новую выполнение этапа подготовки записи дневника Изменения, которые нам нужно было внести в `main`, чтобы переназначить `post` означают, что эта выполнение теперь не совсем соответствует предметно-направленному образцу "Состояние": преобразования между состояниями больше не инкапсулированы внутри выполнения `Post` полностью. Тем не менее, мы получили большую выгоду в том, что недопустимые состояния теперь невозможны из-за системы видов и проверки видов, которая происходит во время сборки! У нас есть заверенияия, что некоторые ошибки, такие как отображение содержимого необнародованной обнародования, будут обнаружены до того, как они дойдут до пользователей. -Попробуйте выполнить задачи, предложенные в начале этого раздела, в исполнения ящика `blog`, каким он стал после приложения 17-20, чтобы создать своё мнение о внешнем виде этой исполнения кода. Обратите внимание, что некоторые задачи в этом исходе могут быть уже выполнены. +Попробуйте выполнить задачи, предложенные в начале этого раздела, в исполнения ящика `blog`, каким он стал после приложения 17-20, чтобы создать своё мнение о внешнем виде этой исполнения рукописи. Обратите внимание, что некоторые задачи в этом исходе могут быть уже выполнены. -Мы увидели, что хотя Ржавчина и способен выполнить предметно-направленные образцы разработки, в нём также доступны и другие образцы, такие как кодирование состояния с помощью системы видов. Эти подходы имеют различные соглашения. Хотя вы, возможно, очень хорошо знакомы с предметно-направленными образцами, переосмысление неполадок для использования преимуществ и возможностей Ржавчина может дать такие выгоды, как предотвращение некоторых ошибок во время сборки. Предметно-направленные образцы не всегда будут лучшим решением в Ржавчина из-за наличия определённых возможностей, таких как владение, которого нет у предметно-направленных языков. +Мы увидели, что хотя Ржавчина и способен выполнить предметно-направленные образцы разработки, в нём также доступны и другие образцы, такие как кодирование состояния с помощью системы видов. Эти подходы имеют различные соглашения. Хотя вы, возможно, очень хорошо знакомы с предметно-направленными образцами, переосмысление неполадок для использования преимуществ и возможностей Ржавчина может дать такие выгоды, как предотвращение некоторых ошибок во время сборки. Предметно-направленные образцы не всегда будут лучшим решением в Ржавчине из-за наличия определённых возможностей, таких как владение, которого нет у предметно-направленных языков. ## Итоги -Независимо от того, что вы думаете о принадлежности Ржавчина к предметно-направленным языкам после прочтения этой главы, теперь вы знаете, что можете использовать особенность-предметы, чтобы выполнить некоторые предметно-направленные свойства в Rust. Изменяемая управление может дать вашему коду некоторую гибкость в обмен на небольшое ухудшение производительности во время выполнения. Вы можете использовать эту гибкость для выполнения предметно-направленных образцов, которые могут улучшить сопровождаемость вашего кода. В Ржавчина также есть другие особенности, такие как владение, которых нет у предметно-направленных языков. Предметно-направленный образец не всегда будет лучшим способом использовать преимущества Rust, но является доступной возможностью. +Независимо от того, что вы думаете о принадлежности Ржавчина к предметно-направленным языкам после прочтения этой главы, теперь вы знаете, что можете использовать особенность-предметы, чтобы выполнить некоторые предметно-направленные свойства в Ржавчине Изменяемая управление может дать вашему рукописи некоторую гибкость в обмен на небольшое ухудшение производительности во время выполнения. Вы можете использовать эту гибкость для выполнения предметно-направленных образцов, которые могут улучшить сопровождаемость вашей рукописи. В Ржавчине также есть другие особенности, такие как владение, которых нет у предметно-направленных языков. Предметно-направленный образец не всегда будет лучшим способом использовать преимущества Ржавчина, но является доступной возможностью. -Далее мы рассмотрим образцы, которые являются ещё одной особенностью Rust, обеспечивающей высокую гибкость. Мы бегло рассказывали о них на протяжении всей книги, но ещё не видели всех их возможностей. Вперёд! +Далее мы рассмотрим образцы, которые являются ещё одной особенностью Ржавчина, обеспечивающей высокую гибкость. Мы бегло рассказывали о них на протяжении всей книги, но ещё не видели всех их возможностей. Вперёд! ["Случаи, когда у вас больше сведений, чем у сборщика"]: ch09-03-to-panic-or-not-to-panic.html#cases-in-which-you-have-more-information-than-the-compiler diff --git a/rustbook-ru/src/ch18-00-patterns.md b/rustbook-ru/src/ch18-00-patterns.md index 3e559f48e..3bc80b550 100644 --- a/rustbook-ru/src/ch18-00-patterns.md +++ b/rustbook-ru/src/ch18-00-patterns.md @@ -1,15 +1,15 @@ # Образцы и сопоставление -*Образцы* - это особый правила написания в Ржавчина для сопоставления со устройством видов, как сложных, так и простых. Использование образцов в сочетании с выражениями `match` и другими устройствоми даёт вам больший управление над потоком управления программы. Образец состоит из некоторой сочетания следующего: +*Образцы* - это особый правила написания в Ржавчине для сопоставления со устройством видов, как сложных, так и простых. Использование образцов в сочетании с выражениями `match` и другими устройствоми даёт вам больший управление над потоком управления программы. Образец состоит из некоторой сочетания следующего: - Записи - Деупорядоченные массивы, перечисления, устройства или упорядоченные ряды - Переменные -- Особые символы +- Особые знаки - Заполнители -Некоторые примеры образцов включают `x` , `(a, 3)` и `Some(Color::Red)` . В средах, в которых допустимы образцы, эти составляющие описывают разновидность данных. Затем наша программа сопоставляет значения с образцами, чтобы определить, имеет ли значение правильную разновидность данных для продолжения выполнения определённого отрывка кода. +Некоторые примеры образцов включают `x` , `(a, 3)` и `Some(Color::Red)` . В средах, в которых допустимы образцы, эти составляющие описывают разновидность данных. Затем наша программа сопоставляет значения с образцами, чтобы определить, имеет ли значение правильную разновидность данных для продолжения выполнения определённого отрывка рукописи. -Чтобы использовать образец, мы сравниваем его с некоторым значением. Если образец соответствует значению, мы используем части значения в нашем дальнейшем коде. Вспомните выражения `match` главы 6, в которых использовались образцы, например, описание машины для сортировки монет. Если значение в памяти соответствует виде образца, мы можем использовать именованные части образца. Если этого не произойдёт, то не выполнится код, связанный с образцом. +Чтобы использовать образец, мы сравниваем его с некоторым значением. Если образец соответствует значению, мы используем части значения в нашем дальнейшем рукописи. Вспомните выражения `match` главы 6, в которых использовались образцы, например, описание машины для сортировки монет. Если значение в памяти соответствует виде образца, мы можем использовать именованные части образца. Если этого не произойдёт, то не выполнится рукопись, связанный с образцом. Эта глава - справочник по всем особенностим, связанным с образцами. Мы расскажем о допустимых местах использования образцов, разнице между опровержимыми и неопровержимыми образцами и про различные виды правил написания образцов, которые вы можете увидеть. К концу главы вы узнаете, как использовать образцы для ясного выражения многих понятий. diff --git a/rustbook-ru/src/ch18-01-all-the-places-for-patterns.md b/rustbook-ru/src/ch18-01-all-the-places-for-patterns.md index e64de09a6..e9cfce17c 100644 --- a/rustbook-ru/src/ch18-01-all-the-places-for-patterns.md +++ b/rustbook-ru/src/ch18-01-all-the-places-for-patterns.md @@ -31,11 +31,11 @@ match x { ### Условные выражения `if let` -В главе 6 мы обсуждали, как использовать выражения `if let` как правило в качестве более короткого способа записи эквивалента `match`, которое обрабатывает только один случай. Дополнительно `if let` может иметь соответствующий `else`, содержащий код для выполнения, если образец выражения `if let` не совпадает. +В главе 6 мы обсуждали, как использовать выражения `if let` как правило в качестве более короткого способа записи эквивалента `match`, которое обрабатывает только один случай. Дополнительно `if let` может иметь соответствующий `else`, содержащий рукопись для выполнения, если образец выражения `if let` не совпадает. -В приложении 18-1 показано, что можно также смешивать и сопоставлять выражения `if let`, `else if` и `else if let`. Это даёт больше гибкости, чем `match` выражение, в котором можно выразить только одно значение для сравнения с образцами. Кроме того, условия в серии `if let`, `else if`, `else if let` не обязаны соотноситься друг с другом. +В приложении 18-1 показано, что можно также смешивать и сопоставлять выражения `if let`, `else if` и `else if let`. Это даёт больше гибкости, чем `match` выражение, в котором можно выразить только одно значение для сравнения с образцами. Кроме того, условия в последовательности `if let`, `else if`, `else if let` не обязаны соотноситься друг с другом. -Код в приложении 18-1 показывает последовательность проверок нескольких условий, определяющих каким должен быть цвет фона. В данном примере мы создали переменные с предопределёнными значениями, которые в существующей программе могли бы быть получены из пользовательского ввода. +Рукопись в приложении 18-1 показывает последовательность проверок нескольких условий, определяющих каким должен быть цвет фона. В данном примере мы создали переменные с предопределёнными значениями, которые в существующей программе могли бы быть получены из пользовательского ввода. Файл: src/main.rs @@ -45,37 +45,37 @@ match x { Приложение 18-1: Использование условных устройств if let, else if, else if let, и else -Если пользователь указывает любимый цвет, то этот цвет используется в качестве цвета фона. Если любимый цвет не указан, и сегодня вторник, то цвет фона - зелёный. Иначе, если пользователь указывает свой возраст в виде строки, и мы можем успешно проанализировать её и представить в виде числа, то цвет будет либо фиолетовым, либо оранжевым, в зависимости от значения числа. Если ни одно из этих условий не выполняется, то цвет фона будет синим. +Если пользователь указывает любимый цвет, то этот цвет используется в качестве цвета фона. Если любимый цвет не указан, и сегодня вторник, то цвет фона - зелёный. Иначе, если пользователь указывает свой возраст в виде строки, и мы можем успешно прассмотретьь её и представить в виде числа, то цвет будет либо фиолетовым, либо оранжевым, в зависимости от значения числа. Если ни одно из этих условий не выполняется, то цвет фона будет синим. Эта условная устройства позволяет поддерживать сложные требования. С жёстко закодированными значениями, которые у нас здесь есть, этот пример напечатает `Using purple as the background color`. -Можно увидеть, что `if let` может также вводить затенённые переменные, как это можно сделать в `match` ветках: строка `if let Ok(age) = age` вводит новую затенённую переменную `age`, которая содержит значение внутри исхода `Ok`. Это означает, что нам нужно поместить условие `if age > 30` внутри этого блок: мы не можем объединить эти два условия в `if let Ok(age) = age && age > 30`. Затенённый `age`, который мы хотим сравнить с 30, не является действительным, пока не начнётся новая область видимости с фигурной скобки. +Можно увидеть, что `if let` может также вводить затенённые переменные, как это можно сделать в `match` ветках: строка `if let Ok(age) = age` вводит новую затенённую переменную `age`, которая содержит значение внутри исхода `Ok`. Это означает, что нам нужно поместить условие `if age > 30` внутри этого раздел: мы не можем объединить эти два условия в `if let Ok(age) = age && age > 30`. Затенённый `age`, который мы хотим сравнить с 30, не является действительным, пока не начнётся новая область видимости с фигурной скобки. -Недостатком использования `if let` выражений является то, что сборщик не проверяет полноту (exhaustiveness) всех исходов, в то время как с помощью выражения `match` это происходит. Если мы не напишем последний раздел`else` и, благодаря этому, пропустим обработку некоторых случаев, сборщик не предупредит нас о возможной логической ошибке. +Недостатком использования `if let` выражений является то, что сборщик не проверяет полноту (exhaustiveness) всех исходов, в то время как с помощью выражения `match` это происходит. Если мы не напишем последний раздел`else` и, благодаря этому, пропустим обработку некоторых случаев, сборщик не предупредит нас о возможной разумной ошибке. -### Условные циклы `while let` +### Условные круговороты `while let` -Подобно устройства `if let`, устройство условного цикла `while let` позволяет повторять цикл `while` до тех пор, пока образец продолжает совпадать. Пример в приложении 18-2 отображает цикл `while let`, который использует вектор в качестве обоймы и печатает значения вектора в порядке, обратном тому, в котором они были помещены. +Подобно устройства `if let`, устройство условного круговорота `while let` позволяет повторять круговорот `while` до тех пор, пока образец продолжает совпадать. Пример в приложении 18-2 отображает круговорот `while let`, который использует вектор в качестве обоймы и печатает значения вектора в порядке, обратном тому, в котором они были помещены. ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-02/src/main.rs:here}} ``` -Приложение 18-2: Использование цикла while let для печати значений до тех пор, пока stack.pop() возвращает Some +Приложение 18-2: Использование круговорота while let для печати значений до тех пор, пока stack.pop() возвращает Some -В этом примере выводится 3, 2, а затем 1. Способ `pop` извлекает последний элемент из вектора и возвращает `Some(value)`. Если вектор пуст, то `pop` возвращает `None`. Цикл `while` продолжает выполнение кода в своём разделе, пока `pop` возвращает `Some`. Когда `pop` возвращает `None`, цикл останавливается. Мы можем использовать `while let` для удаления каждого элемента из обоймы. +В этом примере выводится 3, 2, а затем 1. Способ `pop` извлекает последний элемент из вектора и возвращает `Some(value)`. Если вектор пуст, то `pop` возвращает `None`. Круговорот `while` продолжает выполнение рукописи в своём разделе, пока `pop` возвращает `Some`. Когда `pop` возвращает `None`, круговорот останавливается. Мы можем использовать `while let` для удаления каждого элемента из обоймы. -### Цикл `for` +### Круговорот `for` -В цикле `for` значение, которое следует непосредственно за ключевым словом `for` , является образцом. Например, в `for x in y` выражение `x` является образцом. В приложении 18-3 показано, как использовать образец в цикле `for` , чтобы разъединять или разбить упорядоченный ряд как часть цикла `for` . +В круговороте `for` значение, которое следует непосредственно за ключевым словом `for` , является образцом. Например, в `for x in y` выражение `x` является образцом. В приложении 18-3 показано, как использовать образец в круговороте `for` , чтобы разъединять или разбить упорядоченный ряд как часть круговорота `for` . ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-03/src/main.rs:here}} ``` -Приложение 18-3: Использование образца в цикле for для разъединения упорядоченного ряда +Приложение 18-3: Использование образца в круговороте for для разъединения упорядоченного ряда -Код в приложении 18-3 выведет следующее: +Рукопись в приложении 18-3 выведет следующее: ```console {{#include ../listings/ch18-patterns-and-matching/listing-18-03/output.txt}} @@ -117,7 +117,7 @@ let PATTERN = EXPRESSION; Приложение 18-5: Неправильное построение образца, переменные не соответствуют количеству элементов в упорядоченном ряде -Попытка собрать этот код приводит к ошибке: +Попытка собрать этот рукопись приводит к ошибке: ```console {{#include ../listings/ch18-patterns-and-matching/listing-18-05/output.txt}} @@ -127,7 +127,7 @@ let PATTERN = EXPRESSION; ### Свойства функции -Свойства функции также могут быть образцами. Код в приложении 18-6 объявляет функцию с именем `foo`, которая принимает один свойство с именем `x` вида `i32`, к настоящему времени это должно выглядеть знакомым. +Свойства функции также могут быть образцами. Рукопись в приложении 18-6 объявляет функцию с именем `foo`, которая принимает один свойство с именем `x` вида `i32`, к настоящему времени это должно выглядеть знакомым. ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-06/src/main.rs:here}} @@ -145,7 +145,7 @@ let PATTERN = EXPRESSION; Приложение 18-7: Функция с свойствами, которая разрушает упорядоченный ряд -Этот код печатает `Current location: (3, 5)`. Значения `&(3, 5)` соответствуют образцу `&(x, y)`, поэтому `x` - это значение `3`, а `y` - это значение `5`. +Этот рукопись печатает `Current location: (3, 5)`. Значения `&(3, 5)` соответствуют образцу `&(x, y)`, поэтому `x` - это значение `3`, а `y` - это значение `5`. Добавляя к вышесказанному, мы можем использовать образцы в списках свойств замыкания таким же образом, как и в списках свойств функции, потому что, как обсуждалось в главе 13, замыкания похожи на функции. diff --git a/rustbook-ru/src/ch18-02-refutability.md b/rustbook-ru/src/ch18-02-refutability.md index b3358ebb1..8080aa77a 100644 --- a/rustbook-ru/src/ch18-02-refutability.md +++ b/rustbook-ru/src/ch18-02-refutability.md @@ -2,11 +2,11 @@ Образцы бывают двух видов: опровержимые и неопровержимые. Образцы, которые будут соответствовать любому возможному переданному значению, являются *неопровержимыми* (irrefutable). Примером может быть `x` в указания `let x = 5;`, потому что `x` соответствует чему-либо и, следовательно, не может не совпадать. Образцы, которые могут не соответствовать некоторому возможному значению, являются *опровержимыми* (refutable). Примером может быть `Some(x)` в выражении `if let Some(x) = a_value`, потому что если значение в переменной `a_value` равно `None`, а не `Some`, то образец `Some(x)` не будет совпадать. -Свойства функций, указания `let` и циклы `for` могут принимать только неопровержимые образцы, поскольку программа не может сделать ничего значимого, если значения не совпадают. А выражения `if let` и `while let` принимают опровержимые и неопровержимые образцы, но сборщик предостерегает от неопровержимых образцов, поскольку по определению они предназначены для обработки возможного сбоя: возможность условного выражения заключается в его способности выполнять разный код в зависимости от успеха или неудачи. +Свойства функций, указания `let` и круговороты `for` могут принимать только неопровержимые образцы, поскольку программа не может сделать ничего значимого, если значения не совпадают. А выражения `if let` и `while let` принимают опровержимые и неопровержимые образцы, но сборщик предостерегает от неопровержимых образцов, поскольку по определению они предназначены для обработки возможного сбоя: возможность условного выражения заключается в его способности выполнять разный рукопись в зависимости от успеха или неудачи. -В общем случае, вам не нужно беспокоиться о разнице между опровержимыми (refutable) и неопровержимыми (irrefutable) образцами; тем не менее, вам необходимо ознакомиться с подходом возможности опровержения, чтобы вы могли отреагировать на неё, увидев в сообщении об ошибке. В таких случаях вам потребуется изменить либо образец, либо устройство, с которой вы используете образец, в зависимости от предполагаемого поведения кода. +В общем случае, вам не нужно беспокоиться о разнице между опровержимыми (refutable) и неопровержимыми (irrefutable) образцами; тем не менее, вам необходимо ознакомиться с подходом возможности опровержения, чтобы вы могли отреагировать на неё, увидев в сообщении об ошибке. В таких случаях вам потребуется изменить либо образец, либо устройство, с которой вы используете образец, в зависимости от предполагаемого поведения рукописи. -Давайте посмотрим на пример того, что происходит, когда мы пытаемся использовать опровержимый образец, где Ржавчина требует неопровержимый образец, и наоборот. В приложении 18-8 показана указание `let`, но для образца мы указали `Some(x)`, являющийся образцом, который можно опровергнуть. Как и следовало ожидать, этот код не будет собираться. +Давайте посмотрим на пример того, что происходит, когда мы пытаемся использовать опровержимый образец, где Ржавчина требует неопровержимый образец, и наоборот. В приложении 18-8 показана указание `let`, но для образца мы указали `Some(x)`, являющийся образцом, который можно опровергнуть. Как и следовало ожидать, этот рукопись не будет собираться. ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-08/src/main.rs:here}} @@ -14,7 +14,7 @@ Приложение 18-8: Попытка использовать опровержимый образец вместе с let -Если `some_option_value` было бы значением `None`, то оно не соответствовало бы образцу `Some(x)`, что означает, что образец является опровержимым. Тем не менее, указание `let` может принимать только неопровержимый образец, потому что нет правильного кода, который может что-то сделать со значением `None`. Во время сборки Ржавчина будет жаловаться на то, что мы пытались использовать опровержимый образец, для которого требуется неопровержимый образец: +Если `some_option_value` было бы значением `None`, то оно не соответствовало бы образцу `Some(x)`, что означает, что образец является опровержимым. Тем не менее, указание `let` может принимать только неопровержимый образец, потому что нет правильного рукописи, который может что-то сделать со значением `None`. Во время сборки Ржавчина будет жаловаться на то, что мы пытались использовать опровержимый образец, для которого требуется неопровержимый образец: ```console {{#include ../listings/ch18-patterns-and-matching/listing-18-08/output.txt}} @@ -22,7 +22,7 @@ Поскольку мы не покрыли (и не могли покрыть!) каждое допустимое значение с помощью образца `Some(x)`, то Ржавчина выдаёт ошибку сборки. -Чтобы исправить неполадку наличия опровержимого образца, там, где нужен неопровержимый образец, можно изменить код, использующий образец: вместо использования `let`, можно использовать `if let`. Затем, если образец не совпадает, выполнение кода внутри фигурных скобок будет пропущено, что даст возможность продолжить правильное выполнение. В приложении 18-9 показано, как исправить код из приложения 18-8. +Чтобы исправить неполадку наличия опровержимого образца, там, где нужен неопровержимый образец, можно изменить рукопись, использующий образец: вместо использования `let`, можно использовать `if let`. Затем, если образец не совпадает, выполнение рукописи внутри фигурных скобок будет пропущено, что даст возможность продолжить правильное выполнение. В приложении 18-9 показано, как исправить рукопись из приложения 18-8. ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-09/src/main.rs:here}} @@ -30,7 +30,7 @@ Приложение 18-9. Использование if let и раздела с опровергнутыми образцами вместо let -Код исправлен! Этот код совершенно правильный, хотя это означает, что мы не можем использовать неопровержимый образец без получения ошибки. Если мы используем образец `if let`, который всегда будет совпадать, то для примера `x`, показанного в приложении 18-10, сборщик выдаст предупреждение. +Рукопись исправлен! Этот рукопись совершенно правильный, хотя это означает, что мы не можем использовать неопровержимый образец без получения ошибки. Если мы используем образец `if let`, который всегда будет совпадать, то для примера `x`, показанного в приложении 18-10, сборщик выдаст предупреждение. ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-10/src/main.rs:here}} @@ -38,7 +38,7 @@ Приложение 18-10. Попытка использовать неопровержимый образец с if let -Rust жалуется, что не имеет смысла использовать `if let` с неопровержимым образцом: +Ржавчина жалуется, что не имеет смысла использовать `if let` с неопровержимым образцом: ```console {{#include ../listings/ch18-patterns-and-matching/listing-18-10/output.txt}} diff --git a/rustbook-ru/src/ch18-03-pattern-syntax.md b/rustbook-ru/src/ch18-03-pattern-syntax.md index d8ce0a85e..b4acfaa7d 100644 --- a/rustbook-ru/src/ch18-03-pattern-syntax.md +++ b/rustbook-ru/src/ch18-03-pattern-syntax.md @@ -4,17 +4,17 @@ ### Сопоставление с записью -Как мы уже видели в главе 6, можно сопоставлять образцы с записями напрямую. В следующем коде есть несколько примеров: +Как мы уже видели в главе 6, можно сопоставлять образцы с записями напрямую. В следующем рукописи есть несколько примеров: ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/no-listing-01-literals/src/main.rs:here}} ``` -Этот код печатает `one`, потому что значение в `x` равно 1. Данный правила написания полезен, когда вы хотите, чтобы ваш код предпринял действие, если он получает определенное значение. +Этот рукопись печатает `one`, потому что значение в `x` равно 1. Данный правила написания полезен, когда вы хотите, чтобы ваша рукопись предпринял действие, если он получает определенное значение. ### Сопоставление именованных переменных -Именованные переменные - это неопровержимые (irrefutable) образцы, которые соответствуют любому значению и мы использовали их много раз в книге. Однако при использовании именованных переменных в выражениях `match` возникает сложность. Поскольку `match` начинает новую область видимости, то переменные, объявленные как часть образца внутри выражения `match`, будут затенять переменные с тем же именем вне устройства `match` как и в случае со всеми переменными. В приложении 18-11 мы объявляем переменную с именем `x` со значением `Some(5)` и переменную `y` со значением `10`. Затем мы создаём выражение `match` для значения `x`. Посмотрите на образцы в ветках, `println!` в конце и попытайтесь выяснить, какой код будет напечатан прежде чем запускать его или читать дальше. +Именованные переменные - это неопровержимые (irrefutable) образцы, которые соответствуют любому значению и мы использовали их много раз в книге. Однако при использовании именованных переменных в выражениях `match` возникает сложность. Поскольку `match` начинает новую область видимости, то переменные, объявленные как часть образца внутри выражения `match`, будут затенять переменные с тем же именем вне устройства `match` как и в случае со всеми переменными. В приложении 18-11 мы объявляем переменную с именем `x` со значением `Some(5)` и переменную `y` со значением `10`. Затем мы создаём выражение `match` для значения `x`. Посмотрите на образцы в ветках, `println!` в конце и попытайтесь выяснить, какую рукопись будет напечатан прежде чем запускать его или читать дальше. Файл: src/main.rs @@ -36,7 +36,7 @@ ### объединение образцов -В выражениях `match` можно сравнивать сразу с несколькими образцами, используя правила написания `|`, который является оператором образца *or*. Например, в следующем примере мы сопоставляем значение `x` с ветвями match, первая из которых содержит оператор *or*, так что если значение `x` совпадёт с любым из значений в этой ветви, то будет выполнен её код: +В выражениях `match` можно сравнивать сразу с несколькими образцами, используя правила написания `|`, который является приказчиком образца *or*. Например, в следующем примере мы сопоставляем значение `x` с ветвями match, первая из которых содержит приказчик *or*, так что если значение `x` совпадёт с любым из значений в этой ветви, то будет выполнен её код: ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/no-listing-02-multiple-patterns/src/main.rs:here}} @@ -46,13 +46,13 @@ ### Сопоставление рядов с помощью `..=` -правила написания `..=` позволяет нам выполнять сравнение с рядом значений. В следующем коде, когда в образце найдётся совпадение с любым из значений заданного ряда, будет выполнена эта ветка: +правила написания `..=` позволяет нам выполнять сравнение с рядом значений. В следующем рукописи, когда в образце найдётся совпадение с любым из значений заданного ряда, будет выполнена эта ветка: ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/no-listing-03-ranges/src/main.rs:here}} ``` -Если `x` равен 1, 2, 3, 4 или 5, то совпадение будет достигнуто в первой ветке. Этот правила написания более удобен при указании нескольких значений для сравнения, чем использование оператора `|` для определения этой же мысли; если бы мы решили использовать `|`, нам пришлось бы написать `1 | 2 | 3 | 4 | 5`. Указание ряда намного короче, особенно если мы хотим подобрать, скажем, любое число от 1 до 1 000! +Если `x` равен 1, 2, 3, 4 или 5, то совпадение будет достигнуто в первой ветке. Этот правила написания более удобен при указании нескольких значений для сравнения, чем использование приказчика `|` для определения этой же мысли; если бы мы решили использовать `|`, нам пришлось бы написать `1 | 2 | 3 | 4 | 5`. Указание ряда намного короче, особенно если мы хотим подобрать, скажем, любое число от 1 до 1 000! Сборщик проверяет, что рядне является пустым во время сборки, и поскольку единственными видами, для которых Ржавчина может определить, пуст рядили нет, являются `char` и числовые значения, ряды допускаются только с числовыми или `char` значениями. @@ -62,7 +62,7 @@ {{#rustdoc_include ../listings/ch18-patterns-and-matching/no-listing-04-ranges-of-char/src/main.rs:here}} ``` -Rust может сообщить, что `'c'` находится в ряде первого образца и напечатать `early ASCII letter`. +Ржавчина может сообщить, что `'c'` находится в ряде первого образца и напечатать `early ASCII letter`. ### Разъединение для получения значений @@ -80,7 +80,7 @@ Rust может сообщить, что `'c'` находится в ряде п Приложение 18-12: Разбиение полей устройства в отдельные переменные -Этот код создаёт переменные `a` и `b` , которые сопоставляются значениям полей `x` и `y` устройства `p` . Этот пример показывает, что имена переменных в образце не обязательно должны совпадать с именами полей устройства. Однако обычно имена переменных сопоставляются с именами полей, чтобы было легче запомнить, какие переменные взяты из каких полей. Из-за этого, а также из-за того, что строчка `let Point { x: x, y: y } = p;` содержит много повторения, в Ржавчина ввели особое сокращение для образцов, соответствующих полям устройства: вам нужно только указать имя поля устройства, и тогда переменные, созданные из образца, будут иметь те же имена. Код в приложении 18-13 подобен коду в Приложении 18-12, но в образце `let` создаются переменные `x` и `y`, вместо `a` и `b` . +Этот рукопись создаёт переменные `a` и `b` , которые сопоставляются значениям полей `x` и `y` устройства `p` . Этот пример показывает, что имена переменных в образце не обязательно должны совпадать с именами полей устройства. Однако обычно имена переменных сопоставляются с именами полей, чтобы было легче запомнить, какие переменные взяты из каких полей. Из-за этого, а также из-за того, что строчка `let Point { x: x, y: y } = p;` содержит много повторения, в Ржавчине ввели особое сокращение для образцов, соответствующих полям устройства: вам нужно только указать имя поля устройства, и тогда переменные, созданные из образца, будут иметь те же имена. Рукопись в приложении 18-13 подобен рукописи в Приложении 18-12, но в образце `let` создаются переменные `x` и `y`, вместо `a` и `b` . Файл: src/main.rs @@ -90,7 +90,7 @@ Rust может сообщить, что `'c'` находится в ряде п Приложение 18-13: Разъединение полей устройства с использованием сокращённой записи -Этот код создаёт переменные `x` и `y`, которые соответствуют полям `x` и `y` из переменной `p`. В итоге переменные `x` и `y` содержат значения из устройства `p`. +Этот рукопись создаёт переменные `x` и `y`, которые соответствуют полям `x` и `y` из переменной `p`. В итоге переменные `x` и `y` содержат значения из устройства `p`. А ещё, используя записанные значения в образце, мы можем разъединять, не создавая переменные для всех полей. Это даёт возможность, проверяя одни поля на соответствие определенным значениям, создавать переменные для разъединения других. @@ -104,13 +104,13 @@ Rust может сообщить, что `'c'` находится в ряде п Приложение 18-14: Разъединение и сопоставление с записями в одном образце -Первая ветвь будет соответствовать любой точке, лежащей на оси `x`, если значение поля `y` будет соответствовать записи `0`. Образец по-прежнему создаёт переменную `x`, которую мы сможем использовать в коде этой ветви. +Первая ветвь будет соответствовать любой точке, лежащей на оси `x`, если значение поля `y` будет соответствовать записи `0`. Образец по-прежнему создаёт переменную `x`, которую мы сможем использовать в рукописи этой ветви. Подобно, вторая ветвь совпадёт с любой точкой на оси `y`, в случае, если значение поля `x` будет равно `0`, а для значения поля `y` будет создана переменная `y`. Третья ветвь не содержит никаких записей, поэтому она соответствует любому другому `Point` и создаёт переменные как для поля `x`, так и для поля `y`. -В этом примере значение `p` совпадает по второй ветке, так как `x` содержит значение 0, поэтому этот код будет печатать `On the y axis at 7`. +В этом примере значение `p` совпадает по второй ветке, так как `x` содержит значение 0, поэтому этот рукопись будет печатать `On the y axis at 7`. -Помните, что выражение `match` перестаёт проверять следующие ветви, как только оно находит первый совпадающий образец, поэтому, даже если `Point { x: 0, y: 0}` находится на оси `x` и оси `y`, этот код будет печатать только `On the x axis at 0` . +Помните, что выражение `match` перестаёт проверять следующие ветви, как только оно находит первый совпадающий образец, поэтому, даже если `Point { x: 0, y: 0}` находится на оси `x` и оси `y`, этот рукопись будет печатать только `On the x axis at 0` . #### Разъединение перечислений @@ -124,17 +124,17 @@ Rust может сообщить, что `'c'` находится в ряде п Приложение 18-15: Разъединение исходов перечисления, содержащих разные виды значений -Этот код напечатает `Change the color to red 0, green 160, and blue 255`. Попробуйте изменить значение переменной `msg`, чтобы увидеть выполнение кода в других ветках. +Этот рукопись напечатает `Change the color to red 0, green 160, and blue 255`. Попробуйте изменить значение переменной `msg`, чтобы увидеть выполнение рукописи в других ветках. Для исходов перечисления без каких-либо данных, вроде `Message::Quit`, мы не можем разъединять значение, которого нет. Мы можем сопоставить только буквальное значение `Message::Quit` в этом образце, но без переменных. -Для исходов перечисления похожих на устройства, таких как `Message::Move`, можно использовать образец, подобный образцу, который мы указываем для сопоставления устройств. После имени исхода мы помещаем фигурные скобки и затем перечисляем поля именами переменных. Таким образом мы разделяем отрывки, которые будут использоваться в коде этой ветки. Здесь мы используем сокращённую разновидность, как в приложении 18-13. +Для исходов перечисления похожих на устройства, таких как `Message::Move`, можно использовать образец, подобный образцу, который мы указываем для сопоставления устройств. После имени исхода мы помещаем фигурные скобки и затем перечисляем поля именами переменных. Таким образом мы разделяем отрывки, которые будут использоваться в рукописи этой ветки. Здесь мы используем сокращённую разновидность, как в приложении 18-13. Для исходов перечисления, подобных упорядоченному ряду, вроде `Message::Write`, который содержит упорядоченный ряд с одним элементом и `Message::ChangeColor`, содержащему упорядоченный ряд с тремя элементами, образец подобен тому, который мы указываем для сопоставления упорядоченных рядов. Количество переменных в образце должно соответствовать количеству элементов в исходе, который мы сопоставляем. #### Разъединение вложенных устройств и перечислений -До сих пор все наши примеры сопоставляли устройства или перечисления на один уровень глубины, но сопоставление может работать и с вложенными элементами! Например, мы можем ресогласовать код в приложении 18-15 для поддержки цветов RGB и HSV в сообщении `ChangeColor` , как показано в приложении 18-16. +До сих пор все наши примеры сопоставляли устройства или перечисления на один уровень глубины, но сопоставление может работать и с вложенными элементами! Например, мы можем ресогласовать рукопись в приложении 18-15 для поддержки цветов RGB и HSV в сообщении `ChangeColor` , как показано в приложении 18-16. ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-16/src/main.rs}} @@ -152,7 +152,7 @@ Rust может сообщить, что `'c'` находится в ряде п {{#rustdoc_include ../listings/ch18-patterns-and-matching/no-listing-05-destructuring-structs-and-tuples/src/main.rs:here}} ``` -Этот код позволяет нам разбивать сложные виды на составные части, чтобы мы могли использовать нужным нас значения по отдельности. +Этот рукопись позволяет нам разбивать сложные виды на составные части, чтобы мы могли использовать нужным нас значения по отдельности. Разъединение с помощью образцов - это удобный способ использования отрывков значений, таких как как значение из каждого поля в устройстве, по отдельности друг от друга. @@ -172,13 +172,13 @@ Rust может сообщить, что `'c'` находится в ряде п Приложение 18-15: Использование _ в ярлыке функции -Этот код полностью пренебрегает значение `3`, переданное в качестве первого переменной, и выведет на печать `This code only uses the y parameter: 4`. +Этот рукопись полностью пренебрегает значение `3`, переданное в качестве первого переменной, и выведет на печать `This code only uses the y parameter: 4`. В большинстве случаев, когда вам больше не нужен какой-то из свойств функции, вы можете изменить её ярлык, убрав неиспользуемый свойство. Пренебрежение свойства функции может быть особенно полезно в случаях когда, например, вы выполняете особенность с определённой ярлыком, но тело функции в вашей выполнения не нуждается в одном из свойств. В таком случае сборщик не будет выдавать предупреждения о неиспользуемых свойствах функции, как это было бы, если бы вы указали имя свойства. #### Пренебрежение частей значения с помощью вложенного `_` -Также, `_` можно использовать внутри образцов, чтобы пренебрегать какую-то часть значения, например, когда мы хотим проверить только определённую подробность, а остальные свойства нам не понадобятся в коде, который нужно выполнить. В приложении 18-18 показан код, ответственный за управление значениями настроек. Согласно бизнес-требованиям, пользователь не может изменить установленное значение свойства, но может удалить его и задать ему новое значение, если на данный мгновение оно отсутствует. +Также, `_` можно использовать внутри образцов, чтобы пренебрегать какую-то часть значения, например, когда мы хотим проверить только определённую подробность, а остальные свойства нам не понадобятся в рукописи, который нужно выполнить. В приложении 18-18 показан рукопись, ответственный за управление значениями настроек. Согласно бизнес-требованиям, пользователь не может изменить установленное значение свойства, но может удалить его и задать ему новое значение, если на данный мгновение оно отсутствует. ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-18/src/main.rs:here}} @@ -186,7 +186,7 @@ Rust может сообщить, что `'c'` находится в ряде п Приложение 18-18: Использование подчёркивания в образцах, соответствующих исходам Some, когда нам не нужно использовать значение внутри Some -Этот код будет печатать `Can't overwrite an existing customized value`, а затем `setting is Some(5)`. В первой ветке нам не нужно сопоставлять или использовать значения внутри исхода `Some`, но нам нужно проверить случай, когда `setting_value` и `new_setting_value` являются исходом `Some`. В этом случае мы печатаем причину, почему мы не меняем значение `setting_value` и оно не меняется. +Этот рукопись будет печатать `Can't overwrite an existing customized value`, а затем `setting is Some(5)`. В первой ветке нам не нужно сопоставлять или использовать значения внутри исхода `Some`, но нам нужно проверить случай, когда `setting_value` и `new_setting_value` являются исходом `Some`. В этом случае мы печатаем причину, почему мы не меняем значение `setting_value` и оно не меняется. Во всех других случаях (если либо `setting_value`, либо `new_setting_value` являются исходом `None`), выраженных образцом `_` во второй ветке, мы хотим, чтобы `new_setting_value` стало равно `setting_value`. @@ -198,11 +198,11 @@ Rust может сообщить, что `'c'` находится в ряде п Приложение 18-19: Пренебрежение нескольких частей упорядоченного ряда -Этот код напечатает `Some numbers: 2, 8, 32`, а значения 4 и 16 будут пренебрежены. +Этот рукопись напечатает `Some numbers: 2, 8, 32`, а значения 4 и 16 будут пренебрежены. -#### Пренебрежение неиспользуемой переменной, начинающейся с символа `_` в имени +#### Пренебрежение неиспользуемой переменной, начинающейся с знака `_` в имени -Если вы создаёте переменную, но нигде её не используете, Ржавчина обычно выдаёт предупреждение, потому что неиспользуемая переменная может быть ошибкой. Но иногда полезно создать переменную, которую вы пока не используете, например, когда вы создаёте протовид или только начинаете дело. В этой случаи вы можете сказать Ржавчина не предупреждать вас о неиспользуемой переменной, начав имя переменной с подчёркивания. В приложении 18-20 мы создаём две неиспользуемые переменные, но когда мы собираем такой код, мы должны получить предупреждение только об одной из них. +Если вы создаёте переменную, но нигде её не используете, Ржавчина обычно выдаёт предупреждение, потому что неиспользуемая переменная может быть ошибкой. Но иногда полезно создать переменную, которую вы пока не используете, например, когда вы создаёте протовид или только начинаете дело. В этой случаи вы можете сказать Ржавчина не предупреждать вас о неиспользуемой переменной, начав имя переменной с подчёркивания. В приложении 18-20 мы создаём две неиспользуемые переменные, но когда мы собираем такой рукопись, мы должны получить предупреждение только об одной из них. Файл: src/main.rs @@ -230,7 +230,7 @@ Rust может сообщить, что `'c'` находится в ряде п Приложение 18-22. Использование подчёркивания не привязывает значение -Этот код работает правильно, потому что мы никогда не привязываем `s` к чему либо; оно не перемещается. +Этот рукопись работает правильно, потому что мы никогда не привязываем `s` к чему либо; оно не перемещается. #### Пренебрежение оставшихся частей значения с помощью `..` @@ -242,7 +242,7 @@ Rust может сообщить, что `'c'` находится в ряде п Приложение 18-21: Пренебрежение полей устройства Point кроме поля x с помощью .. -Мы перечисляем значение `x` и затем просто включаем образец `..`. Это быстрее, чем перечислять `y: _` и `z: _`, особенно когда мы работаем со устройствами, которые имеют много полей, в случаейх, когда только одно или два поля представляют для нас влечение. +Мы перечисляем значение `x` и затем просто включаем образец `..`. Это быстрее, чем перечислять `y: _` и `z: _`, особенно когда мы работаем со устройствами, которые имеют много полей, в случаях, когда только одно или два поля представляют для нас влечение. правила написания `..` раскроется до необходимого количества значений. В приложении 18-24 показано, как использовать `..` с упорядоченным рядом. @@ -254,7 +254,7 @@ Rust может сообщить, что `'c'` находится в ряде п Приложение 18-24: Сопоставление только первого и последнего значений в упорядоченном ряде и пренебрежение всех других значений -В этом коде первое и последнее значение соответствуют `first` и `last`. Устройство `..` будет соответствовать и пренебрегать всё, что находится между ними. +В этом рукописи первое и последнее значение соответствуют `first` и `last`. Устройство `..` будет соответствовать и пренебрегать всё, что находится между ними. Однако использование `..` должно быть однозначным. Если неясно, какие значения предназначены для сопоставления, а какие следует пренебрегать, Ржавчина выдаст ошибку. В приложении 18-25 показан пример неоднозначного использования `..`, поэтому он не будет собираться. @@ -272,9 +272,9 @@ Rust может сообщить, что `'c'` находится в ряде п {{#include ../listings/ch18-patterns-and-matching/listing-18-25/output.txt}} ``` -Rust не может определить, сколько значений в упорядоченном ряде нужно пренебрегать, прежде чем сопоставить значение с `second`, и сколько следующих значений пренебрегать после этого. Этот код может означать, что мы хотим пренебрегать `2`, связать `second` с `4`, а затем пренебрегать `8`, `16` и `32`; или что мы хотим пренебрегать `2` и `4`, связать `second` с `8`, а затем пренебрегать `16` и `32`; и так далее. Имя переменной `second` не означает ничего особенного для Rust, поэтому мы получаем ошибку сборщика, так как использование `..` в двух местах как здесь, является неоднозначным. +Ржавчина не может определить, сколько значений в упорядоченном ряде нужно пренебрегать, прежде чем сопоставить значение с `second`, и сколько следующих значений пренебрегать после этого. Этот рукопись может означать, что мы хотим пренебрегать `2`, связать `second` с `4`, а затем пренебрегать `8`, `16` и `32`; или что мы хотим пренебрегать `2` и `4`, связать `second` с `8`, а затем пренебрегать `16` и `32`; и так далее. Имя переменной `second` не означает ничего особенного для Ржавчины, поэтому мы получаем ошибку сборщика, так как использование `..` в двух местах как здесь, является неоднозначным. -### Дополнительные условия оператора сопоставления (Match Guards) +### Дополнительные условия приказчика сопоставления (Match Guards) *Условие сопоставления* (match guard) является дополнительным условием `if`, указанным после образца в ветке `match`, которое также должно быть выполнено, чтобы ветка была выбрана. Условия сопоставления полезны для выражения более сложных мыслей, чем позволяет только образец. @@ -290,7 +290,7 @@ Rust не может определить, сколько значений в у Если бы `num` вместо этого было `Some(5)`, условие в сопоставлении первой ветки было бы ложным, потому что остаток от 5 делённый на 2, равен 1, что не равно 0. Ржавчина тогда перешёл бы ко второй ветке, которое совпадает, потому что вторая ветка не имеет условия сопоставления и, следовательно, соответствует любому исходу `Some`. -Невозможно выразить условие `if x % 2 == 0` внутри образца, поэтому условие в сопоставлении даёт нам возможность выразить эту логику. Недостатком этой дополнительной выразительности является то, что сборщик не пытается проверять полноту, когда задействованы выражения с условием в сопоставлении. +Невозможно выразить условие `if x % 2 == 0` внутри образца, поэтому условие в сопоставлении даёт нам возможность выразить эту ход мыслей. Недостатком этой дополнительной выразительности является то, что сборщик не пытается проверять полноту, когда задействованы выражения с условием в сопоставлении. В приложении 18-11 мы упомянули, что можно использовать условия сопоставления для решения нашей сбоев затенения образца. Напомним, что внутри образца в выражении `match` была создана новая переменная, вместо использования внешней к `match` переменной. Эта новая переменная означала, что мы не могли выполнить сравнение с помощью значения внешней переменной. В приложении 18-27 показано, как мы можем использовать условие сопоставления для решения этой сбоев. @@ -302,11 +302,11 @@ Rust не может определить, сколько значений в у Приложение 18-27. Использование условия сопоставления для проверки на равенство со значением внешней переменной -Этот код теперь напечатает `Default case, x = Some(5)`. Образец во второй ветке не вводит новую переменную `y`, которая будет затенять внешнюю `y`, это означает, что теперь можно использовать внешнюю переменную `y` в условии сопоставления. Вместо указания образца как `Some(y)`, который бы затенял бы внешнюю `y`, мы указываем `Some(n)`. Это создаёт новую переменную `n`, которая ничего не затеняет, так как переменной `n` нет вне устройства `match`. +Этот рукопись теперь напечатает `Default case, x = Some(5)`. Образец во второй ветке не вводит новую переменную `y`, которая будет затенять внешнюю `y`, это означает, что теперь можно использовать внешнюю переменную `y` в условии сопоставления. Вместо указания образца как `Some(y)`, который бы затенял бы внешнюю `y`, мы указываем `Some(n)`. Это создаёт новую переменную `n`, которая ничего не затеняет, так как переменной `n` нет вне устройства `match`. Условие сопоставления `if n == y` не является образцом и следовательно, не вводит новые переменные. Переменная `y` *и есть* внешняя `y`, а не новая затенённая `y`, и теперь мы можем искать элемент, который будет иметь то же значение, что и внешняя `y`, путём сравнения `n` и `y`. -Вы также можете использовать оператор *или* `|` в условии сопоставления, чтобы указать несколько образцов; условие сопоставления будет применяться ко всем образцам. В приложении 18-28 показан приоритет соединения условия сопоставления с образцом, который использует `|`. Важной частью этого примера является то, что условие сопоставления `if y` применяется к `4`, `5`, *и* к `6`, хотя это может выглядеть как будто `if y` относится только к `6`. +Вы также можете использовать приказчик *или* `|` в условии сопоставления, чтобы указать несколько образцов; условие сопоставления будет применяться ко всем образцам. В приложении 18-28 показан приоритет соединения условия сопоставления с образцом, который использует `|`. Важной частью этого примера является то, что условие сопоставления `if y` применяется к `4`, `5`, *и* к `6`, хотя это может выглядеть как будто `if y` относится только к `6`. ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-28/src/main.rs:here}} @@ -314,7 +314,7 @@ Rust не может определить, сколько значений в у Приложение 18-28: Соединение нескольких образцов с условием сопоставления -Условие сопоставления гласит, что ветка совпадает, только если значение `x` равно `4`, `5` или `6`, *и* если `y` равно `true`. Когда этот код выполняется, образец первой ветки совпадает, потому что `x` равно `4`, но условие сопоставления `if y` равно false, поэтому первая ветка не выбрана. Код переходит ко второй ветке, которая совпадает, и эта программа печатает `no`. Причина в том, что условие `if` применяется ко всему образцу `4 | 5 | 6`, а не только к последнему значению `6`. Другими словами, приоритет условия сопоставления по отношению к образцу ведёт себя так: +Условие сопоставления гласит, что ветка совпадает, только если значение `x` равно `4`, `5` или `6`, *и* если `y` равно `true`. Когда этот рукопись выполняется, образец первой ветки совпадает, потому что `x` равно `4`, но условие сопоставления `if y` равно false, поэтому первая ветка не выбрана. Рукопись переходит ко второй ветке, которая совпадает, и эта программа печатает `no`. Причина в том, что условие `if` применяется ко всему образцу `4 | 5 | 6`, а не только к последнему значению `6`. Другими словами, приоритет условия сопоставления по отношению к образцу ведёт себя так: ```text (4 | 5 | 6) if y => ... @@ -326,11 +326,11 @@ Rust не может определить, сколько значений в у 4 | 5 | (6 if y) => ... ``` -После запуска кода, старшинство в поведении становится очевидным: если условие сопоставления применялось бы только к конечному значению в списке, указанном с помощью оператора `|`, то ветка бы совпала и программа напечатала бы `yes`. +После запуска рукописи, старшинство в поведении становится очевидным: если условие сопоставления применялось бы только к конечному значению в списке, указанном с помощью приказчика `|`, то ветка бы совпала и программа напечатала бы `yes`. ### Связывание `@` -Оператор *at* (`@`) позволяет создать переменную, которая содержит значение, одновременно с тем, как мы проверяем, соответствует ли это значение образцу. В приложении 18-29 показан пример, в котором мы хотим проверить, что перечисление `Message::Hello` со значением поля `id` находится в ряде `3..=7`. Но мы также хотим привязать такое значение к переменной `id_variable`, чтобы использовать его внутри кода данной ветки. Мы могли бы назвать эту переменную `id`, так же как поле, но для этого примера мы будем использовать другое имя. +Приказчик *at* (`@`) позволяет создать переменную, которая содержит значение, одновременно с тем, как мы проверяем, соответствует ли это значение образцу. В приложении 18-29 показан пример, в котором мы хотим проверить, что перечисление `Message::Hello` со значением поля `id` находится в ряде `3..=7`. Но мы также хотим привязать такое значение к переменной `id_variable`, чтобы использовать его внутри рукописи данной ветки. Мы могли бы назвать эту переменную `id`, так же как поле, но для этого примера мы будем использовать другое имя. ```rust {{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-29/src/main.rs:here}} @@ -340,9 +340,9 @@ Rust не может определить, сколько значений в у В этом примере будет напечатано `Found an id in range: 5`. Указывая `id_variable @` перед рядом `3..=7`, мы захватываем любое значение, попадающее в ряд, одновременно проверяя, что это значение соответствует ряду в образце. -Во второй ветке, где у нас в образце указан только ряд, код этой ветки не имеет переменной, которая содержит действительное значение поля `id`. Значение поля `id` могло бы быть 10, 11 или 12, но код, соответствующий этому образцу, не знает, чему оно равно. Код образца не может использовать значение из поля `id`, потому что мы не сохранили значение `id` в переменной. +Во второй ветке, где у нас в образце указан только ряд, рукопись этой ветки не имеет переменной, которая содержит действительное значение поля `id`. Значение поля `id` могло бы быть 10, 11 или 12, но рукопись, соответствующий этому образцу, не знает, чему оно равно. Рукопись образца не может использовать значение из поля `id`, потому что мы не сохранили значение `id` в переменной. -В последней ветке, где мы указали переменную без ряда, у нас есть значение, доступное для использования в коде ветки, в переменной с именем `id`. Причина в том, что мы использовали упрощённый правила написания полей устройства. Но мы не применяли никакого сравнения со значением в поле `id` в этой ветке, как мы это делали в первых двух ветках: любое значение будет соответствовать этому образцу. +В последней ветке, где мы указали переменную без ряда, у нас есть значение, доступное для использования в рукописи ветки, в переменной с именем `id`. Причина в том, что мы использовали упрощённый правила написания полей устройства. Но мы не применяли никакого сравнения со значением в поле `id` в этой ветке, как мы это делали в первых двух ветках: любое значение будет соответствовать этому образцу. Использование `@` позволяет проверять значение и сохранять его в переменной в пределах одного образца. @@ -350,4 +350,4 @@ Rust не может определить, сколько значений в у Образцы Ржавчина очень помогают различать разные виды данных. При использовании их в выражениях `match`, Ржавчина заверяет, что ваши образцы охватывают все возможные значения, потому что иначе ваша программа не собирается. Образцы в указаниях `let` и свойствах функций делают такие устройства более полезными, позволяя разбивать элементы на более мелкие части, одновременно присваивая их значения переменным. Мы можем создавать простые или сложные образцы в соответствии с нашими потребностями. -Далее, в предпоследней главе книги, мы рассмотрим некоторые продвинутые особенности различных возможностей Rust. +Далее, в предпоследней главе книги, мы рассмотрим некоторые продвинутые особенности различных возможностей Ржавчины. diff --git a/rustbook-ru/src/ch19-00-advanced-features.md b/rustbook-ru/src/ch19-00-advanced-features.md index 6f2b8ac61..cc1060d1c 100644 --- a/rustbook-ru/src/ch19-00-advanced-features.md +++ b/rustbook-ru/src/ch19-00-advanced-features.md @@ -1,13 +1,13 @@ # Расширенные возможности -На данный мгновение вы изучили все наиболее используемые части языка программирования Rust. Прежде чем мы выполним ещё один дело в главе 20, мы рассмотрим несколько особенностей языка, с которыми вы можете сталкиваться время от времени, но не использовать каждый день. Вы можете использовать эту главу в качестве справочника, когда столкнётесь с какими-либо незнакомыми вещами. Рассмотренные здесь функции будут полезны в очень отличительных случаейх. Хотя вы, возможно, не будете часто пользоваться ими, мы хотим убедиться, что вы знаете все возможности языка Rust. +На данный мгновение вы изучили все наиболее используемые части языка программирования Ржавчина. Прежде чем мы выполним ещё один дело в главе 20, мы рассмотрим несколько особенностей языка, с которыми вы можете сталкиваться время от времени, но не использовать каждый день. Вы можете использовать эту главу в качестве справочника, когда столкнётесь с какими-либо незнакомыми вещами. Рассмотренные здесь функции будут полезны в очень отличительных случаях. Хотя вы, возможно, не будете часто пользоваться ими, мы хотим убедиться, что вы знаете все возможности языка Ржавчина. В этой главе мы рассмотрим: -- Небезопасный Rust: как отказаться от некоторых заверений Ржавчина и взять на себя ответственность за их ручное соблюдение +- Небезопасный язык Ржавчина: как отказаться от некоторых заверений Ржавчина и взять на себя ответственность за их ручное соблюдение - Продвинутые особенности: сопряженные виды, свойства вида по умолчанию, полностью квалифицированный правила написания, супер-особенности и образец создания (newtype) по отношению к особенностям - Расширенные виды: больше о образце newtype, псевдонимах вида, вид never и виды изменяемыхх размеров - Расширенные функции и замыкания: указатели функций и возврат замыканий -- Макросы: способы определения кода, который определяет большую часть кода во время сборки +- Макросы: способы определения рукописи, который определяет большую часть рукописи во время сборки Это набор возможностей Ржавчина для всех! Давайте погрузимся в него! diff --git a/rustbook-ru/src/ch19-01-unsafe-rust.md b/rustbook-ru/src/ch19-01-unsafe-rust.md index c07582451..f8c851bfd 100644 --- a/rustbook-ru/src/ch19-01-unsafe-rust.md +++ b/rustbook-ru/src/ch19-01-unsafe-rust.md @@ -1,14 +1,14 @@ ## Unsafe Rust -Во всех предыдущих главах этой книги мы обсуждали код на Rust, безопасность памяти в котором обеспечивается во время сборки. Однако внутри Ржавчина скрывается другой язык - небезопасный Rust, который не обеспечивает безопасной работы с памятью. Этот язык называется *unsafe Rust* и работает также как и первый, но предоставляет вам дополнительные возможности. +Во всех предыдущих главах этой книги мы обсуждали рукопись на Ржавчине безопасность памяти в котором обеспечивается во время сборки. Однако внутри Ржавчина скрывается другой язык - небезопасный Ржавчина, который не обеспечивает безопасной работы с памятью. Этот язык называется *unsafe Ржавчина* и работает также как и первый, но предоставляет вам дополнительные возможности. -Небезопасный Ржавчина существует потому что по своей природе постоянной анализ довольно устоявшийся. Когда сборщик пытается определить, соответствует ли код заверениям, то он скорее отвергнет несколько допустимых программ, чем пропустит несколько недопустимых. Не смотря на то, что код *может* быть в порядке, если сборщик Ржавчина не будет располагать достаточной сведениями, чтобы убедиться в этом, он отвергнет код. В таких случаях вы можете использовать небезопасный код, чтобы сказать сборщику: "Поверь мне, я знаю, что делаю". Однако имейте в виду, что вы используете небезопасный Ржавчина на свой страх и риск: если вы неправильно используете небезопасный код, могут возникнуть сбоев, связанные с нарушением безопасности памяти, например, разыменование нулевого указателя. +Небезопасный Ржавчина существует потому что по своей природе постоянной оценка довольно устоявшийся. Когда сборщик пытается определить, соответствует ли рукопись заверениям, то он скорее отвергнет несколько допустимых программ, чем пропустит несколько недопустимых. Не смотря на то, что рукопись *может* быть в порядке, если сборщик Ржавчина не будет располагать достаточной сведениями, чтобы убедиться в этом, он отвергнет рукопись. В таких случаях вы можете использовать небезопасную рукопись, чтобы сказать сборщику: "Поверь мне, я знаю, что делаю". Однако имейте в виду, что вы используете небезопасный Ржавчина на свой страх и риск: если вы неправильно используете небезопасную рукопись, могут возникнуть сбоев, связанные с нарушением безопасности памяти, например, разыменование нулевого указателя. -Другая причина, по которой у Ржавчина есть небезопасное альтер эго, заключается в том, что по существу аппаратное обеспечение компьютера небезопасно. Если Ржавчина не позволял бы вам выполнять небезопасные действия, вы не могли бы выполнять определённые задачи. Ржавчина должен позволить вам использовать системное, низкоуровневое программирование, такое как прямое взаимодействие с операционной системой, или даже написание вашей собственной операционной системы. Возможность написания низкоуровневого, системного кода является одной из целей языка. Давайте рассмотрим, что и как можно делать с небезопасным Rust. +Другая причина, по которой у Ржавчина есть небезопасное альтер эго, заключается в том, что по существу аппаратное обеспечение компьютера небезопасно. Если Ржавчина не позволял бы вам выполнять небезопасные действия, вы не могли бы выполнять определённые задачи. Ржавчина должен позволить вам использовать системное, низкоуровневое программирование, такое как прямое взаимодействие с операционной системой, или даже написание вашей собственной операционной системы. Возможность написания низкоуровневого, системного рукописи является одной из целей языка. Давайте рассмотрим, что и как можно делать с небезопасным Ржавчиной. ### Небезопасные сверхспособности -Чтобы переключиться на небезопасный Rust, используйте ключевое слово `unsafe`, а затем начните новый блок, содержащий небезопасный код. В небезопасном Ржавчина можно выполнять пять действий, которые недоступны в безопасном Rust, которые мы называем *небезопасными супер силами*. Эти супер силы включают в себя следующее: +Чтобы переключиться на небезопасный Ржавчина используйте ключевое слово `unsafe`, а затем начните новый раздел, содержащий небезопасный рукопись. В небезопасном Ржавчина можно выполнять пять действий, которые недоступны в безопасном Ржавчина, которые мы называем *небезопасными супер силами*. Эти супер силы включают в себя следующее: - Разыменование сырого указателя - Вызов небезопасной функции или небезопасного способа @@ -16,19 +16,19 @@ - Выполнение небезопасного особенности - Доступ к полям в `union` -Важно понимать, что `unsafe` не отключает проверку заимствования или любые другие проверки безопасности Rust: если вы используете ссылку в небезопасном коде, она всё равно будет проверена. Единственное, что делает ключевое слово `unsafe` - даёт вам доступ к этим пяти возможностям, безопасность работы с памятью в которых не проверяет сборщик. Вы по-прежнему получаете некоторую степень безопасности внутри небезопасного раздела. +Важно понимать, что `unsafe` не отключает проверку заимствования или любые другие проверки безопасности языка Ржавчина: если вы используете ссылку в небезопасном рукописи, она всё равно будет проверена. Единственное, что делает ключевое слово `unsafe` - даёт вам доступ к этим пяти возможностям, безопасность работы с памятью в которых не проверяет сборщик. Вы по-прежнему получаете некоторую степень безопасности внутри небезопасного раздела. -Кроме того, `unsafe` не означает, что код внутри этого раздела является неизбежно опасным или он точно будет иметь сбоев с безопасностью памяти: цель состоит в том, что вы, как программист, заверяете, что код внутри раздела `unsafe` будет обращаться к действительной памяти правильным образом. +Кроме того, `unsafe` не означает, что рукопись внутри этого раздела является неизбежно опасным или он точно будет иметь сбоев с безопасностью памяти: цель состоит в том, что вы, как программист, заверяете, что рукопись внутри раздела `unsafe` будет обращаться к действительной памяти правильным образом. Люди подвержены ошибкам и ошибки будут происходить, но требуя размещение этих четырёх небезопасных действия внутри разделов, помеченных как `unsafe`, вы будете знать, что любые ошибки, связанные с безопасностью памяти, будут находиться внутри `unsafe` разделов. Делайте `unsafe` разделы маленькими; вы будете благодарны себе за это позже, при исследовании ошибок с памятью. -Чтобы наиболее изолировать небезопасный код, советуется заключить небезопасный код в безопасную абстракцию и предоставить безопасный API, который мы обсудим позже, когда будем обсуждать небезопасные функции и способы. Части встроенной библиотеки выполнены как проверенные, безопасные абстракции над небезопасным кодом. Оборачивание небезопасного кода в безопасную абстракцию предотвращает возможную утечку использования `unsafe` кода во всех местах, где вы или ваши пользователи могли бы захотеть напрямую использовать возможность, выполненную `unsafe` кодом, потому что использование безопасной абстракции само безопасно. +Чтобы наиболее изолировать небезопасную рукопись, советуется заключить небезопасную рукопись в безопасную абстракцию и предоставить безопасный API, который мы обсудим позже, когда будем обсуждать небезопасные функции и способы. Части встроенной библиотеки выполнены как проверенные, безопасные абстракции над небезопасным рукописью. Оборачивание небезопасного рукописи в безопасную абстракцию предотвращает возможную утечку использования `unsafe` рукописи во всех местах, где вы или ваши пользователи могли бы захотеть напрямую использовать возможность, выполненную `unsafe` рукописью, потому что использование безопасной абстракции само безопасно. -Давайте поговорим о каждой из четырёх небезопасных сверх способностей, и по ходу дела рассмотрим некоторые абстракции, которые обеспечивают безопасный внешняя оболочка для небезопасного кода. +Давайте поговорим о каждой из четырёх небезопасных сверх способностей, и по ходу дела рассмотрим некоторые абстракции, которые обеспечивают безопасный внешняя оболочка для небезопасного рукописи. ### Разыменование сырых указателей -В главе 4 раздела ["Недействительные ссылки"](ch04-02-references-and-borrowing.html#dangling-references) мы упоминали, что сборщик заверяет, что ссылки всегда действительны. Небезопасный Ржавчина имеет два новых вида, называемых *сырыми указателями* (raw pointers), которые похожи на ссылки. Как и в случае ссылок, сырые указатели могут быть неизменяемыми или изменяемыми и записываться как `*const T` и `*mut T` соответственно. Звёздочка не является оператором разыменования; это часть имени вида. В среде сырых указателей *неизменяемый* (immutable) означает, что указателю нельзя напрямую присвоить что-то после того как он разыменован. +В главе 4 раздела ["Недействительные ссылки"](ch04-02-references-and-borrowing.html#dangling-references) мы упоминали, что сборщик заверяет, что ссылки всегда действительны. Небезопасный Ржавчина имеет два новых вида, называемых *сырыми указателями* (raw pointers), которые похожи на ссылки. Как и в случае ссылок, сырые указатели могут быть неизменяемыми или изменяемыми и записываться как `*const T` и `*mut T` соответственно. Звёздочка не является приказчиком разыменования; это часть имени вида. В среде сырых указателей *неизменяемый* (immutable) означает, что указателю нельзя напрямую присвоить что-то после того как он разыменован. В отличие от ссылок и умных указателей, сырые указатели: @@ -47,11 +47,11 @@ Приложение 19-1: Создание необработанных указателей из ссылок -Обратите внимание, что мы не используем ключевое слово `unsafe` в этом коде. Можно создавать сырые указатели в безопасном коде; мы просто не можем разыменовывать сырые указатели за пределами небезопасного раздела, как вы увидите чуть позже. +Обратите внимание, что мы не используем ключевое слово `unsafe` в этом рукописи. Можно создавать сырые указатели в безопасном рукописи; мы просто не можем разыменовывать сырые указатели за пределами небезопасного раздела, как вы увидите чуть позже. Мы создали сырые указатели, используя `as` для приведения неизменяемой и изменяемой ссылки к соответствующим им видам сырых указателей. Поскольку мы создали их непосредственно из ссылок, которые обязательно являются действительными, мы знаем, что эти определенные сырые указатели являются действительными, но мы не можем делать такое же предположение о любом сыром указателе. -Чтобы отобразить это, создадим сырой указатель, в достоверности которого мы не можем быть так уверены. В приложении 19-2 показано, как создать необработанный указатель на произвольное место в памяти. Попытка использовать произвольную память является непредсказуемой: по этому адресу могут быть данные, а могут и не быть, сборщик может перерабатывать код так, что доступа к памяти не будет, или программа может завершиться с ошибкой сегментации. Обычно нет веских причин писать такой код, но это возможно. +Чтобы отобразить это, создадим сырой указатель, в достоверности которого мы не можем быть так уверены. В приложении 19-2 показано, как создать необработанный указатель на произвольное место в памяти. Попытка использовать произвольную память является непредсказуемой: по этому адресу могут быть данные, а могут и не быть, сборщик может перерабатывать рукопись так, что доступа к памяти не будет, или программа может завершиться с ошибкой сегментации. Обычно нет веских причин писать такой рукопись, но это возможно. ```rust {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-02/src/main.rs:here}} @@ -59,7 +59,7 @@ Приложение 19-2: Создание сырого указателя на произвольный адрес памяти -Напомним, что можно создавать сырые указатели в безопасном коде, но нельзя *разыменовывать* сырые указатели и читать данные, на которые они указывают. В приложении 19-3 мы используем оператор разыменования `*` для сырого указателя, который требует `unsafe` раздела. +Напомним, что можно создавать сырые указатели в безопасном рукописи, но нельзя *разыменовывать* сырые указатели и читать данные, на которые они указывают. В приложении 19-3 мы используем приказчик разыменования `*` для сырого указателя, который требует `unsafe` раздела. ```rust {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-03/src/main.rs:here}} @@ -69,13 +69,13 @@ Создание указателей безопасно. Только при попытке доступа к предмету по адресу в указателе мы можем получить недопустимое значение. -Также обратите внимание, что в примерах кода 19-1 и 19-3 мы создали `*const i32` и `*mut i32`, которые ссылаются на одну и ту же область памяти, где хранится `num`. Если мы попытаемся создать неизменяемую и изменяемую ссылку на `num` вместо сырых указателей, такой код не собирается, т.к. будут нарушены правила заимствования, запрещающие наличие изменяемой ссылки одновременно с неизменяемыми ссылками. С помощью сырых указателей мы можем создать изменяемый указатель и неизменяемый указатель на одну и ту же область памяти и изменять данные с помощью изменяемого указателя, возможно создавая эффект гонки данных. Будьте осторожны! +Также обратите внимание, что в примерах рукописи 19-1 и 19-3 мы создали `*const i32` и `*mut i32`, которые ссылаются на одну и ту же область памяти, где хранится `num`. Если мы попытаемся создать неизменяемую и изменяемую ссылку на `num` вместо сырых указателей, такой рукопись не собирается, т.к. будут нарушены правила заимствования, запрещающие наличие изменяемой ссылки одновременно с неизменяемыми ссылками. С помощью сырых указателей мы можем создать изменяемый указатель и неизменяемый указатель на одну и ту же область памяти и изменять данные с помощью изменяемого указателя, возможно создавая итог гонки данных. Будьте осторожны! -С учётом всех этих опасностей, зачем тогда использовать сырые указатели? Одним из основных применений является взаимодействие с кодом C, как вы увидите в следующем разделе ["Вызов небезопасной функции или способа"](#calling-an-unsafe-function-or-method). Другой случай это создание безопасных абстракций, которые не понимает анализатор заимствований. Мы введём понятие небезопасных функций и затем рассмотрим пример безопасной абстракции, которая использует небезопасный код. +С учётом всех этих опасностей, зачем тогда использовать сырые указатели? Одним из основных применений является взаимодействие с рукописью C, как вы увидите в следующем разделе ["Вызов небезопасной функции или способа"](#calling-an-unsafe-function-or-method). Другой случай это создание безопасных абстракций, которые не понимает оценщик заимствований. Мы введём понятие небезопасных функций и затем рассмотрим пример безопасной абстракции, которая использует небезопасный рукопись. ### Вызов небезопасной функции или способа -Второй вид действий, которые можно выполнять в небезопасном разделе - это вызов небезопасных функций. Небезопасные функции и способы выглядят точно так же, как обычные функции и способы, но перед остальным определением у них есть дополнительное `unsafe`. Ключевое слово `unsafe` в данном среде указывает на то, что к функции предъявляются требования, которые мы должны соблюдать при вызове этой функции, поскольку Ржавчина не может обеспечить, что мы их выполняем. Вызывая небезопасную функцию внутри раздела `unsafe`, мы говорим, что прочитали документацию к этой функции и берём на себя ответственность за соблюдение её условий. +Второй вид действий, которые можно выполнять в небезопасном разделе - это вызов небезопасных функций. Небезопасные функции и способы выглядят точно так же, как обычные функции и способы, но перед остальным определением у них есть дополнительное `unsafe`. Ключевое слово `unsafe` в данном среде указывает на то, что к функции предъявляются требования, которые мы должны соблюдать при вызове этой функции, поскольку Ржавчина не может обеспечить, что мы их выполняем. Вызывая небезопасную функцию внутри раздела `unsafe`, мы говорим, что прочитали пособие к этой функции и берём на себя ответственность за соблюдение её условий. Вот небезопасная функция с именем `dangerous` которая ничего не делает в своём теле: @@ -89,13 +89,13 @@ {{#include ../listings/ch19-advanced-features/output-only-01-missing-unsafe/output.txt}} ``` -С помощью раздела `unsafe` мы сообщаем Rust, что прочитали документацию к функции, поняли, как правильно её использовать, и убедились, что выполняем договор функции. +С помощью раздела `unsafe` мы сообщаем Ржавчине, что прочитали пособие к функции, поняли, как правильно её использовать, и убедились, что выполняем договор функции. -Тела небезопасных функций являются в действительности `unsafe` разделами, поэтому для выполнения других небезопасных действий внутри небезопасной функции не нужно добавлять ещё один `unsafe` блок. +Тела небезопасных функций являются в действительности `unsafe` разделами, поэтому для выполнения других небезопасных действий внутри небезопасной функции не нужно добавлять ещё один `unsafe` раздел. -#### Создание безопасных абстракций вокруг небезопасного кода +#### Создание безопасных абстракций вокруг небезопасного рукописи -То, что функция содержит небезопасный код, не означает, что мы должны пометить всю функцию как небезопасную. На самом деле, обёртывание небезопасного кода в безопасную функцию - это обычная абстракция. В качестве примера рассмотрим функцию `split_at_mut` из встроенной библиотеки, которая требует некоторого небезопасного кода. Рассмотрим, как мы могли бы её выполнить. Этот безопасный способ определён для изменяемых срезов: он берет один срез и превращает его в два, разделяя срез по порядковому указателю, указанному в качестве переменной. В приложении 19-4 показано, как использовать `split_at_mut`. +То, что функция содержит небезопасную рукопись, не означает, что мы должны пометить всю функцию как небезопасную. На самом деле, обёртывание небезопасного рукописи в безопасную функцию - это обычная абстракция. В качестве примера рассмотрим функцию `split_at_mut` из встроенной библиотеки, которая требует некоторого небезопасного рукописи. Рассмотрим, как мы могли бы её выполнить. Этот безопасный способ определён для изменяемых срезов: он берет один срез и превращает его в два, разделяя срез по порядковому указателю, указанному в качестве переменной. В приложении 19-4 показано, как использовать `split_at_mut`. ```rust {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-04/src/main.rs:here}} @@ -103,43 +103,43 @@ Приложение 19-4: Использование безопасной функции split_at_mut -Эту функцию нельзя выполнить, используя только безопасный Rust. Попытка выполнения могла бы выглядеть примерно как в приложении 19-5, который не собирается. Для простоты мы выполняем `split_at_mut` как функцию, а не как способ, и только для значений вида `i32`, а не обобщённого вида `T`. +Эту функцию нельзя выполнить, используя только безопасный Ржавчина. Попытка выполнения могла бы выглядеть примерно как в приложении 19-5, который не собирается. Для простоты мы выполняем `split_at_mut` как функцию, а не как способ, и только для значений вида `i32`, а не обобщённого вида `T`. ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-05/src/main.rs:here}} ``` -Приложение 19-5: Попытка выполнения split_at_mut с использованием только безопасного Rust +Приложение 19-5: Попытка выполнения split_at_mut с использованием только безопасного Ржавчина -Эта функция сначала получает общую длину среза. Затем она проверяет (assert), что порядковый указатель, переданный в качестве свойства, находится в границах среза, сравнивая его с длиной. Assert означает, что если мы передадим порядковый указатель, который больше, чем длина среза, функция запаникует ещё до попытки использования этого порядкового указателя. +Эта функция сначала получает общую длину среза. Затем она проверяет (assert), что порядковый указатель, переданный в качестве свойства, находится в границах среза, сравнивая его с длиной. Assert означает, что если мы передадим порядковый указатель, который больше, чем длина среза, функция завызывает сбой ещё до попытки использования этого порядкового указателя. Затем мы возвращаем два изменяемых отрывка в упорядоченном ряде: один от начала исходного отрывка до `mid` порядкового указателя (не включая сам mid), а другой - от `mid` (включая сам mid) до конца отрывка. -При попытке собрать код в приложении 19-5, мы получим ошибку. +При попытке собрать рукопись в приложении 19-5, мы получим ошибку. ```console {{#include ../listings/ch19-advanced-features/listing-19-05/output.txt}} ``` -Анализатор заимствований Ржавчина не может понять, что мы заимствуем различные части среза, он понимает лишь, что мы хотим осуществить заимствование частей одного среза дважды. Заимствование различных частей среза в принципе в порядке вещей, потому что они не перекрываются, но Ржавчина недостаточно умён, чтобы это понять. Когда мы знаем, что код верный, но Ржавчина этого не понимает, значит пришло время прибегнуть к небезопасному коду. +Оценщик заимствований Ржавчина не может понять, что мы заимствуем различные части среза, он понимает лишь, что мы хотим осуществить заимствование частей одного среза дважды. Заимствование различных частей среза в принципе в порядке вещей, потому что они не перекрываются, но Ржавчина недостаточно умён, чтобы это понять. Когда мы знаем, что рукопись верный, но Ржавчина этого не понимает, значит пришло время прибегнуть к небезопасному рукописи. -Приложение 19-6 отображает, как можно использовать `unsafe` блок, сырой указатель и вызовы небезопасных функций чтобы `split_at_mut` заработала: +Приложение 19-6 отображает, как можно использовать `unsafe` раздел, сырой указатель и вызовы небезопасных функций чтобы `split_at_mut` заработала: ```rust {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-06/src/main.rs:here}} ``` -Приложение 19-6. Использование небезопасного кода в выполнения функции split_at_mut +Приложение 19-6. Использование небезопасного рукописи в выполнения функции split_at_mut Напомним, из раздела ["Вид срез"] главы 4, что срезы состоят из указателя на некоторые данные и длины. Мы используем способ `len` для получения длины среза и способ `as_mut_ptr` для доступа к сырому указателю среза. Поскольку у нас есть изменяемый срез на значения вида `i32`, функция `as_mut_ptr` возвращает сырой указатель вида `*mut i32`, который мы сохранили в переменной `ptr`. -Далее проверяем, что порядковый указатель`mid` находится в границах среза. Затем мы обращаемся к небезопасному коду: функция `slice::from_raw_parts_mut` принимает сырой указатель, длину и создаёт срез. Мы используем эту функцию для создания среза, начинающегося с `ptr` и имеющего длину в `mid` элементов. Затем мы вызываем способ `add` у `ptr` с `mid` в качестве переменной, чтобы получить сырой указатель, который начинается с `mid`, и создаём срез, используя этот указатель и оставшееся количество элементов после `mid` в качестве длины. +Далее проверяем, что порядковый указатель`mid` находится в границах среза. Затем мы обращаемся к небезопасному рукописи: функция `slice::from_raw_parts_mut` принимает сырой указатель, длину и создаёт срез. Мы используем эту функцию для создания среза, начинающегося с `ptr` и имеющего длину в `mid` элементов. Затем мы вызываем способ `add` у `ptr` с `mid` в качестве переменной, чтобы получить сырой указатель, который начинается с `mid`, и создаём срез, используя этот указатель и оставшееся количество элементов после `mid` в качестве длины. -Функция `slice::from_raw_parts_mut` является небезопасной, потому что она принимает необработанный указатель и должна полагаться на то, что этот указатель действителен. Способ `add` для необработанных указателей также небезопасен, поскольку он должен считать, что местоположение смещения также является действительным указателем. Поэтому мы были вынуждены разместить `unsafe` разделвокруг наших вызовов `slice::from_raw_parts_mut` и `add`, чтобы иметь возможность вызвать их. Посмотрев на код и добавив утверждение, что `mid` должен быть меньше или равен `len`, мы можем сказать, что все необработанные указатели, используемые в разделе `unsafe`, будут правильными указателями на данные внутри среза. Это приемлемое и уместное использование `unsafe`. +Функция `slice::from_raw_parts_mut` является небезопасной, потому что она принимает необработанный указатель и должна полагаться на то, что этот указатель действителен. Способ `add` для необработанных указателей также небезопасен, поскольку он должен считать, что местоположение смещения также является действительным указателем. Поэтому мы были вынуждены разместить `unsafe` разделвокруг наших вызовов `slice::from_raw_parts_mut` и `add`, чтобы иметь возможность вызвать их. Посмотрев на рукопись и добавив утверждение, что `mid` должен быть меньше или равен `len`, мы можем сказать, что все необработанные указатели, используемые в разделе `unsafe`, будут правильными указателями на данные внутри среза. Это приемлемое и уместное использование `unsafe`. -Обратите внимание, что нам не нужно помечать результирующую функцию `split_at_mut` как `unsafe`, и мы можем вызвать эту функцию из безопасного Rust. Мы создали безопасную абстракцию для небезопасного кода с помощью выполнения функции, которая использует код `unsafe` раздела безопасным образом, поскольку она создаёт только допустимые указатели из данных, к которым эта функция имеет доступ. +Обратите внимание, что нам не нужно помечать результирующую функцию `split_at_mut` как `unsafe`, и мы можем вызвать эту функцию из безопасного Ржавчины. Мы создали безопасную абстракцию для небезопасного рукописи с помощью выполнения функции, которая использует рукопись `unsafe` раздела безопасным образом, поскольку она создаёт только допустимые указатели из данных, к которым эта функция имеет доступ. -Напротив, использование `slice::from_raw_parts_mut` в приложении 19-7 приведёт к вероятному сбою при использовании среза. Этот код использует произвольный адрес памяти и создаёт срез из 10000 элементов. +Напротив, использование `slice::from_raw_parts_mut` в приложении 19-7 приведёт к вероятному сбою при использовании среза. Этот рукопись использует произвольный адрес памяти и создаёт срез из 10000 элементов. ```rust {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-07/src/main.rs:here}} @@ -147,13 +147,13 @@ Приложение 19-7: Создание среза из произвольного адреса памяти -Мы не владеем памятью в этом произвольном месте, и нет никакой заверения, что созданный этим кодом отрывок содержит допустимые значения `i32`. Попытка использовать `values` так, как будто это допустимый срез, приводит к неопределённому поведению. +Мы не владеем памятью в этом произвольном месте, и нет никакой заверения, что созданный этим рукописью отрывок содержит допустимые значения `i32`. Попытка использовать `values` так, как будто это допустимый срез, приводит к неопределённому поведению. -#### Использование `extern` функций для вызова внешнего кода +#### Использование `extern` функций для вызова внешнего рукописи -Иногда вашему коду на языке Ржавчина может потребоваться взаимодействие с кодом, написанным на другом языке. Для этого в Ржавчина есть ключевое слово `extern`, которое облегчает создание и использование *внешней оболочки внешних функций (Foreign Function Interface - FFI)*. FFI - это способ для языка программирования определить функции и позволить другому (внешнему) языку программирования вызывать эти функции. +Иногда вашему рукописи на языке Ржавчина может потребоваться взаимодействие с рукописью, написанным на другом языке. Для этого в Ржавчине есть ключевое слово `extern`, которое облегчает создание и использование *внешней оболочки внешних функций (Foreign Function Interface - FFI)*. FFI - это способ для языка программирования определить функции и позволить другому (внешнему) языку программирования вызывать эти функции. -Приложение 19-8 отображает, как настроить встраивание с функцией `abs` из встроенной библиотеки C. Функции, объявленные внутри разделов `extern`, всегда небезопасны для вызова из кода Rust. Причина в том, что другие языки не обеспечивают соблюдение правил и заверений Rust, Ржавчина также не может проверить заверения, поэтому ответственность за безопасность ложится на программиста. +Приложение 19-8 отображает, как настроить встраивание с функцией `abs` из встроенной библиотеки C. Функции, объявленные внутри разделов `extern`, всегда небезопасны для вызова из рукописи Ржавчины. Причина в том, что другие языки не обеспечивают соблюдение правил и заверений языка Ржавчины, Ржавчина также не может проверить заверения, поэтому ответственность за безопасность ложится на программиста. Имя файла: src/main.rs @@ -167,9 +167,9 @@ > #### Вызов функций Ржавчина из других языков > -> Также можно использовать `extern` для создания внешней оболочки, позволяющего другим языкам вызывать функции Rust. Вместо того чтобы создавать целый раздел`extern`, мы добавляем ключевое слово `extern` и указываем ABI для использования непосредственно перед ключевым словом `fn` для необходимой функции. Нам также нужно добавить изложение `#[no_mangle]`, чтобы сказать сборщику Ржавчина не искажать имя этой функции. *Искажение* - это когда сборщик меняет имя, которое мы дали функции, на другое имя, которое содержит больше сведений для других частей этапа сборки, но менее читабельно для человека. Сборщик каждого языка программирования искажает имена по-разному, поэтому, чтобы функция Ржавчина могла быть использована другими языками, мы должны отключить искажение имён в сборщике Rust. +> Также можно использовать `extern` для создания внешней оболочки, позволяющего другим языкам вызывать функции Ржавчины. Вместо того чтобы создавать целый раздел`extern`, мы добавляем ключевое слово `extern` и указываем ABI для использования непосредственно перед ключевым словом `fn` для необходимой функции. Нам также нужно добавить изложение `#[no_mangle]`, чтобы сказать сборщику Ржавчина не искажать имя этой функции. *Искажение* - это когда сборщик меняет имя, которое мы дали функции, на другое имя, которое содержит больше сведений для других частей этапа сборки, но менее удобно для чтения для человека. Сборщик каждого языка программирования искажает имена по-разному, поэтому, чтобы функция Ржавчина могла быть использована другими языками, мы должны отключить искажение имён в сборщике Ржавчины. > -> В следующем примере мы делаем функцию `call_from_c` доступной из кода на C, после того как она будет собрана в разделяемую библиотеку и прилинкована с C: +> В следующем примере мы делаем функцию `call_from_c` доступной из рукописи на C, после того как она будет собрана в разделяемую библиотеку и прилинкована с C: > > ```rust > #[no_mangle] @@ -182,9 +182,9 @@ ### Получение доступа и внесение изменений в изменяемую постоянную переменную -В этой книге мы ещё не говорили о *вездесущих переменных*, которые Ржавчина поддерживает, но с которыми могут возникнуть сбоев из-за действующих в Ржавчина правил владения. Если два потока обращаются к одной и той же изменяемой вездесущей переменной, это может привести к гонке данных. +В этой книге мы ещё не говорили о *вездесущих переменных*, которые Ржавчина поддерживает, но с которыми могут возникнуть сбоев из-за действующих в Ржавчине правил владения. Если два потока обращаются к одной и той же изменяемой вездесущей переменной, это может привести к гонке данных. -Вездесущие переменные в Ржавчина называют *постоянными* (static). Приложение 19-9 отображает пример объявления и использования в качестве значения постоянной переменной, имеющей вид строкового среза: +Вездесущие переменные в Ржавчине называют *постоянными* (static). Приложение 19-9 отображает пример объявления и использования в качестве значения постоянной переменной, имеющей вид строкового среза: Имя файла: src/main.rs @@ -206,7 +206,7 @@ Приложение 19-10: Чтение из изменяемой постоянной переменной или запись в неё небезопасны -Как и с обычными переменными, мы определяем изменяемость с помощью ключевого слова `mut`. Любой код, который читает из или пишет в переменную `COUNTER` должен находиться в `unsafe` разделе. Этот код собирается и печатает `COUNTER: 3`, как и следовало ожидать, потому что выполняется в одном потоке. Наличие нескольких потоков с доступом к `COUNTER` приведёт к случаи гонки данных. +Как и с обычными переменными, мы определяем изменяемость с помощью ключевого слова `mut`. Любой рукопись, который читает из или пишет в переменную `COUNTER` должен находиться в `unsafe` разделе. Этот рукопись собирается и печатает `COUNTER: 3`, как и следовало ожидать, потому что выполняется в одном потоке. Наличие нескольких потоков с доступом к `COUNTER` приведёт к случаи гонки данных. Наличие изменяемых данных, которые доступны вездесуще, делает трудным выполнение заверения отсутствия гонок данных, поэтому Ржавчина считает изменяемые постоянные переменные небезопасными. Там, где это возможно, предпочтительно использовать техники многопоточности и умные указатели, направленные на многопоточное исполнение, которые мы обсуждали в главе 16. Таким образом, сборщик сможет проверить, что обращение к данным, доступным из разных потоков, выполняется безопасно. @@ -222,15 +222,15 @@ Используя `unsafe impl`, мы даём обещание поддерживать неизменные величины, которые сборщик не может проверить. -Для примера вспомним маркерные особенности `Sync` и `Send`, которые мы обсуждали в разделе "Расширяемый одновременность с помощью особенностей `Sync` и `Send`" главы 16: сборщик выполняет эти особенности самостоятельно , если наши виды полностью состоят из видов `Send` и `Sync`. Если мы создадим вид, который содержит вид, не являющийся `Send` или `Sync`, такой, как сырой указатель, и мы хотим пометить этот вид как `Send` или `Sync`, мы должны использовать `unsafe` блок. Ржавчина не может проверить, что наш вид поддерживает заверения того, что он может быть безопасно передан между потоками или доступен из нескольких потоков; поэтому нам нужно добавить эти проверки вручную и указать это с помощью `unsafe`. +Для примера вспомним маркерные особенности `Sync` и `Send`, которые мы обсуждали в разделе "Расширяемый одновременность с помощью особенностей `Sync` и `Send`" главы 16: сборщик выполняет эти особенности самостоятельно , если наши виды полностью состоят из видов `Send` и `Sync`. Если мы создадим вид, который содержит вид, не являющийся `Send` или `Sync`, такой, как сырой указатель, и мы хотим пометить этот вид как `Send` или `Sync`, мы должны использовать `unsafe` раздел. Ржавчина не может проверить, что наш вид поддерживает заверения того, что он может быть безопасно передан между потоками или доступен из нескольких потоков; поэтому нам нужно добавить эти проверки вручную и указать это с помощью `unsafe`. ### Доступ к полям объединений (union) -Последнее действие, которое работает только с `unsafe` - это доступ к полям *union*. `union` похож на `struct`, но в каждом определенном образце одновременно может использоваться только одно объявленное поле. Объединения в основном используются для взаимодействия с объединениями в коде на языке Си. Доступ к полям объединений небезопасен, поскольку Ржавчина не может обязательно определить вид данных, которые в данный мгновение хранятся в образце объединения. Подробнее об объединениях вы можете узнать в [the Ржавчина Reference]. +Последнее действие, которое работает только с `unsafe` - это доступ к полям *union*. `union` похож на `struct`, но в каждом определенном образце одновременно может использоваться только одно объявленное поле. Объединения в основном используются для взаимодействия с объединениями в рукописи на языке Си. Доступ к полям объединений небезопасен, поскольку Ржавчина не может обязательно определить вид данных, которые в данный мгновение хранятся в образце объединения. Подробнее об объединениях вы можете узнать в [the Ржавчина Reference]. ### Когда использовать небезопасный код -Использование `unsafe` для выполнения одного из пяти действий (супер способностей), которые только что обсуждались, не является ошибочным или не одобренным. Но получить правильный `unsafe` код сложнее, потому что сборщик не может помочь в обеспечении безопасности памяти. Если у вас есть причина использовать `unsafe` код, вы можете делать это, а наличие явной `unsafe` изложении облегчает отслеживание источника неполадок. если они возникают. +Использование `unsafe` для выполнения одного из пяти действий (супер способностей), которые только что обсуждались, не является ошибочным или не одобренным. Но получить правильный `unsafe` рукопись сложнее, потому что сборщик не может помочь в обеспечении безопасности памяти. Если у вас есть причина использовать `unsafe` рукопись, вы можете делать это, а наличие явной `unsafe` изложении облегчает отслеживание источника неполадок. если они возникают. ["Вид срез"]: ch04-03-slices.html#the-slice-type diff --git a/rustbook-ru/src/ch19-02-advanced-lifetimes.md b/rustbook-ru/src/ch19-02-advanced-lifetimes.md index 63bf35c97..bde3ff080 100644 --- a/rustbook-ru/src/ch19-02-advanced-lifetimes.md +++ b/rustbook-ru/src/ch19-02-advanced-lifetimes.md @@ -6,11 +6,11 @@ ### Подвиды -Представьте, что мы хотим выполнить текстовый анализатор (parser). Для этого +Представьте, что мы хотим выполнить текстовый оценщик (parser). Для этого необходимо создать устройство, образцы которой будут хранить ссылки на строку, которую -мы анализируем. Назовём эту устройство `Context`. Мы создадим анализатор, который -будет анализировать эту строку и возвращать индикатор успеха или неудачи. Анализатору -необходимо заимствовать строку для анализа. Выполнение может быть похожа на код +мы рассмотриваем. Назовём эту устройство `Context`. Мы создадим оценщик, который +будет рассмотривать эту строку и возвращать показатель успеха или неудачи. Оценщику +необходимо заимствовать строку для оценки. Выполнение может быть похожа на код 19-12, который не собирается, т.к. мы не указали МВЖ: ```rust,ignore @@ -27,7 +27,7 @@ impl Parser { } ``` -код 19-12: определение устройства `Context`, которая содержит +рукопись 19-12: определение устройства `Context`, которая содержит строковый срез. Устройства `Parser` содержит ссылку на образец `Context`. Способ `parse` всегда возвращает ошибку со ссылкой на строковый срез @@ -52,10 +52,10 @@ impl<'a> Parser<'a> { } ``` -код 19-13: определение ссылок в `Context` и `Parser` +рукопись 19-13: определение ссылок в `Context` и `Parser` -Этот код собирается. Следующий код (19-14) декларирует функцию, которая получает -входной свойство `Context` и используя `Parser` для анализа текста. +Этот рукопись собирается. Следующий рукопись (19-14) декларирует функцию, которая получает +входной свойство `Context` и используя `Parser` для оценки текста. ```rust,ignore fn parse_context(context: Context) -> Result<(), &str> { @@ -154,7 +154,7 @@ fn parse_context(context: Context) -> Result<(), &str> { } ``` -код 19-15: определение различных переменных времени жизни +рукопись 19-15: определение различных переменных времени жизни We’ve annotated the lifetimes of the references in all the same places that we @@ -188,14 +188,14 @@ note: but the referenced data is only valid for the lifetime 's as defined on th | |_^ ``` -Rust не знает как связаны между собой `'c` и `'s`. Для того, чтобы +Ржавчина не знает как связаны между собой `'c` и `'s`. Для того, чтобы обеспечить, что ссылочные данные в `Context` со временем жизни `'s` допустимы, необходимо установить ограничение, чтобы сборщик знал, что они живут дольше, чем ссылка на `Context` со временем жизни `'c`. Если время жизни `'s` не длиннее `'c`, то ссылка на `Context` может быть не правильна. -Что приводит нас к основной части этого раздела: В Ржавчина есть рычаг под названием *lifetime subtyping*, +Что приводит нас к основной части этого раздела: В Ржавчине есть рычаг под названием *lifetime subtyping*, который является способом, чтобы указать, что один свойство времени жизни жив по крайней мере, пока жив другой свойство. В угловых скобках, где мы объявляем свойства времени жизни, мы можем объявить продолжительность жизни `'a` как обычно, и объявить свойство времени жизни @@ -227,7 +227,7 @@ struct Parser<'c, 's: 'c> { struct Ref<'a, T>(&'a T); ``` -код 19-16: определение устройства-оболочки для ссылки на обобщенный +рукопись 19-16: определение устройства-оболочки для ссылки на обобщенный вид без переменной времени жизни Без связи обобщенного свойства и переменной времени жизни мы получим ошибку, т.к. @@ -251,22 +251,22 @@ note: ...so that the reference type `&'a T` does not outlive the data it points Т.к. `T` может быть любым видом, `T` сам может быть ссылкой или видом содержащим ссылки. Поэтому сборщик не может определить время жизни `T`. -Для решения этой задачи в Ржавчина есть подсказка: +Для решения этой задачи в Ржавчине есть подсказка: ```text consider adding an explicit lifetime bound `T: 'a` so that the reference type `&'a T` does not outlive the data it points at. ``` -Код 19-17 отображает выполнение данного совета: +Рукопись 19-17 отображает выполнение данного совета: ```rust struct Ref<'a, T: 'a>(&'a T); ``` -код 19-17: добавления ограничения времени жизни для `T` +рукопись 19-17: добавления ограничения времени жизни для `T` -Мы можем решить эту задачу и другим способом. В коде 19-18 отображена +Мы можем решить эту задачу и другим способом. В рукописи 19-18 отображена работа со постоянными переменными. Это означает, что если `T` содержит какую-либо ссылку, она должна иметь `'static` время жизни: @@ -274,7 +274,7 @@ struct Ref<'a, T: 'a>(&'a T); struct StaticRef(&'static T); ``` -код 19-18: добавление `'static` время жизни для `T` для +рукопись 19-18: добавление `'static` время жизни для `T` для введения ограничения `T` Виды без каких-либо ссылок считаются видами со постоянном временем жизни `T: 'static`. @@ -288,7 +288,7 @@ struct StaticRef(&'static T); В главе 17 вы изучали предметы-особенности. Они применяются при изменяемой управления. Но мы ещё не обсуждали случай использования переменных времени жизни в таких -устройствох. Рассмотрим такой пример. В коде 19-19 у нас есть особенность `Foo` и устройства +устройствох. Рассмотрим такой пример. В рукописи 19-19 у нас есть особенность `Foo` и устройства `Bar`, которая содержит ссылку (и, следовательно, имеет переменную времени жизни): ```rust @@ -305,10 +305,10 @@ let num = 5; let obj = Box::new(Bar { x: &num }) as Box; ``` -код 19-19: использование вида, который имеет переменную времени +рукопись 19-19: использование вида, который имеет переменную времени жизни -Этот код собирается без ошибок. Это происходит потому, что существуют правила +Этот рукопись собирается без ошибок. Это происходит потому, что существуют правила между особенностями предметов и переменными времени жизни: * по умолчанию ПВЖ для особенностей-предметов `'static`. diff --git a/rustbook-ru/src/ch19-03-advanced-traits.md b/rustbook-ru/src/ch19-03-advanced-traits.md index ee66f9d82..41911aa20 100644 --- a/rustbook-ru/src/ch19-03-advanced-traits.md +++ b/rustbook-ru/src/ch19-03-advanced-traits.md @@ -1,14 +1,14 @@ ## Продвинутые особенности -Мы познакомились с особенностями в разделе ["Особенности: Определение общего поведения"](ch10-02-traits.html#traits-defining-shared-behavior) в главе 10, но там мы не обсуждали более сложные подробности. Теперь, когда вы больше знаете о Rust, мы можем перейти к более подробному рассмотрению. +Мы познакомились с особенностями в разделе ["Особенности: Определение общего поведения"](ch10-02-traits.html#traits-defining-shared-behavior) в главе 10, но там мы не обсуждали более сложные подробности. Теперь, когда вы больше знаете о Ржавчина мы можем перейти к более подробному рассмотрению. ### Указание видов-заполнителей в определениях особенностей с сопряженными видами -*Сопряженные виды* связывают вид-заполнитель с особенностью таким образом, что определения способов особенности могут использовать эти виды-заполнители в своих ярлыках. Для именно выполнения особенности вместо типа-заполнителя указывается определенный вид, который будет использоваться. Таким образом, мы можем определить особенности, использующие некоторые виды, без необходимости точно знать, что это за виды, пока особенности не будут выполнены. +*Сопряженные виды* связывают вид-заполнитель с особенностью таким образом, что определения способов особенности могут использовать эти виды-заполнители в своих ярлыках. Для именно выполнения особенности вместо вида-заполнителя указывается определенный вид, который будет использоваться. Таким образом, мы можем определить особенности, использующие некоторые виды, без необходимости точно знать, что это за виды, пока особенности не будут выполнены. Мы назвали большинство продвинутых возможностей в этой главе редко востребованными. Сопряженные виды находятся где-то посередине: они используются реже чем возможности описанные в остальной части книги, но чаще чем многие другие возможности обсуждаемые в этой главе. -Одним из примеров особенности с сопряженным видом является особенность `Iterator` из встроенной библиотеки. Сопряженный вид называется `Item` и символизирует вид значений, по которым повторяется вид, выполняющий особенность `Iterator`. Определение особенности Iterator показано в приложении 19-12. +Одним из примеров особенности с сопряженным видом является особенность `Iterator` из встроенной библиотеки. Сопряженный вид называется `Item` и представляет вид значений, по которым повторяется вид, выполняющий особенность `Iterator`. Определение особенности Iterator показано в приложении 19-12. ```rust,noplayground {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-12/src/lib.rs}} @@ -38,15 +38,15 @@ С сопряженными видами не нужно определять виды, потому что мы не можем выполнить особенность у вида несколько раз. В приложении 19-12 с определением, использующим сопряженные виды можно выбрать только один вид `Item`, потому что может быть только одно объявление `impl Iterator for Counter`. Нам не нужно указывать, что нужен повторитель значений вида `u32` везде, где мы вызываем `next` у `Counter`. -Сопряженные виды также становятся частью договора особенности: разработчики особенности должны предоставить вид, который заменит сопряженный заполнитель вида. Связанные виды часто имеют имя, описывающее то, как будет использоваться вид, и хорошей опытом является документирование связанного вида в документации по API. +Сопряженные виды также становятся частью договора особенности: разработчики особенности должны предоставить вид, который заменит сопряженный заполнитель вида. Связанные виды часто имеют имя, описывающее то, как будет использоваться вид, и хорошей опытом является документирование связанного вида в пособия по API. -### Свойства обобщённого вида по умолчанию и перегрузка операторов +### Свойства обобщённого вида по умолчанию и перегрузка приказчиков Когда мы используем свойства обобщённого вида, мы можем указать определенный вид по умолчанию для обобщённого вида. Это устраняет необходимость разработчикам указывать определенный вид, если работает вид по умолчанию. Вид по умолчанию указывается при объявлении обобщённого вида с помощью правил написания ``. -Отличным примером, когда этот способ полезен, является *перегрузка оператора* (operator overloading), когда вы настраиваете поведение оператора (например, `+` ) для определённых случаев. +Отличным примером, когда этот способ полезен, является *перегрузка приказчика* (operator overloading), когда вы настраиваете поведение приказчика (например, `+` ) для определённых случаев. -Rust не позволяет создавать собственные операторы или перегружать произвольные операторы. Но можно перегрузить перечисленные действия и соответствующие им особенности из `std::ops` путём выполнения особенностей, связанных с этими операторами. Например, в приложении 19-14 мы перегружаем оператор `+`, чтобы складывать два образца `Point`. Мы делаем это выполняя особенность `Add` для устройства `Point`: +Ржавчина не позволяет создавать собственные приказчики или перегружать произвольные приказчики. Но можно перегрузить перечисленные действия и соответствующие им особенности из `std::ops` путём выполнения особенностей, связанных с этими приказчиками. Например, в приложении 19-14 мы перегружаем приказчик `+`, чтобы складывать два образца `Point`. Мы делаем это выполняя особенность `Add` для устройства `Point`: Файл: src/main.rs @@ -54,11 +54,11 @@ Rust не позволяет создавать собственные опер {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-14/src/main.rs}} ``` -Приложение 19-14: Выполнение особенности Add для перегрузки оператора + для образцов Point +Приложение 19-14: Выполнение особенности Add для перегрузки приказчика + для образцов Point Способ `add` складывает значения `x` двух образцов `Point` и значения `y` у `Point` для создания нового образца `Point`. Особенность `Add` имеет сопряженный вид с именем `Output`, который определяет вид, возвращаемый из способа `add`. -Обобщённый вид по умолчанию в этом коде находится в особенности `Add` . Вот его определение: +Обобщённый вид по умолчанию в этом рукописи находится в особенности `Add` . Вот его определение: ```rust trait Add { @@ -68,7 +68,7 @@ trait Add { } ``` -Этот код должен выглядеть знакомым: особенность с одним способом и сопряженным видом. Новый правила написания это `RHS=Self`. Такой правила написания называется *свойства вида по умолчанию* (default type parameters). Свойство обобщённого вида `RHS` (сокращённо “right hand side”) определяет вид свойства `rhs` в способе `add`. Если мы не укажем определенный вид для `RHS` при выполнения особенности `Add`, то видом для `RHS` по умолчанию будет `Self`, который будет видом для которого выполняется особенность `Add`. +Этот рукопись должен выглядеть знакомым: особенность с одним способом и сопряженным видом. Новый правила написания это `RHS=Self`. Такой правила написания называется *свойства вида по умолчанию* (default type parameters). Свойство обобщённого вида `RHS` (сокращённо “right hand side”) определяет вид свойства `rhs` в способе `add`. Если мы не укажем определенный вид для `RHS` при выполнения особенности `Add`, то видом для `RHS` по умолчанию будет `Self`, который будет видом для которого выполняется особенность `Add`. Когда мы выполнили `Add` для устройства `Point`, мы использовали обычное значение для `RHS`, потому что хотели сложить два образца `Point`. Давайте посмотрим на пример выполнения особенности `Add`, где мы хотим пользовательский вид `RHS` вместо использования вида по умолчанию. @@ -89,15 +89,15 @@ trait Add { - Чтобы расширить вид без внесения изменений ломающих существующий код - Чтобы позволить пользовательское поведение в особых случаях, которые не нужны большинству пользователей -Особенность `Add` из встроенной библиотеки является примером второй цели: обычно вы складываете два одинаковых вида, но особенность `Add` позволяет сделать больше. Использование свойства вида по умолчанию в объявлении особенности `Add` означает, что не нужно указывать дополнительный свойство большую часть времени. Другими словами, большая часть кода выполнения не нужна, что делает использование особенности проще. +Особенность `Add` из встроенной библиотеки является примером второй цели: обычно вы складываете два одинаковых вида, но особенность `Add` позволяет сделать больше. Использование свойства вида по умолчанию в объявлении особенности `Add` означает, что не нужно указывать дополнительный свойство большую часть времени. Другими словами, большая часть рукописи выполнения не нужна, что делает использование особенности проще. -Первая цель похожа на вторую, но используется наоборот: если вы хотите добавить свойство вида к существующему особенности, можно дать ему значение по умолчанию, чтобы разрешить расширение возможности особенности без нарушения кода существующей выполнения. +Первая цель похожа на вторую, но используется наоборот: если вы хотите добавить свойство вида к существующему особенности, можно дать ему значение по умолчанию, чтобы разрешить расширение возможности особенности без нарушения рукописи существующей выполнения. ### Полностью квалифицированный правила написания для устранения неоднозначности: вызов способов с одинаковым именем -В Ржавчина ничего не мешает особенности иметь способ с одинаковым именем, таким же как способ другого особенности и Ржавчина не мешает выполнить оба таких особенности у одного вида. Также возможно выполнить способ с таким же именем непосредственно у вида, такой как и способы у особенностей. +В Ржавчине ничего не мешает особенности иметь способ с одинаковым именем, таким же как способ другого особенности и Ржавчина не мешает выполнить оба таких особенности у одного вида. Также возможно выполнить способ с таким же именем непосредственно у вида, такой как и способы у особенностей. -При вызове способов с одинаковыми именами в Ржавчина нужно указать, какой из трёх возможных вы хотите использовать. Рассмотрим код в приложении 19-16, где мы определили два особенности: `Pilot` и `Wizard`, у обоих есть способ `fly`. Затем мы выполняем оба особенности у вида `Human` в котором уже выполнен способ с именем `fly`. Каждый способ `fly` делает что-то своё. +При вызове способов с одинаковыми именами в Ржавчине нужно указать, какой из трёх возможных вы хотите использовать. Рассмотрим рукопись в приложении 19-16, где мы определили два особенности: `Pilot` и `Wizard`, у обоих есть способ `fly`. Затем мы выполняем оба особенности у вида `Human` в котором уже выполнен способ с именем `fly`. Каждый способ `fly` делает что-то своё. Файл: src/main.rs @@ -117,7 +117,7 @@ trait Add { Приложение 19-17: Вызов fly у образца Human -Запуск этого кода напечатает `*waving arms furiously*` , показывая, что Ржавчина называется способ `fly` выполненный непосредственно у `Human`. +Запуск этого рукописи напечатает `*waving arms furiously*` , показывая, что Ржавчина называется способ `fly` выполненный непосредственно у `Human`. Чтобы вызвать способы `fly` у особенности `Pilot` или особенности `Wizard` нужно использовать более явный правила написания, указывая какой способ `fly` мы имеем в виду. Приложение 19-18 отображает такой правила написания. @@ -129,9 +129,9 @@ trait Add { Приложение 19-18: Указание какой способа fly мы хотим вызвать -Указание имени особенности перед именем способа проясняет сборщику Rust, какую именно выполнение `fly` мы хотим вызвать. Мы могли бы также написать `Human::fly(&person)`, что эквивалентно используемому нами `person.fly()` в приложении 19-18, но это писание немного длиннее, когда нужна неоднозначность. +Указание имени особенности перед именем способа проясняет сборщику Ржавчина, какую именно выполнение `fly` мы хотим вызвать. Мы могли бы также написать `Human::fly(&person)`, что эквивалентно используемому нами `person.fly()` в приложении 19-18, но это писание немного длиннее, когда нужна неоднозначность. -Выполнение этого кода выводит следующее: +Выполнение этого рукописи выводит следующее: ```console {{#include ../listings/ch19-advanced-features/listing-19-18/output.txt}} @@ -149,15 +149,15 @@ trait Add { Приложение 19-19: Особенность с сопряженной функцией и вид с сопряженной функцией с тем же именем, которая тоже выполняет особенность -Мы выполнили код для приюта для животных, который хочет назвать всех щенков именем Spot, в сопряженной функции `baby_name`, которая определена для `Dog`. Вид `Dog` также выполняет особенность `Animal`, который описывает свойства, которые есть у всех животных. Маленьких собак называют щенками, и это выражается в выполнения `Animal` у `Dog` в функции `baby_name` сопряженной с особенностью `Animal`. +Мы выполнили рукопись для приюта для животных, который хочет назвать всех щенков именем Spot, в сопряженной функции `baby_name`, которая определена для `Dog`. Вид `Dog` также выполняет особенность `Animal`, который описывает свойства, которые есть у всех животных. Маленьких собак называют щенками, и это выражается в выполнения `Animal` у `Dog` в функции `baby_name` сопряженной с особенностью `Animal`. -В `main` мы вызываем функцию `Dog::baby_name`, которая вызывает сопряженную функцию определённую напрямую у `Dog`. Этот код печатает следующее: +В `main` мы вызываем функцию `Dog::baby_name`, которая вызывает сопряженную функцию определённую напрямую у `Dog`. Этот рукопись печатает следующее: ```console {{#include ../listings/ch19-advanced-features/listing-19-19/output.txt}} ``` -Этот вывод не является тем, что мы хотели бы получить. Мы хотим вызвать функцию `baby_name`, которая является частью особенности `Animal` выполненного у `Dog`, так чтобы код печатал `A baby dog is called a puppy`. Техника указания имени особенности использованная в приложении 19-18 здесь не помогает; если мы изменим `main` код как в приложении 19-20, мы получим ошибку сборки. +Этот вывод не является тем, что мы хотели бы получить. Мы хотим вызвать функцию `baby_name`, которая является частью особенности `Animal` выполненного у `Dog`, так чтобы рукопись печатал `A baby dog is called a puppy`. Техника указания имени особенности использованная в приложении 19-18 здесь не помогает; если мы изменим `main` рукопись как в приложении 19-20, мы получим ошибку сборки. Файл: src/main.rs @@ -173,7 +173,7 @@ trait Add { {{#include ../listings/ch19-advanced-features/listing-19-20/output.txt}} ``` -Чтобы устранить неоднозначность и сказать Rust, что мы хотим использовать выполнение `Animal` для `Dog`, нужно использовать полный правила написания. Приложение 19-21 отображает, как использовать полный правила написания. +Чтобы устранить неоднозначность и сказать Ржавчине, что мы хотим использовать выполнение `Animal` для `Dog`, нужно использовать полный правила написания. Приложение 19-21 отображает, как использовать полный правила написания. Файл: src/main.rs @@ -183,7 +183,7 @@ trait Add { Приложение 19-21: Использование полного правил написания для указания, что мы мы хотим вызвать функцию baby_name у особенности Animal выполненную в Dog -Мы указываем изложение вида в угловых скобках, которая указывает на то что мы хотим вызвать способ `baby_name` из особенности `Animal` выполненный в `Dog`, также указывая что мы хотим рассматривать вид `Dog` в качестве `Animal` для вызова этой функции. Этот код теперь напечатает то, что мы хотим: +Мы указываем изложение вида в угловых скобках, которая указывает на то что мы хотим вызвать способ `baby_name` из особенности `Animal` выполненный в `Dog`, также указывая что мы хотим рассматривать вид `Dog` в качестве `Animal` для вызова этой функции. Этот рукопись теперь напечатает то, что мы хотим: ```console {{#include ../listings/ch19-advanced-features/listing-19-21/output.txt}} @@ -249,7 +249,7 @@ trait Add { ### Образец Newtype для выполнение внешних особенностей у внешних видов -В разделе ["Выполнение особенности у типа"](ch10-02-traits.html#implementing-a-trait-on-a-type) главы 10, мы упоминали "правило сироты" (orphan rule), которое гласит, что разрешается выполнить особенность у вида, если либо особенность, либо вид являются местными для нашего ящика. Можно обойти это ограничение, используя *образец нового вида* (newtype pattern), который включает в себя создание нового вида в упорядоченной в ряд устройстве. (Мы рассмотрели упорядоченные в ряд устройства в разделе ["Использование устройств упорядоченных рядов без именованных полей для создания различных видов"] главы 5.) Устройства упорядоченного ряда будет иметь одно поле и будет тонкой оболочкой для вида которому мы хотим выполнить особенность. Тогда вид оболочки является местным для нашего ящика и мы можем выполнить особенность для местной обёртки. *Newtype* это понятие, который происходит от языка программирования Haskell. В нем нет ухудшения производительности времени выполнения при использовании этого образца и вид оболочки исключается во время сборки. +В разделе ["Выполнение особенности у вида"](ch10-02-traits.html#implementing-a-trait-on-a-type) главы 10, мы упоминали "правило сироты" (orphan rule), которое гласит, что разрешается выполнить особенность у вида, если либо особенность, либо вид являются местными для нашего ящика. Можно обойти это ограничение, используя *образец нового вида* (newtype pattern), который включает в себя создание нового вида в упорядоченной в ряд устройстве. (Мы рассмотрели упорядоченные в ряд устройства в разделе ["Использование устройств упорядоченных рядов без именованных полей для создания различных видов"] главы 5.) Устройства упорядоченного ряда будет иметь одно поле и будет тонкой оболочкой для вида которому мы хотим выполнить особенность. Тогда вид оболочки является местным для нашего ящика и мы можем выполнить особенность для местной обёртки. *Newtype* это понятие, который происходит от языка программирования Haskell. В нем нет ухудшения производительности времени выполнения при использовании этого образца и вид оболочки исключается во время сборки. В качестве примера, мы хотим выполнить особенность `Display` для вида `Vec`, где "правило сироты" (orphan rule) не позволяет нам этого делать напрямую, потому что особенность `Display` и вид `Vec` объявлены вне нашего ящика. Мы можем сделать устройство `Wrapper`, которая содержит образец `Vec`; тогда мы можем выполнить `Display` у устройства `Wrapper` и использовать значение `Vec` как показано в приложении 19-23. @@ -265,7 +265,7 @@ trait Add { Недостатком использования этой техники является то, что `Wrapper` является новым видом, поэтому он не имеет способов для значения, которое он держит в себе. Мы должны были бы выполнить все способы для `Vec` непосредственно во `Wrapper`, так чтобы эти способы делегировались внутреннему `self.0`, что позволило бы нам обращаться с `Wrapper` точно так же, как с `Vec`. Если бы мы хотели, чтобы новый вид имел каждый способ имеющийся у внутреннего вида, выполняя особенность `Deref` (обсуждается в разделе "Работа с умными указателями как с обычными ссылками с помощью `Deref` особенности" главы 15) у `Wrapper` для возвращения внутреннего вида, то это было бы решением. Если мы не хотим, чтобы вид `Wrapper` имел все способы внутреннего вида, например, для ограничения поведения вида `Wrapper`, то пришлось бы вручную выполнить только те способы, которые нам нужны. -Этот образец newtype также полезен, даже когда особенности не задействованы. Давайте переключим внимание и рассмотрим некоторые продвинутые способы взаимодействия с системой видов Rust. +Этот образец newtype также полезен, даже когда особенности не задействованы. Давайте переключим внимание и рассмотрим некоторые продвинутые способы взаимодействия с системой видов Ржавчины. ["Образец Newtype для выполнение внешних особенностей у внешних видов"]: ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types diff --git a/rustbook-ru/src/ch19-04-advanced-types.md b/rustbook-ru/src/ch19-04-advanced-types.md index 81d639524..56f771ccd 100644 --- a/rustbook-ru/src/ch19-04-advanced-types.md +++ b/rustbook-ru/src/ch19-04-advanced-types.md @@ -1,6 +1,6 @@ ## Продвинутые виды -Система видов Ржавчина имеет некоторые особенности, о которых мы уже упоминали, но ещё не обсуждали. Мы начнём с общего обзора newtypes, а затем разберёмся, чем они могут пригодиться в качестве видов. Далее мы перейдём к псевдонимам видов - возможности, похожей на newtypes, но с несколько иной смыслом. Мы также обсудим вид `!` и виды с изменяемым размером. +Система видов Ржавчине имеет некоторые особенности, о которых мы уже упоминали, но ещё не обсуждали. Мы начнём с общего обзора newtypes, а затем разберёмся, чем они могут пригодиться в качестве видов. Далее мы перейдём к псевдонимам видов - возможности, похожей на newtypes, но с несколько иной смыслом. Мы также обсудим вид `!` и виды с изменяемым размером. ### Использование образца Newtype для обеспечения безопасности видов и создания абстракций @@ -10,11 +10,11 @@ Мы также можем использовать образец newtype для абстрагирования от некоторых подробностей выполнения вида: новый вид может предоставлять открытый API, который отличается от API скрытого внутри вида. -Newtypes также позволяют скрыть внутреннюю выполнение. Например, мы можем создать вид `People`, который обернёт `HashMap`, хранящий ID человека, связанный с его именем. Код, использующий `People`, будет взаимодействовать только с открытым API, который мы предоставляем, например, способ добавления имени в собрание `People`; этому коду не нужно будет знать, что внутри мы присваиваем `i32` ID именам. Образец newtype - это лёгкий способ достижения инкапсуляции для скрытия подробностей выполнения, который мы обсуждали в разделе ["Инкапсуляция, скрывающая подробности выполнения"](ch17-01-what-is-oo.html#encapsulation-that-hides-implementation-details) главы 17. +Newtypes также позволяют скрыть внутреннюю выполнение. Например, мы можем создать вид `People`, который обернёт `HashMap`, хранящий ID человека, связанный с его именем. Рукопись, использующий `People`, будет взаимодействовать только с открытым API, который мы предоставляем, например, способ добавления имени в собрание `People`; этому рукописи не нужно будет знать, что внутри мы присваиваем `i32` ID именам. Образец newtype - это лёгкий способ достижения инкапсуляции для скрытия подробностей выполнения, который мы обсуждали в разделе ["Инкапсуляция, скрывающая подробности выполнения"](ch17-01-what-is-oo.html#encapsulation-that-hides-implementation-details) главы 17. ### Создание родственных вида с помощью псевдонимов вида -Rust предоставляет возможность объявить *псевдоним вида* чтобы дать существующему виду другое имя. Для этого мы используем ключевое слово `type`. Например, мы можем создать псевдоним вида `Kilometers` для `i32` следующим образом: +Ржавчина предоставляет возможность объявить *псевдоним вида* чтобы дать существующему виду другое имя. Для этого мы используем ключевое слово `type`. Например, мы можем создать псевдоним вида `Kilometers` для `i32` следующим образом: ```rust {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-04-kilometers-alias/src/main.rs:here}} @@ -34,7 +34,7 @@ Rust предоставляет возможность объявить *псе Box ``` -Написание таких длинных видов в ярлыках функций и в виде наставлений видов по всему коду может быть утомительным и чреватым ошибками. Представьте себе дело, наполненный таким кодом, как в приложении 19-24. +Написание таких длинных видов в ярлыках функций и в виде наставлений видов по всему рукописи может быть утомительным и чреватым ошибками. Представьте себе дело, наполненный таким рукописью, как в приложении 19-24. ```rust {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-24/src/main.rs:here}} @@ -42,7 +42,7 @@ Box Приложение 19-24: Использование длинного вида во многих местах -Псевдоним вида делает этот код более удобным для работы, сокращая количество повторений. В приложении 19-25 мы ввели псевдоним `Thunk` для вида verbose и можем заменить все использования этого вида более коротким псевдонимом `Thunk`. +Псевдоним вида делает этот рукопись более удобным для работы, сокращая количество повторений. В приложении 19-25 мы ввели псевдоним `Thunk` для вида verbose и можем заменить все использования этого вида более коротким псевдонимом `Thunk`. ```rust {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-25/src/main.rs:here}} @@ -50,7 +50,7 @@ Box Приложение 19-25: Представление псевдонима Thunk для уменьшения количества повторений -Такой код гораздо легче читать и писать! Выбор осмысленного имени для псевдонима вида также может помочь прояснить ваши намерения (*thunk* - название для кода, который будет вычисляться позднее, поэтому это подходящее имя для сохраняемого замыкания). +Такой рукопись гораздо легче читать и писать! Выбор осмысленного имени для псевдонима вида также может помочь прояснить ваши намерения (*thunk* - название для рукописи, который будет вычисляться позднее, поэтому это подходящее имя для сохраняемого замыкания). Псевдонимы видов также часто используются с видом `Result` для сокращения повторений. Рассмотрим звено `std::io` в встроенной библиотеке. Действия ввода-вывода часто возвращают `Result` для обработки случаев, когда эти действия не удаются. В данной библиотеке есть устройства `std::io::Error`, которая отражает все возможные ошибки ввода/вывода. Многие функции в `std::io` будут возвращать `Result`, где `E` - это `std::io::Error`, например, эти функции в особенности `Write`: @@ -70,19 +70,19 @@ Box {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-06-result-alias/src/lib.rs:there}} ``` -Псевдоним вида помогает двумя способами: он облегчает написание кода *и* даёт нам согласованный внешняя оболочка для всего из `std::io`. Поскольку это псевдоним, то это просто ещё один вид `Result`, что означает, что с ним мы можем использовать любые способы, которые работают с `Result`, а также особый правила написания вроде `?` оператора. +Псевдоним вида помогает двумя способами: он облегчает написание рукописи *и* даёт нам согласованный внешняя оболочка для всего из `std::io`. Поскольку это псевдоним, то это просто ещё один вид `Result`, что означает, что с ним мы можем использовать любые способы, которые работают с `Result`, а также особый правила написания вроде `?` приказчика. ### Вид Never, который никогда не возвращается -В Ржавчина есть особый вид `!`, который на жаргоне теории видов известен как *empty type* (пустой вид), потому что он не содержит никаких значений. Мы предпочитаем называть его *never type* (никакой вид), потому что он используется в качестве возвращаемого вида, когда функция ничего не возвращает. Вот пример: +В Ржавчине есть особый вид `!`, который на жаргоне теории видов известен как *empty type* (пустой вид), потому что он не содержит никаких значений. Мы предпочитаем называть его *never type* (никакой вид), потому что он используется в качестве возвращаемого вида, когда функция ничего не возвращает. Вот пример: ```rust,noplayground {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-07-never-type/src/lib.rs:here}} ``` -Этот код читается как "функция `bar` ничего не возвращает". Функции, которые ничего не возвращают, называются *рассеивающими функциями* (diverging functions). Мы не можем производить значения вида `!`, поэтому `bar` никогда ничего не вернёт. +Этот рукопись читается как "функция `bar` ничего не возвращает". Функции, которые ничего не возвращают, называются *рассеивающими функциями* (diverging functions). Мы не можем производить значения вида `!`, поэтому `bar` никогда ничего не вернёт. -Но для чего нужен вид, для которого вы никогда не сможете создать значения? Напомним код из приложения 2-5, отрывка "игры в загадки"; мы воспроизвели его часть здесь в приложении 19-26. +Но для чего нужен вид, для которого вы никогда не сможете создать значения? Напомним рукопись из приложения 2-5, отрывка "игры в загадки"; мы воспроизвели его часть здесь в приложении 19-26. ```rust,ignore {{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-05/src/main.rs:ch19}} @@ -90,25 +90,25 @@ Box Приложение 19-26: Сопоставление match с веткой, которая заканчивается continue -В то время мы опуисполнения некоторые подробности в этом коде. В главе 6 раздела ["Оператор управления потоком `match`"](ch06-02-match.html#the-match-control-flow-operator) мы обсуждали, что все ветви `match` должны возвращать одинаковый вид. Например, следующий код не работает: +В то время мы опуисполнения некоторые подробности в этом рукописи. В главе 6 раздела ["Приказчик управления потоком `match`"](ch06-02-match.html#the-match-control-flow-operator) мы обсуждали, что все ветви `match` должны возвращать одинаковый вид. Например, следующий рукопись не работает: ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-08-match-arms-different-types/src/main.rs:here}} ``` -Вид `guess` в этом коде должен быть целым *и* строкой, а Ржавчина требует, чтобы `guess` имел только один вид. Так что же возвращает `continue`? Как нам позволили вернуть `u32` из одной ветви и при этом иметь другую ветвь, которая оканчивается `continue` в приложении 19-26? +Вид `guess` в этом рукописи должен быть целым *и* строкой, а Ржавчина требует, чтобы `guess` имел только один вид. Так что же возвращает `continue`? Как нам позволили вернуть `u32` из одной ветви и при этом иметь другую ветвь, которая оканчивается `continue` в приложении 19-26? Как вы уже возможно догадались, `continue` имеет значение `!`. То есть, когда Ржавчина вычисляет вид `guess`, он смотрит на обе сопоставляемые ветки, первая со значением `u32` и последняя со значением `!`. Так как `!` никогда не может иметь значение, то Ржавчина решает что видом `guess` является вид `u32`. -Условный подход к описанию такого поведения заключается в том, что выражения вида `!` могут быть преобразованы в любой другой вид. Нам позволяется завершить этот `match` с помощью `continue`, потому что `continue` не возвращает никакого значения; вместо этого он передаёт управление обратно в начало цикла, поэтому в случае `Err` мы никогда не присваиваем значение `guess`. +Условный подход к описанию такого поведения заключается в том, что выражения вида `!` могут быть преобразованы в любой другой вид. Нам позволяется завершить этот `match` с помощью `continue`, потому что `continue` не возвращает никакого значения; вместо этого он передаёт управление обратно в начало круговорота, поэтому в случае `Err` мы никогда не присваиваем значение `guess`. -Вид never полезен также для макроса `panic!`. Вспомните функцию `unwrap`, которую мы вызываем для значений `Option`, чтобы создать значение или вызвать панику с этим определением: +Вид never полезен также для макроса `panic!`. Вспомните функцию `unwrap`, которую мы вызываем для значений `Option`, чтобы создать значение или вызвать сбой с этим определением: ```rust,ignore {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-09-unwrap-definition/src/lib.rs:here}} ``` -В этом коде происходит то же самое, что и в `match` в приложении 19-26: Ржавчина видит, что `val` имеет вид `T`, а `panic!` имеет вид `!`, поэтому итогом общего выражения `match` является `T`. Этот код работает, потому что `panic!` не производит никакого значения; он завершает программу. В случае `None` мы не будем возвращать значение из `unwrap`, поэтому этот код работает. +В этом рукописи происходит то же самое, что и в `match` в приложении 19-26: Ржавчина видит, что `val` имеет вид `T`, а `panic!` имеет вид `!`, поэтому итогом общего выражения `match` является `T`. Этот рукопись работает, потому что `panic!` не производит никакого значения; он завершает программу. В случае `None` мы не будем возвращать значение из `unwrap`, поэтому этот рукопись работает. Последнее выражение, которое имеет вид `!` это `loop`: @@ -116,25 +116,25 @@ Box {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-10-loop-returns-never/src/main.rs:here}} ``` -В данном случае цикл никогда не завершится, поэтому `!` является значением выражения. Но это не будет так, если мы добавим `break`, так как цикл завершит свою работу, когда дойдёт до `break`. +В данном случае круговорот никогда не завершится, поэтому `!` является значением выражения. Но это не будет так, если мы добавим `break`, так как круговорот завершит свою работу, когда дойдёт до `break`. ### Виды с изменяемым размером и особенность `Sized` -Rust необходимо знать некоторые подробности о видах, например, сколько места нужно выделить для значения определённого вида. Из-за этого один из особенностей системы видов поначалу вызывает некоторое недоумение: подход *видов с изменяемым размером*. Иногда называемые *DST* или *безразмерные виды*, эти виды позволяют нам писать код, используя значения, размер которых мы можем узнать только во время выполнения. +Ржавчина необходимо знать некоторые подробности о видах, например, сколько места нужно выделить для значения определённого вида. Из-за этого один из особенностей системы видов поначалу вызывает некоторое недоумение: подход *видов с изменяемым размером*. Иногда называемые *DST* или *безразмерные виды*, эти виды позволяют нам писать рукопись, используя значения, размер которых мы можем узнать только во время выполнения. -Давайте углубимся в подробности изменяемого вида `str`, который мы использовали на протяжении всей книги. Все верно, не вида `&str`, а вида `str` самого по себе, который является DST. Мы не можем знать, какой длины строка до особенности времени выполнения, то есть мы не можем создать переменную вида `str` и не можем принять переменная вида `str`. Рассмотрим следующий код, который не работает: +Давайте углубимся в подробности изменяемого вида `str`, который мы использовали на протяжении всей книги. Все верно, не вида `&str`, а вида `str` самого по себе, который является DST. Мы не можем знать, какой длины строка до особенности времени выполнения, то есть мы не можем создать переменную вида `str` и не можем принять переменная вида `str`. Рассмотрим следующий рукопись, который не работает: ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-11-cant-create-str/src/main.rs:here}} ``` -Rust должен знать, сколько памяти выделить для любого значения определенного вида и все значения вида должны использовать одинаковый размер памяти. Если Ржавчина позволил бы нам написать такой код, то эти два значения `str` должны были бы занимать одинаковое количество памяти. Но они имеют разную длину: `s1` нужно 12 байтов памяти, а для `s2` нужно 15. Вот почему невозможно создать переменную имеющую вид изменяемого размера. +Ржавчина должен знать, сколько памяти выделить для любого значения определенного вида и все значения вида должны использовать одинаковый размер памяти. Если Ржавчина позволил бы нам написать такой рукопись, то эти два значения `str` должны были бы занимать одинаковое количество памяти. Но они имеют разную длину: `s1` нужно 12 байтов памяти, а для `s2` нужно 15. Вот почему невозможно создать переменную имеющую вид изменяемого размера. -Так что же нам делать? В этом случае вы уже знаете ответ: мы преобразуем виды `s1` и `s2` в `&str`, а не в `str`. Вспомните из раздела ["Строковые срезы"] главы 4, что устройства данных среза просто хранит начальную положение и длину среза. Так, в отличие от `&T`, который содержит только одно значение - адрес памяти, где находится `T`, в `&str` хранятся *два* значения - адрес `str` и его длина. Таким образом, мы можем узнать размер значения `&str` во время сборки: он вдвое больше длины `usize`. То есть, мы всегда знаем размер `&str`, независимо от длины строки, на которую оно ссылается. В целом, именно так в Ржавчина используются виды изменяемого размера: они содержат дополнительный бит метаданных, который хранит размер изменяемой сведений. Золотое правило изменяемых размерных видов заключается в том, что мы всегда должны помещать значения таких видов за каким-либо указателем. +Так что же нам делать? В этом случае вы уже знаете ответ: мы преобразуем виды `s1` и `s2` в `&str`, а не в `str`. Вспомните из раздела ["Строковые срезы"] главы 4, что устройства данных среза просто хранит начальную положение и длину среза. Так, в отличие от `&T`, который содержит только одно значение - адрес памяти, где находится `T`, в `&str` хранятся *два* значения - адрес `str` и его длина. Таким образом, мы можем узнать размер значения `&str` во время сборки: он вдвое больше длины `usize`. То есть, мы всегда знаем размер `&str`, независимо от длины строки, на которую оно ссылается. В целом, именно так в Ржавчине используются виды изменяемого размера: они содержат дополнительный бит метаданных, который хранит размер изменяемой сведений. Золотое правило изменяемых размерных видов заключается в том, что мы всегда должны помещать значения таких видов за каким-либо указателем. Мы можем соединенять `str` со всеми видами указателей: например, `Box` или `Rc`. На самом деле, вы уже видели это раньше, но с другим изменяемым размерным видом: особенностями. Каждый особенность - это изменяемый размерный вид, на который мы можем ссылаться, используя имя особенности. В главе 17 в разделе ["Использование особенность-предметов, допускающих значения разных видов"](ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types) мы упоминали, что для использования особенностей в качестве особенность-предметов мы должны поместить их за указателем, например `&dyn Trait` или `Box` (`Rc` тоже подойдёт). -Для работы с DST Ржавчина использует особенность `Sized` чтобы решить, будет ли размер вида известен на стадии сборки. Этот особенность самостоятельно выполняется для всего, чей размер известен к времени сборки. Кроме того, Ржавчина неявно добавляет ограничение на `Sized` к каждой гибкой функции. То есть, определение гибкой функции, такое как: +Для работы с DST Ржавчина использует особенность `Sized` чтобы решить, будет ли размер вида известен на этапе сборки. Этот особенность самостоятельно выполняется для всего, чей размер известен к времени сборки. Кроме того, Ржавчина неявно добавляет ограничение на `Sized` к каждой гибкой функции. То есть, определение гибкой функции, такое как: ```rust,ignore {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-12-generic-fn-definition/src/lib.rs}} diff --git a/rustbook-ru/src/ch19-05-advanced-functions-and-closures.md b/rustbook-ru/src/ch19-05-advanced-functions-and-closures.md index fa81f63ec..93b086a39 100644 --- a/rustbook-ru/src/ch19-05-advanced-functions-and-closures.md +++ b/rustbook-ru/src/ch19-05-advanced-functions-and-closures.md @@ -16,13 +16,13 @@ Приложение 19-27: Использование вида fn для получения указателя на функцию в качестве переменной -Этот код выводит `Ответ: 12`. Мы указали, что свойство `f` в `do_twice` является `fn`, которая принимает на вход единственный свойство вида `i32` и возвращает `i32`. Затем мы можем вызвать `f` в теле `do_twice`. В `main` мы можем передать имя функции `add_one` в качестве первого переменной в `do_twice`. +Этот рукопись выводит `Ответ: 12`. Мы указали, что свойство `f` в `do_twice` является `fn`, которая принимает на вход единственный свойство вида `i32` и возвращает `i32`. Затем мы можем вызвать `f` в теле `do_twice`. В `main` мы можем передать имя функции `add_one` в качестве первого переменной в `do_twice`. В отличие от замыканий, `fn` является видом, а не особенностью, поэтому мы указываем `fn` непосредственно в качестве вида свойства, а не объявляем свойство гибкого вида с одним из особенностей `Fn` в качестве связанного. Указатели функций выполняют все три особенности замыканий (`Fn`, `FnMut` и `FnOnce`), то есть вы всегда можете передать указатель функции в качестве переменной функции, которая ожидает замыкание. Лучше всего для описания функции использовать гибкий вид и один из особенностей замыканий, чтобы ваши функции могли принимать как функции, так и замыкания. -Однако, одним из примеров, когда вы бы хотели принимать только `fn`, но не замыкания, является взаимодействие с внешним кодом, который не имеет замыканий: функции языка C могут принимать функции в качестве переменных, однако замыканий в языке C нет. +Однако, одним из примеров, когда вы бы хотели принимать только `fn`, но не замыкания, является взаимодействие с внешним рукописью, который не имеет замыканий: функции языка C могут принимать функции в качестве переменных, однако замыканий в языке C нет. В качестве примера того, где можно использовать либо замыкание, определяемое непосредственно в месте передачи, либо именованную функцию, рассмотрим использование способа `map`, предоставляемого особенностью `Iterator` в встроенной библиотеке. Чтобы использовать функцию `map` для преобразования вектора чисел в вектор строк, мы можем использовать замыкание, например, так: @@ -44,13 +44,13 @@ {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-17-map-initializer/src/main.rs:here}} ``` -Здесь мы создаём образцы `Status::Value`, используя каждое значение `u32` в ряде (0..20), с которым вызывается `map` с помощью функции объявителя `Status::Value`. Некоторые люди предпочитают этот исполнение, а некоторые предпочитают использовать замыкания. Оба исхода собирается в один и тот же код, поэтому используйте любой исполнение, который вам понятнее. +Здесь мы создаём образцы `Status::Value`, используя каждое значение `u32` в ряде (0..20), с которым вызывается `map` с помощью функции объявителя `Status::Value`. Некоторые люди предпочитают этот исполнение, а некоторые предпочитают использовать замыкания. Оба исхода собирается в один и тот же рукопись, поэтому используйте любой исполнение, который вам понятнее. ### Возврат замыканий Замыкания представлены особенностями, что означает, что вы не можете возвращать замыкания из функций. В большинстве случаев, когда вам захочется вернуть особенность, вы можете использовать определенный вид, выполняющий этот особенность, в качестве возвращаемого значения функции. Однако вы не можете сделать подобного с замыканиями, поскольку у них не может быть определенного вида, который можно было бы вернуть; например, вы не можете использовать указатель на функцию `fn` в качестве возвращаемого вида. -Следующий код пытается напрямую вернуть замыкание, но он не собирается: +Следующий рукопись пытается напрямую вернуть замыкание, но он не собирается: ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-18-returns-closure/src/lib.rs}} @@ -68,7 +68,7 @@ {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-19-returns-closure-trait-object/src/lib.rs}} ``` -Этот код просто отлично собирается. Для получения дополнительной сведений об особенность-предмета. обратитесь к разделу ["Использование особенность-предметов которые допускают значения разных видов"](ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types) главы 17. +Этот рукопись просто отлично собирается. Для получения дополнительной сведений об особенность-предмета. обратитесь к разделу ["Использование особенность-предметов которые допускают значения разных видов"](ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types) главы 17. Далее давайте посмотрим на макросы! diff --git a/rustbook-ru/src/ch19-06-macros.md b/rustbook-ru/src/ch19-06-macros.md index e7ad4a308..645eda119 100644 --- a/rustbook-ru/src/ch19-06-macros.md +++ b/rustbook-ru/src/ch19-06-macros.md @@ -1,8 +1,8 @@ ## Макросы -Мы использовали макросы, такие как `println!` на протяжении всей этой книги, но мы не изучили полностью, что такое макрос и как он работает. Понятие *макрос* относится к семейству возможностей в Rust. Это *декларативные* (declarative) макросы с помощью `macro_rules!` и три вида *процедурных* (procedural) макросов: +Мы использовали макросы, такие как `println!` на протяжении всей этой книги, но мы не изучили полностью, что такое макрос и как он работает. Понятие *макрос* относится к семейству возможностей в Ржавчине. Это *декларативные* (declarative) макросы с помощью `macro_rules!` и три вида *процедурных* (procedural) макросов: -- Пользовательские (выводимые) `#[derive]` макросы, которые указывают код, добавленный с помощью свойства `derive`, используемые для устройств и перечислений +- Пользовательские (выводимые) `#[derive]` макросы, которые указывают рукопись, добавленный с помощью свойства `derive`, используемые для устройств и перечислений - Макросы подобные свойствам (attribute-like), которые определяют настраиваемые свойства, используемые для любого элемента языка - Похожие на функции (function-like) макросы, которые выглядят как вызовы функций, но работают с TokenStream @@ -10,19 +10,19 @@ ### Разница между макросами и функциями -По сути, макросы являются способом написания кода, который записывает другой код, что известно как *мета программирование*. В Приложении C мы обсуждаем свойство `derive`, который порождает за вас выполнение различных особенностей. Мы также использовали макросы `println!` и `vec!` на протяжении книги. Все эти макросы *раскрываются* для создания большего количества кода, чем исходный код написанный вами вручную. +По сути, макросы являются способом написания рукописи, который записывает другой рукопись, что известно как *мета программирование*. В Приложении C мы обсуждаем свойство `derive`, который порождает за вас выполнение различных особенностей. Мы также использовали макросы `println!` и `vec!` на протяжении книги. Все эти макросы *раскрываются* для создания большего количества рукописи, чем исходную рукопись написанный вами вручную. -Мета программирование полезно для уменьшения объёма кода, который вы должны написать и поддерживать, что также является одним из предназначений функций. Однако макросы имеют некоторые дополнительные возможности, которых функции не имеют. +Мета программирование полезно для уменьшения объёма рукописи, который вы должны написать и поддерживать, что также является одним из предназначений функций. Однако макросы имеют некоторые дополнительные возможности, которых функции не имеют. -Ярлык функции должна объявлять некоторое количество и вид этих свойств имеющихся у функции. Макросы, с другой стороны, могут принимать переменное число свойств: мы можем вызвать `println!("hello")` с одним переменнаяом или `println!("hello {}", name)` с двумя переменнойми. Также макросы раскрываются до того как сборщик преобразует смысл кода, поэтому макрос может, например, выполнить особенность заданного вида. Функция этого не может, потому что она вызывается во время выполнения и особенность должен быть выполнен во время сборки. +Ярлык функции должна объявлять некоторое количество и вид этих свойств имеющихся у функции. Макросы, с другой стороны, могут принимать переменное число свойств: мы можем вызвать `println!("hello")` с одним переменнаяом или `println!("hello {}", name)` с двумя переменнойми. Также макросы раскрываются до того как сборщик преобразует смысл рукописи, поэтому макрос может, например, выполнить особенность заданного вида. Функция этого не может, потому что она вызывается во время выполнения и особенность должен быть выполнен во время сборки. -Обратной стороной выполнения макроса вместо функции является то, что определения макросов являются более сложными, чем определения функций, потому что вы создаёте Ржавчина код, который записывает другой Ржавчина код. Из-за этой косвенности, объявления макросов, как правило, труднее читать, понимать и поддерживать, чем объявления функций. +Обратной стороной выполнения макроса вместо функции является то, что определения макросов являются более сложными, чем определения функций, потому что вы создаёте Ржавчина рукопись, который записывает другой Ржавчина рукопись. Из-за этой косвенности, объявления макросов, как правило, труднее читать, понимать и поддерживать, чем объявления функций. Другое важное различие между макросами и функциями заключается в том, что вы должны объявить макросы или добавить их в область видимости *прежде* чем можете вызывать их в файле, в отличии от функций, которые вы можете объявить где угодно и вызывать из любого места. ### Декларативные макросы с `macro_rules!` для общего мета программирования -Наиболее широко используемой способом макросов в Ржавчина являются *декларативные макросы*. Они также иногда упоминаются как "макросы на примере", "`macro_rules!` макрос" или просто "макросы". По своей сути декларативные макросы позволяют писать нечто похожее на выражение `match` в Rust. Как обсуждалось в главе 6, `match` выражения являются управляющими устройствами, которые принимают некоторое выражение, итог значения выражения сопоставляют с образцами, а затем запускают код для сопоставляемой ветки. Макросы также сравнивают значение с образцами, которые связаны с определенным кодом: в этой случаи значение является записью исходного кода Rust, переданным в макрос. Образцы сравниваются со устройствами этого исходного кода и при совпадении код, связанный с каждым образцом, заменяет код переданный макросу. Все это происходит во время сборки. +Наиболее широко используемой способом макросов в Ржавчине являются *декларативные макросы*. Они также иногда упоминаются как "макросы на примере", "`macro_rules!` макрос" или просто "макросы". По своей сути декларативные макросы позволяют писать нечто похожее на выражение `match` в Ржавчине. Как обсуждалось в главе 6, `match` выражения являются управляющими устройствами, которые принимают некоторое выражение, итог значения выражения сопоставляют с образцами, а затем запускают рукопись для сопоставляемой ветки. Макросы также сравнивают значение с образцами, которые связаны с определенным рукописью: в этой случаи значение является записью исходного рукописи Ржавчина, переданным в макрос. Образцы сравниваются со устройствами этого исходного рукописи и при совпадении рукопись, связанный с каждым образцом, заменяет рукопись переданный макросу. Все это происходит во время сборки. Для определения макроса используется устройство `macro_rules!`. Давайте рассмотрим, как использовать `macro_rules!` глядя на то, как объявлен макрос `vec!`. В главе 8 рассказано, как можно использовать макрос `vec!` для создания нового вектора с определёнными значениями. Например, следующий макрос создаёт новый вектор, содержащий три целых числа: @@ -42,23 +42,23 @@ let v: Vec = vec![1, 2, 3]; Приложение 19-28: Упрощённая исполнение определения макроса vec! -> Примечание: действительное определение макроса `vec!` в встроенной библиотеке содержит код для предварительного выделения правильного объёма памяти. Этот код является переработкой, которую мы здесь не используем, чтобы сделать пример проще. +> Примечание: действительное определение макроса `vec!` в встроенной библиотеке содержит рукопись для предварительного выделения правильного объёма памяти. Этот рукопись является переработкой, которую мы здесь не используем, чтобы сделать пример проще. Изложение `#[macro_export]` указывает, что данный макрос должен быть доступен всякий раз, когда ящик с объявленным макросом, добавлен в область видимости. Без этой изложении макрос нельзя добавить в область видимости. Затем мы начинаем объявление макроса с помощью `macro_rules!` и имени макроса, который объявляется *без* восклицательного знака. Название, в данном случае `vec`, после которого следуют фигурные скобки, указывающие тело определения макроса. -Устройства в теле макроса `vec!` похожа на устройство `match` выражения. Здесь у нас есть одна ветвь с образцом `( $( $x:expr ),* )`, затем следует ветвь `=>` и раздел кода, связанный с этим образцом. Если образец сопоставлен успешно, то соответствующий раздел кода будет создан. Учитывая, что данный код является единственным образцом в этом макросе, существует только один действительный способ сопоставления, любой другой образец приведёт к ошибке. Более сложные макросы будут иметь более одной ветви. +Устройства в теле макроса `vec!` похожа на устройство `match` выражения. Здесь у нас есть одна ветвь с образцом `( $( $x:expr ),* )`, затем следует ветвь `=>` и раздел рукописи, связанный с этим образцом. Если образец сопоставлен успешно, то соответствующий раздел рукописи будет создан. Учитывая, что данный рукопись является единственным образцом в этом макросе, существует только один действительный способ сопоставления, любой другой образец приведёт к ошибке. Более сложные макросы будут иметь более одной ветви. -Допустимый правила написания образца в определениях макросов отличается от правил написания образца рассмотренного в главе 18, потому что образцы макроса сопоставляются со устройствами кода Rust, а не со значениями. Давайте пройдёмся по тому, какие части образца в приложении 19-28 что означают; полный правила написания образцов макроса можно найти в [Справочнике по Rust]. +Допустимый правила написания образца в определениях макросов отличается от правил написания образца рассмотренного в главе 18, потому что образцы макроса сопоставляются со устройствами рукописи Ржавчина, а не со значениями. Давайте пройдёмся по тому, какие части образца в приложении 19-28 что означают; полный правила написания образцов макроса можно найти в [Справочнике по Ржавчине]. -Во-первых, мы используем набор скобок, чтобы охватить весь образец. Мы используем знак доллара ( `$`) для объявления переменной в системе макросов, которая будет содержать код на Rust, соответствующий образцу. Знак доллара показывает, что это макропеременная, а не обычная переменная Rust. Далее следует набор скобок, в котором определятся значения, соответствующие образцу в скобках, для использования в коде замены. Внутри `$()` находится `$x:expr`, которое соответствует любому выражению Ржавчина и даёт выражению имя `$x`. +Во-первых, мы используем набор скобок, чтобы охватить весь образец. Мы используем знак доллара ( `$`) для объявления переменной в системе макросов, которая будет содержать рукопись на Ржавчине, соответствующий образцу. Знак доллара показывает, что это макропеременная, а не обычная переменная Ржавчины. Далее следует набор скобок, в котором определятся значения, соответствующие образцу в скобках, для использования в рукописи замены. Внутри `$()` находится `$x:expr`, которое соответствует любому выражению Ржавчина и даёт выражению имя `$x`. -Запятая, следующая за `$()` указывает на то, что буквенный символ-разделитель запятая может дополнительно появиться после кода, который соответствует коду в `$()`. Звёздочка `*` указывает, что образец соответствует ноль или больше раз тому, что предшествует `*`. +Запятая, следующая за `$()` указывает на то, что буквенный знак-разделитель запятая может дополнительно появиться после рукописи, который соответствует рукописи в `$()`. Звёздочка `*` указывает, что образец соответствует ноль или больше раз тому, что предшествует `*`. Когда вызывается этот макрос с помощью `vec![1, 2, 3];` образец `$x` соответствует три раза всем трём выражениям `1`, `2` и `3`. -Теперь давайте посмотрим на образец в теле кода, связанного с этой ветвью: `temp_vec.push()` внутри `$()*` порождается для каждой части, которая соответствует символу `$()` в образце ноль или более раз в зависимости от того, сколько раз образец сопоставлен. Символ `$x` заменяется на каждое совпадающее выражение. Когда мы вызываем этот макрос с `vec![1, 2, 3];`, созданный код, заменяющий этот вызов макроса будет следующим: +Теперь давайте посмотрим на образец в теле рукописи, связанного с этой ветвью: `temp_vec.push()` внутри `$()*` порождается для каждой части, которая соответствует знаку `$()` в образце ноль или более раз в зависимости от того, сколько раз образец сопоставлен. Знак `$x` заменяется на каждое совпадающее выражение. Когда мы вызываем этот макрос с `vec![1, 2, 3];`, созданный рукопись, заменяющий этот вызов макроса будет следующим: ```rust,ignore { @@ -70,13 +70,13 @@ let v: Vec = vec![1, 2, 3]; } ``` -Мы определили макрос, который может принимать любое количество переменных любого вида и может порождать код для создания вектора, содержащего указанные элементы. +Мы определили макрос, который может принимать любое количество переменных любого вида и может порождать рукопись для создания вектора, содержащего указанные элементы. -Чтобы узнать больше о том, как писать макросы, обратитесь к онлайн-документации или другим ресурсам, таким как [«Маленькая книга макросов Rust»] , начатая Дэниелом Кипом и продолженная Лукасом Виртом. +Чтобы узнать больше о том, как писать макросы, обратитесь к в сети-пособия или другим источникамм, таким как [«Маленькая книга макросов Rust»] , начатая Дэниелом Кипом и продолженная Лукасом Виртом. -### Процедурные макросы для создания кода из свойств +### Процедурные макросы для создания рукописи из свойств -Вторая разновидность макросов - это *процедурные макросы* (procedural macros), которые действуют как функции (и являются видом процедуры). Процедурные макросы принимают некоторый код в качестве входных данных, работают над этим кодом и создают некоторый код в качестве вывода, а не выполняют сопоставления с образцами и замену кода другим кодом, как это делают декларативные макросы. Процедурные макросы могут быть трёх видов: "пользовательского вывода" (custom-derive), "похожие на свойство" (attribute-like) и "похожие на функцию" (function-like), все они работают схожим образом. +Вторая разновидность макросов - это *процедурные макросы* (procedural macros), которые действуют как функции (и являются видом процедуры). Процедурные макросы принимают некоторый рукопись в качестве входных данных, работают над этим рукописью и создают некоторый рукопись в качестве вывода, а не выполняют сопоставления с образцами и замену рукописи другим рукописью, как это делают декларативные макросы. Процедурные макросы могут быть трёх видов: "пользовательского вывода" (custom-derive), "похожие на свойство" (attribute-like) и "похожие на функцию" (function-like), все они работают схожим образом. При создании процедурных макросов объявления должны находиться в собственном ящике целенаправленного вида. Это из-за сложных технических причин, которые мы надеемся будут устранены в будущем. В приложении 19-29 показано, как задать процедурный макрос, где `some_attribute` является заполнителем для использования целенаправленного макроса. @@ -92,13 +92,13 @@ pub fn some_name(input: TokenStream) -> TokenStream { Приложение 19-29: Пример определения процедурного макроса -Функция, которая определяет процедурный макрос, принимает `TokenStream` в качестве входных данных и создаёт `TokenStream` в качестве вывода. Вид `TokenStream` объявлен ящиком `proc_macro`, включённым в Ржавчина и представляет собой последовательность токенов. Это ядро макроса: исходный код над которым работает макрос, является входным `TokenStream`, а код создаваемый макросом является выходным `TokenStream`. К функции имеет также прикреплённый свойство, определяющий какой вид процедурного макроса мы создаём. Можно иметь несколько видов процедурных макросов в одном и том же ящике. +Функция, которая определяет процедурный макрос, принимает `TokenStream` в качестве входных данных и создаёт `TokenStream` в качестве вывода. Вид `TokenStream` объявлен ящиком `proc_macro`, включённым в Ржавчине и представляет собой последовательность токенов. Это ядро макроса: исходную рукопись над которым работает макрос, является входным `TokenStream`, а рукопись создаваемый макросом является выходным `TokenStream`. К функции имеет также прикреплённый свойство, определяющий какой вид процедурного макроса мы создаём. Можно иметь несколько видов процедурных макросов в одном и том же ящике. Давайте посмотрим на различные виды процедурных макросов. Начнём с пользовательского, выводимого (derive) макроса и затем объясним небольшие различия, делающие другие разновидности отличающимися. ### Как написать пользовательский `derive` макрос -Давайте создадим ящик с именем `hello_macro`, который определяет особенность с именем `HelloMacro` и имеет одну с ним сопряженную функцию с именем `hello_macro`. Вместо того, чтобы пользователи нашего ящика самостоятельно выполнили особенность `HelloMacro` для каждого из своих видов, мы предоставим им процедурный макрос, чтобы они могли определять свой вид с помощью свойства `#[derive(HelloMacro)]` и получили выполнение по умолчанию для функции `hello_macro`. Выполнение по умолчанию выведет `Hello, Macro! My name is TypeName!`, где `TypeName` - это имя вида, для которого был определён этот особенность. Другими словами, мы напишем ящик, использование которого позволит другому программисту писать код показанный в приложении 19-30. +Давайте создадим ящик с именем `hello_macro`, который определяет особенность с именем `HelloMacro` и имеет одну с ним сопряженную функцию с именем `hello_macro`. Вместо того, чтобы пользователи нашего ящика самостоятельно выполнили особенность `HelloMacro` для каждого из своих видов, мы предоставим им процедурный макрос, чтобы они могли определять свой вид с помощью свойства `#[derive(HelloMacro)]` и получили выполнение по умолчанию для функции `hello_macro`. Выполнение по умолчанию выведет `Hello, Macro! My name is TypeName!`, где `TypeName` - это имя вида, для которого был определён этот особенность. Другими словами, мы напишем ящик, использование которого позволит другому программисту писать рукопись показанный в приложении 19-30. Файл: src/main.rs @@ -106,9 +106,9 @@ pub fn some_name(input: TokenStream) -> TokenStream { {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-30/src/main.rs}} ``` -Приложение 19-30: Код, который сможет писать пользователь нашего ящика при использовании нашего процедурного макроса +Приложение 19-30: Рукопись, который сможет писать пользователь нашего ящика при использовании нашего процедурного макроса -Этот код напечатает `Hello, Macro! My name is Pancakes!`, когда мы закончим. Первый шаг - создать новый, библиотечный ящик так: +Этот рукопись напечатает `Hello, Macro! My name is Pancakes!`, когда мы закончим. Первый шаг - создать новый, библиотечный ящик так: ```console $ cargo new hello_macro --lib @@ -128,9 +128,9 @@ $ cargo new hello_macro --lib {{#rustdoc_include ../listings/ch19-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/pancakes/src/main.rs}} ``` -Тем не менее, ему придётся написать разделвыполнения для каждого вида, который он хотел использовать вместе с `hello_macro`; а мы хотим избавить их от необходимости делать эту работу. +Тем не менее, ему придётся написать раздел выполнения для каждого вида, который он хотел использовать вместе с `hello_macro`; а мы хотим избавить их от необходимости делать эту работу. -Кроме того, мы пока не можем предоставить функцию `hello_macro` с выполнением по умолчанию, которая будет печатать имя вида, для которого выполнен особенность: Ржавчина не имеет возможностей рефлексии (reflection), поэтому он не может выполнить поиск имени вида во время выполнения кода. Нам нужен макрос для создания кода во время сборки. +Кроме того, мы пока не можем предоставить функцию `hello_macro` с выполнением по умолчанию, которая будет печатать имя вида, для которого выполнен особенность: Ржавчина не имеет возможностей рефлексии (reflection), поэтому он не может выполнить поиск имени вида во время выполнения рукописи. Нам нужен макрос для создания рукописи во время сборки. Следующим шагом является определение процедурного макроса. На мгновение написания этой статьи процедурные макросы должны быть в собственном ящике. Со временем это ограничение может быть отменено. Соглашение о внутреннем выстраивании @@ -140,7 +140,7 @@ $ cargo new hello_macro --lib $ cargo new hello_macro_derive --lib ``` -Наши два ящика тесно связаны, поэтому мы создаём процедурный макрос-ящик в папке ящика `hello_macro`. Если мы изменим определение особенности в `hello_macro`, то нам придётся также изменить выполнение процедурного макроса в `hello_macro_derive`. Два ящика нужно будет обнародовать отдельно и программисты, использующие эти ящики, должны будут добавить их как зависимости, а затем добавить их в область видимости. Мы могли вместо этого сделать так, что ящик `hello_macro` использует `hello_macro_derive` как зависимость и реэкспортирует код процедурного макроса. Однако то, как мы внутренне выстраивали +Наши два ящика тесно связаны, поэтому мы создаём процедурный макрос-ящик в папке ящика `hello_macro`. Если мы изменим определение особенности в `hello_macro`, то нам придётся также изменить выполнение процедурного макроса в `hello_macro_derive`. Два ящика нужно будет обнародовать отдельно и программисты, использующие эти ящики, должны будут добавить их как зависимости, а затем добавить их в область видимости. Мы могли вместо этого сделать так, что ящик `hello_macro` использует `hello_macro_derive` как зависимость и реэкспортирует рукопись процедурного макроса. Однако то, как мы внутренне выстраивали дело, делает возможным программистам использовать `hello_macro` даже если они не хотят `derive` возможность. @@ -152,7 +152,7 @@ $ cargo new hello_macro_derive --lib {{#include ../listings/ch19-advanced-features/listing-19-31/hello_macro/hello_macro_derive/Cargo.toml:6:12}} ``` -Чтобы начать определение процедурного макроса, поместите код приложения 19-31 в ваш файл *src/lib.rs* ящика `hello_macro_derive`. Обратите внимание, что этот код не собирается пока мы не добавим определение для функции `impl_hello_macro`. +Чтобы начать определение процедурного макроса, поместите рукопись приложения 19-31 в ваш файл *src/lib.rs* ящика `hello_macro_derive`. Обратите внимание, что этот рукопись не собирается пока мы не добавим определение для функции `impl_hello_macro`. Файл: hello_macro_derive/src/lib.rs @@ -160,17 +160,17 @@ $ cargo new hello_macro_derive --lib {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-31/hello_macro/hello_macro_derive/src/lib.rs}} ``` -Приложение 19-31: Код, который потребуется в большинстве процедурных макро ящиков для обработки Ржавчина кода +Приложение 19-31: Рукопись, который потребуется в большинстве процедурных макро ящиков для обработки Ржавчина рукописи -Обратите внимание, что мы разделили код на функцию `hello_macro_derive`, которая отвечает за синтаксический анализ `TokenStream` и функцию `impl_hello_macro`, которая отвечает за преобразование синтаксического дерева: это делает написание процедурного макроса удобнее. Код во внешней функции ( `hello_macro_derive` в данном случае) будет одинаковым для почти любого процедурного макрос ящика, который вы видите или создаёте. Код, который вы указываете в теле внутренней функции (в данном случае `impl_hello_macro` ) будет отличаться в зависимости от цели вашего процедурного макроса. +Обратите внимание, что мы разделили рукопись на функцию `hello_macro_derive`, которая отвечает за связанный оценка `TokenStream` и функцию `impl_hello_macro`, которая отвечает за преобразование согласно правил написания дерева: это делает написание процедурного макроса удобнее. Рукопись во внешней функции ( `hello_macro_derive` в данном случае) будет одинаковым для почти любого процедурного макрос ящика, который вы видите или создаёте. Рукопись, который вы указываете в теле внутренней функции (в данном случае `impl_hello_macro` ) будет отличаться в зависимости от цели вашего процедурного макроса. -Мы представили три новых ящика: `proc_macro` [`syn`] и [`quote`]. Макрос `proc_macro` поставляется с Rust, поэтому нам не нужно было добавлять его в зависимости внутри *Cargo.toml*. Макрос `proc_macro` - это API сборщика, который позволяет нам читать и управлять Ржавчина кодом из нашего кода. +Мы представили три новых ящика: `proc_macro` [`syn`] и [`quote`]. Макрос `proc_macro` поставляется с Ржавчина, поэтому нам не нужно было добавлять его в зависимости внутри *Cargo.toml*. Макрос `proc_macro` - это API сборщика, который позволяет нам читать и управлять Ржавчина рукописью из нашего рукописи. -Ящик `syn` разбирает Ржавчина код из строки в устройство данных над которой мы может выполнять действия. Ящик `quote` превращает устройства данных `syn` обратно в код Rust. Эти ящики упрощают разбор любого вида Ржавчина кода, который мы хотели бы обрабатывать: написание полного синтаксического анализатора для кода Ржавчина не является простой задачей. +Ящик `syn` разбирает Ржавчина рукопись из строки в устройство данных над которой мы может выполнять действия. Ящик `quote` превращает устройства данных `syn` обратно в рукопись Ржавчины. Эти ящики упрощают разбор любого вида Ржавчина рукописи, который мы хотели бы обрабатывать: написание полного согласно правил написания оценщика для рукописи Ржавчина не является простой задачей. Функция `hello_macro_derive` будет вызываться, когда пользователь нашей библиотеки указывает своему виду `#[derive(HelloMacro)]`. Это возможно, потому что мы определяли функцию `hello_macro_derive` с помощью `proc_macro_derive` и указали имя `HelloMacro`, которое соответствует имени нашего особенности; это соглашение, которому следует большинство процедурных макросов. -Функция `hello_macro_derive` сначала преобразует `input` из `TokenStream` в устройство данных, которую мы можем затем преобразовать и над которой выполнять действия. Здесь ящик `syn` вступает в игру. Функция `parse` в `syn` принимает `TokenStream` и возвращает устройство `DeriveInput`, представляющую разобранный код Rust. Приложение 19-32 показывает соответствующие части устройства `DeriveInput`, которые мы получаем при разборе строки `struct Pancakes;`: +Функция `hello_macro_derive` сначала преобразует `input` из `TokenStream` в устройство данных, которую мы можем затем преобразовать и над которой выполнять действия. Здесь ящик `syn` вступает в игру. Функция `parse` в `syn` принимает `TokenStream` и возвращает устройство `DeriveInput`, представляющую разобранный рукопись Ржавчины. Приложение 19-32 показывает соответствующие части устройства `DeriveInput`, которые мы получаем при разборе строки `struct Pancakes;`: ```rust,ignore DeriveInput { @@ -192,15 +192,15 @@ DeriveInput { } ``` -Приложение 19-32: Образец DeriveInput получаемый, когда разбирается код имеющий свойство макроса из приложения 19-30 +Приложение 19-32: Образец DeriveInput получаемый, когда разбирается рукопись имеющий свойство макроса из приложения 19-30 -Поля этой устройства показывают, что код Rust, который мы разобрали, является разделустройства с `ident` (определителем, означающим имя) `Pancakes`. В этой устройстве есть больше полей для описания всех видов кода Rust; проверьте [документацию `syn` о устройстве `DeriveInput`] для получения дополнительной сведений. +Поля этой устройства показывают, что рукопись Ржавчина, который мы разобрали, является разделустройства с `ident` (определителем, означающим имя) `Pancakes`. В этой устройстве есть больше полей для описания всех видов рукописи Ржавчины; проверьте [пособие `syn` о устройстве `DeriveInput`] для получения дополнительной сведений. -Вскоре мы определим функцию `impl_hello_macro`, в которой построим новый, дополнительный код Rust. Но прежде чем мы это сделаем, обратите внимание, что выводом для нашего выводимого (derive) макроса также является `TokenStream`. Возвращаемый `TokenStream` добавляется в код, написанный пользователями макроса, поэтому, когда они соберут свой ящик, они получат дополнительную возможность, которую мы предоставляем в изменённом `TokenStream`. +Вскоре мы определим функцию `impl_hello_macro`, в которой построим новый, дополнительный рукопись Ржавчины. Но прежде чем мы это сделаем, обратите внимание, что выводом для нашего выводимого (derive) макроса также является `TokenStream`. Возвращаемый `TokenStream` добавляется в рукопись, написанный пользователями макроса, поэтому, когда они соберут свой ящик, они получат дополнительную возможность, которую мы предоставляем в изменённом `TokenStream`. -Возможно, вы заметили, что мы вызываем `unwrap` чтобы выполнить панику в функции `hello_macro_derive`, если вызов функции `syn::parse` потерпит неудачу. Наш процедурный макрос должен паниковать при ошибках, потому что функции `proc_macro_derive` должны возвращать `TokenStream`, а не вид `Result` для соответствия API процедурного макроса. Мы упроисполнения этот пример с помощью `unwrap`, но в рабочем коде вы должны предоставить более определенные сообщения об ошибках, если что-то пошло не правильно, используя `panic!` или `expect`. +Возможно, вы заметили, что мы вызываем `unwrap` чтобы выполнить сбой в функции `hello_macro_derive`, если вызов функции `syn::parse` потерпит неудачу. Наш процедурный макрос должен вызвать сбой при ошибках, потому что функции `proc_macro_derive` должны возвращать `TokenStream`, а не вид `Result` для соответствия API процедурного макроса. Мы упроисполнения этот пример с помощью `unwrap`, но в рабочем рукописи вы должны предоставить более определенные сообщения об ошибках, если что-то пошло не правильно, используя `panic!` или `expect`. -Теперь, когда у нас есть код для преобразования определеного Ржавчина кода из `TokenStream` в образец `DeriveInput`, давайте создадим код выполняющий особенность `HelloMacro` у определеного вида, как показано в приложении 19-33. +Теперь, когда у нас есть рукопись для преобразования определеного Ржавчина рукописи из `TokenStream` в образец `DeriveInput`, давайте создадим рукопись выполняющий особенность `HelloMacro` у определеного вида, как показано в приложении 19-33. Файл: hello_macro_derive/src/lib.rs @@ -208,31 +208,31 @@ DeriveInput { {{#rustdoc_include ../listings/ch19-advanced-features/listing-19-33/hello_macro/hello_macro_derive/src/lib.rs:here}} ``` -Приложение 19-33: Выполнение особенности HelloMacro с использованием проанализированного кода Rust. +Приложение 19-33: Выполнение особенности HelloMacro с использованием рассмотренного рукописи Ржавчины. -Мы получаем образец устройства `Ident` содержащий имя (определитель) определеного вида с использованием `ast.ident`. Устройства в приложении 19-32 показывает, что когда мы запускаем функцию `impl_hello_macro` для кода из приложения 19-30, то получаемый `ident` будет иметь поле `ident` со значением `"Pancakes"`. Таким образом, переменная `name` в приложении 19-33 будет содержать образец устройства `Ident`, что при печати выдаст строку `"Pancakes"`, что является именем устройства в приложении 19-30. +Мы получаем образец устройства `Ident` содержащий имя (определитель) определеного вида с использованием `ast.ident`. Устройства в приложении 19-32 показывает, что когда мы запускаем функцию `impl_hello_macro` для рукописи из приложения 19-30, то получаемый `ident` будет иметь поле `ident` со значением `"Pancakes"`. Таким образом, переменная `name` в приложении 19-33 будет содержать образец устройства `Ident`, что при печати выдаст строку `"Pancakes"`, что является именем устройства в приложении 19-30. -Макрос `quote!` позволяет определить код Rust, который мы хотим вернуть. Сборщик ожидает что-то отличное от прямого итога выполнения макроса `quote!`, поэтому нужно преобразовать его в `TokenStream`. Мы делаем это путём вызова способа `into`, который использует промежуточное представление и возвращает значение требуемого вида `TokenStream`. +Макрос `quote!` позволяет определить рукопись Ржавчина, который мы хотим вернуть. Сборщик ожидает что-то отличное от прямого итога выполнения макроса `quote!`, поэтому нужно преобразовать его в `TokenStream`. Мы делаем это путём вызова способа `into`, который использует промежуточное представление и возвращает значение требуемого вида `TokenStream`. -Макрос `quote!` также предоставляет очень полезную механику образцов: мы можем ввести `#name` и `quote!` заменит его значением из переменной `name`. Вы можете даже сделать некоторое повторение, подобное тому, как работают обычные макросы. Проверьте [документацию ящика `quote`] для подробного введения. +Макрос `quote!` также предоставляет очень полезную механику образцов: мы можем ввести `#name` и `quote!` заменит его значением из переменной `name`. Вы можете даже сделать некоторое повторение, подобное тому, как работают обычные макросы. Проверьте [пособие ящика `quote`] для подробного введения. Мы хотим, чтобы наш процедурный макрос порождал выполнение нашего особенности `HelloMacro` для вида, который определял пользователь, который мы можем получить, используя `#name`. Выполнение особенности имеет одну функцию `hello_macro`, тело которой содержит возможность, которую мы хотим предоставить: напечатать `Hello, Macro! My name is` с именем определеного вида. -Макрос `stringify!` используемый здесь, встроен в Rust. Он принимает Ржавчина выражение, такое как `1 + 2` и во время сборки сборщик превращает выражение в строковый запись, такой как `"1 + 2"`. Он отличается от макросов `format!` или `println!`, которые вычисляют выражение, а затем превращают итог в виде вида `String`. Существует возможность того, что введённый `#name` может оказаться выражением для печати буквально как есть, поэтому здесь мы используем `stringify!`. Использование `stringify!` также уменьшает выделение памяти путём преобразования `#name` в строковый запись во время сборки. +Макрос `stringify!` используемый здесь, встроен в Ржавчину. Он принимает Ржавчина выражение, такое как `1 + 2` и во время сборки сборщик превращает выражение в строковый запись, такой как `"1 + 2"`. Он отличается от макросов `format!` или `println!`, которые вычисляют выражение, а затем превращают итог в виде вида `String`. Существует возможность того, что введённый `#name` может оказаться выражением для печати буквально как есть, поэтому здесь мы используем `stringify!`. Использование `stringify!` также уменьшает выделение памяти путём преобразования `#name` в строковый запись во время сборки. -На этом этапе приказ `cargo build` должна завершиться успешно для обоих `hello_macro` и `hello_macro_derive`. Давайте подключим эти ящики к коду в приложении 19-30, чтобы увидеть процедурный макрос в действии! Создайте новый двоичный дело в папке ваших *дел* с использованием приказы `cargo new pancakes`. Нам нужно добавить `hello_macro` и `hello_macro_derive` в качестве зависимостей для ящика `pancakes` в файл *Cargo.toml*. Если вы размещаете свои исполнения `hello_macro` и `hello_macro_derive` на сайт [crates.io](https://crates.io/), они будут обычными зависимостями; если нет, вы можете указать их как `path` зависимости следующим образом: +На этом этапе приказ `cargo build` должна завершиться успешно для обоих `hello_macro` и `hello_macro_derive`. Давайте подключим эти ящики к рукописи в приложении 19-30, чтобы увидеть процедурный макрос в действии! Создайте новый двоичный дело в папке ваших *дел* с использованием приказы `cargo new pancakes`. Нам нужно добавить `hello_macro` и `hello_macro_derive` в качестве зависимостей для ящика `pancakes` в файл *Cargo.toml*. Если вы размещаете свои исполнения `hello_macro` и `hello_macro_derive` на сайт [crates.io](https://crates.io/), они будут обычными зависимостями; если нет, вы можете указать их как `path` зависимости следующим образом: ```toml {{#include ../listings/ch19-advanced-features/no-listing-21-pancakes/pancakes/Cargo.toml:7:9}} ``` -Поместите код в приложении 19-30 в *src/main.rs* и выполните `cargo run`: он должен вывести `Hello, Macro! My name is Pancakes!`. Выполнение особенности `HelloMacro` из процедурного макроса была включена без необходимости его выполнения ящиком `pancakes`; `#[derive(HelloMacro)]` добавил выполнение особенности. +Поместите рукопись в приложении 19-30 в *src/main.rs* и выполните `cargo run`: он должен вывести `Hello, Macro! My name is Pancakes!`. Выполнение особенности `HelloMacro` из процедурного макроса была включена без необходимости его выполнения ящиком `pancakes`; `#[derive(HelloMacro)]` добавил выполнение особенности. Далее давайте рассмотрим, как другие виды процедурных макросов отличаются от пользовательских выводимых макросов. ### Макросы, похожие на свойство -Подобные свойствам макросы похожи на пользовательские выводимые макросы, но вместо создания кода для `derive` свойства, они позволяют создавать новые свойства. Они являются также более гибкими: `derive` работает только для устройств и перечислений; свойство-подобные могут применяться и к другим элементам, таким как функции. Вот пример использования имеющего свойство макроса: допустим, у вас есть свойство именованный `route` который определяет функции при использовании фреймворка для веб-приложений: +Подобные свойствам макросы похожи на пользовательские выводимые макросы, но вместо создания рукописи для `derive` свойства, они позволяют создавать новые свойства. Они являются также более гибкими: `derive` работает только для устройств и перечислений; свойство-подобные могут применяться и к другим элементам, таким как функции. Вот пример использования имеющего свойство макроса: допустим, у вас есть свойство именованный `route` который определяет функции при использовании фреймворка для сетевых-приложений: ```rust,ignore #[route(GET, "/")] @@ -248,35 +248,35 @@ pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream { Здесь есть два свойства вида `TokenStream`. Первый для содержимого свойства: часть `GET, "/"` . Второй это тело элемента, к которому прикреплён свойство: в данном случае `fn index() {}` и остальная часть тела функции. -Кроме того, имеющие свойства макросы работают так же как и пользовательские выводимые макросы: вы создаёте ящик с видом `proc-macro` и выполняете функцию, которая порождает код, который хотите! +Кроме того, имеющие свойства макросы работают так же как и пользовательские выводимые макросы: вы создаёте ящик с видом `proc-macro` и выполняете функцию, которая порождает рукопись, который хотите! ### Макросы, похожие на функции -Макросы, похожие на функции, выглядят подобно вызову функций. Подобно макросам `macro_rules!` они являются более гибкими, чем функции; например, они могут принимать неизвестное количество переменных. Тем не менее, макросы `macro_rules!` можно объявлять только с использованием правил написания подобного сопоставлению, который мы обсуждали ранее в разделе "Декларативные макросы `macro_rules!` для общего мета программирования". Макросы, похожие на функции, принимают свойство TokenStream и их определение управляет этим `TokenStream`, используя код Rust, как это делают два других вида процедурных макроса. Примером подобного возможностей макроса является макрос `sql!`, который можно вызвать так: +Макросы, похожие на функции, выглядят подобно вызову функций. Подобно макросам `macro_rules!` они являются более гибкими, чем функции; например, они могут принимать неизвестное количество переменных. Тем не менее, макросы `macro_rules!` можно объявлять только с использованием правил написания подобного сопоставлению, который мы обсуждали ранее в разделе "Декларативные макросы `macro_rules!` для общего мета программирования". Макросы, похожие на функции, принимают свойство TokenStream и их определение управляет этим `TokenStream`, используя рукопись Ржавчина, как это делают два других вида процедурных макроса. Примером подобного возможностей макроса является макрос `sql!`, который можно вызвать так: ```rust,ignore let sql = sql!(SELECT * FROM posts WHERE id=1); ``` -Этот макрос будет разбирать SQL указанию внутри него и проверять, что она синтаксически правильная, что является гораздо более сложной обработкой, чем то что может сделать макрос `macro_rules!`. Макрос `sql!` мог бы быть определён так: +Этот макрос будет разбирать SQL указанию внутри него и проверять, что она правильная согласно правил написания, что является гораздо более сложной обработкой, чем то что может сделать макрос `macro_rules!`. Макрос `sql!` мог бы быть определён так: ```rust,ignore #[proc_macro] pub fn sql(input: TokenStream) -> TokenStream { ``` -Это определение похоже на ярлык пользовательского выводимого макроса: мы получаем токены, которые находятся внутри скобок и возвращаем код, который мы хотели создать. +Это определение похоже на ярлык пользовательского выводимого макроса: мы получаем токены, которые находятся внутри скобок и возвращаем рукопись, который мы хотели создать. ## Итоги -Фух! Теперь у вас в распоряжении есть некоторые возможности Rust, которые вы не будете часто использовать, но вы будете знать, что они доступны в особых обстоятельствах. Мы представили несколько сложных тем, чтобы при появлении сообщения с предложением исправить ошибку или в коде других людей, вы могли бы распознать эти подходы и правила написания. Используйте эту главу как справочник, который поможет вам найти решение. +Фух! Теперь у вас в распоряжении есть некоторые возможности Ржавчины, которые вы не будете часто использовать, но вы будете знать, что они доступны в особых обстоятельствах. Мы представили несколько сложных тем, чтобы при появлении сообщения с предложением исправить ошибку или в рукописи других людей, вы могли бы распознать эти подходы и правила написания. Используйте эту главу как справочник, который поможет вам найти решение. -Далее мы применим в действительностивсе, что обсуждали на протяжении всей книги, и выполним ещё один дело! +Далее мы применим в действительности все, что обсуждали на протяжении всей книги, и выполним ещё один дело! -[Справочнике по Rust]: ../reference/macros-by-example.html +[Справочнике по Ржавчине]: ../reference/macros-by-example.html [«Маленькая книга макросов Rust»]: https://veykril.github.io/tlborm/ [`syn`]: https://crates.io/crates/syn [`quote`]: https://crates.io/crates/quote -[документацию `syn` о устройстве `DeriveInput`]: https://docs.rs/syn/1.0/syn/struct.DeriveInput.html -[документацию ящика `quote`]: https://docs.rs/quote \ No newline at end of file +[пособие `syn` о устройстве `DeriveInput`]: https://docs.rs/syn/1.0/syn/struct.DeriveInput.html +[пособие ящика `quote`]: https://docs.rs/quote \ No newline at end of file diff --git a/rustbook-ru/src/ch20-00-final-project-a-web-server.md b/rustbook-ru/src/ch20-00-final-project-a-web-server.md index 4a6587e70..a4a6b66b5 100644 --- a/rustbook-ru/src/ch20-00-final-project-a-web-server.md +++ b/rustbook-ru/src/ch20-00-final-project-a-web-server.md @@ -1,19 +1,19 @@ -# Конечный дело: создание многопоточного веб-сервера +# Конечный дело: создание многопоточного сетевого-отделенного вычислителя Это был долгий путь, но мы дошли до конца книги. В этой главе мы сделаем ещё один дело, чтобы закрепить несколько тем из последних глав и резюмировать то, что прошли в самом начале. -В качестве нашего конечного дела мы напишем веб-сервер, который выводит надпись “hello” в веб-браузере, как на рисунке 20-1. +В качестве нашего конечного дела мы напишем сетевой-отделеный вычислитель, который выводит надпись “hello” в сетевом-обозревателе, как на рисунке 20-1. ![hello from rust](https://github.com/ruRust/book/blob/master/rustbook-en/src/img/trpl20-01.png?raw=true) Рисунок 20-1: Наш последний совместный дело -Для создания веб-сервера нам понадобится: +Для создания сетевого-отделенного вычислителя нам понадобится: 1. Узнать немного о протоколах TCP и HTTP. 2. Сделать прослушивание TCP соединения у сокета. 3. Создать возможность для парсинга небольшого количества HTTP-запросов. -4. Научить сервер отдавать правильный HTTP-ответ. -5. Улучшить пропускную способность нашего сервера с помощью объединения потоков. +4. Научить отделеный вычислитель отдавать правильный HTTP-ответ. +5. Улучшить пропускную способность нашего отделенного вычислителя с помощью объединения потоков. -Прежде чем мы начнём, заметим: способ, который мы будем использовать - не лучшим способ создания веб-сервера на Rust. Члены сообщества уже обнародовали на [crates.io](https://crates.io/) несколько готовых к использованию ящиков, которые предоставляют более полные выполнения веб-сервера и объединения потоков, чем те, которые мы создадим. Однако наша цель в этой главе — научиться новому, а не идти по лёгкому пути. Поскольку Ржавчина — это язык системного программирования, мы можем выбирать тот уровень абстракции, который нам подходит, и можем переходить на более низкий уровень, что может быть невозможно или неприменимо в других языках. Поэтому мы напишем основной HTTP-сервер и объединениепотоков вручную, чтобы вы могли изучить общие мысли и способы, лежащие в основе ящиков, которые, возможно, вы будете использовать в будущем. +Прежде чем мы начнём, заметим: способ, который мы будем использовать - не лучшим способ создания сетевого-отделенного вычислителя на Ржавчины. Члены сообщества уже обнародовали на [crates.io](https://crates.io/) несколько готовых к использованию ящиков, которые предоставляют более полные выполнения сетевого-отделенного вычислителя и объединения потоков, чем те, которые мы создадим. Однако наша цель в этой главе — научиться новому, а не идти по лёгкому пути. Поскольку Ржавчина — это язык системного программирования, мы можем выбирать тот уровень абстракции, который нам подходит, и можем переходить на более низкий уровень, что может быть невозможно или неприменимо в других языках. Поэтому мы напишем основной HTTP-отделеный вычислитель и объединениепотоков вручную, чтобы вы могли изучить общие мысли и способы, лежащие в основе ящиков, которые, возможно, вы будете использовать в будущем. diff --git a/rustbook-ru/src/ch20-01-single-threaded.md b/rustbook-ru/src/ch20-01-single-threaded.md index 84d4c357c..eabe5746f 100644 --- a/rustbook-ru/src/ch20-01-single-threaded.md +++ b/rustbook-ru/src/ch20-01-single-threaded.md @@ -1,14 +1,14 @@ -## Создание однопоточного веб-сервера +## Создание однопоточного сетевому-отделенного вычислителя -Начнём с однопоточного веб-сервера. Перед тем, как начать, давайте сделаем краткий обзор протоколов, задействованных при создании веб-серверов. Детальное описание этих протоколов выходит за рамки этой книги, но краткий обзор даст вам необходимую сведения. +Начнём с однопоточного сетевого-отделенного вычислителя. Перед тем, как начать, давайте сделаем краткий обзор протоколов, задействованных при создании сетевых-отделеный вычислительов. Детальное описание этих протоколов выходит за рамки этой книги, но краткий обзор даст вам необходимую сведения. -Двумя основными протоколами, используемыми в веб-серверах, являются *протокол передачи гипертекста* *(HTTP - Hypertext Transfer Protocol)* и *Протокол управления передачей* *(TCP - Transmission Control Protocol)*. Оба протокола являются протоколами вида *запрос-ответ* (request-response), то есть *клиент* объявляет запросы, а *сервер* слушает эти запросы и предоставляет ответ клиенту. Содержимое этих запросов и ответов определяется протоколами. +Двумя основными протоколами, используемыми в сетевых-отделенных вычислителях, являются *протокол передачи гипертекста* *(HTTP - Hypertext Transfer Protocol)* и *Протокол управления передачей* *(TCP - Transmission Control Protocol)*. Оба протокола являются протоколами вида *запрос-ответ* (request-response), то есть *конечный потребитель* объявляет запросы, а *отделеный вычислитель* слушает эти запросы и предоставляет ответ конечному потребителю. Содержимое этих запросов и ответов определяется протоколами. -TCP - это протокол нижнего уровня, который описывает подробности того, как сведения передаётся от одного сервера к другому, но не определяет, что это за сведения. HTTP строится поверх TCP, определяя содержимое запросов и ответов. Технически возможно использовать HTTP с другими протоколами, но в подавляющем большинстве случаев HTTP отправляет свои данные поверх TCP. Мы будем работать с необработанными байтами в TCP и запросами и ответами в HTTP. +TCP - это протокол нижнего уровня, который описывает подробности того, как сведения передаётся от одного отделенного вычислителя к другому, но не определяет, что это за сведения. HTTP строится поверх TCP, определяя содержимое запросов и ответов. Технически возможно использовать HTTP с другими протоколами, но в подавляющем большинстве случаев HTTP отправляет свои данные поверх TCP. Мы будем работать с необработанными байтами в TCP и запросами и ответами в HTTP. ### Прослушивание TCP соединения -Нашему веб-серверу необходимо прослушивать TCP-соединение, так что это первая часть, над которой мы будем работать. Обычная библиотека предлагает для этого звено `std::net`. Сделаем новый дело обычным способом: +Нашему сетевому-отделеному вычислителю необходимо прослушивать TCP-соединение, так что это первая часть, над которой мы будем работать. Обычная библиотека предлагает для этого звено `std::net`. Сделаем новое дело обычным способом: ```console $ cargo new hello @@ -16,7 +16,7 @@ $ cargo new hello $ cd hello ``` -Дл начала добавьте код из приложения 20-1 в файл *src/main.rs*. Этот код будет прослушивать входящие TCP потоки по адресу `127.0.0.1:7878`. Когда сервер примет входящий поток, он напечатает `Connection established!` ("Соединение установлено!"). +Дл начала добавьте рукопись из приложения 20-1 в файл *src/main.rs*. Этот рукопись будет прослушивать входящие TCP потоки по адресу `127.0.0.1:7878`. Когда отделеный вычислитель примет входящий поток, он напечатает `Connection established!` ("Соединение установлено!"). Файл: src/main.rs @@ -26,17 +26,17 @@ $ cd hello Приложение 20-1: Прослушивание входящих потоков и печать сообщения при получении потока -Используя `TcpListener` мы можем слушать TCP соединения к адресу `127.0.0.1:7878`. В адресе, в его части перед двоеточием, сначала идёт IP-адрес, относящийся к вашему компьютеру (он одинаковый на каждом компьютере и не представляет определенный компьютер автора), а часть `7878` является портом. Мы выбрали этот порт по двум причинам: HTTP обычно не используется на этом порту, поэтому маловероятно, что наш сервер будет враждовать с каким-нибудь другим сервером, который может выполняться на вашей машине, и ещё 7878 - это слово *rust*, набранное на телефоне. +Используя `TcpListener` мы можем слушать TCP соединения к адресу `127.0.0.1:7878`. В адресе, в его части перед двоеточием, сначала идёт IP-адрес, относящийся к вашему компьютеру (он одинаковый на каждом компьютере и не представляет определенный компьютер составителя), а часть `7878` является портом. Мы выбрали этот порт по двум причинам: HTTP обычно не используется на этом порту, поэтому маловероятно, что наш отделеный вычислитель будет враждовать с каким-нибудь другим отделеный вычислительом, который может выполняться на вашей машине, и ещё 7878 - это слово *ржавчина*, набранное на телефоне. -Функция `bind` в этом сценарии работает так же, как функция `new`, поскольку она возвращает новый образец `TcpListener` . Причина, по которой функция называется `bind` заключается в том, что в сетевой совокупности понятий подключение к порту для прослушивания называется «привязка к порту» (“binding to a port”). +Функция `bind` в этом задумки работает так же, как функция `new`, поскольку она возвращает новый образец `TcpListener` . Причина, по которой функция называется `bind` заключается в том, что в сетевой совокупности понятий подключение к порту для прослушивания называется «привязка к порту» (“binding to a port”). -Функция `bind` возвращает `Result`, а это значит, что привязка может не состояться. Так, например, подключение к порту 80 предполагает наличие привилегий администратора (прочие пользователи могут прослушивать порты только от 1023-го и выше), поэтому если мы попытаемся подключиться к порту 80, не будучи администратором, привязка не сработает. Привязка также не выполнится, например, если мы запустим два образца нашей программы, прослушивающие один и тот же порт. Поскольку мы пишем простейший сервер в учебных целях, мы не будем беспокоиться об обработке подобных ошибок; вместо этого мы используем `unwrap` для прекращения работы программы в случае возникновения ошибок. +Функция `bind` возвращает `Result`, а это значит, что привязка может не состояться. Так, например, подключение к порту 80 предполагает наличие прав хозяина (прочие пользователи могут прослушивать порты только от 1023-го и выше), поэтому если мы попытаемся подключиться к порту 80, не будучи хозяином, привязка не сработает. Привязка также не выполнится, например, если мы запустим два образца нашей программы, прослушивающие один и тот же порт. Поскольку мы пишем простейший отделеный вычислитель в учебных целях, мы не будем беспокоиться об обработке подобных ошибок; вместо этого мы используем `unwrap` для прекращения работы программы в случае возникновения ошибок. -Способ `incoming` в `TcpListener` возвращает повторитель , который даёт нам последовательность потоков (определеннее, потоков вида `TcpStream` ). Один *поток* представляет собой открытое соединение между клиентом и сервером. *Соединением* называется полный этап запроса и ответа, в котором клиент подключается к серверу, сервер порождает ответ, и сервер закрывает соединение. Таким образом, мы будем читать из потока `TcpStream` то, что отправил клиент, а затем записывать наш ответ в поток, для отправки его обратно клиенту. В целом, цикл `for` будет обрабатывать каждое соединение по очереди и создавать серию потоков, которые мы будем обрабатывать. +Способ `incoming` в `TcpListener` возвращает повторитель , который даёт нам последовательность потоков (определеннее, потоков вида `TcpStream` ). Один *поток* представляет собой открытое соединение между конечным потребителем и отделеный вычислительом. *Соединением* называется полный этап запроса и ответа, в котором конечный потребитель подключается к отделеному вычислителю, отделеный вычислитель порождает ответ, и отделеный вычислитель закрывает соединение. Таким образом, мы будем читать из потока `TcpStream` то, что отправил конечный потребитель, а затем записывать наш ответ в поток, для отправки его обратно конечному потребителю. В целом, круговорот `for` будет обрабатывать каждое соединение по очереди и создавать последовательность потоков, которые мы будем обрабатывать. -На текущий мгновение наша обработка потока состоит из вызова `unwrap` для завершения программы, если в потоке возникли ошибки, если же таковых не обнаружится, программа выведет сообщение. В следующем приложении мы добавим больше возможности для успешного сценария. Причиной того, что мы можем получать ошибки от способа `incoming`, когда клиент подключается к серверу, является то, что на самом деле мы не перебираем подключения. На самом деле мы перебираем *попытки подключения*. Подключение может не состояться по ряду причин, многие из которых зависят от операционной системы. Например, многие операционные системы имеют ограничение на количество одновременно открытых соединений, которые они могут поддерживать; при превышении этого предела новые попытки установить соединение будут приводить к ошибке, пока какие-либо из уже открытых соединений не будут закрыты. +На текущий мгновение наша обработка потока состоит из вызова `unwrap` для завершения программы, если в потоке возникли ошибки, если же таковых не обнаружится, программа выведет сообщение. В следующем приложении мы добавим больше возможности для успешного задумки. Причиной того, что мы можем получать ошибки от способа `incoming`, когда конечный потребитель подключается к отделеному вычислителю, является то, что на самом деле мы не перебираем подключения. На самом деле мы перебираем *попытки подключения*. Подключение может не состояться по ряду причин, многие из которых зависят от операционной системы. Например, многие операционные системы имеют ограничение на количество одновременно открытых соединений, которые они могут поддерживать; при превышении этого предела новые попытки установить соединение будут приводить к ошибке, пока какие-либо из уже открытых соединений не будут закрыты. -Попробуем запустить этот код! Вызовите `cargo run` в окне вызова, а затем загрузите *127.0.0.1:7878* в веб-браузере. В браузере должно отображаться сообщение об ошибке, например «Connection reset», поскольку сервер в настоящее время не отправляет обратно никаких данных. Но когда вы посмотрите на свой окно вызова, вы должны увидеть несколько сообщений, которые были напечатаны, когда браузер подключался к серверу! +Попробуем запустить этот код! Вызовите `cargo run` в окне вызова, а затем загрузите *127.0.0.1:7878* в сетевом-обозревателе. В обозревателе должно отображаться сообщение об ошибке, например «Connection reset», поскольку отделеный вычислитель в настоящее время не отправляет обратно никаких данных. Но когда вы посмотрите на свой окно вызова, вы должны увидеть несколько сообщений, которые были напечатаны, когда обозреватель подключался к отделеному вычислителю! ```text Running `target/debug/hello` @@ -45,15 +45,15 @@ $ cd hello Connection established! ``` -Иногда вы видите несколько сообщений, напечатанных для одного запроса браузера; Причина может заключаться в том, что браузер выполняет запрос страницы, а также других ресурсов, таких как значок *favicon.ico,* который отображается на вкладке браузера. +Иногда вы видите несколько сообщений, напечатанных для одного запроса обозревателя; Причина может заключаться в том, что обозреватель выполняет запрос страницы, а также других источников, таких как значок *favicon.ico,* который отображается на вкладке обозревателя. -Также может быть, что браузер пытается подключиться к серверу несколько раз, потому что сервер не отвечает. Когда `stream` выходит из области видимости и отбрасывается в конце цикла, соединение закрывается как часть выполнения `drop`. Браузеры иногда обрабатывают закрытые соединения, повторяя попытки, потому что неполадка может быть временной. Важным обстоятельством является то, что мы успешно получили указатель TCP-соединения! +Также может быть, что обозреватель пытается подключиться к отделеному вычислителю несколько раз, потому что отделеный вычислитель не отвечает. Когда `stream` выходит из области видимости и отбрасывается в конце круговорота, соединение закрывается как часть выполнения `drop`. Обозреватели иногда обрабатывают закрытые соединения, повторяя попытки, потому что неполадка может быть временной. Важным обстоятельством является то, что мы успешно получили указатель TCP-соединения! -Не забудьте остановить программу, нажав ctrl-c, когда вы закончите выполнение определённой исполнения кода. Затем перезапустите программу, вызвав приказ `cargo run`, после того, как вы внесли какой-либо набор изменений, чтобы убедиться, что выполняется самая свежая исполнение кода. +Не забудьте остановить программу, нажав ctrl-c, когда вы закончите выполнение определённой исполнения рукописи. Затем перезапустите программу, вызвав приказ `cargo run`, после того, как вы внесли какой-либо набор изменений, чтобы убедиться, что выполняется самая свежая исполнение рукописи. ### Чтение запросов -Выполняем возможности чтения запроса из браузера! Чтобы разделить части, связанные с получением соединения и последующим действием с ним, мы запустим новую функцию для обработки соединения. В этой новой функции `handle_connection` мы будем читать данные из потока TCP и распечатывать их, чтобы мы могли видеть данные, отправленные из браузера. Измените код, чтобы он выглядел как в приложении 20-2. +Выполняем возможности чтения запроса из обозревателя! Чтобы разделить части, связанные с получением соединения и последующим действием с ним, мы запустим новую функцию для обработки соединения. В этой новой функции `handle_connection` мы будем читать данные из потока TCP и распечатывать их, чтобы мы могли видеть данные, отправленные из обозревателя. Измените рукопись, чтобы он выглядел как в приложении 20-2. Файл: src/main.rs @@ -63,18 +63,18 @@ $ cd hello Приложение 20-2: Чтение из TcpStream и печать данных -Мы добавляем `std::io::prelude` и `std::io::BufReader` в область видимости, чтобы получить доступ к особенностям и видам, которые позволяют нам читать и писать в поток. В цикле `for` функции `main` вместо вывода сообщения о том, что мы установили соединение, мы теперь вызываем новую функцию `handle_connection` и передаём ей `stream`. +Мы добавляем `std::io::prelude` и `std::io::BufReader` в область видимости, чтобы получить доступ к особенностям и видам, которые позволяют нам читать и писать в поток. В круговороте `for` функции `main` вместо вывода сообщения о том, что мы установили соединение, мы теперь вызываем новую функцию `handle_connection` и передаём ей `stream`. В функции `handle_connection` мы создаём новый образец `BufReader`, который оборачивает изменяемую ссылку на `stream`. `BufReader` добавляет буферизацию, управляя вызовами способов особенности `std::io::Read` за нас. -Мы создаём переменную `http_request` для сбора строк запроса, который браузер отправляет на наш сервер. Мы указываем, что хотим собрать эти строки в вектор, добавляя изложение вида `Vec<_>`. +Мы создаём переменную `http_request` для сбора строк запроса, который обозреватель отправляет на наш отделеный вычислитель. Мы указываем, что хотим собрать эти строки в вектор, добавляя изложение вида `Vec<_>`. `BufReader` выполняет особенность `std::io::BufRead`, который выполняет способ `lines`. Способ `lines` возвращает повторитель `Result`, разделяющий поток данных на части всякий раз, когда ему попадается байт новой строки. Чтобы получить все строки `String`, мы с помощью map вызываем `unwrap` у каждого `Result`. Значение `Result` может быть ошибкой, если данные не соответствуют исполнению UTF-8 или если возникли сбоев с чтением из потока. Опять же, программа в промышленном исполнении должна обрабатывать эти ошибки более изящно, но мы для простоты решили прекращать работу программы в случае ошибки. -Браузер указывает об окончании HTTP-запроса, отправляя два символа перевода строки подряд, поэтому, чтобы получить один запрос из потока, мы забираем строки, пока не получим строку, которая является пустой строкой. После того, как мы собрали строки в вектор, мы распечатываем их, используя красивое отладочное изменение -, чтобы мы могли взглянуть на указания, которые веб-браузер отправляет на наш сервер. +Обозреватель указывает об окончании HTTP-запроса, отправляя два знака перевода строки подряд, поэтому, чтобы получить один запрос из потока, мы забираем строки, пока не получим строку, которая является пустой строкой. После того, как мы собрали строки в вектор, мы распечатываем их, используя красивое отладочное изменение +, чтобы мы могли взглянуть на указания, которые сетевой-обозреватель отправляет на наш отделеный вычислитель. -Попробуем этот код! Запустите программу и снова сделайте запрос в веб-браузере. Обратите внимание, что мы по-прежнему будем получать в браузере страницу с ошибкой, но вывод нашей программы в окне вызова теперь будет выглядеть примерно так: +Попробуем этот код! Запустите программу и снова сделайте запрос в сетевом-обозревателе. Обратите внимание, что мы по-прежнему будем получать в обозревателе страницу с ошибкой, но вывод нашей программы в окне вызова теперь будет выглядеть примерно так: ```console $ cargo run @@ -99,9 +99,9 @@ Request: [ ] ``` -В зависимости от вашего браузера итог может немного отличаться. Теперь, когда мы печатаем данные запроса, мы можем понять, почему мы получаем несколько подключений из одного запроса браузера, посмотрев на путь после `GET` в первой строке запроса. Если все повторяющиеся соединения запрашивают */* , мы знаем, что браузер пытается получить */* повторно, потому что он не получает ответа от нашей программы. +В зависимости от вашего обозревателя итог может немного отличаться. Теперь, когда мы печатаем данные запроса, мы можем понять, почему мы получаем несколько подключений из одного запроса обозревателя, посмотрев на путь после `GET` в первой строке запроса. Если все повторяющиеся соединения запрашивают */* , мы знаем, что обозреватель пытается получить */* повторно, потому что он не получает ответа от нашей программы. -Давайте разберём эти данные запроса, чтобы понять, что браузер запрашивает у нашей программы. +Давайте разберём эти данные запроса, чтобы понять, что обозреватель запрашивает у нашей программы. ### Пристальный взгляд на HTTP запрос @@ -113,23 +113,23 @@ headers CRLF message-body ``` -Первая строка - это *строка запроса* , содержащая сведения о том, что запрашивает клиент. Первая часть строки запроса указывает используемый *способ* , например `GET` или `POST` , который описывает, как клиент выполняет этот запрос. Наш клиент использовал запрос `GET`, что означает, что он просит нас предоставить сведения. +Первая строка - это *строка запроса* , содержащая сведения о том, что запрашивает конечный потребитель. Первая часть строки запроса указывает используемый *способ* , например `GET` или `POST` , который описывает, как конечный потребитель выполняет этот запрос. Наш конечный потребитель использовал запрос `GET`, что означает, что он просит нас предоставить сведения. -Следующая часть строки запроса - это */*, которая указывает *унифицированный определитель* *ресурса (URI),* который запрашивает клиент: URI почти, но не совсем то же самое, что и *унифицированный указатель ресурса* *(URL)*. Разница между URI и URL-адресами не важна для наших целей в этой главе, но согласно принятых требований HTTP использует понятие URI, поэтому мы можем просто мысленно заменить URL-адрес здесь. +Следующая часть строки запроса - это */*, которая указывает *единый определитель* *источника (URI),* который запрашивает конечный потребитель: URI почти, но не совсем то же самое, что и *единый указатель источника* *(URL)*. Разница между URI и URL-адресами не важна для наших целей в этой главе, но согласно принятых требований HTTP использует понятие URI, поэтому мы можем просто мысленно заменить URL-адрес здесь. -Последняя часть - это исполнение HTTP, которую использует клиент, а затем строка запроса заканчивается *последовательностью CRLF* . (CRLF обозначает *возврат каретки* и *перевод строки* , что является понятием из дней пишущих машинок!) Последовательность CRLF также может быть записана как `\r\n` , где `\r` - возврат каретки, а `\n` - перевод строки. Последовательность CRLF отделяет строку запроса от остальных данных запроса. Обратите внимание, что при печати CRLF мы видим начало новой строки, а не `\r\n` . +Последняя часть - это исполнение HTTP, которую использует конечный потребитель, а затем строка запроса заканчивается *последовательностью CRLF* . (CRLF обозначает *возврат каретки* и *перевод строки* , что является понятием из дней пишущих машинок!) Последовательность CRLF также может быть записана как `\r\n` , где `\r` - возврат каретки, а `\n` - перевод строки. Последовательность CRLF отделяет строку запроса от остальных данных запроса. Обратите внимание, что при печати CRLF мы видим начало новой строки, а не `\r\n` . Глядя на данные строки запроса, которые мы получили от запуска нашей программы, мы видим, что `GET` - это способ, */* - это URI запроса, а `HTTP/1.1` - это исполнение. После строки запроса оставшиеся строки, начиная с `Host:` далее, являются заголовками. `GET` запросы не имеют тела. -Попробуйте сделать запрос из другого браузера или запросить другой адрес, например *127.0.0.1:7878/test* , чтобы увидеть, как изменяются данные запроса. +Попробуйте сделать запрос из другого обозревателя или запросить другой адрес, например *127.0.0.1:7878/test* , чтобы увидеть, как изменяются данные запроса. -Теперь, когда мы знаем, что запрашивает браузер, давайте отправим обратно в ответ некоторые данные! +Теперь, когда мы знаем, что запрашивает обозреватель, давайте отправим обратно в ответ некоторые данные! ### Написание ответа -Теперь выполняем отправку данных в ответ на запрос клиента. Ответы имеют следующий вид: +Теперь выполняем отправку данных в ответ на запрос конечного потребителя. Ответы имеют следующий вид: ```text HTTP-Version Status-Code Reason-Phrase CRLF @@ -137,15 +137,15 @@ headers CRLF message-body ``` -Первая строка - это *строка состояния*, которая содержит исполнение HTTP, используемую в ответе, числовой код состояния, который суммирует итог запроса, и фразу причины, которая предоставляет текстовое описание кода состояния. После последовательности CRLF идут любые заголовки, другая последовательность CRLF и тело ответа. +Первая строка - это *строка состояния*, которая содержит исполнение HTTP, используемую в ответе, числовой рукопись состояния, который складывает итог запроса, и фразу причины, которая предоставляет текстовое описание рукописи состояния. После последовательности CRLF идут любые заголовки, другая последовательность CRLF и тело ответа. -Вот пример ответа, который использует HTTP исполнения 1.1, имеет код состояния 200, фразу причины OK, без заголовков и без тела: +Вот пример ответа, который использует HTTP исполнения 1.1, имеет рукопись состояния 200, фразу причины OK, без заголовков и без тела: ```text HTTP/1.1 200 OK\r\n\r\n ``` -Код состояния 200 - это обычный успешный ответ. Текст представляет собой крошечный успешный HTTP-ответ. Давайте запишем это в поток как наш ответ на успешный запрос! Из функции `handle_connection` удалите `println!` который печатал данные запроса и заменял их кодом из Приложения 20-3. +Рукопись состояния 200 - это обычный успешный ответ. Текст представляет собой крошечный успешный HTTP-ответ. Давайте запишем это в поток как наш ответ на успешный запрос! Из функции `handle_connection` удалите `println!` который печатал данные запроса и заменял их рукописью из Приложения 20-3. Файл: src/main.rs @@ -157,11 +157,11 @@ HTTP/1.1 200 OK\r\n\r\n Первый перевод строки определяет переменную `response`, которая содержит данные сообщения об успешном выполнении. Затем мы вызываем `as_bytes` в нашем `response`, чтобы преобразовать строковые данные в байты. Способ `write_all` в `stream` принимает вид `&[u8]` и отправляет эти байты непосредственно получателю. Поскольку действие `write_all` может завершиться с ошибкой, мы, как и ранее, используем `unwrap` на любом возможно ошибочном итоге. И опять, в существующем приложении здесь вам нужно было бы добавить обработку ошибок. -После этих изменений давайте запустим наш код и сделаем запрос. Мы больше не печатаем никаких данных в окно вызова, поэтому мы не увидим никакого вывода, кроме сообщений от Cargo. Когда вы загрузите *127.0.0.1:7878* в веб-браузере, вы должны получить пустую страницу вместо ошибки. Вы только что вручную написали код получения HTTP-запроса и отправки ответа на него! +После этих изменений давайте запустим нашу рукопись и сделаем запрос. Мы больше не печатаем никаких данных в окно вызова, поэтому мы не увидим никакого вывода, кроме сообщений от Cargo. Когда вы загрузите *127.0.0.1:7878* в сетевом-обозревателе, вы должны получить пустую страницу вместо ошибки. Вы только что вручную написали рукопись получения HTTP-запроса и отправки ответа на него! ### Возвращение существующего HTML -Давайте выполняем возможности чего-нибудь большего, чем просто пустой страницы. Создайте новый файл *hello.html* в корне папки вашего дела, а не в папке *src* . Вы можете ввести любой HTML-код, который вам заблагорассудится; В приложении 20-4 показан один из исходов. +Давайте выполняем возможности чего-нибудь большего, чем просто пустой страницы. Создайте новый файл *hello.html* в корне папки вашего дела, а не в папке *src* . Вы можете ввести любой HTML-рукопись, который вам заблагорассудится; В приложении 20-4 показан один из исходов. Файл: hello.html @@ -171,7 +171,7 @@ HTTP/1.1 200 OK\r\n\r\n Приложение 20-4: Пример HTML-файла для ответа на запрос -Это простейший HTML5-документ с заголовком и каким-то текстом. Чтобы сервер возвращал его в ответ на полученный запрос, мы изменим `handle_connection`, как показано в приложении 20-5, чтобы считать HTML-файл, добавить его в ответ в качестве тела и отправить. +Это простейший HTML5-документ с заголовком и каким-то текстом. Чтобы отделеный вычислитель возвращал его в ответ на полученный запрос, мы изменим `handle_connection`, как показано в приложении 20-5, чтобы считать HTML-файл, добавить его в ответ в качестве тела и отправить. Файл: src/main.rs @@ -181,17 +181,17 @@ HTTP/1.1 200 OK\r\n\r\n Приложение 20-5. Отправка содержимого hello.html в качестве тела ответа -Мы добавили элемент `fs` в указанию `use`, чтобы включить в область видимости звено файловой системы встроенной библиотеки. Код для чтения содержимого файла в строку должен выглядеть знакомым для вас; мы использовали его в главе 12, когда читали содержимое файла для нашего дела ввода-вывода в приложении 12-4. +Мы добавили элемент `fs` в указанию `use`, чтобы включить в область видимости звено файловой системы встроенной библиотеки. Рукопись для чтения содержимого файла в строку должен выглядеть знакомым для вас; мы использовали его в главе 12, когда читали содержимое файла для нашего дела ввода-вывода в приложении 12-4. Далее мы используем `format!` чтобы добавить содержимое файла в качестве тела ответа об успешном завершении. Чтобы обеспечить действительный HTTP-ответ, мы добавляем заголовок `Content-Length` который имеет размер тела нашего ответа, в данном случае размер `hello.html` . -Запустите этот код приказом `cargo run` и загрузите *127.0.0.1:7878* в браузере; вы должны увидеть выведенный HTML в браузере! +Запустите этот рукопись приказом `cargo run` и загрузите *127.0.0.1:7878* в обозревателе; вы должны увидеть выведенный HTML в обозревателе! -В настоящее время мы пренебрегаем данные запроса в переменной `http_request` и в любом случае просто отправляем обратно содержимое HTML-файла. Это означает, что если вы попытаетесь запросить адрес *127.0.0.1:7878/something-else* в своём браузере, вы все равно получите тот же самый HTML-ответ. Пока что наш сервер очень ограничен, и не умеет делать то, что делает большинство веб-серверов. Мы хотим настроить наши ответы в зависимости от запроса и отправлять обратно HTML-файл только для правильно созданного запроса к пути */* . +В настоящее время мы пренебрегаем данные запроса в переменной `http_request` и в любом случае просто отправляем обратно содержимое HTML-файла. Это означает, что если вы попытаетесь запросить адрес *127.0.0.1:7878/something-else* в своём обозревателе, вы все равно получите тот же самый HTML-ответ. Пока что наш отделеный вычислитель очень ограничен, и не умеет делать то, что делает большинство сетевых-отделеный вычислительов. Мы хотим настроить наши ответы в зависимости от запроса и отправлять обратно HTML-файл только для правильно созданного запроса к пути */* . ### Проверка запроса и выборочное возвращение ответа -Сейчас наш веб-сервер возвращает HTML из файла независимо от того, что определенно запросил клиент. Давайте добавим проверку того, что браузер запрашивает */*, прежде чем вернуть HTML-файл, и будем возвращать ошибку, если браузер запрашивает что-то постороннее. Для этого нам нужно изменять `handle_connection`, как показано в приложении 20-6. Новый код проверяет соответствует ли требуемый запросом ресурс с определителем */*, и содержит разделы `if` и `else`, чтобы иначе обрабатывать другие запросы. +Сейчас наш сетевой-отделеный вычислитель возвращает HTML из файла независимо от того, что определенно запросил конечный потребитель. Давайте добавим проверку того, что обозреватель запрашивает */*, прежде чем вернуть HTML-файл, и будем возвращать ошибку, если обозреватель запрашивает что-то постороннее. Для этого нам нужно изменять `handle_connection`, как показано в приложении 20-6. Новый рукопись проверяет соответствует ли требуемый запросом источник с определителем */*, и содержит разделы `if` и `else`, чтобы иначе обрабатывать другие запросы. Файл: src/main.rs @@ -199,17 +199,17 @@ HTTP/1.1 200 OK\r\n\r\n {{#rustdoc_include ../listings/ch20-web-server/listing-20-06/src/main.rs:here}} ``` -Приложение 20-6: Обрабатываем запросы для корневого ресурса / не так, как запросы для других ресурсов +Приложение 20-6: Обрабатываем запросы для корневого источника / не так, как запросы для других источников Мы будем рассматривать только первую строку HTTP-запроса, поэтому вместо того, чтобы читать весь запрос в вектор, мы вызываем `next` , чтобы получить первый элемент из повторителя. Первый вызов `unwrap` заботится об обработке `Option` и останавливает программу, если в повторителе нет элементов. Второй `unwrap` обрабатывает `Result` и имеет тот же эффект, что и `unwrap`, который был в `map`, добавленном в приложении 20-2. Затем мы проверяем переменную `request_line`, чтобы увидеть, равна ли она строке запроса, соответствующей запросу GET для пути */* . Если это так, раздел`if` возвращает содержимое нашего HTML-файла. -Если `request_line` *не* равна запросу GET для пути */*, это означает, что мы получили какой-то другой запрос. Мы скоро добавим код в раздел`else`, чтобы ответить на все остальные запросы. +Если `request_line` *не* равна запросу GET для пути */*, это означает, что мы получили какой-то другой запрос. Мы скоро добавим рукопись в раздел`else`, чтобы ответить на все остальные запросы. -Запустите этот код сейчас и запросите *127.0.0.1:7878* ; вы должны получить HTML в *hello.html* . Если вы сделаете любой другой запрос, например *127.0.0.1:7878/something-else* , вы получите ошибку соединения, подобную той, которую вы видели при запуске кода из Приложения 20-1 и Приложения 20-2. +Запустите этот рукопись сейчас и запросите *127.0.0.1:7878* ; вы должны получить HTML в *hello.html* . Если вы сделаете любой другой запрос, например *127.0.0.1:7878/something-else* , вы получите ошибку соединения, подобную той, которую вы видели при запуске рукописи из Приложения 20-1 и Приложения 20-2. -Теперь давайте добавим код из приложения 20-7 в раздел`else` чтобы вернуть ответ с кодом состояния 404, который указывает о том, что содержание для запроса не найден. Мы также вернём HTML-код для страницы, отображаемой в браузере, с указанием ответа конечному пользователю. +Теперь давайте добавим рукопись из приложения 20-7 в раздел`else` чтобы вернуть ответ с рукописью состояния 404, который указывает о том, что содержание для запроса не найден. Мы также вернём HTML-рукопись для страницы, отображаемой в обозревателе, с указанием ответа конечному пользователю. Файл: src/main.rs @@ -217,9 +217,9 @@ HTTP/1.1 200 OK\r\n\r\n {{#rustdoc_include ../listings/ch20-web-server/listing-20-07/src/main.rs:here}} ``` -Приложение 20-7: Отвечаем кодом состояния 404 и страницей ошибки, если было запрошено что-то, отличающееся от ресурса / +Приложение 20-7: Отвечаем рукописью состояния 404 и страницей ошибки, если было запрошено что-то, отличающееся от источника / -Здесь ответ имеет строку состояния с кодом 404 и фразу причины `NOT FOUND`. Тело ответа будет HTML из файла *404.html*. Вам нужно создать файл *404.html* рядом с *hello.html* для этой страницы ошибки; снова не стесняйтесь использовать любой HTML код или пример HTML кода в приложении 20-8. +Здесь ответ имеет строку состояния с рукописью 404 и фразу причины `NOT FOUND`. Тело ответа будет HTML из файла *404.html*. Вам нужно создать файл *404.html* рядом с *hello.html* для этой страницы ошибки; снова не стесняйтесь использовать любой HTML рукопись или пример HTML рукописи в приложении 20-8. Файл: 404.html @@ -229,11 +229,11 @@ HTTP/1.1 200 OK\r\n\r\n Приложение 20-8. Пример содержимого страницы для отправки с любым ответом 404 -С этими изменениями снова запустите сервер. Запрос на *127.0.0.1:7878* должен возвращать содержимое *hello.html*, и любой другой запрос, как *127.0.0.1:7878/foo*, должен возвращать сообщение об ошибке HTML от *404.html*. +С этими изменениями снова запустите отделеный вычислитель. Запрос на *127.0.0.1:7878* должен возвращать содержимое *hello.html*, и любой другой запрос, как *127.0.0.1:7878/foo*, должен возвращать сообщение об ошибке HTML от *404.html*. -### Переработка кода +### Переработка рукописи -На текущий мгновение разделы `if` и `else` во многом повторяются: они оба читают файлы и записывают содержимое файлов в поток. Разница лишь в строке состояния и имени файла. Давайте сделаем код более кратким, вынеся эти отличия в отдельные разделы `if` и `else`, в которых переменным будут присвоены значения строки состояния и имени файла; далее эти переменные мы сможем использовать в коде для чтения файла и создания ответа. В приложении 20-9 показан код после изменения объёмных разделов `if` и `else`. +На текущий мгновение разделы `if` и `else` во многом повторяются: они оба читают файлы и записывают содержимое файлов в поток. Разница лишь в строке состояния и имени файла. Давайте сделаем рукопись более кратким, вынеся эти отличия в отдельные разделы `if` и `else`, в которых переменным будут присвоены значения строки состояния и имени файла; далее эти переменные мы сможем использовать в рукописи для чтения файла и создания ответа. В приложении 20-9 показан рукопись после изменения объёмных разделов `if` и `else`. Файл: src/main.rs @@ -241,12 +241,12 @@ HTTP/1.1 200 OK\r\n\r\n {{#rustdoc_include ../listings/ch20-web-server/listing-20-09/src/main.rs:here}} ``` -Приложение 20-9: Переработка кода разделов if и else, чтобы они содержали только код, который отличается для каждого из случаев +Приложение 20-9: Переработка рукописи разделов if и else, чтобы они содержали только рукопись, который отличается для каждого из случаев Теперь разделы `if` и `else` возвращают только соответствующие значения для строки состояния и имени файла в упорядоченном ряде. Затем мы используем разъединение, чтобы присвоить эти два значения `status_line` и `filename` используя образец в указания `let`, как обсуждалось в главе 18. -Ранее повторяющийся код теперь находится вне разделов `if` и `else` и использует переменные `status_line` и `filename`. Это позволяет легче увидеть разницу между этими двумя случаями и означает, что у нас есть только одно место для обновления кода, если захотим изменить работу чтения файлов и записи ответов. Поведение кода в приложении 20-9 будет таким же, как и в 20-8. +Ранее повторяющийся рукопись теперь находится вне разделов `if` и `else` и использует переменные `status_line` и `filename`. Это позволяет легче увидеть разницу между этими двумя случаями и означает, что у нас есть только одно место для обновления рукописи, если захотим изменить работу чтения файлов и записи ответов. Поведение рукописи в приложении 20-9 будет таким же, как и в 20-8. -Потрясающие! Теперь у нас есть простой веб-сервер примерно на 40 строках кода Rust, который отвечает на один запрос страницей с содержанием и отвечает на все остальные запросы ответом 404. +Потрясающие! Теперь у нас есть простой сетевой-отделеный вычислитель примерно на 40 строках рукописи Ржавчины, который отвечает на один запрос страницей с содержанием и отвечает на все остальные запросы ответом 404. -В настоящее время наш сервер работает в одном потоке, что означает, что он может обслуживать только один запрос за раз. Давайте разберёмся, почему это может быть неполадкой, сымитировав несколько медленных запросов. Затем мы исправим случай так, чтобы наш сервер мог обрабатывать несколько запросов одновременно. +В настоящее время наш отделеный вычислитель работает в одном потоке, что означает, что он может обслуживать только один запрос за раз. Давайте разберёмся, почему это может быть неполадкой, сымитировав несколько медленных запросов. Затем мы исправим случай так, чтобы наш отделеный вычислитель мог обрабатывать несколько запросов одновременно. diff --git a/rustbook-ru/src/ch20-02-multithreaded.md b/rustbook-ru/src/ch20-02-multithreaded.md index 1dec3c958..506e56f95 100644 --- a/rustbook-ru/src/ch20-02-multithreaded.md +++ b/rustbook-ru/src/ch20-02-multithreaded.md @@ -1,10 +1,10 @@ -## Превращение однопоточного сервера в многопоточный сервер +## Превращение однопоточного отделенного вычислителя в многопоточный отделеный вычислитель -В текущей выполнения сервер обрабатывает каждый запрос по очереди, то есть, он не начнёт обрабатывать второе соединение, пока не завершит обработку первого. При росте числа запросов к серверу, такое последовательное выполнение было бы все менее и менее разумным. Если сервер получает какой-то запрос, обработка которого занимает достаточно много времени, последующим запросам придётся ждать завершения обработки длительного запроса, даже если эти новые запросы сами по себе могут быть обработаны быстро. Нам нужно это исправить, но сначала рассмотрим неполадку в действии. +В текущей выполнения отделеный вычислитель обрабатывает каждый запрос по очереди, то есть, он не начнёт обрабатывать второе соединение, пока не завершит обработку первого. При росте числа запросов к отделеному вычислителю, такое последовательное выполнение было бы все менее и менее разумным. Если отделеный вычислитель получает какой-то запрос, обработка которого занимает достаточно много времени, последующим запросам придётся ждать завершения обработки длительного запроса, даже если эти новые запросы сами по себе могут быть обработаны быстро. Нам нужно это исправить, но сначала рассмотрим неполадку в действии. -### Подражание медленного запроса в текущей выполнения сервера +### Подражание медленного запроса в текущей выполнения отделенного вычислителя -Мы посмотрим, как запрос с медленной обработкой может повлиять на другие запросы, сделанные к серверу в текущей выполнения. В приложении 20-10 выполнена обработка запроса к ресурсу */sleep* с эмуляцией медленного ответа, при которой сервер будет ждать 5 секунд перед тем, как ответить. +Мы посмотрим, как запрос с медленной обработкой может повлиять на другие запросы, сделанные к отделеному вычислителю в текущей выполнения. В приложении 20-10 выполнена обработка запроса к источнику */sleep* с эмуляцией медленного ответа, при которой отделеный вычислитель будет ждать 5 секунд перед тем, как ответить. Файл: src/main.rs @@ -16,27 +16,27 @@ Мы переключились с `if` на `match`, так как теперь у нас есть три случая. Нам придётся явно сопоставить срез от `request_line` для проверки совпадения образца со строковыми записями; `match` не делает самостоятельно е ссылки и разыменования, как это делает способ равенства. -Первая ветка совпадает с разделом `if` из приложения 20-9. Вторая ветка соответствует запросу */sleep* . Когда этот запрос получен, сервер заснёт на 5 секунд, прежде чем отдать успешную HTML-страницу. Третья ветка совпадает с разделом `else` из приложения 20-9. +Первая ветка совпадает с разделом `if` из приложения 20-9. Вторая ветка соответствует запросу */sleep* . Когда этот запрос получен, отделеный вычислитель заснёт на 5 секунд, прежде чем отдать успешную HTML-страницу. Третья ветка совпадает с разделом `else` из приложения 20-9. -Можно увидеть, насколько прост наш сервер: в существующих библиотеках распознавание разных запросов осуществлялось бы гораздо менее многословно! +Можно увидеть, насколько прост наш отделеный вычислитель: в существующих библиотеках распознавание разных запросов осуществлялось бы гораздо менее многословно! -Запустите сервер приказом `cargo run`. Затем откройте два окна браузера: одно с адресом *http://127.0.0.1:7878/*, другое с *http://127.0.0.1:7878/sleep*. Если вы несколько раз обратитесь к URI */*, то как и раньше увидите, что сервер быстро ответит. Но если вы введёте URI */sleep*, а затем загрузите URI */*, то увидите что */* ждёт, пока `/sleep` не отработает полные 5 секунд перед загрузкой страницы. +Запустите отделеный вычислитель приказом `cargo run`. Затем откройте два окна обозревателя: одно с адресом *http://127.0.0.1:7878/*, другое с *http://127.0.0.1:7878/sleep*. Если вы несколько раз обратитесь к URI */*, то как и раньше увидите, что отделеный вычислитель быстро ответит. Но если вы введёте URI */sleep*, а затем загрузите URI */*, то увидите что */* ждёт, пока `/sleep` не отработает полные 5 секунд перед загрузкой страницы. Есть несколько способов, которые можно использовать, чтобы избавиться от подтормаживания запросов после одного медленного запроса; способ, который мы выполняем, называется объединением потоков. ### Улучшение пропускной способности с помощью объединения потоков -*Объединение потоков* является объединением заранее порождённых потоков, ожидающих в объединении и готовых выполнить задачу. Когда программа получает новую задачу, она назначает эту задачу одному из потоков в объединении, и тогда задача будет обработана этим потоком. Остальные потоки в объединении доступны для обработки любых других задач, поступающих в то время, пока первый поток занят. Когда первый поток завершает обработку своей задачи, он возвращается в объединениесвободных потоков, готовых приступить к новой задаче. Объединение потоков позволяет обрабатывать соединения одновременно, увеличивая пропускную способность вашего сервера. +*Объединение потоков* является объединением заранее порождённых потоков, ожидающих в объединении и готовых выполнить задачу. Когда программа получает новую задачу, она назначает эту задачу одному из потоков в объединении, и тогда задача будет обработана этим потоком. Остальные потоки в объединении доступны для обработки любых других задач, поступающих в то время, пока первый поток занят. Когда первый поток завершает обработку своей задачи, он возвращается в объединениесвободных потоков, готовых приступить к новой задаче. Объединение потоков позволяет обрабатывать соединения одновременно, увеличивая пропускную способность вашего отделенного вычислителя. -Мы ограничим число потоков в объединении небольшим числом, чтобы защитить нас от атак вида «отказ в обслуживании» (DoS - Denial of Service); если бы наша программа создавала новый поток в мгновение поступления каждого запроса, то кто-то сделавший 10 миллионов запросов к серверу, мог бы создать хаос, использовать все ресурсы нашего сервера и остановить обработку запросов. +Мы ограничим число потоков в объединении небольшим числом, чтобы защитить нас от атак вида «отказ в обслуживании» (DoS - Denial of Service); если бы наша программа создавала новый поток в мгновение поступления каждого запроса, то кто-то сделавший 10 миллионов запросов к отделеному вычислителю, мог бы создать хаос, использовать все мощности нашего отделенного вычислителя и остановить обработку запросов. -Вместо порождения неограниченного количества потоков, у нас будет определенное количество потоков, ожидающих в объединении. Поступающие запросы будут отправляться в объединениедля обработки. Объединение будет иметь очередь входящих запросов. Каждый из потоков в объединении будет извлекать запрос из этой очереди, обрабатывать запрос и затем запрашивать в очереди следующий запрос. При таком внешнем виде мы можем обрабатывать `N` запросов одновременно, где `N` - количество потоков. Если каждый поток отвечает на длительный запрос, последующие запросы могут по-прежнему задержаться в очереди, но теперь мы увеличили количество "длинных" запросов, которые мы можем обработать, перед тем, как эта случаей снова возникнет. +Вместо порождения неограниченного количества потоков, у нас будет определенное количество потоков, ожидающих в объединении. Поступающие запросы будут отправляться в объединениедля обработки. Объединение будет иметь очередь входящих запросов. Каждый из потоков в объединении будет извлекать запрос из этой очереди, обрабатывать запрос и затем запрашивать в очереди следующий запрос. При таком внешнем виде мы можем обрабатывать `N` запросов одновременно, где `N` - количество потоков. Если каждый поток отвечает на длительный запрос, последующие запросы могут по-прежнему задержаться в очереди, но теперь мы увеличили количество "длинных" запросов, которые мы можем обработать, перед тем, как эта случай снова возникнет. -Этот подход - лишь один из многих способов улучшить пропускную способность веб-сервера. Другими исходами, на которые возможно стоило бы обратить внимание, являются: *прообраз fork/join*, *прообраз однопоточного не согласованного ввода-вывода* или *прообраз многопоточного не согласованного ввода-вывода*. Если вам важна эта тема, вы можете почитать больше сведений о других решениях и попробовать выполнить их самостоятельно. С таким низкоуровневым языком как Rust, любой из этих исходов осуществим. +Этот подход - лишь один из многих способов улучшить пропускную способность сетевого-отделенного вычислителя. Другими исходами, на которые возможно стоило бы обратить внимание, являются: *прообраз fork/join*, *прообраз однопоточного не согласованного ввода-вывода* или *прообраз многопоточного не согласованного ввода-вывода*. Если вам важна эта тема, вы можете почитать больше сведений о других решениях и попробовать выполнить их самостоятельно. С таким низкоуровневым языком как Ржавчина, любой из этих исходов осуществим. -Прежде чем приступить к выполнения объединения потоков, давайте поговорим о том, как должно выглядеть использование объединения . Когда вы пытаетесь создать код, сначала необходимо написать клиентский внешнюю оболочку. Напишите API кода, чтобы он был внутренне выстроен так, как вы хотите его вызывать, затем выполните возможность данной устройства, вместо подхода выполнить возможности. а затем разрабатывать общедоступный API. +Прежде чем приступить к выполнения объединения потоков, давайте поговорим о том, как должно выглядеть использование объединения . Когда вы пытаетесь создать рукопись, сначала необходимо написать конечного потребителя внешнюю оболочку. Напишите API рукописи, чтобы он был внутренне выстроен так, как вы хотите его вызывать, затем выполните возможность данной устройства, вместо подхода выполнить возможности. а затем разрабатывать общедоступный API. -Подобно тому, как мы использовали разработку через проверка (test-driven) в деле главы 12, мы будем использовать здесь разработку, управляемую сборщиком (compiler-driven). Мы напишем код, вызывающий нужные нам функции, а затем посмотрим на ошибки сборщика, чтобы определить, что мы должны изменить дальше, чтобы заставить код работать. Однако перед этим, в качестве отправной точки, мы рассмотрим технику, которую мы не будем применять в дальнейшем. +Подобно тому, как мы использовали разработку через проверка (test-driven) в деле главы 12, мы будем использовать здесь разработку, управляемую сборщиком (compiler-driven). Мы напишем рукопись, вызывающий нужные нам функции, а затем посмотрим на ошибки сборщика, чтобы определить, что мы должны изменить дальше, чтобы заставить рукопись работать. Однако перед этим, в качестве отправной точки, мы рассмотрим технику, которую мы не будем применять в дальнейшем. @@ -44,7 +44,7 @@ #### Порождение потока для каждого запроса -Сначала давайте рассмотрим, как мог бы выглядеть код, если бы он создавал бы новый поток для каждого соединения. Как упоминалось ранее, мы не собираемся использовать этот способ в окончательной выполнения, из-за возможных неполадок при возможно неограниченном числе порождённых потоков. Это лишь отправная точка, с которой начнёт работу наш многопоточный сервер. Затем мы улучшим код, добавив объединениепотоков, и тогда разницу между этими двумя решениями будет легче заметить. В приложении 20-11 показаны изменения, которые нужно внести в код `main`, чтобы порождать новый поток для обработки каждого входящего соединения внутри цикла `for`. +Сначала давайте рассмотрим, как мог бы выглядеть рукопись, если бы он создавал бы новый поток для каждого соединения. Как упоминалось ранее, мы не собираемся использовать этот способ в окончательной выполнения, из-за возможных неполадок при возможно неограниченном числе порождённых потоков. Это лишь отправная точка, с которой начнёт работу наш многопоточный отделеный вычислитель. Затем мы улучшим рукопись, добавив объединениепотоков, и тогда разницу между этими двумя решениями будет легче заметить. В приложении 20-11 показаны изменения, которые нужно внести в рукопись `main`, чтобы порождать новый поток для обработки каждого входящего соединения внутри круговорота `for`. Файл: src/main.rs @@ -54,7 +54,7 @@ Приложение 20-11: Порождение нового потока для каждого соединения -Как вы изучили в главе 16, функция `thread::spawn` создаст новый поток и затем запустит код замыкания в этом новом потоке. Если вы запустите этот код и загрузите */sleep* в своём браузере, а затем загрузите */* в двух других вкладках браузера, вы действительно увидите, что запросам к */* не приходится ждать завершения */sleep*. Но, как мы уже упоминали, это в какой-то мгновение приведёт к сильному снижению производительности системы, так как вы будете создавать новые потоки без каких-либо ограничений. +Как вы изучили в главе 16, функция `thread::spawn` создаст новый поток и затем запустит рукопись замыкания в этом новом потоке. Если вы запустите этот рукопись и загрузите */sleep* в своём обозревателе, а затем загрузите */* в двух других вкладках обозревателя, вы действительно увидите, что запросам к */* не приходится ждать завершения */sleep*. Но, как мы уже упоминали, это в какой-то мгновение приведёт к сильному снижению производительности системы, так как вы будете создавать новые потоки без каких-либо ограничений. @@ -62,7 +62,7 @@ #### Создание конечного числа потоков -Мы хотим, чтобы наш объединениепотоков работал подобным, знакомым образом, чтобы переключение с потоков на объединениепотоков не требовало больших изменений в коде использующем наш API. В приложении 20-12 показан гипотетический внешняя оболочка для устройства `ThreadPool`, который мы хотим использовать вместо `thread::spawn`. +Мы хотим, чтобы наш объединениепотоков работал подобным, знакомым образом, чтобы переключение с потоков на объединениепотоков не требовало больших изменений в рукописи использующем наш API. В приложении 20-12 показан гипотетический внешняя оболочка для устройства `ThreadPool`, который мы хотим использовать вместо `thread::spawn`. Файл: src/main.rs @@ -72,7 +72,7 @@ Приложение 20-12: Наш наилучший внешняя оболочка ThreadPool -Мы используем `ThreadPool::new`, чтобы создать новый объединениепотоков с конфигурируемым числом потоков, в данном случае четырьмя. Затем в цикле `for` функция `pool.execute` имеет внешнюю оболочку, похожий на `thread::spawn`, в том смысле, что он так же принимает замыкание, код которого объединениедолжен выполнить для каждого соединения. Нам нужно выполнить `pool.execute`, чтобы он принимал замыкание и передавал его потоку из объединения для выполнения. Этот код пока не собирается, но мы постараемся, чтобы сборщик помог нам это исправить. +Мы используем `ThreadPool::new`, чтобы создать новый объединениепотоков с конфигурируемым числом потоков, в данном случае четырьмя. Затем в круговороте `for` функция `pool.execute` имеет внешнюю оболочку, похожий на `thread::spawn`, в том смысле, что он так же принимает замыкание, рукопись которого объединениедолжен выполнить для каждого соединения. Нам нужно выполнить `pool.execute`, чтобы он принимал замыкание и передавал его потоку из объединения для выполнения. Этот рукопись пока не собирается, но мы постараемся, чтобы сборщик помог нам это исправить. @@ -86,9 +86,9 @@ {{#include ../listings/ch20-web-server/listing-20-12/output.txt}} ``` -Замечательно! Ошибка говорит о том, что нам нужен вид или звено `ThreadPool`, поэтому мы сейчас его создадим. Наша выполнение `ThreadPool` не будет зависеть от того, что делает наш веб-сервер. Итак, давайте переделаем ящик `hello` из двоичного в библиотечный, чтобы хранить там нашу выполнение `ThreadPool`. После того, как мы переключимся в библиотечный ящик, мы также сможем использовать отдельную библиотеку объединения потоков для любой подходящей работы, а не только для обслуживания веб-запросов. +Замечательно! Ошибка говорит о том, что нам нужен вид или звено `ThreadPool`, поэтому мы сейчас его создадим. Наша выполнение `ThreadPool` не будет зависеть от того, что делает наш сетевой-отделеный вычислитель. Итак, давайте переделаем ящик `hello` из двоичного в библиотечный, чтобы хранить там нашу выполнение `ThreadPool`. После того, как мы переключимся в библиотечный ящик, мы также сможем использовать отдельную библиотеку объединения потоков для любой подходящей работы, а не только для обслуживания сетевых-запросов. -Создайте файл *src/lib.rs*, который содержит следующий код, который является простейшим определением устройства `ThreadPool`, которое мы можем иметь на данный мгновение: +Создайте файл *src/lib.rs*, который содержит следующий рукопись, который является простейшим определением устройства `ThreadPool`, которое мы можем иметь на данный мгновение: Файл: src/lib.rs @@ -96,7 +96,7 @@ {{#rustdoc_include ../listings/ch20-web-server/no-listing-01-define-threadpool-struct/src/lib.rs}} ``` -Затем изменените файл *main.rs*, чтобы внести `ThreadPool` из библиотечного ящика в текущую область видимости, добавив следующий код в начало *src/main.rs*: +Затем изменените файл *main.rs*, чтобы внести `ThreadPool` из библиотечного ящика в текущую область видимости, добавив следующий рукопись в начало *src/main.rs*: Файл: src/main.rs @@ -104,7 +104,7 @@ {{#rustdoc_include ../listings/ch20-web-server/no-listing-01-define-threadpool-struct/src/main.rs:here}} ``` -Этот код по-прежнему не будет работать, но давайте проверим его ещё раз, чтобы получить следующую ошибку, которую нам нужно устранить: +Этот рукопись по-прежнему не будет работать, но давайте проверим его ещё раз, чтобы получить следующую ошибку, которую нам нужно устранить: ```console {{#include ../listings/ch20-web-server/no-listing-01-define-threadpool-struct/output.txt}} @@ -120,7 +120,7 @@ Мы выбираем `usize` в качестве вида свойства `size`, потому что мы знаем, что отрицательное число потоков не имеет никакого смысла. Мы также знаем, что мы будем использовать число 4 в качестве количества элементов в собрания потоков, для чего предназначен вид `usize`, как обсуждалось в разделе ["Целочисленные виды"] главы 3. -Давайте проверим код ещё раз: +Давайте проверим рукопись ещё раз: ```console {{#include ../listings/ch20-web-server/no-listing-02-impl-threadpool-new/output.txt}} @@ -128,7 +128,7 @@ Теперь мы ошибка возникает из-за того, что у нас нет способа `execute` в устройстве `ThreadPool`. Вспомните раздел ["Создание конечного числа потоков"](#creating-a-finite-number-of-threads), в котором мы решили, что наш объединениепотоков должен иметь внешнюю оболочку, похожий на `thread::spawn`. Кроме того, мы выполняем функцию `execute`, чтобы она принимала замыкание и передавала его свободному потоку из объединения для запуска. -Мы определим способ `execute` у `ThreadPool`, принимающий замыкание в качестве свойства. Вспомните из раздела ["Перемещение захваченных значений из замыканий и особенности `Fn`"](ch13-01-closures.html#moving-captured-values-out-of-the-closure-and-the-fn-traits) главы 13 сведения о том, что мы можем принимать замыкания в качестве свойств тремя различными особенностями: `Fn` , `FnMut` и `FnOnce`. Нам нужно решить, какой вид замыкания использовать здесь. Мы знаем, что в конечном счёте мы сделаем что-то похожее на выполнение встроенной библиотеки `thread::spawn`, поэтому мы можем посмотреть, какие ограничения накладывает на свой свойство ярлык функции `thread::spawn`. Документация показывает следующее: +Мы определим способ `execute` у `ThreadPool`, принимающий замыкание в качестве свойства. Вспомните из раздела ["Перемещение захваченных значений из замыканий и особенности `Fn`"](ch13-01-closures.html#moving-captured-values-out-of-the-closure-and-the-fn-traits) главы 13 сведения о том, что мы можем принимать замыкания в качестве свойств тремя различными особенностями: `Fn` , `FnMut` и `FnOnce`. Нам нужно решить, какой вид замыкания использовать здесь. Мы знаем, что в конечном счёте мы сделаем что-то похожее на выполнение встроенной библиотеки `thread::spawn`, поэтому мы можем посмотреть, какие ограничения накладывает на свой свойство ярлык функции `thread::spawn`. Пособие показывает следующее: ```rust,ignore pub fn spawn(f: F) -> JoinHandle @@ -138,7 +138,7 @@ pub fn spawn(f: F) -> JoinHandle T: Send + 'static, ``` -Свойство вида `F` - это как раз то, что нас важно; свойство вида `T` относится к возвращаемому значению и нам он не важен. Можно увидеть, что `spawn` использует `FnOnce` в качестве ограничения особенности у `F`. Возможно это как раз то, чего мы хотим, так как в конечном итоге мы передадим полученный в `execute` переменная в функцию `spawn`. Дополнительную уверенность в том, что `FnOnce` - это именно тот особенность, который мы хотим использовать, нам даётобстоятельство, что поток для выполнения запроса будет выполнять замыкание этого запроса только один раз, что соответствует части `Once` ("единожды") в названии особенности `FnOnce`. +Свойство вида `F` - это как раз то, что нас важно; свойство вида `T` относится к возвращаемому значению и нам он не важен. Можно увидеть, что `spawn` использует `FnOnce` в качестве ограничения особенности у `F`. Возможно это как раз то, чего мы хотим, так как в конечном итоге мы передадим полученный в `execute` переменная в функцию `spawn`. Дополнительную уверенность в том, что `FnOnce` - это именно тот особенность, который мы хотим использовать, нам даёт обстоятельство, что поток для выполнения запроса будет выполнять замыкание этого запроса только один раз, что соответствует части `Once` ("единожды") в названии особенности `FnOnce`. Свойство вида `F` также имеет ограничение особенности `Send` и ограничение времени жизни `'static`, которые полезны в нашей случаи: нам нужен `Send` для передачи замыкания из одного потока в другой и `'static`, потому что мы не знаем, сколько времени поток будет выполняться. Давайте создадим способ `execute` для `ThreadPool`, который будет принимать обобщённый свойство вида `F` со следующими ограничениями: @@ -150,19 +150,19 @@ pub fn spawn(f: F) -> JoinHandle Мы по-прежнему используем `()` после `FnOnce` потому что особенность `FnOnce` представляет замыкание, которое не принимает свойств и возвращает единичный вид `()`. Также как и при определении функций, вид возвращаемого значения в ярлыке может быть опущен, но даже если у нас нет свойств, нам все равно нужны скобки. -Опять же, это самая простая выполнение способа `execute`: она ничего не делает, мы просто пытаемся сделать код собираемым. Давайте проверим снова: +Опять же, это самая простая выполнение способа `execute`: она ничего не делает, мы просто пытаемся сделать рукопись собираемым. Давайте проверим снова: ```console {{#include ../listings/ch20-web-server/no-listing-03-define-execute/output.txt}} ``` -Сейчас мы получаем только предупреждения, что означает, что код собирается! Но обратите внимание, если вы попробуете `cargo run` и сделаете запрос в браузере, вы увидите ошибки в браузере, которые мы видели в начале главы. Наша библиотека на самом деле ещё не вызывает замыкание, переданное в `execute`! +Сейчас мы получаем только предупреждения, что означает, что рукопись собирается! Но обратите внимание, если вы попробуете `cargo run` и сделаете запрос в обозревателе, вы увидите ошибки в обозревателе, которые мы видели в начале главы. Наша библиотека на самом деле ещё не вызывает замыкание, переданное в `execute`! -> Примечание: вы возможно слышали высказывание о языках со строгими сборщиками, таких как Haskell и Rust, которое звучит так: «Если код собирается, то он работает». Но это высказывание не всегда верно. Наш дело собирается, но абсолютно ничего не делает! Если бы мы создавали существующий, законченный дело, это был бы хороший мгновение начать писать состоящие из звеньев проверки, чтобы проверять, что код собирается *и* имеет желаемое поведение. +> Примечание: вы возможно слышали высказывание о языках со строгими сборщиками, таких как Haskell и Ржавчина, которое звучит так: «Если рукопись собирается, то он работает». Но это высказывание не всегда верно. Наш дело собирается, но безусловно ничего не делает! Если бы мы создавали существующий, законченный дело, это был бы хороший мгновение начать писать состоящие из звеньев проверки, чтобы проверять, что рукопись собирается *и* имеет желаемое поведение. #### Проверка количества потоков в `new` -Мы ничего не делаем с свойствами `new` и `execute`. Давайте выполняем тела этих функций с нужным нам поведением. Для начала давайте подумаем о `new`. Ранее мы выбрали беззнаковый вид для свойства `size`, потому что объединениес отрицательным числом потоков не имеет смысла. Объединение с нулём потоков также не имеет смысла, однако ноль - это вполне допустимое значение `usize`. Мы добавим код для проверки того, что `size` больше нуля, прежде чем вернуть образец `ThreadPool`, и заставим программу паниковать, если она получит ноль, используя макрос `assert!`, как показано в приложении 20-13. +Мы ничего не делаем с свойствами `new` и `execute`. Давайте выполняем тела этих функций с нужным нам поведением. Для начала давайте подумаем о `new`. Ранее мы выбрали беззнаковый вид для свойства `size`, потому что объединениес отрицательным числом потоков не имеет смысла. Объединение с нулём потоков также не имеет смысла, однако ноль - это вполне допустимое значение `usize`. Мы добавим рукопись для проверки того, что `size` больше нуля, прежде чем вернуть образец `ThreadPool`, и заставим программу вызвать сбой, если она получит ноль, используя макрос `assert!`, как показано в приложении 20-13. Файл: src/lib.rs @@ -172,7 +172,7 @@ pub fn spawn(f: F) -> JoinHandle Приложение 20-13: Выполнение ThreadPool::new с со сбоем завершениям работы, если size равен нулю -Мы добавили немного документации для нашей устройства `ThreadPool` с помощью примечаниев. Обратите внимание, что мы следовали хорошим применением документирования, добавив раздел, в котором указывается случаей, при которой функция может со сбоем завершаться, как это обсуждалось в главе 14. Попробуйте запустить `cargo doc --open` и кликнуть на устройство `ThreadPool`, чтобы увидеть как выглядит созданная документация для `new`! +Мы добавили немного пособия для нашей устройства `ThreadPool` с помощью примечаниев. Обратите внимание, что мы следовали хорошим применением документирования, добавив раздел, в котором указывается случай, при которой функция может со сбоем завершаться, как это обсуждалось в главе 14. Попробуйте запустить `cargo doc --open` и кликнуть на устройство `ThreadPool`, чтобы увидеть как выглядит созданная пособие для `new`! Вместо добавления макроса `assert!`, как мы здесь сделали, мы могли бы преобразовать функцию `new` в функцию `build` таким образом, чтобы она возвращала `Result` , подобно тому, как мы делали в функции `Config::new` дела ввода/вывода в приложении 12-9. Но в данном случае мы решили, что попытка создания объединения потоков без указания хотя бы одного потока должна быть непоправимой ошибкой. Если вы чувствуете такое стремление, попробуйте написать функцию `build` с ярлыком ниже, для сравнения с функцией `new`: @@ -194,7 +194,7 @@ pub fn spawn(f: F) -> JoinHandle Функция `spawn` возвращает вид `JoinHandle`, где `T` является видом, который возвращает замыкание. Давайте попробуем использовать `JoinHandle` и посмотрим, что произойдёт. В нашем случае замыкания, которые мы передаём объединению потоков, будут обрабатывать соединение и не будут возвращать ничего, поэтому `T` будет единичным (unit) видом `()`. -Код в приложении 20-14 собирается, но пока не создаст ни одного потока. Мы изменили определение `ThreadPool` так, чтобы он содержал вектор образцов `thread::JoinHandle<()>`, объявляли вектор ёмкостью `size`, установили цикл `for`, который будет выполнять некоторый код для создания потоков, и вернули образец `ThreadPool`, содержащий их. +Рукопись в приложении 20-14 собирается, но пока не создаст ни одного потока. Мы изменили определение `ThreadPool` так, чтобы он содержал вектор образцов `thread::JoinHandle<()>`, объявляли вектор ёмкостью `size`, установили круговорот `for`, который будет выполнять некоторый рукопись для создания потоков, и вернули образец `ThreadPool`, содержащий их. Файл: src/lib.rs @@ -206,26 +206,26 @@ pub fn spawn(f: F) -> JoinHandle Мы включили `std::thread` в область видимости библиотечного ящика, потому что мы используем `thread::JoinHandle` в качестве вида элементов вектора в `ThreadPool`. -После получения правильного значения size, наш `ThreadPool` создаёт новый вектор, который может содержать `size` элементов. Функция `with_capacity` выполняет ту же задачу, что и `Vec::new`, но с важным отличием: она заранее выделяет необходимый объём памяти в векторе. Поскольку мы знаем, что нам нужно хранить `size` элементов в векторе, предварительное выделение памяти для этих элементов будет немного более эффективным, чем использование `Vec::new`, при котором размер вектора будет увеличиваться по мере вставки элементов. +После получения правильного значения size, наш `ThreadPool` создаёт новый вектор, который может содержать `size` элементов. Функция `with_capacity` выполняет ту же задачу, что и `Vec::new`, но с важным отличием: она заранее выделяет необходимый объём памяти в векторе. Поскольку мы знаем, что нам нужно хранить `size` элементов в векторе, предварительное выделение памяти для этих элементов будет немного более производительным, чем использование `Vec::new`, при котором размер вектора будет увеличиваться по мере вставки элементов. Если вы снова запустите приказ `cargo check`, она должна завершиться успешно. -#### Устройства `Worker`, ответственная за отправку кода из `ThreadPool` в поток +#### Устройства `Worker`, ответственная за отправку рукописи из `ThreadPool` в поток -Мы целенаправленно оставили примечание в цикле `for` в Приложении 20-14 по поводу создания потоков. Сейчас мы разберёмся, как на самом деле создаются потоки. Обычная библиотека предоставляет `thread::spawn` для создания потоков, причём `thread::spawn` ожидает получить некоторый код, который поток должен выполнить, как только он будет создан. Однако в нашем случае мы хотим создавать потоки и заставлять их *ожидать* код, который мы будем передавать им позже. Выполнение потоков в встроенной библиотеке не предоставляет никакого способа сделать это, мы должны выполнить это вручную. +Мы целенаправленно оставили примечание в круговороте `for` в Приложении 20-14 по поводу создания потоков. Сейчас мы разберёмся, как на самом деле создаются потоки. Обычная библиотека предоставляет `thread::spawn` для создания потоков, причём `thread::spawn` ожидает получить некоторый рукопись, который поток должен выполнить, как только он будет создан. Однако в нашем случае мы хотим создавать потоки и заставлять их *ожидать* рукопись, который мы будем передавать им позже. Выполнение потоков в встроенной библиотеке не предоставляет никакого способа сделать это, мы должны выполнить это вручную. -Мы будем выполнить это поведение, добавив новую устройство данных между `ThreadPool` и потоками, которая будет управлять этим новым поведением. Мы назовём эту устройство Worker ("работник"), это общепринятое имя в выполнения объединений. Работник берёт код, который нужно выполнить, и запускает этот код внутри рабочего потока. Представьте людей, работающих на кухне ресторана: работники ожидают, пока не поступят заказы от клиентов, а затем они несут ответственность за принятие этих заказов и их выполнение. +Мы будем выполнить это поведение, добавив новую устройство данных между `ThreadPool` и потоками, которая будет управлять этим новым поведением. Мы назовём эту устройство Worker ("работник"), это общепринятое имя в выполнения объединений. Работник берёт рукопись, который нужно выполнить, и запускает этот рукопись внутри рабочего потока. Представьте людей, работающих на кухне ресторана: работники ожидают, пока не поступят заказы от конечных потребителей, а затем они несут ответственность за принятие этих заказов и их выполнение. -Вместо того чтобы хранить вектор образцов `JoinHandle<()>` в объединении потоков, мы будем хранить образцы устройства `Worker`. Каждый `Worker` будет хранить один образец `JoinHandle<()>`. Затем мы выполняем способ у `Worker`, который будет принимать замыкание и отправлять его в существующий поток для выполнения. Для того чтобы мы могли различать работники в объединении при логировании или отладке, мы также присвоим каждому работнику `id`. +Вместо того чтобы хранить вектор образцов `JoinHandle<()>` в объединении потоков, мы будем хранить образцы устройства `Worker`. Каждый `Worker` будет хранить один образец `JoinHandle<()>`. Затем мы выполняем способ у `Worker`, который будет принимать замыкание и отправлять его в существующий поток для выполнения. Для того чтобы мы могли различать работники в объединении при записей действий или отладке, мы также присвоим каждому работнику `id`. -Вот как выглядит новая последовательность действий, которые будут происходить при создании `ThreadPool`. Мы выполняем код, который будет отправлять замыкание в поток, после того, как у нас будет `Worker` , заданный следующим образом: +Вот как выглядит новая последовательность действий, которые будут происходить при создании `ThreadPool`. Мы выполняем рукопись, который будет отправлять замыкание в поток, после того, как у нас будет `Worker` , заданный следующим образом: 1. Определим устройство `Worker`, которая содержит `id` и `JoinHandle<()>`. 2. Изменим `ThreadPool`, чтобы он содержал вектор образцов `Worker`. 3. Определим функцию `Worker::new`, которая принимает номер `id` и возвращает образец `Worker`, который содержит `id` и поток, порождённый с пустым замыканием. -4. В `ThreadPool::new` используем счётчик цикла `for` для создания `id`, создаём новый `Worker` с этим `id` и сохраняем образец "работника" в вектор. +4. В `ThreadPool::new` используем счётчик круговорота `for` для создания `id`, создаём новый `Worker` с этим `id` и сохраняем образец "работника" в вектор. -Если вы готовы принять вызов, попробуйте выполнить эти изменения самостоятельно, не глядя на код в приложении 20-15. +Если вы готовы принять вызов, попробуйте выполнить эти изменения самостоятельно, не глядя на рукопись в приложении 20-15. Готовы? Вот приложение 20-15 с одним из способов сделать указанные ранее изменения. @@ -237,22 +237,22 @@ pub fn spawn(f: F) -> JoinHandle Приложение 20-15: Изменение ThreadPool для хранения образцов Worker вместо непосредственного хранения потоков -Мы изменили название поля в `ThreadPool` с `threads` на `workers`, поскольку теперь оно содержит образцы `Worker` вместо образцов `JoinHandle<()>`. Мы используем счётчик в цикле `for` для передачи цифрового определителя в качестве переменной `Worker::new`, и сохраняем каждый новый `Worker` в векторе с именем `workers`. +Мы изменили название поля в `ThreadPool` с `threads` на `workers`, поскольку теперь оно содержит образцы `Worker` вместо образцов `JoinHandle<()>`. Мы используем счётчик в круговороте `for` для передачи цифрового определителя в качестве переменной `Worker::new`, и сохраняем каждый новый `Worker` в векторе с именем `workers`. -Внешний код (вроде нашего сервера в *src/bin/main.rs*) не обязательно должен знать подробности выполнения, касающиеся использования устройства `Worker` внутри `ThreadPool`, поэтому мы делаем устройство `Worker` и её функцию `new` закрытыми. Функция `Worker::new` использует заданный нами `id` и сохраняет образец `JoinHandle<()>`, который создаётся при порождении нового потока с пустым замыканием. +Внешний рукопись (вроде нашего отделенного вычислителя в *src/bin/main.rs*) не обязательно должен знать подробности выполнения, касающиеся использования устройства `Worker` внутри `ThreadPool`, поэтому мы делаем устройство `Worker` и её функцию `new` закрытыми. Функция `Worker::new` использует заданный нами `id` и сохраняет образец `JoinHandle<()>`, который создаётся при порождении нового потока с пустым замыканием. -> Примечание: Если операционная система не может создать поток из-за нехватки системных ресурсов, `thread::spawn` со сбоем завершится. Это приведёт к со сбоемму завершению нашего сервера целиком, даже если некоторые потоки были созданы успешно. Для простоты будем считать, что нас устраивает такое поведение, но в существующей выполнения объединения потоков вы, вероятно, захотите использовать [`std::thread::Builder`] и его способ [`spawn`], который вместо этого возвращает `Result` . +> Примечание: Если операционная система не может создать поток из-за нехватки системных мощностей, `thread::spawn` со сбоем завершится. Это приведёт к со сбоемму завершению нашего отделенного вычислителя целиком, даже если некоторые потоки были созданы успешно. Для простоты будем считать, что нас устраивает такое поведение, но в существующей выполнения объединения потоков вы, вероятно, захотите использовать [`std::thread::Builder`] и его способ [`spawn`], который вместо этого возвращает `Result` . > -Этот код собирается и будет хранить количество образцов `Worker`, которое мы указали в качестве переменной функции `ThreadPool::new`. Но мы всё *ещё* не обрабатываем замыкание, которое мы получаем в способе `execute`. Давайте посмотрим, как это сделать далее. +Этот рукопись собирается и будет хранить количество образцов `Worker`, которое мы указали в качестве переменной функции `ThreadPool::new`. Но мы всё *ещё* не обрабатываем замыкание, которое мы получаем в способе `execute`. Давайте посмотрим, как это сделать далее. #### Отправка запросов в потоки через потоки -Следующая неполадка, с которой мы будем бороться, заключается в том, что замыкания, переданные в `thread::spawn` абсолютно ничего не делают. Сейчас мы получаем замыкание, которое хотим выполнить, в способе `execute`. Но мы должны передать какое-то замыкание в способ `thread::spawn`, при создании каждого `Worker` во время создания `ThreadPool`. +Следующая неполадка, с которой мы будем бороться, заключается в том, что замыкания, переданные в `thread::spawn` безусловно ничего не делают. Сейчас мы получаем замыкание, которое хотим выполнить, в способе `execute`. Но мы должны передать какое-то замыкание в способ `thread::spawn`, при создании каждого `Worker` во время создания `ThreadPool`. -Мы хотим, чтобы вновь созданные устройства `Worker` извлекали код для запуска из очереди, хранящейся в `ThreadPool` и отправляли этот код в свой поток для выполнения. +Мы хотим, чтобы вновь созданные устройства `Worker` извлекали рукопись для запуска из очереди, хранящейся в `ThreadPool` и отправляли этот рукопись в свой поток для выполнения. -потоки (channels), простой способ связи между двумя потоками, с которыми мы познакомились в главе 16, кажется наилучше подойдут для этого сценария. Мы будем использовать поток в качестве очереди заданий, а приказ `execute` отправит задание из `ThreadPool` образцам Worker, которые будут отправлять задание в свой поток. Расчет таков: +потоки (channels), простой способ связи между двумя потоками, с которыми мы познакомились в главе 16, кажется наилучше подойдут для этого задумки. Мы будем использовать поток в качестве очереди заданий, а приказ `execute` отправит задание из `ThreadPool` образцам Worker, которые будут отправлять задание в свой поток. Расчет таков: 1. `ThreadPool` создаст поток и будет хранить отправитель. 2. Каждый `Worker` будет хранить приёмник. @@ -270,9 +270,9 @@ pub fn spawn(f: F) -> JoinHandle Приложение 20-16: Изменение ThreadPool для хранения отправляющей части потока, который отправляет образцы Job -В `ThreadPool::new` мы создаём наш новый поток и сохраняем в объединении его отправляющую сторону. Код успешно собирается. +В `ThreadPool::new` мы создаём наш новый поток и сохраняем в объединении его отправляющую сторону. Рукопись успешно собирается. -Давайте попробуем передавать принимающую сторону потока каждому "работнику" (устройстве Worker), когда объединениепотоков создаёт поток. Мы знаем, что хотим использовать получающую часть потока в потоке, порождаемым "работником", поэтому мы будем ссылаться на свойство `receiver` в замыкании. Код 20-17 пока не собирается. +Давайте попробуем передавать принимающую сторону потока каждому "работнику" (устройстве Worker), когда объединениепотоков создаёт поток. Мы знаем, что хотим использовать получающую часть потока в потоке, порождаемым "работником", поэтому мы будем ссылаться на свойство `receiver` в замыкании. Рукопись 20-17 пока не собирается. Файл: src/lib.rs @@ -284,13 +284,13 @@ pub fn spawn(f: F) -> JoinHandle Мы внесли несколько небольших и простых изменений: мы передаём принимающую часть потока в `Worker::new`, а затем используем его внутри замыкания. -При попытке проверить код, мы получаем ошибку: +При попытке проверить рукопись, мы получаем ошибку: ```console {{#include ../listings/ch20-web-server/listing-20-17/output.txt}} ``` -Код пытается передать `receiver` нескольким образцам `Worker`. Это не сработает, поскольку, как вы можете помнить из главы 16: выполнение потока, которую предоставляет Ржавчина - несколько *производителей*, один *потребитель*. Это означает, что мы не можем просто клонировать принимающую сторону потока, чтобы исправить этот код. Кроме этого, мы не хотим отправлять одно и то же сообщение нескольким потребителям, поэтому нам нужен единый список сообщений для множества обработчиков, чтобы каждое сообщение обрабатывалось лишь один раз. +Рукопись пытается передать `receiver` нескольким образцам `Worker`. Это не сработает, поскольку, как вы можете помнить из главы 16: выполнение потока, которую предоставляет Ржавчина - несколько *производителей*, один *потребитель*. Это означает, что мы не можем просто клонировать принимающую сторону потока, чтобы исправить этот рукопись. Кроме этого, мы не хотим отправлять одно и то же сообщение нескольким потребителям, поэтому нам нужен единый список сообщений для множества обработчиков, чтобы каждое сообщение обрабатывалось лишь один раз. Кроме того, удаление задачи из очереди потока включает изменение `receiver`, поэтому потокам необходим безопасный способ делиться и изменять `receiver`, в противном случае мы можем получить условия гонки (как описано в главе 16). @@ -306,11 +306,11 @@ pub fn spawn(f: F) -> JoinHandle В `ThreadPool::new` мы помещаем принимающую сторону потока внутрь `Arc` и `Mutex`. Для каждого нового "работника" мы клонируем `Arc`, чтобы увеличить счётчик ссылок так, что "работники" могут разделять владение принимающей стороной потока. -С этими изменениями код собирается! Мы подбираемся к цели! +С этими изменениями рукопись собирается! Мы подбираемся к цели! #### Выполнение способа `execute` -Давайте выполняем наконец способ `execute` у устройства `ThreadPool`. Мы также изменим вид `Job` со устройства на псевдоним вида для особенность-предмета. который будет содержать вид замыкания, принимаемый способом `execute`. Как описано в разделе ["Создание родственных вида с помощью псевдонимов типа"](ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases) главы 19, псевдонимы видов позволяют делать длинные виды короче, облегчая их использование. Посмотрите на приложение 20-19. +Давайте выполняем наконец способ `execute` у устройства `ThreadPool`. Мы также изменим вид `Job` со устройства на псевдоним вида для особенность-предмета. который будет содержать вид замыкания, принимаемый способом `execute`. Как описано в разделе ["Создание родственных вида с помощью псевдонимов вида"](ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases) главы 19, псевдонимы видов позволяют делать длинные виды короче, облегчая их использование. Посмотрите на приложение 20-19. Файл: src/lib.rs @@ -322,7 +322,7 @@ pub fn spawn(f: F) -> JoinHandle После создания нового образца `Job` с замыканием, полученным в `execute`, мы посылаем его через отправляющий конец потока. На тот случай, если отправка не удастся, вызываем `unwrap` у `send`. Это может произойти, например, если мы остановим выполнение всех наших потоков, что означает, что принимающая сторона прекратила получать новые сообщения. На данный мгновение мы не можем остановить выполнение наших потоков: наши потоки будут исполняться до тех пор, пока существует объединение Причина, по которой мы используем `unwrap`, заключается в том, что, хотя мы знаем, что сбой не произойдёт, сборщик этого не знает. -Но мы ещё не закончили! В "работнике" (worker) наше замыкание, переданное в `thread::spawn` все ещё *ссылается* только на принимающую сторону потока. Вместо этого нам нужно, чтобы замыкание работало в бесконечном цикле, запрашивая задание у принимающей части потока и выполняя задание, когда оно принято. Давайте внесём изменения, показанные в приложении 20-20 внутри `Worker::new`. +Но мы ещё не закончили! В "работнике" (worker) наше замыкание, переданное в `thread::spawn` все ещё *ссылается* только на принимающую сторону потока. Вместо этого нам нужно, чтобы замыкание работало в бесконечном круговороте, запрашивая задание у принимающей части потока и выполняя задание, когда оно принято. Давайте внесём изменения, показанные в приложении 20-20 внутри `Worker::new`. Файл: src/lib.rs @@ -332,11 +332,11 @@ pub fn spawn(f: F) -> JoinHandle Приложение 20-20: Получение и выполнение заданий в потоке "работника" -Здесь мы сначала вызываем `lock` у `receiver`, чтобы получить мьютекс, а затем вызываем `unwrap`, чтобы со сбоем завершить работу при любых ошибках. Захват блокировки может завершиться неудачей, если мьютекс находится в *отравленном* состоянии (poisoned state), что может произойти, если какой-то другой поток завершился со сбоем, удерживая блокировку, вместо снятия блокировки. В этой случаи вызвать `unwrap` для со сбоемго завершения потока вполне оправдано. Не стесняйтесь заменить `unwrap` на `expect` с сообщением об ошибке, которое имеет для вас значение. +Здесь мы сначала вызываем `lock` у `receiver`, чтобы получить взаимное исключение, а затем вызываем `unwrap`, чтобы со сбоем завершить работу при любых ошибках. Захват запрета может завершиться неудачей, если взаимное исключение находится в *отравленном* состоянии (poisoned state), что может произойти, если какой-то другой поток завершился со сбоем, удерживая запрет, вместо снятия запрета. В этой случаи вызвать `unwrap` для со сбоем завершения потока вполне оправдано. Не стесняйтесь заменить `unwrap` на `expect` с сообщением об ошибке, которое имеет для вас значение. -Если мы получили блокировку мьютекса, мы вызываем `recv`, чтобы получить `Job` из потока. Последний вызов `unwrap` позволяет миновать любые ошибки, которые могут возникнуть, если поток, управляющий отправитель, прекратил исполняться, подобно тому, как способ `send` возвращает `Err`, если получатель не принимает сообщение. +Если мы получили запрет взаимного исключения, мы вызываем `recv`, чтобы получить `Job` из потока. Последний вызов `unwrap` позволяет миновать любые ошибки, которые могут возникнуть, если поток, управляющий отправитель, прекратил исполняться, подобно тому, как способ `send` возвращает `Err`, если получатель не принимает сообщение. -Вызов `recv` - блокирующий, поэтому пока задач нет, текущий поток будет ждать, пока задача не появится. `Mutex` заверяет, что только один поток `Worker` за раз попытается запросить задачу. +Вызов `recv` - запрещающий, поэтому пока задач нет, текущий поток будет ждать, пока задача не появится. `Mutex` заверяет, что только один поток `Worker` за раз попытается запросить задачу. Наш объединениепотоков теперь находится в рабочем состоянии! Выполните `cargo run` и сделайте несколько запросов: @@ -385,11 +385,11 @@ Worker 0 got a job; executing. Worker 2 got a job; executing. ``` -Успех! Теперь у нас есть объединениепотоков, который обрабатывает соединения не согласованно. Никогда не создаётся более четырёх потоков, поэтому наша система не будет перегружена, если сервер получит много запросов. Если мы отправим запрос ресурса */sleep*, сервер сможет обслуживать другие запросы, обрабатывая их в другом потоке. +Успех! Теперь у нас есть объединениепотоков, который обрабатывает соединения не согласованно. Никогда не создаётся более четырёх потоков, поэтому наша система не будет перегружена, если отделеный вычислитель получит много запросов. Если мы отправим запрос источника */sleep*, отделеный вычислитель сможет обслуживать другие запросы, обрабатывая их в другом потоке. -> Примечание: если вы запросите */sleep* в нескольких окнах браузера одновременно, они могут загружаться по одному, с интервалами в 5 секунд. Некоторые веб-браузеры выполняют несколько образцов одного и того же запроса последовательно из-за кэширования. Такое ограничение не связано с работой нашего веб-сервера. +> Примечание: если вы запросите */sleep* в нескольких окнах обозревателя одновременно, они могут загружаться по одному, с интервалами в 5 секунд. Некоторые сетевые-обозреватели выполняют несколько образцов одного и того же запроса последовательно из-за кэширования. Такое ограничение не связано с работой нашего сетевого-отделенного вычислителя. -После изучения цикла `while let` в главе 18 вы можете удивиться, почему мы не написали код рабочего потока (worker thread), как показано в приложении 20-22. +После изучения круговорота `while let` в главе 18 вы можете удивиться, почему мы не написали рукопись рабочего потока (worker thread), как показано в приложении 20-22. Файл: src/lib.rs @@ -399,9 +399,9 @@ Worker 2 got a job; executing. Приложение 20-22: Иная выполнение Worker::new с использованием while let -Этот код собирается и запускается, но не даёт желаемого поведения: медленный запрос всё равно приведёт к тому, что другие запросы будут ждать обработки. Причина здесь несколько тоньше: устройства `Mutex` не имеет открытого способа `unlock`, так как владение блокировкой основано на времени жизни `MutexGuard` внутри `LockResult>`, которое возвращает способ `lock`. Во время сборки анализатор заимствований может проследить за выполнением правила, согласно которому к ресурсу, охраняемому `Mutex`, нельзя получить доступ пока мы удерживаем блокировку. Однако в этой выполнение мы также можем получить случай, когда блокировка будет удерживаться дольше, чем предполагалось, если мы не будем внимательно учитывать время жизни `MutexGuard`. +Этот рукопись собирается и запускается, но не даёт желаемого поведения: медленный запрос всё равно приведёт к тому, что другие запросы будут ждать обработки. Причина здесь несколько тоньше: устройства `Mutex` не имеет открытого способа `unlock`, так как владение запретом основано на времени жизни `MutexGuard` внутри `LockResult>`, которое возвращает способ `lock`. Во время сборки оценщик заимствований может проследить за выполнением правила, согласно которому к источнику, охраняемому `Mutex`, нельзя получить доступ пока мы удерживаем запрет. Однако в этой выполнение мы также можем получить случай, когда запрет будет удерживаться дольше, чем предполагалось, если мы не будем внимательно учитывать время жизни `MutexGuard`. -Код в приложении 20-20, использующий `let job = receiver.lock().unwrap().recv().unwrap();` работает, потому что при использовании `let` любые промежуточные значения, используемые в выражении справа от знака равенства, немедленно уничтожаются после завершения указания `let`. Однако `while let` (и `if let` и `match`) не удаляет временные значения до конца связанного раздела. Таким образом, в приложении 20-21 блокировка не снимается в течение всего времени вызова `job()`, что означает, что другие работники не могут получать задания. +Рукопись в приложении 20-20, использующий `let job = receiver.lock().unwrap().recv().unwrap();` работает, потому что при использовании `let` любые промежуточные значения, используемые в выражении справа от знака равенства, немедленно уничтожаются после завершения указания `let`. Однако `while let` (и `if let` и `match`) не удаляет временные значения до конца связанного раздела. Таким образом, в приложении 20-21 запрет не снимается в течение всего времени вызова `job()`, что означает, что другие работники не могут получать задания. ["Целочисленные виды"]: ch03-02-data-types.html#integer-types diff --git a/rustbook-ru/src/ch20-02-slow-requests.md b/rustbook-ru/src/ch20-02-slow-requests.md index 31a548d2d..bf0e7cef7 100644 --- a/rustbook-ru/src/ch20-02-slow-requests.md +++ b/rustbook-ru/src/ch20-02-slow-requests.md @@ -1,6 +1,6 @@ ## Как медленные запросы влияют на пропускную способность -Сейчас наш сервер обрабатывает каждый запрос по очереди. Это работает для систем +Сейчас наш отделеный вычислитель обрабатывает каждый запрос по очереди. Это работает для систем с небольшой загрузкой (которая получает не очень много запросов), но как только приложения становятся более сложными, такая выполнение уже не будет разумной. @@ -11,11 +11,11 @@ завершен, даже если новый запрос может быть обработан быстро. Давайте посмотрим на это в действии. -### Подражание медленного запроса в выполнения текущего сервера +### Подражание медленного запроса в выполнения текущего отделенного вычислителя -Давайте посмотрим на эффект от запроса, который требует много времени для обработки. -В коде 20-10 показано, пример симуляции медленной обработки запроса. Код при ответе -на запрос `/sleep`, сервер "заснёт" на пять секунд. +Давайте посмотрим на итог от запроса, который требует много времени для обработки. +В рукописи 20-10 показано, пример симуляции медленной обработки запроса. Рукопись при ответе +на запрос `/sleep`, отделеный вычислитель "заснёт" на пять секунд. Filename: src/main.rs @@ -48,22 +48,22 @@ fn handle_connection(mut stream: TcpStream) { } ``` -код 20-10: симуляция обработки медленного запроса +рукопись 20-10: симуляция обработки медленного запроса Мы создали особый запрос `sleep`. При выполнении данного запроса будет 5-секундная задержка, перед тем, как отобразиться содержимое файла "hello.html". -Вы можете увидеть в существующем времени, насколько прост наш сервер. В существующих дела +Вы можете увидеть в существующем времени, насколько прост наш отделеный вычислитель. В существующих дела может происходить и более длинная задержка. -Запустите программу приказом `cargo run`, а затем в окне браузеры запросите данные +Запустите программу приказом `cargo run`, а затем в окне обозреватели запросите данные по адресам `http://localhost:8080/` и `http://localhost:8080/sleep`. Если вы запросите данные и строка запроса будет начинаться с `/` даже несколько раз - вы получите быстрый ответ. Но если вы запросите `/sleep` и затем попробуете ещё раз получить -данные стартовой страницы - вы будете ожидать пока `sleep` код функции не закончит +данные стартовой страницы - вы будете ожидать пока `sleep` рукопись функции не закончит ожидания и не приступит к дальнейшей работе. -Существует несколько способов изменить работу нашего веб-сервера, чтобы избежать +Существует несколько способов изменить работу нашего сетевого-отделенного вычислителя, чтобы избежать повторного запроса всех запросов следовавших за медленным запросом. Тот, что мы собираемся выполнить называется объединением потоков. @@ -76,14 +76,14 @@ fn handle_connection(mut stream: TcpStream) { Объединение потоков позволит нам одновременно обрабатывать соединения: мы можем начать обработку нового соединения до завершения старого соединения. Это увеличит -пропускную способность нашего сервера. +пропускную способность нашего отделенного вычислителя. Итак, вот что мы собираемся выполнить: вместо ожидания каждого запроса перед тем, как начать с следующей, мы отправим обработку каждого соединение с другой поток. Потоки будут поступать из объединения , который мы будем создавать после запуска программы на выполнение. Причина, по которой мы ограничиваем число потоков на небольшое число (четыре) - потому, что если бы мы создавали бы -новый поток для каждого запроса, то ресурсы системы были бы быстро израсходованы +новый поток для каждого запроса, то мощности системы были бы быстро израсходованы при увеличении количества запросов. Вместо того, чтобы создавать неограниченное количество потоков, у нас будет определенное @@ -96,8 +96,8 @@ fn handle_connection(mut stream: TcpStream) { длительных запросов, которые мы можем обрабатывать до этого особенности от одного до `N`. Такое решение является одним из способов повысить пропускную способность нашего -веб-сервера. Однако, это книга не о веб-серверах, поэтому мы не будем углубляться +сетевого-отделенного вычислителя. Однако, это книга не о сетевых-отделенных вычислителях, поэтому мы не будем углубляться в сбоев выполнений. Скажем только, что способами увеличения пропускной способности является прообраз fork/join и прообраз однопоточного не согласованного ввода-выводы. Если вас важно эта тема, вы можете больше узнать о ней и попытаться выполнить их -в Rust. Ржавчина является языком низкого уровня и может выполнить все эти подходы. +в Ржавчине. Ржавчина является языком низкого уровня и может выполнить все эти подходы. diff --git a/rustbook-ru/src/ch20-03-designing-the-interface.md b/rustbook-ru/src/ch20-03-designing-the-interface.md index 32b105fb5..7097abc17 100644 --- a/rustbook-ru/src/ch20-03-designing-the-interface.md +++ b/rustbook-ru/src/ch20-03-designing-the-interface.md @@ -1,22 +1,22 @@ ## Создание внешней оболочки объединения потоков -Давайте поговорим о том, как должен выглядеть объединение Авторы часто находят -что при попытке создать некоторый код, напив сначала клиентский внешняя оболочка можно -лучше понять как лучше выполнить серверную часть. Напишите API кода, который будет +Давайте поговорим о том, как должен выглядеть объединение Составители часто находят +что при попытке создать некоторый рукопись, напив сначала конечного потребителя внешнюю оболочка можно +лучше понять как лучше выполнить отделенную вычисляемую часть. Напишите API рукописи, который будет внутренне выстроен таким образом, чтобы его было удобно вызывать, а затем выполните возможность этой устройства, а не наоборот. Подобно тому, как мы использовали Test Driven Development в деле в главе 12, здесь мы собираемся использовать Compiler Driven Development. Мы собираемся написать -код, который вызывает функции, которые мы хотели бы иметь. Ошибки сборки будут +рукопись, который вызывает функции, которые мы хотели бы иметь. Ошибки сборки будут направлять нашу дальнейшую разработку -### Устройства кода при использовании `thread::spawn` +### Устройства рукописи при использовании `thread::spawn` -Первое, мы рассмотрим код, который нам нужно выполнить для создания нового +Первое, мы рассмотрим рукопись, который нам нужно выполнить для создания нового потока. Это не будет окончательным решение, т.к. существует вероятная неполадка -(создание множества потоков), о которой мы говорили ранее. В коде 20-11 показаны -изменения в функции `main`, которые необходимы для создания нового потока в цикле +(создание множества потоков), о которой мы говорили ранее. В рукописи 20-11 показаны +изменения в функции `main`, которые необходимы для создания нового потока в круговороте `for`: Filename: src/main.rs @@ -41,18 +41,18 @@ fn main() { # fn handle_connection(mut stream: TcpStream) {} ``` -код 20-11: Создание нового потока для каждого соединения с клиентом +рукопись 20-11: Создание нового потока для каждого соединения с конечным потребителем Как мы узнали в главе 16, `thread::spawn` создаст новый поток, а затем запустит -код в замыкании. Если вы запустите этот код и загрузите `/sleep` и затем `/` в -двух вкладках браузера, вы действительно увидите, что запрос `/` не будет +рукопись в замыкании. Если вы запустите этот рукопись и загрузите `/sleep` и затем `/` в +двух вкладках обозревателя, вы действительно увидите, что запрос `/` не будет дождаться окончания `/sleep`. Но, как мы уже говорили, это в конечном итоге -будет избыточно расходовать ресурсы системы, так как мы создаем новые потоки +будет избыточно расходовать мощности системы, так как мы создаем новые потоки без ограничений. ### Выполнение подобного внешней оболочки с помощью `ThreadPool` -Мы хотим, чтобы объединениепотоков работал похожим образом. В коде 20-12 заменим +Мы хотим, чтобы объединениепотоков работал похожим образом. В рукописи 20-12 заменим предыдущее решения использование устройства `ThreadPool`: Filename: src/main.rs @@ -84,15 +84,15 @@ fn main() { # fn handle_connection(mut stream: TcpStream) {} ``` -код 20-12: использование `ThreadPool` без выполнения +рукопись 20-12: использование `ThreadPool` без выполнения Мы используем `ThreadPool::new` для создания нового объединения с изменяемым количеством -потоков (в данном случае 4). Далее в цикле `for` мы выполняем `pool.execute` также +потоков (в данном случае 4). Далее в круговороте `for` мы выполняем `pool.execute` также как мы выполняли `thread::spawn`. -### Использование Compiler Driven Development для выполнения рабочего кода +### Использование Compiler Driven Development для выполнения рабочего рукописи -Давайте попробуем собрать данный код. Мы получим следующие ошибки: +Давайте попробуем собрать данный рукопись. Мы получим следующие ошибки: ```text $ cargo check @@ -108,7 +108,7 @@ error: aborting due to previous error ``` Отлично! Нам нужен `ThreadPool`. Давайте вернёмся к дополнению из двоичного файла. -Выполнение `ThreadPool` будет независимой от работы веб-сервера. После того, как +Выполнение `ThreadPool` будет независимой от работы сетевого-отделенного вычислителя. После того, как библиотека выполняющая работу объединения потоков будет написана, мы сможем использовать её в любых выполнениех. @@ -135,7 +135,7 @@ extern crate hello; use hello::ThreadPool; ``` -Далее, попытайтесь теперь проверить соблюдение правил нашего кода, получил следующие +Далее, попытайтесь теперь проверить соблюдение правил нашего рукописи, получил следующие указания сборщика для нас: ```text @@ -193,7 +193,7 @@ current scope ошибку. Выполняем способ `execute`. Если вы помните главу 13, мы можем использовать замыкание в качестве свойства, как в трёх различных особенностях: `Fn`, `FnMut` и `FnOnce`. Какой же особенность нам лучше использовать? Т.к. мы должны выполнить что-то вроде -`thread::spawn` мы можем посмотреть документацию: +`thread::spawn` мы можем посмотреть пособие: ```rust,ignore pub fn spawn(f: F) -> JoinHandle @@ -256,11 +256,11 @@ warning: unused variable: `f`, #[warn(unused_variables)] on by default | ^ ``` -Обратите внимание, что код собирается. Но если вы попытаетесь запустить программу +Обратите внимание, что рукопись собирается. Но если вы попытаетесь запустить программу `cargo run` вы получите ошибки, как в начала нашей главы. Пока наша библиотека не готова к использованию. -> О языках со строгими сборщиками говорят (как о Haskell и Rust), что если -> код собирается - он работает. Очень важно понять, что это всего лишь этап, а -> не конечное решение. Наш код собирается, но он пока ещё ничего не делает. Сейчас -> наступает этап написать проверки, которые бы проверили бы соблюдение правил поведения кода. +> О языках со строгими сборщиками говорят (как о Haskell и Ржавчине), что если +> рукопись собирается - он работает. Очень важно понять, что это всего лишь этап, а +> не конечное решение. Нашу рукопись собирается, но он пока ещё ничего не делает. Сейчас +> наступает этап написать проверки, которые бы проверили бы соблюдение правил поведения рукописи. diff --git a/rustbook-ru/src/ch20-03-graceful-shutdown-and-cleanup.md b/rustbook-ru/src/ch20-03-graceful-shutdown-and-cleanup.md index 05c4fb6d7..d15a0c68b 100644 --- a/rustbook-ru/src/ch20-03-graceful-shutdown-and-cleanup.md +++ b/rustbook-ru/src/ch20-03-graceful-shutdown-and-cleanup.md @@ -1,12 +1,12 @@ ## Мягкое завершение работы и очистка -Приложение 20-20 не согласованно отвечает на запросы с помощью использования объединения потоков, как мы и хотели. Мы получаем некоторые предупреждения про `workers`, `id` и поля `thread`, которые мы не используем напрямую, что напоминает нам о том, что мы не освобождаем все ресурсы. Когда мы используем менее элегантный способ остановки основного потока клавишной сочетанием ctrl-c, все остальные потоки также немедленно останавливаются, даже если они находятся в середине обработки запроса. +Приложение 20-20 не согласованно отвечает на запросы с помощью использования объединения потоков, как мы и хотели. Мы получаем некоторые предупреждения про `workers`, `id` и поля `thread`, которые мы не используем напрямую, что напоминает нам о том, что мы не освобождаем все мощности. Когда мы используем менее элегантный способ остановки основного потока клавишной сочетанием ctrl-c, все остальные потоки также немедленно останавливаются, даже если они находятся в середине обработки запроса. -Далее, выполняем особенность `Drop` для вызова `join` у каждого потока в объединении, чтобы они могли завершить запросы, над которыми они работают, перед закрытием. Затем мы выполняем способ сообщить потокам, что они должны перестать принимать новые запросы и завершить работу. Чтобы увидеть этот код в действии, мы изменим наш сервер так, чтобы он принимал только два запроса, после чего правильно завершал работу объединения потоков. +Далее, выполняем особенность `Drop` для вызова `join` у каждого потока в объединении, чтобы они могли завершить запросы, над которыми они работают, перед закрытием. Затем мы выполняем способ сообщить потокам, что они должны перестать принимать новые запросы и завершить работу. Чтобы увидеть этот рукопись в действии, мы изменим наш отделеный вычислитель так, чтобы он принимал только два запроса, после чего правильно завершал работу объединения потоков. ### Выполнение особенности `Drop` для `ThreadPool` -Давайте начнём с выполнения `Drop` у нашего объединения потоков. Когда объединениеудаляется, все наши потоки должны объединиться (join), чтобы убедиться, что они завершают свою работу. В приложении 20-22 показана первая попытка выполнения `Drop`, код пока не будет работать. +Давайте начнём с выполнения `Drop` у нашего объединения потоков. Когда объединениеудаляется, все наши потоки должны объединиться (join), чтобы убедиться, что они завершают свою работу. В приложении 20-22 показана первая попытка выполнения `Drop`, рукопись пока не будет работать. Файл: src/lib.rs @@ -16,9 +16,9 @@ Приложение 20-22: Присоединение (Joining) каждого потока, когда объединениепотоков выходит из области видимости -Сначала мы пройдёмся по каждому `worker` из объединения потоков. Для этого мы используем `&mut` с `self`, потому что нам нужно иметь возможность изменять `worker`. Для каждого обработчика мы выводим сообщение о том, что он завершает работу, а затем вызываем `join` у потока этого обработчика. Для случаев, когда вызов `join` не удался, мы используем `unwrap`, чтобы заставить Ржавчина запаниковать и перейти в режим грубого завершения работы. +Сначала мы пройдёмся по каждому `worker` из объединения потоков. Для этого мы используем `&mut` с `self`, потому что нам нужно иметь возможность изменять `worker`. Для каждого обработчика мы выводим сообщение о том, что он завершает работу, а затем вызываем `join` у потока этого обработчика. Для случаев, когда вызов `join` не удался, мы используем `unwrap`, чтобы заставить Ржавчина вызвать сбой и перейти в режим грубого завершения работы. -Ошибка получаемая при сборки этого кода: +Ошибка получаемая при сборки этого рукописи: ```console {{#include ../listings/ch20-web-server/listing-20-22/output.txt}} @@ -34,13 +34,13 @@ {{#rustdoc_include ../listings/ch20-web-server/no-listing-04-update-worker-definition/src/lib.rs:here}} ``` -Теперь давайте опираться на сборщик, чтобы найти другие места, которые нужно изменить. Проверяя код, мы получаем две ошибки: +Теперь давайте опираться на сборщик, чтобы найти другие места, которые нужно изменить. Проверяя рукопись, мы получаем две ошибки: ```console {{#include ../listings/ch20-web-server/no-listing-04-update-worker-definition/output.txt}} ``` -Давайте обратимся ко второй ошибке, которая указывает на код в конце `Worker::new`; нам нужно обернуть значение `thread` в исход `Some` при создании нового `Worker`. Внесите следующие изменения, чтобы исправить эту ошибку: +Давайте обратимся ко второй ошибке, которая указывает на рукопись в конце `Worker::new`; нам нужно обернуть значение `thread` в исход `Some` при создании нового `Worker`. Внесите следующие изменения, чтобы исправить эту ошибку: Файл: src/lib.rs @@ -60,9 +60,9 @@ ### Тревожное оповещение потокам прекратить прослушивание получения задач -Теперь, после всех внесённых нами изменений, код собирается без каких-либо предупреждений. Но плохая новость в том, что этот код всё ещё не работает так, как мы этого хотим. Причина заключается в логике замыканий, запускаемых потоками образцов Worker: в данный мгновение мы вызываем join, но это не приводит к завершению потоков, так как они находятся в бесконечном цикле, ожидая новую задачу. Если мы попытаемся удалить ThreadPool в текущей выполнения drop, основной поток навсегда заблокируется в ожидании завершения первого потока из объединения . +Теперь, после всех внесённых нами изменений, рукопись собирается без каких-либо предупреждений. Но плохая новость в том, что этот рукопись всё ещё не работает так, как мы этого хотим. Причина заключается в ходу мыслей замыканий, запускаемых потоками образцов Worker: в данный мгновение мы вызываем join, но это не приводит к завершению потоков, так как они находятся в бесконечном круговороте, ожидая новую задачу. Если мы попытаемся удалить ThreadPool в текущей выполнения drop, основной поток навсегда станет недоступным в ожидании завершения первого потока из объединения . -Чтобы решить эту неполадку, нам нужно будет изменить выполнение `drop` в `ThreadPool`, а затем внести изменения в цикл `Worker` . +Чтобы решить эту неполадку, нам нужно будет изменить выполнение `drop` в `ThreadPool`, а затем внести изменения в круговорот `Worker` . Во-первых, изменим выполнение `drop` `ThreadPool` таким образом, чтобы явно удалять `sender` перед тем, как начнём ожидать завершения потоков. В приложении 20-23 показаны изменения в `ThreadPool` для явного удаления `sender` . Мы используем ту же технику `Option` и `take`, что и с потоком, чтобы переместить `sender` из `ThreadPool`: @@ -74,7 +74,7 @@ Приложение 20-23. Явное удаление sender перед ожиданием завершения рабочих потоков -Удаление `sender` закрывает поток, что указывает на то, что сообщения больше не будут отправляться. Когда это произойдёт, все вызовы `recv`, выполняемые рабочими этапами в бесконечном цикле, вернут ошибку. В приложении 20-24 мы меняем цикл `Worker` для правильного выхода из него в этом случае, что означает, что потоки завершатся, когда выполнение `drop` `ThreadPool` вызовет для них `join`. +Удаление `sender` закрывает поток, что указывает на то, что сообщения больше не будут отправляться. Когда это произойдёт, все вызовы `recv`, выполняемые рабочими этапами в бесконечном круговороте, вернут ошибку. В приложении 20-24 мы меняем круговорот `Worker` для правильного выхода из него в этом случае, что означает, что потоки завершатся, когда выполнение `drop` `ThreadPool` вызовет для них `join`. Файл: src/lib.rs @@ -82,9 +82,9 @@ {{#rustdoc_include ../listings/ch20-web-server/listing-20-24/src/lib.rs:here}} ``` -Приложение 20-24: Явный выход из цикла, когда recv возвращает ошибку +Приложение 20-24: Явный выход из круговорота, когда recv возвращает ошибку -Чтобы увидеть этот код в действии, давайте изменим `main`, чтобы принимать только два запроса, прежде чем правильно завершить работу сервера как показано в приложении 20-25. +Чтобы увидеть этот рукопись в действии, давайте изменим `main`, чтобы принимать только два запроса, прежде чем правильно завершить работу отделенного вычислителя как показано в приложении 20-25. Файл: src/main.rs @@ -92,13 +92,13 @@ {{#rustdoc_include ../listings/ch20-web-server/listing-20-25/src/main.rs:here}} ``` -Код 20-25. Выключение сервера после обслуживания двух запросов с помощью выхода из цикла +Рукопись 20-25. Выключение отделенного вычислителя после обслуживания двух запросов с помощью выхода из круговорота -Вы бы не хотели, чтобы существующий веб-сервер отключался после обслуживания только двух запросов. Этот код всего лишь отображает, что правильное завершение работы и освобождение ресурсов находятся в рабочем состоянии. +Вы бы не хотели, чтобы существующий сетевой-отделеный вычислитель отключался после обслуживания только двух запросов. Этот рукопись всего лишь отображает, что правильное завершение работы и освобождение мощностей находятся в рабочем состоянии. Способ `take` определён в особенности `Iterator` и ограничивает повторение самое большее первыми двумя элементами. `ThreadPool` выйдет из области видимости в конце `main` и будет запущена его выполнение `drop`. -Запустите сервер с `cargo run` и сделайте три запроса. Третий запрос должен выдать ошибку и в окне вызова вы должны увидеть вывод, подобный следующему: +Запустите отделеный вычислитель с `cargo run` и сделайте три запроса. Третий запрос должен выдать ошибку и в окне вызова вы должны увидеть вывод, подобный следующему: для установки или обновления Rust. +В этой исполнения учебника предполагается, что вы используете Ржавчина 1.67.1 (выпущен 09.02.2023) или новее. См. [раздел «Установка» главы 1] для установки или обновления Ржавчина. -HTML-исполнение книги доступна онлайн по адресам [https://doc.rust-lang.org/stable/book/](https://doc.rust-lang.org/stable/book/)(англ.) и [https://doc.rust-lang.ru/book](https://doc.rust-lang.ru/book)(рус.) и офлайн. При установке Ржавчина с помощью `rustup`: просто запустите `rustup docs --book`, чтобы её открыть. +HTML-исполнение книги доступна в сети по адресам [https://doc.rust-lang.org/stable/book/](https://doc.rust-lang.org/stable/book/)(англ.) и [https://doc.rust-lang.ru/book](https://doc.rust-lang.ru/book)(рус.) и офлайн. При установке Ржавчины с помощью `rustup`: просто запустите `rustup docs --book`, чтобы её открыть. Также доступны несколько [переводов] от сообщества. Этот источник доступен в виде [печатной книги в мягкой обложке и в виде электронной книги от No Starch Press] . -> **🚨 Предпочитаете более увлекательный этап обучения? Попробуйте другую исполнение Ржавчина Book, в которой есть: проверочные вопросы, цветовое выделение, наглядные визуализации и многое другое**: [https://rust-book.cs.brown.edu](https://rust-book.cs.brown.edu) +> **🚨 Предпочитаете более увлекательный этап обучения? Попробуйте другое исполнение Ржавчины Book, в которой есть: проверочные вопросы, цветовое выделение, наглядные визуализации и многое другое**: [https://rust-book.cs.brown.edu](https://rust-book.cs.brown.edu) [раздел «Установка» главы 1]: ch01-01-installation.html