What is the closest scheme Crystal offers which is similar to using Resut / Options in Rust ?
Union types. This is an extract of a pending blog I have on the matter. The example below is about having a function that parses a string and returns an AST or an exception. That is, it doesn’t raise
the exception, it returns it.
With union types it’s easy to provide information about the issues found in the input provided to our parsing function:
class ParseException < Exception
getter issue : String
getter line : Int32
getter col : Int32
def initialize(@issue, @line, @col)
end
end
alias ParseResult = Ast | ParseException
def parse(input : String) : ParseResult
if input == "hello" # just a silly example
Hello.new # Hello is an Ast
else
ParseException.new "Should start saluting with 'hello'", 0, 0
end
end
Now, when using parse
, we must consider the exception. This is easy to do with a case statement:
case result = parse "this is an example"
in Ast
puts "yay!"
in ParseException
STDERR.puts "#{result.issue} at #{result.line}:#{result.col}"
end
Give me the gun, I want to shoot at my foot
Having to case
at each returned object might sound like a bit too much. No need to suffer: we can mimic the same idea as with Nil
and extend the Object
class with a pure!
method to assume an object is the pure value and not an exception:
class Object
def pure! : self
self
end
end
class Exception
def pure! : NoReturn
raise self
end
end
With this extension we can now assume a call to parse
won’t fail (or the exception will explode in our face!):
parse("hello").pure!.to_s + " pure world"
EDIT: An Option
is nothing but a nilable type, as in MyType?
. Object#not_nil!
plays the role of pure!
above.
This is the thing that catch my eye when starting to know Crystal. The ability to embed so nicely such types is
Here it is done for exceptions, but of course you can do a similar thing for your own hierarchy.
On the other hand, a language which uses "try / catch"constructs expects you to write the code which throws (not merely returns) exceptions.
I wouldn’t say expects. It allows raising exceptions, but doesn’t demand it.
Libraries can use either way of error reporting.
The stdlib largely raises, but there are also many methods that don’t and just return nil
on an error condition. That’s a very crude error reporting as there is no actual description of the error, but in these cases it’s decently clear from the methods’ function what went wrong.
If there is a way to ignore errors, the danger is always that a piece of code will fail silently at some point in the future, isn’t it?
Not sure what you’re referring to. What do you mean with ignore errors?
Say, you want to open a file, for instance and, say, in case the operation fails the result is just an integer returned, which is supposed to be non-null in case of success and conversely.
No mechanism forces you to check for that integer, you can ignore it, if you wish, as you can in C or in Go.
Say, these lines of codes are in a library.
Now, say, another person is using that library. That person is not aware of your deliberate decision to ignore the return code. If the expected file is not found, then the app will crash without pointing clearly to the cause of the error, I suppose.
Right, you can use any tool for wrong-doing, no doubt of that. I think raising an exception or returning it are two alternatives for different cases. You can even combine them: raise when something really went wrong, and return it when you can expect it (like when parsing user input).
Sounds reasonable.
Hi,
To manage the Exception a bit like Rust / Monad, I did this GitHub - Nicolab/crystal-result: Rust-like error handling for Crystal (`Ok` / `Err`)
Thank you.
It sounds interesting. I’ll have a closer look.