エラーハンドリング構成 in 2020

元ネタ

nick.groenen.me

メモ

std::error::Errorトレイトが抱える問題

スタックトレースがとれない。

アプリケーションとライブラリとでエラーのあり方が異なる

ライブラリにおけるエラー
  • 意味のあるエラーを返す必要がある
  • ライブラリ内で発生したエラーはライブラリ用にラッピングされるべきである
  • ライブラリで定義されるエラーの変更は注意深くあるべきである
アプリケーションにおけるエラー
  • エラーを消費する側である
  • ユーザーへのエラー表示を考える
  • エラーの解析と検証ができるようにする

エラーハンドリングクレート 2選

作者が同一。

thiserror

エラーを定義することが出来る。

use thiserror::Error;

/// WordCountError enumerates all possible errors returned by this library.
#[derive(Error, Debug)]
pub enum WordCountError {
    /// Represents an empty source. For example, an empty text file being given
    /// as input to `count_words()`.
    #[error("Source contains no data")]
    EmptySource,

    /// Represents a failure to read from input.
    #[error("Read error")]
    ReadError { source: std::io::Error },

    /// Represents all other cases of `std::io::Error`.
    #[error(transparent)]
    IOError(#[from] std::io::Error),
}

anyhow

エラーを表示することが出来る。

use anyhow::{Context, Result};

fn main() -> Result<()> {
    for filename in env::args().skip(1).collect::<Vec<String>>() {
        let mut reader = File::open(&filename).context(format!("unable to open '{}'", filename))?;
        let wordcount =
            count_words(&mut reader).context(format!("unable to count words in '{}'", filename))?;
        println!("{} {}", wordcount, filename);
    }
    Ok(())
}

ファイルが存在しなかった場合

$ cargo run --quiet -- words.txt
Error: unable to open 'words.txt'

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

readでエラーが発生した場合

$ cargo run --quiet -- words.txt
Error: unable to count words in 'words.txt'

Caused by:
    0: Error encountered while reading from input
    1: read: broken pipe

スタックトレースが表示される。

関連

Rust のエラーまわりの変遷 - Qiita

The ? operator for easier error handling - The Edition Guide

参照型 `&T` `&mut T`

Primitive Type 参照 &T or &mut T

参照, 共有と可変

参照は、ある所有された値の借用を表す。 & ,&mut 演算子もしくは、ref, ref mutキーワードを使って参照を得ることが出来る。

ポインタ型(*const T, *mut T)と違って、null出ないことが保証されている。

殆どの場合において、参照はオリジナルの値と同様に使うことが出来る。

  • フィールドへのアクセス
  • メソッド呼び出し
  • indexing

参照は、ライフタイムを持つ

ライフタイム - 借用が有効であることを表すスコープ

'staticライフタイムは、プログラムと同じスコープであることを表す。 例として、文字列リテラルは、'staticライフタイムを持つ。

実装しているトレイト

  • Copy
  • Clone TがCloneを実装しているしていないに関わらず
  • Deref
  • Borrow
  • Pointer

Tが実装している場合

