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

    View Slide

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

    View Slide

  3. 自己紹介
    Introduction

    View Slide

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

    View Slide

  5. 会社紹介
    Intoroduction

    View Slide

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

    View Slide

  7. Rustの概要
    Introduction

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  14. 文法の基本
    Chapter 1

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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関数

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  35. 所有権と借用
    Chapter 2

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  39. 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);
    どうやる?

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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へ渡す(ムーブ)
    }

    View Slide

  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のスコープが所有権を持っているためエラー
    }

    View Slide

  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

    View Slide

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

    View Slide

  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
    }

    View Slide

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

    View Slide

  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
    }

    View Slide

  52. 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は後でも表示などに用いたいので借用

    View Slide

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

    View Slide

  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, 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

    View Slide

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

    View Slide

  56. 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(())
    }

    View Slide

  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)?;

    View Slide

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

    View Slide

  59. 59 所有権と借用
    Chapter 2

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  63. 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(())
    }

    View Slide

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

    View Slide

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

    View Slide

  66. モジュール
    Chapter 3

    View Slide

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

    View Slide

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

    View Slide

  69. 代数的データ型
    Chapter 4

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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 {
    vec![
    Item::new("ひのきのぼう", 100.0, "Weapon"),
    ]
    }
    }
    代数的データ型
    Chapter 4

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    代数的データ型
    Chapter 4

    View Slide

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

    View Slide

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

    View Slide

  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
    とにかく、コンパイラが担保してくれる範囲を広げることが大事

    View Slide

  82. 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

    View Slide

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

    View Slide

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

    View Slide

  85. View Slide