4장 - Generic 자료형

 
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 enum
enum 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이라는 함수
  1. Option/Result 내부의 값을 꺼내오고
  1. 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 링크

 

의문점

  • main 함수 리턴 값 프로그램 레벨에서 어떻게 되는지?
    • → 리턴 값 = 1, stderr로 에러 메세지 출력됨