Open14

『並行プログラミング入門』をやっていく会

ピン留めされたアイテム

2021-11-25(木) 20:00-22:00

実績

  • p.43 2.3.5 借用から 2.3.6 メソッド定義まで

次はどこから?

  • p.48 2.3.7 トレイト から

MEMO

Discordの開始地点

https://discord.com/channels/508093437187457045/882639711447961710/913383622617219072

雑談

  • オーバーホール 洗濯機編でのトラブル! 次回に期待!
  • 一升炊きの炊飯器最高!
  • ブラックフライデー何狙う?
  • 生活の知恵が集まる勉強会
  • ホットサンドと良い包丁
  • 鉄パイプ整体と謎のイメージ
  • 「ゆめぴりか」が気になる話
  • Bluetoothに対応させたい話
  • コミットメッセージがHotTopic!
    • いつか語らおう!

2週間に1回ペースが良いという話

  • ちょうど人と話したいくらいの開催が良いという説
  • 読破までの時間はかかるけど、速攻で読む本でもないからいい感じな話
  • みんなで喋れるのがいいよね

借用の簡易モデルをベースにして、コードで実験する方法を理解した!

invalidな状態遷移を含むコードを書くと、ルートによって違ったエラーメッセージがでるっぽいので、意識的な学習がしやすいと思う!

コンパイラ任せもいけど、意識的な学習もあり!

状態遷移モデル(のび/たけし/スネ夫)

所有権とライフタイムとスコープの話がわかった感じ!?

所有権の実験 → https://discord.com/channels/508093437187457045/882639711447961710/913408437432516688

struct Vec2 {
  x: f64,
  y: f64,
}
impl Vec2 {
  fn new(x: f64, y: f64) -> Self {
    Vec2{x, y}
  }
  fn norm(&self) -> f64 {
    (self.x * self.x + self.y * self.y).sqrt()
  }
  fn set(&mut self, x: f64, y: f64) {
    self.x = x;
    self.y = y;
  }

  fn double(self) -> Self {
    Vec2::new(self.x * 2.0, self.y * 2.0)
  }
}

fn main() {
  let mut v = Vec2::new(10.0, 5.0);
  println!("v.norm = {}", v.norm());
  v.set(3.8, 9.1);
  println!("v.norm = {}", v.norm());
  // v.double();
  let v = v.double();
  println!("v.norm = {}", v.norm());
}

fn transform(self) -> AnotherSelf

アイデンティティを保存しながら複数は存在させない感じです
https://discord.com/channels/508093437187457045/882639711447961710/913425228116422716

struct Magikarp {
    hp: u32,
}
impl Magikarp {
    fn haneru(&self) {
        println!("No effect!")
    }
    fn evolve(self) -> Gyarados {
        println!("What? Magikarp is evolving!");
        Gyarados { hp: self.hp + 100 }
    }
}

struct Gyarados {
    hp: u32,
}
impl Gyarados {
    fn hydro_pump(&self) {
        println!("Critical hit!")
    }
}

fn main() {
    let zako = Magikarp { hp: 5 };
    zako.haneru();
    zako.evolve().hydro_pump()
}

未解決: selfを引数にもつメソッドの使い所は?

変換したあとに、変換前のオブジェクトをいじっちゃって事件になることない的な?

struct Vec2 {
    x: f64,
    y: f64,
}

impl Vec2 {
    fn new(x: f64, y: f64) -> Self {
        Vec2 { x, y }
    }
    fn norm(&self) -> f64 {
        (self.x * self.x + self.y * self.y).sqrt()
    }
    fn set(&mut self, x: f64, y: f64) {
        self.x = x;
        self.y = y;
    }

    fn double(self) -> Self {
        Vec2::new(self.x * 2.0, self.y * 2.0)
    }
}

fn main() {
    let mut v = Vec2::new(10.0, 5.0);

    println!("v.norm = {}", v.norm());

    v.set(3.8, 9.1);
    println!("v.norm = {}", v.norm());

    v.double();
    // let v = v.double();
    println!("v.norm = {}", v.norm());
}

