Charaken 技術系ブログ

技術系に関して学んだことや、気になったことを記事にしていく。

【Rust】がばがばRust独学 - 10. Generic Types/Traits/Lifetimes - 1 Generic Types

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回は、Generic Typesについて学びつつ、気になった点をコード確認した結果を記事にしています。

  • versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)


Generic型

Option<T>Vec<T>HashMap<K, V>Resultt<T, E> などの TKVE がGeneric型になります。

Generic型は独自の型や関数、メソッドの定義に役立ちます。

関数へのGeneric型適応例

重複を関数による共通化

Generic Types, Traits, and Lifetimes - The Rust Programming Language

例えば、下記のように公式ドキュメントから少し変更した Vec<usize> から最大値を求める処理が2つある場合を想定します。

fn main() {
    let list: Vec<usize> = vec![1, 2, 3, 99, 5];

    let mut largest: usize = list[0];

    for number in list {
        if number > largest {
            largest = number;
        }
    }
    println!("largest -> {}", largest);

    // -----------------------

    let list: Vec<usize> = vec![6, 7, 8, 100, 10];

    let mut largest: usize = list[0];

    for number in list {
        if number > largest {
            largest = number;
        }
    }
    println!("largest -> {}", largest);
}
largest -> 99
largest -> 100

上記の場合、 largest を求める部分は重複しているため関数として利用させた方が見通しがよくなります。

fn main() {
    let list: Vec<usize> = vec![1, 2, 3, 99, 5];

    let result = largest(&list);
    println!("largest -> {}", result);

    // -----------------------

    let list: Vec<usize> = vec![6, 7, 8, 100, 10];
    let result = largest(&list);

    println!("largest -> {}", result);
}

fn largest(list: &[usize]) -> usize {
    let mut largest: usize = list[0];
    for &number in list.iter() {
        if number > largest {
            largest = number;
        }
    }
    largest
}
largest -> 99
largest -> 100
重複をGeneric型の関数による共通化

Generic Data Types - The Rust Programming Language

下記のように usize 以外にも、 char に対して最大を求める場合、型の制約により別々の関数として宣言する必要があります。

fn main() {
    let list: Vec<usize> = vec![1, 2, 3, 99, 5];

    let result = largest_i(&list);
    println!("largest -> {}", result);

    // -----------------------

    let list: Vec<char> = vec!['a', 'b', 'z', 'd'];
    let result = largest_c(&list);

    println!("largest -> {}", result);
}

fn largest_i(list: &[usize]) -> usize {
    let mut largest: usize = list[0];
    for &number in list.iter() {
        if number > largest {
            largest = number;
        }
    }
    largest
}

fn largest_c(list: &[char]) -> char {
    let mut largest: char = list[0];
    for &number in list.iter() {
        if number > largest {
            largest = number;
        }
    }
    largest
}
largest -> 99
largest -> z

しかし、上記の場合冗長になるため、Generic型を利用して関数を作成します。

下記はGeneric型 T を「単純に」利用した場合になります。

fn main() {
    let list: Vec<usize> = vec![1, 2, 3, 99, 5];

    let result = largest(&list);
    println!("largest -> {}", result);

    // -----------------------

    let list: Vec<char> = vec!['a', 'b', 'z', 'd'];
    let result = largest(&list);

    println!("largest -> {}", result);
}

fn largest<T>(list: &[T]) -> T {
    let mut largest: T = list[0];
    for &number in list.iter() {
        if number > largest {
            largest = number;
        }
    }
    largest
}
error[E0369]: binary operation `>` cannot be applied to type `T`
  --> src/demo.rs:18:13
   |
18 |         if number > largest {
   |            ------ ^ ------- T
   |            |
   |            T
   |
   = note: `T` might need a bound for `std::cmp::PartialOrd`

上記のように、 T が比較できるかどうかの制約が無いため、エラーとなります。

先取りになりますが、比較を持つTraitの PartialOrd と、let mut largest: T = list[0]; でのCopyさせるためのTraitの Copy による制約をかけることにより、 T として必要となるTraitを設定できます。

fn main() {
    let list: Vec<usize> = vec![1, 2, 3, 99, 5];

    let result = largest(&list);
    println!("largest -> {}", result);

    // -----------------------

    let list: Vec<char> = vec!['a', 'b', 'z', 'd'];
    let result = largest(&list);

    println!("largest -> {}", result);
}

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest: T = list[0];
    for &number in list.iter() {
        if number > largest {
            largest = number;
        }
    }
    largest
}
largest -> 99
largest -> z

Structに対するGeneric型

struct Point<T> のように定義可能です。

#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}
fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };

    println!("integer -> {:?}", integer);
    println!("float -> {:?}", float);
}
integer -> Point { x: 5, y: 10 }
float -> Point { x: 1.0, y: 4.0 }

T で縛っているため、型が違えばエラーとなります。

#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}
fn main() {
    let point = Point { x: 5, y: 1.0 };
    println!("point -> {:?}", point);
}
error[E0308]: mismatched types
 --> src/demo.rs:7:31
  |
7 |     let point = Point { x: 5, y: 1.0 };
  |                                  ^^^ expected integer, found floating-point number
  |
  = note: expected type `{integer}`
             found type `{float}

ちょっとしたメモなのですが、型確認はフィールド名での優先順ではなく、宣言順のようです。

#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}
fn main() {
    let point = Point { y: 5, x: 1.0 };
    println!("point -> {:?}", point);
}
error[E0308]: mismatched types
 --> src/demo.rs:7:31
  |
7 |     let point = Point { y: 5, x: 1.0 };
  |                                  ^^^ expected integer, found floating-point number
  |
  = note: expected type `{integer}`
             found type `{float}`

もし、Point に対して2つの型を利用したい場合は、 struct Point<T, U> とすることで、各々に対して型をつけることが可能です。

#[derive(Debug)]
struct Point<T, U> {
    x: T,
    y: U,
}
fn main() {
    let point = Point { y: 5, x: 1.0 };
    println!("point -> {:?}", point);
}
point -> Point { x: 1.0, y: 5 }

Enumに対するGeneric型

Option<T>Result<T, E> のように定義することが可能です。

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

implに対するGeneric型

公式ドキュメントのデモコードの impl<T> Point<T> として記載が可能です。

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };
    println!("p.x = {}", p.x());
}
p.x = 5

最後に

Generic型の定義方法についてまとめました。

largest<T: PartialOrd + Copy> で少し触れましたが、Genetic型に対して制約をもたせる場合はTraitを利用するとのことでしたので、次回のTraitsで深堀りしていきます。