Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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_

Slide 4

Slide 4 text

Next Experts について Next Experts 3つの役割 特定領域への貢献意欲や一定の実績を有し、将来的なDeveloper Expertsを目指すエンジ ニアのための活動支援制度。サイバーエージェントにはDeveloper Expertsと呼ばれる、 特定の専門領域における社内・社外活動の支援制度が存在します。 各専門領域における高い専門性を身につける 対外活動を通して、各専門領域の発展に貢献 管轄や事業部の垣根を越え、担当領域のサポート

Slide 5

Slide 5 text

『実践Rustプログラミング入門』 Rustは、C/C++の代わりとなる最新の爆速言語として注目 されています。「とにかく実行速度が速い」「モダンな言 語機能が一通り入っている」「OSからWebアプリケー ションまで幅広く実装できる」「ツール群がとても充実し ている」「安全性が強力に担保されている」など、数多く の魅力があります。本書は、JavaやPythonなど他の言語 に習熟しているエンジニアを対象に、Rustの独特な仕様と 開発ノウハウをわかりやすく解説した入門書です。

Slide 6

Slide 6 text

Rust.Tokyo, RustFest Global オーガナイザ 日本初の Rust カンファレンス「Rust.Tokyo」や、グローバルな Rust カンファレン ス「RustFest Global」のオーガナイザも務めています。2021年9月18日に Rust.Tokyo は開催予定です。CfP も受付中ですので、ご興味がある方はぜひ、ご応募ください。 RustFest Global ミーティングの様子 Rust.Tokyo 2021 のロゴ

Slide 7

Slide 7 text

目次 Rust とは 基本的な文法 cat コマンドの実装 grep コマンドの実装

Slide 8

Slide 8 text

Rust とは

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

本日の内容をより詳しく知るために Rust をはじめるための資料集 https://blog-dry.com/entry/2021/01/23/141936

Slide 16

Slide 16 text

基本的な文法

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

まずは Hello, World してみましょう fn main() は関数を宣言しています。 Rust では関数の定義は fn キーワードで行うことができます。

Slide 23

Slide 23 text

まずは Hello, World してみましょう println! で標準出力します。 は であることを意味します。 マクロとは、Rust のプログラムをプログラミングする仕組みのことです。 他の言語ではメタプログラミングとも呼ぶ機能のことです。 `!` マクロ

Slide 24

Slide 24 text

実行してみましょう

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

よく見るプリミティブ型 (※これ以外にもあります。詳しくはドキュメントをご覧ください) i8, i16, i32, i64 u8, u16, u32, u64 f32, f64 isize / usize bool &str, String 符号付き整数型。 浮動小数点数型。 CPU アーキテクチャごとにビット分のサイズを取る整数型。isize は符号付き。 ブール値。true または false をとる。 文字列を扱う際によく利用される型。後述。 符号なし整数型。

Slide 32

Slide 32 text

文字列型 今日は String と &str を紹介します。 よく質問を受けるので、先に軽く紹介してしまいます。 文字列型はこれ以外にもあります。

Slide 33

Slide 33 text

String と &str String &str ヒープメモリにアロケートされる。 リサイズ可能な UTF-8 のテキストを保持するバッファ。 いわゆる 。 なので、String と違ってリサイズ等はできない。 「スライス」 read-only

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

&str のメモリモデル stack preallocated read-only memory t u t o r i a l 8 length buffer length ※ preallocated read-only memory: 機械語生成時にすでに確保済みのメモリ領域を指す。

Slide 36

Slide 36 text

String, &str を使うには ダブルクォーテーションで囲うと文字列を扱うことができますが、 その際の型は &str 型になります。 たとえば、to_string() 関数を呼び出すと、String 型になります。

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

if 式を使って FizzBuzz を作ってみる 他の言語と同様に if キーワードが存在しま す。 if は なので、結果を変数に束縛できます。 セミコロンを文末につけると式になり、な い場合は文として解釈が行われます。 if 式がある関係で、三項演算子は Rust には ありません。 式

Slide 40

Slide 40 text

for 式を使って0〜99の数字をイテレートする

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

関数に切り出す

Slide 43

Slide 43 text

