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. 4 自己紹介 Introduction @rail44 Satoshi Amemiya • Web広告配信 • 普段はTypeScript

    • Rust ◦ Script Lang ◦ Frontend Framework ◦ Text Editor ◦ 役に立たないツールとか • 好きなもの ◦ Linux ◦ Rust ◦ ヨーヨー ◦ 自作キーボード ◦ Pokemon ◦ Splatoon
  2. 6 CARTA HOLDINGS は 2019年1月 VOYAGE GROUP と CCI が経営統合して誕生しました

    多くの事業・サービスを運営しています(下記は一部を抜粋)
  3. 17 文法の基本 Chapter 1 お買物アプリ • RPGの道具屋のイメージ • 所持金以内のアイテムを購入できる •

    購入時に状態遷移 ◦ 所持アイテムに追加 ◦ 所持金を減らす ◦ 在庫を減らす
  4. 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関数
  5. 21 文法の基本 Chapter 1 let name = inquire::Text::new("あなたのお名前は?").prompt().unwrap(); let user

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

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

    f64) -> User { User { // Userの初期化記法 name, // `name: name` の省略記法 wallet, owned_items: Vec::new(), } } // 行末からセミコロンを除くことで、returnの省略記法になる メソッド
  8. 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
  9. 30 文法の基本 Chapter 1 よく言われる安全さ • 信頼性 • 可用性 •

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

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

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

    formatter • mut のような修飾は、コードを読む助けにも 見落とされがちですが
  13. 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); どうやる?
  14. 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
  15. 44 所有権と借用 Chapter 2 fn hoge() { let user =

    User::new(name); // 1. User構造体のメモリを確保してuserに束縛 } // 2. スコープを脱出するタイミングでuserのメモリを解放する
  16. 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へ渡す(ムーブ) }
  17. 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のスコープが所有権を持っているためエラー }
  18. 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
  19. 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 }
  20. 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 }
  21. 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は後でも表示などに用いたいので借用
  22. 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
  23. 55 所有権と借用 Chapter 2 所有権を貸し出す • 借用元のデータはデフォルトでは変更できない • 明示的に &mut

    と宣言する必要がある ◦ 同時に複数の &mut を貸し出すことはできない ◦ mutで宣言された変数からでないと&mutを貸し出 すことはできない
  24. 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(()) }
  25. 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)?;
  26. 61 所有権と借用 Chapter 2 ガベージコレクタのトレードオフ • それなりにコストの大きい処理 ◦ 動作するデバイスや環境を選ぶ •

    いつ、どのように動作するかが一般開発者からは不明 ◦ Stop the world ◦ リアルタイムアプリケーション • そもそも複雑性の要因 ◦ コンカレントGCや世代別GCなど ◦ これだけでトピックになる
  27. 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(()) }
  28. 64 文法の基本 Chapter 2 • 副作用を作ること自体が難しいデザイン ◦ 自然とコードがシンプルになる ◦ e.g.

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

    ◦ 命名 • オブジェクトのたらい回しによる地雷を踏みづらい ◦ オブジェクトのたらい回しをしない! 他の言語でも
  30. 68 モジュール Chapter 3 変更点 • 連続して買い物ができるように • モジュール分割 ◦

    深く掘るとキリが無いのでファイルを見なが ら表面的な説明をします
  31. 71 • たとえば ◦ 書籍は10% OFFにしつつ ◦ 武器は20% OFFにしたい =>

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

    category: String, } 代数的データ型 Chapter 4
  33. 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
  34. 74 fn discounted_price(&self) -> f64 { match &self.category { "Book"

    => self.price * 0.9, "Weapon" => self.price * 0.8, _ => self.price, // マッチしない条件 } } 代数的データ型 Chapter 4
  35. 76 pub fn default_stocks() -> Vec<Item> { vec![ Item::new("ひのきのぼう", 100.0,

    "Weapon"), Item::new("チャージライフル", 10000.0, "Arm"), ] } 代数的データ型 Chapter 4
  36. 80 #[derive(Clone)] pub enum Category { Weapon, Laptop, Keyboard, Book,

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