Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Language: Add null-conditional operators and null-propagation in Dart #21695

Closed
DartBot opened this issue Nov 22, 2014 · 10 comments
Closed

Language: Add null-conditional operators and null-propagation in Dart #21695

DartBot opened this issue Nov 22, 2014 · 10 comments
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-duplicate Closed in favor of an existing report type-enhancement A request for a change that isn't a bug

Comments

@DartBot
Copy link

DartBot commented Nov 22, 2014

This issue was originally filed by @Emasoft


It would be very useful to have Null-Conditional Operators and Null-Propagation in Dart.

The null-conditional has two syntax forms.

First, is available as a member operator, adding the question mark prior to the dot operator ( ?. ). When this is present if the value of the object is null, the null-conditional operator will return null. For example:

return value?.substring(0, length);

Second, is to use the question mark in combination with the index operator ( ?[…] ), causing indexing into collection only to occur if collection isn’t null. For example:

T? item = collection?[index];

Null-Propagation is a way to avoid null exceptions avoiding all additional invocations in the call chain if the operand is null. For example:

return value?.substring(0, length).padRight(3);

If .substring() is called via the null-conditional operator, and the null value?.substring() could seemingly return null, Null-Propagation will short-circuits the call to padRight(), and immediately returns null, avoiding the programming error that would otherwise result in a null reference exception.

See for example the C# implementation:
http://msdn.microsoft.com/en-us/magazine/dn802602.aspx

@lrhn
Copy link
Member

lrhn commented Nov 22, 2014

Added Area-Language, Triaged labels.

@DartBot
Copy link
Author

DartBot commented Nov 22, 2014

This comment was originally written by @zoechi


Seems similar to http://dartbug.com/41
also related to http://dartbug.com/1236

@lrhn
Copy link
Member

lrhn commented Jan 2, 2015

I'm a little worried about the "null propagation" described here. In Dart, null is an object, and it has methods, so this null propagation would prevent calling those methods.

Example:
  var string = foo?.convert().toString();
With just a single null-coercion, this would be equivalent to:
  var tmp;
  var string = (((tmp = foo) != null) ? tmp.convert() : null).toString();
which is always assigning a string to "string".
With the proposed "null propagation", it would instead assign null if "foo" is null.

It has non-local effect - changing one call affects other calls. That makes some otherwise safe rewrites no longer preserve semantics:
  var tmp = foo?.convert();
  var string = tmp.toString(); // always assigns a string.
== inline tmp ==>
  var string = foo?.convert().toString(); // sometimes assigns null.

I'd rather not have a case where
  foo?.convert().toString()
is not the same as
  (foo?.convert()).toString()
so that's not a solution.

@DartBot
Copy link
Author

DartBot commented Jan 2, 2015

This comment was originally written by @Emasoft


lrn: you are missing the point. Having a situation where foo.convert().toString() evaluate differently than (foo.convert()).toString() is just what we want to avoid, because it creates confusion and impredictability.
A null should be a null, in no case it should become a string. Otherwise it would be impossible to make a consistent null check. If I need to check if a certain return value is null, to execute the proper code for this case, I need to be certain that the value is null, no matter what methods I've tried to call from it. In other words I need to be consistent: garbage in, garbage out. Putting in a null and get back sometimes a null and some other times a string forces the programmer to add a null check manually at each step and before each method call, creating unnecessary complexity and consequently more bugs prone code. Having an entire line of code to evaluate to null if even one object is null is a clean and efficient way to handle nulls, because you have to make only one null check on the entire cascading line of code to decide what to do next. The null propagation is a truly brilliant solution to the very difficult and troublesome problem of handling null values. A small change in the classic syntax rules is a very acceptable trade off to have it finally solved.

@lrhn
Copy link
Member

lrhn commented Jan 2, 2015

The difference between C# and Dart is that in C#, a null is not an object, but in Dart, it is.
That means that it is not always an error to call a method on null. That's one of the reasons null can be added to HashMap without special casing: It has a hashCode getter and an operator== method.

