1

I am trying to implement the Singleton pattern in Hack. However, I keep running into issues with Nullable.

<?hh //strict

class Foo {
    private static Foo $foo;

    public function __construct() {
        // Do stuff here.
    }        

    public static function theFoo(): Foo {
        if (null === self::$foo) {
            self::$foo = new Foo();
        }

        return self::$foo;
    }
}

$aFoo = Foo::theFoo();

When executed I get the error:

Catchable fatal error: Hack type error: Please assign a value at foo.hh line 4

The type checker returns a similar as well:

foo.hh:4:24,27: Please assign a value (Typing[4055])

How do I assign a default value to a static property?

joshwbrick
  • 5,882
  • 9
  • 48
  • 72

1 Answers1

2

How do I assign a default value to a static property?

If it's an object, like in this case, you can't. With primitives, you can do something like this:

<?hh // strict

class Foo {
  private static int $x = 0;
  // ...
}

However, for objects, you'd need to say private static Foo $x = new Foo() which isn't allowed -- you can't initialize static variables like that, since it has to call the constructor, which involves running code, and PHP doesn't have a notion of initialization order and running code like this (which other languages, notably C++, do). There's no deep technical reason we couldn't have such a notion, it's just not part of the language right now.

The right thing to do is just make the static nullable. You're actually already implicitly treating it as such, when you do the if (null === self::$foo) -- as written, self::$foo actually can't be null since you haven't given it a nullable type, and so that check does nothing. What you probably mean to do is this:

<?hh // strict

class Foo {
  private static ?Foo $foo;
  // ...
}
Josh Watzman
  • 7,060
  • 1
  • 18
  • 26
  • You mentioned: "You're actually already implicitly treating it as such, when you do the `if (null === self::$foo)`" The only reason I have to check if it is null is because I can't initialize it. So is this pattern impossible in Hack? I don't want to mark it as Nullable as it really isn't it should always be set and I don't want null checks all over my codebase when I access that property. – joshwbrick Jun 15 '15 at 19:10
  • 2
    But it's not always set, not until you call `Foo::theFoo`, and so has to be nullable. However, you don't need null checks through all your code -- the return type of `Foo::theFoo` is not nullable, and so callers of that function don't need a null check. `Foo::theFoo` encapsulates the null check and does the right thing if it finds that it is null. So you're fine as long as you don't access `$foo` directly, and the way your code is written, it looks like that was the intent anyways. – Josh Watzman Jun 16 '15 at 06:40
  • If I have the `Foo::$foo` property set as nullable and then I return `self::$foo` from `Foo::theFoo()` as a non-nullable value the type checker gives a `Invalid return type (Typing[4110])` error saying the return value is incompatible with a nullable type. This indicates to me that the return type hint for `Foo::theFoo()` should be nullable and that I will need null checks anywhere I reference it. – joshwbrick Jun 22 '15 at 05:04
  • No, the return type doesn't need to be nullable, Hack is control-flow sensitive so patterns like this one will work. Can you post a link to exactly the code you're hitting this in, and exactly the error you got, so I can see if this is a problem with your code or a bug in the typechecker? – Josh Watzman Jun 22 '15 at 06:09