README

公式情報

https://www.oreilly.co.jp/books/9784873119595/

https://github.com/oreilly-japan/conc_ytakano/

正誤表(PRでサッとコントリビューション可能!)↓↓

https://github.com/oreilly-japan/conc_ytakano/blob/main/errata.md

読書会の進め方

https://docs.google.com/presentation/d/1r54zmoTSlAsCl8SUgHjiq1ZBefYo19-NBBisy6DiO74/edit#slide=id.p

この本の推しポイント

  • 説明を正確にしていこうスタンスがビビットに感じられる
    • 必ずしも"理解しやすいかどうか"はわからないけど、理解できれば大丈夫だ! という安心感
  • 並行処理の話題を包括的に扱っている(きっと誰が読んでも嬉しい)

関連する参考文献

全般

アクターモデル

進捗

  • ✅ はじめに
  • ✅ 1.1 プロセス
  • ✅ 1.2 並行性
  • ✅ 1.3 並列性
  • ✅ 1.3.1 タスク並列性
  • ✅ 1.3.2 データ並列性
  • ✅ 1.3.3 インストラクションレベル並列性
  • ✅ 1.4 並行と並列処理の必要性
    • ✅ 1.4.1 並列処理と性能向上
    • ✅ 1.4.2 並行処理の必要性と計算パス数爆発
  • 2.1 アセンブリ言語
    • 2.1.1 アセンブリ言語の基本

2021-09-08(水) 20:00-22:00

実績

  • はじめに 〜 1.3.2.1 応答速度とスループット まで
  • 10人くらいで交代音読した(2周ちょっと)
  • 参加メンバーの知識や興味はいい感じにバラバラ

次はどこから?

p.7 1.3.2.2 アムダールの法則(Amdahl's law)から

MEMO

話題がめちゃくちゃ広い!

  • 「本書の内容と読み進め方」を読もう

プロセス、そして並行性と並列性

  • 「プロセス」の定義
  • 「OSのプロセス」ではない
  • 並行性と並列性の厳密な定義
  • シングルコアの前提で読むといい感じ
  • 計算途中状態の定義が素晴らしいと思う

意外とあいまいワード集(いつかわかると嬉しい)

  • ファイルディスクリプタ
  • ユーザランド
  • カーネルランド
  • ネイティブスレッド
  • ユーザスレッド
  • コルーチン(Coroutine)
  • ゴルーチン(Goroutine)
  • スレッドのメモリ消費量
  • アクターモデル
  • Eralng
  • mmap / メモリマップトファイル
  • IO多重化
  • ミューテックス(Mutex; Mutual Exclusion) / 排他制御
  • CPUのインストラクション
  • クロック周波数
  • 命令サイクル

グっと来たところ

このような知識よりも、日々の積み重ねと、実際に 自分でコードを書いて動かしてみることが何よりも重要である。(はじめに v)

未解決

mmap

「ファイルを実メモリ空間として使うアイデア」という理解。

メモリ上のアドレス領域をファイル中の領域に対応付けることで、メモリ (中のアドレス) にアクセスするとファイルに読み書きする仕組み。
メモリにおさまりきらない巨大な空間をファイルとして実現したり、ファイルストリームを操作するのでなくメモリ上をランダムアクセスするように扱ったりできる。便利っちゃ便利。

2021-09-16(木) 20:00-22:00

実績

  • p.7 1.3.2.2 アムダールの法則(Amdahl's law)から
  • 8人くらいで交代音読した(3周) + チャット参加3名くらい

次はどこから?

p.17 2.1.1アセンブリ言語の基本 から

MEMO

  • アムダールの法則
    • オーバーヘッドも考慮したアムダールの法則はいい感じ
    • 並列度
    • どれだけ並列処理が可能なのか
  • パイプライン処理
  • インストラクション並列性
  • データ並列性
  • シリコンウェハーとダイ
  • 製造コスト、発熱、消費電力、動作周波数、リーク電流
  • 「並行処理の複雑性を計算パス数爆発から考える」というアプローチが新鮮
  • 並列化のモチベーションは性能向上
  • CASL(キャスル)は教育用のアセンブリ言語的なやつ
  • いろんなハザード
    • 構造ハザード
    • データハザード
    • 制御ハザード

Cのvolatile(発音 【vάləṭl】(米国英語) 【vˈɔlətὰɪl】(英国英語))

C の volatile は、値が安定ではない変数。別スレッドで変更されているかもしれない共有変数とか、ハードウェア的に値が変化してしまったりするとか、そういうの。値を使う場面ごとにその値が変化している可能性を考えなければいけない (読み直さないといけない) ので、レジスタに乗せた値を使い回すみたいな最適化ができないとか、そういう並列性を阻害する要因になったりする。

2021-09-30(木) 20:00-22:00

実績

  • p.17 2.1.1アセンブリ言語の基本 から 2.2.2 volatile修飾子 まで

次はどこから?

p.25 2.2.3 スタックメモリとヒープメモリ

MEMO

  • アセンブリ、アセンブラ、アセンブル
  • ニーモニックとオペコード
  • ニーモニックとオペコードは1対1とは限らない
  • デタッチスレッド
  • 最適化によるメモリアクセス回数の抑制
  • アセンブリコードで比較すると違いがわかる(楽しい)
  • .LBB0_2とかは「ラベル」←コードJUMPするときの目印
  • cbzは Condtion Branch Zero らしい。
    • この命令があるから、サンプルでは「非ゼロであーだこーだ」になっているのかな?
    • たとえば、メモリバリアの話も、cbzっぽい話があった → https://ja.wikipedia.org/wiki/メモリバリア

残念な最適化

// 実現したいコード
void wait_while_0(int *p) {
  while (*p == 0) { }
}
; 確かにメモリアクセス(つまり、 [x0]) は減っているけど
; LBB0_2から無限ループしちゃう(LBB0_2で、w8レジスタの値をチェックしてないので、抜け出せない==無限ループ)
wait_while_0:
  
  ldr w8, [x0] ; w8 にメモリから読み込み ❶ 
  cbz w8, .LBB0_2 ; if w8 == 0 then goto .LBB0_2 ❷
  ret 

.LBB0_2:
  b .LBB0_2 ; goto .LBB0_2
// Cだとこんな感じのコードになっちゃっている感じっぽい
// `*p`の値をチェックしてない!
int x = (*p == 0);  // ldr w8, [x0] ; w8 にメモリから読み込み ❶ 

while(x) {};
#include <stdio.h>
#include <pthread.h> // ①
#include <unistd.h>

#define NUM_THREADS 3

// スレッド生成用関数
void *thread_func(void *arg) { // ②
    int id = (int) arg; // ③

    for (int i = 0; i < 5; i++) { // ④
        printf("id = %d, i = %d\n", id, i);
        sleep(1);
    }

    return "finished!";
}

int main() {
    pthread_t v[NUM_THREADS]; // ⑤

    // スレッド生成 ⑥
    for (int i = 0; i < NUM_THREADS; i++) {
        if (pthread_create(&v[i], NULL, thread_func, (void *) i != 0)) {
            perror("pthread_create");
            return -1;
        }
    }

    // スレッドの終了を待機 ⑦
    for (int i = 0; i < NUM_THREADS; i++) {
        char *ptr;
        if (pthread_join(v[i], (void **) &ptr) == 0) {
            printf("msg = %s\n", ptr);
        } else {
            perror("pthread_join");
            return -1;
        }
    }
    printf("main done");

    return 0;


}

デタッチスレッドのモチベーション: 勝手に仕事してね! 終わったら帰っていいよ!

  • 営業のメタファー
#include <stdio.h>
#include <pthread.h> // ①
#include <unistd.h>

// スレッド生成用関数
void *thread_func(void *arg) {
    for (int i = 0; i < 5; i++) {
        printf("i = %d\n", i);
        sleep(1);
    }

    return NULL;
}

int main() {
    // アトリビュート初期化 ①
    pthread_attr_t attr;

    if (pthread_attr_init(&attr) != 0) {
        perror("pthread_attr_init");
        return -1;
    }

    // デタッチスレッドに設定 ②
    if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0) {
        perror("pthread_attr_setdetachstate");
        return -1;
    }

    // アトリビュートを指定してスレッド生成
    pthread_t th;
    if (pthread_create(&th, &attr, thread_func, NULL) != 0) {
        perror("pthread_create");
        return -1;
    }

    // アトリビュート破棄
    if (pthread_attr_destroy(&attr) != 0) {
        perror("pthread_attr_destroy");
        return -1;
    }

    // MEMO: デタッチスレッドを使う時はmain関数が無限ループみたいなことが多いです
    // デタッチスレッドを使うモチベーション? → 同期しない
    // →メモリリーク対策
    // しかし、joinできないとなると、同期できなくね? → そもそも無限ループ的なところで使う?
    // https://discord.com/channels/508093437187457045/882639711447961710/893108272037494784
    sleep(7); // Q. joinじゃなくてデタッチスレッド使うから、sleepで無理やり待ち合わせ?

    // デタッチスレッド = 同期しなくて良いスレッド 👀 ←
    // 動いたら勝手に動いて勝手に止まってくれるスレッドを放置するときにデタッチ。
    // 勝手にぶん回ってくれて良くて、終了時に後始末さえしてくれてれば、それがいつ終わるかは関心の外、みたいな
    return 0;
}

ARMとIntelの比較

  • レジスタとメモリアクセスの記法をおさえれば行ける気がする
int main() {
    int a = 10;
    int b = 0;
    int c = 0;

    b = a;
    c = a + b;

    return 0;
}
.arch armv8-a
    .file    "assemble.c"
    .text
    .align    2
    .global    main
    .type    main, %function
main:
.LFB0:
    .cfi_startproc
    sub    sp, sp, #16
    .cfi_def_cfa_offset 16
    mov    w0, 10
    str    w0, [sp, 12]
    str    wzr, [sp, 8]
    str    wzr, [sp, 4]
    ldr    w0, [sp, 12]
    str    w0, [sp, 8]
    ldr    w1, [sp, 12]
    ldr    w0, [sp, 8]
    add    w0, w1, w0
    str    w0, [sp, 4]
    mov    w0, 0
    add    sp, sp, 16
    .cfi_def_cfa_offset 0
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (Debian 8.3.0-2) 8.3.0"
    .section    .note.GNU-stack,"",@progbits
.file    "assemble.c"
    .text
    .globl    main
    .type    main, @function
main:
.LFB0:
    .cfi_startproc
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $10, -4(%rbp)
    movl    $0, -8(%rbp)
    movl    $0, -12(%rbp)
    movl    -4(%rbp), %eax
    movl    %eax, -8(%rbp)
    movl    -4(%rbp), %edx
    movl    -8(%rbp), %eax
    addl    %edx, %eax
    movl    %eax, -12(%rbp)
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (Debian 8.3.0-6) 8.3.0"
    .section    .note.GNU-stack,"",@progbits

2021-10-14(木) 20:00-22:00

実績

  • p.25 2.2.3 スタックメモリとヒープメモリから、p.32 2.3.2.1 let文 まで読んだ

次はどこから?

p.33 2.3.2.2 関数定義と呼び出し から

MEMO

雑談

  • エアコン自力分解と清掃
  • 魚をたべよう。Fishヘビロテ事件
  • 自炊の悩み(肉は良いのか悪いのか実験)

Enumは直和、Structは直積(Atsukiさんの説明がGreat)

  • Enumはそれぞれの値(Variant)が独立しているので直和的な感じ
  • で、Structは例えば、2つの属性をもつStructがとれる値の組み合わせは、総当たりだから、直積って感じ

集合Aが{0,1,2}、集合Bが{a,b,c}のとき、直和は{0,1,2,a,b,c}だからenum。直積は{{0,a},{0,b},...,{2,c}}だからstructということ?

Player {
  name string
  age  int
}  

たとえば、こういう構造体があったときに、そのインスタンスは、nameの全通り x ageの全通り がありえる的な感じ。

実用上のコンパイラなら、レジスタに置くのがもう普通(スタックには置かない)

なお、コンパイラによる最適化が行われると、 a も b もスタックではなくレジスタに保存 されるだろうが、今回はコンパイラによる最適化は行われないものとする。
p.25

雑メモ(間違ってたら教えて下さい)
80年台) だったら、レジスタにおいて、メモリアクセスへらそうぞ
90年代) レジスタに置く流れ。
00年代) レジスタに置くようなプロセッサが主流

