안전 탐색 연산자

객체 지향 프로그래밍에서 **안전 탐색 연산자**(safe navigation operator), **옵셔널 체이닝 연산자**, **안전 호출 연산자**, **널-조건부 연산자**, **널-전파 연산자**는 첫 번째 인수가 null이면 null을 반환하는 이항 연산자이다. 그렇지 않으면 두 번째 인수에 따라 역참조 작업을 수행한다(일반적으로 객체 멤버 접근, 배열 인덱스 또는 람다 호출).

순차적인 명시적 null 검사와 할당을 피하고 이를 메서드/속성 체이닝으로 대체하는 데 사용된다. 탐색 연산자(예: ".")가 null 객체에 적용될 때 오류를 발생시키는 프로그래밍 언어에서 안전 탐색 연산자는 메서드/필드 체인의 평가를 중지하고 체인 표현식의 값으로 null을 반환한다. 2007년 Groovy 1.0에서 처음 사용되었으며 현재 다음과 같은 언어에서 지원된다.
C 샤프, 스위프트, 타입스크립트, 루비, 코틀린, 러스트, 자바스크립트,
등. 이 연산자에 대한 공통 명명 규칙은 현재 없지만 **안전 탐색 연산자**가 가장 널리 사용되는 용어이다.

이 연산자를 사용하는 주요 장점은 파멸의 피라미드를 피할 수 있다는 것이다. 프로그래머는 여러 중첩된 `if` 문을 작성하는 대신 일반적인 체이닝을 사용하고 점(또는 체이닝에 사용되는 다른 문자) 앞에 물음표 기호를 추가할 수 있다.

**안전 탐색 연산자**와 **[[널 병합 연산자]]**는 모두 **널-인식 연산자**이지만, 작동 방식은 다르다.

## 예시

### Apex
Apex의 안전 탐색 연산자 예시:
```java
a[x]?.aMethod().aField // a[x] == null 이면 null로 평가된다
a[x].aMethod()?.aField // a[x].aMethod()가 null로 평가되면 null을 반환한다
String profileUrl = user.getProfileUrl()?.toExternalForm();
return [SELECT Name FROM Account WHERE Id = :accId]?.Name;
```


### C#
C# 6.0 이상에는 `?.` (널-조건부 멤버 접근 연산자, Microsoft에서는 엘비스 연산자라고도 부르지만, C#에서 널 병합 연산자인 `??`와 동일한 일반적인 엘비스 연산자의 사용과는 혼동해서는 안 된다)와 인덱서 get 접근자의 널-안전 호출을 수행하는 `?[]` (널-조건부 요소 접근 연산자)가 있다. 멤버 접근 결과의 유형이 값 형식인 경우, 해당 멤버에 대한 널-조건부 접근 결과의 유형은 해당 값 형식의 널러블 버전이다.

다음 예제는 문서 배열에서 첫 번째 문서의 저자 이름을 검색한다(각 문서에 `Author` 멤버가 있고 각 저자에 `Name` 멤버가 있다고 가정). 배열이 `null`이거나, 첫 번째 요소가 `null`이거나, 해당 문서의 `Author` 멤버가 `null`이거나, 해당 저자의 `Name` 멤버가 `null`이면 결과는 `null`이다. 배열이 null이 아니지만 비어 있는 경우(즉, 길이가 0인 경우) `IndexOutOfRangeException`이 여전히 발생한다는 점에 유의하라.


```csharp
string name = articles?[0]?.Author?.Name;
```


람다를 호출하려면 `callback?.Invoke()`가 필요하다. 널-조건부 호출(`callback?()`는 허용되지 않는다)이 없기 때문이다.


```csharp
Func<int, int> callback = x => x * x; // x를 제곱한다
int result = callback?.Invoke(5);
```


### 클로저 (프로그래밍 언어)
클로저는 다른 언어에서 사용하는 의미의 진정한 연산자를 가지고 있지 않지만, 자바와 상호 운용되고 객체 탐색을 수행해야 하므로 `some->` 매크로를 사용하여 안전 탐색을 수행할 수 있다. 확장된 매크로 `safe->`는 nil 값 호출을 방지한다.


```clojure
(some-> article .author .name)
```


### CoffeeScript
실존 연산자:

```coffeescript
zip = lottery.drawWinner?().address?.zipcode
```


### Crystal
Crystal은 `try` 안전 탐색 메서드를 지원한다


```crystal
name = article.try &.author.try &.name
```


### Dart
조건부 멤버 접근 연산자:
```dart
var name = article?.author?.name
```


### Gosu
널 안전 호출 연산자:


```gosu
var name = article?.author?.name
```


널-안전 호출 연산자는 Gosu 속성으로 선언된 클래스 속성에는 필요하지 않다.


```gosu
class Foo {
    var _bar: String as Bar
}

var foo: Foo = null

// 아래 코드는 null로 평가되며 NullPointerException을 반환하지 않는다
var bar = foo.Bar
```


### Groovy
안전 탐색 연산자 및 안전 인덱스 연산자:


```groovy
def name = article?.authors?[0].name
```


