diff --git a/translations/ru/01_getting_started/01_chapter.md b/translations/ru/01_getting_started/01_chapter.md new file mode 100644 index 00000000..eaafd74b --- /dev/null +++ b/translations/ru/01_getting_started/01_chapter.md @@ -0,0 +1,30 @@ +# Начало работы + +Добро пожаловать в книгу "Асинхронное программирование в Rust"! Если вы +собираетесь начать писать асинхронный код на Rust, вы находитесь в правильном +месте. Строите ли вы веб-сервер, базу данных или операционную систему, эта книга +покажет вам как использовать инструменты асинхронного программирования, чтобы +получить максимальную отдачу от вашего оборудования. + +## Что охватывает эта книга? + +Эта книга призвана стать исчерпывающим, современным +руководством по использованию асинхронных языковых +возможностей и библиотек Rust, подходящим как новичкам, так и +опытным разработчикам. + +- Ранние главы содержат введение в асинхронное программирование в целом, а также + его особенности в Rust. + +- В средних главах обсуждаются ключевые утилиты и инструменты + управления потоком, которые вы можете использовать, когда + пишете асинхронный код. Также здесь описаны лучшие практики + структурирования библиотек и приложений для получения + максимальной производительности и повторного использования кода. + +- Последняя глава книги покрывает асинхронную экосистему и + предоставляет ряд примеров того, как можно выполнить общие + задачи. + +Итак, давайте исследуем захватывающий мир асинхронного +программирования в Rust! diff --git a/translations/ru/01_getting_started/02_why_async.md b/translations/ru/01_getting_started/02_why_async.md new file mode 100644 index 00000000..307dfbf2 --- /dev/null +++ b/translations/ru/01_getting_started/02_why_async.md @@ -0,0 +1,49 @@ +# Для чего нужна асинхронность? + +Все мы любим то, что Rust позволяет нам писать быстрые и безопасные +приложения. Но для чего писать асинхронный код? + +Асинхронный код позволяет нам запускать несколько задач +параллельно в одном потоке ОС. Если вы в обычном приложении +хотите одновременно загрузить две разных web-страницы, вы +должны разделить работу между двумя разным потоками, как тут: + +```rust +{{#include ../../../examples/01_02_why_async/src/lib.rs:get_two_sites}} +``` + +Для многих приложений это замечательно работает - в конце концов, +потоки были разработаны именно для этого: одновременно +запускать несколько разных задач. Однако, они имеют некоторые +ограничения. В процессе переключения между разными потоками и +обменом данными между ними возникает много накладных расходов. +Даже поток, который сидит и ничего не делает, использует ценные +системные ресурсы. Асинхронный код предназначен для устранения +этих проблем. Мы можем переписать функции выше используя +нотацию `async`/`.await`, которая позволяет +нам запустить несколько задач одновременно, не создавая нескольких потоков: + +```rust +{{#include ../../../examples/01_02_why_async/src/lib.rs:get_two_sites_async}} +``` + +В целом, асинхронные приложения могут быть намного быстрее и +использовать меньше ресурсов, чем соответствующая +многопоточная реализация. Однако, есть и обратная сторона. +Потоки изначально поддерживаются операционной системой и их +использование не требует какой-либо специальной модели +программирования - любая функция может создать поток и вызов +функции, использующей потоки, обычно так же прост, как вызов +обычной функции. Тем не менее, асинхронные функции требуют +специальной поддержки со стороны языка или библиотек. В Rust, +`async fn` создаёт асинхронную функцию, которая +возвращает `Future`. Для выполнения тела функции, +возвращённая `Future` должна быть завершена. + +Важно помнить, что традиционные приложения с потоками могут +быть вполне эффективными и предсказуемость Rust и небольшой +объём используемой памяти могут значить, что вы можете далеко +продвинуться и без использования `async`. +Повышенная сложность асинхронной модели программирования +не всегда стоит этого и важно понимать, когда ваше приложение +будет лучше работать с использованием простой поточной модели. diff --git a/translations/ru/01_getting_started/03_state_of_async_rust.md b/translations/ru/01_getting_started/03_state_of_async_rust.md new file mode 100644 index 00000000..58b94cc1 --- /dev/null +++ b/translations/ru/01_getting_started/03_state_of_async_rust.md @@ -0,0 +1,24 @@ +# Состояние асинхронности в Rust + +Асинхронная экосистема Rust претерпела большие изменения с течением времени, +поэтому может быть трудно понять, какие инструменты использовать, в какие библиотеки инвестировать, +или какую документацию читать. Однако типаж `Future` внутри стандартной библиотеки и `async`/`await` в языке были недавно стабилизированы. +Таким образом, экосистема в целом находится в процессе миграции +к недавно стабилизированному API, после чего точка оттока будет значительно +уменьшена. + +Тем не менее, сейчас экосистема всё ещё находится в стадии +активной разработки и асинхронный опыт в Rust не отполирован. +Многие библиотеки до сих пор используют пакет +`futures` версии 0.1, а это значит, что для +взаимодействия с ними разработчикам часто требуется +функциональность `compat` из пакета +`futures` версии 0.3. Синтаксис `async`/`await` до сих пор достаточно нов. Важные расширения +синтаксиса, такие как `async fn` для методов типажей, до +сих пор не реализованы, и текущие сообщения компилятора об +ошибках могут быть сложны для восприятия. + +Это говорит о том, что Rust на пути к более эффективной и +эргономичной поддержке асинхронного программирования и если +вы не боитесь изменений, наслаждайтесь погружением в мир +асинхронного программирования в Rust! diff --git a/translations/ru/01_getting_started/04_async_await_primer.md b/translations/ru/01_getting_started/04_async_await_primer.md new file mode 100644 index 00000000..c24ea726 --- /dev/null +++ b/translations/ru/01_getting_started/04_async_await_primer.md @@ -0,0 +1,67 @@ +# Пример `async`/`.await` + +`async`/`.await` - это встроенные в Rust +инструменты для написания асинхронного кода, который выглядит +как синхронный код. Ключевое слово `async` преобразует блок кода в конечный +автомат, который реализует типаж, называемый `Future`. В то время как вызов +блокирующей функции в синхронном методе заблокирует весь поток, заблокированная +`Future` вернёт контроль над потоком, позволяя работать другим `Future`. + +Для создания асинхронной функции, вы можете использовать +синтаксис `async fn`: + +```rust +async fn do_something() { ... } +``` + +Значение, возвращённое`async fn` является `Future`. Что бы ни произошло, +`Future` должна быть запущена в исполнителе. + +```rust +{{#include ../../../examples/01_04_async_await_primer/src/lib.rs:hello_world}} +``` + +Внутри `async fn` можно использовать +`.await` для ожидания завершения другого типа, +реализующего типаж `Future` (например, полученного из другой `async fn`). +В отличие от `block_on`, `.await` не блокирует текущий поток, но асинхронно ждёт +завершения футуры, позволяя другим задачам выполняться, если в данный момент +футура не может добиться прогресса. + +Например, представим что у нас есть три синхронный функции `async fn`: +`learn_song`, `sing_song` и `dance`: + +```rust +async fn learn_song() -> Song { ... } +async fn sing_song(song: Song) { ... } +async fn dance() { ... } +``` + +Одним из способов выполнения функций учить, петь и танцевать будет остановка на +каждом из них: + +```rust +{{#include ../../../examples/01_04_async_await_primer/src/lib.rs:block_on_each}} +``` + +Тем не менее, в этом случае мы не получаем наилучшей +производительности - в один момент мы делаем только одно дело! +Очевидно, что мы должны выучить песню до того, как петь её, но +мы можем танцевать в то же время, пока учим песню и поём её. +Чтобы сделать это, мы создадим две отдельные `async fn`, которые могут +запуститься параллельно: + +```rust +{{#include ../../../examples/01_04_async_await_primer/src/lib.rs:block_on_main}} +``` + +В этом примере, изучение песни должно быть завершено до пения, но и изучение и +пение могут завершиться одновременно с танцем. Если мы использовали бы +`block_on(learn_song())` вместо `learn_song().await` внутри `learn_and_sing`, +поток не смог бы делать ничего другого, пока работает `learn_song`. Из-за этого +мы одновременно с этим не можем танцевать. +С помощью ожидания `.await` футуры `learn_song`, мы разрешаем другим задачам +захватить текущий поток, пока `learn_song` заблокирована. Это делает возможным +запуск нескольких футур, завершающихся параллельно в одном потоке. + +Теперь мы изучили основы `async`/`.await`, давайте посмотрим их в действии. diff --git a/translations/ru/01_getting_started/05_http_server_example.md b/translations/ru/01_getting_started/05_http_server_example.md new file mode 100644 index 00000000..d90deef5 --- /dev/null +++ b/translations/ru/01_getting_started/05_http_server_example.md @@ -0,0 +1,88 @@ +# Применение: HTTP сервер + +Давайте используем `async`/`.await` для создания echo-сервера! + +Для начала, запустите `rustup update stable` чтобы +быть уверенным, что используете стабильную версию Rust - 1.39 или более новую. +Когда вы закончите это, создайте новый проект с +помощь `cargo new async-await-echo` и +откройте созданную директорию `async-await-echo`. + +Добавим некоторые зависимости в файл `Cargo.toml`: + +```toml +{{#include ../../../examples/01_05_http_server/Cargo.toml:9:18}} +``` + +Теперь, когда у нас есть свои зависимости, давайте начнём писать код. Вот список +зависимостей, которые необходимо добавить: + +```rust +{{#include ../../../examples/01_05_http_server/src/lib.rs:imports}} +``` + +Как только закончим с импортами, мы можем собрать вместе весь +шаблонный код, который позволит обрабатывать запросы: + +```rust +{{#include ../../../examples/01_05_http_server/src/lib.rs:boilerplate}} +``` + +Если вы сейчас запустите `cargo run`, в консоли вы +увидите сообщение "Listening on http://127.0.0.1:3000". Если вы +откроете URL в вашем любимом браузере, вы увидите как в нём +отобразится "hello, world!". Поздравляем! Вы только что написали +свой первый асинхронный web-сервер на Rust. + +Вы также можете посмотреть сам запрос, который содержит такую +информацию, как URI, версию HTTP, заголовки и другие +метаданные. Например, мы можем вывести URI запроса +следующим образом: + +```rust +println!("Got request at {:?}", req.uri()); +``` + +Вы могли заметить, что мы до сих пор не делали ничего +асинхронного для обработки запроса - мы только незамедлительно +ответили на него, мы не пользуемся гибкостью, которую нам даёт +`async fn`. Вместо этого, мы только возвращаем +статическое сообщение. Давайте попробуем проксировать +пользовательский запрос на другой web-сайт используя +HTTP-клиент Hyper'а. + +Мы начнём с парсинга URL, который мы хотим запросить: + +```rust +{{#include ../../../examples/01_05_http_server/src/lib.rs:parse_url}} +``` + +Затем мы создадим новый `hyper::Client` и +используем его для создания `GET` запроса, который +вернём пользователю ответ: + +```rust +{{#include ../../../examples/01_05_http_server/src/lib.rs:get_request}} +``` + +`Client::get` возвращает +`hyper::client::FutureResponse`, который реализует +`Future>` +(или `Future` в +терминах `futures` 0.1). Когда мы разрешаем (`.await`) +футуру, отправляется HTTP-запрос, текущая задача +приостанавливается и становится в очередь, чтобы продолжить +работу после получения ответа. + +Если вы сейчас запустите `cargo run` и откроете +`http://127.0.0.1:3000/foo` в браузере, вы увидите +домашнюю страницу Rust, а в консоли следующий вывод: + +``` +Listening on http://127.0.0.1:3000 +Got request at /foo +making request to http://www.rust-lang.org/en-US/ +request finished-- returning response +``` + +Поздравляем! Вы только что проксировали HTTP запрос. diff --git a/translations/ru/02_execution/01_chapter.md b/translations/ru/02_execution/01_chapter.md new file mode 100644 index 00000000..ce6685ed --- /dev/null +++ b/translations/ru/02_execution/01_chapter.md @@ -0,0 +1,14 @@ +# Под капотом: выполнение `Future` и задач + +В этом разделе мы рассмотрим как планируются `Future` +и асинхронные задачи. Если вам только интересно изучить как писать +высокоуровневый код, который использует существующие типы +`Future`, и не интересуетесь тем, как работает `Future`, то можете сразу перейти к главе +`async`/`await`. Тем не менее, некоторые +темы, которые обсуждаются в этой главе, полезны для понимания +работы `async`/`await` кода и построения +новых асинхронных примитивов. Если сейчас вы решили пропустить +этот раздел, вы можете добавить его в закладки, чтобы вернуться к +нему в будущем. + +Теперь давайте рассмотрим типаж `Future`. diff --git a/translations/ru/02_execution/02_future.md b/translations/ru/02_execution/02_future.md new file mode 100644 index 00000000..2e1128a7 --- /dev/null +++ b/translations/ru/02_execution/02_future.md @@ -0,0 +1,99 @@ +# Типаж `Future` + +Типаж `Future` является центральным для асинхронного +программирования в Rust. `Future` - это асинхронное +вычисление, которое может производить значение (хотя значение +может быть и пустым, например `()`). +*Упрощённый* вариант этого типажа может выглядеть как-то +так: + +```rust +{{#include ../../../examples/02_02_future_trait/src/lib.rs:simple_future}} +``` + +Футуры могут прогрессировать при помощи функции +`poll`, которая продвигает их к завершению на сколько +это возможно. Если футура завершается, она возвращает +`Poll::Ready(result)`. Если же она всё ещё не готова +завершиться, то - `Poll::Pending` и обрабатывает +функцию `wake()` таким образом, что она будет вызвана, когда +`Future` будет готова прогрессировать. Когда +`wake()` вызван, исполнитель снова вызывает у +`Future` метод `poll`, чтобы она смогла +продвинуться далее. + +Без `wake()`, исполнитель не имеет возможности узнать, +когда какая-либо футура может продвинуться, и ему необходимо +постоянно опрашивать каждую футуру. С `wake()` он точно +знает какие футуры готовы прогрессировать. + +Например, представим ситуацию, когда мы хотим прочитать из сокета, который может +иметь, а может и не иметь данных. Если данные есть, мы можем прочитать их и +вернуть `Poll::Ready(data)`, но если данных ещё нет, наша футура блокируется и +не может продвинуться дальше. Если данных нет, то мы должны зарегистрировать +вызов `wake`, чтобы он был вызван, когда данные появятся в сокете, и сообщил +нашему исполнителю, что футура готова прогрессировать. Простая футура +`SocketRead` может выглядеть следующим образом: + +```rust +{{#include ../../../examples/02_02_future_trait/src/lib.rs:socket_read}} +``` + +Такая модель футур позволяет держать вместе несколько +асинхронных операций без лишних промежуточных выделений памяти. +Одновременный запуск нескольких футур или соединение их в +цепочку может быть реализовано при помощи не выделяющей +памяти машины состояний, например так: + +```rust +{{#include ../../../examples/02_02_future_trait/src/lib.rs:join}} +``` + +Здесь показано, как несколько футур могут быть запущены +одновременно без необходимости раздельной аллокации, позволяя +асинхронным программам быть более эффективными. Аналогично, +несколько последовательных футур могут быть запущены одна за +другой, как тут: + +```rust +{{#include ../../../examples/02_02_future_trait/src/lib.rs:and_then}} +``` + +Этот пример показывает, как типаж `Future` может +использоваться для выражения асинхронного управления потоком +без необходимости множественной аллокации объектов и глубоко +вложенных замыканий. Давайте оставим базовое управление +потоком в стороне и поговорим о реальном типаже +`Future` и чем он отличается от написанного нами. + +```rust +{{#include ../../../examples/02_02_future_trait/src/lib.rs:real_future}} +``` + +Первое, что вы могли заметить, что наш тип `self` +больше не `&mut self`, а заменён на +`Pin<&mut Self>`. Мы поговорим о +закреплении (pinning) [в следующей секции], но пока что знайте, что +оно позволяет нам создавать неперемещаемые футуры. +Неперемещаемые объекты могут хранить указатели на +собственные поля, например +`struct MyFut { a: i32, ptr_to_a: *const i32 }`. +Закрепление необходимо для `async`/`await`. + +Второе, `wake: fn()` была изменена на +`&mut Context<'_>`. В +`SimpleFuture` мы использовали вызов указателя на +функцию (`fn()`), чтобы сказать исполнителю, что +футура должна быть опрошена. Однако, так как `fn()` +имеет нулевой тип, она не может сохранить информацию о том +_какая_ футура вызвала `wake`. + +В примере из реального мира, сложное приложение, такое как web-сервер, +может иметь тысячи различных подключений все пробуждения +которых должны обрабатываться отдельно. Тип +`Context` решает это предоставляя доступ к значению +типа `Waker`, который может быть использован для +пробуждения конкретной задачи. + + +[в следующей секции]: ../04_pinning/01_chapter.md diff --git a/translations/ru/02_execution/03_wakeups.md b/translations/ru/02_execution/03_wakeups.md new file mode 100644 index 00000000..455e28f8 --- /dev/null +++ b/translations/ru/02_execution/03_wakeups.md @@ -0,0 +1,73 @@ +# Вызовы задачи при помощи `Waker` + +Обычно футуры не могут разрешиться сразу же, как для них +вызвали метод `poll`. Когда это произойдёт, футура +должна быть уверена, что её снова опросят, когда она будет готова +прогрессировать. Это решается при помощи типа `Waker`. + +Футура опрашивается как часть "задачи" каждый раз, когда +происходит её опрос. Задачи - это высокоуровневые футуры, с +которыми работает исполнитель. + +`Waker` предоставляет метод `wake()`, +который может быть использован, чтобы сказать исполнителю, что +соответствующая задача должна быть пробуждена. Когда +вызывается `wake()`, исполнитель +знает, что задача, связанная с `Waker`, готова к +выполнению, и её футура должна быть опрошена снова. + +`Waker` так же реализует `clone()`, так +что вы можете его копировать, где это необходимо, и хранить. + +Давайте попробуем реализовать простой таймер с использованием `Waker`. + +## Применение: Создание таймера + +В качестве примера, мы просто раскручиваем новый поток при +создании таймера, спим в течение необходимого времени, а затем +через какое-то время сообщаем о том, что заданный временной +промежуток истёк. + +Вот список импортов, которые нам понадобятся: + +```rust +{{#include ../../../examples/02_03_timer/src/lib.rs:imports}} +``` + +Начнём с определения типа футуры. Нашей футуре необходим +канал связи, чтобы сообщить о том, что время таймера истекло и +футура должна завершиться. В качестве канала связи между +таймером и футурой мы будем использовать разделяемое +значение `Arc>`. + +```rust +{{#include ../../../examples/02_03_timer/src/lib.rs:timer_decl}} +``` + +Теперь давайте реализуем `Future` для нашей футуры! + +```rust +{{#include ../../../examples/02_03_timer/src/lib.rs:future_for_timer}} +``` + +Просто, не так ли? Если поток установит `shared_state.completed = true`, мы +закончили! В противном случае мы клонируем `Waker` для +текущей задачи и сохраняем его в `shared_state.waker`. +Так поток может разбудить задачу позже. + +Важно отметить, что мы должны обновлять `Waker` +каждый раз, когда футура опрашивается, потому что она может +быть перемещена в другую задачу с другим `Waker`. +Это может произойти когда футуры после опроса передаются +между задачами. + +Наконец, нам нужен API, чтобы фактически построить таймер и +запустить поток: + +```rust +{{#include ../../../examples/02_03_timer/src/lib.rs:timer_new}} +``` + +Это всё, что нам нужно, чтобы построить простую футуру +таймером. Теперь нам нужен исполнитель, чтобы запустить её на +исполнение. diff --git a/translations/ru/02_execution/04_executor.md b/translations/ru/02_execution/04_executor.md new file mode 100644 index 00000000..e96ff677 --- /dev/null +++ b/translations/ru/02_execution/04_executor.md @@ -0,0 +1,94 @@ +# Применение: создание исполнителя + +Футуры Rust'a ленивы: они ничего не будут делать, если не будут активно +доводиться до завершения. Один из способов довести их до завершения - это +`.await` внутри `async` функции, но это просто подталкивает проблему на один +уровень вверх: кто будет запускать футуры, возвращённые из `async` функций +верхнего уровня? Ответ в том, что нам нужен исполнитель для `Future`. + +Исполнители берут набор футур верхнего уровня и запускают их через вызов метода +`poll`, до тех пор, пока они не завершатся. Как правило, исполнитель будет +вызывать метод `poll` у футуры один раз, чтобы запустить. Когда футура сообщает, +что готова продолжить вычисления и нужно вызвать метод `wake()`, она помещается +обратно в очередь и затем снова выполняется вызов `poll`. Это повторяется до тех +пор, пока `Future` не будет завершена. + +В этом разделе мы напишем нашего собственного простого исполнителя, способного +одновременно запускать большое количество футур верхнего уровня. + +Для этого примера мы используем пакет `futures`, в котором определён типаж +`ArcWake`. Данный типаж предоставляет простой способ создания `Waker`. + +```toml +[package] +name = "xyz" +version = "0.1.0" +authors = ["XYZ Author"] +edition = "2018" + +[dependencies] +futures-preview = "=0.3.0-alpha.17" +``` + +Дальше, мы должны в верхней части `src/main.rs` импортировать следующее: + +```rust +{{#include ../../../examples/02_04_executor/src/lib.rs:imports}} +``` + +Наш исполнитель будет работать, посылая задачи для запуска по каналу. +Исполнитель извлечёт события из канала и запустит их. Когда задача будет готова +выполнить больше работы (будет пробуждена), она может запланировать повторный +опрос самой себя, отправив себя обратно в канал. + +В этом проекте самому исполнителю просто необходим получатель для канала задачи. +Пользователь получит экземпляр отправителя, чтобы он мог создавать новые футуры. +Сами задачи - это просто футуры, которые могут перезапланировать самих себя, +поэтому мы сохраним их как сочетание футуры и отправителя, который задача может +использовать, чтобы добавить себя в очередь. + +```rust +{{#include ../../../examples/02_04_executor/src/lib.rs:executor_decl}} +``` + +Давайте добавим к `Spawner` метод, облегчающий создание футур. Этот метод +возьмёт футуру, упакует и поместит её в `FutureObj` и создаст новый `Arc` +с полученным объектом внутри. Созданный `Arc` может быть поставлен в +очередь исполнителя. + +```rust +{{#include ../../../examples/02_04_executor/src/lib.rs:spawn_fn}} +``` + +Чтобы опросить футуры, нам нужно создать `Waker`. +Как описано [в главе о пробуждении задач], `Waker` отвечает за планирование +задач, которые будут повторно опрошены после вызова `wake`. `Waker` сообщают +исполнителю, какая именно задача завершилась, позволяя опрашивать только те +футуры, которые готовы к продолжению выполнения. Наипростейший способ +создания нового `Waker` заключается в реализации типажа `ArcWake` и последующем +использовании функций `waker_ref` или `.into_waker()` для преобразования +`Arc` в `Waker`. Давайте реализуем `ArcWake` для наших задач, +чтобы они могли превращаться в `Waker` и могли пробуждаться: + +```rust +{{#include ../../../examples/02_04_executor/src/lib.rs:arcwake_for_task}} +``` + +Когда `Waker` создаётся на основе `Arc`, вызов `wake()` для него приведёт +к отправке копии `Arc` в канал задач. Тогда нашему исполнителю +нужно подобрать задание и опросить его. Давайте реализуем это: + +```rust +{{#include ../../../examples/02_04_executor/src/lib.rs:executor_run}} +``` + +Поздравляю! Теперь у нас есть работающий исполнитель. Мы даже можем использовать +его для запуска `async/.await` кода и пользовательских футур, таких как +`TimerFuture`, которую мы написали ранее: + +```rust +{{#include ../../../examples/02_04_executor/src/lib.rs:main}} +``` + + +[в главе о пробуждении задач]: ./03_wakeups.md diff --git a/translations/ru/02_execution/05_io.md b/translations/ru/02_execution/05_io.md new file mode 100644 index 00000000..2ec23c1e --- /dev/null +++ b/translations/ru/02_execution/05_io.md @@ -0,0 +1,120 @@ +# Исполнители и системный ввод/вывод + +В главе ["Типаж `Future`"], мы обсуждали футуру, которая выполняет асинхронное +чтение сокета: + +```rust +{{#include ../../../examples/02_02_future_trait/src/lib.rs:socket_read}} +``` + +Эта футура читает из сокета доступные данные и если таковых нет, +то она передаётся исполнителю с запросом +активирования задачи, когда сокет снова станет читаемым. Однако, +из текущего примера не ясна реализация типа +`Socket` и, в частности, не совсем очевидно как +работает функция `set_readable_callback`. Как мы +можем сделать так, чтобы `lw.wake()` был вызван, +когда сокет станет читаемым? Один из вариантов - иметь поток, +который постоянно проверяет стал ли `socket` +читаемым, вызывая при необходимости метод +`wake()`. Тем не менее, такой подход будет весьма не +эффективным, так как он требует отдельного потока для каждой +блокирующей IO футуры. Это значительно снизит эффективность +нашего асинхронного кода. + +На практике эта проблема решается при помощи интеграции с +IO-зависимыми системными блокирующими примитивами такими, +как `epoll` в Linux, `kqueue` во FreeBSD и +Mac OS, `IOCP` в Windows и `port` в Fuchsia (все они +предоставляются при помощи кроссплатформенного Rust-пакета +[`mio`]). Все эти примитивы позволяют потоку +заблокироваться с несколькими асинхронными IO-событиями, +возвращая одно из завершённых событий. На практике эти API +выглядят примерно так: + +```rust +struct IoBlocker { + ... +} + +struct Event { + // ID уникально идентифицирующий событие, которое уже произошло и на которое мы подписались + id: usize, + + // Набор сигналов, которые мы ожидаем или которые произошли + signals: Signals, +} + +impl IoBlocker { + /// Создаём новую коллекцию асинхронных IO-событий для блокировки + fn new() -> Self { ... } + + /// Подпишемся на определённое IO-событие. + fn add_io_event_interest( + &self, + + /// Объект, на котором происходит событие + io_object: &IoObject, + + /// Набор сигналов, которые могут применяться к `io_object`, + /// для которого должно быть инициировано событие, в паре с + /// ID, которые передадутся событиям, получившимся в результате нашей подписки. + event: Event, + ) { ... } + + /// Заблокируется до появления одного из событий + fn block(&self) -> Event { ... } +} + +let mut io_blocker = IoBlocker::new(); +io_blocker.add_io_event_interest( + &socket_1, + Event { id: 1, signals: READABLE }, +); +io_blocker.add_io_event_interest( + &socket_2, + Event { id: 2, signals: READABLE | WRITABLE }, +); +let event = io_blocker.block(); + +// выведет что-то похожее на "Socket 1 is now READABLE", если сокет станет доступным для чтения. +println!("Socket {:?} is now {:?}", event.id, event.signals); +``` + +Исполнители футур могут использовать эти примитивы для предоставления +асинхронных объектов ввода-вывода, таких как сокеты, которые могут настроить +обратные вызовы для запуска при определённом IO-событии. В случае нашего примера +c `SocketRead`, функция `Socket::set_readable_callback` может выглядеть +следующим псевдокодом: + +```rust +impl Socket { + fn set_readable_callback(&self, waker: Waker) { + // `local_executor` является ссылкой на локальный исполнитель. + // Это может быть предусмотрено при создании сокета, + // большинство реализаций исполнителей делают это через локальный поток, так удобнее. + let local_executor = self.local_executor; + + // Уникальный ID для объекта ввода вывода. + let id = self.id; + + // Сохраним `waker` в данных исполнителя, + // чтобы его можно было вызвать после того, как будет получено событие. + local_executor.event_map.insert(id, waker); + local_executor.add_io_event_interest( + &self.socket_file_descriptor, + Event { id, signals: READABLE }, + ); + } +} +``` + +Теперь у нас может быть только один поток исполнителя, который может принимать и +отправлять любые события ввода-вывода в нужный `Waker`, который разбудит +соответствующую задачу, позволяющая исполнителю довести больше задач до +завершения перед возвратом к проверке новых событий ввода-вывода (и цикл +продолжается...). + + +["Типаж `Future`"]: ./02_future.md +[`mio`]: https://github.com/tokio-rs/mio diff --git a/translations/ru/03_async_await/01_chapter.md b/translations/ru/03_async_await/01_chapter.md new file mode 100644 index 00000000..e8686f7c --- /dev/null +++ b/translations/ru/03_async_await/01_chapter.md @@ -0,0 +1,87 @@ +# `async`/`await` + +В [первой главе] мы бросили беглый взгляд на `async`/`.await` и использовали +их чтобы построить простой сервер. В этой главе мы обсудим `async`/`.await` +более подробно, объясняя, как они работают и как `async`-код отличается от +традиционных программ на Rust. + +`async`/`.await` - это специальный синтаксис Rust, который позволяет не +блокировать поток, а передавать управление другому коду, пока ожидается +завершение операции. + +Существует два основных способа использования `async`: `async fn` и `async`-блоки. +Каждый возвращает значение, реализующее типаж `Future`: + +```rust +{{#include ../../../examples/03_01_async_await/src/lib.rs:async_fn_and_block_examples}} +``` + +Как мы видели в первой главе, `async`-блоки и другие футуры ленивы: они ничего +не делают, пока их не запустят. Наиболее распространённый способ запустить +`Future` - это `.await`. Когда `.await` вызывается на `Future`, он пытается +выполнить её до конца. Если `Future` заблокирована, то контроль будет передан +текущему потоку. Чтобы добиться большего прогресса, будет выбрана верхняя +`Future` исполнителя, позволяя `.await` продолжить работу. + +## Времена жизни `async` + +В отличие от традиционных функций, `async fn`, которые принимают ссылки или другие +не-`'static` аргументы, возвращают `Future`, которая ограничена временем жизни +аргумента: + +```rust +{{#include ../../../examples/03_01_async_await/src/lib.rs:lifetimes_expanded}} +``` + +Это означает, что для футуры, возвращаемая из `async fn`, должен быть вызван +`.await` до тех пор, пока её не-`'static` аргументы все ещё действительны. В +общем случае, вызов `.await` у футуры сразу после вызова функции +(как в `foo(&x).await`), не является проблемой. Однако, проблемой может +оказаться сохранение футуры или отправка её в другую задачу или поток. + +Один общий обходной путь для включения `async fn` со ссылками в аргументах +в `'static` футуру состоит в том, чтобы связать аргументы с вызовом +`async fn` внутри `async`-блока: + +```rust +{{#include ../../../examples/03_01_async_await/src/lib.rs:static_future_with_borrow}} +``` + +Перемещая аргумент в `async`-блок, мы продлеваем его время жизни до времени +жизни `Future`, которая возвращается при вызове `good`. + +## `async move` + +`async`-блоки и `async`-замыкания позволяют использовать ключевое слово `move`, +как и в обычном замыкании. `async move` блок получает владение переменными со +ссылками, позволяя им пережить текущую область, но отказывая им в возможности +делиться этими переменными с другим кодом: + +```rust +{{#include ../../../examples/03_01_async_await/src/lib.rs:async_move_examples}} +``` + +## `.await` в многопоточном исполнителе + +Обратите внимание, что при использовании `Future` в многопоточном исполнителе, +`Future` может перемещаться между потоками. Поэтому любые переменные, +используемые в телах `async`, должны иметь возможность перемещаться между +потоками, так как любой `.await` потенциально может привести к переключению на +новый поток. + +Это означает, что не безопасно использовать `Rc`, `&RefCell` или любые другие +типы, не реализующие типаж `Send` (включая ссылки на типы, которые не реализуют +типаж `Sync`). + +(Предостережение: можно использовать эти типы до тех пор, пока они не находятся +в области действия вызова `.await`.) + +Точно так же не очень хорошая идея держать традиционную `non-futures-aware` +блокировку через `.await`, так как это может привести к блокировке пула потоков: +одна задача может получить объект блокировки, вызвать `.await` и передать +управление исполнителю, разрешив другой задаче совершить попытку взять +блокировку, что вызовет взаимную блокировку. Чтобы избежать этого, используйте +`Mutex` из `futures::lock`, а не из `std::sync`. + + +[первой главе]: ../01_getting_started/04_async_await_primer.md diff --git a/translations/ru/04_pinning/01_chapter.md b/translations/ru/04_pinning/01_chapter.md new file mode 100644 index 00000000..9080d0ee --- /dev/null +++ b/translations/ru/04_pinning/01_chapter.md @@ -0,0 +1,150 @@ +# Закрепление (pinning) + +Для опроса футуры должны быть закреплены с помощью специального типа под названием +`Pin`. Если Вы прочитаете описание [типажа `Future`] в +предыдущем разделе ["Выполнение `Future` и задач"], вы узнаете о +`Pin` из `self: Pin<&mut Self>` в методе `Future::poll`. +Но что это значит, и зачем нам это нужно? + +## Для чего нужно закрепление + +Закрепление даёт возможность гарантировать, что объект не будет перемещён. +Чтобы понять почему это важно, нам надо помнить как работает `async`/`.await`. +Рассмотрим следующий код: + +```rust +let fut_one = ...; +let fut_two = ...; +async move { + fut_one.await; + fut_two.await; +} +``` + +Под капотом, он создаёт анонимный тип, который реализует типаж `Future`, +предоставляющий метод `poll`, выглядящий примерно так: + +```rust +// Тип `Future`, созданный нашим `async { ... }` блоком +struct AsyncFuture { + fut_one: FutOne, + fut_two: FutTwo, + state: State, +} + +// Список возможных состояний нашего `async` блока +enum State { + AwaitingFutOne, + AwaitingFutTwo, + Done, +} + +impl Future for AsyncFuture { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + loop { + match self.state { + State::AwaitingFutOne => match self.fut_one.poll(..) { + Poll::Ready(()) => self.state = State::AwaitingFutTwo, + Poll::Pending => return Poll::Pending, + } + State::AwaitingFutTwo => match self.fut_two.poll(..) { + Poll::Ready(()) => self.state = State::Done, + Poll::Pending => return Poll::Pending, + } + State::Done => return Poll::Ready(()), + } + } + } +} +``` + +Когда `poll` вызывается первый раз, он опрашивает +`fut_one`. Если `fut_one` не завершена, +возвращается `AsyncFuture::poll`. Следующие вызовы +`poll` будут начинаться там, где завершился +предыдущий вызов. Этот процесс продолжается до тех пор, пока +футура не сможет завершиться. + +Однако, что будет, если `async` блок использует ссылки? +Например: + +```rust +async { + let mut x = [0; 128]; + let read_into_buf_fut = read_into_buf(&mut x); + read_into_buf_fut.await; + println!("{:?}", x); +} +``` + +Во что скомпилируется эта структура? + +```rust +struct ReadIntoBuf<'a> { + buf: &'a mut [u8], // указывает на `x` далее +} + +struct AsyncFuture { + x: [u8; 128], + read_into_buf_fut: ReadIntoBuf<'?>, // какое тут время жизни? +} +``` + +Здесь футура `ReadIntoBuf` содержит ссылку на другое +поле нашей структуры, `x`. Однако, если +`AsyncFuture` будет перемещена, положение +`x` тоже будет изменено, что инвалидирует указатель, +сохранённый в `read_into_buf_fut.buf`. + +Закрепление футур в определённом месте памяти предотвращает +эту проблему, делая безопасным создание ссылок на данные за +пределами `async` блока. + +## Как использовать закрепление + +Тип `Pin` оборачивает указатель на другие типы, +гарантируя, что значение за указателем не будет перемещено. +Например, `Pin<&mut T>`, `Pin<&T>`, +`Pin>` - все гарантируют, что положение +`T` останется неизменным. + +У большинства типов нет проблем с перемещением. Эти типы +реализуют типаж `Unpin`. Указатели на +`Unpin`-типы могут свободно помещаться в +`Pin` или извлекаться из него. Например, тип +`u8` реализует `Unpin`, таким образом +`Pin<&mut u8>` ведёт себя также, как и +`&mut u8`. + +Некоторые функции требуют, чтобы футуры, с которыми они работают, были `Unpin`. Чтобы использовать +`Future` или `Stream`, который не реализует `Unpin`, с функцией, которая требует +`Unpin`-типы, сначала нужно закрепить значение, используя либо +`Box::pin` (чтобы создать `Pin>`) или макрос `pin_utils::pin_mut!` +(чтобы создать `Pin<&mut T>`). `Pin>` и `Pin<&mut Fut>` могут быть +использованы как футура и оба реализуют `Unpin`. + +Например: + +```rust +use pin_utils::pin_mut; // `pin_utils` - удобный пакет, доступный на crates.io + +// Функция, принимающая `Future`, которая реализует `Unpin`. +fn execute_unpin_future(x: impl Future + Unpin) { ... } + +let fut = async { ... }; +execute_unpin_future(fut); // Ошибка: `fut` не реализует типаж `Unpin` + +// Закрепление с помощью `Box`: +let fut = async { ... }; +let fut = Box::pin(fut); +execute_unpin_future(fut); // OK + +// Закрепление с помощью `pin_mut!`: +let fut = async { ... }; +pin_mut!(fut); +execute_unpin_future(fut); // OK +``` + + +["Выполнение `Future` и задач"]: ../02_execution/01_chapter.md +[типажа `Future`]: ../02_execution/02_future.md \ No newline at end of file diff --git a/translations/ru/05_streams/01_chapter.md b/translations/ru/05_streams/01_chapter.md new file mode 100644 index 00000000..8f7652c4 --- /dev/null +++ b/translations/ru/05_streams/01_chapter.md @@ -0,0 +1,17 @@ +# Типаж `Stream` + +Типаж `Stream` похож на `Future`, но до своего завершения может давать несколько +значений. Также он похож на типаж `Iterator` из стандартной библиотеки: + +```rust +{{#include ../../../examples/05_01_streams/src/lib.rs:stream_trait}} +``` + +Одним из распространённых примеров `Stream` является `Receiver` для типа канала из +пакета `futures`. Он даёт `Some(val)` каждый раз, когда значение отправляется +от `Sender`, и даст `None` после того, как `Sender` был удалён из памяти и все +ожидающие сообщения были получены: + +```rust +{{#include ../../../examples/05_01_streams/src/lib.rs:channels}} +``` diff --git a/translations/ru/05_streams/02_iteration_and_concurrency.md b/translations/ru/05_streams/02_iteration_and_concurrency.md new file mode 100644 index 00000000..dc10571c --- /dev/null +++ b/translations/ru/05_streams/02_iteration_and_concurrency.md @@ -0,0 +1,24 @@ +# Итерирование и параллелизм + +Подобно синхронным итераторам, существует множество различных способов итерации +и обработки значений в `Stream`. Существуют методы комбинаторного стиля +например, `map`, `filter` и `fold` и их братьев раннего-выхода-из-за-ошибки +`try_map`, `try_filter` и `try_fold`. + +К сожалению, цикл `for` не может использоваться для `Stream`, но для +императивного стиля написания кода, могут быть использованы `while let` +и функции `next`/`try_next`: + +```rust +{{#include ../../../examples/05_02_iteration_and_concurrency/src/lib.rs:nexts}} +``` + +Однако, если мы просто обрабатываем один элемент за раз, мы потенциально +оставляем возможность для параллелизма, который, в конце концов, стоит на первом +месте при написании асинхронного кода. Для обработки нескольких элементов из +потока одновременно, используйте методы `for_each_concurrent` и +`try_for_each_concurrent`: + +```rust +{{#include ../../../examples/05_02_iteration_and_concurrency/src/lib.rs:try_for_each_concurrent}} +``` diff --git a/translations/ru/06_multiple_futures/01_chapter.md b/translations/ru/06_multiple_futures/01_chapter.md new file mode 100644 index 00000000..25dacb6b --- /dev/null +++ b/translations/ru/06_multiple_futures/01_chapter.md @@ -0,0 +1,17 @@ +# Одновременное выполнение нескольких `Future` + +До этого времени, мы в основном выполняли футуры используя +`.await`, который блокирует текущую задачу до тех +пор, пока не завершится отдельная `Future`. +Однако, настоящие асинхронные приложения чаще всего должны +выполнять несколько различных операций одновременно. + +# Одновременное выполнение нескольких `Future` + +В этой главе мы рассмотрим разные способы одновременного +выполнения нескольких асинхронных операций: + +- `join!`: ждёт завершения всех футур +- `select!`: ждёт завершения одной из футур +- Порождение: создание задач верхнего уровня, которые запускают футуры до их завершения +- `FuturesUnordered`: группа футур, которые возвращают результат каждой подфутуры diff --git a/translations/ru/06_multiple_futures/02_join.md b/translations/ru/06_multiple_futures/02_join.md new file mode 100644 index 00000000..bcd9e0cf --- /dev/null +++ b/translations/ru/06_multiple_futures/02_join.md @@ -0,0 +1,57 @@ +# `join!` + +Макрос `futures::join` позволяет дождаться завершения нескольких разных +футур при одновременном их выполнении. + +# `join!` + +При выполнении нескольких асинхронных операций возникает соблазн просто +последовательно вызвать несколько `.await`: + +```rust +{{#include ../../../examples/06_02_join/src/lib.rs:naiive}} +``` + +Однако это будет медленнее, чем необходимо, так как он не начнёт пытаться выполнять +`get_music` до завершения `get_book`. В некоторых других языках, +футуры выполняются до завершения, поэтому две операции могут быть запущены +одновременно сначала вызовом каждой `async fn`, для запуска футур, а потом их ожиданием: + +```rust +{{#include ../../../examples/06_02_join/src/lib.rs:other_langs}} +``` + +Однако футуры на Rust не будут работать, пока для них не будет вызван `.await`. +Это означает, что оба приведённых выше фрагмента кода запустят +`book_future` и `music_future` последовательно, вместо того, чтобы запустить их +параллельно. Чтобы правильно распараллелить их выполнение, используйте +`futures::join!`: + +```rust +{{#include ../../../examples/06_02_join/src/lib.rs:join}} +``` + +Значение, возвращаемое `join!` - это кортеж, содержащий выходные данные каждой +из переданных `Future`. + +## `try_join!` + +Для футур, которые возвращают `Result`, может использоваться `try_join!`, а не +`join!`. Так как `join!` завершается только после завершения всех подфутур, +он будет продолжать обрабатывать другие футуры даже после того, как одна из +подфутур вернёт `Err`. + +В отличие от`join!`, `try_join!` завершится +немедленно, если какая-либо из подфутур вернёт ошибку. + +```rust +{{#include ../../../examples/06_02_join/src/lib.rs:try_join}} +``` + +Обратите внимание, что все футуры, переданные в `try_join!`, должны иметь один и тот же тип ошибки. +Рассмотрите возможность использования функций `.map_err(|e| ...)` и `.err_into()` из +`futures::future::TryFutureExt` для приведения типов ошибок к единому виду: + +```rust +{{#include ../../../examples/06_02_join/src/lib.rs:try_join_map_err}} +``` diff --git a/translations/ru/06_multiple_futures/03_select.md b/translations/ru/06_multiple_futures/03_select.md new file mode 100644 index 00000000..b2b0a23c --- /dev/null +++ b/translations/ru/06_multiple_futures/03_select.md @@ -0,0 +1,90 @@ +# `select!` + +Макрос `futures::select` запускает несколько футур +одновременно и передаёт управление пользователю, как только любая из футур завершится. + +```rust +{{#include ../../../examples/06_03_select/src/lib.rs:example}} +``` + +Функция выше запустит обе `t1` и `t2` +параллельно. Когда `t1` или `t2` +закончится, соответствующий дескриптор вызовет +`println!` и функция завершится без выполнения +оставшейся задачи. + +Базовый синтаксис для `select`: ` = => ,`, +повторяемый столько раз, из скольких футур вам надо сделать `select`. + +## `default => ...` и `complete => ...` + +Также `select` поддерживает ветки `default` и `complete`. + +Ветка `default` выполнится, если ни одна из футур, +переданных в `select`, не завершится. Поэтому +`select` с веткой `default` всегда будет +незамедлительно завершаться, так как `default` будет +запущен, когда ещё ни одна футура не готова. + +Ветка `complete` может быть использована для +обработки случая, когда все футуры, бывшие в +`select`, завершились и уже не могут прогрессировать. Это бывает удобно, когда +`select!` используется в цикле. + +```rust +{{#include ../../../examples/06_03_select/src/lib.rs:default_and_complete}} +``` + +## Взаимодействие с `Unpin` и `FusedFuture` + +Одна вещь, на которую вы могли обратить внимание в первом +примере, это то, что мы вызвали `.fuse()` для футур, +возвращённых из двух `async fn`, а потом закрепили +их с помощью `pin_mut`. Оба этих вызова важны, +потому что футуры, используемые в `select`, должны +реализовывать и типаж `Unpin`, и типаж +`FusedFuture`. + +`Unpin` важен по той причине, что футуры, используемые в `select`, берутся не по +значению, а по изменяемой ссылке. Так как владение футурами никому не передано, +незавершённые футуры могут быть снова использованы после вызова `select`. + +Аналогично, типаж `FusedFuture` необходим, так как `select` не должен опрашивать футуры после их +завершения. `FusedFuture` реализуется футурами, которые отслеживают, завершены ли они или нет. Это делает +возможным использование `select` в цикле, опрашивая только те футуры, которые до сих пор не завершились. +Это можно увидеть в примере выше, где `a_fut` или `b_fut` будут завершены во второй раз за цикл. Так +как футура, возвращённая `future::ready`, реализует `FusedFuture`, она может сообщить +`select`, что её не надо снова опросить. + +Заметьте, что у стримов есть соответствующий типаж `FusedStream`. Стримы, реализующие этот типаж +или имеющие обёртку, созданную `.fuse()`, возвращают `FusedFuture` из комбинаторов +`.next()` и `.try_next()`. + +```rust +{{#include ../../../examples/06_03_select/src/lib.rs:fused_stream}} +``` + +## Распараллеливание задач в цикле с `select` с помощью `Fuse` и `FuturesUnordered` + +Одна довольно труднодоступная, но удобная функция - `Fuse::terminated()`, которая позволяет создавать уже +прекращённые пустые футуры, которые в последствии могут быть заполнены другой футурой, которую надо запустить. + +Это может быть удобно, когда есть задача, которую надо запустить в цикле в `select`, но которая +создана вне этого цикла. + +Обратите внимание на функцию `.select_next_some()`. Она может использоваться с +`select` для запуска полученных из стрима тех ветвей, которые имеют значение +`Some(_)`, а не `None`. + +```rust +{{#include ../../../examples/06_03_select/src/lib.rs:fuse_terminated}} +``` + +Когда надо одновременно запустить много копий какой-либо футуры, используйте тип `FuturesUnordered`. +Следующий пример похож на один из тех, что был выше, но здесь мы дожидаемся завершения каждой выполненной копии +`run_on_new_num_fut`, а не останавливаем её при создании новой. Он также отобразит значение, возвращённое +`run_on_new_num_fut`. + +```rust +{{#include ../../../examples/06_03_select/src/lib.rs:futures_unordered}} +``` diff --git a/translations/ru/07_workarounds/01_chapter.md b/translations/ru/07_workarounds/01_chapter.md new file mode 100644 index 00000000..bdba65fe --- /dev/null +++ b/translations/ru/07_workarounds/01_chapter.md @@ -0,0 +1,7 @@ +# Обходные пути, которые мы понимаем и любим + +Поддержка `async` в Rust всё ещё довольно нова и +некоторые востребованные функции активно разрабатываются, а +некоторые диагностики до сих пор не полноценны. В этой главе +обсуждаются некоторые болевые точки и объясняется как с ними +работать. diff --git a/translations/ru/07_workarounds/02_return_type.md b/translations/ru/07_workarounds/02_return_type.md new file mode 100644 index 00000000..c84844ca --- /dev/null +++ b/translations/ru/07_workarounds/02_return_type.md @@ -0,0 +1,77 @@ +# Ошибки вывода для возвращаемых типов + +В типичной функции на Rust, возврат значения неправильного типа +приведёт к тому, что мы увидим примерно такую ошибку: + +``` +error[E0308]: mismatched types + --> src/main.rs:2:12 + | +1 | fn foo() { + | - expected `()` because of default return type +2 | return "foo" + | ^^^^^ expected (), found reference + | + = note: expected type `()` + found type `&'static str` +``` + +Однако текущая версия `async fn` не знает как +"доверять" возвращаемому типу, записанному в сигнатуре +функции, что приводит к не совпадающим или `reversed-sounding` +ошибкам. Например, для функции `async fn foo() { "foo" }` +будет следующая ошибка: + +``` +error[E0271]: type mismatch resolving `::Output == ()` + --> src/lib.rs:1:16 + | +1 | async fn foo() { + | ^ expected &str, found () + | + = note: expected type `&str` + found type `()` + = note: the return type of a function must have a statically known size +``` + +Ошибка говорит, что *ожидает* `&str`, а +находит `()`, что совершенно противоположно тому, +что мы хотим. Это потому, что компилятор +некорректно разрешает телу функции вернуть корректный тип. + +Временным решением для этой проблемы является признание +факта, что ошибка, указывающая на сигнатуру функции с +сообщением "expected `SomeType`, found `OtherType`", обычно показывает, что один или +несколько возвращаемых вариантов не корректны. + +Исправление этой ошибки отслеживается [здесь](https://github.com/rust-lang/rust/issues/54326). + +## `Box` + +Аналогично, так как возвращаемый тип из сигнатуры функции не +распространяется должным образом, значение, которое +возвращает`async fn` не правильно приводится к +ожидаемому типу. + +На практике, это означает, что возвращаемый из `async fn` +объект `Box` требует ручного +преобразования при помощи `as` из `Box` в `Box`. + +Этот код приведёт к ошибке: + +``` +async fn x() -> Box { + Box::new("foo") +} +``` + +Временным решением для этого будет ручное преобразование с +использованием `as`: + +``` +async fn x() -> Box { + Box::new("foo") as Box +} +``` + +Исправление этой ошибки отслеживается [здесь](https://github.com/rust-lang/rust/issues/60424). diff --git a/translations/ru/07_workarounds/03_err_in_async_blocks.md b/translations/ru/07_workarounds/03_err_in_async_blocks.md new file mode 100644 index 00000000..34c6d092 --- /dev/null +++ b/translations/ru/07_workarounds/03_err_in_async_blocks.md @@ -0,0 +1,42 @@ +# `?` в `async` блоках + +Как и в `async fn`, `?` также может +использоваться внутри `async` блоков. Однако +возвращаемый тип `async` блоков явно не +указывается. Это может привести тому, что компилятор не сможет +определить тип ошибки `async` блока. + +Например, этот код: + +```rust +let fut = async { + foo().await?; + bar().await?; + Ok(()) +}; +``` + +вызовет ошибку: + +``` +error[E0282]: type annotations needed + --> src/main.rs:5:9 + | +4 | let fut = async { + | --- consider giving `fut` a type +5 | foo().await?; + | ^^^^^^^^^^^^ cannot infer type +``` + +К сожалению, сейчас не способа "задать тип для `fut`" +кроме как явно указать возвращаемый тип `async` +блока. Для обработки этого, используйте "turbofish" оператор для +предоставления типов ошибки и успеха `async` блока: + +```rust +let fut = async { + foo().await?; + bar().await?; + Ok::<(), MyError>(()) // <- обратите внимание на явное указание типа +}; +``` diff --git a/translations/ru/07_workarounds/04_send_approximation.md b/translations/ru/07_workarounds/04_send_approximation.md new file mode 100644 index 00000000..8b4378d8 --- /dev/null +++ b/translations/ru/07_workarounds/04_send_approximation.md @@ -0,0 +1,99 @@ +# `Send` Approximation + +Некоторые машины состояний асинхронных функций безопасны +для передачи между потокам, в то время как другие - нет. Так или +иначе, `async fn` `Future` является +`Send` если тип, содержащийся в +`.await`, тоже `Send`. Компилятор +делает всё возможное, чтобы при близиться к моменту, когда +значения могут удерживаться в `.await`, но сейчас в +некоторых местах этот анализ слишком консервативен. + +Например, рассмотрим простой не-`Send` тип, +например, содержащий `Rc`: + +```rust +use std::rc::Rc; + +#[derive(Default)] +struct NotSend(Rc<()>); +``` + +Переменные типа `NotSend` могут появляться как +временные внутри `async fn` даже когда тип `Future`, +возвращаемой из `async fn` должен быть +`Send`: + +```rust +async fn bar() {} +async fn foo() { + NotSend::default(); + bar().await; +} + +fn require_send(_: impl Send) {} + +fn main() { + require_send(foo()); +} +``` + +Но если мы изменим `foo` таким образом, что она +будет хранить `NotSend` в переменной, пример не +скомпилируется: + +```rust +async fn foo() { + let x = NotSend::default(); + bar().await; +} +``` + +``` +error[E0277]: `std::rc::Rc<()>` cannot be sent between threads safely + --> src/main.rs:15:5 + | +15 | require_send(foo()); + | ^^^^^^^^^^^^ `std::rc::Rc<()>` cannot be sent between threads safely + | + = help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>` + = note: required because it appears within the type `NotSend` + = note: required because it appears within the type `{NotSend, impl std::future::Future, ()}` + = note: required because it appears within the type `[static generator@src/main.rs:7:16: 10:2 {NotSend, impl std::future::Future, ()}]` + = note: required because it appears within the type `std::future::GenFuture<[static generator@src/main.rs:7:16: 10:2 {NotSend, impl std::future::Future, ()}]>` + = note: required because it appears within the type `impl std::future::Future` + = note: required because it appears within the type `impl std::future::Future` +note: required by `require_send` + --> src/main.rs:12:1 + | +12 | fn require_send(_: impl Send) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`. +``` + +Эта ошибка корректна. Если мы сохраним `x` в +переменную, она не будет удалена пока не будет завершён +`.await`. В этот момент `async fn` может +быть запущена в другом потоке. Так как `Rc` не +является `Send`, перемещение между потоками будет +некорректным. Простым решением будет вызов +`drop` у `Rc` до вызова +`.await`, но к сожалению пока что это не работает. + +Для того, чтобы успешно обойти эту проблему, вы можете создать +блок, инкапсулирующий любые не-`Send` +переменные. С помощью этого, компилятору будет проще понять, +что такие переменные не переживут момент вызова +`.await`. + +```rust +async fn foo() { + { + let x = NotSend::default(); + } + bar().await; +} +``` diff --git a/translations/ru/07_workarounds/05_recursion.md b/translations/ru/07_workarounds/05_recursion.md new file mode 100644 index 00000000..a912f963 --- /dev/null +++ b/translations/ru/07_workarounds/05_recursion.md @@ -0,0 +1,55 @@ +# Рекурсия + +Под капотом, `async fn` создаёт тип конечного автомата, содержащий каждую подфутуру +для который был вызван `.await`. Из-за этого +рекурсивные `async fn` становятся более сложными, +так как итоговый конечный автомат содержит сам себя: + +```rust +// Эта функция: +async fn foo() { + step_one().await; + step_two().await; +} +// создаёт типы, подобные следующим: +enum Foo { + First(StepOne), + Second(StepTwo), +} + +// А эта функция: +async fn recursive() { + recursive().await; + recursive().await; +} + +// создаёт такие типы: +enum Recursive { + First(Recursive), + Second(Recursive), +} +``` + +Это не будет работать - мы создали тип бесконечного размера! +Компилятор будет жаловаться: + +``` +error[E0733]: recursion in an `async fn` requires boxing + --> src/lib.rs:1:22 + | +1 | async fn recursive() { + | ^ an `async fn` cannot invoke itself directly + | + = note: a recursive `async fn` must be rewritten to return a boxed future. +``` + +Чтобы исправить это, мы должны ввести косвенность при помощи +`Box`. К сожалению, из-за ограничений компилятора, +обернуть вызов `recursive()` в `Box::pin` +не достаточно. Чтобы это заработало, мы должны сделать +`recursive` не асинхронной функцией, которая +возвращает `.boxed()` с `async` блоком: + +```rust +{{#include ../../../examples/07_05_recursion/src/lib.rs:example}} +``` diff --git a/translations/ru/07_workarounds/06_async_in_traits.md b/translations/ru/07_workarounds/06_async_in_traits.md new file mode 100644 index 00000000..d5c5552f --- /dev/null +++ b/translations/ru/07_workarounds/06_async_in_traits.md @@ -0,0 +1,14 @@ +# `async` в типажах + +В настоящий момент `async fn` не могут +использоваться в типажах. Причиной является большая сложность, +но снятие этого ограничения находится в планах на будущее. + +Однако вы можете обойти это ограничение при помощи [пакета `async_trait` с crates.io](https://github.com/dtolnay/async-trait). + +Заметьте, что использование этих методов типажей приведёт к +выделениям памяти в куче для каждого вызова функции. Это не +значительная стоимость для большинства приложений, но следует +это учитывать при принятии решения использовать данную +функциональность в публичном API низкоуровневых функций, +которые будут вызываться миллион раз в секунду. diff --git a/translations/ru/404.md b/translations/ru/404.md new file mode 100644 index 00000000..e69de29b diff --git a/translations/ru/SUMMARY.md b/translations/ru/SUMMARY.md new file mode 100644 index 00000000..9cd7d766 --- /dev/null +++ b/translations/ru/SUMMARY.md @@ -0,0 +1,35 @@ +# Содержание + +- [Начало работы](01_getting_started/01_chapter.md) + - [Для чего нужна асинхронность?](01_getting_started/02_why_async.md) + - [Состояние асинхронности в Rust](01_getting_started/03_state_of_async_rust.md) + - [Пример `async`/`.await`](01_getting_started/04_async_await_primer.md) + - [Применение: HTTP сервер](01_getting_started/05_http_server_example.md) +- [Под капотом: выполнение `Future` и задач](02_execution/01_chapter.md) + - [Типаж `Future`](02_execution/02_future.md) + - [Вызовы задачи при помощи `Waker`](02_execution/03_wakeups.md) + - [Применение: создание исполнителя](02_execution/04_executor.md) + - [Исполнители и системный ввод/вывод](02_execution/05_io.md) +- [`async`/`await`](03_async_await/01_chapter.md) +- [Закрепление (pinning)](04_pinning/01_chapter.md) +- [Потоки](05_streams/01_chapter.md) + - [Итерирование и параллелизм](05_streams/02_iteration_and_concurrency.md) +- [Одновременное выполнение нескольких `Future` ](06_multiple_futures/01_chapter.md) + - [`join!`](06_multiple_futures/02_join.md) + - [`select!`](06_multiple_futures/03_select.md) + - [TODO: Порождение](404.md) + - [TODO: Отмена и таймауты](404.md) + - [TODO: `FuturesUnordered`](404.md) +- [Обходные пути, которые мы понимаем и любим](07_workarounds/01_chapter.md) + - [Ошибки вывода для возвращаемых типов](07_workarounds/02_return_type.md) + - [`?` в `async` блоках](07_workarounds/03_err_in_async_blocks.md) + - [`Send` Approximation](07_workarounds/04_send_approximation.md) + - [Рекурсия](07_workarounds/05_recursion.md) + - [`async` в типажах](07_workarounds/06_async_in_traits.md) +- [TODO: Ввод/вывод](404.md) + - [TODO: `AsyncRead` и `AsyncWrite`](404.md) +- [TODO: Паттерны асинхронного дизайна: решения и предложения](404.md) + - [TODO: Моделирование серверов и паттерн Request/Response](404.md) + - [TODO: Управление общим состоянием](404.md) +- [TODO: Экосистема: Tokio и другие](404.md) + - [TODO: Много, много больше?...](404.md)