トレイトオブジェクト

Trait objects

動的サイズ決定型 (DSTs)

トレイトオブジェクトは、以下のポインタを持つワイドポインタ(動的サイズ決定型)

  • 値へのポインタ
  • 仮想メソッドテーブル

トレイトオブジェクトの形

  • Box<dyn SomeTrait>
  • &dyn SomeTrait

トレイトオブジェクトの目的

動的ディスパッチを実現する。

trait Printable {
    fn stringify(&self) -> String;
}

impl Printable for i32 {
    fn stringify(&self) -> String {
        self.to_string()
    }
}

// Box
fn print(a: Box<dyn Printable>) {
    println!("{}", a.stringify());
}

// 参照
fn print2(a: &dyn Printable) {
    println!("{}", a.stringify());
}

fn main() {
    print(Box::new(10));
    print2(&20);
}

関連

トレイトオブジェクトになれないトレイト - あるマのメモ書き

関連型を持つトレイトオブジェクトを宣言する - あるマのメモ書き

参照

https://doc.rust-lang.org/reference/types/trait-object.html

https://qiita.com/hadashiA/items/d0c34a4ba74564337d2f#comment-99db3667a99310a68644

&Foo はトレイトオブジェクトですよ。 トレイトオブジェクトで重要なのは、仮想関数テーブルとオブジェクト実体へのポインタの組であるという点です。トレイトオブジェクトそのものはトレイト境界が実装された型の参照からキャストすればいくらでも得ることができるので、ボクシングを行う必要はありません。

ボクシングされたトレイトオブジェクトの利点は、具体的な型情報が消去された状態でトレイトオブジェクトの所有権を管理できるという点です。なので、逆に言えば所有権を管理する必要がなければボクシングは必要ありません。上に例示したコードのように借用で済むはずです。

ジェネリクス関数 - impl trait引数とトレイト境界の違い

元ネタ

rust - What are the differences between an impl trait argument and generic function parameter? - Stack Overflow

回答

impl trait引数は、トレイト境界に脱糖(desugar)される。(ゆえにトレイト境界の糖衣構文である)

したがって以下は同じ。

trait Foo {}

fn func1(_: impl Foo) {}

fn func2<T: Foo>(_: T) {}

唯一の違いは、impl Trait引数は、その型を明白に指定させることができない。(ターボフィッシュが使えない)

trait Trait {}

fn foo<T: Trait>(t: T) {}
fn bar(t: impl Trait) {}

impl Trait for u32 {}

fn main() {
    foo::<u32>(0); // this is allowed
    bar::<u32>(0); // this is not
    /*
     error[E0632]: cannot provide explicit generic arguments when `impl Trait` is used in argument position
  --> src/main.rs:10:11
   |
10 |     bar::<u32>(0); // this is not
   |           ^^^ explicit generic argument not allowed
    */
}

anyhowを使う

anyhow::Error

https://docs.rs/anyhow/1.0.44/anyhow/struct.Error.html

動的エラー型を包み込んだエラー型。

Box<dyn std::error::Error>との違い

  • 静的な型
  • もどり道(backtrace)を保証する
  • narrow pointer

Display representations

Display 表し方

esprintln!("{}", err)

undering error(anyhow::Errorが包み込んだerror) もしくは、contextを表示する。

例: Failed to read instrs from ./path/to/instrs.json

esprintln!("{:#}", err)

anyhowのcausesのデフォルトフォーマットを出力する

例: Failed to read instrs from ./path/to/instrs.json: No such file or directory (os error 2)

esprintln!("{:?}", err)

キャプチャーしたバックトレースを出力する。

例: Error: Failed to read instrs from ./path/to/instrs.json

Caused by: No such file or directory (os error 2)

anyhow::Result<T>

実態

//anyhow
type Result = Result<T, anyhow::Error>

fn mainでは返すことができない。 anyhow::Errorを伝搬してくれる。

anyhow::Context

Result<T, anyhow::Error>に変換するcontextメソッドを提供する。

std::core::Result<T, E>からの変換