「文字集合」と「文字エンコード方式」を区別するのじゃ!

ただ、文字コードについては、『文字集合』の話をしてるのか『エンコード方式』の話をしてるのかを意識する癖を付ければ(そのうち無意識になるはず)、そこまで混乱することはないかもです。
バックスラッシュと円マーク問題などややこしい話もありますが、そのへんは枝葉の話なので。
https://discord.com/channels/508093437187457045/882639711447961710/898196642866536458

Rust の文字列は UTF-32 なのか UTF-8 なのか問題

結論としては、 char (文字)は UTF-32、 str , String (文字列)は UTF-8 でした。
https://discord.com/channels/508093437187457045/882639711447961710/898421666001072188

https://doc.rust-lang.org/std/primitive.char.html

char is a ‘Unicode scalar value’,

https://doc.rust-lang.org/std/string/struct.String.html

A UTF-8–encoded, growable string.

宿題: UTF-○ とか Unicodeとか、UTF-32を勉強しましょう!

参考文献

宿題: ○○積シリーズ

直積、デカルト積、アダマール積、外積、内積

ラムダ計算ってなんだろう?

いつか知りたいね。

rust の char は utf-32らしい

rust の char は UTF32らしいです。UTF32 は Unicode Scaler Point そのままの表現とのこと。
https://discord.com/channels/508093437187457045/882639711447961710/898180598529687593

