トレイトオブジェクトの保持とライフタイム
トレイトオブジェクトとライフタイム
トレイトオブジェクトを保持する場合は、ライフタイムが必要となる時がある。
- ジェネリクス型パラメーター
- 参照型
トレイトオブジェクト x ジェンリクス型パラメーター
struct Zoo { animals: Vec<Box<dyn Animal>>, // trait Animal { .. } impl Zoo { // 型パラメーターを用いる fn insert<A>(&mut self, animal: A) where A: Animal { self.animals.push(Box::new(animal)); } } /* error[E0310]: the parameter type `A` may not live long enough | 17 | fn insert<A>(&mut self, animal: A) | - help: consider adding an explicit lifetime bound...: `A: 'static` ... 21 | self.animals.push(Box::new(animal)); | ^^^^^^^^^^^^^^^^ ...so that the type `A` will meet its required lifetime bounds */
ライフタイムを付与する。
struct Zoo<'a> { animals: Vec<Box<dyn Animal + 'a>>, } impl<'a> Zoo<'a> { fn insert<A>(&mut self, animal: A) where A: Animal + 'a { self.animals.push(Box::new(animal)); } }
トレイトオブジェクト x 参照型
トレイトオブジェクトに参照型を指定する場合は、ライフタイムを付ける必要がある。
impl<'a> Quack for &'a bool {} struct MasterQuack { q: Box<dyn Quack>, // trait Quack .. } let a = true; MasterQuack {q: Box::new(&a)}; /* error[E0597]: `a` does not live long enough | 19 | MasterQuack {q: Box::new(&a)}; | ---------^^- | | | | | borrowed value does not live long enough | cast requires that `a` is borrowed for `'static` 20 | } | - `a` dropped here while still borrowed */
ライフタイムを付与する
struct MasterQuack<'a> { q: Box<dyn Quack + 'a>, }
トレイトオブジェクト x クロージャー
pub struct MessageHandler<T: Hash + Eq, U> { handlers: HashMap<T, Vec<Box<dyn Fn(&U)>>>, } impl<T, U>MessageHandler<T, U> where T: Hash + Eq, { pub fn on<F>(&mut self, event: T, listener: F) where F: Fn(&U), { let listeners = self.handlers.entry(event).or_insert(vec![]); listeners.push(Box::new(listener)); } error[E0310]: the parameter type `F` may not live long enough | 18 | pub fn on<F>(&mut self, event: T, listener: F) | - help: consider adding an explicit lifetime bound...: `F: 'static` ... 23 | listeners.push(Box::new(listener)); | ^^^^^^^^^^^^^^^^^^ ...so that the type `F` will meet its required lifetime bounds
トレイトオブジェクト x 具体型
具体型を指定する場合は、ライフタイムの指定は不要。
struct Zoo<'a> { animals: Vec<Box<dyn Animal + 'a>>, } impl<'a> Zoo<'a> { // ジェネリクス型パラメーターは、ライフタイムが必要 fn insert<A>(&mut self, animal: A) where A: Animal + 'a { self.animals.push(Box::new(animal)); } // 具体型は、ライフタイムが不要 fn insert(&mut self, monkey: Monkey) { self.animals.push(Box::new(monkey)); } }
トレイト - 共通の振る舞いを定義する
https://doc.rust-lang.org/book/ch10-02-traits.html
トレイト: 共通の振る舞いの定義
概要
- トレイトを使って共通の振る舞いを定義
- トレイトのデフォルト実装
- トレイトをパラメーターに指定できる
impl Trait
- トレイト境界
impl Trait
のシンタックスシュガー - 複数のトレイト境界を指定できる
+ Sntax
- 明快なwhere節を使ったトレイト境界
- トレイト実装型を返す
impl Trait
- 異なる型をダイナミックに選択して返すことはできない
- トレイト境界を使ってlargest関数を修正
- トレイト境界を使って条件的にメソッドを実装する
トレイトを使って共通の振る舞いを定義
トレイトの定義
共通の振る舞いをトレイトとして抽出する。
トレイトの実装
共通の振る舞いを持つ型にたいしてトレイトを実装する。
トレイトのデフォルト実装
トレイトをパラメーターに指定できる impl Trait
トレイト境界 impl Trait
のシンタックスシュガー
複数のトレイト境界を指定できる + Sntax
明快なwhere節を使ったトレイト境界
トレイト実装型を返す impl Trait
* 異なる型をダイナミックに選択して返すことはできない
トレイト境界を使ってlargest関数を修正
ジェネリック型パラメーターを使った関数
// Listing 10-5: A definition of the largest function that uses 「generic type parameters」 but doesn’t compile yet fn largest<T>(list: &[T]) -> T { let mut larget = list[0]; for &item in list { // error[E0369]: binary operation `>` cannot be applied to type `&T` if item > largest { largest = item; } } largest }
&T
型はバイナリオペレーターを適用することができないというエラーが表示される。
>
オペレーターは、std::cmp::PartialOrd
トレイトで定義されているため、T
に対してPartialOrd
トレイト境界を指定すれば良い。
fn larget<T: PartialOrd>(list: &[T]) -> &T {
しかしスライス(配列も)型、共有参照型から所有権を移動することができない。
$ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0508]: cannot move out of type `[T]`, a non-copy slice --> src/main.rs:2:23 | // スライスから所有権を移動させることはできない 2 | let mut largest = list[0]; | ^^^^^^^ | | | cannot move out of here | move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait | help: consider borrowing here: `&list[0]` Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0507]: cannot move out of a shared reference --> src/main.rs:4:18 | // パターンマッチにより所有権が移動させようとしている 4 | for &item in list { | ----- ^^^^ | || | |data moved here | |move occurs because `item` has type `T`, which does not implement the `Copy` trait | help: consider removing the `&`: `item`
Copy
トレイトをつけることによって解消することができる。(ただしCopyトレイトを実装していない型では使えなくなる)
その場合は、Clone
トレイトをつけることができる。
fn largest<T: PartialOrd + Copy>(list: &[T] -> &T {
トレイト境界を使って条件的にメソッドを実装する
関連
Advanced Traits - The Rust Programming Language
Object Oriented Programming Features of Rust - The Rust Programming Language
トレイトの選択 完全限定構文 (Fully Qualified Syntax)
Fully Qualified Syntax
Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
fully qualified syntax (完全限定構文?)
<Type as Trait>::function(receiver_if_method, next_arg, ...);
概要
Rustの型(struct, enum)は複数のトレイトを実装することができる。 名前が重複した場合は、どのトレイトの関数を呼び出すかを指定する。
trait A { fn name() -> String; } trait B { fn name() -> String; } struct T {} impl A for T { fn name() -> String { "I'm a A".to_string() } } impl B for T { fn name() -> String { "I'm a B".to_string() } } fn main() { println!("{:?}", <T as A>::name()); // fully qualified syntaxでトレイトを指定する println!("{:?}", <T as B>::name()); }
Iterator / IntoIterator / FromIterator
trait Iterator
pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>;
実装している主要な型
Note:
Vec<T>や配列[T]はIteratorではない。 これらがfor文で使えるのは、暗黙的にIntoIteratorのメソッドが呼ばれるため。
std::slice::Iter<'a, T>
impl<'a, T> Iterator for Iter<'a, T> type Item = &'a T pub fn next(&mut self) -> Option<&'a T>
trait IntoIterator
- 配列 [T]
Vec<T>
配列
[T; N]
要素がCopyトレイト関わらず、所有権を失う。
impl<T, const N: usize> IntoIterator for [T; N] type Item = T type IntoIter = IntoIter<T, N> pub fn into_iter(self) -> <[T; N] as IntoIterator>::IntoIter
&'a [T; N]
impl<'a, T, const N: usize> IntoIterator for &'a [T; N] type Item = &'a T type IntoIter = Iter<'a, T> pub fn into_iter(self) -> Iter<'a, T>
Vec<T>
要素がCopyトレイトか関わらず所有権を失う
impl<T, A> IntoIterator for Vec<T, A> type Item = T type IntoIter = IntoIter<T, A> pub fn into_iter(self) -> IntoIter
`&Vec
impl<'a, T> IntoIterator for &'a Vec<T> tpye Item = &'a T type IntoIter = Iter<'a, T> pub fn into_iter(self) -> Iter<'a, T>
FromIterator<A>
collect()
で変換できるCollectionはFromIterator<Self::Item>
トレイトが実装されている必要がある。
反対のトレイトとして、IntoIterator
がある。
Vec<_> への変換
impl<T> FromIterator<T> for Vec<T>
let five_fives = std::iter::repeat(5).take(5); let v: Vec<i32> = five_fives.collect();
charsからStringの生成
impl FromIterator<char> for String
let chars = ['h', 'e', 'l', 'l', 'o']; let text = chars.iter().collect::<String>();
クロージャー
概要
- Rustのラムダ式はクロージャーと呼ぶ
- クロージャーの種類 (キャプチャー方法が異なる)
FnOnce<Args>
:self
メソッド, キャプチャー変数を消費する。FnMut<Args>
:&mut self
メソッド, 環境を変更するため、値を借用する。Fn<Args>
:&self
メソッド, 環境から不変的に値を借用する。
- 所有権をクロージャー(Fn・FnMut)に移動するには、
move
キーワードを使用する
クロージャー | キャプチャー方法 |
---|---|
FnOnce | 移動 |
FnMut | ミュータブル参照 |
Fn | コピー |
詳細
trait FnOnce
- 外部変数のキャプチャーは、所有権を移動する
- クロージャーを実行できるのは一回のみ
Exmaple
クロージャー内部で所有権を移動すると自動でFnOnce
トレイトになる
let m: String = String::from("hello"); let fn_once = || { let s = m; // 所有権を移動 ... })
クロージャーは一回だけ実行できる
fn take_once<F: FnOnce()>(f: F) { f(); // 内部で所有権が移動している f(); // NG }
trait FnMut: FnOnce
Example
クロージャー内部で外部変数を変更できる
let mut s = String::from(""); let mut fnmut = |str| { let m = &mut s; m.push_str(str); }; fnmut("hi"); fnmut(" "); fnmut("there"); let mut count = 0; let mut counter = || { count += 1; // mut変数をキャプチャーするとFnMutクロージャーになる }; counter(); counter(); }
trait Fn: FnMut
- 外部変数は全てコピーされる (キャプチャーする値はCopyトレイトを実装している必要がある)
Example
#[derive(Debug,Copy, Clone)] struct Foo(i32); impl Foo { fn increment(&mut self) { self.0 += 1; } } fn call_times<F: Fn()>(time: i32, f: F) { for _ in 0..time { f(); } } fn main() { let f = Foo(3); call_times(10, || { let mut s = f; // Copyが発生する s.increment(); }); dbg!(f.0); // 3 }
所有権の強制移動 move
キーワード
ローカル変数の値をキャプチャーしたクロージャーを返すとき、ローカル変数の値の所有権をクロージャーに移す必要がある。
fn counter(start: i32) -> impl FnMut() -> i32 { let mut s = start; return || { s += 1; s }; } /* error[E0373]: closure may outlive the current function, but it borrows `s`, which is owned by the current function | 4 | return || { | ^^ may outlive borrowed value `s` 5 | s += 1; | - `s` is borrowed here error[E0373]: closure may outlive the current function, but it borrows `s`, which is owned by the current function | 4 | return || { | ^^ may outlive borrowed value `s` 5 | s += 1; | - `s` is borrowed here | note: closure is returned here | 1 | fn counter(start: i32) -> impl FnMut() -> i32 | ^^^^^^^^^^^^^^^^^^^ help: to force the closure to take ownership of `s` (and any other referenced variables), use the `move` keyword | 4 | return move || { | ^^^^^^^
move
キーワードを使って、s
の所有権をクロージャーに渡す必要がある。
fn counter(start: i32) -> impl FnMut() -> i32 { let mut s = start; return move || { s += 1; s }; }
まとめ
- Fn 外部変数のキャプチャーは、コピーが発生する
- FnMut 外部変数のキャプチャーは、ミュータブル参照が発生する
- FnOnce 外部変数のキャプチャーは、所有権が移動する
move
キーワードによってキャプチャー変数の所有権をクロージャ内部に移すことができる
スマートポインタの役割
Rustは関数ローカルの変数を参照として返すことができない
impl Reader { fn new(path: impl AsRef<std::path::Path>) -> Reader { let file = std::fs::File::open(path).unwrap(); Reader { file: file, } } fn read_bytes(&mut self, len: usize) -> &[u8] { let mut bytes = vec![0_u8; len]; self.file.read(&mut bytes); &bytes } /* error[E0515]: cannot return reference to local variable `bytes` | 22 | &bytes | ^^^^^^ returns a reference to data owned by the current function */ }
スマートポインタを使ってスライスを包み込んで返すようにする。 こうすることによって、値がヒープ上に確保され、その参照を渡すことが出来る。
fn read_bytes(&mut self, len: usize) -> Vec<u8> { let mut bytes = vec![0_u8; len]; self.file.read(&mut bytes); bytes }
標準スタンダードにおける例
std::fs::File
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize>
スライスを受け取るのではなく、Vecで受け取っている。
トレイト まとめ
Self
将来実装される型を表す。
pub trait FromStr { type Err; fn from_str(s: &str) -> Result<Self, Self::Err>;
impl FromStr for
Haskellで言うところの型変数 a
class Eq a where
Eq
: 型クラスa
: 型変数, 具体型である必要がある。(型コンストラクタはNG)
関連型 type
実装側で定義できる型を表す
put trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>;
ジェネリクスパラメーター
トレイトはジェネリクスを持てる。
pub trait From<T> { fn from(T) -> Self; }
デフォルトジェネリックス型
pub trait Add<Rhs=Self> { type Output; fn add(self, rhs: Rhs) -> Self::Output; }
use std::ops::Add; #[derive(Debug)] struct Point { x: i32, y: i32, } // ジェネリックスパラメーターを指定しなくてよい impl Add for Point { type Output = Point; // 推測されない fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y, } } } let p1 = Point { x: 1, y: 2 }; let p2 = Point { x: 3, y: 4 }; let p3 = p1 + p2; println!("{:?}", p3);
デフォルトのジェネリックス型と異なる型の場合は指定する
use std::ops::Add; struct Millimeters(u32); struct Meters(u32); impl Add<Meters> for Millimeters { type Output = Millimeters; fn add(self, other: Meters) -> Millimeters { Millimeters(self.0 + (other.0 * 1000)) } }
トレイト境界も可能
pub trait AsRef<T> where T: ?Sized, { fn as_ref(&self) -> &T; }
サブトレイト
トレイトの継承。
pub trait Copy: Clone { }
Haskellでいうところのサブクラス
class Eq a => Ord a where
トレイトオブジェクト dyn
トレイトは型宣言に使うことはできない
// warning: trait objects without an explicit `dyn` are deprecated fn open(path: AsRef<std::path::Path>) { std::fs::File::open(path).unwrap(); }
'&dyn'をつける。&
を付ける理由はトレイトオブジェクトはサイズ未決定型であるため。
fn open(path: &dyn AsRef<std::path::Path>) {