Slide 1

Slide 1 text

Mitama.Result v2 C++でもResultが使いたい

Slide 2

Slide 2 text

自己紹介  主にTwitterに生息しています @mitama_rs  仕事でC++17でなんかやってます  その前は高校で理科を教えていた

Slide 3

Slide 3 text

例外じゃない エラーハンドリングほしくない?  標準化委員会でもstd::expectedとかの提案があるが  まってらんねえ  Boost.Outcomeもあるが  モナディック関数がないやつは僕の欲しいものじゃない  いつものようにマイライブラリでなんとかする()

Slide 4

Slide 4 text

RustのResult  Rustのエラーハンドリングで使われる型  成功を表し値を含むOk(T)  およびエラーを表しエラー値を含むErr(E)  の列挙型(代数的データ型)  正常値も異常値も両方運搬できる  エラーメッセージも運搬できる

Slide 5

Slide 5 text

Mitama.Result v2  https://github.com/LoliGothick/mitama-cpp-result  Resultの主要なAPIをほとんど再現したC++17のライブラリ  サポートコンパイラは  gcc-8.3  gcc-9.1  clang-7  clang-8  依存ライブラリ(Boost)  Hana, Variant, Optional, Format

Slide 6

Slide 6 text

クラス宣言 enum class mutability: bool { mut = false, immut = true, }; template class basic_result;

Slide 7

Slide 7 text

ミュータビリティ  ミュータビリティが型に埋め込まれいる(正直やりすぎ感ある)  内部で保持する値へのアクセスの制御に使われている  利用者はシノニムを利用する /// alias template for immutable result template using result = basic_result; /// alias template for mutable result template using mut_result = basic_result;

Slide 8

Slide 8 text

resultの構築  success, failureというfactoryクラスから作れる  関数からresultを返す場合も result res(success{"foo"}); result res(failure{"foo"}); auto func(bool b) -> result { if (b) { return success{"foo"}; } else { return failure{"bar"}; } }

Slide 9

Slide 9 text

おしらせ  以降  正常値の型をTとします  異常値の型をEとします  幅節約のためstd::stringのエイリアスとしてstrを使います

Slide 10

Slide 10 text

APIs Observers

Slide 11

Slide 11 text

is_ok(), is_err()  それぞれ正常値・異常値を保持しているかをboolで返す result res(success{"foo"}); res.is_ok(); // true res.is_err(); // false result res(failure{"foo"}); res.is_ok(); // false res.is_err(); // true

Slide 12

Slide 12 text

ok(), err()  それぞれ正常値・異常値をboost::optionalで取得できる result res(success{42}); res.ok(); // Some(42) res.err(); // None result res(failure{42}); res.ok(); // None res.err(); // Some(42)

Slide 13

Slide 13 text

unwrap(), unwrap_err()  それぞれ正常値・異常値を取り出す  値を持っていなければ例外を送出する result res(success{42}); res.unwrap(); // 42 res.unwrap_err(); // raise the exception `mitama::runtime_panic` result res(failure{42}); res.unwrap(); // raise the exception `mitama::runtime_panic` res.unwrap_err(); // 42

Slide 14

Slide 14 text

unwrap_or(T optb)  正常値を取り出す  値を持っていなければ指定された値optbを返す result res(success{42}); res.unwrap_or(57); // 42 result res(failure{42}); res.unwrap_or(57); // 57

Slide 15

Slide 15 text

unwrap_or_else(F func) F: E -> T  正常値を取り出す  異常値errを持っていなければfunc(err)を返す  遅延評価したいときに使う  引数なしで呼べる場合はなしで呼ぶ result res(success{42}); res.unwrap_or_else([](int err){ return err.length(); }); // 42 result res(failure{42}); res.unwrap_or_else([]{ return 57; }); // 57

Slide 16

Slide 16 text

unwrap_or_default()  正常値を取り出す  値を持っていなければ指定された値を返す  正常型がデフォルトコンストラクション可能かアグリゲートである必要がある result res(success{42}); res.unwrap_or_default(); // 42 result res(failure{42}); res.unwrap_or_default(); // 0

Slide 17

Slide 17 text

expect(string_view msg)  unwrap()の亜種で例外のメッセージに含める文字列を指定できりゅ result res(failure{42}); auto ok = res.expect("foo"); // panicked at 'foo: failure(42)' result res(failure{42}); auto ok = res.unwrap(); // panicked at 'called `result::unwrap()` on a value: failure(42)'

Slide 18

Slide 18 text

expect_err(string_view msg)  unwrap_err()の亜種で例外のメッセージに含める文字列を指定できりゅ result res(success{42}); auto err = res.expect_err("foo"); // panicked at 'foo: success(42)' result res(success{42}); auto err = res.unwrap_err(); // panicked at 'called `result::unwrap_err()` on a value: success(42)'

Slide 19

Slide 19 text

APIs Monadic functions

Slide 20