型安全って何よ?

前述のように型安全性が具体的にどのようなものであるかはプログラミング言語や文脈に依存する。
https://ja.wikipedia.org/wiki/型システム

「型安全 is 何」ではなく、「型安全ではない is 何」で考えると、「私の中の型安全の定義」がしやすそう。

発音シリーズ

『プログラミング言語Rust(オライリー)』によれは、"mut"は「ミュート」と発音するそうです。
"let"は「れっと」でよかったはずです。
"fn"は、「ふぁん」とのことです。

その他

  • ダングリングポインタ
  • 英単語は画像検索しような
  • Enumのvariant
  • メモリリークのLeak感のなさ
  • トルコ語の hata は英語でいうと misstakefault的な意味

はじめてのRust

fn let_example() -> u32 {
    let x = 100;
    let mut y = 20; // ①
    let z: u32 = 5;
    let w;

    y *= x + z;
    w = 8;

    y + w;
}

fn main() {
    println!("{}", let_example());
    println!("Hello, world!");
}

コンパイラのエラーメッセージがめっちゃ親切!

2021-10-28(木) 20:00-22:00

実績

  • p.33 2.3.2.2 関数定義と呼び出し から p.36 2.3.2.8 関数ポインタ まで

次はどこから?

p.37 2.3.2.9 クロージャから

MEMO

Discordの開始地点

https://discord.com/channels/508093437187457045/882639711447961710/903246666646323230

雑談

  • 最近読んでいる技術書やマンガ
  • 浴室の乾燥機の自力分解で音が逆に大きくなった話
  • 魚で開眼! 鱧!
  • ドイツ語たのしい
  • タンパク質補給計画

