Avsnitt
-
第47章へようこそ。
今日のテーマは「タスクのライフサイクル」です。
第44章で、1つの流れの中で複数のタスクを交互に進めることで並行を作る、非同期処理を見ました。
第45章で、asyncをつけた関数が返すFutureという、いずれ手に入る結果を表すあたいを見ました。
第46章で、そのFutureにpollを繰り返し呼んで完了まで進める実行器を見ました。
今日扱うのは、タスクが途中でパニックしたとき何が起きるか、そしてタスクを途中でやめるキャンセルという操作が何を意味するか、です。
-
第46章へようこそ。
今日のテーマは「実行器」です。
第45章で、Futureは自分自身を進める手段を持たないと見ました。
pollを繰り返し呼んで完了まで進める主体は、Futureの外にいる。
今日扱うのは、その外にいる主体が何か、それがどうやって無駄なくpollを繰り返すのか、そしてRustがその主体を標準では持たない設計です。
-
Saknas det avsnitt?
-
第45章へようこそ。
今日のテーマは「Future」です。
第44章で、asyncをつけた関数が、中断と再開ができる関数になることを見ました。
asyncをつけた関数を呼ぶと、結果そのものではなく、Futureと呼ばれる値が1つ返ってきます。
今日扱うのは、このFutureが何を表すのか、外からの問い合わせにどう答えるのか、そして中断した地点までの進行をどこに保持するのか、です。
-
第44章へようこそ。
今日から、非同期処理を扱う一連の章に入ります。
今日のテーマは「非同期入門」です。
これまでの一連の章で見たのは、スレッドによる並行処理でした。
スレッドを使って流れを複数に分け、それぞれの流れを別々に進める方法です。
非同期処理は、並行処理を作るもう1つの道です。
スレッドとは異なる方法で、複数の処理を同時に進行させます。
今日扱うのは、なぜスレッドとは別の道が要るのか、並行と並列という2つの言葉が指す違い、そして非同期がどちらの軸に位置するのか、という基本構造です。
-
第43章へようこそ。
今日のテーマは「デッドロック」です。
ここまで、複数のスレッドが安全に値を共有するための手段を見てきました。
MutexやRwLockは、ロックを取った流れだけに接触を許し、ほかの流れを待たせることで、同時の書き換えを防いだ。
ロックは、待たせることで安全を作る仕組みでした。
しかし、待たせることそのものが、新しい停止を生むことがあります。
複数の流れが、互いに相手の解放を待ち、どちらも進めなくなる。
この、互いを待ち続けて永遠に進まない状態を、デッドロックと呼びます。
デッドロックは、Rustがコンパイル時に防がない種類の問題です。
今日扱うのは、デッドロックがなぜ起こるのか、なぜ型システムがそれを防がないのか、
そして、取得の順序を統一することがなぜ回避になるのか、という思考法です。
-
第42章へようこそ。
今日のテーマは「アトミック」です。
これまでの章で、共有した値を複数のスレッドから安全に書き換える手段として、ロックを見てきました。
MutexやRwLockは、ロックを取った流れだけに値への接触を許し、ほかの流れを待たせる。
ロックを取り、解放するという手順そのものにコストがかかる。
しかし、共有したい値が1つの数や1つの真偽の値のように単純なとき、ロックでひとくくりに囲むのは大げさです。
ロックを使わずに、その1つの値の更新だけを安全に行う手段がある。
それがアトミックです。
第37章と第39章で、参照カウントの操作が「原子的」であるかどうかを見ました。
今日は、その「原子的」を正面から扱います。
今日扱うのは、アトミックが何を保証するのか、メモリ順序という付随する指定、そしてアトミックで扱える範囲の限界です。
-
第41章へようこそ。
今日のテーマは「RwLock」です。
前章のミューテックスは、読み取りでも書き換えでも、一度に1つの流れだけにあたいへの接触を許しました。
触れる目的が読み取りであっても、ロックを取った1つの流れが終えるまで、ほかの流れは待つ。
しかし、複数の流れが同じ値をただ読むだけなら、同時に触れても問題は起こりません。
第37章で見たように、読み取りしか起こらないなら、書き換えによる競合は生じないからです。
読み取りどうしは衝突しないのに、Mutexはそれも1つずつに制限する。
読み取りが多い場面では、この制限が並行性を必要以上に下げる。
RwLockは、読み取りと書き換えを区別するロックです。
読み取りは複数の流れに同時に許し、書き換えだけを1つの流れに絞る。
排他の範囲を、接触の目的によって変える。
今日扱うのは、RwLockが提供する2種類のロック、その取得の規則が借用の規則とどう重なるのか、
そして、どういう場面でこの区別が効くのか、です。
-
第40章へようこそ。
今日のテーマは「Mutex」です。
前章で、Arcが共有された読み取りのアクセスを与えることを見ました。
複数のスレッドが1つの値を共通の所有者として持ち、それぞれが読める。
ただし、Arcが解決するのは「誰がその値を所有するか」までで、「いつその値を書き換えてよいか」は別の問題でした。
複数のスレッドが同じ値を同時に書き換えれば、書き込みが互いに干渉し、データ競合が起こる。
Mutexは、この「いつ書き換えてよいか」を解く仕組みです。
1つの値に対して、一度に1つのスレッドだけが触れることを保証する。
触れている流れがいる間、ほかの流れは待つ。
この「一度に1つだけ」という保証を、排他アクセスと呼びます。
今日扱うのは、Mutexが排他アクセスをどう保証するのか、それを支えるガードの仕組み、
そしてArcと組み合わせて複数のスレッドで可変な値を共有する形です。
-
第39章へようこそ。
今日のテーマは「Arc」です。
ここまで、並行処理で値を扱う道は、所有権を移すことでした。
第36章のmoveは、値の所有権を新しいスレッドへ移した。
第38章のチャネルは、値の所有権を流れの間で送って受け渡した。
どちらも、値は常にどこか1つの流れにあり、移ることで次の流れへ渡っていく。
しかし、値を移すのではなく、1つの値を複数のスレッドが同時に保持したい場面があります。
大きな値を、それぞれのスレッドへ複製して配るのは無駄が大きい。
1つの値を、複数のスレッドが共通の所有者として持ちたい。
ここから、所有権を渡さずに共有する道に入ります。
今日扱うのは、複数のスレッドが1つの値を共有して所有するための型、Arcです。
第22章のRcを振り返りながら、なぜRcではスレッドを越えられないのか、
Arcがそれをどう解決するのか、その代償は何か、という核心を見ていきます。
-
第38章へようこそ。
今日のテーマは「チャネル」です。
第36章で、moveが値の所有権を新しいスレッドへ移すことを見ました。
あのときの移動は、スレッドを生成する一度きりのものでした。
クロージャに渡した値が、新しいスレッドへ移って、そこで使われる。
チャネルは、この所有権の移動を、スレッドが動いている間ずっと繰り返せるようにする仕組みです。
1つの流れが値を送り出し、別の流れがそれを受け取る。
送り出した側は、その値の所有権を手放す。
受け取った側が、新しい所有者になる。
スレッドを生成するときの一度きりの移動が、実行の途中で何度でも起こる形に広がる。
これは、第36章のmoveの延長です。
所有権を移して受け渡すという同じ考え方が、動き続ける流れの間の通信に使われる。
今日扱うのは、チャネルがどう所有権を運ぶのか、受け取る側がどう待つのか、
複数の送り手をどう束ねるのか、そして送る速さと受け取る速さをどう調停するのか、です。
-
第37章へようこそ。
今日のテーマは、SendとSyncです。
前章で、スレッドに値を渡すときは所有権ごと渡すことを見ました。
moveが、外側の値の所有権をクロージャへ移し、新しいスレッドへ持ち込む。
ただし、すべての値がスレッド境界を越えてよいわけではありません。
ある値をスレッドに渡すと、データ競合という危険が生じる場合がある。
データ競合は、複数の流れが同じ値に同時に触れ、少なくとも一方が書き換えるときに起こる、結果の定まらない状態のことです。
Rustは、この危険を実行してから気づくのではなく、コンパイルの時点で防ぐ。
そのために、どの型がスレッド境界を越えてよいかを、型のうえで区別する。
この区別を担うのが、SendとSyncという2つのトレイトです。
今日扱うのは、SendとSyncがそれぞれ何を表すのか、これらがどう自動的に決まるのか、
そして、なぜRcがこの境界を越えられないのか、という核心です。
-
第36章へようこそ。
今日から、並行処理を扱う一連の章に入ります。
最初のテーマは「スレッド」です。
スレッドは、プログラムの中で独立して進む実行の流れのことです。
1つのプログラムが複数の流れを同時に持つと、別々の処理が並行して進む。
並行処理は、所有権の規律が新しい場面に置かれる領域です。
これまで所有権は、1つの実行の流れの中で、値がいつ生きていつ解放されるかを決めてきました。
スレッドが増えると、値が2つ以上の流れにまたがる可能性が出てくる。
値が流れをまたぐとき、誰がそれを所有し、いつ解放するかを決める規律が要る。
Rustはこれを、型と所有権の規律で扱います。
今日扱うのは、スレッドの生成、スレッドの終了を待つ仕組み、
そして、なぜスレッドに渡す値は所有権ごと渡さなければならないのか、という核心です。
-
第35章へようこそ。
今日のテーマは「手続き的マクロ」です。
これまでの3章で扱ってきたのは宣言的マクロでした。
macro_rules!の宣言で、パターンと展開の組をルールとして並べる。
入力のトークンがパターンに照らされ、対応する展開が呼び出し位置に差し込まれる。
宣言的マクロには表現の限界があります。
パターンと展開の対応で書ける範囲を超えた変換、たとえば構造体のフィールド名を文字列として取り出して別のコードに埋め込む、こうした処理は宣言的の枠を超える。
これを扱うために用意されているのが、今日扱う手続き的マクロです。
今日扱うのは、宣言的マクロの限界、手続き的マクロが解く問題、3つの形態、トークンストリームという界面、synとquoteによる分業、そして別クレートとしてコンパイルされる構造的な理由です。
-
第34章へようこそ。
今日のテーマは「繰り返しと衛生性」です。
前章で、フラグメント指定子がトークン列の中の特定の位置を捕捉する仕組みを見ました。
式を1つ、識別子を1つ、型を1つ、それぞれ捕捉する。
固定の個数なら、これだけで足りる。
しかしマクロは、任意個のトークンを受け取りたい場面がある。
vecマクロは要素を何個でも受け取って配列を作る。
また、マクロが導入した変数の名前が、呼び出し側の変数の名前と衝突しないように、識別子のスコープにも工夫が要る。
今日扱うのは、可変長のトークンを扱う繰り返し構文、
繰り返しでは難しい変換を担う再帰的展開、
マクロの識別子が呼び出し側に漏れない衛生性、
そしてマクロ内のパスがクレート境界を越えても壊れない仕組みです。
-
第33章へようこそ。
今日のテーマは「マクロのパターン」です。
前章で、マクロ定義はパターンと展開の組であるルールの集まりだと見ました。
呼び出し位置にあるトークン列がパターンに照らされ、一致したルールの展開部分が差し込まれる。
ではパターンの中身はどう書くのか。
ここで中心になるのが、フラグメント指定子と呼ばれる仕組みです。
パターンの中の特定の位置に「ここは式が来る」「ここは識別子が来る」「ここは型が来る」と宣言することで、トークン列に意味の粒度を与える。
今日扱うのは、フラグメント指定子の体系、特に万能だが粗い「任意のトークンツリー」の役割、
指定子の後ろに何が来てよいかの制約、
複数のルールがどう選ばれるかの順序、
そして指定子の粒度の設計判断です。
-
第32章へようこそ。
今日のテーマは「宣言的マクロ」です。
これまでの章では、関数を使ってコードの構造を組み立ててきました。
引数を取り、処理を行い、戻り値を返す。
再利用したい処理を関数として切り出す。
ほとんどの抽象は関数で書ける。
しかし関数では書けないものがあります。
要素の個数を決めずに配列を作る、引数の型に応じてフォーマットを組み立てる、ある構造体に対する一連のメソッドをまとめて生成する。
こうした操作は関数の枠を出ている。
これらを書くために、Rustにはマクロという別の仕組みが用意されている。
今日扱うのは、マクロが関数では届かない領域を扱う仕組みであること、
コンパイル時にコードを変換するという位置づけ、
そして基本の道具であるmacro_rules!の構造です。
-
第31章へようこそ。
今日のテーマはCargoです。
Cargoはビルドツール、パッケージマネージャ、テストランナーを兼ねた、Rustに同梱されているツールです。
コマンドの使い方は調べれば分かるので、本章では扱わない。
扱うのは「Cargoがあることで、Rustの言語側で何が成立しているか」です。
Rustは、コンパイラだけが用意されている言語ではない。
言語仕様、コンパイラ、標準ツール、この3つが一体となって配布されている。
標準ツールとしてCargoがあることが、Rustの言語そのものの設計判断にどう関わっているか。
今日はその関係を扱います。
-
第30章へようこそ。
ここまでの章では、型、所有権、参照、コレクションといった個々の仕組みを扱ってきました。
これらを使ってプログラムを実際に組み立てると、コードは大きくなる。
大きくなったコードを「どう配置し、何を外に見せ、どう参照するか」を決めるのが、モジュールシステムの役割です。
Rustのモジュールシステムには3つの構文があります。
モジュール宣言、public指定、use宣言の3つです。
ソースコード上では、moduleを3文字に略してエム・オー・ディーと綴るのがモジュール宣言、
publicを3文字に略してピー・ユー・ビーと綴るのがpublic指定、
useをそのままユー・エス・イーと綴るのがuse宣言です。
本章では、この3つの構文をそれぞれモジュール宣言、public指定、use宣言という名前で呼んでいきます。
モジュール宣言は名前空間を組み立てる。
public指定は何を外から見えるかを選ぶ。
use宣言は別の場所の名前を短く参照するためにスコープに持ち込む。
それぞれの機能は単純で、1文ずつで言い切れる。
3つが別々の軸を扱うために、別々の構文として分かれている。
今日扱うのは、それぞれが何をしているかと、3つを別々の軸として読むことが何を整理するか。
構造の軸、境界の軸、名前の軸。この3つの分けで章を進めます。
-
第29章へようこそ。
これまでに扱ったHashMapとHashSetには、共通する性質がありました。
内部はハッシュテーブルで、要素はハッシュ関数によって決まる位置に置かれる。
そのため、要素は挿入順にも整列順にも並ばず、イテレーションの順序は保証されない。
個々の操作はほぼ一定時間で動くが、順序にまつわる操作は構造的に難しい。
順序が必要な場面があります。
キーが範囲で問い合わされる場合。たとえば「ある日付以降のすべてのエントリ」「キーが10から20の間にあるもの」。
イテレーション順序が一定であることが望ましい場合。出力順を整えたい、結果を予測可能にしたい。
最小のキーや最大のキーを高速に取り出したい場合。
これらの場面のために用意されているのがBTreeMapとBTreeSetです。
役割はHashMapとHashSetと同じく、ペアの対応表と要素の集合。違うのは内部構造で、ハッシュテーブルではなくBツリーと呼ばれる木構造を使う。
今日扱うのは、Bツリーがどう順序を保つか、その代償は何か、求められるトレイトはなぜ変わるか、そして範囲クエリという固有の操作です。
-
第28章へようこそ。
これまでの章では、Vecが連続した領域に要素を並べる構造、HashMapがハッシュ関数でキーと値を対応させる構造として扱ってきました。
この2つはRustでよく使われるコレクションだが、すべての用途を覆えるわけではない。
VecとHashMapには、それぞれの構造から来る向き不向きがある。
Vecは連続配置のため、両端のうち末尾は高速だが、先頭への操作は重い。
HashMapはペアを管理するため、あたいを持たない単純な「要素の集合」を表現するには余分な情報を持つことになる。
これらの不足を埋めるために、VecとHashMapの構造を別の用途に特化させた派生形がある。
今日扱うのは、HashSetとVecDeque。
HashSetはHashMapを集合用に特化させたもの、VecDequeはVecを両端操作用に特化させたものです。
それぞれが何を変えて、何を引き継いでいるかを構造から見ていきます。
- Visa fler