Slide 20 text

map(op) op: T -> U  正常値okを持っているとき場合、op(ok) を適用しresultとして返す  異常値を持っていた場合はそれをresultとして返す result res(success{42}); res.map([](auto ok){ return ok + 1; }); // success(43) result res(failure{42}); res.map([](auto ok){ return ok + 1; }); // failure(42)

Slide 21

Slide 21 text

map_err(op) op: E -> F  異常値errを持っているとき場合、op(err) を適用しresultとして返す  正常値を持っていた場合はそれをresultとして返す result res(success{42}); res.map_err([](auto err){ return err - 1; }); // success(42) result res(failure{42}); res.map_err([](auto err){ return err - 1; }); // failure(41)

Slide 22

Slide 22 text

and_then(op) op: T -> result  正常値を持っているとき、更に失敗する可能性のある関数へチェインする result res(success{41}); res.and_then([](auto ok) -> result { if ( ok % 2 == 0 ) return success{ok+1}; else return failure{ok}; // ここを通る }); // failure(41)

Slide 23

Slide 23 text

or_else(op) op: E -> result  異常値を持っているとき、更に失敗する可能性のある関数へチェインする result res(failure{42}); res.or_else([](auto ok) -> result { if ( ok % 2 == 0 ) return success{ok+1}; else return failure{ok}; }); // success(43)

Slide 24

Slide 24 text

参照のResult

Slide 25

Slide 25 text

参照のResult  result< T&, E& >  内部ではBoost.VariantとBoost.Optionalを利用  ライフタイム安全性を考慮してdanglingを導入(後述)

Slide 26

Slide 26 text

参照のResult  mutabilityは保持する値のアクセスでも伝搬する str hoge = "foo"; mut_result res(success{hoge}); res.unwrap() = "bar"; // now hoge == "bar" result res(success{hoge}); // Error: immutable result’s unwrap returns const reference res.unwrap() = "bar";

Slide 27

Slide 27 text

as_ref()  Resultの参照を参照のResultに変換する  Resultが内部に持っている値のconstな参照を持つResultを作れる result res(success{"bar"}); res.as_ref(); // new result, containing a const reference into the original

Slide 28

Slide 28 text

as_mut()  Resultのミュータブルな参照をミュータブルな参照のResultに変換する  Resultが内部に持っている値の参照を持つResultを作れる mut_result res(success{"foo"}); res .as_mut() // new result, containing a reference into the original .unwrap() = "bar"; // now res contains a successful value "bar"

Slide 29

Slide 29 text

indirect()  要素型をデリファレンスした型の参照のResultを作る  result<T*, E*> => result<T, E>  (T ok, E err) に対して { *ok, *err } という操作が要求されりゅ using vec_iter = typename std::vector::iterator; std::vector vec{1, 2, 3}; mut_result res(success{vec.begin()}); auto indirect = res.indirect(); // indirect: `mut_result` auto& ref = indirect.unwrap(); ref = 42;

Slide 30

Slide 30 text

indirect_ok()  success型をデリファレンスした型の参照のResultを作る  result<T*, E> => result<T, E>  (T ok) に対して { *ok } という操作が要求されりゅ using vec_iter = typename std::vector::iterator; std::vector vec{1, 2, 3}; mut_result res(success{vec.begin()}); auto indirect = res.indirect_ok(); // indirect: `mut_result` auto& ref = indirect.unwrap(); ref = 42;

Slide 31

Slide 31 text

indirect_err()  failure型をデリファレンスした型の参照のResultを作る  result<T, E*> => result<T, E>  (E err) に対して { *err } という操作が要求されりゅ using vec_iter = typename std::vector::iterator; std::vector vec{1, 2, 3}; mut_result res(failure{vec.begin()}); auto indirect = res.indirect_err(); // indirect: `mut_result` auto& ref = indirect.unwrap(); ref = 42;

Slide 32

Slide 32 text

ライフタイムについて  右辺値のresultで内部を参照して場合、寿命切れている可能性がる using vec = std::vector; mut_result(success{vec{1, 2, 3}}) .as_mut() .unwrap() // undefined behavior!

Slide 33

Slide 33 text

dangling  右辺値のresultの内部を参照した場合  dangling>( dangle_ref )ができる using vec = std::vector; mut_result(success{vec{1, 2, 3}}) .as_mut(); // returns `mut_result, dangle_ref>`

Slide 34

Slide 34 text

dangling::transmute()  参照が生きていることを確信する場合は明示的に中身を取り出す using vec = std::vector; vec v{1, 2, 3}; mut_result(success{v}) .unwrap() // `mut_result, dangle_ref>` .transmute(); // get `vec&`

Slide 35

Slide 35 text

さいごに  Rustの?に相当する操作がないので使いにくい  match式もないし、作ったとしても網羅検査がないので心もとない  でもなにもないよりはだいぶまし  使ってみてフィードバックほしい