トレイトオブジェクトの保持とライフタイム

トレイトオブジェクトとライフタイム

トレイトオブジェクトを保持する場合は、ライフタイムが必要となる時がある。

トレイトオブジェクト 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>;

実装している主要な型

  • std::slice::Iter<'a, T>
  • Iterator派生型
    • Map
    • Filter
    • String.bytes

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のラムダ式クロージャーと呼ぶ
    • 関数ポインタ(fn)は、外部環境変数を含めることができない
    • 関数ポインタもクロージャートレイトを実装している
  • クロージャーの種類 (キャプチャー方法が異なる)
    • 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

  • 外部変数のキャプチャーは、ミュータブル参照する
  • FnMutクロージャーには、mutキーワードを付ける必要がある
  • 何度もクロージャーを実行できる

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>) {