### 자바스크립트
ECMA스크립트 2020에 추가된 선택적 체이닝 연산자는 참조나 함수가 undefined 또는 null일 가능성이 있을 때 연결된 객체를 통해 값에 접근하는 것을 간소화하는 방법을 제공한다. 주요 데스크톱 브라우저는 2020년부터 이를 지원했으며, 대부분의 모바일 브라우저는 2024년까지 지원을 추가했다.

```javascript
const name = article?.authors?.[0]?.name
const result = callback?.()
```


이 연산자는 오른쪽에 있는 전체 호출 체인을 단락한다. 다음 예시에서는 bar가 "접근"되지 않는다.

```javascript
null?.foo.bar
```


### Kotlin
안전 호출 연산자:


```kotlin
val name = article?.author?.name
```


### Objective-C
대부분의 경우 NULL에 대해 걱정할 필요 없이 일반적인 탐색 구문을 사용할 수 있다. 이는 NULL로 메시지가 전송될 때 아무런 부작용 없이 폐기되기 때문이다.

```objc
NSString *name = article.author[0].name;
```


### Perl 5
Perl 5에는 이러한 종류의 연산자가 없지만, 다음 구문으로 포함 제안이 수락되었다.

```perl
my $name = $article?->author?->name;
```


### PHP
널 안전 연산자는 PHP 8에 채택되었다.


```php
$name = $article?->author?->name;
```


### Raku (Perl 6)
안전한 메서드 호출:


```pl6
my $name = $article.?author.?name;
```


### 루비 (프로그래밍 언어)
루비는 버전 2.3.0부터 `&.` 안전 탐색 연산자(외로운 연산자라고도 함)를 지원한다.


```ruby
name = article&.author&.name
```


### 러스트 (프로그래밍 언어)
러스트는 안전 탐색 연산자처럼 보일 수 있는 `?` 연산자를 제공한다. 그러나 주요 차이점은 `?`가 `None` 값을 만날 때 `None`으로 평가되지 않는다는 것이다. 대신 `return` 문처럼 동작하여 둘러싼 함수나 클로저가 즉시 `None`을 반환하게 한다.

`Option` 메서드 `map()` 및 `and_then()`을 안전 탐색에 사용할 수 있지만, 이 옵션은 안전 탐색 연산자보다 장황하다.

```rust
fn print_author(article: Option<Article>) {
    println!(
        "Author: {}",
        article.and_then(|y| y.author)
            .map(|z| z.name)
            .unwrap_or("Unknown".to_owned())
    );
}
```


`?`를 사용하는 구현은 `article`이 `None`이거나 `article.unwrap().author`가 `None`이면 아무것도 출력하지 않는다("Author:"조차도). `?`가 `None`을 보는 즉시 함수는 반환된다.

```rust
fn try_print_author(article: Option<Article>) -> Option<()>{
    println!("Author: {}", article?.author?.name);
    Some(())
}
```


### Scala

Scala의 null-safe 연산자는 Dsl.scala 라이브러리에서 제공한다.


```scala
val name = article.?.author.?.name : @ ?
```


`@ ?` 어노테이션은 null을 허용하는 값을 나타내는 데 사용될 수 있다.


```scala
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")
)
```


Scala의 일반적인 `.`는 `null` 값에서 메서드를 수행할 때 null-safe하지 않다.


```scala
a[NullPointerException] should be thrownBy {
root.right.left.right.value // root.right.left는 null이다!
}
```


대신 널 허용 값에 `?` 연산자를 사용하여 예외를 피할 수 있다.


```scala
root.?.right.?.left.?.value should be(null)
```


`?` 중 하나가 `null` 값에서 수행되면 전체 표현식은 `null`이다.

`null` 안전 연산자 `?`의 경계는 `@ ?`로 타입이 주석 처리된 가장 가까운 바깥쪽 표현식이다.


```scala
("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)
```


### 스위프트 (프로그래밍 언어)
옵셔널 체이닝 연산자, 서브스크립트 연산자, 그리고 호출:
```swift
let name = article?.authors?[0].name
let result = protocolVar?.optionalRequirement?()
```


### 타입스크립트
옵셔널 체이닝 연산자는 타입스크립트 3.7 릴리스에 포함되었다.

```typescript
let x = foo?.bar?.[0]?.baz();
```


### Visual Basic .NET
Visual Basic 14 이상에는 C#과 유사한 `?.`(null-조건부 멤버 접근 연산자) 및 `?()`(null-조건부 인덱스 연산자)가 있다. 이들은 C#의 해당 연산자와 동일한 동작을 한다.

다음 문은 위의 C# 예시와 동일하게 동작한다.

```vbnet
Dim name = articles?(0)?.Author?.Name
```


## 같이 보기
* 엘비스 연산자
* 널 병합 연산자

## 각주

## 외부 링크
* PEP 505, 파이썬용 안전 탐색 연산자 가능성 논의

분류:객체 지향 프로그래밍
분류:코드 예시에 관한 문서
분류:조건문
분류:연산자 (프로그래밍)