[1. 개요]
열거형은 하나의 타입이 가질 수 있는 값들을 열거 함으로써 타입을 정의할 수 있도록 한다.
Option 이라는 열거형의 존재 (boost::optional 처럼 사용?)
열거형 값에 따른 분기를 위해 match 표현식과 , if let 구문
[2. 열거형 정의]
enum IpAddr {
V4(String), // 타입을 명시할 수 있다.
V6(String),
}
enum Message {
Quit,
Move { x: i32, y: i32 }, // 익명 구조체
Write(String), // 문자열
ChangeColor(i32, i32, i32), // 튜플
}
fn main() {
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
home = loopback; // 같은 열거형에 속하지만, 타입이 달라 컴파일 에러 발생
}
열거형 variant 에 어떤 종류의 데이터라도 넣을 수 있습니다.
Message 열거형은 하나의 타입으로 관련된 함수를 정의 할 수 있다.
=> 타입별로 함수를 정의할 필요 없다.
[3. Option 열거형]
Option<T> 열거형은 많이 사용되며,
값이 있거나 없을 수 있는 상황을 표현 할 수 있다.
=> 컴파일러가 발생할 수 있는 모든 경우를 처리했는지 체크 할 수 있다.
또한, 기본적으로 포함되어 있어서 명시적으로 가져오지 않아도 사용 할 수 있다.
enum Option<T> {
Some(T),
None,
}
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
let zz = None; // 컴파일 에러 발생
}
Some 이 아닌 None 을 사용한다면, Option<T> 가 어떤 타입인지 명시 할 필요가 있다.
Option<T> 가 Null 보다 나은 이유는?
=> Option<T> 와 T 는 서로 다른 타입이다.
=> i8 타입과 Option<i8> 간 덧셈은 컴파일 에러를 유발한다.
==> 덧셈을 위해 Some 인지 None 인지 확인해야 한다.
==> None 인 경우 처리를 반드시 하게 된다.
[4. match 흐름 제어 연산자]
match 라는 흐름 제어 연산자는 일련의 패턴에 대해 어떤 값을 비교한 뒤,
어떤 패턴에 매치되었는지를 바탕으로 코드를 수행하도록 해준다.
match 의 힘은 패턴의 표현성으로부터 오며, 컴파일러는 모든 가능한 경우가 다루어지는지 검사한다.
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
bool 값을 사용해야하는 if ~ else 와 달리 타입에 영향을 받지 않는다.
패턴 다음에는 => 연산자가 오며,
매칭되는 패턴을 발견하면, 패턴과 연관된 코드가 실행된다.
[5. Option<T> 를 이용한 매칭]
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("{}", match six {
//None => 0, // 컴파일 에러
Some(i) => i
});
}
match 내 에서 Some 내 값을 i (변수 이름, not keyword) 에 바인드 할 수 있다.
None 에 대한 처리가 없는 경우, 컴파일 에러가 발생한다.
[6. 변경자, placeholder]
fn main() {
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
256 => println!("seven"), // 컴파일 에러
_ => (),
}
}
match 내 모든 값을 나열 할 필요가 없는 경우,
_ (placeholder) 를 사용하여, 명시할 수 있다.
또한, 위 예제에서 some_u8_value 는 unsigned 8bit 이므로,
256 을 표현 할 수 없다.
여기서 컴파일 에러가 발생한다.
[7. if let 을 사용한 간결한 흐름제어]
fn main() {
let some_u8_value = Some(3u8); // unsigned 8bit: 0
if let Some(0u8) = some_u8_value {
println!("three");
} else {
println!("four");
}
println!("{:?}", some_u8_value);
if let some_u8_value = Some(5) { // 잘못된 사용 방법
println!("inner {:?}", some_u8_value);
}
println!("{:?}", some_u8_value);
}
// 출력결과
// four
// Some(3)
// inner Some(5)
// Some(3)
if let 은 match 가 강제했던 하나도 빠짐없는 검사를 할 수 없게한다.
그러나 보일러 플레이트 코드를 덜 쓰게 한다.
적절한 trade off 가 있는 상황이므로, 적절히 사용해야 한다.
또한, 사용 시 순서를 주의하도록 한다.
의도하지 않은 실행이 발생 할 수 있다.
(let 으로 인한 변수 할당을 참으로 보기 때문인가?)
fn main() {
let some_u8_value = Some(3u8); // unsigned 8bit: 0
println!("{:?}", some_u8_value);
{
let some_u8_value = 111;
println!("{}", some_u8_value);
}
println!("{:?}", some_u8_value);
}
// 출력
// Some(3)
// 111
// Some(3)