Hi all,

As briefly mentioned, I think the approach to non-nullable types in the typed 
properties proposal needs more discussion, to make sure we're not repeating 
Tony Hoare's "billion-dollar mistake".

For parameter hints, nullability is naturally optional, particularly in PHP 
where "nullable Foo" is equivalent to a union type "null or Foo". For 
properties, this isn't the case, because every property must have some initial 
state.

The simplest solution, which I think would be a sensible starting point if 
we're not ready for more complex changes, is to say that all typed properties 
must have a default value, and if that default value is null, the type hint 
must naturally be nullable. In other words, make "public Foo $foo" illegal, but 
allow "public ?Foo $foo=null".

The current RFC proposes the next simplest solution, which is to allow 
non-nullable types, and trust the user to initialise them before use. 

My first objection to this is that it creates an entirely new state for object 
properties to be in, which behaves differently from everything else in the 
language. There will then be three or four different ways for an object 
property to be "unset":

- not declared at all; access gives a notice and the implicit value null
- declared with no default, uninitialised, implicitly null
- declared with a default of null, or explicitly assigned as null
- declared with a non-nullable type, never initialised; rather than an implicit 
null, access generates an Error

The bigger problem is that the properties are non-nullable in name only, and 
the declaration can't be trusted. Consider the following class:

class Validity {
    public \DateTimeInterface $validFrom;
    public ?\DateTimeInterface $validTo = null;
}

If I have an instance $v of that class, I would expect to be able to safely 
call $v->validFrom->getTimestamp(), but need to check for nulls before doing 
the same with validTo. Under the current proposal, doing so will risk errors, 
so I will have to check both properties before access anyway. Worse, using 
is_null() or ?: will attempt to retrieve the value, so I actually have to be 
*more* careful, and use isset() or ?? instead. 

Alternatively, I can trust the author of the class, but at that point they 
might as well just use a docblock - the type hint documents their intent, but 
is not enforced.


Swift is often cited as a language which gets nullability right, and a lot of 
attention is given to Options, and the compiler ensuring that the None / null 
case is handled; but at least as important is how it handles initialisation of 
non-nullable properties, using "two-phase initialisation": 
https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

In short, Swift defines a specific point after which all properties must have a 
valid value; before this point, the entire object is invalid for read 
operations, so there is no need for a specific error when accessing 
uninitialised properties.

Swift marks this point by the call to the parent initialiser, ultimately going 
up the chain to the root object. PHP has no root object, and no mandatory 
parent constructor calls, so would need a different way to mark this stage. 
Constructors can already violate the rules the first phase should impose, so we 
can't just use the end of the constructor as the validation point.

One possibility would be to add a new keyword like "initialize" which must be 
added to constructors in the presence of non-nullable properties. Above that 
keyword, use of $this other than to write to its properties would be an error; 
afterwards, the constructor could carry on as normal.


As I say, this is complex, and it may be best to add nullable typed properties 
first, and give more time to try out approaches to initialisation.

Regards,
-- 
Rowan Collins
[IMSoP]

Reply via email to