Self/super in initialize constructor - does the order of the call matter? Why?

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
1 Like

make the ivar nilable. That allows you to initialize its value later.
Together with a non-nilable getter

Oops, I understand what “property!” really applies to until today.

Yeah, but it’s “raise-on-nil” getter - you get the error “later” in the runtime (if you forget to create it in the constructor) - which may be too late for someone’s taste.