Open21

プログラミングRUST 第2版

Daiki OkayamaDaiki Okayama

3章 基本的な型

Daiki OkayamaDaiki Okayama

3.5.3 raw ポインタ

  • *const T へのキャストにより、参照型を不変な生ポインタへ変換できる
  • *mut T へのキャストにより、可変参照を可変な生ポインタへ変換できる
main.rs
fn main() {
    let x = 42;
    let raw_pointer = &x as *const i32;
    println!("x: {}", x);
    println!("x at: {:p}", &x);
    println!("x at: {:?}", raw_pointer);

    let mut y = 42.;
    let raw_mut_pointer = &mut y as *mut f64;
    println!("y: {}", y);
    println!("y at: {:p}", &y);
    println!("y at: {:?}", raw_mut_pointer);
}
$ cargo run
...
x: 42
x at: 0x16bd26784
x at: 0x16bd26784
y: 42
y at: 0x16bd26858
y at: 0x16bd26858
Daiki OkayamaDaiki Okayama

3.6 配列、ベクタ、スライス

各型のメモリサイズを確認する。

use std::mem::size_of;

fn main() {
    // 固定長配列
    assert_eq!(size_of::<[i32; 5]>(), 20); // 4bytes(32bit) * 5
    assert_eq!(size_of::<[f64; 10]>(), 80); // 8bytes(64bit) * 10

    // ベクタ
    assert_eq!(size_of::<Vec<i32>>(), 24); // 8bytes(pointer size) + 8bytes(capacity size) + 8bytes(length size)

    // スライスの参照(ファットポインタ)
    assert_eq!(size_of::<&[i32]>(), 16); // 8bytes(pointer size) + 8bytes(length size)
    assert_eq!(size_of::<&[f64]>(), 16); // 8bytes(pointer size) + 8bytes(length size)

    // 参照
    assert_eq!(size_of::<&[i32; 5]>(), 8); // 8bytes(pointer size)
    assert_eq!(size_of::<&[f64; 10]>(), 8); // 8bytes(pointer size)
    assert_eq!(size_of::<&Vec<i32>>(), 8); // 8bytes(pointer size)
}
Daiki OkayamaDaiki Okayama

5章 参照

Daiki OkayamaDaiki Okayama

5.2.6 任意の式への参照の借用

節の主張とは逸れるが、数値演算子 + が自動的に参照を解決している点に興味を惹かれた。

// rustc 1.75.0
fn main() {
    let r: &i32 = &10;
    let x1 = r + 5; // &i32 + i32
    let x2 = *r + 5; // i32 + i32
    let x3 = r + &5; // &i32 + &i32
    let x4 = *r + &5; // i32 + &i32
    assert_eq!(x1, 15);
    assert_eq!(x2, 15);
    assert_eq!(x3, 15);
    assert_eq!(x4, 15);
}

静的解析ツールは、変数 x1, x2, x3, x4 がいずれも i32 型であると推測した。
""

明示的に参照解決を行った i32 に同じ型の i32 を加える *r + 5 の結果、 x2 の型が i32 であることは直感的である。

疑問に感じるのは次の 2 点である。

  1. &i32 に対して演算を行った結果である x1x3 の型は &i32 ではないのだろうか
  2. i32&i32 を加えた結果である x4 の型はなぜ i32 なのだろうか

実際に演算子のオーバーロードを試してみることで、これらの疑問を解消することができた。

use std::ops;

#[derive(Clone, Copy, Debug)]
struct Myi32(i32);

impl ops::Add<Myi32> for Myi32 {
    type Output = Myi32;

    fn add(self, _rhs: Myi32) -> Myi32 {
        Myi32(self.0 + _rhs.0)
    }
}

fn main() {
    let r = &Myi32(10);
    let x2 = *r + Myi32(5);
    assert_eq!(x2.0, 15);
}

この状態では、r + Myi32(5)r + &Myi32(5)*r + &Myi32(5) はコンパイルエラーとなる。

  let r = &Myi32(10);
  let x1 = r + Myi32(5); // Error: cannot add `Myi32` to `&Myi32`
  let x2 = *r + Myi32(5); // OK
  let x3 = r + &Myi32(5); // Error: cannot add `&Myi32` to `&Myi32`
  let x4 = *r + &Myi32(5); // Error: mismatched types; expected `Myi32`, found `&Myi32`

つまり、上述の疑問に自ら回答すると、答えは単純に i32 に対してそのようにシグネチャが定義されていたからである。実際のところ、直前の Myi32 のコンパイルエラーを解消したければ、Myi32 に対して、演算を行いたい型に対する Add トレイトを実装すればよい。

use std::ops;

#[derive(Clone, Copy, Debug)]
struct Myi32(i32);

impl ops::Add<Myi32> for Myi32 {
    type Output = Myi32;

    fn add(self, _rhs: Myi32) -> Myi32 {
        Myi32(self.0 + _rhs.0)
    }
}

