【Rust】がばがばRust独学 - 10. Generic Types/Traits/Lifetimes - 1 Generic Types
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回は、Generic Typesについて学びつつ、気になった点をコード確認した結果を記事にしています。
- versions
$ rustc --version rustc 1.40.0 (73528e339 2019-12-16)
Generic型
Option<T>
、 Vec<T>
、 HashMap<K, V>
、 Resultt<T, E>
などの T
、 K
、 V
、E
が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で深堀りしていきます。