  • PartialOrd
  • Ord
  • PartialEq
  • Eq
  • AsRef
  • Fn
  • Hash

参照

doc.rust-lang.org

ジェネリクス Generics

Generic(総称型) vs Concreate(具体型)

Rustにおいて、"generic"は、一つ以上のgeneric type parameters<T>を持つものである。

それ以外は、concrete(non-generic)である。

例: 関数

fn foo<T>(arg: T) { .... }

例 : struct

struct SingleGen<T>(T);

参照

doc.rust-lang.org

bat

概要

github.com

出力の整形や構文ハイライトをしてくれるツール。

プロジェクト構成

src構成

▾  src/
  ▾  bin/bat/
       ツールファイル一式
       app.rs
       assets.rs
       clap_app.rs
       config.rs
       directories.rs
       input.rs
       main.rs
     lib.rs
     ソースファイル一式

main関数

fn main() {
    // 実行
    let result = run();

    // 最後にエラーをハンドリングする
    match result {
        Err(error) => {
            let stderr = std::io::stderr();
            default_error_handler(&error, &mut stderr.lock());
            process::exit(1);
        }
        Ok(false) => {
            process::exit(1);
        }
        Ok(true) => {
            process::exit(0);
        }
    }
}

run関数概要

/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Ok(true)` on full success and
/// `Ok(false)` if any intermediate errors occurred (were printed).
fn run() -> Result<bool> {
    // エラーをプロパゲーションしていく
    let app = App::new()?;

    省略

その他

エラーハンドリング

error_chainを使っている

error_chain! {
    foreign_links {
        Clap(::clap::Error) #[cfg(feature = "application")];
        Io(::std::io::Error);
        SyntectError(::syntect::LoadingError);
        ParseIntError(::std::num::ParseIntError);
        GlobParsingError(::globset::Error);
        SerdeYamlError(::serde_yaml::Error);
    }

    errors {
        UndetectedSyntax(input: String) {
            description("unable to detect syntax"),
            display("unable to detect syntax for {}", input)
        }
        UnknownSyntax(name: String) {
            description("unknown syntax"),
            display("unknown syntax: '{}'", name)
        }
        InvalidPagerValueBat {
            description("invalid value `bat` for pager property"),
            display("Use of bat as a pager is disallowed in order to avoid infinite recursion problems")
        }
    }
}
コマンドライン引数のパース

clapを使用している

use clap::ArgMatches;

fn matches(interactive_output: bool) -> Result<ArgMatches<'static>> {
    let args = if wild::args_os().nth(1) == Some("cache".into())
        || wild::args_os().any(|arg| arg == "--no-config")
    {
        // Skip the arguments in bats config file

        wild::args_os().collect::<Vec<_>>()
    } else {
        let mut cli_args = wild::args_os();

        // Read arguments from bats config file
        let mut args = get_args_from_env_var()
            .unwrap_or_else(get_args_from_config_file)
            .chain_err(|| "Could not parse configuration file")?;

        // Put the zero-th CLI argument (program name) first
        args.insert(0, cli_args.next().unwrap());

        // .. and the rest at the end
        cli_args.for_each(|a| args.push(a));

        args
    };

    Ok(clap_app::build_app(interactive_output).get_matches_from(args))
}

関連

Rust のエラーまわりの変遷 - Qiita

  • error_chain 開発中止

オブジェクト構造

Document structure

f:id:yossan2:20210616231152p:plain
オブジェクト階層 in PDF

  • direct object 相互参照テーブルからオフセット位置を読み取ることが出来るオブジェクト (名前付きオブジェクトと呼ばれることもある。)
  • indirect object direct object以外のオブジェクト。 オブジェクト階層では唯一トレーラー辞書のみがindirect dictionaryとなっており、読み取るために、ファイル構造で定義されたオブジェクト配置戦略が必要となる。

Trailer Dictionary

オブジェクトグラフのルートオブジェクト。

trailer
<< /Size 46 /Root 24 0 R /Info 1 0 R /ID [ <378d78094ee3afcd9aa56448d2631c29>
<378d78094ee3afcd9aa56448d2631c29> ] >>
Key Value type Value
/Size Integer 相互参照テーブルのentityの総数。
/Root 辞書への参照(indirect reference) ドキュメントカタログへの参照
/Info 辞書への参照(indirect reference) ドキュメントのドキュメント情報辞書
/ID 2つのStringで構成された配列 作成IDと更新ID

Document Information Dictionary

作成日、更新日などファイルのメタ情報が記載されている。

1 0 obj
<< /Title 42 0 R /Producer 43 0 R /Creator 44 0 R /CreationDate 45 0 R /ModDate
45 0 R >>
endobj
<<
   /ModDate (D:20060926213913+02'00')
   /CreationDate (D:20060926213913+02'00')
   /Title (catalogueproduit-UK.qxd)
   /Creator (QuarkXPress: pictwpstops filter 1.0)
   /Producer (Acrobat Distiller 6.0 for Macintosh)
   /Author (James Smith)
>>
Key Value type Value
/Title text文字列 ドキュメントタイトル。最初のページで表示されるタイトルとはいかなる関係もない。
/Subject text文字列 副題
/Keywords text文字列 ドキュメントに関連するキーワード
/Author text文字列 ドキュメントの著作者
/CreationDate date文字列 作成日
/ModDate date文字列 更新日
/Creator text文字列 このPDFのオリジナル。例: "Microsoft Word"
/Producer text文字列 PDFを生成したアプリケーション

Document Catalog

メインオブジェクトグラフのルートオブジェクト。

24 0 obj
<< /Type /Catalog /Metadata 2 0 R /Pages 4 0 R >>
endobj
Key Value type Value
/Type name 必ず /Catalog
/Pages 辞書への参照(indirect reference) ページツリーのルートノード。
/PageLabels number tree ページへのラベル。例: i,ii,iii,...。電子デバイス上のViewerには表示されるが、印刷はされない。
/Names dictionary The name dictionary.
/Dests dictionary nameとdestinationマッピング辞書。destinationは、PDFドキュメント内部へのハイパーリンクを表す。
/Outlines 辞書への参照 目次

Page Tree

Pages

ページツリーのルートノード。

4 0 obj
<< /Type /Pages /MediaBox [0 0 612 792] /Count 2 /Kids [ 3 0 R 20 0 R ] >>
endobj
3 0 obj
<< /Type /Pages /Kids [
4 0 R
7 0 R
9 0 R
73 0 R
11 0 R
] /Count 5
/Rotate 0>>
endobj
Key Value type Value
/Type name 必ず /Pages
/Kids 参照への配列 直下のこのノードの子ページツリー
/Count integer ページ数
/Parent ページツリーノードへの参照 このノードの親への参照。

Page

ページ情報。ContentsやResourceへの参照を持つ。

3 0 obj
<< /Type /Page /Parent 4 0 R /Resources 7 0 R /Contents 5 0 R /MediaBox [0 0 612 792]
>>
20 0 obj
<< /Type /Page /Parent 4 0 R /Resources 23 0 R /Contents 21 0 R /MediaBox
[0 0 612 792] >>
endobj
4 0 obj
<</Type/Page/MediaBox [0 0 612 792]
/Rotate 0/Parent 3 0 R
/Resources<</ProcSet[/PDF /Text]
/ExtGState 41 0 R
/Font 42 0 R
>>
/Annots[13 0 R
14 0 R
15 0 R
16 0 R
17 0 R
18 0 R
19 0 R
20 0 R
21 0 R
22 0 R
23 0 R
24 0 R
25 0 R
26 0 R]/Contents 30 0 R
>>
endobj
/MediaBox [0 0 500 800]
/CropBox [100 100 400 700]
Key Value type Value
/Type name 必ず /Page
/Parent 辞書への参照 ページツリーの親ノード
/Resources dictionary font,imageなどのページリソース情報。リソースが存在しない場合は、空辞書が指定される。
/Contents ストリームへの参照 or 参照の配列 一つ以上のセクションにおける描画ページコンテンツ。ページが空の場合は、このエントリーが存在しない。
/Rotate integer ページの回転。時計回りでdegreeで表せる。
/MediaBox rectangle 紙のサイズ。
/CropBox rectangle ページの切り取りサイズ。印刷領域を表す。

Page Contents

5 0 obj
<< /Length 1074 >>
stream
2 J
BT
0 0 0 rg
/F1 0027 Tf
57.3750 722.2800 Td
( A Simple PDF File ) Tj
ET
BT
/F1 0010 Tf
69.2500 688.6080 Td
( This is a small demonstration .pdf file - ) Tj
ET
BT
/F1 0010 Tf
69.2500 664.7040 Td
( just for use in the Virtual Mechanics tutorials. More text. And more ) Tj
ET
BT
/F1 0010 Tf
69.2500 652.7520 Td
( text. And more text. And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 628.8480 Td
( And more text. And more text. And more text. And more text. And more ) Tj
ET
BT
/F1 0010 Tf
69.2500 616.8960 Td
( text. And more text. Boring, zzzzz. And more text. And more text. And ) Tj
ET
BT
/F1 0010 Tf
69.2500 604.9440 Td
( more text. And more text. And more text. And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 592.9920 Td
( And more text. And more text. ) Tj
ET
BT
/F1 0010 Tf
69.2500 569.0880 Td
( And more text. And more text. And more text. And more text. And more ) Tj
ET
BT
/F1 0010 Tf
69.2500 557.1360 Td
( text. And more text. And more text. Even more. Continued on page 2 ...) Tj
ET
endstream
endobj

Page Resources

heap領域で確保される型

users.rust-lang.org

Rustは通常スタック領域に値を確保するが、一部の型はヒープ領域で内部の値を確保し、その参照を返す。

Containers:

  • Box
  • Rc
  • Arc

Collections types:

  • Vec, VecDeque
  • String
  • HashMap, HashSet
  • BTreeMap, BTreeSet

Concurrency:

  • thread::spawn(): the closure is boxed so it can be passed as a pointer into the new thread’s environment (required by the C-based thread APIs)
  • JoinHandle: the return value of the closure passed to thread::spawn() is returned as a pointer (same)
  • Sender/SyncSender & Receiver

I/O:

  • BufReader
  • The TCP streams are buffered at the OS level, I don’t know how much Rust has a say in those buffer sizes
  • stdin, stdout, and stderr use global heap-allocated buffers (which you interact with whenever you invoke println!() or panic!())

FFI:

  • CString
  • OsString

Box<T>型

doc.rust-lang.org

Rustの全ての値は、デフォルトはstack領域で確保される。 値は、Box<T>を生成することによって、boxed(ヒープ上での確保)することが出来る。

box値は、T型の値をヒープ上に確保するためのスマートポインタである。

box値がスコープから消えた場合は、デストラクタが呼ばれ、内部のオブジェクトは破壊され、メモリが開放される。

ボックスされた値は、*演算子を使用することで、デリファレンスされることが出来る。

Note: デリファレンス

  • スマートポインタ 内部の値を返す
  • 参照型 参照先の取得 (参照を外す)
use std::mem;

#[derive(Debug)]
struct Point {
    x: f64,    // 8 bytes
    y: f64,    // 8 bytes
}
#[derive(Debug)]
struct Rectangle {
    top_left: Point,     // 16 bytes
    bottom_right: Point, // 16 bytes
}

fn main() {
    let rectangle = Rectangle {
        top_left: Point { x: 0.0, y: 0.0 },
        bottom_right: Point { x: 2.0, y: -4.0 },
    };
    println!("Rectangle occupies {} bytes on the stack", mem::size_of_val(&rectangle)); // 32 bytes

    let boxed_rectangle = Box::new(rectangle);
    println!("Boxed rectangle occupies {} bytes on the stack", mem::size_of_val(&boxed_rectangle)); // 8 bytes

    // Copyトレイトでないので、デリファレンスによって値の所有権が移動する
    let unboxed_rectangle = *boxed_rectangle;
    println!("Unboxed point occpies {} bytes on the stack", mem::size_of_val(&unboxed_rectangle)); // 32 bytes
}

関連

users.rust-lang.org

https://yossan.hatenablog.com/entry/2020/09/20/224329

https://yossan.hatenablog.com/entry/2021/02/02/213844