Module ivar collision and potential solutions

Been thinking about module based object construction a bit and was wondering what the Crystal community’s take was for modules being able to overwrite ivars in the types that inherit them, and how people get around dealing with the possibility of this happening. I know that this can technically happen with methods too, and I think that’s a whole other can of worms to look at but, ivars aren’t really documented by crystal docs so the only way you know you might be colliding with one is by reading the code, various macro techniques, or accidentally colliding.

Basic Example:

module A
  @myvar = 100 # Error: instance variable '@myvar' of C must be Int32, not (Float64 | Int32)
end 

module B
  @myvar = 100.0
end 

class C
  include A
  include B
  @myvar = "One hundred"
end

puts C.new.@myvar

Full error with trace:

Error target sandbox2 failed to compile:
In src\sandbox2.cr:2:3

 2 | @myvar = 100 # Error: instance variable '@myvar' of C must be Int32, not (Float64 | Int32)
     ^-----
Error: instance variable '@myvar' of C must be Int32, not (Float64 | Int32)

Float64 trace:

  src\sandbox2.cr:6

      @myvar = 100.0

The error is also a bit cryptic not telling the user which module this error is coming from, just the line the instance variable is defined, the expected type, and the including type, just not the included type.

There are some obvious workarounds to this but nothing 100% concise or concrete:

module A
  # Fresh variables but we have to wrap it in a macro def
  macro make
    @%my_var = 100000
    def my_var
      @%my_var
    end
  end

  make # macro call

  # All of these could still be overwritten by accident, just more and more unlikely
  @name_this_variable_very_esoterically= 100
  @_even_more_weird = 1000
  @_____________________yeah_we_can_do_this = 10000
  @_UUID_helpful_name = 1000000
end

What’s everyone’s take?

I don’t think this is a huge problem in practice. And I don’t think there’s any obvious solution to somehow fix this situation. It’s an implicit property of the module system. We can search for ways to improve dealing with it, though.

Modules that define instance variables and are inherited outside their own code base are relatively rare. And then the instance variables they define are typically closely bounded to their purpose making it less likely that an including type would use the same names. So there are not that many chances for collisions.
The other can of worms, collisions on method names are probably more common.

It probably makes sense to clearly document the instance variables in cases where a module is intended to be used for public inheritance.
This can just be in the module docs.

1 Like