impl ops::Add<Myi32> for &Myi32 {
    type Output = Myi32;

    fn add(self, _rhs: Myi32) -> Myi32 {
        Myi32(self.0 + _rhs.0)
    }
}

impl ops::Add<&Myi32> for Myi32 {
    type Output = Myi32;

    fn add(self, _rhs: &Myi32) -> Myi32 {
        Myi32(self.0 + _rhs.0)
    }
}

impl ops::Add<&Myi32> for &Myi32 {
    type Output = Myi32;

    fn add(self, _rhs: &Myi32) -> Myi32 {
        Myi32(self.0 + _rhs.0)
    }
}

fn main() {
    let r = &Myi32(10);
    let x1 = r + Myi32(5); // &Myi32 + Myi32
    let x2 = *r + Myi32(5); // Myi32 + Myi32
    let x3 = r + &Myi32(5); // &Myi32 + &Myi32
    let x4 = *r + &Myi32(5); // Myi32 + &Myi32
    assert_eq!(x1.0, 15);
    assert_eq!(x2.0, 15);
    assert_eq!(x3.0, 15);
    assert_eq!(x4.0, 15);
}

(前節で学んだように、. 演算子が参照を自動的に解決することに注意する必要がある。トレイトの実装の中で、&Myi32.0(*Myi32).0 と等価である)

Daiki OkayamaDaiki Okayama

5.3.1 ローカル変数の借用

  1. 参照は、参照先より長生きしない
  2. 参照の生存期間は、代入された変数の生存期間を包含する
Daiki OkayamaDaiki Okayama

7章 エラー処理

Daiki OkayamaDaiki Okayama

7.2.4 エラーの伝播

? 演算子と unwrap() メソッドの違いについてまとめる。

? 演算子も unwrap() メソッドも Result<_> 型や Option<_> 型から値を取り出すという点では同じだが、取り出せなかった場合の挙動が異なる。? 演算子ではエラー処理を呼び出し元に移譲でき、unwrap() メソッドではその場でパニックが起こる。

より具体的には、下記のコードで、

use std::io::{Error, ErrorKind};

fn strange_add(x: i32, y: i32) -> Result<i32, Error> {
    if x + y != 42 {
        Ok(x + y)
    } else {
        Err(Error::new(
            ErrorKind::Other,
            "The answer to the ultimate question of life, the universe and everything",
        ))
    }
}

fn sub() -> Result<i32, Error> {
    let z = strange_add(40, 2)?; // or strange_add(40, 2).unwrap();
    Ok(z)
}

fn main() {
    match sub() {
        Ok(result) => println!("The result is: {}", result),
        Err(err) => eprintln!("Error: {}", err),
    }
}

? では std::io::Error が呼び出し元の main 関数に返り、そこで処理が行われる一方で、unwrap() では sub 関数の中でパニックが発生する。

Daiki OkayamaDaiki Okayama

9章 構造体

Daiki OkayamaDaiki Okayama

9.11 内部可変性

std::cell::RefCell により、mutable でない MyStruct の一部のフィールドに mut でアクセスできる。

use std::cell;

struct Logger;

impl Logger {
    fn ref_mut_method(&mut self) {}
}

struct MyStruct {
    logger: cell::RefCell<Logger>,
    other_immutable_field: i32,
}

fn main() {
    let my_struct = MyStruct {
        logger: cell::RefCell::new(Logger),
        other_immutable_field: 42,
    };

    my_struct.logger.borrow_mut().ref_mut_method();
}

std::cell::RefCell を利用しない場合、MyStructmut 宣言する必要がある。

struct Logger;

impl Logger {
    fn ref_mut_method(&mut self) {}
}

struct MyStruct {
    logger: Logger,
    other_immutable_field: i32,
}

fn main() {
    let mut my_struct = MyStruct {
        logger: Logger,
        other_immutable_field: 42,
    };

    my_struct.logger.ref_mut_method();
}
Daiki OkayamaDaiki Okayama

13章 ユーティリティトレイト

Daiki OkayamaDaiki Okayama

13.3 Clone, 13.4 Copy

  • Clone トレイトを実装した型は、.clone() あるいは .clone_from() メソッドによって値のコピーを作成することができる

    #[derive(Clone)]
    struct Myi32(i32);
    
    fn main() {
        let v1 = Myi32(42);
        let v2 = v1.clone();
        assert!(v1.0 == v2.0); // OK
    }
    
  • Copy トレイトを実装した型は、代入による変数の移動が起こらない

    struct Myi32(i32);
    
    fn main() {
        let v1 = Myi32(42);
        let v2 = v1;
        assert!(v1.0 == v2.0); // Error: use of moved value: `v1`; value used here after move
    }
    
    #[derive(Clone, Copy)]
    struct Myi32(i32);
    
    fn main() {
        let v1 = Myi32(42);
        let v2 = v1; // 暗黙的に v1.clone() が代入される
        assert!(v1.0 == v2.0); // OK
    }