ジェネリクス
構造体の型を構造体を生成するときに決めるやり方。
ジェネリクスなデータ型
以下の構造体Point
が持つ値の型はi8。
struct Point {
x: i8,
y: i8
}
fn main() {
let point = Point::<i8> {x: 10, y: 20};
println!("{}, {}", point.x, point.y);
}
ここで同じような構造体だが、値の型がi32の構造体も作りたいとき、
同じような構造体を2つ定義するのは効率が悪い。
そこにジェネリクスなデータ型を使う
struct Point<T> {
x: T,
y: T
}
fn main() {
let point_i8 = Point::<i8> {x: 10, y: 20}; // これはパラメータの型がi8
println!("{}, {}", point_i8.x, point_i8.y);
let point_i32 = Point::<i32> {x: 200, y: 3400}; // これはパラメータの型がi32
println!("{}, {}", point_i32.x, point_i32.y);
}
いろいろな書き方
- 基本
let point = Point::<i32> { x: 300, y: 400};
- 左辺で型を定義している
let point: Point<i32> = Point { x: 300, y: 400};
- フィールドの型から型パラメータをRustが推論
let point = Point { x: 300i32, y: 400i32};
- 整数リテラルはデフォルトi32
let point = Point { x: 300, y: 400}; // 300, 400はそれぞれデフォルトの300i32, 400i32になる
ジェネリック関数
メソッドにジェネリクスな型を使う。
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> { // impl, 構造体名両方にジェネリックパラメータTが必要
fn new(x: T, y: T) -> Point<T> {
Point::<T> { x: x, y: y }
}
fn update(&mut self, x: T, y: T) {
self.x = x;
self.y = y;
}
}
fn main() {
let mut point = Point::<i8>::new(8, 8);
point.update(10, 10);
println!("{}, {}", point.x, point.y);
}
トレイト
異なる構造体同士がもつ共通の機能を定義する。
使い方として、トレイトを定義して、構造体に実装する。
// トレイトを定義する
trait Sensor {
fn read(&self) -> u32;
// メソッド内容を書いたらデフォルトの動作になる
// fn read(&self) -> u32 {
// 43
// }
}
// これまで同様に構造体を定義
struct LightSensor {
value: u32,
}
struct TemperatureSensor {
value: f32,
}
// LightSensorにトレイトSensorを実装する
impl Sensor for LightSensor {
fn read(&self) -> u32 {
self.value
}
}
// TemperatureSensorにトレイトSensorを実装する
impl Sensor for TemperatureSensor {
fn read(&self) -> u32 {
self.value as u32
}
}
fn main() {
let light_sensor = LightSensor { value: 42 };
let temp_sensor = TemperatureSensor { value: 35.5 };
println!("light sensor value = {}", light_sensor.read()); // メソッドのように使用できる
println!("temp sensor value = {}", temp_sensor.read());
}
出力結果
light sensor value = 42
temp sensor value = 35
トレイトを関数パラメータとして使用する
特定のトレイトを実装した構造体をパラメータに取ることを指定
// ~~ 略 ~~
// トレイトを実装した構造体を引数にとる関数
fn print_sensor_value(sensor: impl Sensor) { // トレイトSensorを実装した構造体を引数に指定
println!("sensor value: {}", sensor.read());
}
fn main() {
let light_sensor = LightSensor { value: 42 };
let temp_sensor = TemperatureSensor { value: 35.5 };
println!("light sensor value = {}", light_sensor.read()); // メソッドのように使用できる
println!("temp sensor value = {}", temp_sensor.read());
print_sensor_value(temp_sensor);
}
出力結果
light sensor value = 42
temp sensor value = 35
sensor value: 35
もちろん、Sensorを実装していない構造体を入れたらコンパイルエラーとなる
// ~~ 略 ~~
// トレイト`Sensor`を実装していない構造体
struct InvalidSensor {
value: u32,
}
fn main() {
let light_sensor = LightSensor { value: 42 };
let temp_sensor = TemperatureSensor { value: 35.5 };
let invalid_sensor = InvalidSensor { value: 0 };
println!("light sensor value = {}", light_sensor.read()); // メソッドのように使用できる
println!("temp sensor value = {}", temp_sensor.read());
print_sensor_value(temp_sensor);
print_sensor_value(invalid_sensor); // Sensorを実装していない構造体を引数に入れてしまう
}
出力結果
error[E0277]: the trait bound `InvalidSensor: Sensor` is not satisfied
--> src/main.rs:46:24
|
46 | print_sensor_value(invalid_sensor);
| ------------------ ^^^^^^^^^^^^^^ the trait `Sensor` is not implemented for `InvalidSensor`
| |
| required by a bound introduced by this call
|
note: required by a bound in `print_sensor_value`
--> src/main.rs:34:36
|
34 | fn print_sensor_value(sensor: impl Sensor) {
| ^^^^^^ required by this bound in `print_sensor_value`
トレイト境界
トレイト境界は型パラメータに制約を追加する
先に使用した
fn print_sensor_value(sensor: impl Sensor) {
println!("sensor value: {}", sensor.read());
}
は以下のシンタックスシュガー
fn print_sensor_value<S: Sensor>(sensor: S) {
println!("sensor value: {}", sensor.read());
}
上記の:Sensor
がトレイト境界
複数の制約
以下はSensorトレイトとDebugトレイト両方が実装された構造体を引数に取るように制約をかけている
fn print_sensor_value<S: Sensor + Debug>(sensor: S) {
println!("sensor value: {}", sensor.read());
}
トレイトが複雑になってくると、whereを使うほうが見やすい
fn print_sensor_value<S, T>(sensor: S, t: T)
where S: Sensor + Debug,
T: Debug + Clone
{
// ~~~~
}