実践 Rust 入門 第4章プリミティブ型

Rust における型

  • すべての値の型はコンパイル時に決定される
    • これは安全性、効率性という利点がある
      • 安全性: エラーの早期発見、所有権システムとの連携による型安全かつデータ競合のないプログラム開発の促進
      • 効率性: メモリの細かい制御、コンパイラによる最適化の恩恵

型の分類

  • 定義による分類

    • プリミティブ型
    • ユーザ定義型
  • 構成による分類

    • スカラ型
    • 複合型

スカラ型

  • ユニット
  • 真理値
  • 固定精度の整数
  • 固定精度の浮動小数
  • 文字
  • 参照
  • 生ポインタ
  • 関数ポイント
let n = 40;
let c = 'R';

ユニット

  • 値を返さない関数の戻り値はユニット型
  • ユニット型は意味のある情報を持たないため、サイズは0
    • キーバリューのペアを格納するデータ構造でキーのみデータを保持したい場合には、バリューにユニットを格納すると無駄にメモリを消費しない
assert_eq!(std::mem::size_of::<()>(), 0);

真偽値

  • true と false を持つ
    let b1 = true;
    let b2 = !b1;
    let n1 = 1;
    let n2 = 2;

    let b3 = n1 >= n2;
    let b4 = n1 < n2;
    let b5 = b3 && b4;
    let b6 = b3 || b4;

固定精度の整数

  • ビット幅指定の整数型
    • 符号なし (uxx)
    • 符号あり (ixx)
let n1: i32 = 32;
  • アドレス幅の整数型
    • usize
    • isize
let n2: usize = 32;
  • 整数リテラル
    • デフォルト10進数
    • プレフィクスで他の基数に基づく数を表される
      • 0x: 16進数
      • 0o: 8進数
      • 0b: 2進数
let h1 = 0xff;
let o1 = 0o744;
let b1 = 0b1010_0110_1110_1001;
let n1 = 10u8;
let n2 = b'A';
assert_eq!(n2, 65u8);
  • 代表的な整数演算

    • よくある算術演算、ビット演算、複合代入演算、比較演算は利用可能 
    • Cで使えるけど Rust で使用できないものは以下の2つ
  • 整数型のメソッドや定数

    • 整数型のメソッド
    • pow(), abs(), rotate_left(), from_str(), to_string(), checked_add()
    • 定数
    • 最大値 MAX 等
  • 整数演算の桁あふれ

  • リリースモードでは桁あふれは検出できない
    • 以下の方法で対応するメソッドを使用することを検討する
      • 検査付き演算
      • 飽和演算
      • ラッピング演算
      • 桁あふれ演算
fn overflow_calc() {
    let n1 = 200u8;
    let n2 = 3u8;

    assert_eq!(n1.checked_mul(n2), None);
    assert_eq!(n1.saturating_mul(n2), std::u8::MAX);
    assert_eq!(n1.wrapping_mul(n2), 88);
    assert_eq!(n1.overflowing_mul(n2), (88, true));
}

固定精度の浮動小数

  • ビット数に応じてf32, f64の2つの型が用意されている
  • サフィックスで型を指定可能
  • 指数部も指定可能
    let f1 = 10.0;
    let f2 = 1_234.897f64;
    let f3 = 576.6E77;

文字

  • 1つのUnicodeのスカラ値
  • シングルクォートで囲む
  • 1文字を表すのにたとえ英数字であっても4バイトを消費する
  • 複数のコードポイントが組み合わされた文字例えば'が'などは char リテラルとして使用することはできない(コンパイルエラー)
    let c1 = 'A';
    let c2 = 'B';

    assert!(c1 <= c2);
    assert!(c1.is_uppercase());

    let c3 = '0';
    assert!(c3.is_digit(10));

    let c4 = '\t';
    let c5 = '\'';
    let c6 = '\\';
    let c7 = '\x7F';

    let c8 = '漢';
    let c9 = '\u{5b57}';
    let c10 = '\u{1f600}';
    println!("{}", c10);

参照

  • メモリ安全ポインタ
    • データが格納されている場所を指す
    • usize と同じビット幅の整数で表される
    fn f1(mut n: u32){
        n = 1;
        println!("{}", n);
    }

    fn f2(n_ptr: &mut u32){
        println!("f2: n_ptr {:p}", n_ptr);
        *n_ptr = 2;
        println!("{}", n_ptr);
    }

    let mut f_n1 = 0;
    f1(f_n1);
    println!("{}", f_n1);
    f2(&mut f_n1);
    println!("{}", f_n1);

生ポインタ

  • メモリ安全ではないポインタ
  • dereference するときに unsafe ブロック内に処理を記述する必要性がある
    let mut c1 = 'A';
    let c1_ptr: *mut char = &mut c1;

    assert_eq!(unsafe{*c1_ptr}, 'A');

    unsafe {
        *c1_ptr = 'B';
    }

関数ポインタ

  • 関数を指すポインタ
  fn double(n: i32) -> i32 {
      n + n
  }

  fn abs(n: i32) -> i32 {
      if n >= 0 { n } else { -n } 
  }

  let f1: fn(i32) -> i32 = double;
  let f2: fn(i32) -> i32 = abs;

  assert_eq!(std::mem::size_of_val(&f1), std::mem::size_of::<usize>());

  let mut f3 = double;
  assert_eq!(std::mem::size_of_val(&f3), 0);