関数に切り出す fn 関数 セミコロンを書 かないと return 扱い と宣言すると を宣言できます。 関数名は snake_case です。 引数は「仮引数名: 型名」で定義できます。 返りの型は -> のあとに書きます。 return は基本は必要なく、 になります。

Slide 44

Slide 44 text

関数型的なアプローチ これまでは for を用いた手続き型的なアプローチを見てきました。 Rust は関数型プログラミングの手法をいくつかとりいれています。 関数型的なアプローチを使った FizzBuzz を紹介します。

Slide 45

Slide 45 text

関数型的なアプローチ

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

cat コマンドを実装する

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

最終目標

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

main.rs 自身を表示するプログラムを書く read_to_string 関数は、パスの内容を読み込んで String 型にして返します。 は他の言語だと import のような記法で、 を使用することを意味します。 は という文法です。 Ok や Err は Result 型という です。 use モジュール match 「パターンマッチ」 enum のバリアント

Slide 55

Slide 55 text

パターンマッチ 他の言語でいう です。 Rust では多くの型がパターンマッチに対応しています。 いくつか例を示します。 switch 文をもう少し強力にしたもの

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

Rust のエラーハンドリング: Result 型 Rust ではエラーハンドリングを Result 型で行います。 成功なら Ok 、失敗なら Err が返ります。 パターンマッチなどを使って中身を取り出します。

Slide 59

Slide 59 text

Result 型のパターンマッチングの仕方 Ok(content) で、content という変数に内容をもちます。 Err(reason) の場合は、reason という変数にエラー内容が入ります。 それらを後続の処理で使用することができます。

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

grep コマンドを実装する

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

最終目標

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

指定した文字列がある行を検索し出力する 先ほどの run_cat 関数を run に変更してい ます。 grep 関数を作り、中で指定した文字パター ンに一致するかチェックしています。 .lines() 関数により、1行1行イテレータを 回すことができるようになっています。

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

構造体を定義する struct キーワードを使って を定義することができます。 pattern や path はフィールドです。ちなみに snake_case です。 「フィールド名: 型名」の順に定義します。 構造体

Slide 76

Slide 76 text

構造体に対する実装を定義する 1 impl 実装を定義できます キーワードを使って、構造体に対する 。 今回はスタティックメソッドの new を定義し、それを後々 main で呼び出しします。 インスタンスメソッドについては、これから解説します。

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

クレートを追加する Cargo.toml の [dependencies] に structopt に対する依存を追加します。 今回はバージョン 0.3.21 で使用します。 追加したら、一度 cargo build などを回しましょう。

Slide 83

Slide 83 text

structopt の設定を行う #[derive(StructOpt)] は StructOpt トレイトを という意味です。 #[structopt(...)] は、今回は説明しませんが と呼ばれるマクロです。 継承する 「手続きマクロ」 ※ はこのハンズオンでは紹介できませんが、他の言語で言うインタフェースのようなものです。 トレイト

Slide 84

Slide 84 text

structopt で実行時引数をパースする from_args という関数を使うと、実行時引数をパースしてくれます。 これを main 関数内で呼び出すようにします。 std::env::args を使用していた箇所は、軒並み from_args で置き換え可能です。

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

grep 関数の引数を GrepArgs を使うように修正する grep の引数を GrepArgs を使用するように修正します。 さらに、line.contains 関数内で使用していた pattern も state から呼び出しします。

Slide 88

Slide 88 text

run 関数の引数を GrepArgs を使うように修正する run 関数でも同様に GrepArgs を使用するように引数を修正します。 grep 関数に state を渡して grep 関数内でも使えるようにします。

Slide 89

Slide 89 text

おや…?コンパイルエラーが コンパイルエラーが発生するようになりました。これはなんでしょうか?

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

所有権の移動を見てみる

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

ここまでの実装 path を複数受け取れるよ う、Vec を使って修正しまし た。 受け取った path は for 文でイ テレートされ、中身が取り出さ れるようになりました。 また、検索結果にファイル名を 含められるよう、引数の渡し方 を調整しました。

Slide 112

Slide 112 text

関数型的なアプローチで書き直してみる run 関数は for_each 関数を使って書き換えることができます。

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

参考資料 『実践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