ジェネリクス関数 - 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冊目。

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

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

クラス図

クラス関係

矢印でクラス同士を結ぶ事ができる。 この際、その関係の種類によって矢印の形が異なる。

関係 意味 備考
Extension 継承 f:id:yossan2:20210826224743p:plain
Aggregation (集約) has-a 授業(親)と生徒(子)といった関係を表す。また授業を消したからと言って、生徒は消えない。 f:id:yossan2:20210826224737p:plain
Composition part-of 家(親)と部屋(子)といった関係を表す。部屋は家と切り離して存在しない f:id:yossan2:20210826224740p:plain

Note: Aggregation vs Composition

  • 親を消したら子も消えるのかどうか
    • 消える → Composition
    • 消えない → Aggregation

参照

https://www.ogis-ri.co.jp/otc/swec/process/am-res/am/artifacts/classDiagram.html#CompositionAssociations

https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-aggregation-vs-composition/

https://www.itsenka.com/contents/development/uml/class.html

https://plantuml.com/class-diagram