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

Rustで学ぶ安全なプログラミング / GEEK-SAI-2022-SPRING-Study-Session

Rustで学ぶ安全なプログラミング / GEEK-SAI-2022-SPRING-Study-Session

技育祭 2022 春での勉強会資料です。@rail44
https://talent.supporterz.jp/geeksai/2022spring/

CARTA Engineering

March 18, 2022
Tweet

More Decks by CARTA Engineering

Other Decks in Technology

Transcript

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

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

  3. 自己紹介 Introduction

  4. 4 自己紹介 Introduction @rail44 Satoshi Amemiya • Web広告配信 • 普段はTypeScript

    • Rust ◦ Script Lang ◦ Frontend Framework ◦ Text Editor ◦ 役に立たないツールとか • 好きなもの ◦ Linux ◦ Rust ◦ ヨーヨー ◦ 自作キーボード ◦ Pokemon ◦ Splatoon
  5. 会社紹介 Intoroduction

  6. 6 CARTA HOLDINGS は 2019年1月 VOYAGE GROUP と CCI が経営統合して誕生しました

    多くの事業・サービスを運営しています(下記は一部を抜粋)
  7. Rustの概要 Introduction

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

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

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

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

    • 小さなランタイム
  12. 12 Rustの概要 Introduction 信頼性 • 静的型安全 • メモリ安全性 • スレッド安全性

  13. 13 Rustの概要 Introduction 生産性 • 馴染みのある文法 • 公式によるエコシステム ◦ Packager

    ◦ Linter ◦ Formatter ◦ Tester ◦ Language Server
  14. 文法の基本 Chapter 1

  15. 15 おことわり 説明の簡単のために、以下の点で日常的に書くコードとは相違点があり ます • なるべく基本的な文法を使っている • オンラインコンパイラでの互換性があるEditionを使っている ◦ 2018

    edition • ライブラリはインターフェース周りのみ導入した 文法の基本 Chapter 1
  16. 16 文法の基本 Chapter 1 • 簡単なCLIアプリケーションを作っていく • 比較的馴染みやすい文法から

  17. 17 文法の基本 Chapter 1 お買物アプリ • RPGの道具屋のイメージ • 所持金以内のアイテムを購入できる •

    購入時に状態遷移 ◦ 所持アイテムに追加 ◦ 所持金を減らす ◦ 在庫を減らす
  18. 18 文法の基本 Chapter 1 お買物アプリ $ cargo run --bin chapter_1

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

    • 購入処理
  20. 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関数
  21. 21 文法の基本 Chapter 1 let name = inquire::Text::new("あなたのお名前は?").prompt().unwrap(); let user

    = User::new(name); let stocks = Item::default_stocks(); letによる変数の定義と代入 「::」はモジュールや構造体などへのアクセス 「.」はメソッドへのアクセス (詳しくは後で説明)
  22. 22 文法の基本 Chapter 1 struct User { name: String, //

    文字列 wallet: f64, // 64bitの浮動小数点数 owned_items: Vec<Item>, // ItemのVector(配列) } 構造体
  23. 23 文法の基本 Chapter 1 // 引数リストにselfを含まない場合、クラスメソッドのような働きになる fn new(name: String, wallet:

    f64) -> User { User { // Userの初期化記法 name, // `name: name` の省略記法 wallet, owned_items: Vec::new(), } } // 行末からセミコロンを除くことで、returnの省略記法になる メソッド
  24. 24 文法の基本 Chapter 1 // &selfを第一引数にすることでインスタンスメソッドのような働きになる fn _has_enough_money(&self, money: f64)

    -> bool { self.wallet >= money } メソッド
  25. 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
  26. 26 文法の基本 Chapter 1 簡単ですね!!

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

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

  29. 29 文法の基本 Chapter 1 よく言われる安全さ • 信頼性 • 可用性 •

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

    保守性 • 完全性 • 機密性 まあ要するに、「怖さがない」というニュアンス!!!
  31. 31 文法の基本 Chapter 1 • default immutable ◦ 予期せぬデータの変更を防ぐ •

    静的データ型 ◦ 関数やメソッドの責任範囲が明確に ◦ フィールド名の取り違えのfool proofにもなる • テスト ◦ ロジックの検証 ◦ TDD文脈では保守や機能追加にも有用 この章で見てきた安全さの例
  32. 32 文法の基本 Chapter 1 • default immutable ◦ const, readonlyとかがあれば使う

    ◦ 変数を使いまわさない ◦ 命名でがんばる • 静的データ型 ◦ エンティティ型の導入 ◦ アクセス修飾子を適切に使う(public, private ◦ エディタ支援に頼りまくる • テスト ◦ もちろん書きますよね? 他の言語にも考え方を持ちこむことができる
  33. 33 文法の基本 Chapter 1 読み書きしやすいコードも安全さを高める • コミュニティの標準に沿ったコード ◦ linter ◦

    formatter • mut のような修飾は、コードを読む助けにも 見落とされがちですが
  34. 34 文法の基本 Chapter 1 演習 • passするテストケースを1つ追加しましょう • failするテストケースを追加してテストが失敗するこ とを確かめましょう

  35. 所有権と借用 Chapter 2

  36. 36 所有権と借用 Chapter 2 • 所持アイテムに追加 • 所持金を減らす • 在庫を減らす

    実装したい購入処理
  37. 37 所有権と借用 Chapter 2 $ cargo run --bin chapter_2

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

  39. 39 所有権と借用 Chapter 2 fn buy(_user: User, _cart: Vec<Item>, _stocks:

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

  41. 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
  42. 42 所有権と借用 Chapter 2 ????????????

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

    される
  44. 44 所有権と借用 Chapter 2 fn hoge() { let user =

    User::new(name); // 1. User構造体のメモリを確保してuserに束縛 } // 2. スコープを脱出するタイミングでuserのメモリを解放する
  45. 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へ渡す(ムーブ) }
  46. 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のスコープが所有権を持っているためエラー }
  47. 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
  48. 48 所有権と借用 Chapter 2 解決策 • 変数を複製する • 所有権を貸し出す

  49. 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 }
  50. 50 所有権と借用 Chapter 2 変数を複製する • データのコピーはコストが高い • 関数を呼ぶ毎にデータコピー ◦

    つまり全部値渡し • 呼び出した側のデータが変更できない
  51. 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 }
  52. 52 所有権と借用 Chapter 2 fn buy(_user: &User, _cart: Vec<Item>, _stocks:

    &Vec<Item>) { … } … 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は後でも表示などに用いたいので借用
  53. 53 所有権と借用 Chapter 2 $ cargo check

  54. 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<Item>, stocks: &Vec<Item>) -> 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
  55. 55 所有権と借用 Chapter 2 所有権を貸し出す • 借用元のデータはデフォルトでは変更できない • 明示的に &mut

    と宣言する必要がある ◦ 同時に複数の &mut を貸し出すことはできない ◦ mutで宣言された変数からでないと&mutを貸し出 すことはできない
  56. 56 所有権と借用 Chapter 2 fn buy(user: &mut User, cart: Vec<Item>,

    stocks: &mut Vec<Item>) -> 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(()) }
  57. 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)?;
  58. 58 ちょっと難しいですね!! 所有権と借用 Chapter 2

  59. 59 所有権と借用 Chapter 2

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

  61. 61 所有権と借用 Chapter 2 ガベージコレクタのトレードオフ • それなりにコストの大きい処理 ◦ 動作するデバイスや環境を選ぶ •

    いつ、どのように動作するかが一般開発者からは不明 ◦ Stop the world ◦ リアルタイムアプリケーション • そもそも複雑性の要因 ◦ コンカレントGCや世代別GCなど ◦ これだけでトピックになる
  62. 62 所有権と借用 Chapter 2 Rustのコンパイラは賢い • 適当に思うまま全部Hogeのような実体型で書いちゃおう ◦ cargo checkを叩くとコンパイラが激怒する

    ◦ 言うことを聞いていればそのうち安全なコードになっている
  63. 63 所有権と借用 Chapter 2 fn buy(user: &mut User, cart: Vec<Item>,

    stocks: &mut Vec<Item>) -> 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(()) }
  64. 64 文法の基本 Chapter 2 • 副作用を作ること自体が難しいデザイン ◦ 自然とコードがシンプルになる ◦ e.g.

    has_enough_money() • 実装内容の役割がより表現されている ◦ 「userとstocksに副作用を起こす関数」なのが一目でわ かる • オブジェクトのたらい回しによる地雷を踏みづらい コード上で表現される副作用
  65. 65 文法の基本 Chapter 2 • 副作用を作ること自体が難しいデザイン ◦ ユーティリティやプライベートメソッド、モジュール内関数 • 実装内容の役割がより表現されている

    ◦ 命名 • オブジェクトのたらい回しによる地雷を踏みづらい ◦ オブジェクトのたらい回しをしない! 他の言語でも
  66. モジュール Chapter 3

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

  68. 68 モジュール Chapter 3 変更点 • 連続して買い物ができるように • モジュール分割 ◦

    深く掘るとキリが無いのでファイルを見なが ら表面的な説明をします
  69. 代数的データ型 Chapter 4

  70. 70 • たとえば ◦ 書籍は10% OFFにしつつ ◦ 武器は20% OFFにしたい 突然の機能追加要望

    代数的データ型 Chapter 4
  71. 71 • たとえば ◦ 書籍は10% OFFにしつつ ◦ 武器は20% OFFにしたい =>

    itemの種別を判別できるようにしたい 突然の機能追加要望 代数的データ型 Chapter 4
  72. 72 #[derive(Clone)] pub struct Item { name: String, price: f64,

    category: String, } 代数的データ型 Chapter 4
  73. 73 impl Item { fn new(name: &str, price: f64, category:

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

    => self.price * 0.9, "Weapon" => self.price * 0.8, _ => self.price, // マッチしない条件 } } 代数的データ型 Chapter 4
  75. 75 代数的データ型 Chapter 4 これで困ることないんだっけ?

  76. 76 pub fn default_stocks() -> Vec<Item> { vec![ Item::new("ひのきのぼう", 100.0,

    "Weapon"), Item::new("チャージライフル", 10000.0, "Arm"), ] } 代数的データ型 Chapter 4
  77. 77 代数的データ型 Chapter 4 (一般に) 文字列は名前空間が めちゃくちゃ広い

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

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

  80. 80 #[derive(Clone)] pub enum Category { Weapon, Laptop, Keyboard, Book,

    } #[derive(Clone)] pub struct Item { name: String, price: f64, category: Category, } 代数的データ型 Chapter 4
  81. 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 とにかく、コンパイラが担保してくれる範囲を広げることが大事
  82. 82 #[derive(Debug)] enum Error { InsufficientMoney } fn buy(user: &mut

    User, cart: Vec<Item>, stocks: &mut Vec<Item>) -> 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
  83. 83 演習(尺調整) • buy()がErrを返却したとき、panicではなくてエ ラーメッセージを表示するようにしましょう 代数的データ型 Chapter 4

  84. 84 まとめ • 色々な目新しい機能は全部理由がある • 有用なテクニックは言語を跨いで有用 • ツールに静的に任せる姿勢が大事 ◦ スクリプト言語でもLanguage

    Serverを使おう • Rustはいいぞ ◦ まだまだ紹介しきれない
  85. None