Skip to content

NNBO rather than NNBD for null-safety #1649

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

Closed
ramsestom opened this issue May 25, 2021 · 1 comment
Closed

NNBO rather than NNBD for null-safety #1649

ramsestom opened this issue May 25, 2021 · 1 comment
Labels
feature Proposed language feature that solves one or more problems

Comments

@ramsestom
Copy link

ramsestom commented May 25, 2021

The approach taken by Dart to address null-safety is currently a NNBD model. I am currious to know why it as been prefered to what I would call a NNBO (non-nullable by option) model and if this has even been considered?
I think that it is always a bad thing for a langage to make some assumptions on behalf of the developper. Here, with NNBD, it assume that any variable is non-nullable unless explicitly declared as so (with the ? type) by the developer. It would have make more sense to do the reverse and let all variables be nullable unless declared as not with the bang operator (!) after the type declaration. That way, devloppers that want to track, before runtime, unexpected null values that may occur in their code would still be able to do it but it would also allow to use any third party libs that did not yet migrate to null-safety smoothly (an error would simply occur at runtime if some unexpected null value appear in the code of this lib until the developpers optionaly migrate it to null-safety (which may not be necessary if they already correctly manually tested for null values everywhere in the code, so no null can be unexpected))
So why not allow Dart to use an NNBO approach for developers that want to (which, I think, most would favor compared to NNBD)?

My proposal would be to have Dart to be able to work with an NNBD or an NNBO approach, depending on some compiler flag
So in your Dart code, you could declare a variable of type T in three different ways:

T myvar;
T? myvar; //<- myvar can be of type T or null
T! myvar; //<- myvar is of type T and nether null 

when compiling with the NNBD option, T myvar would resume to be the same thing as T! myvar (meaning myvar can't be null) whereas with the NNBO option T myvar would be the same as T? myvar
That would allow to write null-safety code that would work with both approches (if the developper knows if a variable is nullable or not, he just have to tag its type with a ? or a !) and would resolve many of the current developer headaches to migrate their code to null-safety with third party libraries that did not migrate yet or part of their code where unexpected null values are already correctly manually addressed (so migrating this code to null-safety is just an unecessary waste of time)

@ramsestom ramsestom added the feature Proposed language feature that solves one or more problems label May 25, 2021
@lrhn
Copy link
Member

lrhn commented May 26, 2021

The main argument for preferring NNBD (types T? nullable and T non-nullable) over nullable-by-default (types T nullable and T! not nullable) is that most variables are going to be non-nullable.
It's the difference between writing ? on one out of twenty variables, or having to write ! on nineteen out of twenty variables. The syntactic noise would be deafening 😁.
That's why we didn't choose the opposite approach for the null-safe types.

We can't just introduce a type which means either T? or T! depending on a flag, and then assume that things compile.
Take int foo(int x) => x;, and either int? Function(int?) f = foo; or int! Function(int!) g = foo;. No matter which flag you pass, one of these won't compile. Or int foo() => null; would compile with one flag and not the other. You'd effectively be splitting the language into two incompatible versions. That was very much not a goal.

The one thing that might make sense is to treat int as unsafely nullable, the pre-null-safety meaning, and let it interact and co-exist with explicitly null safe code (int! and int?) in the same library.
That's almost what we did, we just require you to keep the unsafe code in a different library and at a different language version.

If we allowed null-safe types to be one of three: T! (not-nullable), T? (nullable) or T (unsafely nullable), we would still be generally unsafe.
Forget a ?, and you can get null errors at run-time. That was not a goal. Avoiding unintended run-time errors was a goal.
You can still write unsafe code, you just have to keep using a language version below 2.12, but going forward, it's null safe code. The non-safe code should eventually die out.

What we could do instead, and what other languages have done, is to introduce a new unsafe nullable type:

int! x;

This x can be null, but it can also be read and used without checking it first, as if every use of x which requires it to actually be an int was implicitly x!. Basically it's "dynamically non-null".

That would also give you the three types of NNBO (safely non-nullable, nullable, and unsafely nullable), but the default is still safely non-nullable. You have to write something extra to get unsafe behavior, something which stands out to readers and tools, just as explicitly writing dynamic or late does. See also #1187.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

2 participants