Slide 1

Slide 1 text

Rustで学ぶ安全なプログラミング 2022-03-18 rail44

Slide 2

Slide 2 text

2 https://replit.com/github/rail44/geekp

Slide 3

Slide 3 text

自己紹介 Introduction

Slide 4

Slide 4 text

4 自己紹介 Introduction @rail44 Satoshi Amemiya ● Web広告配信 ● 普段はTypeScript ● Rust ○ Script Lang ○ Frontend Framework ○ Text Editor ○ 役に立たないツールとか ● 好きなもの ○ Linux ○ Rust ○ ヨーヨー ○ 自作キーボード ○ Pokemon ○ Splatoon

Slide 5

Slide 5 text

会社紹介 Intoroduction

Slide 6

Slide 6 text

6 CARTA HOLDINGS は 2019年1月 VOYAGE GROUP と CCI が経営統合して誕生しました 多くの事業・サービスを運営しています(下記は一部を抜粋)

Slide 7

Slide 7 text

Rustの概要 Introduction

Slide 8

Slide 8 text

8 https://japan.zdnet.com/article/35174931/ Rustの概要 Introduction

Slide 9

Slide 9 text

9 https://www.rust-lang.org/ Rustの概要 Introduction

Slide 10

Slide 10 text

10 Rustの概要 Introduction ● 高性能 ● 信頼性 ● 生産性 それぞれの特徴について軽く触れていく

Slide 11

Slide 11 text

11 Rustの概要 Introduction 高性能 ● ガベージコレクタの排除 ● 省メモリ ● 高度なコンパイル時最適化 ● 小さなランタイム

Slide 12

Slide 12 text

12 Rustの概要 Introduction 信頼性 ● 静的型安全 ● メモリ安全性 ● スレッド安全性

Slide 13

Slide 13 text

13 Rustの概要 Introduction 生産性 ● 馴染みのある文法 ● 公式によるエコシステム ○ Packager ○ Linter ○ Formatter ○ Tester ○ Language Server

Slide 14

Slide 14 text

文法の基本 Chapter 1

Slide 15

Slide 15 text

15 おことわり 説明の簡単のために、以下の点で日常的に書くコードとは相違点があり ます ● なるべく基本的な文法を使っている ● オンラインコンパイラでの互換性があるEditionを使っている ○ 2018 edition ● ライブラリはインターフェース周りのみ導入した 文法の基本 Chapter 1

Slide 16

Slide 16 text

16 文法の基本 Chapter 1 ● 簡単なCLIアプリケーションを作っていく ● 比較的馴染みやすい文法から

Slide 17

Slide 17 text

17 文法の基本 Chapter 1 お買物アプリ ● RPGの道具屋のイメージ ● 所持金以内のアイテムを購入できる ● 購入時に状態遷移 ○ 所持アイテムに追加 ○ 所持金を減らす ○ 在庫を減らす

Slide 18

Slide 18 text

18 文法の基本 Chapter 1 お買物アプリ $ cargo run --bin chapter_1

Slide 19

Slide 19 text

19 文法の基本 Chapter 1 できること ● 名前を入力できる ● 在庫と値段が表示され、選択できる できないこと ● 購入処理

Slide 20

Slide 20 text

20 文法の基本 Chapter 1 fn main() { let name = inquire::Text::new("あなたのお名前は?").prompt().unwrap(); let user = User::new(name); let stocks = Item::default_stocks(); println!("{}", user); let _cart = inquire::MultiSelect::new("買いたい商品を選んでください", stocks).prompt(); println!("{}", user); } main関数

Slide 21

Slide 21 text

21 文法の基本 Chapter 1 let name = inquire::Text::new("あなたのお名前は?").prompt().unwrap(); let user = User::new(name); let stocks = Item::default_stocks(); letによる変数の定義と代入 「::」はモジュールや構造体などへのアクセス 「.」はメソッドへのアクセス (詳しくは後で説明)

Slide 22

Slide 22 text

