├── .github
└── workflows
│ ├── mdbook.yaml
│ └── rust.yaml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── book.toml
├── marktest
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── build.rs
├── src
│ └── main.rs
└── tests
│ └── skeptic.rs
├── slides
├── ch1.md
├── ch1.pdf
├── ch10.md
├── ch10.pdf
├── ch11.md
├── ch11.pdf
├── ch12.md
├── ch12.pdf
├── ch13.md
├── ch13.pdf
├── ch14.md
├── ch14.pdf
├── ch15.md
├── ch15.pdf
├── ch2.md
├── ch2.pdf
├── ch3.md
├── ch3.pdf
├── ch4.md
├── ch4.pdf
├── ch5-2.md
├── ch5-2.pdf
├── ch5.md
├── ch5.pdf
├── ch6.md
├── ch6.pdf
├── ch7.md
├── ch7.pdf
├── ch8.md
├── ch8.pdf
├── ch9.md
├── ch9.pdf
└── template.md
└── src
├── SUMMARY.md
├── assets
├── ch01-02-1.jpg
├── ch01-02-2.png
├── ch01-02-3.png
├── ch01-03-01.png
├── ch01-03-02.png
├── ch01-03-03.png
├── ch01-03-04.png
├── ch01-04-01.png
├── ch01-1.png
├── ch01-2.png
├── ch01-3.png
├── ch01-4.png
├── ch01-5.png
├── ch01-6.png
├── ch02-1.png
├── ch02-2.png
├── ch02-3.png
├── ch02-4.png
├── ch13-1.png
├── ch14-1.png
└── ch14-2.png
├── ch1-00.md
├── ch1-01.md
├── ch1-02.md
├── ch1-03.md
├── ch1-04.md
├── ch10-00.md
├── ch10-01.md
├── ch10-02.md
├── ch10-03.md
├── ch10-04.md
├── ch11-00.md
├── ch11-01.md
├── ch11-02.md
├── ch11-03.md
├── ch12-00.md
├── ch12-01.md
├── ch12-02.md
├── ch12-03.md
├── ch13-00.md
├── ch13-01.md
├── ch13-02.md
├── ch13-03.md
├── ch14-00.md
├── ch14-01.md
├── ch14-02.md
├── ch14-03.md
├── ch15-00.md
├── ch15-01.md
├── ch15-02.md
├── ch15-03.md
├── ch2-00.md
├── ch2-01.md
├── ch2-02.md
├── ch2-03.md
├── ch2-04.md
├── ch2-05.md
├── ch3-00.md
├── ch3-01.md
├── ch3-02.md
├── ch3-03.md
├── ch4-00.md
├── ch4-01.md
├── ch4-02.md
├── ch4-03.md
├── ch4-04.md
├── ch4-05.md
├── ch5-00.md
├── ch5-01.md
├── ch5-02.md
├── ch5-03.md
├── ch6-00.md
├── ch6-01.md
├── ch6-02.md
├── ch6-03.md
├── ch7-00.md
├── ch7-01.md
├── ch7-02.md
├── ch8-00.md
├── ch8-01.md
├── ch8-02.md
├── ch9-00.md
├── ch9-01.md
├── ch9-02.md
├── ch9-03.md
├── ch9-04.md
└── project.md
/.github/workflows/mdbook.yaml:
--------------------------------------------------------------------------------
1 | name: Deploy mdbook
2 |
3 | on:
4 | push:
5 | branches: ["main"]
6 | pull_request:
7 | branches: ["main"]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v3
18 |
19 | - name: mdBook Action
20 | uses: peaceiris/actions-mdbook@v1.2.0
21 |
22 | - run: mdbook build
23 |
24 | - name: Deploy
25 | uses: peaceiris/actions-gh-pages@v3
26 | if: ${{ github.ref == 'refs/heads/main' }}
27 | with:
28 | github_token: ${{ secrets.GITHUB_TOKEN }}
29 | publish_dir: ./book
30 |
--------------------------------------------------------------------------------
/.github/workflows/rust.yaml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on:
4 | push:
5 | branches: ["main"]
6 | pull_request:
7 | branches: ["main"]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Run tests
19 | run: cargo test --verbose
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | *target*
4 |
5 | book/
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["marktest"]
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 파이썬과 비교하며 배우는 러스트 프로그래밍
2 |
3 | 이 책은 러스트를 배우고 싶은 파이썬 프로그래머를 위해 만들어졌습니다. 러스트 언어의 핵심적인 개념을 파이썬 코드와 러스트 코드를 1:1로 비교하며 배울 수 있습니다.
4 |
5 | # 하드카피 출간!
6 |
7 | 더 자세한 설명, 다양한 예제가 있는 출판본도 서점에서 만나보실 수 있습니다.
8 |
9 |
10 |
11 | https://www.yes24.com/Product/Goods/126578252
12 |
13 | # 온라인 강의
14 |
15 | 책 출간에 맞추어 인프런에서 새로운 강의 "세상에서 제일 쉬운 러스트 프로그래밍"도 시작했습니다 ✨
16 |
17 | [강의 바로가기](https://inf.run/kXsni)
18 |
19 | ## 기여
20 |
21 | 이 책에 기여하고자 하시는 분은 Pull request를 만들어주세요.
22 |
23 | ## 라이센스
24 |
25 | 이 책은 CC-BY-4.0-SA 라이센스를 따릅니다. 자세한 내용은 [LICENSE](LICENSE) 파일을 참고해주세요.
26 |
--------------------------------------------------------------------------------
/book.toml:
--------------------------------------------------------------------------------
1 | [book]
2 | authors = ["Indosaram"]
3 | language = "kr"
4 | multilingual = false
5 | src = "src"
6 | title = "파이썬과 비교하며 배우는 러스트 프로그래밍"
7 |
8 | [output.html]
9 | git-repository-url = "https://github.com/Indosaram/rust-python-book"
10 |
--------------------------------------------------------------------------------
/marktest/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/marktest/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "marktest"
3 | version = "0.1.0"
4 | edition = "2021"
5 | build = "build.rs"
6 |
7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8 |
9 | [dependencies]
10 |
11 | [dev-dependencies]
12 | skeptic = "0.13.7"
13 |
14 | [build-dependencies]
15 | skeptic = "0.13.0"
16 |
--------------------------------------------------------------------------------
/marktest/build.rs:
--------------------------------------------------------------------------------
1 | extern crate skeptic;
2 |
3 | fn main() {
4 | // Get list of .md files in src directory
5 | let files = std::fs::read_dir("../src").unwrap();
6 |
7 | // To a array
8 | let files: Vec<_> = files.map(|f| f.unwrap().path()).collect();
9 |
10 | skeptic::generate_doc_tests(&files);
11 | }
12 |
--------------------------------------------------------------------------------
/marktest/src/main.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | println!("Hello, world!");
3 | }
4 |
--------------------------------------------------------------------------------
/marktest/tests/skeptic.rs:
--------------------------------------------------------------------------------
1 | include!(concat!(env!("OUT_DIR"), "/skeptic-tests.rs"));
2 |
--------------------------------------------------------------------------------
/slides/ch1.md:
--------------------------------------------------------------------------------
1 | ---
2 | marp: true
3 | paginate: true
4 | theme: default
5 |
6 | ---
7 |
8 | # 파이썬 프로그래머를 위한 러스트 입문
9 |
10 | 윤인도
11 | freedomzero91@gmail.com
12 |
13 | ---
14 |
15 | ## 가장 사랑받는 언어, 러스트
16 |
17 | ```python
18 | print("Hello, Pythonista!")
19 | ```
20 |
21 | ```rust
22 | fn main() {
23 | println!("Hello, Rustacean!");
24 | }
25 | ```
26 |
27 | ---
28 |
29 | "Loved vs Dreaded(사랑하는 언어 대 두려운 언어)"
30 |
31 | 
32 |
33 | ---
34 |
35 |
36 | 
37 |
38 | ---
39 |
40 | ## 파이썬 개발자가 러스트를 배워야 하는 이유
41 |
42 | 1. 멀티스레드 프로그램 작성이 쉽다
43 | 2. 속도가 빠르다
44 | 3. 파이썬과 문법이 비슷하다
45 |
46 |
47 | ---
48 |
49 | ## 파이썬과 러스트의 차이점
50 |
51 | ### 언어상의 차이
52 |
53 |
54 | | 파이썬 | 러스트 |
55 | | --------------------------------- | -------------------------------- |
56 | | 인터프리터 언어 | 컴파일 언어 |
57 | | 강타입 언어이면서 동적 타입 언어 | 강타입 언어이면서 정적 타입 언어 |
58 | | 메모리 관리에 가비지 콜렉터 사용 | 메모리 관리에 소유권 모델 사용 |
59 | | 대부분의 경우 객체지향 프로그래밍 | 함수형 프로그래밍 |
60 | | 스타일 가이드가 유연함 | 명확한 스타일 가이드 존재 |
61 |
62 | ---
63 |
64 | ### 툴 비교
65 |
66 | > "cargo"
67 |
68 | | | 파이썬 | 러스트 |
69 | | ------------------ | -------------------------------- | ------------ |
70 | | 패키지 관리자 | pip | cargo |
71 | | 포매터 | black, yapf, autopep8 | cargo fmt |
72 | | 린터 | pylint, flake8 | cargo clippy |
73 | | 테스트 | pytest | cargo test |
74 | | 프로젝트 환경 관리 | virtualenv, pipenv, pyenv, conda | cargo new |
75 | | 문서화 | sphinx | cargo doc |
76 | | 벤치마크 | cProfile, pyspy | cargo bench |
77 |
78 | ---
79 |
80 | 
81 |
82 | https://docs.rs/serde_v8/0.49.0/serde_v8/
83 |
84 | ---
85 |
86 | ### 그러면 러스트는 또 다른 C/C++ 대체 언어인가요?
87 |
88 | | Apple | Google | Mozilla |
89 | | ------------------------- | ------------------------------------------ | ------------------------------------------------------------- |
90 | | Swift | Go | Rust |
91 | | Mainly for iOS apps | Dominant in network/server applications | Dominant in system programming and CPU intensive applications |
92 | | Memory leak still present | Possible memory leak through `goroutine` | Guarantees no memory leak |
93 |
94 |
95 | ---
96 |
97 | ## Rust로 뭘 할 수 있나요?
98 |
99 | 
100 | - [솔라나(Solana)](https://github.com/solana-labs/solana)
101 | - [AppFlowy](https://github.com/AppFlowy-IO/appflowy)
102 |
103 | ---
104 |
105 | ### 러스트 사용 실제 사례들
106 |
107 | 코어 로직을 러스트로 재작성
108 |
109 | #### [Dropbox](https://dropbox.tech/infrastructure/rewriting-the-heart-of-our-sync-engine)
110 |
111 |
112 | #### [Figma](https://blog.figma.com/rust-in-production-at-figma-e10a0ec31929)
113 |
114 | 
115 |
116 | ---
117 |
118 | #### [npm](https://www.rust-lang.org/static/pdfs/Rust-npm-Whitepaper.pdf)
119 |
120 | 레지스트리 서비스(registry service)의 병목 현상을 해결
121 |
122 | #### [Discord](https://discord.com/blog/why-discord-is-switching-from-go-to-rust)
123 |
124 | Go에서 Rust로 이전
125 |
126 | 
127 |
128 | ---
129 | - 페이스북에서는 백엔드 서버를 작성하는 언어 중 하나로 러스트를 채택했습니다.
130 | - 러스트의 후원 재단인 모질라에서 개발하는 파이어폭스 브라우저의 엔진(Servo Engine)은 러스트로 작성되었습니다.
131 | - Next.js의 컴파일 엔진은 러스트로 재작성되었습니다.
132 | - AWS(아마존웹서비스)의 Lambda에서 컨테이너는 FireCracker라는 러스트 툴 위에서 실행됩니다.
133 | - Sentry 역시 파이썬의 낮은 퍼포먼스를 러스트를 도입해 해결했습니다.
134 | ---
135 | ## 러스트 개발 환경 설정하기
136 | ### 러스트 툴체인 설치하기: https://rustup.rs/#
137 |
138 | #### macOS / Linux
139 |
140 | 맥(macOS) 또는 리눅스 사용자들은 아래 명령어를 통해 간단하게 설치가 가능합니다.
141 |
142 | ```bash
143 | $ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
144 | ```
145 | #### Windows
146 |
147 | 윈도우 사용자의 경우 위 홈페이지에서 34비트 또는 64비트 설치 파일을 다운로드 받습니다.
148 |
149 | ---
150 | ### Visual Studio Code 설치 및 설정하기
151 |
152 | 러스트에서 제공하는 컴파일, 디버깅, 언어 서버(Language server) 등의 기능을 쉽고 편리하게 사용 가능
153 |
154 | #### rust-analyzer 설치하기
155 |
156 | ---
157 | #### VSCode에서 코드 실행해보기
158 | - 메뉴에서 File - Open Folder를 클릭합니다.
159 |
160 | - 프로젝트 폴더를 선택하거나 새로 생성한 다음 선택합니다.
161 |
162 | - 터미널을 실행합니다. 메뉴에서 Terminal - New Terminal
163 |
164 | - 새로운 프로젝트를 현재 폴더에 생성합니다. 터미널에 아래 명령어를 입력하고 실행합니다.
165 | ```bash
166 | $ cargo new rust_part
167 | ```
168 |
169 | ---
170 | - `rust_part` 폴더로 이동
171 | - `cargo run` 명령어로 기본 코드를 컴파일하고, 바이너리를 실행합니다.
172 |
173 | ```bash
174 | $ cargo run
175 | Compiling temp v0.1.0 (/Users/code/temp)
176 | Finished dev [unoptimized + debuginfo] target(s) in 4.55s
177 | Running `target/debug/temp`
178 | Hello, world!
179 | ```
180 | ---
181 | #### rustfmt 사용하기
182 |
183 | - 윈도우 또는 리눅스의 경우는 Alt + Shift + F
184 | - 맥의 경우는 Option + Shift + F를 누르면 됩니다.
185 |
186 | `main.rs` 에 입력하고 포맷을 실행해 보겠습니다.
187 |
188 | ```rust
189 | fn main( ){
190 | println! (
191 | "Please run 'rustfmt!'"
192 | );
193 | }
194 | ```
195 |
196 | 실행 결과
197 |
198 | ```rust
199 | fn main() {
200 | println!("Please run 'rustfmt!'");
201 | }
202 |
203 | ```
204 |
205 | ---
206 | ## 파이썬 프로젝트 생성하기
207 | 현재 폴더에 `python` 폴더 생성
208 | ```
209 | .
210 | ├── rust_part
211 | │ ├── Cargo.toml
212 | │ └── src
213 | └── python
214 | └── main.py
215 | ```
216 |
217 | ---
218 | ## 러스트 폴더 구조
219 |
220 | 러스트의 프로젝트 폴더에는 다음과 같은 파일 구조가 만들어집니다.
221 |
222 | ```
223 | .
224 | ├── Cargo.toml
225 | └── src
226 | └── main.rs
227 | ```
228 | `Cargo.toml` 파일은 프로젝트의 모든 설정값을 가지고 있는 파일
229 | ```toml
230 | [package]
231 | name = "rust_part"
232 | version = "0.1.0"
233 | edition = "2021"
234 |
235 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
236 |
237 | [dependencies]
238 |
239 | ```
240 | ---
241 | - `[package]` 부분에는 현재 프로젝트의 이름과 버전, 그리고 러스트 에디션 버전이 들어 있습니다.
242 |
243 | - `[dependencies]` 는 프로젝트의 크레이트(러스트에서는 패키지를 크레이트(crate)라고 부릅니다)의 이름과 버전이 들어가게 됩니다.
244 |
245 | - `src` 폴더가 실제 러스트 소스코드가 들어가는 곳입니다.
246 |
247 | ---
--------------------------------------------------------------------------------
/slides/ch1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch1.pdf
--------------------------------------------------------------------------------
/slides/ch10.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch10.pdf
--------------------------------------------------------------------------------
/slides/ch11.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch11.pdf
--------------------------------------------------------------------------------
/slides/ch12.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch12.pdf
--------------------------------------------------------------------------------
/slides/ch13.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch13.pdf
--------------------------------------------------------------------------------
/slides/ch14.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch14.pdf
--------------------------------------------------------------------------------
/slides/ch15.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch15.pdf
--------------------------------------------------------------------------------
/slides/ch2.md:
--------------------------------------------------------------------------------
1 | ---
2 | marp: true
3 | paginate: true
4 | theme: default
5 |
6 | ---
7 |
8 | # 파이썬 프로그래머를 위한 러스트 입문
9 |
10 | 윤인도
11 | freedomzero91@gmail.com
12 |
13 | ---
14 | # CH2. 변수
15 |
16 | ---
17 |
18 | ## 값 출력하기
19 |
20 | ```python
21 | print("Hello, world!")
22 | ```
23 |
24 | ```rust
25 | fn main() {
26 | println!("Hello, world!");
27 | }
28 |
29 | ```
30 |
31 | ---
32 | ## 변수 선언
33 |
34 | ```python
35 | x = 1.0
36 | y = 10
37 |
38 | print(f"x = {x}, y = {y}")
39 | ```
40 |
41 | 파이썬 코드 실행 결과는 다음과 같습니다. 폴더를 하위 폴더인 "python"으로 이동한 다음 코드를 실행해야 합니다.
42 |
43 | ```bash
44 | /code/temp/python $ python main.py
45 | x = 1.0, y = 10
46 | ```
47 |
48 | ---
49 |
50 | ```rust
51 | 변수명 타입 값
52 | let x: i32 = 10;
53 | ```
54 | 대부분의 경우에서는 컴파일러가 타입을 추측해주지만, 몇몇 경우에는 직접 타입을 명시해줘야 하기도 합니다.
55 | ```rust
56 | fn main() {
57 | let x: f64 = 1.0;
58 | let y = 10;
59 |
60 | println!("x = {}, y = {}", x, y);
61 | }
62 | ```
63 | ---
64 | 하위 폴더인 "rust_part" 폴더로 이동한 다음, `cargo run` 을 실행해 결과를 확인해보겠습니다.
65 |
66 | ```bash
67 | /code/temp/rust_part $ cargo run
68 | x = 1, y = 10
69 | ```
70 |
71 | ---
72 |
73 | VSCode 터미널의 분할(split) 기능을 사용하면 편리합니다.
74 |
75 | 
76 |
77 | ---
78 | ## 작명 규칙
79 |
80 | 파이썬과 러스트의 작명 규칙은 거의 동일합니다. 변수의 경우, 둘 다 스네이크 케이스(snake case)를 사용합니다.
81 |
82 | ```python
83 | snake_case = 3
84 | ```
85 |
86 | ```rust
87 | let snake_case = 3;
88 | ```
89 |
90 | ---
91 | 상수의 경우는 둘 다 스크리밍 스네이크 케이스(Screaming snake case)를 사용합니다.
92 |
93 | ```python
94 | SCREAMING_SNAKE_CASE = 1
95 | ```
96 |
97 |
98 | ```rust
99 | const SCREAMING_SNAKE_CASE: i32 = 1;
100 | ```
101 |
102 | ---
103 | ## 불변성
104 |
105 | 파이썬의 변수는 언제든 다른 타입의 값을 넣을 수 있습니다.
106 |
107 | ```python
108 | x = 1
109 | x = "2"
110 | x = 3.141592
111 | ```
112 |
113 | ---
114 | ```rust
115 | fn main() {
116 | let x = 1;
117 | x = 2; // won't compile!
118 | println!("{}", x);
119 | }
120 |
121 | ```
122 | 위 코드를 실행해보면 다음과 같은 에러가 발생합니다.
123 |
124 | ```
125 | error[E0384]: cannot assign twice to immutable variable `x`
126 | --> src/main.rs:3:5
127 | |
128 | 2 | let x = 1;
129 | | -
130 | | |
131 | | first assignment to `x`
132 | | help: consider making this binding mutable: `mut x`
133 | 3 | x = 2; // won't compile!
134 | | ^^^^^ cannot assign twice to immutable variable
135 |
136 | ```
137 | ---
138 | ```rust
139 | let mut x = 1;
140 | ```
141 |
142 | 컴파일러의 조언에 따라 수정된 코드를 아래와 같이 작성하고 실행해봅시다.
143 |
144 | ```rust
145 | fn main() {
146 | let mut x = 1;
147 | x = 2;
148 | println!("{}", x);
149 | }
150 |
151 | ```
152 | 값을 바꾸고자 하는 변수에는 `mut` 키워드로 가변성을 부여해야 합니다.
153 |
154 | ---
155 | ## 섀도잉
156 | 한번 선언한 불변 변수의 값을 변경하는 것은 불가능하지만, 변수 자체를 새로 선언하는 것은 가능합니다. 이렇게 변수 이름을 재사용해서 새로운 변수를 다시 선언하는 것을 섀도잉(shadowing)이라고 합니다.
157 |
158 | ```rust
159 | fn main() {
160 | let x = "5";
161 |
162 | let x = 6; // x is redeclared as 6
163 |
164 | println!("The value of x is: {}", x); // 6
165 | }
166 |
167 | ```
168 |
169 | ---
170 | ## 타입
171 |
172 | C언어 계열과 마찬가지로, Rust는 타입이 존재합니다. 러스트의 원시 타입(primitive type) 목록은 다음과 같습니다.
173 |
174 | ---
175 |
176 | | 이름 | 타입 | 이름 | 타입 |
177 | | ---- | ---- | ---- | ---- |
178 | | 8비트 정수 | `i8` | 부호 없는 32비트 정수 | `u32` |
179 | | 16비트 정수 | `i16` | 부호 없는 64비트 정수 | `u64` |
180 | | 32비트 정수 | `i32` | 부호 없는 128비트 정수 |`u128` |
181 | | 64비트 정수 | `i64` | 부호 없는 아키텍처 |`usize` |
182 | | 128비트 정수 | `i128` | 불리언 | `bool` |
183 | | 아키텍처 | `isize` | 문자열 | `String` |
184 | | 부호 없는 8비트 정수 | `u8` | 문자열 슬라이스 | `str` |
185 | | 부호 없는 16비트 정수 |`u16` | 32비트 부동소수점 실수 | `f32` |
186 | | | |64비트 부동소수점 실수 | `f64` |
187 |
188 | ---
189 | 다음 코드를 VSCode에 붙여넣으면 아래 그림과 같이 타입이 추론되는 것을 볼 수 있습니다.
190 |
191 | ```rust
192 | fn main(){
193 | let x = 1;
194 | let y = 1.0;
195 | println!("{} {}", x, y);
196 | }
197 | ```
198 |
199 | 
200 |
201 | ---
202 |
203 | 변수의 타입을 다른 타입으로 바꾸는 타입 변환(Casting)도 가능합니다. 파이썬에서는 타입 이름을 바로 사용해 타입 변환을 수행합니다.
204 |
205 | ```python
206 | x = 1.2
207 | y = int(x)
208 | print(f"{x} -> {y}");
209 | ```
210 |
211 | 실행결과
212 |
213 | ```
214 | 1.2 -> 1
215 | ```
216 |
217 |
218 | ---
219 |
220 | 러스트에서는 아래와 같이 `as` 키워드를 사용하면 됩니다.
221 |
222 | ```rust
223 | fn main() {
224 | let x: f64 = 1.2;
225 | let y = x as i32;
226 | println!("{} -> {}", x, y);
227 | }
228 |
229 | ```
230 |
231 | 실행결과
232 |
233 | ```
234 | 1.2 -> 1
235 | ```
236 |
237 | ---
238 | ## 상수
239 |
240 | 상수(constant)란, 한 번 선언되면 값이 바뀌지 않는 변수를 의미합니다. 먼저 파이썬에서 상수를 다음과 같이 선언해 보겠습니다.
241 |
242 | ```python
243 | THRESHOLD = 10
244 |
245 |
246 | def is_big(n: int) -> bool:
247 | return n > THRESHOLD
248 |
249 |
250 | if __name__ == '__main__':
251 | print(THRESHOLD)
252 | print(is_big(THRESHOLD))
253 |
254 | THRESHOLD = 5
255 |
256 | ```
257 |
258 | ---
259 | ```rust
260 | const THRESHOLD: i32 = 10;
261 |
262 | fn is_big(n: i32) -> bool {
263 | n > THRESHOLD
264 | }
265 |
266 | fn main() {
267 | println!("{}", THRESHOLD);
268 | println!("{}", is_big(5));
269 | }
270 |
271 | ```
272 |
273 | 실행결과
274 |
275 | ```
276 | 10
277 | false
278 | ```
279 |
280 | ---
281 |
282 | 값이 불변이기 때문에 `THRESHOLD = 5;`와 같이 새로운 값을 할당하게 되면 오류가 발생합니다.
283 |
284 | ```rust
285 | ...
286 | THRESHOLD = 5;
287 | ...
288 |
289 | ```
290 |
291 | 실행결과
292 |
293 | ```
294 | --> src/main.rs:11:15
295 | |
296 | 11 | THRESHOLD = 5;
297 | | --------- ^
298 | | |
299 | | cannot assign to this expression
300 | ```
301 |
302 | ---
303 |
304 | 컴파일러가 친절하게 상수 `THRESHOLD` 에는 새로운 값을 할당할 수 없다고 알려주게 됩니다. 실행하기 전 편집기 안에서도 빨간 줄로 해당 코드에 문제가 있음을 알려주기 때문에 문제를 빠르게 찾고 해결할 수 있습니다.
305 |
306 | 
307 |
308 | ---
309 |
310 |
311 | ---
312 |
313 |
314 | ---
315 |
316 |
317 | ---
318 |
319 |
320 | ---
321 |
322 |
323 | ---
324 |
325 |
326 | ---
327 |
328 |
329 | ---
330 |
331 |
332 | ---
--------------------------------------------------------------------------------
/slides/ch2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch2.pdf
--------------------------------------------------------------------------------
/slides/ch3.md:
--------------------------------------------------------------------------------
1 | ---
2 | marp: true
3 | paginate: true
4 | theme: default
5 |
6 | ---
7 |
8 | # 파이썬 프로그래머를 위한 러스트 입문
9 |
10 | 윤인도
11 | freedomzero91@gmail.com
12 |
13 | ---
14 | # CH3. 함수
15 |
16 | 이번 챕터에서는 함수의 선언과 사용에 대해서 자세히 다루겠습니다.
17 |
18 | ---
19 |
20 | ## 함수 선언
21 |
22 | 파이썬
23 |
24 | ```python
25 | def add(num1: int, num2: int) -> int:
26 | return num1 + num2
27 | ```
28 |
29 | ---
30 | 러스트
31 |
32 | 함수의 선언에 `fn` 키워드를 사용하고, 함수에서 실행할 코드를 중괄호로 묶어줍니다. 그리고 파이썬과 비슷하게 파라미터에는 `:i32`로 타입을 표기하고, 리턴값에는 `-> i32`처럼 화살표를 사용해 타입을 명시했습니다.
33 |
34 |
35 |
36 | > 이때 주의해야 하는 점은 반드시 파라미터와 리턴 타입을 명시해야 한다는 것입니다.
37 |
38 |
39 |
40 | ```rust,ignore
41 | fn add(num1: i32, num2: i32) -> i32 {
42 | return num1 + num2;
43 | }
44 | ```
45 |
46 | ---
47 |
48 | 러스트는 코드 마지막에서 `return` 키워드를 생략할 수 있습니다. 이때 세미콜론이 없다는 점에 주의하세요.
49 |
50 | ```rust,ignore
51 | fn add(num1: i32, num2: i32) -> i32 {
52 | num1 + num2
53 | }
54 | ```
55 |
56 | ---
57 | 이제 `add` 함수를 메인 함수에서 호출하고 값을 프린트해 보겠습니다.
58 |
59 | ```rust
60 | fn add(num1: i32, num2: i32) -> i32 {
61 | num1 + num2
62 | }
63 |
64 | fn main() {
65 | println!("{}", add(1, 2));
66 | }
67 |
68 | ```
69 |
70 | 실행 결과
71 |
72 | ```
73 | 3
74 | ```
75 |
76 |
77 | ---
78 | 파이썬에서 `swap` 이라는 함수를 아래와 같이 구현합니다.
79 |
80 | ```python
81 | def swap(num1: int, num2: int) -> tuple[int, int]:
82 | return num2, num1
83 |
84 |
85 | num1, num2 = swap(1, 2)
86 | print(f"{num1}, {num2}")
87 |
88 | ```
89 |
90 | 실행 결과
91 |
92 | ```
93 | 2, 1
94 | ```
95 |
96 | ---
97 | 러스트도 여러 개의 값을 리턴하는 경우, 값들이 튜플로 묶이게 됩니다.
98 | 따라서 함수의 리턴 타입도 튜플로 `(i32, i32)` 표기합니다.
99 |
100 | ```rust
101 | fn swap(num1: i32, num2: i32) -> (i32, i32) {
102 | (num2, num1)
103 | }
104 |
105 | fn main() {
106 | let (num1, num2) = swap(1, 2);
107 | println!("{num1}, {num2}");
108 | }
109 |
110 | ```
111 |
112 | 실행 결과
113 |
114 | ```
115 | 2, 1
116 | ```
117 |
118 |
119 |
120 | ---
121 | ## 스코프
122 |
123 | 스코프(scope)란 변수에 접근할 수 있는 범위를 의미합니다. 먼저 파이썬에서는 스코프를 기본적으로 함수 단위로 구분합니다.
124 |
125 | > 실제로는 파이썬은 LEGB 룰이라고 불리는 좀더 복잡한 스코프 규칙을 가지고 있지만, 여기서는 단순화해서 함수 기준으로 설명합니다.
126 |
127 |
128 |
129 | ---
130 |
131 | ```python
132 | def hello(name: str):
133 | num = 3
134 | print(f"Hello {name}")
135 |
136 |
137 | if __name__ == '__main__':
138 | my_name = "buzzi"
139 |
140 | if True:
141 | print("My name is", my_name)
142 | my_name = "mellon"
143 |
144 | hello(my_name)
145 |
146 | # print(num) # error
147 |
148 | ```
149 |
150 | 실행 결과
151 |
152 | ```python
153 | My name is buzzi
154 | Hello mellon
155 | ```
156 |
157 | ---
158 | ```rust
159 | fn hello(name: String) {
160 | let num = 3;
161 | println!("Hello {}", name);
162 | }
163 |
164 | fn main() {
165 | let my_name = "buzzi".to_string();
166 |
167 | {
168 | println!("My name is {}", my_name);
169 | let my_name = "mellon";
170 | }
171 |
172 | hello(my_name);
173 |
174 | // println!("{}", num); // error
175 | }
176 |
177 | ```
178 |
179 | 실행 결과
180 |
181 | ```
182 | My name is buzzi
183 | Hello buzzi
184 | ```
185 |
186 | ---
187 | ## 익명 함수
188 |
189 | 익명 함수란 이름이 없는 함수라는 뜻으로, 프로그램 내에서 변수에 할당하거나 다른 함수에 파라미터로 전달되는 함수입니다. 따라서 익명 함수를 먼저 만들어 놓고 나중에 함수를 실행할 수 있습니다.
190 |
191 | 파이썬에서는 `lambda` 키워드를 사용합니다.
192 | ```python
193 | my_func = lambda x: x + 1
194 | print(my_func(3))
195 |
196 | ```
197 |
198 | ---
199 | 러스트에도 람다 함수와 비슷한 개념이 있는데 바로 클로저(Closure)입니다.
200 | 클로저는 파라미터를 `| |` 의 사이에 선언하고, 그 뒤에 함수에서 리턴하는 부분을 작성합니다.
201 |
202 | ```rust
203 | fn main() {
204 | let my_func = |x| x + 1;
205 | println!("{}", my_func(3));
206 | }
207 |
208 | ```
209 |
210 | ---
211 | 이때 컴파일러가 클로저의 파라미터와 리턴값의 타입을 `i32`로 추측해서 보여줍니다.
212 | 하지만 타입을 명시하는 것도 가능합니다.
213 |
214 | ```rust
215 | fn main() {
216 | let my_func = |x: i32| -> i32 { x + 1 };
217 | println!("{}", my_func(3));
218 | }
219 |
220 | ```
221 |
222 | ---
223 |
224 | ### Quiz
225 |
226 | 1. 두 개의 정수를 인자로 받아 두 정수의 곱을 반환하는 클로저를 작성해 보세요.
227 | ```rust
228 | fn main() {
229 | let multiply_numbers = |?| -> ? { };
230 |
231 | let result = multiply_numbers(3, 4);
232 | println!("The product of 3 and 4 is: {}", result); // 12
233 | }
234 | ```
235 |
236 | ---
237 |
238 | 정답1
239 | ```rust
240 | fn main() {
241 | let multiply_numbers = |a: i32, b: i32| -> i32 { a * b };
242 |
243 | let result = multiply_numbers(3, 4);
244 | println!("The product of 3 and 4 is: {}", result);
245 | }
246 |
247 | ```
248 |
249 | ---
250 | 2. 두 개의 정수를 인자로 받아 두 정수 중 더 큰 값을 반환하는 함수 `find_max`를 작성합니다.
251 | ```rust
252 | fn find_max(?) ? {
253 | }
254 |
255 | fn main() {
256 | let result = find_max(3, 4);
257 | println!("The larger number is: {}", result); // 4
258 | }
259 | ```
260 | ---
261 | 정답2
262 | ```rust
263 | fn find_max(a: i32, b: i32) -> i32 {
264 | if a > b {
265 | a
266 | } else {
267 | b
268 | }
269 | }
270 |
271 | fn main() {
272 | let result = find_max(3, 4);
273 | println!("The larger number is: {}", result);
274 | }
275 |
276 |
277 | ```
278 |
279 | ---
--------------------------------------------------------------------------------
/slides/ch3.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch3.pdf
--------------------------------------------------------------------------------
/slides/ch4.md:
--------------------------------------------------------------------------------
1 | ---
2 | marp: true
3 | paginate: true
4 | theme: default
5 | ---
6 |
7 | # 파이썬 프로그래머를 위한 러스트 입문
8 |
9 | 윤인도
10 | freedomzero91@gmail.com
11 |
12 | ---
13 | # CH4. 흐름제어
14 |
15 | 다음은 프로그램의 논리적 흐름을 결정할 수 있는 흐름제어문(control flow)에 대해서 알아보겠습니다.
16 |
17 |
18 | ---
19 | ## if/else
20 |
21 | `if` 문은 어떤 조건을 만족하는 경우, 그에 해당하는 코드를 실행하도록 논리적 분기를 만드는 방법입니다.
22 |
23 |
24 | ---
25 | ```python
26 | x = 1.0
27 | y = 10
28 |
29 | if x < y:
30 | print("x is less than y")
31 | elif x == y:
32 | print("x is equal to y")
33 | else:
34 | print("x is not less than y")
35 | ```
36 |
37 | 실행 결과
38 |
39 | ```
40 | x is less than y
41 | ```
42 |
43 |
44 | ---
45 | ```rust
46 | fn main() {
47 | let x = 1.0;
48 | let y = 10;
49 |
50 | if x < (y as f64) { // casting
51 | println!("x is less than y");
52 | } else if x == (y as f64) {
53 | println!("x is equal to y");
54 | } else {
55 | println!("x is not less than y");
56 | }
57 | }
58 | ```
59 |
60 | 실행 결과
61 |
62 | ```
63 | x is less than y
64 | ```
65 |
66 |
67 | ---
68 |
69 | ### let if
70 |
71 | 러스트에서는 if문의 각 분기를 변수에 바로 할당하는 것이 가능합니다.
72 |
73 | ```rust
74 | fn main() {
75 | let x = 1.0;
76 | let y = 10;
77 |
78 | let result = if x < (y as f64) {
79 | "x is less than y"
80 | } else if x == (y as f64) {
81 | "x is equal to y"
82 | } else {
83 | "x is not less than y"
84 | };
85 |
86 | println!("{}", result);
87 | }
88 |
89 | ```
90 | 각 분기에서 할당하는 값들이 모두 동일한 타입이어야 한다는 것입니다.
91 |
92 | ---
93 | ## for
94 |
95 | ```python
96 | for i in range(6, 10):
97 | print(i, end=",")
98 | ```
99 |
100 | 실행 결과
101 |
102 | ```
103 | 6,7,8,9,
104 | ```
105 |
106 | ---
107 | ```rust
108 | fn main() {
109 | for i in 6..10 {
110 | print!("{},", i);
111 | }
112 | }
113 |
114 | ```
115 |
116 | 실행 결과
117 |
118 | ```
119 | 6,7,8,9,
120 | ```
121 |
122 |
123 | ---
124 |
125 |
126 | ```python
127 | num_range = range(6, 10)
128 |
129 | for i in num_range:
130 | print(i, end=",")
131 |
132 | ```
133 |
134 |
135 |
136 | ```rust
137 | fn main() {
138 | let num_range = 6..10;
139 | for i in num_range {
140 | print!("{},", i);
141 | }
142 | }
143 |
144 | ```
145 |
146 | ---
147 | ## while
148 | ```python
149 | x = 0
150 | while x < 5:
151 | print(x, end=",")
152 | x += 1
153 | ```
154 |
155 | 실행 결과
156 |
157 | ```
158 | 0,1,2,3,4,
159 | ```
160 |
161 | ---
162 | 참고로, 러스트는 파이썬과 마찬가지로 증감 연산자(`++, --`)가 없습니다.
163 | ```rust
164 | fn main() {
165 | let mut x = 0;
166 | while x < 5 {
167 | print!("{},", x);
168 | x += 1; // no incremental operator: x++
169 | }
170 | }
171 |
172 | ```
173 |
174 | 실행 결과
175 |
176 | ```
177 | 0,1,2,3,4,
178 | ```
179 |
180 |
181 | ---
182 | ## loop
183 |
184 | 파이썬에서 무한 루프를 구현할 때는 `while True`를 사용합니다.
185 |
186 | ```python
187 |
188 | ```python
189 | x = 0
190 | while True:
191 | x += 1
192 | if x == 5:
193 | break
194 | print(x, end=",")
195 |
196 | ```
197 |
198 | 실행 결과
199 |
200 | ```
201 | 0,1,2,3,4,
202 | ```
203 |
204 |
205 | ---
206 | 러스트의 `loop`는 루프를 종료하는 `break` 에 해당하는 조건문이 있어야 루프를 종료하고 다음으로 진행할 수 있습니다.
207 |
208 | ```rust
209 | fn main() {
210 | let mut x = 0;
211 | loop {
212 | x += 1;
213 | if x == 5 {
214 | break;
215 | }
216 | print!("{},", x);
217 | }
218 | }
219 | ```
220 |
221 | 실행 결과
222 |
223 | ```
224 | 0,1,2,3,4,
225 | ```
226 |
227 |
228 | ---
229 | `loop`는 조건이 만족되면 루프를 탈출하는데, 이때 특정 값을 리턴할 수 있습니다. `break` 뒤에 리턴할 값을 넣어주면 됩니다. `x`가 5가 됐을 때 `x`를 리턴하도록 코드를 고치면 다음과 같습니다.
230 |
231 | ```rust
232 | fn main() {
233 | let mut x = 0;
234 | let y = loop {
235 | x += 1;
236 | if x == 5 {
237 | break x;
238 | }
239 | print!("{},", x);
240 | };
241 |
242 | println!("{}", y);
243 | }
244 |
245 | ```
246 | 루프 안에서 1부터 4까지가 출력되고, 그 뒤에 `y`의 값 5가 출력됩니다.
247 |
248 | ---
249 | ## match
250 |
251 | 다음은 다른 언어에서는 `switch ... case` 로 많이 사용되는 `match` 입니다. 파이썬에는 동일한 문법이 없으므로 `if ... else` 문으로 구현해 보겠습니다.
252 |
253 | > 파이썬에서는 최신 버전인 3.10 이후부터 `match ... case`가 추가되었습니다.
254 |
255 |
256 |
257 | ---
258 | 아래 코드는 `name` 변수에 값에 따라서 서로 다른 결과를 출력하는 코드입니다. 현재 `name` 변수의 값이 `"John"` 이므로 `"Hello, John!"`가 출력됩니다.
259 |
260 | ```python
261 | name = "John"
262 | if name == "John":
263 | print("Hello, John!")
264 | elif name == "Mary":
265 | print("Hello, Mary!")
266 | else:
267 | print("Hello, stranger!")
268 | ```
269 |
270 | 실행 결과
271 |
272 | ```
273 | Hello, John!
274 | ```
275 |
276 |
277 | ---
278 | `match`에서 나머지 경우를 나타내기 위해서 매칭할 값을 생략하는 `_`을 사용합니다. 여기서 `name` 변수의 값이 `"John"`이기 때문에 `"Hello, John!"`이 출력됩니다.
279 |
280 | ```rust
281 | fn main() {
282 | let name = "John";
283 | match name {
284 | "John" => println!("Hello, John!"),
285 | "Mary" => println!("Hello, Mary!"),
286 | _ => println!("Hello, stranger!"),
287 | }
288 | }
289 |
290 | ```
291 |
292 | 실행 결과
293 |
294 | ```
295 | Hello, John!
296 | ```
297 |
298 |
299 | ---
300 | `loop`와 마찬가지로 `match` 문도 값을 리턴할 수 있습니다. `let <변수명> = match ...`와 같이 선언하면 됩니다. 이때 컴파일러가 `match`문의 리턴값으로부터 변수 `greet`의 타입을 추론합니다. 또한, 각 조건마다 리턴하는 값들의 타입이 반드시 동일해야 합니다.
301 |
302 | ```rust
303 | fn main() {
304 | let name = "John";
305 | let greet = match name {
306 | "John" => "Hello, John!",
307 | "Mary" => "Hello, Mary!",
308 | _ => "Hello, stranger!",
309 | };
310 |
311 | println!("{}", greet);
312 | }
313 |
314 | ```
315 |
316 |
317 | ---
318 | ## Quiz
319 | 1. 정수를 인자로 받아 정수가 양수이면 "positive"를, 그렇지 않으면 "negative"를 프린트하는 함수 `check_sign`을 작성합니다.
320 |
321 |
322 | ---
323 | ```rust
324 | fn check_sign(n: i32) {
325 | if n > 0 {
326 | println!("positive")
327 | } else {
328 | println!("negative")
329 | }
330 | }
331 |
332 | fn main() {
333 | check_sign(3);
334 | }
335 |
336 | ```
337 |
338 |
339 | ---
340 | 2. 정수를 인자로 받아, 1부터 인수 이하의 모든 정수의 합을 반환하는 함수 `sum_integers`를 작성합니다.
341 | 예) 3을 입력받으면 1+2+3=6을 반환
342 |
343 | ---
344 |
345 | ```rust
346 | fn sum_integers(n: i32) -> i32 {
347 | let mut sum = 0;
348 | for i in 1..=n {
349 | sum += i;
350 | }
351 | sum
352 | }
353 |
354 | fn main() {
355 | let result = sum_integers(3);
356 | println!("The sum of positive integers up to 3 is: {}", result);
357 | }
358 | ```
359 |
360 | ---
361 | 3.
362 |
363 |
364 | ---
365 |
366 |
367 |
368 | ---
369 |
370 |
371 |
372 | ---
373 |
374 |
375 |
376 | ---
377 |
378 |
379 |
380 | ---
381 |
382 |
383 |
384 | ---
385 |
386 |
387 |
388 | ---
--------------------------------------------------------------------------------
/slides/ch4.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch4.pdf
--------------------------------------------------------------------------------
/slides/ch5-2.md:
--------------------------------------------------------------------------------
1 | ---
2 | marp: true
3 | paginate: true
4 | theme: default
5 | ---
6 |
7 | # 파이썬 프로그래머를 위한 러스트 입문
8 |
9 | 윤인도
10 | freedomzero91@gmail.com
11 |
12 | ---
13 |
14 | ## 클로저와 소유권
15 |
16 | 앞에서 클로저를 단순히 익명 함수라고만 설명하고 넘어갔습니다. 하지만 이제 스코프와 소유권을 배웠기 때문에, 클로저에 대해 좀더 자세한 얘기를 해보려고 합니다.
17 |
18 | *클로저의 가장 큰 특징은 익명 함수를 만들고 이를 변수에 저장하거나 다른 함수의 인자로 전달할 수 있다는 것입니다.*
19 |
20 | ---
21 |
22 | ### 클로저의 환경 캡처
23 |
24 | 클로저는 클로저가 선언된 스코프에 있는 지역 변수를 자신의 함수 내부에서 사용할 수 있는데, 이를 환경 캡처(Environment capture)라고 부릅니다. 클로저가 변수를 자신의 스코프 내부로 가져가는 방법은 총 3가지가 존재합니다.
25 |
26 | - 불변 소유권 대여
27 | - 가변 소유권 대여
28 | - 소유권 가져가기
29 |
30 | ---
31 |
32 | #### 불변 소유권 대여
33 | 클로저 `func` 는 같은 스코프에 선언된 변수 `multiplier`를 자신의 함수 내부에서 사용할 수 있습니다. 이때 `multiplier`의 값은 클로저에서 사용된 이후에도 스코프 내부에서 사용이 가능합니다.
34 |
35 | ```rust
36 | fn main() {
37 | let multiplier = 5;
38 |
39 | let func = |x: i32| -> i32 { x * multiplier };
40 |
41 | for i in 1..=5 {
42 | println!("{}", func(i));
43 | }
44 |
45 | println!("{}", multiplier); // 👍
46 | }
47 |
48 | ```
49 |
50 | ---
51 |
52 | #### 가변 소유권 대여
53 |
54 | 아래 예제는 `multiplier`를 가변 변수로 선언하고, 클로저 내부에서 `multiplier`의 값을 변경시키고 있습니다. 방금 살펴본 예제와 마찬가지로 클로저 호출이 끝난 다음에도 여전히 `multiplier`에 접근이 가능합니다.
55 |
56 | ```rust
57 | fn main() {
58 | let mut multiplier = 5;
59 |
60 | let mut func = |x: i32| -> i32 {
61 | multiplier += 1;
62 | x * multiplier
63 | };
64 |
65 | for i in 1..=5 {
66 | println!("{}", func(i));
67 | }
68 |
69 | println!("{}", multiplier); // 👍
70 | }
71 |
72 | ```
73 |
74 |
75 | ---
76 |
77 | ### `move` 를 사용한 소유권 이동
78 |
79 | 클로저가 환경으로부터 사용하는 값의 소유권을 가져갈 수도 있습니다. 클로저가 같은 스코프에 선언된 지역 변수의 소유권을 가져가도록 하려면 클로저의 파라미터를 선언하는 코드 앞에 `move` 키워드를 사용하면 됩니다.
80 |
81 | ```rust
82 | move | param, ... | body;
83 | ```
84 |
85 |
86 | ---
87 |
88 | 다음 예제에서는 클로저를 리턴하는 함수 `factory`를 만들었습니다.
89 | - 여기서 리턴되는 클로저는 `factory` 함수의 파라미터인 `factor`를 캡처해 사용합니다.
90 | - `multiplier` 변수를 모든 클로저에서 공유할 수 있게 됩니다.
91 |
92 | ```rust
93 | fn factory(factor: i32) -> impl Fn(i32) -> i32 {
94 | |x| x * factor
95 | }
96 |
97 | fn main() {
98 | let multiplier = 5;
99 | let mult = factory(multiplier);
100 | for i in 1..=3 {
101 | println!("{}", mult(i));
102 | }
103 | }
104 |
105 | ```
106 |
107 | ---
108 | 하지만 위 코드를 컴파일하면, 아래와 같은 에러가 발생합니다.
109 |
110 | ```text
111 | error[E0597]: `factor` does not live long enough
112 | --> src/main.rs:2:13
113 | |
114 | 2 | |x| x * factor
115 | | --- ^^^^^^ borrowed value does not live long enough
116 | | |
117 | | value captured here
118 | 3 | }
119 | | -
120 | | |
121 | | `factor` dropped here while still borrowed
122 | | borrow later used here
123 |
124 | For more information about this error, try `rustc --explain E0597`.
125 | error: could not compile `notebook` due to previous error
126 | ```
127 |
128 |
129 | ---
130 | 문제점:
131 | - `factor` 변수가 클로저 안에 캡처될 때, 소유권이 `factory`로부터 클로저로 대여됩니다.
132 | - `factory`함수가 종료되면 `factor` 변수의 값이 삭제됩니다.
133 |
134 |
135 | ---
136 |
137 | 해결 방법:
138 | `move`는 캡처된 변수의 소유권을 클로저 안으로 이동시킵니다.
139 |
140 | ```rust
141 | fn factory(factor: i32) -> impl Fn(i32) -> i32 {
142 | move |x| x * factor
143 | }
144 |
145 | fn main() {
146 | let multiplier = 5;
147 | let mult = factory(multiplier);
148 | for i in 1..=3 {
149 | println!("{}", mult(i));
150 | }
151 | }
152 | ```
153 |
154 |
155 | ---
156 |
157 | > 클로저에서 `move` 를 가장 많이 사용하는 경우는 멀티스레드 혹은 비동기 프로그래밍을 작성할 때입니다.
158 |
159 |
160 |
161 | ---
162 |
163 | ## Quiz
164 |
165 | #### 1.
166 | 다음 페이지의 코드에서 `inc1`과 `inc2`는 같은 `count` 변수를 캡처해서 사용합니다. 아래 코드를 수정해서, `inc1`과 `inc2`가 각각 다른 `count` 변수를 캡처하도록 만들어보세요. 코드를 실행했을 때, 다음 결과가 나오면 됩니다.
167 |
168 | ```
169 | count: 1
170 | count: 1
171 | ```
172 |
173 | ---
174 |
175 | ```rust
176 | fn main() {
177 | let mut count = 0;
178 |
179 | let mut inc1 = || {
180 | count += 1;
181 | println!("count: {}", count);
182 | };
183 |
184 | inc1();
185 |
186 | let mut inc2 = || {
187 | count += 1;
188 | println!("count: {}", count);
189 | };
190 |
191 | inc2();
192 | }
193 |
194 | ```
195 | ---
196 |
197 | 정답
198 | ```rust
199 | fn main() {
200 | let mut count = 0;
201 |
202 | let mut inc1 = move || {
203 | count += 1;
204 | println!("count: {}", count);
205 | };
206 |
207 | inc1();
208 |
209 | let mut inc2 = move || {
210 | count += 1;
211 | println!("count: {}", count);
212 | };
213 |
214 | inc2();
215 | }
216 |
217 | ```
218 |
219 |
220 | ---
221 |
222 | #### 2.
223 |
224 | 아래 코드가 정상적으로 실행되도록 `factory` 함수를 수정하세요.
225 |
226 | 힌트: 클로저의 타입은 `impl Fn(_) -> _`과 같이 작성하면 됩니다.
227 |
228 | ```rust
229 | fn factory() -> _ {
230 | let num = 5;
231 |
232 | |x| x + num
233 | }
234 |
235 | fn main() {
236 | println!("{}", factory()(1));
237 | }
238 | ```
239 |
240 |
241 | ---
242 |
243 | 정답
244 | ```rust
245 | fn factory() -> impl Fn(i32) -> i32 {
246 | let num = 5;
247 |
248 | move |x| x + num
249 | }
250 |
251 | fn main() {
252 | println!("{}", factory()(1));
253 | }
254 | ```
255 |
--------------------------------------------------------------------------------
/slides/ch5-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch5-2.pdf
--------------------------------------------------------------------------------
/slides/ch5.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch5.pdf
--------------------------------------------------------------------------------
/slides/ch6.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch6.pdf
--------------------------------------------------------------------------------
/slides/ch7.md:
--------------------------------------------------------------------------------
1 | ---
2 | marp: true
3 | paginate: true
4 | theme: default
5 | ---
6 |
7 | # 파이썬 프로그래머를 위한 러스트 입문
8 |
9 | 윤인도
10 | freedomzero91@gmail.com
11 |
12 | ---
13 |
14 | CH7. 구조체
15 |
16 | ---
17 |
18 | ### 러스트는 객체지향 프로그래밍보다는 함수형 프로그래밍에 더 가깝습니다.
19 | - 단적인 예로 러스트 코드는 이터레이터와 클로저를 적극적으로 사용합니다.
20 | - 이러한 이유에서 클래스가 존재하지 않습니다.
21 | - 대신 비슷한 역할을 구조체 `struct` 를 통해서 구현할 수 있습니다.
22 |
23 | ---
24 |
25 | ## 구조체의 정의
26 |
27 | ### 구조체 선언
28 |
29 | 먼저 파이썬에서 클래스를 하나 정의해 보겠습니다. `Person` 클래스는 객체화 시 `name`, `age` 두 변수를 파라미터로 받고, `self.name`, `self.age` 라는 인스턴스 프로퍼티에 할당됩니다.
30 |
31 | ```python
32 | class Person:
33 | def __init__(self, name, age):
34 | self.name = name
35 | self.age = age
36 | ```
37 |
38 | ---
39 |
40 | 러스트에서 구조체를 선언하기 위해서는 `struct` 키워드 뒤에 구조체 이름을 명시하면 됩니다.
41 |
42 | ```rust
43 | #[derive(Debug)] // derived traits
44 | struct Person {
45 | name: String,
46 | age: i32,
47 | }
48 | ```
49 |
50 | > 여기서 `#[derive(Debug)]` 는 미리 정의되어 있는 기능으로(derived trait 라고 합니다), 구조체의 내용을 보기 위해서 필요합니다.
51 |
52 | ---
53 | 파이썬
54 | ```python
55 | jane = Person("jane", 30)
56 | jane.age += 1
57 | print(jane.name, jane.age)
58 | print(jane.__dict__)
59 | ```
60 | 러스트
61 | ```rust
62 | fn main() {
63 | let mut jane = Person {
64 | name: String::from("Jane"),
65 | age: 30
66 | };
67 | jane.age += 1;
68 | println!("{} {}", jane.name, jane.age);
69 | println!("{:?}", jane);
70 | }
71 | ```
72 |
73 | ---
74 | ### 메소드
75 |
76 | `alive = True` 라는 프로퍼티를 추가
77 |
78 | ```python
79 | class Person:
80 | def __init__(self, name, age):
81 | self.name = name
82 | self.age = age
83 | self.alive = True
84 | ```
85 |
86 |
87 | ---
88 |
89 | `new`는 메소드가 아닌 연관 함수(Associated function)로 파라미터에 `self`가 들어있지 않습니다.
90 |
91 | ```rust
92 | #[derive(Debug)] // derived traits
93 | struct Person {
94 | name: String,
95 | age: i32,
96 | alive: bool,
97 | }
98 |
99 | impl Person {
100 | fn new(name: &str, age: i32) -> Self {
101 | Person {
102 | name: String::from(name),
103 | age: age,
104 | alive: true,
105 | }
106 | }
107 | }
108 | ```
109 |
110 | ---
111 | 인스턴스를 생성하는 메소드 말고 일반적인 메소드도 추가가 가능합니다. 먼저 파이썬에서는
112 |
113 | ```python
114 | class Person:
115 | def __init__(self, name, age):
116 | self.name = name
117 | self.age = age
118 | self.alive = True
119 |
120 | def info(self):
121 | print(self.name, self.age)
122 |
123 | def get_older(self, year):
124 | self.age += year
125 | ```
126 |
127 |
128 | ---
129 | 러스트에서는 아래와 같습니다.
130 |
131 | ```rust
132 | impl Person {
133 | fn new(name: &str, age: i32) -> Person {
134 | Person {
135 | name: String::from(name),
136 | age: age,
137 | alive: true,
138 | }
139 | }
140 |
141 | fn info(&self) {
142 | println!("{} {}", self.name, self.age)
143 | }
144 |
145 | fn get_older(&mut self, year: i32) {
146 | self.age += year;
147 | }
148 | }
149 | ```
150 |
151 |
152 | ---
153 |
154 | ### 이때 `self` 가 borrowed 되면서 mutable 인 것에 주의합니다.
155 | 왜냐하면 인스턴스 프로퍼티가 변경되기 때문에 `self`가 mutable이어야 합니다.
156 |
157 | 여기서 `info` 메소드의 `&self`를 `self`로 바꾸면 어떻게 될까요?
158 |
159 | ---
160 |
161 | ```python
162 | john = Person("john", 20)
163 | john.info()
164 | john.get_older(3)
165 | john.info()
166 | ```
167 |
168 | `get_older` 메소드를 통해 age가 3 증가합니다. 러스트에서도 동일합니다.
169 |
170 | ```rust
171 | fn main() {
172 | let mut john = Person::new("john", 20);
173 | john.info();
174 | john.get_older(3);
175 | john.info();
176 | }
177 | ```
178 |
179 | ---
180 |
181 | 정리하면, 구조체 안에는
182 | - `self` 파라미터를 사용하지 않는 연관 함수
183 | - `self` 파라미터를 사용하는 메소드
184 |
185 | 모두를 정의할 수 있습니다.
186 |
187 | ---
188 |
189 | ## 튜플 구조체(Tuple struct)
190 |
191 | 튜플 구조체는 구조체 필드가 이름 대신 튜플 순서대로 정의되는 구조체입니다. 필드 참조 역시 튜플의 원소를 인덱스로 참조하는 것과 동일합니다.
192 |
193 | ```rust
194 | struct Color(i32, i32, i32);
195 | struct Point(i32, i32, i32);
196 |
197 | fn main() {
198 | let black = Color(0, 0, 0);
199 | let origin = Point(0, 0, 0);
200 |
201 | println!("{} {}", black.0, origin.0);
202 | }
203 |
204 | ```
205 |
206 | ---
207 |
208 | ## 트레이트(trait)
209 |
210 | 파이썬은 클래스를 상속해 공통된 메소드를 사용할 수 있지만, 러스트는 구조체의 상속이 되지 않습니다.
211 |
212 |
213 | ---
214 |
215 | 먼저 파이썬에서 다음과 같이 `Person` 을 상속하는 새로운 클래스 `Student` 를 선언합니다.
216 |
217 | ```python
218 | class Person:
219 | ...
220 |
221 |
222 | class Student(Person):
223 | def __init__(self, name, age, major):
224 | super().__init__(name, age)
225 | self.major = major
226 |
227 | def say_hello(self):
228 | print(f"Hello, I am {self.name} and I am studying {self.major}")
229 |
230 | ```
231 |
232 | ---
233 |
234 | Rust는 하나의 struct를 상속하는 방법이 존재하지 않는 대신 메소드를 공유하는 방법인 `trait`을 사용합니다.
235 |
236 | ```rust
237 | trait Greet {
238 | fn say_hello(&self) {}
239 | }
240 | ```
241 |
242 | ---
243 |
244 | ```rust
245 | ...
246 |
247 | impl Greet for Person {}
248 |
249 | struct Student {
250 | name: String,
251 | age: i32,
252 | alive: bool,
253 | major: String,
254 | }
255 |
256 | ```
257 |
258 | ---
259 |
260 | ```rust
261 |
262 | impl Student {
263 | fn new(name: &str, age: i32, major: &str) -> Student {
264 | Student {
265 | name: String::from(name),
266 | age: age,
267 | alive: true,
268 | major: String::from(major),
269 | }
270 | }
271 | }
272 |
273 | impl Greet for Student {
274 | fn say_hello(&self) {
275 | println!("Hello, I am {} and I am studying {}", self.name, self.major)
276 | }
277 | }
278 | ```
279 |
280 | ---
281 |
282 | ```rust
283 | fn main() {
284 | let mut person = Person::new("John", 20);
285 | person.say_hello(); // 🫢
286 | person.get_older(1);
287 | println!("{} is now {} years old", person.name, person.age);
288 |
289 | let student = Student::new("Jane", 20, "Computer Science");
290 | student.say_hello();
291 | }
292 |
293 | ```
294 |
295 |
296 | ---
297 |
298 | 만일 아래와 같이 기본 구현체를 변경하면 코드가 컴파일되지 않습니다. 여기서 파라미터로 `&self` 를 받고 있지만, 트레이트에 정의되는 함수는 인스턴스 프로퍼티에 접근할 수 없습니다.
299 |
300 | ```rust
301 | trait Greet {
302 | fn say_hello(&self) {
303 | println!("Hello, Rustacean!");
304 | }
305 | }
306 | ```
307 |
308 |
309 | ---
310 |
311 | ### 파생(Derive)
312 |
313 | 컴파일러는 `#[derive]` 트레이트을 통해 일부 특성에 대한 기본 구현을 제공할 수 있습니다. 보다 복잡한 동작이 필요한 경우 이러한 특성은 직접 구현할 수 있습니다.
314 |
315 | 다음은 파생 가능한 트레이트 목록입니다:
316 |
317 | - 비교: [`Eq`](https://doc.rust-lang.org/std/cmp/trait.Eq.html), [`PartialEq`](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html), [`Ord`](https://doc.rust-lang.org/std/cmp/trait.Ord.html), [`PartialOrd`](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html).
318 | - [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html), 복사본을 통해 `&T`에서 `T`를 생성합니다.
319 | - [`Copy`](https://doc.rust-lang.org/core/marker/trait.Copy.html), '이동 시맨틱' 대신 '복사 시맨틱' 타입을 제공합니다.
320 | - [`Hash`](https://doc.rust-lang.org/std/hash/trait.Hash.html), `&T`에서 해시를 계산합니다.
321 | - [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html), 데이터 타입의 빈 인스턴스를 생성합니다.
322 | - `{:?}` 포매터를 사용하여 값의 형식을 지정하려면 [`Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html).
323 |
324 |
325 |
326 | ---
327 |
328 | 다음 코드는 컴파일되지 않습니다.
329 |
330 | ```rust
331 | struct Rectangle {
332 | width: u32,
333 | height: u32,
334 | }
335 |
336 | fn main() {
337 | let rect1 = Rectangle {
338 | width: 30,
339 | height: 50,
340 | };
341 |
342 | println!("rect1 is {:?}", rect1); // 🤯
343 | }
344 |
345 | ```
346 |
347 |
348 |
349 | ---
350 |
351 | 에러 내용을 살펴보면 `Rectangle`을 프린트할 수 없다고 합니다.
352 |
353 | ```
354 | error[E0277]: `Rectangle` doesn't implement `Debug`
355 | --> src/main.rs:12:31
356 | |
357 | 12 | println!("rect1 is {:?}", rect1); // 🤯
358 | | ^^^^^ `Rectangle` cannot be formatted using `{:?}`
359 | |
360 | = help: the trait `Debug` is not implemented for `Rectangle`
361 | = note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
362 | ```
363 |
364 |
365 | ---
366 |
367 | 이때 컴파일러의 조언대로 트레이트를 파생시키면 됩니다.
368 |
369 | ```rust
370 | #[derive(Debug)]
371 | struct Rectangle {
372 | width: u32,
373 | height: u32,
374 | }
375 |
376 | fn main() {
377 | let rect1 = Rectangle {
378 | width: 30,
379 | height: 50,
380 | };
381 |
382 | println!("rect1 is {:?}", rect1);
383 | }
384 |
385 | ```
386 |
387 |
388 |
389 | ---
390 |
391 |
392 |
393 |
394 |
395 | ---
--------------------------------------------------------------------------------
/slides/ch7.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch7.pdf
--------------------------------------------------------------------------------
/slides/ch8.md:
--------------------------------------------------------------------------------
1 | ---
2 | marp: true
3 | paginate: true
4 | theme: default
5 | ---
6 |
7 | # 파이썬 프로그래머를 위한 러스트 입문
8 |
9 | 윤인도
10 | freedomzero91@gmail.com
11 |
12 | ---
13 |
14 | # CH8. 모듈과 크레이트
15 |
16 | ---
17 |
18 | ## Rust의 모듈 시스템
19 |
20 | 러스트의 모듈 시스템은 아래 4가지를 말합니다.
21 |
22 | - 패키지(Packages) : cargo에서 제공하는 기능으로, crate를 빌드하고 생성할 수 있습니다.
23 | - 크레이트(Crates) : 라이브러리 또는 바이너리를 생성하는 모듈 트리(a tree of modules)입니다.
24 | - `mod` 와 `use`: 코드 안에서 다른 모듈들을 구성하고, 불러오거나 다른 모듈에 노출할 지 여부(private or public)를 결정합니다.
25 | - 경로: 모듈에서 특정 요소(함수, 구조체, 변수 등)를 찾기 위한 방법
26 |
27 | ---
28 |
29 | ### 패키지
30 |
31 | `cargo.toml` 파일
32 |
33 | 하나의 패키지에는 단 하나의 라이브러리 크레이트만 포함할 수 있습니다. 하지만 바이너리 크레이트는 여러 개를 넣을 수 있습니다.
34 |
35 | ---
36 |
37 | ### 크레이트
38 |
39 | #### 바이너리 크레이트
40 |
41 | `main.rs`
42 |
43 | ```bash
44 | cargo new
45 | ```
46 |
47 | 컴파일되어 바이너리 파일을 생성하는 크레이트입니다.
48 |
49 | ---
50 |
51 | #### 라이브러리 크레이트
52 |
53 | `lib.rs`
54 |
55 | ```bash
56 | cargo new --lib
57 | ```
58 |
59 | 컴파일되지 않기 때문에 바이너리를 생성하지 않습니다. 다른 크레이트나 패키지에서 코드를 참조할 수 있도록 제공되는 크레이트입니다.
60 |
61 | ---
62 |
63 | #### 크레이트 루트
64 |
65 | 크레이트 루트란 컴파일 엔트리포인트를 의미합니다. 바이너리 크레이트는 `src/main.rs` 파일이, 라이브러리 크레이트는 `src/lib.rs` 파일이 크레이트 루트가 됩니다.
66 |
67 | ---
68 |
69 | ### private vs public
70 |
71 | 러스트의 모든 모듈과 객체는 기본적으로 private입니다. 외부에서 모듈에 접근하거나 모듈 내부의 객체에 접근을 허용하려면 `pub` 키워드를 사용해야 합니다.
72 |
73 | ```rust
74 | pub mod {
75 |
76 | }
77 |
78 | pub fn {
79 |
80 | }
81 |
82 | pub struct {
83 |
84 | }
85 |
86 | pub static
87 | ```
88 |
89 | ---
90 |
91 | ### `use` 와 `mod`
92 |
93 | `use` 키워드는 특정 경로를 현재 스코프로 가져오는 역할을 합니다. 주의해야 하는 점은 경로는 항상 크레이트 루트로부터 시작된다는 점입니다.
94 |
95 | ---
96 |
97 | `mod` 키워드는 해당 모듈을 사용하겠다고 선언하는 역할입니다. 예를 들어 `mod new_module`이 사용되면, 컴파일러는 아래 위치에서 해당 모듈을 찾아봅니다.
98 |
99 | 1. `mod new_module` 다음에 해당 모듈의 정의가 나와야 합니다.
100 |
101 | ```rust
102 | mod new_module {
103 | fn new_func() {
104 | ...
105 | }
106 | ...
107 | }
108 | ```
109 |
110 | 2. `src/new_module.rs` 파일을 찾아봅니다.
111 | 3. `src/new_module` 폴더에서 `mod.rs` 파일을 찾아봅니다.
112 |
113 | ```rust
114 | pub mod new_module;
115 | ```
116 |
117 | ---
118 |
119 | 특정 모듈에 대한 접근은 크레이트 루트를 기준으로 절대경로를 사용하면 됩니다. 예를 들어 코드 어디에서라도 다음과 같이 모듈에 접근이 가능합니다.
120 |
121 | ```rust
122 | // src/new_module.rs -> MyType
123 | use crate::new_module::MyType
124 | ```
125 |
126 | 상대 경로도 사용 가능합니다.
127 |
128 | ---
129 |
130 | `self` 는 struct 자기 자신
131 |
132 | ```rust
133 | mod mod2 {
134 | fn func() {
135 | println!("mod2::func()");
136 | }
137 | mod mod1 {
138 | pub fn func() {
139 | println!("mod2::mod1::func()");
140 | }
141 | }
142 | pub fn dummy() {
143 | func();
144 | self::func();
145 | mod1::func();
146 | self::mod1::func();
147 | }
148 | }
149 | fn main() {
150 | mod2::dummy();
151 | }
152 | ```
153 |
154 | ---
155 |
156 | `super`는 현재 모듈의 상위 모듈을 의미합니다.
157 |
158 | ---
159 |
160 | `super`는 현재 모듈의 상위 모듈을 의미합니다.
161 |
162 | ```rust
163 | mod mod1 {
164 | pub fn dummy() {
165 | println!("Hello, world!");
166 | }
167 | }
168 |
169 | mod mod2 {
170 | // use crate::mod1;
171 | use super::mod1;
172 |
173 | pub fn dummy() {
174 | mod1::dummy();
175 | }
176 | }
177 |
178 | fn main() {
179 | mod2::dummy();
180 | }
181 |
182 | ```
183 |
184 | ---
185 |
186 | ## 모듈과 크레이트 사용해보기
187 |
188 | 파이썬 폴더에 `my_modle.py` 를 생성합니다.
189 |
190 | ```python
191 | def greet():
192 | print(f"Hi! I am hello_bot")
193 |
194 |
195 | class Person:
196 | def __init__(self, name, age):
197 | self.name = name
198 | self.age = age
199 |
200 | def get_older(self, year):
201 | self.age += year
202 | ```
203 |
204 | ---
205 |
206 | 이제 이 함수와 클래스를 `main.py`에서 참조합니다.
207 |
208 | ```python
209 | from my_module import greet, Person
210 |
211 | if __name__ == '__main__':
212 | greet()
213 |
214 | john = Person("john", 20)
215 | john.get_older(3)
216 | print(john.age)
217 |
218 | ```
219 |
220 | ---
221 |
222 | 이번에는 `bots` 폴더를 만들고 `hello_bot.py` 파일을 추가합니다.
223 |
224 | ```
225 | .
226 | ├── bots
227 | │ └── hello_bot.py
228 | ├── main.py
229 | └── my_module.py
230 | ```
231 |
232 | ---
233 |
234 | `hello_bot.py` 는 다음과 같습니다.
235 |
236 | ```python
237 | BOT_NAME = "hello_bot"
238 |
239 |
240 | def hello():
241 | print("Hello, humans!")
242 |
243 | ```
244 |
245 | ---
246 |
247 | `my_module.py`에서 `greet` 함수가 `BOT_NAME` 을 이용하도록 합니다.
248 |
249 | ```python
250 | from bots.hello_bot import BOT_NAME
251 |
252 |
253 | def greet():
254 | print(f"Hi! I am {BOT_NAME}")
255 | ```
256 |
257 | ---
258 |
259 | 그 다음 `main.py` 에서 `bots` 모듈을 사용해 보겠습니다.
260 |
261 | ```python
262 | from bots.hello_bot import hello
263 | from my_module import greet, Person
264 |
265 | if __name__ == '__main__':
266 | hello()
267 |
268 | greet()
269 |
270 | john = Person("john", 20)
271 | john.get_older(3)
272 | print(john.age)
273 |
274 | ```
275 |
276 | ---
277 |
278 | 이번에는 동일한 구조를 러스트에서 구현해 보겠습니다. src 폴더에 `my_module.rs` 를 생성합니다.
279 |
280 | ```
281 | src
282 | ├── main.rs
283 | └── my_module.rs
284 | ```
285 |
286 | ---
287 |
288 | 이때 public으로 만드려면 `pub` 키워드를 사용해야 합니다.
289 |
290 | ```rust
291 | pub fn greet() {
292 | println!("Hi! I am hello_bot");
293 | }
294 |
295 | pub struct Person {
296 | pub name: String,
297 | age: i32,
298 | }
299 |
300 | impl Person {
301 | pub fn new(name: &str, age: i32) -> Self {
302 | Person {
303 | name: String::from(name),
304 | age: age,
305 | }
306 | }
307 | pub fn get_older(&mut self, year: i32) {
308 | self.age += year;
309 | }
310 | }
311 | ```
312 |
313 | ---
314 |
315 | ```rust
316 | mod my_module; // will look for a file src/my_module.rs
317 |
318 | // actually import the function and struct from my_module.rs
319 | use my_module::{greet, Person};
320 |
321 | fn main() {
322 | greet();
323 |
324 | let mut john = Person::new("john", 20);
325 | john.get_older(3);
326 | println!("{}", john.name);
327 | // println!("Am I alive? {}", john.alive); // won't compile!
328 | }
329 |
330 | ```
331 |
332 | ---
333 |
334 | 다음으로는 하위 폴더 `bots`를 만들어 보겠습니다. `bots` 폴더에는 `hello_bot.rs`와 `mod.rs` 두 파일을 생성합니다.
335 |
336 | ```
337 | src
338 | ├── bots
339 | │ ├── hello_bot.rs
340 | │ └── mod.rs
341 | ├── main.rs
342 | └── my_module.rs
343 | ```
344 |
345 | ---
346 |
347 | 항상 하위 폴더를 모듈로 만드는 경우에는 `mod.rs` 가 있어야 합니다. 이 파일은 해당 모듈의 엔트리포인트가 되어 이 모듈 안에 있는 다른 하위 모듈들을 찾을 수 있게 합니다. 따라서 `mod.rs` 에는 `hello_bot` 모듈의 정보가 있어야 합니다.
348 |
349 | ```rust
350 | pub mod hello_bot; // will look for hello_bot.rs
351 | ```
352 |
353 | 이제 `hello_bot.rs` 파일을 작성합니다.
354 |
355 | ```rust
356 | pub static BOT_NAME: &str = "hello_bot";
357 |
358 | pub fn hello() {
359 | println!("Hello, humans!");
360 | }
361 |
362 | ```
363 |
364 | ---
365 |
366 | static 변수와 함수 하나가 생성되어 있고, 둘 다 public으로 선언되었습니다. 먼저, `BOT_NAME` 스태틱을 `src/my_module.rs` 에서 참조해 보겠습니다. `my_module.rs` 는 크레이트 루트가 아니기 때문에 `use crate::` 문법으로 참조해야 합니다. 여기서 `greet` 함수가 이 `BOT_NAME` 스태틱을 참조해 실행되도록 수정해 봅시다.
367 |
368 | ```rust
369 | use crate::bots::hello_bot::BOT_NAME;
370 |
371 | pub fn greet() {
372 | println!("Hi! I am {}", BOT_NAME);
373 | }
374 |
375 | ```
376 |
377 | ---
378 |
379 | 이제 `main.rs` 에서 `bots` 모듈을 사용해 보겠습니다. `main.rs`는 크레이트 루트기 때문에 `use bots::hello_bot::hello;` 로 모듈을 불러올 수 있습니다.
380 |
381 | ```rust
382 | mod my_module; // will look for a file src/my_module.rs
383 | mod bots; // will look for a file src/hello/mod.rs
384 |
385 | use my_module::{greet, Person};
386 | use bots::hello_bot::hello;
387 |
388 |
389 | fn main() {
390 | hello();
391 |
392 | greet();
393 |
394 | let mut john = Person::new("john", 20);
395 | john.get_older(3);
396 | println!("{}", john.name);
397 | // println!("Am I alive? {}", john.alive); // won't compile!
398 | }
399 |
400 | ```
401 |
--------------------------------------------------------------------------------
/slides/ch8.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch8.pdf
--------------------------------------------------------------------------------
/slides/ch9.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/slides/ch9.pdf
--------------------------------------------------------------------------------
/slides/template.md:
--------------------------------------------------------------------------------
1 | ---
2 | marp: true
3 | paginate: true
4 | theme: default
5 | ---
6 |
7 | # 파이썬 프로그래머를 위한 러스트 입문
8 |
9 | 윤인도
10 | freedomzero91@gmail.com
11 |
12 | ---
13 |
14 |
15 |
16 | ---
17 |
18 |
19 |
20 | ---
21 |
22 |
23 |
24 | ---
25 |
26 |
27 |
28 | ---
29 |
30 |
31 |
32 | ---
33 |
34 |
35 |
36 | ---
37 |
38 |
39 |
40 | ---
41 |
42 |
43 |
44 | ---
45 |
46 |
47 |
48 | ---
49 |
50 |
51 |
52 | ---
53 |
54 |
55 |
56 | ---
57 |
58 |
59 |
60 | ---
61 |
62 |
63 |
64 | ---
65 |
66 |
67 |
68 | ---
69 |
70 |
71 |
72 | ---
73 |
74 |
75 |
76 | ---
77 |
78 |
79 |
80 | ---
81 |
82 |
83 |
84 | ---
85 |
86 |
87 |
88 | ---
89 |
90 |
91 |
92 | ---
93 |
94 |
95 |
96 | ---
97 |
98 |
99 |
100 | ---
101 |
102 |
103 |
104 | ---
105 |
106 |
107 |
108 | ---
109 |
110 |
111 |
112 | ---
113 |
114 |
115 |
116 | ---
117 |
118 |
119 |
120 | ---
121 |
122 |
123 |
124 | ---
--------------------------------------------------------------------------------
/src/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | - [러스트 시작하기](./ch1-00.md)
4 | - [파이썬 개발자가 러스트를 배워야 하는 이유](./ch1-01.md)
5 | - [러스트로 뭘 할 수 있나요?](./ch1-02.md)
6 | - [러스트 개발 환경 설정하기](./ch1-03.md)
7 | - [러스트 코드 실행하기](./ch1-04.md)
8 | - [변수](./ch2-00.md)
9 | - [변수 선언하고 값 출력하기](./ch2-01.md)
10 | - [변수의 불변성](./ch2-02.md)
11 | - [섀도잉](./ch2-03.md)
12 | - [타입](./ch2-04.md)
13 | - [상수](./ch2-05.md)
14 | - [함수](./ch3-00.md)
15 | - [함수 선언하기](./ch3-01.md)
16 | - [스코프](./ch3-02.md)
17 | - [익명 함수](./ch3-03.md)
18 | - [흐름제어](./ch4-00.md)
19 | - [if](./ch4-01.md)
20 | - [for](./ch4-02.md)
21 | - [while](./ch4-03.md)
22 | - [loop](./ch4-04.md)
23 | - [match](./ch4-05.md)
24 | - [소유권](./ch5-00.md)
25 | - [컴퓨터의 메모리](./ch5-01.md)
26 | - [소유권 규칙](./ch5-02.md)
27 | - [클로저와 소유권](./ch5-03.md)
28 | - [데이터 구조와 이터레이터](./ch6-00.md)
29 | - [자료형](./ch6-01.md)
30 | - [열거형](./ch6-02.md)
31 | - [이터레이터](./ch6-03.md)
32 | - [구조체](./ch7-00.md)
33 | - [구조체](./ch7-01.md)
34 | - [트레이트](./ch7-02.md)
35 | - [모듈과 크레이트](./ch8-00.md)
36 | - [러스트의 모듈 시스템](./ch8-01.md)
37 | - [모듈과 크레이트 사용해 보기](./ch8-02.md)
38 | - [제네릭](./ch9-00.md)
39 | - [타입 파라미터]((./ch9-01.md))
40 | - [제네릭과 트레이트](./ch9-02.md)
41 | - [미니프로젝트: `cat` 만들어보기](./ch9-03.md)
42 | - [라이프타임과 스태틱](./ch9-04.md)
43 | - [예외 처리](./ch10-00.md)
44 | - [panic!](./ch10-01.md)
45 | - [Option 열거형](./ch10-02.md)
46 | - [Result 열거형](./ch10-03.md)
47 | - [에러 로깅](./ch10-04.md)
48 | - [스마트 포인터](./ch11-00.md)
49 | - [Box 타입](./ch11-01.md)
50 | - [Rc](./ch11-02.md)
51 | - [RefCell](./ch11-03.md)
52 | - [멀티스레딩](./ch12-00.md)
53 | - [스레드 스폰](./ch12-01.md)
54 | - [메모리 공유](./ch12-02.md)
55 | - [메시지 전달](./ch12-03.md)
56 | - [비동기 프로그래밍](./ch13-00.md)
57 | - [비동기 프로그래밍이란?](./ch13-01.md)
58 | - [tokio](./ch13-02.md)
59 | - [rayon](./ch13-03.md)
60 | - [테스트](./ch14-00.md)
61 | - [유닛 테스트](./ch14-01.md)
62 | - [문서 테스트](./ch14-02.md)
63 | - [모킹](./ch14-03.md)
64 | - [파이썬 바인딩](./ch15-00.md)
65 | - [파이썬 가상환경 만들기](./ch15-01.md)
66 | - [PyO3 프로젝트 생성하기](./ch15-02.md)
67 | - [파이썬에서 러스트 코드 실행해 보기](./ch15-03.md)
68 |
--------------------------------------------------------------------------------
/src/assets/ch01-02-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-02-1.jpg
--------------------------------------------------------------------------------
/src/assets/ch01-02-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-02-2.png
--------------------------------------------------------------------------------
/src/assets/ch01-02-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-02-3.png
--------------------------------------------------------------------------------
/src/assets/ch01-03-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-03-01.png
--------------------------------------------------------------------------------
/src/assets/ch01-03-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-03-02.png
--------------------------------------------------------------------------------
/src/assets/ch01-03-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-03-03.png
--------------------------------------------------------------------------------
/src/assets/ch01-03-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-03-04.png
--------------------------------------------------------------------------------
/src/assets/ch01-04-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-04-01.png
--------------------------------------------------------------------------------
/src/assets/ch01-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-1.png
--------------------------------------------------------------------------------
/src/assets/ch01-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-2.png
--------------------------------------------------------------------------------
/src/assets/ch01-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-3.png
--------------------------------------------------------------------------------
/src/assets/ch01-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-4.png
--------------------------------------------------------------------------------
/src/assets/ch01-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-5.png
--------------------------------------------------------------------------------
/src/assets/ch01-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch01-6.png
--------------------------------------------------------------------------------
/src/assets/ch02-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch02-1.png
--------------------------------------------------------------------------------
/src/assets/ch02-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch02-2.png
--------------------------------------------------------------------------------
/src/assets/ch02-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch02-3.png
--------------------------------------------------------------------------------
/src/assets/ch02-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch02-4.png
--------------------------------------------------------------------------------
/src/assets/ch13-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch13-1.png
--------------------------------------------------------------------------------
/src/assets/ch14-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch14-1.png
--------------------------------------------------------------------------------
/src/assets/ch14-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Indosaram/rust-python-book/e4bf57723cd0cc14888e12255f5d67d2036e1b44/src/assets/ch14-2.png
--------------------------------------------------------------------------------
/src/ch1-00.md:
--------------------------------------------------------------------------------
1 | # 하드카피 출간!
2 |
3 | 더 자세한 설명, 다양한 예제가 있는 출판본도 서점에서 만나보실 수 있습니다.
4 |
5 |
6 |
7 | [https://www.yes24.com/Product/Goods/126578252](https://www.yes24.com/Product/Goods/126578252)
8 |
9 |
10 | ---
11 |
12 | # 온라인 강의
13 |
14 | 책 출간에 맞추어 인프런에서 새로운 강의 "세상에서 제일 쉬운 러스트 프로그래밍"도 오픈 예정입니다!
15 |
16 | [강의 바로가기](https://inf.run/kXsni)
17 |
18 |
19 | ---
20 |
21 | # CH1. 러스트 시작하기
22 |
23 | ## 가장 사랑받는 언어, 러스트
24 |
25 | ```python
26 | print("Hello, Pythonista!")
27 | ```
28 |
29 | ```rust
30 | fn main() {
31 | println!("Hello, Rustacean!");
32 | }
33 | ```
34 |
35 | 파이썬은 최근 가장 인기가 높은 언어 중 하나입니다. 간결한 문법과 범용성 덕분에 서버 개발부터 딥러닝 모델 개발까지 다양한 분야에서 사용되고 있습니다. 하지만 파이썬은 개발 시의 높은 생산성을 위해 코드 실행 속도를 일정 부분 포기한 언어입니다. 특히 파이썬의 태생적 한계인 GIL(Global interpreter lock)때문에 빠른 연산이 필요한 작업이나 멀티스레딩 프로그램에서 좋은 성능을 내기 어려운 단점이 있습니다.
36 |
37 | 러스트는 높은 추상성으로 높은 생산성을 가지고 있는 동시에 C/C++의 99%에 가까운 성능을 가지고 있어서 빠른 연산속도가 필요한 분야에서 각광받고 있습니다. 2022년 스택오버플로우 개발자 설문조사의 "Loved vs Dreaded(사랑하는 언어 대 두려운 언어)"에서 러스트는 86.73%라는 높은 비율로 가장 사랑받는 언어로 선정되었습니다. 참고로, 러스트는 현재 7년 연속 가장 사랑받는 언어 1위로 선정되었습니다.
38 |
39 | 
40 |
41 | 국내에서도 2022 프로그래머스 설문조사에 따르면 러스트는 5.3% 비율로 7위를 차지해 꽤 상위권에 위치해 있는 편입니다.
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/ch1-01.md:
--------------------------------------------------------------------------------
1 | ## 파이썬 개발자가 러스트를 배워야 하는 이유
2 |
3 | 왜 국내/외에서 러스트를 배우고 싶어할까요? 그리고 파이썬과 러스트의 어떤 차이점 때문에 파이썬 개발자들이 러스트를 배워야만 할까요?
4 |
5 |
6 |
7 | ### 첫째로, CPU 연산이 많이 필요한 코드를 러스트로 교체하면 빠르게 동작하는 프로그램을 만들 수 있습니다.
8 |
9 | 파이썬은 빠르게 코드를 작성할 수 있지만, 인터프리터 언어이기 때문에 다른 컴파일 언어에 비해서 속도가 느릴 수밖에 없습니다. 따라서 많은 계산이 필요한 데이터 분석이나 수치계산 분야에서는 이미 널리 쓰이는 pandas나 numpy와 같은 라이브러리가 C++로 작성되어 있습니다. 이와 비슷하게 파이썬 코드에서 병목 현상이 발생하는 부분을 러스트로 대체한 파이썬 함수나 패키지를 만들면 큰 성능 향상을 얻을 수 있습니다.
10 |
11 | > 러스트와 자주 비교되는 언어인 고(Go)와 다르게, 러스트에는 가비지 콜렉터가 없기 때문에 훨씬 좋은 성능을 내게 됩니다. 이러한 특징 때문에 퍼포먼스가 매우 중요한 서비스에 자주 사용됩니다.
12 |
13 |
14 |
15 | ### 둘째로, 멀티스레딩 구현이 훨씬 쉽습니다.
16 |
17 | 파이썬에서 멀티스레딩 프로그램을 구현할 때 가장 많이 겪는 문제가 스레드 레이스 조건(race condition)입니다. 러스트만의 독특한 타입 시스템과 소유권(ownership) 모델 덕분에 코드가 컴파일될 때 발생할 수 있는 메모리 혹은 스레드 문제를 미리 찾아낼 수 있기 때문에 훨씬 안정적인 프로그램을 만들 수 있습니다.
18 |
19 | 특히 파이썬은 GIL(Global Interpreter Lock) 때문에 멀티스레딩이라 하더라도 한 번에 하나의 코어밖에 사용하지 않습니다. 하지만 러스트를 사용하면 GIL 락이 걸린 순간에 여러 스레드를 사용해 더 빠르게 계산을 완료할 수 있습니다.
20 |
21 |
22 |
23 | ### 마지막으로, 개발 도구가 매우 편리합니다.
24 |
25 | 러스트는 언어 입문서와 예제 모음집이 공식적으로 제공됩니다. 그리고 매우 친절한 컴파일러가 있습니다. 때로는 컴파일 시에 발생하는 오류에 대해 적절한 해결책을 컴파일러가 제시해 주기도 합니다. 러스트의 내장 패키지 매니저인 Cargo 덕분에 빌드, 테스트, 의존성 관리 등이 매우 간편합니다. 또한 Visual Studio Code와 같은 통합개발환경(Integrated Developement Environment, IDE) 지원이 잘 되어 있어 자동완성, 타입 검사, 포매팅 등을 자연스럽게 사용할 수 있습니다.
26 |
27 |
28 |
29 | 이같은 점 때문에 7년 연속 "가장 사랑받는 프로그래밍 언어" 1위를 차지하고 있는 러스트는 더 이상 파이썬 개발자에게 선택이 아닌 필수가 되었습니다.
30 |
31 |
32 |
33 | ## 파이썬과 러스트의 차이점
34 |
35 | ### 언어상의 차이
36 |
37 | 먼저 기본적인 언어상의 차이를 살펴보면 다음과 같습니다.
38 |
39 | | 파이썬 | 러스트 |
40 | | --------------------------------- | -------------------------------- |
41 | | 인터프리터 언어 | 컴파일 언어 |
42 | | 강타입 언어이면서 동적 타입 언어 | 강타입 언어이면서 정적 타입 언어 |
43 | | 메모리 관리에 가비지 콜렉터 사용 | 메모리 관리에 소유권 모델 사용 |
44 | | 대부분의 경우 객체지향 프로그래밍 | 함수형 프로그래밍 |
45 | | 스타일 가이드가 유연함 | 명확한 스타일 가이드 존재 |
46 |
47 | 러스트는 컴파일 언어이기 때문에 파이썬과 다르게 코드가 실행되기 전 컴파일 단계를 거쳐야 합니다. 하지만 이때 대부분의 오류와 버그를 잡아낼 수 있기 때문에 이는 오히려 러스트의 장점이기도 합니다. 파이썬은 객체 지향 프로그래밍 언어이지만, 러스트는 함수형 프로그래밍 언어이기 때문에 파이썬과는 코드 작성 패턴이 조금 다릅니다. 이 책에서는 파이썬 코드와 러스트 코드를 비교하면서 러스트를 설명하기 때문에 두 언어의 유사성과 차이점을 통해 더욱 쉽게 러스트를 배울 수 있습니다.
48 |
49 | > 강타입 언어란?
50 | >
51 | > 서로 다른 타입의 변수를 사용해 계산을 수행했을 때, 모든 경우에 대해 에러가 발생하면 강타입 언어입니다. 예를 들어 자바스크립트는 약타입 언어입니다.
52 | >
53 | > ```javascript
54 | > > console.log(1 + "2");
55 | >
56 | > 13
57 | > ```
58 |
59 |
60 |
61 | ### 툴 비교
62 |
63 | 아래 표는 파이썬과 러스트의 기본 툴들을 비교한 표입니다. 파이썬의 경우, `pip` 를 제외한 툴들은 일반적으로 별도 설치가 필요합니다. 하지만 러스트는 `cargo` 라는 툴을 통해 대부분의 기능을 바로 사용할 수 있습니다. `cargo`는 크레이트(crate)라고 불리는 패키지를 관리하는 도구이면서, 동시에 소스코드를 컴파일하고 빌드하는 시스템 빌드 매니저 기능도 포함하고 있습니다. 아래 표와 같이 `cargo` 에 내장된 기능은 포맷(format), 린트(lint), 테스트, 문서화, 벤치마크 등 다양합니다. 여기에 추가로 플러그인을 설치해서 더 다양하게 `cargo` 를 사용할 수도 있습니다.
64 |
65 | | | 파이썬 | 러스트 |
66 | | ------------------ | -------------------------------- | ------------ |
67 | | 패키지 관리자 | pip | cargo |
68 | | 포매터 | black, yapf, autopep8 | cargo fmt |
69 | | 린터 | pylint, flake8 | cargo clippy |
70 | | 테스트 | pytest | cargo test |
71 | | 프로젝트 환경 관리 | virtualenv, pipenv, pyenv, conda | cargo new |
72 | | 문서화 | sphinx | cargo doc |
73 | | 벤치마크 | cProfile, pyspy | cargo bench |
74 |
75 |
76 |
77 | > 포맷과 린트의 차이점
78 | >
79 | > 포맷은 코드의 스타일을 기준에 맞춰 바꾸어주는 것을 의미하고, 린트는 코드가 문법 규칙에 맞는지를 검사합니다.
80 |
81 |
82 |
83 | 예를 들어 `cargo doc`을 실행하면, 아래와 같은 API 문서가 자동으로 생성됩니다. 이와 비슷하게 파이썬 문서를 생성해주는 패키지인 `sphinx` 의 경우, 별도의 추가 설치 뿐만 아니라 프로젝트 별로 섬세한 설정이 필요하다는 점을 생각해보면 이는 굉장히 편리한 기능입니다.
84 |
85 | 
86 |
87 | 출처: https://docs.rs/serde_v8/0.49.0/serde_v8/
88 |
89 |
90 |
91 | ### 러스트의 경쟁 언어들
92 |
93 | 위에서 잠깐 설명했듯이, 러스트로 C/C++ 바인딩 함수나 패키지를 대신할 수 있습니다. 그렇다면 왜 C/C++로 직접 프로그램을 만들거나, 다른 비슷한 언어들을 사용하지 않는 걸까요? 일단 C/C++는 생산성이 매우 떨어집니다. 숙련된 개발자가 아니라면, 메모리 안정성과 스레드 안전성을 생각하며 C/C++로 프로그램을 만드는 것 자체가 굉장한 어렵습니다. 따라서 C/C++보다는 이와 비슷한 모던 프로그래밍 언어들이 훨씬 각광받고 있습니다.
94 |
95 | 아래 표는 Rust와 경쟁 관계에 있는 비슷한 언어들의 특징을 정리해놓은 표입니다. 먼저 Apple의 Swift는 컴파일 언어이며, 높은 생산성과 성능을 자랑하지만 주로 iOS 앱 개발에만 사용됩니다. 두 번째로 Go는 높은 생산성과 비교적 빠른 성능을 가지고 있습니다. 주로 네트워크/서버 분야에 많이 사용됩니다. Go는 메모리가 완전히 안전하지 않으며, 컴파일 타임에 이러한 오류를 감지하지 못하는 경우가 많아 안정성이 떨어집니다. 또한 가비지 콜렉션을 사용하기 때문에 러스트에 비해 성능이 떨어지는 단점이 있습니다. 이러한 이유 때문에 빠른 성능과 메모리 안정성이 필요한 경우 러스트가 최고의 옵션이 됩니다.
96 |
97 |
98 |
99 | | | Swift | Go | Rust |
100 | | ------------- | ------------------------------------- | -------------------------------------------------- | ----------------------------------------------------- |
101 | | 개발 | Apple | Google | Mozilla |
102 | | 주 사용처 | iOS, iPadOs, macOS 어플리케이션 | 네트워크 및 서버 프레임워크/어플리케이션 | CPU 사용량이 많은 어플리케이션 혹은 시스템 소프트웨어 |
103 | | 메모리 안전성 | 메모리 누수 문제가 아직 해결되지 않음 | `goroutine` 사용 시 잠재적인 메모리 누수 발생 가능 | 메모리 안전성 보장 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/ch1-02.md:
--------------------------------------------------------------------------------
1 | ## Rust로 뭘 할 수 있나요?
2 |
3 | 러스트는 현재 다양한 분야에서 널리 사용되고 있습니다. 러스트 공식 문서에 설명에 따르면, 아래 네 분야에 가장 많이 사용됩니다.
4 |
5 | 
6 |
7 | 시스템 소프트웨어의 CLI(Command Line Interface)를 만들거나, 고성능 네트워크가 필요한 분야에서 사용되고 있습니다. 러스트의 높은 성능 때문에 웹 어셈블리(WebAssembly)에서 다른 프로그래밍 언어 중에서 가장 많이 사용되고 있습니다. 하드웨어의 CPU나 메모리가 매우 제한적인 임베디드 분야에서는 전통적으로 C/C++가 가장 많이 사용되어 왔는데, 기존에 C/C++로 작성하던 코드를 러스트로 마이그레이션해 생산성과 안정성이 대폭 향상시킨 사례가 많이 보고되고 있습니다.
8 |
9 | > 웹어셈블리란, 브라우저에서 자바스크립트가 아닌 다른 프로그래밍 언어를 실행시킬 수 있는 방법입니다. 즉 기존에 자바스크립트를 사용했을 때 발생하는 성능 저하를 러스트와 웹어셈블리를 사용하면 간단하게 해결할 수 있습니다.
10 |
11 | 
12 |
13 | 가장 유명한 러스트 프로젝트로는 암호화폐 [솔라나(Solana)](https://github.com/solana-labs/solana)가 있습니다. "세계에서 가장 빠른 블록체인"이라는 표어가 보여주듯이 러스트의 성능과 안정성을 잘 이용하고 있는 프로젝트입니다. 솔라나를 이용해 NFT, DeFi 등 다양한 프로젝트가 이어져 나가고 있어서 솔라나는 러스트 생태계에 큰 역할을 하고 있습니다.
14 |
15 | 
16 |
17 | 웹 어플리케이션의 백엔드로 러스트가 사용되기도 합니다. [AppFlowy](https://github.com/AppFlowy-IO/appflowy)는 러스트로 작성된 노션(Notion) 대체제로, 온라인에서 문서를 작성하고 협업할 수 있는 오픈소스 도구입니다.
18 |
19 | 
20 |
21 | 러스트로 모바일 게임을 제작할 수도 있습니다. 가장 유명한 프로젝트로는 Bevy가 있습니다. Bevy를 사용하면 브라우저에서 게임을 실행하거나, 안드로이드, iOS 등 모바일 앱으로 게임을 만들 수도 있습니다.
22 |
23 | 이처럼 러스트는 이미 다양한 분야에서 활용되고 있습니다.
24 |
25 |
26 |
27 | ### 러스트 사용 실제 사례들
28 |
29 | 러스트는 실제 산업 전반에서 다양하게 사용되고 있습니다. 특히 빠른 계산 성능이 필요한 복잡한 프로그램에서 그 진가를 발휘하고 있습니다. 다음은 여러 유명 IT 회사들에서 러스트를 실제로 사용하고 있는 사례들입니다.
30 |
31 |
32 |
33 | #### [Dropbox](https://dropbox.tech/infrastructure/rewriting-the-heart-of-our-sync-engine)
34 |
35 | 드랍박스는 클라우드 저장소 서비스를 운영하는 회사입니다. 드랍박스에서 가장 핵심적인 기능 중 하나는 로컬 컴퓨터에 있는 데이터를 원격 클라우드에 빠르게 동기화하는 것입니다. 기존에 C++로 작성되어 있었던 동기화 로직을 러스트로 재작성했다고 합니다.
36 |
37 |
38 |
39 | #### [Figma](https://blog.figma.com/rust-in-production-at-figma-e10a0ec31929)
40 |
41 | 
42 |
43 | 피그마는 UI 프로토타입을 제작할 수 있는 도구입니다. 웹 기반으로 동작하기 때문에, 화면에 결과를 빠르게 보여주는 것이 중요합니다. 기존의 타입스크립트 서버를 러스트로 재작성한 결과, 비약적인 성능 향상을 얻을 수 있었습니다.
44 |
45 |
46 |
47 | #### [npm](https://www.rust-lang.org/static/pdfs/Rust-npm-Whitepaper.pdf)
48 |
49 | npm은 Node.JS의 패키지 저장소로, 노드 패키지를 다운받으려면 반드시 거쳐야 하는 서비스입니다. 레지스트리 서비스(registry service)의 병목 현상을 해결하기 위해 다양한 프로그래밍 언어를 고려했다고 합니다. Node.JS, Go, Java 등으로 실제 구현도 해보았지만 결과적으로는 러스트가 채택되었습니다.
50 |
51 |
52 |
53 | #### [Discord](https://discord.com/blog/why-discord-is-switching-from-go-to-rust)
54 |
55 | 
56 |
57 | 디스코드는 기존에 Go로 작성된 서비스 백엔드에서 간헐적인 성능 하락이 발생하는 것을 발견했습니다. 위 그래프에서 보라색이 Go 구현체입니다. 주기적으로 CPU 피크가 발생하고 이때문에 응답시간에도 피크가 발생하는 것을 알 수 있습니다. 이런 현상의 원인은 Go의 가비지 컬렉터 때문으로, 러스트로 재작성한 후 CPU 사용량이 안정화되고, 응답 시간이 훨씬 짧아진 것을 알 수 있습니다.
58 |
59 | 이외에도 다양한 기업에서 러스트를 도입해 사용하고 있습니다.
60 |
61 | - 페이스북에서는 백엔드 서버를 작성하는 언어 중 하나로 러스트를 채택했습니다.
62 | - 러스트의 후원 재단인 모질라에서 개발하는 파이어폭스 브라우저의 엔진(Servo Engine)은 러스트로 작성되었습니다.
63 | - Next.js의 컴파일 엔진은 러스트로 재작성되었습니다.
64 | - AWS(아마존웹서비스)의 Lambda에서 컨테이너는 FireCracker라는 러스트 툴 위에서 실행됩니다.
65 | - Sentry 역시 파이썬의 낮은 퍼포먼스를 러스트를 도입해 해결했습니다.
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/src/ch1-03.md:
--------------------------------------------------------------------------------
1 | ## 러스트 개발 환경 설정하기
2 |
3 | 러스트로 코드를 작성하기 위해, 필요한 도구들을 설치해주어야 합니다. 운영체제별로 설치 방법이 조금씩 다르므로 사용하고 계신 환경에 맞추어 설치하세요.
4 |
5 |
6 |
7 | ### 러스트 툴체인 설치하기
8 |
9 | 가장 먼저 러스트 언어를 컴파일해주는 컴파일러와 시스템 매니저인 `cargo` 를 설치합니다. 이 두 가지 도구는 `rustup` 이라고 하는 툴체인에 포함되어 있기 때문에, `rustup`만 설치하면 됩니다. 공식 홈페이지 https://rustup.rs/# 로 접속하면 운영체제별 설치 방법을 볼 수 있습니다.
10 |
11 |
12 |
13 | #### macOS / Linux
14 |
15 | 맥(macOS) 또는 리눅스 사용자들은 아래 명령어를 통해 간단하게 설치가 가능합니다.
16 |
17 | ```bash
18 | $ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
19 | ```
20 |
21 |
22 |
23 | #### Windows
24 |
25 | 윈도우 사용자의 경우 위 홈페이지에서 34비트 또는 64비트 설치 파일을 다운로드 받습니다.
26 |
27 | 
28 |
29 |
30 |
31 | ### Visual Studio Code 설치 및 설정하기
32 |
33 | 이 책에서는 통합 개발 환경(Integrated Development Environment, IDE)로 Visual Studio Code(이하 VSCode)를 사용합니다. 러스트에서 제공하는 컴파일, 디버깅, 언어 서버(Language server) 등의 기능을 쉽고 편리하게 사용할 수 있기 때문에 VSCode를 사용하시는 것을 추천합니다.
34 |
35 |
36 |
37 | #### VSCode 설치
38 |
39 | 
40 |
41 | [Visual Studio Code 다운로드 페이지](https://code.visualstudio.com/download)로 이동한 다음, 운영체제에 맞는 설치파일을 다운받고 설치를 진행합니다.
42 |
43 |
44 |
45 | #### 확장 프로그램(Extension) 설치
46 |
47 | 이제 Visual Studio Code를 실행합니다. Visual Studio Code에서는 `rust-analyzer`란 확장 프로그램 하나만 설치하면 됩니다. `rust-analyzer`는 러스트 코드를 작성하는데 많은 도움을 주는 확장 프로그램입니다. 코드 자동완성, 에러 표시, 관련 문서 표시 등 다양한 기능이 있지만 가장 좋은 기능 중 하나는 변수의 타입을 추측해서 화면에 표시해주는 것입니다. 자세한 내용은 나중에 소스코드를 작성할 때 다시 살펴보겠습니다.
48 |
49 | 
50 |
51 | 설치를 위해서는 화면 왼쪽의 블록 모양 버튼을 누른 다음, `rust-analyzer`를 검색합니다. 가장 위에 나오는 확장 프로그램을 설치합니다.
52 |
53 |
54 |
55 | ### 프로젝트 생성하기
56 |
57 | 프로그래밍에서는 항상 프로젝트를 폴더 단위로 관리합니다. 여기서는 새로운 프로젝트를 하나 생성해 보겠습니다.
58 |
59 | 
60 |
61 | Visual Studio Code의 상단 메뉴에서, "파일 - 폴더 열기"를 클릭합니다. 폴더를 새로 생성한 다음, 해당 폴더를 선택합니다. 그러면 창이 새로고침되고 빈 프로젝트 화면이 나타납니다.
62 |
63 |
64 |
65 | #### 파이썬 폴더 만들기
66 |
67 | 우리는 파이썬 코드와 러스트 코드를 비교하면서 러스트 문법을 배워나갈 것입니다. 따라서 파이썬 프로젝트와 러스트 프로젝트를 같은 폴더 밑에 만들어 두겠습니다. 현재 경로가 `/code/temp/`라고 했을 때, 하위 폴더로 "python" 폴더를 하나 생성합니다. 그리고 폴더 안에 파이썬 코드가 들어갈 `main.py` 모듈을 생성합니다. 현재 파일 구조는 다음과 같습니다.
68 |
69 | ```
70 | .
71 | └── python
72 | └── main.py
73 | ```
74 |
75 |
76 |
77 | #### 러스트 폴더 만들기
78 |
79 | 러스트 프로젝트를 시작하는 방법은 두 가지가 있습니다.
80 |
81 | > 아직 과정을 따라하지 마세요! 저희는 두 번째 방법으로 폴더를 만들 것입니다.
82 |
83 | 첫째, 터미널에서 빈 폴더에서 `cargo init` 으로 프로젝트를 시작합니다. 이 경우에는 현재 폴더에 러스트 프로젝트가 생성됩니다.
84 |
85 | ```bash
86 | $ cargo init
87 | ```
88 |
89 | 예를 들어, 터미널에서 현재 경로가 `/code/temp/` 라고 했을 때, `cargo init` 을 수행하면 이 폴더가 프로젝트 폴더가 됩니다. 방금 파이썬 폴더를 새로 만들었기 대문에, 현재 폴더의 파일 목록을 출력하는 `ls` 명령어를 수행한 결과는 다음과 같습니다.
90 |
91 | ```bash
92 | /code/temp $ ls
93 | Cargo.toml src python
94 | ```
95 |
96 | 현재 폴더에서 `cargo init` 으로 프로젝트를 생성하는 경우에는 현재 폴더 이름이 프로젝트 이름이 됩니다. 따라서 현재 생성된 프로젝트 이름은 "temp"가 됩니다. 프로젝트 이름을 확인하는 방법은 잠시 후에 `Cargo.toml` 파일을 설명하면서 다루겠습니다.
97 |
98 | 이제 두 번째 방법으로 프로젝트 만들기를 함께 해보겠습니다. 먼저 터미널을 열기 위해서 메뉴의 "터미널 - 새 터미널"을 클릭합니다. 또는 단축키 `` Ctrl + Shift + ` ``를 입력해도 됩니다. 이제 터미널에 `cargo new <프로젝트명>` 명령어를 입력해서 프로젝트 이름을 직접 설정할 수 있습니다. 이 경우에는 하위 폴더가 해당 이름으로 만들어집니다. 현재 폴더 경로가 동일하게 `/code/temp/` 라고 했을 때, 다음과 같이 실행해 보겠습니다.
99 |
100 | ```bash
101 | $ cargo new rust_part
102 | ```
103 |
104 | 그러면 현재 폴더 밑에 "rust_part"라는 폴더가 생성되고, 이 폴더 안에 파일이 생성됩니다. 이제 현재 경로에서는 아래와 같이 두 개의 폴더가 존재합니다.
105 |
106 | ```bash
107 | /code/temp $ ls
108 | rust_part python
109 | ```
110 |
111 | 최종적으로 현재 폴더의 파일 구조는 다음과 같습니다.
112 |
113 | ```
114 | .
115 | ├── rust_part
116 | │ ├── Cargo.toml
117 | │ └── src
118 | └── python
119 | └── main.py
120 | ```
121 |
122 |
123 |
124 | #### 러스트 폴더 구조
125 |
126 | 러스트의 프로젝트 폴더에는 다음과 같은 파일 구조가 만들어집니다.
127 |
128 | ```
129 | .
130 | ├── Cargo.toml
131 | └── src
132 | └── main.rs
133 | ```
134 |
135 | 먼저 `Cargo.toml` 파일은 프로젝트의 모든 설정값을 가지고 있는 파일입니다. 파일의 구조는 아래와 같이 생겼습니다.
136 |
137 | ```toml
138 | [package]
139 | name = "rust_part"
140 | version = "0.1.0"
141 | edition = "2021"
142 |
143 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
144 |
145 | [dependencies]
146 |
147 | ```
148 |
149 | - `[package]` 부분에는 현재 프로젝트의 이름과 버전, 그리고 러스트 에디션 버전이 들어 있습니다. 러스트 에디션은 현재 연도보다 이전 연도가 들어 있을 수도 있는데, 이는 러스트 버전의 호환성을 위해서 버전을 에디션으로 구분하고 있기 때문에 그렇습니다.
150 | - `[dependencies]` 는 현재 프로젝트에서 설치하는 크레이트(러스트에서는 패키지를 크레이트(crate)라고 부릅니다)의 이름과 버전이 들어가게 됩니다. 나중에 크레이트를 설치할 때 자세히 다루도록 하겠습니다.
151 |
152 | `src` 폴더가 실제 러스트 소스코드가 들어가는 곳입니다. 현재는 코드의 시작 지점(entry point)인 `main.rs` 파일만 들어 있습니다. 해당 파일에는 `main()` 함수가 들어 있는데, `main.rs` 가 컴파일되고 바이너리가 실행될 때 바로 이 `main()` 함수가 실행됩니다. 따라서 반드시 `main.rs` 파일이 존재해야 하고, 이 파일 안에 `main()` 함수가 존재해야 코드가 컴파일되고 실행될 수 있습니다.
153 |
154 |
--------------------------------------------------------------------------------
/src/ch1-04.md:
--------------------------------------------------------------------------------
1 | ## 러스트 코드 실행하기
2 |
3 | 이번에는 러스트 코드를 컴파일하고 실행하는 방법을 알아보겠습니다.
4 |
5 |
6 |
7 | ### 코드 컴파일하기
8 |
9 | `main.rs`파일을 열어보면, `main()` 함수에 `"Hello, world!"` 라는 문자열을 프린트하는 `println!` 만 들어있습니다.
10 |
11 | ```rust
12 | fn main() {
13 | println!("Hello, world!");
14 | }
15 |
16 | ```
17 |
18 | 코드를 컴파일하려면 `cargo build`를 사용합니다.
19 |
20 | ```bash
21 | $ cargo build
22 |
23 | Compiling rust_part v0.1.0 (/Users/code/temp)
24 | Finished dev [unoptimized + debuginfo] target(s) in 2.07s
25 | ```
26 |
27 | 러스트 폴더 밑에 `target` 이라는 폴더가 생성되고, 여기 밑에 `debug` 폴더를 열어보면 `rust_part`라는 바이너리 파일이 존재합니다.
28 |
29 | 
30 |
31 | 바이너리 파일을 실행하면 다음과 같이 출력됩니다.
32 |
33 | ```bash
34 | $ ./target/debug/rust_part
35 | Hello, world!
36 | ```
37 |
38 | 기본적으로 빌드는 디버그 모드로 수행됩니다. 디버그 모드는 좀더 빨리 컴파일이 수행되지만, 프로그램의 실행 속도는 느려질 수 있습니다. 하지만 실제로 프로그램을 배포할 때는 컴파일 단계에서 코드를 최적화해주어야 성능을 제대로 사용할 수 있습니다. 따라서 프로그램을 배포할 때에는 다음과 같이 `--release` 옵션을 추가로 사용합니다.
39 |
40 | ```bash
41 | $ cargo build --release
42 |
43 | Compiling notebook v0.1.0 (/Users/code/temp)
44 | Finished release [optimized] target(s) in 1.34s
45 | ```
46 |
47 | 이때 바이너리 파일이 `target/release` 폴더에 생성되는 것에 주의하세요.
48 |
49 |
50 |
51 | ### 코드 실행하기
52 |
53 | 위에서 코드를 컴파일한 다음 바이너리를 실행하는 방법을 소개했습니다. 그런데 컴파일한 뒤에 바이너리를 `target` 폴더에서 찾아서 실행하는 것이 너무 번거롭습니다. 그래서 러스트에서는 `cargo run` 명령어로 컴파일과 바이너리 실행을 한 번에 할 수 있습니다.
54 |
55 | ```bash
56 | $ cargo run
57 | Compiling temp v0.1.0 (/Users/code/temp)
58 | Finished dev [unoptimized + debuginfo] target(s) in 4.55
59 | Running `target/debug/temp`
60 | Hello, world!
61 | ```
62 |
63 | 명령어를 터미널에 입력하면, 먼저 코드가 컴파일되고 바이너리가 실행되는 것을 알 수 있습니다. 바이너리가 실행되어 `Hello, world!` 가 터미널에 출력됩니다.
64 |
65 | > `cargo run` 명령어를 사용했을 때, 기본적으로는 디버그 모드로 빌드됩니다. 만일 릴리즈 모드로 실행해보고 싶다면 `--release`옵션을 추가하면 릴리즈 모드로 빌드 후 바이너리를 실행해줍니다.
66 |
67 |
68 |
69 | ### rustfmt
70 |
71 | 러스트에는 내장 코드 포맷터인 rustfmt가 설치되어 있습니다. VSCode에서는 단축키를 사용해 코드를 포맷할 수 있습니다. 윈도우 또는 리눅스의 경우는 `Alt + Shift + F`, 맥의 경우는 `Option + Shift + F`를 누르면 됩니다. 또는 터미널에서 `rustfmt src/main.rs` 명령어를 사용해도 됩니다.
72 |
73 | 아래 코드를 `main.rs` 에 입력하고, 단축키를 사용해 포맷을 실행해 보겠습니다.
74 |
75 | ```rust
76 | fn main( ){
77 | println! (
78 | "Please run 'rustfmt!'"
79 | );
80 | }
81 | ```
82 |
83 | 실행 결과
84 |
85 | ```rust
86 | fn main() {
87 | println!("Please run 'rustfmt!'");
88 | }
89 |
90 | ```
91 |
92 | 공백 간격, 줄바꿈 등을 rustfmt가 알아서 처리해줍니다. 이처럼 공식적인 공통 포맷터가 존재하기 때문에 어떤 러스트 프로젝트라도 일관적인 소스코드 관리가 가능하다는 것이 러스트의 큰 장점입니다.
93 |
94 | 만약 현재 파일을 포함한 프로젝트의 전체 러스트 코드를 한꺼번에 포매팅하고 싶다면 아래 명령어를 사용합니다.
95 |
96 | ```bash
97 | cargo fmt
98 | ```
99 |
100 |
--------------------------------------------------------------------------------
/src/ch10-00.md:
--------------------------------------------------------------------------------
1 | # CH10. 에러 처리
2 |
3 |
4 |
5 | ## 파이썬의 예외 처리
6 |
7 |
8 |
9 | ### LBYL
10 |
11 | 도약하기 전에 살펴보세요(Look before you leap). 이 코딩 스타일은 호출이나 조회를 하기 전에 명시적으로 전제 조건을 테스트합니다. 이 스타일은 많은 `if` 문이 있다는 특징이 있습니다.
12 |
13 | ```python
14 | if key in mapping:
15 | return mapping[key]
16 |
17 | ```
18 |
19 | > 멀티 스레드 환경에서 LBYL 접근 방식은 '보기'와 '도약' 사이에 경쟁 조건이 발생할 위험이 있습니다.
20 |
21 |
22 |
23 | ### EAFP
24 |
25 | 허락보다 용서받는 것이 더 쉽습니다(Easier to ask for forgiveness than permission). 이 코딩 스타일은 유효한 키 또는 속성이 있다고 가정하고, 가정이 거짓으로 판명되면 예외를 포착합니다. 이 깔끔하고 빠른 스타일은 많은 `try except` 블럭이 있다는 특징이 있습니다.
26 |
27 | ```python
28 | try:
29 | file = open("file.txt", "r")
30 | except FileNotFoundError:
31 | print("File not found")
32 |
33 | ```
34 |
35 |
36 |
37 | ## 러스트의 에러 처리
38 |
39 | 오류 처리에 대한 Rust의 접근 방식은 LBYL(Look Before You Leap, 도약하기 전에 살펴보기) 및 EAFP(Easier to Ask Forgiveness than Permission, 권한보다 용서를 구하기) 프로그래밍 스타일과 일부 유사점을 공유하지만, 이 둘 중 어느 쪽과도 엄격하게 일치하지는 않습니다.
40 |
41 | LBYL과 마찬가지로 Rust의 소유권 및 차용 시스템을 사용하면 컴파일러가 컴파일 시점에 많은 오류를 포착할 수 있으므로 런타임 검사의 필요성이 줄어듭니다. 따라서 Rust 코드는 더 효율적이고 오류가 덜 발생합니다. 그러나 Rust는 또한 "결과" 및 "옵션" 유형을 제공하여 보다 우아한 오류 처리 및 복구를 허용하며, 이는 EAFP 스타일에 더 부합합니다.
42 |
43 | Rust가 사용하는 "결과" 유형은 오류가 발생해도 LBYL에서처럼 즉시 중단하지 않고 프로그램을 계속 실행할 수 있다는 점에서 EAFP 스타일과 유사합니다. 연산을 시도하고 실패하면 오류를 나타내는 "결과"를 반환하여 오류를 우아하게 처리하는 것이 아이디어입니다. 이 접근 방식은 LBYL 검사로 채워진 코드보다 읽고 추론하기가 더 쉽습니다.
44 |
45 | 따라서 Rust는 LBYL 또는 EAFP와 엄격하게 일치하지는 않지만, 두 접근 방식과 몇 가지 유사점을 공유하며 두 스타일의 장점을 결합한 고유한 오류 처리 방식을 제공합니다.
46 |
47 | - `panic!`를 사용하여 치명적인 오류를 처리하는 방법
48 | - 값이 선택 사항이거나 값이 없어도 오류 조건이 아닌 경우 `Option` 열거형을 사용하는 방법
49 | - 오류가 발생할 수 있고 호출자가 문제를 처리해야 하는 경우 `Result` 열거형을 사용하는 방법
50 |
--------------------------------------------------------------------------------
/src/ch10-01.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## panic!
4 |
5 | 파이썬에서 코드를 즉시 종료시키고 싶다면 예외를 발생시키면 됩니다. 어떤 종류의 예외든 상관없지만, 가장 일반적인 경우를 발생시켜 보면 다음과 같습니다.
6 |
7 | ```python
8 | raise Exception
9 | ```
10 |
11 | 실행 결과
12 |
13 | ```
14 | Traceback (most recent call last):
15 | File "/temp/python/main.py", line 1, in
16 | raise Exception
17 | Exception
18 | ```
19 |
20 |
21 |
22 | 러스트에서 프로그램이 예상치 못한 오류로 종료되는 경우, 패닉이 발생한다고 합니다. 패닉이 발생하는 경우는 두 가지입니다.
23 |
24 | - 패닉이 발생하는 코드를 실행(예: 배열에 잘못된 인덱스를 참조하는 경우)
25 | - `panic!` 매크로를 직접 실행하는 경우
26 |
27 | `panic!`은 러스트에서 제공하는 편리한 매크로로, 프로그램이 즉시 종료됩니다.
28 |
29 | ```rust,should_panic
30 | fn main() {
31 | panic!("🤯");
32 | }
33 |
34 | ```
35 |
36 | 실행 결과
37 |
38 | ```rust
39 | thread 'main' panicked at '🤯', src/main.rs:2:5
40 | note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
41 | ```
42 |
43 | 여기서 어떤 코드가 문제인지를 알고 싶다면, 컴파일러가 알려준 대로 환경 변수 `RUST_BACKTRACE=1`를 사용해 컴파일하면 됩니다.
44 |
45 | ```
46 | RUST_BACKTRACE=1 cargo run
47 | Finished dev [unoptimized + debuginfo] target(s) in 0.05s
48 | Running `target/debug/rust_part`
49 | thread 'main' panicked at '🤯', src/main.rs:2:5
50 | stack backtrace:
51 | 0: rust_begin_unwind
52 | at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/std/src/panicking.rs:575:5
53 | 1: core::panicking::panic_fmt
54 | at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/panicking.rs:64:14
55 | 2: chat_server::main
56 | at ./src/main.rs:2:5
57 | 3: core::ops::function::FnOnce::call_once
58 | at /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs:507:5
59 | note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
60 | ```
61 |
62 |
63 |
64 | > 릴리즈 모드로 빌드할 경우, 백트레이스는 포함되지 않기 때문에 컴파일 시 별도의 디버그 심볼을 제공해주어야 합니다.
65 |
--------------------------------------------------------------------------------
/src/ch10-02.md:
--------------------------------------------------------------------------------
1 | ## Option 열거형
2 |
3 | 다음과 같은 함수를 생각해 보겠습니다. 입력받는 파라미터에 따라서 결과값이 있을 수도 있고, 없을 수도 있습니다.
4 |
5 | ```rust
6 | fn give_some_or_none(some: bool) -> Option {
7 | if some {
8 | Some(String::from("💖"))
9 | } else {
10 | None
11 | }
12 | }
13 | ```
14 |
15 | 이전에 열거형에서 배웠던 것처럼, 어떤 계산의 결과가 비어 있거나 없을 가능성이 있는 경우, std 라이브러리의 `Option`을 써서 이를 표현할 수 있습니다. 문제는 이를 사용하는 곳에서 결과값을 어떻게 처리할 것인지입니다. 앞에서는 `match`문을 사용해 각 경우를 모두 처리할 수 있다고 배웠습니다. 이렇게 각 경우를 명시적으로 처리할 수도 있지만, 암묵적으로 함수의 결과를 처리하는 방법인 `unwrap` 을 사용할 수도 있습니다.
16 |
17 | ```rust
18 | fn give_some_or_none(some: bool) -> Option {
19 | if some {
20 | Some(String::from("💖"))
21 | } else {
22 | None
23 | }
24 | }
25 |
26 | fn main() {
27 | println!("{}", give_some_or_none(true).unwrap());
28 | }
29 |
30 | ```
31 |
32 | 그런데 만일 `give_some_or_none` 함수에 `false`가 주어진다면 어떨까요?
33 |
34 | ```rust
35 | fn main() {
36 | println!("{}", give_some_or_none(false).unwrap());
37 | }
38 | ```
39 |
40 | 실행 결과
41 |
42 | ```
43 | thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:10:45
44 | ```
45 |
46 | 여기서 알 수 있듯이, `unwrap`을 사용하면 `None`이 리턴되는 경우에 패닉이 발생합니다. 따라서 `Option`를 리턴하는 함수에 `unwrap`을 사용하려면 해당 함수가 반드시 `Some`을 리턴하는 것이 확실해야만 합니다. 그렇지 않으면 `match`를 사용해 모든 경우를 처리하는 게 올바른 방법입니다.
47 |
48 |
49 |
50 | ## unwrap_or...
51 |
52 | 하지만 때로는 `Some`인 경우는 그냥 값을 바로 사용하고, 에러가 발생하는 경우만 따로 처리하고 싶은 경우가 있습니다. `if let Some` 을 사용해도 되지만, 함수형 프로그래밍답게 처리하는 세 가지 방법이 존재합니다.
53 |
54 |
55 |
56 | ### unwrap_or
57 |
58 | 포함된 Some 값 또는 제공된 기본값을 반환합니다.
59 |
60 | unwrap_or에 전달된 인수는 즉시 평가됩니다. 함수 호출의 결과를 전달하는 경우 느리게 평가되는 unwrap_or_else를 사용하는 것이 좋습니다.
61 |
62 | ```rust
63 | fn main() {
64 | assert_eq!(Some("car").unwrap_or("bike"), "car");
65 | assert_eq!(None.unwrap_or("bike"), "bike");
66 | }
67 |
68 | ```
69 |
70 |
71 |
72 |
73 |
74 | ### unwrap_or_else
75 |
76 | 포함된 Some 값을 반환하거나 클로저에서 계산합니다.
77 |
78 | ```rust
79 | let k = 10;
80 | assert_eq!(Some(4).unwrap_or_else(|| 2 * k), 4);
81 | assert_eq!(None.unwrap_or_else(|| 2 * k), 20);
82 | ```
83 |
84 |
85 |
86 | ### unwrap_or_default
87 |
88 | 포함된 Some 값 또는 기본값을 반환합니다.
89 |
90 | self 인자를 소비한 다음 Some이면 포함된 값을 반환하고, None이면 해당 타입의 기본값을 반환합니다.
91 |
92 | ```rust
93 | let x: Option = None;
94 | let y: Option = Some(12);
95 |
96 | assert_eq!(x.unwrap_or_default(), 0);
97 | assert_eq!(y.unwrap_or_default(), 12);
98 | ```
99 |
100 |
101 |
102 | ## ?
103 |
104 | 결과가 `Some` 이 아닌 경우 즉시 함수를 종료하고 `None`을 반환합니다.
105 |
106 | ```rust
107 | fn give_some_or_none(some: bool) -> Option {
108 | if some {
109 | Some(String::from("💖"))
110 | } else {
111 | None
112 | }
113 | }
114 |
115 | fn question_mark(bool: bool) -> Option {
116 | let some = give_some_or_none(bool)?;
117 | Some(some)
118 | }
119 |
120 | fn main() {
121 | println!("{:?}", question_mark(true));
122 | println!("{:?}", question_mark(false));
123 | }
124 |
125 | ```
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/src/ch10-03.md:
--------------------------------------------------------------------------------
1 | ## Result 열거형
2 |
3 | `Result` 열거형은 `Option` 열거형과 비슷하지만, `Option` 열거형은 `Some`과 `None`만을 가지고 있지만, `Result` 열거형은 `Ok`와 `Err`를 가지고 있습니다.
4 |
5 | ```rust
6 | use std::fmt::Error;
7 |
8 | fn give_ok_or_err(bool: bool) -> Result {
9 | if bool {
10 | Ok(String::from("💖"))
11 | } else {
12 | Err(Error)
13 | }
14 | }
15 |
16 | fn main() {
17 | for bool in [true, false].iter() {
18 | let result = give_ok_or_err(*bool);
19 | match result {
20 | Ok(value) => println!("value: {}", value),
21 | Err(e) => println!("error: {}", e),
22 | }
23 | }
24 | }
25 |
26 | ```
27 |
28 |
29 |
30 |
31 |
32 | ## Unwrap...
33 |
34 |
35 |
36 |
37 |
38 | ### unwrap
39 |
40 | ```rust
41 | use std::fmt::Error;
42 |
43 | fn give_ok_or_err(bool: bool) -> Result {
44 | if bool {
45 | Ok(String::from("💖"))
46 | } else {
47 | Err(Error)
48 | }
49 | }
50 |
51 | fn main() {
52 | let result = give_ok_or_err(false).unwrap();
53 | println!("{}", result);
54 | }
55 |
56 | ```
57 |
58 |
59 |
60 |
61 |
62 | ### unwrap_or
63 |
64 |
65 | `unwrap_or`는 `Result`를 `T`로 변환합니다. 결과가 `Ok(v)`이면 `v`를 반환하고, `Err(e)`이면 인수를 반환합니다.
66 |
67 |
68 | ```rust
69 | use std::fmt::Error;
70 |
71 | fn give_ok_or_err(bool: bool) -> Result {
72 | if bool {
73 | Ok(String::from("💖"))
74 | } else {
75 | Err(Error)
76 | }
77 | }
78 |
79 | fn main() {
80 | let result = give_ok_or_err(false).unwrap_or(String::from("💔"));
81 | println!("{}", result);
82 | }
83 |
84 | ```
85 |
86 |
87 |
88 |
89 |
90 | ## ok_or...
91 |
92 | `ok_or`와 `ok_or_else`는 Option를 Result로 변환합니다.
93 |
94 | ### ok_or
95 |
96 | `Option`를 `Result`로 변환하여 일부(`v`)를 `Ok(v)`로, 없음(`None`)을 `Err(err)`로 매핑합니다.
97 |
98 | `ok_or에` 전달된 인수는 즉시 평가되며, 함수 호출의 결과를 전달하는 경우 느리게 평가되는 `ok_or_else`를 사용하는 것이 좋습니다.
99 |
100 |
101 |
102 | ```rust
103 | fn main() {
104 | let x = Some("foo");
105 | assert_eq!(x.ok_or(0), Ok("foo"));
106 |
107 | let x: Option<&str> = None;
108 | assert_eq!(x.ok_or(0), Err(0));
109 | }
110 |
111 | ```
112 |
113 |
114 |
115 | ### ok_or_else
116 |
117 |
118 |
119 | `Option`를 `Result``로 변환하여 일부(v)를 Ok(v)로, 없음을 Err(err())로 매핑합니다.
120 |
121 | ```rust
122 | fn main() {
123 | let x = Some("foo");
124 | assert_eq!(x.ok_or_else(|| 0), Ok("foo"));
125 |
126 | let x: Option<&str> = None;
127 | assert_eq!(x.ok_or_else(|| 0), Err(0));
128 | }
129 |
130 | ```
131 |
132 |
133 |
134 |
135 |
136 | ## ?
137 |
138 | 결과가 `Ok` 이 아닌 경우 즉시 함수를 종료하고 `Err`을 반환합니다.
139 |
140 | ```rust
141 | use std::fmt::Error;
142 |
143 | fn give_ok_or_err(bool: bool) -> Result {
144 | if bool {
145 | Ok(String::from("💖"))
146 | } else {
147 | Err(Error)
148 | }
149 | }
150 |
151 | fn question_mark(bool: bool) -> Result {
152 | let result = give_ok_or_err(bool)?;
153 | Ok(result)
154 | }
155 |
156 | fn main() {
157 | let result = question_mark(true);
158 | println!("{:?}", result);
159 | let result = question_mark(false);
160 | println!("{:?}", result);
161 | }
162 |
163 | ```
164 |
165 |
166 |
167 |
168 |
169 | ## 커스텀 예외 정의하기
170 |
171 |
172 |
173 | ```python
174 | import os
175 |
176 |
177 | def get_content():
178 | filepath = os.path.join(os.path.pardir, "test.txt")
179 | with open(filepath, "r") as f:
180 | return f.read()
181 |
182 |
183 | class CustomError(Exception):
184 | pass
185 |
186 |
187 | if __name__ == '__main__':
188 | try:
189 | with open("hello.txt", "r") as file:
190 | file.read()
191 | except FileNotFoundError as exc:
192 | print(exc)
193 | except:
194 | print("Unexpected error")
195 |
196 | try:
197 | content = get_content()
198 | except:
199 | raise CustomError
200 | print(content)
201 |
202 | ```
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 | ```rust,ignore
211 | use std::fmt;
212 | use std::fs::File;
213 | use std::io::{Error, ErrorKind, Read};
214 | use std::path::Path;
215 |
216 | fn get_content() -> Result {
217 | let mut content = String::new();
218 | let filepath = Path::new(std::env::current_dir().unwrap().parent().unwrap()).join("test.txt");
219 | File::open(filepath)?.read_to_string(&mut content)?;
220 | Ok(content)
221 | }
222 |
223 | #[derive(Debug, Clone)]
224 | struct CustomError;
225 |
226 | impl fmt::Display for CustomError {
227 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228 | write!(f, "File not found!")
229 | }
230 | }
231 |
232 | fn main() {
233 | let file = File::open("hello.txt");
234 | match file {
235 | Ok(file) => println!("{:?}", file),
236 | Err(error) => match error.kind() {
237 | ErrorKind::NotFound => println!("{:?}", error),
238 | _ => println!("Unexpected error"),
239 | },
240 | }
241 |
242 | let content = get_content().unwrap_or_else(|_| {
243 | panic!("{}", CustomError);
244 | });
245 | println!("{}", content);
246 | }
247 |
248 | ```
249 |
250 |
251 |
252 |
--------------------------------------------------------------------------------
/src/ch10-04.md:
--------------------------------------------------------------------------------
1 | ## 에러 로깅
2 |
3 |
4 |
5 | ```toml
6 | [package]
7 | name = "rust_part"
8 | version = "0.1.0"
9 | edition = "2021"
10 |
11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12 |
13 | [dependencies]
14 | log = "0.4"
15 | pretty_env_logger = "0.4.0"
16 |
17 |
18 | [dev-dependencies]
19 |
20 | ```
21 |
22 |
23 |
24 | ### 기본 사용법
25 |
26 | ```rust
27 | extern crate pretty_env_logger;
28 | #[macro_use] extern crate log;
29 |
30 | fn main() {
31 | pretty_env_logger::init();
32 |
33 | trace!("a trace example");
34 | debug!("deboogging");
35 | info!("such information");
36 | warn!("o_O");
37 | error!("boom");
38 | }
39 | ```
40 |
41 |
42 |
43 | ```bash
44 | RUST_LOG=trace cargo run
45 | ```
46 |
47 |
48 |
49 |
50 |
51 | ```
52 | TRACE rust_part > a trace example
53 | DEBUG rust_part > deboogging
54 | INFO rust_part > such information
55 | WARN rust_part > o_O
56 | ERROR rust_part > boom
57 | ```
58 |
59 |
60 |
61 | ### 커스터마이징
62 |
63 |
64 |
65 | ```rust
66 | use log::{debug, error, info, trace, warn};
67 |
68 | const LOG_LEVEL: log::Level = log::Level::Trace;
69 |
70 | fn main() {
71 | let mut log_builder = pretty_env_logger::formatted_timed_builder();
72 | log_builder.parse_filters(LOG_LEVEL.as_str()).init();
73 |
74 | trace!("a trace example");
75 | debug!("deboogging");
76 | info!("such information");
77 | warn!("o_O");
78 | error!("boom");
79 | }
80 |
81 | ```
82 |
83 |
84 |
85 | ```
86 | 2023-03-04T10:21:20.575Z TRACE rust_part > a trace example
87 | 2023-03-04T10:21:20.575Z DEBUG rust_part > deboogging
88 | 2023-03-04T10:21:20.575Z INFO rust_part > such information
89 | 2023-03-04T10:21:20.575Z WARN rust_part > o_O
90 | 2023-03-04T10:21:20.575Z ERROR rust_part > boom
91 | ```
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/ch11-00.md:
--------------------------------------------------------------------------------
1 | # CH11. 스마트 포인터
2 |
3 |
4 |
5 | ## 포인터란?
6 |
7 | 포인터는 메모리에 주소를 포함하는 변수에 대한 일반적인 개념입니다. 이 주소는 다른 데이터를 참조하거나 "가리키고" 있습니다. Rust에서 가장 일반적인 종류의 포인터는 4장에서 배운 참조입니다. 참조는 & 기호로 표시되며 참조가 가리키는 값을 차용합니다. 데이터를 참조하는 것 외에는 특별한 기능이 없으며 오버헤드도 없습니다.
8 |
9 | 반면 스마트 포인터는 포인터처럼 작동하지만 추가적인 메타데이터와 기능을 가진 데이터 구조입니다. 스마트 포인터는 C++에서 시작되었으며 다른 언어에도 존재하기 때문에 스마트 포인터의 개념은 Rust에만 있는 것이 아닙니다. Rust에는 표준 라이브러리에 정의된 다양한 스마트 포인터가 있어 레퍼런스가 제공하는 기능 이상의 기능을 제공합니다. 일반적인 개념을 살펴보기 위해 참조 계수 스마트 포인터 유형을 포함하여 스마트 포인터의 몇 가지 다른 예를 살펴보겠습니다. 이 포인터를 사용하면 소유자 수를 추적하고 소유자가 남아 있지 않을 경우 데이터를 정리하여 데이터에 여러 소유자를 허용할 수 있습니다.
10 |
11 | 소유권과 차용이라는 개념을 가진 Rust는 참조와 스마트 포인터 사이에 또 다른 차이점이 있는데, 참조는 데이터를 차용할 뿐이지만 많은 경우 스마트 포인터는 자신이 가리키는 데이터를 소유합니다.
12 |
13 | 당시에는 스마트 포인터를 그렇게 부르지 않았지만, 이 책에서 이미 8장의 String과 Vec를 포함해 몇 가지 스마트 포인터를 접한 적이 있습니다. 이 두 가지 유형은 모두 약간의 메모리를 소유하고 있고 이를 조작할 수 있기 때문에 스마트 포인터로 간주됩니다. 또한 메타데이터와 추가 기능 또는 보증이 있습니다. 예를 들어 문자열은 용량을 메타데이터로 저장하고 데이터가 항상 유효한 UTF-8이 되도록 보장하는 추가 기능을 가지고 있습니다.
14 |
15 | 스마트 포인터는 일반적으로 구조체를 사용하여 구현됩니다. 일반 구조체와 달리 스마트 포인터는 디레프 및 드롭 특성을 구현합니다. Deref 특성을 사용하면 스마트 포인터 구조체의 인스턴스가 참조처럼 동작할 수 있으므로 참조 또는 스마트 포인터와 함께 작동하도록 코드를 작성할 수 있습니다. Drop 특성을 사용하면 스마트 포인터의 인스턴스가 범위를 벗어날 때 실행되는 코드를 사용자 정의할 수 있습니다. 이 장에서는 두 가지 특성에 대해 설명하고 스마트 포인터에 왜 중요한지 설명하겠습니다.
16 |
17 | 스마트 포인터 패턴은 Rust에서 자주 사용되는 일반적인 디자인 패턴이므로 이 장에서는 기존의 모든 스마트 포인터를 다루지는 않을 것입니다. 많은 라이브러리에는 자체 스마트 포인터가 있으며, 직접 작성할 수도 있습니다. 여기서는 표준 라이브러리에서 가장 일반적인 스마트 포인터를 다루겠습니다:
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/ch11-01.md:
--------------------------------------------------------------------------------
1 | ## Box 타입
2 |
3 | 앞에서 배운 원시 타입과 구조체 타입들은 모두 크기가 일정했습니다. 그런데 만일 어떤 타입의 크기를 컴파일 타임에 미리 알 수 없다면 어떨까요? 어떤 타입의 크기가 런타임에 정해지는 경우가 있을까요? 다음과 같이 자기 자신을 필드값의 타입으로 갖는 재귀 형태의 구조체(Recursive type)를 정의해 보겠습니다.
4 |
5 | `Node` 타입의 크기를 컴파일 타임에 미리 알 수 없다.
6 |
7 | ```rust,ignore
8 | struct Node {
9 | value: i32,
10 | next: Option,
11 | }
12 |
13 | fn main() {
14 | let mut head = Node {
15 | value: 1,
16 | next: None,
17 | };
18 | head.next = Some(Node {
19 | value: 2,
20 | next: None,
21 | });
22 | println!("{}", head.value);
23 | }
24 |
25 | ```
26 |
27 |
28 |
29 | ```
30 | error[E0072]: recursive type `Node` has infinite size
31 | --> src/main.rs:1:1
32 | |
33 | 1 | struct Node {
34 | | ^^^^^^^^^^^
35 | 2 | value: i32,
36 | 3 | next: Option,
37 | | ---- recursive without indirection
38 | |
39 | help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
40 | |
41 | 3 | next: Option>,
42 | | ++++ +
43 | ```
44 |
45 |
46 |
47 | `Box` 를 사용하라고 함
48 |
49 | ```rust
50 | struct Node {
51 | value: i32,
52 | next: Option>,
53 | }
54 |
55 | fn main() {
56 | let mut head = Node {
57 | value: 1,
58 | next: None,
59 | };
60 | head.next = Some(Box::new(Node {
61 | value: 2,
62 | next: None,
63 | }));
64 | println!("{}", head.value);
65 | }
66 |
67 | ```
68 |
69 | 문제없이 컴파일
70 |
71 |
72 |
73 | ## `Box`
74 |
75 | `Box`가 대체 무엇일까?
76 |
77 | `Box`를 사용하면 스택이 아닌 힙에 데이터를 저장할 수 있습니다. 스택에 남는 것은 힙 데이터에 대한 포인터입니다.
78 |
79 |
80 |
81 | ### Box 사용하기
82 |
83 | 아래 예제는 `Box`를 사용하여 `i32` 값을 힙에 저장하는 방법을 보여줍니다.
84 |
85 | ```rust
86 | fn main() {
87 | let my_box = Box::new(5);
88 | println!("my_box = {}", my_box);
89 | }
90 |
91 | ```
92 |
93 | 변수 b가 힙에 할당된 값 5를 가리키는 Box의 값을 갖도록 정의합니다. 이 프로그램은 b = 5를 출력합니다. 이 경우 이 데이터가 스택에 있을 때와 유사하게 상자에 있는 데이터에 액세스할 수 있습니다. 다른 소유 값과 마찬가지로 상자가 범위를 벗어나면, 메인 끝에서 b가 그러하듯이, 상자는 할당 해제됩니다. 할당 해제는 상자(스택에 저장됨)와 상자가 가리키는 데이터(힙에 저장됨) 모두에 대해 발생합니다. 여기까지는 스택에 값을 저장하는 것과 크게 다르지 않습니다.
94 |
95 | `Box`는 주로 다음과 같은 상황에 사용됩니다.
96 |
97 | - 컴파일 시 크기를 알 수 없는 타입 내부의 값에 접근해야 하는 경우
98 | - 크기가 큰 값의 소유권을 이전하고 싶지만, 메모리 효율성을 위해 전체 값이 복사되지 않도록 해야 하는 경우
99 | - 특정 타입이 아닌, 특정 트레이트를 구현하는 타입의 변수의 소유권을 가져오고 싶은 경우
100 |
101 | 첫 번째 상황은 위에서 이미 살펴본 `Node`의 경우입니다. 이제 나머지 각각의 경우를 자세히 살펴보겠습니다.
102 |
103 |
104 |
105 |
106 |
107 | ### 소유권을 효율적으로 전달하기
108 |
109 | 레퍼런스 대신
110 |
111 | ```rust
112 | fn transfer_box(_data: Box>) {}
113 |
114 | fn transfer_vec(_data: Vec) {}
115 |
116 | fn main() {
117 | let data = vec![0; 10_000_000];
118 |
119 | transfer_vec(data.clone());
120 |
121 | let boxed = Box::new(data);
122 | transfer_box(boxed);
123 | }
124 |
125 |
126 | ```
127 |
128 |
129 |
130 | ### `dyn` 과 `Box`로 트레이트 타입 표현하기
131 |
132 |
133 |
134 | ```bash
135 | cargo add rand
136 | ```
137 |
138 |
139 |
140 | ```rust
141 | struct Dog {}
142 | struct Cat {}
143 |
144 | trait Animal {
145 | fn noise(&self) -> &'static str;
146 | }
147 |
148 | impl Animal for Dog {
149 | fn noise(&self) -> &'static str {
150 | "🐶멍멍!"
151 | }
152 | }
153 |
154 | impl Animal for Cat {
155 | fn noise(&self) -> &'static str {
156 | "🐱야옹!"
157 | }
158 | }
159 |
160 | fn random_animal() -> impl Animal {
161 | if rand::random::() < 0.5 {
162 | Dog {}
163 | } else {
164 | Cat {}
165 | }
166 | }
167 |
168 | fn main() {
169 | for _ in 0..10 {
170 | println!("{}", random_animal().noise());
171 | }
172 | }
173 |
174 | ```
175 |
176 | 실행 결과
177 |
178 | ```
179 | error[E0308]: `if` and `else` have incompatible types
180 | --> src/main.rs:24:9
181 | |
182 | 21 | / if rand::random::() < 0.5 {
183 | 22 | | Dog {}
184 | | | ------ expected because of this
185 | 23 | | } else {
186 | 24 | | Cat {}
187 | | | ^^^^^^ expected struct `Dog`, found struct `Cat`
188 | 25 | | }
189 | | |_____- `if` and `else` have incompatible types
190 | |
191 | help: you could change the return type to be a boxed trait object
192 | |
193 | 20 | fn random_animal() -> Box {
194 | | ~~~~~~~ +
195 | help: if you change the return type to expect trait objects, box the returned expressions
196 | |
197 | 22 ~ Box::new(Dog {})
198 | 23 | } else {
199 | 24 ~ Box::new(Cat {})
200 | |
201 | ```
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 | ```rust
210 | struct Dog {}
211 | struct Cat {}
212 |
213 | trait Animal {
214 | fn noise(&self) -> &'static str;
215 | }
216 |
217 | impl Animal for Dog {
218 | fn noise(&self) -> &'static str {
219 | "🐶멍멍!"
220 | }
221 | }
222 |
223 | impl Animal for Cat {
224 | fn noise(&self) -> &'static str {
225 | "🐱야옹!"
226 | }
227 | }
228 |
229 | fn random_animal() -> Box {
230 | if rand::random::() < 0.5 {
231 | Box::new(Dog {})
232 | } else {
233 | Box::new(Cat {})
234 | }
235 | }
236 |
237 | fn main() {
238 | for _ in 0..10 {
239 | println!("{}", random_animal().noise());
240 | }
241 | }
242 |
243 | ```
244 |
245 | 실행 결과
246 |
247 | ```
248 | 🐱야옹!
249 | 🐶멍멍!
250 | 🐶멍멍!
251 | 🐶멍멍!
252 | 🐱야옹!
253 | 🐱야옹!
254 | 🐶멍멍!
255 | 🐶멍멍!
256 | 🐶멍멍!
257 | 🐶멍멍!
258 | ```
259 |
260 |
--------------------------------------------------------------------------------
/src/ch11-02.md:
--------------------------------------------------------------------------------
1 | ## `Rc`
2 |
3 | 대부분의 상황에서 어떤 값에 대한 소유권이 어떤 변수에 있는지를 정확하게 알 수 있습니다. 러스트의 소유권 규칙은 하나의 값에 단 하나만의 소유자를 보장합니다. 그런데 만일 하나의 값에 여러 개의 소유자를 정말로 가지고 싶다면 어떻게 할까요? 이럴 때 사용할 수 있는 자료형이 바로 `Rc`입니다. 레퍼런스 카운팅(Reference counting)의 앞 자를 따서 만든 이름으로, `Rc` 역시 스마트 포인터입니다.
4 |
5 | ```rust
6 | use std::rc::Rc;
7 |
8 | fn main() {
9 | let origin = Rc::new(1);
10 |
11 | assert_eq!(1, *origin);
12 | }
13 |
14 | ```
15 |
16 |
17 |
18 | ### 마지막 순간까지
19 |
20 | 프로그램의 여러 부분이 읽을 수 있도록 힙에 일부 데이터를 할당하고 컴파일 시점에 어느 부분이 데이터를 마지막으로 사용할지 결정할 수 없을 때 `Rc` 타입을 사용합니다. 어떤 부분이 마지막으로 완료될지 알 수 있다면 해당 부분을 데이터의 소유자로 설정하면 컴파일 시점에 적용되는 일반적인 소유권 규칙이 적용될 것입니다.
21 |
22 | 일반적인 레퍼런스와 `Rc`가 다른 점은 여기에 있습니다. 다음과 같은 예제를 살펴보겠습니다.
23 |
24 | ```rust
25 | fn main() {
26 | let cloned;
27 | {
28 | let origin = "Rust".to_string();
29 | cloned = &origin; // 🤯
30 | }
31 | println!("{}", cloned);
32 | }
33 |
34 | ```
35 |
36 | 하지만 `Rc`를 사용하면 원래 값이 스코프를 벗어나더라도 값을 참조하고 있는 `Rc`가 존재하기 때문에 여전히 값을 사용할 수 있습니다. 여기서 `clone`을 사용하면, 실제로 값이 복사되는 것이 아니라 `Rc`의 레퍼런스 카운트가 1 증가합니다.
37 |
38 | ```rust
39 | use std::rc::Rc;
40 |
41 | fn main() {
42 | let cloned;
43 | {
44 | let origin = Rc::new(1);
45 | cloned = origin.clone();
46 | }
47 | println!("{}", cloned);
48 | }
49 |
50 | ```
51 |
52 |
53 |
54 | ### 레퍼런스 카운팅
55 |
56 | `Rc`는 값의 소유권을 가지고 있는 변수가 몇 개인지를 계속 확인하고 있다가, 값을 소유하고 있는 변수가 전부 사라지면 값을 메모리에서 삭제합니다.
57 |
58 | ```rust
59 | use std::rc::Rc;
60 |
61 | fn main() {
62 | let origin = Rc::new(0);
63 | println!("Reference count: {}", Rc::strong_count(&origin));
64 | {
65 | let _dup1 = Rc::clone(&origin);
66 | println!("Reference count: {}", Rc::strong_count(&origin));
67 | {
68 | let _dup2 = &origin.clone();
69 | println!("Reference count: {}", Rc::strong_count(&origin));
70 | }
71 | println!("Reference count: {}", Rc::strong_count(&origin));
72 | }
73 | println!("Reference count: {}", Rc::strong_count(&origin));
74 | // origin drops here
75 | }
76 |
77 | ```
78 |
79 | 실행 결과
80 |
81 | ```
82 | Reference count: 1
83 | Reference count: 2
84 | Reference count: 3
85 | Reference count: 2
86 | Reference count: 1
87 | ```
88 |
89 |
90 |
91 | > `Rc`는 멀티스레드 환경에서 동작하지 않습니다. 멀티스레드 환경에서는 `Arc`를 사용해야 하며, 자세한 내용은 나중에 다루겠습니다.
92 |
93 |
94 |
95 | ### Quiz
96 |
97 |
98 |
99 | ```rust
100 | struct Node {
101 | value: i32,
102 | next: Option>,
103 | }
104 |
105 | fn main() {
106 | let mut head1 = Node {
107 | value: 1,
108 | next: None,
109 | };
110 | let node1 = Node {
111 | value: 2,
112 | next: None,
113 | };
114 | head1.next = Some(Box::new(node1));
115 |
116 | let mut head2 = Node {
117 | value: 3,
118 | next: None,
119 | };
120 | head2.next = Some(Box::new(node1)); // 🤯
121 |
122 | println!("{} {}", head1.value, head1.next.unwrap().value);
123 | println!("{} {}", head2.value, head2.next.unwrap().value);
124 | }
125 |
126 | ```
127 |
128 |
129 |
130 | 정답
131 |
132 |
133 | ```rust
134 | use std::rc::Rc;
135 |
136 | struct Node {
137 | value: i32,
138 | next: Option>,
139 | }
140 |
141 | fn main() {
142 | let mut head1 = Node {
143 | value: 1,
144 | next: None,
145 | };
146 | let node1 = Rc::new(Node {
147 | value: 2,
148 | next: None,
149 | });
150 | head1.next = Some(Rc::clone(&node1));
151 |
152 | let mut head2 = Node {
153 | value: 3,
154 | next: None,
155 | };
156 | head2.next = Some(Rc::clone(&node1));
157 |
158 | println!("{} {}", head1.value, head1.next.unwrap().value);
159 | println!("{} {}", head2.value, head2.next.unwrap().value);
160 | }
161 |
162 | ```
163 |
164 |
--------------------------------------------------------------------------------
/src/ch11-03.md:
--------------------------------------------------------------------------------
1 | ## `RefCell`
2 |
3 | ### `Rc`의 한계
4 |
5 | Rc를 사용하면 프로그램의 여러 부분에서 읽기 전용으로 데이터를 공유할 수 있습니다. 하지만 Rc가 불변 레퍼런스를 통해 값을 공유하기 때문에, 공유받은 값을 변경하는 것은 불가능합니다. 아래 예시를 살펴봅시다.
6 |
7 | ```rust
8 | use std::rc::Rc;
9 |
10 | struct Owner {
11 | name: String,
12 | tools: Rc>>,
13 | }
14 |
15 | struct Tool {
16 | owner: Rc,
17 | }
18 |
19 | pub fn main() {
20 | let indo = Rc::new(Owner {
21 | name: "indo".to_string(),
22 | tools: Rc::new(vec![]),
23 | });
24 | let pliers = Rc::new(Tool {
25 | owner: Rc::clone(&indo),
26 | });
27 | let wrench = Rc::new(Tool {
28 | owner: indo.clone(),
29 | });
30 |
31 | indo.tools.push(Rc::clone(&pliers)); // 🤯
32 | indo.tools.push(Rc::clone(&wrench));
33 |
34 | println!("Pliers owner: {}", pliers.owner.name);
35 |
36 | for tool in indo.tools.iter() {
37 | println!("Tool's owner: {:?}", tool.owner.name);
38 | }
39 | }
40 |
41 | ```
42 |
43 | 실행 결과
44 |
45 | ```
46 | error[E0596]: cannot borrow data in an `Rc` as mutable
47 | --> src/main.rs:24:5
48 | |
49 | 24 | brad.tools.push(Rc::clone(&pliers)); // 🤯
50 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
51 | |
52 |
53 | ...
54 | ```
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | ```rust
63 | use std::{cell::RefCell, rc::Rc};
64 |
65 | struct Owner {
66 | name: String,
67 | tools: RefCell>>,
68 | }
69 |
70 | struct Tool {
71 | owner: Rc,
72 | }
73 |
74 | pub fn main() {
75 | let indo = Rc::from(Owner {
76 | name: "indo".to_string(),
77 | tools: RefCell::new(vec![]),
78 | });
79 | let pliers = Rc::from(Tool {
80 | owner: Rc::clone(&indo),
81 | });
82 | let wrench = Rc::from(Tool {
83 | owner: indo.clone(),
84 | });
85 |
86 | indo.tools.borrow_mut().push(Rc::clone(&pliers));
87 | indo.tools.borrow_mut().push(Rc::clone(&wrench));
88 |
89 | println!("Pliers owner: {}", pliers.owner.name);
90 |
91 | for tool in indo.tools.borrow().iter() {
92 | println!("Tool's owner: {:?}", tool.owner.name);
93 | }
94 | }
95 |
96 | ```
97 |
98 |
99 |
100 | ### 내부 가변성(Interiror mutability)
101 |
102 | `RefCell`가 불변이어도 내부의 값은 가변으로 사용 가능
103 |
104 | ```rust
105 | indo.tools.borrow_mut().push(Rc::clone(&pliers));
106 | ```
107 |
108 | 불변 소유권 대여도 가능
109 |
110 | ``` rust
111 | indo.tools.borrow().iter()
112 | ```
113 |
114 |
115 |
116 | ### 소유권 규칙
117 |
118 | - 여러 번 빌려도 괜찮습니다
119 | - 한 번 빌리는 것도 괜찮습니다
120 | - 하지만 가변과 불변이 대여는 불가능합니다
121 |
122 | 런타임 시간에 소유권이 확인되기 때문에 컴파일이 되지만 런타임 에러 발생
123 |
124 | ```rust
125 | use std::{cell::RefCell, rc::Rc};
126 |
127 | struct Owner {
128 | name: String,
129 | tools: RefCell>>,
130 | }
131 |
132 | struct Tool {
133 | owner: Rc,
134 | }
135 |
136 | pub fn main() {
137 | let indo = Rc::from(Owner {
138 | name: "indo".to_string(),
139 | tools: RefCell::new(vec![]),
140 | });
141 | let pliers = Rc::from(Tool {
142 | owner: Rc::clone(&indo),
143 | });
144 | let wrench = Rc::from(Tool {
145 | owner: indo.clone(),
146 | });
147 |
148 | let mut borrow_mut_tools1 = indo.tools.borrow_mut();
149 | let mut borrow_mut_tools2 = indo.tools.borrow_mut(); // 🤯
150 | borrow_mut_tools1.push(Rc::clone(&pliers));
151 | borrow_mut_tools2.push(Rc::clone(&wrench));
152 |
153 | println!("Pliers owner: {}", pliers.owner.name);
154 |
155 | for tool in indo.tools.borrow().iter() {
156 | println!("Tool's owner: {:?}", tool.owner.name);
157 | }
158 | }
159 |
160 | ```
161 |
162 | 실행 결과
163 |
164 | ```
165 | thread 'main' panicked at 'already borrowed: BorrowMutError', src/main.rs:25:44
166 | ```
167 |
168 |
169 |
170 | ### `Rc>`
171 |
172 | `RefCell`를 사용하는 일반적인 방법은 `Rc`와 함께 사용하는 것입니다. `Rc`를 사용하면 일부 데이터의 소유자를 여러 명 가질 수 있지만, 해당 데이터에 대한 불변 액세스 권한만 부여한다는 점을 기억하세요. `RefCell`를 보유한 `Rc`가 있다면, 여러 소유자를 가질 수 있고 변경할 수 있는 값을 얻을 수 있습니다!
173 |
174 | 요약하자면, Rc는 공유 소유권을 제공합니다. 내부 값에는 여러 소유자가 있으며, 참조 카운팅은 적어도 한 명의 소유자가 데이터를 계속 보유하고 있는 한 데이터가 계속 유지되도록 합니다. 이는 데이터 소유자가 명확하지 않은 경우에 유용합니다. RefCell은 내부 가변성을 제공합니다. 즉, 런타임에 내부 값을 동적으로 빌릴 수 있고 공유 참조를 통해서도 수정할 수 있습니다. Rc> 조합은 소유자가 여러 명인 값을 소유자 중 한 명이 가변적으로 빌릴 수 있는 두 가지의 조합을 제공합니다.
175 |
176 |
177 |
178 | ### 언제 무엇을
179 |
180 | | | `Box` | `Rc` | `RefCell` |
181 | | ---------------- | -------------------------------------- | --------------------------------------------------- | -------------------------------------------------------- |
182 | | 소유권 | 한 개 | 한 개를 공유 | 한 개 |
183 | | 소유권 확인 시점 | 불변/가변 소유권을 컴파일 타임에 확인 | 불변 소유권을 컴파일 타임에 확인 | 불변/가변 소유권을 런타임에 확인 |
184 | | 특징 | 스코프를 벗어나면 레퍼런스도 모두 삭제 | 레퍼런스가 존재한다면 스코프를 벗어나도 값이 유지됨 | `RefCell`가 불변이어도 내부의 값은 가변으로 사용 가능 |
185 |
186 |
187 |
188 | > RefCell는 멀티스레드 코드에서는 작동하지 않는다는 점에 유의하세요! Mutex는 스레드에 안전한 RefCell {
204 | data: T,
205 | children: Vec>,
206 | }
207 |
208 | impl Node {
209 | fn new(data: T) -> Node {
210 | Node {
211 | data,
212 | children: Vec::new(),
213 | }
214 | }
215 |
216 | fn depth_first(&self) {
217 | println!("{}", self.data);
218 | for child in self.children.iter() {
219 | child.depth_first();
220 | }
221 | }
222 | }
223 |
224 | fn main() {
225 | let mut a = Node::new('A');
226 | let mut b = Node::new('B');
227 | let c = Node::new('C');
228 | let d = Node::new('D');
229 |
230 | b.children.push(d);
231 | a.children.push(b);
232 | a.children.push(c);
233 |
234 | a.depth_first();
235 | }
236 |
237 | ```
238 |
239 |
240 |
241 | ```rust
242 | fn add_child(&mut self, child: Wrapper>) {
243 | self.children.push(child);
244 | }
245 | ```
246 |
247 |
248 |
249 | ```rust
250 | fn main() {
251 | let a = wrap(Node::new('A'));
252 | let b = wrap(Node::new('B'));
253 | let c = wrap(Node::new('C'));
254 | let d = wrap(Node::new('D'));
255 |
256 | a.borrow_mut().add_child(Rc::clone(&b));
257 | a.borrow_mut().add_child(Rc::clone(&c));
258 | b.borrow_mut().add_child(Rc::clone(&d));
259 | a.borrow_mut().depth_first();
260 | }
261 | ```
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 | 정답
270 |
271 | ```rust
272 | use std::cell::RefCell;
273 | use std::fmt::Display;
274 | use std::rc::Rc;
275 | use std::vec::Vec;
276 |
277 | type Wrapper = Rc>;
278 |
279 | fn wrap(data: T) -> Wrapper {
280 | Rc::new(RefCell::new(data))
281 | }
282 |
283 | #[derive(Debug)]
284 | struct Node {
285 | data: T,
286 | children: Vec>>,
287 | }
288 |
289 | impl Node {
290 | fn add_child(&mut self, child: Wrapper>) {
291 | self.children.push(child);
292 | }
293 |
294 | fn new(data: T) -> Node {
295 | Node {
296 | data,
297 | children: Vec::new(),
298 | }
299 | }
300 |
301 | fn depth_first(&self) {
302 | println!("node {}", self.data);
303 | for child in self.children.iter() {
304 | child.borrow().depth_first();
305 | }
306 | }
307 | }
308 |
309 | fn main() {
310 | let a = wrap(Node::new('A'));
311 | let b = wrap(Node::new('B'));
312 | let c = wrap(Node::new('C'));
313 | let d = wrap(Node::new('D'));
314 |
315 | a.borrow_mut().add_child(Rc::clone(&b));
316 | a.borrow_mut().add_child(Rc::clone(&c));
317 | b.borrow_mut().add_child(Rc::clone(&d));
318 | a.borrow_mut().depth_first();
319 | }
320 |
321 | ```
322 |
323 |
--------------------------------------------------------------------------------
/src/ch12-00.md:
--------------------------------------------------------------------------------
1 | # CH12. 멀티스레딩
2 |
3 | 멀티코어 프로세서가 등장하기 전에도 컴퓨터 운영체제는 여러 개의 프로그램을 "동시에(concurrently)" 실행할 수 있었습니다. 이는 여러 개의 프로세스 사이를 빠르게 전환하면서, 각각의 프로세스를 조금씩 실행시키는 방식이었습니다. 현재는 모든 컴퓨터가 멀티코어 프로세서를 가지고 있어서 여러 개의 프로세스를 병렬적으로(parallel) 실행할 수 있습니다. 하지만 각 프로세스는 독립적으로 격리되어 있어서 서로간에 데이터를 공유하기가 어렵습니다.
4 |
5 | 하지만 하나의 프로세스로부터 여러 개의 스레드를 만들 수 있습니다. 한 프로세스 안에 속한 스레드들은 서로가 격리되어 있지 않고, 따라서 서로의 메모리를 공유하며 서로 통신하는 것도 가능합니다. 이번 챕터에서는 러스트의 스레드가 어떻게 만들어지는지, 그리고 어떻게 여러 개의 스레드가 안전하게 메모리를 공유하는지를 배워보겠습니다.
6 |
--------------------------------------------------------------------------------
/src/ch12-03.md:
--------------------------------------------------------------------------------
1 | ## 메시지 전달
2 |
3 | 스레드 간에 데이터를 공유하는 방법 중 하나로 널리 쓰이는 것 중 하나가 바로 MPSC입니다. 다중 생성자-단일 소비자(Multiple Producer Single Consumer)란 뜻으로, 여러 개의 스레드에서 하나의 스레드로 데이터를 보내는 방식입니다.
4 |
5 | 파이썬에서는 공식적으로 MPSC를 만드는 방법이 없기 때문에, 스레드 안정성이 보장되는 큐 자료형인 `Queue` 를 사용해 이를 구현해 보겠습니다.
6 |
7 | ```python
8 | import threading
9 | import time
10 | from queue import Queue
11 |
12 | channel = Queue(maxsize=3)
13 |
14 |
15 | def producer():
16 | for msg in ["hello", "from", "the", "other", "side"]:
17 | print(f"Producing {msg}...")
18 | channel.put(msg)
19 |
20 |
21 | def consumer():
22 | while not channel.empty():
23 | item = channel.get()
24 | print(f"Consuming {item}...")
25 | channel.task_done()
26 | time.sleep(0.01)
27 |
28 |
29 | producer_thread = threading.Thread(target=producer)
30 | producer_thread.start()
31 |
32 | consumer_thread = threading.Thread(target=consumer)
33 | consumer_thread.start()
34 |
35 | producer_thread.join()
36 | consumer_thread.join()
37 |
38 | ```
39 |
40 | mpsc(multiple producer, single consumer)는 여러 개의 송신자와 하나의 수신자를 가진 채널을 만듭니다. 이 채널은 송신자가 메시지를 전송하면 수신자가 메시지를 수신할 때까지 기다립니다. 이 채널은 `Sender`와 `Receiver`로 구성됩니다.
41 |
42 | `Receiver`에는 두 가지 유용한 메서드, 즉 `recv`와 `try_recv`가 있습니다. 여기서는 메인 스레드의 실행을 차단하고 값이 채널로 전송될 때까지 기다리는 _receive_의 줄임말인 `recv`를 사용합니다. 값이 전송되면 `recv`는 `Result`에 값을 반환합니다. 송신기가 닫히면 `recv`는 에러를 반환하여 더 이상 값이 오지 않을 것임을 알립니다.
43 |
44 | ```rust
45 | use std::sync::mpsc;
46 | use std::thread;
47 | use std::time::Duration;
48 |
49 | fn main() {
50 | let (tx, rx) = mpsc::channel();
51 |
52 | thread::spawn(move || {
53 | for msg in vec!["hello", "from", "the", "other", "side"] {
54 | let val = String::from(msg);
55 | println!("Producing {}...", val);
56 | tx.send(val).unwrap();
57 | thread::sleep(Duration::from_millis(10));
58 | }
59 | });
60 |
61 | for re in rx {
62 | println!("Consuming {}...", re);
63 | }
64 | }
65 |
66 | ```
67 |
68 |
69 |
70 | `try_recv` 를 사용하면 다음과 같이 할 수 있습니다.
71 |
72 | ```rust
73 | use std::sync::mpsc;
74 | use std::thread;
75 | use std::time::Duration;
76 |
77 | fn main() {
78 | let (tx, rx) = mpsc::channel();
79 |
80 | thread::spawn(move || {
81 | let val = String::from("hello");
82 | thread::sleep(Duration::from_millis(1000));
83 | tx.send(val).unwrap();
84 | });
85 |
86 | loop {
87 | println!("Waiting for the signal...");
88 | if let Ok(received) = rx.try_recv() {
89 | println!("Message: {}", received);
90 | break;
91 | }
92 |
93 | thread::sleep(Duration::from_millis(300));
94 | }
95 | }
96 |
97 | ```
98 |
99 | rx가 어떻게 앞의 메시지를 다 기다릴 수 있는지? Receiver, 즉 `rx`는 송신자 `tx` 가 메시지를 전송할 때까지 기다리려고 시도하며, 해당 채널이 끊어지면 오류를 반환합니다.
100 |
101 | 이 함수는 사용 가능한 데이터가 없고 더 많은 데이터를 전송할 수 있는 경우(적어도 한 명의 발신자가 여전히 존재할 경우) 항상 현재 스레드를 블럭합니다. 해당 발신자(또는 동기화 발신자)에게 메시지가 전송되면 이 수신자가 깨어나서 해당 메시지를 반환합니다. 즉 수신자가 메시지를 아직 보내지 않았다면 계속해서 메시지를 기다립니다.
102 |
103 | 해당 발신자가 연결을 끊었거나 이 스레드가 차단되는 동안 연결이 끊어지면, 이 채널에서 더 이상 메시지를 수신할 수 없음을 나타내는 Err을 반환합니다. 그러나 채널이 버퍼링되므로 연결이 끊어지기 전에 보낸 메시지는 계속 정상적으로 수신됩니다.
104 |
105 | ```rust
106 | use std::sync::mpsc;
107 | use std::thread;
108 | use std::time::Duration;
109 |
110 | fn main() {
111 | let (tx, rx) = mpsc::channel();
112 |
113 | let tx1 = tx.clone();
114 | thread::spawn(move || {
115 | for msg in vec!["hello", "from", "the", "other", "side"] {
116 | let val = String::from(msg);
117 | thread::sleep(Duration::from_millis(100));
118 | tx1.send(val).unwrap();
119 | }
120 | });
121 |
122 | thread::spawn(move || {
123 | let val = String::from("bye");
124 | thread::sleep(Duration::from_millis(1000));
125 | tx.send(val).unwrap();
126 | });
127 |
128 | for re in rx {
129 | println!("{}", re);
130 | }
131 | }
132 |
133 | ```
134 |
--------------------------------------------------------------------------------
/src/ch13-00.md:
--------------------------------------------------------------------------------
1 | # CH13. 비동기 프로그래밍
2 |
--------------------------------------------------------------------------------
/src/ch13-01.md:
--------------------------------------------------------------------------------
1 | ## 비동기 프로그래밍이란?
2 |
3 | 비동기 모델에서는 여러 가지 일이 동시에 일어날 수 있습니다. 프로그램에서 오래 실행되는 함수를 호출해도 실행 흐름이 차단되지 않고 프로그램이 계속 실행됩니다. 함수가 완료되면 프로그램은 결과를 알고 액세스합니다.
4 |
5 | 
6 |
7 | 위 그림에서 왼쪽은 동기 함수의 실행 흐름, 오른쪽은 비동기 함수의 실행 흐름을 나타냅니다. 동기 함수의 경우, 요청1에 대한 응답이 주어질 때까지 기다렸다가 요청 2를 처리합니다. 비동기 함수는 요청1을 보낸 다음 응답이 올 때까지 기다리지 않고 바로 요청2를 처리합니다. 그 후, 응답 1과 응답2가 도착하면 결과를 바로 확인합니다.
8 |
9 | 비동기 함수를 사용하면, 프로그램 외부에서 작업이 끝나길 기다리는 동안에 다른 작업을 수행할 수 있기 때문에 효율적으로 작업을 수행할 수 있다는 장점이 있습니다.
10 |
11 |
12 |
13 | ### 비동기 작동 방식
14 |
15 | 코루틴은 실행 중에 일시 중지했다가 다시 시작할 수 있는 파이썬의 함수입니다. 코루틴은 여러 함수를 동시에 실행할 수 있도록 주기적으로, 또는 비동기 런타임이 유휴 상태일 때, 각각의 코루틴이 자발적으로 제어권을 넘겨주는 협력적 멀티태스킹(Cooperative multitasking)에 사용됩니다.
16 |
17 | 따라서 코루틴과 스레드는 매우 비슷한 일을 수행합니다. 다만 스레드의 경우 운영 체제가 스레드 간에 작업을 전환하는 런타임 환경을 가지고 있습니다. 반면 코루틴의 경우 코루틴 전환 시점은 코드에 따라 결정됩니다. 코드상에 설정된 시점에 작업을 일시 중단했다가 다시 시작하는 방식으로 여러 작업을 동시적으로 수행합니다.
18 |
19 | 러스트 역시 운영체제가 아닌 비동기 런타임에서 관리하는 태스크를 사용해 비동기 함수를 실행합니다. 코루틴과 마찬가지로 협력적 멀티태스킹에 사용됩니다.
20 |
21 |
22 |
23 | > 협력적 멀티태스킹이란, 멀티태스킹에 참여하는 주체들이 언제든 자신의 실행을 자발적으로 멈추고 다른 주체에게 실행 권한을 넘기는 방식을 의미합니다. 운영체제에서는 스케줄러가 어떤 스레드가 언제 작업을 실행하고 종료할지를 관리하기 때문에 멀티스레딩을 협력적 멀티태스킹이 아닙니다.
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/ch13-03.md:
--------------------------------------------------------------------------------
1 | ## rayon
2 |
3 | 비동기 프로그래밍과는 큰 상관이 없지만, `tokio` 와 자주 비교되는 크레이트인 `rayon` 에 대해서 살펴보겠습니다.
4 |
5 |
6 |
7 | ### tokio vs rayon
8 |
9 | Tokio와 Rayon은 모두 Rust에서 병렬 및 비동기 프로그래밍을 위한 라이브러리이지만, 초점과 사용 사례는 서로 다릅니다.
10 |
11 | - Tokio는 주로 비동기 프로그래밍, 특히 네트워크 애플리케이션 구축을 위한 비동기 프로그래밍에 중점을 둡니다. Rust에서 효율적이고 고성능이며 확장 가능한 비동기 애플리케이션을 구축하기 위한 도구 세트를 제공합니다. Tokio는 Rust의 퓨처 및 비동기/대기 언어 기능과 함께 작동하도록 설계되었으며, 비동기 작업을 효율적으로 실행할 수 있는 런타임을 제공합니다.
12 | - 반면 레이온은 데이터 처리 작업을 위한 병렬 처리와 동시성에 중점을 두고 있습니다. 대규모 데이터 컬렉션에 대한 계산을 병렬화하기 위한 간단하고 사용하기 쉬운 인터페이스를 제공합니다. 레이온은 Rust의 이터레이터 특성과 함께 작동하도록 설계되었으며, 데이터를 병렬로 처리하는 데 사용할 수 있는 일련의 병렬 알고리즘을 제공합니다.
13 |
14 | 요약하자면, Tokio는 비동기 네트워크 애플리케이션을 구축하는 데 이상적이며, Rayon은 대규모 데이터 컬렉션에 대한 계산을 병렬화하는 데 이상적입니다. 두 라이브러리 모두 다양한 사용 사례에 유용하며, 비동기 처리와 병렬 처리가 모두 필요한 경우에 함께 사용할 수 있습니다.
15 |
16 |
17 |
18 | ### 병렬 이터레이터
19 |
20 | 레이온은 Rust를 위한 데이터 병렬 처리 라이브러리입니다. 매우 가볍고 순차 계산을 병렬 계산으로 쉽게 변환할 수 있습니다. 또한 데이터 레이스가 발생하지 않는 것이 보장됩니다. 레이온은 아래 명령어로 설치 가능합니다.
21 |
22 | ```bash
23 | cargo add rayon
24 | ```
25 |
26 | 공식 문서에서 권장하는 사용 방법은 `prelude` 밑에 있는 모든 것을 불러오는 것입니다. 이렇게 하면 병렬 이터레이터와 다른 트레이트를 전부 불러오기 때문에 코드를 훨씬 쉽게 작성할 수 있습니다.
27 |
28 | ```rust
29 | use rayon::prelude::*;
30 | ```
31 |
32 | 기존의 순차 계산 함수에 병렬성을 더하려면, 단순히 이터레이터를 `par_iter`로 바꿔주기만 하면 됩니다.
33 |
34 | ```rust,ignore
35 | use rayon::prelude::*;
36 | use std::time::SystemTime;
37 |
38 | fn sum_of_squares(input: &Vec) -> i32 {
39 | input
40 | .par_iter() // ✨
41 | .map(|&i| {
42 | std::thread::sleep(std::time::Duration::from_millis(10));
43 | i * i
44 | })
45 | .sum()
46 | }
47 |
48 | fn sum_of_squares_seq(input: &Vec) -> i32 {
49 | input
50 | .iter()
51 | .map(|&i| {
52 | std::thread::sleep(std::time::Duration::from_millis(10));
53 | i * i
54 | })
55 | .sum()
56 | }
57 |
58 | fn main() {
59 | let start = SystemTime::now();
60 | sum_of_squares(&(1..100).collect());
61 | println!("{}ms", start.elapsed().unwrap().as_millis());
62 | let start = SystemTime::now();
63 | sum_of_squares_seq(&(1..100).collect());
64 | println!("{}ms", start.elapsed().unwrap().as_millis());
65 | }
66 |
67 | ```
68 |
69 | 실행 결과
70 |
71 | ```
72 | 106ms
73 | 1122ms
74 | ```
75 |
76 |
77 |
78 |
79 |
80 | par_iter_mut 는 각 원소의 가변 레퍼런스를 받는 이터레이터입니다.
81 |
82 | ```rust
83 | use rayon::prelude::*;
84 |
85 | use std::time::SystemTime;
86 |
87 | fn plus_one(x: &mut i32) {
88 | *x += 1;
89 | std::thread::sleep(std::time::Duration::from_millis(10));
90 | }
91 |
92 | fn increment_all_seq(input: &mut [i32]) {
93 | input.iter_mut().for_each(plus_one);
94 | }
95 |
96 | fn increment_all(input: &mut [i32]) {
97 | input.par_iter_mut().for_each(plus_one);
98 | }
99 |
100 | fn main() {
101 | let mut data = vec![1, 2, 3, 4, 5];
102 |
103 | let start = SystemTime::now();
104 | increment_all(&mut data);
105 | println!("{:?} - {}ms", data, start.elapsed().unwrap().as_millis());
106 |
107 | let start = SystemTime::now();
108 | increment_all_seq(&mut data);
109 | println!("{:?} - {}ms", data, start.elapsed().unwrap().as_millis());
110 | }
111 |
112 | ```
113 |
114 | 실행 결과
115 |
116 | ```
117 | [2, 3, 4, 5, 6] - 12ms
118 | [3, 4, 5, 6, 7] - 55ms
119 | ```
120 |
121 |
122 |
123 | `par_sort` 는 병합 정렬을 응용한 정렬 알고리즘을 사용해 데이터를 병렬적으로 분할해 정렬합니다.
124 |
125 | ```rust
126 | use rand::Rng;
127 | use rayon::prelude::*;
128 |
129 | use std::time::SystemTime;
130 |
131 | fn main() {
132 | let mut rng = rand::thread_rng();
133 | let mut data1: Vec = (0..1_000_000).map(|_| rng.gen_range(0..=100)).collect();
134 | let mut data2 = data1.clone();
135 |
136 | let start = SystemTime::now();
137 | data1.par_sort();
138 | println!("{}ms", start.elapsed().unwrap().as_millis());
139 |
140 | let start = SystemTime::now();
141 | data2.sort();
142 | println!("{}ms", start.elapsed().unwrap().as_millis());
143 |
144 | assert_eq!(data1, data2);
145 | }
146 |
147 | ```
148 |
149 | 실행 결과
150 |
151 | ```
152 | 68ms
153 | 325ms
154 | ```
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 | ### Rayon 사용 시 주의사항
163 |
164 | 멀티스레드도 마찬가지지만 스레드 스폰 및 조인에 시간이 소요되기 때문에 주의
165 |
166 |
--------------------------------------------------------------------------------
/src/ch14-00.md:
--------------------------------------------------------------------------------
1 | # CH14. 테스트
2 |
3 |
4 |
5 | ## 프로그래밍에서 테스트가 필요한 이유
6 |
7 | 테스트는 소프트웨어 제품이 요구 사항을 충족하고 예상대로 작동하는지 확인하는 데 도움이 되므로 소프트웨어 개발의 필수적인 부분입니다. 테스트가 필요하고 중요한 이유는 여러 가지가 있습니다:
8 |
9 | - 소프트웨어 적응성을 확인하기 위해: 테스트는 소프트웨어가 다양한 환경과 다양한 구성에서 올바르게 작동할 수 있는지 확인합니다.
10 | - 오류 식별: 테스트는 소프트웨어의 버그와 오류를 식별하여 제품 출시 전에 수정할 수 있도록 도와줍니다.
11 | - 추가 비용 방지: 개발 프로세스 초기에 버그를 발견하고 수정하면 출시 후에 버그를 발견하는 것보다 시간과 비용을 절약할 수 있습니다.
12 | - 소프트웨어 개발 가속화: 테스트를 통해 코드 변경 사항에 대한 빠른 피드백을 제공함으로써 개발 속도를 높일 수 있습니다.
13 | - 위험 방지: 테스트는 소프트웨어 제품 사용과 관련된 잠재적 위험을 식별하는 데 도움이 됩니다.
14 |
15 | ## 테스트의 종류
16 |
17 | 단위 테스트(unit test)와 통합 테스트(integration test)는 서로 다른 목적을 가진 두 가지 테스트 유형입니다.
18 |
19 | 단위 테스트는 고립된 작은 코드 조각이 의도한 대로 작동하는지 확인하기 위한 자동화된 테스트의 한 유형입니다. 일반적으로 개발자가 개발 중에 수행하며 함수나 프로시저와 같은 개별 소프트웨어 구성 요소에 중점을 둡니다.
20 |
21 | 반면에 통합 테스트는 여러 소프트웨어 구성 요소가 함께 작동하는 방식을 테스트하는 프로세스입니다. 통합 테스트는 통합된 유닛 간의 인터페이스가 올바른지 확인하는 데 중점을 둡니다. 통합 테스트는 일반적으로 단위 테스트가 완료된 후 시스템 테스트 전에 수행됩니다.
22 |
23 | 단위 테스트와 통합 테스트의 주요 차이점은 단위 테스트는 개별 코드 단위에 초점을 맞추고 통합 테스트는 이들이 함께 작동하는 방식에 초점을 맞춘다는 점입니다. 단위 테스트는 개발자가 코드가 원자 수준에서 올바르게 작동하는지 확인하는 데 도움이 되며, 통합 테스트는 시스템의 여러 부분이 올바르게 함께 작동하는지 확인하는 데 도움이 됩니다.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/ch14-02.md:
--------------------------------------------------------------------------------
1 |
2 | ## 문서 테스트
3 |
4 | 아까 `cargo test` 를 실행했을 때, 유닛 테스트 외에도 한 가지 테스트가 더 추가로 실행됐었습니다. 바로 문서가 잘 작성되었는지를 테스트하는 문서 테스트입니다.
5 |
6 | ```
7 | Doc-tests rust_part
8 |
9 | running 1 test
10 | test src/lib.rs - play (line 10) ... ok
11 |
12 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.55s
13 | ```
14 |
15 | `play` 함수 위의 주석을 보면 다음과 같은 부분이 들어있습니다.
16 |
17 | ```rust,ignore
18 | /// Demonstrate Rock, Scissors, Paper
19 | ///
20 | /// ```
21 | /// use rust_part::{play, Cards};
22 | ///
23 | /// let result = play(Cards::Rock, Cards::Scissors);
24 | /// assert_eq!(result, Some(true));
25 | /// ```
26 | ```
27 |
28 | `/` 을 3개 달아서 함수에 해당하는 주석이라는 것을 표시할 수 있습니다. 가장 윗 줄은 어떤 함수인지를 설명하고 있고, 그 다음 ``` 로 묶인 부분은 이 `play 함수를 사용하기 위한 예제 코드입니다. 문서 테스트가 실행되면 이 예제 코드가 컴파일되는지를 테스트합니다.
29 |
30 | 구조체와 메소드에도 별도로 주석을 추가할 수 있습니다. 아래 두 주석을 구조체와 메소드에 추가해 보겠습니다.
31 |
32 | ```rust,ignore
33 | /// A module for Person struct.
34 | pub mod person {
35 | /// Person struct with name and age.
36 | ///
37 | /// ```
38 | /// use rust_part::person::Person;
39 | ///
40 | /// let person = Person::new("John", 30);
41 | /// person.hi();
42 | /// ```
43 | pub struct Person {
44 | pub name: String,
45 | age: u8,
46 | }
47 | /// Methods defined for Person struct.
48 | impl Person {
49 | pub fn new(name: &str, age: u8) -> Person {
50 | Person {
51 | name: name.to_string(),
52 | age: age,
53 | }
54 | }
55 |
56 | pub fn hi(&self) -> String {
57 | format!("Hi, I'm {}, I am {} years old.", self.name, self.age())
58 | }
59 |
60 | pub fn age(&self) -> u8 {
61 | self.age
62 | }
63 | }
64 | }
65 | ```
66 |
67 | 그 다음 작성된 문서를 브라우저에서 확인하기 위해 아래 명령어를 실행합니다.
68 |
69 | ```bash
70 | cargo doc --open
71 | ```
72 |
73 | 브라우저가 실행되고 아래와 같은 메인 페이지가 나타납니다.
74 |
75 | 
76 |
77 | 여기서 `person::Person` 구조체로 들어가 보겠습니다.
78 |
79 | 
80 |
81 | 구조체 정의에 퍼블릭 필드만 나오는 것을 알 수 있습니다. 그리고 아까 작성한 예제 코드도 나타납니다. 이처럼 코드에 작성한 주석을 바로 문서로 만들 수 있는 것이 러스트의 큰 장점입니다.
82 |
83 | 마지막으로 문서 테스트만 실행하는 방법은 다음과 같습니다.
84 |
85 | ```bash
86 | cargo test --doc
87 | ```
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/src/ch14-03.md:
--------------------------------------------------------------------------------
1 | ## 모킹
2 |
3 | 파이썬에서 다양한 모킹을 사용하기 위해 `pytest-mock` 플러그인이 자주 사용됩니다.
4 |
5 | ```python
6 | pip install pytest-mock
7 | ```
8 |
9 | 유닉스 파일 시스템에서 파일을 지우는 코드를 모킹을 사용해 테스트해보면 다음과 같습니다.
10 |
11 | ```python
12 | import os
13 |
14 | class UnixFS:
15 | @staticmethod
16 | def rm(filename):
17 | os.remove(filename)
18 |
19 | def test_unix_fs(mocker):
20 | mocker.patch('os.remove')
21 | UnixFS.rm('file')
22 | os.remove.assert_called_once_with('file')
23 |
24 | ```
25 |
26 |
27 |
28 | 러스트에서의 모킹은 파이썬과는 매우 다르게 사용됩니다. 일반적으로 `mockall` 크레이트를 사용하는데, 파이썬과 달리 객체를 직접 모킹할 수 없습니다.
29 |
30 | ```bash
31 | cargo add mockall mockall_double
32 | ```
33 |
34 | 이것은 파일 시스템과 상호 작용하는 모듈에 대한 단위 테스트를 작성하기 위해 `mockall` 및 `mockall_double` 크레이트를 사용하는 방법을 보여주는 Rust 코드입니다. `fs_api` 모듈은 `std::fs::remove_file` 함수를 래핑하는 `remove_file` 메서드가 있는 `FS` 구조체를 정의합니다.
35 |
36 | 여기서 `mockall::automock` 은 테스트 코드에서 사용될 모킹된 `MockFS` 구조체를 자동으로 생성합니다.
37 |
38 | ```rust
39 | #[allow(dead_code)]
40 | mod fs_api {
41 | use std::fs;
42 |
43 | pub struct FS {}
44 |
45 | #[cfg_attr(test, mockall::automock)]
46 | impl FS {
47 | pub fn new() -> Self {
48 | Self {}
49 | }
50 | pub fn remove_file(&self, filename: &str) -> Result<(), std::io::Error> {
51 | fs::remove_file(filename)
52 | }
53 | }
54 | }
55 | ```
56 |
57 | `mockall_double`은 `#[double]` 어트리뷰트 매크로를 제공하는 크레이트입니다. 이 매크로는 테스트 빌드에서 구조체 또는 트레이트의 모의 버전을 자동으로 생성하는 데 사용할 수 있습니다. 일반 코드에서는 위에서 정의한 `FS` 구조체가, 테스트에서는 자동으로 생성된 `MockFS` 구조체가 사용됩니다.
58 |
59 | ```rust
60 | use mockall_double::double;
61 |
62 | #[double]
63 | use fs_api::FS;
64 | ```
65 |
66 | `UnixFS` 구조체는 `FS` 구조체의 인스턴스를 사용하여 파일을 제거하는 `rm` 메서드를 정의합니다.
67 |
68 | ```rust
69 | pub struct UnixFS {}
70 |
71 | impl UnixFS {
72 | pub fn rm(fs: &FS, filename: &str) -> Result<(), std::io::Error> {
73 | fs.remove_file(filename)
74 | }
75 | }
76 | ```
77 |
78 | 이 코드에는 `mockall` 를 사용하여 `FS` 구조체에 대한 모의 객체를 생성하고 `UnixFS::rm` 메서드의 동작을 테스트하는 테스트 모듈도 포함되어 있습니다. `use mockall::predicate::*;` 는 모킹한 함수 `expect_remove_file`의 예상 입력을 찾을 수 있도록 하는 함수입니다. 즉 입력으로 문자열 슬라이스 `"file"`이 들어오는 경우에 이 모킹 함수가 작동합니다.
79 |
80 | ```rust
81 | #[cfg(test)]
82 | mod test {
83 | use super::*;
84 | use mockall::predicate::*;
85 |
86 | #[test]
87 | fn test_remove_file() {
88 | let mut fs = FS::default();
89 |
90 | fs.expect_remove_file()
91 | .with(eq("file"))
92 | .returning(|_| Ok(()));
93 |
94 | UnixFS::rm(&fs, "file").unwrap();
95 | }
96 | }
97 |
98 | ```
99 |
100 | `main` 함수를 포함한 전체 코드는 다음과 같습니다.
101 |
102 | ```rust
103 | use mockall_double::double;
104 |
105 | mod fs_api {
106 | use std::fs;
107 |
108 | #[cfg(test)]
109 | use mockall::automock;
110 |
111 | pub struct FS {}
112 |
113 | #[allow(dead_code)]
114 | #[cfg_attr(test, automock)]
115 | impl FS {
116 | pub fn new() -> Self {
117 | Self {}
118 | }
119 | pub fn remove_file(&self, filename: &str) -> Result<(), std::io::Error> {
120 | fs::remove_file(filename)
121 | }
122 | }
123 | }
124 |
125 | #[double]
126 | use fs_api::FS;
127 |
128 | pub struct UnixFS {}
129 |
130 | impl UnixFS {
131 | pub fn rm(fs: &FS, filename: &str) -> Result<(), std::io::Error> {
132 | fs.remove_file(filename)
133 | }
134 | }
135 |
136 | #[cfg(test)]
137 | mod test {
138 | use super::*;
139 | use mockall::predicate::*;
140 |
141 | #[test]
142 | fn test_remove_file() {
143 | let mut fs = FS::default();
144 |
145 | fs.expect_remove_file()
146 | .with(eq("file"))
147 | .returning(|_| Ok(()));
148 |
149 | UnixFS::rm(&fs, "file").unwrap();
150 | }
151 | }
152 |
153 | fn main() {
154 | let fs = FS::new();
155 | if let Err(e) = UnixFS::rm(&fs, "file") {
156 | println!("Error: {}", e);
157 | };
158 | }
159 |
160 | ```
161 |
162 | 실행 결과
163 |
164 | ```
165 | running 1 test
166 | test test::test_remove_file ... ok
167 |
168 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
169 | ```
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/src/ch15-00.md:
--------------------------------------------------------------------------------
1 | # CH15. 파이썬 바인딩
2 |
3 |
4 |
5 | 이번 챕터에서는 러스트 코드를 파이썬에서 실행하는 방법을 배워보겠습니다. 러스트의 높은 성능 때문에, 많은 파이썬 개발자들이 러스트 코드를 파이썬에서 사용하기 위한 도구를 개발했습니다. 그 중에서 가장 널리 사용되는 PyO3 크레이트의 사용법을 알아보겠습니다.
6 |
7 |
--------------------------------------------------------------------------------
/src/ch15-01.md:
--------------------------------------------------------------------------------
1 | ## 파이썬 가상환경 만들기
2 |
3 |
4 |
5 | ### 가상환경이란?
6 |
7 | 프로젝트 단위로 의존성이 관리되는 러스트와 달리, 파이썬은 하나의 글로벌 인터프리터 환경에 모든 패키지가 설치됩니다. 프로젝트들에서 같은 패키지를 재사용할 수 있다는 장점이 있지만, 프로젝트별로 다른 파이썬 버전과 패키지 버전을 관리하는 일이 어려워지는 문제가 발생합니다.
8 |
9 | 가상 환경은 글로벌 Python 설치에 영향을 주지 않고 특정 프로젝트에 특정한 패키지 및 종속성을 설치할 수 있는 격리된 Python 환경입니다. 이를 통해 서로 다른 종속성을 가진 여러 프로젝트에서 프로젝트 간 충돌을 걱정하지 않고 작업할 수 있습니다. 가상 환경을 사용하면 시스템 오염을 방지하고, 종속성 충돌을 피하고, 재현성 문제를 최소화할 수 있습니다.
10 |
11 | 파이썬에서 가상 환경을 생성하는 방법은 여러 가지가 있지만, 여기서는 `pipenv`를 사용하는 방법을 소개합니다.
12 |
13 |
14 |
15 | ### pipenv
16 |
17 | pipenv는 pipenv 명령어 하나로 가상환경의 생성, 삭제, 의존성의 추가, 삭제, 업데이트 등을 모두 할 수 있는 편리한 도구입니다.
18 |
19 | pipenv는 프로젝트의 가상 환경을 자동으로 생성 및 관리하고 패키지를 설치/제거할 때 `Pipfile`에서 패키지를 추가/제거합니다. 또한 패키지 유효성을 검사하는 데 사용되는 매우 중요한 `Pipfile.lock`을 생성합니다.
20 |
21 | Pipfile.lock은 가상 환경에 설치된 각 패키지의 정확한 버전을 기록하는 pipenv에 의해 생성된 파일입니다. 이를 통해 다른 개발자가 동일한 버전의 패키지를 설치하여 동일한 환경을 재현할 수 있습니다.
22 |
23 | pipenv를 사용하려면 pip을 이용해 먼저 설치해주어야 합니다.
24 |
25 | ```bash
26 | pip install pipenv
27 | ```
28 |
29 | 파이썬 버전을 지정해서 가상환경을 생성합니다.
30 |
31 | ```bash
32 | pipenv --python 3.11
33 | ```
34 |
35 | 생성된 가상환경 셸로 진입하는 방법은 다음과 같습니다.
36 |
37 | ```bash
38 | pipenv shell
39 | ```
40 |
41 | 가상환경에 새로운 패키지를 설치합니다.
42 |
43 | ```bash
44 | pipenv install requests
45 | ```
46 |
47 | 만일 개발 단계에서만 사용되는 툴이라면 `--dev` 플래그를 추가합니다. 예를 들어 black과 같은 포매터 패키지는 실제 소스코드에서는 쓰이지 않고 개발 단계에서만 사용되기 때문에 다음과 같시 설치할 수 있습니다.
48 |
49 | ```bash
50 | pipenv install --dev black
51 | ```
52 |
53 | 결과적으로 일반 패키지와 개발 패키지가 Pipfile에 구분되어서 추가되는 것을 알 수 있습니다.
54 |
55 | ```toml
56 | [[source]]
57 | url = "https://pypi.org/simple"
58 | verify_ssl = true
59 | name = "pypi"
60 |
61 | [packages]
62 | requests = "*"
63 |
64 | [dev-packages]
65 | black = "*"
66 |
67 | [requires]
68 | python_version = "3.11"
69 |
70 | [pipenv]
71 | allow_prereleases = true
72 |
73 | ```
74 |
75 |
--------------------------------------------------------------------------------
/src/ch15-02.md:
--------------------------------------------------------------------------------
1 | ## 러스트 프로젝트 생성하기
2 |
3 | ### 파이썬 바인딩이란?
4 |
5 | 파이썬 바인딩은 다른 프로그래밍 언어로 작성된 코드를 파이썬에서 사용하는 것을 의미합니다.
6 |
7 | 파이썬용 러스트 바인딩을 사용하면 파이썬에서 러스트로 함수를 호출하고 데이터를 전달할 수 있으므로 두 언어의 강점을 모두 활용할 수 있습니다. 이 기능은 테스트를 거쳐 안정적으로 작성된 대규모 라이브러리를 파이썬에서 활용하거나 파이썬 코드의 특정 섹션을 러스트로 변환하여 속도를 높이고자 하는 경우에 유용합니다.
8 |
9 | 파이썬에서 러스트 바인딩을 생성하는 데 가장 널리 알려진 프로젝트는 PyO3입니다. 이 프로젝트는 러스트로 파이썬 모듈을 작성하거나 파이썬 런타임을 러스트 바이너리에 임베드하는 데 사용할 수 있습니다. PyO3는 파이썬 패키징 및 바인딩이 포함된 러스트 상자를 작성하는 도구인 Maturin이라는 또 다른 프로젝트를 활용합니다.
10 |
11 |
12 |
13 | ### PyO3
14 |
15 | PyO3는 파이썬에서 러스트 코드를 실행할 수 있고, 반대로 러스트에서 파이썬 코드를 실행할 수 있도록 도와주는 크레이트입니다. 우리는 파이썬에서 러스트 코드를 실행하는 방법을 배워 보겠습니다. 파이썬에서 러스트 코드를 호출해 높은 성능 향상을 달성한 다양한 예시가 있습니다. 아래에서 그 중 유명한 몇 가지 패키지를 소개합니다.
16 |
17 | - [orjson](https://github.com/ijl/orjson) *Fast Python JSON library* 10배
18 | - [fastuuid](https://github.com/thedrow/fastuuid/) *Python bindings to Rust's UUID library*
19 | - [cryptography](https://github.com/pyca/cryptography) *Python cryptography library with some functionality in Rust*
20 |
21 |
22 |
23 | ### maturin
24 |
25 | maturin은 최소한의 구성으로 러스트로 작성한 파이썬 패키지를 빌드할 수 있는 도구입니다.
26 |
27 | ```bash
28 | $ pipenv install maturin --dev
29 | $ pipenv shell
30 | ```
31 |
32 | maturin으로 pyo3 프로젝트를 시작합니다. -b 옵션을 주면 pyo3를 빌드 시스템으로 해서 프로젝트가 생성됩니다.
33 |
34 | ```bash
35 | $ maturin init -b pyo3
36 | ✨ Done! New project created string_sum
37 |
38 | ```
39 |
40 | 프로젝트를 생성하면 다음과 같은 폴더 구조가 만들어집니다.
41 |
42 | ```
43 | .
44 | ├── Cargo.lock
45 | ├── Cargo.toml
46 | ├── Pipfile
47 | ├── pyproject.toml
48 | ├── src
49 | └── lib.rs
50 | ```
51 |
52 | `Cargo.toml`파일에서 패키지와 라이브러리의 이름을 "fibonacci"로 변경합니다.
53 |
54 | ```toml
55 | [package]
56 | name = "fibonacci"
57 | version = "0.1.0"
58 | edition = "2021"
59 |
60 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
61 | [lib]
62 | name = "fibonacci"
63 | crate-type = ["cdylib"]
64 |
65 | [dependencies]
66 | pyo3 = { version = "0.16.5", features = ["extension-module"] }
67 | ```
68 |
69 | `pyproject.toml` 파일에서도 프로젝트 이름을 "fibonacci"로 수정합니다.
70 |
71 | ```toml
72 | [build-system]
73 | requires = ["maturin>=0.13,<0.14"]
74 | build-backend = "maturin"
75 |
76 | [project]
77 | name = "fibonacci"
78 | requires-python = ">=3.7"
79 | classifiers = [
80 | "Programming Language :: Rust",
81 | "Programming Language :: Python :: Implementation :: CPython",
82 | "Programming Language :: Python :: Implementation :: PyPy",
83 | ]
84 |
85 | ```
86 |
87 |
88 |
89 | ### 라이브러리 크레이트 만들기
90 |
91 | `#[pyfunction]`은 PyO3 라이브러리에서 제공하는 어트리뷰트로, 러스트 함수를 파이썬 함수로 정의하는 데 사용할 수 있습니다. 러스트 함수에 `#[pyfunction]` 어트리뷰트를 추가하면 PyO3는 해당 함수가 일반 파이썬 함수인 것처럼 파이썬에서 호출할 수 있는 코드를 생성합니다.
92 |
93 | `#[pymodule]`은 PyO3 라이브러리에서 제공하는 어트리뷰트로, 러스트 함수를 파이썬 모듈로 정의하는 데 사용할 수 있습니다. 러스트 함수에 `#[pymodule]` 어트리뷰트를 추가하면 PyO3는 해당 함수를 파이썬 모듈의 초기화 함수로 사용할 수 있는 코드를 생성합니다.
94 |
95 | 모듈에 함수를 추가하려면 `add_function` 메서드를 사용합니다. 이렇게 하면 모듈 내에서 함수를 호출 가능한 객체로 사용할 수 있습니다.
96 |
97 | ```rust,ignore
98 | use pyo3::prelude::*;
99 |
100 | fn _run(n: u64) -> u64 {
101 | match n {
102 | 0 => 0,
103 | 1 => 1,
104 | _ => _run(n - 1) + _run(n - 2),
105 | }
106 | }
107 |
108 | #[pyfunction]
109 | fn run(n: u64) -> PyResult {
110 | Ok(_run(n))
111 | }
112 |
113 | /// A Python module implemented in Rust.
114 | #[pymodule]
115 | fn fibonacci(_py: Python, m: &PyModule) -> PyResult<()> {
116 | m.add_function(wrap_pyfunction!(run, m)?)?;
117 | Ok(())
118 | }
119 |
120 | ```
121 |
122 |
123 |
124 |
125 |
126 | ## 파이썬에서 러스트 코드 실행해 보기
127 |
128 | ### 개발 모드로 빌드해보기
129 |
130 | `maturin develop` 명령어를 사용하면, 러스트 패키지를 빌드한 다음 파이썬 가상환경에 패키지를 자동으로 설치해줍니다. 이때 러스트 컴파일 타겟이 `[unoptimized + debuginfo]`가 되는데, 빠른 개발을 위해 코드 성능보다는 컴파일 속도를 중요하게 생각한 옵션입니다.
131 |
132 | ```bash
133 | $ maturin develop
134 | 🔗 Found pyo3 bindings
135 | 🐍 Found CPython 3.8 at /Users/.local/share/virtualenvs/ch14-4UzrGkRt/bin/python
136 | Compiling pyo3-build-config v0.16.5
137 | Compiling pyo3-ffi v0.16.5
138 | Compiling pyo3 v0.16.5
139 | Compiling fibonacci v0.1.0 (/Users/code/Tutorials/sap_rust_tutorial/ch14)
140 | Finished dev [unoptimized + debuginfo] target(s) in 12.64s
141 | 📦 Built wheel for CPython 3.8 to /var/folders/74/l6jhlmk114g8kx1pzz2s9fm80000gn/T/.tmpBh1Xiw/fibonacci-0.1.0-cp38-cp38-macosx_10_7_x86_64.whl
142 | 🛠 Installed fibonacci-0.1.0
143 | ```
144 |
145 | > 만일 가상환경에서 실행하지 않을 경우 에러가 발생하므로 주의하세요!
146 | >
147 | > ```bash
148 | > $ maturin develop
149 | > 💥 maturin failed
150 | > Caused by: You need to be inside a virtualenv or conda environment to use develop (neither VIRTUAL_ENV nor CONDA_PREFIX are set). See https://virtualenv.pypa.io/en/latest/index.html on how to use virtualenv or use `maturin build` and `pip install ` instead.
151 | > ```
152 |
153 | `main.py` 파일을 만들고 다음 코드를 추가합니다. 파이썬으로 피보나치 수열을 구하는 함수 `pyrun` 을 추가해 러스트 구현체와 성능을 비교해봅니다.
154 |
155 | ```python
156 | import time
157 |
158 | from fibonacci import run
159 |
160 |
161 | def pyrun(n: int):
162 | if n < 2:
163 | return n
164 |
165 | return pyrun(n - 1) + pyrun(n - 2)
166 |
167 |
168 | N = 35
169 |
170 | start = time.time()
171 | result = pyrun(N)
172 | print(f"python: {time.time()-start:.2f}, result: {result}")
173 | start = time.time()
174 | result = run(N)
175 | print(f"rust: {time.time()-start:.2f}, result: {result}")
176 |
177 | ```
178 |
179 | 실행 결과
180 |
181 | ```bash
182 | $ python main.py
183 | python: 3.13, result: 9227465
184 | rust: 0.10, result: 9227465
185 | ```
186 |
187 |
188 |
189 | ### 릴리즈 모드로 빌드해보기
190 |
191 | 빌드 옵션을 `--release` 로 주면, 러스트 코드를 최대한 최적화해서 컴파일한 바이너리가 패키지로 만들어지게 됩니다. 컴파일 타겟이 `[optimized]`인 걸 알 수 있습니다.
192 |
193 | ```bash
194 | $ maturin build --release
195 | 🔗 Found pyo3 bindings
196 | 🐍 Found CPython 3.8 at /Users/.local/share/virtualenvs/temp-nO4s4P8m/bin/python3
197 | Compiling target-lexicon v0.12.4
198 | Compiling once_cell v1.13.1
199 | Compiling proc-macro2 v1.0.43
200 | Compiling libc v0.2.132
201 | Compiling quote v1.0.21
202 | Compiling unicode-ident v1.0.3
203 | Compiling syn v1.0.99
204 | Compiling autocfg v1.1.0
205 | Compiling parking_lot_core v0.9.3
206 | Compiling cfg-if v1.0.0
207 | Compiling smallvec v1.9.0
208 | Compiling scopeguard v1.1.0
209 | Compiling unindent v0.1.10
210 | Compiling indoc v1.0.7
211 | Compiling lock_api v0.4.7
212 | Compiling pyo3-build-config v0.16.5
213 | Compiling parking_lot v0.12.1
214 | Compiling pyo3-ffi v0.16.5
215 | Compiling pyo3 v0.16.5
216 | Compiling pyo3-macros-backend v0.16.5
217 | Compiling pyo3-macros v0.16.5
218 | Compiling fibonacci v0.1.0 (/Users/code/temp)
219 | Finished release [optimized] target(s) in 20.61s
220 | 📦 Built wheel for CPython 3.8 to /Users/code/temp/target/wheels/fibonacci-0.1.0-cp38-cp38-macosx_10_7_x86_64.whl
221 | ```
222 |
223 | 이제 파이썬 코드를 그대로 실행하면 최적화된 패키지로 실행이 가능합니다.
224 |
225 | 실행 결과
226 |
227 | ```bash
228 | $ python main.py
229 | python: 3.03, result: 9227465
230 | rust: 0.03, result: 9227465
231 | ```
232 |
--------------------------------------------------------------------------------
/src/ch15-03.md:
--------------------------------------------------------------------------------
1 | ## PyO3와 GIL
2 |
3 | 앞에서는 단일 스레드 환경에서 러스트 코드를 실행하는 예제를 살펴봤습니다. 하지만 이 강의의 가장 큰 목표인 파이썬 GIL을 우회하는 러스트 패키지를 만들기 위해서는 멀티스레드 환경에서 러스트 코드를 실행해보아야 합니다. 이를 살펴보기 위해서 새로운 프로젝트 `gil`을 생성합니다.
4 |
5 | ```bash
6 | maturin init -b pyo3
7 | ```
8 |
9 |
10 |
11 | ### GIL 획득과 해제
12 |
13 | `py.allow_threads`는 PyO3에서 제공하는 메서드로, 클로저를 실행하는 동안 GIL을 일시적으로 해제할 수 있습니다. 이 메서드는 파이썬 인터프리터와 상호 작용할 필요가 없는 장기 실행 계산이 있고 다른 스레드가 파이썬 코드를 병렬로 실행하도록 허용하려는 경우에 유용할 수 있습니다. `py.allow_threads`를 사용하면 GIL의 해제 및 획득 시점을 세밀하게 제어할 수 있으므로 일부 상황에서 유용할 수 있습니다.
14 |
15 | PyO3에서 GIL을 해제하는 다른 방법으로는 `Python::with_gil` 메서드를 사용하여 명시적으로 GIL을 획득하고 해제할 수도 있습니다.
16 |
17 | 일반적으로 파이썬 인터프리터와 상호 작용할 필요가 없는 장기 실행 계산이 있을 때마다 GIL을 해제하는 것이 좋습니다. 이렇게 하면 다른 스레드에서 Python 코드를 병렬로 실행할 수 있으므로 프로그램 성능이 향상될 수 있습니다.
18 |
19 | ```rust
20 | use std::thread;
21 | use std::time::Duration;
22 |
23 | use pyo3::prelude::*;
24 | use pyo3::types::PyList;
25 |
26 | #[pyfunction]
27 | fn double_list(py: Python<'_>, list: &PyList, result: &PyList, idx: usize) -> PyResult<()> {
28 | println!("Rust: Enter double_list...");
29 | py.allow_threads(|| {
30 | println!("Rust: Release GIL...");
31 | thread::sleep(Duration::from_secs(1));
32 | });
33 |
34 | let doubled: Vec = list.extract::>()?.iter().map(|x| x * 2).collect();
35 |
36 | Python::with_gil(|py| {
37 | println!("Rust: Acquire GIL...");
38 | thread::sleep(Duration::from_secs(1));
39 | let py_list = PyList::new(py, &doubled);
40 | println!("Rust: Exit...");
41 | result.set_item(idx, py_list)
42 | })
43 | }
44 |
45 | /// A Python module implemented in Rust.
46 | #[pymodule]
47 | fn gil(_py: Python, m: &PyModule) -> PyResult<()> {
48 | m.add_function(wrap_pyfunction!(double_list, m)?)?;
49 | Ok(())
50 | }
51 |
52 | ```
53 |
54 |
55 |
56 | 파이썬에서 `sleep` 함수를 사용해 GIL을 해제한 다음 러스트 코드와 번갈아가면서 실행되는 예제입니다.
57 |
58 | ```python
59 | import time
60 | import threading
61 |
62 | from gil import double_list
63 |
64 |
65 | def double_list_py(list, result, idx):
66 | print("Py: Enter double_list_py...")
67 | time.sleep(0.1)
68 | result[idx] = [x * 2 for x in list]
69 | print("Py: Exit...")
70 |
71 |
72 | result = [[], []]
73 | nums = [1, 2, 3]
74 |
75 | t1 = threading.Thread(target=double_list_py, args=(nums, result, 0))
76 | t2 = threading.Thread(target=double_list, args=(nums, result, 1))
77 |
78 | t1.start()
79 | t2.start()
80 |
81 | t1.join()
82 | t2.join()
83 |
84 | print(f"Py: {result[0]}")
85 | print(f"Rust: {result[1]}")
86 |
87 | ```
88 |
89 | 실행 결과
90 |
91 | ```
92 | Py: Enter double_list_py...
93 | Rust: Enter double_list...
94 | Rust: Release GIL...
95 | Py: Sleep for 1 sec...
96 | Rust: Acquire GIL...
97 | Rust: Exit...
98 | Py: Exit...
99 | Py: [2, 4, 6]
100 | Rust: [2, 4, 6]
101 | ```
102 |
103 |
--------------------------------------------------------------------------------
/src/ch2-00.md:
--------------------------------------------------------------------------------
1 | # CH2. 변수
2 |
3 | 이번 장부터는 파이썬과 러스트 코드를 동시에 비교하면서 러스트 언어의 개념들을 설명해 보도록 하겠습니다.
4 |
5 |
--------------------------------------------------------------------------------
/src/ch2-01.md:
--------------------------------------------------------------------------------
1 | ## 값 출력하기
2 |
3 | 파이썬에서는 모든 객체를 `print` 함수로 출력할 수 있습니다. 문자열 `"Hello, world!"` 를 출력하는 예제는 다음과 같습니다.
4 |
5 | ```python
6 | print("Hello, world!")
7 | ```
8 |
9 | 반면 러스트에서는 `print` 같은 편리한 함수 대신, 매크로(macro)를 사용해 값을 출력합니다. 매크로란 사전 정의된 편리한 기능을 의미하고, 항상 이름 뒤에 `!`가 붙습니다. 매크로에 대해서는 뒤에서 다시 자세히 설명하겠습니다. 러스트 코드는 매 줄의 마지막에 세미콜론(;) 이 붙습니다. 세미콜론이 없으면 컴파일 에러가 발생하니 주의하세요. 위의 파이썬 예제와 동일하게 문자열 `"Hello, world!"` 를 출력하는 코드는 다음과 같습니다.
10 |
11 | ```rust
12 | fn main() {
13 | println!("Hello, world!");
14 | }
15 |
16 | ```
17 |
18 |
19 |
20 | ## 변수 선언
21 |
22 | 변수란 메모리에 값을 저장하기 위해 사용되는 개념입니다. 변수의 이름을 통해 메모리에 저장된 값을 참조해서 사용할 수 있습니다. 파이썬은 변수 선언 시 타입을 명시하지 않아도 되기 때문에 실수값과 정수값 모두 변수에 바로 할당이 가능합니다. 파이썬에서 변수 `x` 와 `y` 를 선언하고 실수 1.0과 정수 10을 할당한 다음, f-string을 사용해 두 변수의 값을 출력합니다. `main.py` 에 다음 내용을 입력합니다.
23 |
24 | ```python
25 | x = 1.0
26 | y = 10
27 |
28 | print(f"x = {x}, y = {y}")
29 | ```
30 |
31 | 파이썬 코드 실행 결과는 다음과 같습니다. 폴더를 하위 폴더인 "python"으로 이동한 다음 코드를 실행해야 합니다.
32 |
33 | ```bash
34 | /code/temp/python $ python main.py
35 | x = 1.0, y = 10
36 | ```
37 |
38 | 러스트에서는 `let` 키워드를 사용해 변수를 선언합니다. 그리고 타입을 `:` 뒤에 명시합니다.
39 |
40 | ```rust,ignore
41 | 변수명 타입 값
42 | let x: i32 = 10;
43 | ```
44 |
45 | 대부분의 경우에서는 컴파일러가 타입을 추측해주지만, 몇몇 경우에는 직접 타입을 명시해줘야 하기도 합니다. 아래 예제에서는 실수 1.0을 `f64` 타입으로 선언했지만, 실제로는 변수 `y`와 같이 명시적으로 타입을 적어주지 않아도 컴파일이 됩니다. 다음으로 `prinln!` 매크로에서는 문자열의 `{}` 자리에 변수가 순서대로 들어가 전체 문자열이 완성됩니다.
46 |
47 | ```rust
48 | fn main() {
49 | let x: f64 = 1.0;
50 | let y = 10;
51 |
52 | println!("x = {}, y = {}", x, y);
53 | }
54 | ```
55 |
56 | 하위 폴더인 "rust_part" 폴더로 이동한 다음, `cargo run` 을 실행해 결과를 확인해보겠습니다.
57 |
58 | ```bash
59 | /code/temp/rust_part $ cargo run
60 | x = 1, y = 10
61 | ```
62 |
63 | 파이썬과 러스트 모두 같은 결과가 나오는 것을 알 수 있습니다.
64 |
65 | 앞으로 파이썬 코드와 러스트 코드를 동시에 실행해야 하기 때문에, VSCode 터미널의 분할(split) 기능을 사용하면 편리합니다. 터미널을 연 다음, 우측 상단의 "터미널 분할"을 클릭합니다.
66 |
67 |
68 |
69 | 그리고 분할된 화면에서 하나는 파이썬 프로젝트를, 나머지는 러스트 프로젝트로 이동해 두면 편리하게 코드를 실행할 수 있습니다.
70 |
71 | 
72 |
73 |
74 |
75 | ## 작명 규칙
76 |
77 | 파이썬과 러스트의 작명 규칙은 정말 비슷합니다. 대표적인 몇 가지 경우를 살펴보면 다음과 같습니다.
78 |
79 | | | 파이썬 | 러스트 |
80 | | ------------- | -------------------------- | -------------------------------------- |
81 | | 변수 | `snake_case = 3` | `let snake_case = 3;` |
82 | | 함수 | `def my_function` | `fn my_function` |
83 | | 클래스/구조체 | `class MyClass` | `struct MyStruct` |
84 | | 상수 | `SCREAMING_SNAKE_CASE = 1` | `const SCREAMING_SNAKE_CASE: i32 = 1;` |
85 |
86 | - 변수와 함수의 경우, 둘 다 스네이크 케이스(Snake case)를 사용합니다. 스네이크 케이스란, 모든 단어를 숫자 또는 알파벳 소문자로 작성하고, 단어 구분은 언더바(_)로 합니다. 단 변수명은 반드시 알파벳 소문자로만 시작해야 합니다.
87 | - 파이썬의 클래스와 러스트 구조체는 파스칼 케이스(Pascal case)를 사용합니다. 파스칼 케이스는 대문자로 단어를 시작하고, 단어 구분을 대문자로 하는 작명법입니다.
88 | - 상수의 경우는 둘 다 스크리밍 스네이크 케이스(Screaming snake case)를 사용합니다. 모든 알파벳이 대문자이고, 단어 구분을 언더바로 합니다. 단, 러스트의 상수는 반드시 타입을 명시해야 합니다.
89 |
--------------------------------------------------------------------------------
/src/ch2-02.md:
--------------------------------------------------------------------------------
1 | ## 불변성
2 |
3 | 러스트에서 변수를 다룰 때, 파이썬에는 없는 두 가지 개념이 있습니다. 러스트의 모든 변수는 기본적으로 불변(immutable)입니다. 파이썬에서는 변수를 선언한 다음 다른 값을 넣는 것이 매우 자유롭습니다. 변수의 타입도 상관 없이 새로운 값을 마음대로 넣을 수 있습니다.
4 |
5 | ```python
6 | x = 1
7 | x = "2"
8 | x = 3.141592
9 | ```
10 |
11 | 하지만 러스트에서는 조금 다릅니다. 예를 들어, 아래 코드와 같이 `let` 키워드로 변수를 선언하고, 해당 변수의 값을 바꾸려고 한다면 컴파일이 되지 않습니다.
12 |
13 | ```rust,ignore
14 | fn main() {
15 | let x = 1;
16 | x = 2; // won't compile!
17 | println!("{}", x);
18 | }
19 |
20 | ```
21 |
22 | 위 코드를 실행해보면 다음과 같은 에러가 발생합니다.
23 |
24 | ```
25 | error[E0384]: cannot assign twice to immutable variable `x`
26 | --> src/main.rs:3:5
27 | |
28 | 2 | let x = 1;
29 | | -
30 | | |
31 | | first assignment to `x`
32 | | help: consider making this binding mutable: `mut x`
33 | 3 | x = 2; // won't compile!
34 | | ^^^^^ cannot assign twice to immutable variable
35 |
36 | ```
37 |
38 | 에러의 내용을 읽어보면, 처음 `let x=1` 로 선언된 변수가 불변(immutable)이기 때문에 값을 두 번 할당할 수 없다고 합니다. 그리고 컴파일러가 "help"에서 문제 해결 방법을 소개하는데, 변수 `x` 를 가변 변수(mutable)로 다음과 같이 선언하라고 합니다.
39 |
40 | ```rust,ignore
41 | let mut x = 1;
42 | ```
43 |
44 | 컴파일러의 조언에 따라 수정된 코드를 아래와 같이 작성하고 실행해봅시다.
45 |
46 | ```rust,ignore
47 | fn main() {
48 | let mut x = 1;
49 | x = 2;
50 | println!("{}", x);
51 | }
52 |
53 | ```
54 |
55 | 이제 콘솔에 값 2가 잘 출력되는 것을 알 수 있습니다. 이처럼, 러스트에서는 모든 변수의 값이 불변으로 선언이 됩니다. 따라서 값을 바꾸고자 하는 변수에는 `mut` 키워드로 가변성을 부여해야 합니다.
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/ch2-03.md:
--------------------------------------------------------------------------------
1 | ## 섀도잉
2 |
3 | 한번 선언한 불변 변수의 값을 변경하는 것은 불가능하지만, 변수 자체를 새로 선언하는 것은 가능합니다. 이렇게 변수 이름을 재사용해서 새로운 변수를 다시 선언하는 것을 섀도잉(shadowing)이라고 합니다.
4 |
5 | 섀도잉을 사용할 경우, `mut` 키워드 없이도 새로운 값을 변수에 할당할 수 있고, 새로운 변수이기 때문에 타입도 변경할 수 있습니다. 아래 예제에서는 변수 `x` 에 처음에는 `"5"` 라는 문자열을 할당했지만, 그 다음에는 섀도잉을 사용해 `x`에 정수 6을 할당했습니다. 코드를 실행해보면 정상적으로 컴파일됩니다.
6 |
7 | ```rust
8 | fn main() {
9 | let x = "5";
10 |
11 | let x = 6; // x is redeclared as 6
12 |
13 | println!("The value of x is: {}", x); // 6
14 | }
15 |
16 | ```
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/ch2-04.md:
--------------------------------------------------------------------------------
1 | ## 타입
2 |
3 | C언어 계열과 마찬가지로, Rust는 타입이 존재합니다. 러스트의 원시 타입(primitive type) 목록은 다음과 같습니다.
4 |
5 | | 이름 | 타입 |
6 | | ---- | ---- |
7 | | 8비트 정수 | `i8` |
8 | | 16비트 정수 | `i16` |
9 | | 32비트 정수 | `i32` |
10 | | 64비트 정수 | `i64` |
11 | | 128비트 정수 | `i128` |
12 | | 아키텍처 | `isize` |
13 | | 부호 없는 8비트 정수 | `u8` |
14 | | 부호 없는 16비트 정수 |`u16` |
15 | | 부호 없는 32비트 정수 | `u32` |
16 | | 부호 없는 64비트 정수 | `u64` |
17 | | 부호 없는 128비트 정수 |`u128` |
18 | | 부호 없는 아키텍처 |`usize` |
19 | | 불리언 | `bool` |
20 | | 문자열 | `String` |
21 | | 문자열 슬라이스 | `str` |
22 | | 32비트 부동소수점 실수 | `f32` |
23 | | 64비트 부동소수점 실수 | `f64` |
24 |
25 | > `isize` 와 `usize` 는 컴퓨터 아키텍처가 32비트인지 64비트인지에 따라서 값이 달라지는 기본 포인터 크기입니다.
26 |
27 |
28 |
29 | ### 타입 추론
30 |
31 | 러스트 코드를 작성할 때 대부분의 경우에는 개발자가 변수에 타입을 지정하지 않아도 앞에서 설치한 `rust-analyzer`가 알아서 타입을 추론(inference)해서 화면에 보여줍니다. 비슷한 원리로 코드가 컴파일될 때에는 컴파일러가 타입을 추론해서 변수를 선언하게 됩니다. 이때, 추측되는 타입의 기본값은 정수형은 `i32` , 실수형은 `f64` 입니다.
32 |
33 | 다음 코드를 VSCode에 붙여넣으면 아래 그림과 같이 타입이 추론되는 것을 볼 수 있습니다.
34 |
35 | ```rust
36 | fn main(){
37 | let x = 1;
38 | let y = 1.0;
39 | println!("{} {}", x, y);
40 | }
41 | ```
42 |
43 |
44 |
45 | 마찬가지로 이 상태로도 컴파일이 잘 실행되고, 컴파일러가 각 변수를 `i32` 와 `f64` 로 추측해서 컴파일합니다.
46 |
47 | 실행결과
48 |
49 | ```
50 | 1 1
51 | ```
52 |
53 |
54 |
55 | ### 타입 캐스팅
56 |
57 | 변수의 타입을 다른 타입으로 바꾸는 타입 변환(Casting)도 가능합니다. 파이썬에서는 타입 이름을 바로 사용해 타입 변환을 수행합니다.
58 |
59 | ```python
60 | x = 1.2
61 | y = int(x)
62 | print(f"{x} -> {y}");
63 | ```
64 |
65 | 실행결과
66 |
67 | ```
68 | 1.2 -> 1
69 | ```
70 |
71 | 러스트에서는 아래와 같이 `as` 키워드를 사용하면 됩니다. 예제에서는 64비트 실수 `f64` 로 선언된 변수 `x` 의 값을 32비트 정수 `i32` 로 변환해 `y` 변수에 할당하고 있습니다. 실수에서 정수로 변환했기 때문에 값이 1.2에서 1로 변경됩니다.
72 |
73 | ```rust
74 | fn main() {
75 | let x: f64 = 1.2;
76 | let y = x as i32;
77 | println!("{} -> {}", x, y);
78 | }
79 |
80 | ```
81 |
82 | 실행결과
83 |
84 | ```
85 | 1.2 -> 1
86 | ```
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/src/ch2-05.md:
--------------------------------------------------------------------------------
1 | ## 상수
2 |
3 | 상수(constant)란, 한 번 선언되면 값이 바뀌지 않는 변수를 의미합니다. 먼저 파이썬에서 상수를 다음과 같이 선언해 보겠습니다.
4 |
5 | ```python
6 | THRESHOLD = 10
7 |
8 |
9 | def is_big(n: int) -> bool:
10 | return n > THRESHOLD
11 |
12 |
13 | if __name__ == '__main__':
14 | print(THRESHOLD)
15 | print(is_big(THRESHOLD))
16 |
17 | THRESHOLD = 5
18 |
19 | ```
20 |
21 | 실행 결과
22 |
23 | ```
24 | 10
25 | False
26 |
27 | ```
28 |
29 | 일반적으로 상수는 모듈의 가장 위에 선언합니다. 이렇게 선언하게 되면, 모듈의 모든 범위에서 상수에 접근하는 것이 가능합니다. `is_big` 이라는 함수 안에서도 상수 `THRESHOLD` 를 사용할 수 있고, 함수를 실행하는 `if` 문 안에서도 `THRESHOLD` 를 사용합니다. 하지만 파이썬의 모든 변수는 기본적으로 가변이기 때문에 위에서 선언한 상수를 변경할 수 있다는 문제가 있습니다.
30 |
31 | 러스트에서 동일한 내용을 구현해 보겠습니다. 여기서 `is_big` 이라는 새로운 함수를 선언했는데, 함수의 선언에 대한 자세한 설명은 다음 챕터에서 다룰 예정입니다.
32 |
33 | ```rust
34 | const THRESHOLD: i32 = 10;
35 |
36 | fn is_big(n: i32) -> bool {
37 | n > THRESHOLD
38 | }
39 |
40 | fn main() {
41 | println!("{}", THRESHOLD);
42 | println!("{}", is_big(5));
43 | }
44 |
45 | ```
46 |
47 | 실행결과
48 |
49 | ```
50 | 10
51 | false
52 | ```
53 |
54 |
55 |
56 | 러스트에서는 상수를 `const` 키워드로 선언하게 됩니다. 이렇게 선언된 상수는 불변이기 때문에 값을 변경할 수 없습니다. 파이썬에서 상수를 모듈 전체에서 접근할 수 있었던 것처럼, 마찬가지로 러스트에서도 선언된 상수 `THRESHOLD` 를 함수 `is_big` 과 `main` 내부에서 참조하는 것이 가능합니다. 하지만 값이 불변이기 때문에 `THRESHOLD = 5;`와 같이 새로운 값을 할당하게 되면 오류가 발생합니다.
57 |
58 | ```rust,ignore
59 | const THRESHOLD: i32 = 10;
60 |
61 | fn is_big(n: i32) -> bool {
62 | n > THRESHOLD
63 | }
64 |
65 | fn main() {
66 | println!("{}", THRESHOLD);
67 | println!("{}", is_big(5));
68 |
69 | THRESHOLD = 5;
70 | }
71 |
72 | ```
73 |
74 | 실행결과
75 |
76 | ```
77 | --> src/main.rs:11:15
78 | |
79 | 11 | THRESHOLD = 5;
80 | | --------- ^
81 | | |
82 | | cannot assign to this expression
83 | ```
84 |
85 | 컴파일러가 친절하게 상수 `THRESHOLD` 에는 새로운 값을 할당할 수 없다고 알려주게 됩니다. 실행하기 전 편집기 안에서도 빨간 줄로 해당 코드에 문제가 있음을 알려주기 때문에 문제를 빠르게 찾고 해결할 수 있습니다.
86 |
87 | 
--------------------------------------------------------------------------------
/src/ch3-00.md:
--------------------------------------------------------------------------------
1 | # CH3. 함수
2 |
3 | 이번 챕터에서는 함수의 선언과 사용에 대해서 자세히 다루겠습니다.
4 |
5 |
--------------------------------------------------------------------------------
/src/ch3-01.md:
--------------------------------------------------------------------------------
1 | ## 함수 선언
2 |
3 | 함수의 입력으로 정수 두 개를 받은 다음 두 수의 합을 리턴하는 `add` 라는 함수를 만들어 보겠습니다.
4 |
5 | 먼저 파이썬 코드는 다음과 같습니다. 여기서 타입 힌트를 사용해 파라미터와 리턴값의 타입을 명시할 수 있습니다. 파라미터 변수 이름 뒤에 `:int`를 붙여 이 파라미터의 타입이 `int`임을 명시합니다. 함수에서 리턴하는 값은 함수명 뒤에 `-> int`와 같이 표기합니다.
6 |
7 | ```python
8 | def add(num1: int, num2: int) -> int:
9 | return num1 + num2
10 | ```
11 |
12 | 동일한 기능의 러스트 코드는 다음과 같습니다. 함수의 선언에 `fn` 키워드를 사용하고, 함수에서 실행할 코드를 중괄호로 묶어줍니다. 그리고 파이썬과 비슷하게 파라미터에는 `:i32`로 타입을 표기하고, 리턴값에는 `-> i32`처럼 화살표를 사용해 타입을 명시했습니다.
13 |
14 | ```rust,ignore
15 | fn add(num1: i32, num2: i32) -> i32 {
16 | return num1 + num2;
17 | }
18 | ```
19 |
20 | > 이때 주의해야 하는 점은 파이썬에서는 타입을 생략할 수 있지만, 러스트에서는 반드시 파라미터와 리턴 타입을 명시해야 한다는 것입니다. 타입이 잘못되거나 표기되지 않았다면 컴파일되지 않습니다.
21 |
22 |
23 |
24 | 러스트는 코드 마지막에서 `return` 키워드를 생략할 수 있습니다. 이때 세미콜론이 없다는 점에 주의하세요. 다음 코드는 위에서 정의한 `add` 와 완전히 동일합니다.
25 |
26 | ```rust,ignore
27 | fn add(num1: i32, num2: i32) -> i32 {
28 | num1 + num2
29 | }
30 | ```
31 |
32 | 이제 `add` 함수를 메인 함수에서 호출하고 값을 프린트해 보겠습니다.
33 |
34 | ```rust
35 | fn add(num1: i32, num2: i32) -> i32 {
36 | num1 + num2
37 | }
38 |
39 | fn main() {
40 | println!("{}", add(1, 2));
41 | }
42 |
43 | ```
44 |
45 | 실행 결과
46 |
47 | ```
48 | 3
49 | ```
50 |
51 | 함수의 호출은 파이썬과 동일하게 `함수명(파라미터, 파라미터, ...)` 와 같이 할 수 있습니다.
52 |
53 |
54 |
55 | ### 여러 개의 값 리턴하기
56 |
57 | 이번에는 함수에서 여러 개의 값을 리턴하는 경우를 살펴보겠습니다. 입력받은 두 정수를 순서를 바꿔서 리턴하는 함수를 만들어 보겠습니다. 먼저 파이썬에서 `swap` 이라는 함수를 아래와 같이 구현합니다. 이렇게 여러 개의 값을 리턴하는 경우, 리턴 타입이 튜플이 됩니다.
58 |
59 | ```python
60 | def swap(num1: int, num2: int) -> tuple[int, int]:
61 | return num2, num1
62 |
63 |
64 | num1, num2 = swap(1, 2)
65 | print(f"{num1}, {num2}")
66 |
67 | ```
68 |
69 | 실행 결과
70 |
71 | ```
72 | 2, 1
73 | ```
74 |
75 | 이번에는 러스트 코드입니다. 러스트도 여러 개의 값을 리턴하는 경우, 값들이 튜플로 묶이게 됩니다. 따라서 리턴하는 두 정수를 소괄호로 묶어서 `(num2, num1)` 과 같이 튜플임을 표시합니다. 따라서 함수의 리턴 타입도 튜플로 `(i32, i32)` 표기합니다.
76 |
77 | ```rust
78 | fn swap(num1: i32, num2: i32) -> (i32, i32) {
79 | (num2, num1)
80 | }
81 |
82 | fn main() {
83 | let (num1, num2) = swap(1, 2);
84 | println!("{num1}, {num2}");
85 | }
86 |
87 | ```
88 |
89 | 실행 결과
90 |
91 | ```
92 | 2, 1
93 | ```
94 |
95 | > 러스트의 튜플에 관해서는 자료구조 챕터에서 자세히 다루겠습니다.
96 |
97 | 만일 `main` 함수와 같이, 함수에서 리턴하는 값이 없는 경우에는 리턴 타입을 생략하거나 `()`와 같이 아무 것도 리턴하지 않음을 표기할 수 있습니다. 파이썬에서 아무것도 리턴하지 않는 경우, `-> None` 으로 표기하거나 표기를 생략하는 것과 비슷합니다.
98 |
99 | ```rust
100 | fn do_nothing() -> () {
101 | return ();
102 | }
103 |
104 | fn me_too() {}
105 |
106 | fn main() {
107 | println!("{:?}", do_nothing());
108 | println!("{:?}", me_too());
109 | }
110 |
111 | ```
--------------------------------------------------------------------------------
/src/ch3-02.md:
--------------------------------------------------------------------------------
1 | ## 스코프
2 |
3 | 스코프(scope)란 변수에 접근할 수 있는 범위를 의미합니다. 먼저 파이썬에서는 스코프를 기본적으로 함수 단위로 구분합니다.
4 |
5 | > 실제로는 파이썬은 LEGB 룰이라고 불리는 좀더 복잡한 스코프 규칙을 가지고 있지만, 여기서는 단순화해서 함수 기준으로 설명합니다.
6 |
7 | ```python
8 | def hello(name: str):
9 | num = 3
10 | print(f"Hello {name}")
11 |
12 |
13 | if __name__ == '__main__':
14 | my_name = "buzzi"
15 |
16 | if True:
17 | print("My name is", my_name)
18 | my_name = "mellon"
19 |
20 | hello(my_name)
21 |
22 | # print(num) # error
23 |
24 | ```
25 |
26 | 실행 결과
27 |
28 | ```python
29 | My name is buzzi
30 | Hello mellon
31 | ```
32 |
33 | 코드 실행 부분을 먼저 보면, `my_name` 변수에 `"buzzi"` 라는 문자열을 할당합니다. 그 다음 `if` 문에서 변수 값을 프린트해보면 "buzzi"가 프린트됩니다. 하지만 그 다음 라인에서 `my_name = "mellon"` 으로 변수의 값을 바꿔 버렸습니다. 파이썬은 스코프를 함수 단위로만 구분하고 있기 때문에 이제 코드 전체에서 값이 바뀌게 됩니다. 따라서 `hello(my_name)`의 출력은 `Hello mellon`이 됩니다. 마지막으로 `# print(num) # error`를 주석 해제하고 실행해 보면 에러가 발생합니다. `hello` 함수 안에서 선언된 `num` 이라는 변수를 프린트하기 때문입니다. 즉, `num` 의 스코프가 ` hello` 함수이기 때문에 함수 바깥에서 참조할 수 없는 것입니다.
34 |
35 | 이번에는 러스트의 스코프를 살펴보겠습니다.
36 |
37 | ```rust
38 | fn hello(name: String) {
39 | let num = 3;
40 | println!("Hello {}", name);
41 | }
42 |
43 | fn main() {
44 | let my_name = "buzzi".to_string();
45 |
46 | {
47 | println!("My name is {}", my_name);
48 | let my_name = "mellon";
49 | }
50 |
51 | hello(my_name);
52 |
53 | // println!("{}", num); // error
54 | }
55 |
56 | ```
57 |
58 | 실행 결과
59 |
60 | ```
61 | My name is buzzi
62 | Hello buzzi
63 | ```
64 |
65 | 러스트에서는 스코프를 중괄호 "{}" 기준으로 구분합니다. 먼저 `my_name` 변수를 "buzzi"로 할당했습니다. 그 다음, 중괄호 안에서 `my_name` 을 프린트해보면 "buzzi"가 프린트됩니다. 중괄호 안에서 `my_name` 을 "mellon"으로 할당하더라도, 중괄호를 벗어나면 중괄호 안에서 선언된 `my_name` 의 스코프가 끝나게 되므로 중괄호 바깥에서는 `my_name` 의 값은 원래대로 "buzzi"가 됩니다. 따라서 `hello(my_name)`의 실행 결과는 "Hello buzzi"가 됩니다. 파이썬에서와 마찬가지로, `hello` 안에서 선언된 변수인 `num`은 함수 바깥에서 참조할 수 없기 때문에 `println!("{}", num);` 을 주석 해제한 다음 코드를 실행하면 에러가 발생합니다.
66 |
67 | 러스트의 스코프는 나중에 배울 소유권 모델과 밀접한 연관이 있기 때문에 중괄호를 기준으로 스코프가 변경된다는 사실을 꼭 기억해 두세요.
68 |
69 | > 러스트의 주석은 `//` 로 표기합니다.
70 |
71 |
--------------------------------------------------------------------------------
/src/ch3-03.md:
--------------------------------------------------------------------------------
1 | ## 익명 함수
2 |
3 | 익명 함수란 이름이 없는 함수라는 뜻으로, 프로그램 내에서 변수에 할당하거나 다른 함수에 파라미터로 전달되는 함수입니다. 따라서 익명 함수를 먼저 만들어 놓고 나중에 함수를 실행할 수 있습니다.
4 |
5 | 파이썬에서는 익명 함수를 람다 함수(Lambda function)이라고 부릅니다. `lambda` 키워드를 쓰고, `파라미터: 리턴값` 형식으로 함수의 내용을 정의합니다. 이렇게 만든 람다 함수를 변수 `my_func` 에 할당해 두었다가 `print` 함수 안에서 호출하는 예제입니다.
6 |
7 | ```python
8 | my_func = lambda x: x + 1
9 | print(my_func(3))
10 |
11 | ```
12 |
13 | 실행 결과
14 |
15 | ```
16 | 4
17 | ```
18 |
19 | 위 예제처럼 람다 함수는 다른 함수에 파라미터로 전달하는 것이 가능합니다. 러스트에도 람다 함수와 비슷한 개념이 있는데 바로 클로저(Closure)입니다. 위에서 만든 람다 함수와 동일한 기능을 하는 클로저를 만들어 보겠습니다. 클로저는 파라미터를 `| |` 의 사이에 선언하고, 그 뒤에 함수에서 리턴하는 부분을 작성합니다.
20 |
21 | ```rust
22 | fn main() {
23 | let my_func = |x| x + 1;
24 | println!("{}", my_func(3));
25 | }
26 |
27 | ```
28 |
29 | 실행 결과
30 |
31 | ```
32 | 4
33 | ```
34 |
35 | 이때 컴파일러가 클로저의 파라미터와 리턴값의 타입을 `i32`로 추측해서 보여줍니다. 이는 실제 함수가 실행되는 부분인 `my_func(3)`로부터 변수 `x`의 타입을 알 수 있기 때문입니다. 이처럼 클로저는 함수와 다르게 타입을 명시할 필요가 없이 컴파일러가 타입을 추론하도록 할 수 있습니다. 하지만 타입을 명시하는 것도 가능합니다.
36 |
37 | ```rust
38 | fn main() {
39 | let my_func = |x: i32| -> i32 { x + 1 };
40 | println!("{}", my_func(3));
41 | }
42 |
43 | ```
44 |
45 | 타입을 명시해야 하는 경우, 함수 실행 부분을 중괄호로 묶어 주어야 합니다.
46 |
47 | 람다 함수는 반드시 한 줄로만 작성해야 하지만, 클로저는 중괄호로 묶어주는 경우 여러 줄을 작성할 수 있습니다. 위 코드를 하나의 클로저로 바꿔 보겠습니다. 이때 입력받은 변수 `x`의 값을 바꾸기 위해 가변으로 선언하고, 첫 번째 줄에서 `x`에 1을 더해줍니다. 그 다음 `x`를 프린트합니다. 이제 `my_func`를 호출하면 동일하게 4가 출력됩니다.
48 |
49 | ```rust
50 | fn main() {
51 | let my_func = |mut x: i32| {
52 | x = x + 1;
53 | println!("{}", x);
54 | };
55 |
56 | my_func(3);
57 | }
58 |
59 | ```
60 |
61 |
62 |
63 | 참고로 파이썬과는 다르게 클로저의 재귀 호출은 아직 지원되지 않습니다. 파이썬에서 클로저를 이용해 피보나치 수를 계산하는 예제는 다음과 같습니다.
64 |
65 | ```python
66 | def fibonacci(n):
67 | cache = {}
68 |
69 | def fib(n):
70 | if n in cache:
71 | return cache[n]
72 | if n < 2:
73 | return n
74 | cache[n] = fib(n - 1) + fib(n - 2)
75 | return cache[n]
76 |
77 | return fib(n)
78 |
79 |
80 | fibonacci(10)
81 |
82 | ```
83 |
84 | 동일한 로직을 구현한 러스트 코드는 클로저가 자기 자신을 부를 수 없기 때문에 컴파일되지 않습니다.
85 |
86 | ```rust,ignore
87 | fn fib(n: u32) -> u32 {
88 | let cache = vec![0, 1];
89 | let _fib = |n| {
90 | if n < cache.len() {
91 | cache[n]
92 | } else {
93 | let result = _fib(n - 1) + _fib(n - 2);
94 | cache.push(result);
95 | result
96 | }
97 | };
98 | _fib(n)
99 | }
100 |
101 | fn main() {
102 | println!("{}", fib(10));
103 | }
104 |
105 | ```
106 |
107 |
--------------------------------------------------------------------------------
/src/ch4-00.md:
--------------------------------------------------------------------------------
1 | # CH4. 흐름제어
2 |
3 | 다음은 프로그램의 논리적 흐름을 결정할 수 있는 흐름제어문(control flow)에 대해서 알아보겠습니다.
--------------------------------------------------------------------------------
/src/ch4-01.md:
--------------------------------------------------------------------------------
1 |
2 | ## if/else
3 |
4 | `if` 문은 어떤 조건을 만족하는 경우, 그에 해당하는 코드를 실행하도록 논리적 분기를 만드는 방법입니다. 만일 조건이 만족되지 않으면 해당 분기의 코드는 실행되지 않고 넘어갑니다. 보통 `if` 문은 첫 번째 조건을 검사하는 부분인 `if` 와, 그 다음 조건을 만족하는지를 검사하는 `else if` , 그리고 모두 해당되지 않는 경우에 실행되는 ` else` 로 구성됩니다.
5 |
6 | 파이썬에서 `if`문을 구현하면 다음과 같습니다. 항상 `if` 문은 `if` 라는 키워드로 시작합니다. 여기서는 변수 `x` 가 `y` 보다 작은지를 검사하고 있는데, `x` 는 1.0이고 ` y`가 10이기 때문에 조건이 만족됩니다. 조건이 만족되는 경우에는 아래에 있는 다른 조건들은 검사하지 않고 넘어가기 때문에 실행 결과는 `"x is less than y"` 가 출력됩니다.
7 |
8 | ```python
9 | x = 1.0
10 | y = 10
11 |
12 | if x < y:
13 | print("x is less than y")
14 | elif x == y:
15 | print("x is equal to y")
16 | else:
17 | print("x is not less than y")
18 | ```
19 |
20 | 실행 결과
21 |
22 | ```
23 | x is less than y
24 | ```
25 |
26 | 만일 `x`의 값을 10으로 수정하고 다시 실행한다면 이번에는 "x is equal to y"가 출력될 것입니다.
27 |
28 | 러스트에서 동일한 코드를 작성해 보겠습니다. 여기서 몇 가지 다른 점이 있는데, 첫 번째로 `x`와 `y`를 조건문에서 바로 비교할 수 없습니다. 왜냐하면 두 변수의 타입이 다르기 때문에 둘 중 하나를 나머지의 타입으로 변환해 주어야 하기 때문입니다. 여기서는 `y`를 `f64`로 타입 변환을 해서 두 값을 비교하고 있습니다. 두 번째로는 파이썬의 `elif`가 `else if` 로 바뀐 것입니다. 세 번째는 러스트는 스코프를 중괄호로 구분하고 있기 때문에, if문의 각 분기에 해당하는 코드를 중괄호로 묶어주고 있습니다.
29 |
30 | ```rust
31 | fn main() {
32 | let x = 1.0;
33 | let y = 10;
34 |
35 | if x < (y as f64) {
36 | // casting
37 | println!("x is less than y");
38 | } else if x == (y as f64) {
39 | println!("x is equal to y");
40 | } else {
41 | println!("x is not less than y");
42 | }
43 | }
44 | ```
45 |
46 | 실행 결과
47 |
48 | ```
49 | x is less than y
50 | ```
51 |
52 | 실행 결과는 파이썬과 동일합니다.
53 |
54 |
55 |
56 | ### let if
57 |
58 | 러스트에서는 if문의 각 분기를 변수에 바로 할당하는 것이 가능합니다. 방금 보았던 if문을 아래와 같이 바꿀 수 있습니다. 각 분기에서 문자열을 프린트했었는데, 프린트가 사라졌고 각 분기의 마지막에 붙어있던 세미콜론도 사라졌습니다. 그 다음 if문 전체를 `result` 라는 변수에다가 할당하고 있습니다.
59 |
60 | ```rust
61 | fn main() {
62 | let x = 1.0;
63 | let y = 10;
64 |
65 | let result = if x < (y as f64) {
66 | "x is less than y"
67 | } else if x == (y as f64) {
68 | "x is equal to y"
69 | } else {
70 | "x is not less than y"
71 | };
72 |
73 | println!("{}", result);
74 | }
75 |
76 | ```
77 |
78 | 실행 결과
79 |
80 | ```
81 | x is less than y
82 | ```
83 |
84 | 실행 결과를 확인해보면 이전과 동일합니다. 그 이유는 if문의 각 분기에 해당하는 문자열들이 `result` 변수에 할당되기 때문인데, 위 코드에서는 첫 번째 조건인 `x < (y as f64)`가 만족되기 때문에, 결국 위 if문은 아래와 동일합니다.
85 |
86 | ```rust,ignore
87 | let result = "x is less than y";
88 | ```
89 |
90 | 주의해야 하는 점은 위처럼 `let if` 문을 쓰려면 각 분기에서 할당하는 값들이 모두 동일한 타입이어야 한다는 것입니다.
91 |
92 |
93 |
94 | 만일 `if`를 함수에서 바로 리턴한다면, 다음과 같은 코드도 가능합니다.
95 |
96 | ```rust
97 | fn check_password(password: i32) -> bool {
98 | if password == 1234 {
99 | true
100 | } else {
101 | false
102 | }
103 | }
104 |
105 | fn main() {
106 | let password = 1234;
107 | let result = check_password(password);
108 | println!("Result: {}", result);
109 | }
110 |
111 | ```
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/src/ch4-02.md:
--------------------------------------------------------------------------------
1 | ## for
2 |
3 | `for`을 사용하면 값들의 모음(collection)에서 각 값들을 순서대로 꺼낼 수 있습니다. 이처럼 값들을 순서대로 꺼내는 것을 순회(loop over)한다고 말합니다.
4 |
5 | 6부터 9까지의 정수를 순서대로 프린트하는 코드를 작성해 보겠습니다. 먼저 파이썬에서는 `range`를 사용하면 됩니다. 이때 `range`는 마지막 값은 생략되므로 `range(6, 10)` 과 같이 입력해야 합니다. 이때 `print` 함수에 `end=","`를 넣어 줄바꿈 대신 콤마가 들어가도록 해서 결과가 한줄로 출력되도록 했습니다.
6 |
7 | ```python
8 | for i in range(6, 10):
9 | print(i, end=",")
10 | ```
11 |
12 | 실행 결과
13 |
14 | ```
15 | 6,7,8,9,
16 | ```
17 |
18 | 러스트에서는 특정 범위의 정수를 `a..b` 와 같은 문법으로 간단하게 만들 수 있습니다. 마찬가지로 마지막 값은 생략되기 때문에 `6..10`과 같이 입력합니다. 결과를 한 줄로 프린트하기 위해서 `println!` 대신 `print!` 함수를 사용합니다.
19 |
20 | ```rust
21 | fn main() {
22 | for i in 6..10 {
23 | print!("{},", i);
24 | }
25 | }
26 |
27 | ```
28 |
29 | 실행 결과
30 |
31 | ```
32 | 6,7,8,9,
33 | ```
34 |
35 | 파이썬에서 `range`를 변수에 할당했다가 나중에 `for` 로 반복할 수 있습니다.
36 |
37 | ```python
38 | num_range = range(6, 10)
39 |
40 | for i in num_range:
41 | print(i, end=",")
42 |
43 | ```
44 |
45 | 마찬가지로 러스트에서도 정수 범위를 변수에 할당해 두었다가 나중에 반복할 수 있습니다.
46 |
47 | ```rust
48 | fn main() {
49 | let num_range = 6..10;
50 | for i in num_range {
51 | print!("{},", i);
52 | }
53 | }
54 |
55 | ```
56 |
57 |
58 |
59 | 러스트에서는 정수 범위를 만들 때, 마지막 숫자를 포함할 수 있습니다.
60 |
61 | ```rust
62 | fn main() {
63 | let num_range = 6..=10;
64 | for i in num_range {
65 | print!("{},", i);
66 | }
67 | }
68 |
69 | ```
70 |
71 | 실행 결과
72 |
73 | ```
74 | 6,7,8,9,10,
75 | ```
76 |
77 |
78 |
79 |
80 |
81 | > 러스트에서 `for` 를 사용해 반복할 수 있는 타입에 대해서는 뒤에서 자세히 다루겠습니다.
82 |
83 |
--------------------------------------------------------------------------------
/src/ch4-03.md:
--------------------------------------------------------------------------------
1 |
2 | ## while
3 |
4 | `while` 문은 조건이 만족되는 동안 코드가 계속 반복해서 실행됩니다. 만일 조건이 만족되지 않으면 코드가 실행되지 않고 반복이 종료됩니다. 파이썬에서 while문을 사용해 0부터 4까지의 정수를 프린트하는 코드를 작성해 보겠습니다.
5 |
6 | ```python
7 | x = 0
8 | while x < 5:
9 | print(x, end=",")
10 | x += 1
11 | ```
12 |
13 | 실행 결과
14 |
15 | ```
16 | 0,1,2,3,4,
17 | ```
18 |
19 | 동일한 코드를 러스트로 작성해 보겠습니다. 중괄호가 들어간 것 말고는 크게 다른 점이 없습니다. 참고로, 러스트는 파이썬과 마찬가지로 증감 연산자(`++, --`)가 없어 변수의 값을 직접 증가시키거나 감소시켜야 합니다.
20 |
21 | ```rust
22 | fn main() {
23 | let mut x = 0;
24 | while x < 5 {
25 | print!("{},", x);
26 | x += 1; // no incremental operator: x++
27 | }
28 | }
29 |
30 | ```
31 |
32 | 실행 결과
33 |
34 | ```
35 | 0,1,2,3,4,
36 | ```
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/ch4-04.md:
--------------------------------------------------------------------------------
1 |
2 | ## loop
3 |
4 | 이번에는 러스트의 특별하고 강력한 문법인 `loop` 를 알아보겠습니다. 먼저 `loop` 중 무한 루프의 경우는 파이썬에서 아래와 같이 구현 가능합니다. 중간에 `x`의 값이 5가 되면 `break`를 통해서 루프를 탈출할 수 있습니다.
5 |
6 | ```python
7 | x = 0
8 | while True:
9 | x += 1
10 | if x == 5:
11 | break
12 | print(x, end=",")
13 |
14 | ```
15 |
16 | 실행 결과
17 |
18 | ```
19 | 0,1,2,3,4,
20 | ```
21 |
22 | `loop`는 파이썬의 무한 루프와 동일한 역할을 합니다. 따라서 루프를 종료하는 `break` 에 해당하는 조건문이 있어야 루프를 종료하고 다음으로 진행할 수 있습니다.
23 |
24 | ```rust
25 | fn main() {
26 | let mut x = 0;
27 | loop {
28 | x += 1;
29 | if x == 5 {
30 | break;
31 | }
32 | print!("{},", x);
33 | }
34 | }
35 | ```
36 |
37 | 실행 결과
38 |
39 | ```
40 | 0,1,2,3,4,
41 | ```
42 |
43 | `loop`는 조건이 만족되면 루프를 탈출하는데, 이때 특정 값을 리턴할 수 있습니다. `break` 뒤에 리턴할 값을 넣어주면 됩니다. `x`가 5가 됐을 때 `x`를 리턴하도록 코드를 고치면 다음과 같습니다.
44 |
45 | ```rust
46 | fn main() {
47 | let mut x = 0;
48 | let y = loop {
49 | x += 1;
50 | if x == 5 {
51 | break x;
52 | }
53 | print!("{},", x);
54 | };
55 |
56 | println!("{}", y);
57 | }
58 |
59 | ```
60 |
61 | 실행 결과
62 |
63 | ```
64 | 1,2,3,4,5
65 | ```
66 |
67 | 루프 안에서 1부터 4까지가 출력되고, 그 뒤에 `y`의 값 5가 출력됩니다.
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/ch4-05.md:
--------------------------------------------------------------------------------
1 | ## match
2 |
3 | 다음은 다른 언어에서는 `switch ... case` 로 많이 사용되는 `match` 입니다. 파이썬에는 동일한 문법이 없으므로 `if ... else` 문으로 구현해 보겠습니다.
4 |
5 | > 파이썬에서는 최신 버전인 3.10 이후부터 `match ... case`가 추가되었습니다.
6 |
7 | 아래 코드는 `name` 변수에 값에 따라서 서로 다른 결과를 출력하는 코드입니다. 현재 `name` 변수의 값이 `"John"` 이므로 `"Hello, John!"`가 출력됩니다.
8 |
9 | ```python
10 | name = "John"
11 | if name == "John":
12 | print("Hello, John!")
13 | elif name == "Mary":
14 | print("Hello, Mary!")
15 | else:
16 | print("Hello, stranger!")
17 | ```
18 |
19 | 실행 결과
20 |
21 | ```
22 | Hello, John!
23 | ```
24 |
25 | 이렇게 특정 변수의 값에 따라서 다른 행동을 하도록 하는 것이 `match` 문의 핵심입니다. `match` 뒤에 값을 비교할 변수를 입력하고, 중괄호 안에서 콤마로 각 경우를 구분해서 표기합니다. `name` 변수가 `"John"`인 경우, `"Mary"`인 경우, 그리고 나머지 모든 경우의 세 가지 경우의 수가 있습니다. 나머지 경우를 나타내기 위해서 매칭할 값을 생략하는 `_`을 사용합니다. 여기서 `name` 변수의 값이 `"John"`이기 때문에 `"Hello, John!"`이 출력됩니다.
26 |
27 | ```rust
28 | fn main() {
29 | let name = "John";
30 | match name {
31 | "John" => println!("Hello, John!"),
32 | "Mary" => println!("Hello, Mary!"),
33 | _ => println!("Hello, stranger!"),
34 | }
35 | }
36 |
37 | ```
38 |
39 | 실행 결과
40 |
41 | ```
42 | Hello, John!
43 | ```
44 |
45 | `loop`와 마찬가지로 `match` 문도 값을 리턴할 수 있습니다. `let <변수명> = match ...`와 같이 선언하면 됩니다. 이때 컴파일러가 `match`문의 리턴값으로부터 변수 `greet`의 타입을 추론합니다. 또한, 각 조건마다 리턴하는 값들의 타입이 반드시 동일해야 합니다.
46 |
47 | ```rust
48 | fn main() {
49 | let name = "John";
50 | let greet = match name {
51 | "John" => "Hello, John!",
52 | "Mary" => "Hello, Mary!",
53 | _ => "Hello, stranger!",
54 | };
55 |
56 | println!("{}", greet);
57 | }
58 |
59 | ```
60 |
61 | 실행 결과
62 |
63 | ```
64 | Hello, John!
65 | ```
66 |
67 |
--------------------------------------------------------------------------------
/src/ch5-00.md:
--------------------------------------------------------------------------------
1 | # CH5. 소유권
--------------------------------------------------------------------------------
/src/ch5-01.md:
--------------------------------------------------------------------------------
1 | ## 메모리 관리
2 |
3 | 모든 프로그램은 컴퓨터의 메모리(Memory)라고 하는 자원을 사용합니다. 이 메모리에 프로그램에서 사용하는 데이터를 저장하기 때문입니다. 우리가 코드상에서 어떤 변수에 값을 할당하면 메모리에 그 값이 저장됩니다. 메모리라는 자원은 한정되어 있기 때문에 프로그래밍 언어들은 각자의 방식으로 이 메모리를 효율적으로 관리하고자 노력합니다. 파이썬이나 고와 같은 언어는 가비지 콜렉터를 이용해 언어 차원에서 자동으로 메모리를 관리하고, C/C++같은 언어들은 개발자가 직접 메모리를 관리합니다.
4 |
5 | 파이썬은 모든 객체의 데이터를 힙 영역에 저장합니다. 그리고 메모리를 가비지 콜렉션을 이용해 관리합니다. 즉, 런타임에 사용되지 않는 객체가 있으면 주기적으로 객체를 삭제하거나 메모리 사용량이 너무 높은 경우 가비지 콜렉션을 수행합니다. 가비지 콜렉션이 수행되는 동안에는 다른 파이썬 코드가 실행될 수 없기 때문에 파이썬의 코드 실행 속도가 느려지는 원인이 됩니다. 또한 어떤 객체가 언제 메모리에서 할당 해제되는지를 개발자가 명시적으로 알 수 있는 방법이 없고 가비지 콜렉터가 이를 전담하기 때문에 프로그램이 불필요하게 많은 메모리를 사용할 가능성도 있습니다.
6 |
7 | 반면 러스트는 소유권(Ownership)이라는 개념을 통해 메모리를 관리합니다. 소유권 모델 덕분에 러스트 프로그램은 메모리 안전성과 스레드 안전성이 보장됩니다. 메모리 안전성이란, 하나의 값에 대해서 단 하나의 코드만 접근하기 때문에 예상치 못하게 값이 변경되는 일이 없다는 의미입니다. C/C++같은 언어에서는 잘못된 포인터 사용이나 잘못된 메모리 접근과 같은 이유로 버그가 발생하거나 메모리 누수가 일어나기도 하지만 러스트에서는 이를 걱정할 필요가 없습니다. 다음으로 스레드 안전성이란, 여러 개의 스레드에서 하나의 값에 접근하고자 할 때 발생할 수 있는 경합 조건(Race condition)이나 데드락(Deadlock)이 발생하지 않는다는 의미입니다. 이 두 가지 문제가 멀티스레딩 프로그램을 만들 때 가장 어렵고 복잡한 문제이지만 러스트에서는 이를 컴파일 타임에 탐지할 수 있기 때문에 안정성이 보장됩니다.
8 |
9 |
10 |
11 | ## 스택과 힙
12 |
13 | 소유권에 대해서 알아보기 전에, 프로그램에서 메모리를 저장하는 영역인 스택과 힙에 대해서 살펴보겠습니다. 스택 영역은 함수가 실행될 때 사용하는 메모리 공간으로, 함수에서 사용하는 지역 변수가 스택에 저장됩니다. 일반적으로 스택에서 사용될 메모리 공간이 미리 정해지기 때문에 매우 빠르게 값을 저장하고 접근할 수 있습니다. 만일 함수 실행이 종료되면 스택 영역에서 사용된 모든 지역 변수는 메모리에서 삭제됩니다. 힙 영역은 동적으로 할당되는 메모리를 위해 존재하는 공간으로, 개발자가 명시적으로 특정 크기의 메모리 공간을 사용하겠다고 선언해야 합니다. 만일 해당 메모리 공간이 더 이상 필요하지 않은 경우에는 해당 메모리를 할당 해제해주어야 합니다. 왜냐하면 이미 점유된 메모리 공간은 다른 프로그램이나 스레드에서 사용할 수 없기 때문입니다.
14 |
15 | 파이썬은 스택을 사용하지 않고 모든 객체를 힙 영역에 저장합니다. 이렇게 저장된 객체들은 파이썬에서 가비지 콜렉션을 통해 메모리를 관리하기 때문에 파이썬을 사용할 때는 메모리 관리에 신경쓰지 않아도 됩니다. 위에서 힙 영역에 대해서 설명할 때 언급한 개발자가 할당하고 할당 해제하는 메모리를 파이썬의 가비지 콜렉터가 대신해주는 것입니다.
16 |
17 | 반면 러스트는 스택 영역과 힙 영역 모두를 사용합니다. 러스트는 기본적으로 아래와 같이 함수에서 사용하는 모든 값을 제한된 크기의 스택 영역에 저장합니다. 따라서 함수 호출이 종료되면 지역 변수 `foo` 와 `var`는 모두 삭제됩니다.
18 |
19 | ```rust,ignore
20 | fn foo() {
21 | let foo = "foo";
22 | let var = 5;
23 | }
24 | ```
25 |
26 | 힙 영역은 함수에서 명시적으로 선언하는 경우에만 사용되는데, 힙 영역에 저장하는 값은 전역적으로(globally) 접근이 가능합니다. 나중에 배울 `Box` 타입을 사용해 선언하면 됩니다.
27 |
28 | ```rust
29 | fn main() {
30 | let num = Box::new(1);
31 | }
32 | ```
33 |
34 | 정리하자면, 함수에서 사용하는 지역 변수의 값들은 모두 스택 영역에 저장되고, 전역적으로 사용되는 값들은 힙 영역에 저장됩니다. 참고로 뒤에서 배울 멀티스레딩에서 여러 스레드가 접근하는 변수의 값은 힙 영역에 저장되게 됩니다.
--------------------------------------------------------------------------------
/src/ch5-02.md:
--------------------------------------------------------------------------------
1 | ## 소유권 규칙 자세히 알아보기
2 |
3 | 소유권을 요약하자면 다음 세 가지 규칙으로 정리할 수 있습니다.
4 |
5 | - 모든 "값"들은 해당 값을 "소유"하고 있는 소유자(Owner)가 존재합니다.
6 | - 한 번에 하나의 소유자만 존재할 수 있습니다. 하나의 값에 두 개의 소유자가 동시에 존재할 수 없습니다.
7 | - 소유자가 현재 코드의 스코프에서 벗어나면, 값은 메모리에서 할당 해제됩니다.
8 |
9 | 안타깝게도 소유권 모델은 파이썬 뿐만 아니라 다른 프로그래밍 언어에는 없는 러스트만의 고유한 특징이기 때문에, 여기서 파이썬과 비교하며 소유권을 설명하기는 조금 어렵습니다.
10 |
11 |
12 |
13 | ### 값에 대한 소유권
14 |
15 | 프로그래밍에서 메모리 관리가 필요한 이유는 더이상 사용되지 않는 "값"을 처리하지 않으면 스택과 힙 메모리 영역이 가득 차기 때문입니다. 러스트에서는 어떤 값이 더이상 사용되지 않는지를 소유권을 사용해 판단합니다. 모든 값에 소유자를 지정하고, 이 값을 소유하고 있는 소유자가 없게 되면 즉시 값이 메모리에서 할당 해제되는 원리입니다. 아래 예제를 보겠습니다.
16 |
17 | ```rust
18 | fn main() {
19 | let x = 1;
20 | // x is dropped
21 | }
22 |
23 | ```
24 |
25 | 이 예제에서, `x` 라는 변수에 담긴 `1` 이라는 값은 `main()` 함수를 벗어나게 되면 더 이상 사용되지 않습니다. 따라서 `x` 는 즉시 메모리에서 지워지게 됩니다. 마찬가지로 같은 함수 내에서라도 스코프를 벗어나면 즉시 값은 사라집니다.
26 |
27 | ```rust,ignore
28 | fn main() {
29 | let x = 1;
30 | {
31 | let y = x;
32 | println!("{} {}", x, y);
33 | // y is dropped
34 | }
35 | println!("{} {}", x, y); // This line won't compile
36 | // x is dropped
37 | }
38 |
39 | ```
40 |
41 | 이번엔 코드 중간에 있는 `{}` 에 의해 스코프가 추가되었고, 이 안에서 `y`가 선언되었습니다. 이 스코프를 벗어나면 `y`는 더이상 사용되지 않으므로 즉시 할당 해제됩니다. 마찬가지로 함수에 파라미터로 변수를 전달하는 경우에도 같은 원리가 적용됩니다. 여기서 `String::from("Hello")`는 러스트에서 문자열을 선언하는 방법으로, 문자열에 대한 자세한 내용은 다음 챕터에서 설명하겠습니다.
42 |
43 | ```rust,ignore
44 | fn dummy(x: String) {
45 | println!("{}", x);
46 | // x is dropped
47 | }
48 |
49 | fn main() {
50 | let x = String::from("Hello");
51 | dummy(x);
52 | println!("{}", x); // This line won't compile
53 | }
54 |
55 | ```
56 |
57 | 함수 `dummy` 에 문자열이 전달된 다음, 함수를 벗어나면 그 즉시 `x` 는 할당 해제됩니다. 그런데 이미 할당 해제된 `x`를 9번 라인에서 참조하고 있기 때문에 오류가 발생합니다. 그러면 모든 값은 다른 함수에 전달하면 영원히 사용하지 못하는 걸까요? 이런 경우 사용할 수 있는 두 가지 방법이 있습니다.
58 |
59 |
60 |
61 | ### 소유권 돌려주기
62 |
63 | 먼저 함수에서 해당 변수의 소유권을 되돌려줄 수 있는 방법이 있습니다. 아래 예제를 보겠습니다.
64 |
65 | ```rust
66 | fn dummy(x: String) -> String {
67 | println!("{}", x);
68 | x
69 | }
70 |
71 | fn main() {
72 | let x = String::from("Hello");
73 | let x = dummy(x);
74 | println!("{}", x);
75 | }
76 |
77 | ```
78 |
79 | 실행 결과
80 |
81 | ```
82 | Hello
83 | Hello
84 | ```
85 |
86 | 함수 `dummy`에서 입력 변수 `x`는 함수 내부에서 사용된 다음 리턴됩니다. 그 다음 함수의 리턴값을 재선언한 변수 `x`에 할당함으로써 소유권이 `x`로 되돌아옵니다. 좀더 이해하기 쉽도록 변수명을 아래와 같이 바꿔보겠습니다. 결론적으로, `"Hello"`라는 값을 소유하고 있는 변수만 `x` → `y` → `z` 순서로 바뀌고, 값은 그대로 있게 됩니다. 하지만 이 방법은 매번 함수의 리턴값을 변수로 재선언해주어야 하기 때문에 코드의 가독성이 떨어지고, 값이 어느 변수로 이동하는지를 알기 어려운 단점이 있습니다.
87 |
88 | ```rust
89 | fn dummy(y: String) -> String {
90 | println!("{}", y);
91 | y
92 | }
93 |
94 | fn main() {
95 | let x = String::from("Hello");
96 | let z = dummy(x);
97 | println!("{}", z);
98 | }
99 |
100 | ```
101 |
102 | 실행 결과
103 |
104 | ```
105 | Hello
106 | Hello
107 | ```
108 |
109 |
110 |
111 | ### 레퍼런스와 소유권 빌리기
112 |
113 | 러스트에는 값의 소유권을 잠시 빌려줄 수 있는 개념인 대여(borrow)가 있습니다. 변수 앞에 `&` 키워드를 사용하면 되는데, 해당 변수의 레퍼런스(reference)를 선언한다는 의미입니다. 레퍼런스란 소유권을 가져가지 않고 해당 값을 참조할 수 있는 방법입니다. 아래 예제를 보겠습니다.
114 |
115 | ```rust
116 | fn main() {
117 | let x = String::from("Hello");
118 | let y = &x;
119 |
120 | println!("{} {}", x, y);
121 | }
122 |
123 | ```
124 |
125 | 실행 결과
126 |
127 | ```
128 | Hello Hello
129 | ```
130 |
131 | `let y = &x;`와 같이 선언하더라도 문자열 "Hello"의 값의 소유권은 여전히 `x`에 있고, `y`는 단순히 값을 참조만 합니다. 따라서 마지막에서 변수 `x`와 `y`를 모두 프린트해도 에러가 발생하지 않습니다.
132 |
133 | 아래 예제에서 `dummy` 함수의 파라미터 타입은 `&String`으로, 문자열의 레퍼런스 타입을 의미합니다. `main`함수에서 `dummy`를 실행할 때, 변수 `x`의 레퍼런스인 `&x` 가 전달되었습니다. 이건 소유권을 잠시 함수 내부의 `y` 파라미터에 빌려준다는 의미입니다. 소유권을 대여한 변수가 `dummy`함수의 스코프를 벗어나면, 그 즉시 소유권은 원래 소유자인 `x` 에게 되돌아갑니다. 따라서 `dummy` 함수에서 `x`에 저장된 문자열 값을 사용하더라도 이후에 `x`를 통해 문자열을 계속 사용할 수 있게 됩니다. 그래서 마지막에 `x`를 프린트해도 에러가 발생하지 않고 잘 컴파일됩니다.
134 |
135 | ```rust
136 | fn dummy(y: &String) {
137 | println!("{}", y);
138 | // ownership returns to `x`
139 | }
140 |
141 | fn main() {
142 | let x = String::from("Hello");
143 | dummy(&x);
144 | println!("{}", x);
145 | }
146 |
147 | ```
148 |
149 | 실행 결과
150 |
151 | ```
152 | Hello
153 | ```
154 |
155 |
156 |
157 | ### 가변 레퍼런스
158 |
159 | 어떤 변수의 레퍼런스를 만들 때, 원래 변수가 불변이라면 레퍼런스를 사용해 원래 변수의 값을 바꿀 수 없습니다. 아래 예제에서는 변수 `x`를 함수 `dummy`에 레퍼런스로 전달합니다. 그리고 `push_str` 함수를 사용해 " world!"라는 문자열을 `x`의 뒤에 추가하고 있습니다. 그런데 코드를 실행하면 에러가 발생합니다.
160 |
161 | ```rust,ignore
162 | fn dummy(y: &String) {
163 | y.push_str(" world!");
164 | println!("{}", y);
165 | // ownership returns to `x`
166 | }
167 |
168 | fn main() {
169 | let x = String::from("Hello");
170 | dummy(&x);
171 | println!("{}", x);
172 | }
173 |
174 | ```
175 |
176 | 실행 결과
177 |
178 | ```
179 | Compiling rust_part v0.1.0 (/Users/code/temp/rust_part)
180 | error[E0596]: cannot borrow `*y` as mutable, as it is behind a `&` reference
181 | --> src/main.rs:2:5
182 | |
183 | 1 | fn dummy(y: &String) {
184 | | ------- help: consider changing this to be a mutable reference: `&mut String`
185 | 2 | y.push_str(" world!");
186 | | ^^^^^^^^^^^^^^^^^^^^^ `y` is a `&` reference, so the data it refers to cannot be borrowed as mutable
187 | ```
188 |
189 | 에러 내용을 읽어보면 `y`에서 소유권을 빌려왔지만, 가변 레퍼런스가 아니기 때문에 값을 수정할 수 없다고 합니다. 컴파일러의 조언에 따라서 `y`를 가변 레퍼런스로 수정해 보겠습니다. 여기서 총 3군데를 수정했습니다.
190 |
191 | 1. `dummy`함수의 파라미터 `y` 의 타입이 `&mut String` 으로 변경
192 | 2. 변수 `x`를 가변 변수로 선언
193 | 3. `dummy`함수에 `x`를 전달할 때 가변 레퍼런스 `&mut x`로 전달
194 |
195 | ```rust
196 | fn dummy(y: &mut String) {
197 | y.push_str(" world!");
198 | println!("{}", y);
199 | // ownership returns to `x`
200 | }
201 |
202 | fn main() {
203 | let mut x = String::from("Hello");
204 | dummy(&mut x);
205 | println!("{}", x);
206 | }
207 |
208 | ```
209 |
210 | 실행 결과
211 |
212 | ```
213 | Hello world!
214 | Hello world!
215 | ```
216 |
217 | 가변 레퍼런스를 사용할 때 주의해야 하는 점은 소유권 규칙의 두 번째 규칙인 "한 번에 하나의 소유자만 존재할 수 있다" 입니다. 예를 들어 하나의 값에 대해서 두 개의 가변 레퍼런스를 만들어 보겠습니다. 변수 `y`와 `z`는 모두 변수 `x`의 가변 레퍼런스입니다.
218 |
219 | ```rust,ignore
220 | fn main() {
221 | let mut x = String::from("Hello");
222 | let y = &mut x;
223 | let z = &mut x;
224 |
225 | println!("{} {}", y, z);
226 | }
227 |
228 | ```
229 |
230 | 실행 결과
231 |
232 | ```
233 | Compiling rust_part v0.1.0 (/Users/code/temp/rust_part)
234 | error[E0499]: cannot borrow `x` as mutable more than once at a time
235 | --> src/main.rs:4:13
236 | |
237 | 3 | let y = &mut x;
238 | | ------ first mutable borrow occurs here
239 | 4 | let z = &mut x;
240 | | ^^^^^^ second mutable borrow occurs here
241 | 5 |
242 | 6 | println!("{} {}", y, z);
243 | | - first borrow later used here
244 | ```
245 |
246 | 실행 시 에러가 발생하는데, 변수 `x`의 소유권을 한 번 이상 대여할 수 없다고 합니다. 만일 하나의 소유권을 여러 개의 변수가 빌릴 수 있다면 큰 문제가 발생할 가능성이 있습니다. 하나의 메모리를 여러 곳에서 접근할 수 있기 때문에 버그가 발생할 수 있습니다. 예를 들어 어떤 가변 레퍼런스에서 값을 변경했는데, 다른 곳에서는 변경 전의 값을 필요로 한다면 예상치 못한 결과가 나올 수 있습니다. 따라서 러스트에서는 하나의 값에 대한 여러 개의 가변 레퍼런스를 허용하지 않습니다. 하지만 단순히 레퍼런스를 여러 개 만드는 것은 문제가 없습니다.
247 |
248 | ```rust
249 | fn main() {
250 | let x = String::from("Hello");
251 | let y = &x;
252 | let z = &x;
253 |
254 | println!("{} {}", y, z);
255 | }
256 |
257 | ```
258 |
259 | 실행 결과
260 |
261 | ```
262 | Hello Hello
263 | ```
264 |
265 | 러스트의 소유권 개념은 처음 러스트를 배우는 사람의 입장에서 정말 어렵고 복잡하게 느껴집니다. 그렇지만 컴파일러가 소유권 규칙이 위반되는 경우, 에러를 발생시키고 그에 대한 해결책을 제시해주기 때문에 생각보다 금방 익숙해질 수 있습니다.
266 |
--------------------------------------------------------------------------------
/src/ch5-03.md:
--------------------------------------------------------------------------------
1 |
2 | ## 클로저와 소유권
3 |
4 | 앞에서 클로저를 단순히 익명 함수라고만 설명하고 넘어갔습니다. 하지만 이제 스코프와 소유권을 배웠기 때문에, 클로저에 대해 좀더 자세한 얘기를 해보려고 합니다. 클로저의 가장 큰 특징은 익명 함수를 만들고 이를 변수에 저장하거나 다른 함수의 인자로 전달할 수 있다는 것입니다.
5 |
6 |
7 |
8 | ### 클로저의 환경 캡처
9 |
10 | 클로저는 클로저가 선언된 스코프에 있는 지역 변수를 자신의 함수 내부에서 사용할 수 있는데, 이를 환경 캡처(Environment capture)라고 부릅니다. 클로저가 변수를 자신의 스코프 내부로 가져가는 방법은 총 3가지가 존재합니다.
11 |
12 | - 불변 소유권 대여
13 | - 가변 소유권 대여
14 | - 소유권 가져가기
15 |
16 | 먼저 아래 예제를 보면, 클로저 `func` 는 같은 스코프에 선언된 변수 `multiplier`를 자신의 함수 내부에서 사용할 수 있습니다. 이때 `multiplier`의 값은 클로저에서 사용된 이후에도 스코프 내부에서 사용이 가능합니다. 따라서 클로저는 `multiplier`를 불변 소유권 대여 방법으로 자신의 내부에서 사용한 것입니다.
17 |
18 | ```rust
19 | fn main() {
20 | let multiplier = 5;
21 |
22 | let func = |x: i32| -> i32 { x * multiplier };
23 |
24 | for i in 1..=5 {
25 | println!("{}", func(i));
26 | }
27 |
28 | println!("{}", multiplier); // 👍
29 | }
30 |
31 | ```
32 |
33 | 실행 결과
34 |
35 | ```
36 | 5
37 | 10
38 | 15
39 | 20
40 | 25
41 | 5
42 | ```
43 |
44 | 아래 예제는 `multiplier`를 가변 변수로 선언하고, 클로저 내부에서 `multiplier`의 값을 변경시키고 있습니다. 방금 살펴본 예제와 마찬가지로 클로저 호출이 끝난 다음에도 여전히 `multiplier`에 접근이 가능합니다.
45 |
46 | ```rust
47 | fn main() {
48 | let mut multiplier = 5;
49 |
50 | let mut func = |x: i32| -> i32 {
51 | multiplier += 1;
52 | x * multiplier
53 | };
54 |
55 | for i in 1..=5 {
56 | println!("{}", func(i));
57 | }
58 |
59 | println!("{}", multiplier); // 👍
60 | }
61 |
62 | ```
63 |
64 | 실행 결과
65 |
66 | ```
67 | 6
68 | 14
69 | 24
70 | 36
71 | 50
72 | 10
73 | ```
74 |
75 |
76 |
77 | ### `move` 를 사용한 소유권 이동
78 |
79 | 클로저가 환경으로부터 사용하는 값의 소유권을 가져갈 수도 있습니다. 클로저가 같은 스코프에 선언된 지역 변수의 소유권을 가져가도록 하려면 클로저의 파라미터를 선언하는 코드 앞에 `move` 키워드를 사용하면 됩니다.
80 |
81 | ```rust,ignore
82 | move | param, ... | body;
83 | ```
84 |
85 | 다음 예제에서는 클로저를 리턴하는 함수 `factory`를 만들었습니다. 여기서 리턴되는 클로저는 `factory` 함수의 파라미터인 `factor`를 캡처해 사용합니다. 그 다음 `factory`를 `main` 함수에서 사용해 만든 클로저를 호출하면 `multiplier` 변수를 모든 클로저에서 공유할 수 있게 됩니다.
86 |
87 | ```rust,ignore
88 | fn factory(factor: i32) -> impl Fn(i32) -> i32 {
89 | |x| x * factor
90 | }
91 |
92 | fn main() {
93 | let multiplier = 5;
94 | let mult = factory(multiplier);
95 | for i in 1..=3 {
96 | println!("{}", mult(i));
97 | }
98 | }
99 |
100 | ```
101 |
102 | 하지만 위 코드를 컴파일하면, 아래와 같은 에러가 발생합니다.
103 |
104 | ```text
105 | error[E0597]: `factor` does not live long enough
106 | --> src/main.rs:2:13
107 | |
108 | 2 | |x| x * factor
109 | | --- ^^^^^^ borrowed value does not live long enough
110 | | |
111 | | value captured here
112 | 3 | }
113 | | -
114 | | |
115 | | `factor` dropped here while still borrowed
116 | | borrow later used here
117 |
118 | For more information about this error, try `rustc --explain E0597`.
119 | error: could not compile `notebook` due to previous error
120 | ```
121 |
122 | `factor` 변수가 클로저 안에 캡처될 때, 소유권이 `factory`로부터 클로저로 대여됩니다. 하지만 `factory`함수가 종료되면 `factor` 변수의 값이 삭제되기 때문에 리턴된 클로저에서 더 이상 `factor` 를 사용할 수 없는 문제가 발생합니다. 이를 방지하기 위해서는 클로저 안으로 `factor`의 소유권을 이동시키면 됩니다. 이때 사용되는 키워드가 `move`입니다. `move`는 캡처된 변수의 소유권을 클로저 안으로 이동시킵니다.
123 |
124 | ```rust
125 | fn factory(factor: i32) -> impl Fn(i32) -> i32 {
126 | move |x| x * factor
127 | }
128 |
129 | fn main() {
130 | let multiplier = 5;
131 | let mult = factory(multiplier);
132 | for i in 1..=3 {
133 | println!("{}", mult(i));
134 | }
135 | }
136 |
137 | ```
138 |
139 | 실행 결과
140 |
141 | ```
142 | 5
143 | 10
144 | 15
145 | ```
146 |
147 | > 클로저에서 `move` 를 가장 많이 사용하는 경우는 멀티스레드 혹은 비동기 프로그래밍을 작성할 때입니다.
148 |
149 |
--------------------------------------------------------------------------------
/src/ch6-00.md:
--------------------------------------------------------------------------------
1 | # CH6. 데이터 구조와 이터레이터
2 |
3 | 데이터 구조(Data structure)란, 컴퓨터에서 어떠한 값의 모음을 효율적으로 나타내기 위한 방법을 의미합니다. 예를 들어, 정수 10개를 다음과 같이 변수 10개에 저장해 보겠습니다.
4 |
5 | ```rust,ignore
6 | let num1 = 1;
7 | let num2 = 2;
8 | let num3 = 3;
9 |
10 | ...생략...
11 |
12 | let num10 = 10;
13 | ```
14 |
15 | 이렇게 변수를 여러 개를 만들면 각 변수들이 독립적으로 존재하기 때문에 의미적으로 연결해서 생각하기가 어렵고, 다른 함수나 변수에 값들을 전달하려면 모든 변수를 전달해야 하기 때문에 번거롭습니다. 따라서 여러 개의 값을 하나로 묶어서 관리하면 편리합니다.
16 |
17 | ```rust,ignore
18 | let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
19 | ```
20 |
21 | 이번 챕터에서는 다양한 용도로 값들을 묶어서 표현할 수 있는 데이터 구조와, 이 데이터 구조에서 값을 하나씩 꺼내 사용하는 이터레이터(iterator)에 대해서 알아보겠습니다.
22 |
23 |
24 |
25 | ## 한 눈에 보기
26 |
27 | 러스트에서 가장 자주 쓰이는 데이터 구조들과 각각에 매칭되는 파이썬의 데이터 구조입니다. 다만 array의 경우는 파이썬 내장 타입 중에는 비슷한 데이터 구조가 없어서 파이썬의 계산과학 라이브러리인 numpy의 array 타입과 비교했습니다.
28 |
29 | | 파이썬 | 러스트 |
30 | | -------- | ------------------------- |
31 | | list | Vec |
32 | | np.array | array |
33 | | tuple | () |
34 | | Enum | Enum |
35 | | dict | std::collections::HashMap |
36 | | str | String, &str |
37 |
38 | 참고로 이 외에도 다양한 데이터 구조가 러스트에 포함되어 있습니다.
39 |
40 | - Sequences: [`VecDeque`](https://doc.rust-lang.org/std/collections/struct.VecDeque.html), [`LinkedList`](https://doc.rust-lang.org/std/collections/struct.LinkedList.html)
41 | - Maps: [`BTreeMap`](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html)
42 | - Sets: [`HashSet`](https://doc.rust-lang.org/std/collections/hash_set/struct.HashSet.html), [`BTreeSet`](https://doc.rust-lang.org/std/collections/struct.BTreeSet.html)
43 | - Misc: [`BinaryHeap`](https://doc.rust-lang.org/std/collections/struct.BinaryHeap.html)
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/ch6-03.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## 이터레이터
4 |
5 | ### 이터레이터란?
6 |
7 | 이터레이터(iterator)는 반복 가능한 시퀀스(sequence)를 입력으로 받아 각 원소에 특정 작업을 수행할 수 있도록 하는 기능입니다. 앞에서 배운 벡터를 이용해 값을 순서대로 출력하는 예제를 만들어 보겠습니다.
8 |
9 | ```rust,ignore
10 | fn main() {
11 | let names = vec!["james", "cameron", "indo"];
12 | for name in names {
13 | println!("{}", name);
14 | }
15 | println!("{:?}", names);
16 | }
17 |
18 | ```
19 |
20 | 실행 결과
21 |
22 | ```
23 | error[E0382]: borrow of moved value: `names`
24 | --> src/main.rs:6:22
25 | |
26 | 2 | let names = vec!["james", "cameron", "indo"];
27 | | ----- move occurs because `names` has type `Vec<&str>`, which does not implement the `Copy` trait
28 | 3 | for name in names {
29 | | -----
30 | | |
31 | | `names` moved due to this implicit call to `.into_iter()`
32 | | help: consider borrowing to avoid moving into the for loop: `&names`
33 | ...
34 | 6 | println!("{:?}", names);
35 | | ^^^^^ value borrowed here after move
36 | |
37 | ```
38 |
39 | 컴파일하면 에러가 발생하는데, `for name in names` 에서 `names`가 암묵적으로 `.into_iter()` 메소드를 호출했다고 나옵니다. 여기서 `into_iter()`가 바로 이터레이터인데, 벡터 원소의 값을 `for` 루프 안으로 가져와 반복하는 역할을 수행합니다. 이때 값이 가져와지기 때문에 원소의 소유권도 함께 이동됩니다. 이미 이동된 소유권을 `println!("{:?}", names);` 에서 참조하기 때문에 에러가 발생합니다.
40 |
41 | 이를 해결하기 위해서는 명시적으로 `iter()` 메소드를 호출해 원소를 `for` 루프 안으로 전달해주어야 합니다.
42 |
43 | ```rust
44 | fn main() {
45 | let names = vec!["james", "cameron", "indo"];
46 | for name in names.iter() {
47 | println!("{}", name);
48 | }
49 | println!("{:?}", names);
50 | }
51 |
52 | ```
53 |
54 | 실행 결과
55 |
56 | ```
57 | james
58 | cameron
59 | indo
60 | ["james", "cameron", "indo"]
61 | ```
62 |
63 | `iter()` 메소드는 선언 즉시 원소를 내놓는 것이 아니라, 값이 필요해지면 그때 원소를 리턴합니다. 따라서 다음과 같은 코드가 가능합니다.
64 |
65 | ```rust
66 | fn main() {
67 | let names = vec!["james", "cameron", "indo"];
68 | let names_iter = names.iter();
69 | for name in names_iter {
70 | println!("{}", name);
71 | }
72 | println!("{:?}", names);
73 | }
74 |
75 | ```
76 |
77 | 실행 결과
78 |
79 | ```
80 | james
81 | cameron
82 | indo
83 | ["james", "cameron", "indo"]
84 | ```
85 |
86 |
87 |
88 | ### 이터레이터를 소비하는 메소드들
89 |
90 | 이번 단원에서는 이터레이터에 속한 메소드들을 이용해 원소에 여러 작업을 수행해 보겠습니다. 파이썬에서는 합계, 최대값, 최소값을 구하는 함수인 `sum`, `max`, `min`을 리스트에 직접 사용합니다.
91 |
92 | ```python
93 | nums = [1, 2, 3]
94 |
95 | sum = sum(nums)
96 | max = max(nums)
97 | min = min(nums)
98 | print(f"sum: {sum}, max: {max}, min: {min}")
99 | ```
100 |
101 | 실행 결과
102 |
103 | ```
104 | sum: 6, max: 3, min: 1
105 | ```
106 |
107 | 러스트에서는 이터레이터에서 `sum`, `max`, `min` 메소드를 호출합니다.
108 |
109 | ```rust
110 | fn main() {
111 | let num = vec![1, 2, 3];
112 |
113 | let sum: i32 = num.iter().sum();
114 | let max = num.iter().max().unwrap();
115 | let min = num.iter().min().unwrap();
116 | println!("sum: {}, max: {}, min: {}", sum, max, min);
117 | }
118 |
119 | ```
120 |
121 | 실행 결과
122 |
123 | ```
124 | sum: 6, max: 3, min: 1
125 | ```
126 |
127 |
128 |
129 | ### 새로운 이터레이터를 만드는 메소드들
130 |
131 | 이터레이터 메소드 중에는 새로운 이터레이터를 만드는 메소드들이 있습니다. 대표적으로 인덱스와 원소를 함께 반복하는 `enumerate` 와 두 시퀀스의 원소를 순서대로 함께 묶어 반복하는 `zip` 입니다.
132 |
133 | 먼저 파이썬 코드는 다음과 같습니다.
134 |
135 | ```python
136 | nums1 = [1, 2, 3]
137 | nums2 = [4, 5, 6]
138 |
139 | enumer = list(enumerate(nums1))
140 | print(enum)
141 | zip = list(zip(nums1, nums2))
142 | print(zip)
143 |
144 | ```
145 |
146 | 실행 결과
147 |
148 | ```
149 | [(0, 1), (1, 2), (2, 3)]
150 | [(1, 4), (2, 5), (3, 6)]
151 | ```
152 |
153 | 마찬가지로 러스트에서도 원소와 인덱스를 동시에 반복하거나 두 시퀀스의 원소를 동시에 반복할 수 있습니다.
154 |
155 | ```rust
156 | fn main() {
157 | let nums1 = vec![1, 2, 3];
158 | let nums2 = vec![4, 5, 6];
159 |
160 | let enumer: Vec<(usize, &i32)> = nums1.iter().enumerate().collect();
161 | println!("{:?}", enumer);
162 |
163 | let zip: Vec<(&i32, &i32)> = nums1.iter().zip(nums2.iter()).collect();
164 | println!("{:?}", zip);
165 | }
166 |
167 | ```
168 |
169 | 실행 결과
170 |
171 | ```
172 | [(0, 1), (1, 2), (2, 3)]
173 | [(1, 4), (2, 5), (3, 6)]
174 | ```
175 |
176 |
177 |
178 | 이터레이터를 만들어내는 메소드 중에서 가장 중요하게 봐야 하는 두 가지가 있는데 바로 `map` 과 `filter` 입니다. `map` 은 주어진 함수를 각 원소에 적용합니다. `filter` 는 주어진 시퀀스에서 기준에 맞는 결과만 남기는 방법입니다. 아래 두 예제에서는 시퀀스의 원소에 1을 더한 새로운 시퀀스를 만들거나, 원소 중 홀수인 값만 남기도록 했습니다.
179 |
180 | ```python
181 | nums = [1, 2, 3]
182 |
183 | f = lambda x: x + 1
184 |
185 | print(list(map(f, nums)))
186 | print(list(filter(lambda x: x % 2 == 1, nums)))
187 |
188 | ```
189 |
190 | 실행 결과
191 |
192 | ```
193 | [2, 3, 4]
194 | [1, 3]
195 | ```
196 |
197 | 러스트 코드에서는 클로저를 이용해 동일한 내용을 구현했습니다. 이때 `filter` 의 경우, 기존의 원소의 값을 이동해서 새로운 벡터를 만들기 때문에 `into_iter` 메소드로 이터레이터를 만들었습니다.
198 |
199 | ```rust
200 | fn main() {
201 | let nums: Vec = vec![1, 2, 3];
202 |
203 | let f = |x: &i32| x + 1;
204 |
205 | let maps: Vec = nums.iter().map(f).collect();
206 | println!("{:?}", maps);
207 |
208 | let filters: Vec = nums.into_iter().filter(|x| x % 2 == 1).collect();
209 | println!("{:?}", filters);
210 | }
211 |
212 | ```
213 |
214 | 실행 결과
215 |
216 | ```
217 | [2, 3, 4]
218 | [1, 3]
219 | ```
220 |
221 | 원본 벡터를 필터 이후에도 사용하기 위해서는 두 가지 방법이 있습니다.
222 | 원본 벡터를 복사(clone)하는 방법
223 |
224 | ```rust
225 | fn main() {
226 | let nums: Vec = vec![1, 2, 3];
227 |
228 | let f = |x: &i32| x + 1;
229 |
230 | let maps: Vec = nums.iter().map(f).collect();
231 | println!("{:?}", maps);
232 |
233 | let filters: Vec = nums.clone().into_iter().filter(|x| x % 2 == 1).collect();
234 | println!("{:?}", filters);
235 |
236 | println!("{:?}", nums);
237 | }
238 |
239 | ```
240 |
241 | 이터레이터를 복사하는 방법
242 |
243 | ```rust
244 | fn main() {
245 | let nums: Vec = vec![1, 2, 3];
246 |
247 | let f = |x: &i32| x + 1;
248 |
249 | let maps: Vec = nums.iter().map(f).collect();
250 | println!("{:?}", maps);
251 |
252 | let filters: Vec = nums.iter().filter(|x| *x % 2 == 1).cloned().collect();
253 | println!("{:?}", filters);
254 |
255 | println!("{:?}", nums);
256 | }
257 | ```
--------------------------------------------------------------------------------
/src/ch7-00.md:
--------------------------------------------------------------------------------
1 | # CH7. 구조체
2 |
3 | 러스트는 객체지향 프로그래밍보다는 함수형 프로그래밍에 더 가깝습니다. 단적인 예로 러스트 코드는 이터레이터와 클로저를 적극적으로 사용합니다. 이러한 이유에서 클래스가 존재하지 않습니다. 대신 비슷한 역할을 구조체 `struct` 를 통해서 구현할 수 있습니다.
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/ch7-01.md:
--------------------------------------------------------------------------------
1 |
2 | ## 구조체의 정의
3 |
4 | ### 구조체 선언
5 |
6 | 먼저 파이썬에서 클래스를 하나 정의해 보겠습니다. `Person` 클래스는 객체화 시 `name`, `age` 두 변수를 파라미터로 받고, `self.name`, `self.age` 라는 인스턴스 프로퍼티에 할당됩니다.
7 |
8 | ```python
9 | class Person:
10 | def __init__(self, name, age):
11 | self.name = name
12 | self.age = age
13 | ```
14 |
15 | 러스트에서 구조체를 선언하기 위해서는 `struct` 키워드 뒤에 구조체 이름을 명시하면 됩니다. 구조체 안에서는 `필드명: 타입명` 으로 필드를 적어줍니다. 필드를 통해 변수를 구조체에 묶어둘 수 있습니다. 여기서 `#[derive(Debug)]` 는 미리 정의되어 있는 기능으로(derived trait 라고 합니다), 구조체의 내용을 보기 위해서 필요합니다.
16 |
17 | ```rust,ignore
18 | #[derive(Debug)] // derived traits
19 | struct Person {
20 | name: String,
21 | age: i32,
22 | }
23 | ```
24 |
25 | 이제 각각 인스턴스를 생성해 보겠습니다.
26 |
27 | ```python
28 | jane = Person("jane", 30)
29 | jane.age += 1
30 | print(jane.name, jane.age)
31 | print(jane.__dict__)
32 | ```
33 |
34 | 그리고 프로퍼티를 변경하고 출력해 보겠습니다. 마지막으로 `__dict__` 를 이용해 인스턴스를 딕셔너리로 출력해 볼 수도 있습니다.
35 |
36 | ```rust
37 | #[derive(Debug)] // derived traits
38 | struct Person {
39 | name: String,
40 | age: i32,
41 | }
42 |
43 | fn main() {
44 | let mut jane = Person {
45 | name: String::from("Jane"),
46 | age: 30
47 | };
48 | jane.age += 1;
49 | println!("{} {}", jane.name, jane.age);
50 | println!("{:?}", jane);
51 | }
52 | ```
53 |
54 | 인스턴스를 생성할 때는 구조체 이름 뒤에서 `{필드명: 값}` 문법으로 값들을 넣어주면 됩니다. 프로퍼티는 파이썬과 동일하게 접근과 변경이 가능합니다. 그런데 여기서 인스턴스 `jane` 이 mutable로 선언되었습니다. 왜냐하면 `jane.age` 의 값을 변경하고 있기 때문에 구조체 자체가 mutable로 선언되어야 합니다.
55 |
56 | 마지막으로 `jane`을 출력하는데 `"{:?}"` 가 사용되었는데, 이 문법은 디버그 출력이라고 합니다. 원래 러스트에서 어떤 값을 출력하려면 그 값은 Format이 정의되어 있어야 하는데, `Person` 구조체는 정의되어 있지 않기 때문에 디버그 출력을 이용해 간편하게 내용을 확인할 수 있습니다.
57 |
58 |
59 |
60 | ### 메소드
61 |
62 | 만일 파이썬의 `Person` 클래스를 객체화할 때, `alive = True` 라는 프로퍼티를 추가하고 싶다면 아래와 같이 할 수 있습니다.
63 |
64 | ```python
65 | class Person:
66 | def __init__(self, name, age):
67 | self.name = name
68 | self.age = age
69 | self.alive = True
70 | ```
71 |
72 | 함수는 메소드를 통해 구조체에 묶을 수 있습니다. `impl` 키워드 뒤에 구조체 이름을 명시해서 해당 구조체에 속한 메소드를 선언할 수 있습니다. 파이썬의 생성자와 마찬가지로, 객체화할때 사용되는 `new` 라는 함수를 정의할 수 있습니다. 메소드가 아닌 "함수"도 구조체 정의에 포함시킬 수 있는데, 이 경우는 연관 함수(Associated function)이라고 부르고, 파라미터에 `self`가 들어있지 않습니다. 먼저 구조체 정의에 `alive: bool',` 을 추가합니다.
73 |
74 | ```rust,ignore
75 | #[derive(Debug)] // derived traits
76 | struct Person {
77 | name: String,
78 | age: i32,
79 | alive: bool,
80 | }
81 |
82 | impl Person {
83 | fn new(name: &str, age: i32) -> Self {
84 | Person {
85 | name: String::from(name),
86 | age: age,
87 | alive: true,
88 | }
89 | }
90 | }
91 | ```
92 |
93 | `new` 함수의 리턴 타입이 `Self`인데, 자신이 속한 구조체 타입인 `Person` 클래스를 리턴한다는 의미입니다. 물론 `-> Person`으로 써도 동일하지만, `Self` 가 더 권장되는 방법입니다. 그리고 이 함수 안에서 구조체를 `alive: true` 값을 넣어서 생성하고 있는 것을 알 수 있습니다.
94 |
95 | 인스턴스를 생성하는 메소드 말고 일반적인 메소드도 추가가 가능합니다. 먼저 파이썬에서는
96 |
97 | ```python
98 | class Person:
99 | def __init__(self, name, age):
100 | self.name = name
101 | self.age = age
102 | self.alive = True
103 |
104 | def info(self):
105 | print(self.name, self.age)
106 |
107 | def get_older(self, year):
108 | self.age += year
109 | ```
110 |
111 | 러스트에서는 아래와 같습니다.
112 |
113 | ```rust,ignore
114 | impl Person {
115 | fn new(name: &str, age: i32) -> Person {
116 | Person {
117 | name: String::from(name),
118 | age: age,
119 | alive: true,
120 | }
121 | }
122 |
123 | fn info(&self) {
124 | println!("{} {}", self.name, self.age)
125 | }
126 |
127 | fn get_older(&mut self, year: i32) {
128 | // if we don't borrow the ownership, ownership will be moved to the
129 | // function and the variable will be dropped
130 | // self must be passed as mutable reference
131 | self.age += year;
132 | }
133 | }
134 | ```
135 |
136 | 이때 `self` 가 borrowed 되면서 mutable 인 것에 주의합니다. 왜냐하면 인스턴스 프로퍼티가 변경되기 때문에 `self`가 mutable이어야 합니다. 여기서 `info` 메소드의 `&self`를 `self`로 바꾸면 어떻게 될까요?
137 |
138 | 다시 인스턴스를 생성하고 메소드를 호출해 보겠습니다.
139 |
140 | ```python
141 | john = Person("john", 20)
142 | john.info()
143 | john.get_older(3)
144 | john.info()
145 | ```
146 |
147 | `get_older` 메소드를 통해 age가 3 증가합니다. 러스트에서도 동일합니다.
148 |
149 | ```rust,ignore
150 | fn main() {
151 | let mut john = Person::new("john", 20);
152 | john.info();
153 | john.get_older(3);
154 | john.info();
155 | }
156 | ```
157 |
158 | 정리하자면, 구조체 안에는 `self` 파라미터를 사용하지 않는 연관 함수와 `self` 파라미터를 사용하는 메소드 모두 정의될 수 있습니다.
159 |
160 |
161 |
162 | ## 튜플 구조체(Tuple struct)
163 |
164 | 마지막으로 튜플 구조체를 간단히 알아보겠습니다. 튜플 구조체는 구조체 필드가 이름 대신 튜플 순서대로 정의되는 구조체입니다. 필드 참조 역시 튜플의 원소를 인덱스로 참조하는 것과 동일합니다.
165 |
166 | ```rust
167 | struct Color(i32, i32, i32);
168 | struct Point(i32, i32, i32);
169 |
170 | fn main() {
171 | let black = Color(0, 0, 0);
172 | let origin = Point(0, 0, 0);
173 |
174 | println!("{} {}", black.0, origin.0);
175 | }
176 |
177 | ```
178 |
--------------------------------------------------------------------------------
/src/ch7-02.md:
--------------------------------------------------------------------------------
1 | ## 트레이트(trait)
2 |
3 | ### 트레이트로 메소드 공유하기
4 |
5 | 파이썬은 클래스를 상속해 공통된 메소드를 사용할 수 있지만, 러스트는 구조체의 상속이 되지 않습니다.
6 |
7 | 먼저 파이썬에서 다음과 같이 `Person` 을 상속하는 새로운 클래스 `Student` 를 선언합니다.
8 |
9 | ```python
10 | class Person:
11 | def __init__(self, name, age):
12 | self.name = name
13 | self.age = age
14 | self.alive = True
15 |
16 | def say_hello(self):
17 | print("Hello, Rustacean!")
18 |
19 | def get_older(self, year):
20 | self.age += year
21 |
22 |
23 | class Student(Person):
24 | def __init__(self, name, age, major):
25 | super().__init__(name, age)
26 | self.major = major
27 |
28 | def say_hello(self):
29 | print(f"Hello, I am {self.name} and I am studying {self.major}")
30 |
31 | ```
32 |
33 | `Student` 클래스는 새로운 프로퍼티 `major` 를 가지고 있고, `Person` 의 `say_hello` 메소드를 오버라이드하고 있습니다.
34 |
35 |
36 |
37 | Rust는 하나의 struct를 상속하는 방법이 존재하지 않습니다. 즉 필드와 메소드를 다른 struct에 전달할 수 없습니다. 하지만 서로 다른 struct 타입들이 메소드를 공유할 수 있는 하나의 속성을 정의할 수 있는데, 바로 trait입니다. 러스트에서는 구조체에서 공유하는 메소드를 구현하기 위해 트레이트를 먼저 선언해야 합니다. 트레이트에서는 공유할 메소드의 원형을 선언합니다.
38 |
39 | ```rust,ignore
40 | trait Greet {
41 | fn say_hello(&self) {}
42 | }
43 | ```
44 |
45 | 이렇게 선언하면, `say_hello`는 아무것도 실행하지 않는 빈 함수이기 때문에, 실제 내용을 각 구조체의 메소드에서 구현해야 합니다. 혹은 `say_hello`의 기본 구현형을 트레이트를 선언할 때 정의할 수도 있습니다.
46 |
47 | 이제 파이썬과 동일하게 러스트 코드를 작성해 보겠습니다.
48 |
49 | ```rust,ignore
50 | struct Person {
51 | name: String,
52 | age: i32,
53 | alive: bool,
54 | }
55 |
56 | impl Person {
57 | fn new(name: &str, age: i32) -> Person {
58 | Person {
59 | name: String::from(name),
60 | age: age,
61 | alive: true
62 | }
63 | }
64 |
65 | fn get_older(&mut self, year: i32) {
66 | self.age += year;
67 | }
68 | }
69 |
70 | impl Greet for Person {}
71 |
72 |
73 | struct Student {
74 | name: String,
75 | age: i32,
76 | alive: bool,
77 | major: String,
78 | }
79 |
80 | impl Student {
81 | fn new(name: &str, age: i32, major: &str) -> Student {
82 | Student {
83 | name: String::from(name),
84 | age: age,
85 | alive: true,
86 | major: String::from(major),
87 | }
88 | }
89 | }
90 |
91 | impl Greet for Student {
92 | fn say_hello(&self) {
93 | println!("Hello, I am {} and I am studying {}", self.name, self.major)
94 | }
95 | }
96 | ```
97 |
98 | 이제 메인 함수에서 `Person`과 `Student` 구조체의 인스턴스를 만들고 `say_hello` 메소드를 각각 호출해 보겠습니다.
99 |
100 | ```rust
101 | fn main() {
102 | let mut person = Person::new("John", 20);
103 | person.say_hello(); // 🫢
104 | person.get_older(1);
105 | println!("{} is now {} years old", person.name, person.age);
106 |
107 | let student = Student::new("Jane", 20, "Computer Science");
108 | student.say_hello();
109 | }
110 |
111 | ```
112 |
113 | 실행 결과
114 |
115 | ```
116 | John is now 21 years old
117 | Hello, I am Jane and I am studying Computer Science
118 | ```
119 |
120 | `person.say_hello()`는 `trait Greet`의 메소드를 그대로 사용하기 때문에 아무것도 출력되지 않는 것을 알 수 있습니다.
121 |
122 | 다시 트레이트 선언으로 돌아가보면 `say_hello` 함수는 파라미터로 `&self` 를 받고 있지만, 트레이트에 정의되는 함수는 인스턴스 프로퍼티에 접근할 수 없습니다. 만일 여기서 다음과 같이 함수의 원형을 수정하고 컴파일해보면 에러가 발생합니다.
123 |
124 | ```rust,ignore
125 | trait Greet {
126 | fn say_hello(&self) {
127 | println!("Hello, Rustacean!");
128 | }
129 | }
130 | ```
131 |
132 |
133 |
134 | ### 파생(Derive)
135 |
136 | 컴파일러는 `#[derive]` 트레이트을 통해 일부 특성에 대한 기본 구현을 제공할 수 있습니다. 보다 복잡한 동작이 필요한 경우 이러한 특성은 직접 구현할 수 있습니다.
137 |
138 | 다음은 파생 가능한 트레이트 목록입니다:
139 |
140 | - 비교: [`Eq`](https://doc.rust-lang.org/std/cmp/trait.Eq.html), [`PartialEq`](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html), [`Ord`](https://doc.rust-lang.org/std/cmp/trait.Ord.html), [`PartialOrd`](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html).
141 | - [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html), 복사본을 통해 `&T`에서 `T`를 생성합니다.
142 | - [`Copy`](https://doc.rust-lang.org/core/marker/trait.Copy.html), '이동 시맨틱' 대신 '복사 시맨틱' 타입을 제공합니다.
143 | - [`Hash`](https://doc.rust-lang.org/std/hash/trait.Hash.html), `&T`에서 해시를 계산합니다.
144 | - [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html), 데이터 타입의 빈 인스턴스를 생성합니다.
145 | - `{:?}` 포매터를 사용하여 값의 형식을 지정하려면 [`Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html).
146 |
147 |
148 |
149 | ```rust
150 | struct Rectangle {
151 | width: u32,
152 | height: u32,
153 | }
154 |
155 | fn main() {
156 | let rect1 = Rectangle {
157 | width: 30,
158 | height: 50,
159 | };
160 |
161 | println!("rect1 is {:?}", rect1); // 🤯
162 | }
163 |
164 | ```
165 |
166 |
167 |
168 | ```
169 | error[E0277]: `Rectangle` doesn't implement `Debug`
170 | --> src/main.rs:12:31
171 | |
172 | 12 | println!("rect1 is {:?}", rect1); // 🤯
173 | | ^^^^^ `Rectangle` cannot be formatted using `{:?}`
174 | |
175 | = help: the trait `Debug` is not implemented for `Rectangle`
176 | = note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
177 | ```
178 |
179 |
180 |
181 | ```rust
182 | #[derive(Debug)]
183 | struct Rectangle {
184 | width: u32,
185 | height: u32,
186 | }
187 |
188 | fn main() {
189 | let rect1 = Rectangle {
190 | width: 30,
191 | height: 50,
192 | };
193 |
194 | println!("rect1 is {:?}", rect1);
195 | }
196 |
197 | ```
198 |
199 |
200 |
201 |
--------------------------------------------------------------------------------
/src/ch8-00.md:
--------------------------------------------------------------------------------
1 | # CH8. 모듈과 크레이트
--------------------------------------------------------------------------------
/src/ch8-01.md:
--------------------------------------------------------------------------------
1 | ## Rust의 모듈 시스템
2 |
3 | 러스트의 모듈 시스템은 아래 4가지를 말합니다.
4 |
5 | - 패키지(Packages) : cargo에서 제공하는 기능으로, crate를 빌드하고 생성할 수 있습니다.
6 | - 크레이트(Crates) : 라이브러리 또는 바이너리를 생성하는 모듈 트리(a tree of modules)입니다.
7 | - `mod` 와 `use`: 코드 안에서 다른 모듈들을 구성하고, 불러오거나 다른 모듈에 노출할 지 여부(private or public)를 결정합니다.
8 | - 경로: 모듈에서 특정 요소(함수, 구조체, 변수 등)를 찾기 위한 방법
9 |
10 | ### 패키지
11 |
12 | `cargo.toml` 파일
13 |
14 | 하나의 패키지에는 단 하나의 라이브러리 크레이트만 포함할 수 있습니다. 하지만 바이너리 크레이트는 여러 개를 넣을 수 있습니다.
15 |
16 | ### 크레이트
17 |
18 | #### 바이너리 크레이트
19 |
20 | `main.rs`
21 |
22 | ```bash
23 | cargo new
24 | ```
25 |
26 | 컴파일되어 바이너리 파일을 생성하는 크레이트입니다.
27 |
28 | #### 라이브러리 크레이트
29 |
30 | `lib.rs`
31 |
32 | ```bash
33 | cargo new --lib
34 | ```
35 |
36 | 컴파일되지 않기 때문에 바이너리를 생성하지 않습니다. 다른 크레이트나 패키지에서 코드를 참조할 수 있도록 제공되는 크레이트입니다.
37 |
38 | #### 크레이트 루트
39 |
40 | 크레이트 루트란 컴파일 엔트리포인트를 의미합니다. 바이너리 크레이트는 `src/main.rs` 파일이, 라이브러리 크레이트는 `src/lib.rs` 파일이 크레이트 루트가 됩니다.
41 |
42 | ### private vs public
43 |
44 | 러스트의 모든 모듈과 객체는 기본적으로 private입니다. 즉 모듈 외부에서 해당 모듈이나 객체에 접근이 불가능합니다. 따라서 외부에서 모듈에 접근하거나 모듈 내부의 객체에 접근을 허용하려면 `pub` 키워드를 사용해야 합니다.
45 |
46 | ```rust,ignore
47 | pub mod {
48 |
49 | }
50 |
51 | pub fn {
52 |
53 | }
54 |
55 | pub struct {
56 |
57 | }
58 |
59 | pub static
60 | ```
61 |
62 | ### `use` 와 `mod`
63 |
64 | `use` 키워드는 특정 경로를 현재 스코프로 가져오는 역할을 합니다. 주의해야 하는 점은 경로는 항상 크레이트 루트로부터 시작된다는 점입니다.
65 |
66 | `mod` 키워드는 해당 모듈을 사용하겠다고 선언하는 역할입니다. 예를 들어 `mod new_module`이 사용되면, 컴파일러는 아래 위치에서 해당 모듈을 찾아봅니다.
67 |
68 | 1. `mod new_module` 다음에 해당 모듈의 정의가 나와야 합니다.
69 |
70 | ```rust,ignore
71 | mod new_module {
72 | fn new_func() {
73 | ...
74 | }
75 |
76 | ...
77 | }
78 | ```
79 |
80 | 2. `src/new_module.rs` 파일을 찾아봅니다.
81 | 3. `src/new_module` 폴더에서 `mod.rs` 파일을 찾아봅니다.
82 |
83 | ```rust,ignore
84 | pub mod new_module;
85 | ```
86 |
87 | 마찬가지로 서브모듈도 정의가 가능합니다. 크레이트 루트가 아닌 모듈에서 선언되는 모듈이 서브모듈이 되며, 해당 서브모듈을 컴파일러가 찾는 규칙은 위와 동일합니다.
88 |
89 | 특정 모듈에 대한 접근은 크레이트 루트를 기준으로 절대경로를 사용하면 됩니다. 예를 들어 코드 어디에서라도 다음과 같이 모듈에 접근이 가능합니다.
90 |
91 | ```rust,ignore
92 | // src/new_module.rs -> MyType
93 | use crate::new_module::MyType
94 | ```
95 |
96 | 상대 경로를 사용할 때도 있는데, 이 경우에는 `self` 와 `super` 키워드를 사용합니다.
97 |
98 | `self` 는 struct 자기 자신을 의미합니다.
99 |
100 | ```rust
101 | mod mod2 {
102 | fn func() {
103 | println!("mod2::func()");
104 | }
105 |
106 | mod mod1 {
107 | pub fn func() {
108 | println!("mod2::mod1::func()");
109 | }
110 | }
111 |
112 | pub fn dummy() {
113 | func();
114 | self::func();
115 | mod1::func();
116 | self::mod1::func();
117 | }
118 | }
119 |
120 | fn main() {
121 | mod2::dummy();
122 | }
123 |
124 | ```
125 |
126 | 실행 결과
127 | ```
128 | mod2::func()
129 | mod2::func()
130 | mod2::mod1::func()
131 | mod2::mod1::func()
132 | ```
133 |
134 | `super`는 현재 모듈의 상위 모듈을 의미합니다.
135 |
136 | ```rust
137 | mod mod1 {
138 | pub fn dummy() {
139 | println!("Hello, world!");
140 | }
141 | }
142 |
143 | mod mod2 {
144 | // use crate::mod1;
145 | use super::mod1;
146 |
147 | pub fn dummy() {
148 | mod1::dummy();
149 | }
150 | }
151 |
152 | fn main() {
153 | mod2::dummy();
154 | }
155 |
156 | ```
--------------------------------------------------------------------------------
/src/ch8-02.md:
--------------------------------------------------------------------------------
1 | ## 모듈과 크레이트 사용해보기
2 |
3 | 파이썬 폴더에 `my_modle.py` 를 생성합니다.
4 |
5 | ```python
6 | def greet():
7 | print(f"Hi! I am hello_bot")
8 |
9 |
10 | class Person:
11 | def __init__(self, name, age):
12 | self.name = name
13 | self.age = age
14 |
15 | def get_older(self, year):
16 | self.age += year
17 | ```
18 |
19 | 이제 이 함수와 클래스를 `main.py`에서 참조합니다.
20 |
21 | ```python
22 | from my_module import greet, Person
23 |
24 | if __name__ == '__main__':
25 | greet()
26 |
27 | john = Person("john", 20)
28 | john.get_older(3)
29 | print(john.age)
30 |
31 | ```
32 |
33 | 이번에는 `bots` 폴더를 만들고 `hello_bot.py` 파일을 추가합니다.
34 |
35 | ```
36 | .
37 | ├── bots
38 | │ └── hello_bot.py
39 | ├── main.py
40 | └── my_module.py
41 | ```
42 |
43 | `hello_bot.py` 는 다음과 같습니다.
44 |
45 | ```python
46 | BOT_NAME = "hello_bot"
47 |
48 |
49 | def hello():
50 | print("Hello, humans!")
51 |
52 | ```
53 |
54 | `my_module.py`에서 `greet` 함수가 `BOT_NAME` 을 이용하도록 합니다.
55 |
56 | ```python
57 | from bots.hello_bot import BOT_NAME
58 |
59 |
60 | def greet():
61 | print(f"Hi! I am {BOT_NAME}")
62 | ```
63 |
64 | 그 다음 `main.py` 에서 `bots` 모듈을 사용해 보겠습니다.
65 |
66 | ```python
67 | from bots.hello_bot import hello
68 | from my_module import greet, Person
69 |
70 | if __name__ == '__main__':
71 | hello()
72 |
73 | greet()
74 |
75 | john = Person("john", 20)
76 | john.get_older(3)
77 | print(john.age)
78 |
79 | ```
80 |
81 | 이번에는 동일한 구조를 러스트에서 구현해 보겠습니다. src 폴더에 `my_module.rs` 를 생성합니다.
82 |
83 | ```
84 | src
85 | ├── main.rs
86 | └── my_module.rs
87 | ```
88 |
89 | 그리고 다음 코드를 입력합니다. 함수 하나와 구조체 하나가 들어있습니다. 여기서 함수, 구조체, 메소드가 모두 `pub` 키워드로 선언되어 있다는 점에 주목하세요. 이때 구조체 필드도 public으로 만드려면 `pub` 키워드를 사용해야 합니다. 여기서는 `name` 만 public입니다.
90 |
91 | ```rust,ignore
92 | pub fn greet() {
93 | println!("Hi! I am hello_bot");
94 | }
95 |
96 | pub struct Person {
97 | pub name: String,
98 | age: i32,
99 | }
100 |
101 | impl Person {
102 | pub fn new(name: &str, age: i32) -> Self {
103 | Person {
104 | name: String::from(name),
105 | age: age,
106 | }
107 | }
108 |
109 | pub fn get_older(&mut self, year: i32) {
110 | self.age += year;
111 | }
112 | }
113 |
114 | ```
115 |
116 | 이제 `main.rs`에서 이 `my_module.rs` 모듈의 함수와 구조체를 사용해 보겠습니다. 먼저 `mod` 키워드를 사용해 `my_module` 을 스코프로 가져옵니다. 그 다음 `use my_module::{greet, Person};` 로 가져오고자 하는 함수와 구조체를 가져올 수 있습니다. 이렇게 가져온 함수와 구조체를 이제 `main()` 함수 내에서 사용할 수 있습니다. 이때 `john.alive` 는 private이므로 주석을 해제하고 컴파일하면 컴파일이 되지 않습니다.
117 |
118 | ```rust,ignore
119 | mod my_module; // will look for a file src/my_module.rs
120 |
121 | use my_module::{greet, Person}; // actually import the function and struct from my_module.rs
122 |
123 | fn main() {
124 | greet();
125 |
126 | let mut john = Person::new("john", 20);
127 | john.get_older(3);
128 | println!("{}", john.name);
129 | // println!("Am I alive? {}", john.alive); // won't compile!
130 | }
131 |
132 | ```
133 |
134 | 다음으로는 하위 폴더 `bots`를 만들어 보겠습니다. `bots` 폴더에는 `hello_bot.rs`와 `mod.rs` 두 파일을 생성합니다.
135 |
136 | ```
137 | src
138 | ├── bots
139 | │ ├── hello_bot.rs
140 | │ └── mod.rs
141 | ├── main.rs
142 | └── my_module.rs
143 | ```
144 |
145 | 항상 하위 폴더를 모듈로 만드는 경우에는 `mod.rs` 가 있어야 합니다. 이 파일은 해당 모듈의 엔트리포인트가 되어 이 모듈 안에 있는 다른 하위 모듈들을 찾을 수 있게 합니다. 따라서 `mod.rs` 에는 `hello_bot` 모듈의 정보가 있어야 합니다.
146 |
147 | ```rust,ignore
148 | pub mod hello_bot; // will look for hello_bot.rs
149 | ```
150 |
151 | 이제 `hello_bot.rs` 파일을 작성합니다.
152 |
153 | ```rust,ignore
154 | pub static BOT_NAME: &str = "hello_bot";
155 |
156 | pub fn hello() {
157 | println!("Hello, humans!");
158 | }
159 |
160 | ```
161 |
162 | static 변수와 함수 하나가 생성되어 있고, 둘 다 public으로 선언되었습니다. 먼저, `BOT_NAME` 스태틱을 `src/my_module.rs` 에서 참조해 보겠습니다. `my_module.rs` 는 크레이트 루트가 아니기 때문에 `use crate::` 문법으로 참조해야 합니다. 여기서 `greet` 함수가 이 `BOT_NAME` 스태틱을 참조해 실행되도록 수정해 봅시다.
163 |
164 | ```rust,ignore
165 | use crate::bots::hello_bot::BOT_NAME;
166 |
167 | pub fn greet() {
168 | println!("Hi! I am {}", BOT_NAME);
169 | }
170 |
171 | ```
172 |
173 | 이제 `main.rs` 에서 `bots` 모듈을 사용해 보겠습니다. `main.rs`는 크레이트 루트기 때문에 `use bots::hello_bot::hello;` 로 모듈을 불러올 수 있습니다.
174 |
175 | ```rust,ignore
176 | mod my_module; // will look for a file src/my_module.rs
177 | mod bots; // will look for a file src/hello/mod.rs
178 |
179 | use my_module::{greet, Person}; // actually import the function and struct from my_module.rs
180 | use bots::hello_bot::hello; // actually import the function from hello.rs
181 |
182 |
183 | fn main() {
184 | hello();
185 |
186 | greet();
187 |
188 | let mut john = Person::new("john", 20);
189 | john.get_older(3);
190 | println!("{}", john.name);
191 | // println!("Am I alive? {}", john.alive); // won't compile!
192 | }
193 |
194 | ```
195 |
--------------------------------------------------------------------------------
/src/ch9-00.md:
--------------------------------------------------------------------------------
1 | # CH9. 제네릭
2 |
3 | ## 제네릭이란?
4 |
5 | 제네릭은 다양한 유형에 사용할 수 있는 코드를 작성할 수 있는 기능입니다. 함수, 구조체, 열거형 또는 특성을 제네릭 파라미터로 정의하면 다양한 데이터 유형에서 작동하는 재사용 가능한 코드를 만들 수 있습니다. 따라서 제네릭은 보다 유연하고 효율적인 코드를 작성하도록 도와줍니다.
6 |
--------------------------------------------------------------------------------
/src/ch9-01.md:
--------------------------------------------------------------------------------
1 | ## 타입 파라미터
2 |
3 | 제네릭이 가장 많이 사용되는 곳은 타입 파라미터입니다. 제네릭을 사용해 타입 파라미터는 ``와 같이 표시합니다.
4 |
5 | ```rust,ignore
6 | fn foo(arg: T) { ... }
7 | ```
8 |
9 |
10 |
11 | ```rust
12 | struct Point {
13 | x: i32,
14 | y: i32,
15 | }
16 |
17 | fn main() {
18 | let integer = Point { x: 5, y: 10 };
19 | }
20 |
21 | ```
22 |
23 |
24 |
25 | ```rust
26 | struct PointI32 {
27 | x: i32,
28 | y: i32,
29 | }
30 |
31 | struct PointF64 {
32 | x: f64,
33 | y: f64,
34 | }
35 |
36 | fn main() {
37 | let integer = PointI32 { x: 5, y: 10 };
38 | let float = PointF64 { x: 5.0, y: 10.0 };
39 | }
40 |
41 | ```
42 |
43 |
44 |
45 | ```rust
46 | struct Point {
47 | x: T,
48 | y: T,
49 | }
50 |
51 | fn main() {
52 | let integer = Point { x: 5, y: 10 };
53 | let float = Point { x: 5.0, y: 10.0 };
54 | }
55 |
56 | ```
57 |
58 |
59 |
60 | ```rust,ignore
61 | struct Point {
62 | x: T,
63 | y: T,
64 | }
65 |
66 | fn main() {
67 | let integer = Point { x: 5, y: 10 };
68 | let float = Point { x: 5.0, y: 10.0 };
69 | let int_float = Point { x: 5, y: 10.0 }; // 🤯
70 | }
71 |
72 | ```
73 |
74 |
75 |
76 | ```rust
77 | struct Point {
78 | x: T,
79 | y: U,
80 | }
81 |
82 | fn main() {
83 | let integer = Point { x: 5, y: 10 };
84 | let float = Point { x: 5.0, y: 10.0 };
85 | let int_float = Point { x: 5, y: 10.0 };
86 | }
87 |
88 | ```
89 |
90 |
91 |
92 | > 알파벳 순서를 따라 T, U, V, X, Y, Z, ... 순으로 많이 사용하지만, 임의의 파스칼 케이스 변수명을 사용해도 상관없습니다.
93 |
94 |
95 |
96 | 메소드 정의에서도 제네릭 타입을 사용할 수 있습니다.
97 |
98 | ```rust
99 | struct Point {
100 | x: T,
101 | y: U,
102 | }
103 |
104 | impl Point {
105 | fn mixup(self, other: Point) -> Point {
106 | Point {
107 | x: self.x,
108 | y: other.y,
109 | }
110 | }
111 | }
112 |
113 | fn main() {
114 | let integer = Point { x: 5, y: 10 };
115 | let float = Point { x: 1.0, y: 4.0 };
116 |
117 | let mixed = integer.mixup(float);
118 |
119 | println!("mixed.x = {}, mixed.y = {}", mixed.x, mixed.y);
120 | }
121 |
122 | ```
123 |
124 |
--------------------------------------------------------------------------------
/src/ch9-02.md:
--------------------------------------------------------------------------------
1 | ## 제네릭과 트레이트
2 |
3 |
4 |
5 |
6 |
7 | ### `impl Trait`
8 |
9 | `impl Trait` can be used in two locations:
10 |
11 | 1. as an argument type
12 | 2. as a return type
13 |
14 |
15 |
16 | #### 파라미터 타입
17 |
18 | If your function is generic over a trait but you don't mind the specific type, you can simplify the function declaration using `impl Trait` as the type of the argument.
19 |
20 | ```rust
21 | fn copy(_item: impl Copy) {
22 | println!("Copy");
23 | }
24 |
25 | fn clone(_item: impl Clone) {
26 | println!("Clone");
27 | }
28 |
29 | fn main() {
30 | let num = 1;
31 | copy(num);
32 | clone(num);
33 |
34 | let string = String::from("Hello");
35 | clone(string);
36 | // copy(string); // 🤯
37 | }
38 |
39 | ```
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | ### 트레이트 바운드
50 |
51 | 트레이트 바운드(Trait bound)란 `impl Trait` 를 사용하는 대신 좀더 간결하게 표현할 수 있는 방법입니다.
52 |
53 | ```rust
54 | use std::fmt::Display;
55 |
56 | fn some_function(t: &T) {
57 | println!("{}", t);
58 | }
59 |
60 | fn main() {
61 | let x = 5;
62 | some_function(&x);
63 | }
64 |
65 | ```
66 |
67 | 이를 원래대로 `impl Trait`를 사용하면 다음과 같습니다.
68 |
69 | ```rust
70 | fn some_function(t: &impl Display) {
71 | println!("{}", t);
72 | }
73 | ```
74 |
75 | 트레이트 바운드를 사용하면 다음과 같이 타입을 복합적으로 표현할 수 있습니다.
76 |
77 | ```rust,ignore
78 | fn some_function(t: &T, u: &U) {}
79 | ```
80 |
81 | 하지만 이러면 함수 선언을 알아보기가 어려워지기 때문에 `where` 문을 사용해 좀더 읽기 쉽게 바꿀 수 있습니다.
82 |
83 | ```rust,ignore
84 | use std::fmt::{Debug, Display};
85 |
86 | fn some_function(t: &T, u: &U)
87 | where
88 | T: Display + Clone,
89 | U: Clone + Debug,
90 | {
91 | println!("{} {:?}", t, u);
92 | }
93 |
94 | fn main() {
95 | let x = 5;
96 | let y = vec![1, 2, 3];
97 | some_function(&x, &y);
98 | }
99 |
100 | ```
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | #### 리턴 타입
113 |
114 | 리턴 타입으로 `impl Trait` 구문을 사용하면 특정 트레이트를 구현하고 있는 타입을 리턴하도록 헐 수도 있습니다:
115 |
116 | ```rust
117 | fn double(vector: Vec) -> impl Iterator- {
118 | vector.into_iter().map(|x| x * 2)
119 | }
120 |
121 | fn main() {
122 | for num in double(vec![1, 2, 3]) {
123 | println!("{}", num);
124 | }
125 | }
126 |
127 | ```
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | ### 터보피시
136 |
137 | 터보 피쉬 신택스는 제네릭 타입인 파라미터에 구체적인 타입을 지정하는 데 사용됩니다.
138 |
139 | ```rust,ignore
140 | identifier::
141 | ```
142 |
143 |
144 |
145 | #### 타입 어노테이션 대신에 사용되는 경우
146 |
147 | 간결성을 위해 명시적 타입 어노테이션 대신에 사용됩니다.
148 |
149 |
150 |
151 | 컴파일러가 대부분의 상황에서 타입을 추론 가능
152 |
153 | ```rust
154 | use std::collections::HashMap;
155 |
156 | fn main() {
157 | let mut students = HashMap::new();
158 | students.insert("buzzi", 100);
159 | }
160 |
161 | ```
162 |
163 |
164 |
165 | 이런 경우는 어떤 원소를 넣는지 알 수 없기 때문에 타입을 명시적으로 알려줘야 함
166 |
167 | ```rust
168 | use std::collections::HashMap;
169 |
170 | fn main() {
171 | let mut students: HashMap<&str, i32> = HashMap::new();
172 | // students.insert("buzzi", 100);
173 | }
174 |
175 | ```
176 |
177 |
178 |
179 | 이 경우 터보피시를 사용해서 타입 어노테이션을 대체 가능
180 |
181 | ```rust
182 | use std::collections::HashMap;
183 |
184 | fn main() {
185 | let mut students: HashMap = HashMap::<&str, i32>::new();
186 | // students.insert("buzzi", 100);
187 | }
188 |
189 | ```
190 |
191 |
192 |
193 |
194 |
195 | 복잡한 예제
196 |
197 | ```rust
198 | fn double(vector: Vec) -> impl Iterator
- {
199 | vector.into_iter().map(|x| x)
200 | }
201 |
202 | fn main() {
203 | let nums = double(vec![1, 2, 3]).collect::>();
204 | println!("{:?}", nums);
205 | let nums: Vec =
206 | double(vec!["1".to_string(), "2".to_string(), "3".to_string()]).collect();
207 | println!("{:?}", nums);
208 | }
209 | ```
210 |
211 |
212 |
213 | #### 명시적 타입 어노테이션이 작동하지 않을 때
214 |
215 | ```rust
216 | fn main() {
217 | let nums: Vec = ["1", "2", "three"]
218 | .iter()
219 | .filter_map(|x| x.parse().ok())
220 | .collect();
221 | }
222 |
223 | ```
224 |
225 |
226 |
227 | ```rust,ignore
228 | fn main() {
229 | let nums: bool = ["1", "2", "three"]
230 | .iter()
231 | .filter_map(|x| x.parse().ok())
232 | .collect() // 🤯
233 | .contains(&1);
234 | }
235 |
236 | ```
237 |
238 |
239 |
240 | ```rust
241 | fn main() {
242 | let nums: bool = ["1", "2", "three"]
243 | .iter()
244 | .filter_map(|x| x.parse().ok())
245 | .collect::>()
246 | .contains(&1);
247 | }
248 |
249 | ```
250 |
251 |
--------------------------------------------------------------------------------
/src/ch9-03.md:
--------------------------------------------------------------------------------
1 | ## 미니프로젝트: `cat` 만들어보기
2 |
3 | `clap`은 러스트에서 CLI 앱을 쉽게 만들 수 있도록 도와주는 크레이트입니다. 최근 릴리즈에서 `derive` 라는 기능을 사용해 앱을 더 쉽게 만드는 기능이 추가되었습니다. 이 기능을 사용하기 위해서는 설치 시 `--features derive` 옵션을 추가하면 됩니다.
4 |
5 | ```console
6 | cargo add clap --features derive
7 | ```
8 |
9 | 제일 먼저 커맨드라인 정보를 읽어올 `Args` 구조체를 선언합니다.
10 |
11 | ```rust
12 | use clap::Parser;
13 |
14 | #[derive(Parser, Debug)]
15 | #[command(author, version, about, long_about = None)]
16 | struct Args {
17 | #[arg(short, long)]
18 | name: String,
19 | }
20 |
21 | ```
22 |
23 | 그 다음 파일로부터 데이터를 읽어올 함수 `cat`을 정의합니다.
24 |
25 | ```rust
26 | fn cat(filename: &str) -> io::Result<()> {
27 | let file = File::open(filename)?;
28 | let reader = BufReader::new(file);
29 |
30 | for line in reader.lines() {
31 | println!("{}", line?);
32 | }
33 |
34 | Ok(())
35 | }
36 | ```
37 |
38 | `cat` 함수가 제대로 작동하는지 테스트해 보겠습니다. 현재 경로에 `test.txt` 파일을 만들고 아래 내용을 입력하세요.
39 |
40 | ```
41 | name: John
42 | age: 32
43 | rating: 10
44 |
45 | ```
46 |
47 | 이제 메인 함수에서 `cat`을 호출합니다.
48 |
49 | ```rust
50 | use std::{
51 | fs::File,
52 | io::{self, BufRead, BufReader},
53 | };
54 |
55 | fn cat(filename: &str) -> io::Result<()> {
56 | let file = File::open(filename)?;
57 | let reader = BufReader::new(file);
58 |
59 | for line in reader.lines() {
60 | println!("{}", line?);
61 | }
62 |
63 | Ok(())
64 | }
65 |
66 | fn main() {
67 | cat("text.txt").unwrap()
68 | }
69 |
70 | ```
71 |
72 | 이제 사용자로부터 정보를 입력받기 위해 처음에 만든 `Args` 구조체를 사용합니다.
73 |
74 | ```rust
75 | use clap::Parser;
76 | use std::{
77 | fs::File,
78 | io::{self, BufRead, BufReader},
79 | };
80 |
81 | #[derive(Parser, Debug)]
82 | #[command(author, version, about, long_about = None)]
83 | struct Args {
84 | #[arg(short, long)]
85 | name: String,
86 | }
87 |
88 | fn cat(filename: &str) -> io::Result<()> {
89 | let file = File::open(filename)?;
90 | let reader = BufReader::new(file);
91 |
92 | for line in reader.lines() {
93 | println!("{}", line?);
94 | }
95 |
96 | Ok(())
97 | }
98 |
99 | fn main() {
100 | let args = Args::parse();
101 |
102 | cat(&args.name).unwrap()
103 | }
104 |
105 | ```
106 |
107 | 원래는 바이너리를 사용해야 하지만, 편의를 위해 만들어진 바이너리에 옵션을 넘기는 `--` 파이프를 사용합니다.
108 |
109 | ```
110 | cargo run -- --name my_best_friends.txt
111 | ```
112 |
113 | 실행 결과
114 |
115 | ```
116 | name: John
117 | age: 32
118 | rating: 10
119 |
120 | ```
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/src/ch9-04.md:
--------------------------------------------------------------------------------
1 | ## 라이프타임과 스태틱
2 |
3 | 레퍼런스 그리고 소유권 대여 규칙에서 다루지 않은 한 가지가 있습니다. 바로 러스트의 모든 레퍼런스는 유효한 범위인 라이프타임이 있다는 것입니다. 대부분의 경우, 레퍼런스의 라이프타임은 변수의 타입이 추론되는 것과 마찬가지로 대부분의 상황에서 컴파일러가 추론 가능합니다.
4 |
5 |
6 |
7 | ### 라이프타임(lifetime)
8 |
9 | 하지만 몇몇 상황의 경우, 컴파일러에게 어떤 레퍼런스가 언제까지 유효(living)한가를 명시적으로 알려줘야 할 때가 있습니다. 예를 들어 아래와 같은 경우는 컴파일되지 않습니다.
10 |
11 | ```rust
12 | fn main() {
13 | let r;
14 |
15 | {
16 | let x = 5;
17 | r = &x;
18 | }
19 |
20 | println!("r: {}", r);
21 | }
22 |
23 | ```
24 |
25 | 내부 스코프에서 참조된 `x`가 스코프를 벗어나면 값이 삭제되기 때문에 `r`이 가리키고 있는 값이 없는 상태가 됩니다. 이러한 경우를 댕글링 레퍼런스(Dangling reference)라고 합니다.
26 |
27 | 아쉽게도 변수에 라이프타임을 추가하는 문법은 아직 러스트에 존재하지 않습니다. 대신 함수에서 파라미터와 리턴 값의 라이프타임을 추가하는 방법을 알아보겠습니다.
28 |
29 |
30 |
31 | ### 함수에서의 라이프타임
32 |
33 |
34 |
35 | ```rust
36 | fn main() {
37 | let string1 = String::from("abcd");
38 | let string2 = "xyz";
39 |
40 | let result = longest(string1.as_str(), string2);
41 | println!("The longest string is {}", result);
42 | }
43 |
44 | fn longest(x: &str, y: &str) -> &str {
45 | if x.len() > y.len() {
46 | x
47 | } else {
48 | y
49 | }
50 | }
51 |
52 | ```
53 |
54 |
55 | 실행 결과
56 |
57 | ```
58 | error[E0106]: missing lifetime specifier
59 | --> src/main.rs:9:33
60 | |
61 | 9 | fn longest(x: &str, y: &str) -> &str {
62 | | ---- ---- ^ expected named lifetime parameter
63 | |
64 | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
65 | help: consider introducing a named lifetime parameter
66 | |
67 | 9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
68 | | ++++ ++ ++ ++
69 |
70 | ```
71 |
72 |
73 | 이 함수가 `x` 혹은 `y` 중 어떤 값을 리턴할 지 알 수 없습니다. 즉 `x`와 `y`가 언제까지 스코프에서 유효한지를 알 수 없기 때문에 리턴되는 스트링 슬라이스 역시 언제까지 유효한지를 알 수 없습니다. 따라서 리턴되는 값이 언제까지 유효한지를 알려줘야 합니다.
74 |
75 | ```rust
76 | &i32 // a reference
77 | &'a i32 // a reference with an explicit lifetime
78 | &'a mut i32 // a mutable reference with an explicit lifetime
79 | ```
80 |
81 | 이 규칙에 따라 `longest`에 라이프타임을 나타내면 다음과 같습니다.
82 |
83 | ```rust
84 | fn main() {
85 | let string1 = String::from("abcd");
86 | let string2 = "xyz";
87 |
88 | let result = longest(string1.as_str(), string2);
89 | println!("The longest string is {}", result);
90 | }
91 |
92 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
93 | if x.len() > y.len() {
94 | x
95 | } else {
96 | y
97 | }
98 | }
99 |
100 | ```
101 |
102 |
103 |
104 | > 라이프타임에 대해서 기억해야 할 가장 중요한 점은 "라이프타임 표기는 레퍼런스의 실제 라이프타임을 바꾸지 않는다" 라는 것입니다. 여러 레퍼런스의 라이프타임 사이의 관계를 나타냅니다.
105 |
106 |
107 |
108 | 이번에는 서로 다른 라이프타임을 갖는 `string1`과 `string2`를 사용해 보겠습니다.
109 |
110 | ```rust
111 | fn main() {
112 | let string1 = String::from("long string is long");
113 | let result;
114 | {
115 | let string2 = String::from("xyz");
116 | result = longest(string1.as_str(), string2.as_str()); // 🤯
117 | }
118 | println!("The longest string is {}", result);
119 | }
120 |
121 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
122 | if x.len() > y.len() {
123 | x
124 | } else {
125 | y
126 | }
127 | }
128 |
129 | ```
130 |
131 | `string2`의 레퍼런스가 스코프 안에서만 유효하기 때문에 이와 같은 라이프타임을 갖는 `result`는 스코프 밖에서 유효하지 않습니다.
132 |
133 | 어찌되었든 유효한 소유권 규칙을 지키기 위해서 서로 다른 라이프타임을 명시하고, 가장 오래 살아남는 `x`만 리턴하도록 하면 코드를 동작하게 할 수 있습니다.
134 |
135 |
136 | ```rust
137 | fn main() {
138 | let string1 = String::from("long string is long");
139 | let result;
140 | {
141 | let string2 = String::from("xyz");
142 | result = longest(string1.as_str(), string2.as_str()); // 🤯
143 | }
144 | println!("The longest string is {}", result);
145 | }
146 |
147 | fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
148 | if x.len() > y.len() {
149 | x
150 | } else {
151 | "y is no use here 🥲"
152 | }
153 | }
154 | ```
155 |
156 |
157 |
158 | ### 스태틱(static) 라이프타임
159 |
160 | 한 가지 특별한 라이프타임이 있습니다. 바로 `static`으로, 해당 레퍼런스가 프로그램이 실행되는 동안 계속해서 존재할 수 있음을 나타냅니다. 모든 문자열 리터럴은 스태틱 라이프타임을 가지고 있습니다.
161 |
162 | ```rust
163 | let s: &'static str = "Long live the static!";
164 | ```
165 |
166 |
167 | 이 문자열의 값은 프로그램의 바이너리에 직접 저장되어 항상 사용할 수 있습니다. 따라서 모든 문자열 리터럴의 수명은 스태틱입니다.
168 |
169 |
170 |
171 | 참고로, 문자열 관련 코드를 작성하다가 레퍼런스 관련 오류가 발생하면 오류 메시지에서 스태틱 라이프타임을 사용하라는 컴파일러의 제안을 볼 수 있습니다. 하지만 라이프타임은 문자열의 존재 기간을 명확하게 명시하는 용도이기 때문에 바로 스태틱 라이프타임을 사용하지 말고, 이 문자열의 정확한 라이프타임을 먼저 적용하는 것이 중요합니다.
172 |
173 |
174 |
175 |
--------------------------------------------------------------------------------
/src/project.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | MongoDB 설치 혹은 MongoDB Atlas 이용
4 |
5 |
6 |
7 |
8 |
9 | Cargo.toml
10 |
11 | ```toml
12 | [package]
13 | name = "chat_server"
14 | version = "0.1.0"
15 | edition = "2021"
16 |
17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
18 |
19 | [dependencies]
20 | rocket = {version = "0.5.0-rc.1", features = ["json"]}
21 | bson = { version = "2", features = ["chrono-0_4"] }
22 | chrono = "0.4"
23 | dotenv = "0.15.0"
24 | serde_with = "2.0.1"
25 | rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", branch = "master" }
26 |
27 | [dev-dependencies]
28 | rand = "0.8.4"
29 |
30 | [dependencies.mongodb]
31 | version = "2.2.0"
32 | default-features = false
33 | features = ["sync"]
34 | ```
35 |
36 |
37 |
38 | ```rust
39 | struct Color(i32, i32, i32);
40 | struct Point(i32, i32, i32);
41 |
42 | fn main() {
43 | let black = Color(0, 0, 0);
44 | let origin = Point(0, 0, 0);
45 |
46 | println!("{} {}", black.0, origin.0);
47 | }
48 |
49 | ```
50 |
51 |
--------------------------------------------------------------------------------