エラーハンドリング構成 in 2020
元ネタ
メモ
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
スタックトレースが表示される。
関連
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
参照
ジェネリクス Generics
bat
概要
出力の整形や構文ハイライトをしてくれるツール。
プロジェクト構成
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)) }
関連
error_chain
開発中止
オブジェクト構造
Document structure
- indirect object 相互参照テーブルからオフセット位置を読み取ることが出来るオブジェクト (名前付きオブジェクトと呼ばれることもある。)
- direct object indirect object以外のオブジェクト。 オブジェクト階層では唯一トレーラー辞書のみがdirect 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領域で確保される型
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>型
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 }