22 文法の基本 Chapter 1 struct User { name: String, // 文字列 wallet: f64, // 64bitの浮動小数点数 owned_items: Vec, // ItemのVector(配列) } 構造体

Slide 23

Slide 23 text

23 文法の基本 Chapter 1 // 引数リストにselfを含まない場合、クラスメソッドのような働きになる fn new(name: String, wallet: f64) -> User { User { // Userの初期化記法 name, // `name: name` の省略記法 wallet, owned_items: Vec::new(), } } // 行末からセミコロンを除くことで、returnの省略記法になる メソッド

Slide 24

Slide 24 text

24 文法の基本 Chapter 1 // &selfを第一引数にすることでインスタンスメソッドのような働きになる fn _has_enough_money(&self, money: f64) -> bool { self.wallet >= money } メソッド

Slide 25

Slide 25 text

25 文法の基本 Chapter 1 // これを書くことでテストランナーの実行対象になる // 文法的にはattributeと呼ばれるもの #[test] fn test_user_has_enough_money() { let user = User::new("hoge".to_string(), 100.0); assert_eq!(user._has_enough_money(100.0), true); assert_eq!(user._has_enough_money(80.0), true); assert_eq!(user._has_enough_money(105.0), false); } テスト $ cargo test --bin chapter_1

Slide 26

Slide 26 text

26 文法の基本 Chapter 1 簡単ですね!!

Slide 27

Slide 27 text

27 文法の基本 Chapter 1 ところで安全さとは?

Slide 28

Slide 28 text

28 文法の基本 Chapter 1 ところで安全さとは? => いろいろある

Slide 29

Slide 29 text

29 文法の基本 Chapter 1 よく言われる安全さ ● 信頼性 ● 可用性 ● 保守性 ● 完全性 ● 機密性

Slide 30

Slide 30 text

30 文法の基本 Chapter 1 よく言われる安全さ ● 信頼性 ● 可用性 ● 保守性 ● 完全性 ● 機密性 まあ要するに、「怖さがない」というニュアンス!!!

Slide 31

Slide 31 text

31 文法の基本 Chapter 1 ● default immutable ○ 予期せぬデータの変更を防ぐ ● 静的データ型 ○ 関数やメソッドの責任範囲が明確に ○ フィールド名の取り違えのfool proofにもなる ● テスト ○ ロジックの検証 ○ TDD文脈では保守や機能追加にも有用 この章で見てきた安全さの例

Slide 32

Slide 32 text

32 文法の基本 Chapter 1 ● default immutable ○ const, readonlyとかがあれば使う ○ 変数を使いまわさない ○ 命名でがんばる ● 静的データ型 ○ エンティティ型の導入 ○ アクセス修飾子を適切に使う(public, private ○ エディタ支援に頼りまくる ● テスト ○ もちろん書きますよね? 他の言語にも考え方を持ちこむことができる

Slide 33

Slide 33 text

33 文法の基本 Chapter 1 読み書きしやすいコードも安全さを高める ● コミュニティの標準に沿ったコード ○ linter ○ formatter ● mut のような修飾は、コードを読む助けにも 見落とされがちですが

Slide 34

Slide 34 text

34 文法の基本 Chapter 1 演習 ● passするテストケースを1つ追加しましょう ● failするテストケースを追加してテストが失敗するこ とを確かめましょう

Slide 35

Slide 35 text

所有権と借用 Chapter 2

Slide 36

Slide 36 text

36 所有権と借用 Chapter 2 ● 所持アイテムに追加 ● 所持金を減らす ● 在庫を減らす 実装したい購入処理

Slide 37

Slide 37 text

37 所有権と借用 Chapter 2 $ cargo run --bin chapter_2

Slide 38

Slide 38 text

38 所有権と借用 Chapter 2 どうやる?

Slide 39

Slide 39 text

39 所有権と借用 Chapter 2 fn buy(_user: User, _cart: Vec, _stocks: Vec) { … } … let user = User::new(name); let stocks = Item::default_stocks(); let cart = inquire::MultiSelect::new("買いたい商品を選んでください ", stocks.clone()) .prompt() .unwrap(); buy(user, cart, stocks); どうやる?

Slide 40

Slide 40 text

40 所有権と借用 Chapter 2 $ cargo check どうやる?

Slide 41

Slide 41 text

41 所有権と借用 Chapter 2 error[E0382]: borrow of moved value: `user` --> src/chapter_2.rs:108:20 | 99 | let user = User::new(name); | ---- move occurs because `user` has type `User`, which does not implement the `Copy` trait ... 106 | buy(user, cart, stocks); | ---- value moved here 107 | 108 | println!("{}", user); | ^^^^ value borrowed here after move

Slide 42

Slide 42 text

42 所有権と借用 Chapter 2 ????????????

Slide 43

Slide 43 text

43 所有権と借用 Chapter 2 Rustにはガベージコレクタが無い ● メモリの確保と解放のタイミングはコードから静 的に確定する必要がある ● 基本的にはスコープ単位で変数の寿命が決定 される

Slide 44

Slide 44 text

44 所有権と借用 Chapter 2 fn hoge() { let user = User::new(name); // 1. User構造体のメモリを確保してuserに束縛 } // 2. スコープを脱出するタイミングでuserのメモリを解放する

Slide 45

Slide 45 text

45 所有権と借用 Chapter 2 fn fuga(user: User) { // 3. ムーブされてきたuserの所有権はこの関数のスコープに紐付く ... } // 4. スコープを脱出するタイミングでuserのメモリを解放する fn hoge() { let user = User::new(name); // 1. User構造体のメモリを確保してuserに束縛 fuga(user); // 2. 変数をメモリの所有権とともにfugaへ渡す(ムーブ) }

Slide 46

Slide 46 text

46 所有権と借用 Chapter 2 fn fuga(user: User) { // 3. ムーブされてきたuserの所有権はこの関数のスコープに紐付く ... } // 4. スコープを脱出するタイミングでuserのメモリを解放する fn hoge() { let user = User::new(name); // 1. User構造体のメモリを確保してuserに束縛 fuga(user); // 2. 変数をメモリの所有権とともにfugaへ渡す(ムーブ) piyo(user); // 5. userは既にfugaのスコープが所有権を持っているためエラー }

Slide 47

Slide 47 text

47 所有権と借用 Chapter 2 error[E0382]: borrow of moved value: `user` --> src/chapter_2.rs:108:20 | 99 | let user = User::new(name); | ---- move occurs because `user` has type `User`, which does not implement the `Copy` trait ... 106 | buy(user, cart, stocks); | ---- value moved here 107 | 108 | println!("{}", user); | ^^^^ value borrowed here after move

Slide 48

Slide 48 text

48 所有権と借用 Chapter 2 解決策 ● 変数を複製する ● 所有権を貸し出す

Slide 49

Slide 49 text

49 所有権と借用 Chapter 2 変数を複製する fn fuga(user: User) { // 3. ムーブされてきたuserの所有権はこの関数のスコープに紐付く ... } // 4. スコープを脱出するタイミングでuserのメモリを解放する fn hoge() { let user = User::new(name); // 1. User構造体のメモリを確保してuserに束縛 fuga(user.clone()); // 2. userを複製してメモリを新たに確保してfugaへ渡す piyo(user); // 5. 元のuserの所有権はまだこのスコープ内にあるのでpass }

Slide 50

Slide 50 text

50 所有権と借用 Chapter 2 変数を複製する ● データのコピーはコストが高い ● 関数を呼ぶ毎にデータコピー ○ つまり全部値渡し ● 呼び出した側のデータが変更できない

Slide 51

Slide 51 text

51 所有権と借用 Chapter 2 所有権を貸し出す fn fuga(user: &User) { // 3. 借用されたuserはこの関数のスコープに紐付く ... } // 4. スコープを脱出するタイミングでuserの所有権を呼び出し元へ返却する fn hoge() { let user = User::new(name); // 1. User構造体のメモリを確保してuserに束縛 fuga(&user); // 2. userの借用を作ってfugaへ渡す piyo(user); // 5. userの所有権はfugaから返却されているのでpass }

Slide 52

Slide 52 text

52 所有権と借用 Chapter 2 fn buy(_user: &User, _cart: Vec, _stocks: &Vec) { … } … let user = User::new(name); let stocks = Item::default_stocks(); // コマンドラインにメッセージを出すためにライブラリが借用ではなく実体を要求するため clone() let cart = inquire::MultiSelect::new("買いたい商品を選んでください ", stocks.clone()) .prompt() .unwrap(); buy(&user, cart, &stocks); // user, stocksは後でも表示などに用いたいので借用

Slide 53

Slide 53 text

53 所有権と借用 Chapter 2 $ cargo check

Slide 54

Slide 54 text

54 所有権と借用 Chapter 2 error[E0594]: cannot assign to `user.wallet`, which is behind a `&` reference --> src/chapter_2.rs:105:5 | 95 | fn buy(user: &User, cart: Vec, stocks: &Vec) -> Result<(), Error> { | ----- help: consider changing this to be a mutable reference: `&mut User` ... 105 | user.wallet -= total_price; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `user` is a `&` reference, so the data it refers to cannot be written

Slide 55

Slide 55 text

55 所有権と借用 Chapter 2 所有権を貸し出す ● 借用元のデータはデフォルトでは変更できない ● 明示的に &mut と宣言する必要がある ○ 同時に複数の &mut を貸し出すことはできない ○ mutで宣言された変数からでないと&mutを貸し出 すことはできない

Slide 56

Slide 56 text

56 所有権と借用 Chapter 2 fn buy(user: &mut User, cart: Vec, stocks: &mut Vec) -> Result<(), Error> { let total_price = Item::total_price(&cart); if !user.has_enough_money(total_price) { return Err(GeekpError::InsufficientMoney.into()); } for item in cart { let pos = stocks.iter().position(|stock| stock == &item).unwrap(); // 買うitemのstocks配列上でのindex stocks.remove(pos); // 在庫から削除 user.owned_items.push(item); // 所持品へ追加 } user.wallet -= total_price; // 所持金を減らす Ok(()) }

Slide 57

Slide 57 text

57 所有権と借用 Chapter 2 // mutableなデータはlet mutで宣言 let mut user = User::new(name); let mut stocks = Item::default_stocks(); println!("{}", user); let cart = inquire::MultiSelect::new("買いたい商品を選んでください ", stocks.clone()).prompt()?; buy(&mut user, cart, &mut stocks)?;

Slide 58

Slide 58 text

58 ちょっと難しいですね!! 所有権と借用 Chapter 2

Slide 59

Slide 59 text

59 所有権と借用 Chapter 2

Slide 60

Slide 60 text

60 所有権と借用 Chapter 2 でも本当にこんなの必要なんだっけ GCがあればよくない?????

Slide 61

Slide 61 text

61 所有権と借用 Chapter 2 ガベージコレクタのトレードオフ ● それなりにコストの大きい処理 ○ 動作するデバイスや環境を選ぶ ● いつ、どのように動作するかが一般開発者からは不明 ○ Stop the world ○ リアルタイムアプリケーション ● そもそも複雑性の要因 ○ コンカレントGCや世代別GCなど ○ これだけでトピックになる

Slide 62

Slide 62 text

62 所有権と借用 Chapter 2 Rustのコンパイラは賢い ● 適当に思うまま全部Hogeのような実体型で書いちゃおう ○ cargo checkを叩くとコンパイラが激怒する ○ 言うことを聞いていればそのうち安全なコードになっている

Slide 63

Slide 63 text

63 所有権と借用 Chapter 2 fn buy(user: &mut User, cart: Vec, stocks: &mut Vec) -> Result<(), Error> { let total_price = Item::total_price(&cart); if !user.has_enough_money(total_price) { return Err(GeekpError::InsufficientMoney.into()); } for item in cart { let pos = stocks.iter().position(|stock| stock == &item).unwrap(); // 買うitemのstocks配列上でのindex stocks.remove(pos); // 在庫から削除 user.owned_items.push(item); // 所持品へ追加 } user.wallet -= total_price; // 所持金を減らす Ok(()) }

Slide 64

Slide 64 text

64 文法の基本 Chapter 2 ● 副作用を作ること自体が難しいデザイン ○ 自然とコードがシンプルになる ○ e.g. has_enough_money() ● 実装内容の役割がより表現されている ○ 「userとstocksに副作用を起こす関数」なのが一目でわ かる ● オブジェクトのたらい回しによる地雷を踏みづらい コード上で表現される副作用

Slide 65

Slide 65 text

65 文法の基本 Chapter 2 ● 副作用を作ること自体が難しいデザイン ○ ユーティリティやプライベートメソッド、モジュール内関数 ● 実装内容の役割がより表現されている ○ 命名 ● オブジェクトのたらい回しによる地雷を踏みづらい ○ オブジェクトのたらい回しをしない! 他の言語でも

Slide 66

Slide 66 text

モジュール Chapter 3

Slide 67

Slide 67 text

67 モジュール Chapter 3 $ cargo run --bin chapter_3

Slide 68

Slide 68 text

68 モジュール Chapter 3 変更点 ● 連続して買い物ができるように ● モジュール分割 ○ 深く掘るとキリが無いのでファイルを見なが ら表面的な説明をします

Slide 69

Slide 69 text

代数的データ型 Chapter 4

Slide 70

Slide 70 text

70 ● たとえば ○ 書籍は10% OFFにしつつ ○ 武器は20% OFFにしたい 突然の機能追加要望 代数的データ型 Chapter 4

Slide 71

Slide 71 text

71 ● たとえば ○ 書籍は10% OFFにしつつ ○ 武器は20% OFFにしたい => itemの種別を判別できるようにしたい 突然の機能追加要望 代数的データ型 Chapter 4

Slide 72

Slide 72 text

72 #[derive(Clone)] pub struct Item { name: String, price: f64, category: String, } 代数的データ型 Chapter 4

Slide 73

Slide 73 text

73 impl Item { fn new(name: &str, price: f64, category: String) -> Item { Item { name: name.to_string(), price, category, } } pub fn default_stocks() -> Vec { vec![ Item::new("ひのきのぼう", 100.0, "Weapon"), ] } } 代数的データ型 Chapter 4

Slide 74

Slide 74 text

74 fn discounted_price(&self) -> f64 { match &self.category { "Book" => self.price * 0.9, "Weapon" => self.price * 0.8, _ => self.price, // マッチしない条件 } } 代数的データ型 Chapter 4

Slide 75

Slide 75 text

75 代数的データ型 Chapter 4 これで困ることないんだっけ?

Slide 76

Slide 76 text

76 pub fn default_stocks() -> Vec { vec![ Item::new("ひのきのぼう", 100.0, "Weapon"), Item::new("チャージライフル", 10000.0, "Arm"), ] } 代数的データ型 Chapter 4

Slide 77

Slide 77 text

77 代数的データ型 Chapter 4 (一般に) 文字列は名前空間が めちゃくちゃ広い

Slide 78

Slide 78 text

78 (一般に) 文字列は名前空間が めちゃくちゃ広い => 文字列がValidかどうかを静的に解析することが難し い 代数的データ型 Chapter 4

Slide 79

Slide 79 text

79 代数的データ型を使おう 代数的データ型 Chapter 4

Slide 80

Slide 80 text

80 #[derive(Clone)] pub enum Category { Weapon, Laptop, Keyboard, Book, } #[derive(Clone)] pub struct Item { name: String, price: f64, category: Category, } 代数的データ型 Chapter 4

Slide 81

Slide 81 text

81 fn discounted_price(&self) -> f64 { match &self.category { Category::Book => self.price * 0.9, Category::Weapon => self.price * 0.8, // ここに到達するのはenumのうちBookとWeapon以外なことも定まる _ => self.price, } } 代数的データ型 Chapter 4 とにかく、コンパイラが担保してくれる範囲を広げることが大事

Slide 82

Slide 82 text

82 #[derive(Debug)] enum Error { InsufficientMoney } fn buy(user: &mut User, cart: Vec, stocks: &mut Vec) -> Result<(), Error> { let total_price = Item::total_price(&cart); if !user.has_enough_money(total_price) { return Err(Error::InsufficientMoney) // <- エラー時にpanicではなくてErrを返すように } for item in cart { let pos = stocks.iter().position(|stock| stock == &item).unwrap(); stocks.remove(pos); user.owned_items.push(item); } user.wallet -= total_price; Ok(()) } 代数的データ型 Chapter 4

Slide 83

Slide 83 text

83 演習(尺調整) ● buy()がErrを返却したとき、panicではなくてエ ラーメッセージを表示するようにしましょう 代数的データ型 Chapter 4

Slide 84

Slide 84 text

84 まとめ ● 色々な目新しい機能は全部理由がある ● 有用なテクニックは言語を跨いで有用 ● ツールに静的に任せる姿勢が大事 ○ スクリプト言語でもLanguage Serverを使おう ● Rustはいいぞ ○ まだまだ紹介しきれない

Slide 85

Slide 85 text

No content