rust

配列とスライスとVecの挙動の違いについて調べてみた

はじめに

いつも使っている言語のArrayのような存在がrustには3種類あった…

  • 配列
  • スライス
  • Vec

みんな微妙に挙動が違う
いつまでも雰囲気で扱っていないでテーマを絞って挙動の違いについて掘り下げてみる


テーマ

  • 所有権
  • ライフタイム
  • 使用できるmethod

概要

        配列                スライス              Vec             
簡易説明 単一の型のオブジェクトの集合 データへのポインタと長さ データへのポインタと長さ、容量
構造体サイズ 静的 動的(DST) 静的
動的可変長 × ×
プリミティブ ×

所有権

Vecのみ非プリミティブ


プリミティブとCopy

  • 全プリミティブの型はCopyを実装している
  • Copyを実装していると所有権が移るのではなくオブジェクトのコピーが新しく生成される

配列はプリミティブ。Vecは非プリミティブ

fn main() {
  let tempArrayA: [i16; 3] = [1, 2, 3];
  let tempArrayB: [i16; 3] = tempArrayA;
  assert_eq!(tempArrayA, tempArrayB);

/* 所有権が移動した変数を使用したためエラー */
//  let tempVecA: Vec<i16> = Vec::new();
//  let tempVecB: Vec<i16> = tempVecA;
//  assert_eq!(tempHogeA, tempHogeB);
}

ライフタイム

スライスのみ動的サイズの型(DST)


動的サイズの型(DST)とファットポインタ

  • スライスはDynamically Sized Type(DST: 動的サイズの型)
  • DSTはサイズやアライメントが静的には判断できない
  • rustが型を静的解析するために他のポインタを利用してファットポインタを生成し、サイズやアライメントを示してやる必要がある

pub fn return_vec() -> Vec<i16> {
    vec![1,2,3]
}

pub fn return_array() -> [i16; 3] {
    [1, 2, 3]
}

// スライスだけではファットポインタを生成できないのでエラー
// pub fn return_slice() -> [i16] {
//     &[1, 2, 3]
// }

// エラー
// pub fn return_slice() -> [i16] {
//     &[1, 2, 3]
// }

// 引数を使ってファットポインタを生成してやる
pub fn return_slice<'a>(slice: &'a [i16]) -> &'a [i16] {
    slice
}

// 引数を使ってファットポインタを生成してやる
pub fn return_slice_implessly(slice: &[i16]) -> &[i16] {
    slice
}

使用できるmethod

特定条件でスライスに型が強制変換される


型の強制変換

  • rustはポインタやライフタイムのために型を強制的に変換する時がある
  • mutable => immutableとかderefrenceとか

配列を使っているつもりが実はsliceを使っている

  let tempArrayA = [1, 2, 3];

  // sliceに型を強制変換
  for i in &tempArrayA {
    println!("{:?}", i);
  }

  // sliceに型を強制変換
  for i in tempArrayA.iter() {
    println!("{:?}", i);
  }

まとめ

  • Vecのみ非プリミティブ
  • スライスのみ動的サイズの型(DST)
  • 特定条件でスライスに型が強制変換される

ご静聴ありがとうございました


参考リンク