package

codeberg.org/elliethepuppy/result

1.1.0 / published Jun 23, 2026 / repository

Making error and value handling one and the same. Inspired by Rust's Result<T,E> type comes the same thing: Crystal-flavored!

Result

The Result type, but for Crystal!

Installation

  1. Add the dependency to your shard.yml:
dependencies:
  result:
    codeberg: elliethepuppy/result
    version: 1.1.0
  1. Run shards install

Usage

require "result"
  • NOTE: this will also include the Result(T, E) type, and the ok(value) and err(value) macros directly into your top-level scope. if you wish to avoid this, require like so:
    require "result/result"

A Result type signature looks like Result(T, E), where T is the type of your good value and E is the type of your error value. It is recommended to always use different types if you are not creating your own Ok and Err objects to give to the Result because the constructors can become confused.

Using a given Ok or Err value, created with the corresponding macro:

good_result = ExampleResult.new(ok(2 * 43)) # => Ok(86)
bad_result  = ExampleResult.new(err("huh")) # => Err("huh")

Using a Block to determine a value or error value to initialize (this is the generally best way to do it).

good_or_bad_result = Result(Float64, String).new do
  random = rand(-10..10)
  if random == 0
    "nooooo!"
  else
    4 / random
  end # => Ok(<Some Float64>) or Err("nooooo!")
end

Similar to the above, a Proc can be used (and is in fact how the block variant is implemented) instead of a Block.

four_six_seven = -> { if rand(0..1) % 2 == 0; 467; else; "467" }
good_or_bad_result = ExampleResult.new four_six_seven # => Ok(467) or Err("467")

Check if a value is good or bad with #ok? and #err?.

good_result = ExampleResult.new(ok(87)) # => Ok(87)
good_result.ok?                         # => true
good_result.err?                        # => false

Trying to unwrap a non-Ok value raises a ResultType::ResultError.

good_result = ExampleResult.new(ok(9300)).unwrap # => Ok(9300), Looks great!
bad_result  = ExampleResult.new(err("l")).unwrap # => raises ResultType::ResultError!! Ick!!!

Of course, if you must unwrap the contained value, whether it is or isn't good, use #unwrap!.

suspect_result = ExampleResult.new(err("dangerous!")).unwrap! # => Err("dangerous!"), scary!!

You can also deliberately unwrap an error using #unwrap_err. This has the same rules as regular #unwrap, except for error values.

bad_result = ExampleResult.new(err("expected!")).unwrap_err # => Err("expected!"), not scary!!

In addition to #unwrap and #unwrap_err are #expect(message) and #expect_err(message). These latter two methods function identically to the former two, except the exception message given on failure is the one you provide to the method. You must provide the message.

good_result = ExampleResult.new(err("string"))
              .expect("Should be Int32")      # => raises `ResultType::ResultError` with "Should be Int32".
bad_result  = ExampleResult.new(ok(75))
              .expect_err("Should be String") # => raises `ResultType::ResultError` with "Should be String".

If you want to give a default value instead of checking for Ok-ness, use #unwrap_or(value) (which can return any type, really), or #unwrap_or_else, which takes a Block or Proc that takes the Err value's type and returns a value of the Ok value's type (not an Ok(T), but a value that is of type T).

bad_result = ExampleResult.new(err("237"))
             .unwrap_or "any value can go here." # => "any value can go here"
bad_result = ExampleResult.new(err("jlft"))
             .unwrap_or_else { |e|
               if e.to_i?
                 e.to_i
               else
                 0
               end
             }                                   # => 0
# using a Proc:
to_i_proc = ->(e : String) { 
  if e.to_i?
    e.to_i
  else
    0
  end
}
bad_result = ExampleResult.new(err("245"))
             .unwrap_or_else to_i_proc           # => 245
# using a method as the Proc:
def str_to_i(str : String) : Int32
  if str.to_i?
    str.to_i
  else
    0
  end
end

bad_result = ExampleResult.new(err("two"))
             .unwrap_or_else ->str_to_i(String) # => 0

Development

TODO:

  • Determine if having Ok(T), Err(E), and Result(T, E) being completely separate types is ideal.
  • Write tests for: Comparables, Ok(T), and Err(E).
    • Do Ok(T) and Err(E) really need tests? they're quite simple, and are tested by being so tied to the Result type itself.

Contributing

  1. Clone it (git clone https://codeberg.org/elliethepuppy/result.git)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Create a patch against the main branch (git diff main > feature-name.patch)
  5. Send me an email whose subject begins with [GIT/Result] (Include your feature name as well!). If you don't, I might not get it.
  6. Wait for me to get back to you with questions and updates!

Contributors

API

  • ResultType

    ResultType is a module that seeks to bring the robustness of Rust's Result<T,E> type to Crystal.