{"id":32286026,"url":"https://github.com/zajrik/option_result","last_synced_at":"2025-10-23T01:55:09.118Z","repository":{"id":173227425,"uuid":"650331013","full_name":"zajrik/option_result","owner":"zajrik","description":"A lightweight Dart library for Rust-like Option/Result types and associated pattern matching","archived":false,"fork":false,"pushed_at":"2024-10-31T20:22:27.000Z","size":451,"stargazers_count":16,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-23T01:54:49.940Z","etag":null,"topics":["dart","library","option","pattern-matching","result"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/option_result","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zajrik.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-06T21:00:28.000Z","updated_at":"2025-10-06T09:19:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"d7417521-bea7-419d-a400-bc5fb1a2cfba","html_url":"https://github.com/zajrik/option_result","commit_stats":null,"previous_names":["zajrik/result_option"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zajrik/option_result","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zajrik%2Foption_result","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zajrik%2Foption_result/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zajrik%2Foption_result/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zajrik%2Foption_result/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zajrik","download_url":"https://codeload.github.com/zajrik/option_result/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zajrik%2Foption_result/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280546411,"owners_count":26348723,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-22T02:00:06.515Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dart","library","option","pattern-matching","result"],"created_at":"2025-10-23T01:55:04.326Z","updated_at":"2025-10-23T01:55:09.102Z","avatar_url":"https://github.com/zajrik.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\nThis README describes the package. If you publish this package to pub.dev,\nthis README's contents appear on the landing page for your package.\n\nFor information about how to write a good package README, see the guide for\n[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).\n\nFor general information about developing packages, see the Dart guide for\n[creating packages](https://dart.dev/guides/libraries/create-library-packages)\nand the Flutter guide for\n[developing packages and plugins](https://flutter.dev/developing-packages).\n--\u003e\n\n## option_result [![Test status](https://github.com/zajrik/option_result/actions/workflows/test.yaml/badge.svg)](https://github.com/zajrik/option_result/actions/workflows/test.yaml) [![codecov](https://codecov.io/gh/zajrik/option_result/branch/main/graph/badge.svg?token=OMC42NL71B)](https://codecov.io/gh/zajrik/option_result)\n\n`option_result` is a lightweight library with the goal of bringing Rust's\n[Option](https://doc.rust-lang.org/stable/std/option/) and\n[Result](https://doc.rust-lang.org/std/result/) types to Dart.\n\nThis library aims to provide as close to a 1:1 experience in Dart as possible to\nRust's implementation of these types, carrying over all of the methods for composing\n`Option` and `Result` values (`and_then()`, `or_else()`, `map()`, etc.) and allowing\nthe use of Dart 3's new exhaustive pattern matching to provide a familiar experience\nwhile working with `Option` and `Result` type values.\n\n## Overview\n\n### Option\n\n`Option` types represent the presence (`Some`) or absence (`None`) of a value.\n\nDart handles this pretty well on its own via `null` and a focus on null-safety built\nin to the compiler and analyzer, but we can do better.\n\nThe advantage of `Option` types over nullable types lies in their composability.\n`Option` type values have many methods that allow composing many `Option`-returning\noperations together and helpers for propagating `None` values in larger operations\nwithout the need for repetitive null-checking.\n\nThis supports writing clean, concise, and most importantly, *safe* code.\n\n```dart\nOption\u003cint\u003e multiplyBy5(int i) =\u003e Some(i * 5);\nOption\u003cint\u003e divideBy2(int i) =\u003e switch (i) {\n  0 =\u003e None(),\n  _ =\u003e Some(i ~/ 2)\n};\n\nOption\u003cint\u003e a = Some(10);\nOption\u003cint\u003e b = Some(0);\nOption\u003cint\u003e c = None();\n\nOption\u003cint\u003e d = a.andThen(divideBy2).andThen(multiplyBy5); // Some(25)\nOption\u003cint\u003e e = b.andThen(divideBy2).andThen(multiplyBy5); // None()\nOption\u003cint\u003e f = c.andThen(divideBy2).andThen(multiplyBy5); // None()\n```\n\nFor safety, operations culminating in an `Option` that make use of other `Option`\nvalues in their logic where the outcome is dependent on those `Option` values can\nbenefit from `None` value propagation via `catchOption()`:\n\n```dart\n// If user or email is None when unwrapped, catchOption will exit early, returning None\nOption\u003cString\u003e getUserEmailLowerCase(int id) =\u003e catchOption(() {\n  Option\u003cUser\u003e user = getUser(id);\n  // Unwrap user easily here by calling it like a function, as an alternative to:\n  // Option\u003cString\u003e email = user.unwrap().email;\n  Option\u003cString\u003e email = user().email;\n\n  return Some(email().toLowerCase());\n});\n\nOption\u003cString\u003e email = getUserEmailLowerCase(12345);\n\nswitch (email) {\n  case Some(value: String value): print('User email: $value');\n  case None(): print('User does not have a valid email');\n}\n```\n\n### Result\n\n`Result` types represent the result of some operation, either success (`Ok`), or\nfailure (`Err`), and both variants can hold data.\n\nThis promotes safe handling of error values without the need for try/catch blocks\nwhile also providing composability like `Option` via methods for composing `Result`-returning\noperations together and helpers for propagating `Err` values within larger operations\nwithout the need for repetitive error catching, checking, and rethrowing.\n\nAgain, like `Option`, this helps promote clean, concise, and safe code.\n\n```dart\nResult\u003cint, String\u003e multiplyBy5(int i) =\u003e Ok(i * 5);\nResult\u003cint, String\u003e divideBy2(int i) =\u003e switch (i) {\n  0 =\u003e Err('divided by 0'),\n  _ =\u003e Ok(i ~/ 2)\n};\n\nResult\u003cint, String\u003e a = Ok(10);\nResult\u003cint, String\u003e b = Ok(0);\nResult\u003cint, String\u003e c = Err('foo');\n\nResult\u003cint, String\u003e d = a.andThen(divideBy2).andThen(multiplyBy5); // Ok(25)\nResult\u003cint, String\u003e e = b.andThen(divideBy2).andThen(multiplyBy5); // Err('divided by 0')\nResult\u003cint, String\u003e f = c.andThen(divideBy2).andThen(multiplyBy5); // Err('foo')\n```\n\nAnd, you guessed it, like `Option`, `Result` types can also benefit from safe propagation\nof their `Err` values using `catchResult()`:\n\n```dart\n// If user or email is Err when unwrapped, catchResult will exit early, returning Err\nResult\u003cString, String\u003e getUserEmailLowerCase(int id) =\u003e catchResult(() {\n  Result\u003cUser, String\u003e user = getUser(id);\n  // Unwrap user easily here by calling it like a function, as an alternative to:\n  // Result\u003cString, String\u003e email = user.unwrap().getEmail();\n  Result\u003cString, String\u003e email = user().getEmail();\n\n  return Ok(email().toLowerCase());\n});\n\nResult\u003cString, String\u003e email = getUserEmailLowerCase(12345);\n\nswitch (email) {\n  case Ok(value: String value): print('User email: $value');\n  case Err(value: String err): print('Error fetching email: $err');\n}\n```\n\nBut `Result` doesn't always have to concern data. A `Result` can be used strictly\nfor error handling, where an `Ok` simply means there was no error and you can safely\ncontinue. In Rust this is typically done by returning the\n[unit](https://doc.rust-lang.org/std/primitive.unit.html) type `()` as `Result\u003c(), E\u003e`\nand the same can be done in Dart with an empty `Record` via `()`.\n\n```dart\nResult\u003c(), String\u003e failableOperation() {\n  if (someReasonToFail) {\n    return Err('Failure');\n  }\n\n  return Ok(());\n}\n\nResult\u003c(), String\u003e err = failableOperation();\n\nif (err case Err(value: String error)) {\n  print(error);\n  return;\n}\n\n// No error, continue...\n```\n\nTo further support this, just like how you can unwrap `Option` and `Result` values\nby calling them like a function, an extension for `Future\u003cOption\u003cT\u003e\u003e` and `Future\u003cResult\u003cT, E\u003e\u003e`\nis provided to allow calling them like a function as well which will transform the\nfuture into a future that unwraps the resulting `Option` or `Result` when completing.\n\n*(This also applies to `FutureOr` values.)*\n\n```dart\n// Here we have two functions that return Result\u003c(), String\u003e, one of which is a Future.\n// We can wrap them in a catchResult block (async in this case) and call them like a function\n// to unwrap them, discarding the unit value if Ok, or propagating the Err value otherwise.\nResult\u003c(), String\u003e err = await catchResultAsync(() async {\n  await failableOperation1()();\n  failableOperation2()();\n\n  return Ok(());\n});\n\nif (err case Err(value: String error)) {\n  print(error);\n  return;\n}\n```\n\n*Note that just like how `unit` has one value in Rust, empty `Record` values in\nDart are optimized to the same runtime constant reference so there is no performance\nor memory overhead when using `()` as a `unit` type.*\n\n## Key differences from Rust\n\n- `Option` and `Result` types provided by this library are immutable. All composition\nmethods either return new instances or the same instance unmodified if applicable, and\nmethods for inserting/replacing values are not provided.\n\u003cbr\u003e\u003cbr\u003e\n  The benefits of immutability speak for themselves, but this also allows compile-time\n`const` `Option` and `Result` values which can help improve application performance.\n\u003cbr\u003e\u003cbr\u003e\n- This library lacks all of the methods Rust's `Option` and `Result` types have\nthat are related to `ref`, `deref`, `mut`, `pin`, `clone`, and `copy` due to not\nbeing applicable to Dart as a higher-level language.\n\u003cbr\u003e\u003cbr\u003e\n- The [Option.filter()](https://doc.rust-lang.org/std/option/enum.Option.html#method.filter)\nmethod has been renamed `where()` to be more Dart-idiomatic.\n\u003cbr\u003e\u003cbr\u003e\n- The `Option` and `Result` methods `mapOr`, `mapOrElse` return `Option\u003cU\u003e` and `Result\u003cU, E\u003e`\nrespectively to aid composition of `Option` and `Result` values. The encapsulated\nvalues of these types should never leave the context of `Option` or `Result` unless\nexplicitly unwrapped via the designated methods (`unwrap()`, `expect()`, etc.).\n\u003cbr\u003e\u003cbr\u003e\n- `None()`/`Err()` propagation is not supported at the language-level in Dart since\nthere's no concept of it so it's not quite as ergonomic as Rust, but is still quite\ncomfy and easily managed via the provided helpers.\n\n## Getting started\n\nAdd the dependency to your `pubspec.yaml` file in your Dart/Flutter project:\n\n```yaml\ndependencies:\n  option_result: ^3.2.0\n```\n\nOr via git:\n\n```yaml\ndependencies:\n  option_result:\n    git: https://github.com/zajrik/option_result.git\n```\n\nThen run `dart pub get` or `flutter pub get` and import the library:\n\n```dart\nimport 'package:option_result/option_result.dart';\n// or import the separate types individually:\nimport 'package:option_result/option.dart';\nimport 'package:option_result/result.dart';\n```\n\n## Basic Usage\n\n```dart\n// Assume getUser() returns some sort of User object\nResult\u003cUser, String\u003e user = await getUser(id: 12345);\n\nif (user case Err(value: String error)) {\n  print('Error retrieving user: $error');\n  return;\n}\n\n// Assume the User object has an email field of type Option\u003cString\u003e\nOption\u003cString\u003e email = user.unwrap().email;\n\nif (email case Some(value: String address)) {\n  print('User email: $address');\n} else {\n  print('User has no email set.');\n}\n\n// Alternative to the above using a switch expression for pattern matching\nprint(switch (email) {\n  Some(value: String address) =\u003e 'User email: $address',\n  None() =\u003e 'User has no email set.'\n});\n\n// Pattern matching with switch is exhaustive for Option and Result, so the compiler\n// will give you warnings/errors to make sure you're providing cases for all potential\n// values for Some()/Ok(), either directly or via a default case, and for None()/Err(),\n// again either directly or via a default case\n```\n\n## Pattern-matching verbosity\n\nAll of the prior examples in this document are using the most verbose syntax for\npattern matching possible, but Dart does provide some sugar to make our lives as\ndevelopers a little easier and this library provides a bit of its own sugar too.\n\nConsider the following if-case:\n\n```dart\nif (result case Err(value: String value)) {}\n```\n\nThis example checks if `result` is `Err` and that its `value` field contains a `String`\ntype value, which it binds to the scoped variable of the same name, `value`.\n\nThis level of verbosity is necessary if you want to rebind the `value` field to\na scoped variable of a different name like so:\n\n```dart\nif (result case Err(value: String foo)) {}\n```\n\nBut if you're comfortable with your scoped variable being named `value` then you can\nmake use of the field-access shorthand that Dart provides:\n\n```dart\nif (result case Err(:String value)) {}\nif (result case Err(:final value)) {}\nif (result case Err(:var value)) {}\n```\n\nThese are all functionally identical but the lack of repetition really cleans things up.\n\nTo clean things up even further, the `Option` and `Result` types have a few shorthand\ngetters you can take advantage of:\n\n```dart\nif (result case Ok(:var v)) {}\nif (result case Ok(:var val)) {}\nif (result case Err(:var e)) {}\nif (result case Err(:var error)) {}\n// Err types also have v, val\n```\n\nThese can also be used for rebinding:\n\n```dart\nif (result case Err(e: var foo)) {}\n```\n\nThe exhaustive list of `value` field shorthand getters for `Option` and `Result`\nis as follows:\n\n- `Option`\n  - `Some`: `v`, `val`\n- `Result`\n  - `Ok`: `v`, `val`\n  - `Err`: `v`, `val`, `e`, `error`\n    - Ideally `err` would be included but is not possible due to the `err` method\n    found on `Result` types\n\n## Potential extension conflicts\n\nThis library provides 4 total class extensions on the following types:\n\n- `Future\u003cOption\u003cT\u003e\u003e`\n- `FutureOr\u003cOption\u003cT\u003e\u003e`\n- `Future\u003cResult\u003cT, E\u003e\u003e`\n- `FutureOr\u003cResult\u003cT, E\u003e\u003e`\n\nThese extensions provide a `call()` method to allow calling these types like functions\nto unwrap the underlying `Option` and `Result` types. In the event that any other\nlibrary provides a `call()` method on `Future`/`FutureOr` types that you need to\nmake use of instead, you can hide the following extensions from this library:\n\n- `OptionFutureUnwrap`\n- `OptionFutureOrUnwrap`\n- `ResultFutureUnwrap`\n- `ResultFutureOrUnwrap`\n\nYou can accomplish this like so:\n\n```dart\n// Whitespace doesn't matter here, just keeping it clean\nimport 'package:option_result/option_result.dart'\n  hide\n    OptionFutureUnwrap,\n    OptionFutureOrUnwrap,\n    ResultFutureUnwrap,\n    ResultFutureOrUnwrap;\n\n// Or if you're only importing one of the types from the package:\nimport 'package:option_result/option.dart'\n  hide\n    OptionFutureUnwrap,\n    OptionFutureOrUnwrap;\n```\n\n## Similar packages\n\nI started writing this library because there are many options (pun-intended) out\nthere that accomplished similar goals but none of them stuck out to me at a glance\nas something that fit my needs. Pretty much all of them provided faux-pattern-matching\nvia higher-order functions which I didn't care for, and I wanted to be able to make\nuse of Dart 3's new exhaustive pattern matching which none of the libraries that\nI could find provided at the time of starting this project.\n\n- [oxidized](https://pub.dev/packages/oxidized) - Provides `Option` and `Result`\ntypes and is smiliarly close to a 1:1 representation of Rust's implementation as\nthis library but with a much cooler name.\n  - Supports Dart 3's exhaustive pattern matching as of v6.0.0. This feature was\n  not available at the time of starting this project and probably would have prevented\n  me from wanting to start it at all had it been 🤣\n\u003cbr\u003e\u003cbr\u003e\n\n- [ruqe](https://pub.dev/packages/ruqe) - Provides `Option` and `Result` types,\nas well as an `Either` type, which is like a `Result` type with extra steps.\n\u003cbr\u003e\u003cbr\u003e\n\n- [either_option](https://pub.dev/packages/either_option) - Provides `Option` and\n`Either` types.\n\u003cbr\u003e\u003cbr\u003e\n\n- [crab](https://pub.dev/packages/crab) - Provides `Option` and `Result` types.\nHas a cool name.\n\u003cbr\u003e\u003cbr\u003e\n\n- [fpdart](https://pub.dev/packages/fpdart) - Functional programming in Dart. Very\nthorougly documented. Provides `Option` and `Either` types and so much more. `sealed`\ntype support is in the works so expect proper pattern matching soon. 😎\n\u003cbr\u003e\u003cbr\u003e\n\n- [dartz](https://pub.dev/packages/dartz) - Another functional programming library.\nPredates `fpdart` but appears to no longer be receiving updates. Provides `Option`,\nand `Either` types as well.\n\u003cbr\u003e\u003cbr\u003e\n  Also has a cool name.\n\n## Final thoughts\n\nI've had a lot of fun writing this library. I haven't had a good project to work on\nin quite some time so even if I'm the only person to ever end up using this, I'm still\ncontent that I took the time to write it and put it out there. It was a nice exercise.\n\nFunctional programming in Dart is not my goal and never really was. I just like `Option`\nand `Result` types for null/error handling. I always find myself thinking about them\nwhenever I try new languages without a similar concept.\n\nWith all of that said, if you're reading this: Thank you for taking the time to explore\nthis library, even if it's not what you need for your projects.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzajrik%2Foption_result","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzajrik%2Foption_result","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzajrik%2Foption_result/lists"}