16

I spent considerable time debugging a script recently, and when I finally found the problem it was because of code that looked like this:

class Foo {
    has $.bar;
    method () {
        # do stuff
        $!.bar;
    }
}

It turned out the problem was with that $!.bar, which should have been either $!bar or $.bar. I get this.

But why doesn't this die?

Looking at this in more detail, it looks like the issue here is that I'm trying to call a (non-existent) method bar on $!, which at this point is Nil because there haven't been any errors.

And it looks like I can actually call any method I want on Nil and they all silently return Nil, including stuff like Nil.this-is-a-fake-method and Nil.reverse-entropy(123).

Is this a feature? If so, what's the rationale?

jja
  • 2,058
  • 14
  • 27

2 Answers2

13

It's intended and documented, yes. The headline for Nil is "Absence of a value or a benign failure", and the class documentation mentions

Any method call on Nil of a method that does not exist, and consequently, any subscripting operation, will succeed and return Nil.

say Nil.ITotallyJustMadeThisUp;  # OUTPUT: «Nil␤» 
say (Nil)[100];                  # OUTPUT: «Nil␤» 
say (Nil){100};                  # OUTPUT: «Nil␤»

Synopsis 2 states "Any undefined method call on Nil returns Nil, so that Nil propagates down method call chains. Likewise any subscripting operation on Nil returns Nil", so the intent seems to be allowing expressions like $foo.Bar()[0].Baz() without requiring checks for Nil at every step, or special "Nil-safe" method call and subscripting operators.

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • 5
    Indeed. And quite a long time ago already as well: https://github.com/rakudo/rakudo/commit/174727377f (December 2013) See also the associated speculation: https://design.raku.org/S02.html#Nil – Elizabeth Mattijsen Feb 10 '20 at 17:31
  • 1
    @ElizabethMattijsen ah, I think S02 has the answer. "Any undefined method call on `Nil` returns `Nil`, so that `Nil` propagates down method call chains." So it's intended that you can do `$foo.Bar().Baz().Blah()` without needing nil tests at every step or a special `?.` sort of operator (as long as you correctly handle nil at the end). I'll edit that in, thank you. – hobbs Feb 10 '20 at 18:39
  • 1
    A `Nil` that doesn't error and just throws back more `Nil` gets fairly extensive use in Obj-C and the NS Frameworks where it's used in a similar way — allows for chained calls that keep passing on Nil. I don't know the exact performance penalties of exceptions and catching them, but my guess is that the `Nil` chaining can be more efficient, if less flexible. – user0721090601 Feb 10 '20 at 19:12
  • 1
    (but that's an entirely uninformed guess, so I'm happy for lizmat or jnthn to tell me I'm wrong and that I need to shut up :-) ) – user0721090601 Feb 10 '20 at 19:13
  • 2
    The `Nil` class has a `FALLBACK` https://docs.raku.org/language/typesystem#index-entry-FALLBACK_(method) method, which returns `Nil`. Basically, just before an exception is thrown because a method could not be found, a check for `FALLBACK` is done, and called if available. – Elizabeth Mattijsen Feb 11 '20 at 12:19
6

This question (and hobbs' answer) also made me feel... uneasy: I eventually found https://docs.raku.org/language/traps: it explains that assigning Nil produces a different value, usually Any. The following basic REPL interaction also demonstrates this:

> my $foo = Nil
(Any)

> $foo.bar
No such method 'bar' for invocant of type 'Any'
  in block <unit> at <unknown file> line 1

> my $bar := Nil
Nil

> $bar.baz
Nil

(( the difference between = and := is covered here: https://docs.raku.org/language/containers#Binding ))

...so the general idea is that the 'absent value or benign failure' is far less widespread in regular code than I (and perhaps you as well?) suddenly feared: it does occur however, when you are going to work with regex matches directly and in your particular accidental situation related to directly invoking methods on $!.

This made me more aware about the difference between Nil and Any, and the provided rationale made more sense to me.

jjmerelo
  • 22,578
  • 8
  • 40
  • 86
chromis
  • 143
  • 4
  • 3
    `Nil` sets a container to its default state. You can change the default value. `my $foo is default(42) = 5;` `$foo = Nil;` `say $foo; # 42` – Brad Gilbert Feb 26 '20 at 17:29