Rustでは、ifは文じゃなくて式; 式である嬉しさ

文は命令なので結果を返さず、式は演算なので結果を返す、的な違いだったかなと。
if文ではなくif式だと嬉しいのは、例えば分岐によって変数に入れる値が変わるときに、文だとと、一発で変数に代入できたりします。
https://discord.com/channels/508093437187457045/882639711447961710/903248905838399528

//typescript
let v: number

if (a) {
  v = 1
else if (b) {
  v = 2
else {
  v = 3
}

と、 mutable な変数の宣言が必要だけど、if式だと

# ruby
v = if a
  1
elsif b
  2
else
  3
end

上の例は typescript で、 typescript のifは文なので、分岐ごとに変数代入が必要です.
下の例は ruby で、rubyのifは式なので、if自体が最終的に値を返します。そのため、ruby では if の結果を直接変数へ代入でき、一時変数が不要になってます

if式は三項演算子の上位互換という考え方は新鮮

実際 Scala とか、 3項演算子がなかったりしますね。if式で十分実現できちゃうので。
if式は三項演算子の上位互換ですね。

これは、なるほど。いままでは「三項演算子はシンタックスシュガー」という感じの捉え方だった!

match式と、Option型とSomeNone

  • Option<u32>型のvariantとして、NoneSomeがある感じ
  • Some型とかNone型ではない
fn pred(v: u32) -> Option<u32> {
    if v == 0 {
        None
    } else {
        Some(v - 1)
    }
}


fn print_pred(v: u32) {
    match pred(v) {
        Some(w) => {
            println!("pred({}) = {}", v, w);
        }
        None => {
            println!("pred({}) is undefined", v);
        }
    }
}

fn main() {
    print_pred(1);  // pred(1) = 0
    print_pred(34);  // pred(34) = 33
    print_pred(0);  // pred(0) is undefined
    print_pred(-1);  // error[E0600]: cannot apply unary operator `-` to type `u32`
}

Stackoverflowになるコード実験 overflowでpanic

こちらを参照 → https://zenn.dev/link/comments/8ccd9dd7119e8b

fn pred(v: u32) -> Option<u32> {
    Some(v - 1)
}

// thread 'main' panicked at 'attempt to subtract with overflow', src/main.rs:2:10

fn main() {
    match pred(0) {
        Some(x) => {
            println!("some = {}", x);
        }
        None => {
            println!("none");
        }
    }
}

参照(reference)と参照外し(dereference)

fn mul(x: &mut u64, y: &u64) {
    // *x *= *x * y; // (*x) = (*x) * ((*x) * (*y)) という意味
    // *x *= x * *xy; // error[E0369]: cannot multiply `&mut u64` by `u64`
    *x *= x * y;  // error[E0369]: cannot multiply `&mut u64` by `&u64`


fn main() {
    let mut n = 10;
    let m = 20;

    println!("n = {}, m = {}", n, m); // n = 10, m = 20

    mul(&mut n, &m);

    println!("n = {}, m = {}", n, m); // n = 2000, m = 20
}

参照とポインタを区別する!

Rustでは、参照とポインタは区別されており、参照は、後に述べる所有権やライフタイムによって安全性が保証されているが、ポインタはその限りではない。

本質はポインタ。そして、ポインタは何でもできる!

何でもできるってことは危険なこともできたり、バグったりするわけで。

という背景からなのか、ポインタに制限をかけて安全に使えるようにしたのが「参照」という理解。

というか、区別したことなかった...

他のメンバーも区別したことが少なかった印象。逆に言えば、厳密な(?)区別をしなくても"使う分には困らん"的な話かも。

江添亮のC++入門 p.340 によると...

  • 参照はポインタの機能制限版
  • 参照は代入が出来ないが、ポインタは代入が出来る
  • 参照は必ず初期化しなければならない

Rustの公式Docによると...(スマートポインタだけど)

「スマートポインタ」 https://doc.rust-jp.rs/book-ja/ch15-00-smart-pointers.html

Rustでは、標準ライブラリに定義された色々なスマートポインタが、 参照以上の機能を提供します。

所有権と借用の概念を使うRustにおいて、参照とスマートポインタにはもう1つ違いがあります。参照はデータを借用するだけのポインタなのです。対照的に多くの場合、スマートポインタは指しているデータを所有します。

Q. 関数を「関数に渡す」ときにどういう挙動になっているんだろう?

Rust 以外でも、一般に「関数を渡す」というのは、関数の定義を指す参照なりポインタなりを渡すという挙動になっているんじゃないかしらと思います。それを言語がカバーしてくれているから、あたかも関数が数値や文字列と同じ値っぽく使えているだけで。
https://discord.com/channels/508093437187457045/882639711447961710/903261576189063178

Q. っていうか、関数を渡したときの挙動を調べる方法がわからないのだが?

調べ方がわからん!

デリファレンスメモ

Goだけど。やっぱ、アドレスとか参照とかポインタとかデリファレンスが曖昧なんだな〜

package main

import "fmt"

func main() {
	var a int = 123
	var x *int = &a

	fmt.Println(x) // 0xc000128008
	fmt.Println(&x) // 0xc00000e028
	fmt.Println(*x) // 123
}
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{3, 4}

	fmt.Println(v) // {3 4}

	// Xのアドレス 0xc00012a010
	fmt.Println(&(v.X))
	fmt.Println(&v.X) 

	// Xの値(つまり、3)を取得する書き方
	fmt.Println(v.X) // .で楽にアクセスできる
	fmt.Println(*(&v.X)) // 明示的なデリファレンス(*)
	fmt.Println((&v).X) // 3
}

Q. println!がマクロなのはなぜ? マクロじゃなくても良くない?

  • 可変長引数に対応するため?
  • Rustで可変長引数はできないから?

Rustでマクロがどうなっているかを展開するライブラリ

https://github.com/dtolnay/cargo-expand
$ cat src/main.rs
#[derive(Debug)]
struct S;

fn main() {
    println!("{:?}", S);
}
$ cargo expand
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;
struct S;
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::fmt::Debug for S {
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match *self {
            S => {
                let mut debug_trait_builder = f.debug_tuple("S");
                debug_trait_builder.finish()
            }
        }
    }
}
fn main() {
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(
            &["", "\n"],
            &match (&S,) {
                (arg0,) => [::core::fmt::ArgumentV1::new(arg0, ::core::fmt::Debug::fmt)],
            },
        ));
    };
}

