본문 바로가기

Rust

[Rust] Option<T> 의 사용방법에 대하여

반응형

Option<T>

이 Option타입은 표준라이브러리에 enum으로 정의되어 있습니다. 러스트에는 기본적으로 다른언어에서 많이 사용하는 null 이 없습니다. 때문에 널포인터 참조와 같은 에러는 발생하지 않습니다. null값을 사용할때의 문제점은 값이 null인 데이터를 사용할때 문제가 발생합니다.

 

하지만, '값이 없음'을 표현하는 방법이 필요할 때가 있습니다. 그럴 때 Option<T> 이라는 enum 타입을 사용할 수 있습니다. 이 Option<T> 은 표준 라이브러리에 아래와 같이 정의되어 있습니다

enum Option<T> { 
  Some(T),
  None, 
}

여기서 T 는 제너릭을 나타냅니다.

Some 은 값이 있음을 나타내며, None 은 값이 없음을 나타냅니다. 이 두 '상태' 를 가지고 null 체크와 같은 일을 수행할 수 있습니다. Option 은 기본적으로 스코프에 포함되어, 명시적으로 로드하지 않아도 사용할 수 있습니다.

 

어떤 함수의 리턴값이 존재하거나 존재하지 않는 경우 리턴타입으로 Option를 사용합니다. Option는 Null Pointer Dereference(널 포인터 참조) 처럼 Null로 리턴되는 값을 Null이 아닌 값처럼 사용할때 발생할 수 있는 문제점을 사전에(컴파일 타임에) 방지하고자 만들어진 열거형 타입입니다

 

그런데 사실 Option를 사용하지 않아도 문제는 없지만, 다만 함수의 안전성을 보장하지 못할 뿐입니다. Rust는 컴파일 타임에 많은 것들을 걸러냅니다. 그래서 코드 작성에 제약이 많고 번거롭기도 하고 복잡합니다. 그것을 코드 작성자가 비용으로 지불하게 강제합니다. 그렇지만, 코드의 장기간 유지보수 비용을 고려하면, 번거롭더라도 코드작성시 시간을 투자하는게 디버깅등에 들어가는 시간을 고려한다면 결과적으로 가장 저렴하게 비용을 지불하는것입니다.

 

결론적으로 함수를 작성할때 리턴타입을 Option나 Result<T, E>으로 하면 안전성이 증가합니다. (컴파일이 실패할 가능성도 증가하지만 런타임에 오류가 발생할 가능성이 줄어든다).

Option 은 아래와 같이 선언됩니다.

let some_number = Some(5); 
let some_string = Some("a string"); 
let absent_number: Option<i32> = None;

타입 추론에 따라 some_number 는 Option 타입, some_string 은 Option<&str> 이 됩니다. 그런데 absent_number 에만 타입을 지정한 이유는, None 은 값을 가지지 않기 때문에 타입을 지정하지 않으면 absent_number 의 타입을 추론할 수 없어 컴파일 에러가 발생합니다. 때문에 None 은 타입이 필요합니다.

 

말했듯이, Some 은 값이 있는 상태를, None 은 null 을 의미합니다. 하지만, 그렇다고 해서 Some 을 값 자체로 사용할 수는 없습니다. Some 은 어쨌든 Option 이라는 enum 타입일 뿐입니다. 아래와 같은 연산은 컴파일 에러가 발생합니다. Integer 타입에 Option 타입을 더할 수는 없기 때문입니다. Some 이 값이 아니라는 걸 기억할 필요가 있습니다.

let x = 5;
let y = Some(5);
let sum = x + y;

5 라는 값이 있을 때, 이 값은 분명히 존재하는 값이며, 러스트는 이 값이 유효한 값인지 아닌지 체크할 필요가 없음. 그런데 Option 타입의 경우, 값이 존재할 수도(Some), 존재하지 않을 수도(None) 있음. 때문에 Option 을 사용하기 위해서는 먼저 이게 Some 인지 None 인지 체크할 필요가 있음. 그리고 우리는 Some 일 경우의 동작과, None 일 경우의 동작을 모두 코드로 작성할 수 있음. 이는 match 를 이용함.

예제: Option 사용하기

let x: Option<i32> = Some(5); 
let y: Option<i32> = None;

Option과 match 사용하기

Option은 match와 함께 아래와 같은 방식으로 사용되기도 합니다.

match x { 
  Some(val) => println!("x has a value of {}", val), 
  None => println!("x has no value"), 
}

Option과 if let 사용하기

Option은 if let과는 아래와 같은 형태로도 사용됩니다.

if let Some(val) = x { 
	println!("x has a value of {}", val); 
} else { 
	println!("x has no value"); 
}

이러한 Option의 메서드들은 값이 Some일 때만 연산을 수행하고, None일 경우에는 아무런 작업도 수행하지 않습니다. 이를 통해 값의 유무를 안전하게 처리할 수 있습니다.

Option과 메서드 체이닝

Rust의 Option 타입은 여러 유용한 메서드를 제공하여 메서드 체이닝을 통한 간결하고 읽기 쉬운 코드 작성을 가능하게 합니다. 아래는 주요 메서드와 그 설명입니다.

메서드설명예제

map Some(T) 값을 받아 새로운 Some(U) 값을 생성합니다. None은 그대로 반환됩니다. `Some(2).map(
and_then Some(T) 값을 받아 새로운 Option<U> 값을 생성합니다. None은 그대로 반환됩니다. `Some(2).and_then(
unwrap_or Some(T)이면 내부 값을, None이면 인자로 주어진 값을 반환합니다. None.unwrap_or(3) == 3
unwrap_or_else Some(T)이면 내부 값을, None이면 인자로 주어진 함수의 반환 값을 반환합니다. `None.unwrap_or_else(
or Some(T)이면 그대로, None이면 인자로 주어진 Option<T>를 반환합니다. None.or(Some(3)) == Some(3)
or_else Some(T)이면 그대로, None이면 인자로 주어진 함수의 반환 값(Option<T>)을 반환합니다. `None.or_else(

예제: 메서드 체이닝

let value = Some(5);
let result = value
	.map(|x| x + 1) 
	.and_then(|x| if x > 5 { Some(x) } else { None })
    .unwrap_or(0); 
println!("Result: {}", result); // 출력: "Result: 6"
이 예제에서는 value가 Some(5)이므로, map을 통해 Some(6)으로 변환됩니다. 그 다음 and_then에서 x > 5 조건을 만족하므로 Some(6)이 유지됩니다. 마지막으로 unwrap_or에서 Some이므로 내부의 6이 반환됩니다.

이렇게 Option 타입의 메서드 체이닝을 사용하면, 값의 유무를 명시적으로 처리하면서도 간결하고 가독성 높은 코드를 작성할 수 있습니다.

반응형

'Rust' 카테고리의 다른 글

[Rust] 포인터의 종류와 사용방법  (0) 2023.12.11
Rust in Jupyter notebook  (0) 2023.12.04
[Rust] Clap과 커맨드 라인 argument 다루기  (0) 2023.09.07
[Rust] Cargo 간단한 사용법  (0) 2023.09.05
[RUST] xlsxwriter  (0) 2023.07.24