Hello,
I’m implementing an abstract HTTP rescuer handler:
require "http/server"
abstract class Rescuer(T)
include HTTP::Handler
def call(context)
call_next(context)
rescue error : T
handle(error)
end
# Bug: https://github.com/crystal-lang/crystal/issues/6762
def handle(error : T)
{% raise "Must be implemented" %}
end
end
class BasicRescuer(T) < Rescuer(T)
def handle(error : T)
puts error.inspect_with_backtrace
end
end
server = HTTP::Server.new([BasicRescuer(Exception).new]) do |context|
context.response.print("Hello\n")
end
server.bind_tcp(5000)
puts "Listening"
server.listen
Unfortunately, I’m getting this compile-time error:
in rescuer.cr:8: T is not a subclass of Exception
rescue error : T
What am I doing wrong?
It’s a bug, in the generic subclasses of Rescuer(T)
there’s the non-instantiated BasicRescuer(T)
and the T
comes from there.
Generic inheritance doesn’t work well right now so my advice is to try to avoid it as much as possible.
1 Like
It actually seems to be a problem with generic types including a module. I’ll investigate but I can’t promise I’ll fix it.
1 Like
@vladfaust Please open a bug report.
1 Like
My 2cents: Using include + Module works however:
require "http/server"
module Rescuer(T)
include HTTP::Handler
def call(context)
call_next(context)
rescue error : T
handle(error)
end
abstract def handle(error : T)
end
class BasicRescuer(T)
include Rescuer(T)
def handle(error : T)
puts error.inspect_with_backtrace
end
end
server = HTTP::Server.new([BasicRescuer(Exception).new]) do |context|
context.response.print("Hello\n")
end
Anyway, I think it’s a good practice to avoid inheritance like pest. More I develop, and less I think inheritance is useful, when you have great composition tools instead (include
& extend
)
1 Like
I’d be happy to get rid of inheritance, but the language has some bugs which requires it in some cases =(
For example, see Type#all_includers and Array co-something inconsistency…
That’s true exotic construction may sometime trigger error in the compiler.
However on your two examples, no offense but I think you try to bend Crystal where you should not.
Crystal is not a language offering reflection at runtime like Java or Ruby. And it would / should not anyway. Storing classes objects for example sounds like an anti-pattern for me. Because basically in Crystal you won’t do anything with it. You cannot call new
over a referenced class object for example. That’s why Array(MyAbstractClass.class)
is a non-sense, and you cannot either store Class
object in variable in Crystal while it this can be useful in Java for example.
If you need to instantiate dynamically some objects using sort-of reflection, you must implement the factory pattern or use compile time linking with name matching to create and manage your link between the code and the Classes.
I would be happy to give you some help, as I faced this kind of design maze in Clear, as there’s some kind of reflection-ish things to deal with
1 Like