diff --git a/README.md b/README.md index 072295b..f0e32b6 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,12 @@ [![Pub Version](https://img.shields.io/pub/v/rust_core.svg)](https://pub.dev/packages/rust_core) [![Dart Package Docs](https://img.shields.io/badge/documentation-pub.dev-blue.svg)](https://pub.dev/documentation/rust_core/latest/) [![License: MIT](https://img.shields.io/badge/license-MIT-purple.svg)](https://opensource.org/licenses/MIT) -[![Build Status](https://github.com/mcmah309/rust_core/actions/workflows/dart.yml/badge.svg)](https://github.com/mcmah309/rust_core/actions) +[![Build Status](https://github.com/mcmah309/rust_core/actions/workflows/test.yml/badge.svg)](https://github.com/mcmah309/rust_core/actions) [rust_core](https://github.com/mcmah309/rust_core) is an implementation of Rust's Core Library in Dart, bringing the power of Rust to Dart! [Rust Core Book 📖](mcmah309.github.io/rust_core) -If you are a Rust developer coming to Dart or vice versa, check out these links: -- [New To Dart](./new_to_dart.md) -- [New To Rust](./new_to_rust.md) - - # Project Goals rust_core's primary goal is to bring Rust's features and ergonomics to Dart. diff --git a/book/book.toml b/book/book.toml index 32a486d..0ac4f89 100644 --- a/book/book.toml +++ b/book/book.toml @@ -4,3 +4,6 @@ language = "en" multilingual = false src = "src" title = "rust_core" + +[output.html] +fold.enable = true \ No newline at end of file diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 9f94180..e480e70 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -14,6 +14,7 @@ - [option](./libs/option/option.md) - [panic](./libs/panic/panic.md) - [result](./libs/result/result.md) + - [Tips and Best Practices](./libs/result/tips_and_best_practices.md) - [slice](./libs/slice/slice.md) - [sync](./libs/sync/sync.md) - [typedefs](./libs/typedefs/typedefs.md) diff --git a/book/src/introduction/new_to_dart.md b/book/src/introduction/new_to_dart.md index db60736..6fd8be2 100644 --- a/book/src/introduction/new_to_dart.md +++ b/book/src/introduction/new_to_dart.md @@ -1 +1,73 @@ -# New To Dart \ No newline at end of file +# New To Dart + +Welcome to Dart! + +Dart is a great language choice for simple yet rich language for fast cross platform development (especially in the ui) and scripting. + +rust_core is great start to learn the Dart semantics as you will feel like you are writing native rust. Just look at this Rust code: +> Goal: Get the index of every "!" in a string not followed by a "?" +```rust +use std::iter::Peekable; + +fn main() { + let mut answer: Vec = Vec::new(); + let string = "kl!sd!?!"; + let chars: Vec = string + .chars() + .collect(); + let mut iter: Peekable<_> = chars + .windows(2) + .enumerate() + .peekable(); + + while let Some((index, window)) = iter.next() { + match window { + ['!', '?'] => continue, + ['!', _] => answer.push(index), + [_, '!'] if iter.peek().is_none() => answer.push(index + 1), + _ => continue, + } + } + assert_eq!(answer, [2, 7]); +} +``` +Dart equivalent with rust_core: +```dart +import 'package:rust_core/prelude'; + +void main() { + List answer = []; + String string = "kl!sd!?!"; + Peekable<(int, Arr)> iter = string + .chars() + .windows(2) + .enumerate() + .peekable(); + while (iter.moveNext()) { + final (index, window) = iter.current; + switch (window) { + case ["!", "?"]: + break; + case ["!", _]: + answer.add(iter.current.$1); + case [_, "!"] when iter.peek().isNone(): + answer.add(index + 1); + } + } + expect(answer, [2, 7]); +} +``` + +rust_core will introduce you to a few new types you may find useful as a Dart developer: + +| Rust Type | Dart Equivalent | rust_core | Description | +|-------------------|-----------------|----------------------|---------------------------------------------------------| +| `Vec` | `List` | `Vec`* | Dynamic array or list. | +| `[T; N]` | `const [...]`/`List(growable: false)` | `Arr` | Fixed size array or list | +| `Iterator` | `Iterable` | `RIterator` | Composable iteration +| `Option` | `T?` | `Option` | A type that may hold a value or none. | +| `Result` | - | `Result` | Type used for returning and propagating errors.| | +| `Path` | - | `Path`* | Type for file system path manipulation. +| `[T]` | - | `Slice` | View into an array or list. | +| `Cell` | - | `Cell` | wrappers of values | +*: Implemented through additional packages found [here](../misc/packages_built_on_rust_core.md) \ No newline at end of file diff --git a/book/src/introduction/new_to_rust.md b/book/src/introduction/new_to_rust.md index da683aa..fe523a5 100644 --- a/book/src/introduction/new_to_rust.md +++ b/book/src/introduction/new_to_rust.md @@ -1 +1,75 @@ -# New To Rust \ No newline at end of file +# New To Rust + +Welcome to Rust! + +Maybe you have heard of Rust and want to see what all the hype is about, maybe you know a little Rust +and want to improve your Rust while writing Dart, for whatever the reason, rust_core is here to help. + +Rust has a solid reputation for writing safe, maintainable, and performant code. rust_core is great start to learn and improve your rust semantics/knowledge. You will write Dart and learn Rust along the way. Just look at this Dart code: +> Goal: Get the index of every "!" in a string not followed by a "?" +```dart +import 'package:rust_core/prelude'; + +void main() { + List answer = []; + String string = "kl!sd!?!"; + Peekable<(int, Arr)> iter = string + .chars() + .windows(2) + .enumerate() + .peekable(); + while (iter.moveNext()) { + final (index, window) = iter.current; + switch (window) { + case ["!", "?"]: + break; + case ["!", _]: + answer.add(iter.current.$1); + case [_, "!"] when iter.peek().isNone(): + answer.add(index + 1); + } + } + expect(answer, [2, 7]); +} +``` +Rust equivalent: +```rust +use std::iter::Peekable; + +fn main() { + let mut answer: Vec = Vec::new(); + let string = "kl!sd!?!"; + let chars: Vec = string + .chars() + .collect(); + let mut iter: Peekable<_> = chars + .windows(2) + .enumerate() + .peekable(); + + while let Some((index, window)) = iter.next() { + match window { + ['!', '?'] => continue, + ['!', _] => answer.push(index), + [_, '!'] if iter.peek().is_none() => answer.push(index + 1), + _ => continue, + } + } + assert_eq!(answer, [2, 7]); +} +``` + +With rust_core you can expect all the usual types you see in Rust. Here is a quick matrix +comparing Rust, Dart, and Dart with rust_core: + +| Rust Type | Dart Equivalent | rust_core | Description | +|-------------------|-----------------|----------------------|---------------------------------------------------------| +| `Vec` | `List` | `Vec`* | Dynamic array or list. | +| `[T; N]` | `const [...]`/`List(growable: false)` | `Arr` | Fixed size array or list | +| `Iterator` | `Iterable` | `RIterator` | Composable iteration +| `Option` | `T?` | `Option` | A type that may hold a value or none. | +| `Result` | - | `Result` | Type used for returning and propagating errors.| | +| `Path` | - | `Path`* | Type for file system path manipulation. +| `[T]` | - | `Slice` | View into an array or list. | +| `Cell` | - | `Cell` | wrappers of values | +*: Implemented through additional packages found [here](../misc/packages_built_on_rust_core.md) \ No newline at end of file diff --git a/book/src/libs/array/array.md b/book/src/libs/array/array.md index b745c6d..31604a7 100644 --- a/book/src/libs/array/array.md +++ b/book/src/libs/array/array.md @@ -1,6 +1,6 @@ # Array -`Arr` (Array) is a zero cost extension type of List, where the list is treated as non-growable. This is useful for correctly handling lists where growable is false and const lists - as these types of lists are treated the same in the regular Dart type system, which may lead to errors. +`Arr` (Array) is a zero cost extension type of `List`, where the list is treated as non-growable. This is useful for correctly handling lists where growable is false and const lists - as these types of lists are treated the same in the regular Dart type system, which may lead to errors. ```dart var array = Arr(null, 10); array = Arr.constant(const [1,2,3,4,5]); @@ -9,4 +9,4 @@ for(final entry in array){ } var (slice1, slice2) = array.splitSlice(3); ``` -`Arr`'s allocation will be more efficient than compare to a List since it does not reserve additional capacity and allocates the full amount eagerly. Which is important since allocations account for most of the cost of the runtime costs of a List. \ No newline at end of file +`Arr`'s allocation will be more efficient than compare to a `List` since it does not reserve additional capacity and allocates the full amount eagerly. Which is important since allocations account for most of the cost of the runtime costs of a List. \ No newline at end of file diff --git a/book/src/libs/cell/cell.md b/book/src/libs/cell/cell.md index 095859d..cb3c863 100644 --- a/book/src/libs/cell/cell.md +++ b/book/src/libs/cell/cell.md @@ -23,6 +23,7 @@ expect(newCell, 22); expect(cell, 12); expect(antherCell, 10); ``` +The base type for all `Cell`s is `ConstCell`. ## OnceCell A cell which can be written to only once. Similar to `late final `, but will never throw an error. @@ -34,6 +35,7 @@ expect(result, const Ok(())); result = cell.set(20); expect(result, const Err(20)); ``` +The base type for all `OnceCell`s is `NullableOnceCell`. ## LazyCell A value which is initialized on the first access. @@ -51,26 +53,6 @@ final secondCall = lazyCell(); expect(callCount, equals(1)); expect(secondCall, equals(20)); ``` - -## Misc - -### Const and Nullability -Most of the time you will not need to know or care about this, and working -with the regular cell types is usually preferred, but -all Cells have `const`, `nullable` and `non-nullable` implementations. These were added for efficiency and to give -more control to developers. e.g. Non-Nullable `OnceCell` and `LazyCell` (which is the default) doesn't need an -additional `bool` to keep track if the value is set. - -The base type for all `Cell`s is `ConstCell`. - -The base type for all `OnceCell`s is `NullableOnceCell`. - The base type for all `LazyCell`s is `NullableLazyCell`. -`OnceCell` and `LazyCell` have special `constNullable` and `constNonNullable` implementations. These -allow for runtime mutability of the inner value. That opens up a lot of possibilities. e.g. -You can wrap non-const types. Allowing them to be used for something like `@Default(...)` -with the [freezed] package. - -[freezed]:https://pub.dev/packages/freezed [pub]:https://pub.dev/documentation/rust_core/latest/cell/cell-library.html \ No newline at end of file diff --git a/book/src/libs/iter/iter.md b/book/src/libs/iter/iter.md index 36a523b..0155a6e 100644 --- a/book/src/libs/iter/iter.md +++ b/book/src/libs/iter/iter.md @@ -45,14 +45,13 @@ Goal: Get the index of every "!" in a string not followed by a "?" import 'package:rust_core/prelude'; void main() { + List answer = []; String string = "kl!sd!?!"; - PeekableRIterator<(int, Arr)> iter = string.runes - .iter() - .map((e) => String.fromCharCode(e)) - .mapWindows(2, (e) => e) + Peekable<(int, Arr)> iter = string + .chars() + .windows(2) .enumerate() .peekable(); - List answer = []; while (iter.moveNext()) { final (int index, Arr window) = iter.current; switch (window) { @@ -67,7 +66,7 @@ void main() { expect(answer, [2, 7]); } ``` -Rust equivlent +Rust equivalent ```rust use std::iter::Peekable; @@ -126,4 +125,4 @@ This means the iterator can be cloned without cloning the underlying data. expect(iter3.collectList(), [4, 5]); expect(iter4.collectList(), [2, 3, 4, 5]); ``` -This for allows for situtations where you want to work ahead and not lose your iterator position, or pass the `RIterator` to another function without needing to call e.g. `collectList()`, `collectArr()`, etc. \ No newline at end of file +This for allows for situations where you want to work ahead and not lose your iterator position, or pass the `RIterator` to another function without needing to call e.g. `collectList()`, `collectArr()`, etc. \ No newline at end of file diff --git a/book/src/libs/option/option.md b/book/src/libs/option/option.md index 8768ced..8ddb48e 100644 --- a/book/src/libs/option/option.md +++ b/book/src/libs/option/option.md @@ -3,7 +3,7 @@ Option represents the union of two types - `Some` and `None`. An `Option` is an extension type of `T?`. Therefore, `Option` has zero runtime cost and has one big advantage over `T?`, you can chain null specific operations! -All types in rust_core support nullable and `Option` implementations of classes and methods for ergonomic convenience, but you +rust_core support nullable and `Option` implementations of classes and methods for ergonomic convenience where possible, but you can easily switch between the two with `toOption` and `toNullable` (or you can use `.v` directly). ### Usage @@ -23,7 +23,7 @@ You can also use Option in pattern matching ```dart switch(Some(2)){ case Some(:final v): - // do somthing + // do something default: // do something } @@ -31,7 +31,7 @@ switch(Some(2)){ ### Early Return Key Notation Option also supports "Early Return Key Notation" (ERKN), which is a derivative of "Do Notation". It allows a -function to return early if the value is `None`, and otherwise safely access the inner value directly without needing to unwrap or typecheck. +function to return early if the value is `None`, and otherwise safely access the inner value directly without needing to unwrap or type check. ```dart Option intNone() => None; Option earlyReturn(int val) => Option(($) { // Early Return Key diff --git a/book/src/libs/result/result.md b/book/src/libs/result/result.md index 5be124c..72a9f9a 100644 --- a/book/src/libs/result/result.md +++ b/book/src/libs/result/result.md @@ -1,86 +1,30 @@ # Result -### Table Of Contents +`Result` is the type used for returning and propagating errors. It is an alternative to throwing exceptions. It is a sealed type with the variants, `Ok(T)`, representing success and containing a value, and `Err(E)`, representing error and containing an error value. -- [What Is a Result Monad Type And Why Use It?](#what-is-a-result-monad-type-and-why-use-it) -- [Intro to Usage](#intro-to-usage) - - [Regular Dart Error Handling](#regular-dart-error-handling) - - [What's Wrong with this Solution?](#whats-wrong-with-this-solution) - - [Result Type](#result-type) - - [Chaining](#chaining) -- [Adding Predictable Control Flow To Legacy Dart Code](#adding-predictable-control-flow-to-legacy-dart-code) -- [Dart Equivalent To The Rust "?" Early Return Operator](#dart-equivalent-to-the-rust--early-return-operator) - - [Into](#into) - - [Early Return Key Notation](#early-return-key-notation) -- [How to Never Unwrap Incorrectly](#how-to-never-unwrap-incorrectly) -- [Misc](#misc) - - [Working with Futures](#working-with-futures) - - [ToResult and ToResultEager](#toresult-and-toresulteager) - - [Iterable Result](#iterable-result) - - [Multiple Results of Different Success Types](#multiple-results-of-different-success-types) - - [Pattern Matching vs Early Return Key](#pattern-matching-vs-early-return-key) - -## What Is a Result Monad Type And Why Use it? -A monad is just a wrapper around an object that provides a standard way of interacting with the inner object. The -`Result` monad is used in place of throwing exceptions. Instead of a function throwing an exception, the function -returns a `Result`, which can either be a `Ok` (Success) or `Err` (Error/Failure), `Result` is the type union -between the two. Before unwrapping the inner object, you check the type of the `Result` through conventions like -`case Ok(:final ok)` and `isOk()`. Checking allows you to -either resolve any potential issues in the calling function or pass the error up the chain until a function resolves -the issue. This provides predictable control flow to your program, eliminating many potential bugs and countless -hours of debugging. - -## Intro to Usage -### Regular Dart Error handling -```dart -void main() { - try { - print(order("Bob", 1)); - } catch(e) { - print(e); - } -} - -String order(String user, int orderNumber) { - final result = makeFood(orderNumber); - return "Order of $result is complete for $user"; -} - -String makeFood(int orderNumber) { - if (orderNumber == 1) { - return makeHamburger(); - } - return "pasta"; -} - -String makeHamburger() { - // Who catches this?? - // How do we know we won't forget to catch this?? - throw "Hmm something went wrong making the hamburger."; -} -``` -#### What's Wrong with this Solution? -* If we forget to catch in the correct spot, we just introduced a bug or worse - crashed our entire program. -* We may later reuse `makeHamburger`, `makeFood`, or `order`, and forget that it can throw. -* The more we reuse functions -that can throw, the less maintainable and error-prone our program becomes. -* Throwing is also an expensive operation, as it requires stack unwinding. - -### Result Type -Other languages address the issues with throwing exception by preventing them entirely. Most that do use a -`Result` monad (Some Languages call it `Either`). +To better understand the motivation around the `Result` type refer to this [article]. +## Example +By using the `Result` type, there is no web of `try`/`catch` statements maintain and hidden control flow bugs, all control flow is defined. ```dart import 'package:rust_core/result.dart'; void main() { - print(order("Bob", 1)); + final result = processOrder("Bob", 2); + result.match( + ok: (value) => print("Success: $value"), + err: (error) => print("Error: $error"), + ); } -Result order(String user, int orderNumber) { +Result processOrder(String user, int orderNumber) { final result = makeFood(orderNumber); - if(result case Ok(:final ok)) { // Could also use "if(result.isOk())" or a switch statement - return Ok("Order of $ok is complete for $user"); + if(result case Ok(:final ok)) { + final paymentResult = processPayment(user); + if(paymentResult case Ok(ok:final paymentOk)) { + return Ok("Order of $ok is complete for $user. Payment: $paymentOk"); + } + return paymentResult; } return result; } @@ -88,19 +32,31 @@ Result order(String user, int orderNumber) { Result makeFood(int orderNumber) { if (orderNumber == 1) { return makeHamburger(); + } else if (orderNumber == 2) { + return makePizza(); } - return Ok("pasta"); + return Err("Invalid order number."); } Result makeHamburger() { - return Err("Hmm something went wrong making the hamburger."); + return Err("Failed to make the hamburger."); +} + +Result makePizza() { + return Ok("pizza"); +} + +Result processPayment(String user) { + if (user == "Bob") { + return Ok("Payment successful."); + } + return Err("Payment failed."); } ``` -Now with the `Result` type there are no more undefined -behaviours due to control flow! -### Chaining + +## Chaining Effects on a `Result` can be chained in a safe way without -needing a bunch of `if` statements, similar to `Option`. +needing to inspect the type. ```dart Result result = Ok(4); Result finalResult = initialResult @@ -166,7 +122,7 @@ Result earlyReturn() => Result(($) { // Early Return Key expect(earlyReturn().unwrapErr(), "message"); ``` Using the Early Return Key Notation reduces the need for pattern matching or checking, in a safe way. This is quite a -powerful tool. See [here](#pattern-matching-vs-early-return-key) for another example. +powerful tool. See [here](tips_and_best_practices.md#pattern-matching-vs-early-return-key) for another example. For async, use the `Result.async` e.g. ```dart @@ -175,166 +131,4 @@ FutureResult earlyReturn() => Result.async(($) async { }); ``` -## How to Never Unwrap Incorrectly -In Rust, as here, it is possible to unwrap values that should not be unwrapped: -```dart -if (x.isErr()) { - return x.unwrap(); // this will panic (should be "unwrapErr()") -} -``` -There are two ways to never unwrap incorrectly: -### Pattern Matching -Simple do a typecheck with `is` or `case` instead of `isErr()`. -```dart -if (x case Err(:final err)){ - return err; -} -``` -and vice versa -```dart -if (x case Ok(:final ok){ - return ok; -} -``` -The type check does an implicit cast, and we now have access to the immutable error and ok value respectively. - -Similarly, we can mimic Rust's `match` keyword, with Dart's `switch` -```dart -switch(x){ - case Ok(:final ok): - print(ok); - case Err(:final err): - print(err); -} - -final y = switch(x){ - Ok(:final ok) => ok, - Err(:final err) => err, -}; -``` - -Or declaratively with `match` -```dart -x.match( - ok: (ok) => ok, - err: (err) => err -); -``` -Or even with `mapOrElse`. -### Early Return Key Notation -We can also use the [Early Return Key Notation](#early-return-key-notation), which is a very powerful idiomatic approach! -## Misc -### Working with Futures -When working with `Future`s it is easy to make a mistake like this -```dart -Future.delayed(Duration(seconds: 1)); // Future not awaited -``` -Where the future is not awaited. With Result's (Or any wrapped type) it is possible to make this mistake -```dart -await Ok(1).map((n) async => await Future.delayed(Duration(seconds: n))); // Outer "await" has no effect -``` -The outer "await" has no effect since the value's type is `Result>` not `Future>`. -To address this use `toFutureResult()` -```dart -await Ok(1).map((n) async => await Future.delayed(Duration(seconds: n))).toFutureResult(); // Works as expected -``` -To avoid these issues all together in regular Dart and with wrapped types like `Result`, it is recommended to enable -these `Future` linting rules in `analysis_options.yaml` -```yaml -linter: - rules: - unawaited_futures: true # Future results in async function bodies must be awaited or marked unawaited using dart:async - await_only_futures: true # "await" should only be used on Futures - avoid_void_async: true # Avoid async functions that return void. (they should return Future) - #discarded_futures: true # Don’t invoke asynchronous functions in non-async blocks. - -analyzer: - errors: - unawaited_futures: error - await_only_futures: error - avoid_void_async: error - #discarded_futures: error -``` -### ToResult and ToResultEager -In various circumstances you just want a single `Result` for these times, think `toResult()` or in some cases -`toResultEager()`. These extension methods have been added to make life easier. -#### Iterable Result -One of these is on `Iterable>`, which can turn into a -`Result,List>`. Also, there is `.toResultEager()` which can turn into a single `Result,F>`. - -```dart -var result = [Ok(1), Ok(2), Ok(3)].toResult(); -expect(result.unwrap(), [1, 2, 3]); - -result = [Ok(1), Err(2), Ok(3)].toResultEager(); -expect(result.unwrapErr(), 2); -``` -#### Multiple Results of Different Success Types -Sometimes you need to call multiple functions that return `Result`s of different types. You could write something -like this: -```dart -final a, b, c; -final boolResult = boolOk(); -switch(boolResult){ - case Ok(:final ok): - a = ok; - case Err(): - return boolResult; -} -final intResult = intOk(); -switch(intResult){ - case Ok(:final ok): - b = ok; - case Err(): - return intResult; -} -final doubleResult = doubleOk(); -switch(doubleResult){ - case Ok(:final ok): - c = ok; - case Err(): - return doubleResult; -} -/// ... Use a,b,c -``` -That is a little verbose. Fortunately, extensions to the recuse, instead do: -```dart -final a, b, c; -final result = (boolOk, intOk, doubleOk).toResult(); -switch(result){ - case Ok(:final ok): - (a, b, c) = ok; - case Err(): - return result; -} -/// ... Use a,b,c -``` -This also has a `toResult()` method. -### Pattern Matching vs Early Return Key -```dart -void main(){ - usingTheEarlyReturnKey(); - usingRegularPatternMatching(); -} - -Result usingTheEarlyReturnKey() => Result(($){ // Early Return Key - // Will return here with 'Err("error")' - int x = willAlwaysReturnErr()[$].toInt(); - return Ok(x); -}); - -Result usingRegularPatternMatching(){ - int x; - switch(willAlwaysReturnErr()){ - case Err(:final err): - return Err(err); - case Ok(:final ok): - x = ok.toInt(); - } - return Ok(x); -} - -Result willAlwaysReturnErr() => Err("error"); -``` - [docs]:https://pub.dev/documentation/rust_core/latest/result/result-library.html \ No newline at end of file diff --git a/book/src/libs/result/tips_and_best_practices.md b/book/src/libs/result/tips_and_best_practices.md new file mode 100644 index 0000000..5486f14 --- /dev/null +++ b/book/src/libs/result/tips_and_best_practices.md @@ -0,0 +1,162 @@ +# Tips and Best Practices + +## How to Never Unwrap Incorrectly +In Rust, as here, it is possible to unwrap values that should not be unwrapped: +```dart +if (x.isErr()) { + return x.unwrap(); // this will panic (should be "unwrapErr()") +} +``` +There are four ways to never unwrap incorrectly: +### Pattern Matching +Simple do a type check with `is` or `case` instead of `isErr()`. +```dart +if (x case Err(:final err)){ + return err; +} +``` +and vice versa +```dart +if (x case Ok(:final ok){ + return ok; +} +``` +The type check does an implicit cast, and we now have access to the immutable error and ok value respectively. +### Switch +Similarly, we can mimic Rust's `match` keyword, with Dart's `switch` +```dart +switch(x){ + case Ok(:final ok): + print(ok); + case Err(:final err): + print(err); +} + +final y = switch(x){ + Ok(:final ok) => ok, + Err(:final err) => err, +}; +``` +### Declaratively +Or declaratively with `match` or `mapOrElse` +```dart +x.match( + ok: (ok) => ok, + err: (err) => err +); +``` +### Early Return Key Notation +We can also use the [Early Return Key Notation](result.md#early-return-key-notation), which is a very powerful idiomatic approach. + +## Working with Futures +When working with `Future`s it is easy to make a mistake like this +```dart +Future.delayed(Duration(seconds: 1)); // Future not awaited +``` +Where the future is not awaited. With Result's (Or any wrapped type) it is possible to make this mistake +```dart +await Ok(1).map((n) async => await Future.delayed(Duration(seconds: n))); // Outer "await" has no effect +``` +The outer "await" has no effect since the value's type is `Result>` not `Future>`. +To address this use `toFutureResult()` +```dart +await Ok(1).map((n) async => await Future.delayed(Duration(seconds: n))).toFutureResult(); // Works as expected +``` +To avoid these issues all together in regular Dart and with wrapped types like `Result`, it is recommended to enable +these `Future` linting rules in `analysis_options.yaml` +```yaml +linter: + rules: + unawaited_futures: true # Future results in async function bodies must be awaited or marked unawaited using dart:async + await_only_futures: true # "await" should only be used on Futures + avoid_void_async: true # Avoid async functions that return void. (they should return Future) + +analyzer: + errors: + unawaited_futures: error + await_only_futures: error + avoid_void_async: error +``` +## ToResult and ToResultEager +In various circumstances you just want a single `Result` for these times, think `toResult()` or in some cases +`toResultEager()`. These extension methods have been added to make life easier. + +## Iterable Result +One of these is on `Iterable>`, which can turn into a +`Result,List>`. Also, there is `.toResultEager()` which can turn into a single `Result,F>`. + +```dart +var result = [Ok(1), Ok(2), Ok(3)].toResult(); +expect(result.unwrap(), [1, 2, 3]); + +result = [Ok(1), Err(2), Ok(3)].toResultEager(); +expect(result.unwrapErr(), 2); +``` +## Multiple Results of Different Success Types +Sometimes you need to call multiple functions that return `Result`s of different types. You could write something +like this: +```dart +final a, b, c; +final boolResult = boolOk(); +switch(boolResult){ + case Ok(:final ok): + a = ok; + case Err(): + return boolResult; +} +final intResult = intOk(); +switch(intResult){ + case Ok(:final ok): + b = ok; + case Err(): + return intResult; +} +final doubleResult = doubleOk(); +switch(doubleResult){ + case Ok(:final ok): + c = ok; + case Err(): + return doubleResult; +} +/// ... Use a,b,c +``` +That is a little verbose. Fortunately, extensions to the recuse, instead do: +```dart +final a, b, c; +final result = (boolOk, intOk, doubleOk).toResultEager(); +switch(result){ + case Ok(:final ok): + (a, b, c) = ok; + case Err(): + return result; +} +/// ... Use a,b,c +``` +This also has a `toResult()` method. + +## Pattern Matching vs Early Return Key +```dart +void main(){ + usingTheEarlyReturnKey(); + usingRegularPatternMatching(); +} + +Result usingTheEarlyReturnKey() => Result(($){ // Early Return Key + // Will return here with 'Err("error")' + int x = willAlwaysReturnErr()[$].toInt(); + return Ok(x); +}); + +Result usingRegularPatternMatching(){ + int x; + switch(willAlwaysReturnErr()){ + case Err(:final err): + return Err(err); + case Ok(:final ok): + x = ok.toInt(); + } + return Ok(x); +} + +Result willAlwaysReturnErr() => Err("error"); +``` \ No newline at end of file diff --git a/book/src/libs/slice/slice.md b/book/src/libs/slice/slice.md index 1d543b2..a29217e 100644 --- a/book/src/libs/slice/slice.md +++ b/book/src/libs/slice/slice.md @@ -1,9 +1,9 @@ # Slice A `Slice` is a contiguous sequence of elements in a [List]. Slices are a view into a list without allocating and copying to a new list, -thus slices are more efficient than creating a sublist,but they do not own their own data. That means shrinking the original list can cause the slices range to become invalid, which may cause an exception. +thus slices are more efficient than creating a sub-list, but they do not own their own data. That means shrinking the original list can cause the slices range to become invalid, which may cause an exception. -`Slice` also have a lot of efficient methods for inplace mutation within and between slices. e.g. +`Slice` also have a lot of efficient methods for in-place mutation within and between slices. e.g. ```dart var list = [1, 2, 3, 4, 5]; diff --git a/lib/result.dart b/lib/result.dart index c053e8b..fe864a6 100644 --- a/lib/result.dart +++ b/lib/result.dart @@ -4,3 +4,50 @@ export 'src/result/execute_protected.dart'; export 'src/result/record_to_result_extensions.dart'; export 'src/result/result.dart'; export 'src/result/result_extensions.dart'; + +import 'package:rust_core/result.dart'; + +void main() { + final result = processOrder("Bob", 2); + result.match( + ok: (value) => print("Success: $value"), + err: (error) => print("Error: $error"), + ); +} + +Result processOrder(String user, int orderNumber) { + final result = makeFood(orderNumber); + if(result case Ok(:final ok)) { + final paymentResult = processPayment(user); + if(paymentResult case Ok(ok:final paymentOk)) { + return Ok("Order of $ok is complete for $user. Payment: $paymentOk"); + } + return paymentResult; + } + return result; +} + +Result makeFood(int orderNumber) { + if (orderNumber == 1) { + return makeHamburger(); + } else if (orderNumber == 2) { + return makePizza(); + } + return Err("Invalid order number."); +} + +Result makeHamburger() { + return Err("Failed to make the hamburger."); +} + +Result makePizza() { + // Simulate a successful pizza making process + return Ok("pizza"); +} + +Result processPayment(String user) { + if (user == "Bob") { + return Ok("Payment successful."); + } + return Err("Payment failed."); +}