トレイトオブジェクト
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引数とトレイト境界の違い
元ネタ
回答
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>