はじめに
こんにちは。細々とプログラミングをしているsotanengelです。
この記事は以下の記事の連載です。
他の連載記事 (詳細)
また本記事はEffective Rust(David Drysdale (著), 中田 秀基 (翻訳))を参考に作成されております。とてもいい書籍ですので興味を持った方は、ぜひ読んでみてください!
今日の内容
概要
Rustには似たような機能を提供する
- ジェネリティクス
- トレイトオブジェクト
が存在します。
そこで今回はこれらの手法のトレードオフを学びましょう。
ジェネリクスでトレイト制約を書いてみよう
問(リンク)
print_area
にArea
トレイト制約を追加してください。
コード (詳細)
// 面積を計算するトレイト
trait Area {
fn area(&self) -> f64;
}
// 長方形の構造体
struct Rectangle {
width: f64,
height: f64,
}
// 円の構造体
struct Circle {
radius: f64,
}
// トレイトをRectangleに実装
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// トレイトをCircleに実装
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
// TODO: Areaのトレイト制約を追加してください。
fn print_area(shape: &T) {
println!("面積は: {:.2}", shape.area());
}
fn main() {
let rectangle = Rectangle {
width: 10.0,
height: 5.0,
};
let circle = Circle { radius: 7.5 };
print_area(&rectangle);
print_area(&circle);
}
解答(リンク)
コード参照。
コード (詳細)
// 面積を計算するトレイト
trait Area {
fn area(&self) -> f64;
}
// 長方形の構造体
struct Rectangle {
width: f64,
height: f64,
}
// 円の構造体
struct Circle {
radius: f64,
}
// トレイトをRectangleに実装
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// トレイトをCircleに実装
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
// Areaの面積を計算するジェネリック関数
fn print_area<T: Area>(shape: &T) {
println!("面積は: {:.2}", shape.area());
}
fn main() {
let rectangle = Rectangle {
width: 10.0,
height: 5.0,
};
let circle = Circle { radius: 7.5 };
print_area(&rectangle);
print_area(&circle);
}
トレイトオブジェクトでトレイト制約を書いてみよう
問(リンク)
先ほどと同じコードに対して、トレイトオブジェクトで制約を書いてみましょう。
コード (詳細)
// 面積を計算するトレイト
trait Area {
fn area(&self) -> f64;
}
// 長方形の構造体
struct Rectangle {
width: f64,
height: f64,
}
// 円の構造体
struct Circle {
radius: f64,
}
// トレイトをRectangleに実装
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// トレイトをCircleに実装
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
// TODO: Areaのトレイト制約を「トレイトオブジェクト」で追加してください。
fn print_area(shape: ) {
println!("面積は: {:.2}", shape.area());
}
fn main() {
let rectangle = Rectangle {
width: 10.0,
height: 5.0,
};
let circle = Circle { radius: 7.5 };
print_area(&rectangle);
print_area(&circle);
}
解答(リンク)
コード参照。
コード (詳細)
// 面積を計算するトレイト
trait Area {
fn area(&self) -> f64;
}
// 長方形の構造体
struct Rectangle {
width: f64,
height: f64,
}
// 円の構造体
struct Circle {
radius: f64,
}
// トレイトをRectangleに実装
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// トレイトをCircleに実装
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
// Areaの図形の面積を計算する関数(トレイトオブジェクト版)
fn print_area(shape: &dyn Area) {
println!("面積は: {:.2}", shape.area());
}
fn main() {
let rectangle = Rectangle {
width: 10.0,
height: 5.0,
};
let circle = Circle { radius: 7.5 };
print_area(&rectangle);
print_area(&circle);
}
結局どっちがいいの?
書籍では以下のようにまとめられていました。
- ジェネリクスを使うとコードサイズが大きくなる
- 全ての型についてコンパイル時にコードがコピーされてしまうため
- ジェネリクスのトレイトメソッドの呼び出しはトレイトオブジェクトよりも少し速い
- 参照解消の経路のため
- ジェネリクスの方がコンパイル時間が長い傾向がある
トレイト制約をトレイトに使用しましょう
問(リンク)
Area
トレイトをShape
にトレイト制約として実装しましょう。
コード (詳細)
// 面積を計算する基本トレイト
trait Area {
fn area(&self) -> f64;
}
// TODO: Areaのトレイト制約をShapeに追加してください。
trait Shape {
fn double_area(&self) -> f64 {
self.area() * 2.0
}
}
// 長方形の構造体
struct Rectangle {
width: f64,
height: f64,
}
// 円の構造体
struct Circle {
radius: f64,
}
// AreaトレイトをRectangleに実装
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// ShapeトレイトをRectangleに実装
impl Shape for Rectangle {}
// AreaトレイトをCircleに実装
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
// ShapeトレイトをCircleに実装
impl Shape for Circle {}
// Shapeを受け取る関数
fn print_double_area<T: Shape>(shape: &T) {
println!("2倍の面積は: {:.2}", shape.double_area());
}
fn main() {
let rectangle = Rectangle {
width: 10.0,
height: 5.0,
};
let circle = Circle { radius: 7.5 };
print_double_area(&rectangle);
print_double_area(&circle);
}
解答(リンク)
コード参照。
コード (詳細)
// 面積を計算する基本トレイト
trait Area {
fn area(&self) -> f64;
}
// 面積を拡張するトレイト(Areaを制約として持つ)
trait Shape: Area {
fn double_area(&self) -> f64 {
self.area() * 2.0
}
}
// 長方形の構造体
struct Rectangle {
width: f64,
height: f64,
}
// 円の構造体
struct Circle {
radius: f64,
}
// AreaトレイトをRectangleに実装
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// ShapeトレイトをRectangleに実装
impl Shape for Rectangle {}
// AreaトレイトをCircleに実装
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
// ShapeトレイトをCircleに実装
impl Shape for Circle {}
// Shapeを受け取る関数
fn print_double_area<T: Shape>(shape: &T) {
println!("2倍の面積は: {:.2}", shape.double_area());
}
fn main() {
let rectangle = Rectangle {
width: 10.0,
height: 5.0,
};
let circle = Circle { radius: 7.5 };
print_double_area(&rectangle);
print_double_area(&circle);
}
トレイトオブジェクトを使って、配列に異なる構造体を格納できるようにしましょう
問(リンク)
shapes
配列にRectangle
やCircle
を格納して、同一のforループで処理できるようにしましょう。
コード (詳細)
// 面積を計算する基本トレイト
trait Area {
fn area(&self) -> f64;
}
// 長方形の構造体
struct Rectangle {
width: f64,
height: f64,
}
// 円の構造体
struct Circle {
radius: f64,
}
// AreaトレイトをRectangleに実装
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// AreaトレイトをCircleに実装
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
// TODO: トレイトオブジェクトとしてAreaを追加してください。
fn print_area(shape: Area) {
println!("面積は: {:.2}", shape.area());
}
fn main() {
let rectangle = Rectangle {
width: 10.0,
height: 5.0,
};
let circle = Circle { radius: 7.5 };
// 異なる型の参照をVecに格納
let shapes: Vec<&dyn Area> = vec![&rectangle, &circle];
// ループで面積を計算
for shape in &shapes {
print_area(*shape);
}
}
解答(リンク)
コード参照。
コード (詳細)
// 面積を計算する基本トレイト
trait Area {
fn area(&self) -> f64;
}
// 長方形の構造体
struct Rectangle {
width: f64,
height: f64,
}
// 円の構造体
struct Circle {
radius: f64,
}
// AreaトレイトをRectangleに実装
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// AreaトレイトをCircleに実装
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
// 全ての図形の面積を表示する関数
fn print_area(shape: &dyn Area) {
println!("面積は: {:.2}", shape.area());
}
fn main() {
let rectangle = Rectangle {
width: 10.0,
height: 5.0,
};
let circle = Circle { radius: 7.5 };
// 異なる型の参照をVecに格納
let shapes: Vec<&dyn Area> = vec![&rectangle, &circle];
// ループで面積を計算
for shape in &shapes {
print_area(*shape);
}
}
さいごに
もしも本リポジトリで不備などあれば、リポジトリのissueやPRなどでご指摘いただければと思います。