I have been considering how a routing library like Roda might work in Crystal. One oddity of the library is that it uses
throw to return from a request handler block (here’s an example). If you aren’t familiar with Ruby’s throw/catch opposed to the normal raise/rescue, here’s a stackoverflow question that might be helpful: What is the difference between Raising Exceptions vs Throwing Exceptions in Ruby? - Stack Overflow
Does anyone know if there’s something roughly equivalent in Crystal?
The api is something like:
class MyApp < Roda
route do |r|
# throw is called after these blocks are run
# so the request doesn't run more code
# than it needs to
r.get("/users/:id") do |id|
The closest would be to use unions and return a given type. However, you have to handle it for each method call if they are nested.
For certain cases, passing a Proc could do it too.
Can you use exceptions instead? It seems
catch is almost the same as exceptions except it doesn’t involve exceptions?
Yea, they are roughly equivalent in their usage but, in Ruby, they don’t deal with stacktraces and other error-related things which makes them much lighter. I will most likely go forward with raising errors for now.
I’m a bit confused by your response. Maybe we are both misunderstanding? In a typical routing library (kemal for example) when you declare a route it builds a list of routes and their connected block of code to run at compile time or at least before it handles a request. This is meant to all run when a request is given, which makes it different, hence the need for a
I’m not sure I follow. If Roda knows of each route and the related block to handle a given request, what extra code would run? Or are you saying that without the
throw Roda would check other defined routes, i.e.
/users/:id even if the path was
/users and matched the first route block?
I guess throw/catch could be implemented similarly to exceptions in Crystal, maybe, but without unwinding the stack. I’m not sure… But I do see its use case
As @Blacksmoke16 said, not sure to follow. Usually routers store the routes as Procs (in lists, and/or hashes, or other data structures). When a request is made, the corresponding Proc matching the path is executed - that’s it.
Maybe Exceptions can be used similarly, but it is not a good idea to use them as a regular basis. Exceptions, as their name says, should occur exceptionally (both for performance reasons and code clarity).
Crystal could take some inspiration from Swift, where errors don’t involve stack unwinding.
Both cuba and roda execute blocks. If they don’t halt the execution, blocks defined later on are executed.
I wouldn’t suggest using return values, that’s basically suggesting using an enitrely different paradigm.
Ok, maybe that’s common in the Ruby world? In the Crystal ecosystem, all the routers I’ve seen work as I described above: find an object corresponding to a route path. A Proc (or more) can be stored inside, and possibly other things.
Here the answer can be there are other ways to design differently the router to achieve the same goal.
@matthewmcgarvey I suggest you to see how other routing shards are implemented Routing on Shardbox
@j8r I don’t think it matters what other routers in Crystal do. He wants to do it the way Cuba and Roda do it and I think that’s perfectly valid.
It is possible to have the same high-level API, but with an other internal implementation.
throw is not present in Crystal, so it must be done differently. I believe seeing how other routers have achieved it can help.
@j8r yes, I get that it’s different than the other routers. That and a couple other reasons are why I’m interested in it. I apologize for giving a bad example. I dont even think what I wrote is valid roda so that definitely caused some confusion.
I think it’s pretty firmly hammered into most of us that using gotos is bad, so I’d love to know about more scenarios where this throw/catch dynamic could be useful.
I guess we could almost implement this entirely in Crystal. For example:
class ThrowError(T) < Exception
getter value : T
def initialize(@value : T)
def catch(value : T) forall T
rescue error : ThrowError(T)
if error.value == value
# This is similar to `raise(Exception)` except that it doesn't compute a callstack.
def raise(exception : ThrowError(T)) : NoReturn forall T
unwind_ex = Pointer(LibUnwind::Exception).malloc
unwind_ex.value.exception_class = LibC::SizeT.zero
unwind_ex.value.exception_cleanup = LibC::SizeT.zero
unwind_ex.value.exception_object = exception.as(Void*)
unwind_ex.value.exception_type_id = exception.crystal_type_id
Benchmark.ips do |x|
raise "OH NO!"
raise/rescue 680.32k ( 1.47µs) (± 2.33%) 256B/op 1.66× slower
throw/catch 1.13M (887.09ns) (± 3.23%) 128B/op fastest
The only thing else we’d need to do is to avoid rescuing
ThrowError when you do
rescue e or
rescue e : Exception. Maybe try to allocate even less memory, but I’m not sure it’s possible.
But then, I’m not sure this is all worth it. If it’s only going to be twice as fast, maybe it doesn’t make much difference.
An alternative solution would be to avoid setting
callstack on a throw exception. If
CallStack hand an initializer that doesn’t unwind and just creates an empty stack, we could just assign that value to the special exception type’s
callstack property and the existing
::raise would work.
Having a root exception type that is not rescued by default would also help Gracefully stopping fibers · Issue #3561 · crystal-lang/crystal · GitHub, so I’d really like to see that.
Then it’s really trivial to implement throw/catch in user code without having to touch any runtime details. We could consider adding it to stdlib or it can easily be implemented externally.
I’ve also thought about throw/catch in Crystal for this exact same reason — I’ve preferred Roda for small Ruby APIs for a long time. The result of that was this mixin (gist includes an example of usage). I’m currently using it in 2 production apps and it works pretty well.
It doesn’t unwind the stack at all, instead it just doesn’t enter any other blocks if the request has been handled by a “terminal” block (basically, anything other than
r.on flips that bit on the request).
One thing that led to this implementation, though, was realizing that
throw is not actually that impactful for this use case in Crystal. Roda uses
throw to throw a value back up the stack (not simply to avoid calling other route matchers) because of Rack’s interface being based on the return value of
call(env), but Crystal’s
HTTP::Server::Context gives you the request and response to work with as
IO objects. So you can, for example, serialize DB records directly to JSON without having multiple representations in memory all at the same time (all the DB records, the intermediate hashes/arrays, and the JSON string output) as you would need to do with a Rack-based framework.
For rendering HTML content within a layout, I typically end up doing stuff like this (
render calls are this macro to render an ECR template to the response):
route context do |r, response|
# do routing in between to render content
I’m working on it here GitHub - matthewmcgarvey/croda: (Experimental) Crystal web routing based on Ruby's Roda library
In my latest commit Giving up on Roda compatibility and adding support for path variables · matthewmcgarvey/croda@e4751e3 · GitHub I’m giving up on trying to exactly copy Roda’s API. If I constrain all those basic methods (
r.get, etc.) to only take one argument I can actually get it working similarly
class App < Croda
route do |r|
r.on "posts" do
r.get Int32 do |post_id|
# GET /posts/:post_id