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

Propose new name for "Null assertion operator" #5252

Open
Tracked by #5247
MaryaBelanger opened this issue Oct 16, 2023 · 25 comments
Open
Tracked by #5247

Propose new name for "Null assertion operator" #5252

MaryaBelanger opened this issue Oct 16, 2023 · 25 comments
Assignees
Labels
a.language Relates to the Dart language tour e2-days Can complete in < 5 days of normal, not dedicated, work p2-medium Necessary but not urgent concern. Resolve when possible. st.triage.ltw Indicates Lead Tech Writer has triaged

Comments

@MaryaBelanger
Copy link
Contributor

Background

@dart-lang/language-team @bwilkerson I would like to settle on a term for "Null assertion operator"/! and then update all of our resources (docs, lints, diagnostics, in the SDK, etc) to use that term exclusively as part of the effort to make Dart's terminology more consistent (#5247).

There's been a lot of discussion going on (dart-lang/linter#3008 , dart-lang/sdk#47185) but it doesn't feel conclusive enough yet to make a decision. Summarizing the major points:

  • null assertion operator (currently standard on dart.dev)
    • Inaccurate: implies the desired outcome is null, when in reality it does the opposite.
  • non-null assertion operator
    • Better, but still possibly misleading: "assert", while its literal definition is appropriate, is already dedicated in Dart and therefore inaccurate.
  • null check operator / non-null check operator
    • Agreeable and ubiquitous, but also vague and inaccurate: "null check" is too general and currently used in the docs to encompass a lot of different ways to check null (... != null, as, etc.).
  • Other options like non-null enforcing, non-null cast, null failure, throw if null, unwrap (Swift), etc are floating around too.

(@parlough's longer summary of options)

Proposal

Considering the pros/cons people seem to have with the ideas discussed so far, I'm wondering...

What about "Null Exception Operator"?

Purely from an effective terminology perspective, to me null exception operator tells users exactly what they need to know by focusing on the end result that actually affects them (the exception thrown), while bypassing the contention around how exactly it's getting there (cast vs. assertion vs. check, etc.). It's clearly saying "this operator causes an exception on null".

  • null failure is similar, technically describing the outcome, but "failure" is vague; if we're choosing this we might as well be more explicit and just say what the failure is: an "exception".
  • You could argue throw-if-null operator is even more explicit, but I feel it's too far from the original terminology to be a contender and is kind of a mouth-full.
  • non-null assertion and non-null check are tied for second place for me, as they're the best combination of familiar/recognizable/accurate, but they're not perfect and there's clearly issue taken with both of them.

Goals

  • Definitely should move away from null assertion operator
  • Can we settle one of these?
    • Is "null exception operator" a reasonable compromise over the issues people have with the others?
@parlough
Copy link
Member

parlough commented Oct 16, 2023

Thanks so much for consolidating all of this information @MaryaBelanger! I'm excited to hear from others and for us to consolidate the terminology.

I'll be happy with whatever is chosen, as in the end, I think consistency and familiarity are the most important over definite accuracy.

I will specifically call out that for "non-null assertion operator", some familiarity is already there with Typescript and Kotlin using similar terminology.

I also want to make sure that it's noted that choosing a name here should likely also apply to the null assert pattern type which has similar semantics. There is also a pre-existing Null check pattern type so we should be careful with that term as well.

Thanks everyone for your thoughts!

@jakemac53
Copy link
Contributor

non-null assertion operator imo, although I recognize the behavior is different from just assert(thing != null) in meaningful ways, it still describes the intent of "I the developer assert this is not null".

@bwilkerson
Copy link
Member

  • non-null assertion operator
    • Better, but still possibly misleading: "assert", while its literal definition is appropriate, is already dedicated in Dart and therefore inaccurate.

I agree that this is probably the best of the suggestions. And I don't think it's inconsistent with an assert statement: both check a condition and throw if it isn't true. For the statement the condition is explicit, for the operator the condition is != null.

One advantage is that the term is used in Kotlin, so there's some familiarity.

One disadvantage is that it's used in TypeScript, but has a different semantic (no exception is thrown unless the pointer is dereferenced at some future point).

null exception operator

The concern I have with this is that it isn't used anywhere that I can see. A search doesn't turn up any uses of that phrase, just pages that happen to have those three words in one order or another. I think it would be better to use a phrase that will be familiar to at least some users.

@natebosch
Copy link
Member

I'm partial to "throw if null operator" because it is unambiguous. If that option doesn't have much other support I'd be OK with calling it "non-null assertion operator".

@atsansone atsansone added a.language Relates to the Dart language tour p2-medium Necessary but not urgent concern. Resolve when possible. e2-days Can complete in < 5 days of normal, not dedicated, work st.triage.ltw Indicates Lead Tech Writer has triaged labels Oct 17, 2023
@bwilkerson
Copy link
Member

throw if null operator

Like "null exception operator", there are zero search hits for that name, suggesting that it would be hard for users to search for it.

@eernstg
Copy link
Member

eernstg commented Oct 17, 2023

I think the phrase 'non-null assertion operator' is delightfully accurate: The presence of this operator indicates that the author strongly believes that the value of the given expression will not be null, and it seems natural that violations of strongly held beliefs will lead to exceptional events.

There is a danger that the word 'assertion' is associated with the assert language mechanism, and developers would automatically assume that it implies 'can be turned off'. However, I think we can handle that, especially if we consistently use the word 'assertion' in multiple situations where there's no connection to assert, and we mention explicitly (perhaps in the documentation of assert) that the word 'assertion' does not imply 'can be turned off', that's just a special property of the assert feature.

The phrase is rather long, though. Perhaps we could say that the non-null assertion operator is also known as the 'bang' operator. If we take care to avoid using 'the bang operator' to denote negation then it's probably OK to reserve that phrase for this operator, and it's certainly a lot shorter. ;-)

