やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rustの高度な機能(トレイト)

 関連型、ジェネリック型、スーパートレイト、ニュータイプパターン。

成果物

参考

関連型

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}
struct Counter { count: u32 }
impl Iterator for Counter {
    type Item = u32;
    fn next(&mut self) -> Option<Self::Item> { None }
}

 type Item;の記述が関連型である。

 なぜ、以下のようにジェネリクスを使わないのか? 各実装で型を注釈せねばならないから。Iterator<String> for Counterなど。これは複数回定義できることを意味する。関連型なら1度のみの実装になる。

pub trait Iterator<T> {
    fn next(&mut self) -> Option<T>;
}

演算子オーバーロード

use std::ops::Add;
#[derive(Debug, PartialEq)]
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,
        }
    }
}
fn main() {
    assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
               Point { x: 3, y: 3 });
}

 std::ops::Addトレイトは以下。

trait Add<RHS=Self> {
    type Output;
    fn add(self, rhs: RHS) -> Self::Output;
}

 RHS=Selfはデフォルト型引数。RHSという型は未指定のときSelf型となる。

 以下、Selfでない型を使う例。

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

同名メソッドを呼ぶ

trait Pilot { fn fly(&self); }
trait Wizard { fn fly(&self); }
struct Human;
impl Pilot for Human {
    fn fly(&self) {println!("This is your captain speaking.");}
}
impl Wizard for Human {
    fn fly(&self) {println!("Up!");}
}
impl Human {
    fn fly(&self) {println!("*waving arms furiously*");}
}
fn main() {
    let person = Human;
    person.fly();
}
$ rustc main.rs
$ ./main
*waving arms furiously*

 トレイト名::メソッド名(&インスタンス);で各同名メソッドを呼べる。

fn main() {
    let person = Human;
    Pilot::fly(&person);
    Wizard::fly(&person);
    person.fly();
}
$ ./main
This is your captain speaking.
Up!
*waving arms furiously*

 だが、関連型にはselfがない。

trait Animal {
    fn baby_name() -> String;
}
struct Dog;
impl Dog {
    fn baby_name() -> String {
        String::from("Spot")
    }
}
impl Animal for Dog {
    fn baby_name() -> String {
        String::from("puppy")
    }
}
fn main() {
    println!("A baby dog is called a {}", Dog::baby_name());
}
$ ./main
A baby dog is called a Spot

 もしAnimal::baby_name()とすれば、どのメソッドを使うか特定できず以下エラーになる。

    println!("A baby dog is called a {}", Animal::baby_name());
$ rustc main.rs
error[E0283]: type annotations required: cannot resolve `_: Animal`
  --> main.rs:21:43
   |
21 |     println!("A baby dog is called a {}", Animal::baby_name());
   |                                           ^^^^^^^^^^^^^^^^^
   |
note: required by `Animal::baby_name`
  --> main.rs:6:5
   |
6  |     fn baby_name() -> String;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^

 もし<Dog as Animal>::baby_name()とすれば、impl Dog { baby_name() }が呼ばれる。

    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
$ ./main
A baby dog is called a puppy
  • trait::method(&instance);
  • <type as trait>::method();

スーパートレイト

use std::fmt;
trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}

 trait OutlinePrint: fmt::Display {}は、OutlinePrintfmt::Displayを必要とする、という意味。

 以下で実装する。

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}
use std::fmt;
trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}
struct Point {
    x: i32,
    y: i32,
}
impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}
impl OutlinePrint for Point {}
fn main() {
    let p = Point { x: 1, y: 3 };
    p.outline_print();
}
$ rustc main.rs
$ ./main
**********
*        *
* (1, 3) *
*        *
**********

ニュータイプパターン

 外部の型に外部のトレイトを実装する。

 オーファンルールにより、型にトレイトを実装するにはトレイトか型がローカルクレートである必要がある。ニュータイプパターンはこれを回避できる。

 タプル構造体に新しい型を作成することで。

 例としてVec<T>型にfmt::Displayトレイトを実装する。

use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}
fn main() {
    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
    println!("w = {}", w);
}
$ rustc main.rs
$ ./main
w = [hello, world]

 ただし、Vec型の既存メソッドがWrapperにはない。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで