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

Rustハンズオン@エウレカ社

68dad178ea4fa6aa86862b3a66a15306?s=47 Yuki Toyoda
July 16, 2021
3.4k

 Rustハンズオン@エウレカ社

エウレカ社にてRustのハンズオンを実施しました。

コード全体は下記で確認できます。
https://github.com/yuk1ty/rust-basic-handson

68dad178ea4fa6aa86862b3a66a15306?s=128

Yuki Toyoda

July 16, 2021
Tweet

Transcript

  1. 2021/7/16 @ 株式会社エウレカ Rust ハンズオン

  2. 江間さんご招待ありがとうございます

  3. Hello, I’m... Yuki Toyoda Software Engineer @ CyberAgent Dynalyst Next

    Experts in Rust @ CyberAgent 共著『実践Rustプログラミング入門』 Rust.Tokyo, RustFest Global オーガナイザ Contributor to rust-lang/rust, servo, etc @helloyuki_
  4. Next Experts について Next Experts 3つの役割 特定領域への貢献意欲や一定の実績を有し、将来的なDeveloper Expertsを目指すエンジ ニアのための活動支援制度。サイバーエージェントにはDeveloper Expertsと呼ばれる、

    特定の専門領域における社内・社外活動の支援制度が存在します。 各専門領域における高い専門性を身につける 対外活動を通して、各専門領域の発展に貢献 管轄や事業部の垣根を越え、担当領域のサポート
  5. 『実践Rustプログラミング入門』 Rustは、C/C++の代わりとなる最新の爆速言語として注目 されています。「とにかく実行速度が速い」「モダンな言 語機能が一通り入っている」「OSからWebアプリケー ションまで幅広く実装できる」「ツール群がとても充実し ている」「安全性が強力に担保されている」など、数多く の魅力があります。本書は、JavaやPythonなど他の言語 に習熟しているエンジニアを対象に、Rustの独特な仕様と 開発ノウハウをわかりやすく解説した入門書です。

  6. Rust.Tokyo, RustFest Global オーガナイザ 日本初の Rust カンファレンス「Rust.Tokyo」や、グローバルな Rust カンファレン ス「RustFest

    Global」のオーガナイザも務めています。2021年9月18日に Rust.Tokyo は開催予定です。CfP も受付中ですので、ご興味がある方はぜひ、ご応募ください。 RustFest Global ミーティングの様子 Rust.Tokyo 2021 のロゴ
  7. 目次 Rust とは 基本的な文法 cat コマンドの実装 grep コマンドの実装

  8. Rust とは

  9. Rust とは 2015年にリリースされたプログラミング言語。 得意な領域はシステムプログラミング。 速度と安全性を両立した新しい言語。

  10. 2015年にリリースされたプログラミング言語 オープンソースで開発が進められている。 ブラウジングエンジン Servo のフィードバックを受けて成長。 Stack Overflow 愛され言語ランキング5年連続1位

  11. 得意な領域はシステムプログラミング OS/コンパイラ/ランタイム・VM/ブラウザなど、 低レイヤーの操作が必要なソフトウェアの実装に最適化されている ️ Redox OS Servo Deno

  12. ただ…Web などでも使える 文法が非常に表現力豊かで書き心地もよいことから、低レイヤーだけでなく Web バックエンドや WebAssembly ベースのアプリケーションなどの レイヤーの高い世界でも活用が始まっている。 Wasmer (WebAssembly

    Runtime) 別所で以前 登壇した資料
  13. 速度と安全性を両立した新しい言語 安全性面 平均してGo/Javaより速く、C/C++並のスピードが出る。 ゼロコスト抽象化により、あらゆるコードを静的に解決する。 GC やランタイムはなく、機械語を直接生成する。 DX と速度がトレードオフにならないエルゴノミックな文法。 速度面 GC

    はないが、メモリ安全である。 safe コードでは、実行時に未定義動作が発生しない保証がされる。 メモリ確保と解放をコンパイラが自動で検査する。
  14. 速度と安全性を両立した新しい言語 安全性面 平均してGo/Javaより速く、C/C++並のスピードが出る。 ゼロコスト抽象化により、あらゆるコードを静的に解決する。 GC やランタイムはなく、機械語を直接生成する。 DX と速度がトレードオフにならないエルゴノミックな文法。 速度面 GC

    はないが、メモリ安全である。 safe コードでは、実行時に未定義動作が発生しない保証がされる。 メモリ確保と解放をコンパイラが自動で検査する。 速度を出しつつ安全性を追求し、 従来存在したトレードオフを打破した言語
  15. 本日の内容をより詳しく知るために Rust をはじめるための資料集 https://blog-dry.com/entry/2021/01/23/141936

  16. 基本的な文法

  17. ツールの準備 cargo をインストール。 これだけ。 https://www.rust-lang.org/ja/tools/install

  18. エディタはどうする? 今日は VSCode を使って作業を進めます。 rust-analyzer という Extension を入れておくと便利です。 もちろんお好きなエディタでも、作業自体はできます。

  19. 今日のコード全体 GitHub 上にすべて公開しています。 もし途中で詰まったり、コピペしたくなったらご覧ください。 https://github.com/yuk1ty/rust-basic-handson

  20. まずは Hello, World してみましょう

  21. まずは Hello, World してみましょう 生成されたディレクトリの中に main.rs というファイルがあります。 中身を見てみましょう。下記が Rust での

    Hello, World コードです。
  22. まずは Hello, World してみましょう fn main() は関数を宣言しています。 Rust では関数の定義は fn

    キーワードで行うことができます。
  23. まずは Hello, World してみましょう println! で標準出力します。 は であることを意味します。 マクロとは、Rust のプログラムをプログラミングする仕組みのことです。

    他の言語ではメタプログラミングとも呼ぶ機能のことです。 `!` マクロ
  24. 実行してみましょう

  25. 変数宣言をしてみましょう let というキーワードで変数を束縛することができます。 になります。 とすると、 な変数を作ることができます。 デフォルトではイミュータブル let mut ミュータブル

  26. 変数宣言をしてみましょう let で変数宣言をできます。 println! 内の {} は変数を文字列内に代入するプレースホルダです。 Rust は型推論が強力なので、型注釈はほぼなしで実装できます。

  27. 値を再代入してみましょう 多くのプログラミング言語では、下記はコンパイルが通るかも知れません。 しかし、Rust ではコンパイルが通りません。 変数はデフォルトでイミュータブルなので、下記はコンパイルエラーです。

  28. Rust のコンパイルエラーはとても親切 先ほどのコードをコンパイルする と、下記のようにエラーが出ます。 指示通りに直すことで、多くのケー スではコンパイルを通せます。

  29. 値を再代入してみましょう let mut に変更するとその変数はミュータブルになります。 ミュータブルな変数では再代入や破壊的な操作が可能です。

  30. 変数宣言まとめ let というキーワードで変数を束縛することができました。 変数は、デフォルトではイミュータブルになります。 let mut とすると、ミュータブルな変数にできました。

  31. よく見るプリミティブ型 (※これ以外にもあります。詳しくはドキュメントをご覧ください) i8, i16, i32, i64 u8, u16, u32, u64

    f32, f64 isize / usize bool &str, String 符号付き整数型。 浮動小数点数型。 CPU アーキテクチャごとにビット分のサイズを取る整数型。isize は符号付き。 ブール値。true または false をとる。 文字列を扱う際によく利用される型。後述。 符号なし整数型。
  32. 文字列型 今日は String と &str を紹介します。 よく質問を受けるので、先に軽く紹介してしまいます。 文字列型はこれ以外にもあります。

  33. String と &str String &str ヒープメモリにアロケートされる。 リサイズ可能な UTF-8 のテキストを保持するバッファ。 いわゆる

    。 なので、String と違ってリサイズ等はできない。 「スライス」 read-only
  34. String のメモリモデル stack heap t u t o r i

    a l 10 8 capacity capacity length buffer length ※ stack: いわゆるスタックメモリを指す。ローカル変数などが保存される領域。 ※ heap: いわゆるヒープメモリを指す。動的に確保される領域で、C では malloc によって確保される。
  35. &str のメモリモデル stack preallocated read-only memory t u t o

    r i a l 8 length buffer length ※ preallocated read-only memory: 機械語生成時にすでに確保済みのメモリ領域を指す。
  36. String, &str を使うには ダブルクォーテーションで囲うと文字列を扱うことができますが、 その際の型は &str 型になります。 たとえば、to_string() 関数を呼び出すと、String 型になります。

  37. FizzBuzz を通じて制御構文や関数を学ぶ if 式を使った簡単な制御構文を学びます。 for 式を使ったループを学びます。 関数への切り出し方を学びます。 関数型プログラミング的なアプローチを学びます。

  38. if 式を使って FizzBuzz を作ってみる

  39. if 式を使って FizzBuzz を作ってみる 他の言語と同様に if キーワードが存在しま す。 if は

    なので、結果を変数に束縛できます。 セミコロンを文末につけると式になり、な い場合は文として解釈が行われます。 if 式がある関係で、三項演算子は Rust には ありません。 式
  40. for 式を使って0〜99の数字をイテレートする

  41. for 式を使って0〜99の数字をイテレートする 0..100 と書くと、0以上100未満の範囲を示 すオブジェクト (Range) を生成できます。 num に0〜99の値が1つずつ流れ込みます。 Range

    は の性質をもちます。 for はイテレータを回す糖衣構文になってい ます。 イテレータ
  42. 関数に切り出す

  43. 関数に切り出す fn 関数 セミコロンを書 かないと return 扱い と宣言すると を宣言できます。 関数名は

    snake_case です。 引数は「仮引数名: 型名」で定義できます。 返りの型は -> のあとに書きます。 return は基本は必要なく、 になります。
  44. 関数型的なアプローチ これまでは for を用いた手続き型的なアプローチを見てきました。 Rust は関数型プログラミングの手法をいくつかとりいれています。 関数型的なアプローチを使った FizzBuzz を紹介します。

  45. 関数型的なアプローチ

  46. 関数型的なアプローチ 0〜99のイテレータを作成し、その結果を fizzbuzz 関数に通します。 結果生成される文字列を結合していき、ひとかたまりにまとめます。 | ... | で囲まれた部分は と呼びます。他の言語ではラムダ式など。

    クロージャー
  47. 関数型的なアプローチ わたしは日頃関数型プログラミング言語を使用しているので、 こちらの方が宣言的でより馴染みがあり好きです。 どちらのスタイルで書いたとしても、速度に差はほぼ出ません。 ※ ゼロコスト抽象化の恩恵によるものです。理論上はそうなります。ただ、ケースや最適化のされ方によっては差が出るものも多少あるかもしれません。

  48. 現時点で紹介していないもの、省いたもの struct、enum、所有権、借用については後述します。 ジェネリクスやトレイト、ライフタイムなどは説明していません。 詳しくは TRPL などのリファレンスを参照してください。 ※ TRPL = The

    Rust Programming Language。Rust を学習する際のバイブル的な本です。
  49. cat コマンドを実装する

  50. 実践編: cat コマンドの実装 基本文法を一通り学んだところで、 実際に使えるツールを作りながら、Rust の書き心地を学びます。 パターンマッチングや enum を紹介します。

  51. 最終目標

  52. cat, grep で使用するプロジェクトを新規作成する

  53. main.rs 自身を表示するプログラムを書く

  54. main.rs 自身を表示するプログラムを書く read_to_string 関数は、パスの内容を読み込んで String 型にして返します。 は他の言語だと import のような記法で、 を使用することを意味します。

    は という文法です。 Ok や Err は Result 型という です。 use モジュール match 「パターンマッチ」 enum のバリアント
  55. パターンマッチ 他の言語でいう です。 Rust では多くの型がパターンマッチに対応しています。 いくつか例を示します。 switch 文をもう少し強力にしたもの

  56. パターンマッチの例 数値型はパターンマッチングの対象になります。 1, 2, 3 の場合にそれぞれの文字を取り出す、 `_` はそれ以外のパターンの場合という意味になります。

  57. パターンマッチの例 文字列もパターンマッチングの対象にできます。 `start...end` は「startからendの範囲で」という意味になります。

  58. Rust のエラーハンドリング: Result 型 Rust ではエラーハンドリングを Result 型で行います。 成功なら Ok

    、失敗なら Err が返ります。 パターンマッチなどを使って中身を取り出します。
  59. Result 型のパターンマッチングの仕方 Ok(content) で、content という変数に内容をもちます。 Err(reason) の場合は、reason という変数にエラー内容が入ります。 それらを後続の処理で使用することができます。

  60. ? キーワード 常にパターンマッチングが必要なわけではなく、 ことができるようになっています。 伝播先での値の取り出し時には、必ずエラーハンドリングが求められます。 `?` というキーワードでエラーを伝播させる

  61. ファイルパスを実行時引数から渡せるようにする

  62. 今回記述が増えた内容について std::env::args は関数で、実行時引数の取り出しに利用できます。 nth で実行時引数を取り出します。 Some, None は Option 型で、値がない可能性があることを示します。

  63. 値がない可能性があることを示す: Option 型 他の言語で言う です。 Some なら値があり、None なら値がなかったことになります。 パターンマッチなどを使って中身を取り出します。 null

    や nil が入る場所に使われる型
  64. Option 型のパターンマッチングの仕方 Some(path) で、path という変数に内容を持ちます。 None の場合は値がないので、何ももちません。

  65. if let Option の場合、よく「値があるときだけ特定の処理をしたい」ケースに出会います。 その際は、None の場合は不要になります。パターンマッチングだと冗長です。 代わりに if let という構文を利用しているコードをよく目にします。

  66. grep コマンドを実装する

  67. 実践編: grep コマンドの実装 cat コマンドを改変すると、grep コマンドを作れます。 構造体や所有権を勉強しつつ、 ライブラリをいくつか使って grep を拡張してみます

  68. 最終目標

  69. 指定した文字列がある行を検索し出力する

  70. 指定した文字列がある行を検索し出力する 先ほどの run_cat 関数を run に変更してい ます。 grep 関数を作り、中で指定した文字パター ンに一致するかチェックしています。

    .lines() 関数により、1行1行イテレータを 回すことができるようになっています。
  71. 指定した文字列がある行を検索し出力する

  72. 指定した文字列がある行を検索し出力する 今回は1つ目の引数で検索対象を、2つ目の引数でファイルパスを指定します。 (pattern, path) は と呼ばれる文法です。パターンマッチして使用しています。 両方にきちんと引数の指定がない場合はエラーメッセージを出すようにしています。 タプル

  73. ここまでの実装 cat 用の実装に検索実装を追加し て、一致した文字列があった際に 標準出力するように修正しまし た。 実行時引数で検索したい文字列を 受け取れるようにしました。

  74. grep に使用する引数を構造体にまとめる 実行時引数として渡されるパラメータが増えてきました。 データをひとまとまりにして新しい型して定義しましょう。 構造体(struct)の使い方を見ていきます。

  75. 構造体を定義する struct キーワードを使って を定義することができます。 pattern や path はフィールドです。ちなみに snake_case です。

    「フィールド名: 型名」の順に定義します。 構造体
  76. 構造体に対する実装を定義する 1 impl 実装を定義できます キーワードを使って、構造体に対する 。 今回はスタティックメソッドの new を定義し、それを後々 main

    で呼び出しします。 インスタンスメソッドについては、これから解説します。
  77. 構造体に対する実装を定義する 2 レシーバの を関数の第一引数につけると、いわゆるインスタンスメソッドになります。 Python などと同じ形式だと思います。 レシーバを経由して、構造体自身のフィールドにアクセスできます。 self

  78. 作ったスタティックメソッドを呼び出してみる 「構造体名::メソッド名」で呼び出しができます。 インスタンスメソッドの場合は「変数名.メソッド名」です。

  79. run 関数も修正する GrepArgs 型を引数で受け取りできるように、run 関数の仮引数も修正しましょう。 また、構造体から必要な値を取り出しするように関数内も修正しましょう。

  80. ここまでの実装 GrepArgs という構造体を作 り、それを run 関数に渡して処 理を行うようにしてみました。

  81. CLI ツールっぽくする structopt というクレート(ライブラリ)を使います。 CLI ツールのように help コマンド等を生やすことができます。 便利な機能をいくつか利用して、CLI ツール化しましょう。

  82. クレートを追加する Cargo.toml の [dependencies] に structopt に対する依存を追加します。 今回はバージョン 0.3.21 で使用します。

    追加したら、一度 cargo build などを回しましょう。
  83. structopt の設定を行う #[derive(StructOpt)] は StructOpt トレイトを という意味です。 #[structopt(...)] は、今回は説明しませんが と呼ばれるマクロです。

    継承する 「手続きマクロ」 ※ はこのハンズオンでは紹介できませんが、他の言語で言うインタフェースのようなものです。 トレイト
  84. structopt で実行時引数をパースする from_args という関数を使うと、実行時引数をパースしてくれます。 これを main 関数内で呼び出すようにします。 std::env::args を使用していた箇所は、軒並み from_args

    で置き換え可能です。
  85. ここまでの実装 GrepArgs にいた new 関数 は、from_args 関数を使用 することにより不要になっ たので、削除しました。

  86. help コマンドが生えてくる この時点で help コマンドも同時に生成されています。 cargo run -- --help と入力し、help

    が生成されていることを確認しましょう。
  87. grep 関数の引数を GrepArgs を使うように修正する grep の引数を GrepArgs を使用するように修正します。 さらに、line.contains 関数内で使用していた

    pattern も state から呼び出しします。
  88. run 関数の引数を GrepArgs を使うように修正する run 関数でも同様に GrepArgs を使用するように引数を修正します。 grep 関数に

    state を渡して grep 関数内でも使えるようにします。
  89. おや…?コンパイルエラーが コンパイルエラーが発生するようになりました。これはなんでしょうか?

  90. 所有権と借用 このコンパイルエラーは Rust の にまつわるものです。 という機能を使うとこのエラーは出なくなります。 Rust の所有権を簡単に解説します。 所有権 借用

  91. 所有権とは Rust には で行われます。 関数やブロックの終了タイミングでリソースの解放が走ります。 Rust には値の所有者が必ず1つだけ存在します。 所有者が移動することを と呼びます。 GC

    がなく、リソースの解放は自動 ムーブ
  92. 所有権の移動を見てみる

  93. 所有権の移動を見てみる 11行目で束縛された user が、12行目に呼び出されている関数にムーブされました。 13行目の println! マクロ内で user の値を使おうとしても、すでにムーブ済みです。

  94. 所有権の移動を見てみる Heap Stack

  95. 所有権の移動を見てみる Heap Stack Main Frame

  96. 所有権の移動を見てみる name User:new Heap Stack Main Frame user “user_a”

  97. 所有権の移動を見てみる Heap Stack name Main Frame user “user_a”

  98. 所有権の移動を見てみる some_action_to_user Heap Stack name Main Frame user “user_a”

  99. 所有権の移動を見てみる tmp some_action_to_user Heap Stack name Main Frame user “user_a”

  100. 所有権の移動を見てみる some_action_to_user Heap Stack Main Frame

  101. 所有権の移動を見てみる Heap Stack Main Frame

  102. 所有権の移動を見てみる Heap Stack

  103. 所有権を貸し出しする 所有権は貸し出しすることができます。これを と呼びます。 変数に & をつけることで、その変数の値を貸し出し することができます。 下記はコンパイルが通るようになります。 借用

  104. 借用の考え方を使って、先ほどのコードを直してみましょう grep 関数の GrepArgs の受け取りを参照にします。

  105. 借用の考え方を使って、先ほどのコードを直してみましょう run 関数の state.path 部分と、grep に渡す state 引数は借用するようにしましょう。

  106. ここまでの実装 run と grep の引数を調整しま した。借用の考え方を使って、 関数を越えてもオブジェクトが 解放されないように実装しまし た。

  107. 複数ファイル扱えるようにする grep は複数のファイルにかけることができますよね。 複数ファイルにかけられるようにコードを修正します。

  108. 複数ファイル扱えるようにする path を (サイズが可変の配列; ベクタ) に変更します。 複数ファイルを検索できる機能が裏で付与されます。 Vec

  109. 各ファイルをイテレートして読み込みを行う iter() を使うと、ベクタ内をイテレートすることができるようになります。 file に各ファイルのパスが取り出され、それを読み込んでいきます。

  110. grep 関数を修正して、ファイル名も出力できるようにする file_name を grep に渡せるように修正しましょう。 中で一致する文字が見つかった場合に、ファイル名も標準出力できるようにしましょう。

  111. ここまでの実装 path を複数受け取れるよ う、Vec を使って修正しまし た。 受け取った path は for

    文でイ テレートされ、中身が取り出さ れるようになりました。 また、検索結果にファイル名を 含められるよう、引数の渡し方 を調整しました。
  112. 関数型的なアプローチで書き直してみる run 関数は for_each 関数を使って書き換えることができます。

  113. 並列処理をできるようにする grep の各ファイルの検索は できるはずです。 rayon というクレートを使って、並列にイテレータを回します。 並列化

  114. rayon を追加する Cargo.toml に rayon への依存を追加します。 今回はバージョン 1.5.1 を利用します。

  115. run 関数を書き換える rayon の prelude をインポートします。そして、run 関数内で先ほど書いた iter() 関数を、par_iter() 関数に置き換えるだけで並列化できます。

  116. このハンズオンで紹介しなかったもの ジェネリクス トレイト ライフタイム スマートポインタ(Box, Rc など) マクロの実装

  117. 興味が出た方は… The Rust Programming Language をぜひご覧ください。 Rust には、他にも魅力的な機能がたくさんあります。

  118. 参考資料 『実践Rustプログラミング入門』初田直也他 『Programming Rust』Blandy & Orendorff 『The Rust Programming Language』

    https://doc.rust-jp.rs/book-ja/ chikoski/rust-handson https://github.com/chikoski/rust-handson