Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rust速習会4

 Rust速習会4

Masaki Hara

December 13, 2018
Tweet

More Decks by Masaki Hara

Other Decks in Programming

Transcript

  1. リリースサイクル (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. リリースサイクル (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. リリースサイクル (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. リリースサイクル (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. エディション ≠ バージョン 1.30 Rust2015 モード 1.31 Rust2018 モード Rust2015

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

    モード 1.32 Rust2018 モード Rust2015 モード エディション間には相互運用性がある。 つまり、同じコンパイラで異なるエディションのソースをコンパイルし、 リンクすることができる。
  7. 相互運用性 • エディションの垣根を越えて依存できる [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. ボーナス機能 • 厳密には非互換性ではないが、Rust2018だけで使える機能や、 Rust2018で本格的に導入される新しいイディオムもある • NLL – より正確なライフタイム推論 (Rust2018で先に有効化される) •

    extern crate が不要に (Rust2018のみ) • dyn Trait によるトレイトオブジェクトの明示 (Rust2018イディオム) • Iter<'_> のようなライフタイム出現箇所の明示 (Rust2018イディオム) • mod.rs が不要に (Rust2018のみ)
  9. 非互換性(1) キーワード • 文脈キーワードからキーワードに昇格 async await dyn try 文脈キーワード: 識別子と紛らわしくないときだけ使えるキーワード。

    ※これにあわせて、以下の4つのワードがキーワードから識別子に降格された(Rust2015/2018共通): alignof offsetof pure sizeof
  10. 非互換性(1) キーワード • Rust2015 async fn main2() -> Result<(), ()>

    { ... await!(expr) ... } fn main() { tokio::run(main2().boxed().compat()); }
  11. 非互換性(1) キーワード • Rust2018 fn main() { tokio::run(async { ...

    await expr ... }.boxed().compat()); } asyncブロックが使えるようになる await式が使えるようになる async {} はRust2015では async という名前の構造体として解釈される。 await(expr) はRust2015では await という名前の関数呼び出しとして解釈される。
  12. 非互換性(1) キーワード • Rust2018 let res = try { let

    f = File::open("/proc/cpuinfo")?; }; try構文が使えるようになる try {} はRust2015では try という構造体として解釈される
  13. 非互換性(2) モジュール (1/4) • extern crate が不要になった extern crate serde_json;

    fn main() { … serde_json::from_str … } fn main() { … serde_json::from_str … } ※これ自体は非互換な変更ではない
  14. 非互換性(2) モジュール (2/4) • #[macro_use] extern crate が不要になった #[macro_use] extern

    crate failure; #[derive(Fail)] struct Foo { … } use failure::Fail; #[derive(Fail)] struct Foo { … } ※これ自体は非互換な変更ではない
  15. 非互換性(2) モジュール • Rust2015 use または pub(in) それ以外 abc::def 絶対パス

    相対パス ローカル変数など self::abc::def super::abc::def 相対パス ::abc::def 絶対パス crate::abc::def 絶対パス Rust2018との 互換性のために存在
  16. 非互換性(2) モジュール • Rust2018 use または pub(in) それ以外 abc::def (相対パス)

    外部crate 相対パス 外部crate ローカル変数など self::abc::def super::abc::def 相対パス ::abc::def 絶対パス 外部crate crate::abc::def 絶対パス Rust2015との 互換性のために存在
  17. 非互換性(3) 匿名引数 • 構文的な一貫性が保証されるようになる fn foo((x, y): (i32, i32)) {}

    trait Foo { fn foo((x, y): (i32, i32)) {} } Rust2015/2018で使用可能 Rust2018で使用可能
  18. 例: ripgrep • ダウンロードして実行 $ git clone https://github.com/BurntSushi/ripgrep.git $ cd

    ripgrep $ cargo build $ cargo run '¥d{20,}' デバッグビルドなので速くはない
  19. 例: ripgrep • Rust2018対応コードに変換 $ rm globset/benches/bench.rs $ cargo fix

    --edition --all --allow-dirty $ cargo test --all ベンチマークはnightlyでしかビルドできないので一旦外す。 これによりdirty treeになるので --allow-dirty が必要になる。 (練習なので削除してコミットしてしまってもよい)
  20. 例: ripgrep • Rust2018を有効化する $ sed -i.bak '4iedition = "2018"'

    $(find . -name Cargo.toml) $ rm $(find . -name Cargo.toml.bak) $ cargo test --all # fail
  21. 例: 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. 例: ripgrep • 残念ながら上手く変換できていない場所があるので手動で直す • src/args.rs • use log; が通らないので

    use crate::log; にする。 • src/search.rs • json! が解決できないので use serde_json::json; を加える。
  23. 例: ripgrep • Rust2018イディオムに適合させる $ cargo fix --edition-idioms –all --allow-dirty

    $ cargo test --all 残念ながらうまく適用できないfixがあるので、手動で適用してあげ るとよい。
  24. 例: ripgrep • Rust2018対応完了! • さらに追加でできること • extern crate を自動で削除してくれるが、行番号が変わらないよう

    に空行を残すので、手動で削除してあげる • #[macro_use] extern crate は自動では変換できないので、うま く対応してあげる
  25. RustはCLIに向いている? • GolangがCLIに向いているのと同じような理由で、RustもCLI には比較的向いているはず • 容易なクロスコンパイル、Windowsの第一級サポート • シングルバイナリ • ただ、CLI用途での非同期I/Oはまだ未開拓感がある

    • 正直、この辺の機微はあんまり詳しくないので、書いてみて判 断してみてほしい • Awesome Rustとか一瞥してみるといいかも https://github.com/rust-unofficial/awesome-rust
  26. 依存関係 • 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. ロギング • src/main.rs use log::debug; fn main() { env_logger::init(); debug!("ferris_watch

    starting..."); } RUST_LOG=ferris_watch=debug cargo run と実行して、デバッグ出力がでる ことを確認
  28. エラー処理 • 面倒なので全部failure::Errorに回収させる fn main() -> Result<(), failure::Error> { …

    Ok(()) } こうしておくと main の中で ? が好き勝手使えて便利
  29. コマンド引数(1) • パーサーを起動する use clap::App; … let matches = App::new("ferris_watch")

    .version("0.1.0") .author("Your Name <[email protected]>") .about("cute watch command") .get_matches(); cargo run -- --help と実行して、ヘルプがでることを確認 ※よりベーシックな機能に絞った getopts 、 clap とは逆にヘルプからパーサーを作る docopts 、 clap の 結果を構造体マップできるようにした structopt などのライブラリもある。
  30. コマンド引数(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. コマンド引数(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. コマンド引数(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. コマンドを実行する • 実行する 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. コマンドを実行する • 実行する 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. 休眠 • コマンド実行後に指定秒数休眠する 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. 無限ループ • 休眠しつつ無限に実行するようにする loop { // … コマンドを実行する処理 … //

    … 休眠する処理 … } Ok(()) // 到達しないという警告が出るが、いったん残しておく あとで書く処理の都合上、定期的に起 きるようにする
  37. 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. 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. graceful shutdown • かわりに、Ctrl-Cを見張って自分で脱出する 'outer: loop { for … {

    // … 休眠処理 … if interrupted() { break 'outer; } } } forが1回も実行されなかった場合に備えているとなおよい
  40. Cursesを使う • ウインドウを立ち上げる let window = pancurses::initscr(); struct EndWin; impl

    Drop for EndWin { fn drop(&mut self) { pancurses::endwin(); } } let _endwin = EndWin; 確実に実行してほしいので drop で実行する (scopeguard や defer クレートを使う手もある)
  41. Cursesを使う • println! のかわりに printw で出力する // println!("{}", output); window.clear();

    window.printw(output); window.refresh(); cargo run date などを試してみよう
  42. 手元でクロスコンパイルをしてみる • クロスコンパイルに必要なもの • ターゲットアーキテクチャ用の標準ライブラリ • ターゲットアーキテクチャ用の外部ライブラリ • ターゲットアーキテクチャ用のリンカ (gcc)

    • 通常のコンパイラに他アーキテクチャ向けのバックエンドも同 梱されているので、コンパイラは不要 • メタビルド系の処理もcargoがいい感じにハンドルしてくれる
  43. Travisでツールをリリースする • こんな感じで.travis.ymlを書く • https://github.com/qnighy/ferris_watch/blob/63adc870937351aead ea7ae8f77804877cbf2d14/.travis.yml • TravisはWindowsをベータサポートしているので、3つのOSで ビルドできる •

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