<T>
(Type)でジェネリックなデータ型を示すOption<T>
,Vec<T>
, ...- 使い方
struct Hoge<T> { foo: T, bar: T, buz: f64, } fn main() { // Hoge<i32>となる let hoge = Hoge { foo: 3, bar: 5, buz: 7.0, }; println!("{}, {}, {:.*}", hoge.foo, hoge.bar, 2, hoge.buz); }
-
「Tに対してジェネリックである関数」
fn takes_anything<T> (x: T) { // ... }
- xはT型である
-
複数のジェネリックな型を取る関数も可能
fn takes_anything<T, U> (x: T, y : U) { // ... }
-
struct内にジェネリックな型を宣言する
struct Point<T> { x: T, y: T, } fn main() { let int_point = Point { x: 0, y: 0 }; // T: i32 let float_point = Point { x: 0.0, y: 0.0 }; // T: f64 }
ある型が提供しなければならない機能をコンパイラに伝える。
- 型クラス
- インターフェース
- メソッドのシグネチャを定義する
- ある型のためにトレイトを実装する
trait HasArea {
fn area(&self) -> f64;
}
struct Circle {
x: f64,
y: f64,
r: f64,
}
impl HasArea for Circle {
fn area(&self) -> f64 { // トレイト'HasArea'のareaシグネチャの実装
std::f64::consts::PI * (self.r * self.r)
}
}
fn main() {
let c = Circle {
x: 0.0,
y: 0.0,
r: 1.0,
};
assert_eq!("3.14", format!("{:.*}", 2, c.area()));
}
ジェネリックな型において、特定のメソッドの呼び出しはエラーとなります。
fn print_area<T>(shape: T) {
println!("this shape has an area of {}", shape.area()); // T型がarea()を持っているかわからないのでエラー
}
トレイトを利用して、ある型における振る舞いの境界を追加する(制約を課る)ことが可能です。
fn print_area<T: HasArea>(shape: T) { // トレイト'HasArea'を実装する型に制約する
println!("this shape has an area of {}", shape.area());
}
型パラメータ(T)に境界を追加することができる(RectangleのT型はPartialEqトレイトを実装している必要がある)。
struct Rectangle<T> {
x: T,
y: T,
width: T,
height: T,
}
// 構造体Rectangleの型パラメータTに境界を追加
impl <T: PartialEq> Rectangle<T> {
fn is_square(&self) -> bool {
self.width == self.height
}
}
fn main() {
let r = Rectangle {
x: 0.0,
y: 0.0,
width: 3.0,
height: 3.0,
};
assert!(r.is_square());
// // PartialEqトレイトを実装していないパラメータの場合はエラーとなる
// struct Hoge;
// let r = Rectangle {
// x: Hoge{},
// y: Hoge{},
// width: Hoge{},
// height: Hoge{},
// };
assert!(r.is_square());
}
- 同じスコープに定義されていないトレイトは適用されない
- 違うスコープのトレイトは
use
する必要がある
- 違うスコープのトレイトは
- トレイトやimplの対象となる型は実装元が同じでなければならない
- 例えば
i32
のToString
トレイトはRustによって提供されているため、再実装できない
- 例えば
+
で複数の制約を課することが可能です。
use std::fmt::Debug;
fn foo<T: Clone + Debug>(x: T) { // CloneとDebugトレイトの実装を要求
let x1 = x.clone();
println!("{:?}", x1);
}
トレイト境界はwhere
節で表現することもできます。
記述が長くなる場合に有効です。
use std::fmt::{Display, Debug}; // Display, Debugトレイトを扱うためuseする必要がある
fn bar<T, K>(x: T, y: K) where
T: Clone + Display,
K: Clone + Debug {
let x1 = x.clone();
println!("{}", x1);
let clone = y.clone();
println!("{:?}", clone);
}
fn main() {
bar(3, "Hello");
}
トレイトはデフォルト実装を定義できます。
trait Foo {
fn is_valid(&self) -> bool;
fn is_invalid(&self) -> bool { !self.is_valid() }
}
struct Hoge;
impl Foo for Hoge {
fn is_valid(&self) -> bool {
true
}
}
fn main() {
let h = Hoge{};
assert!(h.is_valid());
assert!(!h.is_invalid());
}
トレイトは継承可能です。
次の例の場合、HogeはBarの他にFooの実装もしなければなりません。
trait Foo {
fn foo(&self) -> &Self;
}
trait Bar: Foo {
fn bar(&self) -> &Self;
}
struct Hoge;
impl Foo for Hoge {
fn foo(&self) -> &Hoge { println!("foo() called"); self }
}
impl Bar for Hoge {
fn bar(&self) -> &Hoge { println!("bar() called"); self }
}
fn main() {
let h = Hoge{};
h.foo().bar();
}
特定のトレイトは[derive]
アトリビュートでも定義できます。
use std::fmt::Debug;
#[derive(Debug, Clone)]
struct Hoge;
fn foo<T: Clone + Debug>(x: T) {
let x1 = x.clone();
println!("{:?}", x1);
}
fn main() {
foo(&Hoge);
}
- core::maker::Copy
- core::clone::Clone
- core::cmp::Eq
- core::cmp::Ord
- core::cmp::PartialEq
- core::cmp::PartialOrd
- core::fmt::Debug
- core::fmt::Default
- core::hash::Hash
トレイトは基本的にコンパイラで静的にディスパッチ(静的ディスパッチ)されますですが、 Rustではトレイトオブジェクトと呼ばれるメカニズムで動的ディスパッチもサポートしています。
まずはサンプルのトレイトとその実装です。
trait Foo {
fn method(&self) -> String;
}
impl Foo for u8 {
fn method(&self) -> String { format!("u8: {}", *self) }
}
impl Foo for String {
fn method(&self) -> String { format!("string: {}", *self) }
}
静的ディスパッチではつぎのようになります。
fn do_something<T: Foo>(x: T) {
println!("{}", x.method());
}
fn main() {
let x = 5u8;
let y = "Hello".to_string();
do_something(x);
do_something(y);
}
静的ディスパッチされたコードのイメージはつぎのようになります。 コンパイル時に特殊化された関数(ここではdo_something_u8, do_something_string)がインライン展開されます。
fn do_something_u8(x: u8) {
println!("{}", x.method());
}
fn do_something_string(x: String) {
println!("{}", x.method());
}
fn main() {
let x = 5u8;
let y = "Hello".to_string();
do_something_u8(x);
do_something_string(y);
}
静的ディスパッチでは型ごとに同じ関数をコピーすることになり、コードの膨張が発生してしまします。
多くの場合静的ディスパッチが効率的ですが、 命令キャッシュの肥大化が問題となり、時として動的ディスパッチが効率的となる場合があります。
動的ディスパッチはトレイトオブジェクトと呼ばれる機能によって提供されます。
トレイトオブジェクトは&Foo
、Box<Foo>
と記述され、 指定されたトレイトを実装するあらゆる型の値を保持する通常の値 です。
実行時に正確な型が判明します。
fn do_something(x: &Foo) { // 通常の値として&Fooを受け取る
println!("{}", x.method());
}
fn main() {
let x = 5u8;
let y = "Hello".to_string();
do_something(&x as &Foo); // 1. トレイトを実装した型を指すポインタからキャストしてトレイトオブジェクトを取得する
do_something(&y); // 2. トレイトを実装した型を指すポインタを渡し、関数側で型強制する(最終的には1.と同じ)
}
すべてのトレイトがトレイトオブジェクトとして使えるわけではありません。 トレイトオブジェクトにできるのは下記の条件を満たしたオブジェクト安全なトレイトのみです。
- トレイトが
Self: Sized
を要求しないこと(トレイトは実行時にどの構造体がくるかわからず、サイズを特定できないため) - トレイトのメソッドすべてがオブジェクト安全であること
Self: Sized
を要求しないこと- どのような型パラメータも持ってはならない
- Selfを使ってはならない
ざっくりと言うと トレイトのメソッドでSelf
を使うとオブジェクト安全ではなくなる とのことです。
fn main() {
let plus_one = |x: i32| {
x + 1
};
assert_eq!(2, plus_one(1));
}
fn plus_one (x: i32) -> i32 { x + 1 };
let plus_one = |x: i32| -> i32 { x + 1 };
let plus_one = |x: i32| -> x + 1;
クロージャではそれを囲んでいるスコープの束縛を含むことができます。
fn main() {
let num = 5;
let plus_num = |x: i32| x + num; // 束縛numを参照
assert_eq!(6, plus_num(1));
}
ですが、クロージャでは 束縛を借用します 。つぎのようなコードはエラーとなります。
fn main() {
let num = 5;
let plus_num = |x: i32| x + num; // 束縛numを参照
let y = &mut num; // エラー: 束縛numのミュータブルな参照
assert_eq!(6, plus_num(1));
}
move
キーワードでクロージャに環境の所有権を取得することを強制できます。
fn main() {
let mut num = 5;
{
let mut add_num = move |x: i32| num += x; // i32はCopyトレイトを実装しているので、コピーされた値の所有権がmoveされる
add_num(5);
}
assert_eq!(5, num);
}
クロージャは実質的にトレイトへのシンタックスシュガーとなります。 クロージャを引数に取る場合はトレイトと同様に行います。
fn main() {
fn call_with_one<F>(some_closure: F) -> i32 where
F: Fn(i32) -> i32 { // Where節でトレイト境界を定義
some_closure(1)
}
let answer = call_with_one(|x| x + 2); // トレイト境界を満たすクロージャを引数として渡す
assert_eq!(3, answer);
}
動的ディスパッチの場合はトレイトオブジェクトとして扱います。
fn main() {
// トレイトオブジェクトを引数に取る
fn call_with_one(some_closure: &Fn(i32) -> i32) ->i32 {
some_closure(1)
}
let answer = call_with_one(&|x| x + 2); // クロージャの参照を渡して関数側で型強制する
assert_eq!(3, answer);
}
クロージャを引数として期待している関数に関数ポインタを渡すことができます。
fn main() {
fn call_with_one(some_closure: &Fn(i32) -> i32) ->i32 {
some_closure(1)
}
fn add_two(x: i32) -> i32 {
x + 2
}
let answer = call_with_one(&add_two); // 関数ポインタを渡す
assert_eq!(3, answer);
}
クロージャ(Fn
)はトレイトなのでサイズが不定となり、そのまま返すことはできません。
またクロージャは環境別にstructを生成するため、それぞれの型は匿名となり、帰り値の型を一致しません。
そこでFn
をボックス化(ヒープに確保)してトレイトオブジェクトを返します。
またクロージャでは束縛を借用するので、moveクロージャとすることで参照ではなく所有権の移動します。
fn main() {
fn factory() -> Box<Fn(i32) -> i32> { // クロージャをヒープに確保した上で返す
let num = 5;
Box::new(move |x| x + num) // numの所有権を移動する
}
let f = factory(); // クロージャを受け取る
let answer = f(1);
assert_eq!(6, answer);
}
定数を定義します。型を明示する必要があります。プログラム全体のライフタイムの間生きます。
定数はコンパイラでインライン化されるため、同じ定数への参照が同じアドレスを指しているとは限らないので注意してください。
const N: i32 = 5;
グローバル変数。型を明示する必要があります。プログラム全体のライフタイムの間生きます。
定数と違いコンパイラでインライン化されず、ただひとつのインスタンスのみが存在します。
static N: i32 = 5;
static NAME: &'static str = "Taro";
staticな変数もmut
でミュータブルにできますが、スレッドセーフではなくなるためunsafe
ブロック内でアクセスする必要があります。
fn main() {
static mut HELLO: &'static str = "Hello";
// assert_eq!("Hello", HELLO); unsaafeブロック内ではないのでエラー
unsafe {
assert_eq!("Hello", HELLO);
HELLO = "こんにちは";
assert_eq!("こんにちは", HELLO);
}
}
type
キーワードで型へのエイリアスを宣言できます。
fn main() {
type Num = i32;
let x: i32 = 3;
let y: Num = 3;
assert_eq!(y, x);
}