├── .codecov.yml ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dub.sdl ├── dub.selections.json ├── meson.build ├── source └── expected.d └── tests └── expected.d /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Documentation: https://github.com/codecov/support/wiki/codecov.yml 2 | # Validate with: curl --data-binary @.codecov.yml https://codecov.io/validate 3 | 4 | codecov: 5 | notify: 6 | # We don't want to wait for the CodeCov report 7 | # See https://github.com/codecov/support/issues/312 8 | require_ci_to_pass: false 9 | after_n_builds: 1 # send notifications after the first upload 10 | wait_for_ci: false 11 | 12 | # At Travis, the PR is merged into `master` before the testsuite is run. 13 | # This allows CodeCov to adjust the resulting coverage diff, s.t. it matches 14 | # with the GitHub diff. 15 | # https://github.com/codecov/support/issues/363 16 | # https://docs.codecov.io/v4.3.6/docs/comparing-commits 17 | allow_coverage_offsets: true 18 | 19 | coverage: 20 | precision: 3 21 | round: down 22 | range: 50...100 23 | 24 | status: 25 | # Learn more at https://codecov.io/docs#yaml_default_commit_status 26 | project: off 27 | patch: 28 | default: 29 | informational: true 30 | changes: off 31 | 32 | comment: false 33 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | name: ci 6 | 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | jobs: 14 | test: 15 | name: tests 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, windows-latest, macOS-latest] 19 | dc: [dmd-latest, ldc-latest] 20 | include: 21 | - { os: ubuntu-latest, dc: dmd-2.094.2, arch: x86_64 } 22 | - { os: ubuntu-latest, dc: dmd-2.091.1, arch: x86_64 } 23 | - { os: ubuntu-latest, dc: ldc-1.23.0, arch: x86_64 } 24 | runs-on: ${{ matrix.os }} 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: Install D compiler 28 | uses: dlang-community/setup-dlang@v1 29 | with: 30 | compiler: ${{ matrix.dc }} 31 | - id: normal 32 | name: Normal tests 33 | run: dub test 34 | - name: betterC tests 35 | if: matrix.dc != 'dmd-2.091.1' 36 | run: dub test -c=betterC 37 | - name: Upload codecov 38 | if: matrix.os == 'ubuntu-latest' && matrix.dc == 'dmd-latest' 39 | run: | 40 | dub test -b=unittest-cov -v -- -t 1 -v || true 41 | bash <(curl -s https://codecov.io/bash) 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | *-test-* 6 | *.so 7 | *.dylib 8 | *.dll 9 | *.a 10 | *.exe 11 | *.o 12 | *.obj 13 | *.lst 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | sudo: false 3 | 4 | branches: 5 | only: 6 | - master 7 | 8 | notifications: 9 | email: true 10 | 11 | d: 12 | - dmd-2.089.0 13 | - dmd-2.088.1 14 | - dmd-2.087.1 15 | - dmd-2.086.1 16 | - dmd-2.085.1 17 | - ldc-1.18.0 18 | - ldc-1.17.0 19 | - ldc-1.16.0 20 | - ldc-1.15.0 21 | - ldc-1.14.0 22 | - dmd-beta 23 | 24 | script: 25 | - dub test -c basicunittest --compiler=${DC} 26 | 27 | jobs: 28 | include: 29 | - stage: betterC 30 | script: 31 | - dub test -c bettercunittest --main-file=tests/expected.d --compiler=${DC} 32 | - stage: codecov 33 | script: 34 | - dub test -b unittest-cov --compiler=${DC} 35 | - bash <(curl -s https://codecov.io/bash) 36 | - stage: docs 37 | script: 38 | - wget https://github.com/adamdruppe/adrdox/archive/master.zip 39 | - unzip master.zip 40 | - pushd adrdox-master && make && popd 41 | - export PATH=$PATH:$PWD/adrdox-master 42 | - doc2 --genSearchIndex --genSource -o generated-docs source 43 | deploy: &pages 44 | provider: pages 45 | skip_cleanup: true 46 | github_token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable 47 | keep_history: true 48 | local_dir: generated-docs 49 | on: 50 | branch: master 51 | 52 | env: 53 | global: 54 | secure: hNwFLgJSfUjH4foPRWxsnAVDQacqPiFmnMs5LSPohOD95v5IaNhjMJaUDgHXjc49Qmtx0ZXb52uUB3s9EtoEhu5GmDaztIwM7J9q7cpgJLSCun+9mn/vYI2VR2E0/CnjtqE+K93XFE2QQK1PKfefWAv72L2X3oSyHyZRn0QGnN6549KConEO1jU3Bg3sU+Pss6dMIybhtqs66Vg4CCMvrYkx+7OponJJOiNlUwTKmjH083HxPly8gnTzAMtfnxMsmgSR/Rll35Wej1yuUOvd4rSoLQWTwozg0rfORs/BMkl1O+ECeoHB6zTeQtU/CWHj+ANv+q9Yvj8tVkol5Qn79s8NJJNQnTW4N8LPGtE5CFHRtXzyOgMWAfUIRkQ4Ns/Bms3usWFzxCq5iBIHJ3TRBPNIdKhmvCec3Yh0wrl2I5xyjaDsD+XGGnatHU+KPozD11ef1fXWOZfdp6RRxCz6HCsRFvbNtC6Rv8MLyPn0ABCuLCaJPyG5Jm2AjNdiCKsE6Bm0otQbJFluCNNE0c0ED+Z3E8OdFe3z8V3kS08y+lD2UNFxsHkytquc2YFyKboOAaKvxXwN3Y3Y4CZm3dl0aHV6QLQeGY1wM52B+To+zLdcwYheSB3U8zWgpo2iGv3E1ws5ZkjsMYx0m9EegFZJ06THjuA/C7Pi4o3naoYX5yM= 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Expected 2 | 3 | [![Latest version](https://img.shields.io/dub/v/expected.svg)](https://code.dlang.org/packages/expected) 4 | [![Dub downloads](https://img.shields.io/dub/dt/expected.svg)](http://code.dlang.org/packages/expected) 5 | [![Actions Status](https://github.com/tchaloupka/expected/workflows/ci/badge.svg)](https://github.com/tchaloupka/expected/actions) 6 | [![codecov](https://codecov.io/gh/tchaloupka/expected/branch/master/graph/badge.svg)](https://codecov.io/gh/tchaloupka/expected) 7 | [![license](https://img.shields.io/github/license/tchaloupka/expected.svg)](https://github.com/tchaloupka/expected/blob/master/LICENSE) 8 | 9 | Implementation of the Expected idiom. 10 | 11 | See the [Andrei Alexandrescu’s talk (Systematic Error Handling in C++](http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C) 12 | and [its slides](https://skydrive.live.com/?cid=f1b8ff18a2aec5c5&id=F1B8FF18A2AEC5C5!1158). 13 | 14 | Or more recent ["Expect the Expected"](https://www.youtube.com/watch?v=nVzgkepAg5Y) by Andrei Alexandrescu for further background. 15 | 16 | It is also inspired by C++'s proposed [std::expected](https://wg21.link/p0323) and [Rust's](https://www.rust-lang.org/) [Result](https://doc.rust-lang.org/std/result/). 17 | 18 | Similar work is [expectations](http://code.dlang.org/packages/expectations) by Paul Backus. 19 | 20 | ## Features 21 | 22 | * lightweight, no other external dependencies 23 | * works with `pure`, `@safe`, `@nogc`, `nothrow`, and `immutable` 24 | * provides methods: `ok`, `err`, `consume`, `expect`, `expectErr`, `andThen`, `orElse`, `map`, `mapError`, `mapOrElse` 25 | * type inference for ease of use with `ok` and `err` 26 | * allows to use same types for `T` and `E` 27 | * allows to define `Expected` without value (`void` for `T`) - can be disabled with custom `Hook` 28 | * provides facility to change the `Expected` behavior by custom `Hook` implementation using the Design by introspection paradigm. 29 | * can enforce result check (with a cost) 30 | * can behave like a normal `Exception` handled code by changing the used `Hook` implementation 31 | * range interface 32 | 33 | ## Documentation 34 | 35 | [View online on Github Pages](https://tchaloupka.github.io/expected/expected.html) 36 | 37 | `expected` uses [adrdox](https://github.com/adamdruppe/adrdox) to generate it's documentation. To build your own 38 | copy, run the following command from the root of the `expected` repository: 39 | 40 | ```BASH 41 | path/to/adrdox/doc2 --genSearchIndex --genSource -o generated-docs source 42 | ``` 43 | 44 | ## Example usage 45 | 46 | ```D 47 | auto foo(int i) { 48 | if (i == 0) return err!int("oops"); 49 | return ok(42 / i); 50 | } 51 | 52 | auto bar(int i) { 53 | if (i == 0) throw new Exception("err"); 54 | return i-1; 55 | } 56 | 57 | // basic checks 58 | assert(foo(2)); 59 | assert(foo(2).hasValue); 60 | assert(!foo(2).hasError); 61 | assert(foo(2).value == 21); 62 | 63 | assert(!foo(0)); 64 | assert(!foo(0).hasValue); 65 | assert(foo(0).hasError); 66 | assert(foo(0).error == "oops"); 67 | 68 | // void result 69 | assert(ok()); // no error -> success 70 | assert(!ok().hasError); 71 | // assert(err("foo").hasValue); // doesn't have hasValue and value properties 72 | 73 | // expected from throwing function 74 | assert(consume!bar(1) == 0); 75 | assert(consume!bar(0).error.msg == "err"); 76 | 77 | // orElse 78 | assert(foo(2).orElse!(() => 0) == 21); 79 | assert(foo(0).orElse(100) == 100); 80 | 81 | // andThen 82 | assert(foo(2).andThen(foo(6)) == 7); 83 | assert(foo(0).andThen(foo(6)).error == "oops"); 84 | 85 | // map 86 | assert(foo(2).map!(a => a*2).map!(a => a - 2) == 40); 87 | assert(foo(0).map!(a => a*2).map!(a => a - 2).error == "oops"); 88 | 89 | // mapError 90 | assert(foo(0).mapError!(e => "OOPS").error == "OOPS"); 91 | assert(foo(2).mapError!(e => "OOPS") == 21); 92 | 93 | // mapOrElse 94 | assert(foo(2).mapOrElse!(v => v*2, e => 0) == 42); 95 | assert(foo(0).mapOrElse!(v => v*2, e => 0) == 0); 96 | ``` 97 | 98 | See [documentation](https://tchaloupka.github.io/expected/expected.html#examples) for more usage examples. 99 | 100 | ## Installation 101 | 102 | If you're using [dub](), add the [expected](https://code.dlang.org/packages/expected) package to your project as a dependency. 103 | 104 | Alternatively, since it's a single file self-contained implementation, you can simply copy `expected.d` to your project source directory and compile as usual. 105 | 106 | ## Compilers compatibility 107 | 108 | Build is tested against: 109 | 110 | * dmd-latest 111 | * dmd-2.095.1 112 | * dmd-2.094.2 113 | * dmd-2.091.1 114 | * ldc-latest 115 | * ldc-1.27.1 116 | * ldc-1.25.1 117 | * ldc-1.24.0 118 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "expected" 2 | description "Implementation of expected idiom (error handling with ok and err)" 3 | authors "Tomáš Chaloupka" 4 | copyright "Copyright © 2019, Tomáš Chaloupka" 5 | license "BSL-1.0" 6 | 7 | configuration "default" { 8 | } 9 | 10 | configuration "betterC" { 11 | buildOptions "betterC" 12 | } 13 | 14 | configuration "unittest" { 15 | dependency "silly" version=">=1.1.0" 16 | importPaths "tests" 17 | sourcePaths "tests" 18 | } 19 | 20 | configuration "expected-test-betterC" { 21 | targetType "executable" 22 | importPaths "tests" 23 | sourcePaths "tests" 24 | buildOptions "betterC" 25 | } 26 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "silly": "1.1.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('expected', 'd', 2 | version: '0.3.4' 3 | ) 4 | 5 | expected_build_versions = [] 6 | 7 | expected_lib = static_library( 8 | 'expected', 9 | 'source/expected.d', 10 | d_module_versions: expected_build_versions 11 | ) 12 | 13 | expected_dep = declare_dependency( 14 | include_directories: include_directories('source'), 15 | link_with: expected_lib 16 | ) 17 | 18 | bc_link_args = [] 19 | if meson.get_compiler('d').get_id() == 'llvm' 20 | bc_link_args += '-link-defaultlib-shared=false' 21 | endif 22 | 23 | test_exe = executable( 24 | 'expected-test', 25 | ['source/expected.d', 'tests/expected.d'], 26 | include_directories: include_directories('source'), 27 | d_args: ['-betterC', '-unittest'], 28 | link_args: bc_link_args 29 | ) 30 | test('bctest', test_exe) 31 | -------------------------------------------------------------------------------- /source/expected.d: -------------------------------------------------------------------------------- 1 | /++ 2 | This module is implementing the Expected idiom. 3 | 4 | See the [Andrei Alexandrescu’s talk (Systematic Error Handling in C++](http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C) 5 | and [its slides](https://skydrive.live.com/?cid=f1b8ff18a2aec5c5&id=F1B8FF18A2AEC5C5!1158). 6 | 7 | Or more recent ["Expect the Expected"](https://www.youtube.com/watch?v=nVzgkepAg5Y) by Andrei Alexandrescu for further background. 8 | 9 | It is also inspired by C++'s proposed [std::expected](https://wg21.link/p0323) and [Rust's](https://www.rust-lang.org/) [Result](https://doc.rust-lang.org/std/result/). 10 | 11 | Similar work is [expectations](http://code.dlang.org/packages/expectations) by Paul Backus. 12 | 13 | ## Main features 14 | 15 | $(LIST 16 | * lightweight, no other external dependencies 17 | * works with `pure`, `@safe`, `@nogc`, `nothrow`, and `immutable` 18 | * provides methods: `ok`, `err`, `consume`, `expect`, `expectErr`, `andThen`, `orElse`, `map`, `mapError`, `mapOrElse` 19 | * type inference for ease of use with `ok` and `err` 20 | * allows to use same types for `T` and `E` 21 | * allows to define $(LREF Expected) without value (`void` for `T`) - can be disabled with custom `Hook` 22 | * provides facility to change the $(LREF Expected) behavior by custom `Hook` implementation using the Design by introspection paradigm. 23 | * can enforce result check (with a cost) 24 | * can behave like a normal `Exception` handled code by changing the used `Hook` implementation 25 | * range interface 26 | ) 27 | 28 | ## Description 29 | 30 | Actual $(LREF Expected) type is defined as $(D Expected!(T, E, Hook)), where: 31 | 32 | $(LIST 33 | * `T` defines type of the success value 34 | * `E` defines type of the error 35 | * `Hook` defines behavior of the $(LREF Expected) 36 | ) 37 | 38 | Default type for error is `string`, i.e. `Expected!int` is the same as `Expected!(int, string)`. 39 | 40 | $(LREF Abort) is used as a default hook. 41 | 42 | ### Hooks 43 | 44 | $(LREF Expected) has customizable behavior with the help of a third type parameter, 45 | `Hook`. Depending on what methods `Hook` defines, core operations on the 46 | $(LREF Expected) may be verified or completely redefined. 47 | If `Hook` defines no method at all and carries no state, there is no change in 48 | default behavior. 49 | 50 | This module provides a few predefined hooks (below) that add useful behavior to 51 | $(LREF Expected): 52 | 53 | $(BOOKTABLE , 54 | $(TR $(TD $(LREF Abort)) $(TD 55 | Fails every incorrect operation with a call to `assert(0)`. 56 | It is the default third parameter, i.e. $(D Expected!short) is the same as 57 | $(D Expected!(short, string, Abort)). 58 | )) 59 | $(TR $(TD $(LREF Throw)) $(TD 60 | Fails every incorrect operation by throwing an exception. 61 | )) 62 | $(TR $(TD $(LREF AsException)) $(TD 63 | With this hook implementation $(LREF Expected) behaves just like regular 64 | $(D Exception) handled code. 65 | 66 | That means when function returns $(LREF expected) value, it returns instance 67 | of $(LREF Expected) with a success value. 68 | But when it tries to return error, $(D Exception) is thrown right away, 69 | i.e. $(LREF Expected) fails in constructor. 70 | )) 71 | $(TR $(TD $(LREF RCAbort)) $(TD 72 | Similar to $(LREF Abort) hook but uses reference counted payload instead 73 | which enables checking if the caller properly checked result of the 74 | $(LREF Expected). 75 | )) 76 | ) 77 | 78 | The hook's members are looked up statically in a Design by Introspection manner 79 | and are all optional. The table below illustrates the members that a hook type 80 | may define and their influence over the behavior of the `Checked` type using it. 81 | In the table, `hook` is an alias for `Hook` if the type `Hook` does not 82 | introduce any state, or an object of type `Hook` otherwise. 83 | 84 | $(TABLE_ROWS 85 | * + Hook member 86 | + Semantics in Expected!(T, E, Hook) 87 | * - `enableDefaultConstructor` 88 | - If defined, $(LREF Expected) would have enabled or disabled default constructor 89 | based on it's `bool` value. Default constructor is disabled by default. 90 | `opAssign` for value and error types is generated if default constructor is enabled. 91 | * - `enableCopyConstructor` 92 | - If defined, $(LREF Expected) would have enabled or disabled copy constructor based 93 | on it's `bool` value. It is enabled by default. When disabled, it enables automatic 94 | check if the result was checked either for value or error. 95 | When not checked it calls $(D hook.onUnchecked) if provided. 96 | 97 | $(NOTE WARNING: As currently it's not possible to change internal state of `const` 98 | or `immutable` object, automatic checking would't work on these. Hopefully with 99 | `__mutable` proposal..) 100 | * - `enableRefCountedPayload` 101 | - Set $(LREF Expected) instances to use reference counted payload storage. It's usefull 102 | when combined with `onUnchecked` to forcibly check that the result was checked for value 103 | or error. 104 | * - `enableVoidValue` 105 | - Defines if $(LREF Expected) supports `void` values. It's enabled by default so this 106 | hook can be used to disable it. 107 | * - `onAccessEmptyValue` 108 | - If value is accessed on unitialized $(LREF Expected) or $(LREF Expected) with error 109 | value, $(D hook.onAccessEmptyValue!E(err)) is called. If hook doesn't implement the 110 | handler, `T.init` is returned. 111 | * - `onAccessEmptyError` 112 | - If error is accessed on unitialized $(LREF Expected) or $(LREF Expected) with value, 113 | $(D hook.onAccessEmptyError()) is called. If hook doesn't implement the handler, 114 | `E.init` is returned. 115 | * - `onUnchecked` 116 | - If the result of $(LREF Expected) isn't checked, $(D hook.onUnchecked()) is called to 117 | handle the error. If hook doesn't implement the handler, assert is thrown. 118 | $(NOTE Note that `hook.enableCopyConstructor` must be `false` or `hook.enableRefCountedPayload` 119 | must be `true` for checks to work.) 120 | * - `onValueSet` 121 | - $(D hook.onValueSet!T(val)) function is called when success value is being set to 122 | $(LREF Expected). It can be used for loging purposes, etc. 123 | * - `onErrorSet` 124 | - $(D hook.onErrorSet!E(err)) function is called when error value is being set to 125 | $(LREF Expected). This hook function is used by $(LREF AsException) hook implementation 126 | to change `Expected` idiom to normal `Exception` handling behavior. 127 | ) 128 | 129 | License: BSL-1.0 130 | Author: Tomáš Chaloupka 131 | +/ 132 | 133 | //TODO: collect errno function call - see https://dlang.org/phobos/std_exception.html#ErrnoException 134 | 135 | module expected; 136 | 137 | /// $(H3 Basic usage) 138 | @("Basic usage example") 139 | @safe unittest 140 | { 141 | auto foo(int i) { 142 | if (i == 0) return err!int("oops"); 143 | return ok(42 / i); 144 | } 145 | 146 | version (D_Exceptions) 147 | { 148 | auto bar(int i) { 149 | if (i == 0) throw new Exception("err"); 150 | return i-1; 151 | } 152 | } 153 | 154 | // basic checks 155 | assert(foo(2)); 156 | assert(foo(2).hasValue); 157 | assert(!foo(2).hasError); 158 | assert(foo(2).value == 21); 159 | 160 | assert(!foo(0)); 161 | assert(!foo(0).hasValue); 162 | assert(foo(0).hasError); 163 | assert(foo(0).error == "oops"); 164 | 165 | // void result 166 | assert(ok()); // no error -> success 167 | assert(!ok().hasError); 168 | // assert(err("foo").hasValue); // doesn't have hasValue and value properties 169 | 170 | version (D_Exceptions) 171 | { 172 | // expected from throwing function 173 | assert(consume!bar(1) == 0); 174 | assert(consume!bar(0).error.msg == "err"); 175 | } 176 | 177 | // orElse 178 | assert(foo(2).orElse!(() => 0) == 21); 179 | assert(foo(0).orElse(100) == 100); 180 | 181 | // andThen 182 | assert(foo(2).andThen(foo(6)) == 7); 183 | assert(foo(0).andThen(foo(6)).error == "oops"); 184 | 185 | // map 186 | assert(foo(2).map!(a => a*2).map!(a => a - 2) == 40); 187 | assert(foo(0).map!(a => a*2).map!(a => a - 2).error == "oops"); 188 | 189 | // mapError 190 | assert(foo(0).mapError!(e => "OOPS").error == "OOPS"); 191 | assert(foo(2).mapError!(e => "OOPS") == 21); 192 | 193 | // mapOrElse 194 | assert(foo(2).mapOrElse!(v => v*2, e => 0) == 42); 195 | assert(foo(0).mapOrElse!(v => v*2, e => 0) == 0); 196 | } 197 | 198 | version (D_Exceptions) 199 | { 200 | /// $(H3 Advanced usage - behavior modification) 201 | @("Advanced usage example") 202 | @safe unittest 203 | { 204 | import exp = expected; 205 | 206 | // define our Expected type using Exception as Error values 207 | // and Throw hook, which throws when empty value or error is accessed 208 | template Expected(T) 209 | { 210 | alias Expected = exp.Expected!(T, Exception, Throw); 211 | } 212 | 213 | // create wrappers for simplified usage of our Expected 214 | auto ok(T)(T val) { return exp.ok!(Exception, Throw)(val); } 215 | auto err(T)(Exception err) { return exp.err!(T, Throw)(err); } 216 | 217 | // use it as normal 218 | assert(ok(42) == 42); 219 | assert(err!int(new Exception("foo")).orElse(0) == 0); 220 | assertThrown(ok(42).error); 221 | assertThrown(err!int(new Exception("bar")).value); 222 | } 223 | } 224 | 225 | version (unittest) { 226 | import std.algorithm : reverse; 227 | import std.exception : assertThrown, collectExceptionMsg; 228 | } 229 | 230 | /++ 231 | `Expected!(T, E)` is a type that represents either success or failure. 232 | 233 | Type `T` is used for success value. 234 | If `T` is `void`, then $(LREF Expected) can only hold error value and is considered a success when there is no error value. 235 | 236 | Type `E` is used for error value. 237 | The default type for the error value is `string`. 238 | 239 | Default behavior of $(LREF Expected) can be modified by the `Hook` template parameter. 240 | 241 | Params: 242 | T = represents type of the expected value 243 | E = represents type of the error value. 244 | Hook = defines the $(LREF Expected) type behavior 245 | +/ 246 | struct Expected(T, E = string, Hook = Abort) 247 | if (!is(E == void) && (isVoidValueEnabled!Hook || !is(T == void))) 248 | { 249 | import core.lifetime : forward; 250 | import std.meta : AliasSeq, Filter, NoDuplicates; 251 | import std.traits: isAssignable, isCopyable, hasIndirections, Unqual; 252 | 253 | private template noVoid(T) { enum noVoid = !is(T == void); } // Erase removes qualifiers 254 | private alias Types = NoDuplicates!(Filter!(noVoid, AliasSeq!(T, E))); 255 | 256 | static foreach (i, CT; Types) 257 | { 258 | /++ 259 | Constructs an $(LREF Expected) with value or error based on the tye of the provided. 260 | 261 | In case when `T == E`, it constructs $(LREF Expected) with value. 262 | 263 | In case when `T == void`, it constructs $(LREF Expected) with error value. 264 | 265 | Default constructor (if enabled) initializes $(LREF Expected) to `T.init` value. 266 | If `T == void`, it initializes $(LREF Expected) with no error. 267 | +/ 268 | this()(auto ref CT val) 269 | { 270 | static if (isRefCountedPayloadEnabled!Hook) 271 | { 272 | initialize(forward!val); 273 | } 274 | else 275 | { 276 | storage = Payload(forward!val); 277 | } 278 | setState!CT(); 279 | 280 | static if (hasOnValueSet!(Hook, CT)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(getValue()); } 281 | static if (hasOnErrorSet!(Hook, CT)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(getError()); } 282 | } 283 | 284 | // static if (isCopyable!CT) 285 | // { 286 | // / ditto 287 | // this()(auto ref const(CT) val) const 288 | // { 289 | // storage = const(Payload)(val); 290 | // setState!CT(); 291 | 292 | // static if (hasOnValueSet!(Hook, CT)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(val); } 293 | // static if (hasOnErrorSet!(Hook, CT)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(val); } 294 | // } 295 | 296 | // /// ditto 297 | // this()(auto ref immutable(CT) val) immutable 298 | // { 299 | // storage = immutable(Payload)(val); 300 | // setState!CT(); 301 | 302 | // static if (hasOnValueSet!(Hook, CT)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(val); } 303 | // static if (hasOnErrorSet!(Hook, CT)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(val); } 304 | // } 305 | // } 306 | // else 307 | // { 308 | // @disable this(const(CT) val) const; 309 | // @disable this(immutable(CT) val) immutable; 310 | // } 311 | } 312 | 313 | // generate constructor with flag to determine type of value 314 | static if (Types.length == 1 && !is(T == void)) 315 | { 316 | /++ Constructs an $(LREF Expected) with value or error based on the provided flag. 317 | This constructor is available only for cases when value and error has the same type, 318 | so we can still construct $(LREF Expected) with value or error. 319 | 320 | Params: 321 | val = Value to set as value or error 322 | success = If `true`, $(LREF Expected) with value is created, $(LREF Expected) with error otherwise. 323 | +/ 324 | this()(auto ref E val, bool success) 325 | { 326 | static if (isRefCountedPayloadEnabled!Hook) 327 | { 328 | initialize(forward!val); 329 | } 330 | else 331 | { 332 | storage = Payload(forward!val); 333 | } 334 | setState!E(success ? State.value : State.error); 335 | 336 | static if (hasOnValueSet!(Hook, E)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(getValue()); } 337 | static if (hasOnErrorSet!(Hook, E)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(getError()); } 338 | } 339 | 340 | // static if (isCopyable!E) 341 | // { 342 | // /// ditto 343 | // this()(auto ref const(E) val, bool success) const 344 | // { 345 | // storage = const(Payload)(val); 346 | // setState!E(success ? State.value : State.error); 347 | 348 | // static if (hasOnValueSet!(Hook, E)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(val); } 349 | // static if (hasOnErrorSet!(Hook, E)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(val); } 350 | // } 351 | 352 | // /// ditto 353 | // this()(auto ref immutable(E) val, bool success) immutable 354 | // { 355 | // storage = immutable(Payload)(val); 356 | // setState!E(success ? State.value : State.error); 357 | 358 | // static if (hasOnValueSet!(Hook, E)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(val); } 359 | // static if (hasOnErrorSet!(Hook, E)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(val); } 360 | // } 361 | // } 362 | // else 363 | // { 364 | // @disable this(const(E) val, bool success) const; 365 | // @disable this(immutable(E) val, bool success) immutable; 366 | // } 367 | } 368 | 369 | static if (!is(T == void) && !isDefaultConstructorEnabled!Hook) @disable this(); 370 | 371 | static if (!isCopyConstructorEnabled!Hook) @disable this(this); 372 | 373 | static if (isChecked!Hook || isRefCountedPayloadEnabled!Hook) 374 | { 375 | ~this() 376 | { 377 | static void onUnchecked() 378 | { 379 | static if (hasOnUnchecked!Hook) __traits(getMember, Hook, "onUnchecked")(); 380 | else assert(0, "unchecked result"); 381 | } 382 | 383 | static if (isRefCountedPayloadEnabled!Hook) 384 | { 385 | if (!storage) return; 386 | assert(storage.count > 0); 387 | 388 | if (--storage.count) return; 389 | 390 | // Done, deallocate 391 | static if (isChecked!Hook) bool ch = checked; 392 | destroy(storage.payload); 393 | static if (enableGCScan) () @trusted { pureGcRemoveRange(&storage.payload); } (); 394 | 395 | () @trusted 396 | { 397 | pureFree(storage); 398 | storage = null; 399 | }(); 400 | 401 | static if (isChecked!Hook) { if (!ch) onUnchecked(); } 402 | } 403 | else static if (isChecked!Hook) { if (!checked) onUnchecked(); } 404 | } 405 | } 406 | 407 | static if (isDefaultConstructorEnabled!Hook) 408 | { 409 | static foreach (i, CT; Types) 410 | { 411 | static if (isAssignable!CT) 412 | { 413 | /++ Assigns a value or error to an $(LREF Expected). 414 | 415 | Note: This is only allowed when default constructor is also enabled. 416 | +/ 417 | void opAssign()(auto ref CT rhs) 418 | { 419 | setState!CT(); // check state asserts before change 420 | auto s = state; 421 | static if (isRefCountedPayloadEnabled!Hook) 422 | { 423 | if (!storage) initialize(forward!rhs); 424 | else storage.payload = Payload(forward!rhs); 425 | } 426 | else storage = Payload(forward!rhs); 427 | setState!(CT)(s); // set previous state 428 | 429 | static if (hasOnValueSet!(Hook, CT)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(getValue()); } 430 | static if (hasOnErrorSet!(Hook, CT)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(getError()); } 431 | } 432 | } 433 | } 434 | } 435 | 436 | //damn these are ugly :( 437 | static if (!isChecked!Hook) { 438 | /++ Implicit conversion to bool. 439 | Returns: `true` if there is no error set, `false` otherwise. 440 | +/ 441 | bool opCast(T)() const if (is(T == bool)) { return !this.hasError; } 442 | } else { 443 | /// ditto 444 | bool opCast(T)() if (is(T == bool)) { return !this.hasError; } 445 | } 446 | 447 | static if (!is(T == void)) 448 | { 449 | static if (!isChecked!Hook) { 450 | /++ Checks whether this $(LREF Expected) object contains a specific expected value. 451 | 452 | * `opEquals` for the value is available only when `T != void`. 453 | * `opEquals` for the error isn't available, use equality test for $(LREF Expected) in that case. 454 | +/ 455 | bool opEquals()(const auto ref T rhs) const 456 | { 457 | return hasValue && value == rhs; 458 | } 459 | } else { 460 | /// ditto 461 | bool opEquals()(auto ref T rhs) { return hasValue && value == forward!rhs; } 462 | } 463 | } 464 | 465 | static if (!isChecked!Hook) { 466 | /// Checks whether this $(LREF Expected) object and `rhs` contain the same expected value or error value. 467 | bool opEquals()(const auto ref Expected!(T, E, Hook) rhs) const 468 | { 469 | if (state != rhs.state) return false; 470 | static if (!is(T == void)) { if (hasValue) return value == rhs.value; } 471 | return error == rhs.error; 472 | } 473 | } else { 474 | /// ditto 475 | bool opEquals()(auto ref Expected!(T, E, Hook) rhs) 476 | { 477 | if (state != rhs.state) return false; 478 | static if (!is(T == void)) { if (hasValue) return value == forwardValue!rhs; } 479 | return error == forwardError!rhs; 480 | } 481 | } 482 | 483 | static if (!isChecked!Hook) { 484 | /++ Calculates the hash value of the $(LREF Expected) in a way that iff it has a value, 485 | it returns hash of the value. 486 | Hash is computed using internal state and storage of the $(LREF Expected) otherwise. 487 | +/ 488 | size_t toHash()() const nothrow 489 | { 490 | static if (!is(T == void)) { if (hasValue) return value.hashOf; } 491 | return storage.hashOf(state); 492 | } 493 | } else { 494 | /// ditto 495 | size_t toHash()() nothrow 496 | { 497 | static if (!is(T == void)) { if (hasValue) return value.hashOf; } 498 | return storage.hashOf(state); 499 | } 500 | } 501 | 502 | static if (!is(T == void)) 503 | { 504 | static if (!isChecked!Hook) { 505 | /// Checks if $(LREF Expected) has value 506 | @property bool hasValue()() const { return state == State.value; } 507 | } 508 | else { 509 | /// ditto 510 | @property bool hasValue()() 511 | { 512 | checked = true; 513 | return state == State.value; 514 | } 515 | } 516 | 517 | static if (!isChecked!Hook) { 518 | /++ 519 | Returns the expected value if there is one. 520 | 521 | With default `Abort` hook, it asserts when there is no value. 522 | It calls hook's `onAccessEmptyValue` otherwise. 523 | 524 | It returns `T.init` when hook doesn't provide `onAccessEmptyValue`. 525 | +/ 526 | @property auto ref inout(T) value()() inout 527 | { 528 | if (state != State.value) 529 | { 530 | static if (hasOnAccessEmptyValue!(Hook, E)) 531 | __traits(getMember, Hook, "onAccessEmptyValue")(state == State.error ? getError() : E.init); 532 | else return T.init; 533 | } 534 | return getValue(); 535 | } 536 | } else { 537 | @property auto ref T value()() 538 | { 539 | checked = true; 540 | 541 | if (state != State.value) 542 | { 543 | static if (hasOnAccessEmptyValue!(Hook, E)) 544 | __traits(getMember, Hook, "onAccessEmptyValue")(state == State.error ? getError() : E.init); 545 | else return T.init; 546 | } 547 | return getValue(); 548 | } 549 | } 550 | } 551 | 552 | static if (!isChecked!Hook) { 553 | /// Checks if $(LREF Expected) has error 554 | @property bool hasError()() const { return state == State.error; } 555 | } else { 556 | /// ditto 557 | @property bool hasError()() 558 | { 559 | checked = true; 560 | return state == State.error; 561 | } 562 | } 563 | 564 | static if (!isChecked!Hook) { 565 | /++ 566 | Returns the error value. May only be called when `hasValue` returns `false`. 567 | 568 | If there is no error value, it calls hook's `onAccessEmptyError`. 569 | 570 | It returns `E.init` when hook doesn't provide `onAccessEmptyError`. 571 | +/ 572 | @property auto ref inout(E) error() inout 573 | { 574 | if (state != State.error) 575 | { 576 | static if (hasOnAccessEmptyError!Hook) __traits(getMember, Hook, "onAccessEmptyError")(); 577 | else return E.init; 578 | } 579 | return getError; 580 | } 581 | } else { 582 | @property auto ref E error() 583 | { 584 | checked = true; 585 | 586 | if (state != State.error) 587 | { 588 | static if (hasOnAccessEmptyError!Hook) __traits(getMember, Hook, "onAccessEmptyError")(); 589 | else return E.init; 590 | } 591 | return getError; 592 | } 593 | } 594 | 595 | // range interface 596 | static if (!is(T == void)) 597 | { 598 | static if (!isChecked!Hook) { 599 | /++ Range interface defined by `empty`, `front`, `popFront`. 600 | Yields one value if $(LREF Expected) has value. 601 | 602 | If `T == void`, range interface isn't defined. 603 | +/ 604 | @property bool empty() const { return state != State.value; } 605 | 606 | /// ditto 607 | @property auto ref inout(T) front() inout { return value; } 608 | } else { 609 | @property bool empty() { checked = true; return state != State.value; } 610 | 611 | /// ditto 612 | @property auto ref T front() { return value; } 613 | } 614 | 615 | /// ditto 616 | void popFront() { state = State.empty; } 617 | } 618 | 619 | private: 620 | 621 | //FIXME: can probably be union instead, but that doesn't work well with destructors and copy constructors/postblits 622 | //and need to be handled manually - so for now we use a safer variant 623 | struct Payload 624 | { 625 | Types values; 626 | 627 | // generate payload constructors 628 | static foreach (i, CT; Types) 629 | { 630 | this()(auto ref CT val) 631 | { 632 | __traits(getMember, Payload, "values")[i] = forward!val; 633 | } 634 | 635 | // static if (isCopyable!CT) 636 | // { 637 | // this()(auto ref const(CT) val) const { __traits(getMember, Payload, "values")[i] = val; } 638 | // this()(auto ref immutable(CT) val) immutable { __traits(getMember, Payload, "values")[i] = val; } 639 | // } 640 | // else 641 | // { 642 | // @disable this(const(CT) val) const; 643 | // @disable this(immutable(CT) val) immutable; 644 | // } 645 | } 646 | } 647 | 648 | static if (isRefCountedPayloadEnabled!Hook) 649 | { 650 | version (D_BetterC) enum enableGCScan = false; 651 | else enum enableGCScan = hasIndirections!Payload; 652 | 653 | // purify memory management functions - see https://github.com/dlang/phobos/pull/4832 654 | extern(C) pure nothrow @nogc static 655 | { 656 | pragma(mangle, "malloc") void* pureMalloc(size_t); 657 | pragma(mangle, "free") void pureFree( void *ptr ); 658 | static if (enableGCScan) 659 | { 660 | pragma(mangle, "calloc") void* pureCalloc(size_t nmemb, size_t size); 661 | pragma(mangle, "gc_addRange") void pureGcAddRange( in void* p, size_t sz, const TypeInfo ti = null ); 662 | pragma(mangle, "gc_removeRange") void pureGcRemoveRange( in void* p ); 663 | } 664 | } 665 | 666 | struct Impl 667 | { 668 | Payload payload; 669 | State state; 670 | size_t count; 671 | static if (isChecked!Hook) bool checked; 672 | } 673 | 674 | void initialize(A...)(auto ref A args) 675 | { 676 | import std.conv : emplace; 677 | 678 | allocateStore(); 679 | emplace(&storage.payload, forward!args); 680 | storage.count = 1; 681 | storage.state = State.empty; 682 | static if (isChecked!Hook) storage.checked = false; 683 | } 684 | 685 | void allocateStore() nothrow pure @trusted 686 | { 687 | assert(!storage); 688 | static if (enableGCScan) 689 | { 690 | storage = cast(Impl*) pureCalloc(1, Impl.sizeof); 691 | if (!storage) assert(0, "Memory allocation failed"); 692 | pureGcAddRange(&storage.payload, Payload.sizeof); 693 | } 694 | else 695 | { 696 | storage = cast(Impl*) pureMalloc(Impl.sizeof); 697 | if (!storage) assert(0, "Memory allocation failed"); 698 | } 699 | } 700 | 701 | Impl* storage; 702 | 703 | @property nothrow @safe pure @nogc 704 | size_t refCount() const { return storage !is null ? storage.count : 0; } 705 | 706 | @property nothrow @safe pure @nogc 707 | State state() const { return storage !is null ? storage.state : State.empty; } 708 | 709 | @property nothrow @safe pure @nogc 710 | void state(State state) { assert(storage); storage.state = state; } 711 | 712 | static if (isChecked!Hook) 713 | { 714 | @property nothrow @safe pure @nogc 715 | bool checked() const { assert(storage); return storage.checked; } 716 | 717 | @property nothrow @safe pure @nogc 718 | void checked(bool ch) { assert(storage); storage.checked = ch; } 719 | } 720 | 721 | auto ref inout(E) getError()() inout 722 | { 723 | assert(storage); 724 | static if (__VERSION__ < 2078) // workaround - see: https://issues.dlang.org/show_bug.cgi?id=15094 725 | { 726 | auto p = &storage.payload; 727 | static if (Types.length == 1) return __traits(getMember, p, "values")[0]; 728 | else return __traits(getMember, p, "values")[1]; 729 | } 730 | else 731 | { 732 | static if (Types.length == 1) return __traits(getMember, storage.payload, "values")[0]; 733 | else return __traits(getMember, storage.payload, "values")[1]; 734 | } 735 | } 736 | 737 | static if (!is(T == void)) 738 | { 739 | auto ref inout(T) getValue()() inout 740 | { 741 | assert(storage); 742 | static if (__VERSION__ < 2078) // workaround - see: https://issues.dlang.org/show_bug.cgi?id=15094 743 | { 744 | auto p = &storage.payload; 745 | return __traits(getMember, p, "values")[0]; 746 | } 747 | else return __traits(getMember, storage.payload, "values")[0]; 748 | } 749 | } 750 | 751 | this(this) @safe pure nothrow @nogc 752 | { 753 | if (!storage) return; 754 | ++storage.count; 755 | } 756 | } 757 | else 758 | { 759 | Payload storage; 760 | State state = State.empty; 761 | static if (isChecked!Hook) bool checked = false; 762 | 763 | auto ref inout(E) getError()() inout 764 | { 765 | static if (Types.length == 1) return __traits(getMember, storage, "values")[0]; 766 | else return __traits(getMember, storage, "values")[1]; 767 | } 768 | 769 | static if (!is(T == void)) 770 | { 771 | auto ref inout(T) getValue()() inout 772 | { 773 | return __traits(getMember, storage, "values")[0]; 774 | } 775 | } 776 | } 777 | 778 | enum State : ubyte { empty, value, error } 779 | 780 | void setState(MT)(State known = State.empty) 781 | { 782 | State s; 783 | if (known != State.empty) s = known; 784 | else 785 | { 786 | static if (Types.length == 1 && is(T == void)) s = State.error; 787 | else static if (Types.length == 1 || is(MT == T)) s = State.value; 788 | else s = State.error; 789 | } 790 | 791 | //TODO: change with Hook? 792 | assert(state == State.empty || state == s, "Can't change meaning of already set Expected type"); 793 | state = s; 794 | } 795 | } 796 | 797 | /++ Template to determine if hook enables or disables copy constructor. 798 | 799 | It is enabled by default. 800 | 801 | See $(LREF hasOnUnchecked) handler, which can be used in combination with disabled 802 | copy constructor to enforce that the result is checked. 803 | 804 | $(WARNING If copy constructor is disabled, it severely limits function chaining 805 | as $(LREF Expected) needs to be passed as rvalue in that case.) 806 | +/ 807 | template isCopyConstructorEnabled(Hook) 808 | { 809 | static if (__traits(hasMember, Hook, "enableCopyConstructor")) 810 | { 811 | static assert( 812 | is(typeof(__traits(getMember, Hook, "enableCopyConstructor")) : bool), 813 | "Hook's enableCopyConstructor is expected to be of type bool" 814 | ); 815 | enum isCopyConstructorEnabled = __traits(getMember, Hook, "enableCopyConstructor"); 816 | } 817 | else enum isCopyConstructorEnabled = true; 818 | } 819 | 820 | /// 821 | @("isCopyConstructorEnabled") 822 | @safe unittest 823 | { 824 | struct Foo {} 825 | struct Bar { static immutable bool enableCopyConstructor = false; } 826 | static assert(isCopyConstructorEnabled!Foo); 827 | static assert(!isCopyConstructorEnabled!Bar); 828 | } 829 | 830 | /++ Template to determine if hook defines that the $(LREF Expected) storage should 831 | use refcounted state storage. 832 | 833 | If this is enabled, payload is mallocated on the heap and dealocated with the 834 | destruction of last $(Expected) instance. 835 | 836 | See $(LREF hasOnUnchecked) handler, which can be used in combination with refcounted 837 | payload to enforce that the result is checked. 838 | +/ 839 | template isRefCountedPayloadEnabled(Hook) 840 | { 841 | static if (__traits(hasMember, Hook, "enableRefCountedPayload")) 842 | { 843 | static assert( 844 | is(typeof(__traits(getMember, Hook, "enableRefCountedPayload")) : bool), 845 | "Hook's enableCopyConstructor is expected to be of type bool" 846 | ); 847 | enum isRefCountedPayloadEnabled = __traits(getMember, Hook, "enableRefCountedPayload"); 848 | static assert ( 849 | !isRefCountedPayloadEnabled || isCopyConstructorEnabled!Hook, 850 | "Refcounted payload wouldn't work without copy constructor enabled" 851 | ); 852 | } 853 | else enum isRefCountedPayloadEnabled = false; 854 | } 855 | 856 | /// 857 | @("isRefCountedPayloadEnabled") 858 | @safe unittest 859 | { 860 | struct Foo {} 861 | struct Bar { 862 | static immutable bool enableCopyConstructor = false; 863 | static immutable bool enableRefCountedPayload = true; 864 | } 865 | struct Hook { static immutable bool enableRefCountedPayload = true; } 866 | static assert(!isRefCountedPayloadEnabled!Foo); 867 | static assert(!__traits(compiles, isRefCountedPayloadEnabled!Bar)); 868 | static assert(isRefCountedPayloadEnabled!Hook); 869 | } 870 | 871 | // just a helper to determine check behavior 872 | private template isChecked(Hook) 873 | { 874 | enum isChecked = !isCopyConstructorEnabled!Hook || isRefCountedPayloadEnabled!Hook; 875 | } 876 | 877 | /// Template to determine if provided Hook enables default constructor for $(LREF Expected) 878 | template isDefaultConstructorEnabled(Hook) 879 | { 880 | static if (__traits(hasMember, Hook, "enableDefaultConstructor")) 881 | { 882 | static assert( 883 | is(typeof(__traits(getMember, Hook, "enableDefaultConstructor")) : bool), 884 | "Hook's enableDefaultConstructor is expected to be of type bool" 885 | ); 886 | enum isDefaultConstructorEnabled = __traits(getMember, Hook, "enableDefaultConstructor"); 887 | } 888 | else enum isDefaultConstructorEnabled = false; 889 | } 890 | 891 | /// 892 | @("isDefaultConstructorEnabled") 893 | @safe unittest 894 | { 895 | struct Foo {} 896 | struct Bar { static immutable bool enableDefaultConstructor = true; } 897 | static assert(!isDefaultConstructorEnabled!Foo); 898 | static assert(isDefaultConstructorEnabled!Bar); 899 | } 900 | 901 | /// Template to determine if provided Hook enables void values for $(LREF Expected) 902 | template isVoidValueEnabled(Hook) 903 | { 904 | static if (__traits(hasMember, Hook, "enableVoidValue")) 905 | { 906 | static assert( 907 | is(typeof(__traits(getMember, Hook, "enableVoidValue")) : bool), 908 | "Hook's enableVoidValue is expected to be of type bool" 909 | ); 910 | enum isVoidValueEnabled = __traits(getMember, Hook, "isVoidValueEnabled"); 911 | } 912 | else enum isVoidValueEnabled = true; 913 | } 914 | 915 | /// 916 | @("isVoidValueEnabled") 917 | @safe unittest 918 | { 919 | struct Hook { static immutable bool enableVoidValue = false; } 920 | assert(!ok().hasError); // void values are enabled by default 921 | static assert(!__traits(compiles, ok!(string, Hook)())); // won't compile 922 | } 923 | 924 | /// Template to determine if hook provides function called on empty value. 925 | template hasOnAccessEmptyValue(Hook, E) 926 | { 927 | static if (__traits(hasMember, Hook, "onAccessEmptyValue")) 928 | { 929 | static assert( 930 | is(typeof(__traits(getMember, Hook, "onAccessEmptyValue")(E.init))), 931 | "Hook's onAccessEmptyValue is expected to be callable with error value type" 932 | ); 933 | enum hasOnAccessEmptyValue = true; 934 | } 935 | else enum hasOnAccessEmptyValue = false; 936 | } 937 | 938 | /// 939 | @("hasOnAccessEmptyValue") 940 | @safe unittest 941 | { 942 | struct Foo {} 943 | struct Bar { static void onAccessEmptyValue(E)(E err) {} } 944 | static assert(!hasOnAccessEmptyValue!(Foo, string)); 945 | static assert(hasOnAccessEmptyValue!(Bar, string)); 946 | } 947 | 948 | /++ Template to determine if hook provides function called on empty error. 949 | +/ 950 | template hasOnAccessEmptyError(Hook) 951 | { 952 | static if (__traits(hasMember, Hook, "onAccessEmptyError")) 953 | { 954 | static assert( 955 | is(typeof(__traits(getMember, Hook, "onAccessEmptyError")())), 956 | "Hook's onAccessEmptyValue is expected to be callable with no arguments" 957 | ); 958 | enum hasOnAccessEmptyError = true; 959 | } 960 | else enum hasOnAccessEmptyError = false; 961 | } 962 | 963 | /// 964 | @("hasOnAccessEmptyError") 965 | @safe unittest 966 | { 967 | struct Foo {} 968 | struct Bar { static void onAccessEmptyError() {} } 969 | static assert(!hasOnAccessEmptyError!Foo); 970 | static assert(hasOnAccessEmptyError!Bar); 971 | } 972 | 973 | /++ Template to determine if hook provides custom handler for case 974 | when the $(LREF Expected) result is not checked. 975 | 976 | For this to work it currently also has to pass $(LREF isCopyConstructorEnabled) 977 | as this is implemented by simple flag controled on $(LREF Expected) destructor. 978 | +/ 979 | template hasOnUnchecked(Hook) 980 | { 981 | static if (__traits(hasMember, Hook, "onUnchecked")) 982 | { 983 | static assert( 984 | is(typeof(__traits(getMember, Hook, "onUnchecked")())), 985 | "Hook's onUnchecked is expected to be callable with no arguments" 986 | ); 987 | static assert( 988 | !isCopyConstructorEnabled!Hook || isRefCountedPayloadEnabled!Hook, 989 | "For unchecked check to work, it is needed to also have disabled copy constructor or enabled reference counted payload" 990 | ); 991 | enum hasOnUnchecked = true; 992 | } 993 | else enum hasOnUnchecked = false; 994 | } 995 | 996 | version (D_Exceptions) 997 | { 998 | /// 999 | @("hasOnUnchecked") 1000 | @safe unittest 1001 | { 1002 | struct Foo {} 1003 | struct Bar { static void onUnchecked() { } } 1004 | struct Hook { 1005 | static immutable bool enableCopyConstructor = false; 1006 | static void onUnchecked() @safe { throw new Exception("result unchecked"); } 1007 | } 1008 | 1009 | // template checks 1010 | static assert(!hasOnUnchecked!Foo); 1011 | static assert(!__traits(compiles, hasOnUnchecked!Bar)); // missing disabled constructor 1012 | static assert(hasOnUnchecked!Hook); 1013 | 1014 | // copy constructor 1015 | auto exp = ok!(string, Hook)(42); 1016 | auto exp2 = err!(int, Hook)("foo"); 1017 | static assert(!__traits(compiles, exp.andThen(ok!(string, Hook)(42)))); // disabled cc 1018 | assert(exp.andThen(exp2).error == "foo"); // passed by ref so no this(this) called 1019 | 1020 | // check for checked result 1021 | assertThrown({ ok!(string, Hook)(42); }()); 1022 | assertThrown({ err!(void, Hook)("foo"); }()); 1023 | } 1024 | } 1025 | 1026 | /++ Template to determine if hook provides function called when value is set. 1027 | +/ 1028 | template hasOnValueSet(Hook, T) 1029 | { 1030 | static if (__traits(hasMember, Hook, "onValueSet")) 1031 | { 1032 | static assert( 1033 | is(typeof(__traits(getMember, Hook, "onValueSet")(T.init))), 1034 | "Hook's onValueSet is expected to be callable with value argument" 1035 | ); 1036 | enum hasOnValueSet = true; 1037 | } 1038 | else enum hasOnValueSet = false; 1039 | } 1040 | 1041 | /// 1042 | @("hasOnValueSet") 1043 | @safe unittest 1044 | { 1045 | struct Hook { 1046 | static int lastValue; 1047 | static void onValueSet(T)(auto ref T val) { lastValue = val; } 1048 | } 1049 | 1050 | static assert(hasOnValueSet!(Hook, int)); 1051 | auto res = ok!(string, Hook)(42); 1052 | assert(res.hasValue); 1053 | assert(Hook.lastValue == 42); 1054 | } 1055 | 1056 | /++ Template to determine if hook provides function called when error is set. 1057 | +/ 1058 | template hasOnErrorSet(Hook, T) 1059 | { 1060 | static if (__traits(hasMember, Hook, "onErrorSet")) 1061 | { 1062 | static assert( 1063 | is(typeof(__traits(getMember, Hook, "onErrorSet")(T.init))), 1064 | "Hook's onErrorSet is expected to be callable with error argument" 1065 | ); 1066 | enum hasOnErrorSet = true; 1067 | } 1068 | else enum hasOnErrorSet = false; 1069 | } 1070 | 1071 | /// 1072 | @("hasOnErrorSet") 1073 | @safe unittest 1074 | { 1075 | struct Hook { 1076 | static string lastErr; 1077 | static void onErrorSet(E)(auto ref E err) { lastErr = err; } 1078 | } 1079 | 1080 | static assert(hasOnErrorSet!(Hook, string)); 1081 | auto res = err!(int, Hook)("foo"); 1082 | assert(res.hasError); 1083 | assert(Hook.lastErr == "foo"); 1084 | } 1085 | 1086 | /++ Default hook implementation for $(LREF Expected) 1087 | +/ 1088 | struct Abort 1089 | { 1090 | static: 1091 | /++ Default constructor for $(LREF Expected) is disabled. 1092 | Same with the `opAssign`, so $(LREF Expected) can be only constructed 1093 | once and not modified afterwards. 1094 | +/ 1095 | immutable bool enableDefaultConstructor = false; 1096 | 1097 | /// Handler for case when empty value is accessed. 1098 | void onAccessEmptyValue(E)(E err) nothrow @nogc @safe 1099 | { 1100 | assert(0, "Value not set"); 1101 | } 1102 | 1103 | /// Handler for case when empty error is accessed. 1104 | void onAccessEmptyError() nothrow @nogc @safe 1105 | { 1106 | assert(0, "Error not set"); 1107 | } 1108 | } 1109 | 1110 | /// 1111 | @("Abort") 1112 | @system unittest 1113 | { 1114 | static assert(!isDefaultConstructorEnabled!Abort); 1115 | static assert(hasOnAccessEmptyValue!(Abort, string)); 1116 | static assert(hasOnAccessEmptyValue!(Abort, int)); 1117 | static assert(hasOnAccessEmptyError!Abort); 1118 | 1119 | version (D_Exceptions) 1120 | { 1121 | assertThrown!Throwable(ok(42).error); 1122 | assertThrown!Throwable(err!int("foo").value); 1123 | } 1124 | } 1125 | 1126 | version (D_Exceptions) 1127 | { 1128 | /++ Hook implementation that throws exceptions instead of default assert behavior. 1129 | +/ 1130 | struct Throw 1131 | { 1132 | static: 1133 | 1134 | /++ Default constructor for $(LREF Expected) is disabled. 1135 | Same with the `opAssign`, so $(LREF Expected) can be only constructed 1136 | once and not modified afterwards. 1137 | +/ 1138 | immutable bool enableDefaultConstructor = false; 1139 | 1140 | /++ Handler for case when empty value is accessed. 1141 | 1142 | Throws: 1143 | If `E` inherits from `Throwable`, the error value is thrown. 1144 | Otherwise, an [Unexpected] instance containing the error value is 1145 | thrown. 1146 | +/ 1147 | void onAccessEmptyValue(E)(E err) 1148 | { 1149 | import std.traits : Unqual; 1150 | static if(is(Unqual!E : Throwable)) throw err; 1151 | else throw new Unexpected!E(err); 1152 | } 1153 | 1154 | /// Handler for case when empty error is accessed. 1155 | void onAccessEmptyError() @safe 1156 | { 1157 | throw new Unexpected!string("Can't access error on expected value"); 1158 | } 1159 | } 1160 | 1161 | /// 1162 | @("Throw") 1163 | @safe unittest 1164 | { 1165 | static assert(!isDefaultConstructorEnabled!Throw); 1166 | static assert(hasOnAccessEmptyValue!(Throw, string)); 1167 | static assert(hasOnAccessEmptyValue!(Throw, int)); 1168 | static assert(hasOnAccessEmptyError!Throw); 1169 | 1170 | assertThrown!(Unexpected!string)(ok!(string, Throw)(42).error); 1171 | assertThrown!(Unexpected!string)(err!(int, Throw)("foo").value); 1172 | assertThrown!(Unexpected!int)(err!(bool, Throw)(-1).value); 1173 | } 1174 | 1175 | /++ Hook implementation that behaves like a thrown exception. 1176 | It throws $(D Exception) right when the $(LREF Expected) with error is initialized. 1177 | 1178 | With this, one can easily change the code behavior between `Expected` idiom or plain `Exception`s. 1179 | +/ 1180 | struct AsException 1181 | { 1182 | static: 1183 | 1184 | /++ Default constructor for $(LREF Expected) is disabled. 1185 | Same with the `opAssign`, so $(LREF Expected) can be only constructed 1186 | once and not modified afterwards. 1187 | +/ 1188 | immutable bool enableDefaultConstructor = false; 1189 | 1190 | /++ Handler for case when empty error is accessed. 1191 | +/ 1192 | void onErrorSet(E)(auto ref E err) 1193 | { 1194 | import core.lifetime : forward; 1195 | static if (is(E : Throwable)) throw E; 1196 | else throw new Unexpected!E(forward!err); 1197 | } 1198 | } 1199 | 1200 | /// 1201 | @("AsException") 1202 | @safe unittest 1203 | { 1204 | static assert(!isDefaultConstructorEnabled!AsException); 1205 | static assert(hasOnErrorSet!(AsException, string)); 1206 | 1207 | auto div(int a, int b) { 1208 | if (b != 0) return ok!(string, AsException)(a / b); 1209 | return err!(int, AsException)("oops"); 1210 | } 1211 | 1212 | assert(div(10, 2) == 5); 1213 | assert(collectExceptionMsg!(Unexpected!string)(div(1, 0)) == "oops"); 1214 | } 1215 | } 1216 | 1217 | /++ Hook implementation that behaves same as $(LREF Abort) hook, but uses refcounted payload 1218 | instead, which also enables us to check, if the result was properly checked before it is 1219 | discarded. 1220 | +/ 1221 | struct RCAbort 1222 | { 1223 | static: 1224 | /++ Default constructor for $(LREF Expected) is disabled. 1225 | Same with the `opAssign`, so $(LREF Expected) can be only constructed 1226 | once and not modified afterwards. 1227 | +/ 1228 | immutable bool enableDefaultConstructor = false; 1229 | 1230 | /// Copy constructor is enabled so the reference counting makes sense 1231 | immutable bool enableCopyConstructor = true; 1232 | 1233 | /// Enabled reference counted payload 1234 | immutable bool enableRefCountedPayload = true; 1235 | 1236 | void onUnchecked() pure nothrow @nogc @safe { assert(0, "result unchecked"); } 1237 | } 1238 | 1239 | /// 1240 | @("RCAbort") 1241 | @safe unittest 1242 | { 1243 | // behavior checks 1244 | static assert(!isDefaultConstructorEnabled!RCAbort); 1245 | static assert(isCopyConstructorEnabled!RCAbort); 1246 | static assert(isRefCountedPayloadEnabled!RCAbort); 1247 | 1248 | // basics 1249 | assert(ok!(string, RCAbort)(42) == 42); 1250 | assert(err!(int, RCAbort)("foo").error == "foo"); 1251 | 1252 | // checked 1253 | { 1254 | auto res = ok!(string, RCAbort)(42); 1255 | assert(!res.checked); 1256 | assert(res); 1257 | assert(res.checked); 1258 | } 1259 | 1260 | // unchecked - throws assert 1261 | version (D_Exceptions) () @trusted { assertThrown!Throwable({ ok!(string, RCAbort)(42); }()); }(); 1262 | 1263 | { 1264 | auto res = ok!(string, RCAbort)(42); 1265 | { 1266 | auto res2 = res; 1267 | assert(!res.checked); 1268 | assert(res.refCount == 2); 1269 | assert(res2.refCount == 2); 1270 | } 1271 | assert(res.refCount == 1); 1272 | assert(res.hasValue); 1273 | } 1274 | 1275 | // chaining 1276 | assert(err!(int, RCAbort)("foo").orElse!(() => ok!(string, RCAbort)(42)) == 42); 1277 | assert(ok!(string, RCAbort)(42).andThen!(() => err!(int, RCAbort)("foo")).error == "foo"); 1278 | version (D_Exceptions) 1279 | { 1280 | () @trusted 1281 | { 1282 | assertThrown!Throwable(err!(int, RCAbort)("foo").orElse!(() => ok!(string, RCAbort)(42))); 1283 | assertThrown!Throwable(ok!(string, RCAbort)(42).andThen!(() => err!(int, RCAbort)("foo"))); 1284 | }(); 1285 | } 1286 | } 1287 | 1288 | version (D_Exceptions) 1289 | { 1290 | /++ An exception that represents an error value. 1291 | 1292 | This is used by $(LREF Throw) hook when undefined value or error is 1293 | accessed on $(LREF Expected) 1294 | +/ 1295 | class Unexpected(T) : Exception 1296 | { 1297 | // remove possible inout qualifier 1298 | static if (is(T U == inout U)) alias ET = U; 1299 | else alias ET = T; 1300 | 1301 | ET error; /// error value 1302 | 1303 | /// Constructs an `Unexpected` exception from an error value. 1304 | pure @safe @nogc nothrow 1305 | this()(auto ref T value, string file = __FILE__, size_t line = __LINE__) 1306 | { 1307 | import core.lifetime : forward; 1308 | import std.traits : isAssignable; 1309 | static if (isAssignable!(string, T)) super(forward!value, file, line); 1310 | else super("Unexpected error", file, line); 1311 | 1312 | this.error = error; 1313 | } 1314 | } 1315 | } 1316 | 1317 | /++ 1318 | Creates an $(LREF Expected) object from an expected value, with type inference. 1319 | +/ 1320 | Expected!(T, E, Hook) ok(E = string, Hook = Abort, T)(auto ref T value) 1321 | { 1322 | import core.lifetime : forward, move; 1323 | static if (__traits(compiles, Expected!(T, E, Hook)(forward!value))) 1324 | return Expected!(T, E, Hook)(forward!value); 1325 | else 1326 | return Expected!(T, E, Hook)(value.move()); 1327 | } 1328 | 1329 | /// ditto 1330 | Expected!(void, E, Hook) ok(E = string, Hook = Abort)() 1331 | { 1332 | return Expected!(void, E, Hook)(); 1333 | } 1334 | 1335 | /// 1336 | @("Expected from value") 1337 | @safe unittest 1338 | { 1339 | // void 1340 | { 1341 | auto res = ok(); 1342 | static assert(is(typeof(res) == Expected!(void, string))); 1343 | assert(res); 1344 | } 1345 | 1346 | // int 1347 | { 1348 | auto res = ok(42); 1349 | static assert(is(typeof(res) == Expected!(int, string))); 1350 | assert(res); 1351 | assert(res.value == 42); 1352 | } 1353 | 1354 | // string 1355 | { 1356 | auto res = ok("42"); 1357 | static assert(is(typeof(res) == Expected!(string, string))); 1358 | assert(res); 1359 | assert(res.value == "42"); 1360 | } 1361 | 1362 | // other error type 1363 | { 1364 | auto res = ok!bool(42); 1365 | static assert(is(typeof(res) == Expected!(int, bool))); 1366 | assert(res); 1367 | assert(res.value == 42); 1368 | } 1369 | } 1370 | 1371 | /++ Constructs $(LREF Expected) from the result of the provided function. 1372 | 1373 | If the function is `nothrow`, it just returns it's result using $(LREF Expected). 1374 | 1375 | If not, then it consumes it's possible $(D Exception) using `try catch` block and 1376 | constructs $(LREF Expected) in regards of the result. 1377 | +/ 1378 | template consume(alias fun, Hook = Abort) 1379 | { 1380 | import core.lifetime : forward; 1381 | auto consume(Args...)(auto ref Args args) if (is(typeof(fun(forward!args)))) 1382 | { 1383 | import std.traits : hasFunctionAttributes; 1384 | 1385 | alias T = typeof(fun(forward!args)); 1386 | static if (is(hasFunctionAttributes!(fun, "nothrow"))) return ok!Exception(fun(forward!args)); 1387 | else 1388 | { 1389 | try return Expected!(T, Exception)(fun(forward!args)); 1390 | catch (Exception ex) return err!T(ex); 1391 | } 1392 | } 1393 | } 1394 | 1395 | version (D_Exceptions) 1396 | { 1397 | /// 1398 | @("consume from function call") 1399 | @safe unittest 1400 | { 1401 | auto fn(int v) { if (v == 42) throw new Exception("don't panic"); return v; } 1402 | 1403 | assert(consume!fn(1) == 1); 1404 | assert(consume!fn(42).error.msg == "don't panic"); 1405 | } 1406 | } 1407 | 1408 | /++ 1409 | Creates an $(LREF Expected) object from an error value, with type inference. 1410 | +/ 1411 | Expected!(T, E, Hook) err(T = void, Hook = Abort, E)(auto ref E err) 1412 | { 1413 | import core.lifetime : forward; 1414 | static if (Expected!(T, E, Hook).Types.length == 1 && !is(T == void)) 1415 | return Expected!(T, E, Hook)(forward!err, false); 1416 | else return Expected!(T, E, Hook)(forward!err); 1417 | } 1418 | 1419 | /// 1420 | @("Expected from error value") 1421 | @safe unittest 1422 | { 1423 | // implicit void value type 1424 | { 1425 | auto res = err("foo"); 1426 | static assert(is(typeof(res) == Expected!(void, string))); 1427 | assert(!res); 1428 | assert(res.error == "foo"); 1429 | } 1430 | 1431 | // bool 1432 | { 1433 | auto res = err!int("42"); 1434 | static assert(is(typeof(res) == Expected!(int, string))); 1435 | assert(!res); 1436 | assert(res.error == "42"); 1437 | } 1438 | 1439 | // other error type 1440 | { 1441 | auto res = err!bool(42); 1442 | static assert(is(typeof(res) == Expected!(bool, int))); 1443 | assert(!res); 1444 | assert(res.error == 42); 1445 | } 1446 | } 1447 | 1448 | /++ Unwraps a result, yielding the content of expected value. 1449 | If there is none, or error value, it throws $(D assert(0)) with the provided message. 1450 | 1451 | Params: 1452 | res = $(LREF Expected) to check the result of 1453 | msg = message to use with assert 1454 | handler = custom handler to be called on error 1455 | +/ 1456 | auto ref T expect(EX : Expected!(T, E, H), T, E, H)(auto ref EX res, lazy string msg) 1457 | { 1458 | //TODO: hook for customization 1459 | 1460 | static if (!is(T == void)) { 1461 | if (res.hasValue) return forwardValue!res; 1462 | } 1463 | else { if (!res.hasError) return; } 1464 | 1465 | version (D_BetterC) assert(0, msg); 1466 | else 1467 | { 1468 | import std.format : format; 1469 | if (res.hasError) assert(0, format!"%s: %s"(msg, res.error)); 1470 | else assert(0, format!"%s: empty"(msg)); 1471 | } 1472 | } 1473 | 1474 | /// ditto 1475 | auto ref T expect(alias handler, EX : Expected!(T, E, H), T, E, H)(auto ref EX res) 1476 | { 1477 | static if (!is(T == void)) { if (res.hasValue) return forwardValue!res; } 1478 | else { if (!res.hasError) return; } 1479 | 1480 | static if (!is(typeof(handler(forwardError!res)) == noreturn) && !is(typeof(handler(forwardError!res)) == void)) 1481 | return handler(forwardError!res); 1482 | else 1483 | { 1484 | handler(forwardError!res); 1485 | static if (__VERSION__ < 2096) { 1486 | static if (!is(T == void)) 1487 | return T.init; 1488 | } else { 1489 | static if (!is(T == void) && !is(typeof(handler(forwardError!res)) == noreturn)) // avoid 'statement is not reachable' 1490 | return T.init; 1491 | } 1492 | } 1493 | } 1494 | 1495 | /// 1496 | @("expect") 1497 | @safe unittest 1498 | { 1499 | assert(ok(42).expect("oops") == 42); 1500 | ok().expect("oops"); // void value 1501 | 1502 | version (D_Exceptions) 1503 | { 1504 | () @trusted 1505 | { 1506 | assert(collectExceptionMsg!Throwable(Expected!int.init.expect("oops")) == "oops: empty"); 1507 | assert(collectExceptionMsg!Throwable(err!int("foo").expect("oops")) == "oops: foo"); 1508 | }(); 1509 | } 1510 | 1511 | assert(ok("foo").expect!(a => "bar") == "foo"); 1512 | assert(err!string("foo").expect!(a => "bar") == "bar"); 1513 | assert(err!string("foo").expect!((a) {}) is null); 1514 | 1515 | static struct NonCopyable { @disable this(this); int foo; } 1516 | assert(err!NonCopyable(42).expect!((e) {}) == NonCopyable.init); 1517 | err!void(42).expect!((e) {}); 1518 | ok!int().expect!(e => assert(0)); 1519 | ok!int(42).expect!(e => assert(0)); 1520 | } 1521 | 1522 | /++ Unwraps a result, yielding the content of an error value. 1523 | If there is none, or success value, it throws $(D assert(0)) with the provided message. 1524 | 1525 | Params: 1526 | res = $(LREF Expected) to check the result of 1527 | msg = message to use with assert 1528 | handler = custom handler to be called on value 1529 | +/ 1530 | auto ref E expectErr(EX : Expected!(T, E, H), T, E, H)(auto ref EX res, lazy string msg) 1531 | { 1532 | //TODO: hook for customization 1533 | 1534 | if (res.hasError) return forwardError!res; 1535 | 1536 | version (D_BetterC) assert(0, msg); 1537 | else 1538 | { 1539 | import std.format : format; 1540 | static if (!is(T == void)) 1541 | { 1542 | if (res.hasValue) assert(0, format!"%s: %s"(msg, forwardValue!res)); 1543 | } 1544 | assert(0, format!"%s: empty"(msg)); 1545 | } 1546 | } 1547 | 1548 | /// ditto 1549 | auto ref E expectErr(alias handler, EX : Expected!(T, E, H), T, E, H)(auto ref EX res) 1550 | { 1551 | if (res.hasError) return forwardError!res; 1552 | 1553 | static if (!is(typeof(handler(forwardError!res)) == noreturn) && !is(typeof(handler(T.init)) == void)) 1554 | { 1555 | static if (!is(T == void)) return handler(res.hasValue ? forwardValue!res : T.init); 1556 | else return handler(); 1557 | } 1558 | else 1559 | { 1560 | static if (!is(T == void)) handler(res.hasValue ? forwardValue!res : T.init); 1561 | else handler(); 1562 | static if (__VERSION__ < 2096) 1563 | return E.init; 1564 | else static if (!is(typeof(handler(forwardError!res)) == noreturn)) // avoid 'statement is not reachable' 1565 | return E.init; 1566 | } 1567 | } 1568 | 1569 | /// 1570 | @("expectErr") 1571 | @safe unittest 1572 | { 1573 | assert(err("foo").expectErr("oops") == "foo"); 1574 | version (D_Exceptions) 1575 | { 1576 | () @trusted 1577 | { 1578 | assert(collectExceptionMsg!Throwable(Expected!int.init.expectErr("oops")) == "oops: empty"); 1579 | assert(collectExceptionMsg!Throwable(ok(42).expectErr("oops")) == "oops: 42"); 1580 | assert(collectExceptionMsg!Throwable(ok().expectErr("oops")) == "oops: empty"); // void value 1581 | }(); 1582 | } 1583 | 1584 | assert(ok("foo").expectErr!(a => "bar") == "bar"); 1585 | assert(err!string("foo").expectErr!(a => "bar") == "foo"); 1586 | assert(ok!string("foo").expectErr!((a) {}) is null); 1587 | assert(ok!string(42).expectErr!((a) {}) is null); 1588 | err!int(42).expectErr!(a => assert(0)); 1589 | } 1590 | 1591 | /++ 1592 | Returns the error contained within the $(LREF Expected) _and then_ another value if there's no error. 1593 | This function can be used for control flow based on $(LREF Expected) values. 1594 | 1595 | Predicate can accept no arguments, variable arguments, or previous result value with additional variable arguments. 1596 | It must return $(LREF Expected) wth the same error type. But can provide different value type. 1597 | 1598 | Params: 1599 | exp = The $(LREF Expected) to call andThen on 1600 | value = The value to return if there isn't an error 1601 | pred = The predicate to call if the there isn't an error 1602 | +/ 1603 | auto ref andThen(EX : Expected!(T, E, H), VEX : Expected!(VT, E, H), T, VT, E, H)( 1604 | auto ref EX exp, auto ref VEX value) 1605 | { 1606 | import core.lifetime : forward; 1607 | static if (is(T == VT)) return exp.hasError ? exp : value; 1608 | else return exp.hasError ? err!(VT, H)(forwardError!exp) : value; 1609 | } 1610 | 1611 | /// ditto 1612 | auto ref andThen(alias pred, EX : Expected!(T, E, H), T, E, H, Args...)(auto ref EX exp, auto ref Args args) 1613 | { 1614 | import core.lifetime : forward; 1615 | static if (!is(T == void) && is(typeof(pred(forwardValue!exp, forward!args)) : Expected!(VT, E, H), VT)) 1616 | { 1617 | static if (is(T == VT)) return exp.hasError ? exp : pred(forwardValue!exp, forward!args); 1618 | else return exp.hasError ? err!(VT, H)(forwardError!exp) : pred(forwardValue!exp, forward!args); 1619 | } 1620 | else static if (is(typeof(pred(forward!args)) : Expected!(VT, E, H), VT)) 1621 | { 1622 | static if (is(T == VT)) return exp.hasError ? exp : pred(forward!args); 1623 | else return exp.hasError ? err!(VT, H)(forwardError!exp) : pred(forward!args); 1624 | } 1625 | else 1626 | { 1627 | static assert(0, "Expected predicate of Expected type with optional args receiving previous value"); 1628 | } 1629 | } 1630 | 1631 | /// 1632 | @("andThen") 1633 | @safe unittest 1634 | { 1635 | import std.format : format; 1636 | 1637 | assert(ok(42).andThen(ok(1)) == 1); 1638 | assert(ok(42).andThen!(() => ok(0)) == 0); 1639 | assert(ok(42).andThen(err!int("foo")).error == "foo"); 1640 | assert(ok(42).andThen!(() => err!int("foo")).error == "foo"); 1641 | assert(err!int("foo").andThen(ok(42)).error == "foo"); 1642 | assert(err!int("foo").andThen!(() => ok(42)).error == "foo"); 1643 | assert(err!int("foo").andThen(err!int("bar")).error == "foo"); 1644 | assert(err!int("foo").andThen!(() => err!int("bar")).error == "foo"); 1645 | 1646 | // with void value 1647 | assert(ok().andThen!(() => ok())); 1648 | assert(ok().andThen!(() => err("foo")).error == "foo"); 1649 | assert(err("foo").andThen!(() => ok()).error == "foo"); 1650 | 1651 | // with different value type 1652 | assert(ok(42).andThen(ok("foo")) == "foo"); 1653 | assert(err!int("bug").andThen(ok("foo")).error == "bug"); 1654 | assert(ok(42).andThen!(() => err!bool("foo")).error == "foo"); 1655 | assert(err!int("bug").andThen!(() => err!bool("foo")).error == "bug"); 1656 | 1657 | // with args 1658 | assert(ok(42).andThen!((v) => err!bool(v))("foo").error == "foo"); // doesn't use previous value 1659 | version (D_BetterC) 1660 | assert(ok(42).andThen!((i, v) 1661 | { 1662 | assert(i == 42); 1663 | assert(v == "foo"); 1664 | return err!bool("betterc"); 1665 | })("foo").error == "betterc"); // pass previous value to predicate 1666 | else 1667 | assert(ok(42).andThen!((i, v) => err!bool(format!"%s: %s"(v, i)))("foo").error == "foo: 42"); // pass previous value to predicate 1668 | assert(ok().andThen!((v) => ok(v))(42) == 42); // void type on first ok 1669 | } 1670 | 1671 | /++ 1672 | Returns the value contained within the $(LREF Expected) _or else_ another value if there's an error. 1673 | This function can be used for control flow based on $(LREF Expected) values. 1674 | 1675 | Predicate can accept no arguments, variable arguments, or previous result error value with additional variable arguments. 1676 | It must return $(LREF Expected) wth the same value type. But can provide different error value type. 1677 | 1678 | Params: 1679 | exp = The $(LREF Expected) to call orElse on 1680 | value = The value to return if there is an error 1681 | pred = The predicate to call if the there is an error 1682 | +/ 1683 | auto ref U orElse(EX, U)(auto ref EX exp, lazy U value) 1684 | if (is(EX : Expected!(T, E, H), T, E, H) && is(U : T)) 1685 | { 1686 | import core.lifetime : forward; 1687 | return forward!exp.orElse!value; 1688 | } 1689 | 1690 | /// ditto 1691 | auto ref orElse(alias pred, EX : Expected!(T, E, H), T, E, H, Args...)( 1692 | auto ref EX exp, auto ref Args args) 1693 | { 1694 | import core.lifetime : forward; 1695 | static if (is(typeof(pred(forward!args)) : T)) 1696 | return exp.hasError ? pred(forward!args) : exp.value; 1697 | else static if (is(typeof(pred(forwardError!exp, forward!args)) : T)) 1698 | return exp.hasError ? pred(forwardError!exp, forward!args) : exp.value; 1699 | else static if (is(typeof(pred(forward!args)) : Expected!(T, VE, H), VE)) 1700 | { 1701 | static if (is(E == VE)) return exp.hasError ? pred(forward!args) : exp; 1702 | else return exp.hasError ? pred(forward!args) : ok!VE(forwardValue!exp); 1703 | } 1704 | else static if (is(typeof(pred(forwardError!exp, forward!args)) : Expected!(T, VE, H), VE)) 1705 | { 1706 | static if (is(E == VE)) return exp.hasError ? pred(forwardError!exp, forward!args) : exp; 1707 | else return exp.hasError ? pred(forwardError!exp, forward!args) : ok!VE(forwardValue!exp); 1708 | } 1709 | else static assert(0, "Expecting predicate of same value type"); 1710 | } 1711 | 1712 | /// 1713 | @("orElse") 1714 | @safe unittest 1715 | { 1716 | assert(ok(42).orElse(0) == 42); 1717 | assert(ok(42).orElse!(() => 0) == 42); 1718 | assert(err!int("foo").orElse(0) == 0); 1719 | assert(err!int("foo").orElse!(() => 0) == 0); 1720 | assert(ok(42).orElse!(() => ok(0)) == 42); 1721 | assert(err!int("foo").orElse!(() => ok(42)) == 42); 1722 | assert(err!int("foo").orElse!(() => err!int("bar")).error == "bar"); 1723 | 1724 | // with void value 1725 | assert(ok().orElse!(() => err("foo"))); 1726 | assert(err("foo").orElse!(() => ok())); 1727 | assert(err("foo").orElse!(() => err("bar")).error == "bar"); 1728 | 1729 | // with args 1730 | assert(err!int("foo").orElse!((v) => v)(42) == 42); 1731 | 1732 | // with different error type 1733 | assert(err!int("foo").orElse!((v) => ok!int(v))(42).value == 42); // string -> int 1734 | assert(err!int("foo").orElse!((v) => err!int(v))(42).error == 42); 1735 | assert(err!int("foo").orElse!((e, v) => err!int(e.length + v))(42).error == 45); // with previous error 1736 | } 1737 | 1738 | /++ 1739 | Applies a function to the expected value in an $(LREF Expected) object. 1740 | 1741 | If no expected value is present, the original error value is passed through 1742 | unchanged, and the function is not called. 1743 | 1744 | Params: 1745 | op = function called to map $(LREF Expected) value 1746 | hook = use another hook for mapped $(LREF Expected) 1747 | 1748 | Returns: 1749 | A new $(LREF Expected) object containing the result. 1750 | +/ 1751 | template map(alias op, Hook = Abort) 1752 | { 1753 | /++ 1754 | The actual `map` function. 1755 | 1756 | Params: 1757 | self = an [Expected] object 1758 | +/ 1759 | auto ref map(T, E, H)(auto ref Expected!(T, E, H) self) 1760 | if ((is(T == void) && is(typeof(op()))) || (!is(T == void) && is(typeof(op(forwardValue!self))))) 1761 | { 1762 | static if (is(T == void)) alias U = typeof(op()); 1763 | else alias U = typeof(op(forwardValue!self)); 1764 | 1765 | if (self.hasError) return err!(U, Hook)(forwardError!self); 1766 | else 1767 | { 1768 | static if (is(T == void)) return ok!(E, Hook)(op()); 1769 | else return ok!(E, Hook)(op(forwardValue!self)); 1770 | } 1771 | } 1772 | } 1773 | 1774 | /// 1775 | @("map") 1776 | @safe unittest 1777 | { 1778 | { 1779 | assert(ok(42).map!(a => a/2).value == 21); 1780 | assert(ok().map!(() => 42).value == 42); 1781 | assert(err!int("foo").map!(a => 42).error == "foo"); 1782 | assert(err("foo").map!(() => 42).error == "foo"); 1783 | } 1784 | 1785 | // remap hook 1786 | { 1787 | static struct Hook {} 1788 | auto res = ok(42).map!(a => a/2, Hook); 1789 | assert(res == 21); 1790 | static assert(is(typeof(res) == Expected!(int, string, Hook))); 1791 | } 1792 | } 1793 | 1794 | /++ 1795 | Applies a function to the expected error in an $(LREF Expected) object. 1796 | 1797 | If no error is present, the original value is passed through 1798 | unchanged, and the function is not called. 1799 | 1800 | Params: 1801 | op = function called to map $(LREF Expected) error 1802 | hook = use another hook for mapped $(LREF Expected) 1803 | 1804 | Returns: 1805 | A new $(LREF Expected) object containing the result. 1806 | +/ 1807 | template mapError(alias op, Hook = Abort) 1808 | { 1809 | /++ 1810 | The actual `mapError` function. 1811 | 1812 | Params: 1813 | self = an [Expected] object 1814 | +/ 1815 | auto ref mapError(T, E, H)(auto ref Expected!(T, E, H) self) 1816 | if (is(typeof(op(forwardError!self)))) 1817 | { 1818 | alias U = typeof(op(forwardError!self)); 1819 | 1820 | static if (!is(T == void)) 1821 | { 1822 | if (self.hasValue) return ok!(U, Hook)(forwardValue!self); 1823 | } 1824 | return err!(T, Hook)(op(forwardError!self)); 1825 | } 1826 | } 1827 | 1828 | /// 1829 | @("mapError") 1830 | @safe unittest 1831 | { 1832 | { 1833 | assert(ok(42).mapError!(e => e).value == 42); 1834 | assert(err("foo").mapError!(e => 42).error == 42); 1835 | version (D_Exceptions) assert(err("foo").mapError!(e => new Exception(e)).error.msg == "foo"); 1836 | } 1837 | 1838 | // remap hook 1839 | { 1840 | static struct Hook {} 1841 | auto res = ok(42).mapError!(e => e, Hook); 1842 | assert(res == 42); 1843 | static assert(is(typeof(res) == Expected!(int, string, Hook))); 1844 | 1845 | auto res2 = err!int("foo").mapError!(e => "bar", Hook); 1846 | assert(res2.error == "bar"); 1847 | static assert(is(typeof(res2) == Expected!(int, string, Hook))); 1848 | } 1849 | } 1850 | 1851 | /++ 1852 | Maps a `Expected` to `U` by applying a function to a contained value, or a fallback function to a contained error value. 1853 | 1854 | Both functions has to be of the same return type. 1855 | 1856 | This function can be used to unpack a successful result while handling an error. 1857 | 1858 | Params: 1859 | valueOp = function called to map $(LREF Expected) value 1860 | errorOp = function called to map $(LREF Expected) error 1861 | hook = use another hook for mapped $(LREF Expected) 1862 | 1863 | Returns: 1864 | A new $(LREF Expected) object containing the result. 1865 | +/ 1866 | template mapOrElse(alias valueOp, alias errorOp) 1867 | { 1868 | /++ 1869 | The actual `mapOrElse` function. 1870 | 1871 | Params: 1872 | self = an [Expected] object 1873 | +/ 1874 | auto ref mapOrElse(T, E, H)(auto ref Expected!(T, E, H) self) 1875 | if ( 1876 | is(typeof(errorOp(forwardError!self))) && 1877 | ( 1878 | (is(T == void) && is(typeof(valueOp()) == typeof(errorOp(forwardError!self)))) || 1879 | (!is(T == void) && is(typeof(valueOp(self.value)) == typeof(errorOp(forwardError!self)))) 1880 | ) 1881 | ) 1882 | { 1883 | alias U = typeof(errorOp(forwardError!self)); 1884 | 1885 | if (self.hasError) return errorOp(forwardError!self); 1886 | else 1887 | { 1888 | static if (is(T == void)) return valueOp(); 1889 | else return valueOp(forwardValue!self); 1890 | } 1891 | } 1892 | } 1893 | 1894 | /// 1895 | @("mapOrElse") 1896 | @safe unittest 1897 | { 1898 | assert(ok(42).mapOrElse!(v => v/2, e => 0) == 21); 1899 | assert(ok().mapOrElse!(() => true, e => false)); 1900 | assert(err!int("foo").mapOrElse!(v => v/2, e => 42) == 42); 1901 | assert(!err("foo").mapOrElse!(() => true, e => false)); 1902 | } 1903 | 1904 | private template forwardMember(alias arg, string member) 1905 | { 1906 | import core.lifetime : move; 1907 | // lvalue arg or non-moveable member (rvalue or const/immutable) 1908 | static if (__traits(isRef, arg) || 1909 | __traits(isOut, arg) || 1910 | __traits(isLazy, arg) || 1911 | !is(typeof(move(__traits(getMember, arg, member))))) 1912 | @property auto ref forwardMember(){ return __traits(getMember, arg, member); } 1913 | // rvalue arg and moveable member (mutable lvalue) 1914 | else 1915 | @property auto forwardMember(){ return move(__traits(getMember, arg, member)); } 1916 | } 1917 | 1918 | private alias forwardValue(alias arg) = forwardMember!(arg, "value"); 1919 | private alias forwardError(alias arg) = forwardMember!(arg, "error"); 1920 | 1921 | @("forwardMember") 1922 | @safe unittest 1923 | { 1924 | struct A 1925 | { 1926 | int i; 1927 | ~this() {} 1928 | } 1929 | struct S 1930 | { 1931 | A a; 1932 | A rvalue() { return a; } 1933 | ref A lvalue() { return a; } 1934 | } 1935 | 1936 | bool foo(T)(auto ref T val) 1937 | { 1938 | return __traits(isRef, val); 1939 | } 1940 | bool bar(string member, T)(auto ref T val, out int after) 1941 | { 1942 | auto res = foo(forwardMember!(val, member)); 1943 | after = val.a.i; 1944 | return res; 1945 | } 1946 | 1947 | int after; 1948 | 1949 | // rvalue arg, member -> foo gets rvalue by move 1950 | assert(bar!"a"(S(A(1729)), after) == false); 1951 | assert(after == 0); 1952 | 1953 | // rvalue arg, rvalue method -> foo gets rvalue as return value of `rvalue` method, no moves 1954 | assert(bar!"rvalue"(S(A(1729)), after) == false); 1955 | assert(after == 1729); 1956 | 1957 | // rvalue arg, lvalue method -> foo gets rvalue by move 1958 | assert(bar!"lvalue"(S(A(1729)), after) == false); 1959 | assert(after == 0); 1960 | 1961 | auto s = S(A(42)); 1962 | 1963 | // lvalue arg, member -> foo gets lvalue 1964 | assert(bar!"a"(s, after) == true); 1965 | assert(after == 42); 1966 | assert(s.a.i == 42); 1967 | 1968 | // lvalue arg, rvalue method -> foo gets rvalue as return value of `rvalue` method, no moves 1969 | assert(bar!"rvalue"(s, after) == false); 1970 | assert(after == 42); 1971 | assert(s.a.i == 42); 1972 | 1973 | // lvalue arg, lvalue method -> foo gets lvalue 1974 | assert(bar!"lvalue"(s, after) == true); 1975 | assert(after == 42); 1976 | assert(s.a.i == 42); 1977 | } 1978 | 1979 | static if (__VERSION__ < 2096) { 1980 | private alias noreturn = void; 1981 | } 1982 | -------------------------------------------------------------------------------- /tests/expected.d: -------------------------------------------------------------------------------- 1 | module tests; 2 | 3 | import expected; 4 | version (D_Exceptions) import std.exception; 5 | 6 | version (D_BetterC) 7 | { 8 | extern(C) void main() 9 | { 10 | import core.stdc.stdio; 11 | import std.meta : AliasSeq; 12 | 13 | alias modules = AliasSeq!(expected, tests); 14 | static foreach (m; modules) 15 | { 16 | static foreach(u; __traits(getUnitTests, m)) { 17 | debug printf("testing " ~ m.stringof ~ ": '" ~ __traits(getAttributes, u)[0] ~ "'\n"); 18 | u(); 19 | } 20 | } 21 | printf("All unit tests have been run successfully.\n"); 22 | } 23 | } 24 | 25 | @safe: 26 | 27 | @("Expected.init") 28 | @system nothrow unittest 29 | { 30 | struct EnableDefaultConstructor { static immutable bool enableDefaultConstructor = true; } 31 | 32 | { 33 | auto res = Expected!(int, string).init; 34 | assert(!res.hasValue && !res.hasError); 35 | assert(res); 36 | version (D_Exceptions) 37 | { 38 | assertThrown!Throwable(res.value); 39 | assertThrown!Throwable(res.error); 40 | } 41 | static assert(!__traits(compiles, res = 42)); 42 | } 43 | 44 | { 45 | auto res = Expected!(int, string, EnableDefaultConstructor).init; 46 | assert(!res.hasValue && !res.hasError); 47 | assert(res); 48 | assert(res.value == 0); 49 | assert(res.error is null); 50 | res = 42; 51 | assert(res.value == 42); 52 | } 53 | 54 | // T == void 55 | { 56 | auto res = Expected!(void, string).init; 57 | static assert(!__traits(compiles, res.hasValue)); 58 | static assert(!__traits(compiles, res.value)); 59 | static assert(!__traits(compiles, res = "foo")); 60 | assert(!res.hasError); 61 | assert(res); 62 | version (D_Exceptions) assertThrown!Throwable(res.error); 63 | } 64 | 65 | // T == void 66 | { 67 | auto res = Expected!(void, string, EnableDefaultConstructor).init; 68 | static assert(!__traits(compiles, res.hasValue)); 69 | static assert(!__traits(compiles, res.value)); 70 | assert(!res.hasError); 71 | assert(res); 72 | assert(res.error is null); 73 | res = "foo"; 74 | assert(res.error == "foo"); 75 | } 76 | } 77 | 78 | @("Default constructor - disabled") 79 | unittest 80 | { 81 | static assert(!__traits(compiles, Expected!(int, string)())); 82 | } 83 | 84 | @("Default constructor - enabled") 85 | @system nothrow unittest 86 | { 87 | struct EnableDefaultConstructor { static immutable bool enableDefaultConstructor = true; } 88 | { 89 | auto res = Expected!(int, string, EnableDefaultConstructor)(); 90 | assert(!res.hasValue && !res.hasError); 91 | assert(res); 92 | assert(res.value == 0); 93 | assert(res.error is null); 94 | res = 42; 95 | assert(res); 96 | assert(res.value == 42); 97 | } 98 | 99 | { 100 | auto res = Expected!(void, string, EnableDefaultConstructor)(); 101 | assert(!res.hasError); 102 | assert(res); 103 | assert(res.error is null); 104 | res = "foo"; 105 | assert(res.hasError); 106 | assert(!res); 107 | assert(res.error == "foo"); 108 | } 109 | } 110 | 111 | @("Default types") 112 | nothrow @nogc unittest 113 | { 114 | auto res = Expected!(int)(42); 115 | assert(res); 116 | assert(res.hasValue && !res.hasError); 117 | assert(res.value == 42); 118 | res.value = 43; 119 | assert(res.value == 43); 120 | } 121 | 122 | @("Default types with const payload") 123 | nothrow @nogc unittest 124 | { 125 | alias Exp = Expected!(const(int)); 126 | static assert(is(typeof(Exp.init.value) == const(int))); 127 | auto res = Exp(42); 128 | assert(res); 129 | assert(res.hasValue && !res.hasError); 130 | assert(res.value == 42); 131 | static assert(!__traits(compiles, res.value = res.value)); 132 | } 133 | 134 | @("Default types with immutable payload") 135 | unittest 136 | { 137 | alias Exp = Expected!(immutable(int)); 138 | static assert(is(typeof(Exp.init.value) == immutable(int))); 139 | auto res = Exp(42); 140 | assert(res); 141 | assert(res.hasValue && !res.hasError); 142 | assert(res.value == 42); 143 | static assert(!__traits(compiles, res.value = res.value)); 144 | } 145 | 146 | @("opAssign") 147 | @system nothrow unittest 148 | { 149 | struct EnableDefaultConstructor { static immutable bool enableDefaultConstructor = true; } 150 | // value 151 | { 152 | auto res = Expected!(int, string, EnableDefaultConstructor).init; 153 | res = 42; 154 | assert(res); 155 | assert(res.hasValue && !res.hasError); 156 | assert(res.value == 42); 157 | res = 43; 158 | version (D_Exceptions) assertThrown!Throwable(res = "foo"); 159 | } 160 | 161 | // error 162 | { 163 | auto res = Expected!(int, string, EnableDefaultConstructor)("42"); 164 | assert(!res.hasValue && res.hasError); 165 | assert(res.error == "42"); 166 | res = "foo"; 167 | assert(res.error == "foo"); 168 | version (D_Exceptions) assertThrown!Throwable(res = 42); 169 | } 170 | } 171 | 172 | @("Same types") 173 | @system nothrow unittest 174 | { 175 | // value 176 | { 177 | alias Exp = Expected!(int, int); 178 | auto res = Exp(42); 179 | assert(res); 180 | assert(res.hasValue && !res.hasError); 181 | assert(res.value == 42); 182 | version (D_Exceptions) assertThrown!Throwable(res.error()); 183 | } 184 | 185 | // error 186 | { 187 | alias Exp = Expected!(int, int); 188 | auto res = Exp(42, false); 189 | assert(!res); 190 | assert(!res.hasValue && res.hasError); 191 | assert(res.error == 42); 192 | version (D_Exceptions) assertThrown!Throwable(res.value()); 193 | assert(err!int(42).error == 42); 194 | } 195 | 196 | // immutable value 197 | { 198 | alias Exp = Expected!(immutable(int), immutable(int)); 199 | auto res = Exp(immutable int(42)); 200 | assert(res); 201 | assert(res.hasValue && !res.hasError); 202 | assert(res.value == 42); 203 | version (D_Exceptions) assertThrown!Throwable(res.error()); 204 | } 205 | 206 | // immutable error 207 | { 208 | alias Exp = Expected!(immutable(int), immutable(int)); 209 | auto res = Exp(immutable int(42), false); 210 | assert(!res); 211 | assert(!res.hasValue && res.hasError); 212 | assert(res.error == 42); 213 | version (D_Exceptions) assertThrown!Throwable(res.value()); 214 | assert(err!(immutable(int))(immutable int(42)).error == 42); 215 | } 216 | 217 | // const mix 218 | { 219 | alias Exp = Expected!(const(int), int); 220 | auto res = Exp(const int(42)); 221 | auto val = res.value; 222 | static assert(is(typeof(val) == const int)); 223 | assert(res); 224 | assert(res.hasValue && !res.hasError); 225 | assert(res.value == 42); 226 | version (D_Exceptions) assertThrown!Throwable(res.error); 227 | } 228 | 229 | // const mix 230 | { 231 | alias Exp = Expected!(const(int), int); 232 | auto res = Exp(42); 233 | auto err = res.error; 234 | static assert(is(typeof(err) == int)); 235 | assert(!res); 236 | assert(!res.hasValue && res.hasError); 237 | assert(res.error == 42); 238 | version (D_Exceptions) assertThrown!Throwable(res.value); 239 | } 240 | 241 | // immutable mix 242 | { 243 | alias Exp = Expected!(immutable(int), int); 244 | auto res = Exp(immutable int(42)); 245 | auto val = res.value; 246 | static assert(is(typeof(val) == immutable int)); 247 | assert(res); 248 | assert(res.hasValue && !res.hasError); 249 | assert(res.value == 42); 250 | version (D_Exceptions) assertThrown!Throwable(res.error); 251 | } 252 | 253 | // immutable mix 254 | { 255 | alias Exp = Expected!(immutable(int), int); 256 | auto res = Exp(42); 257 | auto err = res.error; 258 | static assert(is(typeof(err) == int)); 259 | assert(!res); 260 | assert(!res.hasValue && res.hasError); 261 | assert(res.error == 42); 262 | version (D_Exceptions) assertThrown!Throwable(res.value); 263 | } 264 | 265 | // immutable mix reverse 266 | { 267 | alias Exp = Expected!(int, immutable(int)); 268 | auto res = Exp(immutable int(42)); 269 | auto err = res.error; 270 | static assert(is(typeof(err) == immutable int)); 271 | assert(!res); 272 | assert(!res.hasValue && res.hasError); 273 | assert(res.error == 42); 274 | version (D_Exceptions) assertThrown!Throwable(res.value); 275 | } 276 | 277 | // immutable mix reverse 278 | { 279 | alias Exp = Expected!(int, immutable(int)); 280 | auto res = Exp(42); 281 | auto val = res.value; 282 | static assert(is(typeof(val) == int)); 283 | assert(res); 284 | assert(res.hasValue && !res.hasError); 285 | assert(res.value == 42); 286 | version (D_Exceptions) assertThrown!Throwable(res.error); 287 | } 288 | } 289 | 290 | @("void payload") 291 | nothrow @nogc unittest 292 | { 293 | alias Exp = Expected!(void, int); 294 | static assert (!__traits(hasMember, Exp, "hasValue")); 295 | static assert (!__traits(hasMember, Exp, "value")); 296 | 297 | { 298 | auto res = Exp(); 299 | assert(res); 300 | assert(!res.hasError); 301 | } 302 | 303 | { 304 | auto res = Exp(42); 305 | assert(!res); 306 | assert(res.hasError); 307 | assert(res.error == 42); 308 | } 309 | } 310 | 311 | @("opEquals") 312 | unittest 313 | { 314 | assert(ok(42) == 42); 315 | assert(ok(42) != 43); 316 | assert(ok("foo") == "foo"); 317 | assert(ok("foo") != "bar"); 318 | assert(ok("foo") == cast(const string)"foo"); 319 | assert(ok("foo") == cast(immutable string)"foo"); 320 | assert(ok(42) == ok(42)); 321 | assert(ok(42) != ok(43)); 322 | assert(ok(42) != err!int("42")); 323 | 324 | static assert(!__traits(compiles, err("foo") == "foo")); 325 | assert(err(42) == err(42)); 326 | assert(err(42) != err(43)); 327 | assert(err("foo") == err("foo")); 328 | assert(err("foo") != err("bar")); 329 | } 330 | 331 | //FIXME: doesn't work - some older dmd error 332 | static if (__VERSION__ >= 2082) 333 | { 334 | @("toHash") 335 | unittest 336 | { 337 | assert(ok(42).hashOf == 42.hashOf); 338 | assert(ok(42).hashOf != 43.hashOf); 339 | assert(ok(42).hashOf == ok(42).hashOf); 340 | assert(ok(42).hashOf != ok(43).hashOf); 341 | assert(ok(42).hashOf == ok!bool(42).hashOf); 342 | assert(ok(42).hashOf != err("foo").hashOf); 343 | assert(err("foo").hashOf == err("foo").hashOf); 344 | } 345 | } 346 | 347 | @("range interface") 348 | unittest 349 | { 350 | { 351 | auto r = ok(42); 352 | assert(!r.empty); 353 | assert(r.front == 42); 354 | r.popFront(); 355 | assert(r.empty); 356 | } 357 | 358 | { 359 | auto r = err!int("foo"); 360 | assert(r.empty); 361 | } 362 | 363 | { 364 | auto r = err("foo"); 365 | static assert(!__traits(compiles, r.empty)); 366 | static assert(!__traits(compiles, r.front)); 367 | static assert(!__traits(compiles, r.popFront)); 368 | } 369 | 370 | // with forced check 371 | { 372 | struct Hook { 373 | static immutable bool enableCopyConstructor = false; 374 | static void onUnchecked() @safe { assert(0); } 375 | } 376 | 377 | auto res = ok(42); 378 | assert(!res.empty); 379 | assert(res.front == 42); 380 | res.popFront(); assert(res.empty); 381 | } 382 | } 383 | 384 | @("Complex payload") 385 | unittest 386 | { 387 | { 388 | struct Value { int val; } 389 | 390 | assert(ok(Value(42)).hasValue); 391 | assert(ok(const Value(42)).hasValue); 392 | assert(ok(immutable Value(42)).hasValue); 393 | } 394 | 395 | { 396 | struct DisabledValue { int val; @disable this(this); } 397 | 398 | assert(ok(DisabledValue(42)).hasValue); 399 | //FIXME? 400 | //assert(ok(const DisabledValue(42)).hasValue); 401 | // assert(ok(immutable DisabledValue(42)).hasValue); 402 | } 403 | } 404 | 405 | @("RC payload") 406 | unittest 407 | { 408 | struct Hook { 409 | static immutable bool enableRefCountedPayload = true; 410 | static immutable bool enableDefaultConstructor = true; 411 | static void onUnchecked() pure nothrow @nogc { assert(0, "result unchecked"); } 412 | } 413 | 414 | static assert(isDefaultConstructorEnabled!Hook); 415 | 416 | { 417 | auto e = ok!(bool, Hook)(42); 418 | e = 43; 419 | assert(e.value == 43); 420 | } 421 | 422 | struct Value { int val; } 423 | 424 | auto res = ok!(bool, Hook)(Value(42)); 425 | assert(res.hasValue); 426 | 427 | assert(ok!(bool, Hook)(true).hasValue); 428 | assert(err!(bool, Hook)(true).hasError); 429 | assert(ok!(bool, Hook)(const Value(42)).hasValue); 430 | assert(ok!(bool, Hook)(immutable Value(42)).hasValue); 431 | 432 | // same types 433 | assert(ok!(int, Hook)(42).value == 42); 434 | assert(err!(int, Hook)(42).error == 42); 435 | 436 | // forced check 437 | () @trusted { 438 | version (D_Exceptions) assertThrown!Throwable({ ok!(bool, Hook)(42); }()); 439 | }(); 440 | 441 | //FIXME? 442 | //immutable r = ok!(bool, Hook)(immutable Value(42)); 443 | // immutable r = Expected!(immutable(Value), bool, Hook)(immutable Value(42)); 444 | // assert(r.value == 42); 445 | } 446 | 447 | @("void hook") 448 | unittest 449 | { 450 | auto empty = Expected!(int, string, void).init; 451 | assert(!empty.hasValue); 452 | assert(!empty.hasError); 453 | assert(empty.value == int.init); 454 | assert(empty.error is null); 455 | } 456 | 457 | @("system pred") 458 | @system unittest 459 | { 460 | auto foo() @system { return ok; } 461 | static assert(__traits(compiles, { auto r = ok.andThen!foo; })); 462 | static assert(__traits(compiles, { auto r = err("foo").orElse!foo; })); 463 | } 464 | 465 | @("disabled copy constructor") 466 | @system unittest 467 | { 468 | import std.algorithm : move; 469 | 470 | static int destroyed; 471 | 472 | static struct Foo 473 | { 474 | int n; 475 | @disable this(this); 476 | ~this() 477 | { 478 | destroyed += n; 479 | } 480 | } 481 | 482 | static auto gen() 483 | { 484 | Foo f; 485 | f.n = 42; 486 | return ok!int(f.move()); 487 | } 488 | 489 | { 490 | Foo f; 491 | f = gen().expect("uh"); 492 | } 493 | assert(destroyed == 42); 494 | } 495 | --------------------------------------------------------------------------------