Closed23
The Rust Programming Language - Chapter 10 メモ
ジェネリック型、トレイト、ライフタイム
まずは関数に切り出して共通化する
Rust
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);
}
型の異なる関数
Rust
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);
}
↑をジェネリクスを使って共通化する
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);
}
構造体定義では
Rust
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 };
}
Rust
// 異なる型
struct Point<T, U> {
x: T,
y: U,
}
fn main() {
let both_integer = Point { x: 5, y: 10 };
let both_float = Point { x: 1.0, y: 4.0 };
let integer_and_float = Point { x: 5, y: 4.0 };
}
enum定義では
Rust
enum Option<T> {
Some(T),
None,
}
//複数のジェリック
```Rust:Rust
enum Result<T, E> {
Ok(T),
Err(E),
}
メソッド定義では
Rust
struct Point<T> {
x: T,
y: T,
}
// impl の直後に <T> を宣言しないといけない
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
// こちらは具体的な f32 を使用しているので impl の後に <T> はなし
// Tがf32ではないPoint<T>の他のインスタンスにはこのメソッドが定義されないことを意味する
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
ジェネリクスを使用したコードのパフォーマンス
ジェネリックな型引数を使用すると、実行時にコストが発生するのかな、と思うかもしれません。 嬉しいことにRustでは、ジェネリクスを、具体的な型があるコードよりもジェネリックな型を使用したコードを実行するのが遅くならないように実装しています。
コンパイラはこれを、ジェネリクスを使用しているコードの単相化をコンパイル時に行うことで達成しています。 単相化(monomorphization)は、コンパイル時に使用されている具体的な型を入れることで、 ジェネリックなコードを特定のコードに変換する過程のことです。
Rust
let integer = Some(5);
let float = Some(5.0);
// ↓単相化
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);
}
トレイト: 共通の振る舞いを定義する
- トレイト
- 共通の振る舞いを抽象的に定義できる
- 他の言語でいう interface のようなもの
- trait の意味:特性
トレイトを定義する
トレイト定義は、メソッドシグニチャをあるグループにまとめ、なんらかの目的を達成するのに必要な一連の振る舞いを定義する手段です。
Rust
// trait キーワード
pub trait Summary {
// メソッドシグニチャを定義
// 具体は実装しない
fn summarize(&self) -> String;
}
トレイトを型に実装する
Rust
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
// impl <trait name> for <struct name>
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: String,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
デフォルト実装
Rust
pub trait Summary {
// デフォルト実装しておくこともできる
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
Rust
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
// "({}さんの文章をもっと読む)"
format!("(Read more from {}...)", self.summarize_author())
}
}
impl Summary for Tweet {
// summarize_author だけを実装することもできる
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,
};
// デフォルト実装の summarize を呼び出せ、その中で、独自実装の summarize_author を呼び出す
println!("1 new tweet: {}", tweet.summarize());
引数としてのトレイト
Rust
// impl Trait 構文を使って、Trait を実装しているあらゆる型を受け付けることができる
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
// impl Trait 構文はトレイト境界(trait bound)の sytax sugar
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
Rust
// item1とitem2の型が(どちらもSummaryを実装する限り)異なっても良いなら、impl Trait で問題ない
pub fn notify(item1: &impl Summary, item2: &impl Summary) {}
// item1とitem2の型が同一でなければならいという制約を与えたいなら、トレイト境界を使わなければならない
pub fn notify<T: Summary>(item1: &T, item2: &T) {}
複数のトレイト境界を+構文で指定する
Rust
// Summary と Display トレイトを実装している型
pub fn notify(item: &(impl Summary + Display)) {}
// トレイト構文でも + を使って複数のトレイトを指定できる
pub fn notify<T: Summary + Display>(item: &T) {}
where句を使ったより明確なトレイト境界
Rust
// たくさんトレイトを使うと読みにくくなる
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 Trait 構文では書けない
pub fn some_function(t: &impl T, u: &impl U) -> i32
where T: Display + Clone,
U: Clone + Debug {}
トレイトを実装している型を返す
Rust
// 戻り値に 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,
}
}
// ただし、複数の型を返すことはできない
// 17章のトレイトオブジェクトで異なる型の値を許容する節で学ぶ
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
NewsArticle {
headline: String::from(
"Penguins win the Stanley Cup Championship!",
),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
}
} else {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
}
トレイト境界でlargest関数を修正する
Rust
// PartialOrd + Copy トレイトを実装する必要がある
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
トレイト境界を使用して、メソッド実装を条件分けする
Rust
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 }
}
}
// Display + PartialOrd を実装している場合にのみ cmp_display メソッドを実装する
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);
}
}
}
Rust
// トレイト境界を満たすあらゆる型にトレイトを実装することは、Rustの標準ライブラリで広く使用されている。
// ブランケット実装(blanket implementation)と呼ぶ
// 例)Display トレイトを実装するあらゆる型は ToString トレイトを実装している
impl<T: Display> ToString for T {
// --snip--
}
ライフタイムで参照を検証する
- Rustにおいて、参照は全てライフタイムを保持する
- ライフタイム:その参照が有効になるスコープ
- 大体の場合、型は推論される
- 複数の型の可能性がある場合は、型を注釈しないといけない
- 同様に、大体の場合、ライフタイムも暗黙的に推論される
- 参照のライフタイムがいくつか異なる方法で関係することがある場合は、注釈しないといけない
- 大体の場合、型は推論される
- ライフタイムの概念は、他の言語になないユニークな機能
- ライフタイム:その参照が有効になるスコープ
ライフタイムでダングリング参照を回避する
- ライフタイムの主な目的は、ダングリング参照を回避すること
- ダングリングとは、ぶらさがり、宙ぶらりんの意味。不正な参照
Rust
// ダングリング参照あり
{
let r;
{
let x = 5;
r = &x;
} // x のスコープはここまで
println!("r: {}", r); // エラー
}
// ダングリング参照なし
{
let x = 5;
let r = &x;
println!("{}", r);
}
関数のジェネリックなライフタイム
Rust
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("Hello, world!");
}
// 返す値が x, y どちらを参照しているか、コンパイラがわからずエラーとなる
fn longest(x: &str, y: &str) -> &str {
if (x.len() > y.len()) {
x
} else {
y
}
}
ライフタイム注釈記法
- ライムタイム注釈
- いかなる参照の生存期間も変えることはない
- 少し不自然な記法
Rust
&i32 // (ただの)参照
&'a i32 // 明示的なライフタイム付きの参照
&'a mut i32 // 明示的なライフタイム付きの可変参照
関数シグニチャにおけるライフタイム注釈
Rust
// 'a という名前でライフタイムを定義し、全ての引数と戻り値が同じライフタイムを持つことをコンパイラに教える
// 'aは、xとyのライフタイムのうち、小さい方に等しい具体的なライフタイムになる
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// こちらはコンパイルできる
// string1 のライフタイム > string2 のライフタイム
// よって longest() の戻り値のライフタイムは string2 のライフタイムと同じになる
// 戻り値を格納している result は string2 と同じスコープで消費されているのでOK
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);
}
}
// こちらはコンパイルできない
// string1 のライフタイム > string2 のライフタイム
// よって longest() の戻り値のライフタイムは string2 のライフタイムと同じになる
// 戻り値を格納している result は string2 よりも外側のスコープで消費されるのでNG
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);
}
ライフタイムの観点で思考する
Rust
// y のライフタイムと関係ないのでコンパイル通る
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() // result は関数が終わるとドロップされるのでライフタイムを満たしていない
}
構造体定義のライフタイム注釈
Rust
// ImportantExcerpt と part フィールドに保持している参照とライフタイムが同じと定義している
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 };
}
ライフタイム省略
Rust
// ライフタイム定義を省略して書ける
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 {}
ライフタイムの3つの規則
- 参照である各引数は、独自のライフタイム引数を得る
Rust
fn foo<'a>(x: &'a i32);
fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
- 1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される
Rust
fn foo<'a>(x: &'a i32) -> &'a i32
- 複数の入力ライフタイム引数があるけれども、メソッドなのでそのうちの一つが&selfや&mut selfだったら、 selfのライフタイムが全出力ライフタイム引数に代入される
メソッド定義におけるライフタイム注釈
Rust
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
// 1番目の省略規則のため self へのライフタイム注釈は不要
fn level(&self) -> i32 {
3
}
// まず1番目の省略規則により、入力ライフタイム注釈が省略される
// 次に3番目の省略規則により、&self と同じライフタイムが出力ライフタイムに適用される
fn announce_and_return_part(&self, announcement: &str) -> &str {
// お知らせします
println!("Attention please: {}", announcement);
self.part
}
}
静的ライフタイム
'static
ライフタイムは、プログラム全体の期間を示す。
Rust
let s: &'static str = "I have a static lifetime.";
ジェネリックな型引数、トレイト境界、ライフタイムを一度に
Rust
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
}
}
このスクラップは2021/07/14にクローズされました