はじめに
こんにちは。細々とプログラミングをしているsotanengelです。
この記事は以下の記事の連載です。
他の連載記事 (詳細)
- Day 1:型システムを使ってデータ構造を再現しよう
- Day 2:型システムを用いて共通の挙動を表現しよう
- Day 3:OptionとResultに対してはmatchを用いずに変換しよう
- Day 4:標準のErrorを使おう
- Day 5:型変換を理解しよう
- Day 6:newtypeパターンを活用しよう
- Day 7:複雑な型にはビルダを使おう
- Day 8:明示的なループの代わりにイテレータ変換を使用することを検討しよう
- Day 9:標準トレイトに習熟しよう
- Day 10:RIIパターンにはDropトレイトを実装しよう
- Day 11:ジェネリクスとトレイトオブジェクトのトレードオフを理解しよう
- Day 12:デフォルト実装を用いて、実装しなければならないトレイトメソッドを最小限にしよう
- Day 13:Don't panic
- Day 14:リフレクションを避けよう
- Day 15:可視範囲を最小化しよう
また本記事はEffective Rust(David Drysdale (著), 中田 秀基 (翻訳))を参考に作成されております。とてもいい書籍ですので興味を持った方は、ぜひ読んでみてください!
今日の内容
概要
共通の挙動を型システムで表現できるようにしましょう。
メソッドのシグネチャで関数の挙動を説明しよう
問(リンク)
enum
にimpl
で関数を定義することで、データ構造に関連したメソッドを作成しましょう。
コード (詳細)
enum Item {
Apple(u32), // 価格
Meat(u32), // 価格
}
impl Item {
// TODO: Itemの価格を返却するメソッドを書いてください。
fn price(&self) -> u32 {}
// TODO: Itemの価格を変更するメソッドを書いてください。
fn increase_price(&mut self, amount: u32) {}
// TODO: 「Consumed {apple or meat} worth {} yen」という出力をするメソッドを書いてください。
fn consume(self) {}
}
fn main() {
let mut apple = Item::Apple(300);
let meat = Item::Meat(1200);
// 参照メソッドの呼び出し
println!("Apple price: {} yen", apple.price());
// 可変参照メソッドの呼び出し
apple.increase_price(100);
println!("Increased apple price: {} yen", apple.price());
// 所有権を消費するメソッドの呼び出し
meat.consume();
}
解答(リンク)
関数のシグネチャ部分により、どんなメソッドなのかを分かりやすく表現できます。
- 参照を取るメソッド(データの構造を取得するが、変更はしないことがシグネチャから分かる)
- 可変参照を取るメソッド(データを変更する可能性がある)
- 所有権を消費するメソッド(データを消費する、それ以降でデータを利用しない)
コード (詳細)
enum Item {
Apple(u32), // 価格
Meat(u32), // 価格
}
impl Item {
// 参照を取るメソッド(データの構造を取得するが、変更はしないことがシグネチャから分かる)
fn price(&self) -> u32 {
match self {
Item::Apple(price) => *price,
Item::Meat(price) => *price,
}
}
// 可変参照を取るメソッド(データを変更する可能性がある)
fn increase_price(&mut self, amount: u32) {
match self {
Item::Apple(price) => *price += amount,
Item::Meat(price) => *price += amount,
}
}
// 所有権を消費するメソッド(データを消費する)
fn consume(self) {
match self {
Item::Apple(price) => println!("Consumed apple worth {} yen", price),
Item::Meat(price) => println!("Consumed meat worth {} yen", price),
}
}
}
fn main() {
let mut apple = Item::Apple(300);
let meat = Item::Meat(1200);
// 参照メソッドの呼び出し
println!("Apple price: {} yen", apple.price());
// 可変参照メソッドの呼び出し
apple.increase_price(100);
println!("Increased apple price: {} yen", apple.price());
// 所有権を消費するメソッドの呼び出し
meat.consume();
}
関数ポインタを使いこなそう
問(リンク)
add
関数をfp1
とfp2
に関数ポインタとして定義しましょう。
コード (詳細)
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
// TODO: fp1とfp2に関数ポインタを適用してください。
let fp1 = add;
let fp2 = add;
assert!(fp1 == fp2, "{:?} != {:?}", fp1, fp2);
}
解答(リンク)
let op:
のように明示的に関数ポインタを定義してから、代入しましょう。
コード (詳細)
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let op: fn(i32, i32) -> i32 = add; // 関数ポインタとして利用するためには明示的なfn型に変換が必要
let fp1 = op;
let fp2 = op;
assert!(fp1 == fp2, "{:?} != {:?}", fp1, fp2);
}
クロージャーを使って環境中の変数を活用しましょう
問(リンク)
環境中のa
とc
をdivide_and_add
に活用できるようにしましょう。
コード (詳細)
fn main() {
let a = 10;
let c = 5;
// TODO: クロージャーとしてdivide_and_addを導入することでコンパイルエラーを解消してください。
fn divide_and_add(b: i32) -> i32 {
if b != 0 {
a / b + c
} else {
c
}
}
println!("Divide: {}", divide_and_add(1));
println!("Divide: {}", divide_and_add(0));
}
解答(リンク)
クロージャーを使うことで環境中の変数を利用することができます。
コード (詳細)
fn main() {
let a = 10;
let c = 5;
let divide_and_add = |b: i32| if b != 0 { a / b + c } else { c }; // クロージャーで定義することで、環境にある変数(a, c)を使った関数にすることができる。
println!("Divide: {}", divide_and_add(1));
println!("Divide: {}", divide_and_add(0));
}
さいごに
もしも本リポジトリで不備などあれば、リポジトリのissueやPRなどでご指摘いただければと思います。