Safe navigation operator
In object-oriented programming, the safe navigation operator (also known as optional chaining operator, safe call operator, null-conditional operator) is a binary operator that returns null if its first argument is null; otherwise it performs a dereferencing operation as specified by the second argument (typically an object member access, array index, or lambda invocation).
It is used to avoid sequential explicit null checks and assignments and replace them with method/property chaining. In programming languages where the navigation operator (e.g. ".") leads to an error if applied to a null object, the safe navigation operator stops the evaluation of a method/field chain and returns null as the value of the chain expression. It is currently supported in languages such as Apex,[1] Apache Groovy, Swift,[3] Ruby,[4] C#,[5] Kotlin,[6] CoffeeScript, Scala,[7] Dart[8] and others. There is currently no common naming convention for this operator, but safe navigation operator is the most widely used term.
The main advantage of using this operator is that it avoids the pyramid of doom. Instead of writing multiple nested if
s, programmers can just use usual chaining, but add question mark symbols before dots (or other characters used for chaining).
While the safe navigation operator and null coalescing operator are both null-aware operators, they are operationally different.
Examples
Apex
Safe navigation operator examples:[9]
a[x]?.aMethod().aField // Evaluates to null if a[x] == null
a[x].aMethod()?.aField // returns null if a[x].aMethod() evaluates to null
String profileUrl = user.getProfileUrl()?.toExternalForm();
return [SELECT Name FROM Account WHERE Id = :accId]?.Name;
Objective-C
Normal navigation syntax can be used in most cases without regarding NULLs, as the underlying messages, when sent to NULL, is discarded without any ill effects.
NSString *name = article.author[0].name;
Swift
Optional chaining operator,[3] subscript operator, and call:
let name = article?.authors?[0].name
let result = protocolVar?.optionalRequirement?()
Ruby
Ruby supports the &.
safe navigation operator (also known as the lonely operator) since version 2.3.0:[4]
name = article&.author&.name
C#
C# 6.0 and above have ?.
, the null-conditional member access operator (which is also called the Elvis operator by Microsoft and is not to be confused with the general usage of the term Elvis operator, whose equivalent in C# is ??
, the null coalescing operator) and ?[]
, the null-conditional element access operator, which performs a null-safe call of an indexer get accessor. If the type of the result of the member access is a value type, the type of the result of a null-conditional access of that member is a nullable version of that value type.[11]
The following example retrieves the name of the author of the first article in an array of articles (provided that each article has a Author
member and that each author has an Name
member), and results in null
if the array is null
, if its first element is null
, if the Author
member of that article is null
, or if the Name
member of that author is null
. Note that an IndexOutOfRangeException
is still thrown if the array is non-null but empty (i.e. zero-length).
var name = articles?[0]?.Author?.Name;
Calling a lambda requires callback?.Invoke()
, as there is no null-conditional invocation (callback?()
is not allowed).
var result = callback?.Invoke(args);
Visual Basic .NET
Visual Basic 14 and above have the ?.
(the null-conditional member access operator) and ?()
(the null-conditional index operator), similar to C#. They have the same behavior as the equivalent operators in C#.[12]
The following statement behaves identically to the C# example above.
Dim name = articles?(0)?.Author?.Name
Gosu
Null safe invocation operator:[13]
var name = article?.author?.name
The null-safe invocation operator is not needed for class attributes declared as Gosu Properties:
class Foo {
var _bar: String as Bar
}
var foo: Foo = null
// the below will evaluate to null and not return a NullPointerException
var bar = foo.Bar
Python
The safe navigation operator is not supported in Python. It was proposed for inclusion with the following syntax:[16]
# Proposed syntax, not yet part of the language:
name = article?.author?.name
JavaScript
Added in ECMAScript 2020, the optional chaining operator provides a way to simplify accessing values through connected objects when it's possible that a reference or function may be undefined or null.[17]
const name = article?.authors?.[0]?.name
const result = callback?.()
TypeScript
Optional chaining operator was included in the Typescript 3.7 release:[19]
let x = foo?.bar?.baz();
Rust
Rust supports the ?
and try!{}
operators for structures implementing the Try
trait.[21]
// The preferred method of quick returning Errors
fn write_to_file_question() -> Result<(), MyError> {
let mut file = File::create("my_best_friends.txt")?;
file.write_all(b"This is a list of my best friends.")?;
Ok(())
}
// The previous method of quick returning Errors
fn write_to_file_using_try() -> Result<(), MyError> {
let mut file = r#try!(File::create("my_best_friends.txt"));
r#try!(file.write_all(b"This is a list of my best friends."));
Ok(())
}
Scala
The null-safe operator in Scala is provided by the library Dsl.scala.[22]
val name = article.?.author.?.name : @ ?
The @ ?
annotation can be used to denote a nullable value.
case class Tree(left: Tree @ ? = null, right: Tree @ ? = null, value: String @ ? = null)
val root: Tree @ ? = Tree(
left = Tree(
left = Tree(value = "left-left"),
right = Tree(value = "left-right")
),
right = Tree(value = "right")
)
The normal .
in Scala is not null-safe, when performing a method on a null
value.
a[NullPointerException] should be thrownBy {
root.right.left.right.value // root.right.left is null!
}
The exception can be avoided by using ?
operator on the nullable value instead:
root.?.right.?.left.?.value should be(null)
The entire expression is null
if one of ?
is performed on a null
value.
The boundary of a null
safe operator ?
is the nearest enclosing expression whose type is annotated as @ ?
.
("Hello " + ("world " + root.?.right.?.left.?.value)) should be("Hello world null")
("Hello " + (("world " + root.?.right.?.left.?.value.?): @ ?)) should be("Hello null")
(("Hello " + ("world " + root.?.right.?.left.?.value.?)): @ ?) should be(null)
Clojure
Clojure doesn't have true operators in the sense other languages uses it, but as it interop with Java, and has to perform object navigation when it does, the some->
[23] macro can be used to perform safe navigation.
(some-> article .author .name)
See also
References
- "Safe Navigation Operator | Apex Developer Guide | Salesforce Developers". developer.salesforce.com. Retrieved 2020-10-13.
- "Optional Chaining". Retrieved 2016-01-28.
- "Ruby 2.3.0 Released". Retrieved 2016-01-28.
- "Null-conditional Operators (C# and Visual Basic)". Retrieved 2016-01-28.
- "Null Safety". Retrieved 2016-01-28.
- "NullSafe: Kotlin / Groovy flavored null-safe ? operator now in Scala". Scala Users. 2018-09-12. Retrieved 2019-06-03.
- "Other Operators". A tour of the Dart language. Retrieved 2020-01-08.
- "Salesforce Winter 21 Release Notes". Salesforce. Retrieved 2020-10-13.
- "8.5. Safe index operator". Retrieved 2020-09-25.
- "Member access operators (C# reference)". Microsoft Docs. Microsoft. Retrieved 29 August 2019.
- "?. and ?() null-conditional operators (Visual Basic)". Microsoft Docs. Microsoft. Retrieved 29 August 2019.
- "The Gosu Programming Language". Retrieved 2018-12-18.
- "Raku Operators". Retrieved 2016-06-28.
- "PHP: rfc:nullsafe_operator". wiki.php.net. Retrieved 2020-10-01.
- "PEP 505 -- None-aware operators". Retrieved 2018-08-27.
- https://tc39.es/proposal-optional-chaining/
- "The Existential Operatior". Retrieved 2017-06-15.
- "Typescript 3.7". Retrieved 2019-11-06.
- "Crystal API: Object#try".
- "Trait std::ops::Try". Retrieved 2019-02-26.
- A framework to create embedded Domain-Specific Languages in Scala: ThoughtWorksInc/Dsl.scala, ThoughtWorks Inc., 2019-06-03, retrieved 2019-06-03
- "Threading Macros Guide". Retrieved 2019-06-07.
External links
- PEP 505, discussing the possibility of safe navigation operators for Python