Rust速習会4

Ba655e3712aaabfbca289fe136f85fe4?s=47 Masaki Hara
December 13, 2018

 Rust速習会4

Ba655e3712aaabfbca289fe136f85fe4?s=128

Masaki Hara

December 13, 2018
Tweet

Transcript

  1. 10.

    リリースサイクル (6週間ごと) nightly beta 1.28 beta 1.29 beta 1.30 beta

    1.31 stable 1.30 stable 1.29 stable 1.28 2018/06/21 2018/08/02 2018/09/13 2018/10/25 2018/12/06
  2. 11.

    リリースサイクル (6週間ごと) nightly beta 1.28 beta 1.29 beta 1.30 beta

    1.31 stable 1.30 stable 1.29 stable 1.28 2018/06/21 2018/08/02 2018/09/13 2018/10/25 2018/12/06 最新版かつ、全ての機能が使える。 バグが入ることもあるし、破壊的変更が入ることもある
  3. 12.

    リリースサイクル (6週間ごと) nightly beta 1.28 beta 1.29 beta 1.30 beta

    1.31 stable 1.30 stable 1.29 stable 1.28 2018/06/21 2018/08/02 2018/09/13 2018/10/25 2018/12/06 安定化する予定の機能だけに制限される。 この期間で、stableからのリグレッションや、新機能のバグを直す。
  4. 13.

    リリースサイクル (6週間ごと) nightly beta 1.28 beta 1.29 beta 1.30 beta

    1.31 stable 1.30 stable 1.29 stable 1.28 2018/06/21 2018/08/02 2018/09/13 2018/10/25 2018/12/06 betaがstableに昇格する。 重大なバグやリグレッションがあったときだけアップデートされる。
  5. 15.

    エディション ≠ バージョン 1.30 Rust2015 モード 1.31 Rust2018 モード Rust2015

    モード 1.32 Rust2018 モード Rust2015 モード コンパイラバージョン間には互換性がある。 つまり、同じソースを次のバージョンのコンパイラでもコンパイルできる。
  6. 16.

    エディション ≠ バージョン 1.30 Rust2015 モード 1.31 Rust2018 モード Rust2015

    モード 1.32 Rust2018 モード Rust2015 モード エディション間には相互運用性がある。 つまり、同じコンパイラで異なるエディションのソースをコンパイルし、 リンクすることができる。
  7. 17.

    相互運用性 • エディションの垣根を越えて依存できる [package] name = "a" version = "0.1.0"

    edition = "2015" [package] name = "b" version = "0.1.0" edition = "2018" [dependencies] a = "0.1.0" [package] name = "c" version = "0.1.0" edition = "2015" [dependencies] b = "0.1.0"
  8. 18.

    ボーナス機能 • 厳密には非互換性ではないが、Rust2018だけで使える機能や、 Rust2018で本格的に導入される新しいイディオムもある • NLL – より正確なライフタイム推論 (Rust2018で先に有効化される) •

    extern crate が不要に (Rust2018のみ) • dyn Trait によるトレイトオブジェクトの明示 (Rust2018イディオム) • Iter<'_> のようなライフタイム出現箇所の明示 (Rust2018イディオム) • mod.rs が不要に (Rust2018のみ)
  9. 21.

    非互換性(1) キーワード • 文脈キーワードからキーワードに昇格 async await dyn try 文脈キーワード: 識別子と紛らわしくないときだけ使えるキーワード。

    ※これにあわせて、以下の4つのワードがキーワードから識別子に降格された(Rust2015/2018共通): alignof offsetof pure sizeof
  10. 22.

    非互換性(1) キーワード • Rust2015 async fn main2() -> Result<(), ()>

    { ... await!(expr) ... } fn main() { tokio::run(main2().boxed().compat()); }
  11. 23.

    非互換性(1) キーワード • Rust2018 fn main() { tokio::run(async { ...

    await expr ... }.boxed().compat()); } asyncブロックが使えるようになる await式が使えるようになる async {} はRust2015では async という名前の構造体として解釈される。 await(expr) はRust2015では await という名前の関数呼び出しとして解釈される。
  12. 26.

    非互換性(1) キーワード • Rust2018 let res = try { let

    f = File::open("/proc/cpuinfo")?; }; try構文が使えるようになる try {} はRust2015では try という構造体として解釈される
  13. 27.

    非互換性(2) モジュール (1/4) • extern crate が不要になった extern crate serde_json;

    fn main() { … serde_json::from_str … } fn main() { … serde_json::from_str … } ※これ自体は非互換な変更ではない
  14. 28.

    非互換性(2) モジュール (2/4) • #[macro_use] extern crate が不要になった #[macro_use] extern

    crate failure; #[derive(Fail)] struct Foo { … } use failure::Fail; #[derive(Fail)] struct Foo { … } ※これ自体は非互換な変更ではない
  15. 31.

    非互換性(2) モジュール • Rust2015 use または pub(in) それ以外 abc::def 絶対パス

    相対パス ローカル変数など self::abc::def super::abc::def 相対パス ::abc::def 絶対パス crate::abc::def 絶対パス Rust2018との 互換性のために存在
  16. 32.

    非互換性(2) モジュール • Rust2018 use または pub(in) それ以外 abc::def (相対パス)

    外部crate 相対パス 外部crate ローカル変数など self::abc::def super::abc::def 相対パス ::abc::def 絶対パス 外部crate crate::abc::def 絶対パス Rust2015との 互換性のために存在
  17. 34.

    非互換性(3) 匿名引数 • 構文的な一貫性が保証されるようになる fn foo((x, y): (i32, i32)) {}

    trait Foo { fn foo((x, y): (i32, i32)) {} } Rust2015/2018で使用可能 Rust2018で使用可能
  18. 38.

    例: ripgrep • ダウンロードして実行 $ git clone https://github.com/BurntSushi/ripgrep.git $ cd

    ripgrep $ cargo build $ cargo run '¥d{20,}' デバッグビルドなので速くはない
  19. 39.

    例: ripgrep • Rust2018対応コードに変換 $ rm globset/benches/bench.rs $ cargo fix

    --edition --all --allow-dirty $ cargo test --all ベンチマークはnightlyでしかビルドできないので一旦外す。 これによりdirty treeになるので --allow-dirty が必要になる。 (練習なので削除してコミットしてしまってもよい)
  20. 40.

    例: ripgrep • Rust2018を有効化する $ sed -i.bak '4iedition = "2018"'

    $(find . -name Cargo.toml) $ rm $(find . -name Cargo.toml.bak) $ cargo test --all # fail
  21. 41.

    例: ripgrep • Rust2018を有効化する $ sed -i.bak '4iedition = "2018"'

    $(find . -name Cargo.toml) $ rm $(find . -name Cargo.toml.bak) $ cargo test --all # fail 「全部3行目に挿入すればいいや」というズボラ処理。 要するに以下のような行が入ればよい edition = "2018"
  22. 42.

    例: ripgrep • 残念ながら上手く変換できていない場所があるので手動で直す • src/args.rs • use log; が通らないので

    use crate::log; にする。 • src/search.rs • json! が解決できないので use serde_json::json; を加える。
  23. 43.

    例: ripgrep • Rust2018イディオムに適合させる $ cargo fix --edition-idioms –all --allow-dirty

    $ cargo test --all 残念ながらうまく適用できないfixがあるので、手動で適用してあげ るとよい。
  24. 44.

    例: ripgrep • Rust2018対応完了! • さらに追加でできること • extern crate を自動で削除してくれるが、行番号が変わらないよう

    に空行を残すので、手動で削除してあげる • #[macro_use] extern crate は自動では変換できないので、うま く対応してあげる
  25. 46.

    RustはCLIに向いている? • GolangがCLIに向いているのと同じような理由で、RustもCLI には比較的向いているはず • 容易なクロスコンパイル、Windowsの第一級サポート • シングルバイナリ • ただ、CLI用途での非同期I/Oはまだ未開拓感がある

    • 正直、この辺の機微はあんまり詳しくないので、書いてみて判 断してみてほしい • Awesome Rustとか一瞥してみるといいかも https://github.com/rust-unofficial/awesome-rust
  26. 50.

    依存関係 • Cargo.tomlはこんな感じにする [dependencies] log = "0.4.6" env_logger = "0.6.0"

    failure = "0.1.3" signal-hook = "0.1.6" clap = "2.32.0" pancurses = "0.16.0"
  27. 51.

    ロギング • src/main.rs use log::debug; fn main() { env_logger::init(); debug!("ferris_watch

    starting..."); } RUST_LOG=ferris_watch=debug cargo run と実行して、デバッグ出力がでる ことを確認
  28. 52.

    エラー処理 • 面倒なので全部failure::Errorに回収させる fn main() -> Result<(), failure::Error> { …

    Ok(()) } こうしておくと main の中で ? が好き勝手使えて便利
  29. 53.

    コマンド引数(1) • パーサーを起動する use clap::App; … let matches = App::new("ferris_watch")

    .version("0.1.0") .author("Your Name <yourname@example.com>") .about("cute watch command") .get_matches(); cargo run -- --help と実行して、ヘルプがでることを確認 ※よりベーシックな機能に絞った getopts 、 clap とは逆にヘルプからパーサーを作る docopts 、 clap の 結果を構造体マップできるようにした structopt などのライブラリもある。
  30. 54.

    コマンド引数(2) • 実行するコマンドを指定する引数 use clap::{App, Arg}; … .arg( Arg::with_name("command") .required(true)

    .multiple(true) .help("The command to run periodically"), ) cargo run -- --help と実行して、ヘルプがでることを確認 .get_matches() の直前に追加
  31. 55.

    コマンド引数(3) • 実行する間隔を指定する .arg( Arg::with_name("interval") .long("interval") .short("n") .takes_value(true) .default_value("2.0") .help("The

    period to run a command"), ) cargo run -- --help と実行して、ヘルプがでることを確認 .get_matches() の直前に追加
  32. 56.

    コマンド引数(4) • 引数を取り出す use clap::{App, Arg, value_t}; … let command

    = matches.values_of("command").unwrap().collect::<Vec<_>>(); let interval = value_t!(matches, "interval", f64)?; debug!("command = {:?}", command); debug!("interval = {:?}", interval); RUST_LOG=ferris_watch cargo run -- -n 0.5 -- ls -a などを実行してみよう
  33. 58.

    コマンドを実行する • 実行する use std::process::Command; … let output = Command::new(command[0]).args(&command[1..]).output()?;

    debug!("output = {:?}", output); let output = String::from_utf8_lossy(&output.stdout); println!("{}", output); cargo run -- -- ls -a などを実行してみよう 実行して出力の取得までやってくれるヘルパー関数。 もちろん、もっと複雑な操作もできる
  34. 59.

    コマンドを実行する • 実行する use std::process::Command; … let output = Command::new(command[0]).args(&command[1..]).output()?;

    debug!("output = {:?}", output); let output = String::from_utf8_lossy(&output.stdout); println!("{}", output); cargo run -- -- ls -a などを実行してみよう この場合 • 実行に失敗したら終了 • コマンドのステータスは無視 という挙動になる
  35. 60.

    休眠 • コマンド実行後に指定秒数休眠する use std::thread::sleep; use std::time::Duration; … let interval10

    = (interval * 10.0) as u32; … for _ in 0..interval10 { sleep(Duration::from_millis(100)); } あとで書く処理の都合上、定期的に起 きるようにする
  36. 61.

    無限ループ • 休眠しつつ無限に実行するようにする loop { // … コマンドを実行する処理 … //

    … 休眠する処理 … } Ok(()) // 到達しないという警告が出るが、いったん残しておく あとで書く処理の都合上、定期的に起 きるようにする
  37. 62.

    graceful shutdown • Ctrl-Cで自動で死なないようにする use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; let

    interrupted = Arc::new(AtomicBool::new(false)); signal_hook::flag::register( signal_hook::SIGINT, interrupted.clone())?; let interrupted = || interrupted.load(Ordering::SeqCst); この状態でうっかり実行してしまった場合、Ctrl-Cでは止められないので SIGTERM (killコマンドのデフォルト) などで止めてあげる
  38. 63.

    graceful shutdown • Ctrl-Cで自動で死なないようにする use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; let

    interrupted = Arc::new(AtomicBool::new(false)); signal_hook::flag::register( signal_hook::SIGINT, interrupted.clone())?; let interrupted = || interrupted.load(Ordering::SeqCst); この状態でうっかり実行してしまった場合、Ctrl-Cでは止められないので SIGTERM (killコマンドのデフォルト) などで止めてあげる SIGINTで止まらずにフラグだけ立てる
  39. 64.

    graceful shutdown • かわりに、Ctrl-Cを見張って自分で脱出する 'outer: loop { for … {

    // … 休眠処理 … if interrupted() { break 'outer; } } } forが1回も実行されなかった場合に備えているとなおよい
  40. 68.

    Cursesを使う • ウインドウを立ち上げる let window = pancurses::initscr(); struct EndWin; impl

    Drop for EndWin { fn drop(&mut self) { pancurses::endwin(); } } let _endwin = EndWin; 確実に実行してほしいので drop で実行する (scopeguard や defer クレートを使う手もある)
  41. 69.

    Cursesを使う • println! のかわりに printw で出力する // println!("{}", output); window.clear();

    window.printw(output); window.refresh(); cargo run date などを試してみよう
  42. 73.

    手元でクロスコンパイルをしてみる • クロスコンパイルに必要なもの • ターゲットアーキテクチャ用の標準ライブラリ • ターゲットアーキテクチャ用の外部ライブラリ • ターゲットアーキテクチャ用のリンカ (gcc)

    • 通常のコンパイラに他アーキテクチャ向けのバックエンドも同 梱されているので、コンパイラは不要 • メタビルド系の処理もcargoがいい感じにハンドルしてくれる
  43. 77.

    Travisでツールをリリースする • こんな感じで.travis.ymlを書く • https://github.com/qnighy/ferris_watch/blob/63adc870937351aead ea7ae8f77804877cbf2d14/.travis.yml • TravisはWindowsをベータサポートしているので、3つのOSで ビルドできる •

    さすがに同じOSでビルドしたほうが楽 • クロスコンパイルで32bit用バイナリは生成できる • ……という話をする予定だったが、ncursesとの相性が異様に 悪くてmuslのコンパイルが通らない……