Why can't redefine #nil? method on a user defined Object?

Following is a real use case.

I create a tiny shard(hashr) for makes test on JSON response easier, following is my spec.

Following is a more simple example:

h = {"nilValue" => nil}.to_json
value = Hashr.new({"foo" => JSON.parse(h)})

value.foo.nilValue.should eq nil # => true
value.foo.nilValue.should be_nil    # => false, because i can't redefine Hashr#nil? method

I expect can test with nil value use be_nil instead of eq nil, but, it not work, because Crystal prevent me define #nil? on Hashr object.

In src/hashr.cr:21:7

 21 | def nil?
          ^
Error: 'nil?' is a pseudo-method and can't be redefined

How to workaround on this?

Thank you.

There is no workaround. nil is a special value in Crystal. The compiler can’t allow any other value to pose as nil with returning true for #nil?. Else the type system would break.

So you can’t have your return value fulfill a be_nil expectation. be_nil is essentially a type check like be_a(Nil) which an object of type Hashr can’t satisfy.

The same applies to JSON::Any wrapping a nil value, btw.

1 Like

To clarify a bit.

Let’s say you have this code:

x = rand < 0.5 ? 1 : nil
# Here x is of type (Int32 | Nil)

if x.nil?
  # Here x is guaranteed to be Nil
else
  puts x + 2 # This works because the compiler knows this can't be Nil
end

If you were allowed to redefine nil? and return true for something that’s actually not exactly Nil, the above guarantees don’t hold anymore.

4 Likes