0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Rustを勉強する part5【ジェネリック型】

Last updated at Posted at 2022-12-12

記事について

次のTour of Rustを用いて学んだ内容のメモになります。

ジェネリック型とは

ジェネリック型を使用することで、structenumを部分的に定義することが可能になる。
コンパイル時にコード内での使用状況に応じて、完全に定義されたバージョンを生成する。

Rustでは通常、インスタンスを生成するコードから最終的な型を推論できる。しかし、補助が必要な場合は、::<T>演算子を使って明示的に指定する。この演算子はturbofishという名前でも知られている。
// 部分的に定義された構造体型

struct BagOfHolding<T> {
    item: T,
}

fn main() {
    // 注意: ジェネリック型を使用すると、型はコンパイル時に作成される。
    // ::<> (turbofish) で明示的に型を指定
    let i32_bag = BagOfHolding::<i32> { item: 42 };
    let bool_bag = BagOfHolding::<bool> { item: true };
    
    // ジェネリック型でも型推論可能
    let float_bag = BagOfHolding { item: 3.14 };
    
    // 注意: 実生活では手提げ袋を手提げ袋に入れないように
    let bag_in_bag = BagOfHolding {
        item: BagOfHolding { item: "boom!" },
    };

    println!(
        "{} {} {} {}",
        i32_bag.item, bool_bag.item, float_bag.item, bag_in_bag.item.item
    );
}

値が無いことの表現

他の言語では値が存在しないことを表すnullキーワードが用いられる。
nullが存在しないRustでは、1つ以上の値をNoneによって代替するパターンが一般的である。

enum Item {
    Inventory(String),
    // None は項目がないことを表す
    None,
}

struct BagOfHolding {
    item: Item,
}

Option

Rustには次のようなOptionと呼ばれるジェネリックな列挙型が組み込まれており、nullを使わずにnull許容な値を表現できる。

enum Option<T> {
    None,
    Some(T),
}

この列挙型はとても一般的で、SomeNoneを使えばどこでもインスタンスを生成できる。

// 部分的に定義された構造体型
struct BagOfHolding<T> {
    // パラメータ T を渡すことが可能
    item: Option<T>,
}

fn main() {
    // 注意: i32 が入るバッグに、何も入っていません!
    // None からは型が決められないため、型を指定する必要があります。
    let i32_bag = BagOfHolding::<i32> { item: None };

    if i32_bag.item.is_none() {
        println!("バッグには何もない!")
    } else {
        println!("バッグには何かある!")
    }

    let i32_bag = BagOfHolding::<i32> { item: Some(42) };

    if i32_bag.item.is_some() {
        println!("バッグには何かある!")
    } else {
        println!("バッグには何もない!")
    }

    // match は Option をエレガントに分解して、
    // すべてのケースが処理されることを保証できます!
    match i32_bag.item {
        Some(v) => println!("バッグに {} を発見!", v),
        None => println!("何も見付からなかった"),
    }
}

Result

Rustには次のようなResultと呼ばれるジェネリックな列挙型が組み込まれており、失敗する可能性のある値を返せる。
慣例的に、これを用いてエラー処理を行う。

enum Result<T,E> {
    Ok(T),
    Err(E),
}

このジェネリック型はカンマで区切られた複数のパラメータ化された型を持つことに注意する必要がある。
この列挙型はとても一般的で、OkErrを使えばどこでもインスタンスを生成できる。

fn do_something_that_might_fail(i:i32) -> Result<f32,String> {
    if i == 42 {
        Ok(13.0)
    } else {
        Err(String::from("正しい値ではありません"))
    }
}

fn main() {
    let result = do_something_that_might_fail(12);

    // match は Result をエレガントに分解して、
    // すべてのケースが処理されることを保証できます!
    match result {
        Ok(v) => println!("発見 {}", v),
        Err(e) => println!("Error: {}",e),
    }
}

失敗するかもしれないmain

main関数はResultを返すことができる。

fn do_something_that_might_fail(i: i32) -> Result<f32, String> {
    if i == 42 {
        Ok(13.0)
    } else {
        Err(String::from("正しい値ではありません"))
    }
}

// main は値を返しませんが、エラーを返すことがあります!
fn main() -> Result<(), String> {
    let result = do_something_that_might_fail(12);

    match result {
        Ok(v) => println!("発見 {}", v),
        Err(_e) => {
            // エラーをうまく処理
            
            // 何が起きたのかを説明する新しい Err を main から返します!
            return Err(String::from("main で何か問題が起きました!"));
        }
    }

    // Result の Ok の中にある unit 値によって、
    // すべてが正常であることを表現していることに注意してください。
    Ok(())
}

簡潔なエラー処理

Resultはよく用いられるので、RustにはResultを扱うための強力な演算子?が用意されている。
次の2つのコードは等価である。

do_something_that_might_fail()?
match do_something_that_might_fail() {
    Ok(v) => v,
    Err(e) => return Err(e),
}

使用例としては次。

fn do_something_that_might_fail(i: i32) -> Result<f32, String> {
    if i == 42 {
        Ok(13.0)
    } else {
        Err(String::from("正しい値ではありません"))
    }
}

fn main() -> Result<(), String> {
    // コードが簡潔なのに注目!
    let v = do_something_that_might_fail(42)?;
    println!("発見 {}", v);
    Ok(())
}

やっつけな Option/Result 処理

OptionとResultの両方には、unwrapと呼ばれる関数があり、簡単に値を取得するのに便利である。
unwrapは次のことを行う。

  1. Option/Result内の値を取得する。
  2. 列挙型がNone/Errの場合、panic!する。

以下の2つのコードは等価である。

my_option.unwrap()
match my_option {
    Some(v) => v,
    None => panic!("Rustによって生成されたエラーメッセージ!"),
}

同様に、

my_result.unwrap()
match my_result {
    Ok(v) => v,
    Err(e) => panic!("Rustによって生成されたエラーメッセージ!"),
}

よいRust使い(Rustacean)であるためには、可能な限りmatch文を使用すべきである。

ベクタ型

最も有用なジェネリック型のいくつかはコレクション型である。
ベクタは構造体Vecで表される可変サイズのリストです。
マクロVec!を使用すると、簡単にベクタを生成できる。

Vecにはメソッドiter()があり、これによりベクタからイテレータを生成すれば、ベクタを簡単にforループに入れることができる。

fn main() {
    // 型を明示的に指定
    let mut i32_vec = Vec::<i32>::new(); // turbofish <3
    i32_vec.push(1);
    i32_vec.push(2);
    i32_vec.push(3);

    // もっと賢く、型を自動的に推論
    let mut float_vec = Vec::new();
    float_vec.push(1.3);
    float_vec.push(2.3);
    float_vec.push(3.4);

    // きれいなマクロ!
    let string_vec = vec![String::from("Hello"), String::from("World")];

    for word in string_vec.iter() {
        println!("{}", word);
    }
}

ベクタのメモリ

  • Vecは構造体ですが、内部的にはヒープ上の固定リストへの参照を保持している。
  • ベクタはデフォルトの容量で始まり、容量よりも多くの項目が追加された場合、ヒープ上により大きな容量の固定リストを生成して、データを再割り当てする。

まとめ

ジェネリック型がどれだけのちからを発揮できるかを理解できた。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?