Generic 자료형이란?아무 것도 없는 것을 표현하기옵션결과실패할 수 있는 메인우아한 오류 처리추한 옵션/결과 처리벡터The Rust Programming Language 링크의문점
Rust에서 generic 자료형은 널 허용(nullable) 값을 표현, 오류 처리, collection 등에 여러군데 많이 쓰임.
Generic 자료형이란?
Generic 자료형은
struct
나 enum
을 부분적으로 정의→ 컴파일러가 컴파일 타임에 코드를 기반으로 완전히 정의된 자료형 만듬
객체 생성(인스턴스화)할 때 최종 자료형을 유추 할 수 있지만,
turbofish
(터보 피쉬… 물고기???)라고 불리는 ::<T>
연산자를 사용해 명시적 지정 가능// 일부만 정의된 struct 자료형 struct BagOfHolding<T> { item: T, } fn main() { // 중요: 여기서 generic 자료형을 쓰면, 컴파일 타임에 생성된 자료형을 생성하게 됨. // Turbofish를 쓰면 명시적일 수 있다. let i32_bag = BagOfHolding::<i32> { item: 42 }; let bool_bag = BagOfHolding::<bool> { item: true }; // Rust는 generic에도 자료형을 유추할 수 있다! let float_bag = BagOfHolding { item: 3.14 }; // 중요: 실생활에서는 가방 속에 가방을 넣지 마시오 let bag_in_bag = BagOfHolding { item: BagOfHolding { item: "쾅!" }, }; println!( "{} {} {} {}", i32_bag.item, bool_bag.item, float_bag.item, bag_in_bag.item.item ); }
아무 것도 없는 것을 표현하기
- 타 언어에서
null
은 변수나 필드를 처리하는 중에 프로그램이 실패를 야기
- Rust에는
null
이 없음 → Rust에서 아무 것도 없음을 표현하는 방법?
None
선택지를 제공하는 방법은null
값이 없는 Rust에서 매우 흔한 패턴
enum Item { Inventory(String), // None은 항목의 부재를 나타냄 None, } struct BagOfHolding { item: Item, }
옵션
Option
이라 불리는 내장된 generic enumenum Option<T> { None, Some(T), }
Option
으로 Some
과 None
으로 enum을 인스턴스화// 일부만 정의된 struct 자료형 struct BagOfHolding<T> { // 자료형 T의 인자는 다른 곳으로 넘겨질 수 있음 item: Option<T>, } fn main() { // 중요: i32를 위한 가방에 아무 것도 안들었다! // Rust가 무슨 자료형의 가방인지 알 수가 없으므로 자료형을 지정해야 함. let i32_bag = BagOfHolding::<i32> { item: None }; if i32_bag.item.is_none() { println!("가방에 아무 것도 없다!") } else { println!("가방에 뭔가 있다!") } let i32_bag = BagOfHolding::<i32> { item: Some(42) }; if i32_bag.item.is_some() { println!("가방에 뭔가 있다!") } else { println!("가방에 아무 것도 없다!") } // match는 Option을 우아하게 분해하고, 모든 케이스를 처리하도록 해준다! match i32_bag.item { Some(v) => println!("가방에서 {}를 찾았다!", v), None => println!("아무 것도 찾지 못했다"), } }
결과
Result
(내장된 generic enum)- 실패할 가능성이 있는 값을 리턴
enum Result<T, E> { Ok(T), Err(E), }
- generic 자료형이 쉼표로 구분된 여러개의 매개화된 자료형(parameterized types)
Ok
와Err
로 enum을 인스턴스화 ← 자주씀
fn do_something_that_might_fail(i: i32) -> Result<f32, String> { if i == 42 { Ok(13.0) } else { Err(String::from("맞는 숫자가 아닙니다")) } } fn main() { let result = do_something_that_might_fail(12); // match는 Result를 우아하게 분해하고, 모든 케이스를 처리하도록 해준다! match result { Ok(v) => println!("{} 발견", v), Err(e) => println!("오류: {}", e), } }
실패할 수 있는 메인
main
은Result
리턴 가능
fn do_something_that_might_fail(i: i32) -> Result<f32, String> { if i == 42 { Ok(13.0) } else { Err(String::from("맞는 숫자가 아닙니다")) } } // Main은 아무 값도 리턴하지 않지만, 오류를 리턴할 수 있다! fn main() -> Result<(), String> { let result = do_something_that_might_fail(12); match result { Ok(v) => println!("{} 발견", v), Err(_e) => { // 이 오류를 우아하게 처리한다 // main으로부터 무슨 일이 발생했는지 새 오류를 리턴한다! return Err(String::from("main에서 뭔가 잘못 되었습니다!")); } } // 모든 일이 잘 끝났음을 표현하기 위해 // Result Ok 안에 unit 값을 쓰고 있는걸 잘 봐두십시오 Ok(()) }
우아한 오류 처리
Result
와 함께 쓸 수 있는 강력한?
연산자
- 아래 두 구문은 동일
do_something_that_might_fail()?
match do_something_that_might_fail() { Ok(v) => v, Err(e) => return Err(e), }
fn do_something_that_might_fail(i: i32) -> Result<f32, String> { if i == 42 { Ok(13.0) } else { Err(String::from("맞는 숫자가 아닙니다")) } } fn main() -> Result<(), String> { // 얼마나 코드를 줄였는지 보세요! let v = do_something_that_might_fail(42)?; /* 아래 코드와 동일 match do_something_that_might_fail(42) { Ok(v) => v, Err(e) => return Err(e), }; */ println!("{} 발견", v); Ok(()) }
추한 옵션/결과 처리
- 빠르고 더러운 방식으로 값을 가져오는데 유용한
unwrap
이라는 함수
- Option/Result 내부의 값을 꺼내오고
- enum이 None/Err인 경우에는
panic!
아래 두 코드는 동일
my_option.unwrap()
match my_option { Some(v) => v, None => panic!("some error message generated by Rust!"), }
아래 두 코드는 비슷
my_result.unwrap()
match my_result { Ok(v) => v, Err(e) => panic!("some error message generated by Rust!"), }
좋은 러스타시안(rustacean)이 되려면 가능한 제대로
match
를 사용!fn do_something_that_might_fail(i: i32) -> Result<f32, String> { if i == 42 { Ok(13.0) } else { Err(String::from("맞는 숫자가 아닙니다")) } } fn main() -> Result<(), String> { // 간결하지만 가정적이고 빠르게 지저분 해짐 let v = do_something_that_might_fail(42).unwrap(); /* match do_something_that_might_fail(42) { Ok(v) => v, Err(e) => panic!("some error message generated by Rust!"), }; */ println!("{} 발견", v); // panic! 될 것임 let v = do_something_that_might_fail(1).unwrap(); println!("{} 발견", v); Ok(()) }
벡터
- collection 자료형 ← 가장 유용한 generic 자료형들 중 하나
- 벡터는
Vec
struct로 표현되는 가변 크기의 리스트
vec!
macro는 벡터를 수동으로 일일이 만드는 대신, 손쉽게 생성
iter()
반복자 메소드
fn main() { // 자료형을 명시적으로 할 수 있음 let mut i32_vec = Vec::<i32>::new(); // Turbofish <3 i32_vec.push(1); i32_vec.push(2); i32_vec.push(3); // 하지만 Rust가 얼마나 똑똑하게 자료형을 자동으로 결정하는지 보십시오 let mut float_vec = Vec::new(); float_vec.push(1.3); float_vec.push(2.3); float_vec.push(3.4); // 아름다운 macro입니다! let string_vec = vec![String::from("Hello"), String::from("World")]; for word in string_vec.iter() { println!("{}", word); } }
메모리 상세
Vec
은 구조체(struct)이지만, 내부적으로는 원소가 힙(heap)에 있는 고정 리스트에 대한 참조를 포함.
벡터는 기본 용량을 가지고 시작. 용량보다 많은 내용물은 힙에 데이터 재할당
The Rust Programming Language 링크
- 9 에러 처리
- 10.1 제네릭 데이터 타입
의문점
- main 함수 리턴 값 프로그램 레벨에서 어떻게 되는지?
- → 리턴 값 = 1, stderr로 에러 메세지 출력됨