[Rust]
- [Rust] Rust 설치
- [Rust] Hello Cargo
- [Rust] VSCode 설정
- [Rust] Rust 공부 시작
- [Rust] Rust 기초
- [Rust] Rust 제어문
- [Rust] Rust 자료형
- [Rust] Rust 제네릭
- [Rust] Rust 소유권, 대여, 데이터
- [Rust] study Text
- [Rust] Rust 객체지향 프로그래밍
- [Rust] Rust 스마트 포인터
- [Rust] Rust 프로젝트 구성 및 구조
Rust Study Generic
generic 자료형은 Rust에서 엄청나게 중요합니다. 널 허용(nullable) 값을 표현할 때에도 쓰이며 (i.e. 아직 값이 없을 수도 있는 변수), 오류 처리, collection, 등등에도 쓰입니다! 이 장에서는 언제든 사용될만한 기본적인 generic 자료형을 살펴보겠습니다.
1. Generic 자료형이란?
generic 자료형은 struct
나 enum
을 부분적으로 정의하여, 컴파일러가 컴파일 타임에 코드 사용을 기반으로 완전히 정의된 버전을 만들 수 있게 해줍니다.
Rust는 일반적으로 인스턴스화 하는 것을 보고 최종 자료형을 유추할 수 있으나, 여러분의 도움이 필요한 경우에는 turbofish
(내 친한 친구입니다!)로도 알려진 ::<T>
연산자를 사용해 언제든 명시적으로 지정할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 일부만 정의된 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
);
}
$ 42 true 3.14 쾅!
2. 아무 것도 없는 것을 표현하기
다른 언어에서 null
키워드는 값이 없음을 나타내기 위해 사용됩니다. 이는 프로그래밍 언어에 어려움을 유발하는데, 변수나 field를 처리하는 중에 프로그램이 실패할 수도 있기 때문입니다.
Rust에는 null
이 없습니다만, 아무 것도 없음을 표현하는 것의 중요성을 무시하는 것은 아닙니다! 우리가 이미 아는 도구를 사용한 순수한 표현법을 고려해봅시다.
이렇게 한 개 이상의 선택 가능한 값에 대해 None
선택지를 제공하는 방법은 null
값이 없는 Rust에서 매우 흔한 패턴입니다. generic 자료형은 이러한 어려움을 해결하는 데에 도움을 줍니다.
1
2
3
4
5
6
7
8
9
enum Item {
Inventory(String),
// None은 항목의 부재를 나타냄
None,
}
struct BagOfHolding {
item: Item,
}
3.옵션
Rust에는 null
을 쓰지 않고도 nullable한 값을 표현할 수 있는 Option
이라 불리는 내장된 generic enum이 있습니다.
1
2
3
4
enum Option<T> {
None,
Some(T),
}
이 enum은 매우 흔해서, 어디서나 Some
과 None
으로 enum을 인스턴스화 할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 일부만 정의된 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!("아무 것도 찾지 못했다"),
}
}
$ 가방에 아무 것도 없다!
$ 가방에 뭔가 있다!
$ 가방에서 42를 찾았다!
4. 결과
Rust에는 실패할 가능성이 있는 값을 리턴할 수 있도록 해주는 Result
라 불리는 내장된 generic enum이 있습니다. 이는 Rust에서 오류 처리를 하는 관용적인 방법입니다.
1
2
3
4
enum Result<T, E> {
Ok(T),
Err(E),
}
우리의 generic 자료형이 쉼표로 구분된 여러개의 매개화된 자료형(parameterized types)을 갖고 있는 것을 기억해 두기 바랍니다.
이 enum은 매우 흔해서, 어디서나 Ok
와 Err
로 enum을 인스턴스화 할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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),
}
}
$ 오류: 맞는 숫자가 아닙니다
5. 실패할 수 있는 메인
main
은 Result
를 리턴할 수 있다!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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(())
}
6. 우아한 오류 처리
Rust는 매우 흔히 사용되는 Result
와 함께 쓸 수 있는 강력한 연산자 ?
를 갖고 있습니다. 다음 두 구문은 동일합니다:
1
2
3
4
5
6
7
//줄였을때
do_something_that_might_fail()?
//풀어쓸때
match do_something_that_might_fail() {
Ok(v) => v,
Err(e) => return Err(e),
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)?;
println!("{} 발견", v);
Ok(())
}
$ 13 발견
7.추한 옵션/결과 처리
간단한 코드를 짤 때에도 Option
/Result
를 쓰는 것은 귀찮은 일일 수 있습니다. Option
과 Result
둘 다 빠르고 더러운 방식으로 값을 가져오는데 유용한 unwrap
이라는 함수를 갖고 있습니다. unwrap
은:
- Option/Result 내부의 값을 꺼내오고
- enum이 None/Err인 경우에는 panic!
다음 두 코드는 동일합니다:
1
2
3
4
5
6
7
8
9
10
11
12
//unwrap Option
my_option.unwrap()
match my_option {
Some(v) => v,
None => panic!("some error message generated by Rust!"),
}
//unwrap Result
my_result.unwrap()
match my_result {
Ok(v) => v,
Err(e) => panic!("some error message generated by Rust!"),
}
좋은 러스타시안(rustacean)이 되어 가능한 제대로 match
를 사용하세요!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();
println!("{} 발견", v);
// panic! 될 것임
let v = do_something_that_might_fail(1).unwrap();
println!("{} 발견", v);
Ok(())
}
$ 13 발견
8. 벡터
가장 유용한 generic 자료형들 중 하나가 collection 자료형입니다. vector는 Vec
struct로 표현되는 가변 크기의 리스트입니다.
vec!
macro는 vector를 수동으로 일일이 만드는 대신, 손쉽게 생성할 수 있게 해줍니다.
Vec
는 vector를 for
반복문에 손쉽게 넣을 수 있도록 vector로부터 반복자를 생성할 수 있는 iter()
메소드를 갖고 있습니다.
메모리 상세:
Vec
은 struct이나, 내부적으로는 내용물이 heap에 있는 고정 리스트에 대한 참조를 포함하고 있습니다.- vector는 기본 용량을 갖고 시작하는데, 용량보다 많은 내용물이 추가될 경우, 큰 용량을 가진 새 고정 리스트를 위해 heap에 데이터를 재할당합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
}
}
$ Hello
$ World
마무리
rust를 공부하다보니 js
에 try/catch
문을 rust
에서는 match
로 잡아내는거 같다.
unwrap()
사용하기도 하지만 좋지않은방법 match
를 사용하자.
그러면서 match
를 사용 안하고 함수뒤에 ?
를 붙여 에러처리를 하기도하네 신기방기
다음 장에서는 Rust의 중요한 개념인 데이터 소유권(data ownership)에 대해 레스기릿.