ジェネリクスとは?
ジェネリクスは、具体型や他のプロパティの抽象的な代役です。
コード記述の際、コンパイルやコード実行時に、 ジェネリクスの位置に何が入るかを知ることなく、ジェネリクスの振る舞いや他のジェネリクスとの関係を表現できるのです。
概念の重複を効率的に扱う道具
ジェネリック型は以前の章に出てきました。
第4章で参照、借用
第6章で Option<T>
第8章でVec<T>とHashMap<K, V>
第9章でResult<T, E>
最大値を求めるプログラム例:
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = number_list[0];
for number in number_list {
if number > largest {
largest = number;
}
}
// 最大値は{}です
println!("The largest number is {}", largest);
}
重複がある例:
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = number_list[0];
for number in number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {}", largest);
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let mut largest = number_list[0];
for number in number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {}", largest);
}
重複を修正した例:
fn largest(list: &[i32]) -> i32 {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let result = largest(&number_list);
println!("The largest number is {}", result);
}
例:
1つはi32値のスライスから最大の要素を探す関数
1つはchar値のスライスから最大要素を探す関数
fn largest_i32(list: &[i32]) -> i32 {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn largest_char(list: &[char]) -> char {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest_i32(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest_char(&char_list);
println!("The largest char is {}", result);
}
"type"の省略形なので、Tが多くのRustプログラマの既定の選択
コンパイルエラーが出ます!!
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
error[E0369]: binary operation `>` cannot be applied to type `T`
(エラー: 2項演算`>`は、型`T`に適用できません)
--> src/main.rs:5:12
|
5 | if item > largest {
| ^^^^^^^^^^^^^^
|
= note: an implementation of `std::cmp::PartialOrd` might be missing for `T`
(注釈: `std::cmp::PartialOrd`の実装が`T`に対して存在しない可能性があります)
注釈がstd::cmp::PartialOrdに触れています。これは、トレイトです。
とりあえず、このエラーは、largestの本体は、Tがなりうる全ての可能性のある型に対して動作しないと述べています。
本体で型Tの値を比較したいので、値が順序付け可能な型のみしか使用できないのです。比較を可能にするために、 標準ライブラリには型に実装できるstd::cmp::PartialOrdトレイトがあります(このトレイトについて詳しくは付録Cを参照されたし)。
ジェネリックな型が特定のトレイトを持つと指定する方法は「トレイト境界」節で習うでしょうが、 先にジェネリックな型引数を使用する他の方法を探究しましょう
#[derive(Debug)] // 第5章でやったやつ
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!("Debug {:?}", integer);
println!("Debug {:?}", float);
}
エラー
どちらも同じジェネリックなデータ型Tなので、xとyというフィールドは同じ型でなければならない
#[derive(Debug)] // 第5章でやったやつ
struct Point<T> {
x: T,
y: T,
}
fn main() {
let point = Point { x: 5, y: 4.0 };
// デバッグしたかったら
println!("Debug {:?}", point);
}
エラーを修正すると
#[derive(Debug)]
struct Point<T, U> {
x: T,
y: U,
}
fn main() {
let point = Point { x: 5, y: 4.0 };
// デバッグしたかったら
println!("Debug {:?}", point);
}
6章で出てきたOption<T>
この意味は…
Option<T>は、 型Tに関してジェネリックで2つの列挙子のあるenumです
その列挙子は、型Tの値を保持するSomeと、 値を何も保持しないNoneです。
enum Option<T> {
Some(T),
None,
}
9章のenum Result<T, E>
TとEは、ジェネリックな型引数です。
Tが成功した時にOk列挙子に含まれて返される値の型を表すことと、 Eが失敗した時にErr列挙子に含まれて返されるエラーの型を表すことです。
enum Result<T, E> {
Ok(T),
Err(E),
}
定義にジェネリックな型を使うメソッドを構造体やenumに実装することもできます。
implの直後に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());
}
ジェネリックな型を持つPoint<T>インスタンスではなく、Point<f32>だけにメソッドを実装することもできる
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
異なった型を返すこともできる
x(値は5)にi32、y(値は10.4)にf64を持つPointを定義しました。p2変数は、 x(値は"Hello")に文字列スライス、y(値はc)にcharを持つPoint構造体です。
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c'};
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
Rustでは、ジェネリクスを、具体的な型があるコードよりもジェネリックな型を使用したコードを実行するのが遅くならないように実装しています。
コンパイラはこれを、ジェネリクスを使用しているコードの単相化をコンパイル時に行うことで達成しています。 単相化(monomorphization)は、コンパイル時に使用されている具体的な型を入れることで、 ジェネリックなコードを特定のコードに変換する過程のことです。
enum Option_i32 {
Some(i32),
None,
}
enum Option_f64 {
Some(f64),
None,
}
fn main() {
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
}
pub trait Summary { // トレイト名
fn summarize(&self) -> String; // トレイトシグニチャ
}
NewsArticle または Tweet インスタンスに保存されているデータのサマリーを表示できるメディア アグリゲータ ライブラリを作成します。
各型のサマリーが必要で、インスタンスで summarize メソッドを呼び出してサマリーを要求する
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle { // forキーワードを使用
fn summarize(&self) -> String { // トレイト定義で定義したメソッドシグニチャを置
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet { // forキーワードを使用
fn summarize(&self) -> String { // トレイト定義で定義したメソッドシグニチャを置
format!("{}: {}", self.username, self.content)
}
}
トレイトを実装後、普通のメソッド同様にNewsArticleやTweetのインスタンスに対してこのメソッドを呼び出せます。
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
// もちろん、ご存知かもしれませんがね、みなさん
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
1 new tweet: horse_ebooks: of course, as you probably already know, people
メソッドシグニチャだけを定義するのではなく、 Summaryトレイトのsummarizeメソッドにデフォルトの文字列を指定する
定義:
pub trait Summary {
fn summarize(&self) -> String {
// "(もっと読む)"
String::from("(Read more...)")
}
}
let article = NewsArticle {
// ペンギンチームがスタンレーカップチャンピオンシップを勝ち取る!
headline: String::from("Penguins win the Stanley Cup Championship!"),
// アメリカ、ペンシルベニア州、ピッツバーグ
location: String::from("Pittsburgh, PA, USA"),
// アイスバーグ
author: String::from("Iceburgh"),
// ピッツバーグ・ペンギンが再度NHL(National Hockey League)で最強のホッケーチームになった
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
};
println!("New article available! {}", article.summarize());
New article available! (Read more...)
定義:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
// "({}さんの文章をもっと読む)"
format!("(Read more from {}...)", self.summarize_author())
}
}
オーバーライド
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
使い方
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
1 new tweet: (Read more from @horse_ebooks...)
この関数を呼び出すときに、Stringやi32のような他の型を渡すようなコードはコンパイルできません。
この引数は、指定されたトレイトを実装しているあらゆる型を受け付けます。
トレイト境界 (trait bound) と呼ばれる姿の糖衣構文 (syntax sugar)
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
もともとは下記のように書きます。
pub fn notify<T: Summary>(item: &T) {
// 速報! {}
println!("Breaking news! {}", item.summarize());
}
2つの引数を取る表現
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
pub fn notify<T: Summary>(item1: &T, item2: &T) {
summarizeメソッドに加えてitemの画面出力形式(ディスプレイフォーマット)を使わせたいとします。
notifyの定義にitemはDisplayとSummaryの両方を実装していなくてはならないと指定することってのができます。
pub fn notify(item: &(impl Summary + Display)) {
pub fn notify<T: Summary + Display>(item: &T) {
関数名と引数のリストが大量のトレイト境界に関する情報で非常に見にくくなります。
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
Where句を使うことができます。
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
impl Summaryを使うことにより、
具体的な型が何かを言うことなく、returns_summarizable関数はSummaryトレイトを実装している何らかの型を返す
impl Traitは一種類の型を返す場合にのみ使える。
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
今日のエラー
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `>` cannot be applied to type `T`
--> src/main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- T
| |
| T
|
= note: `T` might need a bound for `std::cmp::PartialOrd`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10`.
To learn more, run the command again with --verbose.
下記に書き換えてみる
fn largest<T: PartialOrd>(list: &[T]) -> T {
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
ジェネリックな型引数を持つimplブロックにトレイト境界を与えることで、 特定のトレイトを実装する型に対するメソッド実装を条件分けできます。例えば、 型Pair<T>は、常にnew関数を実装します。しかし、Pair<T>は、 内部の型Tが比較を可能にするPartialOrdトレイトと出力を可能にするDisplayトレイトを実装している時のみ、 cmp_displayメソッドを実装します。
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
参照を検証する
{
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
コンパイラには下記のように見えている。
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
{
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {}", r); // | |
// --+ |
} // ----------+
関数に取ってほしい引数が文字列スライス、つまり参照であることに注意してください。 何故なら、longest関数に引数の所有権を奪ってほしくない
スライスと文字列リテラルを受け取らせて長さの比較を行うプログラムです。
コンパイルできない例
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
}
返している参照がxかyのどちらを参照しているか、コンパイラにはわからない
error[E0106]: missing lifetime specifier
(エラー: ライフタイム指定子が不足しています)
--> src/main.rs:1:33
|
1 | fn longest(x: &str, y: &str) -> &str {
| ^ expected lifetime parameter
| (ライフタイム引数が予想されます)
|
= help: this function's return type contains a borrowed value, but the
signature does not say whether it is borrowed from `x` or `y`
(助言: この関数の戻り値型は借用された値を含んでいますが、
シグニチャは、それが`x`か`y`由来のものなのか宣言していません)
&i32 // a reference
// (ただの)参照
&'a i32 // a reference with an explicit lifetime
// 明示的なライフタイム付きの参照
&'a mut i32 // a mutable reference with an explicit lifetime
// 明示的なライフタイム付きの可変参照
参照間の関係を定義するジェネリックなライフタイム引数
シグニチャの全参照が同じライフタイム'aになると指定したlongest関数の定義
このシグニチャで表現したい制約は、引数の全参照と戻り値が同じライフタイムになること
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
string1は外側のスコープの終わりまで有効で、string2は内側のスコープの終わりまで有効、 そしてresultは内側のスコープの終わりまで有効な何かを参照
fn main() {
// 長い文字列は長い
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}
}
resultの参照のライフタイムが2つの引数の小さい方のライフタイムになることを示す例
resultがprintln!文に対して有効
無効な参照がある可能性があるとして許可しない
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is {}", result);
}
ワーニングは出ますが、これはコンパイルできます。
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}
これはコンパイルできません。ダングリングですね。
fn longest<'a>(x: &str, y: &str) -> &'a str {
// 本当に長い文字列
let result = String::from("really long string");
result.as_str() // ここでスコープが有効でなくなります。
}
構造体に参照を保持させることもできます
例:
参照を含む構造体なので、定義にライフタイム注釈が必要
文字列スライスを保持するImportantExcerpt(重要な一節)という構造
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
// 僕をイシュマエルとお呼び。何年か前・・・
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.')
.next()
.expect("Could not find a '.'"); // '.'が見つかりませんでした
let i = ImportantExcerpt { part: first_sentence };
println!("{}", i.part)
}
ライフタイム注釈なしでコンパイルできる
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
歴史的経緯らしいです。。。このパターンをコンパイラのコードに落とし込んだので、 このような場面には借用チェッカーがライフタイムを推論できるらしい。。。なんと。
fn first_word<'a>(s: &'a str) -> &'a str {
1引数の関数は、1つのライフタイム引数を得るということです: fn foo<'a>(x: &'a i32); 2つ引数のある関数は、2つの個別のライフタイム引数を得ます: fn foo<'a, 'b>(x: &'a i32, y: &'b i32);
2番目の規則は、1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入されるというものです: fn foo<'a>(x: &'a i32) -> &'a i32。
複数の入力ライフタイム引数があるけれども、メソッドなのでそのうちの一つが&selfや&mut selfだったら、 selfのライフタイムが全出力ライフタイム引数に代入されるというものです。
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
// お知らせします
println!("Attention please: {}", announcement);
self.part
}
}
議論する必要のある1種の特殊なライフタイムが、'staticであり、これはプログラム全体の期間を示します。 文字列リテラルは全て'staticライフタイムになり、次のように注釈できます:
// 静的ライフタイムを持ってるよ
let s: &'static str = "I have a static lifetime.";
use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where T: Display
{
// アナウンス!
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}