Rust速習会3

Ba655e3712aaabfbca289fe136f85fe4?s=47 Masaki Hara
November 01, 2018

 Rust速習会3

Ba655e3712aaabfbca289fe136f85fe4?s=128

Masaki Hara

November 01, 2018
Tweet

Transcript

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

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

  3. Webフレームワーク

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

    募集して復活したっぽい
  5. Rocket rocket.rs • 不安定版コンパイラで動作 / 非同期は未対応 • 構文拡張 (独自の #[])

    をふんだんに使って書きやすくしてい る • そのため、nightlyの更新ですぐに壊れる • 全体的に作りこまれていて人気っぽい
  6. Gotham gotham.rs • 安定版コンパイラで動作 / 非同期対応 • futures/tokioの非同期をうまく使って無難に設計されている • 作りこみは甘い印象

    • メンテナ不足のアナウンスが出ていたが、何らかの進展はあっ た模様
  7. Actix-Web actix.rs • 安定版コンパイラで動作 / 非同期対応 • 非同期フレームワークの暫定覇権 • futures/tokioに対応しているが、Actixというアクターフレーム

    ワークが一枚噛んでいる • 比較的ちゃんと作りこまれていてproduction-readyっぽい
  8. Tower-Web carllerche/tower-web • 安定版コンパイラで動作 / 非同期対応 • Tokioの作者(のひとり)が書いている期待のフレームワーク

  9. Tide (wg-net) • まだ存在しないフレームワーク • RustのNetwork Services Working Groupで計画されているモ ジュラーなフレームワーク

    • これ自体が流行るかは別として、成果物は各フレームワークに も還元されると思われる • コードだけでなくドキュメントも成果物の一部として規定され ているので、そちらも要注目
  10. Rustと非同期I/O

  11. そもそも非同期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に溜めてもらう。 ………
  12. そもそも非同期I/Oとは? • 元々の動機: I/Oは待ち時間が発生するので、できる処理から先 に進める仕組みにしたい • ……しかし、実際のプログラムはできるだけ直列的に書きたい。 接続13については パースできるまで読む。76バイト 読み込んでリクエストがわかった。

    ファイルを探しにいく。 ファイルが見つかったので合計85 バイト送る。 パースできるまで読む。13バイト 読んでcloseリクエストだったので 閉じる。 接続14については パースできるまで読む。45バイト 読み込んでリクエストがわかった。 データベースに問い合わせる。 その結果をもとにまたデータベー スに問い合わせる。 レスポンスを作って送信する。 …… 接続15については ……
  13. 並行実行を誰が司るか? • 選択肢1: 物理的に並列に実行してしまう: マルチCPU/マルチコ ア/ハイパースレッディング • コアの数しかスケールしない • 選択肢1を直接使うことはあまりない

    CPU #0 接続13を処理している CPU #1 接続14を処理している CPU #3 接続15を処理している
  14. 並行実行を誰が司るか? • 選択肢2: OSのスレッド(またはプロセス)管理に任せる • CPUの並列性をいい感じに使ってくれる • そこそこスケールするけど10000並列とかは難しい CPU #0

    今は接続13を処理している CPU #1 今は接続14を処理している スレッド 50733 接続13を処理している スレッド 51252 接続14を処理している スレッド 52000 接続15を処理している OS 定期的にどれかのCPUで起動して、CPUを割り当て直す
  15. 並行実行を誰が司るか? • 選択肢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 エクゼキュータ エクゼキュータ
  16. 並行実行を誰が司るか? • 選択肢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 エクゼキュータ エクゼキュータ 高性能といわれるサーバーは、この方法を採用していることが多い
  17. 非同期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で削除されている
  18. 非同期I/O戦国時代になった経緯 • Zero cost abstractionに反するなどいくつかの理由からグリー ンスレッドが削除される • 紆余曲折あってFuture(Promise)ベースのライブラリである futures/tokioを推していくことになった •

    詳しくはlegokichiさんの明快なスライドを参照 https://qiita.com/legokichi/items/882bcacd12870d087555
  19. 3つのライブラリ! futures 汎用の非同期計算ライブラリ mio I/O多重化の 薄い抽象レイヤ tokio 非同期I/Oライブラリ

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

  21. mioを説明する前に: I/O多重化とは • 例: 接続27と接続28を並行に処理していて、どちらも読み込み 待ち中とする • OSスレッドを使う場合 スレッド 24887

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

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

  24. mioを説明する前に: I/O多重化とは • 例: 接続27と接続28を並行に処理していて、どちらも読み込み 待ち中とする • 軽量スレッドを使う場合 スレッド 24887

    接続27と接続28を処理している OS 接続27または接続28のうち、早いほうから読み込んでください それまでスレッドを止めてください
  25. mioを説明する前に: I/O多重化とは • をやるためのシステムコールはOSによってまちまち 接続27または接続28のうち、早いほうから読み込んでください それまでスレッドを止めてください Man page of EPOLL_WAIT

    kqueue(2) select(2) という古いシステムコールもあり、 これは多くのPOSIX系OSで共通に使える。
  26. 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)はまとまった解説になっている。
  27. mio – I/O多重化の薄い抽象レイヤ • mio (Metal I/O): epollやkqueueなどのI/O多重化システムコー ルを薄く抽象化するライブラリ •

    使い方はepollとほぼ一緒なので、人類が直接使うのには向いて いない 人類が使うための抽象化を別途行う必要がある
  28. おまけ: ノンブロッキングI/O • I/O多重化に加えて、以下の操作が必要 • これは O_NONBLOCK などとして比較的標準化されているので、mio のような専用の抽象化は要らない スレッド

    24887 接続27と接続28を処理している OS 接続27から読んでください。 すぐに読めないときはブロックせずに戻ってください。
  29. おまけ: 真の非同期I/O • OSから能動的な通知を受け取る仕組みもある (aio(7) などを参 照) スレッド 24887 接続27と接続28を処理している

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

    OS 読み取り完了しました。(シグナルによる割り込み)
  31. futures: 汎用非同期計算

  32. Future トレイト • 「時間がかかりそうだったら別のことをやる」をするためのア プローチの一つ • 親が根気強く poll を呼び続けるとやがて答えが返ってくる 親「もしもし、今計算できそうですか

    (poll を呼ぶ)」 Future「まだですよ (NotReady)」 親「もしもし、今計算できそうですか (poll を呼ぶ)」 Future「まだですよ (NotReady)」 親「もしもし、今計算できそうですか (poll を呼ぶ)」 Future「できました! (Ready(42))」 Future「もう今後は呼ばないでね」 Futureには先物(未来の価格での取引を保証する金融派生商品)という意味があり、そちらが由来では ないかとも言われている
  33. Future トレイト • ざっくり定義すると、こんな感じ pub trait Future { type Output;

    /// 完了したら `Some`, 途中だったら `None` を返す。 /// `Some` が一度でも返ったら、それ以上呼んではいけない。 fn poll(&mut self) -> Option<Self::Output>; } 実はIteratorと同じ。しかし、IteratorはNoneが出るまで呼び続けるのに対して、FutureはSomeが 出るまで呼び続ける。
  34. Futureを作ってみよう • フィボナッチ数列をゆっくり計算する例 extern crate futures; // 0.1.25 use futures::prelude::*;

  35. Futureを作ってみよう • 計算途中の状態を覚えておく構造体 pub struct Fib { n: u32, //

    残りループ回数 a: u32, // 今のフィボナッチ数列の項 b: u32, // 次のフィボナッチ数列の項 }
  36. Futureを作ってみよう • 最初の状態を作って返す impl Fib { pub fn new(n: u32)

    -> Self { Fib { n: n + 1, a: 1, b: 0, } } }
  37. Futureを作ってみよう • Fibが最終的に整数を返せるようにしていく impl Future for Fib { type Item

    = u32; type Error = (); fn poll(&mut self) -> Result<Async<Self::Item>, Self::Error> { } }
  38. Futureを作ってみよう • 直前の状態を取り出して、計算を1ステップ進める let (a, b) = (self.a, self.b); self.a

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

    else { futures::task::current().notify(); Ok(Async::NotReady) }
  40. コンビネーター • JavaScriptのPromiseとかと同じで、 コンビネーターで組み立てていく • 組み立てるたびに専用の型がついて いくのがRust流

  41. コンビネーターたち(1) future::ok(x) 値を返すだけ future::err(e) エラーを返すだけ future::lazy(|| f()) 非同期関数をあとで実行する futures-0.1, futures-0.2の

    Future は Result と統合されているので、コンビネーターもエラーが考慮さ れている。ここでは「成功」と「失敗」をあわせて「完了」と呼んでいる。 • 基本ブロック
  42. コンビネーターたち(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 と統合されているので、コンビネーターもエラーが考慮さ れている。ここでは「成功」と「失敗」をあわせて「完了」と呼んでいる。
  43. コンビネーターたち(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 と統合されているので、コンビネーターもエラーが考慮さ れている。ここでは「成功」と「失敗」をあわせて「完了」と呼んでいる。
  44. コンビネーターたち(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 と統合されているので、コンビネーターもエラーが考慮さ れている。ここでは「成功」と「失敗」をあわせて「完了」と呼んでいる。
  45. コンビネーターたち(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 と統合されているので、コンビネーターもエラーが考慮さ れている。ここでは「成功」と「失敗」をあわせて「完了」と呼んでいる。
  46. タスク • Future = 非同期Rust世界の関数 • Task = 非同期Rust世界のスレッド •

    各タスクは1つのトップレベルFutureを持つ Task1 AndThen Select read read write タスクの 実行状態
  47. 実行待ちキュー Executor • タスクを抱えて実行する責務を負う Task6 Task3 Task2 Task5 spawn Executor

    Task6のpollを呼んでいる Task7 再enqueue drop
  48. Executor Executor • 複数のワーカースレッドを使う実装もある Worker #1 実行待ちキュー Task6 Task3 Task8

    キューに偏りがある ときは融通しあう (work stealing) Worker #2 実行待ちキュー Task9 Task4 Task1
  49. 実行待ちキュー 通知 • この図の仕組みはちょっと不正確 Task6 Task3 Task2 Task5 spawn Executor

    Task6のpollを呼んでいる Task7 再enqueue drop
  50. 休眠中のタスクたち 実行待ちキュー 通知 • NotReadyだったタスクは通知待ちに回される Task15 Task10 Task2 Task3 spawn

    Executor Task6 drop Task1 Task7 Task12 Task18
  51. 通知 • 休眠する(NotReadyを返す)前に、通知を受けられるよう準備し ておく必要がある let task = futures::task::current(); some_watcher.register(task); return

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

    Ok(Async::NotReady); このタスクが再開できる条件を別スレッドが監視している。 再開できそうになったら通知が行われる task.notify(); イベントの種類ごとに通知の責務を受けもつ媒体をドライバーという
  53. 通知 • 計算負荷が理由で一時的に休眠する場合は、セルフ通知でOK futures::task::current().notify(); return Ok(Async::NotReady); セルフ通知してからNotReadyを返すと、 即座にenqueueされる

  54. 通知が間違っているとどうなるか • 偽陰性の場合 (再開可能なのに通知されない) • 眠りっぱなしになる • 大変困る • 偽陽性の場合

    (意味もなく通知される) • 無駄pollが1回増えるだけ • 困らない • selectで遅いほうのイベントが完了したとき(後の祭り)とかに起こる
  55. 間違ってブロックするとどうなるか • OSスレッドはCPUのタイマー割り込みで強制的にスレッドを 切り替える仕組みがある (preemptive) が、Rustのタスクには この機能はない (non-preemptive, cooperative) •

    タスクが間違ってブロックすると、進むはずのタスクまで巻き 添えを食らう。 • スレッドプールベースの場合、何個か同時にブロックしてはじめて発 覚するのでたちが悪い • ブロッキングI/O、Mutex、重い計算などでタスクをブロック しないように気を遣う必要がある
  56. futures-cpupool • ブロッキング処理を行うための方法のひとつ Executor 実行待ちキュー Task6 Task3 Task8 Task3は何もせず、 ワーカースレッドの進捗

    を問い合わせるだけ Task3の実体 重い計算とかをしてる Futures-cpupool専用 ワーカースレッド
  57. tokio: 非同期I/Oライブラリ

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

  59. 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
  60. tokio_threadpool::blocking • ワーカーが1個でも生きていればTokioは回る • Tokioのexecutorに「blocking」フラグを立てることで、その場 でブロッキングを可能にするAPI Executor Worker #1 実行待ちキュー

    Task6 Task3 Task8 ブロック中なので キューを融通する ブロック中 実行待ちキュー Task9 Task4 ブロック中
  61. fahrenheit • futures/tokio のエッセンスを抽出した学習用の実装 • 実質1ファイルに収まっている • futures-rs blogに記事がある •

    Toykioという名前だったが、あまりの紛らわしさに改名した
  62. tokio-core

  63. tokio-core • Tokioプロジェクトの古いランタイムライブラリ • tokio-rfcsにデザインドキュメントがある • tokio で書かれたライブラリは tokio-core ランタイムと互

    換性がある • tokio-core で書かれたライブラリは tokio ランタイムと互 換性がない • ライブラリが先に新しくなる必要がある!
  64. tokio-core • tokio-core はシングルスレッド • tokio に移行するときにはスレッドセーフな実装に切り替える 必要がある • Rc→Arc,

    RefCell→Mutex, etc. のような書き換えが必要 • ライブラリ作者が頑張る
  65. Actix

  66. Actix • Rustで書かれたアクターフレームワーク • Tokioと多少の互換性がある • Tokio-reactor を使っているが、tokio-threadpool は使ってい ない

    (独自executorで動かしている) • したがって、tokio-threadpool の存在を仮定しているライブラ リは動かない可能性がある
  67. futures-0.2

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

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

  70. futures-0.2 yank事件 • 諸事情あってfutures-0.2はyankされて別の名前で再公開された (futures-preview 0.2) • yankされると、既存の Cargo.lock を使わない限り解決されなくなる

    • 特にちゃんとしたアナウンスもなくyankされた • この「yank事件」は結構議論になった • gtk-rs とかが futures-0.2 を使っているらしい
  71. async/await

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

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

  74. futures-awaitの重大な制約 • 借用がawaitをまたぐことができない #[async] fn foo() { let conn =

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

    await!(connect()); let (conn, result) = await!(conn.get(bar)); conn.close(); } 所有権を渡して、あとで同じものを返してもらう というインターフェースになる
  76. 借用とFuture • 「借用している途中のプログラム」を構造体として切り出す • 変数と、その参照がどちらもスタックに乗っていたはず future 変数conn 変数connへの参照

  77. 借用とFuture • Rustの構造体として書こうとするとこんな感じ struct FooState { conn: Connection, conn_ref: &'self.conn

    mut Connection, } こういう気持ちは表明できない こういうのを自己参照構造体 (self-referencial struct) あるいは貫入データ構造 (intrusive data structure) という
  78. 貫入データ構造の問題点 • Rustの値は全てムーブ・リプレースできるが、貫入データ構造 はムーブ・リプレースできない A ARef

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

  80. ?Move の顛末 • Sizedと同様に、デフォルトで有効のトレイト Move を導入す る提案があったが、いくつかの懸念から却下された • これ以上デフォルトトレイトを追加すると混乱が増える •

    Moveできない型は、関数から返すこともできないから生成することも できない。この部分を解決するにはさらに多くの道具が必要になる
  81. Pinned reference (RFC 2349) • ムーブを禁止するのではなく、後からムーブできるような参照 の取り方を禁止してしまえばよいというアイデア • どんなデータも、最初はムーブできる。 •

    しかし、 Pin<&mut T> という特殊な参照を取ると、その中身 はムーブできない(ムーブしないままDropする必要がある) • これは Pin<Box<T>> というラッパーや pin_mut!() というマクロ によって担保される • ただし、貫入データ構造でない場合は、 Pin<&mut T> から &mut T を復元する余地が残されている。
  82. 真の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 を組み合わせることで試せる
  83. futures-0.3 • async/await対応により、 Future を標準ライブラリ入りさせる 必要が出てきた。 • また、Pinned referenceを使って書き直す必要が出てきた。

  84. futures-0.3 pub trait Future { type Output; fn poll(self: Pin<&mut

    Self>, lw: &LocalWaker) -> Poll<Self::Output>; } std::task に移動 Pinned referenceを受け取るようになった。 Resultとの統合が廃止された。 ReadyとNotReadyの2択になった。 Futures-0.2 に導入されていたcontextが 単純化された。 通知用のハンドルだけを持ち回す。
  85. futures-0.3 • futures-0.3は(現状)不安定版のコンパイラに強く依存している。 • しかし、今後はfutures-0.2ではなくfutures-0.3が開発される。 • Tokioのfutures-0.2移行も白紙に戻ってしまった。 • (おそらく)そういう事情から、futures-0.2/futures-0.3はプレ ビュー版としての立場を明確にしたいという動きがあり、それ

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

  87. ハンズオンの準備

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

  89. Dieselの準備 • MySQLとPostgreSQLのdevライブラリを入れておく。例: $ sudo apt install libpq-dev libmysqlclient-dev $

    brew install postgresql mysql dieselがデフォルトで両方を要求するので、両方入れておく。 必要なほうを入れておいて、あとでdieselのオプションを指定するのでもOK
  90. Dieselの準備 • diesel-cliを入れる $ cargo install diesel_cli ビルドに少し時間がかかるので、次に進んでおくとよい

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

    install diesel_cli --no-default-features --features postgres
  92. データベースの作成 $ createdb todoapp うまいURL

  93. データベースの接続確認 $ psql postgres:///todoapp psql (10.5 (Ubuntu 10.5-0ubuntu0.18.04)) Type "help"

    for help. todoapp=# ¥q うまいURL
  94. データベースの接続確認 $ 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
  95. サーバーを書き始めよう

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

  97. プロジェクトを立てる • 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"
  98. プロジェクトを立てる • 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
  99. プロジェクトを立てる • 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は不要になる。
  100. ロギングの準備 fn main() { dotenv::dotenv().ok(); env_logger::init(); info!("Hello, world!"); } dotenvは失敗してもいいので

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

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

    world!
  103. サーバーを起動する use actix_web::{server, App}; fn main() { env_logger::init(); debug!("Launching an

    app..."); server::new(|| App::new()).bind("[::]:8080").unwrap().run(); } イントラネットに公開したくない場合は "localhost:8080"
  104. サーバーを起動する • これでサーバーは立ち上がる $ 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が返ってくるはず
  105. エンドポイントを追加する use actix_web::{http, Path, Responder}; fn ping(_: Path<()>) -> impl

    Responder { "pong" } 既存の行にいい感じに追加してください
  106. エンドポイントを追加する server::new(|| App::new() .route("/ping", http::Method::GET, ping) ) ここを増やす

  107. サーバーを再起動する $ 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が返ってくる
  108. テンプレート

  109. Askamaを使う • Cargo.tomlに依存関係を追加 [dependencies] askama = { version = "0.7.2",

    features = ["with-actix-web"] } [build-dependencies] askama = "0.7.2" 両方必要
  110. Askamaを使う • build.rs というファイルをプロジェクト直下に作成する extern crate askama; fn main() {

    askama::rerun_if_templates_changed(); } build.rs はコンパイル時に実行される。 そのため以下の特徴がある • クロスコンパイル時もホストアーキテクチャにコンパイルされる • 依存関係が区別される。 ([build-dependencies])
  111. テンプレートを書く • templates/base.html <!doctype html> <title>{% block title %} default

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

    %}Top{% endblock %} {% block content %} <h1>Top</h1> <p>Hello, Askama!</p> {% endblock %}
  113. テンプレートを使う #[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] は不要になる
  114. テンプレートを使う fn index(_: Path<()>) -> impl Responder { IndexTemplate }

    … App::new() .route("/", http::Method::GET, index) .route("/ping", http::Method::GET, ping) 今のところテンプレート引数はないので、こうなる ここまで書いて実行すると描画される
  115. 静的ファイル

  116. 静的ファイルをサーブする 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 を作ると無駄スレッドが大量にできるので、 こちらも工夫が必要
  117. 静的ファイルをサーブする • ./static/style.css とかを書いてみる body { background-color: #888888; }

  118. 静的ファイルをサーブする • ./templates/base.html に行を足してみる <link rel="stylesheet" href="/static/style.css">

  119. データベース

  120. データベース • Cargo.tomlに依存関係を追加 [dependencies] chrono = "0.4.6" diesel = {

    version = "1.3.3", features = ["postgres"] } cargo build を流しつつ次に進もう
  121. データベース • .env に以下を追加 (psqlで繋がるURLを指定する) DATABASE_URL=postgres:///todoapp

  122. データベース $ diesel setup

  123. データベース $ diesel migration generate create_todos

  124. データベース • up.sql ができているので以下のようにする CREATE TABLE todos ( id BIGSERIAL

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

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

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

  128. データベース • スキーマを読み込む #[macro_use] extern crate diesel; mod schema; src/schema.rs

    が自動生成されている
  129. 時間切れ…… • 残りは上手くいったらライブコーディングします!