diff --git a/.dockerignore b/.dockerignore index 652c688..5563600 100755 --- a/.dockerignore +++ b/.dockerignore @@ -1,17 +1,17 @@ -# Keep the build context small and deterministic. -# NOTE: do NOT exclude *.md — the book sources are markdown. +# Держим контекст сборки небольшим и детерминированным. +# ПРИМЕЧАНИЕ: НЕ исключайте *.md — исходники книг написаны в Markdown. .git .github .gitignore -# Build output (mirrors .gitignore) +# Результаты сборки (повторяют .gitignore) target/ site/ docs/ **/book/ -# Editor / OS noise +# Файлы редакторов и ОС .vscode/ .idea/ **/*:Zone.Identifier diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2de737c..de3e829 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,7 +1,8 @@ -name: Docker image +name: Образ Docker -# Build-only. Nothing is published, so this adds no release surface or -# registry credentials — it exists so the Dockerfile cannot silently rot. +# Только сборка. Ничего не публикуется, поэтому нет дополнительной поверхности +# релизов и учётных данных реестра. Workflow нужен, чтобы Dockerfile не +# сломался незаметно. on: push: branches: [main] @@ -31,7 +32,7 @@ jobs: - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - - name: Build image + - name: Сборка образа uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . @@ -42,7 +43,7 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - - name: Smoke test + - name: Дымовой тест run: | docker run -d --name books -p 3000:8080 rust-training:ci for i in $(seq 1 30); do @@ -50,12 +51,12 @@ jobs: sleep 2 done if [ "${ok:-0}" != "1" ]; then - echo "::error::server did not come up"; docker logs books; exit 1 + echo "::error::сервер не поднялся"; docker logs books; exit 1 fi - # Landing page and at least one book must resolve. - # NB: match the
` guarantees and how it prevents moves -> - The three practical pinning patterns: `Box::pin()`, `tokio::pin!()`, `Pin::new()` -> - When `Unpin` gives you an escape hatch +> **Что вы узнаете:** +> - Почему самоссылающиеся структуры ломаются при перемещении в памяти +> - Что гарантирует `Pin
` и как он предотвращает перемещение
+> - Три практических паттерна закрепления: `Box::pin()`, `tokio::pin!()`, `Pin::new()`
+> - Когда `Unpin` даёт вам лазейку
-## Why Pin Exists
+## Зачем нужен Pin
-This is the most confusing concept in async Rust. Let's build the intuition step by step.
+Это самое запутанное понятие в асинхронном Rust. Построим интуицию шаг за шагом.
-### The Problem: Self-Referential Structs
+### Проблема: самоссылающиеся структуры
-When the compiler transforms an `async fn` into a state machine, that state machine may contain references to its own fields. This creates a *self-referential struct* — and moving it in memory would invalidate those internal references.
+Когда компилятор превращает `async fn` в конечный автомат, этот автомат может содержать ссылки на собственные поля. Так возникает *самоссылающаяся структура* — и её перемещение в памяти сделает внутренние ссылки недействительными.
```rust
-// What the compiler generates (simplified) for:
+// Что компилятор генерирует (упрощённо) для:
// async fn example() {
// let data = vec![1, 2, 3];
-// let reference = &data; // Points to data above
+// let reference = &data; // Указывает на data выше
// use_ref(reference).await;
// }
-// Becomes something like:
+// Превращается во что-то вроде:
enum ExampleStateMachine {
State0 {
data: Vec ` is a wrapper that prevents moving the value behind the pointer:
+`Pin ` — это обёртка, которая запрещает перемещать значение, на которое указывает указатель:
```rust
use std::pin::Pin;
let mut data = String::from("hello");
-// Pin it — now it can't be moved
+// Закрепляем — теперь его нельзя переместить
let pinned: Pin<&mut String> = Pin::new(&mut data);
-// Can still use it:
+// Всё ещё можно использовать:
println!("{}", pinned.as_ref().get_ref()); // "hello"
-// But we can't get &mut String back (which would allow mem::swap):
-// let mutable: &mut String = Pin::into_inner(pinned); // Only if String: Unpin
-// String IS Unpin, so this actually works for String.
-// But for self-referential state machines (which are !Unpin), it's blocked.
+// Но получить обратно &mut String нельзя (это позволило бы mem::swap):
+// let mutable: &mut String = Pin::into_inner(pinned); // Только если String: Unpin
+// String — это Unpin, поэтому здесь это на самом деле работает.
+// Но для самоссылающихся конечных автоматов (они !Unpin) это заблокировано.
```
-In real code, you mostly encounter Pin in three places:
+В реальном коде Pin встречается в основном в трёх местах:
```rust
-// 1. poll() signature — all futures are polled through Pin
+// 1. Сигнатура poll() — все future опрашиваются через Pin
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll
at addr 0x1000"]
- B["reference: 0x1000
(points to data)"]
- B -->|"valid"| A
+ subgraph "До перемещения (корректно)"
+ A["data: [1,2,3]
по адресу 0x1000"]
+ B["reference: 0x1000
(указывает на data)"]
+ B -->|"корректно"| A
end
- subgraph "After Move (INVALID)"
- C["data: [1,2,3]
at addr 0x2000"]
- D["reference: 0x1000
(still points to OLD location!)"]
- D -->|"dangling!"| E["💥 0x1000
(freed/garbage)"]
+ subgraph "После перемещения (НЕКОРРЕКТНО)"
+ C["data: [1,2,3]
по адресу 0x2000"]
+ D["reference: 0x1000
(всё ещё указывает на СТАРОЕ место!)"]
+ D -->|"висячий указатель!"| E["💥 0x1000
(освобождено / мусор)"]
end
style E fill:#ffcdd2,color:#000
@@ -56,141 +56,139 @@ graph LR
style B fill:#c8e6c9,color:#000
```
-### Self-Referential Structs
+### Самоссылающиеся структуры
-This isn't an academic concern. Every `async fn` that holds a reference across an `.await` point creates a self-referential state machine:
+Это не академическая проблема. Любая `async fn`, которая удерживает ссылку через точку `.await`, создаёт самоссылающийся конечный автомат:
```rust
async fn problematic() {
let data = String::from("hello");
- let slice = &data[..]; // slice borrows data
-
- some_io().await; // <-- .await point: state machine stores both data AND slice
-
- println!("{slice}"); // uses the reference after await
+ let slice = &data[..]; // slice заимствует data
+
+ some_io().await; // <-- точка .await: автомат хранит и data, и slice
+
+ println!("{slice}"); // использует ссылку после await
}
-// The generated state machine has `data: String` and `slice: &str`
-// where slice points INTO data. Moving the state machine = dangling pointer.
+// Сгенерированный автомат содержит `data: String` и `slice: &str`,
+// где slice указывает ВНУТРЬ data. Перемещение автомата = висячий указатель.
```
-### Pin in Practice
+### Pin на практике
-`Pin