let file = File::open("memo.txt").context(format!("unable to open memo.txt)")?;

Option<T>からの変換

let first = vec.first()?.context("unable to get first from vec")

anyhow! bail!

anyhow!マクロ

anyhow::Errorをかんたんに生成

例: stringからの生成

fn lookup(key: &str) -> Result<V> {
    if key.len() != 16 {
        return Err(anyhow!("key length must be 16 characters, got {:?}", key));
    }

    ...

bail!マクロ

実態

return(anyhow!($args....))

これを使うことでかんたんにanyhow::Result<T>にして返すことができる

if !has_permission(user, resource) {
    bail!("permission denied for accessing {}", resource);
}

thiserrorとの組み合わせ

#[derive(Error, Debug)]
enum ScienceError {
    #[error("resouce limit exceeded")]
    RecursionLimitExceeded,
    ...
}

if depth > MAX_DEPTH {
    bail!(ScienceError::RecursionLimitExceeded);
}

anyhowメモ

anyhowのメリット

  • エラーに説明を追加できる
  • anyhow::Result<T>は、エラーの伝播ルートをスタックトレースとして出力してくれる

anyhowがない場合

Result<T, E>std::error::Errorトレイトを使う。

fn count_words<R: Read>(input: &mut R) -> Result<u32, Box<dyn Error>> {
    let reader = BufReader::new(input);
    let mut wordcount = 0;
    for line in reader.lines() { // line = Result<String>
        for _word in line?.split_whitespace() {
            wordcount += 1;
        }
    }
    Ok(wordcount)
}

fn main() -> Result<(), Box<dyn Error>> {
    // 複数ファイル対応
    for filename in env::args().skip(1).collect::<Vec<String>>() {
        // どのファイルが開けなかったのか出力されない
        let mut reader = File::open(&filename)?;

        let wordcount = count_words(&mut reader)?;
        println!("{} {}", wordcount, filename);
    }
    Ok(())
}

anyhowがある場合

anyhow::Result<T>anyhow::Error型を使う。

use anyhow::{Result, Context};

use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;

fn count_words<R: Read>(input: R) -> Result<u32> {
    let reader = BufReader::new(input);
    let mut wordcount = 0;
    for line in reader.lines() {
        for _word in line.context("not line")?.split_whitespace() {
            wordcount += 1;
        }
    }

    Ok(wordcount)
}

fn run() -> Result<()> {
    for filename in env::args().skip(1).collect::<Vec<String>>() {
        // contextで、anyhow::Errorに変換し、説明を付与できる。
        let file = File::open(&filename).context(format!("unable to open {}", filename))?;
        let wordcount = count_words(&file).context(format!("unable to count words in {}", filename))?;
        println!("{} {}", wordcount, filename);
    }
    Ok(())
}

fn main() /*NG main関数ではResult<(), anyhow::Error>を返せない-> Result<()>*/ {
    if let Err(error) = run() {
        // Errorを出力する
        eprintln!("Error: {:?}", error);
        std::process::exit(0);
    }
}

Borrow<Borrowed>とToOwnedトレイト

  • Borrow<Borrowed>トレイト - Borrowed型を借用できることを表す
  • ToOwnedトレイト - Borrow<Borrowed>型を実装すると自動実装される
use std::borrow::Borrow;

#[derive(Debug)]
struct ToolBox(Tool);
#[derive(Debug)]
struct Tool;

impl Borrow<Tool> for ToolBox {
    fn borrow(&self) -> &Tool {
        &self.0
    }
}


let tool_box = ToolBox(Tool);
let tool = tool_box.borrow() as &Tool;
// ToOwnedが自動導出される
let tool_box = tool.to_owned();
dbg!(tool_box);
代表的なBorrow<Borrowed>型
  • String = Borrow<str>
  • Vec<T> = Borrow<[T]>
  • Box<T> = Borrow<T>
  • PathBuf = Borrow<Path>

関連

yossan.hatenablog.com

葉桜の季節に君を想うということ

発行: 2003年

著書: 歌野晶午

まさかのつながりからの展開にびっくり。

十角館の殺人を彷彿させる本格ミステリーであったが、最後の最後までミステリー小説なのかどうか分からなかった。

読後、タイトルを読み、今や恋人の帰りを待っている主人公の姿を思い浮かぶ。

2004年のミステリーに関わる賞を総なめにしたというのも納得の一冊。

向日葵の咲かない夏

著書: 道尾秀介
発行: 2008年9月1日

いわゆる叙述トリックを使ったミステリ小説で、最後は嫌な気持ちになるイヤミス

こういったミステリの醍醐味は、 最後まで読んでタネが分かった上で、また読み直すことできることだと思う。

道尾秀介の本は、「カラスの親指」「カエルの小指」に続いて3冊目。

彼の本の特徴は、壮大な仕掛けが行われているかとみせかけて、最後は現実的な話に落ち着いていくというストーリー展開ではないか。

いやぁ、本当に嫌な気持ちになった。 救いを求めて再度序章を読み始めたら、さらに嫌な気持ちになるんだからびっくり。