Q. なんで、連続したアドレス領域を確保するようになっているんでしょうか? しないといけない?

https://discord.com/channels/508093437187457045/882639711447961710/903276478710546692
  • スタックの性能の良さ

スタックはデータ構造、スタックメモリはメモリのための実装

  • 「スタック」というデータ構造
  • 「スタックメモリ」という、メモリのモデルの話

Stackoverflowになるコード実験

として掲載されているコードですが、Stackoverflow を起こす意図のものではなかったです(実際、起こるのは算術オーバーフローであってスタックオーバーフローではないです)

書籍のサンプルコードについて、当時口頭で出ていた「なんでわざわざ分岐してOptionにラップしてるんだろう」という疑問に対して

u32 は符号無し整数なので、マイナスの範囲を取れないからというのもあるかと。 > Some でラップしてる理由

と回答していましたが、それ = 結果がマイナスになる計算をすると実際マズイ、ということを確認・実証するためのコードでした。

https://discord.com/channels/508093437187457045/882639711447961710/903252859137720350
↓から
https://discord.com/channels/508093437187457045/882639711447961710/903253988013662269
の流れ。

実行してみると、実際に overflow (とメッセージが出るが、正確にはアンダーフロー?)によってpanicが発生し、プログラムが停止してしまうことが確認できるかと。

サンプルコードは特に再帰を行っていないので、 stack overflow にはならないですね。

2021-11-11(木) 20:00-22:00

実績

  • p.37 2.3.2.9 クロージャから p.42 2.3.4 ライフタイム まで

次はどこから?

  • p.43 2.3.5 借用から

MEMO

Discordの開始地点

https://discord.com/channels/508093437187457045/882639711447961710/908310043856891904

雑談

  • 最近読んでいる技術書やマンガ
    • 『ちはやふる』
    • 『トリリオンゲーム』
  • エアコンは会社ごとにちょっと違う話
  • ハモを鍋にぶちこむ
  • 肉はやっぱり必要だ
  • ガトーショコラの砂糖の量そして、砂糖と脂肪はうまい
  • こたつデプロイOK
  • 食あたりには気をつけよう

Box

Box というのはコンテナの一種で、ヒープ上にデータを配置したい場合に用いられる。
https://doc.rust-jp.rs/rust-by-example-ja/std/box.html

ポリモーフィズムを実現するための仕組みとしての、仮想関数テーブル(C++)

Animal型の具象クラスa があって、a.bark() としたときに、a は実際には Cat なりDogなりになる。

で、bark()がCatのbark()なのか、Dogのbark()なのかを区別できないのであれば、このポリモーフィズムが実現できない。

それを実現するための仕組みの1つの例が、「C++の仮想関数テーブル」

Q. トレイトって何?

TODO

Q. (メモ) クロージャでBoxを使っている意味 多分こうかも?(仮)

https://discord.com/channels/508093437187457045/882639711447961710/908323030537756722

Rustのキーワード

  • dyn: たぶん、Dynamicの略
  • impl
// 実行時に決まる
// fn mul_x(x: u64) -> dyn Box::<Fn(u64) -> u64> {
//     Box::new(move |y| x * y) // ②
// }

// Static dispatch: コンパイル時点で確定する
fn mul_x(x: u64) -> Fn(u64) -> u64 {
    move |y| x * y
}

fn main() {
    let f = mul_x(3);

    println!("f(5) = {}", f(5));
}

Q. doesn't have a size known at compile-time のsizeって何?

https://discord.com/channels/508093437187457045/882639711447961710/908324536808767519

線形論理

  • シークエント
  • -o: loli
  • `├: tee

線形論理ちょっとおもろいかも? コンパイルの仕組みがみえてきそうかも

コンパイラ内部では、所有権の移動を線形論理式に変換して、その式が成り立たなければエラーにするということをしてそうですね。
https://discord.com/channels/508093437187457045/882639711447961710/908330858920960051

MoveセマンティクスとCopyセマンティクス

https://discord.com/channels/508093437187457045/882639711447961710/908333135014543401
// copyセマンティクスにすると、moveしなくてもいける
// というか、値をコピーして渡していそう
// コピーしちゃうので、でかい構造体を使うときとかはもったないかもしれない
#[derive(Clone, Copy)]
struct Apple {}

struct Gold {}

struct FullStomach {}

fn get_gold(a: Apple) -> Gold {
    Gold {}
}

fn get_full_stomach(a: Apple) -> FullStomach {
    FullStomach{}
}

fn main() {
    let a = Apple{};

    // copy
    let g = get_gold(a);

    let s = get_full_stomach(a);
}
#[derive(Clone)]
struct Apple {}

struct Gold {}

struct FullStomach {}

fn get_gold(a: Apple) -> Gold {
    Gold {}
}

fn get_full_stomach(a: Apple) -> FullStomach {
    FullStomach{}
}

fn main() {
    let a = Apple{};

    // [Clone]のみにすると、明示的なcloneすればOK(しないとコンパイルエラーになる)
    let g = get_gold(a.clone());

    let s = get_full_stomach(a);
}

Q. ライフタイム感あふれるサンプルコードがほしいね?

  • Q. スコープとライフタイムの違いがびみょい
  • Q. ライフタイムを体験したいのだが、いいサンプルコードはないか!
ログインするとコメントできます