Hello, I extracted a little example from my codebase:
Playground here.
abstract class Component
protected getter parent : Component
def initialize(@parent)
end
end
class SomethingReal1 < Component
property title : String
def initialize
# OK no error here - super at the end
@title = "title"
super self
end
end
class SomethingReal2 < Component
property title : String
def initialize
# ERROR in this constructor - super at the begin
# Error: instance variable '@title' of SomethingReal2 was not initialized directly in all of the 'initialize' methods, rendering it nilable. Indirect initialization is not supported.
super self
@title = "title"
end
end
I didn’t understand for about 2 hours why my SomethingReal2 class init wasn’t working - I have to call super
(superclass initializer) as the last call. Ugh…
Why please? I can’t find anything in the doc. Why I need to set first all instvars from the subclass and then call superclass initializer? Is it a bug?
Thanks very much.
Related: Compiler consider instance var nilable when assigning @var = self in constructor · Issue #13714 · crystal-lang/crystal · GitHub. tl;dr no it’s not a bug. It ensures the parent types do not try and access an object that isn’t fully initialized yet.
So what trickery to use here:
Playground
abstract class Component
protected getter parent : Component
def initialize(@parent)
end
end
class SearchField < Component
property search_string = ""
end
class SomethingReal < Component
property title : String
property search_field : SearchField
def initialize
@title = "title"
@search_field = SearchField.new self
super self
end
end
pp SomethingReal.new
when I need to send self
in SomethingReal#initialize
component as parent to the initilalization of @search_field
component?
A possible solution is to make the ivar nilable. That allows you to initialize its value later.
Together with a non-nilable getter (e.g. property!
), using it should be simple and safe.
abstract class Component
protected getter parent : Component
def initialize(@parent)
end
end
class SearchField < Component
property search_string = ""
end
class SomethingReal < Component
property title : String
property! search_field : SearchField
def initialize
@title = "title"
super self
@search_field = SearchField.new self
end
end
pp SomethingReal.new