rfc:nullsafe_operator

This is an old revision of the document!


PHP RFC: Nullsafe operator

Introduction

This RFC proposes a new operator nullsafe operator ?-> with full short circuiting.

Proposal

It is fairly common to only want to call a method or fetch a property on the result of an expression if it is not null.

Currently in PHP, checking for null leads to deeper nesting and repetition:

$country =  null;
 
if ($session !== null) {
    $user = $session->user;
 
    if ($user !== null) {
        $address = $user->getAddress();
 
        if ($address !== null) {
            $country = $address->country;
        }
    }
}
 
// do something with $country

With the nullsafe operator ?-> this code could instead be written as:

$country = $session?->user?->getAddress()?->country;
 
// do something with $country

When the left hand side of the operator evaluates to null the execution of the entire chain will stop and evalute to null. When it is not null it will behave exactly like the normal -> operator.

Short circuiting

This RFC proposes full short circuiting. This means when the evaluation of one element in the chain fails the execution of the entire chain is aborted and the entire chain evaluates to null. The following elements are considered part of the chain.

  • Array access ([])
  • Property access (->)
  • Nullsafe property access (?->)
  • Static property access (::)
  • Method call (->)
  • Nullsafe method call (?->)
  • Static method call (::)
  • Assignment (=, +=, ??=, = &, etc.)
  • Post/pre increment (++, --)

The following elements will cause new sub-chains.

  • Right hand side of an assignment
  • Arguments in a function call
  • The expression in [] of an array access

Chains are automatically inferred. Only the closest chain will terminate. The following examples will try to illustrate.

   $foo = $a?->b();
// --------------- chain 1
//        -------- chain 2
// If $a is null chain 2 is aborted, method b() isn't called, null is assigned to $foo
 
   $a?->b($c->d());
// --------------- chain 1
//        -------  chain 2
// If $a is null chain 1 is aborted, method b() isn't called, the expression `$c->d()` is not evaluated
 
   $a->b($c?->d());
// --------------- chain 1
//       --------  chain 2
// If $c is null chain 2 is aborted, method d() isn't called, null is passed to `$a->b()`
 
   $foo?->bar = $a->b();
// -------------------- chain 1
//              ------- chain 2
// If $foo is null chain 1 is aborted, `$a->b()` is not evaluated, the assignment is skipped
 
   $foo?->bar++;
// ------------ chain 1
// If $foo is null, chain 1 is aborted, ++ is skipped

Syntax choice

The syntax has been chosen to indicate the precise place in the code that the short circuiting occurs.

Other languages

Lets look the most popular high-level programming languages (according to the Stack Overflow 2020 survey) and our sister language Hack to see how the nullsafe operator is implemented.

Language Has nullsafe operatorSymbolHas short circuiting
JavaScript ?.
Python
Java
C# ?.
TypeScript ?.
Kotlin ?.
Ruby &.
Swift ?.
Rust
Objective-C ✗*
Dart ?.
Scala ✗†
Hack ?-> ✗‡

* In Object-C accessing properties and calling methods on nil is always ignored
† Possible via DSL
‡ Hack evaluates method arguments even if the callee is null

8/13 languages have a nullsafe operator. 4/8 of those implement the nullsafe operator with short circuiting.

Why short circuiting?

As with most things short circuiting has benefits and drawbacks.

Benefits

1. You can see which methods/properties return null

// Without short circuiting
$foo?->bar()?->baz();
 
// With short circuiting
$foo?->bar()->baz();

In this example $foo might be null but bar() will never return null. Without short circuiting every subsequent method call and property access in the chain will require the nullsafe operator. With short circuiting this isn’t necessary which makes it more obvious which methods/properties might return null.

2. Allows for nullsafe operator in write context

$foo?->bar = 'bar';
var_dump($foo);
 
// Without short circuiting:
// Fatal error: Can't use nullsafe result value in write context
 
// With short circuiting:
// NULL

Without short circuiting the assignment to a nullsafe property would be illegal because it produces an r-value (a value that cannot be assigned to). With short circuiting if a nullsafe operation on the left hand side of the assignment fails the assignment is simply skipped.

3. Mixing with other operators

$baz = $foo?->bar()['baz'];
var_dump($baz);
 
// Without short circuiting:
// Notice: Trying to access array offset on value of type null
// NULL
 
// With short circuiting
// NULL

Since with short circuiting the array access ['baz'] will be completely skipped no notice is emitted. This might be less of a problem once we have a nullsafe array access operator ?[].

Drawbacks

1. More rules

Short circuiting must define which elements belong to the short circuiting chain and which do not. Not all of them might be immediately obvious but they should be intuitive for the most part.

2. Complexity

It’s also very likely that the implementation of the nullsafe operator will be slightly more complicated than the alternative. No short circutiing poses it’s own set of complications though (like checking that ?-> can’t be used in write context).

Backward Incompatible Changes

There are no known backward incompatible changes in this RFC.

Future Scope

Since PHP 7.4 a notice is emitted on array access on null (null["foo"]). Thus the operator ?[] could also be useful ($foo?["foo"]). Unfortunately, this code introduces a parser ambiguity because of the ternary operator and short array syntax ($foo?["foo"]:["bar"]). Because of this complication the ?[] operator is not part of this RFC.

The same also goes for a nullsafe function call syntax ($callableOrNull?()).

Vote

rfc/nullsafe_operator.1591448117.txt.gz · Last modified: 2020/06/06 12:55 by ilijatovilo