In C#, if you do foo.bar().baz(), and foo is null, then bar always fails.
If you do foo?.bar().baz(), and ?.bar() just propagated the null, then you know that .baz() will always fail anyway, so it makes sense to allow you to omit the second ?. in foo?.bar()?.baz().
Even if it means that the meaning of .baz() depends on some marker elsewhere in the syntax, which is generally not a good idea.

In Dart you don't know that the .baz() call throws. Someone writing foo?.bar().toString() might want to always return a string. He knows that toString exists on all objects, including null.

I'm also not satisfied with using expressions as delimiters for null propagation.
I don't want there to be a difference between:
   var tmp = foo?.bar();
   tmp.baz();
and
   for?.bar().baz()
but with the C#-like behavior suggested here, the former throws and the latter evaluates to null.

I'd rather have some sort of explicit delimiter, like:
  strictnull { stmts; }
  strictnull expression
so you could write:
  return strictnull foo.bar().baz();
or
  strictnull {
    return foo.bar().baz();
  }
(syntax is obviously not optimal :)

I know it gives flashbacks to Java's strictfp, and deliberately so. It's just a horrible idea to have two different meanings of the same syntax depending on a non-local marker. Let's not do that.

@gbracha
Copy link
Contributor

gbracha commented Jan 2, 2015

Obviously, we aren't about to do the null propagation thing described here. The semantics of ?., if we adopt it (which seems increasingly likely) will be clean and local.


Set owner to @gbracha.
Added Accepted label.

@gbracha
Copy link
Contributor

gbracha commented Jan 2, 2015

Added Duplicate label.
Marked as being merged into #41.

@DartBot
Copy link
Author

DartBot commented Jan 3, 2015

This comment was originally written by @Emasoft


@gbracha: Obviously? Sorry but a "clean and local" implementation of the null conditional dot operator ?. is redundant, because without null propagation it requires more complex code and more calls that impact performances.

Let me explain this with an example.
If you do not have a null conditional dot operator, you are force to do null checks manually, like this:

if( data != null
    && data.Address != null
    && data.Address.lenght > 0
    && data.Address[0] != null
    && data.Address[0].State(“Eurasia”) != null
    && data.Address[0].State(“Eurasia”).City(“Carthage”) != null
    && data.Address[0].State(“Eurasia”).City(“Carthage”).Street(“Elm”, “13a”) != null )
  {
     location = data.Address[0].State(“Eurasia”).City(“Carthage”).Street(“Elm”, “13a”).toGpsCoordinates();
  }

If you have just the null conditional operator, you can rewrite the code:

var location = data.Address[0]
    .State(“Eurasia”)
    .City( “Carthage”)
    .Street(“Elm”, “13a”)
    .toGpsCoordinates();

as this:

var location = data?.Address[0]
    ?.State(“Eurasia”)
    ?.City(“Carthage”)
    ?.Street(“Elm”, “13a”)
    ?.toGpsCoordinates();

With the null propagation you would have the option to explicitly check only a single call, for example:

var location = data.Address[0]
    ?.State(“Eurasia”)
    .City(“Carthage”)
    .Street(“Elm”, “13a”)
    .toGpsCoordinates();

If that call is null because the list is empty, all subsequent calls on that objects are ignored. Without null propagation you have to explicitly add the null conditional operator to all method calls, slowing down the execution time.

@DartBot DartBot added Type-Enhancement area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-duplicate Closed in favor of an existing report labels Jan 3, 2015
@kevmoo kevmoo added type-enhancement A request for a change that isn't a bug and removed priority-unassigned labels Mar 1, 2016
@cubuspl42
Copy link

Can I achieve null-propagation with functions (not methods)? Let's say I have a value a of type Foo and a helper function Bar f(Foo a). I'd like to get f(a) only when a != null and null otherwise. Like a?.let(f) in Kotin.

@eernstg
Copy link
Member

eernstg commented Apr 5, 2019

The ability to "call off" a function invocation based on some arguments being null has been raised before. One thing to consider here is readability: dart-lang/language#219 (comment).

Kotlin let moves the ? up front, which eliminates this particular readability problem, and we may be able to do something similar with the extension method features which are under consideration (dart-lang/language#41, dart-lang/language#42, dart-lang/language#177).

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-duplicate Closed in favor of an existing report type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

6 participants