├── analysis_options.yaml ├── CHANGELOG.md ├── lib ├── src │ ├── extensions.dart │ ├── unwrap_err.dart │ ├── err.dart │ ├── ok.dart │ └── default.dart └── result_in_dart.dart ├── .gitignore ├── pubspec.yaml ├── LICENSE ├── example └── result_in_dart_example.dart └── README.md /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version. 4 | 5 | ## 1.0.1 6 | 7 | - Renamed package 8 | -------------------------------------------------------------------------------- /lib/src/extensions.dart: -------------------------------------------------------------------------------- 1 | part of result_in_dart; 2 | 3 | extension FlattenResult on Result, E> { 4 | /// Converts from [Result, E>] to [Result] 5 | Result flatten() => andThen((value) => value); 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # https://dart.dev/guides/libraries/private-files 2 | # Created by `dart pub` 3 | .dart_tool/ 4 | 5 | # Avoid committing pubspec.lock for library packages; see 6 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 7 | pubspec.lock 8 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: result_in_dart 2 | description: Result in Dart is a Dart package that provides a Result type for modeling success or failure operations. 3 | version: 1.0.1 4 | repository: https://github.com/devhammed/result_in_dart 5 | 6 | environment: 7 | sdk: ^3.0.5 8 | 9 | dev_dependencies: 10 | lints: ^2.0.0 11 | -------------------------------------------------------------------------------- /lib/src/unwrap_err.dart: -------------------------------------------------------------------------------- 1 | part of result_in_dart; 2 | 3 | /// Error thrown by the runtime system when `unwrap` fails. 4 | class ResultUnwrapError extends Error { 5 | ResultUnwrapError( 6 | this.message, { 7 | this.obj, 8 | }); 9 | 10 | final String message; 11 | final Object? obj; 12 | 13 | @override 14 | String toString() => obj != null ? '$message: $obj' : message; 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/err.dart: -------------------------------------------------------------------------------- 1 | part of result_in_dart; 2 | 3 | /// [Err] is a type that represents failure and contains a [E] type error value. 4 | class Err extends Result { 5 | const Err(this.value); 6 | 7 | final E value; 8 | 9 | @override 10 | bool operator ==(Object other) { 11 | if (identical(this, other)) { 12 | return true; 13 | } 14 | 15 | return other is Err && other.value == value; 16 | } 17 | 18 | @override 19 | int get hashCode => Object.hash('Err()', value); 20 | 21 | @override 22 | String toString() => 'Err(${value.toString()})'; 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/ok.dart: -------------------------------------------------------------------------------- 1 | part of result_in_dart; 2 | 3 | /// [Ok] is a type that represents success and contains a [T] type success 4 | /// value. 5 | class Ok extends Result { 6 | const Ok(this.value); 7 | 8 | final T value; 9 | 10 | @override 11 | bool operator ==(Object other) { 12 | if (identical(this, other)) { 13 | return true; 14 | } 15 | 16 | return other is Ok && other.value == value; 17 | } 18 | 19 | @override 20 | int get hashCode => Object.hash('Ok()', value); 21 | 22 | @override 23 | String toString() => 'Ok(${value.toString()})'; 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/default.dart: -------------------------------------------------------------------------------- 1 | part of result_in_dart; 2 | 3 | /// Default result class. 4 | class Default { 5 | static T get(Result result) { 6 | if (result.isOk()) { 7 | return result.okValue; 8 | } 9 | 10 | switch (T) { 11 | case int: 12 | return 0 as T; 13 | case double: 14 | return 0.0 as T; 15 | case String: 16 | return '' as T; 17 | case bool: 18 | return false as T; 19 | case List: 20 | return [] as T; 21 | case Map: 22 | return {} as T; 23 | case Set: 24 | return {} as T; 25 | case BigInt: 26 | return BigInt.zero as T; 27 | case DateTime: 28 | return DateTime.now() as T; 29 | case Duration: 30 | return Duration.zero as T; 31 | case RegExp: 32 | return RegExp('') as T; 33 | case Uri: 34 | return Uri() as T; 35 | default: 36 | throw ArgumentError( 37 | 'Type $T is not supported, please use unwrapOr or unwrapOrElse.', 38 | T.toString(), 39 | ); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Hammed Oyedele 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/result_in_dart_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:result_in_dart/result_in_dart.dart'; 2 | 3 | enum Version { 4 | version1, 5 | version2; 6 | } 7 | 8 | Result parseVersion(int versionNum) { 9 | if (versionNum == 1) { 10 | return const Ok(Version.version1); 11 | } 12 | 13 | if (versionNum == 2) { 14 | return const Ok(Version.version2); 15 | } 16 | 17 | return const Err('invalid version'); 18 | } 19 | 20 | void main() { 21 | final version = parseVersion(3); 22 | 23 | // you can then unwrap... 24 | if (version.isOk()) { 25 | print('unwrap: working with version: ${version.unwrap()}'); 26 | } else { 27 | print('unwrap: error parsing header: ${version.unwrapErr()}'); 28 | } 29 | 30 | // or mapping... 31 | parseVersion(1).mapOrElse( 32 | (err) => print('mapOrElse: error parsing header: $err'), 33 | (v) => print('mapOrElse: working with version: $v'), 34 | ); 35 | 36 | // or using Dart 3.0 patterns... 37 | if (version case Ok(value: Version v)) { 38 | print('patterns: working with version: $v'); 39 | } 40 | 41 | if (version case Err(value: String err)) { 42 | print('patterns: error parsing header: $err'); 43 | } 44 | 45 | // unwrapping using the ~ operator 46 | final a = Ok(1); 47 | final b = Ok(2); 48 | 49 | print('~ operator: ${~a + ~b}'); 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # result_in_dart 2 | 3 | Dart implementation of Rust's `[Result]` type. 4 | 5 | `Result` is the type used for returning and propagating 6 | errors. It is a class with the two variants, `[Ok(T)]` and `[Err(E)]`. 7 | where `[T]` is the type of a successful value and `[E]` is the type of an error. 8 | 9 | Loosely based on https://doc.rust-lang.org/src/core/result.rs.html source code. 10 | 11 | ```dart 12 | import 'package:result_in_dart/result_in_dart.dart'; 13 | 14 | enum Version { 15 | version1, 16 | version2; 17 | } 18 | 19 | Result parseVersion(int versionNum) { 20 | if (versionNum == 1) { 21 | return const Ok(Version.version1); 22 | } 23 | 24 | if (versionNum == 2) { 25 | return const Ok(Version.version2); 26 | } 27 | 28 | return const Err('invalid version'); 29 | } 30 | 31 | void main() { 32 | final version = parseVersion(1); 33 | 34 | // you can then unwrap... 35 | if (version.isOk()) { 36 | print('unwrap: working with version: ${version.unwrap()}'); 37 | } else { 38 | print('unwrap: error parsing header: ${version.unwrapErr()}'); 39 | } 40 | 41 | // or mapping... 42 | parseVersion(1).mapOrElse( 43 | (err) => print('mapOrElse: error parsing header: $err'), 44 | (v) => print('mapOrElse: working with version: $v'), 45 | ); 46 | 47 | // or using Dart 3.0 patterns... 48 | if (version case Ok(value: Version v)) { 49 | print('patterns: working with version: $v'); 50 | } 51 | 52 | if (version case Err(value: String err)) { 53 | print('patterns: error parsing header: $err'); 54 | } 55 | 56 | // unwrapping using the ~ operator 57 | final a = Ok(1); 58 | final b = Ok(2); 59 | 60 | print('~ operator: ${~a + ~b}'); 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /lib/result_in_dart.dart: -------------------------------------------------------------------------------- 1 | /// Dart implementation of Rust's [Result] type. 2 | /// 3 | /// [Result] is the type used for returning and propagating 4 | /// errors. It is a class with the two variants, [Ok(T)] and [Err(E)]. 5 | /// where [T] is the type of a successful value and [E] is the type of an error. 6 | /// 7 | /// Loosely based on https://doc.rust-lang.org/src/core/result.rs.html source code. 8 | library result_in_dart; 9 | 10 | /// Includes all the [Result] classes. 11 | part 'src/ok.dart'; 12 | part 'src/err.dart'; 13 | part 'src/default.dart'; 14 | part 'src/extensions.dart'; 15 | part 'src/unwrap_err.dart'; 16 | 17 | /// [Result] is a type that represents either success [Ok] or failure [Err]. 18 | abstract class Result { 19 | /// Default constructor. 20 | const Result(); 21 | 22 | /// Cast this [Result] to [Ok]. 23 | Ok get asOk => this as Ok; 24 | 25 | /// Cast this [Result] to [Err]. 26 | Err get asErr => this as Err; 27 | 28 | /// Get the contained [Ok] value, if [Ok], otherwise it throws an error. 29 | T get okValue => asOk.value; 30 | 31 | /// Get the contained [Err] value, if [Err], otherwise it throws an error. 32 | E get errValue => asErr.value; 33 | 34 | /// Returns `true` if the result is [Ok]. 35 | bool isOk() => this is Ok; 36 | 37 | /// Returns [true] if the result is [Ok] and the value inside of it matches a predicate. 38 | bool isOkAnd(bool Function(T value) f) => isOk() && f(okValue); 39 | 40 | /// Returns `true` if the result is [Err]. 41 | bool isErr() => !isOk(); 42 | 43 | /// Returns [true] if the result is [Err] and the value inside of it matches a predicate. 44 | bool isErrAnd(bool Function(E err) f) => isErr() && f(errValue); 45 | 46 | /// Converts from `Result` to `T?`. 47 | /// 48 | /// Converts `this` into a nullable value, consuming `this`, 49 | /// and discarding the error, if any. 50 | T? ok() => isOk() ? okValue : null; 51 | 52 | /// Converts from `Result` to `E?`. 53 | /// 54 | /// Converts `this` into a nullable value, consuming `this`, 55 | /// and discarding the success value, if any. 56 | E? err() => isOk() ? null : errValue; 57 | 58 | /// Maps a `Result` to `Result` by applying [op] function to a 59 | /// contained [Ok] value, leaving an [Err] value untouched. 60 | /// 61 | /// This function can be used to compose the results of two functions. 62 | Result map(U Function(T value) op) => 63 | isOk() ? Ok(op(okValue)) : Err(errValue); 64 | 65 | /// Returns the provided default (if [Err]), or 66 | /// applies [f] function to the contained value (if [Ok]), 67 | /// 68 | /// Arguments passed to [mapOr] are eagerly evaluated; if you are passing 69 | /// the result of a function call, it is recommended to use [mapOrElse], 70 | /// which is lazily evaluated. 71 | U mapOr(U defaultValue, U Function(T value) f) => 72 | isOk() ? f(okValue) : defaultValue; 73 | 74 | /// Maps a [Result] to [U] by applying fallback function [defaultF] to 75 | /// a contained [Err] value, or function [f] to a contained [Ok] value. 76 | /// 77 | /// This function can be used to unpack a successful result 78 | /// while handling an error. 79 | U mapOrElse(U Function(E err) defaultF, U Function(T value) f) => 80 | isOk() ? f(okValue) : defaultF(errValue); 81 | 82 | /// Maps a [Result] to [Result] by applying [op] function to a 83 | /// contained [Err] value, leaving an [Ok] value untouched. 84 | /// 85 | /// This function can be used to pass through a successful result while 86 | /// handling an error. 87 | Result mapErr(F Function(E err) op) => 88 | isOk() ? Ok(okValue) : Err(op(errValue)); 89 | 90 | /// Calls the provided closure with a reference to the contained value (if [Ok]). 91 | Result inspect(void Function(T value) f) { 92 | if (isOk()) { 93 | f(okValue); 94 | } 95 | 96 | return this; 97 | } 98 | 99 | /// Calls the provided closure with a reference to the contained error (if [Err]). 100 | Result inspectErr(void Function(E err) f) { 101 | if (isErr()) { 102 | f(errValue); 103 | } 104 | 105 | return this; 106 | } 107 | 108 | /// Returns an iterable over the possibly contained value. 109 | /// 110 | /// The iterable yields one value if the result is [Ok], otherwise none. 111 | Iterable iter() => isOk() ? [okValue] : const []; 112 | 113 | /// Returns the contained [Ok] value, consuming the `this` value. 114 | /// 115 | /// Throws an error if the value is an [Err], with an error message including 116 | /// the passed message, and the content of the [Err]. 117 | T expect(String msg) => 118 | isOk() ? okValue : throw ResultUnwrapError(msg, obj: errValue); 119 | 120 | /// Returns the contained [Ok] value, consuming the `this` value. 121 | /// 122 | /// Because this function may throw an error, its use is generally 123 | /// discouraged. 124 | /// Instead, prefer to handle the [Err] case explicitly, 125 | /// or call [unwrapOr] or [unwrapOrElse]. 126 | T unwrap() => isOk() 127 | ? okValue 128 | : throw ResultUnwrapError( 129 | 'called `Result#unwrap` on an `Err` value', 130 | obj: errValue, 131 | ); 132 | 133 | /// Returns the contained [Ok] value or a default. 134 | T unwrapOrDefault() => Default.get(this); 135 | 136 | /// Returns the contained [Err] value, consuming the `this` value. 137 | /// 138 | /// Throw an error if the value is an [Ok], with an error message including 139 | /// the passed message, and the content of the [Ok]. 140 | E expectErr(String msg) => 141 | isOk() ? throw ResultUnwrapError(msg, obj: okValue) : errValue; 142 | 143 | /// Returns the contained [Err] value, consuming the `this` value. 144 | /// 145 | /// Exceptions if the value is an [Ok], with a custom error message provided 146 | /// by the [Ok]'s value. 147 | E unwrapErr() => isOk() 148 | ? throw ResultUnwrapError( 149 | 'called `Result#unwrapErr` on an `Ok` value', 150 | obj: okValue, 151 | ) 152 | : errValue; 153 | 154 | /// Returns [res] if the result is [Ok], otherwise returns the [Err] value of 155 | /// [this]. 156 | Result and(Result res) => isOk() ? res : Err(errValue); 157 | 158 | /// Calls [op] if the result is [Ok], otherwise returns the [Err] value of 159 | /// [Result]. 160 | /// 161 | /// This function can be used for control flow based on [Result] values. 162 | Result andThen(Result Function(T value) op) => 163 | isOk() ? op(okValue) : Err(errValue); 164 | 165 | /// Returns [res] if the result is [Err], otherwise returns the [Ok] value of 166 | /// [this]. 167 | /// 168 | /// Arguments passed to [or] are eagerly evaluated; if you are passing the 169 | /// result of a function call, it is recommended to use [orElse], which is 170 | /// lazily evaluated. 171 | Result or(Result res) => isOk() ? Ok(okValue) : res; 172 | 173 | /// Calls [op] if the result is [Err], otherwise returns the [Ok] value of 174 | /// [Result]. 175 | /// 176 | /// This function can be used for control flow based on result values. 177 | Result orElse(Result Function(E err) op) => 178 | isOk() ? Ok(okValue) : op(errValue); 179 | 180 | /// Returns the contained [Ok] value or a provided default. 181 | /// 182 | /// Arguments passed to [unwrapOr] are eagerly evaluated; if you are passing 183 | /// the result of a function call, it is recommended to use 184 | /// [unwrapOrElse], which is lazily evaluated. 185 | T unwrapOr(T defaultValue) => isOk() ? okValue : defaultValue; 186 | 187 | /// Returns the contained [Ok] value or computes it from a closure. 188 | T unwrapOrElse(T Function(E err) op) => isOk() ? okValue : op(errValue); 189 | 190 | /// Shortcut to call [Result.unwrap()]. 191 | /// 192 | /// This is as close to analogous to Rust's `?` postfix operator for `Result` 193 | /// values as Dart can manage. 194 | /// 195 | /// ```dart 196 | /// final foo = Ok(1); 197 | /// final bar = Ok(2); 198 | /// 199 | /// print(~foo + ~bar); // prints: 3 200 | /// ``` 201 | /// 202 | /// **Note**: if you need to access fields or methods on the held value when 203 | /// using `~`, you'll need to use parentheses like so: 204 | /// 205 | /// ```dart 206 | /// final res = Ok(1); 207 | /// 208 | /// print((~res).toString()); 209 | /// ``` 210 | /// 211 | /// Additionally, If you need to perform a bitwise NOT on the held value of 212 | /// a [Result], you have a few choices: 213 | /// 214 | /// ```dart 215 | /// final res = Ok(1); 216 | /// 217 | /// print(~(~res)); // prints: -2 218 | /// print(~~res); // prints: -2 219 | /// print(~res.unwrap()); // prints: -2; 220 | /// ``` 221 | T operator ~() => unwrap(); 222 | } 223 | --------------------------------------------------------------------------------