Upgrade to Pro — share decks privately, control downloads, hide ads and more …

C++でもRustのResultが使いたい

 C++でもRustのResultが使いたい

C++MIX #4の発表スライド

Ea5ac91e16bd33383140d4c2753432d7?s=128

Cranberries

June 26, 2019
Tweet

Transcript

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

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

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

     いつものようにマイライブラリでなんとかする()
  4. RustのResult<T, E>  Rustのエラーハンドリングで使われる型  成功を表し値を含むOk(T)  およびエラーを表しエラー値を含むErr(E)  の列挙型(代数的データ型)

     正常値も異常値も両方運搬できる  エラーメッセージも運搬できる
  5. 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
  6. クラス宣言 enum class mutability: bool { mut = false, immut

    = true, }; template <mutability, // mutability specification class = std::monostate, // success type class = std::monostate, // failure type class = decltype(nullptr) // for detection idiom > class basic_result;
  7. ミュータビリティ  ミュータビリティが型に埋め込まれいる(正直やりすぎ感ある)  内部で保持する値へのアクセスの制御に使われている  利用者はシノニムを利用する /// alias template

    for immutable result template <class T = std::monostate, class E = std::monostate> using result = basic_result<mutability::immut, T, E>; /// alias template for mutable result template <class T = std::monostate, class E = std::monostate> using mut_result = basic_result<mutability::mut, T, E>;
  8. resultの構築  success, failureというfactoryクラスから作れる  関数からresultを返す場合も result<str, str> res(success<str>{"foo"}); result<str,

    str> res(failure<str>{"foo"}); auto func(bool b) -> result<str, str> { if (b) { return success{"foo"}; } else { return failure{"bar"}; } }
  9. おしらせ  以降  正常値の型をTとします  異常値の型をEとします  幅節約のためstd::stringのエイリアスとしてstrを使います

  10. APIs Observers

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

    res.is_err(); // false result<str, str> res(failure<str>{"foo"}); res.is_ok(); // false res.is_err(); // true
  12. ok(), err()  それぞれ正常値・異常値をboost::optionalで取得できる result<int, int> res(success{42}); res.ok(); // Some(42)

    res.err(); // None result<int, int> res(failure{42}); res.ok(); // None res.err(); // Some(42)
  13. unwrap(), unwrap_err()  それぞれ正常値・異常値を取り出す  値を持っていなければ例外を送出する result<int, int> res(success{42}); res.unwrap();

    // 42 res.unwrap_err(); // raise the exception `mitama::runtime_panic` result<int, int> res(failure{42}); res.unwrap(); // raise the exception `mitama::runtime_panic` res.unwrap_err(); // 42
  14. unwrap_or(T optb)  正常値を取り出す  値を持っていなければ指定された値optbを返す result<int, int> res(success{42}); res.unwrap_or(57);

    // 42 result<int, int> res(failure{42}); res.unwrap_or(57); // 57
  15. unwrap_or_else(F func) F: E -> T  正常値を取り出す  異常値errを持っていなければfunc(err)を返す

     遅延評価したいときに使う  引数なしで呼べる場合はなしで呼ぶ result<int, str> res(success{42}); res.unwrap_or_else([](int err){ return err.length(); }); // 42 result<int, int> res(failure{42}); res.unwrap_or_else([]{ return 57; }); // 57
  16. unwrap_or_default()  正常値を取り出す  値を持っていなければ指定された値を返す  正常型がデフォルトコンストラクション可能かアグリゲートである必要がある result<int, int> res(success{42});

    res.unwrap_or_default(); // 42 result<int, int> res(failure{42}); res.unwrap_or_default(); // 0
  17. expect(string_view msg)  unwrap()の亜種で例外のメッセージに含める文字列を指定できりゅ result<int, int> res(failure{42}); auto ok =

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

    res.expect_err("foo"); // panicked at 'foo: success(42)' result<int, int> res(success{42}); auto err = res.unwrap_err(); // panicked at 'called `result::unwrap_err()` on a value: success(42)'
  19. APIs Monadic functions

  20. map(op) op: T -> U  正常値okを持っているとき場合、op(ok) を適用しresult<U, E>として返す 

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

    正常値を持っていた場合はそれをresult<T, F>として返す result<int, int> res(success{42}); res.map_err([](auto err){ return err - 1; }); // success(42) result<int, int> res(failure{42}); res.map_err([](auto err){ return err - 1; }); // failure(41)
  22. and_then(op) op: T -> result<U, E>  正常値を持っているとき、更に失敗する可能性のある関数へチェインする result<int, int>

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

    res(failure{42}); res.or_else([](auto ok) -> result<int, int> { if ( ok % 2 == 0 ) return success{ok+1}; else return failure{ok}; }); // success(43)
  24. 参照のResult

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

  26. 参照のResult  mutabilityは保持する値のアクセスでも伝搬する str hoge = "foo"; mut_result<str&, str&> res(success<str&>{hoge});

    res.unwrap() = "bar"; // now hoge == "bar" result <str&, str&> res(success<str&>{hoge}); // Error: immutable result’s unwrap returns const reference res.unwrap() = "bar";
  27. as_ref()  Resultの参照を参照のResultに変換する  Resultが内部に持っている値のconstな参照を持つResultを作れる result<str, str> res(success{"bar"}); res.as_ref(); //

    new result, containing a const reference into the original
  28. as_mut()  Resultのミュータブルな参照をミュータブルな参照のResultに変換する  Resultが内部に持っている値の参照を持つResultを作れる mut_result<str, str> res(success{"foo"}); res .as_mut()

    // new result, containing a reference into the original .unwrap() = "bar"; // now res contains a successful value "bar"
  29. indirect()  要素型をデリファレンスした型の参照のResultを作る  result<T*, E*> => result<T, E> 

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

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

    (E err) に対して { *err } という操作が要求されりゅ using vec_iter = typename std::vector<int>::iterator; std::vector<int> vec{1, 2, 3}; mut_result<int, vec_iter> res(failure<vec_iter>{vec.begin()}); auto indirect = res.indirect_err(); // indirect: `mut_result<int&, int>` auto& ref = indirect.unwrap(); ref = 42;
  32. ライフタイムについて  右辺値のresultで内部を参照して場合、寿命切れている可能性がる using vec = std::vector<int>; mut_result<vec, vec>(success{vec{1, 2,

    3}}) .as_mut() .unwrap() // undefined behavior!
  33. dangling<T>  右辺値のresultの内部を参照した場合  dangling<std::reference_wrapper<T>>( dangle_ref )ができる using vec =

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

    3}; mut_result<vec&, vec&>(success<vec&>{v}) .unwrap() // `mut_result<dangle_ref<vec>, dangle_ref<vec>>` .transmute(); // get `vec&`
  35. さいごに  Rustの?に相当する操作がないので使いにくい  match式もないし、作ったとしても網羅検査がないので心もとない  でもなにもないよりはだいぶまし  使ってみてフィードバックほしい