🦀
The Rust Programing Language 8日目
前回のあらすじ
panic!
とResult
について知る
今までプログラムの実行事例外に対しては「そういうもの」と言う感じで理解したフリをしてきたが、関数が例外を投げてくるんではなくResult
型を返してくると言うのは理解しやすくていいなと思った
Javaであれば例外のハンドリングを書き忘れたりする所も、Result
ならErr
の処理を忘れることはない
(matchを網羅できずコンパイル通らないので)
まあpanic!
の中で何が行われてるとかは結局わかってないが、それはそれ
本日は悪名高いライフタイムなどが含まれる10章 ジェネリック型、トレイト、ライフタイム
本日の学び
ジェネリクス
事前知識としてのジェネリクス、正直なんとなくの概念はわかるがちゃんと使えていない
汎用的に使える型の代用みたいなイメージ
-
fn largest<T>(list: &[T]) -> T {
のように定義する- 関数
largest
は型Tに関してジェネリックである - 本筋じゃないけど、
char
型って大小比較できるの知らなかった、pythonもできるのか?- 辞書順で後が大きいという判定になるぽい
- 取りうる全てのTに対して動作する必要がある
- 関数
- 構造体もジェネリックにできる(表現合ってる?)
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 }; }
- この例ではxとyのフィールドは同じ型になっている
- 別の型を使用する場合は、複数のジェネリック型引数を使用する
-
<T, U>
みたいな
-
- 別の型を使用する場合は、複数のジェネリック型引数を使用する
- この例ではxとyのフィールドは同じ型になっている
- enumもジェネリックにできる
- これは
Result
やOption
で履修済みenum Result<T, E> { Ok(T), Err(E), }
- これは
- コード内で型のみが異なる構造体やenum定義が現れたら、ジェネリック型で重複を削減できる可能性を考慮しよう!
- 構造体にメソッドを定義する際にもジェネリック型を使用できる
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()); }
-
impl
の直後にTを宣言する必要がある- 型
Point<T>
にメソッドを実装している - 逆に、具体的な型を使用することもできる
impl Point<f32> { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } }
- これは、Tが
f32
の時だけこのメソッドが定義されることを表す
- これは、Tが
- 型
-
- ジェネリクスを使用していてもコードの実行は遅くならない!
- コンパイル時に、使用されている箇所を全てチェックして、具体的な型のコードを生成する
トレイト
トレイト、一発で変換できながち(トレイと、になる)
interface
かな?と思っている
- 違いはあるけど大体インターフェースです!
-
trait
キーワードで宣言- 一発で変換できないし今後はtraitと言うことにする
- trait定義
pub trait Summary { fn summarize(&self) -> String; }
- traitの実装
pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String, } impl Summary for NewsArticle { 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 { fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) } }
- 通常のメソッド実装に似ているが、forキーワードと実装対象の型を記述する
-
for
ってループの予約語だと思うけど他の部分でも使うの不思議だ
-
- 通常のメソッド実装に似ているが、forキーワードと実装対象の型を記述する
- traitのメソッドにデフォルトの振る舞いを定義できる
pub trait Summary { fn summarize(&self) -> String { // "(もっと読む)" String::from("(Read more...)") } }
- そのまま使うかOverrideするか選べる
- そのまま使うなら空でまとめてあげれば良い
impl Summary for NewsArticle {}
- Overrideするのも、普通に実装するのと同じでいい
- そのまま使うなら空でまとめてあげれば良い
- デフォルト実装の中で、デフォルト実装を持たないメソッドを呼べる
pub trait Summary { fn summarize_author(&self) -> String; fn summarize(&self) -> String { // "({}さんの文章をもっと読む)" format!("(Read more from {}...)", self.summarize_author()) } }
- 一部の振る舞いだけを型によって変えたいみたいな時めっちゃ便利そうだ。。
- オーバーライドしている実装から、元のデフォルト実装を呼ぶことはできない
- まあそれはそうか
- そのまま使うかOverrideするか選べる
- 特定のtraitを実装している型を引数に指定する
pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize()); }
- 具体的な型の代わりにtrait名を指定して、それを実装しているあらゆる型を受け付ける
-
impl Trait
構文は実はsyntax sugar- 元々は、トレイト境界(trait bound)と呼ばれる構文
pub fn notify<T: Summary>(item: &T) { // 速報! {} println!("Breaking news! {}", item.summarize()); }
- 複雑な場面ではトレイト境界を用いて表現できる
- 例として
Summary
を実装する2つのパラメータを持つケースを考える-
impl Trait
構文:pub fn notify(item1: &impl Summary, item2: &impl Summary) {
- トレイト境界構文:
pub fn notify<T: Summary>(item1: &T, item2: &T) {
- この2つは意味が違っていて、
impl Trait
構文では2引数の型が異なっていても良い - 型が同一であることを強制するにはトレイト境界構文を使用するしかない
- ジェネリクス使ってるので、同一の型になるということですね
-
- 元々は、トレイト境界(trait bound)と呼ばれる構文
- 2つのTraitを実装していることを求めるには
+
構文を使うpub fn notify(item: &(impl Summary + Display)) {
pub fn notify<T: Summary + Display>(item: &T) {
- トレイト境界を複数使っていると、情報が多くなり読みにくくなる
- それを解消するため、
where
句をサポートしている
これをfn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
こうじゃfn some_function<T, U>(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug {
- それを解消するため、
-
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, } }
-
Summary
を実装している型を返却する - これは一種類の型を返している時にしか使用できない
-
NewsArticle
かTweet
を返すというのは許されない- これをする方法は後日!
-
-
- トレイト境界を使用して、メソッド実装に条件を付与する
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); } } }
- Tが
Display
とPartialOrd
を実装している場合のみcmp_display
を実装する -
ブランケット実装
により、別のTraitを実装する全ての型に対するTrait実装を条件分けできるimpl<T: Display> ToString for T { // --snip-- }
- 標準ライブラリは
Display
トレイトを実装するあらゆる型にToString
トレイトを実装している - 整数に対して
to_string
メソッドを呼べるのはこのため
- 標準ライブラリは
- Tが
ライフタイム
スコープが生きてる期間、みたいなイメージ
ライフタイムとは、その参照が有効になるスコープのことです。
概念の理解としては合ってそう
- 型が推論されるように、ライフタイムも大体の場合暗黙的に推論される
- 型の選択肢が複数ある際に注釈をつけるように、ライフタイムにも注釈をつけるパターンもある
- ここではライフタイムの全てを解説しません
- 助かる〜
- ライフタイムでダングリング参照を回避
- ライフタイムの主な目的 -> 解放済みのメモリを参照するケースを回避する
- ライフタイムをコンパイラが把握できないパターン
fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); } fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } }
- ぱっと見コンパイル通りそうな感じもするが通らない
- xとyのライフタイムがどう戻り値のライフタイムと関係するかわからない
ライフタイム注釈記法
- ジェネリックなライフタイム引数を指定された関数は、あらゆるライフタイムの参照を受け取れる
- ライフタイム引数の名称はアポストロフィー(')で始まり、全部小文字
- 大体は
'a
とするらしい
- 大体は
&i32 // a reference
// (ただの)参照
&'a i32 // a reference with an explicit lifetime
// 明示的なライフタイム付きの参照
&'a mut i32 // a mutable reference with an explicit lifetime
// 明示的なライフタイム付きの可変参照
関数シグニチャにおけるライフタイム注釈
-
longest
関数に、全ての引数と戻り値が同じライフタイムを持つことを示す注釈を追加するfn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
-
longest
関数は、渡された参照のうち小さい方のライフタイムと同じライフタイムの参照を返す
-
- 受け取る参照のライフタイムが戻り値のライフタイムに関与しないなら注釈は要らない
構造体定義のライフタイム注釈
- 構造体に参照を保持させる場合、構造体定義の全参照にライフタイム注釈が必要
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, }; }
- この構造体のインスタンスが、
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[..]
}
- 4章で登場したこの関数がライフタイム注釈なしでコンパイルできたのな〜んでだ
- Rust1.0以前では注釈が必要だった
fn first_word<'a>(s: &'a str) -> &'a str {
- パターン化できるものについては計算できるようにし、注釈が不要になった
- Rust1.0以前では注釈が必要だった
- 下記の規則で計算できる場合は注釈が不要
- 参照である各引数は独自のライフタイム引数を得る
a. 引数1つならライフタイム1つ - ライフタイムが1つであるなら、全ての戻り値のライフタイムにそれが代入される
- 複数のライフタイム引数のうち、selfの参照が含まれれば、全ての戻り値のライフタイムにそれが代入される
- 参照である各引数は独自のライフタイム引数を得る
- 上記例は規則1,2により全てのライフタイムが計算できるため、注釈が不要になる
メソッド定義におけるライフタイム注釈
- 構造体にライフタイムのあるメソッドを実装する際は、ジェネリック型引数と同じ記法を使用する
impl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 } }
-
impl
後のライフタイム引数宣言と型名の後に付けるのは必須だが、省略規則によりselfには不要
-
静的ライフタイム
-
'static
ライフタイムという特殊なライフタイムが存在する- 参照がプログラムの全期間生存できることを意味する
- 文字列リテラルはバイナリに直接格納されるので、全て
'static
ライフタイムを持つlet s: &'static str = "I have a static lifetime.";
- 文字列リテラルはバイナリに直接格納されるので、全て
- 参照がプログラムの全期間生存できることを意味する
- コンパイラから「'staticにしたらいいよ」と言われることがあるが、それが最適なのかよく考えましょう
- global変数みたいなことかな?
全部登場する関数
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest_with_an_announcement(
string1.as_str(),
string2,
"Today is someone's birthday!",
);
println!("The longest string is {}", result);
}
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
}
}
本日のまとめ
Traitとライフタイムを1章でやるのおかしくない?(KONAMI)
少しずつ難解になっていっているが、まだギリわかる
明日は11章!テストについてとのこと
Rustは言語仕様にテストが組み込まれているらしいので楽しみ
ようやく折り返したぞ〜〜〜
Discussion