Slide 1

Slide 1 text

Rust速習会(3) Webサーバー 2018-11-01 @Wantedlyオフィス (白金台) 原 将己 (@qnighy) 実況用ハッシュタグ: #rust_jp

Slide 2

Slide 2 text

Are We Web Yet? • RustのWeb関係のライブラリの まとめサイト https://www.arewewebyet.org/

Slide 3

Slide 3 text

Webフレームワーク

Slide 4

Slide 4 text

Iron ironframework.io • 安定版コンパイラで動作 / 非同期は未対応 • 昔の覇権 • ちょっと前までメンテナンスが放棄されていたが、メンテナを 募集して復活したっぽい

Slide 5

Slide 5 text

Rocket rocket.rs • 不安定版コンパイラで動作 / 非同期は未対応 • 構文拡張 (独自の #[]) をふんだんに使って書きやすくしてい る • そのため、nightlyの更新ですぐに壊れる • 全体的に作りこまれていて人気っぽい

Slide 6

Slide 6 text

Gotham gotham.rs • 安定版コンパイラで動作 / 非同期対応 • futures/tokioの非同期をうまく使って無難に設計されている • 作りこみは甘い印象 • メンテナ不足のアナウンスが出ていたが、何らかの進展はあっ た模様

Slide 7

Slide 7 text

Actix-Web actix.rs • 安定版コンパイラで動作 / 非同期対応 • 非同期フレームワークの暫定覇権 • futures/tokioに対応しているが、Actixというアクターフレーム ワークが一枚噛んでいる • 比較的ちゃんと作りこまれていてproduction-readyっぽい

Slide 8

Slide 8 text

Tower-Web carllerche/tower-web • 安定版コンパイラで動作 / 非同期対応 • Tokioの作者(のひとり)が書いている期待のフレームワーク

Slide 9

Slide 9 text

Tide (wg-net) • まだ存在しないフレームワーク • RustのNetwork Services Working Groupで計画されているモ ジュラーなフレームワーク • これ自体が流行るかは別として、成果物は各フレームワークに も還元されると思われる • コードだけでなくドキュメントも成果物の一部として規定され ているので、そちらも要注目

Slide 10

Slide 10 text

Rustと非同期I/O

Slide 11

Slide 11 text

そもそも非同期I/Oとは? • 元々の動機: I/Oは待ち時間が発生するので、できる処理から先 に進める仕組みにしたい 新しい接続リクエストが来たので、これを接続13とする。次のリクエストを受け付けられる状態にしておく。 接続13の最初の35バイトがあるのでパースする。データ不足でパースできなかったのでバッファに溜める。 新しい接続リクエストが来たので、これを接続14とする。次のリクエストを受け付けられる状態にしておく。 接続14の最初の45バイトがあるのでパースする。リクエストの内容がわかったのでデータベースに問い合わせにいく。データベースからはすぐには返 答はこない。 その間に接続13の続きの35バイトが来たのでパースする。まだ十分な情報がないのでバッファに溜める。 また新しい接続リクエストが来たので、これを接続15とする。キューが多すぎるので追加のリクエストはOSに溜めてもらう。 接続13の続きの6バイトが来て、リクエストの内容がわかったので、ファイルを探しに行く。ファイルが見つかったのでヘッダーと最初の20バイトを送 信する。 その間に接続14のためにデータベースにしていた問い合わせが返ってきたので、これをもとにもう一度データベースに問い合わせる。 接続13に追加の35バイトを送信する。 特に何もできないので待機する。 接続13に追加の30バイトを送信する。 接続14のためにデータベースにしていた問い合わせが返ってきたので、レスポンスを作ってヘッダーと最初の20バイトを送信する。 接続13から13バイトが来た。閉じてよいというリクエストなので閉じる。キューが開いたので次のリクエストを受け付けられる状態にしておく。 さっそく新しい接続リクエストが来たので、これを接続16とする。キューが多すぎるので追加のリクエストはOSに溜めてもらう。 ………

Slide 12

Slide 12 text

そもそも非同期I/Oとは? • 元々の動機: I/Oは待ち時間が発生するので、できる処理から先 に進める仕組みにしたい • ……しかし、実際のプログラムはできるだけ直列的に書きたい。 接続13については パースできるまで読む。76バイト 読み込んでリクエストがわかった。 ファイルを探しにいく。 ファイルが見つかったので合計85 バイト送る。 パースできるまで読む。13バイト 読んでcloseリクエストだったので 閉じる。 接続14については パースできるまで読む。45バイト 読み込んでリクエストがわかった。 データベースに問い合わせる。 その結果をもとにまたデータベー スに問い合わせる。 レスポンスを作って送信する。 …… 接続15については ……

Slide 13

Slide 13 text

並行実行を誰が司るか? • 選択肢1: 物理的に並列に実行してしまう: マルチCPU/マルチコ ア/ハイパースレッディング • コアの数しかスケールしない • 選択肢1を直接使うことはあまりない CPU #0 接続13を処理している CPU #1 接続14を処理している CPU #3 接続15を処理している

Slide 14

Slide 14 text

並行実行を誰が司るか? • 選択肢2: OSのスレッド(またはプロセス)管理に任せる • CPUの並列性をいい感じに使ってくれる • そこそこスケールするけど10000並列とかは難しい CPU #0 今は接続13を処理している CPU #1 今は接続14を処理している スレッド 50733 接続13を処理している スレッド 51252 接続14を処理している スレッド 52000 接続15を処理している OS 定期的にどれかのCPUで起動して、CPUを割り当て直す

Slide 15

Slide 15 text

並行実行を誰が司るか? • 選択肢3: プログラミング言語/ライブラリレベルの抽象化 • スレッド並列性をさらにラップしている • OSスレッドを使うよりもスケールする(らしい) CPU #0 CPU #1 スレッド 50733 スレッド 51252 スレッド 52000 エクゼキュータ 軽量スレッド1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 エクゼキュータ エクゼキュータ

Slide 16

Slide 16 text

並行実行を誰が司るか? • 選択肢3: プログラミング言語/ライブラリレベルの抽象化 • スレッド並列性をさらにラップしている • OSスレッドを使うよりもスケールする(らしい) CPU #0 CPU #1 スレッド 50733 スレッド 51252 スレッド 52000 エクゼキュータ 軽量スレッド1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 エクゼキュータ エクゼキュータ 高性能といわれるサーバーは、この方法を採用していることが多い

Slide 17

Slide 17 text

非同期I/O戦国時代になった経緯 • 古いRust (~0.11) はグリーンスレッドとlibuvサポートを持っ ていた! use std::task::spawn; // proc is the closure which will be spawned. spawn(proc() { println!("I'm a new task") }); ちなみにこの頃のRustは組み込みGCサポートやランタイムリフレクションもあったが、それぞれ 0.12と1.0.0-alphaで削除されている

Slide 18

Slide 18 text

非同期I/O戦国時代になった経緯 • Zero cost abstractionに反するなどいくつかの理由からグリー ンスレッドが削除される • 紆余曲折あってFuture(Promise)ベースのライブラリである futures/tokioを推していくことになった • 詳しくはlegokichiさんの明快なスライドを参照 https://qiita.com/legokichi/items/882bcacd12870d087555

Slide 19

Slide 19 text

3つのライブラリ! futures 汎用の非同期計算ライブラリ mio I/O多重化の 薄い抽象レイヤ tokio 非同期I/Oライブラリ

Slide 20

Slide 20 text

mio: I/O多重化の薄い抽象化

Slide 21

Slide 21 text

mioを説明する前に: I/O多重化とは • 例: 接続27と接続28を並行に処理していて、どちらも読み込み 待ち中とする • OSスレッドを使う場合 スレッド 24887 接続27を処理している スレッド 24890 接続28を処理している OS 接続27から読み込んでください それまでスレッドを止めてください 接続28から読み込んでください それまでスレッドを止めてください

Slide 22

Slide 22 text

mioを説明する前に: I/O多重化とは • をやるためのシステムコールは相場が決まっている 接続27から読み込んでください それまでスレッドを止めてください Man page of READ

Slide 23

Slide 23 text

mioを説明する前に: I/O多重化とは • をやるためのシステムコールは相場が決まっている • RustのAPIもほとんどそのまま 接続27から読み込んでください それまでスレッドを止めてください TcpStream

Slide 24

Slide 24 text

mioを説明する前に: I/O多重化とは • 例: 接続27と接続28を並行に処理していて、どちらも読み込み 待ち中とする • 軽量スレッドを使う場合 スレッド 24887 接続27と接続28を処理している OS 接続27または接続28のうち、早いほうから読み込んでください それまでスレッドを止めてください

Slide 25

Slide 25 text

mioを説明する前に: I/O多重化とは • をやるためのシステムコールはOSによってまちまち 接続27または接続28のうち、早いほうから読み込んでください それまでスレッドを止めてください Man page of EPOLL_WAIT kqueue(2) select(2) という古いシステムコールもあり、 これは多くのPOSIX系OSで共通に使える。

Slide 26

Slide 26 text

mioを説明する前に: epoll(7) の場合 1. 通知を受けたいイベントを全てファイル記述子(整数)として 表す • dup(2), signalfd(2), eventfd(2) などを使う 2. OS側に、イベントの集合を送信する • epoll_create(2), epoll_ctl(2) を使って、逐次的に組み立てる 3. epoll_wait(2)を呼ぶと、指定したイベントのどれかが来るま で待つ。 “(2)” とか “(7)” というのは伝統的なマニュアルの章番号である。Linuxの場合、(1)はコマンド、 (2)はシステムコール、(3)はCライブラリ関数、(7)はまとまった解説になっている。

Slide 27

Slide 27 text

mio – I/O多重化の薄い抽象レイヤ • mio (Metal I/O): epollやkqueueなどのI/O多重化システムコー ルを薄く抽象化するライブラリ • 使い方はepollとほぼ一緒なので、人類が直接使うのには向いて いない 人類が使うための抽象化を別途行う必要がある

Slide 28

Slide 28 text

おまけ: ノンブロッキングI/O • I/O多重化に加えて、以下の操作が必要 • これは O_NONBLOCK などとして比較的標準化されているので、mio のような専用の抽象化は要らない スレッド 24887 接続27と接続28を処理している OS 接続27から読んでください。 すぐに読めないときはブロックせずに戻ってください。

Slide 29

Slide 29 text

おまけ: 真の非同期I/O • OSから能動的な通知を受け取る仕組みもある (aio(7) などを参 照) スレッド 24887 接続27と接続28を処理している OS 接続27から読んでください。 今はすぐ復帰して、あとで完了したら通知してください。

Slide 30

Slide 30 text

おまけ: 真の非同期I/O • OSから能動的な通知を受け取る仕組みもある (aio(7) などを参 照) スレッド 24887 接続27と接続28を処理している OS 読み取り完了しました。(シグナルによる割り込み)

Slide 31

Slide 31 text

futures: 汎用非同期計算

Slide 32

Slide 32 text

Future トレイト • 「時間がかかりそうだったら別のことをやる」をするためのア プローチの一つ • 親が根気強く poll を呼び続けるとやがて答えが返ってくる 親「もしもし、今計算できそうですか (poll を呼ぶ)」 Future「まだですよ (NotReady)」 親「もしもし、今計算できそうですか (poll を呼ぶ)」 Future「まだですよ (NotReady)」 親「もしもし、今計算できそうですか (poll を呼ぶ)」 Future「できました! (Ready(42))」 Future「もう今後は呼ばないでね」 Futureには先物(未来の価格での取引を保証する金融派生商品)という意味があり、そちらが由来では ないかとも言われている

Slide 33

Slide 33 text

Future トレイト • ざっくり定義すると、こんな感じ pub trait Future { type Output; /// 完了したら `Some`, 途中だったら `None` を返す。 /// `Some` が一度でも返ったら、それ以上呼んではいけない。 fn poll(&mut self) -> Option; } 実はIteratorと同じ。しかし、IteratorはNoneが出るまで呼び続けるのに対して、FutureはSomeが 出るまで呼び続ける。

Slide 34

Slide 34 text

Futureを作ってみよう • フィボナッチ数列をゆっくり計算する例 extern crate futures; // 0.1.25 use futures::prelude::*;

Slide 35

Slide 35 text

Futureを作ってみよう • 計算途中の状態を覚えておく構造体 pub struct Fib { n: u32, // 残りループ回数 a: u32, // 今のフィボナッチ数列の項 b: u32, // 次のフィボナッチ数列の項 }

Slide 36

Slide 36 text

Futureを作ってみよう • 最初の状態を作って返す impl Fib { pub fn new(n: u32) -> Self { Fib { n: n + 1, a: 1, b: 0, } } }

Slide 37

Slide 37 text

Futureを作ってみよう • Fibが最終的に整数を返せるようにしていく impl Future for Fib { type Item = u32; type Error = (); fn poll(&mut self) -> Result, Self::Error> { } }

Slide 38

Slide 38 text

Futureを作ってみよう • 直前の状態を取り出して、計算を1ステップ進める let (a, b) = (self.a, self.b); self.a = b; self.b = a + b; self.n -= 1;

Slide 39

Slide 39 text

Futureを作ってみよう • 終わってたら答えを返す。そうじゃなかったら次回まで待つ if self.n == 0 { Ok(Async::Ready(self.a)) } else { futures::task::current().notify(); Ok(Async::NotReady) }

Slide 40

Slide 40 text

コンビネーター • JavaScriptのPromiseとかと同じで、 コンビネーターで組み立てていく • 組み立てるたびに専用の型がついて いくのがRust流

Slide 41

Slide 41 text

コンビネーターたち(1) future::ok(x) 値を返すだけ future::err(e) エラーを返すだけ future::lazy(|| f()) 非同期関数をあとで実行する futures-0.1, futures-0.2の Future は Result と統合されているので、コンビネーターもエラーが考慮さ れている。ここでは「成功」と「失敗」をあわせて「完了」と呼んでいる。 • 基本ブロック

Slide 42

Slide 42 text

コンビネーターたち(2) fut1.map(|x| f(x)) 成功したら次を実行する (fは普通の関数) fut1.and_then(|x| f(x)) 成功したら次を実行する (fは非同期関数) fut1.map_err(|x| f(x)) 失敗したら次を実行する (fは普通の関数) fut1.or_else(|x| f(x)) 失敗したら次を実行する (fは非同期関数) fut1.from_err(|x| f(x)) 失敗を From で変換する fut1.then(|x| f(x)) 完了したら次を実行する (fは非同期関数) • 続けて何かをする系 futures-0.1, futures-0.2の Future は Result と統合されているので、コンビネーターもエラーが考慮さ れている。ここでは「成功」と「失敗」をあわせて「完了」と呼んでいる。

Slide 43

Slide 43 text

コンビネーターたち(3) fut1.select(fut2) 両方を実行して、どちらか先に完了したほうを使う。 fut1.select2(fut2) 両方を実行して、どちらか先に完了したほうを使う。 違う型を返す Future にも使える future::select_all(iter_fut) 全てを実行して、一番最初に完了したものを使う。 future::select_ok(iter_fut) 全てを実行して、一番最初に成功したものを使う。 • select系 futures-0.1, futures-0.2の Future は Result と統合されているので、コンビネーターもエラーが考慮さ れている。ここでは「成功」と「失敗」をあわせて「完了」と呼んでいる。

Slide 44

Slide 44 text

コンビネーターたち(4) fut1.join(fut2) 両方を実行して、両方の完了を待つ fut1.join3(fut2, fut3) 上に同じ (3個) fut1.join4(fut2, fut3, fut4) 上に同じ (4個) fut1.join5(fut2, fut3, fut4, fut5) 上に同じ (5個) future::join_all(iter_fut) 上に同じ (任意個) • join系 futures-0.1, futures-0.2の Future は Result と統合されているので、コンビネーターもエラーが考慮さ れている。ここでは「成功」と「失敗」をあわせて「完了」と呼んでいる。

Slide 45

Slide 45 text

コンビネーターたち(5) fut1.join(fut2) 両方を実行して、両方の完了を待つ fut1.join3(fut2, fut3) 上に同じ (3個) fut1.join4(fut2, fut3, fut4) 上に同じ (4個) fut1.join5(fut2, fut3, fut4, fut5) 上に同じ (5個) future::join_all(iter_fut) 上に同じ (任意個) • ループ系 futures-0.1, futures-0.2の Future は Result と統合されているので、コンビネーターもエラーが考慮さ れている。ここでは「成功」と「失敗」をあわせて「完了」と呼んでいる。

Slide 46

Slide 46 text

タスク • Future = 非同期Rust世界の関数 • Task = 非同期Rust世界のスレッド • 各タスクは1つのトップレベルFutureを持つ Task1 AndThen Select read read write タスクの 実行状態

Slide 47

Slide 47 text

実行待ちキュー Executor • タスクを抱えて実行する責務を負う Task6 Task3 Task2 Task5 spawn Executor Task6のpollを呼んでいる Task7 再enqueue drop

Slide 48

Slide 48 text

Executor Executor • 複数のワーカースレッドを使う実装もある Worker #1 実行待ちキュー Task6 Task3 Task8 キューに偏りがある ときは融通しあう (work stealing) Worker #2 実行待ちキュー Task9 Task4 Task1

Slide 49

Slide 49 text

実行待ちキュー 通知 • この図の仕組みはちょっと不正確 Task6 Task3 Task2 Task5 spawn Executor Task6のpollを呼んでいる Task7 再enqueue drop

Slide 50

Slide 50 text

休眠中のタスクたち 実行待ちキュー 通知 • NotReadyだったタスクは通知待ちに回される Task15 Task10 Task2 Task3 spawn Executor Task6 drop Task1 Task7 Task12 Task18

Slide 51

Slide 51 text

通知 • 休眠する(NotReadyを返す)前に、通知を受けられるよう準備し ておく必要がある let task = futures::task::current(); some_watcher.register(task); return Ok(Async::NotReady); 現在実行中のタスクのハンドルを取得 ハンドルを誰かに託す 通知を受けるまで休眠する

Slide 52

Slide 52 text

通知 • 休眠する(NotReadyを返す)前に、通知を受けられるよう準備し ておく必要がある let task = futures::task::current(); some_watcher.register(task); return Ok(Async::NotReady); このタスクが再開できる条件を別スレッドが監視している。 再開できそうになったら通知が行われる task.notify(); イベントの種類ごとに通知の責務を受けもつ媒体をドライバーという

Slide 53

Slide 53 text

通知 • 計算負荷が理由で一時的に休眠する場合は、セルフ通知でOK futures::task::current().notify(); return Ok(Async::NotReady); セルフ通知してからNotReadyを返すと、 即座にenqueueされる

Slide 54

Slide 54 text

通知が間違っているとどうなるか • 偽陰性の場合 (再開可能なのに通知されない) • 眠りっぱなしになる • 大変困る • 偽陽性の場合 (意味もなく通知される) • 無駄pollが1回増えるだけ • 困らない • selectで遅いほうのイベントが完了したとき(後の祭り)とかに起こる

Slide 55

Slide 55 text

間違ってブロックするとどうなるか • OSスレッドはCPUのタイマー割り込みで強制的にスレッドを 切り替える仕組みがある (preemptive) が、Rustのタスクには この機能はない (non-preemptive, cooperative) • タスクが間違ってブロックすると、進むはずのタスクまで巻き 添えを食らう。 • スレッドプールベースの場合、何個か同時にブロックしてはじめて発 覚するのでたちが悪い • ブロッキングI/O、Mutex、重い計算などでタスクをブロック しないように気を遣う必要がある

Slide 56

Slide 56 text

futures-cpupool • ブロッキング処理を行うための方法のひとつ Executor 実行待ちキュー Task6 Task3 Task8 Task3は何もせず、 ワーカースレッドの進捗 を問い合わせるだけ Task3の実体 重い計算とかをしてる Futures-cpupool専用 ワーカースレッド

Slide 57

Slide 57 text

tokio: 非同期I/Oライブラリ

Slide 58

Slide 58 text

tokio • mio と futures を組み合わせて、非同期I/Oの統一された枠組 みを提供する

Slide 59

Slide 59 text

tokio tokioランタイム tokio-threadpool executor worker worker worker task task task task task task task task task 各種ドライバー tokio-reactor mio を使ってネットワークを 駆動する tokio-fs ファイルシステム tokio-timer タイマー register notify

Slide 60

Slide 60 text

tokio_threadpool::blocking • ワーカーが1個でも生きていればTokioは回る • Tokioのexecutorに「blocking」フラグを立てることで、その場 でブロッキングを可能にするAPI Executor Worker #1 実行待ちキュー Task6 Task3 Task8 ブロック中なので キューを融通する ブロック中 実行待ちキュー Task9 Task4 ブロック中

Slide 61

Slide 61 text

fahrenheit • futures/tokio のエッセンスを抽出した学習用の実装 • 実質1ファイルに収まっている • futures-rs blogに記事がある • Toykioという名前だったが、あまりの紛らわしさに改名した

Slide 62

Slide 62 text

tokio-core

Slide 63

Slide 63 text

tokio-core • Tokioプロジェクトの古いランタイムライブラリ • tokio-rfcsにデザインドキュメントがある • tokio で書かれたライブラリは tokio-core ランタイムと互 換性がある • tokio-core で書かれたライブラリは tokio ランタイムと互 換性がない • ライブラリが先に新しくなる必要がある!

Slide 64

Slide 64 text

tokio-core • tokio-core はシングルスレッド • tokio に移行するときにはスレッドセーフな実装に切り替える 必要がある • Rc→Arc, RefCell→Mutex, etc. のような書き換えが必要 • ライブラリ作者が頑張る

Slide 65

Slide 65 text

Actix

Slide 66

Slide 66 text

Actix • Rustで書かれたアクターフレームワーク • Tokioと多少の互換性がある • Tokio-reactor を使っているが、tokio-threadpool は使ってい ない (独自executorで動かしている) • したがって、tokio-threadpool の存在を仮定しているライブラ リは動かない可能性がある

Slide 67

Slide 67 text

futures-0.2

Slide 68

Slide 68 text

futures-0.2 (futures-preview-0.2) • futures-0.1 の後継として作られていた • futures-rfcsにデザインドキュメントがある (futures-02, task- context, executors)

Slide 69

Slide 69 text

futures-0.2 の Future • pollが明示的にタスク文脈を受け取るようになった

Slide 70

Slide 70 text

futures-0.2 yank事件 • 諸事情あってfutures-0.2はyankされて別の名前で再公開された (futures-preview 0.2) • yankされると、既存の Cargo.lock を使わない限り解決されなくなる • 特にちゃんとしたアナウンスもなくyankされた • この「yank事件」は結構議論になった • gtk-rs とかが futures-0.2 を使っているらしい

Slide 71

Slide 71 text

async/await

Slide 72

Slide 72 text

Futureのつらみ • Future には現状2つほど「つらみ」がある 1. then/and_then/map によるメソッドチェーンは普通に面倒 2. 借用との相性が悪い

Slide 73

Slide 73 text

futures-await • Rustのasync/awaitの前身 • なんかいい感じにdesugarしてくれるマクロ君 正確に言うとexperimental coroutineという機能に翻訳しているだけなので、 futuresのコンビネーターになっているわけではない

Slide 74

Slide 74 text

futures-awaitの重大な制約 • 借用がawaitをまたぐことができない #[async] fn foo() { let conn = await!(connect()); let result = await!(conn.get(bar)); conn.close(); } こういうのが使えない (普通にFuturesを使っても書けない)

Slide 75

Slide 75 text

futures-awaitの重大な制約 • 借用がawaitをまたぐことができない #[async] fn foo() { let conn = await!(connect()); let (conn, result) = await!(conn.get(bar)); conn.close(); } 所有権を渡して、あとで同じものを返してもらう というインターフェースになる

Slide 76

Slide 76 text

借用とFuture • 「借用している途中のプログラム」を構造体として切り出す • 変数と、その参照がどちらもスタックに乗っていたはず future 変数conn 変数connへの参照

Slide 77

Slide 77 text

借用とFuture • Rustの構造体として書こうとするとこんな感じ struct FooState { conn: Connection, conn_ref: &'self.conn mut Connection, } こういう気持ちは表明できない こういうのを自己参照構造体 (self-referencial struct) あるいは貫入データ構造 (intrusive data structure) という

Slide 78

Slide 78 text

貫入データ構造の問題点 • Rustの値は全てムーブ・リプレースできるが、貫入データ構造 はムーブ・リプレースできない A ARef

Slide 79

Slide 79 text

貫入データ構造の問題点 • Rustの値は全てムーブ・リプレースできるが、貫入データ構造 はムーブ・リプレースできない A ARef

Slide 80

Slide 80 text

?Move の顛末 • Sizedと同様に、デフォルトで有効のトレイト Move を導入す る提案があったが、いくつかの懸念から却下された • これ以上デフォルトトレイトを追加すると混乱が増える • Moveできない型は、関数から返すこともできないから生成することも できない。この部分を解決するにはさらに多くの道具が必要になる

Slide 81

Slide 81 text

Pinned reference (RFC 2349) • ムーブを禁止するのではなく、後からムーブできるような参照 の取り方を禁止してしまえばよいというアイデア • どんなデータも、最初はムーブできる。 • しかし、 Pin<&mut T> という特殊な参照を取ると、その中身 はムーブできない(ムーブしないままDropする必要がある) • これは Pin> というラッパーや pin_mut!() というマクロ によって担保される • ただし、貫入データ構造でない場合は、 Pin<&mut T> から &mut T を復元する余地が残されている。

Slide 82

Slide 82 text

真のasync/await (RFC 2394) • Pin API により、参照を自然に使える • 処理系に組み込みなので、マクロ特有の面倒くささがない async fn foo() { let conn = await!(connect()); let result = await!(conn.get(bar)); conn.close(); } async/awaitは鋭意開発中 futures-0.3 と nightly を組み合わせることで試せる

Slide 83

Slide 83 text

futures-0.3 • async/await対応により、 Future を標準ライブラリ入りさせる 必要が出てきた。 • また、Pinned referenceを使って書き直す必要が出てきた。

Slide 84

Slide 84 text

futures-0.3 pub trait Future { type Output; fn poll(self: Pin<&mut Self>, lw: &LocalWaker) -> Poll; } std::task に移動 Pinned referenceを受け取るようになった。 Resultとの統合が廃止された。 ReadyとNotReadyの2択になった。 Futures-0.2 に導入されていたcontextが 単純化された。 通知用のハンドルだけを持ち回す。

Slide 85

Slide 85 text

futures-0.3 • futures-0.3は(現状)不安定版のコンパイラに強く依存している。 • しかし、今後はfutures-0.2ではなくfutures-0.3が開発される。 • Tokioのfutures-0.2移行も白紙に戻ってしまった。 • (おそらく)そういう事情から、futures-0.2/futures-0.3はプレ ビュー版としての立場を明確にしたいという動きがあり、それ がyank事件につながった?

Slide 86

Slide 86 text

Futures/tokioまとめ • 複雑で面倒くさい事情がある • 最終的にasync/awaitに収斂していって皆幸せになる……はず

Slide 87

Slide 87 text

ハンズオンの準備

Slide 88

Slide 88 text

Rustを最新にしておく $ rustup update

Slide 89

Slide 89 text

Dieselの準備 • MySQLとPostgreSQLのdevライブラリを入れておく。例: $ sudo apt install libpq-dev libmysqlclient-dev $ brew install postgresql mysql dieselがデフォルトで両方を要求するので、両方入れておく。 必要なほうを入れておいて、あとでdieselのオプションを指定するのでもOK

Slide 90

Slide 90 text

Dieselの準備 • diesel-cliを入れる $ cargo install diesel_cli ビルドに少し時間がかかるので、次に進んでおくとよい

Slide 91

Slide 91 text

Dieselの準備 • diesel-cliを入れる $ cargo install diesel_cli PostgreSQLサポートのみで入れるには以下のようにする: $ cargo install diesel_cli --no-default-features --features postgres

Slide 92

Slide 92 text

データベースの作成 $ createdb todoapp うまいURL

Slide 93

Slide 93 text

データベースの接続確認 $ psql postgres:///todoapp psql (10.5 (Ubuntu 10.5-0ubuntu0.18.04)) Type "help" for help. todoapp=# ¥q うまいURL

Slide 94

Slide 94 text

データベースの接続確認 $ psql postgres:///todoapp psql (10.5 (Ubuntu 10.5-0ubuntu0.18.04)) Type "help" for help. todoapp=# ¥q 以下のような亜種が有効かも: postgres://localhost/todoapp postgres://username:@localhost/todoapp postgres://postgres:@localhost/todoapp postgres://username:password@localhost/todoapp postgres://postgres:password@localhost/todoapp

Slide 95

Slide 95 text

サーバーを書き始めよう

Slide 96

Slide 96 text

プロジェクトを立てる $ cargo new todo-app $ cd todo-app

Slide 97

Slide 97 text

プロジェクトを立てる • Cargo.tomlに依存関係を追加 [dependencies] log = "0.4.6" env_logger = "0.5.13" dotenv = "0.13.0" serde = "1.0.80" serde_derive = "1.0.80" serde_json = "1.0.32" actix-web = "0.7.13"

Slide 98

Slide 98 text

プロジェクトを立てる • Cargo.tomlに依存関係を追加 [dependencies] log = "0.4.6" env_logger = "0.5.13" dotenv = "0.13.0" serde = "1.0.80" serde_derive = "1.0.80" serde_json = "1.0.32" actix-web = "0.7.13" 依存関係を変えたらとりあえず しておくと便利 $ cargo build

Slide 99

Slide 99 text

プロジェクトを立てる • extern crateしておく #[macro_use] extern crate log; extern crate env_logger; extern crate dotenv; extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_json; extern crate actix_web; log, serde_derive由来のマクロを使うので明示 最新安定版の1.30.0からは通常のuseで マクロをインポートすることができる。 また1.31.0からはデフォルトでRust 2018が 有効になるため、extern crateは不要になる。

Slide 100

Slide 100 text

ロギングの準備 fn main() { dotenv::dotenv().ok(); env_logger::init(); info!("Hello, world!"); } dotenvは失敗してもいいので .ok() でOptionに変換することで 無視している

Slide 101

Slide 101 text

ロギングの準備 • .env というファイルを作成 RUST_LOG=todo_app=debug,actix=info ライブラリごと・モジュールごとに ログレベルを指定できる。 このファイルは dotenvライブラリを使って 環境変数として読み込まれる

Slide 102

Slide 102 text

ロギングの準備 • ロギングできていることを確認 $ cargo run INFO 2018-11-01T06:33:40Z: todo_app: Hello, world!

Slide 103

Slide 103 text

サーバーを起動する use actix_web::{server, App}; fn main() { env_logger::init(); debug!("Launching an app..."); server::new(|| App::new()).bind("[::]:8080").unwrap().run(); } イントラネットに公開したくない場合は "localhost:8080"

Slide 104

Slide 104 text

サーバーを起動する • これでサーバーは立ち上がる $ cargo run DEBUG 2018-11-01T06:44:05Z: todo_app: Launching an app... INFO 2018-11-01T06:44:05Z: actix_net::server::server: Starting 2 workers INFO 2018-11-01T06:44:05Z: actix_net::server::server: Starting server on [::]:8080 ブラウザからアクセスすると404が返ってくるはず

Slide 105

Slide 105 text

エンドポイントを追加する use actix_web::{http, Path, Responder}; fn ping(_: Path<()>) -> impl Responder { "pong" } 既存の行にいい感じに追加してください

Slide 106

Slide 106 text

エンドポイントを追加する server::new(|| App::new() .route("/ping", http::Method::GET, ping) ) ここを増やす

Slide 107

Slide 107 text

サーバーを再起動する $ RUST_LOG=todo_app=debug,actix=info cargo run DEBUG 2018-11-01T06:44:05Z: todo_app: Launching an app... INFO 2018-11-01T06:44:05Z: actix_net::server::server: Starting 2 workers INFO 2018-11-01T06:44:05Z: actix_net::server::server: Starting server on [::]:8080 /pingにアクセスするとpongが返ってくる

Slide 108

Slide 108 text

テンプレート

Slide 109

Slide 109 text

Askamaを使う • Cargo.tomlに依存関係を追加 [dependencies] askama = { version = "0.7.2", features = ["with-actix-web"] } [build-dependencies] askama = "0.7.2" 両方必要

Slide 110

Slide 110 text

Askamaを使う • build.rs というファイルをプロジェクト直下に作成する extern crate askama; fn main() { askama::rerun_if_templates_changed(); } build.rs はコンパイル時に実行される。 そのため以下の特徴がある • クロスコンパイル時もホストアーキテクチャにコンパイルされる • 依存関係が区別される。 ([build-dependencies])

Slide 111

Slide 111 text

テンプレートを書く • templates/base.html {% block title %} default title {% endblock %} {% block content %}

default content

{% endblock %}

Slide 112

Slide 112 text

テンプレートを書く • templates/index.html {% extends "base.html" %} {% block title %}Top{% endblock %} {% block content %}

Top

Hello, Askama!

{% endblock %}

Slide 113

Slide 113 text

テンプレートを使う #[macro_use] extern crate askama; use askama::Template; #[derive(Debug, Template)] #[template(path = "index.html")] struct IndexTemplate; テンプレートの描画に必要な引数は、この構造体に詰める 最新安定版の1.30.0からはuse askama::Templateで Templateのderive macroもインポートされるので、 #[macro_use] は不要になる

Slide 114

Slide 114 text

テンプレートを使う fn index(_: Path<()>) -> impl Responder { IndexTemplate } … App::new() .route("/", http::Method::GET, index) .route("/ping", http::Method::GET, ping) 今のところテンプレート引数はないので、こうなる ここまで書いて実行すると描画される

Slide 115

Slide 115 text

静的ファイル

Slide 116

Slide 116 text

静的ファイルをサーブする use actix_web::fs::StaticFiles; … App::new() .handler("/static", StaticFiles::new("./static").unwrap()) .route("/", http::Method::GET, index) .route("/ping", http::Method::GET, ping) ./static を / からサーブしたりするのはもう少し工夫が必要そう。 複数個の StaticFiles を作ると無駄スレッドが大量にできるので、 こちらも工夫が必要

Slide 117

Slide 117 text

静的ファイルをサーブする • ./static/style.css とかを書いてみる body { background-color: #888888; }

Slide 118

Slide 118 text

静的ファイルをサーブする • ./templates/base.html に行を足してみる

Slide 119

Slide 119 text

データベース

Slide 120

Slide 120 text

データベース • Cargo.tomlに依存関係を追加 [dependencies] chrono = "0.4.6" diesel = { version = "1.3.3", features = ["postgres"] } cargo build を流しつつ次に進もう

Slide 121

Slide 121 text

データベース • .env に以下を追加 (psqlで繋がるURLを指定する) DATABASE_URL=postgres:///todoapp

Slide 122

Slide 122 text

データベース $ diesel setup

Slide 123

Slide 123 text

データベース $ diesel migration generate create_todos

Slide 124

Slide 124 text

データベース • up.sql ができているので以下のようにする CREATE TABLE todos ( id BIGSERIAL PRIMARY KEY, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, content VARCHAR NOT NULL )

Slide 125

Slide 125 text

データベース • down.sql ができているので以下のようにする DROP TABLE todos

Slide 126

Slide 126 text

データベース • upを実行する $ diesel migration run

Slide 127

Slide 127 text

データベース • downの動きも見たいので、downしてupしてみる $ diesel migration redo

Slide 128

Slide 128 text

データベース • スキーマを読み込む #[macro_use] extern crate diesel; mod schema; src/schema.rs が自動生成されている

Slide 129

Slide 129 text

時間切れ…… • 残りは上手くいったらライブコーディングします!