@MaryaBelanger
Copy link
Contributor Author

The phrase is rather long, though.

That's why I was trying to think of a new, slightly more compact name. But since it sounds like non-null assertion operator is the best choice, I will make a point of listing synonyms in the glossary definition so if we do want to abbreviate to "bang" or something else, those will be listed with "non-null assertion operator" for reference.

Thanks everyone!

@jakemac53
Copy link
Contributor

jakemac53 commented Oct 17, 2023

There is a danger that the word 'assertion' is associated with the assert language mechanism, and developers would automatically assume that it implies 'can be turned off'.

Fwiw the current language spec heavily uses the term "assertion" and qualifications such as "When assertions are not enabled", and these are frequently if not always disconnected from the "assert" function. So we might want to update the language spec there 🤷‍♂️ .

Very few people would actually be reading the spec and get confused, but the terminology is in conflict with the behavior of !, which cannot be disabled.

I still think non-null assertion operator is the best option though.

@lrhn
Copy link
Member

lrhn commented Oct 18, 2023

I like the "null-bang" operator, because if the value is null, the program goes "Bang!". It's short and descriptive of the syntax. What's not to like? 😁

I'd accept "null check operator". It's not absolutely precise (it deosn't say whether being null is good or bad), but it's short and doesn't use "assert", which I also think is a word which risks being too overloaded if we also use it for checks that are not asserts.

The "throw if null operator" is precise and to the point. It's just long. Long isn't good in naming.

We should call ! "the postfix ! operator", that'd be consistent with what we otherwise do.
We haven't said whether + is "the plus operator" or "the add operator", and spec doesn't say anything. We just call it "the + operator".
For ambiguous cases, it's "the unary/prefix - operator" and "the binary/infix - operator".

(And people are still unable to name the ?/: operator - the spec doesn't name it, it's just the syntax of the conditional expression. So I'll name it: "The conditional expression operator". There, please use that in the future.)

If we're trying to name the operation, then that's a different beast. 😉
(I'd want to go with something containing "throw-if-null".)

@munificent
Copy link
Member

"throw if null operator"

For what it's worth, the language already has a "if-null expression" (??), so a separate "throw if null expression" might be confusingly read as "throw (if null expression)".

I don't love "non-null assertion" because it suggests they get turned off when asserts are off, but the familiarity argument probably outweighs that.

@mit-mit
Copy link
Member

mit-mit commented Oct 23, 2023

Maybe non-null cast operator or is that confusing in some way I'm not seeing?

@parlough
Copy link
Member

parlough commented Oct 26, 2023

Since no one else has chimed on in on that suggestion, I think non-null cast operator is an okay choice as well. It has the advantage of relying on an idea already familiar to programmers of many languages (casting) and being quite close in behavior.

It's not as searchable yet, but some sort-of relevant results do come up.

The one concern is that to quote @eernstg: "there may not exist a type T such that e! and e as T is exactly the same thing." To me, it doesn't seem like a large enough source of confusion to avoid the term though.

In the end though, I don't think the assertion worry is a big problem either, as whether or not assertions are enabled, I expect them to hold true. There's also the fact that the word "assertion" has been in the common term and propagated for the past few years through the official documentation, it may be hard to fight against anyway.

@parlough
Copy link
Member

parlough commented Oct 26, 2023

Overall, I think having any consistent term (that is still searchable eventually) is better than what we have now, even if it's slightly incorrect. Even if we don't come to a decision on this soon, it seems we should at least update the null assertion operator mentions to non-null on the website.

Since these two seem to be the most likely to be understandable and searchable to Dart programmers from what's been discussed, I'm curious if anyone feels they're not acceptable at all? Otherwise maybe we should just move forward with one, at least on the website, and see how it goes.

How about some informal opinions?

  • 👎 Neither is acceptable
  • 🎉 non-null assertion operator
  • 🚀 non-null cast operator

@bwilkerson
Copy link
Member

I'm not fond of 'cast'. While the operator could accurately be thought of as a cast, that isn't now it's presented to the user in the exception that's thrown:

Null check operator used on a null value

I also suspect that users don't think of it as a cast.

(If we settle on a name we should update the analyzer / CFE / VM messages to match.)

@rakudrama
Copy link
Member

I call it a null check or null check operator but would be happy to start calling it a not-null check.

The term check is used in lots of places for things that check something and throw if not satisfied, e.g. RangeError.check. If it was not for this established nomenclature, I might suggest calling ! a null guard.

I'm not fond of cast, which in some languages is a conversion.

@munificent
Copy link
Member

While the operator could accurately be thought of as a cast, that isn't now it's presented to the user in the exception that's thrown:

It may not be how it's presented to the user in error messages, but I think it is like a cast in terms of actual semantics:

  1. You have an inner expression of some type T.
  2. If the evaluated value doesn't have some other type R, throw an exception.
  3. Otherwise, yield the resulting value. The whole expression has type R.

The only difference between ! and a cast that I know of is that R is inferred to be NOTNULL(T) instead of making the user write a type.

@bwilkerson
Copy link
Member

The only difference between ! and a cast that I know of ...

While I understand that the semantics of the operations, from a specification point of view, are the same, I was thinking about the UX presentation. Of course, the UX presentation can be changed, but existing users might be familiar with the current behavior and the current behavior might have influenced their mental model.

In attempting to make that argument, I probably should have been more detailed. I used the following to get the exception message for the operator ("Null check operator used on a null value"):

void main() {
  f(null);
}

void f(int? x) {
  x!;
}

But if we instead write

void main() {
  f(null);
}

void f(int? x) {
  x as int;
}

the exception message is "type 'Null' is not a subtype of type 'int' in type cast".

The two exception messages are different enough that users might not think of ! as a cast. Whether we want to try to re-educate them is a different question.

@mit-mit
Copy link
Member

mit-mit commented Oct 31, 2023

I also suspect that users don't think of it as a cast.

I think you are right about that, but I often find that is actually a mistake: Some developers think of it as a non-problematic operator, that you just add to "please the type system" and don't consider that it can actually fail. I speculate that having a stronger 'cast' connotation will make it much more clear, that it should only be used when you are confident about the type.

@bwilkerson
Copy link
Member

Some developers think of it as a non-problematic operator, that you just add to "please the type system" and don't consider that it can actually fail.

That's true, and it's because that's how it works in TypeScript.

But the Kotlin version works the same way as Dart's and is also called the "non-null assertion operator", so Kotlin doesn't call out an association with casts. (Just a data point, not an argument.)

@lrhn
Copy link
Member

lrhn commented Nov 1, 2023

It's not really a cast, because a cast is to something, and there may be no type to cast to.

It's not the same as e as NON_NULL(typeof(e)), because for some types T = NON_NULL(T).
For example if you have FutureOr<int?> x; then x! doesn't cast x to anything, it just throws-if-null, but the type afterwards is still FutureOr<int?>.

So, not a cast. It's a throw-if-null operator. (I can live with "non-null assertion operator").

@mit-mit
Copy link
Member

mit-mit commented Nov 1, 2023

Sorry, I don't undetstand, @lrhn. Can you elaborate please on how it's not a cast?

It seems to fail in pretty similar ways (just with a different error message, which we could update):

int? i;
int? j;  

...

int x = i!;
// Throws Unhandled exception: Null check operator used on a null value

int y = j as int;
// Throws Unhandled exception: type 'Null' is not a subtype of type 'int' in type cast

@lrhn
Copy link
Member

lrhn commented Nov 1, 2023

A "cast" is, in Dart terminology, a check for whether a value satifies a specific type.
When applied to an expression, the static type of the result is the type that was checked against.

Example:

num? x = ...;
var y = x as int;  // `y` gets type `int`.

The cast of the value of x, with static type num?, to the type int, is a way to say that the value of x is an int.
(And throw if it isn't, to preserve soundness. It's not the throwing that people want, but confirmation that the value has the type that they think it has, and making the type system know it too. It should never actually fail, which is why we throw an Error if it does.)

Now take:

FutureOr<int?> x = ...;
var y = x!;

Which type is x being cast to?

It's not x as NON_NULL(FutureOr<int?>), because that type is FutureOr<int?> again, so a cast like would never throw. The ! does throw on the null value.

One could argue that it's cast to the intersection type FutureOr<int?> & Object. Then, afterwards, the & Object is erased again, and the static type becomes just FutureOr<int?> again.
But the intersection type doesn't exist in the Dart type system, so it's not a good reasoning to say how it would work, if it had existed.

There is no type T in the Dart type system such that x! is equivalent to x as T. So, it's not a cast.

The ! operator is more like a shorthand for a null guard: e ?? (throw TypeError("Value is null")).

So is it a "null-throw-guard"?
A "non-null-prevention-operator". (Or other ways to say "not-null-assertion".)

Maybe just "null-throw operator"? Feels ambiguous, so "throw-if-null operator" is better.
Use that :)

(A cast is a "type guard", it prevents bad types from getting past. A ! is a "null guard", or "non-null guard", it prevents bad nulls from getting past.)

@ericwindmill
Copy link
Contributor

ericwindmill commented Nov 1, 2023

I'm going to put my DevRel hat on and suggest that technical accuracy is less important than how easy it is to communicate between developers.

I'd argue that leaning too heavily on technicality and naming it something like non-null assertion operator could have an adverse effect on how easy it is to communicate, especially for developers that are newer to Dart, as they'll have to pause and think about each piece of that phrase.

It might have been a facetious suggestion, but I actually vote for this:

I like the "null-bang" operator, because if the value is null, the program goes "Bang!".

For these reasons:

It's short and descriptive of the syntax. What's not to like? 😁

I'd accept "null check operator". It's not absolutely precise (it deosn't say whether being null is good or bad), but it's short and doesn't use "assert", which I also think is a word which risks being too overloaded if we also use it for checks that are not asserts.

The "throw if null operator" is precise and to the point. It's just long. Long isn't good in naming.

Also, it's much easier and more fun to write null-bang. Who will think of the docs writers and content creators! 🙃

@parlough
Copy link
Member

parlough commented Nov 1, 2023

I'm going to put my DevRel hat on and suggest that technical accuracy is less important than how easy it is to communicate between developers.

I agree that accuracy isn't super essential here and communication between developers is super important, but also understanding something from the name is too and helps enable communication.

I feel a name such as "null bang" wouldn't be understood in discussion by a developer unfamiliar with the name. I'd argue it actually adds work for beginners, since it's a separate concept to learn, while non-null assertion operator can rely on what they learned about nullable types and their understanding of the word assertion. There's also the fact "null bang" is less straightforward to translate.

@lrhn
Copy link
Member

lrhn commented Nov 7, 2023

Completely generally, I prefer "not-null" to "non-null" for what this operator does.

Maybe because "non-null" sounds like an adjective, but it's about the value, not the next word. A "non-null assert" is not an assert which is non-null?

Calling suffix-! a "not null assert" or "not null assertion" is describing what it does, "assert" that the value is "not null".
But the word "assert" is still questionable, since the operator works when asserts are disabled.

Using "not null check" sounds more like it returns a null.
But "not null guard" is pretty good. The word "guard" is not used today, so it's not used incorrectly. But also has no existing usage to rely on for being understood.

(Still like "null-bang!", but it's true that it has no associations to help it being understood.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a.language Relates to the Dart language tour e2-days Can complete in < 5 days of normal, not dedicated, work p2-medium Necessary but not urgent concern. Resolve when possible. st.triage.ltw Indicates Lead Tech Writer has triaged
Projects
None yet
Development

No branches or pull requests