Comprehensive Rust์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค ๐ฆ
์ด ๊ฐ์๋ ๋ฌด๋ฃ์ด๋ฉฐ, Google์ Android ํ์ด ๋ง๋ค์์ต๋๋ค. ๊ธฐ๋ณธ ๋ฌธ๋ฒ๋ถํฐ ์ ๋ค๋ฆญ, ์๋ฌ ํธ๋ค๋ง๊ณผ ๊ฐ์ ๊ณ ๊ธ์ฃผ์ ๊น์ง ๋ฌ์คํธ์ ๋ชจ๋ ๊ฒ์ ํฌํจํฉ๋๋ค.
์ด ๊ณผ์ ์ ์ต์ ๋ฒ์ ์ https://google.github.io/comprehensive-rust/์์ ํ์ธํ ์ ์์ต๋๋ค. ๋ค๋ฅธ ๊ณณ์์ ์ฝ๊ณ ์๋ ๊ฒฝ์ฐ ์ด๊ณณ์์ ์ ๋ฐ์ดํธ๋ฅผ ํ์ธํ์๊ธฐ ๋ฐ๋๋๋ค.
The course is also available as a PDF.
๊ฐ์๋ ๋น์ ์ด ๋ฌ์คํธ์ ๋ํด์ ์๋ฌด๊ฒ๋ ๋ชจ๋ฅธ๋ค๊ณ ๊ฐ์ ํ๊ณ ์๋์ ๋ชฉํ๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค:
- ๋ฌ์คํธ ๊ตฌ๋ฌธ๊ณผ ์ธ์ด์ ๋ํ ํฌ๊ด์ ์ธ ์ดํด๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ๊ธฐ์กด ํ๋ก๊ทธ๋จ์ ์์ ํ๊ณ ๋ฌ์คํธ์์ ์ ํ๋ก๊ทธ๋จ์ ์์ฑํ ์ ์์ต๋๋ค.
- ์ผ๋ฐ์ ์ธ ๋ฌ์คํธ ๊ด์ฉ๊ตฌ๋ฅผ ๋ณด์ฌ์ค๋๋ค.
We call the first four course days Rust Fundamentals.
๊ทธ ํ์๋, ์๋์ ๊ฐ์ ๊ฐ๋ณ ์ฃผ์ ๋ฅผ ์ฌํํด์ ๊ณต๋ถํ ์ ์์ต๋๋ค:
- Android: Android ํ๋ซํผ ๊ฐ๋ฐ(AOSP) ์ Rust ์ฌ์ฉ์ ๊ดํ ๋ฐ๋์ ๊ณผ์ ์ ๋๋ค. ์ฌ๊ธฐ์๋ C, C++, Java์์ ์ํธ ์ด์ฉ์ฑ์ด ํฌํจ๋ฉ๋๋ค.
- Chromium์ Rust ์ฌ์ธต ๋ถ์์ Chromium ๋ธ๋ผ์ฐ์ ์ ์ผ๋ถ๋ก Rust๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๊ดํ ๋ฐ๋์ ๊ณผ์ ์ ๋๋ค. ์ฌ๊ธฐ์๋ Chromium์ โgnโ ๋น๋ ์์คํ ์์ Rust๋ฅผ ์ฌ์ฉํ์ฌ ์๋ ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(โcratesโ)์ C++ ์ํธ ์ด์ฉ์ฑ์ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ด ํฌํจ๋์ด ์์ต๋๋ค.
- Bare-metal: bare-metal(์๋ฒ ๋๋) ๊ฐ๋ฐ ์ Rust ์ฌ์ฉ์ ๊ดํ ์ข ์ผ ๊ณผ์ ์ ๋๋ค. ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ์ ์ ํ๋ฆฌ์ผ์ด์ ํ๋ก์ธ์๋ฅผ ๋ชจ๋ ๋ค๋ฃน๋๋ค.
- ๋์์ฑ: Rust์ ๋์์ฑ์ ๊ดํ ์ข ์ผ ๊ณผ์ ์ ๋๋ค. ์ฌ๊ธฐ์๋ ๊ณ ์ ์ ์ธ ๋์์ฑ(์ค๋ ๋์ ๋ฎคํ ์ค๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ ํ ์ค์ผ์ค๋ง์ ํ๋ ๊ฒ)๊ณผ async/await ๋์์ฑ(future๋ฅผ ์ฌ์ฉํ๋ ํ๋ ฅ์ ์ธ ๋ฉํฐํ์คํน)์ ๋ชจ๋ ๋ค๋ฃน๋๋ค.
์ ์ธ์ฌํญ
๋ฌ์คํธ๋ ๋ฉฐ์น ๋ง์ ๋ชจ๋ ๊ฒ์ ๋ค๋ฃจ๊ธฐ์๋ ๋๋ฌด ํฐ ์ธ์ด์ ๋๋ค. ๊ทธ๋์ ์๋์ ๊ฐ์๊ฒ์ ๋ชฉํ๋ก ํ์ง ์์ต๋๋ค:
- ๋งคํฌ๋ก ๋ง๋ค๊ธฐ: ๋งคํฌ๋ก์ ๊ด๋ จํ ์์ธํ ๋ด์ฉ์ ๋ฌ์คํธ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด, 19.1์ ๊ณผ Rustonomicon๋ฅผ ์ฐธ์กฐํ์ธ์.
๋ ์ ์์ค์ ๋ํ ๊ฐ์
๋ณธ ๊ฐ์๋ ์ฌ๋ฌ๋ถ์ด ํ๋ก๊ทธ๋๋ฐ ์์ฒด์ ๋ํด์๋ ์๊ณ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. ๋ฌ์คํธ๋ ์ ์ ํ์ ์ธ์ด์ด๋ฉฐ, ๊ฐ์ข์์๋ C/C++ ์์ ๋น๊ต, ๋์กฐ๋ฅผ ํตํด ๋ฌ์คํธ๋ฅผ ์ค๋ช ํ ๊ฒ์ ๋๋ค.
C/C++์ ๋ชจ๋ฅด๋๋ผ๋ ๋์ ํ์ ์ธ์ด(Python์ด๋ JavaScript ๋ฑ) ํ๋ก๊ทธ๋๋ฐ ๊ฒฝํ์ด ์๋ค๋ฉด ๋ฐ๋ผ์ค๋๋ฐ ํฐ ๋ฌธ์ ๋ ์์ ๊ฒ์ ๋๋ค.
์ด๊ฒ์ โ๋ฐํ์ ๋ ธํธโ์ ์์ ์ ๋๋ค. ์ด ๋ถ๋ถ์ ์ด์ฉํด์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ฃผ๋ก ๊ฐ์์ค์์ ์ ๊ธฐ๋๋ ์ผ๋ฐ์ ์ธ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ๊ณผ ๊ฐ์ฌ๊ฐ ๋ค๋ฃจ์ด์ผ ํ ํค ํฌ์ธํธ์ผ ์ ์์ต๋๋ค.
๊ฐ์ ์งํ
๊ฐ์ฌ๋ฅผ ์ํ ์๋ด ํ์ด์ง์ ๋๋ค.
๋ค์์ ๊ตฌ๊ธ ๋ด๋ถ์์ ์ด ๊ณผ์ ์ ์ด๋ค์์ผ๋ก ์ด์ํด์๋์ง์ ๋ํ ๋ฐฐ๊ฒฝ ์ ๋ณด์ ๋๋ค.
์์ ์ ๋ณดํต ์ค์ 9์๋ถํฐ ์คํ 4์๊น์ง ์งํ๋๋ฉฐ, ์ค๊ฐ์ 1์๊ฐ์ ์ ์ฌ์๊ฐ์ ์ ๊ณตํฉ๋๋ค. ๋ฐ๋ผ์ ์ค์ ์์ ์ด 3์๊ฐ, ์คํ ์์ ์ด 3์๊ฐ์ ๋๋ค. ๋ ์ธ์ ์๋ ์ฌ๋ฌ ๋ฒ์ ํด์ ์๊ฐ ๋ฐ ํ์๋ค์ด ์ฐ์ต๋ฌธ์ ๋ฅผ ํ ์ ์๋ ์๊ฐ์ด ํฌํจ๋์ด ์์ต๋๋ค.
๊ฐ์๋ฅผ ์คํํ๊ธฐ ์ํ ์ค๋น:
-
๊ฐ์ ์๋ฃ๋ฅผ ์์งํฉ๋๋ค. ์ฃผ์ ์์ ์ ๊ฐ์กฐํ๊ธฐ ์ํด ๊ฐ์ ์ฐธ์กฐ ๋ ธํธ๋ฅผ ํฌํจํ์์ต๋๋ค. (์ถ๊ฐ์ ์ธ ๋ ธํธ๋ฅผ ์์ฑํ์ฌ ์ ๊ณตํด ์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.) ๊ฐ์ ์ฐธ์กฐ ๋ ธํธ์ ๋งํฌ๋ฅผ ๋๋ฅด๋ฉด ๋ณ๋์ ํ์ ์ผ๋ก ๋ถ๋ฆฌ๊ฐ ๋๋ฉฐ, ๋ฉ์ธ ํ๋ฉด์์๋ ์ฌ๋ผ์ง๋๋ค. ๊น๋ํ ํ๋ฉด์ผ๋ก ๊ฐ์๋ฅผ ์งํํ ์ ์์ต๋๋ค.
-
Decide on the dates. Since the course takes four days, we recommend that you schedule the days over two weeks. Course participants have said that they find it helpful to have a gap in the course since it helps them process all the information we give them.
-
์ถฉ๋ถํ ๊ณต๊ฐ์ ํ๋ณดํฉ๋๋ค. 15์์ 20๋ช ๊ท๋ชจ์ ๊ณต๊ฐ์ ์ถ์ฒํฉ๋๋ค. ์๊ฐ์๊ณผ ๊ฐ์ฌ๊ฐ ์ง์๋ฅผ ํ๊ธฐ์ ์ถฉ๋ถํ ์๊ฐ๊ณผ ๊ณต๊ฐ์ด์ด์ผ ํฉ๋๋ค. ๊ฐ์ฌ๋ ์๊ฐ์ ๋ชจ๋ _์ฑ ์_์ ์ฌ์ฉํ ์ ์๋ ๊ฐ์์ค์ด๋ฉด ์ข์ต๋๋ค. ๊ฐ์ ์ค์ ๊ฐ์ฌ๊ฐ ๋ผ์ด๋ธ ์ฝ๋ฉ์ ํ๊ฒ ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ผ๋ฉฐ, ์ด๋ ์๋ฆฌ์ ์์ ๋ ธํธ๋ถ์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋์์ด ๋ฉ๋๋ค.
-
๊ฐ์ ๋น์ผ ์กฐ๊ธ ์ผ์ฐ ์์ ์ค๋นํฉ๋๋ค. ๊ฐ์ฌ ๋ ธํธ๋ถ์์
mdbook serve
๋ฅผ ์ด์ฉํด ์ง์ ํ๋ ์ ํ ์ด์ ํ๋ฉด ํ์ด์ง ์ด๋ ์์ ์ง์ฐ์ด ์์ต๋๋ค.(์ค์น ๋ฐฉ๋ฒ์ ์ฐธ์กฐํ์ธ์.) ๋ํ, ๊ทธ๋ ๊ฒ ํ๋ฉด ๊ฐ์ ๋์ค ์คํ๋ฅผ ๋ฐ๊ฒฌํ์ ๋ ๊ทธ ์๋ฆฌ์์ ๋ฐ๋ก ์์ ๊ฐ๋ฅํ๋ค๋ ์ฅ์ ๋ ์์ต๋๋ค. -
์๊ฐ์๋ค์ด ์ง์ (๊ฐ๋ณ ํน์ ๊ทธ๋ฃน์ผ๋ก) ์ฐ์ต๋ฌธ์ ๋ฅผ ํ๋๋ก ํฉ๋๋ค. ๋์ฒด๋ก ์ค์ , ์คํ์ ๊ฐ๊ฐ 30-45๋ถ ์ ๋๋ฅผ ์ฐ์ต๋ฌธ์ ์ ํ ๋นํฉ๋๋ค (์ด๋ ํด๋ต์ ๋ณด๊ณ ์ค๋ช ํ๋ ์๊ฐ๊น์ง ํฌํจํฉ๋๋ค). ๋งํ ๋์์ ํ์๋ก ํ๋ ์๊ฐ์์ด ์๋์ง ์์๋ก ํ์ธํฉ๋๋ค. ๋ง์ฝ ๊ฐ์ ๋ฌธ์ ๋ฅผ ์ฌ๋ฌ ์ฌ๋์ด ๊ฒช๊ณ ์๋ค๋ฉด, ๊ทธ ๋ฌธ์ ๋ฅผ ๊ฐ์์ค ์ ์ฒด ์ธ์์๊ฒ ์๋ฆฌ๊ณ ํด๊ฒฐ์ฑ ์ ์ ์ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ด๋์ ๊ฐ๋ฉด ๊ทธ ๋ฌธ์ ์ ๋ํ ํด๋ต์ ์ฐพ์ ์ ์๋์ง ์๋ ค ์ค๋๋ค.
์ด์ ์ค๋น๋ ๋๋ฌ์ต๋๋ค. ์ฐ๋ฆฌ๊ฐ ๊ทธ๋ฌ๋ฏ์ด ์ฌ๋ฌ๋ถ๋ค๋ ์ด ๊ฐ์๋ฅผ ์ฆ๊ธฐ์๊ธธ ๋ฐ๋๋๋ค!
๊ฐ์๋ฅผ ๊ณ์ ๊ฐ์ ํ ์ ์๋๋ก ํผ๋๋ฐฑ์ ์ ๊ณตํด ์ฃผ์ญ์์ค. ์ฐ๋ฆฌ๋ ๋ฌด์์ด ์ข์๊ณ , ๋ฌด์์ด ๋ชจ์๋๋์ง ๋ฃ๊ณ ์ถ์ต๋๋ค. ์๊ฐ์๋ค๋ก ๋ถํฐ์ ํผ๋๋ฐฑ๋ ํ์ํฉ๋๋ค!
๊ฐ์ ๊ตฌ์ฑ
๊ฐ์ฌ๋ฅผ ์ํ ์๋ด ํ์ด์ง์ ๋๋ค.
Rust ๊ธฐ์ด
์ฒซ 4์ผ์ Rust ๊ธฐ์ด๋ก ์ด๋ฃจ์ด์ง๋ฉฐ ์งง์ ์๊ฐ ์์ ๋ง์ ๋ด์ฉ์ ๋ค๋ฃจ๊ฒ ๋ฉ๋๋ค.
Course schedule:
- Day 1 Morning (2 hours and 10 minutes, including breaks)
Segment | Duration |
---|---|
๊ฐ์ | 5 minutes |
Hello World! | 15 minutes |
ํ์ ๋ฐ ๊ฐ | 45 minutes |
ํ๋ฆ ์ ์ด | 40 minutes |
- Day 1 Afternoon (2 hours and 15 minutes, including breaks)
Segment | Duration |
---|---|
ํํ ๋ฐ ๋ฐฐ์ด | 35 minutes |
์ฐธ์กฐ | 35 minutes |
์ฌ์ฉ์ ์ ์ ํ์ | 50 minutes |
- Day 2 Morning (2 hours and 55 minutes, including breaks)
Segment | Duration |
---|---|
๊ฐ์ | 3 minutes |
ํจํด ๋งค์นญ | 1 hour |
๋ฉ์๋์ ํธ๋ ์ดํธ | 50 minutes |
์ ๋ค๋ฆญ | 40 minutes |
- Day 2 Afternoon (3 hours and 10 minutes, including breaks)
Segment | Duration |
---|---|
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ | 1 hour and 20 minutes |
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ | 1 hour and 40 minutes |
- Day 3 Morning (2 hours and 20 minutes, including breaks)
Segment | Duration |
---|---|
๊ฐ์ | 3 minutes |
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ | 1 hour |
์ค๋งํธ ํฌ์ธํฐ | 55 minutes |
- Day 3 Afternoon (2 hours and 10 minutes, including breaks)
Segment | Duration |
---|---|
๋น๋ฆผ | 50 minutes |
์๋ช | 1 hour and 10 minutes |
- Day 4 Morning (2 hours and 40 minutes, including breaks)
Segment | Duration |
---|---|
๊ฐ์ | 3 minutes |
Iterators | 45 minutes |
๋ชจ๋ | 40 minutes |
ํ ์คํธ | 45 minutes |
- Day 4 Afternoon (2 hours and 10 minutes, including breaks)
Segment | Duration |
---|---|
์ค๋ฅ์ฒ๋ฆฌ | 55 minutes |
์์ ํ์ง ์์ ๋ฌ์คํธ | 1 hour and 5 minutes |
์ฌํ ํ์ต
In addition to the 4-day class on Rust Fundamentals, we cover some more specialized topics:
Rust in Android
The Rust in Android deep dive is a half-day course on using Rust for Android platform development. This includes interoperability with C, C++, and Java.
AOSP ์ฝ๋๋ฅผ ์ฌ๋ฌ๋ถ์ ์ปดํจํฐ์ ์ฒดํฌ์์ํด์ผ ํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์, ๊ทธ ์ปดํจํฐ์์ ๊ณผ์ ์ ์ฅ์๋ฅผ ์ฒดํฌ์์ํ๊ณ src/android/
๋๋ ํฐ๋ฆฌ๋ฅผ AOSP ์ฝ๋์ ๋ฃจํธ๋ก ์ด๋ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์๋๋ก์ด๋ ๋น๋ ์์คํ
์์ ๊ณผ์ ์ฉ์ผ๋ก ์ถ๊ฐ๋ Android.bp
ํ์ผ์ ์ธ์ํ ์ ์์ต๋๋ค.
adb sync
๋ช
๋ ์ด๊ฐ ์๋ฎฌ๋ ์ดํฐ ํน์ ์ค์ ์ฅ์น์ ์๋ํ๋์ง ํ์ธํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ src/android/build_all.sh
๋ฅผ ์ํํด์ ๋ชจ๋ ์๋๋ก์ด๋ ์์ ๋ฅผ ๋ฏธ๋ฆฌ ๋น๋ํด ๋ณด์ธ์. ๊ทธ ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฝ๊ณ , ๊ทธ ์์์ ์ํ๋๋ ๋ช
๋ น์ด๋ค์ ํ์ธํ ํ ๊ฐ ๋ช
๋ น์ด๋ค์ ์๋์ผ๋ก ์คํํด๋ ์ ๋๋์ง ํ์ธํ์ธ์.
Chromium์์ Rust ์ฌ์ฉ
Chromium์์ Rust ์ฌ์ฉ ์ด ๊ณผ์ ์ Chromium ๋ธ๋ผ์ฐ์ ์ ์ผ๋ถ๋ก Rust๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๊ดํ ๋ฐ๋์ ๊ณผ์ ์ ๋๋ค. ์ฌ๊ธฐ์๋ Chromium์ gn ๋น๋ ์์คํ ์์ Rust๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๊ณผ ์๋ ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (โcratesโ)๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ, C++ ์ํธ ์ด์ฉ์ฑ ๋ฑ์ด ํฌํจ๋์ด ์์ต๋๋ค.
Chromium์ ๋น๋ํ ์ ์์ด์ผ ํฉ๋๋ค. ๋๋ฒ๊ทธ, ๊ตฌ์ฑ์์ ๋น๋๋ ์๋๋ฅผ ๋์ด๊ธฐ ์ํด ๊ถ์ฅ๋์ง๋ง ๋ชจ๋ ๋น๋๊ฐ ์๋ํฉ๋๋ค. ๋น๋ํ Chromium ๋ธ๋ผ์ฐ์ ๋ฅผ ์คํํ ์ ์๋์ง ํ์ธํฉ๋๋ค.
Bare-Metal Rust
The Bare-Metal Rust deep dive is a full day class on using Rust for bare-metal (embedded) development. Both microcontrollers and application processors are covered.
๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ ํํธ๋ฅผ ์งํํ๊ธฐ ์ํด์๋ BBC micro:bit v2 ๊ฐ๋ฐ ๋ณด๋๋ฅผ ๋ฏธ๋ฆฌ ๊ตฌ๋งคํด์ผ ํฉ๋๋ค. ๋ชจ๋ ์ฌ์ฉ์๋ ์์ ํ์ด์ง์ ์ค๋ช ๋ ๋๋ก ๊ฐ์ข ํจํค์ง๋ฅผ ์ค์นํด์ผ ํฉ๋๋ค.
Concurrency in Rust
The Concurrency in Rust deep dive is a full day class on classical as well as async
/await
concurrency.
์ ํฌ๋ ์ดํธ๋ฅผ ์ค์ ํ๊ณ ๋ช ๊ฐ์ง ์์กด์ฑ์ ๋ค์ด๋ก๋ํด ๋์ด์ผ ํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ์์ ๋ฅผ src/main.rs
์ ๋ณต์ฌ/๋ถ์ฌ๋ฃ๊ธฐ ํ์ฌ ํ
์คํธ ํด ๋ณผ ์ ์์ต๋๋ค:
cargo init concurrency
cd concurrency
cargo add tokio --features full
cargo run
๊ฐ์ ํ์
์ด ๊ฐ์๋ ๊ฐ์ฌ์ ์๊ฐ์์ด ์๋ฐฉํฅ์ผ๋ก ์ํตํ๋ฉด์ ์งํํ๋๋ก ๋์์ธ ๋์์ต๋๋ค. ๋ค์ํ ์ง๋ฌธ์ ํตํด ๋ฌ์คํธ์ ์ฌ๋ฌ ๋ถ๋ถ์ ํํํ ์ ์๋๋ก ํ์ธ์!
๋จ์ถํค
๋ค์์, mdBook ์์คํ (ํ ์ฌ์ดํธ)์์ ์ ์ฉํ ๋จ์ถํค๋ค ์ ๋๋ค:
- ์ผ์ชฝ ํ์ดํ: ์ด์ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
- ์ค๋ฅธ์ชฝ ํ์ดํ: ๋ค์ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
- Ctrl + Enter: ํ์ฌ ํฌ์ปค์ค๋ฅผ ๋ฐ์ ์ฝ๋ ์ํ ๋ธ๋ก์ ์คํํฉ๋๋ค.
- s: ๊ฒ์์ฐฝ์ ํ์ฑํํฉ๋๋ค.(mdBook ๋ฌธ์ ๋ก 23.01.19 ๊ธฐ์ค ์์ด๋ก๋ง ๊ฐ๋ฅํฉ๋๋ค.)
๋ค๋ฅธ ์ธ์ด๋ค
์ด ๊ณผ์ ์ ๋ค๋ฅธ ์ธ์ด๋ก๋ ์ ๊ณต๋ฉ๋๋ค. ๊ดํธ ์์ ๋ฒ์ญ์ ๋์ ์ฃผ์ ๋ถ๋ค์ ๋๋ค:
- Brazilian Portuguese by @rastringer, @hugojacob, @joaovicmendes, and @henrif75.
- Chinese (Simplified) by @suetfei, @wnghl, @anlunx, @kongy, @noahdragon, @superwhd, @SketchK, and @nodmp.
- Chinese (Traditional) by @hueich, @victorhsieh, @mingyc, @kuanhungchen, and @johnathan79717.
- Korean by @keispace, @jiyongp, @jooyunghan, and @namhyung.
- Spanish by @deavid.
ํ์ด์ง ์ค๋ฅธ์ชฝ ์์ ๋ฉ๋ด๋ฅผ ํตํด ๋ค๋ฅธ ์ธ์ด๋ก ์ ํํ ์ ์์ต๋๋ค.
๋ฒ์ญ ๋ฌธ์
์งํ ์ค์ธ ๋ฒ์ญ์ด ๋ง์ต๋๋ค. ์ต๊ทผ์ ์ ๋ฐ์ดํธ๋ ๋ฒ์ญ๋ณธ์ผ๋ก ์ฐ๊ฒฐ๋๋ ๋งํฌ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ๋ฒต๊ฐ์ด: @raselmandol ์ ๊ณต.
- ํ๋์ค์ด: @KookaS ๋ฐ @vcaen ์ ๊ณต.
- ๋ ์ผ์ด: @Throvn ๋ฐ @ronaldfw ์ ๊ณต.
- ์ผ๋ณธ์ด: @CoinEZ-JPN ๋ฐ @momotaro1105 ์ ๊ณต.
- Italian by @henrythebuilder and @detro.
์ด ๊ณผ์ ์ ๋ฒ์ญ ์์ ์ ๋์์ ์ฃผ๊ณ ์ถ๋ค๋ฉด ์ฌ๊ธฐ ์ค๋ช ๋ ๋ด์ฉ์ ์ฐธ๊ณ ํ์ธ์. ์งํ ์ค์ธ ๋ฒ์ญ ์์ ์ ๋ํ ๋ด์ฉ์ ์ด์ ํธ๋์ปค๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์นด๊ณ ์ฌ์ฉํ๊ธฐ
๋ฌ์คํธ๋ฅผ ์์ํ๋ ค๊ณ ํ๋ฉด ๋น์ ์ ๊ณง Cargo๋ผ๋, ๋ฌ์คํธ ์ํ๊ณ์์ ์ฌ์ฉํ๋ ํ์ค ๋น๋/์คํ ๋๊ตฌ๋ฅผ ๋ง๋ ๊ฒ ์ ๋๋ค. ์ฌ๊ธฐ์๋ ์นด๊ณ ๊ฐ ๋ฌด์์ธ์ง, ๊ทธ๋ฆฌ๊ณ ์นด๊ณ ๊ฐ ๋ฌ์คํธ ์ํ๊ณ์์ ์ด๋ค ์ญํ ์ ํ๋์ง, ๊ทธ๋ฆฌ๊ณ ์ด ๊ฐ์์์ ์ด๋ป๊ฒ ์ฌ์ฉ๋ ์ง์ ๋ํด ๊ฐ๋ตํ ์ค๋ช ํ๊ฒ ์ต๋๋ค.
์ค์นํ๊ธฐ
https://rustup.rs/์ ์ค์น ๋ฐฉ๋ฒ์ ๋ฐ๋ฅด์ธ์.
This will give you the Cargo build tool (cargo
) and the Rust compiler (rustc
). You will also get rustup
, a command line utility that you can use to install to different compiler versions.
Rust๋ฅผ ์ค์นํ ํ์๋ Rust์ ํธํ๋๋๋ก ํธ์ง๊ธฐ๋ IDE๋ฅผ ๊ตฌ์ฑํด์ผ ํฉ๋๋ค. ๋๋ถ๋ถ์ ํธ์ง๊ธฐ๋ VS Code, Emacs, Vim/Neovim ๋ฑ์ ์๋ ์์ฑ ๋ฐ ์ ์๋ก ์ด๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ rust-analyzer์ ํต์ ํ์ฌ ์ด๋ฅผ ์คํํฉ๋๋ค. RustRover๋ผ๋ ๋ค๋ฅธ IDE๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
-
๋ฐ๋น์/์ฐ๋ถํฌ ์์คํ ์์๋
apt
๋ฅผ ์ด์ฉํด์ ์นด๊ณ , ๋ฌ์คํธ ์์ค, ๋ฌ์คํธ ํฌ๋งคํฐ๋ฅผ ์ค์นํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ด ๋ฐฉ๋ฒ์ ๋ฐ๋ฅผ ๊ฒฝ์ฐ ์ต์ ๋ฒ์ ์ด ์๋ ๋ฌ์คํธ๋ฅผ ์ฌ์ฉ๊ฒ๋๋ฉฐ, ๊ทธ ๊ฒฐ๊ณผ ์์์น ๋ชปํ ๋ฌธ์ ๋ฅผ ๊ฒช์ ์๋ ์์ต๋๋ค. ์ค์น ๋ช ๋ น์ด๋ ์๋์ ๊ฐ์ต๋๋ค:sudo apt install cargo rust-src rustfmt
๋ฌ์คํธ ์ํ๊ณ
๋ฌ์คํธ์ ์ํ๊ณ๋ ์ฌ๋ฌ๊ฐ์ง ๋๊ตฌ๋ค๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ, ๊ทธ ์ค ์ค์ํ ๊ฒ๋ค์ ์๋์ ๊ฐ์ต๋๋ค:
-
rustc
:.rs
ํ์ฅ์ ํ์ผ์ ๋ฐ์ด๋๋ฆฌ ํน์ ๋ค๋ฅธ ์ค๊ฐ ํ์์ผ๋ก ๋ณํํด์ฃผ๋ Rust ์ปดํ์ผ๋ฌ์ ๋๋ค. -
cargo
: ๋ฌ์คํธ ์์กด์ฑ ๊ด๋ฆฌ์์ด์ ๋น๋ ์์คํ ์ ๋๋ค. ์ฌ๋ฌ๋ถ์ ํ๋ก์ ํธ์ ๋ช ์๋ ์์กด์ฑ๋ค์ https://crates.io์์ ์๋์ผ๋ก ๋ค์ด๋ก๋ ๋ฐ๊ณ , ๊ทธ ์์ค์ฝ๋๋ฅผrustc
๋ก ์ ๋ฌํ์ฌ ๋น๋๋ฅผ ์ํต๋๋ค. ๋ํ ์ ๋ ํ ์คํธ๋ฅผ ์คํํ๋ ํ ์คํธ ๋ฌ๋๋ฅผ ๋ด์ฅํ๊ณ ์์ต๋๋ค. -
rustup
: the Rust toolchain installer and updater. This tool is used to install and updaterustc
andcargo
when new versions of Rust are released. In addition,rustup
can also download documentation for the standard library. You can have multiple versions of Rust installed at once andrustup
will let you switch between them as needed.
ํค ํฌ์ธํธ:
-
๋ฌ์คํธ๋ 6์ฃผ๋ง๋ค ์๋ก์ด ๋ฆด๋ฆฌ์ฆ๊ฐ ๋ฐํ๋๋ฉฐ ์ด์ ๋ฆด๋ฆฌ์ฆ์์ ํธํ์ฑ์ ์ ์งํ๊ณ ์์ต๋๋ค.
-
๋ฆด๋ฆฌ์ฆ๋ 3๊ฐ์ง ๋ฒ์ ์ผ๋ก ์ ๊ณต๋ฉ๋๋ค: โstableโ, โbetaโ ๊ทธ๋ฆฌ๊ณ โnightlyโ.
-
์๋ก์ด ๊ธฐ๋ฅ์ โnightlyโ -> โbetaโ -(6์ฃผ ํ)-> โstableโ ๋ก ๋ณ๊ฒฝ๋ฉ๋๋ค.
-
์์กด์ฑ์ ๋ค์ํ ์ ์ฅ์, git ํ๋ก์ ํธ, ๋๋ ํฐ๋ฆฌ ๋ฑ์์ ์ ๊ณต๋ ์ ์์ต๋๋ค.
-
๋ฌ์คํธ๋ ์๋์ ์ผ๋ก ๊ตฌ๋ถ๋ฉ๋๋ค. ํ์ฌ๋ Rust 2021 ์๋์ ์ ๋๋ค. ์ด ์ ์๋์ ์ผ๋ก Rust 2015์ Rust 2018์ด ์์ต๋๋ค.
-
์๋์ ์ ์ด์ ์๋์ ๊ณผ ํธํ์ด ๋์ง ์์ ์ ์์ต๋๋ค.
-
์๋์ ์ด ๋ฐ๋๋ฉด์ ํ๋ก๊ทธ๋จ์ด ์๋์น ์๊ฒ ๊นจ์ง๋ ๋ฌธ์ ๋ฅผ ๋ง๊ธฐ ์ํด, ๊ฐ ํ๋ก๊ทธ๋จ์ ์์ ์ด ๋น๋๋ ์๋์ ์ ๋ช ์์ ์ผ๋ก
Cargo.toml
์ ์ง์ ํด์ผ ํฉ๋๋ค. -
๋ฌ์คํธ ์ํ๊ณ๊ฐ ์๋์ ๋ณ๋ก ํํธํ ๋๋ ๊ฒ์ ๋ง๊ธฐ ์ํด, ๋ฌ์คํธ ์ปดํ์ผ๋ฌ๋ ์๋ก ๋ค๋ฅธ ์๋์ ์์ ์์ฑ๋ ์ฝ๋๋ค์ ํ๋์ ๋ฐ์ด๋๋ฆฌ๋ก ๋ฌถ์ ์ ์์ต๋๋ค.
-
cargo
๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์ปดํ์ผ๋ฌ๋ฅผ ์ง์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๋ ๊ฑฐ์ ์์์ ์ธ๊ธํด ์ฃผ์๊ธฐ ๋ฐ๋๋๋ค. -
It might be worth alluding that Cargo itself is an extremely powerful and comprehensive tool. It is capable of many advanced features including but not limited to:
- ํ๋ก์ ํธ/ํจํค์ง ๊ตฌ์กฐํ
- ์ํฌ์คํ์ด์ค
- ๊ฐ๋ฐ/๋ฐํ์ ์ข ์์ฑ ๊ด๋ฆฌ ๋ฐ ์บ์ฑ
- ๋น๋ ์คํฌ๋ฆฝํธ
- ์ ์ญ ์ค์น
- cargo clippy์ ๊ฐ์ ํ์ ํ๋ฌ๊ทธ์ธ์ผ๋ก ํ์ฅ ๊ฐ๋ฅ.
-
๊ณต์ Cargo Book์์ ์์ธํ ์ฌํญ์ ํ์ธํ์๊ธฐ ๋ฐ๋๋๋ค.
-
๊ฐ์์์์ ์ฝ๋ ์ํ
์ด ๊ฐ์์๋ฃ์ ์๋ ๋ชจ๋ ์์ ๋ ๋ธ๋ผ์ฐ์ ์์ ๋ฐ๋ก ์ํ ๊ฐ๋ฅํฉ๋๋ค. ์ด๋ ๊ฒ ํ ์ด์ ๋, ์ค๋น ๊ณผ์ ์ ๋จ์ํ ์ํค๊ณ , ๋ชจ๋๊ฐ ๊ฐ์ ํ๊ฒฝ์์ ์์ ํ ์ ์๋๋ก ํ๊ธฐ ์ํจ์ ๋๋ค.
๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ , ์นด๊ณ (cargo)๋ฅผ ์ง์ ์ค์นํ๋ ๊ฒ์ ๊ฐ๋ ฅ ๊ถ์ฅํฉ๋๋ค. ์ด๊ฒ ๊ณผ์ ์์ฑ์ ๋ ๋์์ด ๋ ๊ฒ๋๋ค. ๋ํ, ๋ง์ง๋ง ๋ ์๋ ์์กด์ฑ์ด ์๋ ์์ ๋ฅผ ์์ ํ๊ฒ ๋ ํ ๋ฐ, ๊ทธ ๋์๋ ์ด์ฐจํผ ์นด๊ณ ๊ฐ ํ์ํฉ๋๋ค.
์ด ๊ฐ์ ์๋ฃ์ ์ฝ๋ ๋ธ๋ก๋ค์ ์ ๋ถ ์ธํฐ์ํฐ๋ธ ํฉ๋๋ค:
fn main() { println!("์์ ํด ์ฃผ์ธ์!"); }
์ฝ๋ ๋ธ๋ก์ ํฌ์ปค์ค๋ฅผ ๋๊ณ Ctrl + Enter๋ฅผ ๋๋ฌ ์คํํด ๋ณผ ์ ์์ต๋๋ค.
๊ฐ์์์ ๋๋ถ๋ถ์ ์ฝ๋ ์ํ์ ์์ ๊ฐ์ด ์์ ํ ์ ์์ง๋ง ์ผ๋ถ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก ์์ ํ ์ ์์ต๋๋ค:
-
์ ๋ ํ ์คํธ๋ ๋ด์ฅ ํ๋ ์ด๊ทธ๋ผ์ด๋์์ ์คํ์ด ์๋ฉ๋๋ค. ์ธ๋ถ ํ๋ ์ด๊ทธ๋ผ์ด๋ ์ฌ์ดํธ์ ๋ถ์ฌ๋ฃ์ด ํ ์คํธ๋ฅผ ์คํํ์๊ธฐ ๋ฐ๋๋๋ค.
-
๋ด์ฅ๋ ํ๋ ์ด๊ทธ๋ผ์ด๋์์๋ ํ์ด์ง ์ด๋์ ์์ฑ๋ ๋ชจ๋ ๋ด์ฉ์ด ์ฌ๋ผ์ง๋๋ค. ๋ฐ๋ผ์ ๋ก์ปฌ ํ๊ฒฝ์ด๋ ์ธ๋ถ ํ๋ ์ด๊ทธ๋ผ์ด๋ ์ฌ์ดํธ์์ ์ฐ์ต๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
๋ก์ปฌ ํ๊ฒฝ์ ์นด๊ณ
๋ง์ฝ ๊ฐ์ธ์ฉ ์ปดํจํฐ์์ ์ฝ๋๋ฅผ ์คํํด๋ณด๋ ค๋ฉด ๋จผ์ ๋ฌ์คํธ๋ฅผ ์ค์นํด์ผ ํฉ๋๋ค. Rust Book์ ์ง์นจ์ ๋ฐ๋ผ rustc
์ cargo
๋ฅผ ํจ๊ป ์ค์น ํ์๊ธฐ ๋ฐ๋๋๋ค. ์ค์น ํ ์๋ ์ปค๋งจ๋๋ฅผ ํตํด ๊ฐ ํด์ ๋ฒ์ ์ ํ์ธ ํ ์ ์์ต๋๋ค:
% rustc --version
rustc 1.69.0 (84c898d65 2023-04-16)
% cargo --version
cargo 1.69.0 (6e9a83356 2023-04-12)
์ด ๋ฒ์ ๋ณด๋ค ๋ ์ต์ ์ ๋ฒ์ ์ด์ด๋ ์๊ด ์์ต๋๋ค. ๋ฌ์คํธ๋ ํ์ ํธํ์ฑ์ ์ง์ํฉ๋๋ค.
์ ์์ ์ผ๋ก ์ค์น๊ฐ ๋์์ผ๋ฉด, ๊ฐ์ ์์ ์ค ํ๋๋ฅผ ๋ฌ์คํธ ๋ฐ์ด๋๋ฆฌ๋ก ๋น๋ํด ๋ด ์๋ค:
-
์์ ๋ธ๋ก์ ์๋ โCopy to clipboardโ ๋ฒํผ์ ํด๋ฆญํด์ ๋ณต์ฌํฉ๋๋ค.
-
ํฐ๋ฏธ๋์์
cargo new exercise
๋ฅผ ์ ๋ ฅํด์ ์๋ก์ดexercise/
ํด๋๋ฅผ ๋ง๋ญ๋๋ค:$ cargo new exercise Created binary (application) `exercise` package
-
exercise/
ํด๋๋ก ์ด๋ํ ํ,cargo run
์ปค๋งจ๋๋ก ์ฝ๋๋ฅผ ์คํํฉ๋๋ค:$ cd exercise $ cargo run Compiling exercise v0.1.0 (/home/mgeisler/tmp/exercise) Finished dev [unoptimized + debuginfo] target(s) in 0.75s Running `target/debug/exercise` Hello, world!
-
src/main.rs
์ ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค. ์๋ฅผ ๋ค์ด ์ด์ ํ์ด์ง์ ์์ค๋ฅผ ์๋์ ๊ฐ์ดsrc/main.rs
์ ์์ฑํฉ๋๋คfn main() { println!("์์ ํด ์ฃผ์ธ์!"); }
-
cargo run
์ปค๋งจ๋๋ก ์์ค๋ฅผ ๋น๋ํ๊ณ ์คํํฉ๋๋ค:$ cargo run Compiling exercise v0.1.0 (/home/mgeisler/tmp/exercise) Finished dev [unoptimized + debuginfo] target(s) in 0.24s Running `target/debug/exercise` Edit me!
-
cargo check
์ปค๋งจ๋๋ ๋น ๋ฅด๊ฒ ์๋ฌ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.cargo build
๋ ์คํ์์ด ์ปดํ์ผ๋ง ํฉ๋๋ค. ์ด ๊ฒฝ์ฐ์target/debug/
ํด๋์์ output์ ํ์ธ ํ ์ ์์ต๋๋ค.cargo build --release
์ปค๋งจ๋๋ ๋ฆด๋ฆฌ์ฆ ๋ฒ์ ์ฉ ์ต์ ํ๋ฅผ ์ผ์ ์ปดํ์ผํ๋ฉฐtarget/release/
ํด๋์์ ํ์ธ ํ ์ ์์ต๋๋ค. -
Cargo.toml
ํ์ผ์๋ ์์กด์ฑ ํจํค์ง๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.cargo
์ปค๋งจ๋๋ฅผ ์คํํ๋ฉด ์๋์ผ๋ก ์์กด์ฑ ํจํค์ง๋ฅผ ๋ค์ด๋ก๋ํ๊ณ ์ปดํ์ผ ๊น์ง ํด ์ค๋๋ค.
์๊ฐ์๋ค์ด ์นด๊ณ ๋ฅผ ์ค์นํ๊ณ ๋ก์ปฌ ํธ์ง๊ธฐ๋ฅผ ์ด์ฉํ๋๋ก ๋ ๋ คํ์ธ์. ์กฐ๊ธ ๊ท์ฐฎ์ ์๋ ์์ง๋ง, ์ด๋ ๊ฒ ํด์ผ๋ง ์ข ๋ ์ค์ ์ ๊ฐ๊น์ด ๊ฐ๋ฐํ๊ฒฝ์ ๊ฐ์ถ๊ฒ ๋๋ ๊ฒ์ ๋๋ค.
1์ผ์ฐจ ๊ฐ์
๊ฐ์ ์ฒซ ๋ ์ ๋๋ค. ์ค๋ ๋ฐฐ์ธ ๊ฒ์ด ์ฐธ ๋ง์ต๋๋ค:
- ๋ฌ์คํธ ๊ธฐ๋ณธ ๋ฌธ๋ฒ: ๋ณ์, ์ค์นผ๋ผ / ๋ณตํฉ ํ์ , ์ด๊ฑฐํ, ๊ตฌ์กฐ์ฒด, ์ฐธ์กฐํ, ํจ์์ ๋ฉ์๋.
- Types and type inference.
- ์ ์ด ํ๋ฆ์ ๋ฃจํ, ์กฐ๊ฑด๋ฌธ ๋ฑ์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
- ์ฌ์ฉ์ ์ ์ ํ์ : ๊ตฌ์กฐ์ฒด ๋ฐ enum
- ํจํด ๋งค์นญ: ์ด๊ฑฐํ, ๊ตฌ์กฐ์ฒด ๊ทธ๋ฆฌ๊ณ ๋ฐฐ์ด ๋ถํด.
์ผ์ ์์ฝ
Including 10 minute breaks, this session should take about 2 hours and 10 minutes. It contains:
Segment | Duration |
---|---|
๊ฐ์ | 5 minutes |
Hello World! | 15 minutes |
ํ์ ๋ฐ ๊ฐ | 45 minutes |
ํ๋ฆ ์ ์ด | 40 minutes |
ํ์๋ค์๊ฒ ๋ค์์ ์๊ธฐ์์ผ ์ฃผ์๊ธฐ ๋ฐ๋๋๋ค:
- ๊ถ๊ธํ ์ ์ด ์์ผ๋ฉด ์ฃผ์ ํ์ง ๋ง๊ณ ์ง๋ฌธ ํด์ผ ํฉ๋๋ค.
- ์ด ์์
์ ์ํธ์์ฉ์ด ์ํฉ๋๋ค. ํ ๋ก ์ ๋ง์ค์ด์ง ๋ง์ธ์!
- As an instructor, you should try to keep the discussions relevant, i.e., keep the discussions related to how Rust does things vs some other language. It can be hard to find the right balance, but err on the side of allowing discussions since they engage people much more than one-way communication.
- ์ง๋ฌธ์ด ์ฌ๋ผ์ด๋๋ณด๋ค ์์๊ฐ๋ ๊ด์ฐฎ์ต๋๋ค.
- ํ์ต์ ์์ด์ ๋ฐ๋ณต์ ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ฌ๋ผ์ด๋๋ ๊ทธ์ ๋์์ ์ค ๋ฟ, ์ํ๋ ๋๋ก ๊ฑด๋๋์ด๋ ๋ฉ๋๋ค.
์ฒซ๋ ์๋ ๋ค๋ฅธ ์ธ์ด์์๋ ์ง์ ์ ์ธ ๊ด๋ จ์ด ์๋ Rust์ โ๊ธฐ๋ณธ์ฌํญโ์ ๋ณด์ฌ๋๋ฆฌ๊ณ ์ ํฉ๋๋ค. Rust์ ๊ณ ๊ธ ๋ถ๋ถ์ ๊ทธ๋ค์์ ์ ๊ณต๋ฉ๋๋ค.
๊ต์ค์์ ๊ฐ๋ฅด์น๋ ๊ฒฝ์ฐ ์ฌ๊ธฐ์์ ์ผ์ ์ ๊ฒํ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๊ฐ ์ธ๊ทธ๋จผํธ๊ฐ ๋๋๋ฉด ์ฐ์ต๋ฌธ์ ๊ฐ ์๊ณ ๊ทธ ๋ค์ ํด์์ด ์ด์ด์ง๋๋ค. ํด์ ํ ์ฐ์ต๋ฌธ์ ํด๋ต์ ๋ค๋ฃฐ ๊ณํ์ ๋๋ค. ์ฌ๊ธฐ์ ํ์๋ ์๊ฐ์ ๊ณผ์ ์ ์ผ์ ์ ๋ง๊ฒ ์งํํ๊ธฐ ์ํ ๊ถ์ฅ ์๊ฐ์ ๋๋ค. ํ์์ ๋ฐ๋ผ ์ ์ฐํ๊ฒ ์กฐ์ ํ์๊ธฐ ๋ฐ๋๋๋ค.
Hello World!
This segment should take about 15 minutes. It contains:
Slide | Duration |
---|---|
๋ฌ์คํธ๋? | 10 minutes |
Rust์ ์ด์ | 3 minutes |
ํ๋ ์ด๊ทธ๋ผ์ด๋ | 2 minutes |
๋ฌ์คํธ๋?
๋ฌ์คํธ๋ 2015๋ ์ ๋ฒ์ 1.0์ ๋ฆด๋ฆฌ์ฆ ํ ์๋ก์ด ํ๋ก๊ทธ๋จ ์ธ์ด์ ๋๋ค:
- ๋ฌ์คํธ๋ C++์ ์ ์ฌํ ์ ์ ์ปดํ์ผ ์ธ์ด์
๋๋ค
rustc
๋ LLVM์ ๋ฐฑ์๋๋ก ์ฌ์ฉํฉ๋๋ค.
- ๋ฌ์คํธ๋ ๋ค์ํ ํ๋ซํผ๊ณผ ์ํคํ
์ณ๋ฅผ ์ง์ํฉ๋๋ค:
- x86, ARM, WebAssembly, โฆ
- Linux, Mac, Windows, โฆ
- ๋ฌ์คํธ๋ ๋ค์ํ ์ฅ์น์์ ์ฌ์ฉ๋ ์ ์์ต๋๋ค:
- ํ์จ์ด์ ๋ถํธ๋ก๋(์๋ฒ ๋๋)
- ์ค๋งํธ ๋์คํ๋ ์ด,
- ์ค๋งํธํฐ,
- ๋ฐ์คํฌํ,
- ์๋ฒ.
๋ฌ์คํธ๋ C++๊ฐ ์ฌ์ฉ๋๋ ๋๋ถ๋ถ์ ๊ณณ์์ ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค:
- ๋์ ์ ์ฐ์ฑ.
- ๋์ ์์ค์ ์ ์ด.
- ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ ๊ฐ์ ๋งค์ฐ ์ ํ๋ ์ฅ์น๋ก ์ค์ผ์ผ ๋ค์ด ๊ฐ๋ฅ.
- ๋ณ๋์ ๋ฐํ์์ ํ์๋ก ํ์ง ์์ผ๋ฉฐ, ๊ฐ๋น์ง ์ปฌ๋ ์ ๋ ์์.
- ์ฑ๋ฅ์ ํํํ์ง ์์ผ๋ฉด์๋ ์์ ์ฑ๊ณผ ์์ ์ ์ค์ ์ ๋ .
Rust์ ์ด์
๋ฌ์คํธ๋ง์ ๋ ํนํ ์ธ์ผ์ฆ ํฌ์ธํธ(์ฅ์ ):
-
์ปดํ์ผ ์๊ฐ ๋ฉ๋ชจ๋ฆฌ ์์ - ๋ฉ๋ชจ๋ฆฌ ๋ฒ๊ทธ์ ์ ์ฒด ํด๋์ค๊ฐ ์ปดํ์ผ ์๊ฐ์ ๋ฐฉ์ง๋ฉ๋๋ค.
- ์ด๊ธฐํ๋์ง ์๋ ๋ณ์๊ฐ ์์ต๋๋ค.
- ๋ฉ๋ชจ๋ฆฌ ์ด์ค ํด์ ๊ฐ ์์ฒ์ ์ผ๋ก ๋ถ๊ฐ๋ฅ ํฉ๋๋ค.
- ๋ฉ๋ชจ๋ฆฌ ํด์ ํ ์ฌ์ฉ์ด ์์ฒ์ ์ผ๋ก ๋ถ๊ฐ๋ฅ ํฉ๋๋ค.
NULL
ํฌ์ธํฐ๋ ์์ต๋๋ค.- ๋ฎคํ ์ค๋ฅผ ์ ๊ถ ๋๊ณ ์ฌ๋ ๊ฒ์ ์๋ ์ค์๋ฅผ ํ ์ ์์ต๋๋ค.
- ์ค๋ ๋๊ฐ ๋ฐ์ดํฐ ๋ ์ด์ค๋ฅผ ๋ง์์ค๋๋ค.
- ๋ฐ๋ณต์๊ฐ ๊ฐ์๊ธฐ ๋ฌดํจํ ๋๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค.
-
์ ์๋์ง ์์ ๋ฐํ์ ๋์ ์์ - Rust ๋ฌธ์ด ์คํํ๋ ์์ ์ ์ง์ ๋์ง ์์ ์ํ๋ก ๋์ง ์์ต๋๋ค.
- ๋ฐฐ์ด ์ ๊ทผ์ ๊ฒฝ๊ณ ์ฒดํฌ.
- ์ ์ํ ํ์ ์ ๋ณ์์์ ์ค๋ฒํ๋ก์ฐ ๋ฐ์์ ๋์์ด ์ ์ ์๋์ด์์ต๋๋ค.
-
์ต์ ์ธ์ด ๊ธฐ๋ฅ - ์์ ์์ค ์ธ์ด๋งํผ ํํ๋ ฅ์ด ๋ฐ์ด๋๊ณ ์ธ์ฒด๊ณตํ์ ์ ๋๋ค.
- ์ด๊ฑฐํ๊ณผ ํจํด ๋งค์นญ.
- ์ ๋ค๋ฆญ.
- FFI ๋ฐํ์ ์ค๋ฒํค๋ ์์.
- ๋น์ฉ์ด ๋ค์ง ์๋ ์ถ์ํ.
- ์น์ ํ ์ปดํ์ผ๋ฌ ์ค๋ฅ๋ฉ์์ง.
- ๋ด์ฅ ์ข ์์ฑ ๊ด๋ฆฌ์.
- ๋ด์ฅ ํ ์คํธ ์ง์.
- LSP (Language Server Protocol, ์ธ์ด ์๋ฒ ํ๋กํ ์ฝ) ์ง์์ด ์๋์ด ์์.
์ฌ๊ธฐ์์ ๋ง์ ์๊ฐ์ ๋ณด๋ด์ง ๋ง์ธ์. ์ด ๋ชจ๋ ์ฌํญ์ ๋์ค์ ์์ธํ ๋ค๋ฃน๋๋ค.
์๊ฐ์๋ค์๊ฒ ์ด๋ค ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ฅผ ์ฌ์ฉํ๋์ง ๋ฌผ์ด๋ณด์๊ธฐ ๋ฐ๋๋๋ค. ์ด๋ค ์ธ์ด๋ฅผ ์ฌ์ฉํ๋๋์ ๋ฐ๋ผ ๋ฌ์คํธ์์ ์ด๋ค ์ ์ ๊ฐ์กฐํด์ผ ํ ์ง๋ฅผ ๊ณ ๋ฏผํด ๋ณด์ธ์:
-
C/C++: ๋ฌ์คํธ๋ โ๋น๋ฆผโ๊ฒ์ฌ๊ธฐ๋ฅผ ํตํด์ ์ํ์ค์ ๋ฐ์ํ ์ ์๋ ๋ชจ๋ ์๋ฌ๋ฅผ ์ ๊ฑฐํฉ๋๋ค. ๋ฌ์คํธ๋ C์ C++๊ณผ ๋น์ทํ ์์ค์ ์ฑ๋ฅ์ ๋ณด์ฌ์ฃผ๋ฉด์๋, ๊ทธ ์ธ์ด๋ค์์ ์ข ์ข ๋ฐ์ํ๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ จ ์ค๋ฅ๊ฐ ์์ต๋๋ค. ๋ํ, ํจํด ๋งค์นญ์ด๋, ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณต๋๋ ์ข ์์ฑ ๊ด๋ฆฌ์ ๊ฐ์ ํ๋์ ์ธ ์ธ์ด์ ๊ธฐ๋ฅ๋ค์ ์ ๊ณตํฉ๋๋ค.
-
Java, Go, Python, JavaScript: ์ด ์ธ์ด๋ค๊ณผ ๋์ผํ ๋ฉ๋ชจ๋ฆฌ ์์ ์ฑ๊ณผ ํจ๊ป, โํ์ด๋ ๋ฒจโ์ธ์ด์ ๋๋์ ๋๋ ์ ์์ต๋๋ค. ๊ฑฐ๊ธฐ์ ๋ํด, ๊ฐ๋น์ง ์ปฌ๋ ํฐ๊ฐ ์๋ C/C++์ ์ ์ฌํ ์์ค์ ๋น ๋ฅด๊ณ ์์ธก ๊ฐ๋ฅํ ์ฑ๋ฅ์ ๊ธฐ๋ํ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ํ์ํ ๊ฒฝ์ฐ ์ ์์ค ํ๋์จ์ด๋ฅผ ๋ค๋ฃจ๋ ์ฝ๋๋ก ์์ฑํ ์ ์์ต๋๋ค.
ํ๋ ์ด๊ทธ๋ผ์ด๋
The Rust Playground provides an easy way to run short Rust programs, and is the basis for the examples and exercises in this course. Try running the โhello-worldโ program it starts with. It comes with a few handy features:
-
โ๋๊ตฌโ์์
rustfmt
์ต์ ์ ์ฌ์ฉํ์ฌ โํ์คโ ๋ฐฉ์์ผ๋ก ์ฝ๋ ํ์์ ์ง์ ํฉ๋๋ค. -
Rust์๋ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ํ ๋ ๊ฐ์ง ๊ธฐ๋ณธ โํ๋กํโ์ด ์์ต๋๋ค. ๋๋ฒ๊ทธ(์ถ๊ฐ ๋ฐํ์ ๊ฒ์ฌ, ์ต์ ํ ๊ฐ์) ๋ฐ ์ถ์(๋ ์ ์ ๋ฐํ์ ๊ฒ์ฌ, ์ต์ ํ ์ฆ๊ฐ)์ ๋๋ค. ์๋จ์ โ๋๋ฒ๊ทธโ์์ ์ก์ธ์คํ ์ ์์ต๋๋ค.
-
๊ด์ฌ์ด ์์ผ์๋ฉด โโฆโ ์๋์ โASMโ์ ์ฌ์ฉํด ์์ฑ๋ ์ด์ ๋ธ๋ฆฌ ์ฝ๋๋ฅผ ํ์ธํ์ธ์.
As students head into the break, encourage them to open up the playground and experiment a little. Encourage them to keep the tab open and try things out during the rest of the course. This is particularly helpful for advanced students who want to know more about Rustโs optimizations or generated assembly.
ํ์ ๋ฐ ๊ฐ
This segment should take about 45 minutes. It contains:
Slide | Duration |
---|---|
Hello World! | 5 minutes |
๋ณ์ | 5 minutes |
๊ฐ | 5 minutes |
์ฐ์ฐ | 3 minutes |
๋ฌธ์์ด | 5 minutes |
ํ์ ์ถ๋ก | 3 minutes |
์ฐ์ต๋ฌธ์ : ํผ๋ณด๋์น | 15 minutes |
Hello World!
๊ฐ์ฅ ๊ฐ๋จํ ๋ฌ์คํธ ํ๋ก๊ทธ๋จ์ผ๋ก์จ, ๊ณ ์ ์ ์ธ Hello World ๋ฅผ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค:
fn main() { println!("Hello ๐!"); }
ํ์ธํ ์ ์๋ ๊ฒ๋ค:
- ํจ์๋
fn
์ผ๋ก ์ ์ธํฉ๋๋ค. - C/C++ ์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ค๊ดํธ
{}
๋ก ๋ธ๋ก์ ํ์ํฉ๋๋ค. main
ํจ์๋ ํ๋ก๊ทธ๋จ ์ง์ ์ ์ ๋๋ค.- ๋ฌ์คํธ๋ ๋๋ํ ๋งคํฌ๋ก(hygienic macros) ์์คํ
์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
println!
๋ ๊ทธ ์์์ ๋๋ค. - ๋ฌ์คํธ์ ๋ฌธ์์ด์ UTF-8๋ก ์ธ์ฝ๋ฉ๋๋ฉฐ ์ด๋ชจ์ง์ ๊ฐ์ ์ ๋์ฝ๋ ๋ฌธ์๋ฅผ ํฌํจํ ์ ์์ต๋๋ค.
This slide tries to make the students comfortable with Rust code. They will see a ton of it over the next four days so we start small with something familiar.
ํค ํฌ์ธํธ:
-
๋ฌ์คํธ๋ C/C++/Java์ ๊ฐ์ ์ ํต์ ์ธ ๋ค๋ฅธ ์ธ์ด์ ๋งค์ฐ ์ ์ฌํฉ๋๋ค. ๋ฌ์คํธ๋ ์ ์ฐจ์ ์ธ์ด์ ๋๋ค. ์ ๋ง๋ก ํ์ํ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ๋ฉด, ๋ฌ์คํธ๋ ์ด๋ฏธ ์กด์ฌํ๋ ๊ฒ์ ์๋ก ๊ตฌํํ๋ ค๊ณ ํ์ง ์์ต๋๋ค.
-
๋ฌ์คํธ๋ ์ ๋์ฝ๋ ์ง์๊ณผ ๊ฐ์ ํ๋ ์ธ์ด์ ํน์ง์ ์ ๋ถ ์ง์ํฉ๋๋ค.
-
Rust uses macros for situations where you want to have a variable number of arguments (no function overloading).
-
๋๋ํ ๋งคํฌ๋ก(hygienic macro)๋ ๋งคํฌ๋ก๊ฐ ์ฌ์ฉ๋๋ ์ค์ฝํ์์ ์๋์น ์๊ฒ ๋ณ์๋ฅผ ๊ฐ๋ก์ฑ์ง ์์ต๋๋ค. ์ฌ์ค ๋ฌ์คํธ ๋งคํฌ๋ก๋ ์์ ํ hygenicํ์ง๋ ์์ต๋๋ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ์ธ์.
-
๋ฌ์คํธ๋ ๋ฉํฐ ํจ๋ฌ๋ค์ ์ธ์ด์ ๋๋ค. ์๋ฅผ ๋ค์ด ๊ฐ๋ ฅํ ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฅ์ ์ง์ํ๊ธฐ๋ ํ๋ฉฐ, ํจ์ํ ์ธ์ด๋ก ๋ถ๋ฅ๋์ง๋ ์์ง๋ง ํญ๋์ ๋ฒ์์ ํจ์ํ ์ปจ์ ์ ์ง์ํฉ๋๋ค.
๋ณ์
Rust provides type safety via static typing. Variable bindings are made with let
:
fn main() { let x: i32 = 10; println!("x: {x}"); // x = 20; // println!("x: {x}"); }
-
x = 20
์ ์ฃผ์ ์ฒ๋ฆฌ๋ฅผ ์ญ์ ํ์ฌ ๋ณ์๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ถ๋ณ์์ ๋ณด์ฌ์ค๋๋ค.mut
ํค์๋๋ฅผ ์ถ๊ฐํ์ฌ ๋ณ๊ฒฝ์ ํ์ฉํฉ๋๋ค. -
์ฌ๊ธฐ์ โi32โ๋ ๋ณ์์ ํ์ ์ ๋๋ค. ์ด๋ ์ปดํ์ผ ์๊ฐ์ ์๋ ค์ ธ์ผ ํ์ง๋ง, ํ์ ์ถ๋ก (๋์ค์ ์ค๋ช )์ ์ฌ์ฉํ๋ฉด ํ๋ก๊ทธ๋๋จธ๊ฐ ์ด๋ฅผ ์๋ตํ ์ ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
๊ฐ
๋ค์์ ๋ช ๊ฐ์ง ๊ธฐ๋ณธ ๋ด์ฅ ํ์ ๊ณผ ๊ฐ ํ์ ์ ๋ฆฌํฐ๋ด ๊ฐ ๋ฌธ๋ฒ์ ๋๋ค.
ํ์ | ๋ฆฌํฐ๋ด ๊ฐ | |
---|---|---|
๋ถํธ์๋ ์ ์ | i8 , i16 , i32 , i64 , i128 , isize | -10 , 0 , 1_000 , 123_i64 |
๋ถํธ์๋ ์ ์ | u8 , u16 , u32 , u64 , u128 , usize | 0 , 123 , 10_u16 |
๋ถ๋์์ | f32 , f64 | 3.14 , -10.0e20 , 2_f32 |
์ ๋์ฝ๋ ๋ฌธ์ | char | 'a' , 'ฮฑ' , 'โ' |
๋ถ๋ฆฌ์ธ | bool | true , false |
๊ฐ ํ์ ์ ํฌ๊ธฐ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
iN
,uN
,fN
์ ๋ชจ๋ _N_๋นํธ ์ ๋๋ค.isize
์usize
๋ ํฌ์ธํฐ์ ๊ฐ์ ํฌ๊ธฐ์ ๋๋ค,char
32 ๋นํธ ์ ๋๋ค,bool
์ 8 ๋นํธ ์ ๋๋ค.
์์ ํ์๋์ง ์์ ๋ช ๊ฐ์ง ๋ฌธ๋ฒ์ด ์์ต๋๋ค:
- All underscores in numbers can be left out, they are for legibility only. So
1_000
can be written as1000
(or10_00
), and123_i64
can be written as123i64
.
์ฐ์ฐ
fn interproduct(a: i32, b: i32, c: i32) -> i32 { return a * b + b * c + c * a; } fn main() { println!("๊ฒฐ๊ณผ: {}", interproduct(120, 100, 248)); }
main
์ด์ธ์ ํจ์๋ ์ด๋ฒ์ด ์ฒ์์ด์ง๋ง ์๋ฏธ๋ ๋ช
ํํฉ๋๋ค. ์ธ ๊ฐ์ ์ ์๋ฅผ ์ฌ์ฉํ๊ณ ์ ์๋ฅผ ๋ฐํํฉ๋๋ค. ํจ์๋ ๋์ค์ ๋ ์์ธํ ๋ค๋ฃจ๊ฒ ์ต๋๋ค.
์ฐ์ ์ฐ์ฐ์ ๋ค๋ฅธ ์ธ์ด์ ๋งค์ฐ ์ ์ฌํ๋ฉฐ ์ฐ์ ์์๊ฐ ๋น์ทํฉ๋๋ค.
์ ์ ์ค๋ฒํ๋ก๋ ์ด๋ป๊ฒ ๋๋์? C ๋ฐ C++์์ ๋ถํธ์๋ ์ ์์ ์ค๋ฒํ๋ก๋ ์ค์ ๋ก ์ ์๋์ง ์์ผ๋ฉฐ, ๋ค๋ฅธ ํ๋ซํผ์ด๋ ์ปดํ์ผ๋ฌ์์ ๋ค๋ฅธ ์์ ์ ์คํํ ์ ์์ต๋๋ค. Rust์์๋ ์ ์ ์ค๋ฒํ๋ก ์์ ๋์์ด ์ ์๋์ด ์์ต๋๋ค.
Change the i32
โs to i16
to see an integer overflow, which panics (checked) in a debug build and wraps in a release build. There are other options, such as overflowing, saturating, and carrying. These are accessed with method syntax, e.g., (a * b).saturating_add(b * c).saturating_add(c * a)
.
์ฌ์ค ์ปดํ์ผ๋ฌ๋ ์์ ํํ์์ ์ค๋ฒํ๋ก๋ฅผ ๊ฐ์งํ๋ฏ๋ก ์ด ์์์๋ ๋ณ๋์ ํจ์๊ฐ ํ์ํฉ๋๋ค.
๋ฌธ์์ด
Rust has two types to represent strings, both of which will be covered in more depth later. Both always store UTF-8 encoded strings.
String
- a modifiable, owned string.&str
- ์ฝ๊ธฐ ์ ์ฉ ๋ฌธ์์ด์ ๋๋ค. ๋ฌธ์์ด ๋ฆฌํฐ๋ด์ ์ด ํ์ ์ ๊ฐ์ง๋๋ค.
fn main() { let greeting: &str = " ์ธ์ฌ๋ง"; let planet: &str = "๐ช"; let mut sentence = String::new(); sentence.push_str(greeting); sentence.push_str(", "); sentence.push_str(planet); println!("๋ง์ง๋ง ๋ฌธ์ฅ: {}", sentence); println!("{:?}", &sentence[0..5]); //println!("{:?}", &sentence[12..13]); }
์ด ์ฌ๋ผ์ด๋๋ ๋ฌธ์์ด์ ์๊ฐํฉ๋๋ค. ์ฌ๊ธฐ์ ์๋ ๋ชจ๋ ๋ด์ฉ์ ๋์ค์ ์์ธํ ๋ค๋ฃจ๊ฒ ์ง๋ง ํ์ ์ฌ๋ผ์ด๋์ ์ฐ์ต๋ฌธ์ ์์ ๋ฌธ์์ด์ ์ฌ์ฉํ๋ ๋ฐ๋ ์ด๊ฒ์ผ๋ก ์ถฉ๋ถํฉ๋๋ค.
-
๋ฌธ์์ด ๋ด์ ์๋ชป๋ UTF-8 ์ธ์ฝ๋ฉ์ด ์๋ ๊ฒ์ ์ ์๋์ง ์์ ๋์์ด๋ฉฐ, ์ด๋ ์์ ํ Rust์์๋ ํ์ฉ๋์ง ์์ต๋๋ค.
-
String
์ ์์ฑ์(::new()
) ๋ฐs.push_str(..)
๊ณผ ๊ฐ์ ๋ฉ์๋๊ฐ ํฌํจ๋ ์ฌ์ฉ์ ์ ์ ํ์ ์ ๋๋ค. -
&str
์&
๋ ์ฐธ์กฐ์์ ๋ํ๋ ๋๋ค. ์ฐธ์กฐ๋ ๋์ค์ ๋ค๋ฃจ๋ฏ๋ก ์ง๊ธ์&str
์ โ์ฝ๊ธฐ ์ ์ฉ ๋ฌธ์์ดโ์ ์๋ฏธํ๋ ๋จ์๋ก ์๊ฐํ์ธ์. -
์ฃผ์ ์ฒ๋ฆฌ๋ ์ค์ ๋ฐ์ดํธ ์์น๋ณ๋ก ๋ฌธ์์ด์ ์์ธ์ ์์ฑํฉ๋๋ค.
12..13
์ ๋ฌธ์ ๊ฒฝ๊ณ์์ ๋๋์ง ์์ผ๋ฏ๋ก ํ๋ก๊ทธ๋จ์ด ํจ๋ ์ํ๊ฐ ๋ฉ๋๋ค. ์ค๋ฅ ๋ฉ์์ง์ ๋ฐ๋ผ ๋ฌธ์ ๊ฒฝ๊ณ์์ ๋๋๋ ๋ฒ์๋ก ์กฐ์ ํฉ๋๋ค. -
Raw strings allow you to create a
&str
value with escapes disabled:r"\n" == "\\n"
. You can embed double-quotes by using an equal amount of#
on either side of the quotes:fn main() { println!(r#"<a href="link.html">link</a>"#); println!("<a href=\"link.html\">link</a>"); }
-
Using
{:?}
is a convenient way to print array/vector/struct of values for debugging purposes, and itโs commonly used in code.
ํ์ ์ถ๋ก
๋ฌ์คํธ๋ ๋ณ์๊ฐ ์ด๋ป๊ฒ ์ฌ์ฉ๋๋์ง๋ฅผ ๋ณด๊ณ ๊ทธ ๋ณ์์ ํ์ ์ ์ถ๋ก ํฉ๋๋ค:
fn takes_u32(x: u32) { println!("u32: {x}"); } fn takes_i8(y: i8) { println!("i8: {y}"); } fn main() { let x = 10; let y = 20; takes_u32(x); takes_i8(y); // takes_u32(y); }
์ด ์ฌ๋ผ์ด๋๋, ๋ฌ์คํธ ์ปดํ์ผ๋ฌ๊ฐ ๋ณ์๊ฐ ์ด๋ป๊ฒ ์ ์ธ๋์ด ์๊ณ , ์ด๋ป๊ฒ ์ฌ์ฉ๋๋์ง๋ฅผ ์ ์ฝ ์กฐ๊ฑด์ผ๋ก ์ผ์์ ๋ณ์์ ํ์ ์ ์ถ๋ก ํ๋ ๋ชจ์ต์ ๋ณด์ฌ์ค๋๋ค.
์ฌ๊ธฐ์ ์ค์ํ ๊ฒ์, ์ด๋ ๊ฒ ๋ช ์์ ์ธ ํ์ ์ ์๋ตํ๊ณ ์ ์ธ๋์๋ค๊ณ ํด์ โ์ด๋ค ํ์ โ์ด๋ผ๋ ๋ค ๋ด์ ์ ์๋ ํ์ ์ด ๋๋ ๊ฒ์ ์๋๋ผ๋ ์ ์ ๋๋ค. ๋ช ์์ ์ธ ํ์ ์ ์ธ์ด ์๋ ์๋, ์ปดํ์ผ๋ฌ๊ฐ ์์ฑํ ๋จธ์ ์ฝ๋๋ ๋์ผํฉ๋๋ค. ์ปดํ์ผ๋ฌ๋ ๋จ์ง ํ์ ์ ์ธ์ ์๋ตํ ์ ์๋๋ก ํด์ ํ๋ก๊ทธ๋๋จธ๊ฐ ๋ ๊ฐ๊ฒฐํ ์ฝ๋๋ฅผ ์ธ ์ ์๋๋ก ๋์์ค ๋ฟ์ ๋๋ค.
์๋ฌด๊ฒ๋ ์ ์ ๋ฆฌํฐ๋ด์ ํ์
์ ์ ํํ์ง ์๋ ๊ฒฝ์ฐ Rust๋ ๊ธฐ๋ณธ์ ์ผ๋ก i32
๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋ฌ๋ฉด ์ค๋ฅ ๋ฉ์์ง์ {integer}
๋ก ํ์๋ ์ ์์ต๋๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก ๋ถ๋ ์์์ ๋ฆฌํฐ๋ด์ ๊ธฐ๋ณธ๊ฐ์ f64
์
๋๋ค.
fn main() { let x = 3.14; let y = 20; assert_eq!(x, y); // ERROR: `{float} == {integer}` ๊ตฌํ์ด ์์ }
์ฐ์ต๋ฌธ์ : ํผ๋ณด๋์น
The first and second Fibonacci numbers are both 1
. For n>2, the nโth Fibonacci number is calculated recursively as the sum of the n-1โth and n-2โth Fibonacci numbers.
Write a function fib(n)
that calculates the nโth Fibonacci number. When will this function panic?
fn fib(n: u32) -> u32 { if n <= 2 { // ๊ธฐ๋ณธ ์ฌ๋ก์ ๋๋ค. todo!("Implement this") } else { // ์ฌ๊ท ์ฌ๋ก์ ๋๋ค. todo!("Implement this") } } fn main() { let n = 20; println!("fib({n}) = {}", fib(n)); }
ํด๋ต
fn fib(n: u32) -> u32 { if n <= 2 { return 1; } else { return fib(n - 1) + fib(n - 2); } } fn main() { let n = 20; println!("fib({n}) = {}", fib(n)); }
ํ๋ฆ ์ ์ด
This segment should take about 40 minutes. It contains:
Slide | Duration |
---|---|
if ํํ์ | 4 minutes |
๋ฐฐ์ด๊ณผ for ๋ฐ๋ณต๋ฌธ | 5 minutes |
break์ continue | 4 minutes |
๋ธ๋ก ๋ฐ ๋ฒ์ | 5 minutes |
ํจ์ | 3 minutes |
๋งคํฌ๋ก | 2 minutes |
์ฐ์ต๋ฌธ์ : ์ฝ๋ผ์ธ ์์ด | 15 minutes |
if
ํํ์
๋ค๋ฅธ ์ธ์ด์ if
๋ฌธ๊ณผ ๋๊ฐ์ด if
ํํ์์ ์ฌ์ฉํฉ๋๋ค:
fn main() { let x = 10; if x == 0 { println!("zero!"); } else if x < 100 { println!("ํฐ"); } else { println!("๊ฑฐ๋ํ"); } }
๊ฒ๋ค๊ฐ if
๋ ํํ์์ผ๋ก ์ฌ์ฉํ ์๋ ์์ต๋๋ค. ์๋ ์ฝ๋๋ ์์ ๋์ผํฉ๋๋ค:
fn main() { let x = 10; let size = if x < 20 { "์์" } else { "๋ํ" }; println!("์ซ์ ํฌ๊ธฐ: {}", size); }
Because if
is an expression and must have a particular type, both of its branch blocks must have the same type. Show what happens if you add ;
after "small"
in the second example.
ํํ์์ โifโ๊ฐ ์ฌ์ฉ๋ ๊ฒฝ์ฐ ๋ค์ ๋ฌธ๊ณผ ๊ตฌ๋ถํ๊ธฐ ์ํด ํํ์์ ;
์ด ์์ด์ผ ํฉ๋๋ค. ์ปดํ์ผ๋ฌ ์ค๋ฅ๋ฅผ ๋ณด๋ ค๋ฉด println!
์์ ;
์ ์ญ์ ํ์ธ์.
๋ฐฐ์ด๊ณผ for ๋ฐ๋ณต๋ฌธ
Rust์๋ while
, loop
, for
๋ผ๋ ์ธ ๊ฐ์ง ๋ฐ๋ณต ํค์๋๊ฐ ์์ต๋๋ค.
while
The while
keyword works much like in other languages, executing the loop body as long as the condition is true.
fn main() { let mut x = 200; while x >= 10 { x = x / 2; } println!("์ต์ข x: {x}"); }
for
The for
loop iterates over ranges of values or the items in a collection:
fn main() { for x in 1..5 { println!("x: {x}"); } for elem in [1, 2, 3, 4, 5] { println!("elem: {elem}"); } }
- Under the hood
for
loops use a concept called โiteratorsโ to handle iterating over different kinds of ranges/collections. Iterators will be discussed in more detail later. for
๋ฐ๋ณต๋ฌธ์4
๊น์ง๋ง ์คํ๋ฉ๋๋ค. ๋ง์ง๋ง ๊ฐ์ ํฌํจ์ํค๋ ๋ฐฉ๋ฒ์ผ๋ก1..=5
์ ๊ฐ์ ๋ฌธ๋ฒ์ ๋ณด์ฌ์ฃผ์ธ์.
loop
The loop
statement just loops forever, until a break
.
fn main() { let mut i = 0; loop { i += 1; println!("{i}"); if i > 100 { break; } } }
break
์ continue
๋ค์ ๋ฐ๋ณต์ ์ฆ์ ์์ํ๋ ค๋ฉด continue
๋ฅผ ์ฌ์ฉํฉ๋๋ค.
If you want to exit any kind of loop early, use break
. For loop
, this can take an optional expression that becomes the value of the loop
expression.
fn main() { let mut i = 0; loop { i += 1; if i > 5 { break; } if i % 2 == 0 { continue; } println!("{}", i); } }
Labels
์ฌ์ฉํฉ๋๋ค. ์ค์ฒฉ ๋ฃจํ์์๋ ๋ ์ด๋ธ๊ณผ ํจ๊ป ์ฌ์ฉํ ์ ์์ต๋๋ค:
fn main() { let s = [[5, 6, 7], [8, 9, 10], [21, 15, 32]]; let mut elements_searched = 0; let target_value = 10; 'outer: for i in 0..=2 { for j in 0..=2 { elements_searched += 1; if s[i][j] == target_value { break 'outer; } } } print!("elements searched: {elements_searched}"); }
loop
๋ non-trivial ๊ฐ์ ๋ฐํํ๋ ์ ์ผํ ๋ฐ๋ณต๋ฌธ์ ๋๋ค. ์ด๋while
๋ฐfor
๋ฐ๋ณต๋ฌธ๊ณผ ๋ฌ๋ฆฌ ์ต์ํ ํ ๋ฒ์ ๋ฃจํ๋ฌธ์ ์ํํ๋ ๊ฒ์ด ๋ณด์ฅ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ธ๋ก ๋ฐ ๋ฒ์
๋ธ๋ก
A block in Rust contains a sequence of expressions, enclosed by braces {}
. Each block has a value and a type, which are those of the last expression of the block:
fn main() { let z = 13; let x = { let y = 10; println!("y: {y}"); z - y }; println!("x: {x}"); }
์์ main
ํจ์๋ ๋ง์ง๋ง ํํ์์ด ;
๋ก ๋๋๊ธฐ ๋๋ฌธ์ ๋ฐํ๋๋ ๊ฐ๊ณผ ํ์
์ด ()
์
๋๋ค.
- ๋ธ๋ก ๋ง์ง๋ง ์ค์ ์์ ํ๋ฉด์ ๋ธ๋ก์ ๊ฐ์ด ์ด๋ป๊ฒ ๋ฐ๋๋์ง ๋ณด์ฌ์ฃผ์ธ์. ์๋ฅผ ๋ค์ด, ์ธ๋ฏธ์ฝ๋ก ์ ๋ฃ๊ฑฐ๋ ๋บ๋ค๋ ์ง, ์๋๋ฉด
return
์ ์ฌ์ฉํด ๋ณด์ธ์.
๋ฒ์(Scopes)์ ์๋์(Shadowing)
๋ณ์์ ๋ฒ์๋ ์์ ์ ํฌํจํ๋ ๋ธ๋ก์ผ๋ก ์ ํ๋ฉ๋๋ค.
ํ์ฌ ๋ฒ์์ ์๋ ๋ณ์์, ๋ฐ๊นฅ ๋ฒ์์ ์๋ ๋ณ์ ๋ชจ๋ ๊ฐ๋ฆด(์๋์)์ ์์ต๋๋ค:
fn main() { let a = 10; println!("์ด์ : {a}"); { let a = "hello"; println!("๋ด๋ถ ๋ฒ์: {a}"); let a = true; println!("๋ด๋ถ ๋ฒ์ ์๋ ์ฒ๋ฆฌ๋จ: {a}"); } println!("์ดํ: {a}"); }
- Show that a variableโs scope is limited by adding a
b
in the inner block in the last example, and then trying to access it outside that block. - Shadowing is different from mutation, because after shadowing both variableโs memory locations exist at the same time. Both are available under the same name, depending where you use it in the code.
- A shadowing variable can have a different type.
- ์ฒ์์ ์๋์์ ๋ณด๋ฉด ์ฝ๋๋ฅผ ๋ ๋ชจํธํ๊ฒ ๋ง๋ ๋ค๊ณ ์๊ฐํ ์ ๋ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์ค์ ๋ก ์๋์์ ์ด์ฉํ๋ฉด, ์ด๋ค ๋ณ์์์
.unwrap()
๋ ๊ฐ์ ์๋ก์ด ๋ณ์์ ๋ด์ ๊ฒฝ์ฐ ์๋ก์ด ์ด๋ฆ์ ์ง์ ํ์ ์์ด ๊ธฐ์กด ์ด๋ฆ์ ์ ์งํ ์ ์์ด์ ํธ๋ฆฌํฉ๋๋ค.
ํจ์
fn gcd(a: u32, b: u32) -> u32 { if b > 0 { gcd(b, a % b) } else { a } } fn main() { println!("gcd: {}", gcd(143, 52)); }
- ๋งค๊ฐ๋ณ์๋ฅผ ์ ์ธํ ๋์๋ ์ด๋ฆ์ ๋จผ์ ์ฐ๊ณ , ํ์
์ ๋์ค์ ์๋๋ค. ์ด๋ฆ๊ณผ ํ์
์
:
๋ก ๊ตฌ๋ถํฉ๋๋ค. ์ด๋ ์ผ๋ถ ์ธ์ด(์๋ฅผ ๋ค์ด C)์ ๋ฐ๋์์ ์ ์ํ์๊ธฐ ๋ฐ๋๋๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก, ๋ฆฌํด ํ์ ๋ ํจ์์ ์์์ด ์๋ ๊ฐ์ฅ ๋ท๋ถ๋ถ์ ์ ์ธํฉ๋๋ค. - The last expression in a function body (or any block) becomes the return value. Simply omit the
;
at the end of the expression. Thereturn
keyword can be used for early return, but the โbare valueโ form is idiomatic at the end of a function (refactorgcd
to use areturn
). - ๋ฐํ๊ฐ์ด ์๋ ํจ์์ ๊ฒฝ์ฐ, ์ ๋ ํ์
()
์ ๋ฐํํฉ๋๋ค.-> ()
๊ฐ ์๋ต๋ ๊ฒฝ์ฐ ์ปดํ์ผ๋ฌ๋ ์ด๋ฅผ ์ถ๋ก ํฉ๋๋ค. - Overloading is not supported โ each function has a single implementation.
- ํญ์ ๊ณ ์ ๋ ๊ฐ์์ ๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๊ธฐ๋ณธ ์ธ์๋ ์ง์๋์ง ์์ต๋๋ค. ๋งคํฌ๋ก๋ ๊ฐ๋ณ ํจ์๋ฅผ ์ง์ํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- Always takes a single set of parameter types. These types can be generic, which will be covered later.
๋งคํฌ๋ก
๋งคํฌ๋ก๋ ์ปดํ์ผ ์ค์ Rust ์ฝ๋๋ก ํ์ฅ๋๋ฉฐ ๋ค์ํ ์์ ์ธ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋์ !
๋ก ๊ตฌ๋ถ๋ฉ๋๋ค. Rust ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์๋ ์ฌ๋ฌ ๊ฐ์ง ์ ์ฉํ ๋งคํฌ๋ก๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.
println!(format, ..)
prints a line to standard output, applying formatting described instd::fmt
.format!(format, ..)
์println!
์ฒ๋ผ ์๋ํ์ง๋ง ๊ฒฐ๊ณผ๋ฅผ ๋ฌธ์์ด๋ก ๋ฐํํฉ๋๋ค.dbg!(expression)
์ ํํ์์ ๊ฐ์ ๊ธฐ๋กํ๊ณ ๋ฐํํฉ๋๋ค.todo!()
๋ ์ผ๋ถ ์ฝ๋๋ฅผ ์์ง ๊ตฌํ๋์ง ์์ ๊ฒ์ผ๋ก ํ์ํฉ๋๋ค. ์คํํ๋ฉด ํจ๋์ด ๋ฐ์ํฉ๋๋ค.unreachable!()
์ ์ผ๋ถ ์ฝ๋๋ฅผ ์ฐ๊ฒฐํ ์ ์๋ค๊ณ ํ์ํฉ๋๋ค. ์คํํ๋ฉด ํจ๋์ด ๋ฐ์ํฉ๋๋ค.
fn factorial(n: u32) -> u32 { let mut product = 1; for i in 1..=n { product *= dbg!(i); } product } fn fizzbuzz(n: u32) -> u32 { todo!() } fn main() { let n = 4; println!("{n}! = {}", factorial(n)); }
์ด ์น์ ์์๋ ์ด๋ฌํ ์ผ๋ฐ์ ์ธ ํธ์ ๊ธฐ๋ฅ์ด ์์ผ๋ฉฐ ์ด๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๊ธฐ์ตํด์ผ ํฉ๋๋ค. ๋งคํฌ๋ก๋ก ์ ์๋๋ ์ด์ ์ ํ์ฅ ๋์์ ํน๋ณํ ์ค์ํ์ง ์์ต๋๋ค.
์ด ๊ณผ์ ์์๋ ๋งคํฌ๋ก ์ ์๋ฅผ ๋ค๋ฃจ์ง ์์ง๋ง ์ดํ ์น์ ์์๋ ํ์ ๋งคํฌ๋ก์ ์ฌ์ฉ์ ๊ดํด ์ค๋ช ํฉ๋๋ค.
์ฐ์ต๋ฌธ์ : ์ฝ๋ผ์ธ ์์ด
์ฝ๋ผ์ธ ์์ด์ ์์์ n์ ๋ํด ๋ค์๊ณผ ๊ฐ์ด ์ ์๋ฉ๋๋ค.10๋ณด๋ค ํฐ ๊ฒฝ์ฐ:- _ni_์ด 1์ด๋ฉด ์์ด์ _n์์ ์ข ๋ฃ๋ฉ๋๋ค.i_.
-
_ni_์ด(๊ฐ) ์ง์๋ฉด _ni+1= ni/ 2_์ ๋๋ค.
-
_ni_์ด(๊ฐ) ํ์์ด๋ฉด _ni+1= 3 * ni
- 1_์ ๋๋ค.
์๋ฅผ ๋ค์ด _n1_ = 3:์ผ๋ก ์์ํ๋ฉด- 3 is odd, so _n2_ = 3 * 3 + 1 = 10์ด๋ฉฐ
- 10 is even, so _n3_ = 10 / 2 = 5์ด๋ฉฐ
- 5 is odd, so n4 = 3 * 5 + 1 = 16;
- 16 is even, so n5 = 16 / 2 = 8;
- 8 is even, so n6 = 8 / 2 = 4;
- 4 is even, so n7 = 4 / 2 = 2;
- 2 is even, so _n8_ = 1์ด ๋์ด
- ์์ด์ด ์ข ๋ฃ๋ฉ๋๋ค.
์ฃผ์ด์ง ์ฒซ ๋ฒ์งธ n
์ ๋ํด ์ฝ๋ผ์ธ ์์ด์ ๊ธธ์ด๋ฅผ ๊ณ์ฐํ๋ ํจ์๋ฅผ ์์ฑํฉ๋๋ค.
/// `n`์์ ์์ํ๋ ์ฝ๋ผ์ธ ์์ด์ ๊ธธ์ด๋ฅผ ๊ฒฐ์ ํฉ๋๋ค. fn collatz_length(mut n: i32) -> u32 { todo!("Implement this") } fn main() { todo!("Implement this") }
ํด๋ต
/// `n`์์ ์์ํ๋ ์ฝ๋ผ์ธ ์์ด์ ๊ธธ์ด๋ฅผ ๊ฒฐ์ ํฉ๋๋ค. fn collatz_length(mut n: i32) -> u32 { let mut len = 1; while n > 1 { n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 }; len += 1; } len } #[test] fn test_collatz_length() { assert_eq!(collatz_length(11), 15); } fn main() { println!("๊ธธ์ด: {}", collatz_length(11)); }
Welcome Back
Including 10 minute breaks, this session should take about 2 hours and 15 minutes. It contains:
Segment | Duration |
---|---|
ํํ ๋ฐ ๋ฐฐ์ด | 35 minutes |
์ฐธ์กฐ | 35 minutes |
์ฌ์ฉ์ ์ ์ ํ์ | 50 minutes |
ํํ ๋ฐ ๋ฐฐ์ด
This segment should take about 35 minutes. It contains:
Slide | Duration |
---|---|
๋ฐฐ์ด | 5 minutes |
ํํ | 5 minutes |
Cargo์ ํตํฉ๋จ | 3 minutes |
์ด๊ฑฐํ ๋ถํด(์ญ๊ตฌ์กฐํ) | 5 minutes |
์ฐ์ต๋ฌธ์ : ์ค์ฒฉ ๋ฐฐ์ด | 15 minutes |
๋ฐฐ์ด
fn main() { let mut a: [i8; 10] = [42; 10]; a[5] = 0; println!("a: {a:?}"); }
-
A value of the array type
[T; N]
holdsN
(a compile-time constant) elements of the same typeT
. Note that the length of the array is part of its type, which means that[u8; 3]
and[u8; 4]
are considered two different types. Slices, which have a size determined at runtime, are covered later. -
๋ฒ์๋ฅผ ๋ฒ์ด๋ ๋ฐฐ์ด ์์์ ์ก์ธ์คํด ๋ณด์ธ์. ๋ฐฐ์ด ์ก์ธ์ค๋ ๋ฐํ์์ ํ์ธ๋ฉ๋๋ค. Rust๋ ์ผ๋ฐ์ ์ผ๋ก ์ด๋ฌํ ํ์ธ์ ์ต์ ํํ ์ ์์ผ๋ฉฐ, ์์ ํ์ง ์์ Rust๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฒ์ ํ์ธ์ ํ์ง ์์ ์๋ ์์ต๋๋ค.
-
๋ฆฌํฐ๋ด์ ์ฌ์ฉํ์ฌ ๋ฐฐ์ด์ ๊ฐ์ ํ ๋นํ ์ ์์ต๋๋ค.
-
The
println!
macro asks for the debug implementation with the?
format parameter:{}
gives the default output,{:?}
gives the debug output. Types such as integers and strings implement the default output, but arrays only implement the debug output. This means that we must use debug output here. -
#
์ ์ถ๊ฐํ๋ฉด({a:#?}
) ์ข ๋ ์ฝ๊ธฐ ์ฌ์ด โ์ด์โ ํํ๋ก ์ถ๋ ฅ์ด ๋ฉ๋๋ค.
ํํ
fn main() { let t: (i8, bool) = (7, true); println!("t.0: {}", t.0); println!("t.1: {}", t.1); }
-
๋ฐฐ์ด๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ํํ์ ๊ณ ์ ๊ธธ์ด๋ฅผ ๊ฐ์ต๋๋ค.
-
ํํ์ ์๋ก ๋ค๋ฅธ ํ์ ์ ๊ฐ๋ค์ ํ๋์ ๋ณตํฉ ํ์ ์ผ๋ก ๋ฌถ์ต๋๋ค.
-
ํํ์ ์ํ ๊ฐ์
t.0
,t.1
๊ณผ ๊ฐ์ด ์ธ๋ฑ์ค๋ก ์ ๊ทผํ ์ ์์ต๋๋ค. -
The empty tuple
()
is referred to as the โunit typeโ and signifies absence of a return value, akin tovoid
in other languages.
Cargo์ ํตํฉ๋จ
for
๋ฌธ์ ๋ฐฐ์ด ๋ฐ๋ณต์ ์ง์ํ์ง๋ง ํํ์ ์ง์ํ์ง ์์ต๋๋ค.
fn main() { let primes = [2, 3, 5, 7, 11, 13, 17, 19]; for prime in primes { for i in 2..prime { assert_ne!(prime % i, 0); } } }
์ด ๊ธฐ๋ฅ์ IntoIterator
ํธ๋ ์์ ์ฌ์ฉํ์ง๋ง ์ฌ๊ธฐ์๋ ์์ง ๋ค๋ฃจ์ง ์์์ต๋๋ค.
assert_ne!
๋งคํฌ๋ก๊ฐ ์ฌ๊ธฐ์ ์๋ก ์ถ๊ฐ๋์์ต๋๋ค. assert_eq!
๋ฐ assert!
๋งคํฌ๋ก๋ ์์ต๋๋ค. ์ด ๋งคํฌ๋ก๋ค์ ํญ์ ๊ฐ์ ํ์ธํ์ง๋ง, debug_assert!
์ ๊ฐ์ ๋๋ฒ๊ทธ ์ ์ฉ ๋งคํฌ๋ก๋ ์ถ์ ๋น๋์์ ์ปดํ์ผ๋์ง ์๊ณ ์ฌ๋ผ์ง๋๋ค.
์ด๊ฑฐํ ๋ถํด(์ญ๊ตฌ์กฐํ)
When working with tuples and other structured values itโs common to want to extract the inner values into local variables. This can be done manually by directly accessing the inner values:
fn print_tuple(tuple: (i32, i32)) { let left = tuple.0; let right = tuple.1; println!("left: {left}, right: {right}"); }
However, Rust also supports using pattern matching to destructure a larger value into its constituent parts:
fn print_tuple(tuple: (i32, i32)) { let (left, right) = tuple; println!("left: {left}, right: {right}"); }
This works with any kind of structured value:
struct Foo { a: i32, b: bool, } fn print_foo(foo: Foo) { let Foo { a, b } = foo; println!("a: {a}, b: {b}"); }
- The patterns used here are โirrefutableโ, meaning that the compiler can statically verify that the value on the right of
=
has the same structure as the pattern. - A variable name is an irrefutable pattern that always matches any value, hence why we can also use
let
to declare a single variable. - Rust also supports using patterns in conditionals, allowing for equality comparison and destructuring to happen at the same time. This form of pattern matching will be discussed in more detail later.
- Edit the examples above to show the compiler error when the pattern doesnโt match the value being matched on.
์ฐ์ต๋ฌธ์ : ์ค์ฒฉ ๋ฐฐ์ด
๋ฐฐ์ด์๋ ๋ค์๊ณผ ๊ฐ์ ๋ค๋ฅธ ๋ฐฐ์ด์ด ํฌํจ๋ ์ ์์ต๋๋ค.
#![allow(unused)] fn main() { let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; }
What is the type of this variable?
Use an array such as the above to write a function transpose
which will transpose a matrix (turn rows into columns):
๋ ํจ์ ๋ชจ๋ ํ๋ ฌ์ ํฌ๊ธฐ๋ 3 x 3 ์ผ๋ก ํ๋์ฝ๋ฉ ํฉ๋๋ค.
์๋ ์ฝ๋๋ฅผ https://play.rust-lang.org/์ ๋ณต์ฌํด์ ๊ตฌํํ์๋ฉด ๋ฉ๋๋ค:
// TODO: ๊ตฌํ์ด ์๋ฃ๋๋ฉด ์ด๋ฅผ ์ญ์ ํฉ๋๋ค. #![allow(unused_variables, dead_code)] fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] { unimplemented!() } #[test] fn test_transpose() { let matrix = [ [101, 102, 103], // [201, 202, 203], [301, 302, 303], ]; let transposed = transpose(matrix); assert_eq!( transposed, [ [101, 201, 301], // [102, 202, 302], [103, 203, 303], ] ); } fn main() { let matrix = [ [101, 102, 103], // <-- ์ฃผ์์ผ๋ก rustfmt๊ฐ ์ค๋ฐ๊ฟ์ ์ถ๊ฐํฉ๋๋ค. [201, 202, 203], [301, 302, 303], ]; println!("ํ๋ ฌ: {:#?}", matrix); let transposed = transpose(matrix); println!("์ ์นํ๋ ฌ: {:#?}", transposed); }
ํด๋ต
fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] { let mut result = [[0; 3]; 3]; for i in 0..3 { for j in 0..3 { result[j][i] = matrix[i][j]; } } result } #[test] fn test_transpose() { let matrix = [ [101, 102, 103], // [201, 202, 203], [301, 302, 303], ]; let transposed = transpose(matrix); assert_eq!( transposed, [ [101, 201, 301], // [102, 202, 302], [103, 203, 303], ] ); } fn main() { let matrix = [ [101, 102, 103], // <-- ์ฃผ์์ผ๋ก rustfmt๊ฐ ์ค๋ฐ๊ฟ์ ์ถ๊ฐํฉ๋๋ค. [201, 202, 203], [301, 302, 303], ]; println!("ํ๋ ฌ: {:#?}", matrix); let transposed = transpose(matrix); println!("์ ์นํ๋ ฌ: {:#?}", transposed); }
์ฐธ์กฐ
This segment should take about 35 minutes. It contains:
Slide | Duration |
---|---|
๊ณต์ ์ฐธ์กฐ | 10 minutes |
ํ์(dangling) ์ฐธ์กฐ | 10 minutes |
์ฐ์ต๋ฌธ์ : ๋ํ | 15 minutes |
๊ณต์ ์ฐธ์กฐ
์ฐธ์กฐ๋ ๊ฐ์ ๋ํ ์ฑ ์์ ์ง์ง ์๊ณ ๋ค๋ฅธ ๊ฐ์ ์ก์ธ์คํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ฉฐ โ๋น๋ฆผโ์ด๋ผ๊ณ ๋ ํฉ๋๋ค. ๊ณต์ ์ฐธ์กฐ๋ ์ฝ๊ธฐ ์ ์ฉ์ด๋ฉฐ ์ฐธ์กฐ๋ ๋ฐ์ดํฐ๋ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
fn main() { let a = 'A'; let b = 'B'; let mut r: &char = &a; println!("r: {}", *r); r = &b; println!("r: {}", *r); }
T
ํ์
์ ๋ํ ๊ณต์ ์ฐธ์กฐ๋ &T
ํ์
์ ๊ฐ์ต๋๋ค. ์ฐธ์กฐ ๊ฐ์ &
์ฐ์ฐ์๋ก ์์ฑ๋ฉ๋๋ค. *
์ฐ์ฐ์๋ ์ฐธ์กฐ๋ฅผ โ์ญ์ฐธ์กฐโํ์ฌ ๊ฐ์ ์ฐ์ถํฉ๋๋ค.
๋ฌ์คํธ๋ ํ์(dangling) ์ฐธ์กฐ๋ฅผ ์ปดํ์ผ๋ฌ ๋จ๊ณ์์ ์ฐพ์๋ด๊ณ ๊ธ์งํฉ๋๋ค:
fn x_axis(x: i32) -> &(i32, i32) { let point = (x, 0); return &point; }
-
์ฐธ์กฐ๋ ์ฐธ์กฐ๋๋ ๊ฐ์ โ๋น๋ฆฐ๋คโ๊ณ ํ๋ฉฐ, ์ด๋ ํฌ์ธํฐ์ ์ต์ํ์ง ์์ ํ์๋ค์๊ฒ ์ข์ ๋ชจ๋ธ์ ๋๋ค. ์ฝ๋์์๋ ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ์ก์ธ์คํ ์ ์์ง๋ง ์ฌ์ ํ ์๋ ๋ณ์๊ฐ ๊ฐ์ โ์์ โํฉ๋๋ค. ์์ ๊ถ์ ๊ดํ ์์ธํ ๋ด์ฉ์ 3์ผ ์ฐจ์ ๋ค๋ฃน๋๋ค.
-
์ฐธ์กฐ๋ ํฌ์ธํฐ๋ก ๊ตฌํ๋๋ฉฐ ์ฃผ์ ์ด์ ์ ๊ฐ๋ฆฌํค๋ ๋์๋ณด๋ค ํจ์ฌ ์์ ์ ์๋ค๋ ๊ฒ์ ๋๋ค. C ๋๋ C++์ ์ต์ํ ํ์์ ์ฐธ์กฐ๋ฅผ ํฌ์ธํฐ๋ก ์ธ์ํฉ๋๋ค. ๊ณผ์ ์ ํ๋ฐ๋ถ์์๋ Rust๊ฐ ์์ ํฌ์ธํฐ ์ฌ์ฉ์์ ๋น๋กฏ๋๋ ๋ฉ๋ชจ๋ฆฌ ์์ ๋ฒ๊ทธ๋ฅผ ๋ฐฉ์งํ๋ ๋ฐฉ๋ฒ์ ๋ค๋ฃน๋๋ค.
-
Rust๋ ์ฐธ์กฐ๋ฅผ ์๋์ผ๋ก ์์ฑํ์ง ์์ต๋๋ค.
&
๊ฐ ํญ์ ํ์ํฉ๋๋ค. -
Rust will auto-dereference in some cases, in particular when invoking methods (try
r.is_ascii()
). There is no need for an->
operator like in C++. -
์ด ์์์
r
์ ๋ณ๊ฒฝ ๊ฐ๋ฅํ๋ฏ๋ก ๋ค์ ํ ๋นํ ์ ์์ต๋๋ค(r = &b
). ์ด๋ ๊ฒ ํ๋ฉดr
์ด ๋ค์ ๋ฐ์ธ๋ฉ๋์ด ๋ค๋ฅธ ๊ฒ์ ์ฐธ์กฐํฉ๋๋ค. ์ด๋ ์ฐธ์กฐ์ ํ ๋นํ๋ฉด ์ฐธ์กฐ๋ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ C++์๋ ๋ค๋ฆ ๋๋ค. -
๊ณต์ ์ฐธ์กฐ์์๋ ์ฐธ์กฐ ๊ฐ์ด ๋ณ๊ฒฝ ๊ฐ๋ฅํ๋๋ผ๋ ์ด๋ฅผ ์์ ํ ์ ์์ต๋๋ค.
*r = 'X'
๋ฅผ ์ ๋ ฅํด ๋ณด์ธ์. -
Rust๋ ๋ชจ๋ ์ฐธ์กฐ์ ์ ์ฒด ๊ธฐ๊ฐ์ ์ถ์ ํ์ฌ ์ถฉ๋ถํ ์ค๋ ์ง์๋๋์ง ํ์ธํฉ๋๋ค. ์์ ํ Rust์์๋ ๋๊ธ๋ง ์ฐธ์กฐ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
x_axis
๋point
์ ๋ํ ์ฐธ์กฐ๋ฅผ ๋ฐํํ์ง๋งpoint
๋ ํจ์๊ฐ ๋ฐํ๋๋ฉด ํ ๋น์ด ํด์ ๋๋ฏ๋ก ์ปดํ์ผ๋์ง ์์ต๋๋ค. -
์์ ๊ถ์ ๋ํ ์ฃผ์ ๋ฅผ ๋ค๋ฃฐ ๋ ์ด ๋น๋ฆผ์ ๋ํด ๋ ์์ธํ ์ด์ผ๊ธฐ ํ๊ฒ ์ต๋๋ค.
ํ์(dangling) ์ฐธ์กฐ
๋ณ๊ฒฝ ๊ฐ๋ฅํ ์ฐธ์กฐ๋ผ๊ณ ๋ ํ๋ ๋ฐฐํ์ ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํ๋ฉด ์ฐธ์กฐ๋๋ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค. ํ์
์ &mut T
์
๋๋ค.
fn main() { let mut point = (1, 2); let x_coord = &mut point.0; *x_coord = 20; println!("point: {point:?}"); }
ํค ํฌ์ธํธ:
-
โ๋ฐฐํ์ โ์ด๋ ๊ฐ์ ์ก์ธ์คํ๋ ๋ฐ ์ด ์ฐธ์กฐ๋ง ์ฌ์ฉํ ์ ์์์ ์๋ฏธํฉ๋๋ค. ๋์์ ๋ค๋ฅธ ์ฐธ์กฐ(๊ณต์ ๋๋ ๋ฐฐํ์ )๊ฐ ์กด์ฌํ ์ ์์ผ๋ฉฐ, ๋ฐฐํ์ ์ฐธ์กฐ๊ฐ ์กด์ฌํ๋ ๋์์๋ ์ฐธ์กฐ๋ ๊ฐ์ ์ก์ธ์คํ ์ ์์ต๋๋ค.
x_coord
๊ฐ ํ์ฑํ๋์ด ์๋ ๋์&point.0
์ ๋ง๋ค๊ฑฐ๋point.0
์ ๋ณ๊ฒฝํด ๋ณด์ธ์. -
Be sure to note the difference between
let mut x_coord: &i32
andlet x_coord: &mut i32
. The first one represents a shared reference which can be bound to different values, while the second represents an exclusive reference to a mutable value.
์ฐ์ต๋ฌธ์ : ๋ํ
3์ฐจ์ ๋ํ์ ์ํ ๋ช ๊ฐ์ง ์ ํธ๋ฆฌํฐ ํจ์๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. 3์ฐจ์ ์์ ํ ์ ์ [f64;3]
์ผ๋ก ๋ํ๋ด๋๋ก ํฉ๋๋ค. ํจ์ ์๋ช
์ ๊ฐ๋ฐ์๊ฐ ๊ฒฐ์ ํฉ๋๋ค.
// ํด๋น ์ขํ์ ์ ๊ณฑ์ ๋ํ๊ณ // ์ ๊ณฑ๊ทผ์ ์ฌ์ฉํ์ฌ ๋ฒกํฐ์ ํฌ๊ธฐ๋ฅผ ๊ณ์ฐํฉ๋๋ค. `v.sqrt()`์ ๊ฐ์ `sqrt()` ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ ๊ณฑ๊ทผ์ // ๊ณ์ฐํฉ๋๋ค. fn magnitude(...) -> f64 { todo!() } // ๋ฒกํฐ์ ํฌ๊ธฐ๋ฅผ ๊ณ์ฐํ๊ณ ๋ชจ๋ ์ขํ๋ฅผ ํด๋น ํฌ๊ธฐ๋ก ๋๋ ์ // ๋ฒกํฐ๋ฅผ ์ ๊ทํํฉ๋๋ค. fn normalize(...) { todo!() } // ๋ค์ `main`์ ์ฌ์ฉํ์ฌ ์์ ์ ํ ์คํธํฉ๋๋ค. fn main() { println!("๋จ์ ๋ฒกํฐ์ ํฌ๊ธฐ: {}", magnitude(&[0.0, 1.0, 0.0])); let mut v = [1.0, 2.0, 9.0]; println!("{v:?} ํฌ๊ธฐ: {}", magnitude(&v)); normalize(&mut v); println!("์ ๊ทํ ํ {v:?}์ ํฌ๊ธฐ: {}", magnitude(&v)); }
ํด๋ต
/// ์ง์ ๋ ๋ฒกํฐ์ ํฌ๊ธฐ๋ฅผ ๊ณ์ฐํฉ๋๋ค. fn magnitude(vector: &[f64; 3]) -> f64 { let mut mag_squared = 0.0; for coord in vector { mag_squared += coord * coord; } mag_squared.sqrt() } /// ๋ฐฉํฅ ๋ณ๊ฒฝ ์์ด ๋ฒกํฐ ํฌ๊ธฐ๋ฅผ 1.0์ผ๋ก ๋ณ๊ฒฝํฉ๋๋ค. fn normalize(vector: &mut [f64; 3]) { let mag = magnitude(vector); for item in vector { *item /= mag; } } fn main() { println!("๋จ์ ๋ฒกํฐ์ ํฌ๊ธฐ: {}", magnitude(&[0.0, 1.0, 0.0])); let mut v = [1.0, 2.0, 9.0]; println!("{v:?} ํฌ๊ธฐ: {}", magnitude(&v)); normalize(&mut v); println!("์ ๊ทํ ํ {v:?}์ ํฌ๊ธฐ: {}", magnitude(&v)); }
์ฌ์ฉ์ ์ ์ ํ์
This segment should take about 50 minutes. It contains:
Slide | Duration |
---|---|
๊ตฌ์กฐ์ฒด | 10 minutes |
ํํ | 10 minutes |
์ด๊ฑฐํ | 5 minutes |
์ ์ ๋ณ์(static)์ ์์(const) | 5 minutes |
ํ์ ๋ณ์นญ | 2 minutes |
์ฐ์ต๋ฌธ์ : ์๋ฆฌ๋ฒ ์ดํฐ ์ด๋ฒคํธ | 15 minutes |
๊ตฌ์กฐ์ฒด
C/C++ ์ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ฌ์คํธ๋ ์ปค์คํ ๊ตฌ์กฐ์ฒด๋ฅผ ์ง์ํฉ๋๋ค:
struct Person { name: String, age: u8, } fn describe(person: &Person) { println!("{}์(๋) {}์ธ์ ๋๋ค.", person.name, person.age); } fn main() { let mut peter = Person { name: String::from("ํผํฐ"), age: 27 }; describe(&peter); peter.age = 28; describe(&peter); let name = String::from("์์ด๋ฒ๋ฆฌ"); let age = 39; let avery = Person { name, age }; describe(&avery); let jackie = Person { name: String::from("์ฌํค"), ..avery }; describe(&jackie); }
ํค ํฌ์ธํธ:
- ๊ตฌ์กฐ์ฒด๋ C/C++ ์ ์ ์ฌํฉ๋๋ค.
- C++ ์ ๊ฐ์ง๋ง C์๋ ๋ฌ๋ฆฌ ํ์ ์ ์ ์ํ๊ธฐ ์ํด โtypedefโ๊ฐ ํ์ํ์ง ์์ต๋๋ค.
- C++ ์ ๋ฌ๋ฆฌ ๊ตฌ์กฐ์ฒด ๊ฐ ์์์ ์์ต๋๋ค.
- This may be a good time to let people know there are different types of structs.
- Zero-sized structs (e.g.
struct Foo;
) might be used when implementing a trait on some type but donโt have any data that you want to store in the value itself. - ๋ค์ ์ฌ๋ผ์ด๋์์๋ ํ๋ ์ด๋ฆ์ด ๋ ์ค์ํ ๋ ์ฌ์ฉํ ์ ์๋ ํํ ๊ตฌ์กฐ์ฒด๋ฅผ ์๊ฐํฉ๋๋ค.
- Zero-sized structs (e.g.
- If you already have variables with the right names, then you can create the struct using a shorthand.
- The syntax
..avery
allows us to copy the majority of the fields from the old struct without having to explicitly type it all out. It must always be the last element.
ํํ
๊ฐ ํ๋ ์ด๋ฆ์ด ์ค์ํ์ง ์๋ค๋ฉด ํํ ๊ตฌ์กฐ์ฒด๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค:
struct Point(i32, i32); fn main() { let p = Point(17, 23); println!("({}, {})", p.0, p.1); }
ํํ ๊ตฌ์กฐ์ฒด๋ ์ข ์ข ๋จ์ผ ํ๋์ ๋ํผ(wrapper, ๋ฌ์คํธ์์ ๋ดํ์ (newtype)์ด๋ผ๊ณ ๋ถ๋ฆ)๋ก ์ฌ์ฉ๋ฉ๋๋ค:
struct PoundsOfForce(f64); struct Newtons(f64); fn compute_thruster_force() -> PoundsOfForce { todo!("NASA ๋ก์ผ ๊ณผํ์์๊ฒ ๋ฌผ์ด๋ณด์ธ์") } fn set_thruster_force(force: Newtons) { // ... } fn main() { let force = compute_thruster_force(); set_thruster_force(force); }
- ๋ดํ์
์ ๊ธฐ๋ณธ ํ์
์ ๋ถ๊ฐ์ ์ธ ์๋ฏธ๋ฅผ ๋ํ๋ ์ข์ ๋ฐฉ๋ฒ์
๋๋ค. ์๋ฅผ ๋ค์ด:
- ์ซ์๊ฐ์ ๋จ์๋ฅผ ํ์ํ ์ ์์: ์์์
Newtons
์ด ๊ทธ ์์ ๋๋ค. - The value passed some validation when it was created, so you no longer have to validate it again at every use:
PhoneNumber(String)
orOddNumber(u32)
.
- ์ซ์๊ฐ์ ๋จ์๋ฅผ ํ์ํ ์ ์์: ์์์
Newtons
ํ์ ์ ๊ฐ์f64
๊ฐ์ ๋ํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ฃผ์ธ์.- ๋ฌ์คํธ๋ ๋ถ๋ช ํ์ง ์์ ๊ฒ์ ์ซ์ดํฉ๋๋ค. ์๋ฅผ ๋ค๋ฉด ์๋์ผ๋ก unwrapํ๊ฑฐ๋ ๋ถ๋ฆฌ์ธ ๊ฐ์ ์ ์ ๊ฐ์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ๋ค์ด ๊ทธ๋ ์ต๋๋ค.
- ์ฐ์ฐ์ ์ฌ์ ์๋ 3์ผ์ฐจ ์ ๋ค๋ฆญ ๋ถ๋ถ์์ ๋ค๋ฃน๋๋ค.
- ์ด๋ ํ์ฑ ๊ธฐํ ๊ถค๋์ (Mars Climate Orbiter)์ ์คํจ ์์ธ์ผ๋ก ์ง๋ชฉ๋ ๋๋ํ ์ ๋ ฅ ์ค๋ฅ๋ฅผ ๋ณด์ฌ์ค๋๋ค.
์ด๊ฑฐํ
enum
ํค์๋๋ ๋ช๊ฐ์ง ๋ณํ(variant)์ผ๋ก ํํ๋๋ ์ด๊ฑฐํ ํ์
์ ์์ฑํฉ๋๋ค:
#[derive(Debug)] enum Direction { Left, Right, } #[derive(Debug)] enum PlayerMove { Pass, // ๋จ์ ๋ณํ Run(Direction), // ํํ ๋ณํ Teleport { x: u32, y: u32 }, // ๊ตฌ์กฐ์ฒด ๋ณํ } fn main() { let m: PlayerMove = PlayerMove::Run(Direction::Left); println!("์ด๋ฒ ์ฐจ๋ก: {:?}", m); }
ํค ํฌ์ธํธ:
- Enumerations allow you to collect a set of values under one type.
Direction
์ ๋ณํ์ ๊ฐ์ง๋ ์ด๊ฑฐํ ํ์ ์ ๋๋ค. ์ฌ๊ธฐ์๋Direction::Left
์Direction::Right
์ ๋ ๊ฐ์ด ํฌํจ๋ฉ๋๋ค.PlayerMove
is a type with three variants. In addition to the payloads, Rust will store a discriminant so that it knows at runtime which variant is in aPlayerMove
value.- This might be a good time to compare structs and enums:
- In both, you can have a simple version without fields (unit struct) or one with different types of fields (variant payloads).
- You could even implement the different variants of an enum with separate structs but then they wouldnโt be the same type as they would if they were all defined in an enum.
- Rust๋ ํ๋ณ์์ ์ ์ฅํ๋ ๋ฐ ์ต์ํ์ ๊ณต๊ฐ์ ์ฌ์ฉํฉ๋๋ค.
-
ํ์ํ ๊ฒฝ์ฐ ํ์ํ ๊ฐ์ฅ ์์ ํฌ๊ธฐ์ ์ ์๋ฅผ ์ ์ฅํฉ๋๋ค.
-
ํ์ฉ๋ ๋ณํ ๊ฐ์ด ๋ชจ๋ ๋นํธ ํจํด์ ํฌํจํ์ง ์๋ ๊ฒฝ์ฐ ์๋ชป๋ ๋นํธ ํจํด์ ์ฌ์ฉํ์ฌ ํ๋ณ์์ ์ธ์ฝ๋ฉํฉ๋๋ค(โํ์ ์ต์ ํโ). ์๋ฅผ ๋ค์ด
Option<&u8>
์ ์ ์๋ฅผ ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ๋None
๋ณํ์ ๊ฒฝ์ฐNULL
์ ์ ์ฅํฉ๋๋ค. -
C์์ ์ฐ๋์ ์ํด ์๋ณ์ ๊ฐ์ ์ง์ ์ง์ ํ ์๋ ์์ต๋๋ค:
#[repr(u32)] enum Bar { A, // 0 B = 10000, C, // 10001 } fn main() { println!("A: {}", Bar::A as u32); println!("B: {}", Bar::B as u32); println!("C: {}", Bar::C as u32); }
repr
์์ฑ์ด ์๋ค๋ฉด 10001์ด 2 ๋ฐ์ดํธ๋ก ํํ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ์๋ณ์์ ํ์ ํฌ๊ธฐ๋ 2 ๋ฐ์ดํธ๊ฐ ๋ฉ๋๋ค.
-
๋ ์ดํด๋ณด๊ธฐ
Rust์๋ enum์ด ๋ ์ ์ ๊ณต๊ฐ์ ์ฐจ์งํ๋๋ก ํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ ์ฌ๋ฌ ์ต์ ํ๊ฐ ์์ต๋๋ค.
-
๋ํฌ์ธํฐ ์ต์ ํ: ์ด๋ค ํ์ ๋ค์ ๋ํด์ ๋ฌ์คํธ๋
size_of::<T>()
๊ฐsize_of::<Option<T>>()
์ ๊ฐ์ ๊ฒ์ ๋ณด์ฅํฉ๋๋ค.์ค์ ๋ก ๋ํฌ์ธํฐ ์ต์ ํ๊ฐ ์ ์ฉ๋ ๊ฒ์ ํ์ธํ๊ณ ์ถ๋ค๋ฉด ์๋์ ์์ ์ฝ๋๋ฅผ ์ฌ์ฉํ์ธ์. ์ฃผ์ํ ์ ์, ์ฌ๊ธฐ์์ ๋ณด์ฌ์ฃผ๋ ๋นํธ ํจํด์ด ์ปดํ์ผ๋ฌ๊ฐ ๋ณด์ฅํด ์ฃผ๋ ๊ฒ์ ์๋๋ผ๋ ์ ์ ๋๋ค. ์ฌ๊ธฐ์ ์์กดํ๋ ๊ฒ์ ์์ ํ unsafeํฉ๋๋ค.
use std::mem::transmute; macro_rules! dbg_bits { ($e:expr, $bit_type:ty) => { println!("- {}: {:#x}", stringify!($e), transmute::<_, $bit_type>($e)); }; } fn main() { unsafe { println!("bool:"); dbg_bits!(false, u8); dbg_bits!(true, u8); println!("Option<bool>:"); dbg_bits!(None::<bool>, u8); dbg_bits!(Some(false), u8); dbg_bits!(Some(true), u8); println!("Option<Option<bool>>:"); dbg_bits!(Some(Some(false)), u8); dbg_bits!(Some(Some(true)), u8); dbg_bits!(Some(None::<bool>), u8); dbg_bits!(None::<Option<bool>>, u8); println!("Option<&i32>:"); dbg_bits!(None::<&i32>, usize); dbg_bits!(Some(&0i32), usize); } }
์ ์ ๋ณ์(static)์ ์์(const)
Static and constant variables are two different ways to create globally-scoped values that cannot be moved or reallocated during the execution of the program.
์์(const
)
์์๋ ์ปดํ์ผ ํ ๋ ๊ทธ ๊ฐ์ด ์ ํด์ง๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ๊ฐ์ ๊ทธ ์์๊ฐ ์ฌ์ฉ๋๋ ๋ชจ๋ ๋ถ๋ถ์์ ์ธ๋ผ์ธ ๋ฉ๋๋ค:
const DIGEST_SIZE: usize = 3; const ZERO: Option<u8> = Some(42); fn compute_digest(text: &str) -> [u8; DIGEST_SIZE] { let mut digest = [ZERO.unwrap_or(0); DIGEST_SIZE]; for (idx, &b) in text.as_bytes().iter().enumerate() { digest[idx % DIGEST_SIZE] = digest[idx % DIGEST_SIZE].wrapping_add(b); } digest } fn main() { let digest = compute_digest("Hello"); println!("digest: {digest:?}"); }
Rust RFC Book์ ๋ฐ๋ฅด๋ฉด ์์๋, ๊ทธ ์์๊ฐ ์ฌ์ฉ๋๋ ๊ณณ์ ์ธ๋ผ์ธ ๋ฉ๋๋ค.
const
๊ฐ์ ์์ฑํ ๋์๋ const
๋ก ๋งํน๋ ํจ์๋ง์ด ํธ์ถ ๊ฐ๋ฅํ๋ฉฐ, ์ด ํจ์๋ค์ ์ปดํ์ผ ์์ ํธ์ถ์ด ๋ฉ๋๋ค. ๋ฌผ๋ก const
ํจ์๋ค์ ๋ฐํ์์ ํธ์ถํ๋ ๊ฒ๋ ๊ฐ๋ฅํฉ๋๋ค.
์ ์ ๋ณ์(static
)
์ ์ ๋ณ์๋ ํ๋ก๊ทธ๋จ์ด ์ํ๋๋ ๋์ ์ ์ง๊ฐ ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ฏ๋ก ๋ค๋ฅธ ๋ณ์๋ก ์ด๋(move)๋์ง ์์ต๋๋ค:
static BANNER: &str = "Welcome to RustOS 3.14"; fn main() { println!("{BANNER}"); }
As noted in the Rust RFC Book, these are not inlined upon use and have an actual associated memory location. This is useful for unsafe and embedded code, and the variable lives through the entirety of the program execution. When a globally-scoped value does not have a reason to need object identity, const
is generally preferred.
- ๋ฌ์คํธ์
const
๋ C++์constexpr
๊ณผ ๋งค์ฐ ๋น์ทํฉ๋๋ค. - ๋ฐ๋ฉด์ ๋ฌ์คํธ์
static
์ C++์const
๋ ๊ฐ๋ณ ์ ์ ๋ณ์(mutable global variable)์ ํจ์ฌ ๋ ์ ์ฌํฉ๋๋ค. static
์ ๊ฐ์ฒด์ ์ ์ฒด์ฑ์ ๋ถ์ฌํฉ๋๋ค. ์ ์ฒด์ ์ด๋ ๋ฉ๋ชจ๋ฆฌ ์์์์ ์ฃผ์, ๊ทธ๋ฆฌ๊ณ ๋ด๋ถ ์ํ๋ฅผ ์๋ฏธํฉ๋๋ค.- ํ๋ก๊ทธ๋จ ์ํ์ ๊ทธ ๊ฐ์ด ์ ํด์ง๋ ์์๊ฐ ํ์ํ ๊ฒฝ์ฐ๋ ๋๋ญ ๋๋ค. ๊ทธ๋ฌ๋ ๊ทธ๋ ๋ค๊ณ ํด๋, ์ ์ ๋ณ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ๋ณด๋ค๋ ๋ ์ ์ฉํ๊ณ ์์ ํฉ๋๋ค.
์์ฑ ๋น๊ต ํ ์ด๋ธ:
์์ฑ | ์ ์ (static) ๋ณ์ | ์์(constant) |
---|---|---|
๋ฉ๋ชจ๋ฆฌ ์์ ์ฃผ์๊ฐ ์๋๊ฐ | ์ | ์๋์ค(์ธ๋ผ์ธ ๋จ) |
ํ๋ก๊ทธ๋จ์ด ์ํ๋๋ ๋์ ๊ณ์ ์ด์ ์๋๊ฐ | ์ | ์๋์ค |
๋ณ๊ฒฝ ๊ฐ๋ฅํ๊ฐ | ์ (๊ทธ๋ฌ๋ ์์ ํ์ง ์์) | ์๋์ค |
์ปดํ์ผ์ ๊ทธ ๊ฐ์ด ๊ฒฐ์ ๋๋๊ฐ | ์ (์ปดํ์ผ์ ์ด๊ธฐํ ๋จ) | ์ |
์ฌ์ฉ๋๋ ๊ณณ์ ์ธ๋ผ์ธ ๋๋๊ฐ | ์๋์ค | ์ |
๋ ์ดํด๋ณด๊ธฐ
Because static
variables are accessible from any thread, they must be Sync
. Interior mutability is possible through a Mutex
, atomic or similar.
Thread-local data can be created with the macro std::thread_local
.
ํ์ ๋ณ์นญ
ํ์ ๋ณ์นญ์ ๋ค๋ฅธ ํ์ ์ ์ด๋ฆ์ ์์ฑํฉ๋๋ค. ๋ ํ์ ์ ์๋ก ๋ฐ๊ฟ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
enum CarryableConcreteItem { Left, Right, } type Item = CarryableConcreteItem; // ๋ณ์นญ์ ๋ค์๊ณผ ๊ฐ์ด ๊ธธ๊ณ ๋ณต์กํ ํ์ ์์ ๋ ์ ์ฉํฉ๋๋ค. use std::cell::RefCell; use std::sync::{Arc, RwLock}; type PlayerInventory = RwLock<Vec<Arc<RefCell<Item>>>>;
C ํ๋ก๊ทธ๋๋จธ๋ ์ด๋ฅผ typedef
์ ์ ์ฌํ ๊ฒ์ผ๋ก ์ธ์ํฉ๋๋ค.
์ฐ์ต๋ฌธ์ : ์๋ฆฌ๋ฒ ์ดํฐ ์ด๋ฒคํธ
์๋ฆฌ๋ฒ ์ดํฐ ์ ์ด ์์คํ
์ ์ด๋ฒคํธ๋ฅผ ๋ํ๋ด๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. ๋ค์ํ ์ด๋ฒคํธ๋ฅผ ๊ตฌ์ฑํ๊ธฐ ์ํด ํ์
๊ณผ ํจ์๋ฅผ ์ ์ํ๋ ๊ฒ์ ๊ฐ๋ฐ์์ ๋ชซ์
๋๋ค. ํด๋น ํ์
์ {:?}
๋ฌธ๋ฒ์ ํตํด ์ถ๋ ฅํ ์ ์๋๋ก #[derive(Debug)]
๋ฅผ ์ฌ์ฉํฉ์๋ค.
์ด ์ฐ์ต์์๋ main
ํจ์๊ฐ ์๋ฌ์์ด ๋์ํ๋๋ก ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ง ๋ง๋ค๋ฉด ๋ฉ๋๋ค. ์ด ๊ณผ์ ์ ๋ค์ ๋ถ๋ถ์์๋ ์ด๋ฌํ ๊ตฌ์กฐ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ ๋ค๋ฃน๋๋ค.
#[derive(Debug)] /// ์ปจํธ๋กค๋ฌ๊ฐ ๋ฐ์ํด์ผ ํ๋ ์๋ฆฌ๋ฒ ์ดํฐ ์์คํ ์ ์ด๋ฒคํธ์ ๋๋ค. enum Event { // TODO: ํ์ํ ๋ณํ๋ค์ ์ถ๊ฐํ์ธ์. } /// ์ด๋ ๋ฐฉํฅ์ ๋๋ค. #[derive(Debug)] enum Direction { Up, Down, } /// ์๋ฆฌ๋ฒ ์ดํฐ๊ฐ ์ง์ ๋ ์ธต์ ๋์ฐฉํ์ต๋๋ค. fn car_arrived(floor: i32) -> Event { todo!() } /// ์๋ฆฌ๋ฒ ์ดํฐ ๋ฌธ์ด ์ด๋ ธ์ต๋๋ค. fn car_door_opened() -> Event { todo!() } /// ์๋ฆฌ๋ฒ ์ดํฐ ๋ฌธ์ด ๋ซํ์ต๋๋ค. fn car_door_closed() -> Event { todo!() } /// ์ง์ ๋ ์ธต์ ์๋ฆฌ๋ฒ ์ดํฐ ๋ก๋น์์ ๋ฐฉํฅ ๋ฒํผ์ ๋๋ ์ต๋๋ค. fn lobby_call_button_pressed(floor: i32, dir: Direction) -> Event { todo!() } /// ์๋ฆฌ๋ฒ ์ดํฐ์์ ์ธต ๋ฒํผ์ ๋๋ ์ต๋๋ค. fn car_floor_button_pressed(floor: i32) -> Event { todo!() } fn main() { println!( "1์ธต ์น๊ฐ์ด ์์ชฝ ๋ฒํผ์ ๋๋ ์ต๋๋ค. {:?}", lobby_call_button_pressed(0, Direction::Up) ); println!("์๋ฆฌ๋ฒ ์ดํฐ๊ฐ 1์ธต์ ๋์ฐฉํ์ต๋๋ค: {:?}", car_arrived(0)); println!("์๋ฆฌ๋ฒ ์ดํฐ ๋ฌธ์ด ์ด๋ ธ์ต๋๋ค. {:?}", car_door_opened()); println!( "์น๊ฐ์ด 3์ธต ๋ฒํผ์ ๋๋ ์ต๋๋ค. {:?}", car_floor_button_pressed(3) ); println!("์๋ฆฌ๋ฒ ์ดํฐ ๋ฌธ์ด ๋ซํ์ต๋๋ค: {:?}", car_door_closed()); println!("์๋ฆฌ๋ฒ ์ดํฐ๊ฐ 3์ธต์ ๋์ฐฉํ์ต๋๋ค. {:?}", car_arrived(3)); }
ํด๋ต
#[derive(Debug)] /// ์ปจํธ๋กค๋ฌ๊ฐ ๋ฐ์ํด์ผ ํ๋ ์๋ฆฌ๋ฒ ์ดํฐ ์์คํ ์ ์ด๋ฒคํธ์ ๋๋ค. enum Event { /// ๋ฒํผ์ ๋๋ ์ต๋๋ค. ButtonPressed(Button), /// ์๋ฆฌ๋ฒ ์ดํฐ๊ฐ ์ง์ ๋ ์ธต์ ๋์ฐฉํ์ต๋๋ค. CarArrived(Floor), /// ์๋ฆฌ๋ฒ ์ดํฐ ๋ฌธ์ด ์ด๋ ธ์ต๋๋ค. CarDoorOpened, /// ์๋ฆฌ๋ฒ ์ดํฐ ๋ฌธ์ด ๋ซํ์ต๋๋ค. CarDoorClosed, } /// ์ธต์ ์ ์๋ก ํ์๋ฉ๋๋ค. type Floor = i32; /// ์ด๋ ๋ฐฉํฅ์ ๋๋ค. #[derive(Debug)] enum Direction { Up, Down, } /// ์ฌ์ฉ์๊ฐ ์ก์ธ์คํ ์ ์๋ ๋ฒํผ์ ๋๋ค. #[derive(Debug)] enum Button { /// ํน์ ์ธต์ ์๋ฆฌ๋ฒ ์ดํฐ ๋ก๋น์ ์๋ ๋ฒํผ์ ๋๋ค. LobbyCall(Direction, Floor), /// ์๋ฆฌ๋ฒ ์ดํฐ ๋ด๋ถ์ ์ธต ๋ฒํผ์ ๋๋ค. CarFloor(Floor), } /// ์๋ฆฌ๋ฒ ์ดํฐ๊ฐ ์ง์ ๋ ์ธต์ ๋์ฐฉํ์ต๋๋ค. fn car_arrived(floor: i32) -> Event { Event::CarArrived(floor) } /// ์๋ฆฌ๋ฒ ์ดํฐ ๋ฌธ์ด ์ด๋ ธ์ต๋๋ค. fn car_door_opened() -> Event { Event::CarDoorOpened } /// ์๋ฆฌ๋ฒ ์ดํฐ ๋ฌธ์ด ๋ซํ์ต๋๋ค. fn car_door_closed() -> Event { Event::CarDoorClosed } /// ์ง์ ๋ ์ธต์ ์๋ฆฌ๋ฒ ์ดํฐ ๋ก๋น์์ ๋ฐฉํฅ ๋ฒํผ์ ๋๋ ์ต๋๋ค. fn lobby_call_button_pressed(floor: i32, dir: Direction) -> Event { Event::ButtonPressed(Button::LobbyCall(dir, floor)) } /// ์๋ฆฌ๋ฒ ์ดํฐ์์ ์ธต ๋ฒํผ์ ๋๋ ์ต๋๋ค. fn car_floor_button_pressed(floor: i32) -> Event { Event::ButtonPressed(Button::CarFloor(floor)) } fn main() { println!( "1์ธต ์น๊ฐ์ด ์์ชฝ ๋ฒํผ์ ๋๋ ์ต๋๋ค. {:?}", lobby_call_button_pressed(0, Direction::Up) ); println!("์๋ฆฌ๋ฒ ์ดํฐ๊ฐ 1์ธต์ ๋์ฐฉํ์ต๋๋ค: {:?}", car_arrived(0)); println!("์๋ฆฌ๋ฒ ์ดํฐ ๋ฌธ์ด ์ด๋ ธ์ต๋๋ค. {:?}", car_door_opened()); println!( "์น๊ฐ์ด 3์ธต ๋ฒํผ์ ๋๋ ์ต๋๋ค. {:?}", car_floor_button_pressed(3) ); println!("์๋ฆฌ๋ฒ ์ดํฐ ๋ฌธ์ด ๋ซํ์ต๋๋ค: {:?}", car_door_closed()); println!("์๋ฆฌ๋ฒ ์ดํฐ๊ฐ 3์ธต์ ๋์ฐฉํ์ต๋๋ค. {:?}", car_arrived(3)); }
2์ผ์ฐจ ๊ฐ์
Now that we have seen a fair amount of Rust, today will focus on Rustโs type system:
- Pattern matching: extracting data from structures.
- ๋ฉ์๋: ํจ์๋ฅผ ํ์ ๊ณผ ์ฐ๊ฒฐ
- ํธ๋ ์: ์ฌ๋ฌ ํ์ ์์ ๊ณต์ ํ๋ ๋์
- ์ ๋ค๋ฆญ: ๋ค๋ฅธ ํ์ ์ ํ์ ๋งค๊ฐ๋ณ์ํ
- ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ ๋ฐ ํธ๋ ์: Rust์ ํ๋ถํ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋๋ฌ๋ณด๊ธฐ
์ผ์ ์์ฝ
Including 10 minute breaks, this session should take about 2 hours and 55 minutes. It contains:
Segment | Duration |
---|---|
๊ฐ์ | 3 minutes |
ํจํด ๋งค์นญ | 1 hour |
๋ฉ์๋์ ํธ๋ ์ดํธ | 50 minutes |
์ ๋ค๋ฆญ | 40 minutes |
ํจํด ๋งค์นญ
This segment should take about 1 hour. It contains:
Slide | Duration |
---|---|
Matching Values | 10 minutes |
์ด๊ฑฐํ ๋ถํด(์ญ๊ตฌ์กฐํ) | 10 minutes |
ํ๋ฆ ์ ์ด | 10 minutes |
์ฐ์ต๋ฌธ์ : ํํ์ ํ๊ฐ | 30 minutes |
Matching Values
The match
keyword lets you match a value against one or more patterns. The comparisons are done from top to bottom and the first match wins.
C/C++์ switch
์ ๋น์ทํ๊ฒ ๊ฐ์ ํจํด์ผ๋ก ์ฌ์ฉํ ์๋ ์์ต๋๋ค:
#[rustfmt::skip] fn main() { let input = 'x'; match input { 'q' => println!("Quitting"), 'a' | 's' | 'w' | 'd' => println!("์ด๋ฆฌ์ ๋ฆฌ ์ด๋"), '0'..='9' => println!("์ซ์ ์ ๋ ฅ"), key if key.is_lowercase() => println!("์๋ฌธ์: {key}"), _ => println!("๊ธฐํ"), } }
The _
pattern is a wildcard pattern which matches any value. The expressions must be exhaustive, meaning that it covers every possibility, so _
is often used as the final catch-all case.
Match can be used as an expression. Just like if
, each match arm must have the same type. The type is the last expression of the block, if any. In the example above, the type is ()
.
ํจํด์ ๋ณ์(์ด ์์์๋ key
)๋ ์ผ์น ๋ถ๋ฌธ ๋ด์์ ์ฌ์ฉํ ์ ์๋ ๋ฐ์ธ๋ฉ์ ๋ง๋ญ๋๋ค.
์ผ์น ๊ฐ๋๋ ์กฐ๊ฑด์ด ์ฐธ์ธ ๊ฒฝ์ฐ์๋ง ๋ถ๋ถ์ด ์ผ์นํ๋๋ก ํฉ๋๋ค.
ํค ํฌ์ธํธ:
-
ํจํด์์ ์ฌ์ฉ๋๋ ํน์ ๋ฌธ์๋ค์ ์๋ ค์ฃผ์ธ์
|
: or ๊ธฐํธ์ ๋๋ค..
: ํ์ํ ๋งํผ ํ์ฅํฉ๋๋ค1..=5
: ๋ ๊ฐ(์ฌ๊ธฐ์๋ 5)์ ํฌํจํ๋ ๋ฒ์๋ฅผ ๋ํ๋ ๋๋ค_
: ์์ผ๋์นด๋์ ๋๋ค
-
๋งค์น ๊ฐ๋๋ ๋ณ๋์ ๋ฌธ๋ฒ ์์๋ก์ ํจํด ์์ฒด๋ง์ผ๋ก ํํํ๊ธฐ ์ด๋ ค์ด ๋ณต์กํ ๊ฒฝ์ฐ๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ํํํ๊ณ ์ ํ ๋ ์ ์ฉํฉ๋๋ค.
-
๋งค์น์ ๊ฐ ํ(ํน์ ๊ฐ์ง) ์์ ๋ฐ๋ก
if
๋ฅผ ์ฌ์ฉํ ๊ฒ๊ณผ ๋ค๋ฆ ๋๋ค. ๋งค์น ๊ฐ์ง์=>
๋ค์ ์ฌ์ฉ๋if
ํํ์์ ํด๋น ๊ฐ์ง๊ฐ ์ ํ๋ ๋ค์์ ์คํ๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ฌ๊ธฐ์if
์กฐ๊ฑด์ด ์คํจํ๋๋ผ๋ ์๋match
์ ๋ค๋ฅธ ๊ฐ์ง๋ ๊ณ ๋ ค๋์ง ์์ต๋๋ค. -
๊ฐ๋์ ์ ์๋ ์กฐ๊ฑด์
|
๋ฅผ ํฌํจํ๋ ํจํด์ ๋ชจ๋ ํํ์์ ์ ์ฉ๋ฉ๋๋ค.
์ด๊ฑฐํ ๋ถํด(์ญ๊ตฌ์กฐํ)
ํํ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ๊ตฌ์กฐ์ฒด ๋ฐ enum๋ ๋ค์์ ์ผ์น์์ผ ๋์คํธ๋ญ์ฒ๋งํ ์ ์์ต๋๋ค.
๊ตฌ์กฐ์ฒด
struct Foo { x: (u32, u32), y: u32, } #[rustfmt::skip] fn main() { let foo = Foo { x: (1, 2), y: 3 }; match foo { Foo { x: (1, b), y } => println!("x.0 = 1, b = {b}, y = {y}"), Foo { y: 2, x: i } => println!("y = 2, x = {i:?}"), Foo { y, .. } => println!("y = {y}, ๋ค๋ฅธ ํ๋๋ ๋ฌด์๋จ"), } }
์ด๊ฑฐํ
๊ตฌ์กฐ์ฒด๋ ์ด๊ฑฐํ ๊ฐ์ ์ผ๋ถ๋ฅผ ํจํด ๋งค์น๋ฅผ ํตํด ๋ณ์์ ๋ฐ์ธ๋ฉํ ์ ์์ต๋๋ค. ๊ฐ๋จํ enum
ํ์
์ ๋จผ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
enum Result { Ok(i32), Err(String), } fn divide_in_two(n: i32) -> Result { if n % 2 == 0 { Result::Ok(n / 2) } else { Result::Err(format!("{n}์(๋ฅผ) ๋ ๊ฐ์ ๋์ผํ ๋ถ๋ถ์ผ๋ก ๋๋ ์ ์์")) } } fn main() { let n = 100; match divide_in_two(n) { Result::Ok(half) => println!("{n}์(๋ฅผ) ๋๋ก ๋๋ ๊ฐ์ {half}์ ๋๋ค."), Result::Err(msg) => println!("์ฃ์กํฉ๋๋ค. ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. {msg}"), } }
match
๊ตฌ๋ฌธ์์ divide_in_two
ํจ์์์ ๋ฐํ๋๋ Result
๊ฐ์ ๋ ๊ฐ์ ํ(ํน์ ๊ฐ์ง)๋ก ๋ถํด(destructure) ํ์์ต๋๋ค. ์ฒซ๋ฒ์งธ ํ์์ half
๋ Ok
variant์ ๋ด๊ธด ๊ฐ์ผ๋ก ๋ฐ์ธ๋ฉ๋ฉ๋๋ค. ๋๋ฒ์งธ ํ์์ msg
๋ ์ค๋ฅ ๋ฉ์์ง ๋ฌธ์์ด์ ๋ฐ์ธ๋ฉ๋ฉ๋๋ค.
๊ตฌ์กฐ์ฒด
foo
์ ๋ฆฌํฐ๋ด ๊ฐ์ ๋ค๋ฅธ ํจํด๊ณผ ์ผ์นํ๋๋ก ๋ณ๊ฒฝํฉ๋๋ค.Foo
์ ์ ํ๋๋ฅผ ์ถ๊ฐํ๊ณ ํ์์ ๋ฐ๋ผ ํจํด์ ๋ณ๊ฒฝํฉ๋๋ค.- ์บก์ฒ์ ์์ ํํ์์ ๊ตฌ๋ถํ๊ธฐ ์ด๋ ค์ธ ์ ์์ต๋๋ค. ๋ ๋ฒ์งธ ๋ถ๋ฌธ์
2
๋ฅผ ๋ณ์๋ก ๋ณ๊ฒฝํด ๋ณด๊ณ ์๋ํ์ง ์๋ ๊ฒ์ ํ์ธํ์ธ์.const
๋ก ๋ณ๊ฒฝํ๊ณ ๋ค์ ์๋ํ๋์ง ํ์ธํฉ๋๋ค.
์ด๊ฑฐํ
ํค ํฌ์ธํธ:
if
/else
ํํ์์ ์ด๊ฑฐํ์ ๋ฐํํ๊ณ , ์ด ๊ฐ์ ๋์ค์match
๋ก ๋ถํด๋ฉ๋๋ค.- ์ด๊ฑฐํ์ ์ธ๋ฒ์งธ variant๋ฅผ ์ถ๊ฐํ๊ณ ์ฝ๋๋ฅผ ์คํํ์ฌ ์ค๋ฅ๋ฅผ ํ์ํด๋ณด์ธ์. ์ฝ๋ ์ด๋ ๋ถ๋ถ์ ๋๋ฝ์ด ์๋์ง, ๊ทธ๋ฆฌ๊ณ ์ปดํ์ผ๋ฌ๊ฐ ์ด๋ค ์์ผ๋ก ํํธ๋ฅผ ์ฃผ๋์ง ๊ฐ์ด ์ดํด๋ณด์ธ์.
- The values in the enum variants can only be accessed after being pattern matched.
- Demonstrate what happens when the search is inexhaustive. Note the advantage the Rust compiler provides by confirming when all cases are handled.
- Save the result of
divide_in_two
in theresult
variable andmatch
it in a loop. That wonโt compile becausemsg
is consumed when matched. To fix it, match&result
instead ofresult
. That will makemsg
a reference so it wonโt be consumed. This โmatch ergonomicsโ appeared in Rust 2018. If you want to support older Rust, replacemsg
withref msg
in the pattern.
ํ๋ฆ ์ ์ด
Rust์๋ ๋ค๋ฅธ ์ธ์ด์๋ ๋ค๋ฅธ ๋ช ๊ฐ์ง ์ ์ด ํ๋ฆ ๊ตฌ์กฐ๊ฐ ์์ผ๋ฉฐ ํจํด ์ผ์น์ ์ฌ์ฉ๋ฉ๋๋ค.
if let
ํํ์while let
expressionsmatch
ํํ์
if let
ํํ์
if let
ํํ์์ ์ฌ์ฉํ๋ฉด ๊ฐ์ด ํจํด๊ณผ ์ผ์นํ๋์ง์ ๋ฐ๋ผ ๋ค๋ฅธ ์ฝ๋๋ฅผ ์คํํ ์ ์์ต๋๋ค:
fn sleep_for(secs: f32) { let dur = if let Ok(dur) = std::time::Duration::try_from_secs_f32(secs) { dur } else { std::time::Duration::from_millis(500) }; std::thread::sleep(dur); println!("{:?} ๋์ ์ ๋ค์์ต๋๋ค.", dur); } fn main() { sleep_for(-10.0); sleep_for(0.8); }
let else
expressions
ํจํด์ ์ผ์น์ํค๊ณ ํจ์์์ ๋ฐํํ๋ ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ์๋ let else
๋ฅผ ์ฌ์ฉํฉ๋๋ค. โelseโ ์ฌ๋ก๋ ํด๋น ์ฝ๋๋ฅผ ๋ฒ์ด๋์ผ ํฉ๋๋ค (return
, break
๋๋ ํจ๋ - ๋ธ๋ก์ ๋ค์ ์์น๋ก ์ด๋ํ๋ ๊ฒ๋ง ์๋๋ฉด ๋ฉ๋๋ค).
fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> { let s = if let Some(s) = maybe_string { s } else { return Err(String::from("None์ ๊ฐ์ ธ์ด")); }; let first_byte_char = if let Some(first_byte_char) = s.chars().next() { first_byte_char } else { return Err(String::from("got empty string")); }; if let Some(digit) = first_byte_char.to_digit(16) { Ok(digit) } else { Err(String::from("16์ง์๊ฐ ์๋")) } } fn main() { println!("๊ฒฐ๊ณผ: {:?}", hex_or_die_trying(Some(String::from("foo")))); }
๋ง์ง๋ง์ผ๋ก, ๋ฌดํ ๋ฃจํ๋ฅผ ๋ง๋๋ loop
ํค์๋๊ฐ ์์ต๋๋ค:
fn main() { let mut name = String::from("Comprehensive Rust ๐ฆ"); while let Some(c) = name.pop() { println!("character: {c}"); } // (There are more efficient ways to reverse a string!) }
Here String::pop
returns Some(c)
until the string is empty, after which it will return None
. The while let
lets us keep iterating through all items.
if-let
if let
์ดmatch
๋ณด๋ค ๋ ๊ฐ๊ฒฐํ ์ ์์ต๋๋ค(์: ํ๊ฐ์ง ๋ธ๋์น๋ง ํฅ๋ฏธ๋ก์ด ๊ฒฝ์ฐ). ์ด์ ๋ฌ๋ฆฌmatch
์์๋ ๋ชจ๋ ๋ธ๋์น๊ฐ ์ฒ๋ฆฌ๋์ด์ผ ํฉ๋๋ค.- ์ผ๋ฐ์ ์ฌ์ฉ๋ฒ์
Option
์ ์ฌ์ฉํ ๋Some
๊ฐ์ ์ฒ๋ฆฌํ๋ ๊ฒ์ ๋๋ค. match
์ ๋ฌ๋ฆฌif let
์ ํจํด ์ผ์น๋ฅผ ์ํ ๋ณดํธ ์ ์ ์ง์ํ์ง ์์ต๋๋ค.
let-else
์์์ ๋ณธ ๊ฒ์ฒ๋ผ if-let
์ ์ค์ฒฉํ ์ ์์ต๋๋ค. let-else
๊ตฌ์กฐ๋ ์ด ์ค์ฒฉ๋ ์ฝ๋์ ํ๋ฉดํ๋ฅผ ์ง์ํฉ๋๋ค. ์ฝ๋๊ฐ ์ด๋ป๊ฒ ๋ณํํ๋์ง ํ์๋ค์ด ๋ณผ ์ ์๋๋ก ์ด์ํ ๋ฒ์ ์ ๋ค์ ์์ฑํ์ธ์.
๋ค์ ์์ฑ๋ ๋ฒ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
#![allow(unused)] fn main() { fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> { let Some(s) = maybe_string else { return Err(String::from("None์ ๊ฐ์ ธ์ด")); }; let Some(first_byte_char) = s.chars().next() else { return Err(String::from("got empty string")); }; let Some(digit) = first_byte_char.to_digit(16) else { return Err(String::from("16์ง์๊ฐ ์๋")); }; return Ok(digit); } }
while-let
while let
์ ๊ฐ์ด ํจํด์ ๋งค์น๋๋ ๋์ ๊ณ์๋ฉ๋๋ค.- You could rewrite the
while let
loop as an infinite loop with an if statement that breaks when there is no value to unwrap forname.pop()
. Thewhile let
provides syntactic sugar for the above scenario.
์ฐ์ต๋ฌธ์ : ํํ์ ํ๊ฐ
Letโs write a simple recursive evaluator for arithmetic expressions.
์ฌ๊ธฐ์ Box
ํ์
์ ์ค๋งํธ ํฌ์ธํฐ์ด๋ฉฐ ์ด ๊ณผ์ ์ ํ๋ฐ๋ถ์์ ์์ธํ ๋ค๋ฃน๋๋ค. ํ
์คํธ์์ ๋ณผ ์ ์๋ฏ์ด ํํ์์ Box::new
๋ฅผ ์ฌ์ฉํ์ฌ โ๋ฐ์ค๋ก ํ์โํ ์ ์์ต๋๋ค. ๋ฐ์ค๋ก ํ์๋ ํํ์์ ํ๊ฐํ๋ ค๋ฉด deref ์ฐ์ฐ์ (*
)๋ฅผ ์ฌ์ฉํ์ฌ โ๋ฐ์ค ํ์๋ฅผ ํด์ โํฉ๋๋ค: eval(*boxed_expr)
.
์ผ๋ถ ํํ์์ ํ๊ฐํ ์ ์์ผ๋ฉฐ ์ค๋ฅ๋ฅผ ๋ฐํํฉ๋๋ค. ํ์ค Result<Value, String>
ํ์
์ ์ฑ๊ณตํ ๊ฐ์ ๋ํ๋ด๊ฑฐ๋ (Ok(Value)
) ๋ฉ์์ง์ ํจ๊ป ์ค๋ฅ๋ฅผ ๋ํ๋
๋๋ค (Err(String)
). ๋์ค์ ์ด Result ํ์
์ ๋ํด์ ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์ฝ๋๋ฅผ ๋ณต์ฌํ์ฌ Rust ํ๋ ์ด๊ทธ๋ผ์ด๋์ ๋ถ์ฌ๋ฃ๊ณ eval
๊ตฌํ์ ์์ํฉ๋๋ค. ์ต์ข
์์ฑ๋ฌผ์ ํ
์คํธ๋ฅผ ํต๊ณผํด์ผ ํฉ๋๋ค. todo!()
๋ฅผ ์ฌ์ฉํ๊ณ ํ
์คํธ๋ฅผ ํ๋์ฉ ํต๊ณผํ๋๋ก ํ๋ฉด ๋์์ด ๋ ์ ์์ต๋๋ค. #[ignore]
๋ฅผ ์ฌ์ฉํ์ฌ ํ
์คํธ๋ฅผ ์ผ์์ ์ผ๋ก ๊ฑด๋๋ธ ์๋ ์์ต๋๋ค.
#[test]
#[ignore]
fn test_value() { .. }
์ผ์ฐ ์๋ฃํ ๊ฒฝ์ฐ 0์ผ๋ก ๋๋๊ธฐ๋ ์ ์ ์ค๋ฒํ๋ก๊ฐ ๋ฐ์ํ๋ ํ
์คํธ๋ฅผ ์์ฑํด ๋ณด์ธ์. ํจ๋ ๋์ Result
๋ก ์ด ๋ฌธ์ ๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ ์์๊น์?
#![allow(unused)] fn main() { /// ๋ ๊ฐ์ ํ์ ํํ์์์ ์คํํ ์ฐ์ฐ์ ๋๋ค. #[derive(Debug)] enum Operation { Add, Sub, Mul, Div, } /// ํธ๋ฆฌ ํ์์ ํํ์์ ๋๋ค. #[derive(Debug)] enum Expression { /// ๋ ๊ฐ์ ํ์ ํํ์์ ๊ดํ ์ฐ์ฐ์ ๋๋ค. Op { op: Operation, left: Box<Expression>, right: Box<Expression> }, /// ๋ฆฌํฐ๋ด ๊ฐ Value(i64), } fn eval(e: Expression) -> Result<i64, String> { todo!() } #[test] fn test_value() { assert_eq!(eval(Expression::Value(19)), Ok(19)); } #[test] fn test_sum() { assert_eq!( eval(Expression::Op { op: Operation::Add, left: Box::new(Expression::Value(10)), right: Box::new(Expression::Value(20)), }), Ok(30) ); } #[test] fn test_recursion() { let term1 = Expression::Op { op: Operation::Mul, left: Box::new(Expression::Value(10)), right: Box::new(Expression::Value(9)), }; let term2 = Expression::Op { op: Operation::Mul, left: Box::new(Expression::Op { op: Operation::Sub, left: Box::new(Expression::Value(3)), right: Box::new(Expression::Value(4)), }), right: Box::new(Expression::Value(5)), }; assert_eq!( eval(Expression::Op { op: Operation::Add, left: Box::new(term1), right: Box::new(term2), }), Ok(85) ); } #[test] fn test_error() { assert_eq!( eval(Expression::Op { op: Operation::Div, left: Box::new(Expression::Value(99)), right: Box::new(Expression::Value(0)), }), Err(String::from("0์ผ๋ก ๋๋๊ธฐ")) ); } }
ํด๋ต
/// ๋ ๊ฐ์ ํ์ ํํ์์์ ์คํํ ์ฐ์ฐ์ ๋๋ค. #[derive(Debug)] enum Operation { Add, Sub, Mul, Div, } /// ํธ๋ฆฌ ํ์์ ํํ์์ ๋๋ค. #[derive(Debug)] enum Expression { /// ๋ ๊ฐ์ ํ์ ํํ์์ ๊ดํ ์ฐ์ฐ์ ๋๋ค. Op { op: Operation, left: Box<Expression>, right: Box<Expression> }, /// ๋ฆฌํฐ๋ด ๊ฐ Value(i64), } fn eval(e: Expression) -> Result<i64, String> { match e { Expression::Op { op, left, right } => { let left = match eval(*left) { Ok(v) => v, e @ Err(_) => return e, }; let right = match eval(*right) { Ok(v) => v, e @ Err(_) => return e, }; Ok(match op { Operation::Add => left + right, Operation::Sub => left - right, Operation::Mul => left * right, Operation::Div => { if right == 0 { return Err(String::from("0์ผ๋ก ๋๋๊ธฐ")); } else { left / right } } }) } Expression::Value(v) => Ok(v), } } #[test] fn test_value() { assert_eq!(eval(Expression::Value(19)), Ok(19)); } #[test] fn test_sum() { assert_eq!( eval(Expression::Op { op: Operation::Add, left: Box::new(Expression::Value(10)), right: Box::new(Expression::Value(20)), }), Ok(30) ); } #[test] fn test_recursion() { let term1 = Expression::Op { op: Operation::Mul, left: Box::new(Expression::Value(10)), right: Box::new(Expression::Value(9)), }; let term2 = Expression::Op { op: Operation::Mul, left: Box::new(Expression::Op { op: Operation::Sub, left: Box::new(Expression::Value(3)), right: Box::new(Expression::Value(4)), }), right: Box::new(Expression::Value(5)), }; assert_eq!( eval(Expression::Op { op: Operation::Add, left: Box::new(term1), right: Box::new(term2), }), Ok(85) ); } #[test] fn test_error() { assert_eq!( eval(Expression::Op { op: Operation::Div, left: Box::new(Expression::Value(99)), right: Box::new(Expression::Value(0)), }), Err(String::from("0์ผ๋ก ๋๋๊ธฐ")) ); } fn main() { let expr = Expression::Op { op: Operation::Sub, left: Box::new(Expression::Value(20)), right: Box::new(Expression::Value(10)), }; println!("expr: {:?}", expr); println!("๊ฒฐ๊ณผ: {:?}", eval(expr)); }
๋ฉ์๋์ ํธ๋ ์ดํธ
This segment should take about 50 minutes. It contains:
Slide | Duration |
---|---|
๋ฉ์๋ | 10 minutes |
ํธ๋ ์(Trait) | 15 minutes |
ํธ๋ ์ ์์ํ๊ธฐ | 3 minutes |
์ฐ์ต๋ฌธ์ : ์ผ๋ฐ min | 20 minutes |
๋ฉ์๋
๋ฌ์คํธ์์ ์ ์ธ๋ ํ์
์ ๋ํด impl
๋ธ๋ก์ ํจ์๋ฅผ ์ ์ธํ์ฌ ๋ฉ์๋๋ฅผ ์ฐ๊ฒฐ ํ ์ ์์ต๋๋ค:
#[derive(Debug)] struct Race { name: String, laps: Vec<i32>, } impl Race { // ์์ ์ ์์, ์ ์ ๋ฉ์๋์ ๋๋ค. fn new(name: &str) -> Self { Self { name: String::from(name), laps: Vec::new() } } // self์ ๋ํ ๋ ์ ์ ๋น๋ฆผ ์ฝ๊ธฐ/์ฐ๊ธฐ ์ก์ธ์ค์ ๋๋ค. fn add_lap(&mut self, lap: i32) { self.laps.push(lap); } // self์ ๋ํ ๊ณต์ ๋ฐ ์ฝ๊ธฐ ์ ์ฉ ๋น๋ฆผ ์ก์ธ์ค์ ๋๋ค. fn print_laps(&self) { println!("๋ฉ ํ์ {}ํ, {} ๊ฒฝ๊ธฐ:", self.laps.len(), self.name); for (idx, lap) in self.laps.iter().enumerate() { println!("{idx}๋ฉ: {lap}์ด"); } } // self์ ๋ ์ ์ ์์ ๊ถ fn finish(self) { let total: i32 = self.laps.iter().sum(); println!("{} ๋ ์ด์ค ์ข ๋ฃ, ์ด ๋ฉ ์๊ฐ: {}", self.name, total); } } fn main() { let mut race = Race::new("๋ชจ๋์ฝ ๊ทธ๋ํ๋ฆฌ"); race.add_lap(70); race.add_lap(68); race.print_laps(); race.add_lap(71); race.print_laps(); race.finish(); // race.add_lap(42); }
The self
arguments specify the โreceiverโ - the object the method acts on. There are several common receivers for a method:
&self
: ํธ์ถ์๋ก๋ถํฐ ๊ณต์ ๊ฐ๋ฅํ ๋ถ๋ณ ์ฐธ์กฐ ๋ฐฉ์์ผ๋ก ๊ฐ์ฒด๋ฅผ ๋น๋ ค์ด์ ๋ํ๋ ๋๋ค. ๊ฐ์ฒด๋ ๋ฉ์๋ ํธ์ถ ๋ค์๋ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.&mut self
: ํธ์ถ์๋ก๋ถํฐ ์ ์ผํ ๊ฐ๋ณ ์ฐธ์กฐ ๋ฐฉ์์ผ๋ก ๊ฐ์ฒด๋ฅผ ๋น๋ ค์ด์ ๋ํ๋ ๋๋ค. ๊ฐ์ฒด๋ ๋ฉ์๋ ํธ์ถ ๋ค์๋ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.self
: ํธ์ถ์๋ก๋ถํฐ ๊ฐ์ฒด์ ์์ ๊ถ์ ๊ฐ์ ธ์ค๊ณ ๊ฐ์ฒด๋ ํธ์ถ์๋ก๋ถํฐ ๋ฉ์๋๋ก ์ด๋๋ฉ๋๋ค. ๋ฉ์๋๊ฐ ๊ฐ์ฒด๋ฅผ ์์ ํ๊ฒ ๋๋ฉฐ ๋ฐ๋ผ์ ๋ช ์์ ์ผ๋ก ์์ ๊ถ์ ๋ค๋ฅธ ๊ณณ์ผ๋ก ์ ๋ฌํ์ง ์๋๋ค๋ฉด ๋ฉ์๋ ์ข ๋ฃ์ ํจ๊ป ๊ฐ์ฒด๋ drop(ํด์ )๋ฉ๋๋ค.mut self
: same as above, but the method can mutate the object.- ๋ฆฌ์๋ฒ ์์: ๊ตฌ์กฐ์ฒด์ ์ ์ ๋ฉ์๋๊ฐ ๋ฉ๋๋ค. ์ฃผ๋ก ์์ฑ์๋ฅผ ๋ง๋ค๋ ์ฌ์ฉํ๊ฒ ๋๋ฉฐ, ์์ฑ์๋ ํํ
new
๋ผ๊ณ ์ด๋ฆ๋ถ์ ๋๋ค.
ํค ํฌ์ธํธ:
- ๋ฉ์๋๋ฅผ ํจ์์ ๋น๊ตํ์ฌ ์๊ฐํ๋ ๊ฒ๋ ๋์์ด ๋ ์ ์์ต๋๋ค.
- ๋ฉ์๋๋ ๊ตฌ์กฐ์ฒด๋ ์ด๊ฑฐํ๊ณผ ๊ฐ์ ํ์
์ ์ธ์คํด์ค์์ ํธ์ถ ๋๋ฉฐ, ์ฒซ๋ฒ์งธ ๋งค๊ฐ๋ณ์(ํ๋ผ๋ฉํฐ)๋ ์ธ์คํด์ค๋ฅผ
self
๋ก ํ๊ธฐํฉ๋๋ค. - ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด receiver ๋ฌธ๋ฒ์ ์ฌ์ฉํ ์ ์๊ณ ์ฝ๋๋ฅผ ์ข๋ ์ฒด๊ณ์ ์ผ๋ก ์ ๋ฆฌํ ์ ์์ต๋๋ค. ๋ฉ์๋๋ค์ด ์์ธก ๊ฐ๋ฅํ ์์น์ ๋ชจ์ฌ ์์ผ๋ ์ฐพ๊ธฐ ์ฝ์ต๋๋ค.
- ๋ฉ์๋๋ ๊ตฌ์กฐ์ฒด๋ ์ด๊ฑฐํ๊ณผ ๊ฐ์ ํ์
์ ์ธ์คํด์ค์์ ํธ์ถ ๋๋ฉฐ, ์ฒซ๋ฒ์งธ ๋งค๊ฐ๋ณ์(ํ๋ผ๋ฉํฐ)๋ ์ธ์คํด์ค๋ฅผ
- ๋ฉ์๋ receiver์ธ
self
ํค์๋ ์ฌ์ฉ์ ์ธ๊ธํด ์ฃผ์๊ธฐ ๋ฐ๋๋๋ค.- ์์ ์ ๊ฒฝ์ฐ
self: &Self
์ ์ค์ธ ๋ฒ์ ์์ ์๋ ค์ฃผ๊ณ , ๊ตฌ์กฐ์ฒด์ ์ด๋ฆ์ ์ง์ ์ฌ์ฉํ๋ฉด ์ด๋ป๊ฒ ๋๋์ง ๋ณด์ฌ์ฃผ๋ ๊ฒ๋ ์ข์ต๋๋ค. impl
๋ธ๋ก ๋ด๋ถ์์๋Self
๊ฐ ํด๋น ํ์ ์ ์ด๋ฆ ๋์ฉ์ผ๋ก ์ฌ์ฉ๋ ์ ์์์ ์๋ ค์ฃผ์ธ์.- ๊ตฌ์กฐ์ฒด์ ํ๋๋ฅผ ์ ๊ทผํ ๋ ์ ํ๊ธฐ๋ฅผ ์ฌ์ฉํ๋ฏ์ด
self
์ ์ ํ๊ธฐ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋ณ ํ๋๋ค์ ์ ๊ทผํ ์ ์์ต๋๋ค. - This might be a good time to demonstrate how the
&self
differs fromself
by trying to runfinish
twice. self
๋ฅผ ์ฌ์ฉํ๋ ์ด๊ฐ์ ๋ณํ๋ค ์ธ์๋Box<Self>
์ ๊ฐ์ด ๋ฆฌ์๋ฒ ํ์ ์ผ๋ก ํ์ฉ๋๋ ํน๋ณํ ๋ํผ ํ์ ์ด ์์ต๋๋ค.
- ์์ ์ ๊ฒฝ์ฐ
ํธ๋ ์(Trait)
ํธ๋ ์์ ํ์ ์ ์ถ์ํ ํ๋๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์ธํฐํ์ด์ค์ ๋น์ทํฉ๋๋ค:
trait Pet { /// Return a sentence from this pet. fn talk(&self) -> String; /// Print a string to the terminal greeting this pet. fn greet(&self); }
-
ํธ๋ ์์ ํด๋น ํธ๋ ์์ ๊ตฌํํ๊ธฐ ์ํด ํ์ ์ด ๊ฐ์ ธ์ผ ํ๋ ์ฌ๋ฌ ๋ฉ์๋๋ฅผ ์ ์ํฉ๋๋ค.
-
In the โGenericsโ segment, next, we will see how to build functionality that is generic over all types implementing a trait.
Implementing Traits
trait Pet { fn talk(&self) -> String; fn greet(&self) { println!("์ค, ๊ท์ฌ์! ์ด๋ฆ์ด ๋ญ์ผ? {}", self.talk()); } } struct Dog { name: String, age: i8, } impl Pet for Dog { fn talk(&self) -> String { format!("๋ฉ๋ฉ, ์ ์ด๋ฆ์ {}์ ๋๋ค.", self.name) } } fn main() { let fido = Dog { name: String::from("Fido"), age: 5 }; fido.greet(); }
-
To implement
Trait
forType
, you use animpl Trait for Type { .. }
block. -
Unlike Go interfaces, just having matching methods is not enough: a
Cat
type with atalk()
method would not automatically satisfyPet
unless it is in animpl Pet
block. -
Traits may provide default implementations of some methods. Default implementations can rely on all the methods of the trait. In this case,
greet
is provided, and relies ontalk
.
ํธ๋ ์(Trait)
A trait can require that types implementing it also implement other traits, called supertraits. Here, any type implementing Pet
must implement Animal
.
trait Animal { fn leg_count(&self) -> u32; } trait Pet: Animal { fn name(&self) -> String; } struct Dog(String); impl Animal for Dog { fn leg_count(&self) -> u32 { 4 } } impl Pet for Dog { fn name(&self) -> String { self.0.clone() } } fn main() { let puppy = Dog(String::from("๋ ์ค")); println!("{} has {} legs", puppy.name(), puppy.leg_count()); }
This is sometimes called โtrait inheritanceโ but students should not expect this to behave like OO inheritance. It just specifies an additional requirement on implementations of a trait.
๊ณต์ ํ์
Associated types are placeholder types which are supplied by the trait implementation.
#[derive(Debug)] struct Meters(i32); #[derive(Debug)] struct MetersSquared(i32); trait Multiply { type Output; fn multiply(&self, other: &Self) -> Self::Output; } impl Multiply for Meters { type Output = MetersSquared; fn multiply(&self, other: &Self) -> Self::Output { MetersSquared(self.0 * other.0) } } fn main() { println!("{:?}", Meters(10).multiply(&Meters(20))); }
-
Associated types are sometimes also called โoutput typesโ. The key observation is that the implementer, not the caller, chooses this type.
-
Many standard library traits have associated types, including arithmetic operators and
Iterator
.
ํธ๋ ์ ์์ํ๊ธฐ
์ง์๋๋ ํธ๋ ์์ ๋ค์๊ณผ ๊ฐ์ด ๋ง์ถค ํ์ ์ ์๋์ผ๋ก ๊ตฌํํ ์ ์์ต๋๋ค.
#[derive(Debug, Clone, Default)] struct Player { name: String, strength: u8, hit_points: u8, } fn main() { let p1 = Player::default(); // ๊ธฐ๋ณธ ํธ๋ ์์ `default` ์์ฑ์๋ฅผ ์ถ๊ฐํฉ๋๋ค. let mut p2 = p1.clone(); // Clone ํธ๋ ์์ `clone` ๋ฉ์๋๋ฅผ ์ถ๊ฐํฉ๋๋ค. p2.name = String::from("EldurScrollz"); // Debug ํธ๋ ์์ `{:?}` ํํ์ ์ฌ์ฉํ ์ถ๋ ฅ์ ์ง์ํฉ๋๋ค. println!("{:?} ๋ {:?}", p1, p2); }
์์์ ๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํ๋๋ฉฐ ๋ง์ ํฌ๋ ์ดํธ๊ฐ ์ ์ฉํ ์์ ๋งคํฌ๋ก๋ฅผ ์ ๊ณตํ์ฌ ์ ์ฉํ ๊ธฐ๋ฅ์ ์ถ๊ฐํฉ๋๋ค. ์๋ฅผ ๋ค์ด serde
๋ #[derive(Serialize)]
๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌ์กฐ์ฒด์ ์ง๋ ฌํ ์ง์์ ์์ํ ์ ์์ต๋๋ค.
Exercise: Logger Trait
Letโs design a simple logging utility, using a trait Logger
with a log
method. Code which might log its progress can then take an &impl Logger
. In testing, this might put messages in the test logfile, while in a production build it would send messages to a log server.
However, the StderrLogger
given below logs all messages, regardless of verbosity. Your task is to write a VerbosityFilter
type that will ignore messages above a maximum verbosity.
This is a common pattern: a struct wrapping a trait implementation and implementing that same trait, adding behavior in the process. What other kinds of wrappers might be useful in a logging utility?
use std::fmt::Display; pub trait Logger { /// Log a message at the given verbosity level. fn log(&self, verbosity: u8, message: impl Display); } struct StderrLogger; impl Logger for StderrLogger { fn log(&self, verbosity: u8, message: impl Display) { eprintln!("verbosity={verbosity}: {message}"); } } fn do_things(logger: &impl Logger) { logger.log(5, "FYI"); logger.log(2, "Uhoh"); } // TODO: Define and implement `VerbosityFilter`. fn main() { let l = VerbosityFilter { max_verbosity: 3, inner: StderrLogger }; do_things(&l); }
ํด๋ต
use std::fmt::Display; pub trait Logger { /// Log a message at the given verbosity level. fn log(&self, verbosity: u8, message: impl Display); } struct StderrLogger; impl Logger for StderrLogger { fn log(&self, verbosity: u8, message: impl Display) { eprintln!("verbosity={verbosity}: {message}"); } } fn do_things(logger: &impl Logger) { logger.log(5, "FYI"); logger.log(2, "Uhoh"); } /// Only log messages up to the given verbosity level. struct VerbosityFilter { max_verbosity: u8, inner: StderrLogger, } impl Logger for VerbosityFilter { fn log(&self, verbosity: u8, message: impl Display) { if verbosity <= self.max_verbosity { self.inner.log(verbosity, message); } } } fn main() { let l = VerbosityFilter { max_verbosity: 3, inner: StderrLogger }; do_things(&l); }
์ ๋ค๋ฆญ
This segment should take about 40 minutes. It contains:
Slide | Duration |
---|---|
์ธ๋ถ(๋ค๋ฅธ์ธ์ด) ํจ์๋ค | 5 minutes |
์ ๋ค๋ฆญ ๋ฐ์ดํฐ ํ์ | 10 minutes |
์ ๋ค๋ฆญ ํ์ ์ ํ(ํธ๋ ์ ๊ฒฝ๊ณ) | 10 minutes |
ํธ๋ ์ ๊ตฌํํ๊ธฐ(impl Trait) | 5 minutes |
์ฐ์ต๋ฌธ์ : ์ผ๋ฐ min | 10 minutes |
์ธ๋ถ(๋ค๋ฅธ์ธ์ด) ํจ์๋ค
Rust supports generics, which lets you abstract algorithms or data structures (such as sorting or a binary tree) over the types used or stored.
/// `n` ๊ฐ์ ๋ฐ๋ผ `even` ๋๋ `odd`๋ฅผ ์ ํํฉ๋๋ค. fn pick<T>(n: i32, even: T, odd: T) -> T { if n % 2 == 0 { even } else { odd } } fn main() { println!("์ ํํ ์ซ์: {:?}", pick(97, 222, 333)); println!("์ ํํ ํํ: {:?}", pick(28, ("๊ฐ", 1), ("๊ณ ์์ด", 2))); }
-
Rust๋ ์ธ์ ๋ฐ ๋ฐํ ๊ฐ์ ํ์ ์ ๊ธฐ๋ฐ์ผ๋ก T์ ํ์ ์ ์ถ๋ก ํฉ๋๋ค.
-
์ด๋ C++ ํ ํ๋ฆฟ๊ณผ ๋น์ทํ์ง๋ง, Rust๋ ์ ๋ค๋ฆญ ํจ์๋ฅผ ์ฆ์ ๋ถ๋ถ์ ์ผ๋ก ์ปดํ์ผํ๋ฏ๋ก ์ ์ฝ ์กฐ๊ฑด๊ณผ ์ผ์นํ๋ ๋ชจ๋ ํ์ ์ ํจ์๊ฐ ์ ํจํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด
n == 0
์ธ ๊ฒฝ์ฐeven + odd
๋ฅผ ๋ฐํํ๋๋กpick
์ ์์ ํ์ธ์. ์ ์๊ฐ ํฌํจ๋pick
์ธ์คํด์คํ๋ง ์ฌ์ฉ๋๋๋ผ๋ Rust๋ ์ด๋ฅผ ์ ํจํ์ง ์์ ๊ฒ์ผ๋ก ๊ฐ์ฃผํฉ๋๋ค. C++์์๋ ์ด๋ฅผ ํ์ฉํฉ๋๋ค. -
Generic code is turned into non-generic code based on the call sites. This is a zero-cost abstraction: you get exactly the same result as if you had hand-coded the data structures without the abstraction.
์ ๋ค๋ฆญ ๋ฐ์ดํฐ ํ์
์ ๋ค๋ฆญ์ ์ฌ์ฉํ์ฌ ํ๋์ ํ์ ์ ์ถ์ํ ํ ์ ์์ต๋๋ค:
#[derive(Debug)] struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn coords(&self) -> (&T, &T) { (&self.x, &self.y) } // fn set_x(&mut self, x: T) } fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 }; println!("{integer:?} ๋ฐ {float:?}"); println!("์ขํ: {:?}", integer.coords()); }
-
์ง๋ฌธ:
impl<T> Point<T> {}
์์T
๊ฐ ์ ๋ ๋ฒ ์ฌ์ฉ๋ฉ๋๊น?- ์ ๋ค๋ฆญ ํ์ ์ ๋ํ ์ ๋ค๋ฆญ ๊ตฌํ ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด ๋ ์ ๋ค๋ฆญ์ ์๋ก ๋ ๋ฆฝ์ ์ ๋๋ค.
- ์ด๋ ์์์ ๋ชจ๋
T
์ ๋ํด์ ์ด ๋ฉ์๋๋ค์ด ์ ์๋๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. - It is possible to write
impl Point<u32> { .. }
.Point
๋ ์ฌ์ ํ ์ ๋ค๋ฆญ์ด๋ฉฐPoint<f64>
๋ฅผ ์ฌ์ฉํ ์๋ ์์ง๋ง ์ด ๋ธ๋ก์ ๋ฉ์๋๋Point<u32>
๋ง ์ธ ์ ์์ต๋๋ค.
-
์ ๋ณ์
let p = Point { x: 5, y: 10.0 };
๋ฅผ ์ ์ธํด ๋ณด์ธ์. ๋ค์๊ณผ ๊ฐ์ ๋ ๊ฐ์ ํ์ ๋ณ์๋ฅผ ์ฌ์ฉํ์ฌ ๋ค๋ฅธ ํ์ ์ ์์๋ฅผ ๊ฐ์ง ํฌ์ธํธ๋ฅผ ํ์ฉํ๋๋ก ์ฝ๋๋ฅผ ์ ๋ฐ์ดํธํฉ๋๋ค.T
์U
์ ๋ค๋ฆญ
Traits can also be generic, just like types and functions. A traitโs parameters get concrete types when it is used.
#[derive(Debug)] struct Foo(String); impl From<u32> for Foo { fn from(from: u32) -> Foo { Foo(format!("Converted from integer: {from}")) } } impl From<bool> for Foo { fn from(from: bool) -> Foo { Foo(format!("Converted from bool: {from}")) } } fn main() { let from_int = Foo::from(123); let from_bool = Foo::from(true); println!("{from_int:?}, {from_bool:?}"); }
-
The
From
trait will be covered later in the course, but its definition in thestd
docs is simple. -
Implementations of the trait do not need to cover all possible type parameters. Here,
Foo::From("hello")
would not compile because there is noFrom<&str>
implementation forFoo
. -
Generic traits take types as โinputโ, while associated types are a kind of โoutput type. A trait can have multiple implementations for different input types.
-
In fact, Rust requires that at most one implementation of a trait match for any type T. Unlike some other languages, Rust has no heuristic for choosing the โmost specificโ match. There is work on adding this support, called specialization.
์ ๋ค๋ฆญ ํ์ ์ ํ(ํธ๋ ์ ๊ฒฝ๊ณ)
์ ๋ค๋ฆญ์ ์ด์ฉํ๋ค ๋ณด๋ฉด ํ์ ์ด ์ด๋ค ํธ๋ ์์ ๊ตฌํํ๊ณ ์์ด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ๊ทธ๋์ผ ๊ทธ ํธ๋ ์์ ๋ฉ์๋๋ฅผ ํธ์ถํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
T: Trait
ํน์ impl Trait
๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค:
fn duplicate<T: Clone>(a: T) -> (T, T) { (a.clone(), a.clone()) } // struct NotClonable; fn main() { let foo = String::from("foo"); let pair = duplicate(foo); println!("{pair:?}"); }
-
NonClonable
์ ๋ง๋ค์ดduplicate
์ ์ ๋ฌํด ๋ณด์ธ์. -
์ฌ๋ฌ ํธ๋ ์์ด ํ์ํ ๊ฒฝ์ฐ
+
๋ฅผ ์ฌ์ฉํ์ฌ ํธ๋ ์์ ๊ฒฐํฉํฉ๋๋ค. -
where
๋ฌธ๋ฒ์ ์ฌ์ฉํ ์๋ ์์ต๋๋ค. ์๊ฐ์๋ค๋ ์ฝ๋๋ฅผ ์ฝ๋ค๊ฐ ๊ทธ ๋ฌธ๋ฒ์ ๋ง์ฃผํ ์ ์์ต๋๋ค.fn duplicate<T>(a: T) -> (T, T) where T: Clone, { (a.clone(), a.clone()) }
- ์ด๋ฅผ ์ด์ฉํ๋ฉด ํ์ ํ๋ผ๋ฉํฐ๊ฐ ๋ง์ ๊ฒฝ์ฐ ํจ์ ์๊ทธ๋์ฒ๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ์ ๋ฆฌํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
- ์ข ๋ ๊ฐ๋ ฅํ ์ถ๊ฐ ๊ธฐ๋ฅ๋ ์ ๊ณตํฉ๋๋ค.
:
์ผ์ชฝ์ ์์์ ํ์ (์๋ฅผ ๋ค์ดOption<T>
)์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
-
Rust๋ ์์ง ํน์ํ๋ฅผ ์ง์ํ์ง ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ์๋ณธ
duplicate
๊ฐ ์๋ ๊ฒฝ์ฐ ํน์duplicate(a: u32)
๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ์ ํจํ์ง ์์ต๋๋ค.
ํธ๋ ์ ๊ตฌํํ๊ธฐ(impl Trait
)
ํธ๋ ์ ๋ฐ์ด๋์ ์ ์ฌํ๊ฒ impl Trait
๋ฌธ๋ฒ์ ํจ์์ ์ธ์์ ๋ฐํ๊ฐ์๋ ์ ์ฉ ๊ฐ๋ฅํฉ๋๋ค:
// ๋ค์๊ณผ ๋์ผํฉ๋๋ค: // fn add_42_millions<T: Into<i32>>(x: T) -> i32 { fn add_42_millions(x: impl Into<i32>) -> i32 { x.into() + 42_000_000 } fn pair_of(x: u32) -> impl std::fmt::Debug { (x + 1, x - 1) } fn main() { let many = add_42_millions(42_i8); println!("{many}"); let many_more = add_42_millions(10_000_000); println!("{many_more}"); let debuggable = pair_of(27); println!("๋๋ฒ๊ทธ ๊ฐ๋ฅ: {debuggable:?}"); }
impl Trait
allows you to work with types which you cannot name. The meaning of impl Trait
is a bit different in the different positions.
-
ํจ์ ์ธ์์ ํ์ ์ผ๋ก ์ฌ์ฉ๋์์ ๊ฒฝ์ฐ์๋
impl Trait
๋ ํธ๋ ์ ๊ฒฝ๊ณ๊ฐ ์๋ ์ต๋ช ์ ์ ๋ค๋ฆญ ํ์ ์ ์๋ฏธํฉ๋๋ค. -
๋ฆฌํด ํ์ ์ผ๋ก ์ฌ์ฉ๋์์ ๊ฒฝ์ฐ์๋, ๊ทธ ํธ๋ ์์ ๊ตฌํํ๋ ๊ตฌ์ฒด์ ์ธ ํ์ ์ธ๋ฐ, ํ์ ์ด๋ฆ์ ํ๋ก๊ทธ๋๋จธ๊ฐ ์ง์ง ์์๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์ด๋ ๊ทธ ๊ตฌ์ฒด์ ์ธ ํ์ ์ด๋ฆ์ API๋ก ๊ณต๊ฐํ๊ณ ์ถ์ง ์์ ๊ฒฝ์ฐ์ ์ ์ฉํฉ๋๋ค.
ํจ์๊ฐ ๋ฆฌํด๋๋ ๊ณณ์์์ ํ์ ์ถ๋ก ์ ์ด๋ ต์ต๋๋ค. ์ด๋ค ํจ์์ ๋ฆฌํด ํ์ ์ด
impl Foo
๋ก ์ ์ธ๋์ด ์์ ๊ฒฝ์ฐ, ๊ทธ ํจ์๊ฐ ์ค์ ๋ก ๋ฆฌํดํ๋ ํ์ ์ ์์ค ์ฝ๋ ์ ์ด๋์๋ ๋ํ๋ ์์ง ์์ต๋๋ค.collect<B<() -> B
์ ๊ฐ์ด ์ ๋๋ฆญ ํ์ ์ ๋ฆฌํดํ๋ ํจ์๋B
๋ฅผ ๋ง์กฑํ๋ ์ด๋ค ํ์ ๋ ๋ฆฌํดํ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ, ํธ์ถํ๋ ์ธก์์๋let x: Vec<_> = foo.collect()
๋ ํฐ๋ณดํผ์ ๋ฌธ๋ฒ์ ์จ์foo.collect::<Vec<_>>()
์ ๊ฐ์ด ๋ฆฌํด ํ์ ์ ๋ช ์์ ์ผ๋ก ์จ ์ฃผ์ด์ผ ํ ์๋ ์์ต๋๋ค.
debuggable
ํ์
์ ๋ฌด์์ธ๊ฐ์? let debuggable: () = ..
์ ์๋ํ์ฌ ์ค๋ฅ ๋ฉ์์ง๊ฐ ์ด๋ป๊ฒ ํ์๋๋์ง ํ์ธํฉ๋๋ค.
Exercise: Generic min
In this short exercise, you will implement a generic min
function that determines the minimum of two values, using the Ord
trait.
use std::cmp::Ordering; // TODO: `main`์ ์ฌ์ฉ๋๋ `min` ํจ์๋ฅผ ๊ตฌํํฉ๋๋ค. fn main() { assert_eq!(min(0, 10), 0); assert_eq!(min(500, 123), 123); assert_eq!(min('a', 'z'), 'a'); assert_eq!(min('7', '1'), '1'); assert_eq!(min("hello", "goodbye"), "goodbye"); assert_eq!(min("bat", "armadillo"), "armadillo"); }
ํด๋ต
use std::cmp::Ordering; fn min<T: Ord>(l: T, r: T) -> T { match l.cmp(&r) { Ordering::Less | Ordering::Equal => l, Ordering::Greater => r, } } fn main() { assert_eq!(min(0, 10), 0); assert_eq!(min(500, 123), 123); assert_eq!(min('a', 'z'), 'a'); assert_eq!(min('7', '1'), '1'); assert_eq!(min("hello", "goodbye"), "goodbye"); assert_eq!(min("bat", "armadillo"), "armadillo"); }
Welcome Back
Including 10 minute breaks, this session should take about 3 hours and 10 minutes. It contains:
Segment | Duration |
---|---|
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ | 1 hour and 20 minutes |
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ | 1 hour and 40 minutes |
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ
This segment should take about 1 hour and 20 minutes. It contains:
Slide | Duration |
---|---|
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ | 3 minutes |
๋ฌธ์ํ์ฃผ์ ํ ์คํธ | 5 minutes |
Duration | 10 minutes |
Option, Result | 10 minutes |
String | 10 minutes |
Vec | 10 minutes |
HashMap | 10 minutes |
์ฐ์ต๋ฌธ์ : ์นด์ดํฐ | 20 minutes |
์ด ์น์ ์ ๊ฐ ์ฌ๋ผ์ด๋์์๋ ๋ฌธ์ ํ์ด์ง๋ฅผ ๊ฒํ ํ๊ณ ๋ณด๋ค ์ผ๋ฐ์ ์ธ ๋ฉ์๋๋ฅผ ์ค์ ์ ์ผ๋ก ์ดํด๋ด ๋๋ค.
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ
Rust comes with a standard library which helps establish a set of common types used by Rust libraries and programs. This way, two libraries can work together smoothly because they both use the same String
type.
In fact, Rust contains several layers of the Standard Library: core
, alloc
and std
.
core
includes the most basic types and functions that donโt depend onlibc
, allocator or even the presence of an operating system.alloc
์Vec
,Box
,Arc
์ ๊ฐ์ด ์ ์ญ ํ ํ ๋น์ด ํ์ํ ํ์ ์ ํฌํจํฉ๋๋ค.- ์๋ฒ ๋๋ ๋ฌ์คํธ ์์ฉํ๋ก๊ทธ๋จ์ ์ฃผ๋ก
core
๋ง ์ฌ์ฉํ๊ฑฐ๋ ๊ฐ๋alloc
์ ํจ๊ป ์ฌ์ฉํฉ๋๋ค.
๋ฌธ์ํ์ฃผ์ ํ ์คํธ
Rust comes with extensive documentation. For example:
- All of the details about loops.
u8
๊ณผ ๊ฐ์ ๊ธฐ๋ณธ ํ์- Standard library types like
Option
orBinaryHeap
.
์ฌ์ค ์์ฒด ์ฝ๋๋ฅผ ๋ฌธ์ํํ ์ ์์ต๋๋ค.
/// ์ฒซ ๋ฒ์งธ ์ธ์๋ฅผ ๋ ๋ฒ์งธ ์ธ์๋ก ๋๋ ์ ์๋์ง ํ์ธํฉ๋๋ค. /// /// ๋ ๋ฒ์งธ ์ธ์๊ฐ 0์ด๋ฉด ๊ฒฐ๊ณผ๋ false์ ๋๋ค. fn is_divisible_by(lhs: u32, rhs: u32) -> bool { if rhs == 0 { return false; } lhs % rhs == 0 }
์ฝํ
์ธ ๋ ๋งํฌ๋ค์ด์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค. ๊ฒ์๋ ๋ชจ๋ Rust ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํฌ๋ ์ดํธ๋ rustdoc ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ docs.rs
์ ์๋์ผ๋ก ๋ฌธ์ํ๋ฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก API์ ๋ชจ๋ ๊ณต๊ฐ ํญ๋ชฉ์ ์ด ํจํด์ ์ฌ์ฉํ์ฌ ๋ฌธ์ํ๋ฉ๋๋ค.
ํญ๋ชฉ ๋ด๋ถ(์: ๋ชจ๋ ๋ด๋ถ)์ ํญ๋ชฉ์ ๋ฌธ์ํํ๋ ค๋ฉด โ๋ด๋ถ ๋ฌธ์ ์ฃผ์โ์ด๋ผ๊ณ ํ๋ //!
ํน์ /*! .. */
๋ฅผ ์ฌ์ฉํ์ธ์.
//! ์ด ๋ชจ๋์๋ ์ ์์ ๋ถํ ๊ฐ๋ฅ์ฑ๊ณผ ๊ด๋ จ๋ ๊ธฐ๋ฅ์ด ํฌํจ๋์ด ์์ต๋๋ค.
- Show students the generated docs for the
rand
crate at https://docs.rs/rand.
Duration
Option<T>
์ ์ผ๋ถ ์ฌ์ฉ๋ฒ์ ์ด๋ฏธ ์ดํด๋ดค์ต๋๋ค. โTโ ํ์
์ ๊ฐ์ ์ ์ฅํ๊ฑฐ๋ ์๋ฌด๊ฒ๋ ์ ์ฅํ์ง ์์ต๋๋ค. ์๋ฅผ ๋ค์ด String::find
๋ Option<usize>
๋ฅผ ๋ฐํํฉ๋๋ค.
fn main() { let name = "Lรถwe ่่ Lรฉopard Gepardi"; let mut position: Option<usize> = name.find('รฉ'); println!("find์ ๋ฐํ๊ฐ {position:?}"); assert_eq!(position.unwrap(), 14); position = name.find('Z'); println!("find์ ๋ฐํ๊ฐ {position:?}"); assert_eq!(position.expect("๋ฌธ์๋ฅผ ์ฐพ์ ์ ์์"), 0); }
Option
is widely used, not just in the standard library.unwrap
์Option
์ ๊ฐ์ ๋ฐํํ๊ฑฐ๋ ํจ๋์ ๋ฐํํฉ๋๋ค.expect
๋ ๋น์ทํ์ง๋ง ์ค๋ฅ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค.- None ๋ฐ์ ์ ํจ๋ ์ํ๊ฐ ๋ ์ ์์ง๋ง โ์ค์โ๋ก None์ ์ฒดํฌํ๋ ๊ฒ์ ์์ ์๋ ์์ต๋๋ค.
- ๋ฌด์ธ๊ฐ๋ฅผ ํจ๊ป ํดํนํ ๋ ๋ชจ๋ ๊ณณ์์
unwrap
/expect
๋ฅผ ์คํํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด์ง๋ง ํ๋ก๋์ ์ฝ๋๋ ์ผ๋ฐ์ ์ผ๋ก ๋ ๋์ ๋ฐฉ์์ผ๋กNone
์ ์ฒ๋ฆฌํฉ๋๋ค.
- ํ์ ์ต์ ํ๋
Option<T>
๊ฐ ๋ฉ๋ชจ๋ฆฌ์์T
์ ํฌ๊ธฐ๊ฐ ๊ฐ์ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
Option, Result
Result
๋ Option
๊ณผ ์ ์ฌํ์ง๋ง ์์
์ ์ฑ๊ณต ๋๋ ์คํจ๋ฅผ ๋ํ๋ด๋ฉฐ, ๊ฐ๊ฐ ํ์
์ด ๋ค๋ฆ
๋๋ค. ์ด๋ ํํ์ ์ฐ์ต์์ ์ ์๋ Res
์ ์ ์ฌํ์ง๋ง ์ ๋ค๋ฆญ: Result<T, E>
์
๋๋ค. ์ฌ๊ธฐ์ T
๋ Ok
๋ณํ์ ์ฌ์ฉ๋๊ณ E
๋ Err
๋ณํ์ ํ์๋ฉ๋๋ค.
use std::fs::File; use std::io::Read; fn main() { let file: Result<File, std::io::Error> = File::open("diary.txt"); match file { Ok(mut file) => { let mut contents = String::new(); if let Ok(bytes) = file.read_to_string(&mut contents) { println!("๋ค์ด์ด๋ฆฌ: {contents}({bytes}๋ฐ์ดํธ)"); } else { println!("ํ์ผ ์ฝํ ์ธ ๋ฅผ ์ฝ์ ์ ์์ต๋๋ค."); } } Err(err) => { println!("๋ค์ด์ด๋ฆฌ๋ฅผ ์ด ์ ์์ต๋๋ค. {err}"); } } }
Option
์ ๋ง์ฐฌ๊ฐ์ง๋ก, ์ฑ๊ณตํ ๊ฒฝ์ฐ์ ๊ฐ์Result
๋ด๋ถ์ ์์ต๋๋ค. ๊ทธ๋์, ๊ฐ๋ฐ์๋ ๋ช ์์ ์ผ๋ก ์ด๋ฅผ ์ถ์ถํ์ฌ์ผ ํฉ๋๋ค. ์ด๋ ๊ฒ ํจ์ผ๋ก์จ ๊ฐ์ ์ฝ๊ธฐ ์ ์ ์ค๋ฅ ๋ฐ์ ์ฌ๋ถ๋ฅผ ๋ฐ๋์ ์ฒดํฌํ๋๋ก ์ ๋ํ๊ณ ์์ต๋๋ค. ๋ง์ผ ์ค๋ฅ๊ฐ ์ ๋ ๋ฐ์ํ์ง ์๋ ๊ฒฝ์ฐ๋ผ๋ฉดunwrap()
์ด๋expect()
๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ์ด๋ ๊ฐ๋ฐ์์ ์๋(์ญ์ฃผ: ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์)์ ๋ช ์์ ์ผ๋ก ๋ํ๋ด๋ ๋ฐฉ๋ฒ์ด๊ธฐ๋ ํฉ๋๋ค.Result
documentation is a recommended read. Not during the course, but it is worth mentioning. It contains a lot of convenience methods and functions that help functional-style programming.Result
๋ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ์ํ ํ์ค ํ์ ์ ๋๋ค. 3์ผ์ฐจ ๊ณผ์ ์์ ์ดํด๋ด ๋๋ค.
String
String
์ ํ์ ํ ๋น๋๊ณ ๊ฐ๋ณ ๊ธธ์ด์ ํ์ค UTF-8 ๋ฌธ์์ด ๋ฒํผ์
๋๋ค:
fn main() { let mut s1 = String::new(); s1.push_str("์๋ ํ์ธ์"); println!("s1: len = {}, ์ฉ๋ = {}", s1.len(), s1.capacity()); let mut s2 = String::with_capacity(s1.len() + 1); s2.push_str(&s1); s2.push('!'); println!("s2: len = {}, ์ฉ๋ = {}", s2.len(), s2.capacity()); let s3 = String::from("๐จ๐ญ"); println!("s3: len = {}, ๋ฌธ์ ์ = {}", s3.len(), s3.chars().count()); }
String
์ Deref<Target = str>
์ ๊ตฌํํฉ๋๋ค. ์ด๋ , String
๊ฐ์ ๋ํด์๋ str
์ ๋ชจ๋ ๋ฉ์๋๋ฅผ ํธ์ถ ํ ์ ์๋ค๋ ์๋ฏธ ์
๋๋ค.
String::new
๋ ์๋ก์ด ๋น ๋ฌธ์์ด์ ๋ฐํํฉ๋๋ค.String::with_capacity
๋ ์๋ก ๋ง๋ค ๋ฌธ์์ด ๋ฒํผ์ ๋ฃ์ ๋ฐ์ดํฐ ํฌ๊ธฐ๋ฅผ ์๊ณ ์๋ ๊ฒฝ์ฐ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.String::len
์String
์ ๋ฐ์ดํธ ํฌ๊ธฐ๋ฅผ ๋ฐํํฉ๋๋ค. (์ค์ ๋ฌธ์ ๊ฐ์์๋ ๋ค๋ฅผ ์ ์์ต๋๋ค.)String::chars
๋ ์ค์ ๋ฌธ์(character)๋ค์ ๋ํ ์ดํฐ๋ ์ดํฐ๋ฅผ ๋ฐํํฉ๋๋ค.char
๋ก ํํ๋๋ ๋ฌธ์๋ ์ฐ๋ฆฌ๊ฐ ์ค์ ๋ก ์ธ์ํ๊ณ ์ฌ์ฉํ๋ ๋ฌธ์์๋ ๋ค๋ฅผ ์ ์์ต๋๋ค. ์์ ๊ฒฐํฉ์ผ๋ก ๋ฌธ์๋ฅผ ํํํ๋ ๊ฒฝ์ฐ๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด์ ๋ํด์๋ Grapheme Cluster๋ฅผ ์ฐธ๊ณ ํ์ธ์.- ์ฌ๋๋ค์ด ๋ฌธ์์ด์ด๋ผ๊ณ ๋งํ ๋์๋
&str
์ด๊ฑฐ๋String
์ผ ์ ์์ต๋๋ค. - ์ด๋ค ํ์
์ด
Deref<Target = T>
๋ฅผ ๊ตฌํํ๊ณ ์์ผ๋ฉด, ์ปดํ์ผ๋ฌ๋ ์ฌ๋ฌ๋ถ์ดT
์ ๋ฉ์๋๋ค์ ํธ์ถํ ์ ์๊ฒ ๋์์ค๋๋ค.Deref
ํธ๋ ์์ ๊ดํด์ ์์ง ๋ค๋ฃจ์ง ์์์ผ๋ฏ๋ก ์ด ์์ ์์๋ ์ด๊ฒ์ผ๋ก ๋ฌธ์์ ์ฌ์ด๋๋ฐ ๊ตฌ์กฐ๊ฐ ๋๋ถ๋ถ ์ค๋ช ๋ฉ๋๋ค.String
์Deref<Target = str>
์ ๊ตฌํํ๊ณ ์๊ธฐ ๋๋ฌธ์String
์ ๋ํด์๋str
๋ฉ์๋๋ค์ ํธ์ถํ ์ ์์ต๋๋ค.- Write and compare
let s3 = s1.deref();
andlet s3 = &*s1;
.
String
์ ๋ฐ์ดํธ ๋ฒกํฐ์ ๋ํผ๋ก ๊ตฌํ๋์ด ์์ต๋๋ค. ๋ฒกํฐ๊ฐ ์ง์ํ๋ ์ฌ๋ฌ๊ฐ์ง ์ฐ์ฐ๋ค์String
๋ ์ง์ํฉ๋๋ค. ๋ค๋งString
์ ๋ช๊ฐ์ง ๋ณด์ฅ ๋ด์ฉ์ด ๋ ์์ต๋๋ค.String
์ ์ธ๋ฑ์ค๋ก ์ ๊ทผํ๋ ๋ฐฉ๋ฒ๋ค์ ๋น๊ตํด๋ณด์ธ์:s3.chars().nth(i).unwrap()
๋ฅผ ์ด์ฉํ์ฌ ํ ๋ฌธ์๋ฅผ ์ ํํ๋ ๊ฒฝ์ฐ,i
๊ฐ์ด ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๋, ๋ฒ์ด๋์ง ์์ ๋ ๋์์ ์ค๋ช ํ์ธ์.s3[0..4]
๋ฅผ ์ด์ฉํด์ ๋ฌธ์์ด์ ์ผ๋ถ๋ฅผ ์ ํํ๋๋ฐ, ์ฌ๋ผ์ด์ค๊ฐ ์ ๋์ฝ๋ ๋ฌธ์์ด ๊ฒฝ๊ณ์ ๋ฑ ๋ง์ง ์์ ๊ฒฝ์ฐ ์ด๋ป๊ฒ ๋๋์ง ์ค๋ช ํ์ธ์.
- Many types can be converted to a string with the
to_string
method. This trait is automatically implemented for all types that implementDisplay
, so anything that can be formatted can also be converted to a string.
Vec
Vec
๋ ํ์ ํ ๋น๋ ํ์ค ๊ฐ๋ณ ํฌ๊ธฐ ๋ฒํผ์
๋๋ค:
fn main() { let mut v1 = Vec::new(); v1.push(42); println!("v1: len = {}, ์ฉ๋ = {}", v1.len(), v1.capacity()); let mut v2 = Vec::with_capacity(v1.len() + 1); v2.extend(v1.iter()); v2.push(9999); println!("v2: len = {}, ์ฉ๋ = {}", v2.len(), v2.capacity()); // ์์๊ฐ ์๋ ๋ฒกํฐ๋ฅผ ์ด๊ธฐํํ๋ ํ์ค ๋งคํฌ๋ก์ ๋๋ค. let mut v3 = vec![0, 0, 1, 2, 3, 4]; // ์ง์ ์์๋ง ์ ์งํฉ๋๋ค. v3.retain(|x| x % 2 == 0); println!("{v3:?}"); // ์ฐ์ ์ค๋ณต ์ญ์ v3.dedup(); println!("{v3:?}"); }
Vec
์ Deref<Target = [T]>
๋ฅผ ๊ตฌํํฉ๋๋ค. ์ด๋ Vec
์์ ์ฌ๋ผ์ด์ค ๋ฉ์๋๋ฅผ ํธ์ถ ํ ์ ์๋ค๋ ์๋ฏธ์
๋๋ค.
Vec
is a type of collection, along withString
andHashMap
. The data it contains is stored on the heap. This means the amount of data doesnโt need to be known at compile time. It can grow or shrink at runtime.Vec<T>
๋ ์ ๋ค๋ฆญ ํ์ ์ด๊ธฐ๋ ํฉ๋๋ค. ํ์ง๋งT
๋ฅผ ๊ผญ ์ง์ ํด์ค ํ์๋ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ, ๋ฌ์คํธ ํ์ ์ถ๋ก ์ด ๋ฒกํฐ์ ์ฒ์push
ํ๋ ๋ฐ์ดํฐ๋กT
๋ฅผ ์ ์ ์์์ต๋๋ค.vec![...]
๋Vec::new()
๋์ ์ธ ์ ์๋ ํ์ค ๋งคํฌ๋ก๋ก์, ์ด๊ธฐ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ๋ฒกํฐ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.- ๋ฒกํฐ๋
[
]
๋ฅผ ์ฌ์ฉํ์ฌ ์ธ๋ฑ์ค๋ก ์ ๊ทผํ ์ ์์ต๋๋ค. ํ์ง๋ง ๋ฒ์๋ฅผ ๋ฒ์ด๋๋ฉด ํจ๋์ด ๋ฐ์ํฉ๋๋ค. ๋์get
์ ์ฌ์ฉํ๋ฉดOption
์ ๋ฐํํฉ๋๋ค.pop
ํจ์๋ ๋ง์ง๋ง ์์๋ฅผ ์ ๊ฑฐํฉ๋๋ค. - Slices are covered on day 3. For now, students only need to know that a value of type
Vec
gives access to all of the documented slice methods, too.
HashMap
HashDoS ๊ณต๊ฒฉ์ผ๋ก๋ถํฐ ๋ณดํธ๋๋ ํ์ค ํด์ ๋งต์ ๋๋ค:
use std::collections::HashMap; fn main() { let mut page_counts = HashMap::new(); page_counts.insert("ํํด๋ฒ ๋ฆฌ ํ์ ๋ชจํ".to_string(), 207); page_counts.insert("๊ทธ๋ฆผ ๋ํ".to_string(), 751); page_counts.insert("์ค๋ง๊ณผ ํธ๊ฒฌ".to_string(), 303); if !page_counts.contains_key("๋ ๋ฏธ์ ๋ผ๋ธ") { println!( "{}์ ์ฑ ์ ์๊ณ ์์ง๋ง ๋ ๋ฏธ์ ๋ผ๋ธ์ ์์ง ๋ชปํฉ๋๋ค.", page_counts.len() ); } for book in ["์ค๋ง๊ณผ ํธ๊ฒฌ", "์ด์ํ ๋๋ผ์ ์จ๋ฆฌ์ค"] { match page_counts.get(book) { Some(count) => println!("{book}: {count}ํ์ด์ง"), None => println!("{book}์(๋ฅผ) ์ ์ ์์ต๋๋ค."), } } // ๊ฐ์ ์ฐพ์ ์ ์๋ ๊ฒฝ์ฐ .entry() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ์ฝ์ ํฉ๋๋ค. for book in ["์ค๋ง๊ณผ ํธ๊ฒฌ", "์ด์ํ ๋๋ผ์ ์จ๋ฆฌ์ค"] { let page_count: &mut i32 = page_counts.entry(book.to_string()).or_insert(0); *page_count += 1; } println!("{page_counts:#?}"); }
-
HashMap
์ prelude์ ์ ์๋์ด ์์ง ์๊ธฐ ๋๋ฌธ์ ๋ช ์์ ์ผ๋ก ์ถ๊ฐํด์ค์ผ ํฉ๋๋ค. -
์๋ ์ฝ๋๋ฅผ ํ ์คํธํด๋ณด์ธ์. ์ฒซ ๋ฌธ์ฅ์์๋ ํด์๋งต์ ์ฑ ์ด ์๋์ง ๊ฒ์ฌํ์ฌ, ์์ผ๋ฉด ๋ํดํธ ๊ฐ์ ๋ฐํํฉ๋๋ค. ๋๋ฒ ์งธ ๋ฌธ์ฅ์์๋ ํด์๋งต์ ํด๋น ์ฑ ์ด ์๋ ๊ฒฝ์ฐ, ์ง์ ํ ๊ฐ์ ํด์๋งต์ ์ถ๊ฐํ ๋ค ๊ทธ ๊ฐ์ ๋ฐํํฉ๋๋ค.
let pc1 = page_counts .get("ํด๋ฆฌ ํฌํฐ์ ๋ง๋ฒ์ฌ์ ๋") .unwrap_or(&336); let pc2 = page_counts .entry("ํ๊ฑฐ๊ฒ์".to_string()) .or_insert(374);
-
์ํ๊น์ง๋ง
hashmap!
๊ฐ์ ๋งคํฌ๋ก๊ฐ ์์ต๋๋ค.-
๋ฌ์คํธ 1.56๋ถํฐ๋
HashMap
์ดFrom<[(K, V); N]>
์ ๊ตฌํํ๊ธฐ ๋๋ฌธ์ ๋ฐฐ์ด ๋ฆฌํฐ๋ด์ ์ด์ฉํ์ฌ ์ฝ๊ฒ ํด์๋งต์ ์ด๊ธฐํํ ์ ์์ต๋๋ค:let page_counts = HashMap::from([ ("ํด๋ฆฌ ํฌํฐ์ ๋ง๋ฒ์ฌ์ ๋".to_string(), 336), ("ํ๊ฑฐ๊ฒ์".to_string(), 374), ]);
-
-
ํค-๊ฐ ์์ ๋ํ
Iterator
๋ก ํด์๋งต์ ๋ง๋ค ์๋ ์์ต๋๋ค. -
์์ ์ฝ๋์์๋ ํธ์์ ํด์๋งต์ ํค๋ก
&str
๋ฅผ ์ฌ์ฉํ์ง ์์์ต๋๋ค. ๋ฌผ๋ก ์ปฌ๋ ์ ์ ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํ ์๋ ์์ต๋๋ค. ๋ค๋ง ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด ๋น๋ฆผ ๊ฒ์ฌ๊ธฐ ๋๋ฌธ์ ๋ณต์กํด ์ง ์ ์์ต๋๋ค.- ์์ ์ฝ๋์์
to_string()
์ ์์ ๋ ์ปดํ์ผ์ ๋ฌธ์ ๊ฐ ์๋์ง ํ์ธํด๋ณด์ธ์. ์ด๋ค ๋ฌธ์ ์ ๋ถ๋ชํ๊น์?
- ์์ ์ฝ๋์์
-
ํด์๋งต์ ๋ช ๋ช ๋ฉ์๋๋ ํด์๋งต ๋ด๋ถ์ ํน๋ณํ ํ์ (์๋ฅผ ๋ค์ด
std::collections::hash_map::Keys
)๋ค์ ๋ฆฌํดํฉ๋๋ค. ์ด๋ฌํ ํ์ ๋ค์ Rust ๋ฌธ์์์๋ ๊ฒ์ํ ์ ์์ต๋๋ค. ์๊ฐ์๋ค์๊ฒ ์ด ํ์ ๋ค์ ๋ํ ๋ฌธ์๋ฅผ ๋ณด์ฌ์ฃผ๊ณ , ์ด ๋ฌธ์์keys
๋ฉ์๋๋ก์ ์ญ ๋งํฌ๊ฐ ์์์ ์๋ ค์ฃผ์ธ์.
์ฐ์ต๋ฌธ์ : ์นด์ดํฐ
์ด ์ฐ์ต์์๋ ๋งค์ฐ ๊ฐ๋จํ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ค๋ฆญ์ผ๋ก ๋ง๋ญ๋๋ค. std::collections::HashMap
์ ์ฌ์ฉํ์ฌ ์ด๋ค ๊ฐ์ด ํ์๋์๋์ง, ๊ฐ๊ฐ ์ผ๋ง๋ ํ์๋์๋์ง ์ถ์ ํฉ๋๋ค.
Counter
์ ์ด๊ธฐ ๋ฒ์ ์ u32
๊ฐ์๋ง ์๋ํ๋๋ก ํ๋ ์ฝ๋ฉ๋์ด ์์ต๋๋ค. ์ถ์ ์ค์ธ ๊ฐ ํ์
์ ๋ํด ๊ตฌ์กฐ์ฒด ๋ฐ ๋ฉ์๋๋ฅผ ์ ๋ค๋ฆญ์ผ๋ก ๋ง๋ญ๋๋ค. ๊ทธ๋ฌ๋ฉด Counter
๊ฐ ๋ชจ๋ ํ์
์ ๊ฐ์ ์ถ์ ํ ์ ์์ต๋๋ค.
์ผ์ฐ ์๋ฃํ ๊ฒฝ์ฐ entry
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ, count
๋ฉ์๋๋ฅผ ๊ตฌํํ๋ ๋ฐ ํ์ํ ํด์ ์กฐํ ํ์๋ฅผ ์ ๋ฐ์ผ๋ก ์ค์ฌ๋ณด์ธ์.
use std::collections::HashMap; /// Counter๋ ๊ฐ T ํ์ ๊ฐ์ด ํ์๋ ํ์๋ฅผ ๊ณ์ฐํฉ๋๋ค. struct Counter { values: HashMap<u32, u64>, } impl Counter { /// ์ Counter๋ฅผ ๋ง๋ญ๋๋ค. fn new() -> Self { Counter { values: HashMap::new(), } } /// ์ง์ ๋ ๊ฐ์ ๋ฐ์ ํ์๋ฅผ ์ ๋๋ค. fn count(&mut self, value: u32) { if self.values.contains_key(&value) { *self.values.get_mut(&value).unwrap() += 1; } else { self.values.insert(value, 1); } } /// ์ง์ ๋ ๊ฐ์ด ํ์๋ ํ์๋ฅผ ๋ฐํํฉ๋๋ค. fn times_seen(&self, value: u32) -> u64 { self.values.get(&value).copied().unwrap_or_default() } } fn main() { let mut ctr = Counter::new(); ctr.count(13); ctr.count(14); ctr.count(16); ctr.count(14); ctr.count(14); ctr.count(11); for i in 10..20 { println!("{} ๊ฐ์ {} ๊ฐ์ ๋ฐ๊ฒฌํ์ต๋๋ค.", ctr.times_seen(i), i); } let mut strctr = Counter::new(); strctr.count("์ฌ๊ณผ"); strctr.count("์ค๋ ์ง"); strctr.count("์ฌ๊ณผ"); println!("์ฌ๊ณผ {}๊ฐ ๋ฐ์", strctr.times_seen("์ฌ๊ณผ")); }
ํด๋ต
use std::collections::HashMap; use std::hash::Hash; /// Counter๋ ๊ฐ T ํ์ ๊ฐ์ด ํ์๋ ํ์๋ฅผ ๊ณ์ฐํฉ๋๋ค. struct Counter<T: Eq + Hash> { values: HashMap<T, u64>, } impl<T: Eq + Hash> Counter<T> { /// ์ Counter๋ฅผ ๋ง๋ญ๋๋ค. fn new() -> Self { Counter { values: HashMap::new() } } /// ์ง์ ๋ ๊ฐ์ ๋ฐ์ ํ์๋ฅผ ์ ๋๋ค. fn count(&mut self, value: T) { *self.values.entry(value).or_default() += 1; } /// ์ง์ ๋ ๊ฐ์ด ํ์๋ ํ์๋ฅผ ๋ฐํํฉ๋๋ค. fn times_seen(&self, value: T) -> u64 { self.values.get(&value).copied().unwrap_or_default() } } fn main() { let mut ctr = Counter::new(); ctr.count(13); ctr.count(14); ctr.count(16); ctr.count(14); ctr.count(14); ctr.count(11); for i in 10..20 { println!("{} ๊ฐ์ {} ๊ฐ์ ๋ฐ๊ฒฌํ์ต๋๋ค.", ctr.times_seen(i), i); } let mut strctr = Counter::new(); strctr.count("์ฌ๊ณผ"); strctr.count("์ค๋ ์ง"); strctr.count("์ฌ๊ณผ"); println!("์ฌ๊ณผ {}๊ฐ ๋ฐ์", strctr.times_seen("์ฌ๊ณผ")); }
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ
This segment should take about 1 hour and 40 minutes. It contains:
Slide | Duration |
---|---|
๋น๊ต | 10 minutes |
Iterators | 10 minutes |
From๊ณผ Into | 10 minutes |
ํ ์คํธ | 5 minutes |
Read์ Write | 10 minutes |
Default, ๊ตฌ์กฐ์ฒด ์ ๋ฐ์ดํธ ๋ฌธ๋ฒ | 5 minutes |
ํด๋ก์ (Closure) | 20 minutes |
์ฐ์ต๋ฌธ์ : ๋ฐ์ด๋๋ฆฌ ํธ๋ฆฌ | 30 minutes |
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ๊ฐ ํธ๋ ์์ ๊ดํ ๋ฌธ์๋ฅผ ๊ฒํ ํ๋ ๋ฐ ์๊ฐ์ ํ ์ ํ์ธ์.
์ด ์น์ ์ ๊น๋๋ค. ์ค๊ฐ์ ํด์์ ์ทจํ์ธ์.
๋น๊ต
์ด๋ฌํ ํธ๋ ์์ ๊ฐ ๊ฐ์ ๋น๊ต๋ฅผ ์ง์ํฉ๋๋ค. ๋ชจ๋ ํธ๋ ์์ ์ด๋ฌํ ํธ๋ ์์ ๊ตฌํํ๋ ํ๋๋ฅผ ํฌํจํ๋ ํ์ ์ ๋ํด ์์๋ ์ ์์ต๋๋ค.
PartialEq
and Eq
PartialEq
๋ ํ์ ๋ฉ์๋์ธ eq
๋ฐ ์ ๊ณต๋ ne
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ๋ถ๋ถ ๋ฑ๊ฐ ๊ด๊ณ์
๋๋ค. ==
๋ฐ !=
์ฐ์ฐ์๋ ์ด๋ฌํ ๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค.
struct Key { id: u32, metadata: Option<String>, } impl PartialEq for Key { fn eq(&self, other: &Self) -> bool { self.id == other.id } }
Eq
is a full equivalence relation (reflexive, symmetric, and transitive) and implies PartialEq
. Functions that require full equivalence will use Eq
as a trait bound.
PartialOrd
๋ฐ Ord
PartialOrd
๋ partial_cmp
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๋ถ๋ถ ์์๋ฅผ ์ ์ํฉ๋๋ค. <
, <=
, >=
, >
์ฐ์ฐ์๋ฅผ ๊ตฌํํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
use std::cmp::Ordering; #[derive(Eq, PartialEq)] struct Citation { author: String, year: u32, } impl PartialOrd for Citation { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { match self.author.partial_cmp(&other.author) { Some(Ordering::Equal) => self.year.partial_cmp(&other.year), author_ord => author_ord, } } }
Ord
๋ ์ ์ฒด ์์ ์ง์ ์ด๋ฉฐ cmp
๋ Ordering
์ ๋ฐํํฉ๋๋ค.
PartialEq
๋ ์๋ก ๋ค๋ฅธ ํ์
๊ฐ์ ๊ตฌํ๋ ์ ์์ง๋ง Eq
๋ ๊ตฌํ๋ ์ ์์ต๋๋ค. ๋ฐ์ฌ์ ์ด๊ธฐ ๋๋ฌธ์
๋๋ค.
struct Key { id: u32, metadata: Option<String>, } impl PartialEq<u32> for Key { fn eq(&self, other: &u32) -> bool { self.id == *other } }
์ค์ ๋ก ์ด๋ฌํ ํธ๋ ์์ ์์ํ๋ ๊ฒ์ ์ผ๋ฐ์ ์ด์ง๋ง ๊ตฌํํ๋ ๊ฒ์ ๋๋ฌธ ์ผ์ ๋๋ค.
Iterators
์ฐ์ฐ์ ์ค๋ฒ๋ก๋๋ std::ops
์ ์๋ ๋ค์ํ ํธ๋ ์๋ค์ ํตํด ๊ตฌํ๋ฉ๋๋ค:
#[derive(Debug, Copy, Clone)] struct Point { x: i32, y: i32, } impl std::ops::Add for Point { type Output = Self; fn add(self, other: Self) -> Self { Self { x: self.x + other.x, y: self.y + other.y } } } fn main() { let p1 = Point { x: 10, y: 20 }; let p2 = Point { x: 100, y: 200 }; println!("{:?} + {:?} = {:?}", p1, p2, p1 + p2); }
๋ ผ์์ :
- You could implement
Add
for&Point
. In which situations is that useful?- ๋ต:
Add:add
๋self
๋ฅผ ์๋ชจํฉ๋๋ค. ๋ง์ฝ ํ์T
๊ฐCopy
ํธ๋ ์์ ๊ตฌํํ๊ณ ์์ง ์๋ค๋ฉด&T
์ ๋ํด์๋ ์ฐ์ฐ์ ์ค๋ฒ๋ก๋ฉ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ํธ์ถ๋ถ์์ ๋ถํ์ํ ๋ณต์ฌ๋ฅผ ํผํ ์ ์์ต๋๋ค.
- ๋ต:
- ์
Output
์ด ์ฐ๊ด๋ ํ์ ์ธ๊ฐ์? ํ์ ํ๋ผ๋ฉํฐ๋ก ๋ง๋ค ์ ์์๊น์?- Short answer: Function type parameters are controlled by the caller, but associated types (like
Output
) are controlled by the implementer of a trait.
- Short answer: Function type parameters are controlled by the caller, but associated types (like
Add
๋ฅผ ์ด์ฉํด์ ์๋ก ๋ค๋ฅธ ๋ ๊ฐ์ ํ์ ์ ๋ํ ์๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ดimpl Add<(i32, i32)> for Point
๋ ํํ์Point
์ ๋ํ ์ ์๊ฒ ํด ์ค๋๋ค.
From
๊ณผ Into
ํ์
์ ์ฉ์ดํ ํ๋ณํ์ ์ํด From
๊ณผ Into
๋ฅผ ๊ตฌํํฉ๋๋ค:
fn main() { let s = String::from("hello"); let addr = std::net::Ipv4Addr::from([127, 0, 0, 1]); let one = i16::from(true); let bigger = i32::from(123_i16); println!("{s}, {addr}, {one}, {bigger}"); }
From
์ด ๊ตฌํ๋๋ฉด Into
์ญ์ ์๋์ผ๋ก ๊ตฌํ๋ฉ๋๋ค:
fn main() { let s: String = "hello".into(); let addr: std::net::Ipv4Addr = [127, 0, 0, 1].into(); let one: i16 = true.into(); let bigger: i32 = 123_i16.into(); println!("{s}, {addr}, {one}, {bigger}"); }
- ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ์ ์ ์ ํ์
์ ๊ฒฝ์ฐ์๋
From
๋ง ๊ตฌํํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ ๋๋ค. - โ
String
์ผ๋ก ๋ณํํ ์ ์๋ ๋ชจ๋ ๊ฒโ๊ณผ ๊ฐ์ ํจ์์ ์ธ์ ํ์ ์ ์ ์ธํ ๋์๋Into
๋ฅผ ์ฌ์ฉํด์ผ ํจ์ ์กฐ์ฌํ์ธ์. ๊ทธ๋์ผ๋ง, ํจ์๋From
์ ๊ตฌํํ ํ์ ๊ณผInto
๋ง ๊ตฌํํ ํ์ ๋ชจ๋๋ฅผ ์ธ์๋ก ๋ฐ์ ์ ์์ต๋๋ค.
ํ ์คํธ
Rust์๋ ์์์ ํ์
๋ณํ์ด ์์ง๋ง as
๋ฅผ ์ฌ์ฉํ ๋ช
์์ ๋ณํ์ ์ง์๋ฉ๋๋ค. ์ด๋ ์ผ๋ฐ์ ์ผ๋ก C ์๋ฏธ๋ก ์ ๋ฐ๋ผ ์ ์๋ฉ๋๋ค.
fn main() { let value: i64 = 1000; println!("as u16: {}", value as u16); println!("as i16: {}", value as i16); println!("as u8: {}", value as u8); }
as
์ ๊ฒฐ๊ณผ๋ Rust์์ ํญ์ ์ ์๋๋ฉฐ ์ฌ๋ฌ ํ๋ซํผ์์ ์ผ๊ด๋ฉ๋๋ค. ์ด๋ ๊ธฐํธ๋ฅผ ๋ณ๊ฒฝํ๊ฑฐ๋ ๋ ์์ ํ์
์ผ๋ก ๋ณํํ ๋์ ์ง๊ด๊ณผ ์ผ์นํ์ง ์์ ์ ์์ต๋๋ค. ๋ฌธ์๋ฅผ ํ์ธํ๊ณ ๋ช
ํํ๊ฒ ์ค๋ช
ํด ์ฃผ์ธ์.
Casting with as
is a relatively sharp tool that is easy to use incorrectly, and can be a source of subtle bugs as future maintenance work changes the types that are used or the ranges of values in types. Casts are best used only when the intent is to indicate unconditional truncation (e.g. selecting the bottom 32 bits of a u64
with as u32
, regardless of what was in the high bits).
For infallible casts (e.g. u32
to u64
), prefer using From
or Into
over as
to confirm that the cast is in fact infallible. For fallible casts, TryFrom
and TryInto
are available when you want to handle casts that fit differently from those that donโt.
์ด ์ฌ๋ผ์ด๋๊ฐ ๋๋ ํ ์ ์ ์ฌ์ด๊ฐ๋ ๊ฒ์ด ์ข์ต๋๋ค.
as
is similar to a C++ static cast. Use of as
in cases where data might be lost is generally discouraged, or at least deserves an explanatory comment.
์ด๋ ์ ์๋ฅผ usize
๋ก ๋ณํํ์ฌ ์์ธ์ผ๋ก ์ฌ์ฉํ ๋ ์ผ๋ฐ์ ์
๋๋ค.
Read
์ Write
Read
์ BufRead
๋ฅผ ์ฌ์ฉํ๋ฉด u8
ํ์
์ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ์ฝ์ ์ ์์ต๋๋ค:
use std::io::{BufRead, BufReader, Read, Result}; fn count_lines<R: Read>(reader: R) -> usize { let buf_reader = BufReader::new(reader); buf_reader.lines().count() } fn main() -> Result<()> { let slice: &[u8] = b"foo\nbar\nbaz\n"; println!("์ฌ๋ผ์ด์ค ๋ด ์ค: {}", count_lines(slice)); let file = std::fs::File::open(std::env::current_exe()?)?; println!("ํ์ผ ๋ด ์ค: {}", count_lines(file)); Ok(()) }
์ด์ ๋น์ทํ๊ฒ, Write
๋ฅผ ์ฌ์นํ๋ฉด u8
ํ์
์ ๋ฐ์ดํฐ๋ฅผ ์ธ ์ ์์ต๋๋ค:
use std::io::{Result, Write}; fn log<W: Write>(writer: &mut W, msg: &str) -> Result<()> { writer.write_all(msg.as_bytes())?; writer.write_all("\n".as_bytes()) } fn main() -> Result<()> { let mut buffer = Vec::new(); log(&mut buffer, "์๋ ํ์ธ์")?; log(&mut buffer, "World")?; println!("๋ก๊ทธ ๋ด์ญ: {:?}", buffer); Ok(()) }
Default
ํธ๋ ์
Default
ํธ๋ ์์ ์ด๋ค ํ์
์ ๋ํ ๊ธฐ๋ณธ๊ฐ์ ์ ๊ณตํฉ๋๋ค.
#[derive(Debug, Default)] struct Derived { x: u32, y: String, z: Implemented, } #[derive(Debug)] struct Implemented(String); impl Default for Implemented { fn default() -> Self { Self("์กด ์ค๋ฏธ์ค".into()) } } fn main() { let default_struct = Derived::default(); println!("{default_struct:#?}"); let almost_default_struct = Derived { y: "Y ์ค์ ๋จ".into(), ..Derived::default() }; println!("{almost_default_struct:#?}"); let nothing: Option<Derived> = None; println!("{:#?}", nothing.unwrap_or_default()); }
- ํธ๋ ์์ ์ง์ ๊ตฌํํ๊ฑฐ๋
#[derive(Default)]
๋ฅผ ๋ถ์ฌ์ ์ปดํ์ผ๋ฌ์๊ฒ ๊ตฌํ์ ๋งก๊ธธ ์ ์์ต๋๋ค. - ์ปดํ์ผ๋ฌ๊ฐ ์ ๊ณตํ๋ ์๋ ๊ตฌํ์ ๊ฒฝ์ฐ ๋ชจ๋ ํ๋์ ๋ํด ๊ธฐ๋ณธ ๊ฐ์ ์ค์ ํ ์ ์ธ์คํด์ค๋ฅผ ๋ฐํํฉ๋๋ค.
- ์ด๋ ๊ตฌ์กฐ์ฒด์ ๊ฐ ํ๋ ํ์
๋ค์ด ๋ชจ๋
Default
ํธ๋ ์์ ๊ตฌํํด์ผ ํจ์ ์๋ฏธํฉ๋๋ค.
- ์ด๋ ๊ตฌ์กฐ์ฒด์ ๊ฐ ํ๋ ํ์
๋ค์ด ๋ชจ๋
- ๋ฌ์คํธ ํ์ค ํ์
๋ค์ ๋๋ถ๋ถ
Default
๋ฅผ ๊ตฌํํ๊ณ ์์ผ๋ฉฐ, ๊ธฐ๋ณธ ๊ฐ์0
์ด๋""
์ฒ๋ผ ์์ ๊ฐ๋ฅํ ๊ฐ๋ค์ ๋๋ค. - The partial struct initialization works nicely with default.
- The Rust standard library is aware that types can implement
Default
and provides convenience methods that use it. - The
..
syntax is called struct update syntax.
ํด๋ก์ (Closure)
ํด๋ก์ ํน์ ๋๋คํํ์์ ์ต๋ช
ํ์
์
๋๋ค. ์ด๋ค์ Fn
,FnMut
, FnOnce
๋ผ๋ ํน๋ณํ ํธ๋ ์์ ๊ตฌํํฉ๋๋ค:
fn apply_with_log(func: impl FnOnce(i32) -> i32, input: i32) -> i32 { println!("Calling function on {input}"); func(input) } fn main() { let add_3 = |x| x + 3; println!("add_3: {}", apply_with_log(add_3, 10)); println!("add_3: {}", apply_with_log(add_3, 20)); let mut v = Vec::new(); let mut accumulate = |x: i32| { v.push(x); v.iter().sum::<i32>() }; println!("accumulate: {}", apply_with_log(&mut accumulate, 4)); println!("accumulate: {}", apply_with_log(&mut accumulate, 5)); let multiply_sum = |x| x * v.into_iter().sum::<i32>(); println!("multiply_sum: {}", apply_with_log(multiply_sum, 3)); }
Fn
(์๋ฅผ ๋ค์ด add_3
)์ ์บก์ฒ๋ ๊ฐ์ ์๋ชจ๋ ๋ณ๊ฒฝ๋ ํ์ง ์๊ณ , ํน์ ์ด๋ค ๊ฒ๋ ์บก์ณํ์ง ์์์ ์๋ ์๊ธฐ ๋๋ฌธ์ ๋์์ ์ฌ๋ฌ๋ฒ ํธ์ถํ ์ ์์ต๋๋ค.
FnMut
(์๋ฅผ ๋ค์ด accumulate
)๋ ์บก์ฒ๋ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์์ผ๋ฏ๋ก ์ฌ๋ฌ ๋ฒ ํธ์ถ์ ๊ฐ๋ฅํ์ง๋ง ๋์์ ํธ์ถ ํ ์๋ ์์ต๋๋ค.
FnOnce
(์๋ฅผ ๋ค์ด multiply_sum
)๋ ํ๋ฒ๋ง ํธ์ถ๋๋ฉฐ ์บก์ฒ๋ ๊ฐ์ ์๋ชจํฉ๋๋ค.
FnMut
๋ FnOnce
์ ํ์ํ์
์
๋๋ค. Fn
์ FnMut
๊ณผ FnOnce
์ ํ์ ํ์
์
๋๋ค. ์ฆ, FnMut
๋ FnOnce
๊ฐ ํธ์ถ๋๋ ๊ณณ์ด๋ฉด ์ด๋์๋ ์ฌ์ฉ ํ ์ ์๊ณ Fn
์ FnMut
์ FnOnce
๊ฐ ํธ์ถ๋๋ ๊ณณ์ด๋ฉด ์ด๋๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
When you define a function that takes a closure, you should take FnOnce
if you can (i.e. you call it once), or FnMut
else, and last Fn
. This allows the most flexibility for the caller.
In contrast, when you have a closure, the most flexible you can have is Fn
(it can be passed everywhere), then FnMut
, and lastly FnOnce
.
์ปดํ์ผ๋ฌ๋ ํด๋ก์ ๊ฐ ๋ฌด์์ ์บก์ณํ๋์ง์ ๋ฐ๋ผ Copy
(์๋ฅผ ๋ค์ด add_3
)๊ณผ Clone
(์๋ฅผ ๋ค์ด multiply_sum
)์ ์์์ ์ถ๋ก ํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ํด๋ก์ ธ๋, ๊ฐ๋ฅํ๋ค๋ฉด, ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํ์ฌ ์บก์ณ๋ฅผ ํฉ๋๋ค. move
ํค์๋๋ฅผ ์ฐ๋ฉด ๊ฐ์ผ๋ก ์บก์ณ๊ฐ ๋ฉ๋๋ค.
fn make_greeter(prefix: String) -> impl Fn(&str) { return move |name| println!("{} {}", prefix, name); } fn main() { let hi = make_greeter("Hi".to_string()); hi("Greg"); }
์ฐ์ต๋ฌธ์ : ๋ฐ์ด๋๋ฆฌ ํธ๋ฆฌ
์ด ์์์๋ ๊ธฐ์กด์ โROT13โ ์ํธํ๋ฅผ ๊ตฌํํฉ๋๋ค. ์ด ์ฝ๋๋ฅผ ํ๋ ์ด๊ทธ๋ผ์ด๋์ ๋ณต์ฌํ๊ณ ๋๋ฝ๋ ๋นํธ๋ฅผ ๊ตฌํํฉ๋๋ค. ๊ฒฐ๊ณผ๊ฐ ์ฌ์ ํ ์ ํจํ UTF-8์ธ์ง ํ์ธํ๋ ค๋ฉด ASCII ์๋ฌธ์๋ง ํ์ ํ์ธ์.
use std::io::Read; struct RotDecoder<R: Read> { input: R, rot: u8, } // `RotDecoder`์ `Read` ํธ๋ ์์ ๊ตฌํํฉ๋๋ค. fn main() { let mut rot = RotDecoder { input: "Gb trg gb gur bgure fvqr!".as_bytes(), rot: 13 }; let mut result = String::new(); rot.read_to_string(&mut result).unwrap(); println!("{}", result); } #[cfg(test)] mod test { use super::*; #[test] fn joke() { let mut rot = RotDecoder { input: "Gb trg gb gur bgure fvqr!".as_bytes(), rot: 13 }; let mut result = String::new(); rot.read_to_string(&mut result).unwrap(); assert_eq!(&result, "To get to the other side!"); } #[test] fn binary() { let input: Vec<u8> = (0..=255u8).collect(); let mut rot = RotDecoder::<&[u8]> { input: input.as_ref(), rot: 13 }; let mut buf = [0u8; 256]; assert_eq!(rot.read(&mut buf).unwrap(), 256); for i in 0..=255 { if input[i] != buf[i] { assert!(input[i].is_ascii_alphabetic()); assert!(buf[i].is_ascii_alphabetic()); } } } }
๊ฐ๊ฐ 13์์ฉ ํ์ ํ๋ ๋ ๊ฐ์ RotDecoder
์ธ์คํด์ค๋ฅผ ํจ๊ป ์ฒด์ด๋ํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น์?
ํด๋ต
use std::io::Read; struct RotDecoder<R: Read> { input: R, rot: u8, } impl<R: Read> Read for RotDecoder<R> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { let size = self.input.read(buf)?; for b in &mut buf[..size] { if b.is_ascii_alphabetic() { let base = if b.is_ascii_uppercase() { 'A' } else { 'a' } as u8; *b = (*b - base + self.rot) % 26 + base; } } Ok(size) } } fn main() { let mut rot = RotDecoder { input: "Gb trg gb gur bgure fvqr!".as_bytes(), rot: 13 }; let mut result = String::new(); rot.read_to_string(&mut result).unwrap(); println!("{}", result); } #[cfg(test)] mod test { use super::*; #[test] fn joke() { let mut rot = RotDecoder { input: "Gb trg gb gur bgure fvqr!".as_bytes(), rot: 13 }; let mut result = String::new(); rot.read_to_string(&mut result).unwrap(); assert_eq!(&result, "To get to the other side!"); } #[test] fn binary() { let input: Vec<u8> = (0..=255u8).collect(); let mut rot = RotDecoder::<&[u8]> { input: input.as_ref(), rot: 13 }; let mut buf = [0u8; 256]; assert_eq!(rot.read(&mut buf).unwrap(), 256); for i in 0..=255 { if input[i] != buf[i] { assert!(input[i].is_ascii_alphabetic()); assert!(buf[i].is_ascii_alphabetic()); } } } }
3์ผ์ฐจ ๊ฐ์
์ค๋ ๋ค๋ฃฐ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ, ์๋ช , ๋น๋ฆผ ๊ฒ์ฌ๊ธฐ: Rust๊ฐ ๋ฉ๋ชจ๋ฆฌ ์์ ์ ๋ณด์ฅํ๋ ๋ฐฉ๋ฒ
- ์ค๋งํธ ํฌ์ธํฐ: ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํฌ์ธํฐ ํ์
์ผ์ ์์ฝ
Including 10 minute breaks, this session should take about 2 hours and 20 minutes. It contains:
Segment | Duration |
---|---|
๊ฐ์ | 3 minutes |
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ | 1 hour |
์ค๋งํธ ํฌ์ธํฐ | 55 minutes |
๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
This segment should take about 1 hour. It contains:
Slide | Duration |
---|---|
ํ๋ก๊ทธ๋จ ๋ฉ๋ชจ๋ฆฌ ๊ฒํ | 5 minutes |
์๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ | 10 minutes |
์์ ๊ถ | 5 minutes |
Move ๋ฌธ๋ฒ | 5 minutes |
Clone | 2 minutes |
๋ณตํฉ ํ์ | 5 minutes |
Drop | 10 minutes |
์ฐ์ต๋ฌธ์ : ๋น๋ ํ์ | 20 minutes |
ํ๋ก๊ทธ๋จ ๋ฉ๋ชจ๋ฆฌ ๊ฒํ
ํ๋ก๊ทธ๋จ์ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํฉ๋๋ค.
-
์คํ: ๋ก์ปฌ ๋ณ์๋ฅผ ์ํ ์ฐ์์ ์ธ ๋ฉ๋ชจ๋ฆฌ ์์ญ.
- ์ฌ๊ธฐ ์ ์ฅ๋๋ ๊ฐ์ ์ปดํ์ผ ์ ๊ฒฐ์ ๋๋ ๊ณ ์ ํฌ๊ธฐ๋ฅผ ๊ฐ์ต๋๋ค.
- ๋งค์ฐ ๋น ๋ฆ: ๋ฉ๋ชจ๋ฆฌ ํ ๋น/๋ฐํ์ด ๋จ์ง ์คํ ํฌ์ธํฐ์ ์ด๋๋ง์ผ๋ก ๊ตฌํ๋ฉ๋๋ค.
- ๊ด๋ฆฌ๊ฐ ์ฌ์: ํจ์๊ฐ ํธ์ถ๋๋ฉด ํ ๋น๋๊ณ , ๋ฆฌํดํ๋ฉด ๋ฐํ๋ฉ๋๋ค.
- ์คํ์ ์๋ ๊ฐ๋ค์ ๋งค์ฐ ๋์ ๋ฉ๋ชจ๋ฆฌ ์ธ์ ์ฑ์ ๊ฐ์ง๋๋ค.
-
ํ: ํจ์ ํธ์ถ/๋ฆฌํด๊ณผ ์๊ด ์์ด ์ ์ง๋๋ ๊ฐ์ด ์ ์ฅ๋๋ ๊ณณ.
- ์ฌ๊ธฐ ์ ์ฅ๋๋ ๊ฐ์ ํ๋ก๊ทธ๋จ ์ํ์ ๊ทธ ํฌ๊ธฐ๊ฐ ๊ฒฐ์ ๋ฉ๋๋ค.
- ์คํ ๋ณด๋ค๋ ๋๋ฆผ: ๋ฉ๋ชจ๋ฆฌ ํ ๋น/๋ฐํ์ ํด์ผ ํ ์ผ์ด ์ข ๋ ์์ต๋๋ค.
- ๋ฉ๋ชจ๋ฆฌ ์ธ์ ์ฑ์ ๋ณด์ฅํ์ง ์์ต๋๋ค.
์์
String
์ ํ๋ ๋ง๋ค๊ฒ ๋๋ฉด, ์คํ์๋ ๊ณ ์ ๋ ํฌ๊ธฐ์ ๋ฉํ ๋ฐ์ดํฐ๊ฐ ์์ฑ๋๊ณ , ํ์๋ ๊ฐ๋ณ ํฌ๊ธฐ์ ๋ฐ์ดํฐ, ์ฆ, ์ค์ ๋ฌธ์์ด, ์ด ์์ฑ๋ฉ๋๋ค:
fn main() { let s1 = String::from("์๋ ํ์ธ์"); }
-
๋ฌธ์์ด(
String
)์ ์ค์ ๋ก๋Vec
์ ๋๋ค. ํฌ๊ธฐ(capacity)์ ํ์ฌ ๊ธธ์ด(length) ์ ๋ณด๋ฅผ ๊ฐ์ง๋ฉฐ, ๋ ํฐ ํฌ๊ธฐ๊ฐ ํ์ํ ๊ฒฝ์ฐ ํ์์ ์ฌ ํ ๋น์ ํฉ๋๋ค. -
ํ์ ๊ธฐ๋ณธ์ ์ผ๋ก System Allocator๋ฅผ ํตํด ํ ๋น๋ฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ Allocator API๋ฅผ ์ด์ฉํด์ ์ปค์คํ ๋ฉ๋ชจ๋ฆฌ ํ ๋น์๋ฅผ ๋ง๋ค ์๋ ์์ต๋๋ค.
๋ ์ดํด๋ณด๊ธฐ
We can inspect the memory layout with unsafe
Rust. However, you should point out that this is rightfully unsafe!
fn main() { let mut s1 = String::from("์๋ ํ์ธ์"); s1.push(' '); s1.push_str("world"); // ์ง์์๋ ํ์ง ๋ง์ธ์. ๊ต์ก ๋ชฉ์ ์ผ๋ก๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค. // ๋ฌธ์์ด์ ๋ ์ด์์์ ๋ณด์ฅํ์ง ์์ผ๋ฏ๋ก // ์ ์๋์ง ์์ ๋์์ด ๋ฐ์ํ ์ ์์ต๋๋ค. unsafe { let (capacity, ptr, len): (usize, usize, usize) = std::mem::transmute(s1); println!("capacity = {capacity}, ptr = {ptr:#x}, len = {len}"); } }
์๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ
์ ํต์ ์ผ๋ก, ๋ ์ข ๋ฅ์ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๊ฐ ์์ต๋๋ค:
- ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ํ๋ก๊ทธ๋๋จธ์ ์์ ํ ํต์ ํ์ ์์ง๋ง ์๋(๊ทธ๋์ ์์ ํ์ง ์์ ์ ์๋)์ธ ์ธ์ด: C, C++, Pascal, โฆ
- ํ๋ก๊ทธ๋๋จธ๊ฐ ํ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ๊ฑฐ๋ ํ๋ณดํ ์๊ธฐ๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
- ํ๋ก๊ทธ๋๋จธ๋ ํฌ์ธํฐ๊ฐ ์ฌ์ ํ ์ ํจํ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ฐ๋ฆฌํค๋์ง ํ์ธํด์ผ ํฉ๋๋ค.
- ์ฐ๊ตฌ ๊ฒฐ๊ณผ์ ๋ฐ๋ฅด๋ฉด ํ๋ก๊ทธ๋๋จธ๋ ์ค์ํฉ๋๋ค.
- ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ๋ฐํ์์ ์ํด ๋๋ฏ๋ก ์์ ํ์ง๋ง ์๋(๊ทธ๋์ ํ๋ก๊ทธ๋๋จธ๊ฐ ๊ฐ์
ํ ์ฌ์ง๊ฐ ์ ๊ฑฐ๋ ์๋)์ธ ์ธ์ด: Java, Python, Go, Haskell, โฆ
- ๋ฐํ์ ์์คํ ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ ์ด์ ์ฐธ์กฐํ ์ ์์ ๋๊น์ง ํด์ ๋์ง ์๋๋ก ํฉ๋๋ค.
- ์ผ๋ฐ์ ์ผ๋ก ์ฐธ์กฐ ๊ณ์ฐ, ๊ฐ๋น์ง ์ปฌ๋ ์ ๋๋ RAII๋ก ๊ตฌํ๋ฉ๋๋ค.
๋ฌ์คํธ๋ ์ด ๋์ ํผํฉํ ์๋ก์ด ํํ์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ธฐ๋ฒ์ ์ ๊ณตํฉ๋๋ค:
์ปดํ์ผ ์ ์ฌ๋ฐ๋ฅธ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ๊ฐ์ ํจ์ผ๋ก์จ ์์ ํ ํต์ ์ ์์ ์ฑ ๋ชจ๋ ์ ๊ณต.
์ด๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ ๋ฌ์คํธ์ ์ปจ์ ์ ๋ช ์์ ์ธ ์์ ๊ถ์ ๋๋ค.
์ด ์ฌ๋ผ์ด๋๋ ๋ค๋ฅธ ์ธ์ด๋ฅผ ์ฌ์ฉํ๋ ํ์๋ค์ด ๋งฅ๋ฝ์ ๋ฐ๋ผ Rust๋ฅผ ์ฌ์ฉํ๋ ๋ฐ ๋์์ ์ฃผ๊ธฐ ์ํด ์์ฑ๋์์ต๋๋ค.
-
C๋
malloc
๋ฐfree
๋ฅผ ์ฌ์ฉํ์ฌ ํ์ ์๋์ผ๋ก ๊ด๋ฆฌํด์ผ ํฉ๋๋ค. ์ผ๋ฐ์ ์ธ ์ค๋ฅ๋ก๋free
ํธ์ถ์ ์์ด๋ฒ๋ฆฌ๊ฑฐ๋, ๋์ผํ ํฌ์ธํฐ์ ๋ํด ์ฌ๋ฌ ๋ฒ ํธ์ถํ๊ฑฐ๋, ํฌ์ธํฐ๊ฐ ๊ฐ๋ฆฌํค๋ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํด์ ๋ ํ ํฌ์ธํฐ๋ฅผ ์ญ์ฐธ์กฐํ๋ ๊ฒ ๋ฑ์ด ์์ต๋๋ค. -
C++์๋ ํจ์๊ฐ ๋ฐํ๋ ๋ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํด์ ๋๋๋ก ์๋ฉธ์๋ฅผ ํธ์ถํ๋ ๊ฒ์ ๊ดํ ์ธ์ด ๋ณด์ฅ์ ํ์ฉํ๋ ์ค๋งํธ ํฌ์ธํฐ(
unique_ptr
,shared_ptr
)์ ๊ฐ์ ๋๊ตฌ๊ฐ ์์ต๋๋ค. ์ด๋ฌํ ๋๊ตฌ๋ฅผ ์ค์ฉํ์ฌ C์ ์ ์ฌํ ๋ฒ๊ทธ๋ฅผ ์์ฑํ๋ ๊ฒ์ ์ฌ์ ํ ๋งค์ฐ ์ฝ์ต๋๋ค. -
Java, Go, Python์ ๊ฐ๋น์ง ์ปฌ๋ ํฐ๋ฅผ ์ฌ์ฉํด ๋ ์ด์ ์ฐ๊ฒฐํ ์ ์๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์๋ณํ๊ณ ์ญ์ ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ชจ๋ ํฌ์ธํฐ๊ฐ ์ญ์ฐธ์กฐ๋ ์ ์์ผ๋ฏ๋ก use-after-free ๋ฐ ๊ธฐํ ํด๋์ค์ ๋ฒ๊ทธ๋ฅผ ์ ๊ฑฐํ ์ ์์ต๋๋ค. ํ์ง๋ง GC๋ ๋ฐํ์ ๋น์ฉ์ด ๋ฐ์ํ๋ฉฐ ์ ๋๋ก ์กฐ์ ํ๊ธฐ๊ฐ ์ด๋ ต์ต๋๋ค.
Rust์ ์์ ๊ถ ๋ฐ ๋น๋ฆผ ๋ชจ๋ธ์ ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ์ ํํ ํ์ํ ๊ณณ์ alloc ๋ฐ free ์์ ์ ์คํํ์ฌ C์ ์ฑ๋ฅ์ ์ป์ ์ ์์ต๋๋ค. ์ฆ, ๋น์ฉ์ด ๋ค์ง ์์ต๋๋ค. C++์ ์ค๋งํธ ํฌ์ธํฐ์ ์ ์ฌํ ๋๊ตฌ๋ ์ ๊ณตํฉ๋๋ค. ํ์ํ ๊ฒฝ์ฐ ์ฐธ์กฐ ๊ณ์ฐ๊ณผ ๊ฐ์ ๋ค๋ฅธ ์ต์ ์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ์๋ ํํฐ ํฌ๋ ์ดํธ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐํ์ ๊ฐ๋น์ง ์ปฌ๋ ์ ์ ์ง์ํ ์๋ ์์ต๋๋ค(์ด ํด๋์ค์์๋ ๋ค๋ฃจ์ง ์์).
์์ ๊ถ
๋ชจ๋ ๋ณ์ ๋ฐ์ธ๋ฉ์ ์ ํจํ โ๋ฒ์(์ค์ฝํ)โ๋ฅผ ๊ฐ์ง๋ฉฐ, ๋ฒ์ ๋ฐ์์ ๋ณ์ ์ฌ์ฉํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค:
struct Point(i32, i32); fn main() { { let p = Point(3, 4); println!("x: {}", p.0); } println!("y: {}", p.1); }
We say that the variable owns the value. Every Rust value has precisely one owner at all times.
At the end of the scope, the variable is dropped and the data is freed. A destructor can run here to free up resources.
๊ฐ๋น์ง ์ปฌ๋ ์ ๊ตฌํ์ ์ต์ํ ํ์์ ๊ฐ๋น์ง ์ปฌ๋ ํฐ๊ฐ ์ฐ๊ฒฐ ๊ฐ๋ฅํ ๋ชจ๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฐพ๊ธฐ ์ํด โ๋ฃจํธโ ์ธํธ๋ก ์์ํ๋ค๋ ์ฌ์ค์ ์ ์ ์์ ๊ฒ์ ๋๋ค. Rust์ โ๋จ์ผ ์์ ์โ ์์น๋ ์ด์ ์ ์ฌํฉ๋๋ค.
Move ๋ฌธ๋ฒ
(๋ณ์์) ํ ๋น์ _์์ ๊ถ_์ ๋ณ์ ๊ฐ์ ์ด๋์ํต๋๋ค:
fn main() { let s1: String = String::from("Hello!"); let s2: String = s1; println!("s2: {s2}"); // println!("s1: {s1}"); }
s1
์s2
์ ํ ๋นํ์ฌ ์์ ๊ถ์ ์ด์ ์ํต๋๋ค.s1
์ ์ค์ฝํ๊ฐ ์ข ๋ฃ๋๋ฉด ์๋ฌด ์ผ๋ ์์ต๋๋ค: ์๋ํ๋ฉดs1
์ ์๋ฌด๋ฐ ์์ ๊ถ์ด ์๊ธฐ ๋๋ฌธ์ ๋๋ค.s2
์ ์ค์ฝํ๊ฐ ์ข ๋ฃ๋๋ฉด ๋ฌธ์์ด ๋ฐ์ดํฐ๋ ํด์ ๋ฉ๋๋ค.
s2
๋ก ์ด๋ ์ ๋ฉ๋ชจ๋ฆฌ:
s2
๋ก ์ด๋ ํ ๋ฉ๋ชจ๋ฆฌ:
๊ฐ์ ํจ์์ ์ ๋ฌํ ๋, ๊ทธ ๊ฐ์ ๋งค๊ฐ๋ณ์์ ํ ๋น๋ฉ๋๋ค. ์ด๋ ์์ ๊ถ์ ์ด๋์ด ์ผ์ด๋ฉ๋๋ค:
fn say_hello(name: String) { println!("์๋ ํ์ธ์ {name}") } fn main() { let name = String::from("Alice"); say_hello(name); // say_hello(name); }
-
์ด๋ C++๊ณผ ์ ๋ฐ๋ ์์ ์ค๋ช ํ์ธ์. C++์์๋ ๋ณต์ฌ๊ฐ ๊ธฐ๋ณธ์ด๊ณ ,
std::move
๋ฅผ ์ด์ฉํด์ผ๋ง (๊ทธ๋ฆฌ๊ณ ์ด๋ ์์ฑ์๊ฐ ์ ์๋์ด ์์ด์ผ๋ง!) ์์ ๊ถ ์ด์ ์ด ๋ฉ๋๋ค. -
์ค์ ๋ก ์ด๋๋๋ ๊ฒ์ ์์ ๊ถ์ผ ๋ฟ์ ๋๋ค. ๋จธ์ ์ฝ๋ ๋ ๋ฒจ์์ ๋ฐ์ดํฐ ๋ณต์ฌ๊ฐ ์ผ์ด๋ ์ง ๋ง ์ง์ ๋ํ ๊ฒ์ ์ปดํ์ผ๋ฌ ๋ด๋ถ์์ ์ผ์ด๋๋ ์ต์ ํ ๋ฌธ์ ์ ๋๋ค. ์ด๋ฐ ๋ณต์ฌ๋ ์ต์ ํ ๊ณผ์ ์์ ์ ๊ฑฐ๊ฐ ๋ฉ๋๋ค.
-
์ ์์ ๊ฐ์ ๊ฐ๋จํ ๊ฐ๋ค์
Copy
(๋ค์ ์ค๋ช ํฉ๋๋ค)๋ก ๋งํน๋ ์ ์์ต๋๋ค. -
๋ฌ์คํธ์์๋ ๋ณต์ฌํ ๋์๋ ๋ช ์์ ์ผ๋ก
clone
์ ์ฌ์ฉํฉ๋๋ค.
say_hello
์:
say_hello
ํจ์์ ์ฒซ๋ฒ์งธ ํธ์ถ์main
ํจ์๋ ์์ ์ด ๊ฐ์งname
์ ๋ํ ์์ ๊ถ์ ํฌ๊ธฐํ๋ฏ๋ก, ์ดํmain
ํจ์์์๋name
์ ์ฌ์ฉํ ์ ์์ต๋๋ค.name
์ ํ ๋น๋์๋ ํ ๋ฉ๋ชจ๋ฆฌ๋say_hello
ํจ์์ ๋์์ ํด์ ๋ฉ๋๋ค.main
ํจ์์์name
์ ์ฐธ์กฐ๋ก ์ ๋ฌ(๋น๋ฆผ)ํ๊ณ (&name
),say_hello
์์ ๋งค๊ฐ๋ณ์๋ฅผ ์ฐธ์กฐํ์ผ๋ก ์์ ํ๋ค๋ฉดmain
ํจ์๋name
์ ์์ ๊ถ์ ์ ์งํ ์ ์์ต๋๋ค.- ๋๋ ์ฒซ๋ฒ์งธ ํธ์ถ ์
main
ํจ์์์name
์ ๋ณต์ ํ์ฌ ์ ๋ฌํ ์๋ ์์ต๋๋ค.(name.clone()
) - ๋ฌ์คํธ๋ ์ด๋์ ๊ธฐ๋ณธ์ผ๋ก ํ๊ณ ๋ณต์ ๋ฅผ ๋ช ์์ ์ผ๋ก ์ ์ธํ๋๋ก ๋ง๋ฌ์ผ๋ก, ์๋์น ์๊ฒ ๋ณต์ฌ๋ณธ์ ๋ง๋๋ ๊ฒ์ด C++์์๋ณด๋ค ์ด๋ ต์ต๋๋ค.
๋ ์ดํด๋ณด๊ธฐ
Defensive Copies in Modern C++
Modern C++์ ์ด ๋ฌธ์ ๋ฅผ ๋ค๋ฅด๊ฒ ํด๊ฒฐํฉ๋๋ค:
std::string s1 = "Cpp";
std::string s2 = s1; // s1์ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ ํฉ๋๋ค.
s1
์ ํ ๋ฐ์ดํฐ๋ ๋ณต์ ๋๊ณ ,s2
๋ ๋ ๋ฆฝ์ ์ธ ๋ณต์ฌ๋ณธ์ ์ป์ต๋๋ค.s1
์s2
์ ์ค์ฝํ๊ฐ ์ข ๋ฃ๋๋ฉด ๊ฐ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํด์ ๋ฉ๋๋ค.
๋ณต์ฌ ์ :
๋ณต์ฌ ํ:
ํค ํฌ์ธํธ:
-
C++๋ Rust์ ์ฝ๊ฐ ๋ค๋ฅธ ์ ํ์ ํ์ต๋๋ค.
=
๋ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํ๋ฏ๋ก ๋ฌธ์์ด ๋ฐ์ดํฐ๊ฐ ํด๋ก ๋์ด์ผ ํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ๋ฌธ์์ด ์ค ํ๋๊ฐ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๋ double-free๊ฐ ๋ฐ์ํฉ๋๋ค. -
C++์๋ ๊ฐ์ ์ด๋ํ ์ ์๋ ์์ ์ ๋ํ๋ด๋ ๋ฐ ์ฌ์ฉ๋๋
std::move
๋ ์์ต๋๋ค. ์๊ฐs2 = std::move(s1)
์ด์๋ค๋ฉด ํ ํ ๋น์ด ๋ฐ์ํ์ง ์์ต๋๋ค. ์ด๋ ํ์๋s1
์ด ์ ํจํ์ง๋ง ์ง์ ๋์ง ์์ ์ํ๊ฐ ๋ฉ๋๋ค. Rust์ ๋ฌ๋ฆฌ ํ๋ก๊ทธ๋๋จธ๋s1
์ ๊ณ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. -
Rust์ ๋ฌ๋ฆฌ, C++์
=
๋ ๋ณต์ฌ๋๊ฑฐ๋ ์ด๋๋๋ ํ์ ์ ๋ฐ๋ผ ๊ฒฐ์ ๋ ์์์ ์ฝ๋๋ฅผ ์คํํ ์ ์์ต๋๋ค.
Clone
๊ฐ์ ๋ณต์ฌํด์ผ ํ๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค. Clone
ํธ๋ ์์ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
#[derive(Default)] struct Backends { hostnames: Vec<String>, weights: Vec<f64>, } impl Backends { fn set_hostnames(&mut self, hostnames: &Vec<String>) { self.hostnames = hostnames.clone(); self.weights = hostnames.iter().map(|_| 1.0).collect(); } }
Clone
์ ๊ฐ๋
์ ํ ํ ๋น์ด ๋ฐ์ํ๋ ์์น๋ฅผ ์ฝ๊ฒ ์์๋ด๋ ๊ฒ์
๋๋ค. .clone()
๋ฐ Vec::new
๋๋ Box::new
์ ๊ฐ์ ๋ค๋ฅธ ์ฝ๋๋ ์ฐพ์๋ด
๋๋ค.
๋น๋ฆผ ๊ฒ์ฌ๊ธฐ๋ก ๋ฌธ์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ โํด๋ก โํ๊ณ ๋์ค์ ๋ค์ ๋ฐฉ๋ฌธํ์ฌ ํด๋น ํด๋ก ์ ์ต์ ํํ๋ ค๊ณ ์๋ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
๋ณตํฉ ํ์
์ด๋์ด ๊ธฐ๋ณธ ์ค์ ์ด์ง๋ง, ํน์ ํ์ ์ ๋ณต์ฌ๋ฉ๋๋ค:
fn main() { let x = 42; let y = x; println!("x: {x}"); // would not be accessible if not Copy println!("y: {y}"); }
์ด๋ฌํ ํ์
๋ค์ Copy
ํธ๋ ์์ ๊ตฌํํฉ๋๋ค.
์ง์ ๋ง๋ ํ์
๋ค๋ Copy
ํธ๋ ์์ ๊ตฌํํ์ฌ ๋ณต์ฌ๋ฅผ ํ ์ ์์ต๋๋ค:
#[derive(Copy, Clone, Debug)] struct Point(i32, i32); fn main() { let p1 = Point(3, 4); let p2 = p1; println!("p1: {p1:?}"); println!("p2: {p2:?}"); }
- ํ ๋น ํ,
p1
์p2
๋ ์์ ์ ๋ฐ์ดํฐ๋ฅผ ์์ ํฉ๋๋ค. - ๋ช
์์ ์ผ๋ก
p1.clone()
๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํ ์ ์์ต๋๋ค.
๋ณต์ฌ(copy)์ ๋ณต์ (clone)๋ ๊ฐ์ง ์์ต๋๋ค:
- ๋ณต์ฌ๋ ๋ฉ๋ชจ๋ฆฌ์ ๋ด์ฉ์ ๊ทธ๋๋ก ํ ๋ฒ ๋ ๋ง๋๋ ๊ฒ์ ์๋ฏธํ๋ฉฐ, ์๋ฌด ๊ฐ์ฒด์์๋ ๋ค ์ง์ํ์ง๋ ์์ต๋๋ค.
- ๋ณต์ฌ๋ ์ปค์คํฐ๋ง์ด์ฆ ํ ์ ์์ต๋๋ค. (C++์์ ๋ณต์ฌ ์์ฑ์๋ฅผ ํตํด ๋ณต์ฌ ๋์์ ์์๋ก ๊ตฌํํ ์ ์๋ ๊ฒ๊ณผ ๋น๊ต๊ฐ ๋ฉ๋๋ค.)
- ๋ณต์ ๋ ๋ณด๋ค ์ผ๋ฐ์ ์ธ ์์
์ด๋ฉฐ,
Clone
ํธ๋ ์์ ๊ตฌํํ์ฌ ๋ณต์ ์ ๋์์ ์ปค์คํฐ๋ง์ด์ฆ ํ ์ ์์ต๋๋ค. Drop
ํธ๋ ์์ ๊ตฌํํ ํ์ ์ ๋ณต์ฌ๋์ง ์์ต๋๋ค.
์์ ์์์์ ๋ค์์ ์๋ํด ๋ณด์๊ธฐ ๋ฐ๋๋๋ค:
Point
๊ตฌ์กฐ์ฒด์String
ํ๋๋ฅผ ์ถ๊ฐํ์ธ์. ์ปดํ์ผ ๋์ง ์์ ๊ฒ์ ๋๋ค. ์๋ํ๋ฉดString
์Copy
ํธ๋ ์์ ๊ตฌํํ๊ณ ์์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค.- Remove
Copy
from thederive
attribute. The compiler error is now in theprintln!
forp1
. p1
์ ๋ณต์ ํ๋ฉด ์ ๋์ํจ์ ํ์ธํด ๋ณด์ธ์.
Drop
ํธ๋ ์
Drop
ํธ๋ ์์ ๊ตฌํํ๋ฉด, ๊ทธ ๊ฐ์ด ์ค์ฝํ ๋ฐ์ผ๋ก ๋๊ฐ ๋ ์คํ๋ ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค:
struct Droppable { name: &'static str, } impl Drop for Droppable { fn drop(&mut self) { println!("{} ์ญ์ ์ค", self.name); } } fn main() { let a = Droppable { name: "a" }; { let b = Droppable { name: "b" }; { let c = Droppable { name: "c" }; let d = Droppable { name: "d" }; println!("B ๋ธ๋ก์์ ๋๊ฐ๊ธฐ"); } println!("A ๋ธ๋ก์์ ๋๊ฐ๊ธฐ"); } drop(a); println!("main์์ ๋๊ฐ๊ธฐ"); }
std::mem::drop
์std::ops::Drop::drop
๊ณผ ๊ฐ์ง ์์ต๋๋ค.- ๊ฐ์ด ๋ฒ์๋ฅผ ๋ฒ์ด๋๋ฉด ์๋์ผ๋ก ์ญ์ ๋ฉ๋๋ค.
- ๊ฐ์ด ์ญ์ ๋ ๋
std::ops::Drop
์ ๊ตฌํํ๋ฉดDrop::drop
๊ตฌํ์ด ํธ์ถ๋ฉ๋๋ค. - ๊ทธ๋ฌ๋ฉด
Drop
๊ตฌํ ์ฌ๋ถ์ ๊ด๊ณ์์ด ํด๋น ํ๋๋ ๋ชจ๋ ์ญ์ ๋ฉ๋๋ค. std::mem::drop
์ ๊ฐ์ ์ฌ์ฉํ๋ ๋น ํจ์์ ๋๋ค. ์ค์ํ ์ ์ ๊ฐ์ ์์ ๊ถ์ ๊ฐ์ง๋ฏ๋ก ๋ฒ์ ๋์์ ์ญ์ ๋๋ค๋ ์ ์ ๋๋ค. ๋ฐ๋ผ์ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๋๋ณด๋ค ๋นจ๋ฆฌ ๊ฐ์ ๋ช ์์ ์ผ๋ก ์ญ์ ํ ์ ์๋ ํธ๋ฆฌํ ๋ฐฉ๋ฒ์ ๋๋ค.- ์ด๋
drop
์์ ์ ๊ธ ํด์ , ํ์ผ ๋ซ๊ธฐ ๋ฑ์ ์์ ์ ์คํํ๋ ๊ฐ์ฒด์ ์ ์ฉํ ์ ์์ต๋๋ค.
- ์ด๋
๋ ผ์์ :
Drop::drop
์ ์self
๋ฅผ ์ธ์๋ก ๋ฐ์ง ์์ต๋๊น?- ์งง์ ๋๋ต: ๋ง์ฝ ๊ทธ๋ ๊ฒ ๋๋ค๋ฉด
std::mem::drop
์ด ๋ธ๋ก์ ๋์์ ํธ์ถ๋๊ณ , ๋ค์Drop::drop
์ ํธ์ถํ๊ฒ ๋์ด, ์คํ ์ค๋ฒํ๋ก๊ฐ ๋ฐ์ํฉ๋๋ค!
- ์งง์ ๋๋ต: ๋ง์ฝ ๊ทธ๋ ๊ฒ ๋๋ค๋ฉด
drop(a)
๋ฅผa.drop()
๋ก ๋ณ๊ฒฝํด ๋ณด์๊ธฐ ๋ฐ๋๋๋ค.
์ฐ์ต๋ฌธ์ : ๋น๋ ํ์
์ด ์์์๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ ๋ณต์กํ ๋ฐ์ดํฐ ํ์ ์ ๊ตฌํํฉ๋๋ค. ํธ์ ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์๋ก์ด ๊ฐ์ ํ๋์ฉ ๋น๋ํ๋๋ก ์ง์ํ๊ธฐ ์ํด โ๋น๋ ํจํดโ์ ์ฌ์ฉํฉ๋๋ค.
๋๋ฝ๋ ๋ถ๋ถ์ ์ฑ์ฐ์ธ์.
#[derive(Debug)] enum Language { Rust, Java, Perl, } #[derive(Clone, Debug)] struct Dependency { name: String, version_expression: String, } /// ์ํํธ์จ์ด ํจํค์ง๋ฅผ ๋ํ๋ ๋๋ค. #[derive(Debug)] struct Package { name: String, version: String, authors: Vec<String>, dependencies: Vec<Dependency>, language: Option<Language>, } impl Package { /// Return a representation of this package as a dependency, for use in /// building other packages. fn as_dependency(&self) -> Dependency { todo!("1") } } /// ํจํค์ง์ฉ ๋น๋์ ๋๋ค. `build()`๋ฅผ ์ฌ์ฉํ์ฌ `Package` ์์ฒด๋ฅผ ๋ง๋ญ๋๋ค. struct PackageBuilder(Package); impl PackageBuilder { fn new(name: impl Into<String>) -> Self { todo!("2") } /// ํจํค์ง ๋ฒ์ ์ ์ค์ ํฉ๋๋ค. fn version(mut self, version: impl Into<String>) -> Self { self.0.version = version.into(); self } /// ํจํค์ง ์์ฑ์๋ฅผ ์ค์ ํฉ๋๋ค. fn authors(mut self, authors: Vec<String>) -> Self { todo!("3") } /// ์ข ์ ํญ๋ชฉ์ ์ถ๊ฐํฉ๋๋ค. fn dependency(mut self, dependency: Dependency) -> Self { todo!("4") } /// ์ธ์ด๋ฅผ ์ค์ ํฉ๋๋ค. ์ค์ ํ์ง ์์ผ๋ฉด ์ธ์ด๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก None์ผ๋ก ์ค์ ๋ฉ๋๋ค. fn language(mut self, language: Language) -> Self { todo!("5") } fn build(self) -> Package { self.0 } } fn main() { let base64 = PackageBuilder::new("base64").version("0.13").build(); println!("base64: {base64:?}"); let log = PackageBuilder::new("log").version("0.4").language(Language::Rust).build(); println!("log: {log:?}"); let serde = PackageBuilder::new("serde") .authors(vec!["djmitche".into()]) .version(String::from("4.0")) .dependency(base64.as_dependency()) .dependency(log.as_dependency()) .build(); println!("serde: {serde:?}"); }
ํด๋ต
#[derive(Debug)] enum Language { Rust, Java, Perl, } #[derive(Clone, Debug)] struct Dependency { name: String, version_expression: String, } /// ์ํํธ์จ์ด ํจํค์ง๋ฅผ ๋ํ๋ ๋๋ค. #[derive(Debug)] struct Package { name: String, version: String, authors: Vec<String>, dependencies: Vec<Dependency>, language: Option<Language>, } impl Package { /// Return a representation of this package as a dependency, for use in /// building other packages. fn as_dependency(&self) -> Dependency { Dependency { name: self.name.clone(), version_expression: self.version.clone(), } } } /// ํจํค์ง์ฉ ๋น๋์ ๋๋ค. `build()`๋ฅผ ์ฌ์ฉํ์ฌ `Package` ์์ฒด๋ฅผ ๋ง๋ญ๋๋ค. struct PackageBuilder(Package); impl PackageBuilder { fn new(name: impl Into<String>) -> Self { Self(Package { name: name.into(), version: "0.1".into(), authors: vec![], dependencies: vec![], language: None, }) } /// ํจํค์ง ๋ฒ์ ์ ์ค์ ํฉ๋๋ค. fn version(mut self, version: impl Into<String>) -> Self { self.0.version = version.into(); self } /// ํจํค์ง ์์ฑ์๋ฅผ ์ค์ ํฉ๋๋ค. fn authors(mut self, authors: Vec<String>) -> Self { self.0.authors = authors; self } /// ์ข ์ ํญ๋ชฉ์ ์ถ๊ฐํฉ๋๋ค. fn dependency(mut self, dependency: Dependency) -> Self { self.0.dependencies.push(dependency); self } /// ์ธ์ด๋ฅผ ์ค์ ํฉ๋๋ค. ์ค์ ํ์ง ์์ผ๋ฉด ์ธ์ด๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก None์ผ๋ก ์ค์ ๋ฉ๋๋ค. fn language(mut self, language: Language) -> Self { self.0.language = Some(language); self } fn build(self) -> Package { self.0 } } fn main() { let base64 = PackageBuilder::new("base64").version("0.13").build(); println!("base64: {base64:?}"); let log = PackageBuilder::new("log").version("0.4").language(Language::Rust).build(); println!("log: {log:?}"); let serde = PackageBuilder::new("serde") .authors(vec!["djmitche".into()]) .version(String::from("4.0")) .dependency(base64.as_dependency()) .dependency(log.as_dependency()) .build(); println!("serde: {serde:?}"); }
์ค๋งํธ ํฌ์ธํฐ
This segment should take about 55 minutes. It contains:
Slide | Duration |
---|---|
Box | 10 minutes |
Rc | 5 minutes |
ํธ๋ ์ ๊ฐ์ฒด | 10 minutes |
์ฐ์ต๋ฌธ์ : ๋ฐ์ด๋๋ฆฌ ํธ๋ฆฌ | 30 minutes |
Box<T>
Box
๋ ํ ๋ฐ์ดํฐ์ ๋ํ ์์ ํฌ์ธํฐ์
๋๋ค:
fn main() { let five = Box::new(5); println!("five: {}", *five); }
Box<T>
์ Deref<Target = T>
๋ฅผ ๊ตฌํํฉ๋๋ค. ์ด๋ Box<T>
์์ T
๋ฉ์๋๋ฅผ ์ง์ ํธ์ถ ํ ์ ์๋ค๋ ์๋ฏธ์
๋๋ค.
์ฌ๊ท ๋ฐ์ดํฐ๋ ๋์ ํฌ๊ธฐ์ ๋ฐ์ดํฐ ํ์
์ Box
ํ์
์ ์ฌ์ฉํด์ผ ํฉ๋๋ค:
#[derive(Debug)] enum List<T> { /// A non-empty list: first element and the rest of the list. Element(T, Box<List<T>>), /// An empty list. Nil, } fn main() { let list: List<i32> = List::Element(1, Box::new(List::Element(2, Box::new(List::Nil)))); println!("{list:?}"); }
-
Box
is likestd::unique_ptr
in C++, except that itโs guaranteed to be not null. -
Box
๋ ์๋์ ๊ฒฝ์ฐ์ ์ ์ฉํฉ๋๋ค:- ํ์ ํฌ๊ธฐ๋ฅผ ์ปดํ์ผ ์์ ์ ์ ์ ์๋ ๊ฒฝ์ฐ.
- ์์ฃผ ํฐ ๋ฐ์ดํฐ์ ์์ ๊ถ์ ์ ๋ฌํ๊ณ ์ถ์ ๊ฒฝ์ฐ. ์คํ์ ์๋ ํฐ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํ๋ ๋์
Box
๋ฅผ ์ด์ฉํ์ฌ ๋ฐ์ดํฐ๋ ํ์ ์ ์ฅํ๊ณ ํฌ์ธํฐ๋ง ์ด๋ํ๋ฉด ๋ฉ๋๋ค.
-
If
Box
was not used and we attempted to embed aList
directly into theList
, the compiler would not be able to compute a fixed size for the struct in memory (theList
would be of infinite size). -
Box
๋ ์ผ๋ฐ ํฌ์ธํฐ์ ํฌ๊ธฐ๊ฐ ๊ฐ๊ธฐ ๋๋ฌธ์ ํฌ๊ธฐ๋ฅผ ๊ณ์ฐํ๋ ๋ฐ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๋ค๋ง ํ์ ์์นํList
์ ๋ค์ ์์๋ฅผ ๊ฐ๋ฆฌํฌ ๋ฟ์ ๋๋ค. -
Remove the
Box
in the List definition and show the compiler error. We get the message โrecursive without indirectionโ, because for data recursion, we have to use indirection, aBox
or reference of some kind, instead of storing the value directly.
๋ ์ดํด๋ณด๊ธฐ
๋์น(ํ์) ์ต์ ํ(Niche Optimization)
#[derive(Debug)] enum List<T> { Element(T, Box<List<T>>), Nil, } fn main() { let list: List<i32> = List::Element(1, Box::new(List::Element(2, Box::new(List::Nil)))); println!("{list:?}"); }
Box
๋ ๋น์ด์์ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ํฌ์ธํฐ๋ ํญ์ ์ ํจํ๋ฉฐ null
์ด ์๋๋๋ค. ์ด๋ ์ปดํ์ผ๋ฌ๊ฐ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์์ ์ต์ ํ ํ ์ ์๊ฒ ํด์ค๋๋ค:
Rc
Rc
๋ ์ฐธ์กฐ ์นด์ดํ
๊ณต์ ํฌ์ธํฐ์
๋๋ค. ์ฌ๋ฌ ์์น์์ ๋์ผํ ๋ฐ์ดํฐ๋ฅผ ์ฐธ์กฐํด์ผํ ๊ฒฝ์ฐ ์ฌ์ฉํฉ๋๋ค:
use std::rc::Rc; fn main() { let a = Rc::new(10); let b = Rc::clone(&a); println!("a: {a}"); println!("b: {b}"); }
- ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์ ์์
ํ๋ ๊ฒฝ์ฐ
Arc
์Mutex
๋ฅผ ์ฐธ์กฐํ์ธ์. - drop ๊ฐ๋ฅํ ์ํ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค๊ธฐ ์ํด ๊ณต์ ํฌ์ธํฐ๋ฅผ
Weak
ํฌ์ธํฐ๋ก _๋ค์ด๊ทธ๋ ์ด๋_ํ ์๋ ์์ต๋๋ค.
Rc
๋ ์ฐธ์กฐ ์นด์ดํธ๋ฅผ ํตํด ์ฐธ์กฐ๊ฐ ์๋ ๋์์Rc
๊ฐ ๊ฐ๋ฆฌํค๊ณ ์๋ ๊ฐ์ด ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋์ง ์์์ ๋ณด์ฅํฉ๋๋ค.- C++์
std::shared_ptr
์ ์ ์ฌํฉ๋๋ค. clone
์ ๋น์ฉ์ด ๊ฑฐ์ ๋ค์ง ์์ต๋๋ค. ๊ฐ์ ๊ณณ์ ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ๋ฅผ ํ๋ ๋ ๋ง๋ค๊ณ , ์ฐธ์กฐ ์นด์ดํธ๋ฅผ ๋๋ฆฝ๋๋ค. ํฌ์ธํฐ๊ฐ ๊ฐ๋ฆฌํค๋ ๊ฐ ์์ฒด๊ฐ ๋ณต์ (๊น์ ๋ณต์ )๋์ง๋ ์์ผ๋ฉฐ, ๊ทธ๋์ ์ฝ๋์์ ์ฑ๋ฅ ๋ฌธ์ ๊ฐ ์๋์ง ๊ฒํ ํ ๋ ์ผ๋ฐ์ ์ผ๋กRc
๋ฅผclone
ํ๋ ๊ฒ์ ๋ฌด์ํ ์ ์์ต๋๋ค.make_mut
๋ ์ค์ ๋ก ํ์ํ ๊ฒฝ์ฐ์ ๋ด๋ถ ๊ฐ์ ๋ณต์ ํ๊ณ (โclone-on-writeโ) ๊ฐ๋ณ ์ฐธ์กฐ๋ฅผ ๋ฐํํฉ๋๋ค.- ์ฐธ์กฐ ์นด์ดํธ๋ฅผ ํ์ธํ๋ ค๋ฉด
Rc::strong_count
๋ฅผ ์ฌ์ฉํ์ธ์. Rc::downgrade
gives you a weakly reference-counted object to create cycles that will be dropped properly (likely in combination withRefCell
).
ํธ๋ ์ ๊ฐ์ฒด
ํธ๋ ์ ๊ฐ์ฒด๋ ํ์ ์ด ๋ค๋ฅธ ๊ฐ(์๋ฅผ ๋ค์ด ์ปฌ๋ ์ ์ ์ํ ๊ฐ ๊ฐ)๋ค์ ๊ฐ์ง ์ ์์ต๋๋ค:
struct Dog { name: String, age: i8, } struct Cat { lives: i8, } trait Pet { fn talk(&self) -> String; } impl Pet for Dog { fn talk(&self) -> String { format!("๋ฉ๋ฉ, ์ ์ด๋ฆ์ {}์ ๋๋ค.", self.name) } } impl Pet for Cat { fn talk(&self) -> String { String::from("๋์น!") } } fn main() { let pets: Vec<Box<dyn Pet>> = vec![ Box::new(Cat { lives: 9 }), Box::new(Dog { name: String::from("Fido"), age: 5 }), ]; for pet in pets { println!("Hello, who are you? {}", pet.talk()); } }
pets
๋ฅผ ํ ๋นํ ์ดํ์ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์:
- Types that implement a given trait may be of different sizes. This makes it impossible to have things like
Vec<dyn Pet>
in the example above. dyn Pet
์ด๋ผ๊ณ ํ๋ฉด์ด ํ์ ์ ํฌ๊ธฐ๋ ๋์ ์ด๋ฉฐPet
์ ๊ตฌํํ๊ณ ์๋ค๊ณ ์ปดํ์ผ๋ฌ์๊ฒ ์๋ ค์ฃผ๋ ๊ฒ์ ๋๋ค.- ์ด ์์์๋
pets
๊ฐ ์คํ์ ํ ๋น๋๊ณ ๋ฒกํฐ ๋ฐ์ดํฐ๋ ํ์ ์์ต๋๋ค. ๋ ๋ฒกํฐ ์์๋ _fat ํฌ์ธํฐ_์ ๋๋ค.- A fat pointer is a double-width pointer. It has two components: a pointer to the actual object and a pointer to the virtual method table (vtable) for the
Pet
implementation of that particular object. - ์ด๋ฆ์ด Fido์ธ
Dog
์ ๋ฐ์ดํฐ๋name
๋ฐage
ํ๋์ ๋๋ค.Cat
์๋lives
ํ๋๊ฐ ์์ต๋๋ค.
- A fat pointer is a double-width pointer. It has two components: a pointer to the actual object and a pointer to the virtual method table (vtable) for the
- ์๋ ์ฝ๋์ ๊ฒฐ๊ณผ์ ๋น๊ตํด๋ณด์ธ์:
println!("{} {}", std::mem::size_of::<Dog>(), std::mem::size_of::<Cat>()); println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>()); println!("{}", std::mem::size_of::<&dyn Pet>()); println!("{}", std::mem::size_of::<Box<dyn Pet>>());
์ฐ์ต๋ฌธ์ : ๋ฐ์ด๋๋ฆฌ ํธ๋ฆฌ
๋ฐ์ด๋๋ฆฌ ํธ๋ฆฌ๋ ๋ชจ๋ ๋ ธ๋์ ๋ ๊ฐ์ ํ์ ์์(์ผ์ชฝ๊ณผ ์ค๋ฅธ์ชฝ)๊ฐ ์๋ ํธ๋ฆฌ ์ ํ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ ๋๋ค. ๊ฐ ๋ ธ๋๊ฐ ๊ฐ์ ์ ์ฅํ๋ ํธ๋ฆฌ๋ฅผ ๋ง๋ค๊ฒ ์ต๋๋ค. ์ฃผ์ด์ง ๋ ธ๋ N์ ๊ฒฝ์ฐ N์ ์ผ์ชฝ ํ์ ํธ๋ฆฌ์ ์๋ ๋ชจ๋ ๋ ธ๋๋ ๋ ์์ ๊ฐ์ ํฌํจํ๊ณ , N์ ์ค๋ฅธ์ชฝ ํ์ ํธ๋ฆฌ์ ์๋ ๋ชจ๋ ๋ ธ๋๋ ๋ ํฐ ๊ฐ์ ํฌํจํฉ๋๋ค.
์ง์ ๋ ํ ์คํธ๊ฐ ํต๊ณผํ๋๋ก ๋ค์ ํ์ ์ ๊ตฌํํฉ๋๋ค.
์ถ๊ฐ ํฌ๋ ๋ง: ๊ฐ์ ์์๋๋ก ๋ฐํํ๋ ๋ฐ์ด๋๋ฆฌ ํธ๋ฆฌ์ ๋ํ ๋ฐ๋ณต์๋ฅผ ๊ตฌํํฉ๋๋ค.
/// A node in the binary tree. #[derive(Debug)] struct Node<T: Ord> { value: T, left: Subtree<T>, right: Subtree<T>, } /// A possibly-empty subtree. #[derive(Debug)] struct Subtree<T: Ord>(Option<Box<Node<T>>>); /// ๋ฐ์ด๋๋ฆฌ ํธ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ์งํฉ์ ์ ์ฅํ๋ ์ปจํ ์ด๋์ ๋๋ค. /// /// ๋์ผํ ๊ฐ์ด ์ฌ๋ฌ ๋ฒ ์ถ๊ฐ๋๋ฉด ํ ๋ฒ๋ง ์ ์ฅ๋ฉ๋๋ค. #[derive(Debug)] pub struct BinaryTree<T: Ord> { root: Subtree<T>, } // Implement `new`, `insert`, `len`, and `has`. #[cfg(test)] mod tests { use super::*; #[test] fn len() { let mut tree = BinaryTree::new(); assert_eq!(tree.len(), 0); tree.insert(2); assert_eq!(tree.len(), 1); tree.insert(1); assert_eq!(tree.len(), 2); tree.insert(2); // ๊ณ ์ ํญ๋ชฉ์ด ์๋๋๋ค. assert_eq!(tree.len(), 2); } #[test] fn has() { let mut tree = BinaryTree::new(); fn check_has(tree: &BinaryTree<i32>, exp: &[bool]) { let got: Vec<bool> = (0..exp.len()).map(|i| tree.has(&(i as i32))).collect(); assert_eq!(&got, exp); } check_has(&tree, &[false, false, false, false, false]); tree.insert(0); check_has(&tree, &[true, false, false, false, false]); tree.insert(4); check_has(&tree, &[true, false, false, false, true]); tree.insert(4); check_has(&tree, &[true, false, false, false, true]); tree.insert(3); check_has(&tree, &[true, false, false, true, true]); } #[test] fn unbalanced() { let mut tree = BinaryTree::new(); for i in 0..100 { tree.insert(i); } assert_eq!(tree.len(), 100); assert!(tree.has(&50)); } }
ํด๋ต
use std::cmp::Ordering; /// A node in the binary tree. #[derive(Debug)] struct Node<T: Ord> { value: T, left: Subtree<T>, right: Subtree<T>, } /// A possibly-empty subtree. #[derive(Debug)] struct Subtree<T: Ord>(Option<Box<Node<T>>>); /// ๋ฐ์ด๋๋ฆฌ ํธ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ์งํฉ์ ์ ์ฅํ๋ ์ปจํ ์ด๋์ ๋๋ค. /// /// ๋์ผํ ๊ฐ์ด ์ฌ๋ฌ ๋ฒ ์ถ๊ฐ๋๋ฉด ํ ๋ฒ๋ง ์ ์ฅ๋ฉ๋๋ค. #[derive(Debug)] pub struct BinaryTree<T: Ord> { root: Subtree<T>, } impl<T: Ord> BinaryTree<T> { fn new() -> Self { Self { root: Subtree::new() } } fn insert(&mut self, value: T) { self.root.insert(value); } fn has(&self, value: &T) -> bool { self.root.has(value) } fn len(&self) -> usize { self.root.len() } } impl<T: Ord> Subtree<T> { fn new() -> Self { Self(None) } fn insert(&mut self, value: T) { match &mut self.0 { None => self.0 = Some(Box::new(Node::new(value))), Some(n) => match value.cmp(&n.value) { Ordering::Less => n.left.insert(value), Ordering::Equal => {} Ordering::Greater => n.right.insert(value), }, } } fn has(&self, value: &T) -> bool { match &self.0 { None => false, Some(n) => match value.cmp(&n.value) { Ordering::Less => n.left.has(value), Ordering::Equal => true, Ordering::Greater => n.right.has(value), }, } } fn len(&self) -> usize { match &self.0 { None => 0, Some(n) => 1 + n.left.len() + n.right.len(), } } } impl<T: Ord> Node<T> { fn new(value: T) -> Self { Self { value, left: Subtree::new(), right: Subtree::new() } } } fn main() { let mut tree = BinaryTree::new(); tree.insert("foo"); assert_eq!(tree.len(), 1); tree.insert("bar"); assert!(tree.has(&"foo")); } #[cfg(test)] mod tests { use super::*; #[test] fn len() { let mut tree = BinaryTree::new(); assert_eq!(tree.len(), 0); tree.insert(2); assert_eq!(tree.len(), 1); tree.insert(1); assert_eq!(tree.len(), 2); tree.insert(2); // ๊ณ ์ ํญ๋ชฉ์ด ์๋๋๋ค. assert_eq!(tree.len(), 2); } #[test] fn has() { let mut tree = BinaryTree::new(); fn check_has(tree: &BinaryTree<i32>, exp: &[bool]) { let got: Vec<bool> = (0..exp.len()).map(|i| tree.has(&(i as i32))).collect(); assert_eq!(&got, exp); } check_has(&tree, &[false, false, false, false, false]); tree.insert(0); check_has(&tree, &[true, false, false, false, false]); tree.insert(4); check_has(&tree, &[true, false, false, false, true]); tree.insert(4); check_has(&tree, &[true, false, false, false, true]); tree.insert(3); check_has(&tree, &[true, false, false, true, true]); } #[test] fn unbalanced() { let mut tree = BinaryTree::new(); for i in 0..100 { tree.insert(i); } assert_eq!(tree.len(), 100); assert!(tree.has(&50)); } }
Welcome Back
Including 10 minute breaks, this session should take about 2 hours and 10 minutes. It contains:
Segment | Duration |
---|---|
๋น๋ฆผ | 50 minutes |
์๋ช | 1 hour and 10 minutes |
๋น๋ฆผ
This segment should take about 50 minutes. It contains:
Slide | Duration |
---|---|
๋น๋ฆผ | 10 minutes |
๋น๋ฆผ | 10 minutes |
์ํธ์ด์ฉ์ฑ | 10 minutes |
์ฐ์ต๋ฌธ์ : ์๋ฆฌ๋ฒ ์ดํฐ ์ด๋ฒคํธ | 20 minutes |
๋น๋ฆผ
As we saw before, instead of transferring ownership when calling a function, you can let a function borrow the value:
#[derive(Debug)] struct Point(i32, i32); fn add(p1: &Point, p2: &Point) -> Point { Point(p1.0 + p2.0, p1.1 + p2.1) } fn main() { let p1 = Point(3, 4); let p2 = Point(10, 20); let p3 = add(&p1, &p2); println!("{p1:?} + {p2:?} = {p3:?}"); }
add
ํจ์๋ ๋Point
๊ฐ์ฒด ๊ฐ์ _๋น๋ ค_์์ ์๋ก์ดPoint
๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค.p1
๊ณผp2
์ ์์ ๊ถ์ ์ฌ์ ํ ํธ์ถ์(main
ํจ์)์ ์์ต๋๋ค.
์ด ์ฌ๋ผ์ด๋๋ 1์ผ ์ฐจ์ ์ฐธ์กฐ๋ฅผ ๋ค๋ฃฌ ์๋ฃ๋ฅผ ๊ฒํ ํ๋ ๊ฒ์ผ๋ก, ์ฝ๊ฐ ํ์ฅ๋์ด ํจ์ ์ธ์์ ๋ฐํ ๊ฐ์ ํฌํจํฉ๋๋ค.
๋ ์ดํด๋ณด๊ธฐ
์คํ์ ํ ๋น๋ ๊ฐ์ ๋ฆฌํดํ๋ ๊ฒ์ ๋ํ ์ฐธ๊ณ :
-
Demonstrate that the return from
add
is cheap because the compiler can eliminate the copy operation. Change the above code to print stack addresses and run it on the Playground or look at the assembly in Godbolt. In the โDEBUGโ optimization level, the addresses should change, while they stay the same when changing to the โRELEASEโ setting:#[derive(Debug)] struct Point(i32, i32); fn add(p1: &Point, p2: &Point) -> Point { let p = Point(p1.0 + p2.0, p1.1 + p2.1); println!("&p.0: {:p}", &p.0); p } pub fn main() { let p1 = Point(3, 4); let p2 = Point(10, 20); let p3 = add(&p1, &p2); println!("&p3.0: {:p}", &p3.0); println!("{p1:?} + {p2:?} = {p3:?}"); }
-
๋ฌ์คํธ ์ปดํ์ผ๋ฌ๋ ๋ฐํ๊ฐ ์ต์ ํ(RVO)๋ฅผ ์ํํ ์ ์์ต๋๋ค.
-
C++์์ copy elision์ ์์ฑ์์ ๋ถ์ํจ๊ณผ ๊ฐ๋ฅ์ฑ์ด ์์ด ์ธ์ด๋ ๋ฒจ์ ์ ์๊ฐ ํ์ํ์ง๋ง ๋ฌ์คํธ์์๋ ๋ฌธ์ ๊ฐ ๋์ง ์์ต๋๋ค. ๋ง์ฝ RVO๊ฐ ๋ฐ์ํ์ง ์์ผ๋ฉด ๋ฌ์คํธ๋ ํญ์ ๊ฐ๋จํ๊ณ ํจ์จ์ ์ธ
memcpy
๋ณต์ฌ๋ฅผ ์ํํ ๊ฒ์ ๋๋ค.
๋น๋ฆผ
Rustโs borrow checker puts constraints on the ways you can borrow values. For a given value, at any time:
- You can have one or more shared references to the value, or
- You can have exactly one exclusive reference to the value.
fn main() { let mut a: i32 = 10; let b: &i32 = &a; { let c: &mut i32 = &mut a; *c = 20; } println!("a: {a}"); println!("b: {b}"); }
- ์ถฉ๋ํ๋ ์ฐธ์กฐ๊ฐ ๊ฐ์ ์ง์ ์ _์กด์ฌ_ํด์๋ ์ ๋ฉ๋๋ค. ์ฐธ์กฐ๊ฐ ์ญ์ฐธ์กฐ๋๋ ์์น๋ ์ค์ํ์ง ์์ต๋๋ค.
- ์ ์ฝ๋ ์ปดํ์ผ ๋์ง ์์ต๋๋ค. ์๋ํ๋ฉด
c
๋a
๋ฅผ ๊ฐ๋ณ ๋ณ์๋ก ๋น๋ ธ๊ณ , ์ด์ ๋์์b
๋a
๋ฅผ ๋ถ๋ณ ๋ณ์๋ก ๋น๋ ธ๊ธฐ ๋๋ฌธ์ ๋๋ค. b
์ ๋ํprintln!
๊ตฌ๋ถ์c
๊ฐ ์๋ ์ค์ฝํ ์์ผ๋ก ์ด๋ํ๋ฉด ์ปดํ์ผ์ด ๋ฉ๋๋ค.- ์ด๋ ๊ฒ ๋ฐ๊พธ๋ฉด, ์ปดํ์ผ๋ฌ๋
c
๊ฐa
๋ฅผ ๊ฐ๋ณ ๋ณ์๋ก ๋น๋ฆฌ๊ธฐ ์ ์๋งb
๊ฐ ์ฌ์ฉ๋๋ค๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. ๋น๋ฆผ ๊ฒ์ฌ๊ธฐ์ ์ด๋ฌํ ๊ธฐ๋ฅ์ โnon-lexical lifetimeโ ์ด๋ผ๊ณ ํฉ๋๋ค. - The exclusive reference constraint is quite strong. Rust uses it to ensure that data races do not occur. Rust also relies on this constraint to optimize code. For example, a value behind a shared reference can be safely cached in a register for the lifetime of that reference.
- ๋น๋ฆผ ๊ฒ์ฌ๊ธฐ๋ ๊ตฌ์กฐ์ฒด์ ์ฌ๋ฌ ํ๋์ ๋ํ ๋ฐฐํ์ ์ฐธ์กฐ๋ฅผ ๋์์ ๊ฐ์ ธ์ค๋ ๋ฑ ์ฌ๋ฌ ์ผ๋ฐ์ ์ธ ํจํด์ ์์ฉํ๋๋ก ์ค๊ณ๋์์ต๋๋ค. ํ์ง๋ง ์ ๋๋ก โ์ธ์โํ์ง ๋ชปํด โ๋น๋ฆผ ๊ฒ์ฌ๊ธฐ์์ ์ถฉ๋โ์ด ๋ฐ์ํ๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค.
์ํธ์ด์ฉ์ฑ
In some situations, itโs necessary to modify data behind a shared (read-only) reference. For example, a shared data structure might have an internal cache, and wish to update that cache from read-only methods.
The โinterior mutabilityโ pattern allows exclusive (mutable) access behind a shared reference. The standard library provides several ways to do this, all while still ensuring safety, typically by performing a runtime check.
RefCell
use std::cell::RefCell; use std::rc::Rc; #[derive(Debug, Default)] struct Node { value: i64, children: Vec<Rc<RefCell<Node>>>, } impl Node { fn new(value: i64) -> Rc<RefCell<Node>> { Rc::new(RefCell::new(Node { value, ..Node::default() })) } fn sum(&self) -> i64 { self.value + self.children.iter().map(|c| c.borrow().sum()).sum::<i64>() } } fn main() { let root = Node::new(1); root.borrow_mut().children.push(Node::new(5)); let subtree = Node::new(10); subtree.borrow_mut().children.push(Node::new(11)); subtree.borrow_mut().children.push(Node::new(12)); root.borrow_mut().children.push(subtree); println!("๊ทธ๋ํ: {root:#?}"); println!("๊ทธ๋ํ ํฉ๊ณ: {}", root.borrow().sum()); }
Cell
Cell
wraps a value and allows getting or setting the value, even with a shared reference to the Cell
. However, it does not allow any references to the value. Since there are no references, borrowing rules cannot be broken.
The main thing to take away from this slide is that Rust provides safe ways to modify data behind a shared reference. There are a variety of ways to ensure that safety, and RefCell
and Cell
are two of them.
-
RefCell
enforces Rustโs usual borrowing rules (either multiple shared references or a single exclusive reference) with a runtime check. In this case, all borrows are very short and never overlap, so the checks always succeed. -
Rc
only allows shared (read-only) access to its contents, since its purpose is to allow (and count) many references. But we want to modify the value, so we need interior mutability. -
Cell
is a simpler means to ensure safety: it has aset
method that takes&self
. This needs no runtime check, but requires moving values, which can have its own cost. -
Demonstrate that reference loops can be created by adding
root
tosubtree.children
. -
self.value
๋ฅผ ์ฆ๊ฐ์ํค๋ ๋ฉ์๋์ธfn inc(&mut self)
๋ฅผ ์ถ๊ฐํ๊ณ ๊ทธ ๋ฉ์๋๋ฅผ ์์๋ ธ๋์์ ํธ์ถํ์ธ์. ๊ทธ๋ฌ๋ฉดthread 'main' panicked at 'already borrowed: BorrowMutError'
๋ฐํ์ ํจ๋์ด ๋ฐ์ํจ์ ๋ณด์ด์ธ์.
์ฐ์ต๋ฌธ์ : ์๋ฆฌ๋ฒ ์ดํฐ ์ด๋ฒคํธ
๋น์ ์ ๊ฑด๊ฐ ์ํ๋ฅผ ๋ชจ๋ํฐ๋งํ๋ ์์คํ ์ ๊ตฌํํ๋ ์ผ์ ํ๊ณ ์์ต๋๋ค. ๊ทธ ์ผํ์ผ๋ก ๋น์ ์ ์ฌ์ฉ์์ ๊ฑด๊ฐ ์ํ ํต๊ณ๋ฅผ ์ถ์ ํด์ผํฉ๋๋ค.
Youโll start with a stubbed function in an impl
block as well as a User
struct definition. Your goal is to implement the stubbed out method on the User
struct
defined in the impl
block.
Copy the code below to https://play.rust-lang.org/ and fill in the missing method:
// TODO: ๊ตฌํ์ด ์๋ฃ๋๋ฉด ์ด๋ฅผ ์ญ์ ํฉ๋๋ค. #![allow(unused_variables, dead_code)] #![allow(dead_code)] pub struct User { name: String, age: u32, height: f32, visit_count: usize, last_blood_pressure: Option<(u32, u32)>, } pub struct Measurements { height: f32, blood_pressure: (u32, u32), } pub struct HealthReport<'a> { patient_name: &'a str, visit_count: u32, height_change: f32, blood_pressure_change: Option<(i32, i32)>, } impl User { pub fn new(name: String, age: u32, height: f32) -> Self { Self { name, age, height, visit_count: 0, last_blood_pressure: None } } pub fn visit_doctor(&mut self, measurements: Measurements) -> HealthReport { todo!("๋ณ์ ๋ฐฉ๋ฌธ์ ๋ฐ๋ฅธ ์ธก์ ๊ฐ์ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ์ ํต๊ณ๋ฅผ ์ ๋ฐ์ดํธํฉ๋๋ค.") } } fn main() { let bob = User::new(String::from("Bob"), 32, 155.2); println!("์ ๋ {}์ด๊ณ , ๋์ด๋ {}์ธ์ ๋๋ค.", bob.name, bob.age); } #[test] fn test_visit() { let mut bob = User::new(String::from("Bob"), 32, 155.2); assert_eq!(bob.visit_count, 0); let report = bob.visit_doctor(Measurements { height: 156.1, blood_pressure: (120, 80) }); assert_eq!(report.patient_name, "Bob"); assert_eq!(report.visit_count, 1); assert_eq!(report.blood_pressure_change, None); let report = bob.visit_doctor(Measurements { height: 156.1, blood_pressure: (115, 76) }); assert_eq!(report.visit_count, 2); assert_eq!(report.blood_pressure_change, Some((-5, -4))); }
ํด๋ต
#![allow(dead_code)] pub struct User { name: String, age: u32, height: f32, visit_count: usize, last_blood_pressure: Option<(u32, u32)>, } pub struct Measurements { height: f32, blood_pressure: (u32, u32), } pub struct HealthReport<'a> { patient_name: &'a str, visit_count: u32, height_change: f32, blood_pressure_change: Option<(i32, i32)>, } impl User { pub fn new(name: String, age: u32, height: f32) -> Self { Self { name, age, height, visit_count: 0, last_blood_pressure: None } } pub fn visit_doctor(&mut self, measurements: Measurements) -> HealthReport { self.visit_count += 1; let bp = measurements.blood_pressure; let report = HealthReport { patient_name: &self.name, visit_count: self.visit_count as u32, height_change: measurements.height - self.height, blood_pressure_change: match self.last_blood_pressure { Some(lbp) => { Some((bp.0 as i32 - lbp.0 as i32, bp.1 as i32 - lbp.1 as i32)) } None => None, }, }; self.height = measurements.height; self.last_blood_pressure = Some(bp); report } } fn main() { let bob = User::new(String::from("Bob"), 32, 155.2); println!("์ ๋ {}์ด๊ณ , ๋์ด๋ {}์ธ์ ๋๋ค.", bob.name, bob.age); } #[test] fn test_visit() { let mut bob = User::new(String::from("Bob"), 32, 155.2); assert_eq!(bob.visit_count, 0); let report = bob.visit_doctor(Measurements { height: 156.1, blood_pressure: (120, 80) }); assert_eq!(report.patient_name, "Bob"); assert_eq!(report.visit_count, 1); assert_eq!(report.blood_pressure_change, None); let report = bob.visit_doctor(Measurements { height: 156.1, blood_pressure: (115, 76) }); assert_eq!(report.visit_count, 2); assert_eq!(report.blood_pressure_change, Some((-5, -4))); }
์๋ช
This segment should take about 1 hour and 10 minutes. It contains:
Slide | Duration |
---|---|
Slices: &[T] | 10 minutes |
ํ์(dangling) ์ฐธ์กฐ | 10 minutes |
ํจ์ ํธ์ถ์์์ ์๋ช | 10 minutes |
์๋ช | 5 minutes |
์๋ช | 5 minutes |
์ฐ์ต๋ฌธ์ : Protobuf ํ์ฑ | 30 minutes |
์ฌ๋ผ์ด์ค
์ฌ๋ผ์ด์ค๋ ํฐ ์ปฌ๋์ ์ ์ผ๋ถ(ํน์ ์ ์ฒด)๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ทฐ(view)์ ๋๋ค:
fn main() { let mut a: [i32; 6] = [10, 20, 30, 40, 50, 60]; println!("a: {a:?}"); let s: &[i32] = &a[2..4]; println!("s: {s:?}"); }
- ์ฌ๋ผ์ด์ค๋ ๋ค๋ฅธ(์ฌ๋ผ์ด์ค ๋) ํ์ ์ผ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ โ๋น๋ คโ์ต๋๋ค.
- ์ง๋ฌธ:
s
๋ฅผ ์ถ๋ ฅํ๊ธฐ ์ ์a[3]
์ ์์ ํ๋ฉด ๋ฌด์จ ์ผ์ด ์์ด๋ ๊น์?
-
์ฌ๋ผ์ด์ค๋ ์ฐ์
a
๋ฅผ ๋น๋ฆฐ๋ค์, ์์๊ณผ ๋ ์ธ๋ฑ์ค๋ฅผ ๋ธ๋ํท([]
)์์ ์ง์ ํด์ ๋ง๋ญ๋๋ค. -
์ฌ๋ผ์ด์ค๊ฐ ์ธ๋ฑ์ค 0๋ถํฐ ์์ํ๋ค๋ฉด ์์ ์ธ๋ฑ์ค๋ ์๋ต ๊ฐ๋ฅํฉ๋๋ค. ์ฆ
&a[0..a.len()]
์&a[..a.len()]
๋ ๋์ผํฉ๋๋ค. -
๋ง์ง๋ง ์ธ๋ฑ์ค๋ ์๋ต ๊ฐ๋ฅํฉ๋๋ค. ๊ทธ๋์
&a[2..a.len()]
์&a[2..]
๋ ๋์ผํฉ๋๋ค. -
๋ฐ๋ผ์ ์ ์ฒด ๋ฐฐ์ด์ ๋ํ ์ฌ๋ผ์ด์ค๋
&a[..]
๊ฐ ๋ฉ๋๋ค. -
s
๋i32
๋ค๋ก ์ด๋ฃจ์ด์ง ์ฌ๋ผ์ด์ค์ ๋ํ ์ฐธ์กฐ์ ๋๋ค.s
์ ํ์ (&[i32]
)์ ๋ฐฐ์ด์ ํฌ๊ธฐ๊ฐ ๋น ์ ธ์์์ ์ฃผ๋ชฉํ์๊ธฐ ๋ฐ๋๋๋ค. ์ฆ, ์ฌ๋ผ์ด์ค๋ฅผ ์ด์ฉํ๋ฉด ๋ค์ํ ๊ธธ์ด์ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฐ ์ ์์ต๋๋ค. -
Slices always borrow from another object. In this example,
a
has to remain โaliveโ (in scope) for at least as long as our slice. -
The question about modifying
a[3]
can spark an interesting discussion, but the answer is that for memory safety reasons you cannot do it througha
at this point in the execution, but you can read the data from botha
ands
safely. It works before you created the slice, and again after theprintln
, when the slice is no longer used.
ํ์(dangling) ์ฐธ์กฐ
์ด์ Rust์ ๋ ๊ฐ์ง ๋ฌธ์์ด ํ์
์ ์ดํดํ ์ ์์ต๋๋ค. &str
์ ๊ฑฐ์ &[char]
์ ๋น์ทํ์ง๋ง ๋ฐ์ดํฐ๊ฐ ๊ฐ๋ณ ๊ธธ์ด ์ธ์ฝ๋ฉ(UTF-8)์ผ๋ก ์ ์ฅ๋ฉ๋๋ค.
fn main() { let s1: &str = "World"; println!("s1: {s1}"); let mut s2: String = String::from("Hello "); println!("s2: {s2}"); s2.push_str(s1); println!("s2: {s2}"); let s3: &str = &s2[6..]; println!("s3: {s3}"); }
๋ฌ์คํธ ์ฉ์ด:
&str
์ ๋ฌธ์์ด ์ฌ๋ผ์ด์ค์ ๋ํ (๋ถ๋ณ) ์ฐธ์กฐ์ ๋๋ค.String
์ ๋ฌธ์์ด์ ๋ด์ ์ ์๋ ๋ฒํผ์ ๋๋ค.
-
&str
์ ๋ฌธ์์ด ์ฌ๋ผ์ด์ค์ ๋๋ค. ๋ฌธ์์ด ์ฌ๋ผ์ด์ค๋ UTF-8๋ก ์ธ์ฝ๋ฉ๋ ๋ฌธ์์ด ๋ฐ์ดํฐ๋ฅผ ์๋ฏธํฉ๋๋ค. ๋ฌธ์์ด ๋ฆฌํฐ๋ด("Hello"
)์ ํ๋ก๊ทธ๋จ ๋ฐ์ด๋๋ฆฌ์ ์ ์ฅ๋ฉ๋๋ค. -
๋ฌ์คํธ์
String
ํ์ ์ ์ค์ ๋ก๋ ๋ฌธ์์ด์ ์ด๋ฃจ๋ ๋ฐ์ดํธ์ ๋ํ ๋ฐฑํฐ(Vec<u8>
)์ ๋๋ค.Vec<T>
๊ฐT
๋ฅผ ์์ ํ๊ณ ์๋ฏ์ด,String
์ด ๊ฐ๋ฆฌํค๊ณ ์๋ ๋ฌธ์์ด์String
์ ์์ ์ ๋๋ค. -
As with many other types
String::from()
creates a string from a string literal;String::new()
creates a new empty string, to which string data can be added using thepush()
andpush_str()
methods. -
The
format!()
macro is a convenient way to generate an owned string from dynamic values. It accepts the same format specification asprintln!()
. -
You can borrow
&str
slices fromString
via&
and optionally range selection. If you select a byte range that is not aligned to character boundaries, the expression will panic. Thechars
iterator iterates over characters and is preferred over trying to get character boundaries right. -
For C++ programmers: think of
&str
asstd::string_view
from C++, but the one that always points to a valid string in memory. RustString
is a rough equivalent ofstd::string
from C++ (main difference: it can only contain UTF-8 encoded bytes and will never use a small-string optimization). -
Byte strings literals allow you to create a
&[u8]
value directly:fn main() { println!("{:?}", b"abc"); println!("{:?}", &[97, 98, 99]); }
ํจ์ ํธ์ถ์์์ ์๋ช
A reference has a lifetime, which must not โoutliveโ the value it refers to. This is verified by the borrow checker.
The lifetime can be implicit - this is what we have seen so far. Lifetimes can also be explicit: &'a Point
, &'document str
. Lifetimes start with '
and 'a
is a typical default name. Read &'a Point
as โa borrowed Point
which is valid for at least the lifetime a
โ.
Lifetimes are always inferred by the compiler: you cannot assign a lifetime yourself. Explicit lifetime annotations create constraints where there is ambiguity; the compiler verifies that there is a valid solution.
์๋ช ์ ํจ์์ ๊ฐ์ ์ ๋ฌํ๊ณ ํจ์์์ ๊ฐ์ ๋ฐํํ๋ ๊ฒฝ์ฐ๋ฅผ ๊ณ ๋ คํ ๋ ๋ ๋ณต์กํด์ง๋๋ค.
#[derive(Debug)] struct Point(i32, i32); fn left_most(p1: &Point, p2: &Point) -> &Point { if p1.0 < p2.0 { p1 } else { p2 } } fn main() { let p1: Point = Point(10, 10); let p2: Point = Point(20, 20); let p3 = left_most(&p1, &p2); // p3์ ์๋ช ์ ์ด๋ป๊ฒ ๋๋์? println!("p3: {p3:?}"); }
In this example, the compiler does not know what lifetime to infer for p3
. Looking inside the function body shows that it can only safely assume that p3
โs lifetime is the shorter of p1
and p2
. But just like types, Rust requires explicit annotations of lifetimes on function arguments and return values.
'a
๋ฅผ left_most
์ ์ ์ ํ๊ฒ ์ถ๊ฐํฉ๋๋ค.
fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point {
์ฆ, โa
๋ณด๋ค ์ค๋ ์ง์๋๋ p1๊ณผ p2๊ฐ ์์ผ๋ฉด ๋ฐํ ๊ฐ์ ์ต์ํ 'a
๋์ ์ ์ง๋ฉ๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ์๋ช ์ ๋ค์ ์ฌ๋ผ์ด๋์ ์ค๋ช ๋ ๋๋ก ์๋ต๋ ์ ์์ต๋๋ค.
ํจ์ ํธ์ถ์์์ ์๋ช
Lifetimes for function arguments and return values must be fully specified, but Rust allows lifetimes to be elided in most cases with a few simple rules. This is not inference โ it is just a syntactic shorthand.
- ์๋ช ์ฃผ์์ด ์๋ ๊ฐ ์ธ์์ ํ๋์ฉ ์ ๊ณต๋ฉ๋๋ค.
- ์ธ์ ์๋ช ์ด ํ๋๋ง ์๋ ๊ฒฝ์ฐ ์ฃผ์ ์ฒ๋ฆฌ๋์ง ์์ ๋ชจ๋ ๋ฐํ ๊ฐ์ ์ ๊ณต๋ฉ๋๋ค.
- ์ธ์ ์๋ช ์ด ์ฌ๋ฌ ๊ฐ ์์ง๋ง ์ฒซ ๋ฒ์งธ๊ฐ โselfโ์ ์๋ช ์ด๋ฉด ํด๋น ์ ์ฒด ๊ธฐ๊ฐ์ ์ฃผ์ ์ฒ๋ฆฌ๋์ง ์์ ๋ชจ๋ ๋ฐํ ๊ฐ์ ์ ๊ณต๋ฉ๋๋ค.
#[derive(Debug)] struct Point(i32, i32); fn cab_distance(p1: &Point, p2: &Point) -> i32 { (p1.0 - p2.0).abs() + (p1.1 - p2.1).abs() } fn nearest<'a>(points: &'a [Point], query: &Point) -> Option<&'a Point> { let mut nearest = None; for p in points { if let Some((_, nearest_dist)) = nearest { let dist = cab_distance(p, query); if dist < nearest_dist { nearest = Some((p, dist)); } } else { nearest = Some((p, cab_distance(p, query))); }; } nearest.map(|(p, _)| p) } fn main() { println!( "{:?}", nearest( &[Point(1, 0), Point(1, 0), Point(-1, 0), Point(0, -1),], &Point(0, 2) ) ); }
์ด ์์์ cab_distance
๋ ๊ฐ๋จํ ์๋ต๋ฉ๋๋ค.
nearest
ํจ์๋ ์ธ์์ ์ฌ๋ฌ ์ฐธ์กฐ๊ฐ ํฌํจ๋์ด ๋ช
์์ ์ฃผ์์ด ํ์ํ ํจ์์ ๋ ๋ค๋ฅธ ์๋ฅผ ์ ๊ณตํฉ๋๋ค.
๋ฐํ๋ ์๋ช ์ ๊ดํด โ๊ฑฐ์ง๋งโํ๋๋ก ์๋ช ์ ์กฐ์ ํด ๋ณด์ธ์.
fn nearest<'a, 'q>(points: &'a [Point], query: &'q Point) -> Option<&'q Point> {
This wonโt compile, demonstrating that the annotations are checked for validity by the compiler. Note that this is not the case for raw pointers (unsafe), and this is a common source of errors with unsafe Rust.
Students may ask when to use lifetimes. Rust borrows always have lifetimes. Most of the time, elision and type inference mean these donโt need to be written out. In more complicated cases, lifetime annotations can help resolve ambiguity. Often, especially when prototyping, itโs easier to just work with owned data by cloning values where necessary.
๊ตฌ์กฐ์ฒด์์์ ์๋ช
์ด๋ค ํ์ ์ด ๋น๋ ค์จ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ์๋ค๋ฉด, ๋ฐ๋์ ์๋ช ์ ํ์ํด์ผ ํฉ๋๋ค:
#[derive(Debug)] struct Highlight<'doc>(&'doc str); fn erase(text: String) { println!("์๋ {text}!"); } fn main() { let text = String::from("The quick brown fox jumps over the lazy dog."); let fox = Highlight(&text[4..19]); let dog = Highlight(&text[35..43]); // erase(text); println!("{fox:?}"); println!("{dog:?}"); }
- ์์ ์์ ์์
Highlight
์ ์ด๋ ธํ ์ด์ (<'doc>
)์ ์ ์ด๋Highlight
์ธ์คํด์ค๊ฐ ์ด์์๋ ๋์์๋ ๊ทธ ๋ด๋ถ์&str
๊ฐ ๊ฐ๋ฆฌํค๋ ๋ฐ์ดํฐ ์ญ์ ์ด์์์ด์ผ ํ๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. - ๋ง์ฝ
text
๊ฐfox
(ํน์dog
)์ ์๋ช ์ด ๋คํ๊ธฐ ์ ์erase
ํจ์ ํธ์ถ ๋ฑ์ผ๋ก ์ฌ๋ผ์ง๊ฒ ๋๋ค๋ฉด ๋น๋ฆผ ๊ฒ์ฌ๊ธฐ๊ฐ ์๋ฌ๋ฅผ ๋ฐ์ํฉ๋๋ค. - ๋น๋ฆฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ ํ์ ์ ์ฌ์ฉ์๋ก ํ์ฌ๊ธ ์๋ณธ ๋ฐ์ดํฐ๋ฅผ ์ ์งํ๋๋ก ๊ฐ์ ํฉ๋๋ค. ์ด๋ฐ ํ์ ์ ๊ฒฝ๋ ๋ทฐ(lightweight view)๋ฅผ ๋ง๋๋๋ฐ ์ ์ฉํ์ง๋ง, ์ด ์ ์ฝ ์กฐ๊ฑด ๋๋ฌธ์ ์ด๋ฐ ํ์ ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ฝ์ง๋ง์ ์์ต๋๋ค.
- ๋ฐ๋ผ์, ๊ฐ๋ฅํ๋ค๋ฉด, ๊ตฌ์กฐ์ฒด๊ฐ ์์ ์ ๋ฐ์ดํฐ๋ฅผ ์ง์ ์์ ํ๋๋ก ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
- ํ ๊ตฌ์กฐ์ฒด์์ ์ฌ๋ฌ ์ฐธ์กฐ๊ฐ ์์ผ๋ฉด์, ์ด ์ฐธ์กฐ๋ค์ ์๋ช ์ด ์๋ก ๋ค๋ฅด๊ฒ ์ง์ ๋๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค. ์ด๋ ์ฐธ์กฐ์ ๊ทธ ๊ตฌ์กฐ์ฒด ๊ฐ์ ๊ด๊ณ ๋ฟ๋ง์ด ์๋๋ผ, ๊ทธ ์ฐธ์กฐ๋ค ์ฌ์ด์ ์๋ช ๊ด๊ณ๋ฅผ ์ค๋ช ํด์ผ ํ ๊ฒฝ์ฐ์ ํ์ํฉ๋๋ค. ๋งค์ฐ ๊ณ ๊ธ ๊ธฐ์ ์ ๋๋ค.
์ฐ์ต๋ฌธ์ : Protobuf ํ์ฑ
์ด ์ฐ์ต์์๋ protobuf ๋ฐ์ด๋๋ฆฌ ์ธ์ฝ๋ฉ์ฉ ํ์๋ฅผ ๋น๋ํฉ๋๋ค. ์๊ฐ๋ณด๋ค ๊ฐ๋จํฉ๋๋ค. ์ด๋ ๋ฐ์ดํฐ ์ฌ๋ผ์ด์ค๋ฅผ ์ ๋ฌํ๋ ์ผ๋ฐ์ ์ธ ํ์ฑ ํจํด์ ๋ณด์ฌ์ค๋๋ค. ๊ธฐ๋ณธ ๋ฐ์ดํฐ ์์ฒด๋ ๋ณต์ฌ๋์ง ์์ต๋๋ค.
protobuf ๋ฉ์์ง๋ฅผ ์์ ํ ํ์ฑํ๋ ค๋ฉด ํ๋ ๋ฒํธ๋ก ์์ธ์ด ์์ฑ๋ ํ๋์ ํ์
์ ์์์ผ ํฉ๋๋ค. ์ด๋ ์ผ๋ฐ์ ์ผ๋ก proto
ํ์ผ์ ์ ๊ณต๋ฉ๋๋ค. ์ด ์ฐ์ต์์๋ ์ด๋ฌํ ์ ๋ณด๋ฅผ ๊ฐ ํ๋์ ๋ํด ํธ์ถ๋๋ ํจ์์ match
๋ฌธ์ผ๋ก ์ธ์ฝ๋ฉํฉ๋๋ค.
๋ค์ proto๋ฅผ ์ฌ์ฉํฉ๋๋ค.
message PhoneNumber {
optional string number = 1;
optional string type = 2;
}
message Person {
optional string name = 1;
optional int32 id = 2;
repeated PhoneNumber phones = 3;
}
proto ๋ฉ์์ง๋ ์ผ๋ จ์ ํ๋๋ก ์ฐจ๋ก๋ก ์ธ์ฝ๋ฉ๋ฉ๋๋ค. ๊ฐ๊ฐ์ ๊ฐ์ด ๋ค์ ์ค๋ โํ๊ทธโ๋ก ๊ตฌํ๋ฉ๋๋ค. ํ๊ทธ์๋ ํ๋ ๋ฒํธ(์: Person
๋ฉ์์ง์ id
ํ๋์ ๋ํ 2
) ๋ฐ ๋ฐ์ดํธ ์คํธ๋ฆผ์์ ํ์ด๋ก๋๋ฅผ ๊ฒฐ์ ํ๋ ๋ฐฉ๋ฒ์ ์ ์ํ๋ ์์ด์ด ํ์
์ด ํฌํจ๋ฉ๋๋ค.
ํ๊ทธ๋ฅผ ํฌํจํ ์ ์๋ VARINT๋ผ๋ ๊ฐ๋ณ ๊ธธ์ด ์ธ์ฝ๋ฉ์ผ๋ก ํ์๋ฉ๋๋ค. ๋คํํ parse_varint
๋ ์๋์ ์ ์๋์ด ์์ต๋๋ค. ๋ํ ์ ๊ณต๋ ์ฝ๋๋ ์ฝ๋ฐฑ์ ์ ์ํ์ฌ Person
๋ฐ PhoneNumber
ํ๋๋ฅผ ์ฒ๋ฆฌํ๊ณ ๋ฉ์์ง๋ฅผ ์ด๋ฌํ ์ฝ๋ฐฑ์ ๋ํ ์ผ๋ จ์ ํธ์ถ๋ก ํ์ฑํฉ๋๋ค.
์ด์ parse_field
ํจ์๋ฅผ ๊ตฌํํ๊ณ Person
๊ณผ PhoneNumber
๊ตฌ์กฐ์ฒด์ ๋ํด ProtoMessage
ํธ๋ ์๋ง ๊ตฌํํ๋ฉด ๋ฉ๋๋ค.
use std::convert::TryFrom; use thiserror::Error; #[derive(Debug, Error)] enum Error { #[error("์๋ชป๋ varint")] InvalidVarint, #[error("์๋ชป๋ wire-type")] InvalidWireType, #[error("์์์น ๋ชปํ EOF")] UnexpectedEOF, #[error("์๋ชป๋ ๊ธธ์ด")] InvalidSize(#[from] std::num::TryFromIntError), #[error("์์์น ๋ชปํ wire-type")] UnexpectedWireType, #[error("์๋ชป๋ ๋ฌธ์์ด(UTF-8 ์๋)")] InvalidString, } /// ์์ด์ด์ ํ์๋ ์์ด์ด ํ์ ์ ๋๋ค. enum WireType { /// Varint WireType์ ๊ฐ์ด ๋จ์ผ VARINT์์ ๋ํ๋ ๋๋ค. Varint, //I64, -- not needed for this exercise /// The Len WireType indicates that the value is a length represented as a /// VARINT followed by exactly that number of bytes. Len, /// The I32 WireType indicates that the value is precisely 4 bytes in /// little-endian order containing a 32-bit signed integer. I32, } #[derive(Debug)] /// ์์ด์ด ํ์ ์ ๋ฐ๋ผ ํ์ ์ด ์ง์ ๋ ํ๋ ๊ฐ์ ๋๋ค. enum FieldValue<'a> { Varint(u64), //I64(i64), -- ์ด ์ฐ์ต์์๋ ํ์ํ์ง ์์ต๋๋ค. Len(&'a [u8]), I32(i32), } #[derive(Debug)] /// ํ๋ ๋ฒํธ ๋ฐ ๊ฐ์ ํฌํจํ๋ ํ๋์ ๋๋ค. struct Field<'a> { field_num: u64, value: FieldValue<'a>, } trait ProtoMessage<'a>: Default + 'a { fn add_field(&mut self, field: Field<'a>) -> Result<(), Error>; } impl TryFrom<u64> for WireType { type Error = Error; fn try_from(value: u64) -> Result<WireType, Error> { Ok(match value { 0 => WireType::Varint, //1 => WireType::I64, -- ์ด ์ฐ์ต์์๋ ํ์ํ์ง ์์ต๋๋ค. 2 => WireType::Len, 5 => WireType::I32, _ => return Err(Error::InvalidWireType), }) } } impl<'a> FieldValue<'a> { fn as_string(&self) -> Result<&'a str, Error> { let FieldValue::Len(data) = self else { return Err(Error::UnexpectedWireType); }; std::str::from_utf8(data).map_err(|_| Error::InvalidString) } fn as_bytes(&self) -> Result<&'a [u8], Error> { let FieldValue::Len(data) = self else { return Err(Error::UnexpectedWireType); }; Ok(data) } fn as_u64(&self) -> Result<u64, Error> { let FieldValue::Varint(value) = self else { return Err(Error::UnexpectedWireType); }; Ok(*value) } } /// VARINT๋ฅผ ํ์ฑํ์ฌ ํ์ฑ๋ ๊ฐ๊ณผ ๋๋จธ์ง ๋ฐ์ดํธ๋ฅผ ๋ฐํํฉ๋๋ค. fn parse_varint(data: &[u8]) -> Result<(u64, &[u8]), Error> { for i in 0..7 { let Some(b) = data.get(i) else { return Err(Error::InvalidVarint); }; if b & 0x80 == 0 { // ์ด๋ VARINT์ ๋ง์ง๋ง ๋ฐ์ดํธ์ด๋ฏ๋ก // u64๋ก ๋ณํํ์ฌ ๋ฐํํฉ๋๋ค. let mut value = 0u64; for b in data[..=i].iter().rev() { value = (value << 7) | (b & 0x7f) as u64; } return Ok((value, &data[i + 1..])); } } // 7๋ฐ์ดํธ๋ฅผ ์ด๊ณผํ๋ฉด ์ ํจํ์ง ์์ต๋๋ค. Err(Error::InvalidVarint) } /// ํ๊ทธ๋ฅผ ํ๋ ๋ฒํธ์ WireType์ผ๋ก ๋ณํํฉ๋๋ค. fn unpack_tag(tag: u64) -> Result<(u64, WireType), Error> { let field_num = tag >> 3; let wire_type = WireType::try_from(tag & 0x7)?; Ok((field_num, wire_type)) } /// ํ๋๋ฅผ ํ์ฑํ์ฌ ๋๋จธ์ง ๋ฐ์ดํธ๋ฅผ ๋ฐํํฉ๋๋ค. fn parse_field(data: &[u8]) -> Result<(Field, &[u8]), Error> { let (tag, remainder) = parse_varint(data)?; let (field_num, wire_type) = unpack_tag(tag)?; let (fieldvalue, remainder) = match wire_type { _ => todo!("Based on the wire type, build a Field, consuming as many bytes as necessary.") }; todo!("Return the field, and any un-consumed bytes.") } /// ์ฃผ์ด์ง ๋ฐ์ดํฐ์์ ๋ฉ์์ง๋ฅผ ํ์ฑํ์ฌ ๋ฉ์์ง์ ๊ฐ ํ๋์ ๋ํด `T::add_field`๋ฅผ ํธ์ถํฉ๋๋ค. /// /// ์ ์ฒด ์ ๋ ฅ์ด ์ฌ์ฉ๋ฉ๋๋ค. fn parse_message<'a, T: ProtoMessage<'a>>(mut data: &'a [u8]) -> Result<T, Error> { let mut result = T::default(); while !data.is_empty() { let parsed = parse_field(data)?; result.add_field(parsed.0)?; data = parsed.1; } Ok(result) } #[derive(Debug, Default)] struct PhoneNumber<'a> { number: &'a str, type_: &'a str, } #[derive(Debug, Default)] struct Person<'a> { name: &'a str, id: u64, phone: Vec<PhoneNumber<'a>>, } // TODO: Implement ProtoMessage for Person and PhoneNumber. fn main() { let person: Person = parse_message(&[ 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a, 0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35, 0x2d, 0x31, 0x32, 0x31, 0x32, 0x12, 0x04, 0x68, 0x6f, 0x6d, 0x65, 0x1a, 0x18, 0x0a, 0x0e, 0x2b, 0x31, 0x38, 0x30, 0x30, 0x2d, 0x38, 0x36, 0x37, 0x2d, 0x35, 0x33, 0x30, 0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, ]) .unwrap(); println!("{:#?}", person); }
ํด๋ต
use std::convert::TryFrom; use thiserror::Error; #[derive(Debug, Error)] enum Error { #[error("์๋ชป๋ varint")] InvalidVarint, #[error("์๋ชป๋ wire-type")] InvalidWireType, #[error("์์์น ๋ชปํ EOF")] UnexpectedEOF, #[error("์๋ชป๋ ๊ธธ์ด")] InvalidSize(#[from] std::num::TryFromIntError), #[error("์์์น ๋ชปํ wire-type")] UnexpectedWireType, #[error("์๋ชป๋ ๋ฌธ์์ด(UTF-8 ์๋)")] InvalidString, } /// ์์ด์ด์ ํ์๋ ์์ด์ด ํ์ ์ ๋๋ค. enum WireType { /// Varint WireType์ ๊ฐ์ด ๋จ์ผ VARINT์์ ๋ํ๋ ๋๋ค. Varint, //I64, -- not needed for this exercise /// The Len WireType indicates that the value is a length represented as a /// VARINT followed by exactly that number of bytes. Len, /// The I32 WireType indicates that the value is precisely 4 bytes in /// little-endian order containing a 32-bit signed integer. I32, } #[derive(Debug)] /// ์์ด์ด ํ์ ์ ๋ฐ๋ผ ํ์ ์ด ์ง์ ๋ ํ๋ ๊ฐ์ ๋๋ค. enum FieldValue<'a> { Varint(u64), //I64(i64), -- ์ด ์ฐ์ต์์๋ ํ์ํ์ง ์์ต๋๋ค. Len(&'a [u8]), I32(i32), } #[derive(Debug)] /// ํ๋ ๋ฒํธ ๋ฐ ๊ฐ์ ํฌํจํ๋ ํ๋์ ๋๋ค. struct Field<'a> { field_num: u64, value: FieldValue<'a>, } trait ProtoMessage<'a>: Default + 'a { fn add_field(&mut self, field: Field<'a>) -> Result<(), Error>; } impl TryFrom<u64> for WireType { type Error = Error; fn try_from(value: u64) -> Result<WireType, Error> { Ok(match value { 0 => WireType::Varint, //1 => WireType::I64, -- ์ด ์ฐ์ต์์๋ ํ์ํ์ง ์์ต๋๋ค. 2 => WireType::Len, 5 => WireType::I32, _ => return Err(Error::InvalidWireType), }) } } impl<'a> FieldValue<'a> { fn as_string(&self) -> Result<&'a str, Error> { let FieldValue::Len(data) = self else { return Err(Error::UnexpectedWireType); }; std::str::from_utf8(data).map_err(|_| Error::InvalidString) } fn as_bytes(&self) -> Result<&'a [u8], Error> { let FieldValue::Len(data) = self else { return Err(Error::UnexpectedWireType); }; Ok(data) } fn as_u64(&self) -> Result<u64, Error> { let FieldValue::Varint(value) = self else { return Err(Error::UnexpectedWireType); }; Ok(*value) } } /// VARINT๋ฅผ ํ์ฑํ์ฌ ํ์ฑ๋ ๊ฐ๊ณผ ๋๋จธ์ง ๋ฐ์ดํธ๋ฅผ ๋ฐํํฉ๋๋ค. fn parse_varint(data: &[u8]) -> Result<(u64, &[u8]), Error> { for i in 0..7 { let Some(b) = data.get(i) else { return Err(Error::InvalidVarint); }; if b & 0x80 == 0 { // ์ด๋ VARINT์ ๋ง์ง๋ง ๋ฐ์ดํธ์ด๋ฏ๋ก // u64๋ก ๋ณํํ์ฌ ๋ฐํํฉ๋๋ค. let mut value = 0u64; for b in data[..=i].iter().rev() { value = (value << 7) | (b & 0x7f) as u64; } return Ok((value, &data[i + 1..])); } } // 7๋ฐ์ดํธ๋ฅผ ์ด๊ณผํ๋ฉด ์ ํจํ์ง ์์ต๋๋ค. Err(Error::InvalidVarint) } /// ํ๊ทธ๋ฅผ ํ๋ ๋ฒํธ์ WireType์ผ๋ก ๋ณํํฉ๋๋ค. fn unpack_tag(tag: u64) -> Result<(u64, WireType), Error> { let field_num = tag >> 3; let wire_type = WireType::try_from(tag & 0x7)?; Ok((field_num, wire_type)) } /// ํ๋๋ฅผ ํ์ฑํ์ฌ ๋๋จธ์ง ๋ฐ์ดํธ๋ฅผ ๋ฐํํฉ๋๋ค. fn parse_field(data: &[u8]) -> Result<(Field, &[u8]), Error> { let (tag, remainder) = parse_varint(data)?; let (field_num, wire_type) = unpack_tag(tag)?; let (fieldvalue, remainder) = match wire_type { WireType::Varint => { let (value, remainder) = parse_varint(remainder)?; (FieldValue::Varint(value), remainder) } WireType::Len => { let (len, remainder) = parse_varint(remainder)?; let len: usize = len.try_into()?; if remainder.len() < len { return Err(Error::UnexpectedEOF); } let (value, remainder) = remainder.split_at(len); (FieldValue::Len(value), remainder) } WireType::I32 => { if remainder.len() < 4 { return Err(Error::UnexpectedEOF); } let (value, remainder) = remainder.split_at(4); // `value`์ ๊ธธ์ด๊ฐ 4๋ฐ์ดํธ์ด๋ฏ๋ก ์ค๋ฅ๋ฅผ ๋ํ ํด์ ํฉ๋๋ค. let value = i32::from_le_bytes(value.try_into().unwrap()); (FieldValue::I32(value), remainder) } }; Ok((Field { field_num, value: fieldvalue }, remainder)) } /// ์ฃผ์ด์ง ๋ฐ์ดํฐ์์ ๋ฉ์์ง๋ฅผ ํ์ฑํ์ฌ ๋ฉ์์ง์ ๊ฐ ํ๋์ ๋ํด `T::add_field`๋ฅผ ํธ์ถํฉ๋๋ค. /// /// ์ ์ฒด ์ ๋ ฅ์ด ์ฌ์ฉ๋ฉ๋๋ค. fn parse_message<'a, T: ProtoMessage<'a>>(mut data: &'a [u8]) -> Result<T, Error> { let mut result = T::default(); while !data.is_empty() { let parsed = parse_field(data)?; result.add_field(parsed.0)?; data = parsed.1; } Ok(result) } #[derive(Debug, Default)] struct PhoneNumber<'a> { number: &'a str, type_: &'a str, } #[derive(Debug, Default)] struct Person<'a> { name: &'a str, id: u64, phone: Vec<PhoneNumber<'a>>, } impl<'a> ProtoMessage<'a> for Person<'a> { fn add_field(&mut self, field: Field<'a>) -> Result<(), Error> { match field.field_num { 1 => self.name = field.value.as_string()?, 2 => self.id = field.value.as_u64()?, 3 => self.phone.push(parse_message(field.value.as_bytes()?)?), _ => {} // ๋๋จธ์ง๋ ๋ชจ๋ ๊ฑด๋๋๋๋ค. } Ok(()) } } impl<'a> ProtoMessage<'a> for PhoneNumber<'a> { fn add_field(&mut self, field: Field<'a>) -> Result<(), Error> { match field.field_num { 1 => self.number = field.value.as_string()?, 2 => self.type_ = field.value.as_string()?, _ => {} // ๋๋จธ์ง๋ ๋ชจ๋ ๊ฑด๋๋๋๋ค. } Ok(()) } } fn main() { let person: Person = parse_message(&[ 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a, 0x16, 0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35, 0x2d, 0x31, 0x32, 0x31, 0x32, 0x12, 0x04, 0x68, 0x6f, 0x6d, 0x65, 0x1a, 0x18, 0x0a, 0x0e, 0x2b, 0x31, 0x38, 0x30, 0x30, 0x2d, 0x38, 0x36, 0x37, 0x2d, 0x35, 0x33, 0x30, 0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, ]) .unwrap(); println!("{:#?}", person); } #[cfg(test)] mod test { use super::*; #[test] fn as_string() { assert!(FieldValue::Varint(10).as_string().is_err()); assert!(FieldValue::I32(10).as_string().is_err()); assert_eq!(FieldValue::Len(b"hello").as_string().unwrap(), "hello"); } #[test] fn as_bytes() { assert!(FieldValue::Varint(10).as_bytes().is_err()); assert!(FieldValue::I32(10).as_bytes().is_err()); assert_eq!(FieldValue::Len(b"hello").as_bytes().unwrap(), b"hello"); } #[test] fn as_u64() { assert_eq!(FieldValue::Varint(10).as_u64().unwrap(), 10u64); assert!(FieldValue::I32(10).as_u64().is_err()); assert!(FieldValue::Len(b"hello").as_u64().is_err()); } }
4์ผ์ฐจ ๊ฐ์
Today we will cover topics relating to building large-scale software in Rust:
- ๋ฐ๋ณต์:
Iterator
ํธ๋ ์ ์ฌ์ธต ๋ถ์ - ๋ชจ๋๊ณผ ๊ฐ์์ฑ
- Testing.
- ์ค๋ฅ์ฒ๋ฆฌ(์๋ฌ ํธ๋ค๋ง): ํจ๋,
Result
,?
์ฐ์ฐ์. - ์์ ํ์ง ์์ Rust: ์์ ํ Rust๋ก ์ํ๋ ๊ฒ์ ํํํ ์ ์์ ๋์๋ง ์ฌ์ฉํ์ธ์.
์ผ์ ์์ฝ
Including 10 minute breaks, this session should take about 2 hours and 40 minutes. It contains:
Segment | Duration |
---|---|
๊ฐ์ | 3 minutes |
Iterators | 45 minutes |
๋ชจ๋ | 40 minutes |
ํ ์คํธ | 45 minutes |
Iterators
This segment should take about 45 minutes. It contains:
Slide | Duration |
---|---|
Iterator | 5 minutes |
IntoIterator | 5 minutes |
FromIterator | 5 minutes |
์ฐ์ต๋ฌธ์ : ๋ฐ๋ณต์ ๋ฉ์๋ ์ฒด์ด๋ | 30 minutes |
Iterator
์ปฌ๋์
์ ์๋ ๊ฐ๋ค์ ์ ๊ทผํ๊ธฐ ์ํด์๋ Iterator
ํธ๋ ์์ ์ฌ์ฉํฉ๋๋ค. ์ด ํธ๋ ์์ next
๋ฉ์๋๋ฅผ ๋น๋กฏํ ๋ง์ ๋ฉ์๋๋ฅผ ์ ๊ณตํฉ๋๋ค. ๋ง์ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์
์ด Iterator
๋ฅผ ๊ตฌํํ๊ณ ์์ผ๋ฉฐ, ์ฌ๋ฌ๋ถ๋ ์ฌ๋ฌ๋ถ์ ํ์
์ด ์ด ํธ๋ ์์ ์ง์ ๊ตฌํํ๋๋ก ํ ์ ์์ต๋๋ค.
struct Fibonacci { curr: u32, next: u32, } impl Iterator for Fibonacci { type Item = u32; fn next(&mut self) -> Option<Self::Item> { let new_next = self.curr + self.next; self.curr = self.next; self.next = new_next; Some(self.curr) } } fn main() { let fib = Fibonacci { curr: 0, next: 1 }; for (i, n) in fib.enumerate().take(5) { println!("fib({i}): {n}"); } }
-
The
Iterator
trait implements many common functional programming operations over collections (e.g.map
,filter
,reduce
, etc). This is the trait where you can find all the documentation about them. In Rust these functions should produce the code as efficient as equivalent imperative implementations. -
IntoIterator
๋ ๋ฃจํ๋ฅผ ์๋ํ๊ฒ ๋ง๋๋ ํธ๋ ์์ ๋๋ค. ์ด๋Vec<T>
์ ๊ฐ์ ์ปฌ๋ ์ ํ์ ๊ณผ&Vec<T>
๋ฐ&[T]
์ ๊ฐ์ ์ด์ ๋ํ ์ฐธ์กฐ์ ์ํด ๊ตฌํ๋ฉ๋๋ค. ๋ฒ์๋ ์ด๋ฅผ ๊ตฌํํฉ๋๋ค. ์ด๋ฐ ์ด์ ๋กfor i in some_vec { .. }
๋ฅผ ์ฌ์ฉํ์ฌ ๋ฒกํฐ๋ฅผ ๋ฐ๋ณตํ ์ ์์ง๋งsome_vec.next()
๋ ์กด์ฌํ์ง ์์ต๋๋ค.
IntoIterator
The Iterator
trait tells you how to iterate once you have created an iterator. The related trait IntoIterator
defines how to create an iterator for a type. It is used automatically by the for
loop.
struct Grid { x_coords: Vec<u32>, y_coords: Vec<u32>, } impl IntoIterator for Grid { type Item = (u32, u32); type IntoIter = GridIter; fn into_iter(self) -> GridIter { GridIter { grid: self, i: 0, j: 0 } } } struct GridIter { grid: Grid, i: usize, j: usize, } impl Iterator for GridIter { type Item = (u32, u32); fn next(&mut self) -> Option<(u32, u32)> { if self.i >= self.grid.x_coords.len() { self.i = 0; self.j += 1; if self.j >= self.grid.y_coords.len() { return None; } } let res = Some((self.grid.x_coords[self.i], self.grid.y_coords[self.j])); self.i += 1; res } } fn main() { let grid = Grid { x_coords: vec![3, 5, 7, 9], y_coords: vec![10, 20, 30, 40] }; for (x, y) in grid { println!("point = {x}, {y}"); } }
Click through to the docs for IntoIterator
. Every implementation of IntoIterator
must declare two types:
Item
: the type to iterate over, such asi8
,IntoIter
:into_iter
๋ฉ์๋์์ ๋ฐํ๋๋Iterator
ํ์ .
IntoIter
์๋ Item
์ด ์ฐ๊ฒฐ๋์ด ์์์ ์ฃผ๋ชฉํ์ธ์. IntoIter
๋ฐ๋ณต์๋ Item
ํ์
์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๋ฆฌ์ผ์ผ ํฉ๋๋ค. ์ฆ, ๋ฐ๋ณต์๋ Option<Item>
์ ๋ฆฌํดํฉ๋๋ค
์ด ์๋ x ๋ฐ y ์ขํ์ ๋ชจ๋ ์กฐํฉ์ ์ํํฉ๋๋ค.
main
์์ ๊ทธ๋ฆฌ๋๋ฅผ ๋ ๋ฒ ๋ฐ๋ณตํด ๋ณด์ธ์. ์ ์คํจํ๋์? IntoIterator::into_iter
๋ โselfโ์ ์์ ๊ถ์ ๊ฐ์ ธ์ต๋๋ค.
&Grid
์ IntoIterator
๋ฅผ ๊ตฌํํ๊ณ GridIter
์ Grid
์ฐธ์กฐ๋ฅผ ์ ์ฅํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ธ์.
ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์
์์ ๋์ผํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. for e in some_vector
๋ some_vector
์ ์์ ๊ถ์ ๊ฐ์ ธ์ ํด๋น ๋ฒกํฐ์์ ์์ ํ ์์๋ฅผ ๋ฐ๋ณตํฉ๋๋ค. some_vector
์ ์์์ ๋ํ ์ฐธ์กฐ๋ฅผ ๋ฐ๋ณตํ๋ ค๋ฉด ๋์ for e in &some_vector
๋ฅผ ์ฌ์ฉํ์ธ์.
FromIterator
์ด๋ค ์ปฌ๋ ์
์ด FromIterator
๋ฅผ ๊ตฌํํ๊ณ ์๋ค๋ฉด Iterator
๋ก๋ถํฐ ๊ทธ ์ปฌ๋ ์
์ ๋ง๋ค ์ ์์ต๋๋ค.
fn main() { let primes = vec![2, 3, 5, 7]; let prime_squares = primes.into_iter().map(|p| p * p).collect::<Vec<_>>(); println!("prime_squares: {prime_squares:?}"); }
Iterator
implements
fn collect<B>(self) -> B
where
B: FromIterator<Self::Item>,
Self: Sized
์ด ๋ฉ์๋์ B
๋ฅผ ์ง์ ํ๋ ๋ฐฉ๋ฒ์๋ ๋ ๊ฐ์ง๊ฐ ์์ต๋๋ค.
- With the โturbofishโ:
some_iterator.collect::<COLLECTION_TYPE>()
, as shown. The_
shorthand used here lets Rust infer the type of theVec
elements. - ํ์
์ถ๋ก ์ฌ์ฉ:
let prime_squares: Vec<_> = some_iterator.collect()
๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด ๋ฐฉ๋ฒ์ผ๋ก ์์ ๋ฅผ ๋ค์ ์์ฑํด ๋ณด์ธ์.
There are basic implementations of FromIterator
for Vec
, HashMap
, etc. There are also more specialized implementations which let you do cool things like convert an Iterator<Item = Result<V, E>>
into a Result<Vec<V>, E>
.
์ฐ์ต๋ฌธ์ : ๋ฐ๋ณต์ ๋ฉ์๋ ์ฒด์ด๋
In this exercise, you will need to find and use some of the provided methods in the Iterator
trait to implement a complex calculation.
Copy the following code to https://play.rust-lang.org/ and make the tests pass. Use an iterator expression and collect
the result to construct the return value.
#![allow(unused)] fn main() { /// Calculate the differences between elements of `values` offset by `offset`, /// wrapping around from the end of `values` to the beginning. /// /// Element `n` of the result is `values[(n+offset)%len] - values[n]`. fn offset_differences<N>(offset: usize, values: Vec<N>) -> Vec<N> where N: Copy + std::ops::Sub<Output = N>, { unimplemented!() } #[test] fn test_offset_one() { assert_eq!(offset_differences(1, vec![1, 3, 5, 7]), vec![2, 2, 2, -6]); assert_eq!(offset_differences(1, vec![1, 3, 5]), vec![2, 2, -4]); assert_eq!(offset_differences(1, vec![1, 3]), vec![2, -2]); } #[test] fn test_larger_offsets() { assert_eq!(offset_differences(2, vec![1, 3, 5, 7]), vec![4, 4, -4, -4]); assert_eq!(offset_differences(3, vec![1, 3, 5, 7]), vec![6, -2, -2, -2]); assert_eq!(offset_differences(4, vec![1, 3, 5, 7]), vec![0, 0, 0, 0]); assert_eq!(offset_differences(5, vec![1, 3, 5, 7]), vec![2, 2, 2, -6]); } #[test] fn test_custom_type() { assert_eq!( offset_differences(1, vec![1.0, 11.0, 5.0, 0.0]), vec![10.0, -6.0, -5.0, 1.0] ); } #[test] fn test_degenerate_cases() { assert_eq!(offset_differences(1, vec![0]), vec![0]); assert_eq!(offset_differences(1, vec![1]), vec![0]); let empty: Vec<i32> = vec![]; assert_eq!(offset_differences(1, empty), vec![]); } }
ํด๋ต
/// Calculate the differences between elements of `values` offset by `offset`, /// wrapping around from the end of `values` to the beginning. /// /// Element `n` of the result is `values[(n+offset)%len] - values[n]`. fn offset_differences<N>(offset: usize, values: Vec<N>) -> Vec<N> where N: Copy + std::ops::Sub<Output = N>, { let a = (&values).into_iter(); let b = (&values).into_iter().cycle().skip(offset); a.zip(b).map(|(a, b)| *b - *a).collect() } #[test] fn test_offset_one() { assert_eq!(offset_differences(1, vec![1, 3, 5, 7]), vec![2, 2, 2, -6]); assert_eq!(offset_differences(1, vec![1, 3, 5]), vec![2, 2, -4]); assert_eq!(offset_differences(1, vec![1, 3]), vec![2, -2]); } #[test] fn test_larger_offsets() { assert_eq!(offset_differences(2, vec![1, 3, 5, 7]), vec![4, 4, -4, -4]); assert_eq!(offset_differences(3, vec![1, 3, 5, 7]), vec![6, -2, -2, -2]); assert_eq!(offset_differences(4, vec![1, 3, 5, 7]), vec![0, 0, 0, 0]); assert_eq!(offset_differences(5, vec![1, 3, 5, 7]), vec![2, 2, 2, -6]); } #[test] fn test_custom_type() { assert_eq!( offset_differences(1, vec![1.0, 11.0, 5.0, 0.0]), vec![10.0, -6.0, -5.0, 1.0] ); } #[test] fn test_degenerate_cases() { assert_eq!(offset_differences(1, vec![0]), vec![0]); assert_eq!(offset_differences(1, vec![1]), vec![0]); let empty: Vec<i32> = vec![]; assert_eq!(offset_differences(1, empty), vec![]); } fn main() {}
๋ชจ๋
This segment should take about 40 minutes. It contains:
Slide | Duration |
---|---|
๋ชจ๋ | 3 minutes |
ํ์ผ์์คํ ๊ณ์ธต | 5 minutes |
๊ฐ์์ฑ | 5 minutes |
use, super, self | 10 minutes |
์ฐ์ต๋ฌธ์ : GUI ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ชจ๋ | 15 minutes |
๋ชจ๋
impl
๋ธ๋ก์ ํด๋น ํ์
์ ํจ์๋ค์ ๋ํ ๋ค์์คํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก, mod
๋ ํ์
๊ณผ ํจ์๋ค์ ๋ํด ๋ค์์คํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค:
mod foo { pub fn do_something() { println!("foo ๋ชจ๋ ๋ด๋ถ"); } } mod bar { pub fn do_something() { println!("bar ๋ชจ๋ ๋ด๋ถ"); } } fn main() { foo::do_something(); bar::do_something(); }
- ํจํค์ง๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ฉฐ ํ๋์ ๋ํ
Cargo.toml
ํ์ผ์ ํฌํจํฉ๋๋ค. ํจํค์ง๋ฅผ ๊ตฌ์ฑํ๋ ํฌ๋ ์ดํธ๋ค์ ๋น๋ํ๋ ๋ฐฉ๋ฒ์ด ์ด ํ์ผ์ ๊ธฐ์ ๋ฉ๋๋ค. - ํฌ๋ ์ดํธ๋ ๋ชจ๋์ ํธ๋ฆฌ์ ๋๋ค. ๋ฐ์ด๋๋ฆฌ ํฌ๋ ์ดํธ๋ ์คํํ์ผ๋ก ๋น๋๋๊ณ , ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํฌ๋ ์ดํธ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋น๋๋ฉ๋๋ค.
- ๋ชจ๋์ ์ฝ๋๋ฅผ ์กฐ์งํํ๊ณ ์ค์ฝํ๋ฅผ ์ ์ํ๋ ๋จ์์ ๋๋ค.
ํ์ผ์์คํ ๊ณ์ธต
๋ชจ๋์ ๋ด์ฉ์ ๊ธฐ์ ํ์ง ์์ผ๋ฉด, ๋ฌ์คํธ๋ ๋ค๋ฅธ ํ์ผ์์ ๊ทธ ๋ด์ฉ์ ์ฝ์ต๋๋ค:
mod garden;
์ ์ฝ๋๋ ๋ฌ์คํธ๋ก ํ์ฌ๊ธ garden
๋ชจ๋์ ๋ด์ฉ์ src/garden.rs
์์ ์ฐพ๋๋ก ํฉ๋๋ค. ๋น์ทํ๊ฒ, garden::vegetables
๋ชจ๋์ src/garden/vegetables.rs
์์ ์ฐพ์ต๋๋ค.
crate(ํฌ๋ ์ดํธ)
์ ๋ฃจํธ๋ ์๋ ๊ฒฝ๋ก ์
๋๋ค:
src/lib.rs
(๋ผ์ด๋ธ๋ฌ๋ฆฌ ํฌ๋ ์ดํธ)src/main.rs
(๋ฐ์ด๋๋ฆฌ ํฌ๋ ์ดํธ)
๋ชจ๋๋ โ๋ด๋ถ ๋ฌธ์ ์ฃผ์โ์ ์ฌ์ฉํ์ฌ ๋ฌธ์ํํ ์ ์์ต๋๋ค. ์ด๋ฌํ ๋ชจ๋์ ๋ชจ๋์ด ํฌํจ๋ ํญ๋ชฉ(์ด ๊ฒฝ์ฐ์๋ ๋ชจ๋)์ ๋ฌธ์ํํฉ๋๋ค.
//! ์ด ๋ชจ๋์ ์ ์์ ๊ตฌํํฉ๋๋ค. // ์ด ๋ชจ๋์์ ํ์ ์ ๋ค์ ๋ด๋ณด๋ ๋๋ค. pub use garden::Garden; pub use seeds::SeedPacket; /// ์ฃผ์ด์ง ์จ์ ํจํท์ ๋ฟ๋ฆฝ๋๋ค. pub fn sow(seeds: Vec<SeedPacket>) { todo!() } /// ์ค๋น๋ ๋์๋ฌผ์ ์ ์์์ ์ํํฉ๋๋ค. pub fn harvest(garden: &mut Garden) { todo!() }
-
module/mod.rs
๋ฅผmodule.rs
๋ก ๋ฐ๊พผ๋ค ํ๋๋ผ๋ Rust 2018์์๋ ํ์ ๋ชจ๋์ ์ฌ์ฉํ ์ ์์ต๋๋ค. -
filename.rs
๋ฅผfilename/mod.rs
๋์ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ ์ฃผ๋ ์ด์ ๋,mod.rs
๋ผ๋ ์ด๋ฆ์ ๊ฐ์ง ํ์ผ์ด ๋ง์ ๊ฒฝ์ฐ IDE์์ ์ด๋ค์ ์๋ก ๊ตฌ๋ณํ๋๊ฒ ํ๋ค๊ธฐ ๋๋ฌธ์ ๋๋ค. -
ํด๋๋ฅผ ์ด์ฉํด์ ๋ ๊น์ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค. ์ฌ์ง์ด๋ ๋ฉ์ธ ๋ชจ๋์ด ํ์ผ์ด๋๋ผ๋์:
src/ โโโ main.rs โโโ top_module.rs โโโ top_module/ โโโ sub_module.rs
-
๋ฌ์คํธ๊ฐ ์ด๋์ ๋ชจ๋๋ค์ ์ฐพ์์ง๋ ์ปดํ์ผ๋ฌ ๋๋ ํฐ๋ธ๋ก ๋ณ๊ฒฝ ๊ฐ๋ฅํฉ๋๋ค:
#[path = "some/path.rs"] mod some_module;
์ด๋ Go์ธ์ด ์์์ฒ๋ผ ์ด๋ค ๋ชจ๋์ ํ ์คํธ๋ฅผ
some_module_test.rs
๊ฐ์ ํ์ผ์ ๋๊ณ ์ถ์ ๊ฒฝ์ฐ์ ์ ์ฉํฉ๋๋ค.
๊ฐ์์ฑ
๋ชจ๋์ ํ์ ์ด๋ ํจ์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฐ๊นฅ์ ๋ ธ์ถ๋์ง ์์ต๋๋ค:
- ๋ฐ๋ผ์ ๋ชจ๋์ ์ธ๋ถ ๊ตฌํ ๋ด์ฉ์ด ๊ฐ์ถฐ์ง๋๋ค.
- ๋ถ๋ชจ์ ์ด์ ํญ๋ชฉ์ ์ธ์ ๋ ์ ๊ทผ ๊ฐ๋ฅํฉ๋๋ค.
- ์ฆ, ๋ชจ๋
foo
์์ ์ ๊ทผ ๊ฐ๋ฅํ ํญ๋ชฉ์ด๋ผ๋ฉดfoo
์๋์ ๋ชจ๋ ๋ชจ๋์์ ์ ๊ทผ๊ฐ๋ฅํฉ๋๋ค.
mod outer { fn private() { println!("outer::private"); } pub fn public() { println!("outer::public"); } mod inner { fn private() { println!("outer::inner::private"); } pub fn public() { println!("outer::inner::public"); super::private(); } } } fn main() { outer::public(); }
pub
ํค์๋๋ ๋ชจ๋์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๋ํ, ๊ณ ๊ธ ๊ธฐ๋ฅ์ผ๋ก pub(...)
์ง์ ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ณต๊ฐ ๋ฒ์๋ฅผ ์ ํํ ์ ์์ต๋๋ค.
- ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ธ์.
pub(crate)
๋ก ๊ฐ์์ฑ์ ์ง์ ํ๋ ๊ฒ์ด ์์ฃผ ์ฐ์ ๋๋ค.- ์์ฃผ ์ฐ์ด์ง ์์ง๋ง ํน์ ๊ฒฝ๋ก์ ๋ํด์๋ง ๊ฐ์์ฑ์ ๋ถ์ฌํ ์ ์์ต๋๋ค.
- ์ด๋ค ๊ฒฝ์ฐ์ด๋ ๊ฐ์์ฑ์ด ๋ถ์ฌ๋๋ฉด ํด๋น ๋ชจ๋์ ํฌํจํ์ฌ ํ์์ ๋ชจ๋ ๋ชจ๋์ด ์ ์ฉ๋ฐ์ต๋๋ค.
use, super, self
๋ชจ๋์ use
๋ฅผ ์ฌ์ฉํ์ฌ ๋ค๋ฅธ ๋ชจ๋์ ์ฌ๋ณผ์ ๋ด ์ค์ฝํ๋ก ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ๊ฐ ๋ชจ๋์ ์๋จ์ ๋ค์๊ณผ ๊ฐ์ ๋ด์ฉ์ด ์ต๋๋ค:
use std::collections::HashSet; use std::process::abort;
๊ฒฝ๋ก
๊ฒฝ๋ก๋ ์๋์ ๊ฐ์ด ๊ตฌ๋ถํฉ๋๋ค:
-
์๋ ๊ฒฝ๋ก:
foo
๋๋self::foo
๋ ํ์ฌ ๋ชจ๋ ๋ด๋ถ์foo
๋ฅผ ๊ฐ๋ฆฌํต๋๋ค,super::foo
๋ ๋ถ๋ชจ ๋ชจ๋์foo
๋ฅผ ๊ฐ๋ฆฌํต๋๋ค.
-
์ ๋ ๊ฒฝ๋ก:
crate::foo
๋ ํ์ฌ ํฌ๋ ์ดํธ ๋ฃจํธ์foo
๋ฅผ ๊ฐ๋ฆฌํต๋๋ค,bar::foo
๋bar
ํฌ๋ ์ดํธ์foo
๋ฅผ ๊ฐ๋ฆฌํต๋๋ค.
-
๋ ์งง์ ๊ฒฝ๋ก์์ ๊ธฐํธ๋ฅผ โ๋ค์ ๋ด๋ณด๋ด๊ธฐโํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ ๋๋ค. ์๋ฅผ ๋ค์ด ํฌ๋ ์ดํธ์ ์ต์์
lib.rs
๋mod storage; pub use storage::disk::DiskStorage; pub use storage::network::NetworkStorage;
ํธ๋ฆฌํ๊ณ ์งง์ ๊ฒฝ๋ก๋ก ๋ค๋ฅธ ํฌ๋ ์ดํธ์์
DiskStorage
๋ฐNetworkStorage
๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ํ ์ ์์ต๋๋ค. -
๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๋ชจ๋์ ๋ํ๋๋ ํญ๋ชฉ๋ง โuseโ ์ฒ๋ฆฌ ๋์ด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๋ ํธ๋ ์์ ๊ตฌํํ๋ ํ์ ์ด ์ด๋ฏธ ๋ฒ์ ๋ด์ ์๋๋ผ๋ ํด๋น ํธ๋ ์์์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ค๋ฉด ํธ๋ ์์ด ๋ฒ์ ๋ด์ ์์ด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด
Read
ํธ๋ ์์ ๊ตฌํํ๋ ํ์ ์์read_to_string
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ค๋ฉดuse std::io::Read
๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค. -
use
๋ฌธ์๋ ์์ผ๋ ์นด๋(use std::io::*
)๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด๋ ๊ฐ์ ธ์ค๋ ํญ๋ชฉ์ด ๋ช ํํ์ง ์๊ณ ์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ๋ณ๊ฒฝ๋ ์ ์์ผ๋ฏ๋ก ๊ถ์ฅ๋์ง ์์ต๋๋ค.
์ฐ์ต๋ฌธ์ : GUI ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ชจ๋
In this exercise, you will reorganize a small GUI Library implementation. This library defines a Widget
trait and a few implementations of that trait, as well as a main
function.
It is typical to put each type or set of closely-related types into its own module, so each widget type should get its own module.
Cargo Setup
Rust ํ๋ ์ด๊ทธ๋ผ์ด๋๋ ํ๋์ ํ์ผ๋ง ์ง์ํ๋ฏ๋ก ๋ก์ปฌ ํ์ผ ์์คํ ์ Cargo ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค.
cargo init gui-modules
cd gui-modules
cargo run
Edit the resulting src/main.rs
to add mod
statements, and add additional files in the src
directory.
Source
Hereโs the single-module implementation of the GUI library:
pub trait Widget { /// `self`์ ์์ฐ ๋๋น fn width(&self) -> usize; /// ๋ฒํผ์ ์์ ฏ์ ๊ทธ๋ฆฝ๋๋ค. fn draw_into(&self, buffer: &mut dyn std::fmt::Write); /// ํ์ค ์ถ๋ ฅ์ ์์ ฏ์ ๊ทธ๋ฆฝ๋๋ค. fn draw(&self) { let mut buffer = String::new(); self.draw_into(&mut buffer); println!("{buffer}"); } } pub struct Label { label: String, } impl Label { fn new(label: &str) -> Label { Label { label: label.to_owned() } } } pub struct Button { label: Label, } impl Button { fn new(label: &str) -> Button { Button { label: Label::new(label) } } } pub struct Window { title: String, widgets: Vec<Box<dyn Widget>>, } impl Window { fn new(title: &str) -> Window { Window { title: title.to_owned(), widgets: Vec::new() } } fn add_widget(&mut self, widget: Box<dyn Widget>) { self.widgets.push(widget); } fn inner_width(&self) -> usize { std::cmp::max( self.title.chars().count(), self.widgets.iter().map(|w| w.width()).max().unwrap_or(0), ) } } impl Widget for Window { fn width(&self) -> usize { // ํ ๋๋ฆฌ์ฉ ํจ๋ฉ 4๊ฐ ์ถ๊ฐ self.inner_width() + 4 } fn draw_into(&self, buffer: &mut dyn std::fmt::Write) { let mut inner = String::new(); for widget in &self.widgets { widget.draw_into(&mut inner); } let inner_width = self.inner_width(); // TODO: Change draw_into to return Result<(), std::fmt::Error>. Then use the // ?-operator here instead of .unwrap(). writeln!(buffer, "+-{:-<inner_width$}-+", "").unwrap(); writeln!(buffer, "| {:^inner_width$} |", &self.title).unwrap(); writeln!(buffer, "+={:=<inner_width$}=+", "").unwrap(); for line in inner.lines() { writeln!(buffer, "| {:inner_width$} |", line).unwrap(); } writeln!(buffer, "+-{:-<inner_width$}-+", "").unwrap(); } } impl Widget for Button { fn width(&self) -> usize { self.label.width() + 8 // ํจ๋ฉ์ ์ฝ๊ฐ ์ถ๊ฐํฉ๋๋ค. } fn draw_into(&self, buffer: &mut dyn std::fmt::Write) { let width = self.width(); let mut label = String::new(); self.label.draw_into(&mut label); writeln!(buffer, "+{:-<width$}+", "").unwrap(); for line in label.lines() { writeln!(buffer, "|{:^width$}|", &line).unwrap(); } writeln!(buffer, "+{:-<width$}+", "").unwrap(); } } impl Widget for Label { fn width(&self) -> usize { self.label.lines().map(|line| line.chars().count()).max().unwrap_or(0) } fn draw_into(&self, buffer: &mut dyn std::fmt::Write) { writeln!(buffer, "{}", &self.label).unwrap(); } } fn main() { let mut window = Window::new("Rust GUI ๋ฐ๋ชจ 1.23"); window.add_widget(Box::new(Label::new("์์ ํ ์คํธ GUI ๋ฐ๋ชจ์ ๋๋ค."))); window.add_widget(Box::new(Button::new("ํด๋ฆญํด ์ฃผ์ธ์!"))); window.draw(); }
ํ์๋ค์๊ฒ๋ ๋ณธ์ธ์๊ฒ ์์ฐ์ค๋ฌ์ด ๋ฐฉ์์ผ๋ก ์ฝ๋๋ฅผ ๋๋๋๋ก ๊ถ์ฅํ์ธ์. ๊ทธ๋ฆฌ๊ณ ๋์ ํ์ํ mod
, use
, pub
์ ์ธ์ ์ต์ํด์ง๋๋ก ํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ์ด๋ค ๊ตฌ์ฑ์ด ๊ฐ์ฅ ์์ฐ์ค๋ฌ์ด์ง ๋
ผ์ํฉ๋๋ค.
ํด๋ต
src
โโโ main.rs
โโโ widgets
โย ย โโโ button.rs
โย ย โโโ label.rs
โย ย โโโ window.rs
โโโ widgets.rs
// ---- src/widgets.rs ----
mod button;
mod label;
mod window;
pub trait Widget {
/// `self`์ ์์ฐ ๋๋น
fn width(&self) -> usize;
/// ๋ฒํผ์ ์์ ฏ์ ๊ทธ๋ฆฝ๋๋ค.
fn draw_into(&self, buffer: &mut dyn std::fmt::Write);
/// ํ์ค ์ถ๋ ฅ์ ์์ ฏ์ ๊ทธ๋ฆฝ๋๋ค.
fn draw(&self) {
let mut buffer = String::new();
self.draw_into(&mut buffer);
println!("{buffer}");
}
}
pub use button::Button;
pub use label::Label;
pub use window::Window;
// ---- src/widgets/label.rs ----
use super::Widget;
pub struct Label {
label: String,
}
impl Label {
pub fn new(label: &str) -> Label {
Label { label: label.to_owned() }
}
}
impl Widget for Label {
fn width(&self) -> usize {
// ANCHOR_END: Label-width
self.label.lines().map(|line| line.chars().count()).max().unwrap_or(0)
}
// ANCHOR: Label-draw_into
fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
// ANCHOR_END: Label-draw_into
writeln!(buffer, "{}", &self.label).unwrap();
}
}
// ---- src/widgets/button.rs ----
use super::{Label, Widget};
pub struct Button {
label: Label,
}
impl Button {
pub fn new(label: &str) -> Button {
Button { label: Label::new(label) }
}
}
impl Widget for Button {
fn width(&self) -> usize {
// ANCHOR_END: Button-width
self.label.width() + 8 // ํจ๋ฉ์ ์ฝ๊ฐ ์ถ๊ฐํฉ๋๋ค.
}
// ANCHOR: Button-draw_into
fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
// ANCHOR_END: Button-draw_into
let width = self.width();
let mut label = String::new();
self.label.draw_into(&mut label);
writeln!(buffer, "+{:-<width$}+", "").unwrap();
for line in label.lines() {
writeln!(buffer, "|{:^width$}|", &line).unwrap();
}
writeln!(buffer, "+{:-<width$}+", "").unwrap();
}
}
// ---- src/widgets/window.rs ----
use super::Widget;
pub struct Window {
title: String,
widgets: Vec<Box<dyn Widget>>,
}
impl Window {
pub fn new(title: &str) -> Window {
Window { title: title.to_owned(), widgets: Vec::new() }
}
pub fn add_widget(&mut self, widget: Box<dyn Widget>) {
self.widgets.push(widget);
}
fn inner_width(&self) -> usize {
std::cmp::max(
self.title.chars().count(),
self.widgets.iter().map(|w| w.width()).max().unwrap_or(0),
)
}
}
impl Widget for Window {
fn width(&self) -> usize {
// ANCHOR_END: Window-width
// ํ
๋๋ฆฌ์ ํจ๋ฉ 4๊ฐ ์ถ๊ฐ
self.inner_width() + 4
}
// ANCHOR: Window-draw_into
fn draw_into(&self, buffer: &mut dyn std::fmt::Write) {
// ANCHOR_END: Window-draw_into
let mut inner = String::new();
for widget in &self.widgets {
widget.draw_into(&mut inner);
}
let inner_width = self.inner_width();
// TODO: ์ค๋ฅ ์ฒ๋ฆฌ์ ๊ดํด ์์๋ณธ ํ,
// Result<(), std::fmt::Error>๋ฅผ ๋ฐํํ๋๋ก draw_into๋ฅผ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค. ๊ทธ๋ฐ ๋ค์
// .unwrap() ๋์ ?-์ฐ์ฐ์๋ฅผ ์ฌ๊ธฐ์ ์ฌ์ฉํ์ธ์.
writeln!(buffer, "+-{:-<inner_width$}-+", "").unwrap();
writeln!(buffer, "| {:^inner_width$} |", &self.title).unwrap();
writeln!(buffer, "+={:=<inner_width$}=+", "").unwrap();
for line in inner.lines() {
writeln!(buffer, "| {:inner_width$} |", line).unwrap();
}
writeln!(buffer, "+-{:-<inner_width$}-+", "").unwrap();
}
}
// ---- src/main.rs ----
mod widgets;
use widgets::Widget;
fn main() {
let mut window = widgets::Window::new("Rust GUI ๋ฐ๋ชจ 1.23");
window
.add_widget(Box::new(widgets::Label::new("์์ ํ
์คํธ GUI ๋ฐ๋ชจ์
๋๋ค.")));
window.add_widget(Box::new(widgets::Button::new("ํด๋ฆญํด ์ฃผ์ธ์!")));
window.draw();
}
ํ ์คํธ
This segment should take about 45 minutes. It contains:
Slide | Duration |
---|---|
ํ ์คํธ ๋ชจ๋ | 5 minutes |
๋ค๋ฅธ ํ๋ก์ ํธ | 5 minutes |
์ปดํ์ผ๋ฌ ๋ฆฐํธ ๋ฐ Clippy | 3 minutes |
๋ฃฌ ์๊ณ ๋ฆฌ์ฆ | 30 minutes |
๋จ์ ํ ์คํธ
๋ฌ์คํธ์ ์นด๊ณ (cargo)๋ ๊ฐ๋จํ ๋จ์ ํ ์คํธ ํ๋ ์์ํฌ์ ํจ๊ป ์ ๊ณต๋ฉ๋๋ค:
-
๋จ์ ํ ์คํธ๋ ์ฝ๋ ์ ๋ฐ์์ ์ง์๋ฉ๋๋ค.
-
ํตํฉ ํ ์คํธ๋
tests/
๋๋ ํฐ๋ฆฌ๋ฅผ ํตํด ์ง์๋ฉ๋๋ค.
Tests are marked with #[test]
. Unit tests are often put in a nested tests
module, using #[cfg(test)]
to conditionally compile them only when building tests.
fn first_word(text: &str) -> &str {
match text.find(' ') {
Some(idx) => &text[..idx],
None => &text,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty() {
assert_eq!(first_word(""), "");
}
#[test]
fn test_single_word() {
assert_eq!(first_word("์๋
ํ์ธ์"), "์๋
ํ์ธ์");
}
#[test]
fn test_multiple_words() {
assert_eq!(first_word("Hello World"), "์๋
ํ์ธ์");
}
}
- ์ด๋ ๊ฒ ์๋ธ ๋ชจ๋๋ก ํ ์คํธ๋ฅผ ๋ง๋ค๋ฉด privateํ ํฌํผ ํจ์์ ๋ํ ๋จ์ ํ ์คํธ๋ ๊ฐ๋ฅํฉ๋๋ค.
#[cfg(test)]
์ดํธ๋ฆฌ๋ทฐํธ๊ฐ ์ถ๊ฐ๋ ํญ๋ชฉ์cargo test
๋ฅผ ์ํํ์ ๊ฒฝ์ฐ์๋ง ๋์ํฉ๋๋ค.
ํ๋ ์ด๊ทธ๋ผ์ด๋์์ ํ ์คํธ๋ฅผ ์คํํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ํ์ํฉ๋๋ค.
๋ค๋ฅธ ํ๋ก์ ํธ
ํตํฉ ํ ์คํธ
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉ์ ์ ์ฅ์์ ํ ์คํธ ํ๋ ค๋ฉด, ํตํฉ ํ ์คํธ๋ฅผ ํด์ผ ํฉ๋๋ค.
test/
๋๋ ํฐ๋ฆฌ ์๋์ .rs
ํ์ผ์ ํ๋ ๋ง๋์ธ์:
// tests/my_library.rs
use my_library::init;
#[test]
fn test_init() {
assert!(init().is_ok());
}
์ด ํ ์คํธ๋ ํฌ๋ ์ดํธ์ ๊ณต๊ฐ API์๋ง ์ ๊ทผํ ์ ์์ต๋๋ค.
๋ฌธ์ํ์ฃผ์ ํ ์คํธ
๋ฌ์คํธ๋ ๋ฌธ์ํ์ฃผ์์ ๋ํ ํ ์คํธ๋ฅผ ๋ด์ฅํ์ฌ ์ ๊ณตํฉ๋๋ค:
#![allow(unused)] fn main() { /// Shortens a string to the given length. /// /// ``` /// # use playground::shorten_string; /// assert_eq!(shorten_string("Hello World", 5), "Hello"); /// assert_eq!(shorten_string("Hello World", 20), "Hello World"); /// ``` pub fn shorten_string(s: &str, length: usize) -> &str { &s[..std::cmp::min(length, s.len())] } }
///
์ฃผ์์์ ์ฝ๋ ๋ธ๋ก์ ์๋์ผ๋ก ๋ฌ์คํธ ์ฝ๋๋ก ์ธ์๋ฉ๋๋ค.- ์ด ์ฝ๋ ๋ธ๋ก์
cargo test
ํธ์ถํ๋ฉด ์๋์ผ๋ก ์ปดํ์ผ๋๊ณ ์คํ๋ฉ๋๋ค. - Adding
#
in the code will hide it from the docs, but will still compile/run it. - ์ ์ฝ๋๋ฅผ Rust Playground์์ ํ ์คํธ ํด ๋ณด์๊ธฐ ๋ฐ๋๋๋ค.
์ปดํ์ผ๋ฌ ๋ฆฐํธ ๋ฐ Clippy
Rust ์ปดํ์ผ๋ฌ๋ ์ ์ฉํ ๋ด์ฅ ๋ฆฐํธ๋ฟ ์๋๋ผ ๋ฉ์ง ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์์ฑํฉ๋๋ค. Clippy๋ ํ๋ก์ ํธ๋ณ๋ก ์ฌ์ฉ ์ค์ ํ ์ ์๋ ๊ทธ๋ฃน์ผ๋ก ๊ตฌ์ฑ๋ ๋ ๋ง์ ๋ฆฐํธ๋ฅผ ์ ๊ณตํฉ๋๋ค.
#[deny(clippy::cast_possible_truncation)] fn main() { let x = 3; while (x < 70000) { x *= 2; } println!("X๋ u16์ ๋ง์ง ์์๊น์? {}", x as u16); }
์ฝ๋ ์ํ์ ์คํํ๊ณ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํ์ธํฉ๋๋ค. ์ฌ๊ธฐ์๋ ๋ฆฐํธ๊ฐ ํ์๋์ง๋ง ์ฝ๋๊ฐ ์ปดํ์ผ๋๊ณ ๋๋ฉด ํ์๋์ง ์์ต๋๋ค. ํ๋ ์ด๊ทธ๋ผ์ด๋ ์ฌ์ดํธ๋ก ์ ํํ์ฌ ์ด๋ฌํ ๋ฆฐํธ๋ฅผ ํ์ํฉ๋๋ค.
๋ฆฐํธ๋ฅผ ํด๊ฒฐํ ํ ํ๋ ์ด๊ทธ๋ผ์ด๋ ์ฌ์ดํธ์์ clippy
๋ฅผ ์คํํ์ฌ clippy ๊ฒฝ๊ณ ๋ฅผ ํ์ํฉ๋๋ค. Clippy๋ ๋ฆฐํธ์ ๊ดํ ๊ด๋ฒ์ํ ๋ฌธ์๋ฅผ ๋ณด์ ํ๊ณ ์์ผ๋ฉฐ ํญ์ ์๋ก์ด ๋ฆฐํธ(default-deny ๋ฆฐํธ ํฌํจ)๋ฅผ ์ถ๊ฐํฉ๋๋ค.
help: ...
๊ฐ ํฌํจ๋ ์ค๋ฅ๋ ๊ฒฝ๊ณ ๋ cargo fix
๋๋ ํธ์ง๊ธฐ๋ฅผ ํตํด ์์ ํ ์ ์์ต๋๋ค.
๋ฃฌ ์๊ณ ๋ฆฌ์ฆ
๋ฃฌ ์๊ณ ๋ฆฌ์ฆ
๋ฃฌ(Luhn) ์๊ณ ๋ฆฌ์ฆ์ ์ ์ฉ์นด๋ ๋ฒํธ ๊ฒ์ฆ์ ์ฌ์ฉ๋๋ ์๊ณ ๋ฆฌ์ฆ ์
๋๋ค. ์ด ์๊ณ ๋ฆฌ์ฆ์ ์ ์ฉ์นด๋ ๋ฒํธ๋ฅผ ๋ฌธ์์ด
๋ก ์
๋ ฅ๋ฐ๊ณ , ์๋์ ์์์ ๋ฐ๋ผ ์ ์ฉ์นด๋ ๋ฒํธ์ ์ ํจ์ฑ์ ํ์ธํฉ๋๋ค:
-
Ignore all spaces. Reject number with fewer than two digits.
-
์ค๋ฅธ์ชฝ์์ ์ผ์ชฝ์ผ๋ก ์ด๋ํ๋ฉฐ 2๋ฒ์งธ ์๋ฆฌ๋ง๋ค ์ซ์๋ฅผ 2๋ฐฐ ์ฆ๊ฐ์ํต๋๋ค. ์๋ฅผ ๋ค์ด
1234
์์3
๊ณผ1
์ ๊ฐ๊ฐ 2๋ฅผ ๊ณฑํฉ๋๋ค. -
After doubling a digit, sum the digits if the result is greater than 9. So doubling
7
becomes14
which becomes1 + 4 = 5
. -
๋ชจ๋ ์๋ฆฌ์ ์ซ์๋ฅผ ๋ํฉ๋๋ค.
-
ํฉ๊ณ์ ๋์๋ฆฌ๊ฐ
0
์ธ ๊ฒฝ์ฐ ์ ํจํ ์ ์ฉ์นด๋ ๋ฒํธ์ ๋๋ค.
์ ๊ณต๋ ์ฝ๋๋ ๋๋ถ๋ถ์ ์๊ณ ๋ฆฌ์ฆ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์๋์ง ํ์ธํ๋ ๋ ๊ฐ์ง ๊ธฐ๋ณธ ๋จ์ ํ ์คํธ์ ํจ๊ป luhn ์๊ณ ๋ฆฌ์ฆ์ ๋ฒ๊ทธ๊ฐ ์๋ ๊ตฌํ์ ์ ๊ณตํฉ๋๋ค.
Copy the code below to https://play.rust-lang.org/ and write additional tests to uncover bugs in the provided implementation, fixing any bugs you find.
#![allow(unused)] fn main() { pub fn luhn(cc_number: &str) -> bool { let mut sum = 0; let mut double = false; for c in cc_number.chars().rev() { if let Some(digit) = c.to_digit(10) { if double { let double_digit = digit * 2; sum += if double_digit > 9 { double_digit - 9 } else { double_digit }; } else { sum += digit; } double = !double; } else { continue; } } sum % 10 == 0 } #[cfg(test)] mod test { use super::*; #[test] fn test_valid_cc_number() { assert!(luhn("4263 9826 4026 9299")); assert!(luhn("4539 3195 0343 6467")); assert!(luhn("7992 7398 713")); } #[test] fn test_invalid_cc_number() { assert!(!luhn("4223 9826 4026 9299")); assert!(!luhn("4539 3195 0343 6476")); assert!(!luhn("8273 1232 7352 0569")); } } }
ํด๋ต
// ๋ฌธ์ ์ ํ์๋๋ ๋ฒ๊ทธ๊ฐ ์๋ ๋ฒ์ ์ ๋๋ค. #[cfg(never)] pub fn luhn(cc_number: &str) -> bool { let mut sum = 0; let mut double = false; for c in cc_number.chars().rev() { if let Some(digit) = c.to_digit(10) { if double { let double_digit = digit * 2; sum += if double_digit > 9 { double_digit - 9 } else { double_digit }; } else { sum += digit; } double = !double; } else { continue; } } sum % 10 == 0 } // ์๋ฃจ์ ์ด๋ฉฐ ์๋์ ๋ชจ๋ ํ ์คํธ๋ฅผ ํต๊ณผํฉ๋๋ค. pub fn luhn(cc_number: &str) -> bool { let mut sum = 0; let mut double = false; let mut digits = 0; for c in cc_number.chars().rev() { if let Some(digit) = c.to_digit(10) { digits += 1; if double { let double_digit = digit * 2; sum += if double_digit > 9 { double_digit - 9 } else { double_digit }; } else { sum += digit; } double = !double; } else if c.is_whitespace() { continue; } else { return false; } } digits >= 2 && sum % 10 == 0 } fn main() { let cc_number = "1234 5678 1234 5670"; println!( "{cc_number}์(๋) ์ ํจํ ์ ์ฉ์นด๋ ๋ฒํธ์ธ๊ฐ์? {}", if luhn(cc_number) { "์" } else { "์๋์" } ); } #[cfg(test)] mod test { use super::*; #[test] fn test_valid_cc_number() { assert!(luhn("4263 9826 4026 9299")); assert!(luhn("4539 3195 0343 6467")); assert!(luhn("7992 7398 713")); } #[test] fn test_invalid_cc_number() { assert!(!luhn("4223 9826 4026 9299")); assert!(!luhn("4539 3195 0343 6476")); assert!(!luhn("8273 1232 7352 0569")); } #[test] fn test_non_digit_cc_number() { assert!(!luhn("foo")); assert!(!luhn("foo 0 0")); } #[test] fn test_empty_cc_number() { assert!(!luhn("")); assert!(!luhn(" ")); assert!(!luhn(" ")); assert!(!luhn(" ")); } #[test] fn test_single_digit_cc_number() { assert!(!luhn("0")); } #[test] fn test_two_digit_cc_number() { assert!(luhn(" 0 0 ")); } }
Welcome Back
Including 10 minute breaks, this session should take about 2 hours and 10 minutes. It contains:
Segment | Duration |
---|---|
์ค๋ฅ์ฒ๋ฆฌ | 55 minutes |
์์ ํ์ง ์์ ๋ฌ์คํธ | 1 hour and 5 minutes |
์ค๋ฅ์ฒ๋ฆฌ
This segment should take about 55 minutes. It contains:
Slide | Duration |
---|---|
ํจ๋ | 3 minutes |
Iterator | 5 minutes |
๋ฌต์์ ํ๋ณํ | 5 minutes |
Error | 5 minutes |
From๊ณผ Into | 5 minutes |
Result๋ฅผ ์ด์ฉํ ๊ตฌ์กฐํ๋ ์ค๋ฅ์ฒ๋ฆฌ | 30 minutes |
ํจ๋
Rust๋ โํจ๋โ์ผ๋ก ์น๋ช ์ ์ธ ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.
๋ฌ์คํธ๋ ์ํ ์ค ์น๋ช ์ ์ธ ์ค๋ฅ๋ฅผ ๋ง๋๋ฉด ํจ๋์ ๋ฐ์ํ ๊ฒ์ ๋๋ค:
fn main() { let v = vec![10, 20, 30]; println!("v[100]: {}", v[100]); }
- ํจ๋์ ๋ณต๊ตฌํ ์ ์๊ณ ์์์น ๋ชปํ ์ค๋ฅ์
๋๋ค.
- ํจ๋์ ํ๋ก๊ทธ๋จ์ ๋ฒ๊ทธ๊ฐ ์๋ค๋ ๊ฒ์ ๋ํ๋ ๋๋ค.
- ๊ฒฝ๊ณ ๊ฒ์ฌ ์คํจ์ ๊ฐ์ ๋ฐํ์ ์คํจ๋ก ์ธํด ํจ๋์ด ๋ฐ์ํ ์ ์์ต๋๋ค.
- ์คํจ ์ ์ด์ค์
(์:
assert!
) ํจ๋ - ๋ชฉ์ ๋ณ ํจ๋์
panic!
๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ํจ๋์ ์คํ์ โํด์ โํ์ฌ ํจ์๊ฐ ๋ฐํ๋ ๊ฒ์ฒ๋ผ ๊ฐ์ ์ญ์ ํฉ๋๋ค.
- ์ถฉ๋(ํฌ๋์)์ ํ์ฉํ์ง ์์์ผ ํ๋ ๊ฒฝ์ฐ, ํจ๋์ ์ ๋ฐํ์ง ์๋ API(
Vec::get
๋ฑ)๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก, ํจ๋์ด ๋ฐ์ํ๋ฉด ์คํ ๋๊ฐ๊ธฐ๋ฅผ ํฉ๋๋ค. ์คํ ๋๊ฐ๊ธฐ๋ ๋ค์๊ณผ ๊ฐ์ด ์บ์น๊ฐ ๊ฐ๋ฅํฉ๋๋ค:
use std::panic; fn main() { let result = panic::catch_unwind(|| "๊ด์ฐฎ์ต๋๋ค."); println!("{result:?}"); let result = panic::catch_unwind(|| { panic!("์ด๋ฐ"); }); println!("{result:?}"); }
- ํฌ์ฐฉ์ ํํ์ง ์์ต๋๋ค.
catch_unwind
๋ก ์์ธ๋ฅผ ๊ตฌํํ๋ ค๊ณ ์๋ํ์ง ๋ง์ธ์. - ์ด๊ฒ์ ๋จ์ผ ์์ฒญ์ด ํฌ๋์ ๋๋๋ผ๋ ํ๋ก๊ทธ๋จ์ด ๊ณ์ ์คํ๋์ผ ํ๋ ์๋ฒ์ ์ ์ฉํฉ๋๋ค.
- ๋ง์ฝ
Cargo.toml
์ค์ ํ์ผ์panic = abort
์ ์ค์ ํ๋ค๋ฉด ํฌ๋์๋ฅผ ์บ์นํ ์ ์์ต๋๋ค.
Iterator
Runtime errors like connection-refused or file-not-found are handled with the Result
type, but matching this type on every call can be cumbersome. The try-operator ?
is used to return errors to the caller. It lets you turn the common
match some_expression {
Ok(value) => value,
Err(err) => return Err(err),
}
์ด๋ ๊ฒ ์งง๊ฒ ์ธ ์ ์์ต๋๋ค
some_expression?
์ด์ ์ฐ๋ฆฌ ์์ ์ ์ ์ฉํด ๋ณด๊ฒ ์ต๋๋ค:
use std::io::Read; use std::{fs, io}; fn read_username(path: &str) -> Result<String, io::Error> { let username_file_result = fs::File::open(path); let mut username_file = match username_file_result { Ok(file) => file, Err(err) => return Err(err), }; let mut username = String::new(); match username_file.read_to_string(&mut username) { Ok(_) => Ok(username), Err(err) => Err(err), } } fn main() { //fs::write("config.dat", "alice").unwrap(); let username = read_username("config.dat"); println!("์ฌ์ฉ์ ์ด๋ฆ ๋๋ ์ค๋ฅ: {username:?}"); }
?
๋ฅผ ์ฌ์ฉํ๋๋ก read_username
ํจ์๋ฅผ ๋จ์ํํฉ๋๋ค.
ํค ํฌ์ธํธ:
username
๋ณ์๋Ok(string)
์ด๊ฑฐ๋Err(error)
์ผ ์ ์์ต๋๋ค.fs::write
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ํ์ผ์ด ์๊ฑฐ๋, ๋น์๊ฑฐ๋, ์ค๋ณต๋๋ ๊ฒฝ์ฐ ๋ฑ์ ํ ์คํธํด ๋ด ๋๋ค.- Note that
main
can return aResult<(), E>
as long as it implementsstd::process::Termination
. In practice, this means thatE
implementsDebug
. The executable will print theErr
variant and return a nonzero exit status on error.
๋ฌต์์ ํ๋ณํ
์ค์ ๋ก ?
๊ฐ ์ ์ฉ๋๋ ๊ณผ์ ์ ์๊น ์ค๋ช
ํ ๊ฒ ๋ณด๋ค ์ข ๋ ๋ณต์กํฉ๋๋ค:
expression?
์ ํํ์ ์๋์ ๊ฐ์ต๋๋ค
match expression {
Ok(value) => value,
Err(err) => return Err(From::from(err)),
}
The From::from
call here means we attempt to convert the error type to the type returned by the function. This makes it easy to encapsulate errors into higher-level errors.
์์
use std::error::Error; use std::fmt::{self, Display, Formatter}; use std::fs::File; use std::io::{self, Read}; #[derive(Debug)] enum ReadUsernameError { IoError(io::Error), EmptyUsername(String), } impl Error for ReadUsernameError {} impl Display for ReadUsernameError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::IoError(e) => write!(f, "IO ์ค๋ฅ: {e}"), Self::EmptyUsername(path) => write!(f, "Found no username in {path}"), } } } impl From<io::Error> for ReadUsernameError { fn from(err: io::Error) -> Self { Self::IoError(err) } } fn read_username(path: &str) -> Result<String, ReadUsernameError> { let mut username = String::with_capacity(100); File::open(path)?.read_to_string(&mut username)?; if username.is_empty() { return Err(ReadUsernameError::EmptyUsername(String::from(path))); } Ok(username) } fn main() { //fs::write("config.dat", "").unwrap(); let username = read_username("config.dat"); println!("์ฌ์ฉ์ ์ด๋ฆ ๋๋ ์ค๋ฅ: {username:?}"); }
The ?
operator must return a value compatible with the return type of the function. For Result
, it means that the error types have to be compatible. A function that returns Result<T, ErrorOuter>
can only use ?
on a value of type Result<U, ErrorInner>
if ErrorOuter
and ErrorInner
are the same type or if ErrorOuter
implements From<ErrorInner>
.
From
๊ตฌํ์ ์ผ๋ฐ์ ์ธ ๋์์ ํนํ ๋ณํ์ด ํ ๊ณณ์์๋ง ๋ฐ์ํ๋ ๊ฒฝ์ฐ Result::map_err
์
๋๋ค.
There is no compatibility requirement for Option
. A function returning Option<T>
can use the ?
operator on Option<U>
for arbitrary T
and U
types.
A function that returns Result
cannot use ?
on Option
and vice versa. However, Option::ok_or
converts Option
to Result
whereas Result::ok
turns Result
into Option
.
๋์ ์ธ ์๋ฌ ํ์
Sometimes we want to allow any type of error to be returned without writing our own enum covering all the different possibilities. The std::error::Error
trait makes it easy to create a trait object that can contain any error.
use std::error::Error; use std::fs; use std::io::Read; fn read_count(path: &str) -> Result<i32, Box<dyn Error>> { let mut count_str = String::new(); fs::File::open(path)?.read_to_string(&mut count_str)?; let count: i32 = count_str.parse()?; Ok(count) } fn main() { fs::write("count.dat", "1i3").unwrap(); match read_count("count.dat") { Ok(count) => println!("๊ฐ์: {count}"), Err(err) => println!("์ค๋ฅ: {err}"), } }
read_count
ํจ์๋ std::io::Error
(ํ์ผ ์์
์์) ๋๋ std::num::ParseIntError
(String::parse
์์)๋ฅผ ๋ฐํํ ์ ์์ต๋๋ค.
Boxing errors saves on code, but gives up the ability to cleanly handle different error cases differently in the program. As such itโs generally not a good idea to use Box<dyn Error>
in the public API of a library, but it can be a good option in a program where you just want to display the error message somewhere.
Make sure to implement the std::error::Error
trait when defining a custom error type so it can be boxed. But if you need to support the no_std
attribute, keep in mind that the std::error::Error
trait is currently compatible with no_std
in nightly only.
thiserror
and anyhow
The thiserror
and anyhow
crates are widely used to simplify error handling.
thiserror
is often used in libraries to create custom error types that implementFrom<T>
.anyhow
is often used by applications to help with error handling in functions, including adding contextual information to your errors.
use anyhow::{bail, Context, Result}; use std::fs; use std::io::Read; use thiserror::Error; #[derive(Clone, Debug, Eq, Error, PartialEq)] #[error("{0}์์ ์ฌ์ฉ์ ์ด๋ฆ์ ์ฐพ์ ์ ์์ต๋๋ค.")] struct EmptyUsernameError(String); fn read_username(path: &str) -> Result<String> { let mut username = String::with_capacity(100); fs::File::open(path) .with_context(|| format!("{path}์(๋ฅผ) ์ด์ง ๋ชปํ์ต๋๋ค."))? .read_to_string(&mut username) .context("์ฝ์ง ๋ชปํ์ต๋๋ค.")?; if username.is_empty() { bail!(EmptyUsernameError(path.to_string())); } Ok(username) } fn main() { //fs::write("config.dat", "").unwrap(); match read_username("config.dat") { Ok(username) => println!("์ฌ์ฉ์ ์ด๋ฆ: {username}"), Err(err) => println!("์ค๋ฅ: {err:?}"), } }
thiserror
- The
Error
derive macro is provided bythiserror
, and has lots of useful attributes to help define error types in a compact way. - The
std::error::Error
trait is derived automatically. - The message from
#[error]
is used to derive theDisplay
trait.
anyhow
anyhow::Error
๋Box<dyn Error>
์ ๋ํผ ํ์ ์ด๋ผ ํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ณต๊ฐ API๋ก์ ์ฌ์ฉํ๊ธฐ์ ๋ถ์ ํฉํ๋ค๊ณ ํ ์ ์์ง๋ง ๋ง์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ฆฌ ์ฌ์ฉ๋๊ณ ์์ต๋๋ค.anyhow::Result<V>
๋Result<V, anyhow::Error>
์ ํ์ ์จ๋ฆฌ์ด์ค(alias)์ ๋๋ค.- ํ์ํ๋ค๋ฉด
anyhow::Error
์ ์ ์ฅ๋ ์ง์ง ์๋ฌ ํ์ ์ ๊บผ๋ด์ด ๊ฒ์ฌํ ์๋ ์์ต๋๋ค. anyhow::Result<T>
๊ฐ ์ ๊ณตํ๋ ๊ธฐ๋ฅ๋ค์ด Go ์ธ์ด ๊ฐ๋ฐ์๋ค์๊ฒ๋ ์ต์ํ ๊ฒ์ ๋๋ค. Go์ธ์ด์์ ๋ฐํ ๊ฐ์ผ๋ก ์ฌ์ฉํ๋(T, error)
ํจํด๊ณผ ๋น์ทํ๊ธฐ ๋๋ฌธ์ ๋๋ค.anyhow::Context
is a trait implemented for the standardResult
andOption
types.use anyhow::Context
is necessary to enable.context()
and.with_context()
on those types.
Result๋ฅผ ์ด์ฉํ ๊ตฌ์กฐํ๋ ์ค๋ฅ์ฒ๋ฆฌ
๋ค์์ ํํ์ ์ธ์ด์ ๋งค์ฐ ๊ฐ๋จํ ํ์๋ฅผ ๊ตฌํํฉ๋๋ค. ๊ทธ๋ฌ๋ ํจ๋์ ํตํด ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค. ๋์ ๊ด์ฉ์ ์ธ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ณ ์ค๋ฅ๋ฅผ main
์ ๋ฐํ์ผ๋ก ์ ํํ๋๋ก ๋ค์ ์์ฑํฉ๋๋ค. thiserror
๋ฐ anyhow
๋ฅผ ์ผ๋ง๋ ์ง ์ฌ์ฉํ์ธ์.
ํํธ: ๋จผ์ parse
ํจ์์์ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ์์ ํ์ธ์. ์ ๋๋ก ์๋ํ๋ฉด Iterator<Item=Result<Token, TokenizerError>>
๋ฅผ ๊ตฌํํ๋๋ก Tokenizer
๋ฅผ ์
๋ฐ์ดํธํ๊ณ ํ์์์ ์ฒ๋ฆฌํฉ๋๋ค.
use std::iter::Peekable; use std::str::Chars; /// ์ฐ์ ์ฐ์ฐ์์ ๋๋ค. #[derive(Debug, PartialEq, Clone, Copy)] enum Op { Add, Sub, } /// ํํ์ ์ธ์ด์ ํ ํฐ์ ๋๋ค. #[derive(Debug, PartialEq)] enum Token { Number(String), Identifier(String), Operator(Op), } /// ํํ์ ์ธ์ด์ ํํ์์ ๋๋ค. #[derive(Debug, PartialEq)] enum Expression { /// ๋ณ์ ์ฐธ์กฐ์ ๋๋ค. Var(String), /// ๋ฆฌํฐ๋ด ์ซ์์ ๋๋ค. Number(u32), /// ์ด์ง ์ฐ์ฐ์ ๋๋ค. Operation(Box<Expression>, Op, Box<Expression>), } fn tokenize(input: &str) -> Tokenizer { return Tokenizer(input.chars().peekable()); } struct Tokenizer<'a>(Peekable<Chars<'a>>); impl<'a> Iterator for Tokenizer<'a> { type Item = Token; fn next(&mut self) -> Option<Token> { let c = self.0.next()?; match c { '0'..='9' => { let mut num = String::from(c); while let Some(c @ '0'..='9') = self.0.peek() { num.push(*c); self.0.next(); } Some(Token::Number(num)) } 'a'..='z' => { let mut ident = String::from(c); while let Some(c @ ('a'..='z' | '_' | '0'..='9')) = self.0.peek() { ident.push(*c); self.0.next(); } Some(Token::Identifier(ident)) } '+' => Some(Token::Operator(Op::Add)), '-' => Some(Token::Operator(Op::Sub)), _ => panic!("์๊ธฐ์น ์์ ๋ฌธ์ {c}"), } } } fn parse(input: &str) -> Expression { let mut tokens = tokenize(input); fn parse_expr<'a>(tokens: &mut Tokenizer<'a>) -> Expression { let Some(tok) = tokens.next() else { panic!("์๊ธฐ์น ์์ ์ ๋ ฅ ์ข ๋ฃ"); }; let expr = match tok { Token::Number(num) => { let v = num.parse().expect("์๋ชป๋ 32๋นํธ ์ ์์ ๋๋ค.'"); Expression::Number(v) } Token::Identifier(ident) => Expression::Var(ident), Token::Operator(_) => panic!("์๊ธฐ์น ์์ ํ ํฐ {tok:?}"), }; // ์ด์ง ์ฐ์ฐ์ด ์๋ ๊ฒฝ์ฐ ์ด๋ฅผ ํ์ฑํฉ๋๋ค. match tokens.next() { None => expr, Some(Token::Operator(op)) => Expression::Operation( Box::new(expr), op, Box::new(parse_expr(tokens)), ), Some(tok) => panic!("์๊ธฐ์น ์์ ํ ํฐ {tok:?}"), } } parse_expr(&mut tokens) } fn main() { let expr = parse("10+foo+20-30"); println!("{expr:?}"); }
ํด๋ต
use thiserror::Error; use std::iter::Peekable; use std::str::Chars; /// ์ฐ์ ์ฐ์ฐ์์ ๋๋ค. #[derive(Debug, PartialEq, Clone, Copy)] enum Op { Add, Sub, } /// ํํ์ ์ธ์ด์ ํ ํฐ์ ๋๋ค. #[derive(Debug, PartialEq)] enum Token { Number(String), Identifier(String), Operator(Op), } /// ํํ์ ์ธ์ด์ ํํ์์ ๋๋ค. #[derive(Debug, PartialEq)] enum Expression { /// ๋ณ์ ์ฐธ์กฐ์ ๋๋ค. Var(String), /// ๋ฆฌํฐ๋ด ์ซ์์ ๋๋ค. Number(u32), /// ์ด์ง ์ฐ์ฐ์ ๋๋ค. Operation(Box<Expression>, Op, Box<Expression>), } fn tokenize(input: &str) -> Tokenizer { return Tokenizer(input.chars().peekable()); } #[derive(Debug, Error)] enum TokenizerError { #[error("์ ๋ ฅ์ ์์์น ๋ชปํ ๋ฌธ์ '{0}'์ด(๊ฐ) ์์ต๋๋ค.")] UnexpectedCharacter(char), } struct Tokenizer<'a>(Peekable<Chars<'a>>); impl<'a> Iterator for Tokenizer<'a> { type Item = Result<Token, TokenizerError>; fn next(&mut self) -> Option<Result<Token, TokenizerError>> { let c = self.0.next()?; match c { '0'..='9' => { let mut num = String::from(c); while let Some(c @ '0'..='9') = self.0.peek() { num.push(*c); self.0.next(); } Some(Ok(Token::Number(num))) } 'a'..='z' => { let mut ident = String::from(c); while let Some(c @ ('a'..='z' | '_' | '0'..='9')) = self.0.peek() { ident.push(*c); self.0.next(); } Some(Ok(Token::Identifier(ident))) } '+' => Some(Ok(Token::Operator(Op::Add))), '-' => Some(Ok(Token::Operator(Op::Sub))), _ => Some(Err(TokenizerError::UnexpectedCharacter(c))), } } } #[derive(Debug, Error)] enum ParserError { #[error("ํ ํฐ๋์ด์ ์ค๋ฅ: {0}")] TokenizerError(#[from] TokenizerError), #[error("์๊ธฐ์น ์์ ์ ๋ ฅ ์ข ๋ฃ")] UnexpectedEOF, #[error("์๊ธฐ์น ์์ ํ ํฐ {0:?}")] UnexpectedToken(Token), #[error("์๋ชป๋ ๋ฒํธ")] InvalidNumber(#[from] std::num::ParseIntError), } fn parse(input: &str) -> Result<Expression, ParserError> { let mut tokens = tokenize(input); fn parse_expr<'a>( tokens: &mut Tokenizer<'a>, ) -> Result<Expression, ParserError> { let tok = tokens.next().ok_or(ParserError::UnexpectedEOF)??; let expr = match tok { Token::Number(num) => { let v = num.parse()?; Expression::Number(v) } Token::Identifier(ident) => Expression::Var(ident), Token::Operator(_) => return Err(ParserError::UnexpectedToken(tok)), }; // ์ด์ง ์ฐ์ฐ์ด ์๋ ๊ฒฝ์ฐ ์ด๋ฅผ ํ์ฑํฉ๋๋ค. Ok(match tokens.next() { None => expr, Some(Ok(Token::Operator(op))) => Expression::Operation( Box::new(expr), op, Box::new(parse_expr(tokens)?), ), Some(Err(e)) => return Err(e.into()), Some(Ok(tok)) => return Err(ParserError::UnexpectedToken(tok)), }) } parse_expr(&mut tokens) } fn main() -> anyhow::Result<()> { let expr = parse("10+foo+20-30")?; println!("{expr:?}"); Ok(()) }
์์ ํ์ง ์์ ๋ฌ์คํธ
This segment should take about 1 hour and 5 minutes. It contains:
Slide | Duration |
---|---|
์์ ํ์ง ์์ ๋ฌ์คํธ | 5 minutes |
์์ ํฌ์ธํฐ ์ญ์ฐธ์กฐ(๋ฐ๋ผ๊ฐ๊ธฐ) | 10 minutes |
์ ์ ๊ฐ๋ณ ๋ณ์ | 5 minutes |
Unions | 5 minutes |
์์ ํ์ง ์์ ํจ์ ํธ์ถ | 5 minutes |
์์ ํ์ง ์์ ํธ๋ ์ ๊ตฌํํ๊ธฐ | 5 minutes |
์ฐ์ต๋ฌธ์ : FFI ๋ํผ(wrapper) | 30 minutes |
์์ ํ์ง ์์ ๋ฌ์คํธ
๋ฌ์คํธ๋ก ์์ฑ๋ ํ๋ก๊ทธ๋จ์ ํฌ๊ฒ ๋ ๋ถ๋ถ์ผ๋ก ๋๋ฉ๋๋ค:
- ์์ ํ ๋ฌ์คํธ: ๋ฉ๋ชจ๋ฆฌ ๊ด๋ จ ์ค๋ฅ ๋ฐ์ ๋ถ๊ฐ๋ฅ, ์ ์๋์ง ์์ ๋์ ์์.
- ์์ ํ์ง ์์ ๋ฌ์คํธ: ํน๋ณํ ์กฐ๊ฑด์ ๋ง์กฑํ์ง ์์์ฑ๋ก ์ฌ์ฉ๋๋ฉด ์ ์๋์ง ์์ ๋์์ ์ ๋ฐํ ์ ์์.
We saw mostly safe Rust in this course, but itโs important to know what Unsafe Rust is.
๋ณดํต, ์์ ํ์ง ์์ ๋ฌ์คํธ ์ฝ๋๋ ํฌ๊ธฐ๊ฐ ์์ผ๋ฉฐ, ๋ ๋ฆฝ์ ์ผ๋ก ์กด์ฌํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฝ๋๊ฐ ์ ์ ์๋ํ๋์ง์ ๋ํด ์ธ๋ฐํ๊ฒ ๋ฌธ์ํ๊ฐ ๋์ด ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ , ๋ง์ ๊ฒฝ์ฐ ์์ ํ ๋ฌ์คํธ ์ฝ๋๋ฅผ ํตํด์ ์ถ์ํ๋ฅผ ์ํจ ํ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
์์ ํ์ง ์์ ๋ฌ์คํธ๋ฅผ ์ด์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋ค์ฏ ๊ฐ์ง ๊ฒ๋ค์ด ๊ฐ๋ฅํด ์ง๋๋ค:
- ์์ ํฌ์ธํฐ ์ญ์ฐธ์กฐ(๋ฐ๋ผ๊ฐ๊ธฐ)
- ์ ์ ๊ฐ๋ณ๋ณ์ ์ ๊ทผ ๋ฐ ์์ .
union
ํ๋ ์ ๊ทผ.extern
ํจ์๋ฅผ ํฌํจํunsafe
ํจ์ ํธ์ถ.unsafe
ํธ๋ ์ ๊ตฌํ.
์ ๊ธฐ๋ฅ๋ค์ ๋ํด ๊ฐ๋ตํ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ์์ธํ ๋ด์ฉ์ ๋ฌ์คํธ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด, 19.1์ ๊ณผ Rustonomicon๋ฅผ ์ฐธ์กฐํ์ธ์.
Unsafe Rust does not mean the code is incorrect. It means that developers have turned off some compiler safety features and have to write correct code by themselves. It means the compiler no longer enforces Rustโs memory-safety rules.
์์ ํฌ์ธํฐ ์ญ์ฐธ์กฐ(๋ฐ๋ผ๊ฐ๊ธฐ)
ํฌ์ธํฐ๋ฅผ ๋ง๋๋ ๊ฒ์ ์์ ํฉ๋๋ค. ํ์ง๋ง ์ญ์ฐธ์กฐ(๋ฐ๋ผ๊ฐ๊ธฐ)ํ ๊ฒฝ์ฐ unsafe
๊ฐ ํ์ํฉ๋๋ค:
fn main() { let mut s = String::from("์กฐ์ฌํ์ธ์!"); let r1 = &mut s as *mut String; let r2 = r1 as *const String; // Safe because r1 and r2 were obtained from references and so are // guaranteed to be non-null and properly aligned, the objects underlying // the references from which they were obtained are live throughout the // whole unsafe block, and they are not accessed either through the // references or concurrently through any other pointers. unsafe { println!("r1์ {}์ ๋๋ค.", *r1); *r1 = String::from("์ด๋ฐ"); println!("r2๋ {}์ ๋๋ค.", *r2); } // ์์ ํ์ง ์์. ์ด๋ ๊ฒ ํ์ง ๋ง์ธ์. /* let r3: &String = unsafe { &*r1 }; drop(s); println!("r3 is: {}", *r3); */ }
๋ชจ๋ unsafe
๋ธ๋ก์ ๋ํด ์ ๊ทธ ์ฝ๋๊ฐ ์์ ํ์ง์ ๋ํ ์ค๋ช
์ ์ฃผ์์ผ๋ก ๋ค๋ ๊ฒ์ ์ข์ ์ต๊ด์
๋๋ค(์ฌ์ค ์๋๋ก์ด๋์ ๋ฌ์คํธ ์คํ์ผ ๊ฐ์ด๋์์๋ ์ด๊ฒ ํ์์
๋๋ค).
ํฌ์ธํฐ ์ญ์ฐธ์กฐ๋ฅผ ํ ๊ฒฝ์ฐ, ํฌ์ธํฐ๊ฐ ์ ํจํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด:
- ํฌ์ธํฐ๋ null์ด๋ฉด ์๋ฉ๋๋ค.
- ํฌ์ธํฐ๋ ๋ฐ๋ผ๊ฐ๊ธฐ๊ฐ ๊ฐ๋ฅํด์ผ ํฉ๋๋ค (๊ฐ์ฒด์ ์ด๋ ํ ๋ถ๋ถ์ ๊ฐ๋ฆฌํค๊ณ ์์ด์ผ ํฉ๋๋ค).
- ์ด๋ฏธ ๋ฐํ๋ ๊ฐ์ฒด๋ฅผ ๊ฐ๋ฆฌํค๋ฉด ์๋ฉ๋๋ค.
- ๊ฐ์ ์์น์ ๋ํด ๋์์ ์ธ ์ ๊ทผ์ด ์์ผ๋ฉด ์๋ฉ๋๋ค.
- ์ฐธ์กฐ๋ฅผ ์บ์คํ ํด์ ํฌ์ธํฐ๋ฅผ ๋ง๋ค์๋ค๋ฉด, ๊ทธ ์ฐธ์กฐ๊ฐ ๊ฐ๋ฆฌํค๋ ๊ฐ์ฒด๋ ์ด์ ์์ด์ผ ํ๋ฉฐ, ๊ทธ ๊ฐ์ฒด์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ ๊ทผํ๋ ์ฐธ์กฐ๊ฐ ํ๋๋ ์์ด์ผ ํฉ๋๋ค.
๋๋ถ๋ถ์ ๊ฒฝ์ฐ ํฌ์ธํฐ๋ align๋์ด ์์ด์ผ ํฉ๋๋ค.
The โNOT SAFEโ section gives an example of a common kind of UB bug: *r1
has the 'static
lifetime, so r3
has type &'static String
, and thus outlives s
. Creating a reference from a pointer requires great care.
์ ์ ๊ฐ๋ณ ๋ณ์
๋ถ๋ณ ์ ์ ๋ณ์๋ฅผ ์ฝ๋ ๊ฒ์ ์์ ํฉ๋๋ค:
static HELLO_WORLD: &str = "Hello, world!"; fn main() { println!("HELLO_WORLD: {HELLO_WORLD}"); }
ํ์ง๋ง, ๋ฐ์ดํฐ ๋ ์ด์ค๊ฐ ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก ์ ์ ๊ฐ๋ณ๋ณ์๋ฅผ ์ฝ๊ณ ์ฐ๋ ๊ฒ์ ์์ ํ์ง ์์ต๋๋ค:
static mut COUNTER: u32 = 0; fn add_to_counter(inc: u32) { unsafe { COUNTER += inc; } } fn main() { add_to_counter(42); unsafe { println!("COUNTER: {COUNTER}"); } }
-
์ด ํ๋ก๊ทธ๋จ์ ๋จ์ผ ์ค๋ ๋์ด๋ฏ๋ก ์์ ํฉ๋๋ค. ๊ทธ๋ฌ๋ Rust ์ปดํ์ผ๋ฌ๋ ๋ณด์์ ์ด๋ฉฐ ์ต์ ์ ์ํฉ์ ๊ฐ์ ํฉ๋๋ค.
unsafe
๋ฅผ ์ญ์ ํด ๋ณด๊ณ ์ปดํ์ผ๋ฌ๊ฐ ์ฌ๋ฌ ์ค๋ ๋์์ static์ ๋ณ๊ฒฝํ๋ ๊ฒ์ด ์ ์๋์ง ์์ ๋์์ด๋ผ๊ณ ์ด๋ป๊ฒ ์ค๋ช ํ๋์ง ํ์ธํ์ธ์. -
์ผ๋ฐ์ ์ผ๋ก ์ด์ผ๊ธฐ ํด์, ์ ์ ๊ฐ๋ณ ๋ณ์๋ฅผ ์ฐ๋ ๊ฒ์ ์ข์ ์์ด๋์ด๊ฐ ์๋๋๋ค. ๊ทธ๋ฌ๋
no_std
์ ๊ฐ์ ์ ์์ค ์ฝ๋ฉ์ ํ ๊ฒฝ์ฐ์๋ ํ์ํ๊ธฐ๋ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ํ ํ ๋น๊ธฐ๋ฅผ ๊ตฌํํ๊ฑฐ๋, C API๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ๊ทธ๋ฐ ๊ฒฝ์ฐ์ ๋๋ค.
Unions
์ ๋์จ ํ์ ์ ์ด๊ฑฐํ(enum)๊ณผ ๋น์ทํ์ง๋ง, ์ด๋ค ํ๋์ ํด๋นํ๋ ๊ฐ์ ๊ฐ์ง๊ณ ์๋์ง ์ฌ๋ถ๋ฅผ ํ๋ก๊ทธ๋๋จธ๊ฐ ์๋์ผ๋ก ์ถ์ ํด์ผ ํฉ๋๋ค:
#[repr(C)] union MyUnion { i: u8, b: bool, } fn main() { let u = MyUnion { i: 42 }; println!("int: {}", unsafe { u.i }); println!("๋ถ์ธ: {}", unsafe { u.b }); // Undefined behavior! }
๋ฌ์คํธ์๋ ์ด๊ฑฐํ์ด ์๊ธฐ ๋๋ฌธ์ ์ ๋์จ์ด ํ์ํ ๊ฒฝ์ฐ๋ ๊ทนํ ๋๋ญ ๋๋ค. ์ ๋์จ์ C ๋ผ์ด๋ธ๋ฌ๋ฆฌ API๋ฅผ ์ฌ์ฉํ ๋ ๊ฐ๋ ํ์ํฉ๋๋ค.
๋ฐ์ดํธ๋ค์ ํน์ ํ์
์ผ๋ก ์ฌํด์ ํ๊ณ ์ถ๋ค๋ฉด std::mem::transmute
๋ ์ข ๋ ์์ ํ zerocopy
ํฌ๋ ์ดํธ๋ฅผ ์ฌ์ฉํ์ธ์.
์์ ํ์ง ์์ ํจ์ ํธ์ถ
์์ ํ์ง ์์ ํจ์ ํธ์ถ
ํจ์๋ ๋ฉ์๋๊ฐ ์ ์๋์ง ์์ ๋์์ผ๋ก ๋น ์ง์ง ์๊ฒ ํ๊ธฐ ์ํด์ ๋ง์กฑํด์ผ ํ๋ ์ ์ ์กฐ๊ฑด์ด ์๋ ๊ฒฝ์ฐ, ๊ทธ ํจ์๋ ๋ฉ์๋๋ฅผ unsafe
๋ก ํ์ํ ์ ์์ต๋๋ค:
extern "C" { fn abs(input: i32) -> i32; } fn main() { let emojis = "๐ปโ๐"; // ์์ธ์ด ์ฌ๋ฐ๋ฅธ ์์์ด๊ณ ๋ฌธ์์ด ์ฌ๋ผ์ด์ค์ ๊ฒฝ๊ณ ๋ด์ // ์์ผ๋ฉฐ UTF-8 ์ํ์ค ๊ฒฝ๊ณ์ ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { println!("์ด๋ชจํฐ์ฝ: {}", emojis.get_unchecked(0..4)); println!("์ด๋ชจํฐ์ฝ: {}", emojis.get_unchecked(4..7)); println!("์ด๋ชจํฐ์ฝ: {}", emojis.get_unchecked(7..11)); } println!("๋ฌธ์ ์: {}", count_chars(unsafe { emojis.get_unchecked(0..7) })); unsafe { // Undefined behavior if abs misbehaves. println!("C์ ๋ฐ๋ฅธ ์ ๋๊ฐ -3: {}", abs(-3)); } // Not upholding the UTF-8 encoding requirement breaks memory safety! // println!("emoji: {}", unsafe { emojis.get_unchecked(0..3) }); // println!("char count: {}", count_chars(unsafe { // emojis.get_unchecked(0..3) })); } fn count_chars(s: &str) -> usize { s.chars().count() }
์์ ํ์ง ์์ ํจ์ ์์ฑํ๊ธฐ
์ฌ๋ฌ๋ถ์ด ์์ฑํ ํจ์๋ฅผ ์ฌ์ฉํ ๋ ์ด๋ค ํน๋ณํ ์กฐ๊ฑด์ ๋ง์กฑํด์ผ ํ๋ค๋ฉด, unsafe
๋ก ๋งํนํ ์ ์์ต๋๋ค.
/// ์ง์ ๋ ํฌ์ธํฐ๊ฐ ๊ฐ๋ฆฌํค๋ ๊ฐ์ ๋ฐ๊ฟ๋๋ค. /// /// # Safety /// /// ํฌ์ธํฐ๋ ์ ํจํ๊ณ ์ฌ๋ฐ๋ฅด๊ฒ ์ ๋ ฌ๋์ด์ผ ํฉ๋๋ค. unsafe fn swap(a: *mut u8, b: *mut u8) { let temp = *a; *a = *b; *b = temp; } fn main() { let mut a = 42; let mut b = 66; // ๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก ์์ ํฉ๋๋ค. unsafe { swap(&mut a, &mut b); } println!("a = {}, b = {}", a, b); }
์์ ํ์ง ์์ ํจ์ ํธ์ถ
get_unchecked
, like most _unchecked
functions, is unsafe, because it can create UB if the range is incorrect. abs
is incorrect for a different reason: it is an external function (FFI). Calling external functions is usually only a problem when those functions do things with pointers which might violate Rustโs memory model, but in general any C function might have undefined behaviour under any arbitrary circumstances.
์ ์์ ์ฝ๋์์ "C"
๋ ABI๋ฅผ ์๋ฏธํฉ๋๋ค. ๋ค๋ฅธ ABI๋ ์์ต๋๋ค.
์์ ํ์ง ์์ ํจ์ ์์ฑํ๊ธฐ
We wouldnโt actually use pointers for a swap
function - it can be done safely with references.
Note that unsafe code is allowed within an unsafe function without an unsafe
block. We can prohibit this with #[deny(unsafe_op_in_unsafe_fn)]
. Try adding it and see what happens. This will likely change in a future Rust edition.
์์ ํ์ง ์์ ํธ๋ ์ ๊ตฌํํ๊ธฐ
ํจ์์์์ ๋ง์ฐฌ๊ฐ์ง๋ก ํธ๋ ์๋ unsafe
๋ก ๋งํน ๊ฐ๋ฅํฉ๋๋ค. ๋ง์ฝ ๊ทธ ํธ๋ ์์ ๊ตฌํํ ๋ ์ ์๋์ง ์์ ๋์์ ํผํ๊ธฐ ์ํด ํน๋ณํ ์กฐ๊ฑด์ด ํ์ํ๋ค๋ฉด ๋ง์ด์ง์.
์๋ฅผ ๋ค์ด zerocopy
ํฌ๋ ์ดํธ์๋ ์์ ํ์ง ์์ ํธ๋ ์์ด ์์ต๋๋ค:
use std::mem::size_of_val; use std::slice; /// ... /// # Safety /// ํ์ ์๋ ์ ์๋ ํํ์ด ์์ด์ผ ํ๋ฉฐ ํจ๋ฉ์ ์์ด์ผ ํฉ๋๋ค. pub unsafe trait AsBytes { fn as_bytes(&self) -> &[u8] { unsafe { slice::from_raw_parts( self as *const Self as *const u8, size_of_val(self), ) } } } // u32์ ์ ์๋ ํํ์ด ์๊ณ ํจ๋ฉ์ด ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe impl AsBytes for u32 {}
์์ ํ์ง ์์ ํธ๋ ์์ ๋ง๋ค ๋์๋ ์ฃผ์์ # Safety
ํญ๋ชฉ์ด ์์ด์ ์ด ํธ๋ ์์ ์์ ํ๊ฒ ๊ตฌํํ๋ ค๋ฉด ์ด๋ค ์๊ตฌ์ฌํญ๋ค์ ๋ง์กฑํด์ผ ํ๋์ง๋ฅผ ์ค๋ช
ํด์ผ ํฉ๋๋ค.
AsBytes
์์ ์ง์ผ์ผ ํ ์์ ์ฑ์ ๋ํ ์ค์ ์ค๋ช
์ ์ข ๋ ๊ธธ๊ณ ๋ณต์กํฉ๋๋ค.
๋นํธ์ธ ํธ๋ ์์ธ Send
์ Sync
๋ ์์ ํ์ง ์์ ํธ๋ ์ ์
๋๋ค.
FFI๋ํผ
Rust has great support for calling functions through a foreign function interface (FFI). We will use this to build a safe wrapper for the libc
functions you would use from C to read the names of files in a directory.
์๋ ๋ฆฌ๋ ์ค ๋ฉ๋ด์ผ ๋ฌธ์๋ค์ ์ฐธ์กฐํ์๊ธฐ ๋ฐ๋๋๋ค:
์๋ง std::ffi
๋ชจ๋์ ์ฐธ์กฐํ ํ์๊ฐ ์์ ๊ฒ์
๋๋ค. ๊ฑฐ๊ธฐ์๋ ์ด๋ฒ ์์ ๋ฅผ ์ํํ๋๋ฐ ํ์ํ ๋ค์ํ ์ข
๋ฅ์ ๋ฌธ์์ด ํ์
๋ค์ด ์๊ฐ๋์ด ์์ต๋๋ค:
ํ์ | ์ธ์ฝ๋ฉ | ์ฌ์ฉ |
---|---|---|
str ๊ณผ String | UTF-8 | ๋ฌ์คํธ์์์ ๋ฌธ์์ด ์ฒ๋ฆฌ |
CStr ๊ณผ CString | ๋(NUL)๋ก ๋๋จ | Cํจ์์ ์ฐ๋ํ๊ธฐ |
OsStr ์ OsString | OS๊ฐ ์ ์ํจ | OS์ ์ฐ๋ํ๊ธฐ ์ํ ๋ฌธ์์ด |
์ด ํ์ ๋ค ๊ฐ์ ๋ณํ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
&str
์์CString
์ผ๋ก์ ๋ณํ: ๋งจ ๋ง์ง๋ง์\0
๋ฌธ์๋ฅผ ์ ์ฅํ๊ธฐ ์ํ ๊ณต๊ฐ์ ํ ๋นํด์ผ ํฉ๋๋ค,CString
์์*const i8
๋ก์ ๋ณํ: Cํจ์๋ฅผ ํธ์ถํ๊ธฐ ์ํด์๋ ํฌ์ธํฐ๊ฐ ํ์ํฉ๋๋ค,*const i8
์์&CStr
๋ก์ ๋ณํ: ์ฃผ์ด์ง ๋ฐ์ดํธ ์ํ์ค๊ฐ\0
๋ก ๋๋๋์ง ํ์ธํ๊ณ ์ถ์ ๊ฒฝ์ฐ,&CStr
to&[u8]
: a slice of bytes is the universal interface for โsome unknown dataโ,&[u8]
์์&OsStr
๋ก์ ๋ณํ:&OsStr
๋OsString
์ผ๋ก ๊ฐ๊ธฐ ์ํ ์ค๊ฐ ๋จ๊ณ ์ ๋๋ค.OsStrExt
๋ฅผ ์ฌ์ฉํด์OsStr
๋ฅผ ์์ฑํ์ธ์,&OsStr
์์OsString
์ผ๋ก์ ๋ณํ:&OsStr
์ด ๊ฐ๋ฆฌํค๊ณ ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํจ์ผ๋ก์จ, ์ด ๋ฐ์ดํฐ๋ฅผ ๋ฆฌํดํ๊ณ ,readdir
ํจ์๋ฅผ ํธ์ถํ ๋ ์ฌ์ฉํ ์ ์๊ฒ ํด ์ค๋๋ค.
Nomicon์ FFI์ ๊ด๋ จํ ์์ฃผ ์ ์ฉํ ์ฑํฐ๊ฐ ์์ต๋๋ค.
์๋ ์ฝ๋๋ฅผ https://play.rust-lang.org/์ ๋ณต์ฌํ๊ณ ๋น ์ง ํจ์์ ๋ฉ์๋๋ฅผ ์ฑ์๋ด ๋๋ค:
// TODO: ๊ตฌํ์ด ์๋ฃ๋๋ฉด ์ด๋ฅผ ์ญ์ ํฉ๋๋ค. #![allow(unused_imports, unused_variables, dead_code)] mod ffi { use std::os::raw::{c_char, c_int}; #[cfg(not(target_os = "๋งคํฌ๋ก"))] use std::os::raw::{c_long, c_uchar, c_ulong, c_ushort}; // ๋ถํฌ๋ช ํ์ ์ ๋๋ค. https://doc.rust-lang.org/nomicon/ffi.html์ ์ฐธ๊ณ ํ์ธ์. #[repr(C)] pub struct DIR { _data: [u8; 0], _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } // readdir(3)์ Linux man ํ์ด์ง์ ๋ฐ๋ฅธ ๋ ์ด์์์ ๋๋ค. // ์ฌ๊ธฐ์ ino_t ๋ฐ off_t๋ // /usr/include/x86_64-linux-gnu/{sys/types.h, bits/typesizes.h}์ ์ ์์ ๋ฐ๋ผ ํ์ธ๋ฉ๋๋ค. #[cfg(not(target_os = "๋งคํฌ๋ก"))] #[repr(C)] pub struct dirent { pub d_ino: c_ulong, pub d_off: c_long, pub d_reclen: c_ushort, pub d_type: c_uchar, pub d_name: [c_char; 256], } // dir(5)์ macOS man ํ์ด์ง์ ๋ฐ๋ฅธ ๋ ์ด์์์ ๋๋ค. #[cfg(all(target_os = "๋งคํฌ๋ก"))] #[repr(C)] pub struct dirent { pub d_fileno: u64, pub d_seekoff: u64, pub d_reclen: u16, pub d_namlen: u16, pub d_type: u8, pub d_name: [c_char; 1024], } extern "C" { pub fn opendir(s: *const c_char) -> *mut DIR; #[cfg(not(all(target_os = "๋งคํฌ๋ก", target_arch = "x86_64")))] pub fn readdir(s: *mut DIR) -> *const dirent; // https://github.com/rust-lang/libc/issues/414 ๋ฐ // stat(2)์ ๊ดํ macOS man ํ์ด์ง์ _DARWIN_FEATURE_64_BIT_INODE ์น์ ์ ์ฐธ๊ณ ํ์ธ์. // // ' ์ด ์ ๋ฐ์ดํธ๊ฐ ์ ๊ณต๋๊ธฐ ์ ์ ์กด์ฌํ๋ ํ๋ซํผ์' // Intel ๋ฐ PowerPC์ macOS (iOS/wearOS ๋ฑ์ด ์๋)๋ฅผ ์๋ฏธํฉ๋๋ค. #[cfg(all(target_os = "๋งคํฌ๋ก", target_arch = "x86_64"))] #[link_name = "readdir$INODE64"] pub fn readdir(s: *mut DIR) -> *const dirent; pub fn closedir(s: *mut DIR) -> c_int; } } use std::ffi::{CStr, CString, OsStr, OsString}; use std::os::unix::ffi::OsStrExt; #[derive(Debug)] struct DirectoryIterator { path: CString, dir: *mut ffi::DIR, } impl DirectoryIterator { fn new(path: &str) -> Result<DirectoryIterator, String> { // opendir์ ํธ์ถํ๊ณ ์ ๋๋ก ์๋ํ๋ฉด Ok ๊ฐ์ ๋ฐํํ๊ณ // ๊ทธ๋ ์ง ์์ผ๋ฉด ๋ฉ์์ง์ ํจ๊ป Err์ ๋ฐํํฉ๋๋ค. unimplemented!() } } impl Iterator for DirectoryIterator { type Item = OsString; fn next(&mut self) -> Option<OsString> { // NULL ํฌ์ธํฐ๋ฅผ ๋ค์ ๊ฐ์ ธ์ฌ ๋๊น์ง readdir์ ๊ณ์ ํธ์ถํฉ๋๋ค. unimplemented!() } } impl Drop for DirectoryIterator { fn drop(&mut self) { // ํ์์ ๋ฐ๋ผ closedir์ ํธ์ถํฉ๋๋ค. unimplemented!() } } fn main() -> Result<(), String> { let iter = DirectoryIterator::new(".")?; println!("ํ์ผ: {:#?}", iter.collect::<Vec<_>>()); Ok(()) }
ํด๋ต
mod ffi { use std::os::raw::{c_char, c_int}; #[cfg(not(target_os = "๋งคํฌ๋ก"))] use std::os::raw::{c_long, c_uchar, c_ulong, c_ushort}; // ๋ถํฌ๋ช ํ์ ์ ๋๋ค. https://doc.rust-lang.org/nomicon/ffi.html์ ์ฐธ๊ณ ํ์ธ์. #[repr(C)] pub struct DIR { _data: [u8; 0], _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } // readdir(3)์ Linux man ํ์ด์ง์ ๋ฐ๋ฅธ ๋ ์ด์์์ ๋๋ค. // ์ฌ๊ธฐ์ ino_t ๋ฐ off_t๋ // /usr/include/x86_64-linux-gnu/{sys/types.h, bits/typesizes.h}์ ์ ์์ ๋ฐ๋ผ ํ์ธ๋ฉ๋๋ค. #[cfg(not(target_os = "๋งคํฌ๋ก"))] #[repr(C)] pub struct dirent { pub d_ino: c_ulong, pub d_off: c_long, pub d_reclen: c_ushort, pub d_type: c_uchar, pub d_name: [c_char; 256], } // dir(5)์ macOS man ํ์ด์ง์ ๋ฐ๋ฅธ ๋ ์ด์์์ ๋๋ค. #[cfg(all(target_os = "๋งคํฌ๋ก"))] #[repr(C)] pub struct dirent { pub d_fileno: u64, pub d_seekoff: u64, pub d_reclen: u16, pub d_namlen: u16, pub d_type: u8, pub d_name: [c_char; 1024], } extern "C" { pub fn opendir(s: *const c_char) -> *mut DIR; #[cfg(not(all(target_os = "๋งคํฌ๋ก", target_arch = "x86_64")))] pub fn readdir(s: *mut DIR) -> *const dirent; // https://github.com/rust-lang/libc/issues/414 ๋ฐ // stat(2)์ ๊ดํ macOS man ํ์ด์ง์ _DARWIN_FEATURE_64_BIT_INODE ์น์ ์ ์ฐธ๊ณ ํ์ธ์. // // ' ์ด ์ ๋ฐ์ดํธ๊ฐ ์ ๊ณต๋๊ธฐ ์ ์ ์กด์ฌํ๋ ํ๋ซํผ์' // Intel ๋ฐ PowerPC์ macOS (iOS/wearOS ๋ฑ์ด ์๋)๋ฅผ ์๋ฏธํฉ๋๋ค. #[cfg(all(target_os = "๋งคํฌ๋ก", target_arch = "x86_64"))] #[link_name = "readdir$INODE64"] pub fn readdir(s: *mut DIR) -> *const dirent; pub fn closedir(s: *mut DIR) -> c_int; } } use std::ffi::{CStr, CString, OsStr, OsString}; use std::os::unix::ffi::OsStrExt; #[derive(Debug)] struct DirectoryIterator { path: CString, dir: *mut ffi::DIR, } impl DirectoryIterator { fn new(path: &str) -> Result<DirectoryIterator, String> { // opendir์ ํธ์ถํ๊ณ ์ ๋๋ก ์๋ํ๋ฉด Ok ๊ฐ์ ๋ฐํํ๊ณ // ๊ทธ๋ ์ง ์์ผ๋ฉด ๋ฉ์์ง์ ํจ๊ป Err์ ๋ฐํํฉ๋๋ค. let path = CString::new(path).map_err(|err| format!("์๋ชป๋ ๊ฒฝ๋ก: {err}"))?; // SAFETY: path.as_ptr()์ NULL์ผ ์ ์์ต๋๋ค. let dir = unsafe { ffi::opendir(path.as_ptr()) }; if dir.is_null() { Err(format!("{:?}์(๋ฅผ) ์ด ์ ์์ต๋๋ค.", path)) } else { Ok(DirectoryIterator { path, dir }) } } } impl Iterator for DirectoryIterator { type Item = OsString; fn next(&mut self) -> Option<OsString> { // NULL ํฌ์ธํฐ๋ฅผ ๋ค์ ์ป์ ๋๊น์ง readdir์ ๊ณ์ ํธ์ถํฉ๋๋ค. // SAFETY: self.dir์ NULL์ด ์๋๋๋ค. let dirent = unsafe { ffi::readdir(self.dir) }; if dirent.is_null() { // ๋๋ ํฐ๋ฆฌ์ ๋์ ๋๋ฌํ์ต๋๋ค. return None; } // SAFETY: dirent๋ NULL์ด ์๋๋ฉฐ dirent.d_name์ NUL // ์ข ๋ฃ๋ฉ๋๋ค. let d_name = unsafe { CStr::from_ptr((*dirent).d_name.as_ptr()) }; let os_str = OsStr::from_bytes(d_name.to_bytes()); Some(os_str.to_owned()) } } impl Drop for DirectoryIterator { fn drop(&mut self) { // ํ์์ ๋ฐ๋ผ closedir์ ํธ์ถํฉ๋๋ค. if !self.dir.is_null() { // SAFETY: self.dir์ NULL์ด ์๋๋๋ค. if unsafe { ffi::closedir(self.dir) } != 0 { panic!("{:?}์(๋ฅผ) ๋ซ์ ์ ์์ต๋๋ค.", self.path); } } } } fn main() -> Result<(), String> { let iter = DirectoryIterator::new(".")?; println!("ํ์ผ: {:#?}", iter.collect::<Vec<_>>()); Ok(()) } #[cfg(test)] mod tests { use super::*; use std::error::Error; #[test] fn test_nonexisting_directory() { let iter = DirectoryIterator::new("no-such-directory"); assert!(iter.is_err()); } #[test] fn test_empty_directory() -> Result<(), Box<dyn Error>> { let tmp = tempfile::TempDir::new()?; let iter = DirectoryIterator::new( tmp.path().to_str().ok_or("๊ฒฝ๋ก์ UTF-8์ด ์๋ ๋ฌธ์๊ฐ ์์")?, )?; let mut entries = iter.collect::<Vec<_>>(); entries.sort(); assert_eq!(entries, &[".", ".."]); Ok(()) } #[test] fn test_nonempty_directory() -> Result<(), Box<dyn Error>> { let tmp = tempfile::TempDir::new()?; std::fs::write(tmp.path().join("foo.txt"), "Foo ๋ค์ด์ด๋ฆฌ\n")?; std::fs::write(tmp.path().join("bar.png"), "<PNG>\n")?; std::fs::write(tmp.path().join("crab.rs"), "//! Crab\n")?; let iter = DirectoryIterator::new( tmp.path().to_str().ok_or("๊ฒฝ๋ก์ UTF-8์ด ์๋ ๋ฌธ์๊ฐ ์์")?, )?; let mut entries = iter.collect::<Vec<_>>(); entries.sort(); assert_eq!(entries, &[".", "..", "bar.png", "crab.rs", "foo.txt"]); Ok(()) } }
Welcome to Rust in Android
Rust is supported for system software on Android. This means that you can write new services, libraries, drivers or even firmware in Rust (or improve existing code as needed).
์ฐ๋ฆฌ๋ ์ค๋ ์ฌ๋ฌ๋ถ์ ํ๋ก์ ํธ์์ ๋ฌ์คํธ ์ฝ๋๋ฅผ ํธ์ถํด๋ณผ ๊ฒ์ ๋๋ค. ๊ทธ ํ๋ก์ ํธ์์ ๋ฌ์คํธ๋ก ์ฎ๊ธธ๋ง ํ ์์ ๋ถ๋ถ์ ์ ํ์ธ์. ์์กด์ฑ์ด ์ ๊ณ โํน์ดํโ ํ์ ์ด ์ ์ ์๋ก ์ข์ต๋๋ค. ๋ฐ์ดํธ ๋ช ๊ฐ๋ฅผ ํ์ฑํ๋ ์ฝ๋๋ผ๋ฉด ์๋ฒฝํฉ๋๋ค.
Android์์ Rust ์ฌ์ฉ์ด ๋์ด๋ ์ ์ ๊ฐ์ํ ๋ ๋ฐํ์๋ ๋ค์ ๋ด์ฉ์ ์ธ๊ธํ ์ ์์ต๋๋ค.
-
์๋น์ค ์: DNS over HTTP
-
๋ผ์ด๋ธ๋ฌ๋ฆฌ: Rutabaga ๊ฐ์ ๊ทธ๋ํฝ ์ธํฐํ์ด์ค
-
์ปค๋ ๋๋ผ์ด๋ฒ: ๋ฐ์ธ๋
-
ํ์จ์ด: pKVM ํ์จ์ด
์ค์น
We will be using a Cuttlefish Android Virtual Device to test our code. Make sure you have access to one or create a new one with:
source build/envsetup.sh
lunch aosp_cf_x86_64_phone-trunk_staging-userdebug
acloud create
์์ธํ ๋ด์ฉ์ Android Developer Codelab์ ์ฐธ์กฐํ์ญ์์ค.
ํค ํฌ์ธํธ:
-
Cuttlefish is a reference Android device designed to work on generic Linux desktops. MacOS support is also planned.
-
Cuttlefish๋ ์ค์ ํ๋์จ์ด๋ฅผ ๋งค์ฐ ์ถฉ์คํ ์ฌํํ๊ณ ์์ผ๋ฉฐ, Rust ๋ฅผ ์ฌ์ฉํด ๋ณด๊ธฐ์์ด์์ ์ธ ์๋ฎฌ๋ ์ดํฐ์ ๋๋ค.
๋น๋ ๊ท์น
์๋๋ก์ด๋ ๋น๋ ์์คํ (Soong)์ ๋ค์๊ณผ ๊ฐ์ ์ฌ๋ฌ ๋ชจ๋์ ํตํด ๋ฌ์คํธ๋ฅผ ์ง์ํฉ๋๋ค:
Module Type | Description |
---|---|
rust_binary | ๋ฌ์คํธ ๋ฐ์ด๋๋ฆฌ๋ฅผ ์์ฑํฉ๋๋ค. |
rust_library | ๋ฌ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(rlibํน์ dylib)๋ฅผ ์์ฑํฉ๋๋ค. |
rust_ffi | cc ๋ชจ๋์์ ์ฌ์ฉํ ์ ์๋ C library (์ ์ ํน์ ๋์ )๋ฅผ ์์ฑํฉ๋๋ค. |
rust_proc_macro | proc-macro ๋ฅผ ๊ตฌํํ๋ ๋ฌ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์์ฑํฉ๋๋ค. ์ปดํ์ผ๋ฌ์ ํ๋ฌ๊ทธ์ธ์ผ๋ก ์๊ฐํด๋ ์ข์ต๋๋ค. |
rust_test | ํ์ค ๋ฌ์คํธ ํ ์คํธ ๋ฌ๋๋ฅผ ์ฌ์ฉํ๋ ํ ์คํธ ๋ฐ์ด๋๋ฆฌ๋ฅผ ์์ฑํฉ๋๋ค. |
rust_fuzz | libfuzzer ๋ฅผ ์ฌ์ฉํ์ฌ fuzz ๋ฐ์ด๋๋ฆฌ๋ฅผ ์์ฑํฉ๋๋ค. |
rust_protobuf | ํ๋กํ ๋ฒํ(protobuf) ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ๋ ๋ฌ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์์ฑํฉ๋๋ค. |
rust_bindgen | C ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํ ๋ฌ์คํธ ๋ฐ์ธ๋ฉ์ ์ ๊ณตํ๋ ๋ฌ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์์ฑํฉ๋๋ค. |
๋ค์์ rust_binary
์ rust_library
๋ฅผ ์ดํด๋ด
๋๋ค.
๋ฐํ์๊ฐ ์ธ๊ธํ ์ ์๋ ์ถ๊ฐ ํญ๋ชฉ:
-
Cargo๋ ๋ค๊ตญ์ด ์ ์ฅ์์ ์ต์ ํ๋์ง ์์์ผ๋ฉฐ ์ธํฐ๋ท์์ ํจํค์ง๋ฅผ ๋ค์ด๋ก๋ํฉ๋๋ค.
-
Android์์๋ ๊ท์ ์, ๊ทธ๋ฆฌ๊ณ ๋น๋ ์๋๋ฅผ ์ํด, ํฌ๋ ์ดํธ๋ค์ด Android ์์ค์ฝ๋ ํธ๋ฆฌ ์์ ๋ฏธ๋ฆฌ ํฌํจ๋์ด ์์ด์ผ ํฉ๋๋ค. ๋น๋ ์ ๋ค์ด๋ก๋ ๋ฐ์ ์ ์์ต๋๋ค. ๋ํ C/C++/Java ์ฝ๋์ ์ํธ ์ด์ฉ๋์ด์ผ ํฉ๋๋ค. Android ๋น๋ ์์คํ ์ธ Soong์ด ์ด ๊ณต๋ฐฑ์ ๋ฉ์๋๋ค.
-
Soong์ Blaze(google3์์ ์ฌ์ฉ๋จ)์ ์คํ์์ค ๋ณํ์ธ Bazel๊ณผ ๋ง์ด ๋น์ทํฉ๋๋ค.
-
Android, ChromeOS, Fuchsia๋ฅผ Bazel๋ก ์ ํํ ๊ณํ์ ๋๋ค.
-
Bazel๊ณผ ์ ์ฌํ ๋น๋ ๊ท์น์ ๋ฐฐ์ฐ๋ ๊ฒ์ ๋ชจ๋ Rust OS ๊ฐ๋ฐ์์๊ฒ ์ ์ฉํฉ๋๋ค.
-
์ฌ๋ฏธ์๋ ์ฌ์ค: ์คํํธ๋ ์ ์บ๋ฆญํฐ ์ค ํ๋ช ์ธ ๋ฐ์ดํฐ(Data)๋ ์ฌ์ค Soong ํ์ ์ ์๋๋ก์ด๋(Android)์ ๋๋ค.
๋ฌ์คํธ ๋ฐ์ด๋๋ฆฌ
๊ฐ๋จํ ์์ฉ ํ๋ก๊ทธ๋จ์ผ๋ก ์์ํด ๋ณด๊ฒ ์ต๋๋ค. AOSP ์ฒดํฌ์์์ ๋ฃจํธ์์ ๋ค์ ํ์ผ์ ์์ฑํฉ๋๋ค:
hello_rust/Android.bp:
rust_binary {
name: "hello_rust",
crate_name: "hello_rust",
srcs: ["src/main.rs"],
}
hello_rust/src/main.rs:
//! Rust ๋ฐ๋ชจ์ ๋๋ค. /// ์ธ์ฌ๋ง์ ํ์ค ์ถ๋ ฅ์ผ๋ก ์ธ์ํฉ๋๋ค. fn main() { println!("Hello from Rust!"); }
๊ทธ๋ฐ ๋ค์, ์ด ๋ฐ์ด๋๋ฆฌ๋ฅผ ๋น๋ํ๊ณ , ๊ฐ์ ๋๋ฐ์ด์ค์ ๋ฃ๊ณ , ์คํํฉ๋๋ค:
m hello_rust
adb push "$ANDROID_PRODUCT_OUT/system/bin/hello_rust" /data/local/tmp
adb shell /data/local/tmp/hello_rust
Hello from Rust!
๋ฌ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
rust_library
๋ฅผ ์ฌ์ฉํ์ฌ ์๋๋ก์ด๋์ฉ ์ ๋ฌ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง๋ญ๋๋ค.
์ฌ๊ธฐ์ ๋ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํ ์์กด์ฑ์ ์ ์ธํฉ๋๋ค:
- ์๋์ ์ ์ํ
libgreeting
. external/rust/crates/
์ ์กด์ฌํ๋libtextwrap
.
hello_rust/Android.bp:
rust_binary {
name: "hello_rust_with_dep",
crate_name: "hello_rust_with_dep",
srcs: ["src/main.rs"],
rustlibs: [
"libgreetings",
"libtextwrap",
],
prefer_rlib: true, // Need this to avoid dynamic link error.
}
rust_library {
name: "libgreetings",
crate_name: "์ธ์ฌ๋ง",
srcs: ["src/lib.rs"],
}
hello_rust/src/main.rs:
//! Rust ๋ฐ๋ชจ์
๋๋ค.
use greetings::greeting;
use textwrap::fill;
/// ์ธ์ฌ๋ง์ ํ์ค ์ถ๋ ฅ์ผ๋ก ์ธ์ํฉ๋๋ค.
fn main() {
println!("{}", fill(&greeting("Bob"), 24));
}
hello_rust/src/lib.rs:
//! ์ธ์ฌ๋ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค.
/// `name`์๊ฒ ์ธ์ฌํฉ๋๋ค.
pub fn greeting(name: &str) -> String {
format!("{name}๋, ์๋
ํ์ธ์. ๋ง๋์ ๋ฐ๊ฐ์ต๋๋ค.")
}
์ด์ ์ฒ๋ผ, ๋น๋ํ๊ณ , ๊ฐ์ ๋๋ฐ์ด์ค๋ก ๋ฃ๊ณ , ์คํํฉ๋๋ค:
m hello_rust_with_dep
adb push "$ANDROID_PRODUCT_OUT/system/bin/hello_rust_with_dep" /data/local/tmp
adb shell /data/local/tmp/hello_rust_with_dep
Hello Bob, it is very
nice to meet you!
AIDL
๋ฌ์คํธ๋ ์๋๋ก์ด๋ ์ธํฐํ์ด์ค ์ ์ ์ธ์ด(AIDL)๋ฅผ ์ง์ํฉ๋๋ค:
- ๋ฌ์คํธ ์ฝ๋์์ ๊ธฐ์กด AIDL ์๋ฒ๋ฅผ ํธ์ถ ํ ์ ์์ต๋๋ค.
- ๋ฌ์คํธ์์ ์๋ก์ด AIDL ์๋ฒ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
/** ์์ผ ์๋น์ค ์ธํฐํ์ด์ค์ ๋๋ค. */
To illustrate how to use Rust with Binder, weโre going to walk through the process of creating a Binder interface. Weโre then going to both implement the described service and write client code that talks to that service.
AIDL ์ธํฐํ์ด์ค
AIDL ์ธํฐํ์ด์ค๋ฅผ ์ด์ฉํด์ ์๋น์ค์ API๋ฅผ ์ ์ธํฉ๋๋ค:
birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:
/** ์์ผ ์๋น์ค ์ธํฐํ์ด์ค์
๋๋ค. */
interface IBirthdayService {
/** ์์ผ ์ถํ ๋ฉ์์ง๋ฅผ ์์ฑํฉ๋๋ค. */
String wishHappyBirthday(String name, int years);
}
birthday_service/aidl/Android.bp:
aidl_interface {
name: "com.example.birthdayservice",
srcs: ["com/example/birthdayservice/*.aidl"],
unstable: true,
backend: {
rust: { // Rust๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉ ์ค์ ๋์ง ์์ต๋๋ค.
enabled: true,
},
},
}
- Note that the directory structure under the
aidl/
directory needs to match the package name used in the AIDL file, i.e. the package iscom.example.birthdayservice
and the file is ataidl/com/example/IBirthdayService.aidl
.
Generated Service API
Binder generates a trait corresponding to the interface definition. trait to talk to the service.
birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:
/** ์์ผ ์๋น์ค ์ธํฐํ์ด์ค์
๋๋ค. */
interface IBirthdayService {
/** ์์ผ ์ถํ ๋ฉ์์ง๋ฅผ ์์ฑํฉ๋๋ค. */
String wishHappyBirthday(String name, int years);
}
Generated trait:
trait IBirthdayService {
fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String>;
}
Your service will need to implement this trait, and your client will use this trait to talk to the service.
- The generated bindings can be found at
out/soong/.intermediates/<path to module>/
. - Point out how the generated function signature, specifically the argument and return types, correspond the interface definition.
String
for an argument results in a different Rust type thanString
as a return type.
์๋น์ค ๊ตฌํ
์ด์ AIDL์๋น์ค๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค:
birthday_service/src/lib.rs:
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService;
use com_example_birthdayservice::binder;
/// The `IBirthdayService` implementation.
pub struct BirthdayService;
impl binder::Interface for BirthdayService {}
impl IBirthdayService for BirthdayService {
fn wishHappyBirthday(&self, name: &str, years: i32) -> binder::Result<String> {
Ok(format!("{name}๋์ ์์ผ์ ์ถํํฉ๋๋ค. {years}์ฃผ๋
์ ์ถํํฉ๋๋ค."))
}
}
birthday_service/Android.bp:
rust_library {
name: "libbirthdayservice",
srcs: ["src/lib.rs"],
crate_name: "birthdayservice",
rustlibs: [
"com.example.birthdayservice-rust",
"libbinder_rs",
],
}
- Point out the path to the generated
IBirthdayService
trait, and explain why each of the segments is necessary. - TODO: What does the
binder::Interface
trait do? Are there methods to override? Where source?
AIDL ์๋ฒ
๋ง์ง๋ง์ผ๋ก ์๋น์ค๋ฅผ ์ ๊ณตํ๋ ์๋ฒ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค:
birthday_service/src/server.rs:
//! Birthday service.
use birthdayservice::BirthdayService;
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::BnBirthdayService;
use com_example_birthdayservice::binder;
const SERVICE_IDENTIFIER: &str = "birthdayservice";
/// ์์ผ ์๋น์ค์ ์ง์
์ ์
๋๋ค.
fn main() {
let birthday_service = BirthdayService;
let birthday_service_binder = BnBirthdayService::new_binder(
birthday_service,
binder::BinderFeatures::default(),
);
binder::add_service(SERVICE_IDENTIFIER, birthday_service_binder.as_binder())
.expect("์๋น์ค ๋ฑ๋ก ์คํจ");
binder::ProcessState::join_thread_pool()
}
birthday_service/Android.bp:
rust_binary {
name: "birthday_server",
crate_name: "birthday_server",
srcs: ["src/server.rs"],
rustlibs: [
"com.example.birthdayservice-rust",
"libbinder_rs",
"libbirthdayservice",
],
prefer_rlib: true, // To avoid dynamic link error.
}
The process for taking a user-defined service implementation (in this case the BirthdayService
type, which implements the IBirthdayService
) and starting it as a Binder service has multiple steps, and may appear more complicated than students are used to if theyโve used Binder from C++ or another language. Explain to students why each step is necessary.
- Create an instance of your service type (
BirthdayService
). - Wrap the service object in corresponding
Bn*
type (BnBirthdayService
in this case). This type is generated by Binder and provides the common Binder functionality that would be provided by theBnBinder
base class in C++. We donโt have inheritance in Rust, so instead we use composition, putting ourBirthdayService
within the generatedBnBinderService
. - Call
add_service
, giving it a service identifier and your service object (theBnBirthdayService
object in the example). - Call
join_thread_pool
to add the current thread to Binderโs thread pool and start listening for connections.
๋ฐฐํฌ
์๋น์ค๋ฅผ ๋น๋ํ๊ณ , ๊ฐ์ ๋๋ฐ์ด์ค์ ๋ฃ๊ณ , ์์ ํ ์ ์์ต๋๋ค:
m birthday_server
adb push "$ANDROID_PRODUCT_OUT/system/bin/birthday_server" /data/local/tmp
adb root
adb shell /data/local/tmp/birthday_server
๋ค๋ฅธ ํฐ๋ฏธ๋์ ๋์์ ์๋น์ค๊ฐ ์ ์ํ๋๊ณ ์๋์ง ํ์ธํฉ๋๋ค:
adb shell service check birthdayservice
Service birthdayservice: found
service call
๋ช
๋ ์ด๋ก ์๋น์ค๋ฅผ ํธ์ถํ ์๋ ์์ต๋๋ค:
adb shell service call birthdayservice 1 s16 Bob i32 24
Result: Parcel(
0x00000000: 00000000 00000036 00610048 00700070 '....6...H.a.p.p.'
0x00000010: 00200079 00690042 00740072 00640068 'y. .B.i.r.t.h.d.'
0x00000020: 00790061 00420020 0062006f 0020002c 'a.y. .B.o.b.,. .'
0x00000030: 006f0063 0067006e 00610072 00750074 'c.o.n.g.r.a.t.u.'
0x00000040: 0061006c 00690074 006e006f 00200073 'l.a.t.i.o.n.s. .'
0x00000050: 00690077 00680074 00740020 00650068 'w.i.t.h. .t.h.e.'
0x00000060: 00320020 00200034 00650079 00720061 ' .2.4. .y.e.a.r.'
0x00000070: 00210073 00000000 's.!..... ')
AIDL ํด๋ผ์ด์ธํธ
๋ง์ง๋ง์ผ๋ก, ์๊น ์ถ๊ฐํ ์๋น์ค์ ๋ํ ํด๋ผ์ด์ธํธ๋ฅผ ๋ฌ์คํธ๋ก ๋ง๋ค๊ฒ ์ต๋๋ค.
birthday_service/src/client.rs:
use com_example_birthdayservice::aidl::com::example::birthdayservice::IBirthdayService::IBirthdayService;
use com_example_birthdayservice::binder;
const SERVICE_IDENTIFIER: &str = "birthdayservice";
/// ์์ผ ์๋น์ค๋ฅผ ํธ์ถํฉ๋๋ค.
fn main() -> Result<(), Box<dyn Error>> {
let name = std::env::args().nth(1).unwrap_or_else(|| String::from("Bob"));
let years = std::env::args()
.nth(2)
.and_then(|arg| arg.parse::<i32>().ok())
.unwrap_or(42);
binder::ProcessState::start_thread_pool();
let service = binder::get_interface::<dyn IBirthdayService>(SERVICE_IDENTIFIER)
.map_err(|_| "BirthdayService์ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค.")?;
// Call the service.
let msg = service.wishHappyBirthday(&name, years)?;
println!("{msg}");
}
birthday_service/Android.bp:
rust_binary {
name: "birthday_client",
crate_name: "birthday_client",
srcs: ["src/client.rs"],
rustlibs: [
"com.example.birthdayservice-rust",
"libbinder_rs",
],
prefer_rlib: true, // To avoid dynamic link error.
}
ํด๋ผ์ด์ธํธ๋ libbirthdayservice
์ ์์กดํ์ง ์์์ ์ฃผ๋ชฉํ์ธ์.
๋น๋ํ๊ณ , ๊ฐ์ ๋๋ฐ์ด์ค๋ก ๋ฃ๊ณ , ์คํํฉ๋๋ค:
m birthday_client
adb push "$ANDROID_PRODUCT_OUT/system/bin/birthday_client" /data/local/tmp
adb shell /data/local/tmp/birthday_client Charlie 60
Happy Birthday Charlie, congratulations with the 60 years!
Strong<dyn IBirthdayService>
is the trait object representing the service that the client has connected to.Strong
is a custom smart pointer type for Binder. It handles both an in-process ref count for the service trait object, and the global Binder ref count that tracks how many processes have a reference to the object.- Note that the trait object that the client uses to talk to the service uses the exact same trait that the server implements. For a given Binder interface, there is a single Rust trait generated that both client and server use.
- Use the same service identifier used when registering the service. This should ideally be defined in a common crate that both the client and server can depend on.
API ์์
API๋ฅผ ํ์ฅํ์ฌ ๋ ๋ง์ ๊ธฐ๋ฅ์ ์ ๊ณตํด ๋ด ์๋ค. ํด๋ผ์ด์ธํธ๊ฐ ์์ผ ์นด๋์ ๋ด๊ธธ ๋ด์ฉ์ ์ง์ ํ ์ ์๋๋ก ํ๊ฒ ์ต๋๋ค:
package com.example.birthdayservice;
/** ์์ผ ์๋น์ค ์ธํฐํ์ด์ค์
๋๋ค. */
interface IBirthdayService {
/** ์์ผ ์ถํ ๋ฉ์์ง๋ฅผ ์์ฑํฉ๋๋ค. */
String wishHappyBirthday(String name, int years, in String[] text);
}
This results in an updated trait definition for IBirthdayService
:
trait IBirthdayService {
fn wishHappyBirthday(
&self,
name: &str,
years: i32,
text: &[String],
) -> binder::Result<String>;
}
- Note how the
String[]
in the AIDL definition is translated as a&[String]
in Rust, i.e. that idiomatic Rust types are used in the generated bindings wherever possible:in
array arguments are translated to slices.out
andinout
args are translated to&mut Vec<T>
.- Return values are translated to returning a
Vec<T>
.
Updating Client and Service
Update the client and server code to account for the new API.
birthday_service/src/lib.rs:
impl IBirthdayService for BirthdayService {
fn wishHappyBirthday(
&self,
name: &str,
years: i32,
text: &[String],
) -> binder::Result<String> {
let mut msg = format!(
"{name}๋์ ์์ผ์ ์ถํํฉ๋๋ค. {years}์ฃผ๋
์ ์ถํํฉ๋๋ค.",
);
for line in text {
msg.push('\n');
msg.push_str(line);
}
Ok(msg)
}
}
birthday_service/src/client.rs:
let msg = service.wishHappyBirthday(
&name,
years,
&[
String::from("Habby birfday to yuuuuu"),
String::from("And also: many more"),
],
)?;
- TODO: Move code snippets into project files where theyโll actually be built?
Working With AIDL Types
AIDL types translate into the appropriate idiomatic Rust type:
- Primitive types map (mostly) to idiomatic Rust types.
- Collection types like slices,
Vec
s and string types are supported. - References to AIDL objects and file handles can be sent between clients and services.
- File handles and parcelables are fully supported.
Primitive Types
Primitive types map (mostly) idiomatically:
AIDL Type | Rust Type | Note |
---|---|---|
boolean | bool | |
byte | i8 | Note that bytes are signed. |
char | u16 | Note the usage of u16 , NOT u32 . |
int | i32 | |
long | i64 | |
float | f32 | |
double | f64 | |
String | String |
๋ฐฐ์ด
The array types (T[]
, byte[]
, and List<T>
) get translated to the appropriate Rust array type depending on how they are used in the function signature:
Position | Rust Type |
---|---|
in argument | &[T] |
out /inout argument | &mut Vec<T> |
Return | Vec<T> |
- In Android 13 or higher, fixed-size arrays are supported, i.e.
T[N]
becomes[T; N]
. Fixed-size arrays can have multiple dimensions (e.g. int[3][4]). In the Java backend, fixed-size arrays are represented as array types. - Arrays in parcelable fields always get translated to
Vec<T>
.
ํธ๋ ์ ๊ฐ์ฒด
AIDL objects can be sent either as a concrete AIDL type or as the type-erased IBinder
interface:
birthday_service/aidl/com/example/birthdayservice/IBirthdayInfoProvider.aidl:
package com.example.birthdayservice;
interface IBirthdayInfoProvider {
String name();
int years();
}
birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:
import com.example.birthdayservice.IBirthdayInfoProvider;
interface IBirthdayService {
/** The same thing, but using a binder object. */
String wishWithProvider(IBirthdayInfoProvider provider);
/** The same thing, but using `IBinder`. */
String wishWithErasedProvider(IBinder provider);
}
birthday_service/src/client.rs:
/// Rust struct implementing the `IBirthdayInfoProvider` interface.
struct InfoProvider {
name: String,
age: u8,
}
impl binder::Interface for InfoProvider {}
impl IBirthdayInfoProvider for InfoProvider {
fn name(&self) -> binder::Result<String> {
Ok(self.name.clone())
}
fn years(&self) -> binder::Result<i32> {
Ok(self.age as i32)
}
}
fn main() {
binder::ProcessState::start_thread_pool();
let service = connect().expect("BirthdayService์ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค.");
// Create a binder object for the `IBirthdayInfoProvider` interface.
let provider = BnBirthdayInfoProvider::new_binder(
InfoProvider { name: name.clone(), age: years as u8 },
BinderFeatures::default(),
);
// Send the binder object to the service.
service.wishWithProvider(&provider)?;
// Perform the same operation but passing the provider as an `SpIBinder`.
service.wishWithErasedProvider(&provider.as_binder())?;
}
- Note the usage of
BnBirthdayInfoProvider
. This serves the same purpose asBnBirthdayService
that we saw previously.
๋ณ์
Binder for Rust supports sending parcelables directly:
birthday_service/aidl/com/example/birthdayservice/BirthdayInfo.aidl:
package com.example.birthdayservice;
parcelable BirthdayInfo {
String name;
int years;
}
birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:
import com.example.birthdayservice.BirthdayInfo;
interface IBirthdayService {
/** The same thing, but with a parcelable. */
String wishWithInfo(in BirthdayInfo info);
}
birthday_service/src/client.rs:
fn main() {
binder::ProcessState::start_thread_pool();
let service = connect().expect("BirthdayService์ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค.");
service.wishWithInfo(&BirthdayInfo { name: name.clone(), years })?;
}
Sending Files
Files can be sent between Binder clients/servers using the ParcelFileDescriptor
type:
birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:
interface IBirthdayService {
/** The same thing, but loads info from a file. */
String wishFromFile(in ParcelFileDescriptor infoFile);
}
birthday_service/src/client.rs:
fn main() {
binder::ProcessState::start_thread_pool();
let service = connect().expect("BirthdayService์ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค.");
// Open a file and put the birthday info in it.
let mut file = File::create("/data/local/tmp/birthday.info").unwrap();
writeln!(file, "{name}")?;
writeln!(file, "{years}")?;
// Create a `ParcelFileDescriptor` from the file and send it.
let file = ParcelFileDescriptor::new(file);
service.wishFromFile(&file)?;
}
birthday_service/src/lib.rs:
impl IBirthdayService for BirthdayService {
fn wishFromFile(
&self,
info_file: &ParcelFileDescriptor,
) -> binder::Result<String> {
// Convert the file descriptor to a `File`. `ParcelFileDescriptor` wraps
// an `OwnedFd`, which can be cloned and then used to create a `File`
// object.
let mut info_file = info_file
.as_ref()
.try_clone()
.map(File::from)
.expect("Invalid file handle");
let mut contents = String::new();
info_file.read_to_string(&mut contents).unwrap();
let mut lines = contents.lines();
let name = lines.next().unwrap();
let years: i32 = lines.next().unwrap().parse().unwrap();
Ok(format!("{name}๋์ ์์ผ์ ์ถํํฉ๋๋ค. {years}์ฃผ๋
์ ์ถํํฉ๋๋ค."))
}
}
ParcelFileDescriptor
wraps anOwnedFd
, and so can be created from aFile
(or any other type that wraps anOwnedFd
), and can be used to create a newFile
handle on the other side.- Other types of file descriptors can be wrapped and sent, e.g. TCP, UDP, and UNIX sockets.
Testing in Android
Building on Testing, we will now look at how unit tests work in AOSP. Use the rust_test
module for your unit tests:
testing/Android.bp:
rust_library {
name: "libleftpad",
crate_name: "leftpad",
srcs: ["src/lib.rs"],
}
rust_test {
name: "libleftpad_test",
crate_name: "leftpad_test",
srcs: ["src/lib.rs"],
host_supported: true,
test_suites: ["general-tests"],
}
testing/src/lib.rs:
#![allow(unused)] fn main() { //! Left-padding library. /// Left-pad `s` to `width`. pub fn leftpad(s: &str, width: usize) -> String { format!("{s:>width$}") } #[cfg(test)] mod tests { use super::*; #[test] fn short_string() { assert_eq!(leftpad("foo", 5), " foo"); } #[test] fn long_string() { assert_eq!(leftpad("foobar", 6), "foobar"); } } }
You can now run the test with
atest --host libleftpad_test
The output looks like this:
INFO: Elapsed time: 2.666s, Critical Path: 2.40s
INFO: 3 processes: 2 internal, 1 linux-sandbox.
INFO: Build completed successfully, 3 total actions
//comprehensive-rust-android/testing:libleftpad_test_host PASSED in 2.3s
PASSED libleftpad_test.tests::long_string (0.0s)
PASSED libleftpad_test.tests::short_string (0.0s)
Test cases: finished with 2 passing and 0 failing out of 2 test cases
Notice how you only mention the root of the library crate. Tests are found recursively in nested modules.
GoogleTest
The GoogleTest crate allows for flexible test assertions using matchers:
use googletest::prelude::*;
#[googletest::test]
fn test_elements_are() {
let value = vec!["foo", "bar", "baz"];
expect_that!(value, elements_are!(eq("foo"), lt("xyz"), starts_with("b")));
}
๋ง์ง๋ง ์์๋ฅผ '!'
๋ก ๋ณ๊ฒฝํ๋ฉด ํ
์คํธ๊ฐ ์คํจํ๊ณ ์ค๋ฅ๋ฅผ ์ ํํ ๊ฐ๋ฆฌํค๋ ๊ตฌ์กฐํ๋ ์ค๋ฅ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค.
---- test_elements_are stdout ----
Value of: value
Expected: has elements:
0. is equal to "foo"
1. is less than "xyz"
2. starts with prefix "!"
Actual: ["foo", "bar", "baz"],
where element #2 is "baz", which does not start with "!"
at src/testing/googletest.rs:6:5
Error: See failure output above
-
GoogleTest๋ Rust ํ๋ ์ด๊ทธ๋ผ์ด๋์ ์ผ๋ถ๊ฐ ์๋๋ฏ๋ก ๋ก์ปฌ ํ๊ฒฝ์์ ์ด ์๋ฅผ ์คํํด์ผ ํฉ๋๋ค.
cargo add googletest
๋ฅผ ์ฌ์ฉํ์ฌ ๊ธฐ์กด Cargo ํ๋ก์ ํธ์ ๋น ๋ฅด๊ฒ ์ถ๊ฐํ์ธ์. -
use googletest::prelude::*;
์ค์ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋งคํฌ๋ก ๋ฐ ํ์ ์ ์ฌ๋ฌ ๊ฐ ๊ฐ์ ธ์ต๋๋ค. -
์ด๋ ์ผ๋ถ์ผ ๋ฟ์ด๋ฉฐ ๋ด์ฅ๋ ๋งค์ฒ๊ฐ ๋ง์ด ์์ต๋๋ค.
-
A particularly nice feature is that mismatches in multi-line strings are shown as a diff:
#[test]
fn test_multiline_string_diff() {
let haiku = "Memory safete found,\n\
Rust's strong typing guides the way,\n\
Secure code you'll write.";
assert_that!(
haiku,
eq("Memory safety found,\n\
Rust's silly humor guides the way,\n\
Secure code you'll write.")
);
}
์์์ผ๋ก ๊ตฌ๋ถ๋ diff๋ฅผ ํ์ํฉ๋๋ค(์ฌ๊ธฐ์์๋ ์์์ด ํ์๋์ง ์์ต๋๋ค).
Value of: haiku
Expected: is equal to "Memory safety found,\nRust's silly humor guides the way,\nSecure code you'll write."
Actual: "Memory safety found,\nRust's strong typing guides the way,\nSecure code you'll write.",
which isn't equal to "Memory safety found,\nRust's silly humor guides the way,\nSecure code you'll write."
Difference(-actual / +expected):
Memory safety found,
-Rust's strong typing guides the way,
+Rust's silly humor guides the way,
Secure code you'll write.
at src/testing/googletest.rs:17:5
- ํฌ๋ ์ดํธ๋ C++์ฉ GoogleTest์ Rust ํฌํธ์ ๋๋ค.
๋ชจ์ ์ฒ๋ฆฌ
๋ชจ์ ์ฒ๋ฆฌ์ ๊ฒฝ์ฐ ๋๋ฆฌ ์ฌ์ฉ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ Mockall์ด ์์ต๋๋ค. ํธ๋ ์์ ์ฌ์ฉํ๋๋ก ์ฝ๋๋ฅผ ๋ฆฌํฉํฐ๋งํด์ผ ํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ๋น ๋ฅด๊ฒ ๋ชจ์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
use std::time::Duration;
#[mockall::automock]
pub trait Pet {
fn is_hungry(&self, since_last_meal: Duration) -> bool;
}
#[test]
fn test_robot_dog() {
let mut mock_dog = MockPet::new();
mock_dog.expect_is_hungry().return_const(true);
assert_eq!(mock_dog.is_hungry(Duration::from_secs(10)), true);
}
-
Mockall is the recommended mocking library in Android (AOSP). There are other mocking libraries available on crates.io, in particular in the area of mocking HTTP services. The other mocking libraries work in a similar fashion as Mockall, meaning that they make it easy to get a mock implementation of a given trait.
-
๋ชจ์ ์ฒ๋ฆฌ๋ ๋ค์ ๋ ผ๋์ ์ฌ์ง๊ฐ ์์ต๋๋ค. ๋ชจ์๋ฅผ ์ฌ์ฉํ๋ฉด ํ ์คํธ๋ฅผ ์ข ์ ํญ๋ชฉ์์ ์์ ํ ๋ถ๋ฆฌํ ์ ์์ต๋๋ค. ๊ทธ ๊ฒฐ๊ณผ ํ ์คํธ ์คํ์ด ๋์ฑ ๋น ๋ฅด๊ณ ์์ ์ ์ผ๋ก ์ด๋ฃจ์ด์ง๋๋ค. ๋ฐ๋ฉด์ ๋ชจ์๋ ์๋ชป ๊ตฌ์ฑ๋์ด ์ค์ ์ข ์ ํญ๋ชฉ์ด ์คํํ๋ ๊ฒ๊ณผ ๋ค๋ฅธ ์ถ๋ ฅ์ ๋ฐํํ ์ ์์ต๋๋ค.
๊ฐ๋ฅํ๋ฉด ์ค์ ์ข ์ ํญ๋ชฉ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ง์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์๋ ๋ฉ๋ชจ๋ฆฌ ๋ด ๋ฐฑ์๋๋ฅผ ๊ตฌ์ฑํ ์ ์์ต๋๋ค. ์ฆ, ํ ์คํธ์์ ์ฌ๋ฐ๋ฅธ ๋์์ ๊ฐ์ ธ์ฌ ๋ฟ๋ง ์๋๋ผ ์๋๊ฐ ๋น ๋ฅด๊ณ ์๋์ผ๋ก ์ ๋ฆฌ๋ฉ๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก ๋ง์ ์น ํ๋ ์์ํฌ์์๋
localhost
์ ์์ ํฌํธ์ ๋ฐ์ธ๋ฉ๋๋ ํ๋ก์ธ์ค ๋ด ์๋ฒ๋ฅผ ์์ํ ์ ์์ต๋๋ค. ํ๋ ์์ํฌ๋ฅผ ๋ชจ์ ์ฒ๋ฆฌํ๋ ๊ฒ๋ณด๋ค๋ ํญ์ ์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ค์ ํ๊ฒฝ์์ ์ฝ๋๋ฅผ ํ ์คํธํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค. -
Mockall์ Rust ํ๋ ์ด๊ทธ๋ผ์ด๋์ ์ผ๋ถ๊ฐ ์๋๋ฏ๋ก ๋ก์ปฌ ํ๊ฒฝ์์ ์ด ์๋ฅผ ์คํํด์ผ ํฉ๋๋ค. Mockall์ ๊ธฐ์กด Cargo ํ๋ก์ ํธ์ ๋น ๋ฅด๊ฒ ์ถ๊ฐํ๋ ค๋ฉด
cargo add mockall
์ ์ฌ์ฉํฉ๋๋ค. -
Mockall์๋ ๋ ๋ง์ ๊ธฐ๋ฅ์ด ์์ต๋๋ค. ํนํ ์ ๋ฌ๋ ์ธ์์ ๋ฐ๋ผ ๊ธฐ๋์น๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์๋ ๋ง์ง๋ง์ผ๋ก ๋จน์ด๋ฅผ ๋จน๊ณ 3์๊ฐ์ด ์ง๋๋ฉด ๋ฐฐ๊ณ ํ์ง๋ ๊ณ ์์ด๋ฅผ ๋ชจ์ํ๋ ๋ฐ ์ด๋ฅผ ์ฌ์ฉํฉ๋๋ค.
#[test]
fn test_robot_cat() {
let mut mock_cat = MockPet::new();
mock_cat
.expect_is_hungry()
.with(mockall::predicate::gt(Duration::from_secs(3 * 3600)))
.return_const(true);
mock_cat.expect_is_hungry().return_const(false);
assert_eq!(mock_cat.is_hungry(Duration::from_secs(1 * 3600)), false);
assert_eq!(mock_cat.is_hungry(Duration::from_secs(5 * 3600)), true);
}
.times(n)
๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ์ ๋ฉ์๋๊ฐ ํธ์ถ๋ ์ ์๋ ํ์๋ฅผn
์ผ๋ก ์ ํํ ์ ์์ต๋๋ค. ์ด ์กฐ๊ฑด์ด ์ถฉ์กฑ๋์ง ์์ผ๋ฉด ๋ชจ์ ๋ฉ์๋๊ฐ ์ญ์ ๋ ๋ ์๋์ผ๋ก ํจ๋ ์ํ๊ฐ ๋ฉ๋๋ค.
๋ก๊น
log
ํฌ๋ ์ดํธ๋ฅผ ์ฌ์ฉํ๋ฉด ์๋๋ก์ด๋ ๋๋ฐ์ด์ค ์์์ ์ํ๋ ๋์๋ logcat
์ผ๋ก, ํธ์คํธ์์ ์ํ๋ ๋์๋ stdout
์ผ๋ก ๋ก๊ทธ๊ฐ ์๋์ผ๋ก ์ถ๋ ฅ์ด ๋๋๋ก ํ ์ ์์ต๋๋ค:
hello_rust_logs/Android.bp:
rust_binary {
name: "hello_rust_logs",
crate_name: "hello_rust_logs",
srcs: ["src/main.rs"],
rustlibs: [
"liblog_rust",
"liblogger",
],
host_supported: true,
}
hello_rust_logs/src/main.rs:
//! Rust ๋ก๊น
๋ฐ๋ชจ์
๋๋ค.
use log::{debug, error, info};
/// ์ธ์ฌ๋ง์ ๊ธฐ๋กํฉ๋๋ค.
fn main() {
logger::init(
logger::Config::default()
.with_tag_on_device("rust")
.with_min_level(log::Level::Trace),
);
debug!("ํ๋ก๊ทธ๋จ์ ์์ํ๋ ์ค์
๋๋ค.");
info!("์ ์งํ๋๊ณ ์์ต๋๋ค.");
error!("๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค!");
}
๋น๋ํ๊ณ , ๊ฐ์ ๋๋ฐ์ด์ค์ ๋ฃ๊ณ , ์คํํฉ๋๋ค:
m hello_rust_logs
adb push "$ANDROID_PRODUCT_OUT/system/bin/hello_rust_logs" /data/local/tmp
adb shell /data/local/tmp/hello_rust_logs
adb logcat
์ปค๋งจ๋๋ก ๋ก๊ทธ๋ฅผ ํ์ธํฉ๋๋ค:
adb logcat -s rust
09-08 08:38:32.454 2420 2420 D rust: hello_rust_logs: Starting program.
09-08 08:38:32.454 2420 2420 I rust: hello_rust_logs: Things are going fine.
09-08 08:38:32.454 2420 2420 E rust: hello_rust_logs: Something went wrong!
์ํธ์ด์ฉ์ฑ
๋ฌ์คํธ๋ ๋ค์๊ณผ ๊ฐ์ด ๋ค๋ฅธ ์ธ์ด์์ ์ํธ์ด์ฉ์ฑ์ ํ๋ฅญํ ์ง์ํฉ๋๋ค:
- ํ ์ธ์ด์์ ๋ฌ์คํธ ํจ์๋ฅผ ํธ์ถํฉ๋๋ค.
- ํ ์ธ์ด์ ํจ์๋ฅผ ๋ฌ์คํธ์์ ํธ์ถํฉ๋๋ค.
ํ ์ธ์ด์ ํจ์๋ฅผ ํธ์ถํด์ ์ฌ์ฉํ๋ ๊ฒ์ FFI(foreign function interface)๋ผ๊ณ ํฉ๋๋ค.
C์์ ์ํธ์ด์ฉ์ฑ
๋ฌ์คํธ๋ C ํธ์ถ๊ท์ฝ์ ๋ฐ๋ฅด๋ ์ค๋ธ์ ํธ ํ์ผ๊ณผ ๋งํนํ ์ ์์ต๋๋ค. ๋ํ, ๋ฐ๋๋ก ๋ฌ์คํธ ํจ์๋ฅผ ๋ด๋ณด๋ด์ C์์ ํธ์ถ ํ ์ ๋ ์์ต๋๋ค.
์ํ๋ค๋ฉด ์๋์ ๊ฐ์ด ์๋์ผ๋ก ์ฝ๋ฉํ ์ ์์ต๋๋ค:
extern "C" { fn abs(x: i32) -> i32; } fn main() { let x = -42; let abs_x = unsafe { abs(x) }; println!("{x}, {abs_x}"); }
์ฐ๋ฆฌ๋ ์ด๋ฏธ Safe FFI ๋ํผ ์ฐ์ต๋ฌธ์ ์์ ์ด๋ฅผ ๋ค๋ฃจ์์ต๋๋ค.
์ด๋ฌํ ๋ฐฉ๋ฒ์ ํ๊ฒ ํ๋ซํผ์ ๋ชจ๋ ๋ถ๋ถ์ ์ฌ์ ์ ์๊ณ ์๋ค๋ ์ ์ ๋ฅผ ๊น๊ณ ์์ต๋๋ค. ์์ฉ ํ๋ก์ ํธ์์๋ ๊ถ์ฅํ์ง ์์ต๋๋ค.
์ข ๋ ๋์ ์ต์ ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
Bindgen ์ฌ์ฉํ๊ธฐ
bindgen๋ C ํค๋ํ์ผ์์ ๋ฌ์คํธ ๋ฐ์ธ๋ฉ์ ์๋์ผ๋ก ์์ฑํ๋ ๋๊ตฌ์ ๋๋ค.
๋จผ์ ์์ C๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค:
interoperability/bindgen/libbirthday.h:
typedef struct card {
const char* name;
int years;
} card;
void print_card(const card* card);
interoperability/bindgen/libbirthday.c:
#include <stdio.h>
#include "libbirthday.h"
void print_card(const card* card) {
printf("+--------------\n");
printf("| %s๋, ์์ผ ์ถํํฉ๋๋ค.\n", card->name);
printf("| %i์ฃผ๋
์ ์ถํํฉ๋๋ค!\n", card->years);
printf("+--------------\n");
}
Android.bp
ํ์ผ์ ์๋๋ฅผ ์ถ๊ฐํฉ๋๋ค:
interoperability/bindgen/Android.bp:
cc_library {
name: "libbirthday",
srcs: ["libbirthday.c"],
}
๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํ ํค๋ ํ์ผ์ ๋ง๋ญ๋๋ค(์ด ์์์์๋ ๋ฐ๋์ ํ์ํ ๊ฒ์ ์๋๋๋ค.):
interoperability/bindgen/libbirthday_wrapper.h:
#include "libbirthday.h"
์ด์ ๋ฐ์ธ๋ฉ์ ์๋์ผ๋ก ์์ฑํ ์ ์์ต๋๋ค:
interoperability/bindgen/Android.bp:
rust_bindgen {
name: "libbirthday_bindgen",
crate_name: "birthday_bindgen",
wrapper_src: "libbirthday_wrapper.h",
source_stem: "bindings",
static_libs: ["libbirthday"],
}
๋ง์นจ๋ด, ๋ฌ์คํธ ํ๋ก๊ทธ๋จ์์ ๋ฐ์ธ๋ฉ์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
interoperability/bindgen/Android.bp:
rust_binary {
name: "print_birthday_card",
srcs: ["main.rs"],
rustlibs: ["libbirthday_bindgen"],
}
interoperability/bindgen/main.rs:
//! Bindgen ๋ฐ๋ชจ์ ๋๋ค. use birthday_bindgen::{card, print_card}; fn main() { let name = std::ffi::CString::new("ํผํฐ").unwrap(); let card = card { name: name.as_ptr(), years: 42 }; // SAFETY: `print_card` is safe to call with a valid `card` pointer. unsafe { print_card(&card as *const card); } }
๋น๋ํ๊ณ , ๊ฐ์ ๋๋ฐ์ด์ค์ ๋ฃ๊ณ , ์คํํฉ๋๋ค:
m print_birthday_card
adb push "$ANDROID_PRODUCT_OUT/system/bin/print_birthday_card" /data/local/tmp
adb shell /data/local/tmp/print_birthday_card
๋ง์ง๋ง์ผ๋ก, ๋ฐ์ธ๋ฉ์ด ์ ์๋ํ๋์ง ํ์ธํ๊ธฐ ์ํด, ์๋ ์์ฑ๋ ํ ์คํธ๋ฅผ ์คํํด ๋ณด๊ฒ ์ต๋๋ค:
interoperability/bindgen/Android.bp:
rust_test {
name: "libbirthday_bindgen_test",
srcs: [":libbirthday_bindgen"],
crate_name: "libbirthday_bindgen_test",
test_suites: ["general-tests"],
auto_gen_config: true,
clippy_lints: "none", // ์์ฑ๋ ํ์ผ, ๋ฆฐํธ ์์
๊ฑด๋๋ฐ๊ธฐ
lints: "none",
}
atest libbirthday_bindgen_test
C์์ ๋ฌ์คํธ ํธ์ถ
๋ฌ์คํธ์์ ํ์ ๊ณผ ํจ์๋ฅผ C๋ก ๋ด๋ณด๋ด๋ ๊ฒ์ ๊ฐ๋จํฉ๋๋ค:
interoperability/rust/libanalyze/analyze.rs
//! Rust FFI ๋ฐ๋ชจ์ ๋๋ค. #![deny(improper_ctypes_definitions)] use std::os::raw::c_int; /// ์์น๋ฅผ ๋ถ์ํฉ๋๋ค. #[no_mangle] pub extern "C" fn analyze_numbers(x: c_int, y: c_int) { if x < y { println!("x({x})๊ฐ ๊ฐ์ฅ ์์ต๋๋ค."); } else { println!("y({y})๋ x({x})๋ณด๋ค ํด ์ ์์ต๋๋ค."); } }
interoperability/rust/libanalyze/analyze.h
#ifndef ANALYSE_H
#define ANALYSE_H
extern "C" {
void analyze_numbers(int x, int y);
}
#endif
interoperability/rust/libanalyze/Android.bp
rust_ffi {
name: "libanalyze_ffi",
crate_name: "analyze_ffi",
srcs: ["analyze.rs"],
include_dirs: ["."],
}
์ด์ ์ด ๋ฌ์คํธ ํจ์๋ฅผ C๋ฐ์ด๋๋ฆฌ์์ ํธ์ถํ ์ ์์ต๋๋ค:
interoperability/rust/analyze/main.c
#include "analyze.h"
int main() {
analyze_numbers(10, 20);
analyze_numbers(123, 123);
return 0;
}
interoperability/rust/analyze/Android.bp
cc_binary {
name: "analyze_numbers",
srcs: ["main.c"],
static_libs: ["libanalyze_ffi"],
}
๋น๋ํ๊ณ , ๊ฐ์ ๋๋ฐ์ด์ค์ ๋ฃ๊ณ , ์คํํฉ๋๋ค:
m analyze_numbers
adb push "$ANDROID_PRODUCT_OUT/system/bin/analyze_numbers" /data/local/tmp
adb shell /data/local/tmp/analyze_numbers
#[no_mangle]
์ ๋ฌ์คํธ์ ๋ค์ ๋งน๊ธ๋ง(name mangling)์ ๋นํ์ฑํํ๋ฏ๋ก ์ธ๋ถ๋ก ๋
ธ์ถ๋๋ ์ฌ๋ณผ์ ์ด๋ฆ์ ํจ์์ ์ด๋ฆ ๊ทธ๋๋ก๊ฐ ๋ฉ๋๋ค. ์ฌ๋ณผ ์ด๋ฆ์ ๋ฐ๊พธ๊ณ ์ถ๋ค๋ฉด #[export_name = "some_name"]
์ ์ฌ์ฉํฉ๋๋ค.
C++์์ ์ํธ์ด์ฉ์ฑ
CXX ํฌ๋ ์ดํธ๋ ๋ฌ์คํธ์ C++ ์ฌ์ด์ ์์ ํ ์ํธ์ด์ฉ์ฑ์ ๊ฐ๋ฅํ๊ฒ ํด์ค๋๋ค.
์ ์ฒด์ ์ธ ์ ๊ทผ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
ํ ์คํธ ๋ชจ๋
CXX๋ ๊ฐ ์ธ์ด์์ ๋ค๋ฅธ ์ธ์ด๋ก ๋
ธ์ถ๋๋ ํจ์ ์๋ช
์ ๊ดํ ์ค๋ช
์ ์ฌ์ฉํฉ๋๋ค. #[cxx::bridge]
์์ฑ ๋งคํฌ๋ก๋ก ์ฃผ์์ด ๋ฌ๋ฆฐ Rust ๋ชจ๋์์ extern ๋ธ๋ก์ ์ฌ์ฉํ์ฌ ์ด ์ค๋ช
์ ์ ๊ณตํฉ๋๋ค.
#[allow(unsafe_op_in_unsafe_fn)]
#[cxx::bridge(namespace = "org::blobstore")]
mod ffi {
// ๋ ์ธ์ด ๋ชจ๋์ ํ์๋๋ ํ๋๊ฐ ์๋ ๊ณต์ ๊ตฌ์กฐ์ฒด์
๋๋ค.
struct BlobMetadata {
size: usize,
tags: Vec<String>,
}
// C++์ ๋
ธ์ถ๋ Rust ํ์
๋ฐ ๋ฉ์๋ ์๊ทธ๋์ณ์
๋๋ค.
extern "Rust" {
type MultiBuf;
fn next_chunk(buf: &mut MultiBuf) -> &[u8];
}
// Rust์ ๋
ธ์ถ๋ C++ ํ์
๋ฐ ํจ์ ์๊ทธ๋์ณ์
๋๋ค.
unsafe extern "C++" {
include!("include/blobstore.h");
type BlobstoreClient;
fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
fn put(self: Pin<&mut BlobstoreClient>, parts: &mut MultiBuf) -> u64;
fn tag(self: Pin<&mut BlobstoreClient>, blobid: u64, tag: &str);
fn metadata(&self, blobid: u64) -> BlobMetadata;
}
}
- ๋ธ๋ฆฌ์ง๋ ์ผ๋ฐ์ ์ผ๋ก ํฌ๋ ์ดํธ ๋ด์
ffi
๋ชจ๋์ ์ ์ธ๋ฉ๋๋ค. - ๋ธ๋ฆฌ์ง ๋ชจ๋์์ ์ด๋ฃจ์ด์ง ์ ์ธ์ผ๋ก๋ถํฐ CXX๋ ์ผ์นํ๋ Rust ๋ฐ C++ ํ์ /ํจ์ ์ ์๋ฅผ ์์ฑํ์ฌ ์ด๋ฌํ ํญ๋ชฉ์ ๋ ์ธ์ด ๋ชจ๋์ ๋ ธ์ถํฉ๋๋ค.
- ์์ฑ๋ Rust ์ฝ๋๋ฅผ ๋ณด๋ ค๋ฉด cargo-expand๋ฅผ ์ฌ์ฉํ์ฌ ํ์ฅ๋ proc ๋งคํฌ๋ก๋ฅผ ํ์ธํ์ธ์. ๋๋ถ๋ถ์ ์์์๋
cargo expand ::ffi
๋ฅผ ์ฌ์ฉํ์ฌffi
๋ชจ๋๋ง ํ์ฅํฉ๋๋ค(Android ํ๋ก์ ํธ์๋ ์ ์ฉ๋์ง ์์). - ์์ฑ๋ C++ ์ฝ๋๋ฅผ ๋ณด๋ ค๋ฉด
target/cxxbridge
๋ฅผ ํ์ธํ์ธ์.
Rust Bridge Declarations
#[cxx::bridge]
mod ffi {
extern "Rust" {
type MyType; // ๋ถํฌ๋ช
ํ์
์
๋๋ค.
fn foo(&self); // `MyType`์ ๋ฉ์๋์
๋๋ค.
fn bar() -> Box<MyType>; // Free function
}
}
struct MyType(i32);
impl MyType {
fn foo(&self) {
println!("{}", self.0);
}
}
fn bar() -> Box<MyType> {
Box::new(MyType(123))
}
extern "Rust"
์ ์ ์ธ๋ ํญ๋ชฉ์ ์์ ๋ชจ๋์ ๋ฒ์ ๋ด์ ์๋ ํญ๋ชฉ์ ์ฐธ์กฐํฉ๋๋ค.- CXX ์ฝ๋ ์์ฑ๊ธฐ๋
extern "Rust"
์น์ ์ ์ฌ์ฉํ์ฌ ์์ํ๋ C++ ์ ์ธ์ด ํฌํจ๋ C++ ํค๋ ํ์ผ์ ์์ฑํฉ๋๋ค. ์์ฑ๋ ํค๋๋ ํ์ผ ํ์ฅ์๊ฐ .rs.h์ธ ๊ฒฝ์ฐ๋ฅผ ์ ์ธํ๊ณ ๋ธ๋ฆฌ์ง๊ฐ ํฌํจ๋ Rust ์์ค ํ์ผ๊ณผ ๋์ผํ ๊ฒฝ๋ก๋ฅผ ๊ฐ์ต๋๋ค.
์์ฑ๋ C++
#[cxx::bridge]
mod ffi {
// C++์ ๋
ธ์ถ๋ Rust ํ์
๋ฐ ๋ฉ์๋ ์๊ทธ๋์ณ์
๋๋ค.
extern "Rust" {
type MultiBuf;
fn next_chunk(buf: &mut MultiBuf) -> &[u8];
}
}
๊ทธ ๊ฒฐ๊ณผ๋ ๋๋ต ๋ค์๊ณผ ๊ฐ์ C++์ ๋๋ค.
struct MultiBuf final : public ::rust::Opaque {
~MultiBuf() = delete;
private:
friend ::rust::layout;
struct layout {
static ::std::size_t size() noexcept;
static ::std::size_t align() noexcept;
};
};
::rust::Slice<::std::uint8_t const> next_chunk(::org::blobstore::MultiBuf &buf) noexcept;
C++ ๋ธ๋ฆฌ์ง ์ ์ธ
#[cxx::bridge]
mod ffi {
// Rust์ ๋
ธ์ถ๋ C++ ํ์
๋ฐ ํจ์ ์๊ทธ๋์ณ์
๋๋ค.
unsafe extern "C++" {
include!("include/blobstore.h");
type BlobstoreClient;
fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
fn put(self: Pin<&mut BlobstoreClient>, parts: &mut MultiBuf) -> u64;
fn tag(self: Pin<&mut BlobstoreClient>, blobid: u64, tag: &str);
fn metadata(&self, blobid: u64) -> BlobMetadata;
}
}
๊ฒฐ๊ณผ๋ ๋๋ต ๋ค์๊ณผ ๊ฐ์ Rust์ ๋๋ค.
#[repr(C)]
pub struct BlobstoreClient {
_private: ::cxx::private::Opaque,
}
pub fn new_blobstore_client() -> ::cxx::UniquePtr<BlobstoreClient> {
extern "C" {
#[link_name = "org$blobstore$cxxbridge1$new_blobstore_client"]
fn __new_blobstore_client() -> *mut BlobstoreClient;
}
unsafe { ::cxx::UniquePtr::from_raw(__new_blobstore_client()) }
}
impl BlobstoreClient {
pub fn put(&self, parts: &mut MultiBuf) -> u64 {
extern "C" {
#[link_name = "org$blobstore$cxxbridge1$BlobstoreClient$put"]
fn __put(
_: &BlobstoreClient,
parts: *mut ::cxx::core::ffi::c_void,
) -> u64;
}
unsafe {
__put(self, parts as *mut MultiBuf as *mut ::cxx::core::ffi::c_void)
}
}
}
// ...
- ํ๋ก๊ทธ๋๋จธ๋ ์์ ์ด ์ ๋ ฅํ ์๊ทธ๋์ณ๊ฐ ์ ํํ๋ค๊ณ ๋ณด์ฅํ ํ์๊ฐ ์์ต๋๋ค. CXX๋ ์๊ทธ๋์ณ๊ฐC++์์ ์ ์ธ๋ ๊ฒ๊ณผ ์ ํํ ์ผ์นํ๋์ง๋ฅผ ์ฒดํฌํ๊ธฐ ์ํด ์ ์ ์ผ๋ก assertion์ ์คํํฉ๋๋ค.
unsafe extern
๋ธ๋ก์ ์ฌ์ฉํ๋ฉด Rust์์ ์์ ํ๊ฒ ํธ์ถํ ์ ์๋ C++ ํจ์๋ฅผ ์ ์ธํ ์ ์์ต๋๋ค.
๊ณต์ ํ์
#[cxx::bridge]
mod ffi {
#[derive(Clone, Debug, Hash)]
struct PlayingCard {
suit: Suit,
value: u8, // A=1, J=11, Q=12, K=13
}
enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
}
}
- C์ ๊ฐ์ (๋จ์) enum๋ง ์ง์๋ฉ๋๋ค.
- ๊ณต์ ํ์
์
#[derive()]
์๋ ์ ํ๋ ์์ ํธ๋ ์์ด ์ง์๋ฉ๋๋ค. C++ ์ฝ๋์ ๋ํด์๋ ์์ํ๋ ๊ธฐ๋ฅ์ด ์์ฑ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ดHash
๋ฅผ ํ์ํ๋ฉด ํด๋น C++ ํ์ ์ ๋ํstd::hash
๊ตฌํ๋ ์์ฑ๋ฉ๋๋ค.
๊ณต์ Enum
#[cxx::bridge]
mod ffi {
enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
}
}
์์ฑ๋ ๋ฌ์คํธ:
#![allow(unused)] fn main() { #[derive(Copy, Clone, PartialEq, Eq)] #[repr(transparent)] pub struct Suit { pub repr: u8, } #[allow(non_upper_case_globals)] impl Suit { pub const Clubs: Self = Suit { repr: 0 }; pub const Diamonds: Self = Suit { repr: 1 }; pub const Hearts: Self = Suit { repr: 2 }; pub const Spades: Self = Suit { repr: 3 }; } }
Generated C++:
enum class Suit : uint8_t {
Clubs = 0,
Diamonds = 1,
Hearts = 2,
Spades = 3,
};
- Rust ์ธก์์, ๊ณต์ ๋ enum์ ๊ดํด ์์ฑ๋ ์ฝ๋๋ ์ค์ ๋ก ์ซ์ ๊ฐ์ ๋ํํ๋ ๊ตฌ์กฐ์ฒด์ ๋๋ค. ์ด๋ enum ํด๋์ค๊ฐ ๋์ด๋ ๋ชจ๋ ๋ณํ๊ณผ ๋ค๋ฅธ ๊ฐ์ ๋ณด์ ํ๋ ๊ฒ์ด C++์์ UB๊ฐ ์๋๊ณ Rust ํํ๋ ๋์ผํ ๋์์ ๊ฐ์ ธ์ผ ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ค๋ฅ์ฒ๋ฆฌ
#[cxx::bridge]
mod ffi {
extern "Rust" {
fn fallible(depth: usize) -> Result<String>;
}
}
fn fallible(depth: usize) -> anyhow::Result<String> {
if depth == 0 {
return Err(anyhow::Error::msg("fallible1์ ๊น์ด > 0 ํ์"));
}
Ok("์๋ฃ!".into())
}
Result
๋ฅผ ๋ฐํํ๋ Rust ํจ์๋ C++ ์ธก์์ ์์ธ๋ก ๋ณํ๋ฉ๋๋ค.- ๋ฐ์ํ๋ ์์ธ๋ ํญ์
rust::Error
ํ์ ์ด๋ฉฐ ์ฃผ๋ก ์ค๋ฅ ๋ฉ์์ง ๋ฌธ์์ด์ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ ๋ ธ์ถํฉ๋๋ค. ์ค๋ฅ ๋ฉ์์ง๋ ์ค๋ฅ ํ์ ์Display
impl์์ ๊ฐ์ ธ์ต๋๋ค. - Rust์์ C++๋ก ํจ๋์ด ํด์ ๋๋ฉด ํ๋ก์ธ์ค๊ฐ ์ฆ์ ์ข ๋ฃ๋ฉ๋๋ค.
์ค๋ฅ์ฒ๋ฆฌ
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("example/include/example.h");
fn fallible(depth: usize) -> Result<String>;
}
}
fn main() {
if let Err(err) = ffi::fallible(99) {
eprintln!("์ค๋ฅ: {}", err);
process::exit(1);
}
}
Result
๋ฅผ ๋ฐํํ๋๋ก ์ ์ธ๋ C++ ํจ์๋ C++ ์ธก์์ ๋ฐ์ํ ์์ธ๋ฅผ ํฌ์ฐฉํ๊ณ ์ด๋ฅผ ํธ์ถ Rust ํจ์์Err
๊ฐ์ผ๋ก ๋ฐํํฉ๋๋ค.Result
๋ฅผ ๋ฐํํ๋๋ก CXX ๋ธ๋ฆฌ์ง์์ ์ ์ธํ์ง ์์ extern โC++โ ํจ์์์ ์์ธ๊ฐ ๋ฐ์ํ๋ฉด ํ๋ก๊ทธ๋จ์ C++์std::terminate
๋ฅผ ํธ์ถํฉ๋๋ค. ์ด ๋์์noexcept
C++ ํจ์๋ฅผ ํตํด ๋ฐ์ํ๋ ๋์ผํ ์์ธ์ ๊ฐ์ต๋๋ค.
์ถ๊ฐ ํ์
Rust Type | C++ Type |
---|---|
String | rust::String |
&str | rust::Str |
CxxString | std::string |
&[T] /&mut [T] | rust::Slice |
Box<T> | rust::Box<T> |
UniquePtr<T> | std::unique_ptr<T> |
Vec<T> | rust::Vec<T> |
CxxVector<T> | std::vector<T> |
- ์ด๋ฌํ ํ์ ์ ๊ณต์ ๊ตฌ์กฐ์ฒด์ ํ๋์ extern ํจ์์ ์ธ์ ๋ฐ ๋ฐํ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- Rust์
String
์std::string
์ ์ง์ ๋งคํ๋์ง ์์ต๋๋ค. ์ฌ๊ธฐ์๋ ๋ค์๊ณผ ๊ฐ์ ๋ช ๊ฐ์ง ์ด์ ๊ฐ ์์ต๋๋ค.std::string
์String
์ ํ์ํ UTF-8 ๋ถ๋ณ๊ฐ์ ์ ์งํ์ง ์์ต๋๋ค.- ๋ ํ์ ์ ๋ฉ๋ชจ๋ฆฌ์ ์๋ก ๋ค๋ฅธ ๋ ์ด์์์ ๊ฐ์ง๊ณ ์์ผ๋ฏ๋ก ์ธ์ด ๊ฐ์ ์ง์ ์ ๋ฌ๋ ์ ์์ต๋๋ค.
std::string
์๋ Rust์ ์ด๋ ์๋ฏธ ์ฒด๊ณ์ ์ผ์นํ์ง ์๋ ์ด๋ ์์ฑ์๊ฐ ํ์ํ๋ฏ๋กstd::string
์ ๊ฐ์ผ๋ก Rust์ ์ ๋ฌํ ์ ์์ต๋๋ค.
Building in Android
cc_library_static
์ ๋ง๋ค์ด CXX์์ ์์ฑ๋ ํค๋์ ์์ค ํ์ผ์ ๋น๋กฏํ์ฌ C++ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋น๋ํฉ๋๋ค.
cc_library_static {
name: "libcxx_test_cpp",
srcs: ["cxx_test.cpp"],
generated_headers: [
"cxx-bridge-header",
"libcxx_test_bridge_header"
],
generated_sources: ["libcxx_test_bridge_code"],
}
libcxx_test_bridge_header
๋ฐlibcxx_test_bridge_code
๊ฐ CXX์์ ์์ฑ๋ C++ ๋ฐ์ธ๋ฉ์ ์ข ์ ํญ๋ชฉ์ด๋ผ๋ ์ ์ ์ง์ ํฉ๋๋ค. ๋ค์ ์ฌ๋ผ์ด๋์์ ์ค์ ๋ฐฉ๋ฒ์ ์์๋ด ๋๋ค.- ์ผ๋ฐ์ ์ธ CXX ์ ์๋ฅผ ๊ฐ์ ธ์ค๋ ค๋ฉด
cxx-bridge-header
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ฌ์ฉํด์ผ ํฉ๋๋ค. - Android์์ CXX๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๊ดํ ์ ์ฒด ๋ฌธ์๋ Android ๋ฌธ์์์ ํ์ธํ ์ ์์ต๋๋ค. ํ์๋ค์ด ๋์ค์ ์ด ์๋ด๋ฅผ ์ด๋์์ ๋ค์ ์ฐพ์ ์ ์๋์ง ์ ์ ์๋๋ก ์ด ๋งํฌ๋ฅผ ์์ ์ ๊ณต์ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
Building in Android
๋ ๊ฐ์ genrule์ ๋ง๋ญ๋๋ค. ํ๋๋ CXX ํค๋๋ฅผ ์์ฑํ๊ณ ๋ค๋ฅธ ํ๋๋ CXX ์์ค ํ์ผ์ ์์ฑํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ cc_library_static
์ ๋ํ ์
๋ ฅ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
// lib.rs์ Rust ๋ด๋ณด๋ด๊ธฐ ํจ์์ ๋ํ
// C++ ๋ฐ์ธ๋ฉ์ด ํฌํจ๋ C++ ํค๋๋ฅผ ์์ฑํฉ๋๋ค.
genrule {
name: "libcxx_test_bridge_header",
tools: ["cxxbridge"],
cmd: "$(location cxxbridge) $(in) --header > $(out)",
srcs: ["lib.rs"],
out: ["lib.rs.h"],
}
// Rust๊ฐ ํธ์ถํ๋ C++ ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค.
genrule {
name: "libcxx_test_bridge_code",
tools: ["cxxbridge"],
cmd: "$(location cxxbridge) $(in) > $(out)",
srcs: ["lib.rs"],
out: ["lib.rs.cc"],
}
cxxbridge
๋๊ตฌ๋ C++ ์ธก์ ๋ธ๋ฆฌ์ง ๋ชจ๋์ ์์ฑํ๋ ๋ ๋ฆฝํ ๋๊ตฌ์ ๋๋ค. ์ด ๋๊ตฌ๋ Android์ ํฌํจ๋์ด ์์ผ๋ฉฐ Soong ๋๊ตฌ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.- ์ผ๋ฐ์ ์ผ๋ก Rust ์์ค ํ์ผ์ด
lib.rs
์ธ ๊ฒฝ์ฐ ํค๋ ํ์ผ์ ์ด๋ฆ์lib.rs.h
์ด๊ณ ์์ค ํ์ผ์ ์ด๋ฆ์lib.rs.cc
๊ฐ ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ ์ด ์ด๋ฆ ์ง์ ๊ท์น์ ๊ฐ์ ๋์ง ์์ต๋๋ค.
Building in Android
libcxx
๋ฐ cc_library_static
์ ์ข
์๋๋ rust_binary
๋ฅผ ์์ฑํฉ๋๋ค.
rust_binary {
name: "cxx_test",
srcs: ["lib.rs"],
rustlibs: ["libcxx"],
static_libs: ["libcxx_test_cpp"],
}
Java์์ ์ํธ์ด์ฉ์ฑ
์๋ฐ๋ Java Native Interface(JNI)๋ฅผ ํตํด ๊ณต์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ก๋ํ ์ ์์ต๋๋ค. jni
ํฌ๋ ์ดํธ๋ฅผ ์ฌ์ฉํ์ฌ JNI ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
๋จผ์ , ์๋ฐ๋ก ๋ด๋ณด๋ผ ๋ฌ์คํธ ํจ์๋ฅผ ์์ฑํฉ๋๋ค:
interoperability/java/src/lib.rs:
#![allow(unused)] fn main() { //! Rust <-> Java FFI ๋ฐ๋ชจ์ ๋๋ค. use jni::objects::{JClass, JString}; use jni::sys::jstring; use jni::JNIEnv; /// HelloWorld::hello method implementation. #[no_mangle] pub extern "system" fn Java_HelloWorld_hello( env: JNIEnv, _class: JClass, name: JString, ) -> jstring { let input: String = env.get_string(name).unwrap().into(); let greeting = format!("Hello, {input}!"); let output = env.new_string(greeting).unwrap(); output.into_inner() } }
interoperability/java/Android.bp:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
rustlibs: ["libjni"],
}
We then call this function from Java:
interoperability/java/HelloWorld.java:
class HelloWorld {
private static native String hello(String name);
static {
System.loadLibrary("hello_jni");
}
public static void main(String[] args) {
String output = HelloWorld.hello("Alice");
System.out.println(output);
}
}
interoperability/java/Android.bp:
java_binary {
name: "helloworld_jni",
srcs: ["HelloWorld.java"],
main_class: "HelloWorld",
required: ["libhello_jni"],
}
๋ง์ง๋ง์ผ๋ก ๋ฐ์ด๋๋ฆฌ๋ฅผ ๋น๋, ์ฑํฌ, ์คํํฉ๋๋ค:
m helloworld_jni
adb sync # requires adb root && adb remount
adb shell /system/bin/helloworld_jni
์ฐ์ต๋ฌธ์
This is a group exercise: We will look at one of the projects you work with and try to integrate some Rust into it. Some suggestions:
-
๋น์ ์ AIDL์๋น์ค๋ฅผ ๋ฌ์คํธ ํด๋ผ์ด์ธํธ์์ ํธ์ถํด๋ด ๋๋ค.
-
๋น์ ์ ํ๋ก์ ํธ์ ํจ์๋ฅผ ๋ฌ์คํธ๋ก ์ฎ๊ธฐ๊ณ ํธ์ถํด๋ด ๋๋ค.
์ด ์ฐ์ต๋ฌธ์ ๋ ์ด๋ ค์๊ธฐ ๋๋ฌธ์ ํด๋ต์ด ์ ๊ณต๋์ง ์์ต๋๋ค. ํด๋์ค์์ ์ ์ถ๋ ์ฝ๋์ ์์กดํฉ๋๋ค.
Welcome to Rust in Chromium
Rust๋ Chromium์ ์๋ ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํด ์ง์๋๋ฉฐ, Rust์ ๊ธฐ์กด Chromium C++ ์ฝ๋ ๊ฐ์ ์ฐ๊ฒฐํ๋ ํผ์คํธ ํํฐ ๊ธ๋ฃจ ์ฝ๋๋ฅผ ํฌํจํฉ๋๋ค.
์ค๋์ Rust๋ฅผ ํธ์ถํ์ฌ ๋ฌธ์์ด๋ก ์ฐ์ค๊ฝ์ค๋ฌ์ด ์์ ์ ํด ๋ณด๊ฒ ์ต๋๋ค. ์ฌ์ฉ์์๊ฒ UTF8 ๋ฌธ์์ด์ ํ์ํ๋ ์ฝ๋ ๋ถ๋ถ์ด ์๋ ๊ฒฝ์ฐ, ์ธ๊ธ๋๋ ์ ํํ ๋ถ๋ถ ๋์ ์ฝ๋๋ฒ ์ด์ค์์ ์ด ๋ ์ํผ๋ฅผ ๋ฐ๋ฅด์๋ฉด ๋ฉ๋๋ค.
์ค์น
Chromium์ ๋น๋ํ๊ณ ์คํํ ์ ์๋์ง ํ์ธํฉ๋๋ค. ์ฝ๋๊ฐ ๋น๊ต์ ์ต์ ์ด๋ผ๋ฉด(์ปค๋ฐ ์์น 1223636 ์ดํ, 2023๋ 11์์ ํด๋น) ์ด๋ค ํ๋ซํผ์ด๋ ๋น๋ ํ๋๊ทธ ์งํฉ๋ ๊ด์ฐฎ์ต๋๋ค.
gn gen out/Debug
autoninja -C out/Debug chrome
out/Debug/chrome # or on Mac, out/Debug/Chromium.app/Contents/MacOS/Chromium
๊ฐ๋ฐ ์๋๋ฅผ ๋น ๋ฅด๊ฒ ํ๊ณ ์ถ๋ค๋ฉด ๋๋ฒ๊ทธ ๋น๋๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ด๊ฒ ๊ธฐ๋ณธ๊ฐ์ ๋๋ค.
์์ง ๋น๋ํ์ง ์์๋ค๋ฉด Chromium ๋น๋ ๋ฐฉ๋ฒ์ ์ฐธ๊ณ ํ์ธ์. ์ฃผ์: Chromium ๋น๋ ํ๊ฒฝ ์ ์ ์ ์๊ฐ์ด ๊ฑธ๋ฆฝ๋๋ค.
๋ํ Visual Studio Code๊ฐ ์ค์น๋์ด ์๋ ๊ฒ์ด ์ข์ต๋๋ค.
About the exercises
๊ณผ์ ์ ์ด ๋ถ๋ถ์๋ ์๋ก ์ฐ๊ณ๋๋ ์ผ๋ จ์ ์ฐ์ต๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๋ง์ง๋ง์ ํ๊บผ๋ฒ์ ํ์ง ์๊ณ ๊ณผ์ ์ ๋ฐ์ ์ฐ์ต๋ฌธ์ ๊ฐ ํฉ์ด์ ธ ์์ต๋๋ค. ํน์ ๋ถ๋ถ์ ์๋ฃํ ์๊ฐ์ด ์๋๋ผ๋ ๊ฑฑ์ ํ์ง ๋ง์ธ์. ๋ค์๋ฒ์ ๋ฐ๋ผ์ก์ ์ ์์ต๋๋ค.
Chromium ๋ฐ Cargo ์ํ๊ณ ๋น๊ต
Rust ์ปค๋ฎค๋ํฐ๋ ์ผ๋ฐ์ ์ผ๋ก crates.io์ cargo
๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค. Chromium์ ์ธ์ฌํ๊ฒ ์ ๋ณ๋ ์์กด์ฑ๋ค๊ณผ ํจ๊ป gn
, ninja
์ ์ด์ฉํ์ฌ ๋น๋๋ฉ๋๋ค.
Rust๋ก ์ฝ๋๋ฅผ ์์ฑํ ๋ ์ ํํ ์ ์๋ ์ต์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
//build/rust/*.gni
์ ํ ํ๋ฆฟ(์: ๋์ค์ ๋ค๋ฃฐrust_static_library
)์ ํตํดgn
๋ฐninja
๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด๋ Chromium์ ๊ฐ์ฌ ๋๊ตฌ ๋ชจ์ ๋ฐ ํฌ๋ ์ดํธ๋ฅผ ์ฌ์ฉํฉ๋๋ค.cargo
๋ฅผ ์ฌ์ฉํ๋ [Chromium์ ๊ฐ์ฌ ๋๊ตฌ ๋ชจ์ ๋ฐ ํฌ๋ ์ดํธ๋ก ์ ํ]ํฉ๋๋ค(https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/rust.md#Using-cargo).cargo
๋ฅผ ์ฌ์ฉํ์ฌ ๋๊ตฌ ๋ชจ์ ๋๋ ์ธํฐ๋ท์์ ๋ค์ด๋ก๋ํ ํฌ๋ ์ดํธ๋ฅผ ์ ๋ขฐํฉ๋๋ค.
์ฌ๊ธฐ์๋ถํฐ๋ gn
๊ณผ ninja
์ ์ง์คํ ๊ฒ๋๋ค. ๊ทธ ๋๊ตฌ๋ค์ ์จ์ผ๋ง Chromium ๋ธ๋ผ์ฐ์ ์ Rust ์ฝ๋๋ฅผ๋น๋ํด์ ๋ฃ์ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค. ์ด์ ๋์์, Cargo๋ Rust ์ํ๊ณ์์ ์ค์ํ ๋ถ๋ถ์ด๋ฏ๋ก Cargo์๋ ์ต์ํด์ผ ํฉ๋๋ค.
Mini exercise
์๊ท๋ชจ ๊ทธ๋ฃน์ผ๋ก ๋๋ ๋ค์:
- โcargoโ๊ฐ ์ ๋ฆฌํ ์ ์๋ ์๋๋ฆฌ์ค๋ฅผ ๋ธ๋ ์ธ์คํ ๋ฐํ๊ณ ์ด๋ฌํ ์๋๋ฆฌ์ค์ ์ํ ํ๋กํ์ ํ๊ฐํฉ๋๋ค.
gn
๋ฐninja
, ์คํ๋ผ์ธcargo
๋ฑ์ ์ฌ์ฉํ ๋ ์ ๋ขฐํด์ผ ํ๋ ๋๊ตฌ, ๋ผ์ด๋ธ๋ฌ๋ฆฌ, ์ฌ์ฉ์ ๊ทธ๋ฃน์ ๋ ผ์ํฉ๋๋ค.
ํ์๋ค์๊ฒ ์ฐ์ต๋ฌธ์ ๋ฅผ ์๋ฃํ๊ธฐ ์ ์ ๋ฐํ์ ๋ ธํธ๋ฅผ ์ฟ๋ณด์ง ๋ง๋ผ๊ณ ํฉ๋๋ค. ๊ณผ์ ์ ์๊ฐํ๋ ํ์๋ค์ด ์ค์ ๋ก ํจ๊ป ์๋ค๊ณ ๊ฐ์ ํ๊ณ 3~4๋ช ์ผ๋ก ๊ตฌ์ฑ๋ ์๊ทธ๋ฃน์ผ๋ก ํ ๋ก ํ๋๋ก ํฉ๋๋ค.
์ฐ์ต๋ฌธ์ ์ ์ฒซ ๋ถ๋ถ๊ณผ ๊ด๋ จ๋ ์ฐธ๊ณ ์ฌํญ/ํํธ(โCargo๊ฐ ์ด์ ์ ์ ๊ณตํ ์ ์๋ ์๋๋ฆฌ์คโ):
-
๋๊ตฌ๋ฅผ ์์ฑํ๊ฑฐ๋ Chromium ์ผ๋ถ์ ํ๋กํ ํ์ ์ ์ ์ํ ๋ crates.io ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํ๋ถํ ์ํ๊ณ์ ์ก์ธ์คํ ์ ์๋ค๋ ๊ฒ์ ๋ฉ์ง ์ผ์ ๋๋ค. ๊ฑฐ์ ๋ชจ๋ ๊ฒ์ ์ํด ํฌ๋ ์ดํธ๊ฐ ์์ผ๋ฉฐ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉํ๊ธฐ ๋งค์ฐ ์ข์ต๋๋ค. ๋ช ๋ น์ค ํ์ฑ์ ์ํ
clap
, ๋ค์ํ ํ์ ๊ฐ ์ง๋ ฌํ/์ญ์ง๋ ฌํ๋ฅผ ์ํserde
, ๋ฐ๋ณต์๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์itertools
๋ฑ์ด ์์ต๋๋ค.cargo
๋ฅผ ์ฌ์ฉํ๋ฉด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฝ๊ฒ ์ฌ์ฉํด ๋ณผ ์ ์์ต๋๋ค.Cargo.toml
์ ํ ์ค์ ์ถ๊ฐํ๊ณ ์ฝ๋ ์์ฑ์ ์์ํ๋ฉด ๋ฉ๋๋ค.perl
์ด ์ธ๊ธฐ๋ฅผ ์ป๊ฒ ๋ ๋ฐ CPAN์ด ์ด๋ค ๋์์ด ๋์๋์ง ๋น๊ตํด ๋ณด๋ ๊ฒ๋ ์ข์ต๋๋ค.python
+pip
์ ๋น๊ตํด๋ ๋ฉ๋๋ค.
-
ํต์ฌ Rust ๋๊ตฌ(์: ๋์ดํ๋ฆฌ, ์ต์ ์์ ํ ๋ฒ์ , ์ด์ ์์ ํ ๋ฒ์ ์์ ์๋ํด์ผ ํ๋ ํฌ๋ ์ดํธ๋ฅผ ํ ์คํธํ ๋
rustup
์ ์ฌ์ฉํ์ฌ ๋ค๋ฅธrustc
๋ฒ์ ์ผ๋ก ์ ํ)๋ฟ๋ง ์๋๋ผ ์๋ ํํฐ ๋๊ตฌ์ ์ํ๊ณ(์: Mozilla๋ ๋ณด์ ๊ฐ์ฌ ๊ฐ์ํ ๋ฐ ๊ณต์ ๋ฅผ ์ํดcargo vet
์ ๊ณต,criterion
ํฌ๋ ์ดํธ๋ ๊ฐ์ํ๋ ๋ฒค์น๋งํฌ ์คํ ๋ฐฉ๋ฒ์ ์ ๊ณตํจ)๋ฅผ ํตํด์๋ ๊ฐ๋ฐ ํ๊ฒฝ์ด ๊ฐ์ ๋ฉ๋๋ค.cargo
๋ฅผ ์ฌ์ฉํ๋ฉดcargo install --locked cargo-vet
๋ฅผ ํตํด ๋๊ตฌ๋ฅผ ์ฝ๊ฒ ์ถ๊ฐํ ์ ์์ต๋๋ค.- Chrome ํ์ฅ ํ๋ก๊ทธ๋จ ๋๋ VScode ํ์ฅ ํ๋ก๊ทธ๋จ๊ณผ ๋น๊ตํด ๋ณด๋ ๊ฒ์ด ์ข์ต๋๋ค.
-
cargo
๊ฐ ์ ํฉํ ์ ์๋ ํ๋ก์ ํธ์ ๊ด๋ฒ์ํ๊ณ ์ผ๋ฐ์ ์ธ ์:- ๋๋๊ฒ๋ ์ ๊ณ์์ Rust๊ฐ ๋ช ๋ น์ค ๋๊ตฌ ์์ฑ์ผ๋ก ์ ์ ์ธ๊ธฐ๋ฅผ ์ป๊ณ ์์ต๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ค์ํจ๊ณผ ์ฌ์ฉํธ์์ฑ์ Python๊ณผ ์ ์ฌํ์ง๋ง ํ๋ถํ ํ์ ์์คํ ๋๋ถ์ ๋ ์์ ํ๊ณ ๋ ๋น ๋ฅด๊ฒ ์คํ๋ฉ๋๋ค(์ธํฐํ๋ฆฌํธ ์ธ์ด๊ฐ ์๋ ์ปดํ์ผ๋ ์ธ์ด๋ก).
- Rust ์ํ๊ณ์ ์ฐธ์ฌํ๋ ค๋ฉด Cargo์ ๊ฐ์ ํ์ค Rust ๋๊ตฌ๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ์ธ๋ถ ๊ธฐ์ฌ๋ฅผ ์ํ๊ณ Chromium ์ธ๋ถ(์: Bazel ๋๋ Android/Soong ๋น๋ ํ๊ฒฝ)์์ ์ฌ์ฉํ๊ณ ์ ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ Cargo๋ฅผ ์ฌ์ฉํด์ผ ํ ์ ์์ต๋๋ค.
-
cargo
\ ๊ธฐ๋ฐ Chromium ๊ด๋ จ ํ๋ก์ ํธ์ ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.serde_json_lenient
(Google์ ๋ค๋ฅธ ๋ถ๋ถ์์ ์คํํ์ฌ ์ฑ๋ฅ์ด ๊ฐ์ ๋ PR์ด ๋์ด)font-types
์ ๊ฐ์ ๊ธ๊ผด ๋ผ์ด๋ธ๋ฌ๋ฆฌgnrt
๋๊ตฌ(๊ณผ์ ํ๋ฐ๋ถ์์ ์ค๋ช )๋ ๋ช ๋ น์ค ํ์ฑ์ ๊ฒฝ์ฐclap
, ๊ตฌ์ฑ ํ์ผ์ ๊ฒฝ์ฐtoml
์ ์ข ์๋ฉ๋๋ค.- ์ฃผ์: ์ฌ๊ธฐ์
cargo
๋ฅผ ์ฌ์ฉํ๋ ์ ์ผํ ์ด์ ๋ Rust ๋๊ตฌ ๋ชจ์์ ๋น๋ํ ๋ Rust ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋น๋ํ๊ณ ๋ถํธ์คํธ๋ฉํ๋ ๊ฒฝ์ฐgn
์ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. run_gnrt.py
๋ Chromium์cargo
๋ฐrustc
์ฌ๋ณธ์ ์ฌ์ฉํฉ๋๋ค.gnrt
๋ ์ธํฐ๋ท์์ ๋ค์ด๋ก๋ํ ์๋ ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ข ์๋๋ฉฐrun_gnrt.py
๋cargo
์Cargo.lock
์ ํตํด--locked
์ฝํ ์ธ ๋ง ํ์ฉ๋๋ค๊ณ ํฉ๋๋ค.
- ์ฃผ์: ์ฌ๊ธฐ์
ํ์์ ๋ค์ ํญ๋ชฉ์ ์์์ ๋๋ ๋ช ์์ ์ผ๋ก ์ ๋ขฐํ ์ ์๋ ๊ฒ์ผ๋ก ์๋ณํ ์ ์์ต๋๋ค.
rustc
(Rust ์ปดํ์ผ๋ฌ)๋ ์ฐจ๋ก๋ก LLVM ๋ผ์ด๋ธ๋ฌ๋ฆฌ, Clang ์ปดํ์ผ๋ฌ,rustc
์์ค(GitHub์์ ๊ฐ์ ธ์ด, Rust ์ปดํ์ผ๋ฌํ์์ ๊ฒํ ), ๋ถํธ์คํธ๋ฉ์ ์ํด ๋ค์ด๋ก๋ํ ๋ฐ์ด๋๋ฆฌ Rust ์ปดํ์ผ๋ฌ์ ์ข ์๋ฉ๋๋ค.rustup
(rustup
์ https://github.com/rust-lang/ ์กฐ์ง ์ฐํ์์ ๊ฐ๋ฐ๋์์ผ๋ฉฐrustc
์ ๋์ผํจ)cargo
,rustfmt
๋ฑ- ๋ค์ํ ๋ด๋ถ ์ธํ๋ผ(โrustcโ๋ฅผ ๋น๋ํ๋ ๋ด, ์ฌ์ ๋น๋๋ ๋๊ตฌ ๋ชจ์์ Chromium ์์ง๋์ด์๊ฒ ๋ฐฐํฌํ๊ธฐ ์ํ ์์คํ ๋ฑ)
cargo audit
,cargo vet
๋ฑ๊ณผ ๊ฐ์ Cargo ๋๊ตฌ//third_party/rust
์ ๊ณต๊ธ๋๋ Rust ๋ผ์ด๋ธ๋ฌ๋ฆฌ(security@chromium.org์์ ๊ฐ์ฌ)- ๊ธฐํ Rust ๋ผ์ด๋ธ๋ฌ๋ฆฌ(์ผ๋ถ๋ ํ์์์ฅ์ฉ, ์ผ๋ถ๋ ๋งค์ฐ ์ธ๊ธฐ ์์ผ๋ฉฐ ํํ ์ฌ์ฉ๋จ)
Chromium Rust ์ ์ฑ
Chromium์์๋ ์์ง ํผ์คํธ ํํฐ Rust๋ฅผ ํ์ฉํ์ง ์์ต๋๋ค. ๋จ, Chromium์ Area Tech Leads์์ ์น์ธํ ๋๋ฌธ ๊ฒฝ์ฐ๋ ์์ธ์ ๋๋ค.
์๋ ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ดํ Chromium์ ์ ์ฑ ์ ์ฌ๊ธฐ์ ์ค๋ช ๋์ด ์์ต๋๋ค. Rust๋ ์ฑ๋ฅ์ด๋ ๋ณด์์ ์ํด ์ต์์ ์ต์ ์ธ ๊ฒฝ์ฐ ๋ฑ ๋ค์ํ ์ํฉ์์ ์๋ ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํ์ฉ๋ฉ๋๋ค.
C/C++ API๋ฅผ ์ง์ ๋ ธ์ถํ๋ Rust ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๊ทน์์์ด๋ฏ๋ก ์ด๋ฌํ ๊ฑฐ์ ๋ชจ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์๋ ์๋์ ํผ์คํธ ํํฐ ๊ธ๋ฃจ ์ฝ๋๊ฐ ํ์ํฉ๋๋ค.
ํน์ ์๋ ํํฐ ํฌ๋ ์ดํธ์ ํผ์คํธ ํํฐ Rust ๊ธ๋ฃจ ์ฝ๋๋ ์ผ๋ฐ์ ์ผ๋ก
third_party/rust/<crate>/<version>/wrapper
์ ๋ณด๊ดํด์ผ ํฉ๋๋ค.
๋ฐ๋ผ์ ์ค๋ ๊ณผ์ ์์๋ ๋ค์๊ณผ ๊ฐ์ ๋ด์ฉ์ ์ค์ ์ ์ผ๋ก ๋ค๋ฃน๋๋ค.
- ์๋ ํํฐ Rust ๋ผ์ด๋ธ๋ฌ๋ฆฌ(โcratesโ) ๊ฐ์ ธ์ค๊ธฐ
- Chromium C++์์ ์ด๋ฌํ ํฌ๋ ์ดํธ๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ๊ธ๋ฃจ ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค.
์๊ฐ์ด ์ง๋๋ฉด์ ์ด ์ ์ฑ ์ด ๋ณ๊ฒฝ๋๋ฉด ๊ต์ก ๊ณผ์ ๋ ์ด์ ๋ง๊ฒ ๋ณ๊ฒฝ๋ฉ๋๋ค.
๋น๋ ๊ท์น
Rust ์ฝ๋๋ ์ผ๋ฐ์ ์ผ๋ก cargo
๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋๋ฉ๋๋ค. Chromium์ ํจ์จ์ฑ์ ์ํด gn
๋ฐ ninja
๋ก ๋น๋๋ฉ๋๋ค. ์ ์ ๊ท์น์ ์ต๋ ๋์ ๋ก๋๋ฅผ ํ์ฉํฉ๋๋ค. Rust๋ ์์ธ๋ ์๋๋๋ค.
Chromium์ Rust ์ฝ๋ ์ถ๊ฐ
์ผ๋ถ ๊ธฐ์กด Chromium BUILD.gn
ํ์ผ์์ rust_static_library
๋ฅผ ์ ์ธํฉ๋๋ค.
import("//build/rust/rust_static_library.gni")
rust_static_library("my_rust_lib") {
crate_root = "lib.rs"
sources = [ "lib.rs" ]
}
๋ค๋ฅธ Rust ํ๊ฒ์๋ deps
๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. ๋์ค์ ์๋ ํํฐ ์ฝ๋์ ์์กดํ๊ธฐ ์ํด ์ด๋ฅผ ์ฌ์ฉํฉ๋๋ค.
ํฌ๋ ์ดํธ ๋ฃจํธ ๋ฐ ์์ค ์ ์ฒด ๋ชฉ๋ก _๋ ๋ค_๋ฅผ ์ง์ ํด์ผ ํฉ๋๋ค. crate_root
๋ ์ปดํ์ผ ๋จ์์ ๋ฃจํธ ํ์ผ(์ผ๋ฐ์ ์ผ๋ก lib.rs
)์ ๋ํ๋ด๋ Rust ์ปดํ์ผ๋ฌ์ ์ ๊ณต๋๋ ํ์ผ์
๋๋ค. sources
๋ ์ฌ๋น๋๊ฐ ํ์ํ ์์ ์ ๊ฒฐ์ ํ๊ธฐ ์ํด ninja
์ ํ์ํ ๋ชจ๋ ์์ค ํ์ผ์ ์ ์ฒด ๋ชฉ๋ก์
๋๋ค.
(Rust์์๋ ํฌ๋ ์ดํธ ์ ์ฒด๊ฐ ์ปดํ์ผ ๋จ์์ด๋ฏ๋ก Rust source_set
์ ๊ฐ์ ๊ฒ์ ์์ต๋๋ค. static_library
๊ฐ ์ต์ ๋จ์์
๋๋ค.)
ํ์๋ค์ gn์ ๋ด์ฅ Rust ์ ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ง์์ ์ฌ์ฉํ๋ ๋์ gn ํ ํ๋ฆฟ์ด ํ์ํ ์ด์ ๋ฅผ ๊ถ๊ธํดํ ์ ์์ต๋๋ค. ๋๋ต์ ์ด ํ ํ๋ฆฟ์ด CXX ์ํธ ์ด์ฉ์ฑ, Rust ๊ธฐ๋ฅ, ๋จ์ ํ ์คํธ๋ฅผ ์ง์ํ๋ค๋ ๊ฒ์ ๋๋ค. ์ด ์ค ์ผ๋ถ๋ ๋์ค์ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
unsafe
Rust ์ฝ๋ ํฌํจ
rust_static_library
์๋ ์์ ํ์ง ์์ Rust ์ฝ๋๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ธ์ง๋์ด ์์ผ๋ฏ๋ก ์ปดํ์ผ๋์ง ์์ต๋๋ค. ์์ ํ์ง ์์ Rust ์ฝ๋๊ฐ ํ์ํ๋ฉด gn ํ๊ฒ์ allow_unsafe = true
๋ฅผ ์ถ๊ฐํ์ธ์. (์ด ๊ณผ์ ์ ํ๋ฐ๋ถ์ ์ด๊ฒ์ด ํ์ํ ์ํฉ์ ์ดํด๋ด
๋๋ค.)
import("//build/rust/rust_static_library.gni")
rust_static_library("my_rust_lib") {
crate_root = "lib.rs"
sources = [
"lib.rs",
"hippopotamus.rs"
]
allow_unsafe = true
}
Chromium C++์ Rust ์ฝ๋์ ์์กด
์์ ํ๊ฒ์ ์ผ๋ถ Chromium C++ ํ๊ฒ์ deps
์ ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
import("//build/rust/rust_static_library.gni")
rust_static_library("my_rust_lib") {
crate_root = "lib.rs"
sources = [ "lib.rs" ]
}
# or source_set, static_library etc.
component("preexisting_cpp") {
deps = [ ":my_rust_lib" ]
}
Visual Studio Code
Rust ์ฝ๋์์๋ ํ์ ์ด ์๋ต๋๋ฏ๋ก ์ฐ์ํ IDE๊ฐ C++๋ณด๋ค ํจ์ฌ ๋ ์ ์ฉํด์ง๋๋ค. Visual Studio Code๋ Chromium์ Rust์์ ์ ์๋ํฉ๋๋ค. ์ฌ์ฉํ๋ ค๋ฉด ๋ค์์ ์คํํฉ๋๋ค.
- VSCode์ ์ด์ ํํ์ Rust ์ง์์ด ์๋
rust-analyzer
ํ์ฅ ํ๋ก๊ทธ๋จ์ด ์๋์ง ํ์ธํ์ธ์. gn gen out/Debug --export-rust-project
(๋๋ ์ถ๋ ฅ ๋๋ ํฐ๋ฆฌ์ ์์)ln -s out/Debug/rust-project.json rust-project.json
๋๊ตฐ๊ฐ๊ฐ IDE์ ๋ํด ํ์์ ์ธ ๊ฒฝ์ฐ rust-analyzer์ ์ฝ๋ ์ฃผ์ ๋ฐ ํ์ ๊ธฐ๋ฅ ์ค ์ผ๋ถ๋ฅผ ์์ฐํด ๋ณด๋ฉด ์๊ฐ์ ๋ฐ๊พธ๋๋ฐ ์ ์ฉํ ์ ์์ต๋๋ค.
๋ค์ ๋จ๊ณ๋ ๋ฐ๋ชจ์ ๋์์ด ๋ ์ ์์ต๋๋ค. ํ์ง๋ง ๊ฐ์ฅ ์ต์ํ Chromium ๊ด๋ จ Rust๋ฅผ ๋์ ์ฌ์ฉํด๋ ๋ฉ๋๋ค.
components/qr_code_generator/qr_code_generator_ffi_glue.rs
๋ฅผ ์ฝ๋๋ค.- ์ปค์๋ฅผ
qr_code_generator_ffi_glue.rs์ \
QrCode::new` ํธ์ถ(26๋ฒ ์ค ๋ถ๊ทผ) ์๋ก ์ด๋ํฉ๋๋ค. - ๋ฐ๋ชจ ๋ฌธ์ ํ์(์ผ๋ฐ์ ์ธ ๋ฐ์ธ๋ฉ: vscode = ctrl k i; vim/CoC = K)
- ๋ฐ๋ชจ ์ ์๋ก ์ด๋(์ผ๋ฐ์ ์ธ ๋ฐ์ธ๋ฉ: vscode = F12; vim/CoC = g d) ๊ทธ๋ฌ๋ฉด
//third_party/rust/.../qr_code-.../src/lib.rs
๋ก ์ด๋ํฉ๋๋ค. - ๊ฐ์ ๋ฐ๋ชจ๋ฅผ ์คํํ๊ณ
QrCode::with_bits
๋ฉ์๋(164๋ฒ ์ค ๊ทผ์ฒ, ๊ฐ์๋ vscode์ ํ์ผ ํ์๊ธฐ ์ฐฝ์ ์์, ์ผ๋ฐ์ ์ธ vim/CoC ๋ฐ์ธ๋ฉ = space o)๋ก ์ด๋ํฉ๋๋ค. - ๋ฐ๋ชจ ์ ํ ์ฃผ์(
QrCode::with_bits
๋ฉ์๋์ ๋ช ๊ฐ์ง ์ข์ ์๊ฐ ์์)
BUILD.gn
ํ์ผ์ ์์ ํ ํ gn gen ... --export-rust-project
๋ฅผ ๋ค์ ์คํํด์ผ ํ๋ค๋ ์ ์ ์ฃผ๋ชฉํ ํ์๊ฐ ์์ต๋๋ค. ์ด ์์
์ ์ด ์ธ์
์ ์ฐ์ต ์ ๋ฐ์ ๊ฑธ์ณ ๋ช ๋ฒ ๋ฐ๋ณตํ๊ฒ ๋ฉ๋๋ค.
๋น๋ ๊ท์น
Chromium ๋น๋์์ ๋ค์์ ํฌํจํ๋ //ui/base/BUILD.gn
์ ์ Rust ํ๊ฒ์ ์ถ๊ฐํฉ๋๋ค.
#![allow(unused)] fn main() { #[no_mangle] pub extern "C" fn hello_from_rust() { println!("Hello from Rust!") } }
์ค์: ์ฌ๊ธฐ์ no_mangle
์ Rust ์ปดํ์ผ๋ฌ์ ์ํด ์์ ํ์ง ์์ ์ ํ์ผ๋ก ๊ฐ์ฃผ๋๋ฏ๋ก gn
ํ๊ฒ์์ ์์ ํ์ง ์์ ์ฝ๋๋ฅผ ํ์ฉํด์ผ ํฉ๋๋ค.
์ด ์๋ก์ด Rust ํ๊ฒ์ //ui/base:base
์ ์ข
์ ํญ๋ชฉ์ผ๋ก ์ถ๊ฐํฉ๋๋ค. ์ด ํจ์๋ฅผ ui/base/resource/resource_bundle.cc
์ ๋งจ ์์์ ์ ์ธํฉ๋๋ค. ๋์ค์ ๋ฐ์ธ๋ฉ ์์ฑ ๋๊ตฌ๋ก ์๋ํํ๋ ๋ฐฉ๋ฒ์ ์ดํด๋ด
๋๋ค.
extern "C" void hello_from_rust();
ui/base/resource/resource_bundle.cc
์ ์ด๋๊ฐ์์ ์ด ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. ResourceBundle::MaybeMangleLocalizedString
์ ์๋จ์ด ์ข์ต๋๋ค. Chromium์ ๋น๋ํ๊ณ ์คํํ์ฌ โHello from Rust!โ๊ฐ ์ฌ๋ฌ ๋ฒ ์ถ๋ ฅ๋๋์ง ํ์ธํฉ๋๋ค.
VSCode๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ด์ VSCode์์ ์ ์๋ํ๋๋ก Rust๋ฅผ ์ค์ ํฉ๋๋ค. ์ดํ ์ฐ์ต์์ ์ ์ฉํฉ๋๋ค. ์ฑ๊ณตํ๋ฉด println!
์์ โ์ ์๋ก ์ด๋โ์ ๋ง์ฐ์ค ์ค๋ฅธ์ชฝ ๋ฒํผ์ผ๋ก ํด๋ฆญํ ์ ์์ต๋๋ค.
๋์์ ๋ฐ์ ์ ์๋ ๊ณณ
rust_static_library
gn ํ ํ๋ฆฟ์์ ์ฌ์ฉ ๊ฐ๋ฅํ ์ต์ #[no_mangle]
์ ๊ดํ ์ ๋ณดextern "C"
์ ๊ดํ ์ ๋ณด- gn์
--export-rust-project
์ ํ ์ ๋ณด - VSCode์์ rust-analyzer๋ฅผ ์ค์นํ๋ ๋ฐฉ๋ฒ
์ด ์๋ ์ต์ ๊ณตํต๋ถ๋ชจ ์ํธ ์ด์ฉ์ฑ ์ธ์ด์ธ C๋ก ๊ท๊ฒฐ๋๊ธฐ ๋๋ฌธ์ ์ผ๋ฐ์ ์ด์ง ์์ต๋๋ค. C++์ Rust ๋ชจ๋ ๊ธฐ๋ณธ์ ์ผ๋ก C ABI ํจ์๋ฅผ ์ ์ธํ๊ณ ํธ์ถํ ์ ์์ต๋๋ค. ์ด ๊ณผ์ ์ ํ๋ฐ๋ถ์์ C++๋ฅผ Rust์ ์ง์ ์ฐ๊ฒฐํฉ๋๋ค.
์ฌ๊ธฐ์ allow_unsafe = true
๊ฐ ํ์ํ ์ด์ ๋ #[no_mangle]
์ด Rust๊ฐ ์ด๋ฆ์ด ๊ฐ์ ํจ์ ๋ ๊ฐ๋ฅผ ์์ฑํ ์ ์๋๋ก ํ ์ ์๊ณ Rust๋ ๋ ์ด์ ์ฌ๋ฐ๋ฅธ ํจ์๊ฐ ํธ์ถ๋๋ค๊ณ ๋ณด์ฅํ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
์์ํ Rust ์คํ ํ์ผ์ด ํ์ํ๋ฉด rust_executable
gn ํ
ํ๋ฆฟ์ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
ํ ์คํธ
Rust community typically authors unit tests in a module placed in the same source file as the code being tested. This was covered earlier in the course and looks like this:
#![allow(unused)] fn main() { #[cfg(test)] mod tests { #[test] fn my_test() { todo!() } } }
In Chromium we place unit tests in a separate source file and we continue to follow this practice for Rust โ this makes tests consistently discoverable and helps to avoid rebuilding .rs
files a second time (in the test
configuration).
This results in the following options for testing Rust code in Chromium:
- Native Rust tests (i.e.
#[test]
). Discouraged outside of//third_party/rust
. gtest
tests authored in C++ and exercising Rust via FFI calls. Sufficient when Rust code is just a thin FFI layer and the existing unit tests provide sufficient coverage for the feature.gtest
tests authored in Rust and using the crate under test through its public API (usingpub mod for_testing { ... }
if needed). This is the subject of the next few slides.
Mention that native Rust tests of third-party crates should eventually be exercised by Chromium bots. (Such testing is needed rarely โ only after adding or updating third-party crates.)
Some examples may help illustrate when C++ gtest
vs Rust gtest
should be used:
-
QR has very little functionality in the first-party Rust layer (itโs just a thin FFI glue) and therefore uses the existing C++ unit tests for testing both the C++ and the Rust implementation (parameterizing the tests so they enable or disable Rust using a
ScopedFeatureList
). -
Hypothetical/WIP PNG integration may need to implement memory-safe implementation of pixel transformations that are provided by
libpng
but missing in thepng
crate - e.g. RGBA => BGRA, or gamma correction. Such functionality may benefit from separate tests authored in Rust.
rust_gtest_interop
Library
The rust_gtest_interop
library provides a way to:
- Use a Rust function as a
gtest
testcase (using the#[gtest(...)]
attribute) - Use
expect_eq!
and similar macros (similar toassert_eq!
but not panicking and not terminating the test when the assertion fails).
Example:
use rust_gtest_interop::prelude::*;
#[gtest(MyRustTestSuite, MyAdditionTest)]
fn test_addition() {
expect_eq!(2 + 2, 4);
}
GN Rules for Rust Tests
The simplest way to build Rust gtest
tests is to add them to an existing test binary that already contains tests authored in C++. For example:
test("ui_base_unittests") {
...
sources += [ "my_rust_lib_unittest.rs" ]
deps += [ ":my_rust_lib" ]
}
Authoring Rust tests in a separate static_library
also works, but requires manually declaring the dependency on the support libraries:
rust_static_library("my_rust_lib_unittests") {
testonly = true
is_gtest_unittests = true
crate_root = "my_rust_lib_unittest.rs"
sources = [ "my_rust_lib_unittest.rs" ]
deps = [
":my_rust_lib",
"//testing/rust_gtest_interop",
]
}
test("ui_base_unittests") {
...
deps += [ ":my_rust_lib_unittests" ]
}
chromium::import!
Macro
After adding :my_rust_lib
to GN deps
, we still need to learn how to import and use my_rust_lib
from my_rust_lib_unittest.rs
. We havenโt provided an explicit crate_name
for my_rust_lib
so its crate name is computed based on the full target path and name. Fortunately we can avoid working with such an unwieldy name by using the chromium::import!
macro from the automatically-imported chromium
crate:
chromium::import! {
"//ui/base:my_rust_lib";
}
use my_rust_lib::my_function_under_test;
Under the covers the macro expands to something similar to:
extern crate ui_sbase_cmy_urust_ulib as my_rust_lib;
use my_rust_lib::my_function_under_test;
More information can be found in the doc comment of the chromium::import
macro.
rust_static_library
supports specifying an explicit name via crate_name
property, but doing this is discouraged. And it is discouraged because the crate name has to be globally unique. crates.io guarantees uniqueness of its crate names so cargo_crate
GN targets (generated by the gnrt
tool covered in a later section) use short crate names.
Testing exercise
์๋ก์ด ์ฐ์ต๋ฌธ์ ๋ฅผ ํ์ด๋ด ์๋ค!
In your Chromium build:
- Add a testable function next to
hello_from_rust
. Some suggestions: adding two integers received as arguments, computing the nth Fibonacci number, summing integers in a slice, etc. - Add a separate
..._unittest.rs
file with a test for the new function. - Add the new tests to
BUILD.gn
. - Build the tests, run them, and verify that the new test works.
C์์ ์ํธ์ด์ฉ์ฑ
Rust ์ปค๋ฎค๋ํฐ๋ C++/Rust ์ํธ ์ด์ฉ์ฑ์ ์ํ ์ฌ๋ฌ ์ต์ ์ ์ ๊ณตํ๋ฉฐ, ์๋ก์ด ๋๊ตฌ๊ฐ ๊ณ์ ๊ฐ๋ฐ๋๊ณ ์์ต๋๋ค. ํ์ฌ Chromium์ CXX๋ผ๋ ๋๊ตฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์ธํฐํ์ด์ค ์ ์ ์ธ์ด(Rust์ ๋งค์ฐ ์ ์ฌํจ)์์ ์ ์ฒด ์ธ์ด ๊ฒฝ๊ณ๋ฅผ ์ค๋ช ํ๋ฉด CXX ๋๊ตฌ๊ฐ Rust ๋ฐ C++ ๋ชจ๋์์ ํจ์์ ์ ํ์ ๊ดํ ์ ์ธ์ ์์ฑํฉ๋๋ค.
See the CXX tutorial for a full example of using this.
๋ค์ด์ด๊ทธ๋จ์ ํตํด ์ค๋ช ํฉ๋๋ค. ๋ด๋ถ์ ์ผ๋ก๋ ์ด์ ๊ณผ ๋์ผํ ์์ ์ ์คํํ๋ค๊ณ ์ค๋ช ํฉ๋๋ค. ํ๋ก์ธ์ค๋ฅผ ์๋ํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ด ์์ต๋๋ค.
- ์ด ๋๊ตฌ๋ C++์ Rust ์ธก์ ์ผ์น๋ฅผ ๋ณด์ฅํฉ๋๋ค. ์๋ฅผ ๋ค์ด
#[cxx::bridge]
๊ฐ ์ค์ C++ ๋๋ Rust ์ ์์ ์ผ์นํ์ง ์๋ ๊ฒฝ์ฐ ์ปดํ์ผ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง๋ง ๋๊ธฐํ๋์ง ์์ ์๋ ๋ฐ์ธ๋ฉ์ ์ฌ์ฉํ๋ฉด ์ ์๋์ง ์์ ๋์์ด ๋ฐ์ํฉ๋๋ค. - ์ด ๋๊ตฌ๋ ๋น C ๊ธฐ๋ฅ์ FFI thunk(์ํ, C-ABI ํธํ, ๋ฌด๋ฃ ํจ์) ์์ฑ์ ์๋ํํฉ๋๋ค(์: Rust ๋๋ C++ ๋ฉ์๋์ ๋ํ FFI ํธ์ถ ์ฌ์ฉ ์ค์ , ์๋ ๋ฐ์ธ๋ฉ์ ๊ฒฝ์ฐ ์ด๋ฌํ ์ต์์ ๋ฌด๋ฃ ํจ์๋ฅผ ์๋์ผ๋ก ์์ฑํด์ผ ํจ).
- ๋๊ตฌ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํต์ฌ ์ ํ ์งํฉ์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
&[T]
๋ ํน์ ABI๋ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์์ ๋ณด์ฅํ์ง ์๋๋ผ๋ FFI ๊ฒฝ๊ณ๋ฅผ ๋์ด ์ ๋ฌ๋ ์ ์์ต๋๋ค. ์๋ ๋ฐ์ธ๋ฉ์ ์ฌ์ฉํ๋ฉดstd::span<T>
/&[T]
๋ ์๋์ผ๋ก ๋์คํธ๋ญ์ฒ๋งํด์ผ ํ๊ณ ํฌ์ธํฐ์ ๊ธธ์ด๋ก ๋ค์ ๋น๋ํด์ผ ํฉ๋๋ค. ์ด๋ ๊ฐ ์ธ์ด๊ฐ ๋น ์ฌ๋ผ์ด์ค๋ฅผ ์ฝ๊ฐ ๋ค๋ฅด๊ฒ ํํํ๋ ์ ์ ๊ณ ๋ คํ ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ์ต๋๋ค.std::unique_ptr<T>
,std::shared_ptr<T>
,Box
๋ฑ์ ์ค๋งํธ ํฌ์ธํฐ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์๋ฉ๋๋ค. ์๋ ๋ฐ์ธ๋ฉ์ ์ฌ์ฉํ๋ฉด C-ABI ํธํ ์์ ํฌ์ธํฐ๋ฅผ ์ ๋ฌํด์ผ ํ๋ฏ๋ก ์ ์ฒด ๊ธฐ๊ฐ ๋ฐ ๋ฉ๋ชจ๋ฆฌ ์์ ์ํ์ด ์ฆ๊ฐํฉ๋๋ค.rust::String
๋ฐCxxString
์ ํ์ ์ธ์ด ๊ฐ ๋ฌธ์์ด ํํ์ ์ฐจ์ด๋ฅผ ์ดํดํ๊ณ ์ ์งํฉ๋๋ค. ์๋ฅผ ๋ค์ดrust::String::lossy
๋ UTF8์ด ์๋ ์ ๋ ฅ์์ Rust ๋ฌธ์์ด์ ๋น๋ํ ์ ์๊ณrust::String::c_str
์ ๋ฌธ์์ด์ NUL ์ข ๋ฃํ ์ ์์ต๋๋ค.
์์
CXX์์๋ ์ ์ฒด C++/Rust ๊ฒฝ๊ณ๊ฐ .rs
์์ค ์ฝ๋ ๋ด์ cxx::bridge
โmodulesโ์ ์ ์ธ๋์ด์ผ ํฉ๋๋ค.
#[cxx::bridge]
mod ffi {
extern "Rust" {
type MultiBuf;
fn next_chunk(buf: &mut MultiBuf) -> &[u8];
}
unsafe extern "C++" {
include!("example/include/blobstore.h");
type BlobstoreClient;
fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
fn put(self: &BlobstoreClient, buf: &mut MultiBuf) -> Result<u64>;
}
}
// Rust ์ ํ ๋ฐ ํจ์์ ์ ์๋ ์ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์ฐธ๊ณ :
- Although this looks like a regular Rust
mod
, the#[cxx::bridge]
procedural macro does complex things to it. The generated code is quite a bit more sophisticated - though this does still result in amod
calledffi
in your code. - Rust์์ C++โ์
std::unique_ptr
๊ธฐ๋ณธ ์ง์ - Native support for Rust slices in C++
- C++์์ Rust ํธ์ถ ๋ฐ Rust ์ ํ(์๋จ)
- Rust์์ C++ ํธ์ถ ๋ฐ C++ ์ ํ(ํ๋จ)
์ผ๋ฐ์ ์ธ ์คํด: C++ ํค๋๊ฐ Rust์์ ํ์ฑ๋๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง ์คํด์ ์์ง๊ฐ ์์ต๋๋ค. ์ด ํค๋๋ Rust์์ ํด์๋์ง ์์ผ๋ฉฐ C++ ์ปดํ์ผ๋ฌ์ ์ด์ ์ ์ํด ์์ฑ๋ C++ ์ฝ๋์ ๋จ์ํ #include
๋ฉ๋๋ค.
CXX ์ ํ์ฌํญ
CXX๋ฅผ ์ฌ์ฉํ ๋ ๋จ์ฐ ๊ฐ์ฅ ์ ์ฉํ ํ์ด์ง๋ ์ ํ ์ฐธ์กฐ์ ๋๋ค.
CXX๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ ์ฌ๋ก์ ์ ํฉํฉ๋๋ค.
- Rust-C++ ์ธํฐํ์ด์ค๋ ๋งค์ฐ ๋จ์ํ์ฌ ๋ชจ๋ ์ ์ธํ ์ ์์ต๋๋ค.
- ์ด๋ฏธ CXX์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์ํ๋ ์ ํ๋ง ์ฌ์ฉํ๊ณ ์์ต๋๋ค(์:
std::unique_ptr
,std::string
,&[u8]
๋ฑ).
๋ง์ ์ ํ์ด ์์ต๋๋ค. ์๋ฅผ ๋ค์ด Rust์ Option
์ ํ์ ์ง์๋์ง ์์ต๋๋ค.
์ด๋ฌํ ์ ํ์ฌํญ์ผ๋ก ์ธํด ์์์ Rust-C++ ์ํธ ์ด์ฉ์ฑ์ด ์๋ ์ ๊ฒฉ๋ฆฌ๋ โ๋ฆฌํ ๋ ธ๋โ์ ๊ฒฝ์ฐ์๋ง Chromium์์ Rust๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. Chromium์์ Rust ์ฌ์ฉ ์ฌ๋ก๋ฅผ ๊ณ ๋ คํ ๋ ์ข์ ์ถ๋ฐ์ ์ ์ธ์ด ๊ฒฝ๊ณ์ CXX ๋ฐ์ธ๋ฉ ์ด์์ ์์ฑํ์ฌ ์ถฉ๋ถํ ๋จ์ํ๊ฒ ํ์๋๋์ง ํ์ธํ๋ ๊ฒ์ ๋๋ค.
CXX์ ๋ค๋ฅธ ์ด๋ ค์ด ๋ฌธ์ ๋ ๋ ผ์ํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์ค๋ฅ ์ฒ๋ฆฌ๋ C++ ์์ธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํฉ๋๋ค(๋ค์ ์ฌ๋ผ์ด๋์ ๋์ ์์).
- ํจ์ ํฌ์ธํฐ๋ ์ฌ์ฉํ๊ธฐ ์ด์ํฉ๋๋ค.
์ค๋ฅ์ฒ๋ฆฌ
CXXโs support for Result<T,E>
relies on C++ exceptions, so we canโt use that in Chromium. Alternatives:
-
The
T
part ofResult<T, E>
can be:- Returned via out parameters (e.g. via
&mut T
). This requires thatT
can be passed across the FFI boundary - for exampleT
has to be:- A primitive type (like
u32
orusize
) - A type natively supported by
cxx
(likeUniquePtr<T>
) that has a suitable default value to use in a failure case (unlikeBox<T>
).
- A primitive type (like
- Retained on the Rust side, and exposed via reference. This may be needed when
T
is a Rust type, which cannot be passed across the FFI boundary, and cannot be stored inUniquePtr<T>
.
- Returned via out parameters (e.g. via
-
The
E
part ofResult<T, E>
can be:- Returned as a boolean (e.g.
true
representing success, andfalse
representing failure) - Preserving error details is in theory possible, but so far hasnโt been needed in practice.
- Returned as a boolean (e.g.
CXX Error Handling: QR Example
QR ์ฝ๋ ์์ฑ๊ธฐ์์์ ๊ฐ์ด ๊ฐ๋จํ ๋ถ์ธ๋ก ์ฑ๊ณต์ ๋ํ๋ผ ์ ์๋ ๊ฒฝ์ฐ: ์ฑ๊ณต์ ๋ํ๋ด๋ ๋ถ์ธ์ ๋ฐํํ๊ณ out ๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋กํฉ๋๋ค.
#[cxx::bridge(namespace = "qr_code_generator")]
mod ffi {
extern "Rust" {
fn generate_qr_code_using_rust(
data: &[u8],
min_version: i16,
out_pixels: Pin<&mut CxxVector<u8>>,
out_qr_size: &mut usize,
) -> bool;
}
}
Students may be curious about the semantics of the out_qr_size
output. This is not the size of the vector, but the size of the QR code (and admittedly it is a bit redundant - this is the square root of the size of the vector).
It may be worth pointing out the importance of initializing out_qr_size
before calling into the Rust function. Creation of a Rust reference that points to uninitialized memory results in Undefined Behavior (unlike in C++, when only the act of dereferencing such memory results in UB).
If students ask about Pin
, then explain why CXX needs it for mutable references to C++ data: the answer is that C++ data canโt be moved around like Rust data, because it may contain self-referential pointers.
CXX Error Handling: PNG Example
A prototype of a PNG decoder illustrates what can be done when the successful result cannot be passed across the FFI boundary:
#[cxx::bridge(namespace = "gfx::rust_bindings")]
mod ffi {
extern "Rust" {
/// This returns an FFI-friendly equivalent of `Result<PngReader<'a>,
/// ()>`.
fn new_png_reader<'a>(input: &'a [u8]) -> Box<ResultOfPngReader<'a>>;
/// C++ bindings for the `crate::png::ResultOfPngReader` type.
type ResultOfPngReader<'a>;
fn is_err(self: &ResultOfPngReader) -> bool;
fn unwrap_as_mut<'a, 'b>(
self: &'b mut ResultOfPngReader<'a>,
) -> &'b mut PngReader<'a>;
/// C++ bindings for the `crate::png::PngReader` type.
type PngReader<'a>;
fn height(self: &PngReader) -> u32;
fn width(self: &PngReader) -> u32;
fn read_rgba8(self: &mut PngReader, output: &mut [u8]) -> bool;
}
}
PngReader
and ResultOfPngReader
are Rust types โ objects of these types cannot cross the FFI boundary without indirection of a Box<T>
. We canโt have an out_parameter: &mut PngReader
, because CXX doesnโt allow C++ to store Rust objects by value.
This example illustrates that even though CXX doesnโt support arbitrary generics nor templates, we can still pass them across the FFI boundary by manually specializing / monomorphizing them into a non-generic type. In the example ResultOfPngReader
is a non-generic type that forwards into appropriate methods of Result<T, E>
(e.g. into is_err
, unwrap
, and/or as_mut
).
Chromium์์ cxx ์ฌ์ฉ
Chromium์์๋ Rust๋ฅผ ์ฌ์ฉํ๋ ค๋ ๋ฆฌํ ๋
ธ๋๋ง๋ค ๋
๋ฆฝ์ ์ธ #[cxx::bridge] mod
๋ฅผ ์ ์ํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก rust_static_library
๋ง๋ค ํ๋์ฉ ์์ต๋๋ค. ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
cxx_bindings = [ "my_rust_file.rs" ]
# ๋ชจ๋ ์์ค ํ์ผ์ด ์๋ #[cxx::bridge]๊ฐ ํฌํจ๋ ํ์ผ ๋ชฉ๋ก
allow_unsafe = true
crate_root
๋ฐ sources
์ ํจ๊ป ๊ธฐ์กด rust_static_library
ํ๊ฒ์ ์ถ๊ฐํฉ๋๋ค.
C++ ํค๋๋ ์ ์ ํ ์์น์ ์์ฑ๋ฉ๋๋ค. ๋ฐ๋ผ์
#include 'ui/base/my_rust_file.rs.h'
//base
์์ Chromium C++ ์ ํ์ผ๋ก ๋ณํํ๋ ๋๋ Chromium C++ ์ ํ์์ CXX Rust ์ ํ์ผ๋ก ๋ณํํ๋ ์ ํธ๋ฆฌํฐ ํจ์๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. ์: SpanToRustSlice
allow_unsafe = true
๊ฐ ๊ณ์ ํ์ํ ์ด์ ๊ฐ ๊ถ๊ธํ ์ ์์ต๋๋ค.
๊ด๋ฒ์ํ ์๋ฏธ์ ๋ต๋ณ์ ์ผ๋ฐ์ ์ธ Rust ํ์ค์์๋ C/C++ ์ฝ๋๊ฐ โ์์ ํ์งโ ์๋ค๋ ๊ฒ์
๋๋ค. Rust์์ C/C++๋ฅผ ์ฌ๊ธฐ์ ๊ธฐ ํธ์ถํ๋ฉด ๋ฉ๋ชจ๋ฆฌ์ ์์์ ์ธ ์์
์ ํ ์ ์์ผ๋ฉฐ Rust ์์ฒด ๋ฐ์ดํฐ ๋ ์ด์์์ ์์ ์ฑ์ด ์์๋ ์ ์์ต๋๋ค. C/C++ ์ํธ ์ด์ฉ์ฑ์ unsafe
ํค์๋๊ฐ ๋๋ฌด ๋ง์ผ๋ฉด ์ด๋ฌํ ํค์๋์ ์ ํธ๋ ์ก์๋น์ ํด๋ฅผ ๋ผ์น ์ ์์ผ๋ฉฐ ๋
ผ๋์ ์์ง๊ฐ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ์๊ฒฉํ๊ฒ๋ ์ธ๋ถ ์ฝ๋๋ฅผ Rust ๋ฐ์ด๋๋ฆฌ๋ก ๊ฐ์ ธ์ค๋ฉด Rust์ ๊ด์ ์์ ์๊ธฐ์น ์์ ๋์์ด ๋ฐ์ํ ์ ์์ต๋๋ค.
The narrow answer lies in the diagram at the top of this page โ behind the scenes, CXX generates Rust unsafe
and extern "C"
functions just like we did manually in the previous section.
Exercise: Interoperability with C++
1๋ถ
- ์ด์ ์ ๋ง๋ Rust ํ์ผ์์, C++์์ ํธ์ถํ
hello_from_rust
๋ผ๋ ๋จ์ผ ํจ์๋ฅผ ์ง์ ํ๋#[cxx::bridge]
๋ฅผ ์ถ๊ฐํฉ๋๋ค. ์ด ํจ์๋ ๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๊ฐ์ ๋ฐํํ์ง ์์ต๋๋ค. - ์ด์ ์
hello_from_rust
ํจ์๋ฅผ ์์ ํ์ฌextern "C"
๋ฐ#[no_mangle]
์ ์ญ์ ํฉ๋๋ค. ์ด ํจ์๋ ์ด์ ํ์ค Rust ํจ์์ ๋๋ค. gn
ํ๊ฒ์ ์์ ํ์ฌ ์ด๋ฌํ ๋ฐ์ธ๋ฉ์ ๋น๋ํฉ๋๋ค.- C++ ์ฝ๋์์
hello_from_rust
์ ์ ๋ฐฉํฅ ์ ์ธ์ ์ญ์ ํฉ๋๋ค. ๋์ ์์ฑ๋ ํค๋ ํ์ผ์ ํฌํจํฉ๋๋ค. - ๋น๋ ๋ฐ ์คํ
2๋ถ
CXX๋ฅผ ์ฌ์ฉํด ๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ ๋๋ค. Chromium์ Rust๊ฐ ์ค์ ๋ก ์ผ๋ง๋ ์ ์ฐํ์ง ์๊ฐํด ๋ณด๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
Some things to try:
- Rust์์ C++๋ก ๋ค์ ํธ์ถ ํ์ํ ์ฌํญ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
cxx::bridge
์์include!
ํ ์ ์๋ ์ถ๊ฐ ํค๋ ํ์ผ์ ๋๋ค. ์ ํค๋ ํ์ผ์์ C++ ํจ์๋ฅผ ์ ์ธํด์ผ ํฉ๋๋ค.- ์ด๋ฌํ ํจ์๋ฅผ ํธ์ถํ๊ฑฐ๋ ์ฌ๊ธฐ์ ์ค๋ช
๋ ๋๋ก
#[cxx::bridge]
์์unsafe
ํค์๋๋ฅผ ์ง์ ํ๋unsafe
๋ธ๋ก์ ๋๋ค. #include "third_party/rust/cxx/v1/crate/include/cxx.h"
๊ฐ ํ์ํ ์๋ ์์ต๋๋ค.
- C++์์ Rust๋ก C++ ๋ฌธ์์ด์ ์ ๋ฌํฉ๋๋ค.
- C++ ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ Rust๋ก ์ ๋ฌํฉ๋๋ค.
- ์๋์ ์ผ๋ก
#[cxx::bridge]
์์ ์ผ์นํ์ง ์๋ Rust ํจ์ ์๋ช ์ ๊ฐ์ ธ์์ ํ์๋๋ ์ค๋ฅ์ ์ต์ํด์ง๋๋ค. - ์๋์ ์ผ๋ก
#[cxx::bridge]
์์ ์ผ์นํ์ง ์๋ C++ ํจ์ ์๋ช ์ ๊ฐ์ ธ์์ ํ์๋๋ ์ค๋ฅ์ ์ต์ํด์ง๋๋ค. - Rust๊ฐ C++ ๊ฐ์ฒด๋ฅผ ์์ ํ ์ ์๋๋ก C++์์ ์ผ๋ถ ์ ํ์
std::unique_ptr
์ Rust๋ก ์ ๋ฌํฉ๋๋ค. - Rust ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด C++๋ก ์ ๋ฌํ์ฌ C++์์ ์์ ํ๋๋ก ํฉ๋๋ค. ํํธ:
Box
ํ์ - C++ ์ ํ์ ์ผ๋ถ ๋ฉ์๋๋ฅผ ์ ์ธํฉ๋๋ค. Rust์์ ์ด๋ฅผ ํธ์ถํ์ธ์.
- Rust ์ ํ์ ์ผ๋ถ ๋ฉ์๋๋ฅผ ์ ์ธํฉ๋๋ค. C++์์ ์ด๋ฅผ ํธ์ถํ์ธ์.
3๋ถ
์ง๊ธ๊น์ง CXX ์ํธ ์ด์ฉ์ฑ์ ๊ฐ์ ๊ณผ ํ๊ณ๋ฅผ ์ดํดํ์ผ๋, ์ธํฐํ์ด์ค๊ฐ ์ถฉ๋ถํ ๊ฐ๋จํ Chromium์ Rust ์ฌ์ฉ ์ฌ๋ก๋ฅผ ์๊ฐํด ๋ณด์ธ์. ํด๋น ์ธํฐํ์ด์ค๋ฅผ ์ ์ํ๋ ๋ฐฉ๋ฒ์ ์ค์ผ์นํฉ๋๋ค.
๋์์ ๋ฐ์ ์ ์๋ ๊ณณ
๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๊ฐ ์์ ์ ์์ต๋๋ค.
- ์ ํ Y๋ก ์ ํ X์ ๋ณ์๋ฅผ ์ด๊ธฐํํ๋ ๋ฐ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ์ฌ๊ธฐ์ X์ Y๋ ๋ชจ๋ ํจ์ ์ ํ์
๋๋ค. ์ด๋ C++ ํจ์๊ฐ
cxx::bridge
์ ์ ์ธ๊ณผ ์ผ์นํ์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค. - C++ ์ฐธ์กฐ๋ฅผ Rust ์ฐธ์กฐ๋ก ์์ ๋กญ๊ฒ ๋ณํํ ์ ์๋ ๊ฒ ๊ฐ์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด UB๊ฐ ๋ฐ์ํ์ง ์์๊น์? CXX์ ๋ถํฌ๋ช ์ ํ์ ๊ฒฝ์ฐ ๋ฐ์ํ์ง ์์ต๋๋ค. ํฌ๊ธฐ๊ฐ 0์ด๊ธฐ ๋๋ฌธ์ ๋๋ค. CXX ์ฌ์ํ ์ ํ์ ๊ฒฝ์ฐ UB๋ฅผ ์ ๋ฐํ๋ ๊ฒ์ด _๊ฐ๋ฅ_ํ์ง๋ง CXX์ ์ค๊ณ์ ์ด๋ฌํ ์๋ฅผ ๋ง๋ค๊ธฐ๊ฐ ์๋นํ ์ด๋ ต์ต๋๋ค.
์๋ ํํฐ ํฌ๋ ์ดํธ ์ถ๊ฐ
Rust ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ โํฌ๋ ์ดํธโ๋ผ๊ณ ํ๋ฉฐ crates.io์์ ์ฐพ์ ์ ์์ต๋๋ค. Rust ํฌ๋ ์ดํธ๊ฐ ์๋ก ์ข ์๋๋ ๊ฒ์ ์์ฃผ ์ฝ์ต๋๋ค. ๋ฐ๋ผ์ ์๋ก ์ข ์๋ฉ๋๋ค.
์์ฑ | C++ library | Rust crate |
---|---|---|
Build system | 1์ต+ | ์ผ๊ด์ฑ: Cargo.toml |
์ผ๋ฐ์ ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํฌ๊ธฐ | ํฐ ํธ | ์๊ฒ |
๋ชจ๋ ์ข ์์ฑ๋ค | ์ ์ | 1์ต+ |
Chromium ์์ง๋์ด์๊ฒ๋ ๋ค์๊ณผ ๊ฐ์ ์ฅ๋จ์ ์ด ์์ต๋๋ค.
- ๋ชจ๋ ํฌ๋ ์ดํธ๋ ๊ณตํต ๋น๋ ์์คํ ์ ์ฌ์ฉํ๋ฏ๋ก Chromium์ ์๋์ผ๋ก ํฌํจํ ์ ์์ต๋๋ค.
- ๊ทธ๋ฌ๋ ํฌ๋ ์ดํธ์๋ ์ผ๋ฐ์ ์ผ๋ก ์ ์ด ์ข ์ ํญ๋ชฉ์ด ์์ผ๋ฏ๋ก ์ฌ๋ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ์ ธ์์ผ ํ ์ ์์ต๋๋ค.
๋ค๋ฃฐ ๋ด์ฉ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- Chromium ์์ค ์ฝ๋ ํธ๋ฆฌ์ ํฌ๋ ์ดํธ๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ
- ์ด๋ฅผ ์ํด
gn
๋น๋ ๊ท์น์ ๋ง๋๋ ๋ฐฉ๋ฒ - ์ถฉ๋ถํ ์์ ์ฑ์ ์ํด ์์ค ์ฝ๋๋ฅผ ๊ฐ์ฌํ๋ ๋ฐฉ๋ฒ
ํฌ๋ ์ดํธ๋ฅผ ์ถ๊ฐํ๋๋ก Cargo.toml
ํ์ผ ๊ตฌ์ฑ
Chromium์๋ ์ค์์์ ๊ด๋ฆฌ๋๋ ์ง์ ํฌ๋ ์ดํธ ์ข
์ ํญ๋ชฉ์ ๋จ์ผ ์ธํธ๊ฐ ์์ต๋๋ค. ์ด๋ ๋จ์ผ Cargo.toml
์ ํตํด ๊ด๋ฆฌ๋ฉ๋๋ค.
[dependencies]
bitflags = "1"
cfg-if = "1"
cxx = "1"
# lots more...
๋ค๋ฅธ Cargo.toml
๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ์ข
์ ํญ๋ชฉ์ ๊ดํ ์์ธํ ๋ด์ฉ์ ์ง์ ํ ์ ์์ต๋๋ค. ๊ฐ์ฅ ํํ๊ฒ๋ ํฌ๋ ์ดํธ์์ ์ฌ์ฉ ์ค์ ํ๋ ค๋ features
๋ฅผ ์ง์ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
Chromium์ ํฌ๋ ์ดํธ๋ฅผ ์ถ๊ฐํ ๋๋ ๋ค์ ๋จ๊ณ์์ ๋ค๋ฃฐ ์ถ๊ฐ ํ์ผ gnrt_config.toml
์ ๋ช ๊ฐ์ง ์ ๋ณด๋ฅผ ์ถ๊ฐ๋ก ์ ๊ณตํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
Configuring gnrt_config.toml
Cargo.toml
๊ณผ ํจ๊ป gnrt_config.toml
์ด ์์ต๋๋ค. ์ฌ๊ธฐ์๋ ํฌ๋ ์ดํธ ์ฒ๋ฆฌ๋ฅผ ์ํ Chromium ์ ์ฉ ํ์ฅ ํ๋ก๊ทธ๋จ์ด ํฌํจ๋์ด ์์ต๋๋ค.
์ ํฌ๋ ์ดํธ๋ฅผ ์ถ๊ฐํ๋ ๊ฒฝ์ฐ ์ ์ด๋ group
์ ์ง์ ํด์ผ ํฉ๋๋ค. ๋ค์ ์ค ํ๋์
๋๋ค.
# 'safe': The library satisfies the rule-of-2 and can be used in any process.
# 'sandbox': The library does not satisfy the rule-of-2 and must be used in
# a sandboxed process such as the renderer or a utility process.
# 'test': The library is only used in tests.
์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
[crate.my-new-crate]
group = 'test' # only used in test code
ํฌ๋ ์ดํธ ์์ค ์ฝ๋ ๋ ์ด์์์ ๋ฐ๋ผ ์ด ํ์ผ์ ์ฌ์ฉํ์ฌ LICENSE
ํ์ผ์ ์ฐพ์ ์ ์๋ ์์น๋ฅผ ์ง์ ํด์ผ ํ ์๋ ์์ต๋๋ค.
๋์ค์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ด ํ์ผ์์ ๊ตฌ์ฑํด์ผ ํ๋ ๋ช ๊ฐ์ง ์ฌํญ์ ์ดํด๋ด ๋๋ค.
ํฌ๋ ์ดํธ ๋ค์ด๋ก๋
gnrt
๋ผ๋ ๋๊ตฌ๋ ํฌ๋ ์ดํธ๋ฅผ ๋ค์ด๋ก๋ํ๋ ๋ฐฉ๋ฒ๊ณผ BUILD.gn
๊ท์น์ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์๊ณ ์์ต๋๋ค.
์์ํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ํ๋ ํฌ๋ ์ดํธ๋ฅผ ๋ค์ด๋ก๋ํฉ๋๋ค.
cd chromium/src
vpython3 tools/crates/run_gnrt.py -- vendor
gnrt
๋๊ตฌ๋ Chromium ์์ค ์ฝ๋์ ์ผ๋ถ์ด์ง๋ง ์ด ๋ช ๋ น์ด๋ฅผ ์คํํ๋ฉดcrates.io
์์ ์ข ์ ํญ๋ชฉ์ ๋ค์ด๋ก๋ํ๊ณ ์คํํ๊ฒ ๋ฉ๋๋ค. ์ด ๋ณด์ ๊ด๋ จ ๊ฒฐ์ ์ ๊ดํด์๋ ์ด์ ์น์ ์ ์ฐธ๊ณ ํ์ธ์.
์ด vendor
๋ช
๋ น์ด๋ ๋ค์์ ๋ค์ด๋ก๋ํ ์ ์์ต๋๋ค.
- Your crate
- ์ง์ ๋ฐ ์์ ์ข ์ ํญ๋ชฉ
- Chromium์์ ํ์ํ ์ ์ฒด ํฌ๋ ์ดํธ ์ธํธ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด
cargo
์์ ์๊ตฌํ๋ ๋ค๋ฅธ ํฌ๋ ์ดํธ์ ์ ๋ฒ์ ์ ๋๋ค.
Chromium์ //third_party/rust/chromium_crates_io/patches
์ ๋ณด๊ด๋๋ ์ผ๋ถ ํฌ๋ ์ดํธ์ ํจ์น๋ฅผ ์ ์ง๊ด๋ฆฌํฉ๋๋ค. ์ด๋ ์๋์ผ๋ก ๋ค์ ์ ์ฉ๋์ง๋ง ํจ์น์ ์คํจํ๋ฉด ์ง์ ์กฐ์น๋ฅผ ์ทจํด์ผ ํ ์๋ ์์ต๋๋ค.
Generating gn
Build Rules
ํฌ๋ ์ดํธ๋ฅผ ๋ค์ด๋ก๋ํ ํ์๋ ๋ค์๊ณผ ๊ฐ์ด BUILD.gn
ํ์ผ์ ์์ฑํฉ๋๋ค.
vpython3 tools/crates/run_gnrt.py -- gen
์ด์ git status
๋ฅผ ์คํํฉ๋๋ค. ๋ค์์ ํ์ธํ ์ ์์ต๋๋ค.
third_party/rust/chromium_crates_io/vendor
์ ํ๋ ์ด์์ ์ ํฌ๋ ์ดํธ ์์ค ์ฝ๋๊ฐ ์์ต๋๋ค.third_party/rust/<crate name>/v<major semver version>
์ ์BUILD.gn
์ด ํ๋ ์ด์ ์์ต๋๋ค.- ์ ์ ํ
README.chromium
The โmajor semver versionโ is a Rust โsemverโ version number.
ํนํ third_party/rust
์์ ์์ฑ๋ ํญ๋ชฉ์ ์์ธํ ์ดํด๋ณด์ธ์.
semver์ ๊ดํด ์ข ๋ ์ด์ผ๊ธฐํฉ๋๋ค. ํนํ Chromium์์๋ ํธํ๋์ง ์๋ ํฌ๋ ์ดํธ ๋ฒ์ ์ ์ฌ๋ฌ ๊ฐ ํ์ฉํ๋ ๋ฐฉ์์ด ์๋๋ฐ ์ด๋ ๊ถ์ฅ๋์ง ์์ง๋ง Cargo ์ํ๊ณ์์๋ ๋๋๋ก ํ์ํฉ๋๋ค.
๋ฌธ์ ํด๊ฒฐ
๋น๋๊ฐ ์คํจํ๋ ๊ฒฝ์ฐ, ์ด๋ ๋น๋ ์๊ฐ์ ์์์ ์์
์ ์คํํ๋ ํ๋ก๊ทธ๋จ์ธ build.rs
๋๋ฌธ์ผ ์ ์์ต๋๋ค. ์ด๋ ๋น๋์ ๋ณ๋ ฌ์ฑ๊ณผ ์ฌํ์ฑ์ ์ต๋ํํ๊ธฐ ์ํด ์ ์ ์ธ ๋น๋ ๊ท์น์ ๋ชฉํ๋ก ํ๋ gn
๋ฐ ninja
์ ์ค๊ณ์ ๊ทผ๋ณธ์ ์ผ๋ก ์์ถฉ๋ฉ๋๋ค.
์ผ๋ถ build.rs
์์
์ ์๋์ผ๋ก ์ง์๋ฉ๋๋ค. ๊ทธ ์ธ๋ ์กฐ์น๊ฐ ํ์ํฉ๋๋ค.
๋น๋ ์คํฌ๋ฆฝํธ ํจ๊ณผ | Google์ gn ํ ํ๋ฆฟ์์ ์ง์ | ํ์ํ ์์ |
---|---|---|
๊ธฐ๋ฅ์ ์ฌ์ฉ ๋ฐ ์ฌ์ฉ ์ค์ง๋ก ๊ตฌ์ฑํ๊ธฐ ์ํด rustc ๋ฒ์ ํ์ธ | ์ | ์์ |
๊ธฐ๋ฅ์ ์ฌ์ฉ ๋ฐ ์ฌ์ฉ ์ค์ง๋ก ๊ตฌ์ฑํ๊ธฐ ์ํด ํ๋ซํผ ๋๋ CPU ํ์ธ | ์ | ์์ |
Generating code | ์ | ์ - gnrt_config.toml ์ ์ง์ |
C/C++ ๋น๋ | ์๋์ค | ์ฃผ๋ณ์ ํจ์น๋ฅผ ์ ์ฉํฉ๋๋ค. |
์์์ ๊ธฐํ ์์ | ์๋์ค | ์ฃผ๋ณ์ ํจ์น๋ฅผ ์ ์ฉํฉ๋๋ค. |
๋คํํ ๋๋ถ๋ถ์ ํฌ๋ ์ดํธ์๋ ๋น๋ ์คํฌ๋ฆฝํธ๊ฐ ํฌํจ๋์ด ์์ง ์์ผ๋ฉฐ, ๋คํํ ๋๋ถ๋ถ์ ๋น๋ ์คํฌ๋ฆฝํธ๋ ์์ ๋ ๊ฐ์ง ์์ ๋ง ์คํํฉ๋๋ค.
์ฝ๋๋ฅผ ์์ฑํ๋ ์คํฌ๋ฆฝํธ ๋น๋
ninja
๊ฐ ํ์ผ ๋๋ฝ์ ๊ดํ ๋ถ๋ง์ ์ ๊ธฐํ๋ ๊ฒฝ์ฐ build.rs
์์ ์์ค ์ฝ๋ ํ์ผ์ ์์ฑํ๋์ง ํ์ธํฉ๋๋ค.
๊ทธ๋ ๋ค๋ฉด gnrt_config.toml
์ ์์ ํ์ฌ build-script-outputs
๋ฅผ ํฌ๋ ์ดํธ์ ์ถ๊ฐํฉ๋๋ค. ์ด๊ฒ์ด ์ ์ด ์ข
์ ํญ๋ชฉ, ์ฆ Chromium ์ฝ๋๊ฐ ์ง์ ์ข
์๋๋ฉด ์ ๋๋ ์ข
์ ํญ๋ชฉ์ธ ๊ฒฝ์ฐ allow-first-party-usage=false
๋ ์ถ๊ฐํฉ๋๋ค. ์ด ํ์ผ์๋ ์ด๋ฏธ ๋ค์๊ณผ ๊ฐ์ ๋ช ๊ฐ์ง ์๊ฐ ์์ต๋๋ค.
[crate.unicode-linebreak]
allow-first-party-usage = false
build-script-outputs = [ "tables.rs" ]
์ด์ gnrt.py -- gen
์ ๋ค์ ์คํํ์ฌ BUILD.gn
ํ์ผ์ ๋ค์ ์์ฑํ๊ณ ์ด ํน์ ์ถ๋ ฅ ํ์ผ์ด ํ์ ๋น๋ ๋จ๊ณ์ ์
๋ ฅ์ด๋ผ๊ณ ninja์ ์๋ฆฝ๋๋ค.
C++๋ฅผ ๋น๋ํ๊ฑฐ๋ ์์์ ์์ ์ ์คํํ๋ ์คํฌ๋ฆฝํธ ๋น๋
์ผ๋ถ ํฌ๋ ์ดํธ๋ cc
ํฌ๋ ์ดํธ๋ฅผ ์ฌ์ฉํ์ฌ C/C++ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋น๋ํ๊ณ ์ฐ๊ฒฐํฉ๋๋ค. ๋ค๋ฅธ ํฌ๋ ์ดํธ๋ ๋น๋ ์คํฌ๋ฆฝํธ ๋ด์์ bindgen
์ ์ฌ์ฉํ์ฌ C/C++๋ฅผ ํ์ฑํฉ๋๋ค. ์ด๋ฌํ ์์
์ Chromium ์ปจํ
์คํธ์์๋ ์ง์๋์ง ์์ต๋๋ค. โ Google์ gn, ninja, LLVM ๋น๋ ์์คํ
์ ๋น๋ ์์
๊ฐ์ ๊ด๊ณ๋ฅผ ๋งค์ฐ ๊ตฌ์ฒด์ ์ผ๋ก ํํํฉ๋๋ค.
์ต์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ์ด ํฌ๋ ์ดํธ๋ ํผํ์ธ์.
- ํฌ๋ ์ดํธ์ ํจ์น๋ฅผ ์ ์ฉํฉ๋๋ค.
ํจ์น๋ third_party/rust/chromium_crates_io/patches/<crate>
์ ๋ณด๊ดํด์ผ ํฉ๋๋ค. ์๋ cxx
ํฌ๋ ์ดํธ์ ๋ํ ํจ์น๋ฅผ ์ฐธ๊ณ ํ์ธ์. ๊ทธ๋ฆฌ๊ณ ํฌ๋ ์ดํธ๋ฅผ ์
๊ทธ๋ ์ด๋ํ ๋๋ง๋ค gnrt
์ ์ํด ์๋์ผ๋ก ์ ์ฉ๋ฉ๋๋ค.
ํฌ๋ ์ดํธ์ ๋ฐ๋ผ ๋ค๋ฆ
์๋ ํํฐ ํฌ๋ ์ดํธ๋ฅผ ์ถ๊ฐํ๊ณ ๋น๋ ๊ท์น์ ์์ฑํ๊ณ ๋๋ฉด ํฌ๋ ์ดํธ์ ๋ฐ๋ฅธ ์์
์ ๊ฐ๋จํฉ๋๋ค. rust_static_library
ํ๊ฒ์ ์ฐพ๊ณ ํฌ๋ ์ดํธ ๋ด์ :lib
ํ๊ฒ์ dep
๋ฅผ ์ถ๊ฐํฉ๋๋ค.
Specifically,
์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
rust_static_library("my_rust_lib") {
crate_root = "lib.rs"
sources = [ "lib.rs" ]
deps = [ "//third_party/rust/example_rust_crate/v1:lib" ]
}
์๋ ํํฐ ํฌ๋ ์ดํธ ๊ฐ์ฌ
์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ์๋ Chromium์ ํ์ค ์ ์ฑ ์ด ์ ์ฉ๋์ง๋ง ๋ํ ๋ณด์ ๊ฒํ ๋ ์ ์ฉ๋ฉ๋๋ค. ๋จ์ผ ํฌ๋ ์ดํธ๋ฟ๋ง ์๋๋ผ ์ ์ด ์ข ์ ํญ๋ชฉ๋ ๊ฐ์ ธ์ฌ ์ ์์ผ๋ฏ๋ก ๊ฒํ ํ ์ฝ๋๊ฐ ๋ง์ ์ ์์ต๋๋ค. ๋ฐ๋ฉด์ ์์ ํ Rust ์ฝ๋์์๋ ๋ถ์ ์ ์ธ ๋ถ์์ฉ์ด ์ ํ๋ ์ ์์ต๋๋ค. ์ด๋ป๊ฒ ๊ฒํ ํด์ผ ํ ๊น์?
Chromium์ ์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ cargo vet๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ํ๋ก์ธ์ค๋ก ์ ํํ๋ ๊ฒ์ ๋ชฉํ๋ก ํฉ๋๋ค.
ํํธ ์๋ก์ด ํฌ๋ ์ดํธ๊ฐ ์ถ๊ฐ๋ ๋๋ง๋ค ๋ค์ ์ฌํญ์ ํ์ธํ๊ณ ์์ต๋๋ค.
- ๊ฐ ํฌ๋ ์ดํธ๊ฐ ์ฌ์ฉ๋๋ ์ด์ ๋ฅผ ์ดํดํฉ๋๋ค. ํฌ๋ ์ดํธ ๊ฐ ๊ด๊ณ๋ ์ด๋ ํ๊ฐ์? ๊ฐ ํฌ๋ ์ดํธ์ ๋น๋ ์์คํ
์
build.rs
๋๋ ์ ์ฐจ ๋งคํฌ๋ก๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ ๊ทธ ์ฉ๋๋ฅผ ํ์ ํด์ผ ํฉ๋๋ค. Chromium์ด ์ผ๋ฐ์ ์ผ๋ก ๋น๋๋๋ ๋ฐฉ์๊ณผ ํธํ๋๋์? - ๊ฐ ํฌ๋ ์ดํธ๊ฐ ์ ์ ํ ์ ์ ์ง๊ด๋ฆฌ๋๋์ง ํ์ธํฉ๋๋ค.
cd third-party/rust/chromium_crates_io; cargo audit
์ ์ฌ์ฉํ์ฌ ์๋ ค์ง ์ทจ์ฝ์ ์ ํ์ธํฉ๋๋ค. ๋จผ์ cargo install cargo-audit
์ ์คํํด์ผ ํฉ๋๋ค. ์ฌ๊ธฐ์๋ ์ธํฐ๋ท2์์ ์ฌ๋ฌ ์ข ์ ํญ๋ชฉ์ ๋ค์ด๋ก๋ํ๋ ์์ ์ด ํฌํจ๋์ด ์์ต๋๋ค.unsafe
์ฝ๋๊ฐ 2์ ๋ฒ์น์ ์ ํฉํ์ง ํ์ธํฉ๋๋ค.fs
๋๋net
API์ ์ฌ์ฉ ํ์ธ- ์ ์์ ์ผ๋ก ์ฝ์ ๋์์ ์ ์๋ ์๋ชป๋ ์ฝ๋๋ฅผ ์ฐพ์ ์ ์๋ ์ถฉ๋ถํ ์์ค์ผ๋ก ๋ชจ๋ ์ฝ๋๋ฅผ ์ฝ์ต๋๋ค. ํ์ค์ ์ผ๋ก 100% ์๋ฒฝ์ ์ถ๊ตฌํ ์๋ ์์ต๋๋ค. ์ฝ๋๊ฐ ๋๋ฌด ๋ง์ ๋๊ฐ ๋ง์ต๋๋ค.
์ด ๋ด์ฉ์ ๊ฐ์ด๋๋ผ์ธ์ ๋ถ๊ณผํฉ๋๋ค. security@chromium.org
์ ๊ฒํ ์์ ํ๋ ฅํ์ฌ ํฌ๋ ์ดํธ์ ๊ดํ ํ์ ์ ๊ฐ์ง ์ ์๋ ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด์ธ์.
Chromium ์์ค ์ฝ๋๋ก ํฌ๋ ์ดํธ ํ์ธ
git status
๋ ๋ค์์ ํ์ํด์ผ ํฉ๋๋ค.
//third_party/rust/chromium_crates_io
์ ํฌ๋ ์ดํธ ์ฝ๋//third_party/rust/<crate>/<version>
์ ๋ฉํ๋ฐ์ดํฐ(BUILD.gn
๋ฐREADME.chromium
)
ํ์์ ์์น์ OWNERS
ํ์ผ๋ ์ถ๊ฐํ์ธ์.
์ด ๋ชจ๋ ๊ฒ์ Cargo.toml
๋ฐ gnrt_config.toml
๋ณ๊ฒฝ์ฌํญ๊ณผ ํจ๊ป Chromium ์ ์ฅ์์ ๋ฐฐ์นํด์ผ ํฉ๋๋ค.
์ค์: git add -f
๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ์ง ์์ผ๋ฉด .gitignore
ํ์ผ๋ก ์ธํด ์ผ๋ถ ํ์ผ์ ๊ฑด๋๋ธ ์ ์์ต๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด ํฌ์ฉ์ ์ด์ง ์์ ์ธ์ด๋ก ์ธํด ์ฌ์ ์ ์ถ ๊ฒ์ฌ๊ฐ ์คํจํ ์๋ ์์ต๋๋ค. ์ด๋ Rust ํฌ๋ ์ดํธ ๋ฐ์ดํฐ์๋ git ๋ธ๋์น ์ด๋ฆ์ด ํฌํจ๋๋ ๊ฒฝํฅ์ด ์๊ณ , ๋ง์ ํ๋ก์ ํธ์์๋ ์ฌ์ ํ ํฌ์ฉ์ ์ด์ง ์์ ์ฉ์ด๋ฅผ ๊ฑฐ๊ธฐ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋ฐ๋ผ์ ๋ค์์ ์คํํด์ผ ํ ์ ์์ต๋๋ค.
infra/update_inclusive_language_presubmit_exempt_dirs.sh > infra/inclusive_language_presubmit_exempt_dirs.txt
git add -p infra/inclusive_language_presubmit_exempt_dirs.txt # add whatever changes are yours
ํฌ๋ ์ดํธ๋ฅผ ์ต์ ์ํ๋ก ์ ์ง
์๋ ํํฐ Chromium ์ข ์ ํญ๋ชฉ์ OWNER์ด๋ฏ๋ก ๋ชจ๋ ๋ณด์ ์์ ์ฌํญ์ ํตํด ์ต์ ์ํ๋ก ์ ์งํด์ผ ํฉ๋๋ค. ๊ณง Rust ํฌ๋ ์ดํธ์ ๊ฒฝ์ฐ ์ด ์์ ์ ์๋ํํ ์ ์๊ธฐ๋ฅผ ๋ฐ๋ผ์ง๋ง, ๋น๋ถ๊ฐ์ ๋ค๋ฅธ ์๋ ํํฐ ์ข ์ ํญ๋ชฉ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ๊ฐ๋ฐ์์ ์ฑ ์์ ๋๋ค.
์ฐ์ต๋ฌธ์
Chromium์ uwuify๋ฅผ ์ถ๊ฐํ์ฌ ํฌ๋ ์ดํธ์ ๊ธฐ๋ณธ ๊ธฐ๋ฅ์ ์ฌ์ฉ ์ค์งํฉ๋๋ค. ํฌ๋ ์ดํธ๊ฐ Chromium ๋ฐฐ์ก์ ์ฌ์ฉ๋์ง๋ง ์ ๋ขฐํ ์ ์๋ ์ ๋ ฅ์ ์ฒ๋ฆฌํ๋ ๋ฐ๋ ์ฌ์ฉ๋์ง ์๋๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
๋ค์ ์ฐ์ต์์๋ Chromium์ uwuify๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ํ๋ค๋ฉด ๊ฑด๋๋ฐ๊ณ ์ง๊ธ ํด ๋ด๋ ๋ฉ๋๋ค. ๋๋ uwuify
๋ฅผ ์ฌ์ฉํ๋ ์๋ก์ด rust_executable
ํ๊ฒ์ ๋ง๋ค ์ ์์ต๋๋ค.
์ง์ ๋ฐ ์์ ์ข ์ ํญ๋ชฉ
The total crates needed are:
instant
,lock_api
,parking_lot
,parking_lot_core
,redox_syscall
,scopeguard
,smallvec
, anduwuify
.
If students are downloading even more than that, they probably forgot to turn off the default features.
์ด ํฌ๋ ์ดํธ๋ฅผ ์ ๊ณตํด ์ฃผ์ ๋ค๋์ ๋ฆฌ์ฐ์๊ฒ ๊ฐ์ฌ๋๋ฆฝ๋๋ค.
Bringing It Together โ Exercise
์ด ์ฐ์ต์์๋ ์ด๋ฏธ ํ์ตํ ๋ชจ๋ ๋ด์ฉ์ ์ข ํฉํ๋ ์์ ํ ์๋ก์ด Chromium ๊ธฐ๋ฅ์ ์ถ๊ฐํฉ๋๋ค.
The Brief from Product Management
์ธ๋ด ์ด๋ ์ฐ๋ฆผ์ ์ฌ๋ pixie ์ปค๋ฎค๋ํฐ๊ฐ ๋ฐ๊ฒฌ๋์์ต๋๋ค. Pixie์ฉ Chromium์ ์ต๋ํ ๋นจ๋ฆฌ ๋ฐฐ์กํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
Chromium์ ๋ชจ๋ UI ๋ฌธ์์ด์ Pixie ์ธ์ด๋ก ๋ฒ์ญํด์ผ ํฉ๋๋ค.
์ ๋๋ก ๋ฒ์ญ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆด ์๊ฐ์ ์์ง๋ง, ๋คํํ pixie ์ธ์ด๋ ์์ด์ ๋งค์ฐ ์ ์ฌํ ๋ฐ๋ค ๋ฒ์ญ์ ํ๋ Rust ํฌ๋ ์ดํธ๊ฐ ์์ต๋๋ค.
์ฌ์ค ์ด์ ์ฐ์ต์์ ์ด ํฌ๋ ์ดํธ๋ฅผ ์ด๋ฏธ ๊ฐ์ ธ์์ต๋๋ค.
๋ฌผ๋ก Chrome์ ์ค์ ๋ฒ์ญ ์์ ์ ์ํด์๋ ์๋นํ ์ฃผ์์ ๋ ธ๋ ฅ์ด ํ์ํฉ๋๋ค. ๋ฐฐ์กํ์ง ๋ง์ธ์.
๊ฑธ์์
ํ์ํ๊ธฐ ์ ์ ๋ชจ๋ ๋ฌธ์์ด์ uwuifyํ๋๋ก ResourceBundle::MaybeMangleLocalizedString
์ ์์ ํฉ๋๋ค. ์ด ํน์ Chromium ๋น๋์์๋ mangle_localized_strings_
์ ์ค์ ๊ณผ ๊ด๊ณ์์ด ํญ์ ์ด ์์
์ ์คํํด์ผ ํฉ๋๋ค.
์ด ๋ชจ๋ ์ฐ์ต์์ ๋ชจ๋ ์์ ์ ์ ๋๋ก ์๋ฃํ๋ค๋ฉด ์ถํํฉ๋๋ค. pixie์ฉ Chrome์ ๋ง๋์ จ์ ๊ฒ์ ๋๋ค.
- UTF16๊ณผ UTF8. ํ์๋ค์ Rust ๋ฌธ์์ด์ด ํญ์ UTF8์ด๋ผ๋ ์ ์ ์๊ณ ์์ด์ผ ํ๋ฉฐ,
base::UTF16ToUTF8
์ ์ฌ์ฉํ์ฌ C++ ์ธก์์ ๋ณํ์ ์คํํ๊ณ ๋ค์ ๊ทธ ๋ฐ๋๋ก ๋ณํํ๋ ๊ฒ์ด ๋ ๋ซ๋ค๊ณ ๊ฒฐ์ ํ ์ ์์ต๋๋ค. - Rust ์ธก์์ ๋ณํํ๊ธฐ๋ก ๊ฒฐ์ ํ ํ์๋ค์
String::from_utf16
์ ๊ณ ๋ คํ๊ณ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ๊ณ ๋ คํ๊ณ ๋ง์ u16์ ์ ์กํ ์ ์๋ CXX ์ง์ ์ ํ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค. - ํ์๋ค์ ์ฌ๋ฌ ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก C++/Rust ๊ฒฝ๊ณ๋ฅผ ์ค๊ณํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๊ฐ์ผ๋ก ๋ฌธ์์ด์ ๊ฐ์ ธ์ ๋ฐํํ๊ฑฐ๋ ๋ฌธ์์ด์ ๋ํ ๋ณ๊ฒฝ ๊ฐ๋ฅํ ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ณ๊ฒฝ ๊ฐ๋ฅํ ์ฐธ์กฐ๋ฅผ ์ฌ์ฉํ๋ฉด CXX์์ ํ์์๊ฒ
Pin
์ ์ฌ์ฉํด์ผ ํ๋ค๊ณ ์๋ฆด ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.Pin
์ ์ญํ ์ ์ค๋ช ํ๊ณ ์ด๊ฒ์ด C++ ๋ฐ์ดํฐ์ ๋ํ ๋ณ๊ฒฝ ๊ฐ๋ฅํ ์ฐธ์กฐ๋ฅผ ์ํด CXX์ ํ์ํ ์ด์ ๋ฅผ ์ค๋ช ํด์ผ ํ ์๋ ์์ต๋๋ค. ๋๋ต์ C++ ๋ฐ์ดํฐ๋ Rust ๋ฐ์ดํฐ์ฒ๋ผ ์ด๋ํ ์ ์๋ค๋ ๊ฒ์ ๋๋ค. ์์ฒด ์ฐธ์กฐ ํฌ์ธํฐ๊ฐ ํฌํจ๋์ด ์์ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ResourceBundle::MaybeMangleLocalizedString
์ด ํฌํจ๋ C++ ํ๊ฒ์rust_static_library
ํ๊ฒ์ ์ข ์๋์ด์ผ ํฉ๋๋ค. ํ์์ ์ด๋ฏธ ์ด๋ฅผ ์คํํ์ ๊ฒ์ ๋๋ค.rust_static_library
ํ๊ฒ์//third_party/rust/uwuify/v0_2:lib
์ ์ข ์๋์ด์ผ ํฉ๋๋ค.
์ฐ์ต๋ฌธ์ ํด๋ต
Solutions to the Chromium exercises can be found in this series of CLs.
Welcome to Bare Metal Rust
์ด ๊ณผ์ ์ Rust์ ๋ํด ์ด๋์ ๋ ๊ฒฝํ์ด ์๊ณ (์๋ง๋ Comprehensive Rust ๊ณผ์ ์ ํตํด) C์ ๊ฐ์ ๋ค๋ฅธ ์ธ์ด๋ก bare-metal ํ๋ก๊ทธ๋๋ฐ์ ํด ๋ณธ ์ฌ์ฉ์๋ฅผ ๋์์ผ๋ก ํ๋ bare-metal Rust์ ๊ดํ ๋ ๋ฆฝ์ ์ธ 1์ผ ๊ณผ์ ์ ๋๋ค.
์ค๋์ OS๋ฅผ ์ฌ์ฉํ์ง ์๊ณ Rust ์ฝ๋๋ฅผ ์คํํ๋ โbare-metalโ Rust์ ๊ดํด ์์๋ด ๋๋ค. ๋ณธ ๊ฐ์์ ๊ตฌ์ฑ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
no_std
Rust๋ ๋ฌด์์ธ๊ฐ์?- ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ์ฉ ํ์จ์ด ์์ฑ
- ์ ํ๋ฆฌ์ผ์ด์ ํ๋ก์ธ์๋ฅผ ์ํ ๋ถํธ๋ก๋ / ์ปค๋ ์ฝ๋ ์์ฑ
- bare-metal Rust ๊ฐ๋ฐ์ ์ํ ์ ์ฉํ ํฌ๋ ์ดํธ
์ด ๊ฐ์์์๋ BBC micro:bit v2๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ๋ Nordic nRF51822 ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ์ ๊ธฐ๋ฐํ ๊ฐ๋ฐ ๋ณด๋๋ก์จ, LED์ ๋ฒํผ, I2C ์ฐ๊ฒฐ ๊ฐ์๋๊ณ ๋ฐ ๋์นจ๋ฐ, ์จ๋ณด๋ SWD ๋๋ฒ๊ฑฐ๋ฅผ ํฌํจํ๊ณ ์์ต๋๋ค.
์์ํ๊ธฐ์ ์, ์์ผ๋ก ์ฌ์ฉํ ๋๊ตฌ๋ฅผ ์ค์นํด์ผ ํฉ๋๋ค. gLinux ๋๋ Debian๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด ์๋์ ๊ฐ์ด ํ์ธ์.
sudo apt install gcc-aarch64-linux-gnu gdb-multiarch libudev-dev picocom pkg-config qemu-system-arm
rustup update
rustup target add aarch64-unknown-none thumbv7em-none-eabihf
rustup component add llvm-tools-preview
cargo install cargo-binutils cargo-embed
plugdev
๊ทธ๋ฃน์ ์ฌ์ฉ์์๊ฒ micro:bit ํ๋ก๊ทธ๋๋จธ ์ฅ์น์ ๋ํ ์ก์ธ์ค ๊ถํ์ ๋ถ์ฌํฉ๋๋ค.
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0d28", MODE="0664", GROUP="plugdev"' |\
sudo tee /etc/udev/rules.d/50-microbit.rules
sudo udevadm control --reload-rules
MacOS์์:
xcode-select --install
brew install gdb picocom qemu
brew install --cask gcc-aarch64-embedded
rustup update
rustup target add aarch64-unknown-none thumbv7em-none-eabihf
rustup component add llvm-tools-preview
cargo install cargo-binutils cargo-embed
no_std
|
|
|
---|---|---|
|
|
|
HashMap
์ RNG์ ์์กดํฉ๋๋ค.std
๋core
๋ฐalloc
๋ฅผ ํฌํจํฉ๋๋ค.
์ต์ํ์ no_std
ํ๋ก๊ทธ๋จ
#![no_main] #![no_std] use core::panic::PanicInfo; #[panic_handler] fn panic(_panic: &PanicInfo) -> ! { loop {} }
- ์ด ์ฝ๋๋ ๋น ๋ฐ์ด๋๋ฆฌ๋ก ์ปดํ์ผ๋ฉ๋๋ค.
std
๋ ํจ๋ ํธ๋ค๋ฌ๋ฅผ ์ ๊ณตํ์ง๋ง, ์ฐ๋ฆฌ๋ ์์ฒด์ ์ผ๋ก ํธ๋ค๋ฌ๋ฅผ ๋ง๋ค์ด์ผํฉ๋๋ค.- ํจ๋ ํธ๋ค๋ฌ๋
panic-halt
์ ๊ฐ์ ํฌ๋ ์ดํธ๋ฅผ ํตํด์ ๋ง๋ค์๋ ์์ต๋๋ค. - ํ๊ฒ์ ๋ฐ๋ผ
panic = "abort"
๋ก ์ปดํ์ผํด์ผ ํ ์ ์์ต๋๋ค. ์ด๋eh_personality
์ ๊ดํ ์ค๋ฅ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํจ์ ๋๋ค. main
๊ณผ ๊ฐ์ ํ๋ก๊ทธ๋จ ์ง์ ์ ์ด ์์ต๋๋ค. ๊ฐ๋ฐ์๊ฐ ์์ฒด์ ์ผ๋ก ์ง์ ์ ์ ์ ์ํด์ผ ํฉ๋๋ค. ์ง์ ์ ์ ์ ์ํ๋ ์์ ์, ์ผ๋ฐ์ ์ผ๋ก ๋ง์ปค ์คํฌ๋ฆฝํธ์ ์ด์ ๋ธ๋ฆฌ ์ฝ๋๋ฅผ ํ์๋ก ํฉ๋๋ค.
alloc
alloc
์ ์ฌ์ฉํ๋ ค๋ฉด ์ ์ญ (ํ) ํ ๋น์๋ฅผ ๊ตฌํํด์ผ ํฉ๋๋ค.
#![no_main] #![no_std] extern crate alloc; extern crate panic_halt as _; use alloc::string::ToString; use alloc::vec::Vec; use buddy_system_allocator::LockedHeap; #[global_allocator] static HEAP_ALLOCATOR: LockedHeap<32> = LockedHeap::<32>::new(); static mut HEAP: [u8; 65536] = [0; 65536]; pub fn entry() { // `HEAP`์ด ์ฌ๊ธฐ์๋ง ์ฌ์ฉ๋๊ณ `entry`๊ฐ ํ ๋ฒ๋ง ํธ์ถ๋๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { // ํ ๋น์์๊ฒ ํ ๋นํ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค. HEAP_ALLOCATOR.lock().init(HEAP.as_mut_ptr() as usize, HEAP.len()); } // ์ด์ ํ ํ ๋น์ด ํ์ํ ์์ ์ ํ ์ ์์ต๋๋ค. let mut v = Vec::new(); v.push("A string".to_string()); }
buddy_system_allocator
๋ ๊ฐ๋จํ ๋ฒ๋ ์์คํ ํ ๋น์๋ฅผ ๊ตฌํํ๋ ์๋ ํํฐ ํฌ๋ ์ดํธ์ ๋๋ค. ์ด ์ธ์๋, ๋ค๋ฅธ ํฌ๋ ์ดํธ๋ฅผ ์ฌ์ฉํ๊ฑฐ๋, ์ง์ ํ ๋น์๋ฅผ ๋ง๋ค๊ฑฐ๋, ์ด๋ฏธ ์กด์ฌํ๋ ๋ค๋ฅธ ํ ๋น์์ ํํนํ ์ ์์ต๋๋ค.LockHeap
ํ์ ์ const ๋งค๊ฐ๋ณ์๋ ํ ๋น์์ ์ต๋ ํฌ๊ธฐ๋ฅผ 2์ง์๋ก ํํํ์ ๋์ ์๋ฆฟ์์ ๋๋ค. ์ฆ, ์ด ๊ฒฝ์ฐ์ฒ๋ผ 32์ธ ๊ฒฝ์ฐ ์ต๋ 2**32๋ฐ์ดํธ ํฌ๊ธฐ์ ์์ญ์ ๋ค๋ฃฐ ์ ์์ต๋๋ค.- ํ ๋ฐ์ด๋๋ฆฌ์์
alloc
์ ์์กดํ๋ ํฌ๋ ์ดํธ๊ฐ ํ๋๋ผ๋ ์๋ค๋ฉด ๋ฐ์ด๋๋ฆฌ ์ ์ฒด์์ ์ ์ญ ํ ๋น์๊ฐ ๋ฐ๋์ ํ๋ ์กด์ฌํด์ผ ํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ์ ์ญ ํ ๋น์๋ฅผ ์ ์ธํ๋ ์์ ์ ์ต์์ ๋ฐ์ด๋๋ฆฌ ํฌ๋ ์ดํธ์์ ์ด๋ฃจ์ด์ง๋๋ค. panic_halt
ํฌ๋ ์ดํธ๊ฐ ์ฐ๊ฒฐ๋์ด ํจ๋ ํธ๋ค๋ฌ๋ฅผ ๊ฐ์ ธ์ค๋๋ก ํ๋ ค๋ฉดextern crate panic_halt as _
๊ฐ ํ์ํฉ๋๋ค.- ์ด ์์ ์ฝ๋๋ ๋น๋๋ ๋์ง๋ง, ์ง์ ์ ์ด ์๊ธฐ ๋๋ฌธ์ ์คํ๋์ง๋ ์์ต๋๋ค.
๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ
cortex_m_rt
ํฌ๋ ์ดํธ๋ Cortex M ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ๋ฅผ ์ด๊ธฐํ ํ๋ ํธ๋ค๋ฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
#![no_main] #![no_std] extern crate panic_halt as _; mod interrupts; use cortex_m_rt::entry; #[entry] fn main() -> ! { loop {} }
์ด์ , ์ฃผ๋ณ์ฅ์น์ ์ก์ธ์คํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด๊ฒ ์ต๋๋ค. ๊ฐ์ฅ ๊ธฐ๊ณ์ ๊ฐ๊น์ด ๋ฎ์ ๋จ๊ณ์์ ์์ํด์ ์ ์ ์ถ์ํ ์์ค์ ์ฌ๋ฆฌ๊ฒ ์ต๋๋ค.
cortex_m_rt::entry
๋งคํฌ๋ก๋ ์ง์ ์ ์ผ๋ก ์ฌ์ฉ๋๋ ํจ์๊ฐfn() -> !
ํ์ (์ฆ, ๋ฆฌํดํ์ง ์๋)์์ ์๊ตฌํฉ๋๋ค. ๋ง์ฝ, ๋ฆฌํดํ๊ฒ ๋๋ฉด, ํ๋ก๊ทธ๋จ ์ํ ํ ๋ฆฌ์ ํธ๋ค๋ฌ๋ก ๋์๊ฐ๊ฒ ๋๋ ๊ฒ์ธ๋ฐ ์ด๋ ๋ง์ด ๋์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค.cargo embed --bin minimal
์ ์ฌ์ฉํ์ฌ ์์๋ฅผ ์คํํฉ๋๋ค.
์์ MMIO
๋๋ถ๋ถ์ ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ๋ ๋ฉ๋ชจ๋ฆฌ ๋งคํ IO๋ฅผ ํตํด ์ฃผ๋ณ๊ธฐ๊ธฐ์ ์ก์ธ์คํฉ๋๋ค. micro:bit์์ LED๋ฅผ ์ผ๋ณด๊ฒ ์ต๋๋ค.
#![no_main] #![no_std] extern crate panic_halt as _; mod interrupts; use core::mem::size_of; use cortex_m_rt::entry; /// GPIO ํฌํธ 0 ์ฃผ๋ณ๊ธฐ๊ธฐ ์ฃผ์ const GPIO_P0: usize = 0x5000_0000; // GPIO ์ฃผ๋ณ๊ธฐ๊ธฐ ์คํ์ const PIN_CNF: usize = 0x700; const OUTSET: usize = 0x508; const OUTCLR: usize = 0x50c; // PIN_CNF ํ๋ const DIR_OUTPUT: u32 = 0x1; const INPUT_DISCONNECT: u32 = 0x1 << 1; const PULL_DISABLED: u32 = 0x0 << 2; const DRIVE_S0S1: u32 = 0x0 << 8; const SENSE_DISABLED: u32 = 0x0 << 16; #[entry] fn main() -> ! { // GPIO 0 ํ 21 ๋ฐ 28์ ํธ์-ํ ์ถ๋ ฅ์ผ๋ก ๊ตฌ์ฑํฉ๋๋ค. let pin_cnf_21 = (GPIO_P0 + PIN_CNF + 21 * size_of::<u32>()) as *mut u32; let pin_cnf_28 = (GPIO_P0 + PIN_CNF + 28 * size_of::<u32>()) as *mut u32; // ์ ํจํ ์ฃผ๋ณ๊ธฐ๊ธฐ ์ ์ด ๋ ์ง์คํฐ์ ๋ํ ํฌ์ธํฐ์ด๊ณ // ๋ณ์นญ์ด ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { pin_cnf_21.write_volatile( DIR_OUTPUT | INPUT_DISCONNECT | PULL_DISABLED | DRIVE_S0S1 | SENSE_DISABLED, ); pin_cnf_28.write_volatile( DIR_OUTPUT | INPUT_DISCONNECT | PULL_DISABLED | DRIVE_S0S1 | SENSE_DISABLED, ); } // ํ 28์ ๋ฎ๊ฒ, ํ 21์ ๋๊ฒ ์ค์ ํ์ฌ LED๋ฅผ ์ผญ๋๋ค. let gpio0_outset = (GPIO_P0 + OUTSET) as *mut u32; let gpio0_outclr = (GPIO_P0 + OUTCLR) as *mut u32; // ์ ํจํ ์ฃผ๋ณ๊ธฐ๊ธฐ ์ ์ด ๋ ์ง์คํฐ์ ๋ํ ํฌ์ธํฐ์ด๊ณ // ๋ณ์นญ์ด ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { gpio0_outclr.write_volatile(1 << 28); gpio0_outset.write_volatile(1 << 21); } loop {} }
- GPIO 0 ํ 21์ LED ๋งคํธ๋ฆญ์ค์ ์ฒซ ๋ฒ์งธ ์ด์ ์ฐ๊ฒฐ๋๊ณ ํ 28์ ์ฒซ ๋ฒ์งธ ํ์ ์ฐ๊ฒฐ๋ฉ๋๋ค.
์๋ ๋ช ๋ น์ด๋ก ์์ ์ฝ๋๋ฅผ ์คํํ์ธ์.
cargo embed --bin mmio
์ฃผ๋ณ๊ธฐ๊ธฐ ์ก์ธ์ค ํฌ๋ ์ดํธ
svd2rust
ํฌ๋ ์ดํธ๋ฅผ ์ด์ฉํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋งคํ๋ ์ฃผ๋ณ์ฅ์น๋ฅผ ๊ธฐ์ ํ๋ CMSIS-SVD ํ์ผ๋ก๋ถํฐ Rust ๋ํผ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
#![no_main] #![no_std] extern crate panic_halt as _; use cortex_m_rt::entry; use nrf52833_pac::Peripherals; #[entry] fn main() -> ! { let p = Peripherals::take().unwrap(); let gpio0 = p.P0; // GPIO 0 ํ 21 ๋ฐ 28์ ํธ์-ํ ์ถ๋ ฅ์ผ๋ก ๊ตฌ์ฑํฉ๋๋ค. gpio0.pin_cnf[21].write(|w| { w.dir().output(); w.input().disconnect(); w.pull().disabled(); w.drive().s0s1(); w.sense().disabled(); w }); gpio0.pin_cnf[28].write(|w| { w.dir().output(); w.input().disconnect(); w.pull().disabled(); w.drive().s0s1(); w.sense().disabled(); w }); // ํ 28์ ๋ฎ๊ฒ, ํ 21์ ๋๊ฒ ์ค์ ํ์ฌ LED๋ฅผ ์ผญ๋๋ค. gpio0.outclr.write(|w| w.pin28().clear()); gpio0.outset.write(|w| w.pin21().set()); loop {} }
- SVD(System View Description) ํ์ผ์ ์ผ๋ฐ์ ์ผ๋ก ์ค๋ฆฌ์ฝ ๊ณต๊ธ์
์ฒด์์ ์ ๊ณตํ๋ XML ํ์ผ๋ก, ๊ธฐ๊ธฐ์ ๋ฉ๋ชจ๋ฆฌ ๋งต์ ๊ธฐ์ ํฉ๋๋ค.
- ์ฃผ๋ณ๊ธฐ๊ธฐ, ๋ ์ง์คํฐ, ํ๋, ๊ฐ์ผ๋ก ๊ตฌ์ฑ๋๋ฉฐ ์ด๋ฆ, ์ค๋ช , ์ฃผ์ ๋ฑ์ด ํฌํจ๋ฉ๋๋ค.
- SVD ํ์ผ์๋ ๋ฒ๊ทธ๊ฐ ์์ ์ ์๊ณ ๋ถ์์ ํ๊ธฐ ๋๋ฌธ์, ์ด๋ฌํ ๋ฌธ์ ๋ค์ ํจ์นํ๋ ๋ค์ํ ํ๋ก์ ํธ๋ค์ด ์์ต๋๋ค.
cortex-m-rt
๋ ๋ฌด์๋ณด๋ค๋ ๋ฒกํฐ ํ ์ด๋ธ์ ์ ๊ณตํฉ๋๋ค.cargo install cargo-binutils
์ ์คํํ ํ,cargo objdump --bin pac -- -d --no-show-raw-insn
์ ์คํํ์ฌ ์์ฑ๋ ๋ฐ์ด๋๋ฆฌ์ ๋ด์ฉ์ ํ์ธํ ์ ์์ต๋๋ค.
์๋ ๋ช ๋ น์ด๋ก ์์ ์ฝ๋๋ฅผ ์คํํ์ธ์.
cargo embed --bin pac
HAL ํฌ๋ ์ดํธ๋ค
๋ค์ํ ์ฃผ๋ณ ์ฅ์น์ ๋ํ ๋ํผ๋ฅผ ์ ๊ณตํ๋ HAL ํฌ๋ ์ดํธ๋ค์ด ์์ต๋๋ค. ์ด ํฌ๋ ์ดํธ๋ค์ ์ผ๋ฐ์ ์ผ๋ก embedded-hal
์ ํธ๋ ์์ ๊ตฌํํฉ๋๋ค.
#![no_main] #![no_std] extern crate panic_halt as _; use cortex_m_rt::entry; use nrf52833_hal::gpio::{p0, Level}; use nrf52833_hal::pac::Peripherals; use nrf52833_hal::prelude::*; #[entry] fn main() -> ! { let p = Peripherals::take().unwrap(); // GPIO ํฌํธ 0์ HAL ๋ํผ๋ฅผ ๋ง๋ญ๋๋ค. let gpio0 = p0::Parts::new(p.P0); // GPIO 0 ํ 21 ๋ฐ 28์ ํธ์-ํ ์ถ๋ ฅ์ผ๋ก ๊ตฌ์ฑํฉ๋๋ค. let mut col1 = gpio0.p0_28.into_push_pull_output(Level::High); let mut row1 = gpio0.p0_21.into_push_pull_output(Level::Low); // ํ 28์ ๋ฎ๊ฒ, ํ 21์ ๋๊ฒ ์ค์ ํ์ฌ LED๋ฅผ ์ผญ๋๋ค. col1.set_low().unwrap(); row1.set_high().unwrap(); loop {} }
set_low
๋ฐset_high
๋embedded_hal
OutputPin
ํธ๋ ์์ ๋ฉ์๋์ ๋๋ค.- ๋ค์ํ STM32, GD32, nRF, NXP, MSP430, AVR, PIC ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ๋ฅผ ๋น๋กฏํ ๋ง์ Cortex-M ๋ฐ RISC-V ๊ธฐ๊ธฐ๋ฅผ ์ํ HAL ํฌ๋ ์ดํธ๊ฐ ์์ต๋๋ค
์๋ ๋ช ๋ น์ด๋ก ์์ ์ฝ๋๋ฅผ ์คํํ์ธ์.
cargo embed --bin hal
Board support crates
๋ณด๋ ์ง์ ํฌ๋ ์ดํธ๋ค์, ํน์ ๋ณด๋๋ฅผ ๋ ์์ฝ๊ฒ ์ฌ์ฉํ ์ ์๊ฒ ํด ์ฃผ๋ ๋ ๋์ ์์ค์ ์ถ์ํ๋ฅผ ์ ๊ณตํฉ๋๋ค.
#![no_main] #![no_std] extern crate panic_halt as _; use cortex_m_rt::entry; use microbit::hal::prelude::*; use microbit::Board; #[entry] fn main() -> ! { let mut board = Board::take().unwrap(); board.display_pins.col1.set_low().unwrap(); board.display_pins.row1.set_high().unwrap(); loop {} }
- ์ด ๊ฒฝ์ฐ ๋ณด๋ ์ง์ ํฌ๋ ์ดํธ๋ ์ข ๋ ์ง๊ด์ ์ธ ์ด๋ฆ๋ค๊ณผ ์ ๋นํ ์์ค์ ์ด๊ธฐํ๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์ด ํฌ๋ ์ดํธ๋ ๋ง์ดํฌ๋ก์ปจํธ๋กค ๋ฐ์ ์๋ (์ฆ, ๋ณด๋์ ์ค์น๋) ์ฅ์น์ ๋ํ ๋๋ผ์ด๋ฒ๋ ํฌํจํ ์ ์์ต๋๋ค.
microbit-v2
์๋ LED ๋งคํธ๋ฆญ์ค๋ฅผ ์ํ ๊ฐ๋จํ ๋๋ผ์ด๋ฒ๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.
์๋ ๋ช ๋ น์ด๋ก ์์ ์ฝ๋๋ฅผ ์คํํ์ธ์.
cargo embed --bin board_support
ํ์ ์ํ ํจํด
#[entry] fn main() -> ! { let p = Peripherals::take().unwrap(); let gpio0 = p0::Parts::new(p.P0); let pin: P0_01<Disconnected> = gpio0.p0_01; // let gpio0_01_again = gpio0.p0_01; // ์ค๋ฅ๊ฐ ๋ฐ์ํ์ฌ ์ด๋ํ์ต๋๋ค. let pin_input: P0_01<Input<Floating>> = pin.into_floating_input(); if pin_input.is_high().unwrap() { // ... } let mut pin_output: P0_01<Output<OpenDrain>> = pin_input .into_open_drain_output(OpenDrainConfig::Disconnect0Standard1, Level::Low); pin_output.set_high().unwrap(); // pin_input.is_high(); // ์ค๋ฅ๊ฐ ๋ฐ์ํ์ฌ ์ด๋ํ์ต๋๋ค. let _pin2: P0_02<Output<OpenDrain>> = gpio0 .p0_02 .into_open_drain_output(OpenDrainConfig::Disconnect0Standard1, Level::Low); let _pin3: P0_03<Output<PushPull>> = gpio0.p0_03.into_push_pull_output(Level::Low); loop {} }
- ํ์
Copy
๋๋Clone
์ ๊ตฌํํ์ง ์์ผ๋ฏ๋ก ๊ฐ๊ฐ ํ๋์ ์ธ์คํด์ค๋ง ์กด์ฌํ ์ ์์ต๋๋ค. ํ์ ํฌํธ ๊ตฌ์กฐ์ฒด ๋ฐ์ผ๋ก ์ด๋ํ๋ฉด ์๋ฌด๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. - ํ ๊ตฌ์ฑ์ ๋ณ๊ฒฝํ๋ฉด ์ด์ ํ ์ธ์คํด์ค๊ฐ ์ฌ์ฉ๋๋ฏ๋ก ์ดํ์ ์ด์ ์ธ์คํด์ค๋ฅผ ๊ณ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ๊ฐ ํ์ ์ ํ์ฌ ์ํ๋ฅผ ๋ํ๋ ๋๋ค. ์๋ฅผ ๋ค์ด ์ด ๊ฒฝ์ฐ์๋ GPIO ํ์ ๊ตฌ์ฑ ์ํ์ ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ํ ๋จธ์ ์ด ํ์ ์์คํ ์ผ๋ก ์ธ์ฝ๋ฉ๋๋ฉฐ, ๋จผ์ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌ์ฑํ ํ ํน์ ๋ฐฉ์์ผ๋ก ํ์ ์ฌ์ฉํ๋๋ก ํฉ๋๋ค. ์๋ชป๋ ์ํ ์ ํ์ ์ปดํ์ผ ์๊ฐ์ ํฌ์ฐฉ๋ฉ๋๋ค.
- ์
๋ ฅ ํ์์
is_high
๋ฅผ ํธ์ถํ๊ณ ์ถ๋ ฅ ํ์์set_high
๋ฅผ ํธ์ถํ ์ ์์ง๋ง ๊ทธ ๋ฐ๋๋ก๋ ์ ๋ฉ๋๋ค. - ๋ง์ HAL ํฌ๋ ์ดํธ๋ค์ด ์ด ํจํด์ ๋ฐ๋ฆ ๋๋ค.
embedded-hal
embedded-hal
ํฌ๋ ์ดํธ๋ ๋ค์ํ ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ์์ ๊ณตํต์ ์ผ๋ก ์ฐพ์๋ณผ ์ ์๋ ์ฃผ๋ณ๊ธฐ๊ธฐ๋ฅผ ์ถ์ํ ํ๋ ๋ค์ํ ํธ๋ ์์ ์ ๊ณตํฉ๋๋ค.
- GPIO
- ADC
- I2C, SPI, UART, CAN
- RNG
- ํ์ด๋จธ
- ์์น๋
๊ทธ๋ฌ๋ฉด ๋ค๋ฅธ ํฌ๋ ์ดํธ๋ ์ด ํธ๋ ์๋ค์ ํ์ฉํ์ฌ ๋๋ผ์ด๋ฒ๋ฅผ ๊ตฌํํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๊ฐ์๋๊ณ ๋๋ผ์ด๋ฒ๋ฅผ ๊ตฌํํ ๋ I2C ๋๋ SPI ๋ฒ์ค ๊ตฌํ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ๋ผ์ค๋ฒ ๋ฆฌ ํ์ด์์ ๋์๊ฐ๋ ๋ฆฌ๋ ์ค ๊ฐ์ ํ๋ซํผ ๋ฟ๋ง ์๋๋ผ ๋ค๋ฅธ ์ฌ๋ฌ ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ์ ๋ํ ๊ตฌํ์ด ์์ต๋๋ค.
embedded-hal
์ โasyncโ ๋ฒ์ ์ ๊ดํ ์์ ์ด ์งํ ์ค์ด์ง๋ง ์์ง ์์ ์ ์ด์ง ์์ต๋๋ค.
probe-rs
and cargo-embed
probe-rs๋ ์๋ฒ ๋๋ ์์คํ ์ ์ํ ๋๊ตฌ ๋ชจ์์ ๋๋ค. OpenOCD์ ๋น์ทํ์ง๋ง, Rust์ ๋ ์ ํตํฉ๋์ด ์์ต๋๋ค.
- SWD (Serial Wire Debug) and JTAG via CMSIS-DAP, ST-Link and J-Link probes
- GDB ์คํ ๋ฐ Microsoft DAP(๋๋ฒ๊ทธ ์ด๋ํฐ ํ๋กํ ์ฝ) ์๋ฒ
- Cargo์ ํตํฉ๋จ
cargo-embed
is a cargo subcommand to build and flash binaries, log RTT (Real Time Transfers) output and connect GDB. Itโs configured by an Embed.toml
file in your project directory.
- CMSIS-DAP๋ Arm์์ ์ ์ํ ํ๋กํ ์ฝ๋ก, USB๋ฅผ ํตํด Arm Cortex ํ๋ก์ธ์์ CoreSight ๋๋ฒ๊ทธ ์ก์ธ์ค ํฌํธ์ ์ ๊ทผํ ์ ์๊ฒ ํด ์ค๋๋ค. BBC micro:bit์ ์๋ ์จ๋ณด๋ ๋๋ฒ๊ฑฐ๋ ์ด ํ๋กํ ์ฝ์ ์ง์ํฉ๋๋ค.
- ST-Link๋ ST Microelectronics์ฌ์์ ๋ง๋ in-circuit ๋๋ฒ๊ฑฐ๋ค์ด๋ฉฐ, J-Link๋ SEGGER์ฌ์ in-circuit ๋๋ฒ๊ฑฐ๋ค์ ๋๋ค.
- ๋๋ฒ๊ทธ ์ก์ธ์ค ํฌํธ์ ๋ฌผ๋ฆฌ์ ์ธ ๊ตฌ์ฑ์ ์ผ๋ฐ์ ์ผ๋ก 5ํ JTAG ์ธํฐํ์ด์ค ํน์, 2ํ Serial Wire Debug ์ธํฐํ์ด์ค ์ ๋๋ค.
- probe-rs๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๊ตฌํ๋์ด ์์ด์, ๋ค๋ฅธ ๋๊ตฌ๋ค์ ํตํฉ๋๊ธฐ๊ฐ ์ฝ์ต๋๋ค.
- Microsoft ๋๋ฒ๊ทธ ์ด๋ํฐ ํ๋กํ ์ฝ์ ์ฌ์ฉํ๋ฉด VSCode๋ ๋ค๋ฅธ IDE ์์์ ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ์์ ์ํ์ค์ธ ์ฝ๋๋ฅผ ๋๋ฒ๊น ํ ์ ์์ต๋๋ค.
- cargo-embed๋ probe-rs ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋๋ ๋ฐ์ด๋๋ฆฌ์ ๋๋ค.
- RTT(Real Time Transfers)๋ ์ฌ๋ฌ ๋ง ๋ฒํผ๋ฅผ ํตํด ๋๋ฒ๊ทธ ํธ์คํธ์ ํ๊ฒ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ ๋ฉ์ปค๋์ฆ์ ๋๋ค.
๋๋ฒ๊น
Embed.toml:
[default.general]
chip = "nrf52833_xxAA"
[debug.gdb]
enabled = true
src/bare-metal/microcontrollers/example/
์ ํฐ๋ฏธ๋์ ์ด๊ณ :
cargo embed --bin board_support debug
In another terminal in the same directory:
gLinux ๋๋ Debian์์:
gdb-multiarch target/thumbv7em-none-eabihf/debug/board_support --eval-command="target remote :1337"
MacOS์์:
arm-none-eabi-gdb target/thumbv7em-none-eabihf/debug/board_support --eval-command="target remote :1337"
GDB์์ ๋ค์์ ์คํํด ๋ณด์ธ์.
b src/bin/board_support.rs:29
b src/bin/board_support.rs:30
b src/bin/board_support.rs:32
c
c
c
๋ค๋ฅธ ํ๋ก์ ํธ
- RTIC
- โ์ค์๊ฐ ์ธํฐ๋ฝํธ ๊ธฐ๋ฐ ๋์ ์คํ(Real-Time Interrupt-driven Concurrency)โ
- ๊ณต์ ๋ฆฌ์์ค ๊ด๋ฆฌ, ๋ฉ์์ง ์ ๋ฌ, ํ์คํฌ ์ค์ผ์ค๋ง, ํ์ด๋จธ ๋๊ธฐ์ด ์ง์
- Embassy
- ์ฐ์ ์์, ํ์ด๋จธ, ๋คํธ์ํน, USB๊ฐ ํฌํจ๋
async
์คํ์
- ์ฐ์ ์์, ํ์ด๋จธ, ๋คํธ์ํน, USB๊ฐ ํฌํจ๋
- TockOS
- ์ ์ ํ ์ค์ผ์ค๋ง ๋ฐ MMU๋ฅผ ์ง์ํ๋, ๋ณด์์ ์ค์ ์ ๋ ์ค์๊ฐ ์ด์์ฒด์
- Hubris
- Oxide Computer Company์์ ๋ง๋ ๋ง์ดํฌ๋ก์ปค๋ ๊ธฐ๋ฐ ์ค์๊ฐ ์ด์์ฒด์ ๋ก, ๋ฉ๋ชจ๋ฆฌ ๋ณดํธ, ๊ถํ์ด ์์ด ์ํ๋๋ ๋๋ผ์ด๋ฒ ๋ฑ์ ์ง์ํจ.
- FreeRTOS์ฉ ๋ฐ์ธ๋ฉ
std
๊ฐ ๊ตฌํ๋ ํ๋ซํผ๋ ์์ต๋๋ค(์: esp-idf).
- RTIC๋ ์ค์๊ฐ ์ด์์ฒด์ ๋ก ๋ณผ ์๋ ์๊ณ , ๋์์ฑ ์ง์์ ์ํ ํ๋ ์์ํฌ๋ก ๋ณผ ์๋ ์์ต๋๋ค.
- HAL์ ํฌํจํ์ง๋ ์์ต๋๋ค.
- ์ค์ผ์ค๋ง์ ์ปค๋์ด ์๋๋ผ Cortex-M NVIC(Nested Virtual Interrupt Controller)๋ก ๊ตฌํ์ด ๋ฉ๋๋ค.
- Cortex-M ์ ์ฉ์ ๋๋ค.
- Google์์๋ Titan ๋ณด์ ํค์ ์ฌ์ฉ๋๋ Haven ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ์์ TockOS๋ฅผ ์ฌ์ฉํฉ๋๋ค.
- FreeRTOS๋ ๋๋ถ๋ถ C๋ก ์์ฑ๋์ง๋ง, ์ ํ๋ฆฌ์ผ์ด์ ์ Rust๋ก ์์ฑํ ์ ์๋๋ก ํด ์ฃผ๋ Rust ๋ฐ์ธ๋ฉ์ด ์ ๊ณต๋ฉ๋๋ค.
์ฐ์ต๋ฌธ์
I2C ๋์นจ๋ฐ์์ ๋ฐฉํฅ์ ์ฝ๊ณ , ์ฝ์ ๊ฐ์ ์ง๋ ฌ ํฌํธ์ ๊ธฐ๋กํ์ธ์.
After looking at the exercises, you can look at the solutions provided.
๋์นจ๋ฐ
I2C ๋์นจ๋ฐ์์ ๋ฐฉํฅ์ ์ฝ๊ณ ํ๋ ๊ฐ์ ์ง๋ ฌ ํฌํธ์ ๊ธฐ๋กํ์ธ์. ์๊ฐ์ด ์์ผ๋ฉด ์ด๋ป๊ฒ๋ LED์ ํ์ํ๊ฑฐ๋ ๋ฒํผ์ ์ฌ์ฉํ์ธ์.
ํํธ:
lsm303agr
๋ฐmicrobit-v2
ํฌ๋ ์ดํธ ๋ฐ micro:bit ํ๋์จ์ด์ ๋ฌธ์๋ฅผ ํ์ธํ์ธ์.- LSM303AGR ๊ด์ฑ ์ธก์ ์ฅ์น๋ ๋ด๋ถ I2C ๋ฒ์ค์ ์ฐ๊ฒฐ๋ฉ๋๋ค.
- TWI๋ I2C์ ๋ ๋ค๋ฅธ ์ด๋ฆ์ด๋ฏ๋ก I2C ๋ง์คํฐ ์ฃผ๋ณ๊ธฐ๊ธฐ๋ TWIM์ด๋ผ๊ณ ํฉ๋๋ค.
- LSM303AGR ๋๋ผ์ด๋ฒ์๋
embedded_hal::blocking::i2c::WriteRead
ํธ๋ ์์ ๊ตฌํํ๋ ๊ฒ์ด ํ์ํฉ๋๋ค.microbit::hal::Twim
๊ตฌ์กฐ์ฒด๊ฐ ์ด๋ฅผ ๊ตฌํํฉ๋๋ค. - ๋ค์ํ ํ๊ณผ ์ฃผ๋ณ๊ธฐ๊ธฐ์ ๊ดํ ํ๋๊ฐ ์๋
microbit::Board
๊ตฌ์กฐ์ฒด๊ฐ ์์ต๋๋ค. - ์ํ๋ ๊ฒฝ์ฐ nRF52833 ๋ฐ์ดํฐ์ํธ๋ฅผ ํ์ธํ ์๋ ์์ง๋ง ์ด ์ฐ์ต์์ ๋ฐ๋์ ํ์ํ ๊ฒ์ ์๋๋๋ค.
์ฐ์ต ํ
ํ๋ฆฟ์ ๋ค์ด๋ก๋ํ๊ณ compass
๋๋ ํฐ๋ฆฌ์์ ๋ค์ ํ์ผ์ ์ฐพ์ต๋๋ค.
src/main.rs:
#![no_main] #![no_std] extern crate panic_halt as _; use core::fmt::Write; use cortex_m_rt::entry; use microbit::{hal::uarte::{Baudrate, Parity, Uarte}, Board}; #[entry] fn main() -> ! { let board = Board::take().unwrap(); // Configure serial port. let mut serial = Uarte::new( board.UARTE0, board.uart.into(), Parity::EXCLUDED, Baudrate::BAUD115200, ); // Use the system timer as a delay provider. let mut delay = Delay::new(board.SYST); // Set up the I2C controller and Inertial Measurement Unit. // TODO writeln!(serial, "Ready.").unwrap(); loop { // Read compass data and log it to the serial port. // TODO } }
Cargo.toml (๋ณ๊ฒฝํ ํ์๊ฐ ์์):
[workspace]
[package]
name = "compass"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
cortex-m-rt = "0.7.3"
embedded-hal = "1.0.0"
lsm303agr = "0.3.0"
microbit-v2 = "0.13.0"
panic-halt = "0.2.0"
Embed.toml (๋ณ๊ฒฝํ ํ์๊ฐ ์์):
[default.general]
chip = "nrf52833_xxAA"
[debug.gdb]
enabled = true
[debug.reset]
halt_afterwards = true
.cargo/config.toml (๋ณ๊ฒฝํ ํ์๊ฐ ์์):
[build]
target = "thumbv7em-none-eabihf" # Cortex-M4F
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
rustflags = ["-C", "link-arg=-Tlink.x"]
๋ค์์ ์ฌ์ฉํ์ฌ Linux์์ ์ง๋ ฌ ์ถ๋ ฅ์ ํ์ธํ์ธ์.
picocom --baud 115200 --imap lfcrlf /dev/ttyACM0
๋๋ ๋ค์๊ณผ ๊ฐ์ด Mac OS(๊ธฐ๊ธฐ ์ด๋ฆ์ ์ฝ๊ฐ ๋ค๋ฅผ ์ ์์)์์ ํ์ธํ์ธ์.
picocom --baud 115200 --imap lfcrlf /dev/tty.usbmodem14502
Ctrl+A Ctrl+Q๋ฅผ ์ฌ์ฉํ์ฌ picocom์ ์ข ๋ฃํฉ๋๋ค.
Bare Metal Rust Morning Exercise
๋์นจ๋ฐ
(์ฐ์ต๋ฌธ์ ๋ก ๋์๊ฐ๊ธฐ)
#![no_main] #![no_std] extern crate panic_halt as _; use core::fmt::Write; use cortex_m_rt::entry; use core::cmp::{max, min}; use lsm303agr::{ AccelMode, AccelOutputDataRate, Lsm303agr, MagMode, MagOutputDataRate, }; use microbit::display::blocking::Display; use microbit::hal::prelude::*; use microbit::hal::twim::Twim; use microbit::hal::uarte::{Baudrate, Parity, Uarte}; use microbit::hal::{Delay, Timer}; use microbit::pac::twim0::frequency::FREQUENCY_A; use microbit::Board; const COMPASS_SCALE: i32 = 30000; const ACCELEROMETER_SCALE: i32 = 700; #[entry] fn main() -> ! { let board = Board::take().unwrap(); // ์ง๋ ฌ ํฌํธ๋ฅผ ์ค์ ํ์ธ์. let mut serial = Uarte::new( board.UARTE0, board.uart.into(), Parity::EXCLUDED, Baudrate::BAUD115200, ); // Use the system timer as a delay provider. let mut delay = Delay::new(board.SYST); // I2C ์ปจํธ๋กค๋ฌ์ ๊ด์ฑ ์ธก์ ์ฅ์น๋ฅผ ์ค์ ํฉ๋๋ค. writeln!(serial, "IMU ์ค์ ์ค...").unwrap(); let i2c = Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100); let mut imu = Lsm303agr::new_with_i2c(i2c); imu.init().unwrap(); imu.set_mag_mode_and_odr( &mut delay, MagMode::HighResolution, MagOutputDataRate::Hz50, ) .unwrap(); imu.set_accel_mode_and_odr( &mut delay, AccelMode::Normal, AccelOutputDataRate::Hz50, ) .unwrap(); let mut imu = imu.into_mag_continuous().ok().unwrap(); // ๋์คํ๋ ์ด ๋ฐ ํ์ด๋จธ๋ฅผ ์ค์ ํฉ๋๋ค. let mut timer = Timer::new(board.TIMER0); let mut display = Display::new(board.display_pins); let mut mode = Mode::Compass; let mut button_pressed = false; writeln!(serial, "Ready").unwrap(); loop { // ๋์นจ๋ฐ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ ์ง๋ ฌ ํฌํธ์ ๊ธฐ๋กํฉ๋๋ค. while !(imu.mag_status().unwrap().xyz_new_data() && imu.accel_status().unwrap().xyz_new_data()) {} let compass_reading = imu.magnetic_field().unwrap(); let accelerometer_reading = imu.acceleration().unwrap(); writeln!( serial, "{},{},{}\t{},{},{}", compass_reading.x_nt(), compass_reading.y_nt(), compass_reading.z_nt(), accelerometer_reading.x_mg(), accelerometer_reading.y_mg(), accelerometer_reading.z_mg(), ) .unwrap(); let mut image = [[0; 5]; 5]; let (x, y) = match mode { Mode::Compass => ( scale(-compass_reading.x_nt(), -COMPASS_SCALE, COMPASS_SCALE, 0, 4) as usize, scale(compass_reading.y_nt(), -COMPASS_SCALE, COMPASS_SCALE, 0, 4) as usize, ), Mode::Accelerometer => ( scale( accelerometer_reading.x_mg(), -ACCELEROMETER_SCALE, ACCELEROMETER_SCALE, 0, 4, ) as usize, scale( -accelerometer_reading.y_mg(), -ACCELEROMETER_SCALE, ACCELEROMETER_SCALE, 0, 4, ) as usize, ), }; image[y][x] = 255; display.show(&mut timer, image, 100); // ๋ฒํผ A๋ฅผ ๋๋ฅด๋ฉด ๋ค์ ๋ชจ๋๋ก ์ ํ๋๊ณ ๋ชจ๋ LED๊ฐ ์ ์ ๊น๋ฐ์ ๋๋ค. if board.buttons.button_a.is_low().unwrap() { if !button_pressed { mode = mode.next(); display.show(&mut timer, [[255; 5]; 5], 200); } button_pressed = true; } else { button_pressed = false; } } } #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum Mode { Compass, Accelerometer, } impl Mode { fn next(self) -> Self { match self { Self::Compass => Self::Accelerometer, Self::Accelerometer => Self::Compass, } } } fn scale(value: i32, min_in: i32, max_in: i32, min_out: i32, max_out: i32) -> i32 { let range_in = max_in - min_in; let range_out = max_out - min_out; cap(min_out + range_out * (value - min_in) / range_in, min_out, max_out) } fn cap(value: i32, min_value: i32, max_value: i32) -> i32 { max(min_value, min(value, max_value)) }
์ ํ๋ฆฌ์ผ์ด์ ํ๋ก์ธ์
์ง๊ธ๊น์ง Arm Cortex-M ์๋ฆฌ์ฆ์ ๊ฐ์ ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ์ ๊ดํด ์์๋ดค์ต๋๋ค. ์ด์ ์ ํ๋ฆฌ์ผ์ด์ ํ๋ก์ธ์์ธ Cortex-A๋ฅผ ์ํ ์ฝ๋๋ฅผ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค. ํธ์์ QEMU์ aarch64 โvirtโ ๋ณด๋๋ฅผ ์ฌ์ฉํฉ๋๋ค.
- ์ผ๋ฐ์ ์ผ๋ก ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ์๋ MMU ๋๋ ๋ค์ค ๋ ๋ฒจ ๊ถํ(Arm CPU์์๋ ์ต์ ์ ๋ ๋ฒจ(exception level), x86์์๋ ๋ง(ring))์ด ์์ต๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ํ๋ก์ธ์๋ ์ด๋ค์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
- QEMU๋ ์ํคํ ์ฒ๋ณ๋ก ๋ค์ํ ๋จธ์ ๋๋ ๋ณด๋ ๋ชจ๋ธ์ ์๋ฎฌ๋ ์ด์ ํ ์ ์์ต๋๋ค. โvirtโ ๋ณด๋๋ ํน์ ์ค์ ํ๋์จ์ด๋ฅผ ์๋ฎฌ๋ ์ด์ ํ์ง ์์ผ๋ฉฐ, ๊ฐ์ ๋จธ์ ์ฉ์ผ๋ก๋ง ์ค๊ณ๋์์ต๋๋ค.
Rust ์ํ ์ค๋น
Rust ์ฝ๋ ์คํ์ ์์ํ๊ธฐ ์ ์ ๋ช ๊ฐ์ง ์ด๊ธฐํ๋ฅผ ์คํํด์ผ ํฉ๋๋ค.
.section .init.entry, "ax"
.global entry
entry:
/*
* ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ตฌ์ฑ์ ๋ก๋ํ๊ณ ์ ์ฉํฉ๋๋ค. MMU์ ์บ์๋ฅผ
* ์ฌ์ฉ ์ค์ ํ ์ค๋น๊ฐ ๋์์ต๋๋ค. .
*/
adrp x30, idmap
msr ttbr0_el1, x30
mov_i x30, .Lmairval
msr mair_el1, x30
mov_i x30, .Ltcrval
/* ์ง์๋๋ PA ๋ฒ์๋ฅผ TCR_EL1.IPS์ ๋ณต์ฌํฉ๋๋ค. */
mrs x29, id_aa64mmfr0_el1
bfi x30, x29, #32, #4
msr tcr_el1, x30
mov_i x30, .Lsctlrval
/*
* ์ด ์ง์ ์์ ์ค๋ ๋ชจ๋ ๋ด์ฉ์ด ์๋ฃ๋์๋์ง ํ์ธํ๊ณ
* ์ค๋๋ ๋ก์ปฌ TLB ํญ๋ชฉ์ ์ฌ์ฉํ๊ธฐ ์ ์ ๋ฌดํจํํฉ๋๋ค.
*/
isb
tlbi vmalle1
ic iallu
dsb nsh
isb
/*
* MMU์ ์บ์๋ฅผ ์ฌ์ฉํ๋๋ก sctlr_el1์ ๊ตฌ์ฑํ๊ณ ์ด ์์
์ด
* ์๋ฃ๋ ๋๊น์ง ์งํํ์ง ์์ต๋๋ค.
*/
msr sctlr_el1, x30
isb
/* EL1์์ ํธ๋ํ ๋ถ๋ ์์์ ์ก์ธ์ค๋ฅผ ์ฌ์ฉ ์ค์งํฉ๋๋ค. */
mrs x30, cpacr_el1
orr x30, x30, #(0x3 << 20)
msr cpacr_el1, x30
isb
/* bss ์น์
์ 0์ผ๋ก ๋ง๋ญ๋๋ค. */
adr_l x29, bss_begin
adr_l x30, bss_end
0: cmp x29, x30
b.hs 1f
stp xzr, xzr, [x29], #16
b 0b
1: /* ์คํ์ ์ค๋นํฉ๋๋ค. */
adr_l x30, boot_stack_end
mov sp, x30
/* ์์ธ ๋ฒกํฐ๋ฅผ ์ค์ ํฉ๋๋ค. */
adr x30, vector_table_el1
msr vbar_el1, x30
/* Rust ์ฝ๋๋ฅผ ํธ์ถํฉ๋๋ค. */
bl main
/* ๋ฃจํ๊ฐ ์ธํฐ๋ฝํธ๋ฅผ ์์ํ ๊ธฐ๋ค๋ฆฝ๋๋ค. */
2: wfi
b 2b
- ์ด๋ C์ ๊ฒฝ์ฐ์ ๋์ผํฉ๋๋ค. ํ๋ก์ธ์ ์ํ๋ฅผ ์ด๊ธฐํํ๊ณ BSS๋ฅผ 0์ผ๋ก ์ค์ ํ๊ณ ์คํ ํฌ์ธํฐ๋ฅผ ์ค์ ํฉ๋๋ค.
- BSS(์ญ์ฌ์ ์ธ ์ด์ ๋ก, ๋ธ๋ก ์์ ๊ธฐํธ block starting symbol ์ด๋ผ๊ณ ๋ถ๋ฆผ)๋ ์ค๋ธ์ ํธ ํ์ผ์์ 0์ผ๋ก ์ด๊ธฐํ๋ ์ ์ ์ผ๋ก ํ ๋น๋ ๋ณ์๋ค์ ๋ด๊ณ ์์ต๋๋ค. BSS๋ ์ค์ ๋ก ์ด๋ฏธ์ง์ ํฌํจ๋์ง๋ ์์ต๋๋ค. ์ด์ฐจํผ 0์ผ๋ก ์ด๊ธฐํ ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ปดํ์ผ๋ฌ๋ ๋ก๋ ํ๋ก๊ทธ๋จ์ด BSS์ ์ํ๋ ๋ณ์๋ค์ 0์ผ๋ก ์ด๊ธฐํ ํ ๊ฒ์ผ๋ก๊ธฐ๋ํ๊ณ ์ค๋ธ์ ํธ ํ์ผ์ ๋ง๋ญ๋๋ค.
- ๋ฉ๋ชจ๋ฆฌ๊ฐ ์ด๊ธฐํ๋๊ณ ์ด๋ฏธ์ง๊ฐ ๋ก๋๋๋ ๋ฐฉ์์ ๋ฐ๋ผ BSS๊ฐ ์ด๋ฏธ 0์ผ๋ก ์ค์ ๋์์ ์๋ ์์ง๋ง ํ์คํ ํ๊ธฐ ์ํด 0์ผ๋ก ์ค์ ํฉ๋๋ค.
- ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฝ๊ฑฐ๋ ์ฐ๊ธฐ ์ ์ MMU์ ์บ์๋ฅผ ์ฌ์ฉ ์ค์ ํด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ์ง ์์ผ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ํฉ์ด ๋ฐ์ํ ์ ์์ต๋๋ค.
- ์ ๋ ฌ๋์ง ์์ ์ก์ธ์ค์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ปดํ์ผ๋ฌ๊ฐ ์ ๋ ฌ๋์ง ์์ ์ก์ธ์ค๋ฅผ ์์ฑํ์ง ์๋๋ก
+strict-align
์ ์ค์ ํ๋aarch64-unknown-none
ํ๊ฒ์ Rust ์ฝ๋๋ฅผ ๋น๋ํ๋ฏ๋ก ์ด ๊ฒฝ์ฐ์๋ ๋ฌธ์ ๊ฐ ์์ง๋ง, ์ผ๋ฐ์ ์ผ๋ก ๋ฐ๋์ ๊ทธ๋ฐ ๊ฒ์ ์๋๋๋ค. - VM์์ ์คํํ๋ค๋ฉด ์บ์ ์ผ๊ด์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ฌธ์ ๋ VM์ ์บ์๊ฐ ์ฌ์ฉ ์ค์ง๋ ์ํ๋ก ๋ฉ๋ชจ๋ฆฌ์ ์ง์ ์ก์ธ์คํ๋ ๋ฐ๋ฉด ํธ์คํธ์๋ ๋์ผํ ๋ฉ๋ชจ๋ฆฌ์ ๋ํด ์บ์ํ ์ ์๋ ๋ณ์นญ์ด ์๋ค๋ ์ ์ ๋๋ค. ํธ์คํธ๊ฐ ๋ฉ๋ชจ๋ฆฌ์ ๋ช ์์ ์ผ๋ก ์ก์ธ์คํ์ง ์๋๋ผ๋ ์ถ์ธก ์ก์ธ์ค๋ ์บ์ ์ฑ์ฐ๊ธฐ๋ก ์ด์ด์ง ์ ์์ผ๋ฉฐ, ์บ์๊ฐ ์ ๋ฆฌ๋๊ฑฐ๋ VM์ด ์บ์๋ฅผ ์ฌ์ฉ ์ค์ ํ๋ฉด ๋ ์ค ํ๋์ ๋ณ๊ฒฝ์ฌํญ์ด ์์ค๋ฉ๋๋ค. ์บ์๋ VA ๋๋ IPA๊ฐ ์๋ ์ค์ ์ฃผ์๋ก ํค๊ฐ ์ง์ ๋ฉ๋๋ค.
- ์ ๋ ฌ๋์ง ์์ ์ก์ธ์ค์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ปดํ์ผ๋ฌ๊ฐ ์ ๋ ฌ๋์ง ์์ ์ก์ธ์ค๋ฅผ ์์ฑํ์ง ์๋๋ก
- ํธ์์ ๊ธฐ๊ธฐ์ฉ ์ฃผ์ ๊ณต๊ฐ์ ์ฒ์ 1GiB, DRAM์ฉ ๋ค์ 1GiB, ๋ ๋ง์ ๊ธฐ๊ธฐ๋ฅผ ์ํ ๋ ๋ค๋ฅธ 1GiB๋ฅผ ID ๋งคํํ๋ ํ๋์ฝ๋ฉ๋ ํ์ด์ง ํ
์ด๋ธ(
idmap.S
์ฐธ๊ณ )์ ์ฌ์ฉํฉ๋๋ค. ์ด๋ QEMU์์ ์ฌ์ฉํ๋ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์๊ณผ ์ผ์นํฉ๋๋ค. - ์์ธ ๋ฒกํฐ(
vbar_el1
)๋ ์ค์ ํฉ๋๋ค. ์ด์ ๊ดํด์๋ ๋์ค์ ์์ธํ ์์๋ด ๋๋ค. - ์ค๋ ์คํ์ ๋ชจ๋ ์์์๋ ์์ธ ์์ค 1(EL1)์์ ์คํํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. ๋ค๋ฅธ ์์ธ ์์ค์์ ์คํํด์ผ ํ๋ ๊ฒฝ์ฐ ์ด์ ๋ฐ๋ผ
entry.S
๋ฅผ ์์ ํด์ผ ํฉ๋๋ค.
์ธ๋ผ์ธ ์ด์ ๋ธ๋ฆฌ
Sometimes we need to use assembly to do things that arenโt possible with Rust code. For example, to make an HVC (hypervisor call) to tell the firmware to power off the system:
#![no_main] #![no_std] use core::arch::asm; use core::panic::PanicInfo; mod exceptions; const PSCI_SYSTEM_OFF: u32 = 0x84000008; #[no_mangle] extern "C" fn main(_x0: u64, _x1: u64, _x2: u64, _x3: u64) { // ์ ์ธ๋ ๋ ์ง์คํฐ๋ง ์ฌ์ฉํ๊ณ ๋ฉ๋ชจ๋ฆฌ๋ก๋ // ์๋ฌด๊ฒ๋ ํ์ง ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { asm!("hvc #0", inout("w0") PSCI_SYSTEM_OFF => _, inout("w1") 0 => _, inout("w2") 0 => _, inout("w3") 0 => _, inout("w4") 0 => _, inout("w5") 0 => _, inout("w6") 0 => _, inout("w7") 0 => _, options(nomem, nostack) ); } loop {} }
์ค์ ๋ก ์ด๋ฅผ ์คํํ๋ ค๋ฉด ์ด๋ฌํ ๋ชจ๋ ํจ์๋ฅผ ์ํ ๋ํผ๊ฐ ํฌํจ๋ smccc
ํฌ๋ ์ดํธ๋ฅผ ์ฌ์ฉํ์ธ์.
- PSCI (Power State Coordination Interface)๋ ์์คํ ๋ฐ CPU ์ ์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ Arm์ ํ์ค ์ธํฐํ์ด์ค์ ๋๋ค. ์ด ์ธํฐํ์ด์ค๋ EL3 ํ์จ์ด์ ํ์ดํผ๋ฐ์ด์ ์ ์ํด ๊ตฌํ๋ฉ๋๋ค.
0 => _
๋ฌธ๋ฒ์ ์ธ๋ผ์ธ ์ด์ ๋ธ๋ฆฌ ์ฝ๋๋ฅผ ์คํํ๊ธฐ ์ ์ ๋ ์ง์คํฐ๋ฅผ 0์ผ๋ก ์ด๊ธฐํํ๊ณ ๊ทธ ํ์๋ ๊ทธ ๋ ์ง์คํฐ์ ๊ฐ์ ๋ฌด์ํจ์ ์๋ฏธํฉ๋๋ค. ํธ์ถ ์ ๋ ์ง์คํฐ์ ๊ฐ์ด ๋ฎ์ด ์จ์ง ์ ์์ผ๋ฏ๋กin
๋์inout
์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.- ์ด
main
ํจ์๋#[no_mangle]
๋ฐextern "C"
์ฌ์ผ ํฉ๋๋ค. ์๋ํ๋ฉด ์ด ํจ์๋ Rust ์ฝ๋๊ฐ ์๋, ์ด์ ๋ธ๋ฌ๋ก ์์ฑ๋entry.S
์์ ํธ์ถ๋๊ธฐ ๋๋ฌธ์ ๋๋ค. _x0
โ_x3
๋x0
์์x3
๋ ์ง์คํฐ๋ค์ ๊ฐ์ ๋๋ค. ์ด ๋ ์ง์คํฐ๋ค์ ์ผ๋ฐ์ ์ผ๋ก ๋ถํธ๋ก๋์์ ๋๋ฐ์ด์ค ํธ๋ฆฌ์ ๋ํ ํฌ์ธํฐ ๋ฑ์ ์ ๋ฌํ ๋ ์ฌ์ฉ๋ฉ๋๋ค. ํ์ค aarch64 ํธ์ถ ๊ท์ฝ(extern "C"
์์ ์ฌ์ฉํ๋๋ก ์ง์ )์ ๋ฐ๋ผ ๋ ์ง์คํฐx0
์์x7
์ด ํจ์์ ์ ๋ฌ๋ ์ฒ์ 8๊ฐ ์ธ์์ ์ฌ์ฉ๋๋ฏ๋กentry.S
๋ ์ด๋ฌํ ๋ ์ง์คํฐ๋ฅผ ๋ณ๊ฒฝํ์ง ์๋์ง ํ์ธํ๋ ๊ฒ ์ธ์๋ ํน๋ณํ ํ ์์ ์ด ์์ต๋๋ค.src/bare-metal/aps/examples
์์make qemu_psci
๋ฅผ ์ ๋ ฅํ๋ฉด ์์ ์ฝ๋๊ฐ QEMU์์ ์ํ๋ฉ๋๋ค.
MMIO๋ฅผ ์ํ ํ๋ฐ์ฑ(volatile) ๋ฉ๋ชจ๋ฆฌ ์ก์ธ์ค
pointer::read_volatile
๋ฐpointer::write_volatile
์ ์ฌ์ฉํ์ธ์.- ์ฐธ์กฐ๋ฅผ ์ ์งํ์ง ๋ง์ธ์.
addr_of!
๋ฅผ ์ฌ์ฉํ๋ฉด ์์ ์ฉ๋์ ์ฐธ์กฐ๋ฅผ ๋ง๋ค์ง ์๊ณ ๋ ๊ตฌ์กฐ์ฒด์ ํ๋๋ฅผ ์ฝ์ ์ ์์ต๋๋ค.
- ํ๋ฐ์ฑ(volatile) ์ก์ธ์ค: ์ฝ๊ธฐ ๋๋ ์ฐ๊ธฐ ์์
์ด ๋ถ์ ํจ๊ณผ(side effect)๋ฅผ ๋๋ฐํ ์ ์๊ธฐ ๋๋ฌธ์ ์ปดํ์ผ๋ฌ๋ ํ๋์จ์ด๊ฐ ์์๋ก ์ด๋ฅผ ์ฝ๊ธฐ ์ฐ๊ธฐ ์์
์ ์์๋ฅผ ๋ฐ๊พธ๊ฑฐ๋, ์ค๋ณตํด์ ์ํํ๊ฑฐ๋ ๋๋ ์ ๊ฑฐํ์ง ๋ชปํ๊ฒ ํฉ๋๋ค.
- ์ผ๋ฐ์ ์ผ๋ก ์ฐ๊ณ ๋ ํ ์ฝ์ผ๋ฉด(์: ๋ณ๊ฒฝ ๊ฐ๋ฅํ ์ฐธ์กฐ๋ฅผ ํตํด) ์ปดํ์ผ๋ฌ๋ ์ฝ์ ๊ฐ์ด ๋ฐฉ๊ธ ์ด ๊ฐ๊ณผ ๋์ผํ๋ค๊ณ ๊ฐ์ ํ๊ณ ์ค์ ๋ก ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฝ์ง ์์ ์ ์์ต๋๋ค.
- ํ๋์จ์ด์ ๋ํ ํ๋ฐ์ฑ ์ก์ธ์ค๋ฅผ ์ํ ์ผ๋ถ ๊ธฐ์กด ํฌ๋ ์ดํธ๋ ์ฐธ์กฐ๋ฅผ ์ ์งํ์ง๋ง ์ด๋ ์ฌ๋ฐ๋ฅธ ๊ฒ์ด ์๋๋๋ค. ์ฐธ์กฐ๊ฐ ์์ ๋๋ง๋ค ์ปดํ์ผ๋ฌ๋ ์ด๋ฅผ ์ญ์ฐธ์กฐํ๋๋ก ์ ํํ ์ ์์ต๋๋ค.
addr_of!
๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌ์กฐ์ฒด ํฌ์ธํฐ์์ ๊ตฌ์กฐ์ฒด ํ๋ ํฌ์ธํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
UART ๋๋ผ์ด๋ฒ ์์ฑ
QEMU์ โvirtโ ๋ณด๋์๋ PL011 UART๊ฐ ์์ผ๋ฏ๋ก ์ด๋ฅผ ์ํ ๋๋ผ์ด๋ฒ๋ฅผ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
const FLAG_REGISTER_OFFSET: usize = 0x18; const FR_BUSY: u8 = 1 << 3; const FR_TXFF: u8 = 1 << 5; /// PL011 UART์ ์ต์ ๋๋ผ์ด๋ฒ์ ๋๋ค. #[derive(Debug)] pub struct Uart { base_address: *mut u8, } impl Uart { /// ์ง์ ๋ ๊ธฐ๋ณธ ์ฃผ์์ PL011 ๊ธฐ๊ธฐ์ ๋ํ UART ๋๋ผ์ด๋ฒ์ ์ ์ธ์คํด์ค๋ฅผ /// ์์ฑํฉ๋๋ค. /// /// # ์์ /// /// ์ง์ ๋ ๊ธฐ๋ณธ ์ฃผ์๋ PL011 ๊ธฐ๊ธฐ์ /// MMIO ์ ์ด ๋ ์ง์คํฐ 8๊ฐ๋ฅผ ๊ฐ๋ฆฌ์ผ์ผ ํ๋ฉฐ, /// ์ด๋ ํ๋ก์ธ์ค์ ์ฃผ์ ๊ณต๊ฐ์ ๊ธฐ๊ธฐ ๋ฉ๋ชจ๋ฆฌ๋ก /// ๋งคํ๋์ด์ผ ํ๋ฉฐ ๋ค๋ฅธ ๋ณ์นญ์ ์์ด์ผ ํฉ๋๋ค. pub unsafe fn new(base_address: *mut u8) -> Self { Self { base_address } } /// UART์ ๋จ์ผ ๋ฐ์ดํธ๋ฅผ ์๋๋ค. pub fn write_byte(&self, byte: u8) { // TX ๋ฒํผ์ ๊ณต๊ฐ์ด ํ๋ณด๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค. while self.read_flag_register() & FR_TXFF != 0 {} // ๊ธฐ๋ณธ ์ฃผ์๊ฐ ์ ์ ํ๊ฒ ๋งคํ๋ PL011 ๊ธฐ๊ธฐ์ ์ ์ด ๋ ์ง์คํฐ๋ฅผ // ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { // TX ๋ฒํผ์ ์๋๋ค. self.base_address.write_volatile(byte); } // UART๊ฐ ๋ ์ด์ ์ฌ์ฉ ์ค์ด ์๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค. while self.read_flag_register() & FR_BUSY != 0 {} } fn read_flag_register(&self) -> u8 { // ๊ธฐ๋ณธ ์ฃผ์๊ฐ ์ ์ ํ๊ฒ ๋งคํ๋ PL011 ๊ธฐ๊ธฐ์ ์ ์ด ๋ ์ง์คํฐ๋ฅผ // ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { self.base_address.add(FLAG_REGISTER_OFFSET).read_volatile() } } }
Uart::new
๋ ์์ ํ์ง ์์ง๋ง(usafe), ๊ทธ ์ธ ๋ค๋ฅธ ๋ฉ์๋๋ค์ ์์ ํ(safe) ์ ์ ์ฃผ๋ชฉํ์ธ์.๋ค๋ฅธ ๋ฉ์๋๋ค์ด ์์ ํ ์ ์๋ ์ด์ ๋,Uart::new
์ ์์ ์๊ตฌ์ฌํญ(์ฆ, ์ง์ ๋ UART์ ๋๋ผ์ด๋ฒ ์ธ์คํด์ค๊ฐ ํ๋๋ง ์์ผ๋ฉฐ ์ฃผ์ ๊ณต๊ฐ์ ๋ณ์นญ์ ์ง์ ํ๋ ๋ค๋ฅธ ํญ๋ชฉ์ด ์์) ์ด ๋ง์กฑ๋๊ธฐ๋ง ํ๋ฉดwrite_byte
์ ๊ฐ์ ํจ์๋ฅผ ์์ ํ๊ฒ ํธ์ถํ๋๋ฐ ์์ด์ ํ์ํ ๋ชจ๋ ์ ์ ์กฐ๊ฑด์ด ๋ง์กฑ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.- ๋ฐ๋ ๋ฐฉ๋ฒ์ผ๋ก๋ ์คํํ ์ ์์ง๋ง(
new
๋ฅผ ์์ ํ๊ฒ ๋ง๋ค๊ณwrite_byte
๋ฅผ ์์ ํ์ง ์๊ฒ ๋ง๋ฆ) ์ด๋write_byte
๋ฅผ ํธ์ถํ๋ ๋ชจ๋ ์์น์์ ์์ ์ฑ์ ๊ดํด ์ถ๋ก ํด์ผ ํ๋ฏ๋ก ์ฌ์ฉ ํธ์์ฑ์ด ํจ์ฌ ๋จ์ด์ง๋๋ค. - ์ด๋ ์์ ํ์ง ์์ ์ฝ๋์ ์์ ํ ๋ํผ๋ฅผ ์์ฑํ๋ ์ผ๋ฐ์ ์ธ ํจํด์ ๋๋ค. ์์ ์ ๊ดํ ์ฆ๋ช ๋ถ๋ด์ ์ฌ๋ฌ ๋ง์ ์์น์์ ์์์ ์์น๋ก ์ฎ๊ธฐ๋ ๊ฒ์ ๋๋ค.
More traits
Debug
ํธ๋ ์์ ์์ํ์ต๋๋ค. ๋ช ๊ฐ์ง ํธ๋ ์์ ์ถ๊ฐ๋ก ๋ ๊ตฌํํ๋ ๊ฒ๋ ์ ์ฉํ ์ ์์ต๋๋ค.
use core::fmt::{self, Write}; impl Write for Uart { fn write_str(&mut self, s: &str) -> fmt::Result { for c in s.as_bytes() { self.write_byte(*c); } Ok(()) } } // ๋ชจ๋ ์ปจํ ์คํธ์์ ์ก์ธ์คํ ์ ์๋ ๊ธฐ๊ธฐ ๋ฉ๋ชจ๋ฆฌ์ ๋ํ // ํฌ์ธํฐ๋ง ํฌํจํ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe impl Send for Uart {}
Write
๋ฅผ ๊ตฌํํ๋ฉดwrite!
๋ฐwriteln!
๋งคํฌ๋ก๋ฅผUart
ํ์ ๊ณผ ํจ๊ป ์ฌ์ฉํ ์ ์์ต๋๋ค.src/bare-metal/aps/examples
์์make qemu_minimal
์ ์ฌ์ฉํ์ฌ QEMU์์ ์๋ฅผ ์คํํฉ๋๋ค.
๋ ๋์ UART ๋๋ผ์ด๋ฒ
PL011์๋ ์ค์ ๋ก ํจ์ฌ ๋ ๋ง์ ๋ ์ง์คํฐ๊ฐ ์์ผ๋ฉฐ, ์ด์ ์ก์ธ์คํ ํฌ์ธํฐ๋ฅผ ๊ตฌ์ฑํ๊ธฐ ์ํด ์คํ์ ์ ์ถ๊ฐํ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ๊ณ ์ฝ๊ธฐ ์ด๋ ต์ต๋๋ค. ๋ํ ๊ทธ์ค ์ผ๋ถ๋ ๊ตฌ์กฐํ๋ ๋ฐฉ์์ผ๋ก ์ก์ธ์คํ ์ ์๋ ๋นํธ ํ๋์ ๋๋ค.
์คํ์ | ๋ ์ง์คํฐ ์ด๋ฆ | ๋๋น |
---|---|---|
0x00 | DR | 12 |
0x04 | RSR | 4 |
0x18 | FR | 9 |
0x20 | ILPR | 8 |
0x24 | IBRD | 16 |
0x28 | FBRD | 6 |
0x2c | LCR_H | 8 |
0x30 | CR | 16 |
0x34 | IFLS | 6 |
0x38 | IMSC | 11 |
0x3c | RIS | 11 |
0x40 | MIS | 11 |
0x44 | ICR | 11 |
0x48 | DMACR | 3 |
- ๊ฐ๊ฒฐ์ฑ์ ์ํด ์ผ๋ถ ID ๋ ์ง์คํฐ๋ ์๋ต๋์์ต๋๋ค.
๋นํธํ๋๊ทธ
bitflags
ํฌ๋ ์ดํธ๋ ๋นํธํ๋๊ทธ๋ฅผ ์ฌ์ฉํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค.
use bitflags::bitflags; bitflags! { /// UART ํ๋๊ทธ ๋ ์ง์คํฐ์ ํ๋๊ทธ์ ๋๋ค. #[repr(transparent)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] struct Flags: u16 { /// ๋ณด๋ด๋ ค๋ฉด ์ง์๋๋ค. const CTS = 1 << 0; /// ๋ฐ์ดํฐ ์ธํธ๊ฐ ์ค๋น๋์์ต๋๋ค. const DSR = 1 << 1; /// ๋ฐ์ดํฐ ์ด๋ํต์ ์ฌ ๊ฐ์ง const DCD = 1 << 2; /// UART์์ ๋ฐ์ดํฐ๋ฅผ ์ ์ก ์ค์ ๋๋ค. const BUSY = 1 << 3; /// ์์ FIFO๊ฐ ๋น์ด ์์ต๋๋ค. const RXFE = 1 << 4; /// ์ ์ก FIFO๊ฐ ๊ฐ๋ ์ฐผ์ต๋๋ค. const TXFF = 1 << 5; /// ์์ FIFO๊ฐ ๊ฐ๋ ์ฐผ์ต๋๋ค. const RXFF = 1 << 6; /// ์ ์ก FIFO๊ฐ ๋น์ด ์์ต๋๋ค. const TXFE = 1 << 7; /// ๋ฒจ์๋ฆฌ ํ์๊ธฐ์ ๋๋ค. const RI = 1 << 8; } }
bitflags!
๋งคํฌ๋ก๋ ํ๋๊ทธ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ค์ ํ๊ธฐ ์ํ ์ฌ๋ฌ ๋ฉ์๋ ๊ตฌํ๊ณผ ํจ๊ปFlags(u16)
์ ๊ฐ์ ์๋ก์ด ํ์ ์ ์์ฑํฉ๋๋ค.
Multiple registers
๊ตฌ์กฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ UART ๋ ์ง์คํฐ์ ๋ฉ๋ชจ๋ฆฌ ๋ ์ด์์์ ๋ํ๋ผ ์ ์์ต๋๋ค.
#[repr(C, align(4))] struct Registers { dr: u16, _reserved0: [u8; 2], rsr: ReceiveStatus, _reserved1: [u8; 19], fr: Flags, _reserved2: [u8; 6], ilpr: u8, _reserved3: [u8; 3], ibrd: u16, _reserved4: [u8; 2], fbrd: u8, _reserved5: [u8; 3], lcr_h: u8, _reserved6: [u8; 3], cr: u16, _reserved7: [u8; 3], ifls: u8, _reserved8: [u8; 3], imsc: u16, _reserved9: [u8; 2], ris: u16, _reserved10: [u8; 2], mis: u16, _reserved11: [u8; 2], icr: u16, _reserved12: [u8; 2], dmacr: u8, _reserved13: [u8; 3], }
#[repr(C)]
๋ C์ ๋์ผํ ๊ท์น์ ๋ฐ๋ผ ๊ตฌ์กฐ์ฒด ํ๋๋ฅผ ์์๋๋ก ๋ฐฐ์นํ๋๋ก ์ปดํ์ผ๋ฌ์ ์ง์ํฉ๋๋ค. ๊ธฐ๋ณธ Rust ํํ์ ์ฌ์ฉํ๋ฉด ์ปดํ์ผ๋ฌ๊ฐ ์ํ๋ ๋๋ก ํ๋์ ์์๋ฅผ ๋ณ๊ฒฝํ ์ ์์ผ๋ฏ๋ก ๊ตฌ์กฐ์ฒด์์ ์์ธก ๊ฐ๋ฅํ ๋ ์ด์์์ ์ฌ์ฉํ๋ ๋ฐ ํ์ํฉ๋๋ค.
๋๋ผ์ด๋ฒ
์ด์ ๋๋ผ์ด๋ฒ์์ ์๋ก์ด Registers
๊ตฌ์กฐ์ฒด๋ฅผ ์ฌ์ฉํด ๋ณด๊ฒ ์ต๋๋ค.
/// PL011 UART์ฉ ๋๋ผ์ด๋ฒ #[derive(Debug)] pub struct Uart { registers: *mut Registers, } impl Uart { /// ์ง์ ๋ ๊ธฐ๋ณธ ์ฃผ์์ PL011 ๊ธฐ๊ธฐ์ ๋ํ UART ๋๋ผ์ด๋ฒ์ ์ ์ธ์คํด์ค๋ฅผ /// ์์ฑํฉ๋๋ค. /// /// # ์์ /// /// ์ง์ ๋ ๊ธฐ๋ณธ ์ฃผ์๋ PL011 ๊ธฐ๊ธฐ์ /// MMIO ์ ์ด ๋ ์ง์คํฐ 8๊ฐ๋ฅผ ๊ฐ๋ฆฌ์ผ์ผ ํ๋ฉฐ, /// ์ด๋ ํ๋ก์ธ์ค์ ์ฃผ์ ๊ณต๊ฐ์ ๊ธฐ๊ธฐ ๋ฉ๋ชจ๋ฆฌ๋ก /// ๋งคํ๋์ด์ผ ํ๋ฉฐ ๋ค๋ฅธ ๋ณ์นญ์ ์์ด์ผ ํฉ๋๋ค. pub unsafe fn new(base_address: *mut u32) -> Self { Self { registers: base_address as *mut Registers } } /// UART์ ๋จ์ผ ๋ฐ์ดํธ๋ฅผ ์๋๋ค. pub fn write_byte(&self, byte: u8) { // TX ๋ฒํผ์ ๊ณต๊ฐ์ด ํ๋ณด๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค. while self.read_flag_register().contains(Flags::TXFF) {} // self.registers๊ฐ ์ ์ ํ๊ฒ ๋งคํ๋ PL011 ๊ธฐ๊ธฐ์ ์ ์ด ๋ ์ง์คํฐ๋ฅผ // ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { // TX ๋ฒํผ์ ์๋๋ค. addr_of_mut!((*self.registers).dr).write_volatile(byte.into()); } // UART๊ฐ ๋ ์ด์ ์ฌ์ฉ ์ค์ด ์๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค. while self.read_flag_register().contains(Flags::BUSY) {} } /// Reads and returns a pending byte, or `None` if nothing has been /// received. pub fn read_byte(&self) -> Option<u8> { if self.read_flag_register().contains(Flags::RXFE) { None } else { let data = unsafe { addr_of!((*self.registers).dr).read_volatile() }; // TODO: ๋นํธ 8~11์์ ์ค๋ฅ ์ํ๋ฅผ ํ์ธํฉ๋๋ค. Some(data as u8) } } fn read_flag_register(&self) -> Flags { // self.registers๊ฐ ์ ์ ํ๊ฒ ๋งคํ๋ PL011 ๊ธฐ๊ธฐ์ ์ ์ด ๋ ์ง์คํฐ๋ฅผ // ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { addr_of!((*self.registers).fr).read_volatile() } } }
addr_of!
/addr_of_mut!
๋ฅผ ์ฌ์ฉํ์ฌ ์ค๊ฐ ์ฐธ์กฐ๋ฅผ ๋ง๋ค์ง ์๊ณ ๊ฐ๋ณ ํ๋ ํฌ์ธํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ์ ๋ถ์์ ํ ์ ์์ต๋๋ค.
Using it
๋๋ผ์ด๋ฒ๋ฅผ ์ฌ์ฉํ์ฌ ์ง๋ ฌ ์ฝ์์ ์ฐ๊ณ ์์ ๋๋ ๋ฐ์ดํธ๋ฅผ ์์ฝํ๋ ๊ฐ๋จํ ํ๋ก๊ทธ๋จ์ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
#![no_main] #![no_std] mod exceptions; mod pl011; use crate::pl011::Uart; use core::fmt::Write; use core::panic::PanicInfo; use log::error; use smccc::psci::system_off; use smccc::Hvc; /// ๊ธฐ๋ณธ PL011 UART์ ๊ธฐ๋ณธ ์ฃผ์์ ๋๋ค. const PL011_BASE_ADDRESS: *mut u32 = 0x900_0000 as _; #[no_mangle] extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) { // `PL011_BASE_ADDRESS`๊ฐ PL011 ๊ธฐ๊ธฐ์ ๊ธฐ๋ณธ ์ฃผ์์ด๊ณ // ์ด ์ฃผ์ ๋ฒ์์ ์ก์ธ์คํ๋ ๋ค๋ฅธ ํญ๋ชฉ์ด ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. let mut uart = unsafe { Uart::new(PL011_BASE_ADDRESS) }; writeln!(uart, "main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})").unwrap(); loop { if let Some(byte) = uart.read_byte() { uart.write_byte(byte); match byte { b'\r' => { uart.write_byte(b'\n'); } b'q' => break, _ => {} } } } writeln!(uart, "์๋ !").unwrap(); system_off::<Hvc>().unwrap(); }
- ์ธ๋ผ์ธ ์ด์
๋ธ๋ฆฌ ์์์์ ๊ฐ์ด ์ด
main
ํจ์๋entry.S
์ ์ง์ ์ ์ฝ๋์์ ํธ์ถ๋ฉ๋๋ค. ์์ธํ ๋ด์ฉ์ ํด๋น ๋ฐํ์ ๋ ธํธ๋ฅผ ์ฐธ๊ณ ํ์ธ์. src/bare-metal/aps/examples
์์make qemu
๋ฅผ ์ฌ์ฉํ์ฌ QEMU์์ ์๋ฅผ ์คํํฉ๋๋ค.
๋ก๊น
log
ํฌ๋ ์ดํธ์ ๋ก๊น
๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ฉด ์ข์ต๋๋ค. ์ด๋ Log
ํธ๋ ์์ ๊ตฌํํ๋ฉด ๋ฉ๋๋ค.
use crate::pl011::Uart; use core::fmt::Write; use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; use spin::mutex::SpinMutex; static LOGGER: Logger = Logger { uart: SpinMutex::new(None) }; struct Logger { uart: SpinMutex<Option<Uart>>, } impl Log for Logger { fn enabled(&self, _metadata: &Metadata) -> bool { true } fn log(&self, record: &Record) { writeln!( self.uart.lock().as_mut().unwrap(), "[{}] {}", record.level(), record.args() ) .unwrap(); } fn flush(&self) {} } /// UART ๋ก๊ฑฐ๋ฅผ ์ด๊ธฐํํฉ๋๋ค. pub fn init(uart: Uart, max_level: LevelFilter) -> Result<(), SetLoggerError> { LOGGER.uart.lock().replace(uart); log::set_logger(&LOGGER)?; log::set_max_level(max_level); Ok(()) }
log
ํจ์ ์์์unwrap
ํ๋ ๊ฒ์ ๊ด์ฐฎ์ต๋๋ค.. ์๋ํ๋ฉดset_logger
๋ฅผ ํธ์ถํ๊ธฐ ์ ์LOGGER
๋ฅผ ์ด๊ธฐํํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
Using it
๋ก๊ฑฐ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ ์ด๊ธฐํํด์ผ ํฉ๋๋ค.
#![no_main] #![no_std] mod exceptions; mod logger; mod pl011; use crate::pl011::Uart; use core::panic::PanicInfo; use log::{error, info, LevelFilter}; use smccc::psci::system_off; use smccc::Hvc; /// ๊ธฐ๋ณธ PL011 UART์ ๊ธฐ๋ณธ ์ฃผ์์ ๋๋ค. const PL011_BASE_ADDRESS: *mut u32 = 0x900_0000 as _; #[no_mangle] extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) { // `PL011_BASE_ADDRESS`๊ฐ PL011 ๊ธฐ๊ธฐ์ ๊ธฐ๋ณธ ์ฃผ์์ด๊ณ // ์ด ์ฃผ์ ๋ฒ์์ ์ก์ธ์คํ๋ ๋ค๋ฅธ ํญ๋ชฉ์ด ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. let uart = unsafe { Uart::new(PL011_BASE_ADDRESS) }; logger::init(uart, LevelFilter::Trace).unwrap(); info!("main({x0:#x}, {x1:#x}, {x2:#x}, {x3:#x})"); assert_eq!(x1, 42); system_off::<Hvc>().unwrap(); } #[panic_handler] fn panic(info: &PanicInfo) -> ! { error!("{info}"); system_off::<Hvc>().unwrap(); loop {} }
- ํจ๋ ํธ๋ค๋ฌ๊ฐ ์ด์ ํจ๋์ ์ธ๋ถ์ ๋ณด๋ฅผ ๊ธฐ๋กํ ์ ์์ต๋๋ค.
src/bare-metal/aps/examples
์์make qemu_logger
๋ฅผ ์ฌ์ฉํ์ฌ QEMU์์ ์๋ฅผ ์คํํฉ๋๋ค.
์์ธ
AArch64๋ 4๊ฐ ์ํ(SP0์ ์ฌ์ฉํ๋ ํ์ฌ EL, SPx๋ฅผ ์ฌ์ฉํ๋ ํ์ฌ EL, AArch64๋ฅผ ์ฌ์ฉํ๋ ํ์ EL, AArch32๋ฅผ ์ฌ์ฉํ๋ ํ์ EL)์ 4๊ฐ์ง ์์ธ ํ์ (๋๊ธฐ, IRQ, FIQ, SError)์ ๋ํด 16๊ฐ ํญ๋ชฉ์ด ์๋ ์์ธ ๋ฒกํฐ ํ ์ด๋ธ์ ์ ์ํฉ๋๋ค. Rust ์ฝ๋๋ฅผ ํธ์ถํ๊ธฐ ์ ์ ํ๋ฐ์ฑ ๋ ์ง์คํฐ๋ฅผ ์คํ์ ์ ์ฅํ๊ธฐ ์ํด ์ด์ ๋ธ๋ฆฌ์์ ์ด๋ฅผ ๊ตฌํํฉ๋๋ค.
use log::error; use smccc::psci::system_off; use smccc::Hvc; #[no_mangle] extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) { error!("sync_exception_current"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn irq_current(_elr: u64, _spsr: u64) { error!("irq_current"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn fiq_current(_elr: u64, _spsr: u64) { error!("fiq_current"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn serr_current(_elr: u64, _spsr: u64) { error!("serr_current"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn sync_lower(_elr: u64, _spsr: u64) { error!("sync_lower"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn irq_lower(_elr: u64, _spsr: u64) { error!("irq_lower"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn fiq_lower(_elr: u64, _spsr: u64) { error!("fiq_lower"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn serr_lower(_elr: u64, _spsr: u64) { error!("serr_lower"); system_off::<Hvc>().unwrap(); }
- EL์ ์์ธ ์์ค์ ๋๋ค. ์ค๋ ์คํ์ ๋ชจ๋ ์๋ EL1์์ ์คํ๋ฉ๋๋ค.
- ํธ์์ ํ์ฌ EL ์์ธ์ SP0๊ณผ SPx๋ฅผ ๊ตฌ๋ณํ๊ฑฐ๋ ํ์ EL ์์ธ์ AArch32์ AArch64๋ฅผ ๊ตฌ๋ณํ์ง ์์ต๋๋ค.
- ์ด ์์์๋ ์์ธ๊ฐ ์ค์ ๋ก ๋ฐ์ํ ๊ฒ์ผ๋ก ์์๋์ง ์์ผ๋ฏ๋ก ์์ธ๋ฅผ ๊ธฐ๋กํ๊ณ ์ ์์ ๋๋๋ค.
- ์์ธ ํธ๋ค๋ฌ์ ๊ธฐ๋ณธ ์คํ ์ปจํ
์คํธ๋ ์๋ก ๋ค๋ฅธ ์ค๋ ๋์ ๊ฑฐ์ ๋น์ทํ๋ค๊ณ ์๊ฐํ ์ ์์ต๋๋ค.
Send
๋ฐSync
๋ ์ค๋ ๋์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ด๋ค ๊ฐ์ ๊ณต์ ํ ์ ์๋ ํญ๋ชฉ์ ์ ์ดํฉ๋๋ค. ์๋ฅผ ๋ค์ด ์์ธ ํธ๋ค๋ฌ์ ํ๋ก๊ทธ๋จ์ ๋๋จธ์ง ๋ถ๋ถ ๊ฐ์ ๊ฐ์ ๊ณต์ ํ๋ ค๊ณ ํ๋๋ฐSend
์ด์ง๋งSync
๋ ์๋ ๊ฒฝ์ฐ,Mutex
์ ๊ฐ์ ๊ฒ์ผ๋ก ๋ํํ๊ณ ์ ์ ์ธ ๊ฒ์ผ๋ก ๋ฐฐ์นํด์ผ ํฉ๋๋ค.
๋ค๋ฅธ ํ๋ก์ ํธ
- oreboot
- โcoreboot without the Cโ
- Supports x86, aarch64 and RISC-V.
- Relies on LinuxBoot rather than having many drivers itself.
- Rust RaspberryPi OS tutorial
- Initialisation, UART driver, simple bootloader, JTAG, exception levels, exception handling, page tables
- Some dodginess around cache maintenance and initialisation in Rust, not necessarily a good example to copy for production code.
cargo-call-stack
- Static analysis to determine maximum stack usage.
- RaspberryPi OS ํํ ๋ฆฌ์ผ์์๋ MMU์ ์บ์๊ฐ ์ฌ์ฉ ์ค์ ๋๊ธฐ ์ ์ Rust ์ฝ๋๋ฅผ ์คํํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ฉ๋ชจ๋ฆฌ(์: ์คํ)๋ฅผ ์ฝ๊ณ ์ธ ์ ์์ต๋๋ค. ํ์ง๋ง ๋ค์๊ณผ ๊ฐ์ ์์ธ๊ฐ ์ ์ฉ๋ฉ๋๋ค.
- MMU์ ์บ์๊ฐ ์์ผ๋ฉด ์ ๋ ฌ๋์ง ์์ ์ก์ธ์ค์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ปดํ์ผ๋ฌ๊ฐ ์ ๋ ฌ๋์ง ์์ ์ก์ธ์ค๋ฅผ ์์ฑํ์ง ์๋๋ก
+strict-align
์ ์ค์ ํ๋aarch64-unknown-none
์ผ๋ก ๋น๋๋๋ฏ๋ก ๋ฌธ์ ๊ฐ ์์ง๋ง, ์ผ๋ฐ์ ์ผ๋ก ๋ฐ๋์ ๊ทธ๋ฐ ๊ฒ์ ์๋๋๋ค. - VM์์ ์คํํ๋ค๋ฉด ์บ์ ์ผ๊ด์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ๋ฌธ์ ๋ VM์ ์บ์๊ฐ ์ฌ์ฉ ์ค์ง๋ ์ํ๋ก ๋ฉ๋ชจ๋ฆฌ์ ์ง์ ์ก์ธ์คํ๋ ๋ฐ๋ฉด ํธ์คํธ์๋ ๋์ผํ ๋ฉ๋ชจ๋ฆฌ์ ๋ํด ์บ์ํ ์ ์๋ ๋ณ์นญ์ด ์๋ค๋ ์ ์ ๋๋ค. ํธ์คํธ๊ฐ ๋ฉ๋ชจ๋ฆฌ์ ๋ช ์์ ์ผ๋ก ์ก์ธ์คํ์ง ์๋๋ผ๋ ์ถ์ธก ์ก์ธ์ค๋ ์บ์ ์ฑ์ฐ๊ธฐ๋ก ์ด์ด์ง ์ ์์ผ๋ฉฐ ๋ ์ค ํ๋์ ๋ณ๊ฒฝ์ฌํญ์ด ์์ค๋ฉ๋๋ค. ์ด๋ฒ์๋ ํ์ดํผ๋ฐ์ด์ ์์ด ํ๋์จ์ด์์ ์ง์ ์คํ๋๋ ์ด ํน์ ๊ฒฝ์ฐ์๋ ๋ฌธ์ ๊ฐ ์์ง๋ง, ์ผ๋ฐ์ ์ผ๋ก ์ข์ ํจํด์ ์๋๋๋ค.
- MMU์ ์บ์๊ฐ ์์ผ๋ฉด ์ ๋ ฌ๋์ง ์์ ์ก์ธ์ค์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค. ์ปดํ์ผ๋ฌ๊ฐ ์ ๋ ฌ๋์ง ์์ ์ก์ธ์ค๋ฅผ ์์ฑํ์ง ์๋๋ก
์ ์ฉํ ํฌ๋ ์ดํธ
bare-metal ํ๋ก๊ทธ๋๋ฐ์ ๋ช ๊ฐ์ง ์ผ๋ฐ์ ์ธ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ํฌ๋ ์ดํธ๋ฅผ ์ดํด๋ด ๋๋ค.
zerocopy
Fuchsiaํ์ด ๋ง๋ zerocopy
ํฌ๋ ์ดํธ๋ ๋ฐ์ดํธ ์ํ์ค๋ฅผ ๋ค๋ฅธ ํ์
์ผ๋ก ์์ ํ๊ฒ ๋ณํํ๊ธฐ ์ํ ํธ๋ ์ ๋ฐ ๋งคํฌ๋ก๋ฅผ ์ ๊ณตํฉ๋๋ค.
use zerocopy::AsBytes; #[repr(u32)] #[derive(AsBytes, Debug, Default)] enum RequestType { #[default] In = 0, Out = 1, Flush = 4, } #[repr(C)] #[derive(AsBytes, Debug, Default)] struct VirtioBlockRequest { request_type: RequestType, reserved: u32, sector: u64, } fn main() { let request = VirtioBlockRequest { request_type: RequestType::Flush, sector: 42, ..Default::default() }; assert_eq!( request.as_bytes(), &[4, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0] ); }
์ด ํฌ๋ ์ดํธ๋ ํ๋ฐ์ฑ(volatile) ์ฝ๊ธฐ ๋ฐ ์ฐ๊ธฐ๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก MMIO์ ์ ํฉํ์ง ์์ง๋ง, ํ๋์จ์ด์ ๊ณต์ ๋๊ฑฐ๋(์: DMA์์) ์ธ์ฅ ์ธํฐํ์ด์ค๋ฅผ ํตํด ์ ์ก๋๋ ๊ตฌ์กฐ์ฒด๋ฅผ ๋ค๋ฃจ๋ ๋ฐ์๋ ์ ์ฉํ ์ ์์ต๋๋ค.
- ์ด๋ค ํ์
์ด ๊ฐ๋ฅํ ๋ชจ๋ ๋ฐ์ดํธ ํจํด๋ค์ ๋ํด ์ฌ๋ฐ๋ฅธ ๊ฐ์ ๊ฐ์ง ๋์๋ง , ๊ทธ ํ์
์ด
FromBytes
๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค. ๊ทธ๋ ๊ฒ ํด์ ์ ๋ขฐํ ์ ์๋ ๋ฐ์ดํธ ์ํ์ค๋ฅผ ์์ ํ๊ฒ ํด๋น ํ์ ์ผ๋ก ๋ณํํ ์ ์์ต๋๋ค. - ์ ์ฝ๋์์ ์ ์ํ ํ์
์ ๋ํด
FromBytes
๋ฅผ ๊ตฌํํ๋ ค๊ณ ํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.RequestType
์ ๊ฐ๋ฅํ ๋ชจ๋ u32 ๊ฐ์ ์๋ณ์๋ก ๋ฐ์๋ค์ด์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ฆ ๋ชจ๋ ๋ฐ์ดํธ ํจํด์ด ์ ํจํRequestType
๊ฐ์ ์๋๋๋ค. zerocopy::byteorder
์๋ ๋ฐ์ดํธ ์ค๋์ ๋ฐ๋ฅธ ์๋ก ๋ค๋ฅธ ํํ ๋ฐฉ์์ ์ง์ํ๋ ์ซ์ ํ์ ์ ์ ๊ณตํฉ๋๋ค.src/bare-metal/useful-crates/zerocopy-example/
์์cargo run
์ ์ฌ์ฉํ์ฌ ์์๋ฅผ ์คํํฉ๋๋ค(์ข ์์ฑ ๋ฌธ์ ๋ก ์ธํด ํ๋ ์ด๊ทธ๋ผ์ด๋์์๋ ์คํ๋์ง ์์ต๋๋ค).
aarch64-paging
aarch64-paging
ํฌ๋ ์ดํธ๋ฅผ ์ฌ์ฉํ๋ฉด AArch64 ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ์์คํ
์ํคํ
์ฒ์ ๋ฐ๋ผ ํ์ด์ง ํ
์ด๋ธ์ ๋ง๋ค ์ ์์ต๋๋ค.
use aarch64_paging::{ idmap::IdMap, paging::{Attributes, MemoryRegion}, }; const ASID: usize = 1; const ROOT_LEVEL: usize = 1; // ID ๋งคํ์ ์ฌ์ฉํ์ฌ ์ ํ์ด์ง ํ ์ด๋ธ์ ๋ง๋ญ๋๋ค. let mut idmap = IdMap::new(ASID, ROOT_LEVEL); // 2MiB ๋ฉ๋ชจ๋ฆฌ ์์ญ์ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ๋งคํํฉ๋๋ค. idmap.map_range( &MemoryRegion::new(0x80200000, 0x80400000), Attributes::NORMAL | Attributes::NON_GLOBAL | Attributes::READ_ONLY, ).unwrap(); // `TTBR0_EL1`์ ์ค์ ํ์ฌ ํ์ด์ง ํ ์ด๋ธ์ ํ์ฑํํฉ๋๋ค. idmap.activate();
- ํ์ฌ๋ EL1๋ง ์ง์ํ์ง๋ง ๋ค๋ฅธ ์ต์ ์ ๋ ๋ฒจ(Exception Level: EL)๋ ์ด๋ ต์ง ์๊ฒ ์ถ๊ฐํ ์ ์์ต๋๋ค.
- Android์์ ๋ณดํธ๋ VM ํ์จ์ด์ ์ฌ์ฉ๋ฉ๋๋ค.
- ์ด ์์๋ฅผ ๊ฐ๋จํ๊ฒ ์คํํ๋ ๋ฐฉ๋ฒ์ ์์ต๋๋ค. ์ค์ ํ๋์จ์ด ๋๋ QEMU์์ ์คํํด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
buddy_system_allocator
buddy_system_allocator
๋ ๋ฒ๋ ์์คํ
ํ ๋น์๋ฅผ ๊ตฌํํ๋ ์๋ ํํฐ ํฌ๋ ์ดํธ์
๋๋ค. ์ด ํฌ๋ ์ดํธ์ LockedHeap
์ GlobalAlloc
๋ฅผ ๊ตฌํํฉ๋๋ค. ๋ฐ๋ผ์ ์ฌ๋ฌ๋ถ์ ๋ฒ๋ ์์คํ
ํ ๋น์๋ฅผ โallocโ ํฌ๋ ์ดํธ๋ฅผ ํตํด์ ์ฌ์ฉํ ์ ์์ต๋๋ค(์ด์ ์ ํ์ธํจ). ๋๋ ๋ค๋ฅธ ์ฃผ์ ๊ณต๊ฐ์ ํ ๋นํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด PCI BAR์ MMIO ๊ณต๊ฐ์ ํ ๋นํ ์ ์์ต๋๋ค.
use buddy_system_allocator::FrameAllocator; use core::alloc::Layout; fn main() { let mut allocator = FrameAllocator::<32>::new(); allocator.add_frame(0x200_0000, 0x400_0000); let layout = Layout::from_size_align(0x100, 0x100).unwrap(); let bar = allocator .alloc_aligned(layout) .expect("Failed to allocate 0x100 byte MMIO region"); println!("Allocated 0x100 byte MMIO region at {:#x}", bar); }
- PCI BAR๋ BAR์์ญ์ ํฌ๊ธฐ์ ๋ง์ถ์ด ์ ๋ ฌ๋ฉ๋๋ค.
src/bare-metal/useful-crates/allocator-example/
์์cargo run
์ ์ฌ์ฉํ์ฌ ์์๋ฅผ ์คํํฉ๋๋ค(์ข ์์ฑ ๋ฌธ์ ๋ก ์ธํด ํ๋ ์ด๊ทธ๋ผ์ด๋์์๋ ์คํ๋์ง ์์ต๋๋ค).
tinyvec
ํ์ ๋ฉ๋ชจ๋ฆฌ ํ ๋นํ์ง ์๊ณ ํฌ๊ธฐ ์กฐ์ ์ด ๊ฐ๋ฅํ ์ปจํ
์ด๋(์: Vec
๊ฐ์)๊ฐ ํ์ํ ๋๊ฐ ์์ต๋๋ค. tinyvec
์ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค. tinyvec
์์ ๋ฒกํฐ๋ ๋ฐฐ์ด ๋๋ ์ฌ๋ผ์ด์ค๋ก๋ถํฐ ์์ฑ์ด ๋๋ฉฐ, ์ด๋ค์ ์ ์ ์ผ๋ก ํ ๋น๋์๊ฑฐ๋ ์คํ์ ํ ๋น๋์ด ์์ ์ ์์ต๋๋ค.tinyvec
์ ํ์ฌ ๋ฒกํฐ ์์ ์ผ๋ง๋ ๋ง์ ์๋ฆฌ๋จผํธ๋ค์ด ์กด์ฌํ๋ ์ง๋ฅผ ์ถ์ ํ๊ณ ์์ผ๋ฉฐ, ํ ๋น๋ ์๋ณด๋ค ๋ ๋ง์ด ์ฌ์ฉํ๋ ค๊ณ ํ๋ฉด ํจ๋์ ๋ฐ์์ํต๋๋ค.
use tinyvec::{array_vec, ArrayVec}; fn main() { let mut numbers: ArrayVec<[u32; 5]> = array_vec!(42, 66); println!("{numbers:?}"); numbers.push(7); println!("{numbers:?}"); numbers.remove(1); println!("{numbers:?}"); }
tinyvec
๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ์๋ฆฌ๋จผํธ์ ํ์ ์ดDefault
๋ฅผ ํตํด ์ด๊ธฐํ ๋ ์ ์์ด์ผ ํฉ๋๋ค.- Rust ํ๋ ์ด๊ทธ๋ผ์ด๋์๋
tinyvec
๊ฐ ํฌํจ๋์ด ์์ผ๋ฏ๋ก ์ด ์์๋ ์ธ๋ผ์ธ์ผ๋ก ์คํ๋ฉ๋๋ค.
spin
std::sync::Mutex
๋ฐ std::sync
์ ๊ธฐํ ๋๊ธฐํ ํ๋ฆฌ๋ฏธํฐ๋ธ๋ core
๋๋ alloc
์์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ฉด ์ด๋ป๊ฒ ๋๊ธฐํ ๋๋ interior mutability์ ๊ฐ์ ๊ธฐ๋ฅ์ด ํ์ํ ๊ฒฝ์ฐ ์ด๋ป๊ฒ ํด์ผ ํ ๊น์?
spin
ํฌ๋ ์ดํธ๋ ์ด๋ฌํ ๋๊ธฐํ ํ๋ฆฌ๋ฏธํฐ๋ธ๋ค์ ์คํ๋ก์ผ๋ก ๊ตฌํํ๊ณ ์์ต๋๋ค.
use spin::mutex::SpinMutex; static counter: SpinMutex<u32> = SpinMutex::new(0); fn main() { println!("count: {}", counter.lock()); *counter.lock() += 2; println!("count: {}", counter.lock()); }
- ์ธํฐ๋ฝํธ ํธ๋ค๋ฌ์์ ๋ฝ์ ๊ฑธ ๊ฒฝ์ฐ, ๊ต์ฐฉ ์ํ๊ฐ ๋ฐ์ํ์ง ์๋๋ก ์ฃผ์ํ์ธ์.
spin
also has a ticket lock mutex implementation; equivalents ofRwLock
,Barrier
andOnce
fromstd::sync
; andLazy
for lazy initialisation.once_cell
ํฌ๋ ์ดํธ์๋ ์ง์ฐ๋ ์ด๊ธฐํ๋ฅผ ์ํ ๋ช ๊ฐ์ง ์ ์ฉํ ํ์ ์ด ์๋๋ฐspin::once::Once
์๋ ์ฝ๊ฐ ๋ค๋ฅธ ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค.- Rust ํ๋ ์ด๊ทธ๋ผ์ด๋์๋
spin
์ด ํฌํจ๋์ด ์์ผ๋ฏ๋ก ์ด ์์๋ ์ธ๋ผ์ธ์ผ๋ก ์คํ๋ฉ๋๋ค.
์๋๋ก์ด๋
AOSP์์ bare-metal Rust ๋ฐ์ด๋๋ฆฌ๋ฅผ ๋น๋ํ๋ ค๋ฉด rust_ffi_static
์ ์ฌ์ฉํ์ฌ Rust ์ฝ๋๋ฅผ ๋น๋ํ๊ณ , ๋ง์ปค ์คํฌ๋ฆฝํธ๊ฐ ํฌํจ๋ cc_binary
๋ฅผ ์ฌ์ฉํ์ฌ ELF ๋ฐ์ด๋๋ฆฌ๋ฅผ ์์ฑํ๊ณ , raw_binary
๋ฅผ ์ฌ์ฉํด ELF๋ฅผ ๊ณง๋ฐ๋ก ์ํ๋ ์ ์๋ ์์(raw) ๋ฐ์ด๋๋ฆฌ๋ก ๋ณํํฉ๋๋ค.
rust_ffi_static {
name: "libvmbase_example",
defaults: ["vmbase_ffi_defaults"],
crate_name: "vmbase_example",
srcs: ["src/main.rs"],
rustlibs: [
"libvmbase",
],
}
cc_binary {
name: "vmbase_example",
defaults: ["vmbase_elf_defaults"],
srcs: [
"idmap.S",
],
static_libs: [
"libvmbase_example",
],
linker_scripts: [
"image.ld",
":vmbase_sections",
],
}
raw_binary {
name: "vmbase_example_bin",
stem: "vmbase_example.bin",
src: ":vmbase_example",
enabled: false,
target: {
android_arm64: {
enabled: true,
},
},
}
vmbase
vmbase ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋, aarch64์ crosvm์์ ์คํ๋๋ VM์ ํ๊ฒํ์ฌ, ์ง์ ์ , UART ์ฝ์ ๋ก๊น , ๋ง์ปค ์คํฌ๋ฆฝํธ, ๋น๋ ๋ฃฐ ๋ฑ์ ๋ํ ๊ธฐ๋ณธ ๊ตฌํ๋ค์ ์ ๊ณตํฉ๋๋ค.
#![no_main] #![no_std] use vmbase::{main, println}; main!(main); pub fn main(arg0: u64, arg1: u64, arg2: u64, arg3: u64) { println!("Hello world"); }
main!
๋งคํฌ๋ก๋vmbase
์ง์ ์ ์์ ํธ์ถ๋ main ํจ์๋ฅผ ํ์ํฉ๋๋ค.vmbase
๊ฐ ์ ๊ณตํ๋ ์ง์ ์ ์ ์ฝ์์ ์ด๊ธฐํ ํ๋ฉฐ, main ํจ์๊ฐ ๋ฆฌํดํ๋ฉด PSCI_SYSTEM_OFF ๋ฉ์์ง๋ฅผ PSCI๋ฅผ ํตํด ๋ณด๋ด์ด์ VM์ ์ข ๋ฃ์ํต๋๋ค.
์ฐ์ต๋ฌธ์
PL031 ์ค์๊ฐ ์๊ณ ๊ธฐ๊ธฐ์ฉ ๋๋ผ์ด๋ฒ๋ฅผ ์์ฑํฉ๋๋ค.
After looking at the exercises, you can look at the solutions provided.
RTC driver
The QEMU aarch64 virt machine has a PL031 real-time clock at 0x9010000. For this exercise, you should write a driver for it.
- ์ง๋ ฌ ์ฝ์์ ํ์ฌ ์๊ฐ์ ์ถ๋ ฅํ๋ ๋ฐ ์ฌ์ฉํฉ๋๋ค. ๋ ์ง/์๊ฐ ํ์ ์ง์ ์๋
chrono
ํฌ๋ ์ดํธ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. - ์ผ์น ๋ ์ง์คํฐ์ ์์ ์ธํฐ๋ฝํธ ์ํ๋ฅผ ์ฌ์ฉํ์ฌ ํน์ ์๊ฐ(์: ํฅํ 3์ด)๊น์ง ๋ฐ์ ๋๊ธฐํฉ๋๋ค. ๋ฃจํ ๋ด์์
core::hint::spin_loop
๋ฅผ ํธ์ถํฉ๋๋ค. - ์๊ฐ์ด ์๋ ๊ฒฝ์ฐ ์ฐ์ฅ: RTC ์ผ์น๋ก ์์ฑ๋ ์ธํฐ๋ฝํธ๋ฅผ ์ฌ์ฉ ์ค์ ํ๊ณ ์ฒ๋ฆฌํฉ๋๋ค.
arm-gic
ํฌ๋ ์ดํธ์ ์ ๊ณต๋ ๋๋ผ์ด๋ฒ๋ฅผ ์ฌ์ฉํ์ฌ Arm ์ผ๋ฐ ์ธํฐ๋ฝํธ ์ปจํธ๋กค๋ฌ๋ฅผ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.- GIC์
IntId::spi(2)
๋ก ์ฐ๊ฒฐ๋ RTC ์ธํฐ๋ฝํธ๋ฅผ ์ฌ์ฉํฉ๋๋ค. - ์ธํฐ๋ฝํธ๋ฅผ ์ฌ์ฉ ์ค์ ํ ํ์๋
arm_gic::wfi()
๋ฅผ ํตํด ์ฝ์ด๋ฅผ ์ ์ ๋ชจ๋๋ก ์ ํํ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ ์ธํฐ๋ฝํธ๋ฅผ ์์ ํ ๋๊น์ง ์ฝ์ด๊ฐ ์ ์ ๋ชจ๋๋ก ์ ํ๋ฉ๋๋ค.
- GIC์
์ฐ์ต ํ
ํ๋ฆฟ์ ๋ค์ด๋ก๋ํ๊ณ rtc
๋๋ ํฐ๋ฆฌ์์ ๋ค์ ํ์ผ์ ์ฐพ์ต๋๋ค.
src/main.rs:
#![no_main] #![no_std] mod exceptions; mod logger; mod pl011; use crate::pl011::Uart; use arm_gic::gicv3::GicV3; use core::panic::PanicInfo; use log::{error, info, trace, LevelFilter}; use smccc::psci::system_off; use smccc::Hvc; /// Base addresses of the GICv3. const GICD_BASE_ADDRESS: *mut u64 = 0x800_0000 as _; const GICR_BASE_ADDRESS: *mut u64 = 0x80A_0000 as _; /// Base address of the primary PL011 UART. const PL011_BASE_ADDRESS: *mut u32 = 0x900_0000 as _; #[no_mangle] extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) { // Safe because `PL011_BASE_ADDRESS` is the base address of a PL011 device, // and nothing else accesses that address range. let uart = unsafe { Uart::new(PL011_BASE_ADDRESS) }; logger::init(uart, LevelFilter::Trace).unwrap(); info!("main({:#x}, {:#x}, {:#x}, {:#x})", x0, x1, x2, x3); // Safe because `GICD_BASE_ADDRESS` and `GICR_BASE_ADDRESS` are the base // addresses of a GICv3 distributor and redistributor respectively, and // nothing else accesses those address ranges. let mut gic = unsafe { GicV3::new(GICD_BASE_ADDRESS, GICR_BASE_ADDRESS) }; gic.setup(); // TODO: Create instance of RTC driver and print current time. // TODO: Wait for 3 seconds. system_off::<Hvc>().unwrap(); } #[panic_handler] fn panic(info: &PanicInfo) -> ! { error!("{info}"); system_off::<Hvc>().unwrap(); loop {} }
_src/exceptions.rs_๋ ์ฐ์ต์ ์ธ ๋ฒ์งธ ๋ถ๋ถ์์๋ง ๋ณ๊ฒฝํ๋ฉด ๋ฉ๋๋ค.
#![allow(unused)] fn main() { // Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use arm_gic::gicv3::GicV3; use log::{error, info, trace}; use smccc::psci::system_off; use smccc::Hvc; #[no_mangle] extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) { error!("sync_exception_current"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn irq_current(_elr: u64, _spsr: u64) { trace!("irq_current"); let intid = GicV3::get_and_acknowledge_interrupt().expect("No pending interrupt"); info!("IRQ {intid:?}"); } #[no_mangle] extern "C" fn fiq_current(_elr: u64, _spsr: u64) { error!("fiq_current"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn serr_current(_elr: u64, _spsr: u64) { error!("serr_current"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn sync_lower(_elr: u64, _spsr: u64) { error!("sync_lower"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn irq_lower(_elr: u64, _spsr: u64) { error!("irq_lower"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn fiq_lower(_elr: u64, _spsr: u64) { error!("fiq_lower"); system_off::<Hvc>().unwrap(); } #[no_mangle] extern "C" fn serr_lower(_elr: u64, _spsr: u64) { error!("serr_lower"); system_off::<Hvc>().unwrap(); } }
src/logger.rs(๋ณ๊ฒฝํ ํ์๊ฐ ์์):
#![allow(unused)] fn main() { // Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ANCHOR: main use crate::pl011::Uart; use core::fmt::Write; use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; use spin::mutex::SpinMutex; static LOGGER: Logger = Logger { uart: SpinMutex::new(None) }; struct Logger { uart: SpinMutex<Option<Uart>>, } impl Log for Logger { fn enabled(&self, _metadata: &Metadata) -> bool { true } fn log(&self, record: &Record) { writeln!( self.uart.lock().as_mut().unwrap(), "[{}] {}", record.level(), record.args() ) .unwrap(); } fn flush(&self) {} } /// Initialises UART logger. pub fn init(uart: Uart, max_level: LevelFilter) -> Result<(), SetLoggerError> { LOGGER.uart.lock().replace(uart); log::set_logger(&LOGGER)?; log::set_max_level(max_level); Ok(()) } }
src/pl011.rs (๋ณ๊ฒฝํ ํ์๊ฐ ์์):
#![allow(unused)] fn main() { // Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![allow(unused)] use core::fmt::{self, Write}; use core::ptr::{addr_of, addr_of_mut}; // ANCHOR: Flags use bitflags::bitflags; bitflags! { /// Flags from the UART flag register. #[repr(transparent)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] struct Flags: u16 { /// Clear to send. const CTS = 1 << 0; /// Data set ready. const DSR = 1 << 1; /// Data carrier detect. const DCD = 1 << 2; /// UART busy transmitting data. const BUSY = 1 << 3; /// Receive FIFO is empty. const RXFE = 1 << 4; /// Transmit FIFO is full. const TXFF = 1 << 5; /// Receive FIFO is full. const RXFF = 1 << 6; /// Transmit FIFO is empty. const TXFE = 1 << 7; /// Ring indicator. const RI = 1 << 8; } } // ANCHOR_END: Flags bitflags! { /// Flags from the UART Receive Status Register / Error Clear Register. #[repr(transparent)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] struct ReceiveStatus: u16 { /// Framing error. const FE = 1 << 0; /// Parity error. const PE = 1 << 1; /// Break error. const BE = 1 << 2; /// Overrun error. const OE = 1 << 3; } } // ANCHOR: Registers #[repr(C, align(4))] struct Registers { dr: u16, _reserved0: [u8; 2], rsr: ReceiveStatus, _reserved1: [u8; 19], fr: Flags, _reserved2: [u8; 6], ilpr: u8, _reserved3: [u8; 3], ibrd: u16, _reserved4: [u8; 2], fbrd: u8, _reserved5: [u8; 3], lcr_h: u8, _reserved6: [u8; 3], cr: u16, _reserved7: [u8; 3], ifls: u8, _reserved8: [u8; 3], imsc: u16, _reserved9: [u8; 2], ris: u16, _reserved10: [u8; 2], mis: u16, _reserved11: [u8; 2], icr: u16, _reserved12: [u8; 2], dmacr: u8, _reserved13: [u8; 3], } // ANCHOR_END: Registers // ANCHOR: Uart /// Driver for a PL011 UART. #[derive(Debug)] pub struct Uart { registers: *mut Registers, } impl Uart { /// Constructs a new instance of the UART driver for a PL011 device at the /// given base address. /// /// # Safety /// /// The given base address must point to the MMIO control registers of a /// PL011 device, which must be mapped into the address space of the process /// as device memory and not have any other aliases. pub unsafe fn new(base_address: *mut u32) -> Self { Self { registers: base_address as *mut Registers } } /// Writes a single byte to the UART. pub fn write_byte(&self, byte: u8) { // Wait until there is room in the TX buffer. while self.read_flag_register().contains(Flags::TXFF) {} // Safe because we know that self.registers points to the control // registers of a PL011 device which is appropriately mapped. unsafe { // Write to the TX buffer. addr_of_mut!((*self.registers).dr).write_volatile(byte.into()); } // Wait until the UART is no longer busy. while self.read_flag_register().contains(Flags::BUSY) {} } /// Reads and returns a pending byte, or `None` if nothing has been /// received. pub fn read_byte(&self) -> Option<u8> { if self.read_flag_register().contains(Flags::RXFE) { None } else { let data = unsafe { addr_of!((*self.registers).dr).read_volatile() }; // TODO: Check for error conditions in bits 8-11. Some(data as u8) } } fn read_flag_register(&self) -> Flags { // Safe because we know that self.registers points to the control // registers of a PL011 device which is appropriately mapped. unsafe { addr_of!((*self.registers).fr).read_volatile() } } } // ANCHOR_END: Uart impl Write for Uart { fn write_str(&mut self, s: &str) -> fmt::Result { for c in s.as_bytes() { self.write_byte(*c); } Ok(()) } } // Safe because it just contains a pointer to device memory, which can be // accessed from any context. unsafe impl Send for Uart {} }
Cargo.toml (๋ณ๊ฒฝํ ํ์๊ฐ ์์):
[workspace]
[package]
name = "rtc"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
arm-gic = "0.1.0"
bitflags = "2.4.2"
chrono = { version = "0.4.35", default-features = false }
log = "0.4.21"
smccc = "0.1.1"
spin = "0.9.8"
[build-dependencies]
cc = "1.0.90"
build.rs(๋ณ๊ฒฝํ ํ์๊ฐ ์์):
// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use cc::Build; use std::env; fn main() { #[cfg(target_os = "linux")] env::set_var("CROSS_COMPILE", "aarch64-linux-gnu"); #[cfg(not(target_os = "linux"))] env::set_var("CROSS_COMPILE", "aarch64-none-elf"); Build::new() .file("entry.S") .file("exceptions.S") .file("idmap.S") .compile("empty") }
entry.S(๋ณ๊ฒฝํ ํ์๊ฐ ์์):
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.macro adr_l, reg:req, sym:req
adrp \reg, \sym
add \reg, \reg, :lo12:\sym
.endm
.macro mov_i, reg:req, imm:req
movz \reg, :abs_g3:\imm
movk \reg, :abs_g2_nc:\imm
movk \reg, :abs_g1_nc:\imm
movk \reg, :abs_g0_nc:\imm
.endm
.set .L_MAIR_DEV_nGnRE, 0x04
.set .L_MAIR_MEM_WBWA, 0xff
.set .Lmairval, .L_MAIR_DEV_nGnRE | (.L_MAIR_MEM_WBWA << 8)
/* 4 KiB granule size for TTBR0_EL1. */
.set .L_TCR_TG0_4KB, 0x0 << 14
/* 4 KiB granule size for TTBR1_EL1. */
.set .L_TCR_TG1_4KB, 0x2 << 30
/* Disable translation table walk for TTBR1_EL1, generating a translation fault instead. */
.set .L_TCR_EPD1, 0x1 << 23
/* Translation table walks for TTBR0_EL1 are inner sharable. */
.set .L_TCR_SH_INNER, 0x3 << 12
/*
* Translation table walks for TTBR0_EL1 are outer write-back read-allocate write-allocate
* cacheable.
*/
.set .L_TCR_RGN_OWB, 0x1 << 10
/*
* Translation table walks for TTBR0_EL1 are inner write-back read-allocate write-allocate
* cacheable.
*/
.set .L_TCR_RGN_IWB, 0x1 << 8
/* Size offset for TTBR0_EL1 is 2**39 bytes (512 GiB). */
.set .L_TCR_T0SZ_512, 64 - 39
.set .Ltcrval, .L_TCR_TG0_4KB | .L_TCR_TG1_4KB | .L_TCR_EPD1 | .L_TCR_RGN_OWB
.set .Ltcrval, .Ltcrval | .L_TCR_RGN_IWB | .L_TCR_SH_INNER | .L_TCR_T0SZ_512
/* Stage 1 instruction access cacheability is unaffected. */
.set .L_SCTLR_ELx_I, 0x1 << 12
/* SP alignment fault if SP is not aligned to a 16 byte boundary. */
.set .L_SCTLR_ELx_SA, 0x1 << 3
/* Stage 1 data access cacheability is unaffected. */
.set .L_SCTLR_ELx_C, 0x1 << 2
/* EL0 and EL1 stage 1 MMU enabled. */
.set .L_SCTLR_ELx_M, 0x1 << 0
/* Privileged Access Never is unchanged on taking an exception to EL1. */
.set .L_SCTLR_EL1_SPAN, 0x1 << 23
/* SETEND instruction disabled at EL0 in aarch32 mode. */
.set .L_SCTLR_EL1_SED, 0x1 << 8
/* Various IT instructions are disabled at EL0 in aarch32 mode. */
.set .L_SCTLR_EL1_ITD, 0x1 << 7
.set .L_SCTLR_EL1_RES1, (0x1 << 11) | (0x1 << 20) | (0x1 << 22) | (0x1 << 28) | (0x1 << 29)
.set .Lsctlrval, .L_SCTLR_ELx_M | .L_SCTLR_ELx_C | .L_SCTLR_ELx_SA | .L_SCTLR_EL1_ITD | .L_SCTLR_EL1_SED
.set .Lsctlrval, .Lsctlrval | .L_SCTLR_ELx_I | .L_SCTLR_EL1_SPAN | .L_SCTLR_EL1_RES1
/**
* This is a generic entry point for an image. It carries out the operations required to prepare the
* loaded image to be run. Specifically, it zeroes the bss section using registers x25 and above,
* prepares the stack, enables floating point, and sets up the exception vector. It preserves x0-x3
* for the Rust entry point, as these may contain boot parameters.
*/
.section .init.entry, "ax"
.global entry
entry:
/* Load and apply the memory management configuration, ready to enable MMU and caches. */
adrp x30, idmap
msr ttbr0_el1, x30
mov_i x30, .Lmairval
msr mair_el1, x30
mov_i x30, .Ltcrval
/* Copy the supported PA range into TCR_EL1.IPS. */
mrs x29, id_aa64mmfr0_el1
bfi x30, x29, #32, #4
msr tcr_el1, x30
mov_i x30, .Lsctlrval
/*
* Ensure everything before this point has completed, then invalidate any potentially stale
* local TLB entries before they start being used.
*/
isb
tlbi vmalle1
ic iallu
dsb nsh
isb
/*
* Configure sctlr_el1 to enable MMU and cache and don't proceed until this has completed.
*/
msr sctlr_el1, x30
isb
/* Disable trapping floating point access in EL1. */
mrs x30, cpacr_el1
orr x30, x30, #(0x3 << 20)
msr cpacr_el1, x30
isb
/* Zero out the bss section. */
adr_l x29, bss_begin
adr_l x30, bss_end
0: cmp x29, x30
b.hs 1f
stp xzr, xzr, [x29], #16
b 0b
1: /* Prepare the stack. */
adr_l x30, boot_stack_end
mov sp, x30
/* Set up exception vector. */
adr x30, vector_table_el1
msr vbar_el1, x30
/* Call into Rust code. */
bl main
/* Loop forever waiting for interrupts. */
2: wfi
b 2b
exceptions.S(๋ณ๊ฒฝํ ํ์๊ฐ ์์):
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Saves the volatile registers onto the stack. This currently takes 14
* instructions, so it can be used in exception handlers with 18 instructions
* left.
*
* On return, x0 and x1 are initialised to elr_el2 and spsr_el2 respectively,
* which can be used as the first and second arguments of a subsequent call.
*/
.macro save_volatile_to_stack
/* Reserve stack space and save registers x0-x18, x29 & x30. */
stp x0, x1, [sp, #-(8 * 24)]!
stp x2, x3, [sp, #8 * 2]
stp x4, x5, [sp, #8 * 4]
stp x6, x7, [sp, #8 * 6]
stp x8, x9, [sp, #8 * 8]
stp x10, x11, [sp, #8 * 10]
stp x12, x13, [sp, #8 * 12]
stp x14, x15, [sp, #8 * 14]
stp x16, x17, [sp, #8 * 16]
str x18, [sp, #8 * 18]
stp x29, x30, [sp, #8 * 20]
/*
* Save elr_el1 & spsr_el1. This such that we can take nested exception
* and still be able to unwind.
*/
mrs x0, elr_el1
mrs x1, spsr_el1
stp x0, x1, [sp, #8 * 22]
.endm
/**
* Restores the volatile registers from the stack. This currently takes 14
* instructions, so it can be used in exception handlers while still leaving 18
* instructions left; if paired with save_volatile_to_stack, there are 4
* instructions to spare.
*/
.macro restore_volatile_from_stack
/* Restore registers x2-x18, x29 & x30. */
ldp x2, x3, [sp, #8 * 2]
ldp x4, x5, [sp, #8 * 4]
ldp x6, x7, [sp, #8 * 6]
ldp x8, x9, [sp, #8 * 8]
ldp x10, x11, [sp, #8 * 10]
ldp x12, x13, [sp, #8 * 12]
ldp x14, x15, [sp, #8 * 14]
ldp x16, x17, [sp, #8 * 16]
ldr x18, [sp, #8 * 18]
ldp x29, x30, [sp, #8 * 20]
/* Restore registers elr_el1 & spsr_el1, using x0 & x1 as scratch. */
ldp x0, x1, [sp, #8 * 22]
msr elr_el1, x0
msr spsr_el1, x1
/* Restore x0 & x1, and release stack space. */
ldp x0, x1, [sp], #8 * 24
.endm
/**
* This is a generic handler for exceptions taken at the current EL while using
* SP0. It behaves similarly to the SPx case by first switching to SPx, doing
* the work, then switching back to SP0 before returning.
*
* Switching to SPx and calling the Rust handler takes 16 instructions. To
* restore and return we need an additional 16 instructions, so we can implement
* the whole handler within the allotted 32 instructions.
*/
.macro current_exception_sp0 handler:req
msr spsel, #1
save_volatile_to_stack
bl \handler
restore_volatile_from_stack
msr spsel, #0
eret
.endm
/**
* This is a generic handler for exceptions taken at the current EL while using
* SPx. It saves volatile registers, calls the Rust handler, restores volatile
* registers, then returns.
*
* This also works for exceptions taken from EL0, if we don't care about
* non-volatile registers.
*
* Saving state and jumping to the Rust handler takes 15 instructions, and
* restoring and returning also takes 15 instructions, so we can fit the whole
* handler in 30 instructions, under the limit of 32.
*/
.macro current_exception_spx handler:req
save_volatile_to_stack
bl \handler
restore_volatile_from_stack
eret
.endm
.section .text.vector_table_el1, "ax"
.global vector_table_el1
.balign 0x800
vector_table_el1:
sync_cur_sp0:
current_exception_sp0 sync_exception_current
.balign 0x80
irq_cur_sp0:
current_exception_sp0 irq_current
.balign 0x80
fiq_cur_sp0:
current_exception_sp0 fiq_current
.balign 0x80
serr_cur_sp0:
current_exception_sp0 serr_current
.balign 0x80
sync_cur_spx:
current_exception_spx sync_exception_current
.balign 0x80
irq_cur_spx:
current_exception_spx irq_current
.balign 0x80
fiq_cur_spx:
current_exception_spx fiq_current
.balign 0x80
serr_cur_spx:
current_exception_spx serr_current
.balign 0x80
sync_lower_64:
current_exception_spx sync_lower
.balign 0x80
irq_lower_64:
current_exception_spx irq_lower
.balign 0x80
fiq_lower_64:
current_exception_spx fiq_lower
.balign 0x80
serr_lower_64:
current_exception_spx serr_lower
.balign 0x80
sync_lower_32:
current_exception_spx sync_lower
.balign 0x80
irq_lower_32:
current_exception_spx irq_lower
.balign 0x80
fiq_lower_32:
current_exception_spx fiq_lower
.balign 0x80
serr_lower_32:
current_exception_spx serr_lower
idmap.S(๋ณ๊ฒฝํ ํ์๊ฐ ์์):
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.set .L_TT_TYPE_BLOCK, 0x1
.set .L_TT_TYPE_PAGE, 0x3
.set .L_TT_TYPE_TABLE, 0x3
/* Access flag. */
.set .L_TT_AF, 0x1 << 10
/* Not global. */
.set .L_TT_NG, 0x1 << 11
.set .L_TT_XN, 0x3 << 53
.set .L_TT_MT_DEV, 0x0 << 2 // MAIR #0 (DEV_nGnRE)
.set .L_TT_MT_MEM, (0x1 << 2) | (0x3 << 8) // MAIR #1 (MEM_WBWA), inner shareable
.set .L_BLOCK_DEV, .L_TT_TYPE_BLOCK | .L_TT_MT_DEV | .L_TT_AF | .L_TT_XN
.set .L_BLOCK_MEM, .L_TT_TYPE_BLOCK | .L_TT_MT_MEM | .L_TT_AF | .L_TT_NG
.section ".rodata.idmap", "a", %progbits
.global idmap
.align 12
idmap:
/* level 1 */
.quad .L_BLOCK_DEV | 0x0 // 1 GiB of device mappings
.quad .L_BLOCK_MEM | 0x40000000 // 1 GiB of DRAM
.fill 254, 8, 0x0 // 254 GiB of unmapped VA space
.quad .L_BLOCK_DEV | 0x4000000000 // 1 GiB of device mappings
.fill 255, 8, 0x0 // 255 GiB of remaining VA space
image.ld(๋ณ๊ฒฝํ ํ์๊ฐ ์์):
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Code will start running at this symbol which is placed at the start of the
* image.
*/
ENTRY(entry)
MEMORY
{
image : ORIGIN = 0x40080000, LENGTH = 2M
}
SECTIONS
{
/*
* Collect together the code.
*/
.init : ALIGN(4096) {
text_begin = .;
*(.init.entry)
*(.init.*)
} >image
.text : {
*(.text.*)
} >image
text_end = .;
/*
* Collect together read-only data.
*/
.rodata : ALIGN(4096) {
rodata_begin = .;
*(.rodata.*)
} >image
.got : {
*(.got)
} >image
rodata_end = .;
/*
* Collect together the read-write data including .bss at the end which
* will be zero'd by the entry code.
*/
.data : ALIGN(4096) {
data_begin = .;
*(.data.*)
/*
* The entry point code assumes that .data is a multiple of 32
* bytes long.
*/
. = ALIGN(32);
data_end = .;
} >image
/* Everything beyond this point will not be included in the binary. */
bin_end = .;
/* The entry point code assumes that .bss is 16-byte aligned. */
.bss : ALIGN(16) {
bss_begin = .;
*(.bss.*)
*(COMMON)
. = ALIGN(16);
bss_end = .;
} >image
.stack (NOLOAD) : ALIGN(4096) {
boot_stack_begin = .;
. += 40 * 4096;
. = ALIGN(4096);
boot_stack_end = .;
} >image
. = ALIGN(4K);
PROVIDE(dma_region = .);
/*
* Remove unused sections from the image.
*/
/DISCARD/ : {
/* The image loads itself so doesn't need these sections. */
*(.gnu.hash)
*(.hash)
*(.interp)
*(.eh_frame_hdr)
*(.eh_frame)
*(.note.gnu.build-id)
}
}
Makefile(๋ณ๊ฒฝํ ํ์๊ฐ ์์):
# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
UNAME := $(shell uname -s)
ifeq ($(UNAME),Linux)
TARGET = aarch64-linux-gnu
else
TARGET = aarch64-none-elf
endif
OBJCOPY = $(TARGET)-objcopy
.PHONY: build qemu_minimal qemu qemu_logger
all: rtc.bin
build:
cargo build
rtc.bin: build
$(OBJCOPY) -O binary target/aarch64-unknown-none/debug/rtc $@
qemu: rtc.bin
qemu-system-aarch64 -machine virt,gic-version=3 -cpu max -serial mon:stdio -display none -kernel $< -s
clean:
cargo clean
rm -f *.bin
.cargo/config.toml (๋ณ๊ฒฝํ ํ์๊ฐ ์์):
[build]
target = "aarch64-unknown-none"
rustflags = ["-C", "link-arg=-Timage.ld"]
make qemu
๋ฅผ ์ฌ์ฉํ์ฌ QEMU์์ ์ฝ๋๋ฅผ ์คํํฉ๋๋ค.
์ Bare Metal ์คํ
RTC driver
(์ฐ์ต๋ฌธ์ ๋ก ๋์๊ฐ๊ธฐ)
main.rs:
#![no_main] #![no_std] mod exceptions; mod logger; mod pl011; mod pl031; use crate::pl031::Rtc; use arm_gic::gicv3::{IntId, Trigger}; use arm_gic::{irq_enable, wfi}; use chrono::{TimeZone, Utc}; use core::hint::spin_loop; use crate::pl011::Uart; use arm_gic::gicv3::GicV3; use core::panic::PanicInfo; use log::{error, info, trace, LevelFilter}; use smccc::psci::system_off; use smccc::Hvc; /// GICv3์ ๊ธฐ๋ณธ ์ฃผ์์ ๋๋ค. const GICD_BASE_ADDRESS: *mut u64 = 0x800_0000 as _; const GICR_BASE_ADDRESS: *mut u64 = 0x80A_0000 as _; /// ๊ธฐ๋ณธ PL011 UART์ ๊ธฐ๋ณธ ์ฃผ์์ ๋๋ค. const PL011_BASE_ADDRESS: *mut u32 = 0x900_0000 as _; /// PL031 RTC์ ๊ธฐ๋ณธ ์ฃผ์์ ๋๋ค. const PL031_BASE_ADDRESS: *mut u32 = 0x901_0000 as _; /// PL031 RTC์์ ์ฌ์ฉํ๋ IRQ์ ๋๋ค. const PL031_IRQ: IntId = IntId::spi(2); #[no_mangle] extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) { // `PL011_BASE_ADDRESS`๊ฐ PL011 ๊ธฐ๊ธฐ์ ๊ธฐ๋ณธ ์ฃผ์์ด๊ณ // ์ด ์ฃผ์ ๋ฒ์์ ์ก์ธ์คํ๋ ๋ค๋ฅธ ํญ๋ชฉ์ด ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. let uart = unsafe { Uart::new(PL011_BASE_ADDRESS) }; logger::init(uart, LevelFilter::Trace).unwrap(); info!("main({:#x}, {:#x}, {:#x}, {:#x})", x0, x1, x2, x3); // `GICD_BASE_ADDRESS` ๋ฐ `GICR_BASE_ADDRESS`๊ฐ ๊ฐ๊ฐ GICv3 ๋ฐฐํฌ์ ๋ฐ ์ฌ๋ฐฐํฌ์์ // ๊ธฐ๋ณธ ์ฃผ์์ด์ด๊ณ // ์ด๋ฌํ ์ฃผ์ ๋ฒ์์ ์ก์ธ์คํ๋ ๋ค๋ฅธ ํญ๋ชฉ์ด ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. let mut gic = unsafe { GicV3::new(GICD_BASE_ADDRESS, GICR_BASE_ADDRESS) }; gic.setup(); // `PL031_BASE_ADDRESS`๊ฐ PL031 ๊ธฐ๊ธฐ์ ๊ธฐ๋ณธ ์ฃผ์์ด๊ณ // ์ด ์ฃผ์ ๋ฒ์์ ์ก์ธ์คํ๋ ๋ค๋ฅธ ํญ๋ชฉ์ด ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. let mut rtc = unsafe { Rtc::new(PL031_BASE_ADDRESS) }; let timestamp = rtc.read(); let time = Utc.timestamp_opt(timestamp.into(), 0).unwrap(); info!("RTC: {time}"); GicV3::set_priority_mask(0xff); gic.set_interrupt_priority(PL031_IRQ, 0x80); gic.set_trigger(PL031_IRQ, Trigger::Level); irq_enable(); gic.enable_interrupt(PL031_IRQ, true); // ์ธํฐ๋ฝํธ ์์ด 3์ด๊ฐ ๊ธฐ๋ค๋ฆฝ๋๋ค. let target = timestamp + 3; rtc.set_match(target); info!("{}์(๋ฅผ) ๊ธฐ๋ค๋ฆฌ๋ ์ค", Utc.timestamp_opt(target.into(), 0).unwrap()); trace!( "matched={}, interrupt_pending={}", rtc.matched(), rtc.interrupt_pending() ); while !rtc.matched() { spin_loop(); } trace!( "matched={}, interrupt_pending={}", rtc.matched(), rtc.interrupt_pending() ); info!("๋๊ธฐ ์๋ฃ๋จ"); // ์ธํฐ๋ฝํธ๋ฅผ ์ํด 3์ด ๋ ๊ธฐ๋ค๋ฆฝ๋๋ค. let target = timestamp + 6; info!("{}์(๋ฅผ) ๊ธฐ๋ค๋ฆฌ๋ ์ค", Utc.timestamp_opt(target.into(), 0).unwrap()); rtc.set_match(target); rtc.clear_interrupt(); rtc.enable_interrupt(true); trace!( "matched={}, interrupt_pending={}", rtc.matched(), rtc.interrupt_pending() ); while !rtc.interrupt_pending() { wfi(); } trace!( "matched={}, interrupt_pending={}", rtc.matched(), rtc.interrupt_pending() ); info!("๋๊ธฐ ์๋ฃ๋จ"); system_off::<Hvc>().unwrap(); } #[panic_handler] fn panic(info: &PanicInfo) -> ! { error!("{info}"); system_off::<Hvc>().unwrap(); loop {} }
pl031.rs:
#![allow(unused)] fn main() { use core::ptr::{addr_of, addr_of_mut}; #[repr(C, align(4))] struct Registers { /// ๋ฐ์ดํฐ ๋ ์ง์คํฐ dr: u32, /// ์ผ์น ๋ ์ง์คํฐ mr: u32, /// ๋ก๋ ๋ ์ง์คํฐ lr: u32, /// ์ ์ด ๋ ์ง์คํฐ cr: u8, _reserved0: [u8; 3], /// ์ธํฐ๋ฝํธ ๋ง์คํฌ ์ธํธ ๋๋ ์ ๋ฆฌ ๋ ์ง์คํฐ imsc: u8, _reserved1: [u8; 3], /// ์์ ์ธํฐ๋ฝํธ ์ํ ris: u8, _reserved2: [u8; 3], /// ๋ง์คํน๋ ์ธํฐ๋ฝํธ ์ํ mis: u8, _reserved3: [u8; 3], /// ์ธํฐ๋ฝํธ ์ ๋ฆฌ ๋ ์ง์คํฐ icr: u8, _reserved4: [u8; 3], } /// PL031 ์ค์๊ฐ ์๊ณ์ฉ ๋๋ผ์ด๋ฒ #[derive(Debug)] pub struct Rtc { registers: *mut Registers, } impl Rtc { /// ์ง์ ๋ ๊ธฐ๋ณธ ์ฃผ์์ PL031 ๊ธฐ๊ธฐ์ ๋ํ RTC ๋๋ผ์ด๋ฒ์ ์ ์ธ์คํด์ค๋ฅผ /// ์์ฑํฉ๋๋ค. /// /// # ์์ /// /// ์ง์ ๋ ๊ธฐ๋ณธ ์ฃผ์๋ PL031 ๊ธฐ๊ธฐ์ /// MMIO ์ ์ด ๋ ์ง์คํฐ๋ฅผ ๊ฐ๋ฆฌ์ผ์ผ ํ๋ฉฐ, /// ์ด๋ ํ๋ก์ธ์ค์ ์ฃผ์ ๊ณต๊ฐ์ ๊ธฐ๊ธฐ ๋ฉ๋ชจ๋ฆฌ๋ก /// ๋งคํ๋์ด์ผ ํ๋ฉฐ ๋ค๋ฅธ ๋ณ์นญ์ ์์ด์ผ ํฉ๋๋ค. pub unsafe fn new(base_address: *mut u32) -> Self { Self { registers: base_address as *mut Registers } } /// ํ์ฌ RTC ๊ฐ์ ์ฝ์ต๋๋ค. pub fn read(&self) -> u32 { // self.registers๊ฐ ์ ์ ํ๊ฒ ๋งคํ๋ PL031 ๊ธฐ๊ธฐ์ ์ ์ด ๋ ์ง์คํฐ๋ฅผ // ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { addr_of!((*self.registers).dr).read_volatile() } } /// ์ผ์น ๊ฐ์ ์์ฑํฉ๋๋ค. RTC ๊ฐ์ด ์ด ๊ฐ๊ณผ ์ผ์นํ๋ฉด ์ธํฐ๋ฝํธ๊ฐ /// ์์ฑ๋ฉ๋๋ค(์ฌ์ฉ ์ค์ ๋ ๊ฒฝ์ฐ). pub fn set_match(&mut self, value: u32) { // self.registers๊ฐ ์ ์ ํ๊ฒ ๋งคํ๋ PL031 ๊ธฐ๊ธฐ์ ์ ์ด ๋ ์ง์คํฐ๋ฅผ // ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { addr_of_mut!((*self.registers).mr).write_volatile(value) } } /// ์ธํฐ๋ฝํธ๊ฐ ํ์ฑํ๋์๋์ง ์ฌ๋ถ์ ๊ด๊ณ์์ด ์ผ์น ๋ ์ง์คํฐ๊ฐ RTC ๊ฐ๊ณผ ์ผ์นํ๋์ง ์ฌ๋ถ๋ฅผ /// ๋ฐํํฉ๋๋ค. pub fn matched(&self) -> bool { // self.registers๊ฐ ์ ์ ํ๊ฒ ๋งคํ๋ PL031 ๊ธฐ๊ธฐ์ ์ ์ด ๋ ์ง์คํฐ๋ฅผ // ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. let ris = unsafe { addr_of!((*self.registers).ris).read_volatile() }; (ris & 0x01) != 0 } /// ํ์ฌ ๋๊ธฐ ์ค์ธ ์ธํฐ๋ฝํธ๊ฐ ์๋์ง ์ฌ๋ถ๋ฅผ ๋ฐํํฉ๋๋ค. /// /// ์ด๋ 'matched'๊ฐ true๋ฅผ ๋ฐํํ๊ณ ์ธํฐ๋ฝํธ๊ฐ ๋ง์คํน๋ ๊ฒฝ์ฐ์๋ง /// true์ฌ์ผ ํฉ๋๋ค. pub fn interrupt_pending(&self) -> bool { // self.registers๊ฐ ์ ์ ํ๊ฒ ๋งคํ๋ PL031 ๊ธฐ๊ธฐ์ ์ ์ด ๋ ์ง์คํฐ๋ฅผ // ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. let ris = unsafe { addr_of!((*self.registers).mis).read_volatile() }; (ris & 0x01) != 0 } /// ์ธํฐ๋ฝํธ ๋ง์คํฌ๋ฅผ ์ค์ ํ๊ฑฐ๋ ์ง์๋๋ค. /// /// ๋ง์คํฌ๊ฐ true์ธ ๊ฒฝ์ฐ ์ธํฐ๋ฝํธ๊ฐ ์ฌ์ฉ ์ค์ ๋ฉ๋๋ค. false์ด๋ฉด /// ์ธํฐ๋ฝํธ๊ฐ ์ฌ์ฉ ์ค์ง๋ฉ๋๋ค. pub fn enable_interrupt(&mut self, mask: bool) { let imsc = if mask { 0x01 } else { 0x00 }; // self.registers๊ฐ ์ ์ ํ๊ฒ ๋งคํ๋ PL031 ๊ธฐ๊ธฐ์ ์ ์ด ๋ ์ง์คํฐ๋ฅผ // ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { addr_of_mut!((*self.registers).imsc).write_volatile(imsc) } } /// ๋๊ธฐ ์ค์ธ ์ธํฐ๋ฝํธ๊ฐ ์๋ ๊ฒฝ์ฐ ์ด๋ฅผ ์ง์๋๋ค. pub fn clear_interrupt(&mut self) { // self.registers๊ฐ ์ ์ ํ๊ฒ ๋งคํ๋ PL031 ๊ธฐ๊ธฐ์ ์ ์ด ๋ ์ง์คํฐ๋ฅผ // ๊ฐ๋ฆฌํค๊ณ ์์ผ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe { addr_of_mut!((*self.registers).icr).write_volatile(0x01) } } } // ๋ชจ๋ ์ปจํ ์คํธ์์ ์ก์ธ์คํ ์ ์๋ ๊ธฐ๊ธฐ ๋ฉ๋ชจ๋ฆฌ์ ๋ํ // ํฌ์ธํฐ๋ง ํฌํจํ๋ฏ๋ก ์์ ํฉ๋๋ค. unsafe impl Send for Rtc {} }
Welcome to Concurrency in Rust
๋ฌ์คํธ๋ ๋์์ฑ ์ง์์ด ๋ง๊ฐํฉ๋๋ค. ์ด์์ฒด์ ๋ ๋ฒจ์ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ๋ฉฐ, ๋ฎคํ์ค์ ์ฑ๋๋ ์ง์ํฉ๋๋ค.
๋ฌ์คํธ์ ํ์ ์์คํ ์ ํ๋ก๊ทธ๋จ์ ๋์์ฑ ๋ฒ๊ทธ๊ฐ ์์ ๊ฒฝ์ฐ, ์ปดํ์ผ ์์ ์๋ฌ๊ฐ ๋ฐ์ํ๋๋ก ํด ์ค๋๋ค. ์ปดํ์ผ๋ฌ๋ฅผ ์ด์ฉํด์ ํ๋ก๊ทธ๋จ์ด ์ํ์์ ์ ํํ ๋์ํจ์ ๋ฏธ๋ฆฌ ๋ณด์ฅํด ์ฃผ๊ธฐ ๋๋ฌธ์, ์ฌ๋๋ค์ ์ด๋ฅผ ์ข ์ข ๊ฒ์๋ ๋์์ฑ ์ด๋ผ๊ณ ํฉ๋๋ค.
- Rust lets us access OS concurrency toolkit: threads, sync. primitives, etc.
- The type system gives us safety for concurrency without any special features.
- The same tools that help with โconcurrentโ access in a single thread (e.g., a called function that might mutate an argument or save references to it to read later) save us from multi-threading issues.
์ค๋ ๋
๋ฌ์คํธ์ ์ค๋ ๋๋ ๋ค๋ฅธ ์ธ์ด์ ์ค๋ ๋์ ์ ์ฌํ๊ฒ ๋์ํฉ๋๋ค:
use std::thread; use std::time::Duration; fn main() { thread::spawn(|| { for i in 1..10 { println!("์ค๋ ๋์ ๊ฐ์: {i}!"); thread::sleep(Duration::from_millis(5)); } }); for i in 1..5 { println!("๊ธฐ๋ณธ ์ค๋ ๋: {i}"); thread::sleep(Duration::from_millis(5)); } }
- ์ค๋ ๋๋ ๋ชจ๋ ๋ฐ๋ชฌ ์ค๋ ๋์ ๋๋ค. ๋ฐ๋ผ์ ๋ฉ์ธ ์ค๋ ๋๋ ์ด ์ค๋ ๋๋ค์ด ๋๋๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฌ์ง ์์ต๋๋ค.
- ํ ์ค๋ ๋์์ ๋ฐ์ํ ํจ๋์ ๋ค๋ฅธ ์ค๋ ๋์๊ฒ ์ํฅ์ ๋ผ์น์ง ์์ต๋๋ค.
- ํจ๋์ ์ถ๊ฐ์ ๋ณด(ํ์ด๋ก๋)๋ฅผ ํฌํจํ ์ ์์ผ๋ฉฐ, ์ด๋
downcast_ref
๋ก ํ์ด๋ณผ ์ ์์ต๋๋ค.
- ํจ๋์ ์ถ๊ฐ์ ๋ณด(ํ์ด๋ก๋)๋ฅผ ํฌํจํ ์ ์์ผ๋ฉฐ, ์ด๋
-
Rust thread APIs look not too different from e.g. C++ ones.
-
Run the example.
- 5ms timing is loose enough that main and spawned threads stay mostly in lockstep.
- Notice that the program ends before the spawned thread reaches 10!
- This is because main ends the program and spawned threads do not make it persist.
- Compare to pthreads/C++ std::thread/boost::thread if desired.
-
How do we wait around for the spawned thread to complete?
-
thread::spawn
returns aJoinHandle
. Look at the docs.JoinHandle
has a.join()
method that blocks.
-
Use
let handle = thread::spawn(...)
and laterhandle.join()
to wait for the thread to finish and have the program count all the way to 10. -
Now what if we want to return a value?
-
Look at docs again:
thread::spawn
โs closure returnsT
JoinHandle
.join()
returnsthread::Result<T>
-
Use the
Result
return value fromhandle.join()
to get access to the returned value. -
Ok, what about the other case?
- Trigger a panic in the thread. Note that this doesnโt panic
main
. - Access the panic payload. This is a good time to talk about
Any
.
- Trigger a panic in the thread. Note that this doesnโt panic
-
Now we can return values from threads! What about taking inputs?
- Capture something by reference in the thread closure.
- An error message indicates we must move it.
- Move it in, see we can compute and then return a derived value.
-
If we want to borrow?
- Main kills child threads when it returns, but another function would just return and leave them running.
- That would be stack use-after-return, which violates memory safety!
- How do we avoid this? see next slide.
๋ฒ์ ์ค๋ ๋(Scoped Threads)
๋ณดํต, ์ค๋ ๋๋ ์ค๋ ๋ ๋ฐ์์ ๋ฐ์ดํฐ๋ฅผ ๋น๋ฆด ์ ์์ต๋๋ค:
use std::thread; fn foo() { let s = String::from("์๋ ํ์ธ์"); thread::spawn(|| { println!("๊ธธ์ด: {}", s.len()); }); } fn main() { foo(); }
ํ์ง๋ง, scoped thread์์๋ ๊ฐ๋ฅํฉ๋๋ค:
use std::thread; fn main() { let s = String::from("์๋ ํ์ธ์"); thread::scope(|scope| { scope.spawn(|| { println!("๊ธธ์ด: {}", s.len()); }); }); }
thread::scope
ํจ์๊ฐ ์๋ฃ๋๋ฉด ๊ทธ ์์์ ์์ฑ๋ ๋ชจ๋ ์ค๋ ๋๋ค์ด ์ข ๋ฃํ์์ด ๋ณด์ฅ๋๊ธฐ ๋๋ฌธ์, ๊ทธ ๋ ๋น๋ ธ๋ ๋ฐ์ดํฐ๋ค์ ๋ค์ ๋ฐํํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.- ์ผ๋ฐ์ ์ธ ๋ฌ์คํธ์ ๋น๋ฆผ ๊ท์น์ด ์ ์ฉ๋ฉ๋๋ค: ํ ์ค๋ ๋์ ์ํ ๊ฐ๋ณ ๋น๋ฆผ ๋๋ ์ฌ๋ฌ ์ค๋ ๋์ ๋ํ ๋ถ๋ณ ๋น๋ฆผ์ค ํ๋๋ง ๊ฐ๋ฅํฉ๋๋ค.
์ฑ๋
๋ฌ์คํธ์ ์ฑ๋์ Sender<T>
์ Receiver<T>
๋ ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ์ด ๋์ ์ฑ๋์ ํตํด ์๋ก ์ฐ๊ฒฐ๋์ด ์์ง๋ง, ์ฐ๋ฆฌ๋ ์ฑ๋์ ๋ณผ ์๋ ์๊ณ ์ด ์ ๋๋จ๋ง์ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); tx.send(10).unwrap(); tx.send(20).unwrap(); println!("์์ ๋จ: {:?}", rx.recv()); println!("์์ ๋จ: {:?}", rx.recv()); let tx2 = tx.clone(); tx2.send(30).unwrap(); println!("์์ ๋จ: {:?}", rx.recv()); }
mpsc
๋ โMulti-Produce, Single-Consumerโ๋ฅผ ์๋ฏธํฉ๋๋ค.Sender
์SyncSender
๋Clone
์ ๊ตฌํํ์ง๋ง (์ฆ, ์ฌ๋ฌ๊ฐ์ producer๋ฅผ ๋ง๋ค์ ์์ต๋๋ค)Receiver
๋Clone
์ ๊ตฌํํ์ง ์์ต๋๋ค.send()
์recv()
๋Result
๋ฅผ ๋ฐํํฉ๋๋ค. ๋ง์ผErr
๊ฐ ๋ฐํ๋๋ค๋ฉด, ์๋๋ฐฉ์Sender
๋๋Receiver
๊ฐ ์ญ์ ๋์๊ณ ์ฑ๋์ด ๋ซํ๋ค๋ ๋ป์ ๋๋ค.
๋ฌด๊ฒฝ๊ณ ์ฑ๋
mpsc::channel()
ํจ์๋ ๊ฒฝ๊ณ๊ฐ ์๋ ๋น๋๊ธฐ ์ฑ๋์ ์์ฑํฉ๋๋ค:
use std::sync::mpsc; use std::thread; use std::time::Duration; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let thread_id = thread::current().id(); for i in 1..10 { tx.send(format!("๋ฉ์์ง {i}")).unwrap(); println!("{thread_id:?}: ๋ณด๋ธ ๋ฉ์์ง {i}"); } println!("{thread_id:?}: ์๋ฃ"); }); thread::sleep(Duration::from_millis(100)); for msg in rx.iter() { println!("๊ธฐ๋ณธ: {msg} ๋ฐ์"); } }
๊ฒฝ๊ณ ์ฑ๋
With bounded (synchronous) channels, send
can block the current thread:
use std::sync::mpsc; use std::thread; use std::time::Duration; fn main() { let (tx, rx) = mpsc::sync_channel(3); thread::spawn(move || { let thread_id = thread::current().id(); for i in 1..10 { tx.send(format!("๋ฉ์์ง {i}")).unwrap(); println!("{thread_id:?}: ๋ณด๋ธ ๋ฉ์์ง {i}"); } println!("{thread_id:?}: ์๋ฃ"); }); thread::sleep(Duration::from_millis(100)); for msg in rx.iter() { println!("๊ธฐ๋ณธ: {msg} ๋ฐ์"); } }
send
๋ฅผ ํธ์ถํ๋ฉด ์ฑ๋์ ์ ๋ฉ์์ง๋ฅผ ์ํ ๊ณต๊ฐ์ด ํ๋ณด๋ ๋๊น์ง ํ์ฌ ์ค๋ ๋๊ฐ ์ฐจ๋จ๋ฉ๋๋ค. ์ฑ๋์์ ์ฝ๋ ์ฌ๋์ด ์๋ ๊ฒฝ์ฐ ์ค๋ ๋๊ฐ ๋ฌด๊ธฐํ ์ฐจ๋จ๋ ์ ์์ต๋๋ค.send
ํธ์ถ์ ์ค๋ฅ์ ํจ๊ป ์ค๋จ๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ฑ๋์ด ๋ซํ๋ฉดResult
๋ฅผ ๋ฐํํฉ๋๋ค. ์์ ์๋ฅผ ์ญ์ ํ๋ฉด ์ฑ๋์ด ๋ซํ๋๋ค.- A bounded channel with a size of zero is called a โrendezvous channelโ. Every send will block the current thread until another thread calls
recv
.
Send
์ Sync
How does Rust know to forbid shared access across threads? The answer is in two traits:
Send
:T
๊ฐ ์ค๋ ๋ ๊ฐ ์ด๋์ด ์์ ํ๋ค๋ฉด,T
์ ํ์ ์Send
์ ๋๋ค.Sync
:&T
๊ฐ ์ค๋ ๋ ๊ฐ ์ด๋์ด ์์ ํ๋ค๋ฉด,&T
์ ํ์ ์Sync
์ ๋๋ค.
Send
์ Sync
ํธ๋ ์์ ์์ ํ์ง ์์ ํธ๋ ์์
๋๋ค. ์ปดํ์ผ๋ฌ๋ ํ์
์ ์์๋ค์ด ๋ชจ๋ Send
์ Sync
ํ์
์ด๋ฉด ์๋์ผ๋ก ์ด ํธ๋ ์๋ค์ ์ ์ฉ์์ผ ์ค๋๋ค. ๋ฌผ๋ก ์ฌ๋ฌ๋ถ ์ค์ค๋ก ๋ง๋ค๊ณ ์๊ณ ์๋ค๋ฉด ์ง์ ๊ตฌํํด๋ ๋ฉ๋๋ค.
Sync
์Send
๋ ์ด๋ค ํ์ ์ด ํน์ ํ ์ค๋ ๋-์์ ์์ฑ์ ๊ฐ์ง์ ๋ํ๋ด๋ ๋ง์ปค๋ก ์๊ฐํ ์ ์์ต๋๋ค.- ์ด ๋ ํธ๋ ์ดํธ๋ ์ ๋๋ฆญ์์ ์ ์ฝ ์กฐ๊ฑด์ ๋ํ๋ด๋ ํธ๋ ์ดํธ๋ก ์ฌ์ฉ๋ ์๋ ์์ต๋๋ค.
Send
T
๊ฐ ์ค๋ ๋ ๊ฐ์ ์์ ํ๊ฒ ์ด๋๋ ์ ์๋ค๋ฉด,T
์ ํ์ ์Send
์ ๋๋ค.
์์ ๊ถ์ ๋ค๋ฅธ ์ค๋ ๋๋ก ์ด๋ํ๋ฉด ์๋ฉธ์๊ฐ ํด๋น ์ค๋ ๋์์ ์คํ๋ฉ๋๋ค. ์ฌ๊ธฐ์ ์๋ฌธ์ โ์ธ์ ํ ์ค๋ ๋์์ ๊ฐ์ ํ ๋นํ๊ณ ๋ค๋ฅธ ์ค๋ ๋์์ ๊ฐ์ ํ ๋น ํด์ ํ ์ ์๋๊ฐโ ์ ๋๋ค.
์๋ฅผ ๋ค์ด SQLite ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐ๊ฒฐ์ ๋จ์ผ ์ค๋ ๋์์๋ง ์ก์ธ์คํด์ผ ํฉ๋๋ค.
Sync
&T
๊ฐ ์ฌ๋ฌ ์ค๋ ๋์์ ์์ ํ๊ฒ ์ ๊ทผ๋ ์ ์๋ค๋ฉด,&T
์ ํ์ ์Sync
์ ๋๋ค.
์ข ๋ ์ ํํ ์ ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
&T
๊ฐSend
์ธ ๊ฒฝ์ฐ์๋งT
์ ํ์ ์ดSync
๊ฐ ๋ฉ๋๋ค
์ ๋ฌธ์ฅ์ ํ์ด์ ์ด์ผ๊ธฐ ํ๋ฉด, ์ด๋ค ํ์ ์ด ์ค๋ ๋ ๊ฐ์ ๊ณต์ ๋์ด์ ์ฌ์ฉ๋๊ธฐ์ ์์ ํ๋ค๋ฉด ๊ทธ ํ์ ์ ์ฐธ์กฐ ํ์ ์ ์ค๋ ๋ ๊ฐ์ ์ด๋ ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ ๋๋ค.
์ด๋ ๋ค์๊ณผ ๊ฐ์ด ์ฆ๋ช
ํ ์ ์์ต๋๋ค: ์ด๋ค ํ์
์ด Sync
๋ผ๋ ๋ง์ ๊ณง ๊ทธ ํ์
์ด ์ฌ๋ฌ ์ค๋ ๋๋ค ์ฌ์ด์์ ๋ฐ์ดํฐ ๋ ์ด์ค๋ ์ฌํ ๋๊ธฐํ ๋ฌธ์ ์์ด ๊ณต์ ๊ฐ๋ฅํ๋ค๋ ๋ง์
๋๋ค. ์ค๋ ๋ ๊ฐ ๊ณต์ ๊ฐ ์์ ํ๋ค๋ฉด, ์ค๋ ๋๊ฐ ์ด๋๋ ์์ ํ ์ ๋ฐ์ ์์ต๋๋ค. ์ด๋ค ํ์
์ ์ค๋ ๋๊ฐ ์ด๋์ด ์์ ํ๋ค๋ฉด, ๊ทธ ํ์
์ ์ฐธ์กฐ ๋ํ ์ค๋ ๋๊ฐ ์ด๋์ด ์์ ํ ์ ๋ฐ์ ์์ต๋๋ค.
์์
Send + Sync
์ฌ๋ฌ๋ถ์ด ๋ค๋ฃจ๊ฒ ๋ ๋๋ถ๋ถ์ ํ์
์ Send + Sync
์
๋๋ค:
i8
,f32
,bool
,char
,&str
, โฆ(T1, T2)
,[T; N]
,&[T]
,struct { x: T }
, โฆString
,Option<T>
,Vec<T>
,Box<T>
, โฆArc<T>
: ์ฐธ์กฐ ์นด์ดํธ ์กฐ์์ ์ํ ๋ฏน ํ๊ธฐ ๋๋ฌธ์ ์ค๋ ๋ ์์ ํจ.Mutex<T>
: ๊ฐ์ ์ ๊ทผํ๊ธฐ ์ํด ๋ฎคํ์ค๋ฅผ ์ ๊ถ์ผ ํ๊ธฐ ๋๋ฌธ์ ์ค๋ ๋ ์์ ํจ.AtomicBool
,AtomicU8
, โฆ: ๊ฐ์ ์ ๊ทผํ ๋ ํน๋ณํ ์ํ ๋ฏน ๋ช ๋ น์ด๋ค์ ์ฌ์ฉํฉ๋๋ค.
์ ๋ค๋ฆญ ํ์
์ ์ผ๋ฐ์ ์ผ๋ก ํ์
ํ๋ผ๋ฉํฐ๊ฐ Send + Sync
์ด๋ฉด Send + Sync
์
๋๋ค.
Send + !Sync
์๋ ํ์ ๋ค์ ๋ค๋ฅธ ์ค๋ ๋๋ก ์ด๋๋ ์ ์์ง๋ง ๋ด๋ถ์ ์ผ๋ก ๊ฐ์ด ๋ณ๊ฒฝ๋ ์ ์๊ธฐ ๋๋ฌธ์ ์ค๋ ๋ ์์ ํ์ง ์์ต๋๋ค:
mpsc::Sender<T>
mpsc::Receiver<T>
Cell<T>
RefCell<T>
!Send + Sync
์๋ ํ์ ๋ค์ ์ค๋ ๋ ์์ ํ์ง๋ง ๋ค๋ฅธ ์ค๋ ๋๋ก ์ด๋๋ ์ ์์ต๋๋ค:
MutexGuard<T: Sync>
: Uses OS level primitives which must be deallocated on the thread which created them.
!Send + !Sync
์๋ ํ์ ๋ค์ ์ค๋ ๋ ์์ ํ์ง๋ ์๊ณ ๋ค๋ฅธ ์ค๋ ๋๋ก ์ด๋๋ ์๋ ์์ต๋๋ค:
Rc<T>
:Rc<T>
๋ ์ํ ๋ฏนํ์ง ์์ ๋ฐฉ์์ผ๋ก ์ฐธ์กฐ ์นด์ดํธ๋ฅผ ์กฐ์ํ๋RcBox<T>
๋ฅผ ์ฐธ์กฐํฉ๋๋ค.*const T
,*mut T
: ๋ฌ์คํธ๋ ํฌ์ธํฐ๊ฐ ์ค๋ ๋ ์์ ํ์ง ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
์ํ ๊ณต์
๋ฌ์คํธ๋ ์ฃผ๋ก ์๋ ๋ ๊ฐ์ง ํ์ ์ ์ด์ฉํด์ ๊ณต์ ๋ฐ์ดํฐ ๋๊ธฐํ๋ฅผ ์ํํฉ๋๋ค:
Arc<T>
,T
์ ๋ํ ์ํ ๋ฏน ์ฐธ์กฐ ์นด์ดํธ: ์ด ์ฐธ์กฐ๋ ๋ค์์ ์ค๋ ๋ ์ฌ์ด์์ ๊ณต์ ๋ ์ ์๊ณ , ์ฐธ์กฐํ๋ ๋ง์ง๋ง ์ค๋ ๋๊ฐ ์ข ๋ฃํ ๊ฒฝ์ฐT
๋ฅผ ๋ฐํํฉ๋๋ค.Mutex<T>
:T
๊ฐ์ ๋ํ ์ํธ ๋ฐฐ์ ์์ธ์ค๋ฅผ ๋ณด์ฅํฉ๋๋ค.
Arc
Arc<T>
allows shared read-only access via Arc::clone
:
use std::sync::Arc; use std::thread; fn main() { let v = Arc::new(vec![10, 20, 30]); let mut handles = Vec::new(); for _ in 1..5 { let v = Arc::clone(&v); handles.push(thread::spawn(move || { let thread_id = thread::current().id(); println!("{thread_id:?}: {v:?}"); })); } handles.into_iter().for_each(|h| h.join().unwrap()); println!("v: {v:?}"); }
Arc
stands for โAtomic Reference Countedโ, a thread safe version ofRc
that uses atomic operations.Arc<T>
implementsClone
whether or notT
does. It implementsSend
andSync
if and only ifT
implements them both.Arc::clone()
has the cost of atomic operations that get executed, but after that the use of theT
is free.- Beware of reference cycles,
Arc
does not use a garbage collector to detect them.std::sync::Weak
can help.
Mutex
Mutex<T>
ensures mutual exclusion and allows mutable access to T
behind a read-only interface (another form of interior mutability):
use std::sync::Mutex; fn main() { let v = Mutex::new(vec![10, 20, 30]); println!("v: {:?}", v.lock().unwrap()); { let mut guard = v.lock().unwrap(); guard.push(40); } println!("v: {:?}", v.lock().unwrap()); }
๋ชจ๋ Mutex<T>
๋ impl<T: Send> Sync for Mutex<T>
๋ฅผ ์๋์ผ๋ก ๊ตฌํํจ์ ์ฐธ์กฐํ์ธ์.
Mutex
in Rust looks like a collection with just one element โ the protected data.- It is not possible to forget to acquire the mutex before accessing the protected data.
- You can get an
&mut T
from an&Mutex<T>
by taking the lock. TheMutexGuard
ensures that the&mut T
doesnโt outlive the lock being held. Mutex<T>
implements bothSend
andSync
iff (if and only if)T
implementsSend
.- A read-write lock counterpart:
RwLock
. - Why does
lock()
return aResult
?- If the thread that held the
Mutex
panicked, theMutex
becomes โpoisonedโ to signal that the data it protected might be in an inconsistent state. Callinglock()
on a poisoned mutex fails with aPoisonError
. You can callinto_inner()
on the error to recover the data regardless.
- If the thread that held the
์์
Arc
์ Mutex
์ ๋์์ ์ดํด๋ด
์๋ค:
use std::thread; // std::sync::{Arc, Mutex}; ์ฌ์ฉ fn main() { let v = vec![10, 20, 30]; let handle = thread::spawn(|| { v.push(10); }); v.push(1000); handle.join().unwrap(); println!("v: {v:?}"); }
๊ฐ๋ฅํ ํด๊ฒฐ์ฑ :
use std::sync::{Arc, Mutex}; use std::thread; fn main() { let v = Arc::new(Mutex::new(vec![10, 20, 30])); let v2 = Arc::clone(&v); let handle = thread::spawn(move || { let mut v2 = v2.lock().unwrap(); v2.push(10); }); { let mut v = v.lock().unwrap(); v.push(1000); } handle.join().unwrap(); println!("v: {v:?}"); }
๋์ฌ๊ฒจ ๋ณผ ๋ถ๋ถ:
v
๋Arc
์Mutex
๋ชจ๋์ ํฌํจ๋์ด ์์ต๋๋ค. ์ด๋Arc
์Mutex
๊ฐ ์๋ก ์์ ํ ๋ค๋ฅธ ๋ฌธ์ ๋ฅผ ์ํ ๋๊ตฌ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค.Mutex
๋ฅผArc
๋ก ๋ํํ๋ ๊ฒ์ ๊ฐ๋ณ ์ํ๋ฅผ ์ค๋ ๋๋ค ๊ฐ์ ๊ณต์ ํ ๋ ํํ ์ฌ์ฉํ๋ ํจํด์ ๋๋ค.
v: Arc<_>
๋ฅผ ๋ค๋ฅธ ์ค๋ ๋์์ ์ฌ์ฉํ๋ ค๋ฉด, ๋จผ์ v2
๋ก ๋ณต์ฌ๋ฅผ ํ๊ณ ์ด๋ฅผ ๊ทธ ์ค๋ ๋๋ก ์ด๋ ํด์ผ ํฉ๋๋ค. ๊ทธ๋์ ๋๋ค์ ์๊ทธ๋์ฒ์move
๊ฐ ์๋ ๊ฒ์ ๋๋ค.- ๋ธ๋ก์
LockGuard
์ ๋ฒ์๋ฅผ ์ต๋ํ ์ขํ๊ธฐ ์ํด ์ฌ์ฉ๋์์ต๋๋ค.
์ฐ์ต๋ฌธ์
๋์์ฑ ๊ธฐ๋ฒ๋ค์ ์ฐ์ตํด ๋ด ์๋ค
-
์์ฌํ๋ ์ฒ ํ์ ๋ฌธ์ : ๊ณ ์ ์ ์ธ ๋์์ฑ ๋ฌธ์ ์ ๋๋ค.
-
๋ฉํฐ ์ค๋ ๋ ๋งํฌ ๊ฒ์ฌ๊ธฐ: ๋ณ๋ ฌ์ ์ผ๋ก ์นํ์ด์ง์ ๋งํฌ๋ค์ ์ฒดํฌํฉ๋๋ค. ์นด๊ณ ๋ฅผ ํตํด ๋ช ๊ฐ์ง ์์กด์ฑ๋ค์ ๋ค์ด๋๋ฅด ๋ฐ์์ผ ํ๋ ํฐ ํ๋ก์ ํธ ์ ๋๋ค.
After looking at the exercises, you can look at the solutions provided.
์์ฌํ๋ ์ฒ ํ์๋ค
์์ฌํ๋ ์ฒ ํ์ ๋ฌธ์ ๋ ๋์์ฑ์ ์์ด์ ๊ณ ์ ์ ์ธ ๋ฌธ์ ์ ๋๋ค:
5๋ช ์ ์ฒ ํ์๊ฐ ์ํ์์ ์์ฌ๋ฅผ ํ๊ณ ์์ต๋๋ค. ์ฒ ํ์๋ ์ํ์์ ์์ ์ ์๋ฆฌ์ ์์์์ต๋๋ค. ํฌํฌ๋ ๊ฐ ์ ์ ์ฌ์ด์ ์์ต๋๋ค. ์ ๊ณต๋๋ ์๋ฆฌ๋ฅผ ๋จน๊ธฐ ์ํด์๋ ๋ ๊ฐ์ ํฌํฌ๋ฅผ ๋ชจ๋ ์ฌ์ฉํด์ผํฉ๋๋ค. ์ฒ ํ์๋ ์๊ฐ์ ํ๋ค๊ฐ ๋ฐฐ๊ฐ ๊ณ ํ๋ฉด ์์ ์ ์ข,์ฐ์ ํฌํฌ๋ฅผ ๋ค์ด ์๋ฆฌ๋ฅผ ๋จน์ต๋๋ค. ์ฒ ํ์๋ ์๋ฆฌ๋ฅผ ๋จน์ ํ์๋ ํฌํฌ๋ฅผ ๋ค์ ์๋ฆฌ์ ๋ด๋ ค๋์ต๋๋ค. ์ฒ ํ์๋ ์์ ์ ์ข,์ฐ์ ํฌํฌ๊ฐ ์์๋๋ง ์๋ฆฌ๋ฅผ ๋จน์ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ๋ ๊ฐ์ ํฌํฌ๋ ์ค์ง ์์ ์ ์ข,์ฐ ์ฒ ํ์๊ฐ ์๊ฐ์ ํ ๋๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค.
You will need a local Cargo installation for this exercise. Copy the code below to a file called src/main.rs
, fill out the blanks, and test that cargo run
does not deadlock:
use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::Duration; struct Fork; struct Philosopher { name: String, // left_fork: ... // right_fork: ... // thoughts: ... } impl Philosopher { fn think(&self) { self.thoughts .send(format!("์ ๋ ์นด! {}์ ์๋ก์ด ์์ด๋์ด๊ฐ ์์ต๋๋ค.", &self.name)) .unwrap(); } fn eat(&self) { // ํฌํฌ๋ฅผ ๋์ธ์... println!("{}๋์ด ๋จน๊ณ ์์ต๋๋ค...", &self.name); thread::sleep(Duration::from_millis(10)); } } static PHILOSOPHERS: &[&str] = &["Socrates", "ํํํฐ์", "ํ๋ผํค", "์๋ฆฌ์คํ ํ ๋ ์ค", "ํผํ๊ณ ๋ผ์ค"]; fn main() { // ํฌํฌ ๋ง๋ค๊ธฐ // ์ฒ ํ์ ๋ง๋ค๊ธฐ // ๊ฐ๊ฐ 100๋ฒ ์๊ฐํ๊ณ ๋จน๋๋ก ํฉ๋๋ค. // ์๊ฐ์ ์ถ๋ ฅํฉ๋๋ค. }
๋ค์๊ณผ ๊ฐ์ Cargo.toml
์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
[package]
name = "dining-philosophers"
version = "0.1.0"
edition = "2021"
๋ฉํฐ์ค๋ ๋ ๋งํฌ ๊ฒ์ฌ๊ธฐ
์๋ก ๋ฐฐ์ด๊ฒ๋ค์ ํ์ฉํด์ ๋ฉํฐ ์ค๋ ๋ ๋งํฌ ๊ฒ์ฌ๊ธฐ๋ฅผ ๋ง๋ญ๋๋ค. ์ด ๊ฒ์ฌ๊ธฐ๋ ์นํ์ด์ง ์์ ์๋ ๋งํฌ๋ค์ด ์ ํจํ์ง ํ์ธํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฌ๊ท์ ์ผ๋ก ๋์ผ ๋๋ฉ์ธ์ ๋ค๋ฅธ ๋ชจ๋ ํ์ด์ง๊ฐ ์ ํจํ์ง ํ์ธํฉ๋๋ค.
์ด๋ฅผ ์ํด์ reqwest
์ ๊ฐ์ HTTP ํด๋ผ์ด์ธํธ๊ฐ ํ์ํฉ๋๋ค. ์๋ก์ด ๋ก์ปฌ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค๊ณ reqwest
๋ฅผ ์์กด์ฑ์ ์ถ๊ฐํ์ญ์์:
cargo new link-checker
cd link-checker
cargo add --features blocking,rustls-tls reqwest
๋ง์ผ
cargo add
์ปค๋งจ๋๊ฐerror: no such subcommand
๋ก ์คํจํ๋ค๋ฉดCargo.toml
ํ์ผ์ ์ง์ ์์ ํด๋ ๋ฉ๋๋ค. ์๋์ ์ ์ฒด ์์กด์ฑ ๋ด์ฉ์ด ์์ต๋๋ค.
๋งํฌ๋ฅผ ์ฐพ๊ธฐ ์ํด์ scraper
๋ ์ถ๊ฐํฉ๋๋ค:
cargo add scraper
๋ง์ง๋ง์ผ๋ก ์ค๋ฅ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ผ๋ก thiserror
๋ ์ถ๊ฐํฉ๋๋ค:
cargo add thiserror
๋ชจ๋ cargo add
๊ฐ ๋๋๋ฉด Cargo.toml
์ ์๋ ๋ด์ฉ์ด ์ถ๊ฐ๋ฉ๋๋ค:
[package]
name = "link-checker"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
reqwest = { version = "0.11.12", features = ["blocking", "rustls-tls"] }
scraper = "0.13.0"
thiserror = "1.0.37"
์ด์ https://www.google.org/
๊ฐ์ ์น ํ์ด์ง๋ฅผ ํ์ํ ์ ์์ต๋๋ค.
rc/main.rs
ํ์ผ์ ์๋์ ๊ฐ์ต๋๋ค:
use reqwest::blocking::Client; use reqwest::Url; use scraper::{Html, Selector}; use thiserror::Error; #[derive(Error, Debug)] enum Error { #[error("์์ฒญ ์ค๋ฅ: {0}")] ReqwestError(#[from] reqwest::Error), #[error("์๋ชป๋ http ์๋ต: {0}")] BadResponse(String), } #[derive(Debug)] struct CrawlCommand { url: Url, extract_links: bool, } fn visit_page(client: &Client, command: &CrawlCommand) -> Result<Vec<Url>, Error> { println!("{:#} ํ์ธ ์ค", command.url); let response = client.get(command.url.clone()).send()?; if !response.status().is_success() { return Err(Error::BadResponse(response.status().to_string())); } let mut link_urls = Vec::new(); if !command.extract_links { return Ok(link_urls); } let base_url = response.url().to_owned(); let body_text = response.text()?; let document = Html::parse_document(&body_text); let selector = Selector::parse("a").unwrap(); let href_values = document .select(&selector) .filter_map(|element| element.value().attr("href")); for href in href_values { match base_url.join(href) { Ok(link_url) => { link_urls.push(link_url); } Err(err) => { println!("{base_url:#}์์: ํ์ฑํ ์ ์๋ {href:?}: {err}์(๋ฅผ) ๋ฌด์ํจ"); } } } Ok(link_urls) } fn main() { let client = Client::new(); let start_url = Url::parse("https://www.google.org").unwrap(); let crawl_command = CrawlCommand{ url: start_url, extract_links: true }; match visit_page(&client, &crawl_command) { Ok(links) => println!("๋งํฌ: {links:#?}"), Err(err) => println!("๋งํฌ๋ฅผ ์ถ์ถํ ์ ์์ต๋๋ค: {err:#}"), } }
์๋ ์ปค๋งจ๋๋ก ์์ค๋ฅผ ์คํํฉ๋๋ค
cargo run
ํ์คํฌ
- ์ค๋ ๋๋ฅผ ์ฌ์ฉํ์ฌ ๋งํฌ๋ฅผ ๋ณ๋ ฌ๋ก ํ์ธํฉ๋๋ค: URL์ ์ฑ๋๋ก ๋ณด๋ด์ ๋ช ๊ฐ์ ์ค๋ ๋๊ฐ URL์ ๋ณ๋ ฌ๋ก ์ฒดํฌํ๋๋ก ํฉ๋๋ค.
www.google.org
๋๋ฉ์ธ์ ๋ชจ๋ ํ์ด์ง๋ฅผ ์ฌ๊ท์ ์ผ๋ก ํ์ธํ๊ธฐ ์ํด ์ฝ๋๋ฅผ ํ์ฅํด์ ์์ฑํฉ๋๋ค: ์ฐจ๋จ๋นํ์ง ์๋๋ก 100ํ์ด์ง ์ ๋๋ก ์ ํ์ ๋์๊ธฐ ๋ฐ๋๋๋ค.
Concurrency Morning Exercise
์์ฌํ๋ ์ฒ ํ์๋ค
use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::Duration; struct Fork; struct Philosopher { name: String, left_fork: Arc<Mutex<Fork>>, right_fork: Arc<Mutex<Fork>>, thoughts: mpsc::SyncSender<String>, } impl Philosopher { fn think(&self) { self.thoughts .send(format!("์ ๋ ์นด! {}์ ์๋ก์ด ์์ด๋์ด๊ฐ ์์ต๋๋ค.", &self.name)) .unwrap(); } fn eat(&self) { println!("{}๋์ด ์์ฌ๋ฅผ ํ๋ ค๊ณ ํฉ๋๋ค.", &self.name); let _left = self.left_fork.lock().unwrap(); let _right = self.right_fork.lock().unwrap(); println!("{}๋์ด ๋จน๊ณ ์์ต๋๋ค...", &self.name); thread::sleep(Duration::from_millis(10)); } } static PHILOSOPHERS: &[&str] = &["Socrates", "ํํํฐ์", "ํ๋ผํค", "์๋ฆฌ์คํ ํ ๋ ์ค", "ํผํ๊ณ ๋ผ์ค"]; fn main() { let (tx, rx) = mpsc::sync_channel(10); let forks = (0..PHILOSOPHERS.len()) .map(|_| Arc::new(Mutex::new(Fork))) .collect::<Vec<_>>(); for i in 0..forks.len() { let tx = tx.clone(); let mut left_fork = Arc::clone(&forks[i]); let mut right_fork = Arc::clone(&forks[(i + 1) % forks.len()]); // ๊ต์ฐฉ ์ํ๋ฅผ ๋ฐฉ์งํ๋ ค๋ฉด ์ด๋๊ฐ์์ // ๋์นญ์ ์ค๋จ์์ผ์ผ ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ ์ค ์ด๋ ํ๋๋ผ๋ ์ด๊ธฐํํ์ง ์๊ณ // ํฌํฌ๊ฐ ๊ต์ฒด๋ฉ๋๋ค. if i == forks.len() - 1 { std::mem::swap(&mut left_fork, &mut right_fork); } let philosopher = Philosopher { name: PHILOSOPHERS[i].to_string(), thoughts: tx, left_fork, right_fork, }; thread::spawn(move || { for _ in 0..100 { philosopher.eat(); philosopher.think(); } }); } drop(tx); for thought in rx { println!("{thought}"); } }
Link Checker
(์ฐ์ต๋ฌธ์ ๋ก ๋์๊ฐ๊ธฐ)
use std::sync::{mpsc, Arc, Mutex}; use std::thread; use reqwest::blocking::Client; use reqwest::Url; use scraper::{Html, Selector}; use thiserror::Error; #[derive(Error, Debug)] enum Error { #[error("์์ฒญ ์ค๋ฅ: {0}")] ReqwestError(#[from] reqwest::Error), #[error("์๋ชป๋ http ์๋ต: {0}")] BadResponse(String), } #[derive(Debug)] struct CrawlCommand { url: Url, extract_links: bool, } fn visit_page(client: &Client, command: &CrawlCommand) -> Result<Vec<Url>, Error> { println!("{:#} ํ์ธ ์ค", command.url); let response = client.get(command.url.clone()).send()?; if !response.status().is_success() { return Err(Error::BadResponse(response.status().to_string())); } let mut link_urls = Vec::new(); if !command.extract_links { return Ok(link_urls); } let base_url = response.url().to_owned(); let body_text = response.text()?; let document = Html::parse_document(&body_text); let selector = Selector::parse("a").unwrap(); let href_values = document .select(&selector) .filter_map(|element| element.value().attr("href")); for href in href_values { match base_url.join(href) { Ok(link_url) => { link_urls.push(link_url); } Err(err) => { println!("{base_url:#}์์: ํ์ฑํ ์ ์๋ {href:?}: {err}์(๋ฅผ) ๋ฌด์ํจ"); } } } Ok(link_urls) } struct CrawlState { domain: String, visited_pages: std::collections::HashSet<String>, } impl CrawlState { fn new(start_url: &Url) -> CrawlState { let mut visited_pages = std::collections::HashSet::new(); visited_pages.insert(start_url.as_str().to_string()); CrawlState { domain: start_url.domain().unwrap().to_string(), visited_pages } } /// ์ฃผ์ด์ง ํ์ด์ง ๋ด์ ๋งํฌ๋ฅผ ์ถ์ถํด์ผ ํ๋์ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํฉ๋๋ค. fn should_extract_links(&self, url: &Url) -> bool { let Some(url_domain) = url.domain() else { return false; }; url_domain == self.domain } /// ์ง์ ๋ ํ์ด์ง๋ฅผ ๋ฐฉ๋ฌธํ ๊ฒ์ผ๋ก ํ์ํ๊ณ ์ด๋ฏธ ๋ฐฉ๋ฌธํ ๊ฒฝ์ฐ /// false๋ฅผ ๋ฐํํฉ๋๋ค. fn mark_visited(&mut self, url: &Url) -> bool { self.visited_pages.insert(url.as_str().to_string()) } } type CrawlResult = Result<Vec<Url>, (Url, Error)>; fn spawn_crawler_threads( command_receiver: mpsc::Receiver<CrawlCommand>, result_sender: mpsc::Sender<CrawlResult>, thread_count: u32, ) { let command_receiver = Arc::new(Mutex::new(command_receiver)); for _ in 0..thread_count { let result_sender = result_sender.clone(); let command_receiver = command_receiver.clone(); thread::spawn(move || { let client = Client::new(); loop { let command_result = { let receiver_guard = command_receiver.lock().unwrap(); receiver_guard.recv() }; let Ok(crawl_command) = command_result else { // ๋ฐ์ ์๊ฐ ์ญ์ ๋์์ต๋๋ค. ๋ ์ด์ ๋ช ๋ น์ด๊ฐ ์์ ๋์ง ์์ต๋๋ค. break; }; let crawl_result = match visit_page(&client, &crawl_command) { Ok(link_urls) => Ok(link_urls), Err(error) => Err((crawl_command.url, error)), }; result_sender.send(crawl_result).unwrap(); } }); } } fn control_crawl( start_url: Url, command_sender: mpsc::Sender<CrawlCommand>, result_receiver: mpsc::Receiver<CrawlResult>, ) -> Vec<Url> { let mut crawl_state = CrawlState::new(&start_url); let start_command = CrawlCommand { url: start_url, extract_links: true }; command_sender.send(start_command).unwrap(); let mut pending_urls = 1; let mut bad_urls = Vec::new(); while pending_urls > 0 { let crawl_result = result_receiver.recv().unwrap(); pending_urls -= 1; match crawl_result { Ok(link_urls) => { for url in link_urls { if crawl_state.mark_visited(&url) { let extract_links = crawl_state.should_extract_links(&url); let crawl_command = CrawlCommand { url, extract_links }; command_sender.send(crawl_command).unwrap(); pending_urls += 1; } } } Err((url, error)) => { bad_urls.push(url); println!("ํฌ๋กค๋ง ์ค๋ฅ ๋ฐ์: {:#}", error); continue; } } } bad_urls } fn check_links(start_url: Url) -> Vec<Url> { let (result_sender, result_receiver) = mpsc::channel::<CrawlResult>(); let (command_sender, command_receiver) = mpsc::channel::<CrawlCommand>(); spawn_crawler_threads(command_receiver, result_sender, 16); control_crawl(start_url, command_sender, result_receiver) } fn main() { let start_url = reqwest::Url::parse("https://www.google.org").unwrap(); let bad_urls = check_links(start_url); println!("์๋ชป๋ URL: {:#?}", bad_urls); }
Async Rust
โAsyncโ๋ ๋ธ๋ญ๋ (๋ ์ด์ ์งํํ ์ ์์) ๋๊น์ง ๊ฐ ์์ ์ ์คํํ ๋ค์ ์งํํ ์ค๋น๊ฐ ๋ ๋ค๋ฅธ ์์ ์ผ๋ก ์ ํํ์ฌ ์ฌ๋ฌ ์์ ์ ๋์์ ์คํํ๋ ๋์ ์คํ ๋ชจ๋ธ์ ๋๋ค. ์ด ๋ชจ๋ธ์ ์ฌ์ฉํ๋ฉด ์ ํ๋ ์์ ์ค๋ ๋์์ ๋ ๋ง์ ์์ ์ ์คํํ ์ ์์ต๋๋ค. ์ด๋, ํ ์์ ์ ์ ์งํ๊ณ ์ํํ๋๋ฐ ํ์ํ ์ค๋ฒํค๋๊ฐ (์ค๋ ๋์ ๋นํด) ๋งค์ฐ ๋ฎ๊ณ ์ด์์ฒด์ ๊ฐ ์ฌ๋ฌ I/O๋ค์์ ํ์ฌ ์งํ ๊ฐ๋ฅํ I/O๋ค์ ํจ๊ณผ์ ์ผ๋ก ์๋ณํด ์ฃผ๋ ํ๋ฆฌ๋ฏธํฐ๋ธ๋ฅผ ์ ๊ณตํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
Rust์ ๋น๋๊ธฐ ์์ ์ โfuturesโ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋ฉฐ ์ด๋ ๋ฏธ๋์ ์๋ฃ๋ ์ ์๋ ์์ ์ ๋ํ๋ ๋๋ค. Futures๋ ์๋ฃ๋์๋ค๋ ์ ํธ๋ฅผ ๋ณด๋ผ ๋๊น์ง โํด๋งโ๋ฉ๋๋ค.
Futures๋ ๋น๋๊ธฐ ๋ฐํ์์ ์ํด ํด๋ง๋๋ฉฐ, ๋น๋๊ธฐ ๋ฐํ์์๋ ์ฌ๋ฌ ๋ค์ํ ์ข ๋ฅ๊ฐ ์์ต๋๋ค.
๋น๊ต
-
ํ์ด์ฌ์๋
asyncio
๋ผ๋ ์ ์ฌํ ๋ชจ๋ธ์ด ์์ต๋๋ค. ๊ทธ๋ฌ๋ ํ์ด์ฌ์Future
ํ์ ์ ์ฝ๋ฐฑ ๊ธฐ๋ฐ์ด๋ฉฐ ํด๋ง๋์ง ์์ต๋๋ค. ํ์ด์ฌ์ผ๋ก ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ์ ํ ๋์๋, Rust์์ ๋ฐํ์์ด ๋ด๋ถ์ ์ผ๋ก ํด ์ฃผ๋ ๊ฒ๊ณผ ์ ์ฌํ, โ๋ฃจํโ๋ฅผ ๋ช ์์ ์ผ๋ก ์ฌ์ฉํด์ผ ํฉ๋๋ค. -
์๋ฐ์คํฌ๋ฆฝํธ์
Promise
๋ ๋น์ทํ์ง๋ง ์ญ์ ์ฝ๋ฐฑ ๊ธฐ๋ฐ์ ๋๋ค. ์๋ฐ์คํฌ๋ฆฝํธ์์๋ ์ด๋ฒคํธ ๋ฃจํ๊ฐ๋ฐํ์ ์์ง์์ ๊ตฌํ๋๋ฏ๋กPromise
๊ฐ ์ฒ๋ฆฌ๋๋ ์ธ๋ถ ๊ณผ์ ์ด ์จ๊ฒจ์ง๋๋ค.
async
/await
๊ฒ์์ ๋ณด์์ ๋, ๋น๋๊ธฐ Rust ์ฝ๋๋ ์ผ๋ฐ์ ์ธ ์ ์ฐจ์ ์ฝ๋์ ๋งค์ฐ ์ ์ฌํฉ๋๋ค.
use futures::executor::block_on; async fn count_to(count: i32) { for i in 1..=count { println!("์: {i}๊ฐ!"); } } async fn async_main(count: i32) { count_to(count).await; } fn main() { block_on(async_main(10)); }
ํค ํฌ์ธํธ:
-
Rust ๋น๋๊ธฐ ๋ฌธ๋ฒ์ ๋ณด์ฌ์ฃผ๋ ๊ฐ๋จํ ์์์ ๋๋ค. ์ฌ๊ธฐ์๋ ์ค๋ ์คํ๋๋ ์์ ์ด๋, ์ค์ ๋ก ๋์์ ์ํ๋๋ ๊ฒ๋ค์ ์์ต๋๋ค.
-
async
ํจ์์ ๋ฆฌํด ํ์ ์ ๋ฌด์์ธ๊ฐ์?main
์์ `let future: () = async_main(10);์ ์ฌ์ฉํ์ฌ ํ์ ์ ํ์ธํ์ธ์.
-
The โasyncโ keyword is syntactic sugar. The compiler replaces the return type with a future.
-
main
์ ๋น๋๊ธฐ ํจ์๋ก ๋ง๋ค์๋ ์์ต๋๋ค. ๋ง์ฝ ๊ทธ๋ ๊ฒ ํ ๊ฒฝ์ฐ ์ปดํ์ผ๋ฌ๋ ๋ฆฌํด ํ์ ์ธ future๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ง ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ๋๋ค. -
You need an executor to run async code.
block_on
blocks the current thread until the provided future has run to completion. -
.await
๋ ๋ค๋ฅธ ์์ ์ด ์๋ฃ๋ ๋๊น์ง ๋น๋๊ธฐ์ ์ผ๋ก ๋๊ธฐํฉ๋๋ค.block_on
๊ณผ ๋ฌ๋ฆฌ.await
๋ ํ์ฌ ์ค๋ ๋๋ฅผ ๋ธ๋กํ์ง ์์ต๋๋ค. -
.await
can only be used inside anasync
function (or block; these are introduced later).
Future
Future
๋ ํธ๋ ์์
๋๋ค.์ด ํธ๋ ์์ ์์ง ์๋ฃ๋์ง ์์์ ์๋ ์๋ ์์
์ ๋ํ๋
๋๋ค. Future๋ poll
ํจ์๋ฅผ ํตํด ํด๋ง๋ ์ ์์ผ๋ฉฐ, ์ด ํจ์๋ Poll
์ ๋ฐํํฉ๋๋ค.
#![allow(unused)] fn main() { use std::pin::Pin; use std::task::Context; pub trait Future { type Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>; } pub enum Poll<T> { Ready(T), Pending, } }
๋น๋๊ธฐ ํจ์๋ impl Future
๋ฅผ ๋ฐํํฉ๋๋ค. ์๋ก์ด ํ์
์ ๋ง๋ค๊ณ ์ด ํ์
์ด Future
๋ฅผ ๊ตฌํํ๊ฒ ํ ์๋ ์์ง๋ง ์ผ๋ฐ์ ์ด์ง๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด tokio::spawn
๊ฐ ๋ฆฌํดํ๋ JoinHandle
์ Future
๋ฅผ ๊ตฌํํ๋ฉฐ, ์ด๋ฅผ ํตํด ์์ฑ๋ ์ค๋ ๋์ joinํ ์ ์์ต๋๋ค.
Future์ .await
๋ฅผ ํธ์ถํ๋ฉด, ํด๋น Future๊ฐ ์ค๋น๋ ๋๊น์ง ํ์ฌ ๋น๋๊ธฐ ํจ์๊ฐ ์ผ์ ์ค์ง๋ฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ Future๊ฐ ์ค๋น๊ฐ ๋๋ฉด, ๊ทธ ๊ฐ์ด .await
๊ตฌ๋ฌธ์ ๊ฐ์ด ๋ฉ๋๋ค.
-
Future
์Poll
ํ์ ์ ์ค์ ์ ์๋ ์์ ๋ณด์ด๋ ๊ทธ๋๋ก ์ ๋๋ค. ๋งํฌ๋ฅผ ํด๋ฆญํ๋ฉด Rust ๋ฌธ์์์ ํ ๋ฒ ๋ ํ์ธํ ์ ์์ต๋๋ค. -
๋ณธ ๊ฐ์์ ๋ชฉ์ ์ ๋น๋๊ธฐ ์ฝ๋๋ฅผ ์์ฑํ๋๋ฐ ์๊ธฐ ๋๋ฌธ์, ์๋ก์ด ๋น๋๊ธฐ ํ๋ฆฌ๋ฏธํฐ๋ธ๋ฅผ ๋ง๋๋๋ฐ ํ์ํ
Pin
๊ณผContext
๋ ๋ค๋ฃจ์ง ์์ต๋๋ค. ์ด๋ค์ ๋ํด ๊ฐ๋จํ ์ค๋ช ํ์๋ฉด:-
Context
๋ฅผ ์ฌ์ฉํ๋ฉด Future๊ฐ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋ ๋ค์ ํด๋ง๋๋๋ก ์์ฝํ ์ ์์ต๋๋ค. -
Pin
์ ์ฌ์ฉํ๋ฉด ๋ฉ๋ชจ๋ฆฌ์์ Future์ ์์น๊ฐ ๊ณ ์ ๋๊ธฐ ๋๋ฌธ์ ํด๋น future์ ํฌ์ธํฐ๊ฐ ํญ์ ์ ํจํ๊ฒ ์ ์ง๋ฉ๋๋ค. ์ด๋.await
ํ์ ์ฐธ์กฐ๋ฅผ ์ ํจํ ์ํ๋ก ์ ์งํ๊ธฐ ์ํด ํ์ํฉ๋๋ค.
-
๋น๋๊ธฐ ๋ฐํ์๋ค
๋น๋๊ธฐ _๋ฐํ์_์ ๋ฆฌ์กํฐ (๋น๋๊ธฐ์ ์์ ์คํ์ ์ง์)์ ์คํ์ (futures๋ฅผ ์คํ)์ ๋ ๊ฐ์ง ์ญํ ์ ํฉ๋๋ค. Rust ์ธ์ด ์์ฒด์์ ๊ธฐ๋ณธ ์ ๊ณตํ๋ ๋น๋๊ธฐ ๋ฐํ์์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๋ค์๊ณผ ๊ฐ์ ๋น๋๊ธฐ ๋ฐํ์ ํฌ๋ ์๋ค์ด ์์ต๋๋ค.
- Tokio: performant, with a well-developed ecosystem of functionality like Hyper for HTTP or Tonic for gRPC.
- async-std: aims to be a โstd for asyncโ, and includes a basic runtime in
async::task
. - smol: simple and lightweight
์ฌ๋ฌ ๋๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์ ์๋ ์์ฒด ๋ฐํ์์ด ์๋ ๊ฒฝ์ฐ๋ ์์ต๋๋ค. ์๋ค ๋ค์ด Fuchsia๊ฐ ์์ต๋๋ค.
-
Rust ํ๋ ์ด๊ทธ๋ผ์ด๋์์๋ ์์ ๋์ด๋ ๋น๋๊ธฐ ๋ฐํ์ ์ค์์ Tokio๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ํ Rust ํ๋ ์ด๊ทธ๋ผ์ด๋๋ I/O๋ฅผ ํ์ฉํ์ง ์์ผ๋ฏ๋ก async๋ฅผ ๊ฐ์ง๊ณ ํ ์ ์๋ ๋ง์ ํฅ๋ฏธ๋ก์ด ์์ ๋ค์ด ๋ถ๊ฐ๋ฅ ํฉ๋๋ค.
-
Futures๋ ์คํ์๊ฐ ํด๋งํ์ง ์๋ ํ ์๋ฌด๊ฒ๋ ํ์ง ์๋๋ค๋ ์ ์์(I/O ์์ ์กฐ์ฐจ ์์ํ์ง ์์) โ๋นํ์ฑโ ์ํ์ ๋๋ค. ์ด๋ ์ฌ์ฉ๋์ง ์๋ ๊ฒฝ์ฐ์๋ ์๋ฃ๋ ๋ ๊น์ง ์คํ๋๋, ์๋ฐ ์คํฌ๋ฆฝํธ์ promise์ ๋ค๋ฆ ๋๋ค.
Tokio
Tokio provides:
- ๋น๋๊ธฐ ์ฝ๋ ์คํ์ ์ํ ๋ฉํฐ์ค๋ ๋ ๋ฐํ์
- ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋น๋๊ธฐ ๋ฒ์
- ๋๊ท๋ชจ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ํ๊ณ
use tokio::time; async fn count_to(count: i32) { for i in 1..=count { println!("์์ ๊ฐ์: {i}๊ฐ!"); time::sleep(time::Duration::from_millis(5)).await; } } #[tokio::main] async fn main() { tokio::spawn(count_to(10)); for i in 1..5 { println!("๊ธฐ๋ณธ ์์ : {i}"); time::sleep(time::Duration::from_millis(5)).await; } }
-
์ด์
tokio::main
๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํ๋ฉดmain
์ ๋น๋๊ธฐ๋ก ๋ง๋ค ์ ์์ต๋๋ค. -
spawn
ํจ์๋ ๋์ ์คํ๋๋ ์๋ก์ด โ์์ โ์ ๋ง๋ญ๋๋ค. -
์ฐธ๊ณ :
spawn
์Future
๋ฅผ ์ธ์๋ก ๋ฐ์ต๋๋ค. ๋๋ฌธ์count_to
์.await
๋ฅผ ํธ์ถํ์ง ์๋ ์ ์ ์ฃผ๋ชฉํ์ธ์.
์ฌํ ํ์ต:
-
count_to
๊ฐ 10์ ๋๋ฌํ์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ฐ ๊ทธ ์ด์ ๋ ๋ฌด์์ผ๊น์? ์ด๋ ๋น๋๊ธฐ์ ์ธ ์ทจ์๋ฅผ ๋ณด์ฌ์ฃผ๋ ์์ ๋๋ค.tokio::spawn
์ด ๋ฆฌํดํ๋ ๊ฒ์ ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋๋ก ๋๊ธฐํ๋๋ฐ ์ฌ์ฉ๋๋ ํธ๋ค์ ๋๋ค. -
tokio::spawn
๋์count_to(10).await
๋ฅผ ์ฌ์ฉํด ๋ณด์ธ์. -
tokio::spawn
์์ ๋ฐํ๋ ์์ ์await
ํด ๋ณด์ธ์.
ํ์คํฌ
Rust์ ํ์คํฌ(์์ ) ์์คํ ์ ๊ฒฝ๋ ์ค๋ ๋ฉ์ ํ ์ข ๋ฅ๋ก ๋ณผ ์ ์์ต๋๋ค.
ํ๋์ ์์
์๋, ์คํ์๊ฐ ์ด ์์
์ ์งํํ๊ธฐ ์ํด ๊ณ์ ํด๋งํ๋, ์ต์์ future๊ฐ ํ ๊ฐ ์์ต๋๋ค. ์ด future์๋ poll
๋ฉ์๋๊ฐ ํด๋งํ๋ ์ค์ฒฉ๋ future๊ฐ ํ ๊ฐ ์ด์ ์์ ์ ์์ต๋๋ค. ์ด๋ฌํ ์ค์ฒฉ๋ future๋ ์ผ๋ฐ์ ์ธ ํจ์ ํธ์ถ ์คํํ๊ณ ๋น์ทํ ์ญํ ์ ํฉ๋๋ค. ํ ์์
์์์ ์ฌ๋ฌ ์์ future๋ค์ ํด๋งํ๋ฉด, ํ์ด๋จธ๋ฅผ ์ผ๋ ๊ฒ๊ณผ ์ด๋ค I/O์์
์ ๋์์ ์ํ์ํจ ํ ํ์ด๋จธ์ I/O ์ค ๋จผ์ ๋๋๋ ๊ฒ์ ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ๊ณผ ๊ฐ์๋์์ฑ๋ ๊ตฌํํ ์ ์์ต๋๋ค.
use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpListener; #[tokio::main] async fn main() -> io::Result<()> { let listener = TcpListener::bind("127.0.0.1:0").await?; println!("ํฌํธ {}์์ ์์ ๋๊ธฐ", listener.local_addr()?.port()); loop { let (mut socket, addr) = listener.accept().await?; println!("{addr:?}์์ ์ฐ๊ฒฐ"); tokio::spawn(async move { socket.write_all(b"๋๊ตฌ์ธ์?\n").await.expect("์์ผ ์ค๋ฅ"); let mut buf = vec![0; 1024]; let name_size = socket.read(&mut buf).await.expect("์์ผ ์ค๋ฅ"); let name = std::str::from_utf8(&buf[..name_size]).unwrap().trim(); let reply = format!("{name}๋, ์ ํํด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค.\n"); socket.write_all(reply.as_bytes()).await.expect("์์ผ ์ค๋ฅ"); }); } }
์ด ์์ ๋ฅผ, ๋ก์ปฌ ์ปดํจํฐ์ ๋ง๋ค์ด ๋ src/main.rs
์ ๋ณต์ฌํ๊ณ ๊ฑฐ๊ธฐ์์ ์คํํ์ธ์.
nc ๋๋ telnet๊ณผ ๊ฐ์ TCP ์ฐ๊ฒฐ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ฐ๊ฒฐํด ๋ณด์ธ์.
-
์๊ฐ์๋ค์๊ฒ ์ด ์๋ฒ์ ๋ช ๊ฐ์ ํด๋ผ์ด์ธํธ๊ฐ ์ฐ๊ฒฐ๋๋ฉด ์ด ์๋ฒ์ ์ํ๊ฐ ์ด๋ป๊ฒ ๋ณํ ์ง ๊ทธ๋ฆผ์ ๊ทธ๋ ค๋ณด๋๋ก ํ์ธ์. ์ด๋ค ํ์คํฌ๋ค์ด ์๋์ง, ์ด ํ์คํฌ๋ค์ Future๋ ์ด๋ค ์ํ์ ์๋์ง ๋ฌผ์ด๋ด ๋๋ค.
-
This is the first time weโve seen an
async
block. This is similar to a closure, but does not take any arguments. Its return value is a Future, similar to anasync fn
. -
Async ๋ธ๋ก์ ํจ์๋ก ๋ฆฌํฉํฐ๋งํ๊ณ
?
๋ฅผ ์ฌ์ฉํ์ฌ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ๊ฐ์ ํด ๋ด ์๋ค.
๋น๋๊ธฐ ์ฑ๋
์ฌ๋ฌ ํฌ๋ ์ดํธ์์ ๋น๋๊ธฐ ์ฑ๋์ ์ง์ํฉ๋๋ค. ์๋ฅผ ๋ค์ด tokio
์์๋ ์๋์ ๊ฐ์ดํฉ๋๋ค.
use tokio::sync::mpsc::{self, Receiver}; async fn ping_handler(mut input: Receiver<()>) { let mut count: usize = 0; while let Some(_) = input.recv().await { count += 1; println!("์ง๊ธ๊น์ง ํ {count}๊ฐ๋ฅผ ๋ฐ์์ต๋๋ค."); } println!("ping_handler ์๋ฃ"); } #[tokio::main] async fn main() { let (sender, receiver) = mpsc::channel(32); let ping_handler_task = tokio::spawn(ping_handler(receiver)); for i in 0..10 { sender.send(()).await.expect("ํ์ ๋ณด๋ด์ง ๋ชปํ์ต๋๋ค."); println!("์ง๊ธ๊น์ง ํ {}๊ฐ๋ฅผ ์ ์กํ์ต๋๋ค.", i + 1); } drop(sender); ping_handler_task.await.expect("ํ ํธ๋ค๋ฌ ์์ ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค."); }
-
์ฑ๋ ํฌ๊ธฐ๋ฅผ
3
์ผ๋ก ๋ณ๊ฒฝํ๊ณ ๋์์ด ์ด๋ป๊ฒ ๋ฐ๋๋์ง ํ์ธํ์ธ์. -
๋น๋๊ธฐ ์ฑ๋์ ์ฌ์ฉํ๊ธฐ ์ํ ์ธํฐํ์ด์ค๋ ์ค์ ๊ณผ์ ์์ ๋ฐฐ์ด
sync
์ฑ๋๊ณผ ๋น์ทํฉ๋๋ค. -
std::mem::drop
ํธ์ถํ๋ ์ค์ ์ญ์ ํด ๋ณด์ธ์. ์ด๋ค ๊ฒฐ๊ณผ๊ฐ ๋ํ๋๋์? ์ด์ ๊ฐ ๋ฌด์์ธ๊ฐ์? -
Flume ํฌ๋ ์ดํธ์๋
sync
์async
,send
์recv
๋ฅผ ๋ชจ๋ ๊ตฌํํ๋ ์ฑ๋์ด ์์ต๋๋ค. ์ด๊ฒ์ IO์ CPU ์ฒ๋ฆฌ ์์ ์ด ๋ง์ ๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌํํ ๋ ๋งค์ฐ ์ ์ฉํฉ๋๋ค. -
async
์ฑ๋์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข์ ์ด์ ๋ ์ด๋ฅผ ๋ค๋ฅธfuture
์ ๊ฒฐํฉํ์ฌ ๋ณต์กํ ์ ์ด ํ๋ฆ์ ๋ง๋ค ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
Futures Control Flow
Future๋ค์ ๊ฒฐํฉํ์ฌ ๊ณ์ฐ ๊ณผ์ ์ ๋์์ฑ์ด ์๋ ํ๋ก์ฐ ๊ทธ๋ํ ํํ๋ก ๋ชจ๋ธ๋ง ํ ์ ์์ต๋๋ค. ์์ ๋ฐฐ์ด, ๊ฐ ํ์คํฌ๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ์ํ๋๋๋ก ํ๋ ๊ฒ๋ Future๋ค์ ๊ฒฐํฉํ๋ ํ ๋ฐฉ๋ฒ์ผ๋ก ๋ณผ ์ ์์ต๋๋ค.
Join
Join ์ฐ์ฐ์ ๋ชจ๋ future๊ฐ ์ค๋น๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ํ, ๊ฐ future์ ๊ฒฐ๊ณผ๊ฐ์ ๋ด์ ์ปฌ๋ ์
์ ๋ฆฌํดํฉ๋๋ค. ์ด๋ ์๋ฐ์คํฌ๋ฆฝํธ์ Promise.all
์ด๋ ํ์ด์ฌ์ asyncio.gather
์ ์ ์ฌํฉ๋๋ค.
use anyhow::Result; use futures::future; use reqwest; use std::collections::HashMap; async fn size_of_page(url: &str) -> Result<usize> { let resp = reqwest::get(url).await?; Ok(resp.text().await?.len()) } #[tokio::main] async fn main() { let urls: [&str; 4] = [ "https://google.com", "https://httpbin.org/ip", "https://play.rust-lang.org/", "BAD_URL", ]; let futures_iter = urls.into_iter().map(size_of_page); let results = future::join_all(futures_iter).await; let page_sizes_dict: HashMap<&str, Result<usize>> = urls.into_iter().zip(results.into_iter()).collect(); println!("{:?}", page_sizes_dict); }
์ด ์์ ๋ฅผ, ๋ก์ปฌ ์ปดํจํฐ์ ๋ง๋ค์ด ๋ src/main.rs
์ ๋ณต์ฌํ๊ณ ๊ฑฐ๊ธฐ์์ ์คํํ์ธ์.
-
์๋ก ๋ค๋ฅธ ํ์ ์ ๊ฐ์ง๋ ์ฌ๋ฌ ์ฌ๋ฌ futures๋ค์ joinํ๊ณ ์ ํ ๊ฒฝ์ฐ
std::future::join!
์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด futures๊ฐ ๋ช ๊ฐ๋ ์์์ง ์ปดํ์ผ ํ ๋ ์์์ผ ํ๋ค๋ ์ ์ ์ฃผ์ํ์ธ์. ์ด ๋งคํฌ๋ก๋ ์ง๊ธ์futures
ํฌ๋ ์ดํธ์ ์์ผ๋ฉฐ ๊ณง ์์ ํ ๋์ดstd::future
์ ํฌํจ๋ ์์ ์ ๋๋ค. -
The risk of
join
is that one of the futures may never resolve, this would cause your program to stall. -
join_all
์join!
๊ณผ ๊ฒฐํฉํ์ฌ http ์๋น์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ํ ๋ชจ๋ ์์ฒญ๋ค์ ํ๊บผ๋ฒ์ ์งํ์ํฌ ์๋ ์์ต๋๋ค.futures::join!
์ ์ฌ์ฉํ์ฌtokio::time::sleep
์ future์ ์ถ๊ฐํด ๋ณด์ธ์. ์ด๊ฑด ํ์์์์ ๊ตฌํํ๋ ๊ฒ์ด ์๋์ ์ฃผ์ํ์ธ์. ์ค์ ๋ก, ํ์์์์ ๋ค์ ์ฅ์์ ์ค๋ช ํ๋select!
๋ฅผ ์ฌ์ฉํด์ ๊ตฌํํด์ผ ํฉ๋๋ค. ์ฌ๊ธฐ์๋tokio::time::sleep
์ ์ฌ์ฉํ ๊ฒ์ ๋จ์ํjoin!
์ ๋์์ ์ค๋ช ํ๊ธฐ ์ํจ์ ๋๋ค.
Select
Select ์ฐ์ฐ์ ์ฌ๋ฌ future๋ค ๋ชจ๋์ ๋ํด์ ์ค๋น๋ ๋ ๊น์ง ๊ธฐ๋ค๋ฆฌ๋ค๊ฐ, ๊ทธ ์ค ์ด๋ค ํ future๊ฐ ์ต์ด๋ก ์ค๋น ์ํ๊ฐ ๋๋ฉด ํด๋น future์ ๊ฒฐ๊ณผ๊ฐ์ ๋ฆฌํดํฉ๋๋ค. ์ด๊ฒ์ ์๋ฐ์คํฌ๋ฆฝํธ์์์ Promise.race
์ ๋น์ทํฉ๋๋ค. ํ์ด์ฌ์์๋ผ๋ฉด asyncio.wait(task_set, return_when=asyncio.FIRST_COMPLETED)
๊ฐ ํ๋ ๋์๊ณผ ๋น์ทํฉ๋๋ค.
Similar to a match statement, the body of select!
has a number of arms, each of the form pattern = future => statement
. When a future
is ready, its return value is destructured by the pattern
. The statement
is then run with the resulting variables. The statement
result becomes the result of the select!
macro.
use tokio::sync::mpsc::{self, Receiver}; use tokio::time::{sleep, Duration}; #[derive(Debug, PartialEq)] enum Animal { Cat { name: String }, Dog { name: String }, } async fn first_animal_to_finish_race( mut cat_rcv: Receiver<String>, mut dog_rcv: Receiver<String>, ) -> Option<Animal> { tokio::select! { cat_name = cat_rcv.recv() => Some(Animal::Cat { name: cat_name? }), dog_name = dog_rcv.recv() => Some(Animal::Dog { name: dog_name? }) } } #[tokio::main] async fn main() { let (cat_sender, cat_receiver) = mpsc::channel(32); let (dog_sender, dog_receiver) = mpsc::channel(32); tokio::spawn(async move { sleep(Duration::from_millis(500)).await; cat_sender.send(String::from("ํ ๋ฆญ์ค")).await.expect("๊ณ ์์ด๋ฅผ ๋ณด๋ด์ง ๋ชปํ์ต๋๋ค."); }); tokio::spawn(async move { sleep(Duration::from_millis(50)).await; dog_sender.send(String::from("๋ ์ค")).await.expect("๊ฐ๋ฅผ ๋ณด๋ด์ง ๋ชปํ์ต๋๋ค."); }); let winner = first_animal_to_finish_race(cat_receiver, dog_receiver) .await .expect("์ฐ์น์๋ฅผ ์์ ํ์ง ๋ชปํ์ต๋๋ค."); println!("์ฐ์น์: {winner:?}"); }
-
In this example, we have a race between a cat and a dog.
first_animal_to_finish_race
listens to both channels and will pick whichever arrives first. Since the dog takes 50ms, it wins against the cat that take 500ms. -
You can use
oneshot
channels in this example as the channels are supposed to receive only onesend
. -
Try adding a deadline to the race, demonstrating selecting different sorts of futures.
-
Note that
select!
drops unmatched branches, which cancels their futures. It is easiest to use when every execution ofselect!
creates new futures.- ๋์์ future ์์ฒด ๋์
&mut future
๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ ๋๋ค. ํ์ง๋ง ์ด๋ ๊ฒ ํ๋ฉด ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค(Pinning์ ๋ค๋ฃฐ ๋ ์์ธํ ์ค๋ช ํ ์์ ์).
- ๋์์ future ์์ฒด ๋์
async/await์์ ์ฃผ์ํด์ผํ ํจ์
Async์ await๋ ๋์ ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ์ ์ํ ํธ๋ฆฌํ๊ณ ํจ์จ์ ์ธ ์ถ์ํ๋ฅผ ์ ๊ณตํฉ๋๋ค. ํ์ง๋ง Rust์ async/await ๋ชจ๋ธ์๋ ๋ฌธ์ ๋ ์์ต๋๋ค. ์ด ์ฅ์์ ๋ช ๊ฐ์ง ์๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์คํ์(executor)๋ฅผ ๋ธ๋ก์ํด
๋๋ถ๋ถ์ ๋น๋๊ธฐ ๋ฐํ์์ IO ์์ ๋ง ๋์์ ์คํ๋๋๋ก ํ์ฉํฉ๋๋ค. ์ฆ, CPU๋ฅผ ๋ธ๋ญํ๋ ํ์คํฌ๊ฐ ์๋ ๊ฒฝ์ฐ, ์ด๋ ์คํ์(executor)๋ฅผ ๋ธ๋ญํ๊ฒ ๋๋ฉฐ, ๊ทธ ๊ฒฐ๊ณผ๋ก ๋ค๋ฅธ ํ์คํฌ๊ฐ ์คํ๋์ง ์์ต๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ์, ํญ์ async๋ฅผ ์ง์ํ๋ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค.
use futures::future::join_all; use std::time::Instant; async fn sleep_ms(start: &Instant, id: u64, duration_ms: u64) { std::thread::sleep(std::time::Duration::from_millis(duration_ms)); println!( "future {id}์(๋) {duration_ms}๋ฐ๋ฆฌ์ด ๋์ ์ ์ ๋ชจ๋์๊ณ {}๋ฐ๋ฆฌ์ด ํ์ ์๋ฃ๋จ", start.elapsed().as_millis() ); } #[tokio::main(flavor = "current_thread")] async fn main() { let start = Instant::now(); let sleep_futures = (1..=10).map(|t| sleep_ms(&start, t, t * 10)); join_all(sleep_futures).await; }
-
์ฝ๋๋ฅผ ์คํํ์ฌ sleep๋ค์ด ๋์์ ์งํ๋์ง ์๊ณ ์์ฐจ์ ์ผ๋ก์ผ๋ก ์งํ๋๋์ง ํ์ธํ์ธ์.
-
flavor
๋ฅผ"current_thread"
๋ก ์ค์ ํ๋ฉด ๋ชจ๋ ํ์คํฌ๊ฐ ํ๋์ ์ค๋ ๋์์ ์ํ๋ฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ฌธ์ ์ํฉ์ด ๋ ๋ถ๋ช ํ ๋๋ฌ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ ์ด ๋ฒ๊ทธ๋ ๋ฉํฐ์ค๋ ๋์ธ ๊ฒฝ์ฐ์๋ ์ฌ์ ํ ์กด์ฌํฉ๋๋ค. -
std::thread::sleep
์tokio::time::sleep
์ผ๋ก ๋ฐ๊พธ๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผawait
ํด ๋ณด์ธ์. -
๋ ๋ค๋ฅธ ํด๊ฒฐ ๋ฐฉ๋ฒ์
tokio::task::spawn_blocking
์ ๋๋ค. ์ด๋ ์ค์ ์ค๋ ๋๋ฅผ ์์ฑํ๊ณ , ๊ทธ ์ค๋ ๋์ ๋ํ ํธ๋ค์ future๋ก ๋ณํํจ์ผ๋ก์จ ์คํ์๊ฐ ๋ธ๋ก๋๋ ๊ฒ์ ๋ง์ต๋๋ค. -
ํ์คํฌ๋ฅผ OS ์ค๋ ๋๋ผ๊ณ ์๊ฐํ๋ฉด ์ ๋ฉ๋๋ค. ํ์คํฌ์ OS์ค๋ ๋๋ ์ผ๋์ผ ๋งคํ ๊ด๊ณ์ ์์ง ์์ต๋๋ค. ๋๋ถ๋ถ์ ์คํ์๋ ํ๋์ OS ์ค๋ ๋์์ ์ต๋ํ ๋ง์ ํ์คํฌ๋ฅผ ์ํํ๋๋ก ์ค๊ณ๋์ด ์์ต๋๋ค. ์ด์ ์ FFI๋ฅผ ํตํด ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ํธ์์ฉํ ๋ ํนํ ๋ฌธ์ ๊ฐ ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ค๋ ๋ ๋ก์ปฌ ์ ์ฅ์๋ฅผ ์ด์ฉํ๊ฑฐ๋ ํน์ OS ์ค๋ ๋์ ๋งคํ๋ ์ ์์ต๋๋ค(์: CUDA). ์ด๋ฌํ ์ํฉ์์๋
tokio::task::spawn_blocking
์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. -
๋๊ธฐํ ๋ฎคํ ์ค๋ฅผ ์ฃผ์ํด์ ์ฌ์ฉํ์ธ์.
.await
์์ ๋ฎคํ ์ค๋ฅผ ์ ์ฉํ๋ฉด ๋ค๋ฅธ ์์ ์ด ์ฐจ๋จ๋ ์ ์์ผ๋ฉฐ ํด๋น ์์ ์ ๋์ผํ ์ค๋ ๋์์ ์คํ ์ค์ผ ์ ์์ต๋๋ค.
Pin
Async blocks and functions return types implementing the Future
trait. The type returned is the result of a compiler transformation which turns local variables into data stored inside the future.
Some of those variables can hold pointers to other local variables. Because of that, the future should never be moved to a different memory location, as it would invalidate those pointers.
To prevent moving the future type in memory, it can only be polled through a pinned pointer. Pin
is a wrapper around a reference that disallows all operations that would move the instance it points to into a different memory location.
use tokio::sync::{mpsc, oneshot}; use tokio::task::spawn; use tokio::time::{sleep, Duration}; // ์์ ํญ๋ชฉ. ์ด ๊ฒฝ์ฐ ์ง์ ๋ ์๊ฐ ๋์ ์ ์ ๋ชจ๋์ด๊ณ // `respond_on` ์ฑ๋์ ๋ฉ์์ง๋ก ์๋ตํฉ๋๋ค. #[derive(Debug)] struct Work { input: u32, respond_on: oneshot::Sender<u32>, } // ํ์์ ์์ ์ ์์ ๋๊ธฐํ๊ณ ์คํํ๋ worker์ ๋๋ค. async fn worker(mut work_queue: mpsc::Receiver<Work>) { let mut iterations = 0; loop { tokio::select! { Some(work) = work_queue.recv() => { sleep(Duration::from_millis(10)).await; // ์์ ํ๋ ์ฒํฉ๋๋ค. work.respond_on .send(work.input * 1000) .expect("์๋ต์ ๋ณด๋ด์ง ๋ชปํ์ต๋๋ค."); iterations += 1; } // TODO: 100๋ฐ๋ฆฌ์ด๋ง๋ค ๋ฐ๋ณต ํ์๋ฅผ ๋ณด๊ณ ํฉ๋๋ค. } } } // ์์ ์ ์์ฒญํ๊ณ ์์ ์ด ์๋ฃ๋๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ์์ฒญ์์ ๋๋ค. async fn do_work(work_queue: &mpsc::Sender<Work>, input: u32) -> u32 { let (tx, rx) = oneshot::channel(); work_queue .send(Work { input, respond_on: tx }) .await .expect("์์ ํ์์ ์ ์กํ์ง ๋ชปํ์ต๋๋ค."); rx.await.expect("์๋ต ๋๊ธฐ ์คํจ") } #[tokio::main] async fn main() { let (tx, rx) = mpsc::channel(10); spawn(worker(rx)); for i in 0..100 { let resp = do_work(&tx, i).await; println!("๋ฐ๋ณต ์์ ๊ฒฐ๊ณผ {i}: {resp}"); } }
-
์์์ ์๊ฐํ ๊ฒ์ ์กํฐ(actor) ํจํด์ ํ ์๋ผ๊ณ ๋ด๋ ๋ฌด๋ฐฉํฉ๋๋ค. ์กํฐ๋ ์ผ๋ฐ์ ์ผ๋ก ๋ฃจํ ์์์
select!
๋ฅผ ํธ์ถํฉ๋๋ค. -
์ด์ ๊ฐ์ ๋ช ๊ฐ์ ๋ด์ฉ์ ์์ฝํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์ฒ์ฒํ ์ดํด๋ณด์ธ์.
-
_ = sleep(Duration::from_millis(100)) => { println!(..) }
์select!
์ ์ถ๊ฐํด ๋ณด์ธ์. ์ด ์์ ์ ์คํ๋์ง ์์ต๋๋ค. ์ ๊ทธ๋ด๊น์? -
๋์ , ํด๋น future๊ฐ ํฌํจ๋
timeout_fut
๋ฅผloop
์ธ๋ถ์ ์ถ๊ฐํด ๋ณด์ธ์.#![allow(unused)] fn main() { let mut timeout_fut = sleep(Duration::from_millis(100)); loop { select! { .., _ = timeout_fut => { println!(..); }, } } }
-
์ฌ์ ํ ์๋ํ์ง ์์ต๋๋ค. ์ปดํ์ผ๋ฌ ์ค๋ฅ๋ฅผ ๋ฐ๋ผ
select!
์timeout_fut
์&mut
๋ฅผ ์ถ๊ฐํ์ฌ Move ์๋ฉํฑ ๊ด๋ จํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณBox::pin
์ ์ฌ์ฉํ์ธ์.#![allow(unused)] fn main() { let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100))); loop { select! { .., _ = &mut timeout_fut => { println!(..); }, } } }
-
์ด๋ ์ปดํ์ผ์ ๋์ง๋ง ํ์ ์์์ด ๋๋ฉด ๋งค๋ฒ ๋ฐ๋ณตํ ๋ ๋ง๋ค
Poll::Ready
๊ฐ ๋ฉ๋๋ค(์ตํฉ๋ future๊ฐ ๋์์ด ๋ ์ ์์). ํ์ ์์ ๋ ๋๋ง๋คtimeout_fut
๋ฅผ ๋ฆฌ์ ํ๋๋ก ์์ ํ์ธ์.
-
-
Box๋ ํ์ ํ ๋นํฉ๋๋ค. ๊ฒฝ์ฐ์ ๋ฐ๋ผ
std::pin::pin!
(์ต๊ทผ์์ผ ์์ ํ๋์์ผ๋ฉฐ ์ด์ ์ฝ๋๋tokio::pin!
์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์)๋ ์ฌ์ฉํ ์ ์์ง๋ง ์ด๋ ์ฌํ ๋น๋ future์ ์ฌ์ฉํ๊ธฐ๊ฐ ์ด๋ ต์ต๋๋ค. -
๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์
pin
์ ์์ ์ฌ์ฉํ์ง ์๊ณ 100ms๋ง๋คoneshot
์ฑ๋์ ์ ์กํ ๋ค๋ฅธ ์์ ์ ์์ฑํ๋ ๊ฒ์ ๋๋ค. -
Data that contains pointers to itself is called self-referential. Normally, the Rust borrow checker would prevent self-referential data from being moved, as the references cannot outlive the data they point to. However, the code transformation for async blocks and functions is not verified by the borrow checker.
-
Pin
is a wrapper around a reference. An object cannot be moved from its place using a pinned pointer. However, it can still be moved through an unpinned pointer. -
The
poll
method of theFuture
trait usesPin<&mut Self>
instead of&mut Self
to refer to the instance. Thatโs why it can only be called on a pinned pointer.
๋น๋๊ธฐ ํธ๋ ์
Async methods in traits are were stabilized only recently, in the 1.75 release. This required support for using return-position impl Trait
(RPIT) in traits, as the desugaring for async fn
includes -> impl Future<Output = ...>
.
However, even with the native support today there are some pitfalls around async fn
and RPIT in traits:
-
Return-position impl Trait captures all in-scope lifetimes (so some patterns of borrowing cannot be expressed)
-
Traits whose methods use return-position
impl trait
orasync
are notdyn
compatible.
If we do need dyn
support, the crate async_trait provides a workaround through a macro, with some caveats:
use async_trait::async_trait; use std::time::Instant; use tokio::time::{sleep, Duration}; #[async_trait] trait Sleeper { async fn sleep(&self); } struct FixedSleeper { sleep_ms: u64, } #[async_trait] impl Sleeper for FixedSleeper { async fn sleep(&self) { sleep(Duration::from_millis(self.sleep_ms)).await; } } async fn run_all_sleepers_multiple_times( sleepers: Vec<Box<dyn Sleeper>>, n_times: usize, ) { for _ in 0..n_times { println!("๋ชจ๋ ์๋ฉด์๋ฅผ ์คํ"); for sleeper in &sleepers { let start = Instant::now(); sleeper.sleep().await; println!("{}๋ฐ๋ฆฌ์ด ๋์ ์ ์ ๋ชจ๋", start.elapsed().as_millis()); } } } #[tokio::main] async fn main() { let sleepers: Vec<Box<dyn Sleeper>> = vec![ Box::new(FixedSleeper { sleep_ms: 50 }), Box::new(FixedSleeper { sleep_ms: 100 }), ]; run_all_sleepers_multiple_times(sleepers, 5).await; }
-
async_trait
์ ์ฌ์ฉํ๊ธฐ ์ฝ์ง๋ง ์ด๋ฅผ ์ํด ํ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ๋ค๋ ์ ์ ์ ์ํ์ธ์. ์ด ํ ํ ๋น์๋ ์ฑ๋ฅ ์ค๋ฒํค๋๊ฐ ์์ต๋๋ค. -
async trait
๋ฅผ ์ธ์ด ์ฐจ์์์ ์ง์ํ๋ ๊ฒ๊ณผ ๊ด๋ จ๋ ๋ฌธ์ ๋ ๋งค์ฐ ์ ๋ฌธ์ ์ธ ํ ํฝ์ด๋ฉฐ ๋ฐ๋ผ์ ์ด ๊ฐ์์์ ๋ค๋ฃฐ ๋ด์ฉ์ ์๋๋๋ค. ์ด ๊ฒ์๋ฌผ์ ์ด์ ๊ดํ ๋์ฝ ๋ง์ฌํค์ค์ ์ข์ ์ค๋ช ์ด ์์ผ๋ฏ๋ก ๊ด์ฌ์ด ์๋ค๋ฉด ์ฐธ๊ณ ํ์ธ์. -
์์์ ์๊ฐ ๋์ sleep ํ๋ ์๋ก์ด sleeper ๊ตฌ์กฐ์ฒด๋ฅผ ๋ง๋ค์ด Vec์ ์ถ๊ฐํด ๋ณด์ธ์.
์ทจ์
future๊ฐ ๋๋ฝ๋๋ฉด ๋ค์ ํด๋งํ ์ ์๋ค๋ ์๋ฏธ์
๋๋ค. ์ด๋ฅผ _์ทจ์_๋ผ๊ณ ํ๋ฉฐ, await
์ง์ ์์ ๋ฐ์ํ ์ ์์ต๋๋ค. future๊ฐ ์ทจ์๋๋๋ผ๋ ์์คํ
์ด ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ ์ ์๋๋ก ์ฃผ์๋ฅผ ๊ธฐ์ธ์ฌ์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๊ต์ฐฉ ์ํ๊ฐ ๋๊ฑฐ๋ ๋ฐ์ดํฐ๊ฐ ์์ค๋๋ฉด ์ ๋ฉ๋๋ค.
use std::io::{self, ErrorKind}; use std::time::Duration; use tokio::io::{AsyncReadExt, AsyncWriteExt, DuplexStream}; struct LinesReader { stream: DuplexStream, } impl LinesReader { fn new(stream: DuplexStream) -> Self { Self { stream } } async fn next(&mut self) -> io::Result<Option<String>> { let mut bytes = Vec::new(); let mut buf = [0]; while self.stream.read(&mut buf[..]).await? != 0 { bytes.push(buf[0]); if buf[0] == b'\n' { break; } } if bytes.is_empty() { return Ok(None); } let s = String::from_utf8(bytes) .map_err(|_| io::Error::new(ErrorKind::InvalidData, "not UTF-8"))?; Ok(Some(s)) } } async fn slow_copy(source: String, mut dest: DuplexStream) -> std::io::Result<()> { for b in source.bytes() { dest.write_u8(b).await?; tokio::time::sleep(Duration::from_millis(10)).await } Ok(()) } #[tokio::main] async fn main() -> std::io::Result<()> { let (client, server) = tokio::io::duplex(5); let handle = tokio::spawn(slow_copy("hi\nthere\n".to_owned(), client)); let mut lines = LinesReader::new(server); let mut interval = tokio::time::interval(Duration::from_millis(60)); loop { tokio::select! { _ = interval.tick() => println!("ํฑ!"), line = lines.next() => if let Some(l) = line? { print!("{}", l) } else { break }, } } handle.await.unwrap()?; Ok(()) }
-
์ปดํ์ผ๋ฌ๋ ์ทจ์ ์์ ์ ๋์์ด ๋์ง ์์ต๋๋ค. API ๋ฌธ์๋ฅผ ์ฝ๊ณ
async fn
์ ์ํ๋ฅผ ๊ณ ๋ คํด์ผ ํฉ๋๋ค. -
panic
๋ฐ?
์ ๋ฌ๋ฆฌ ์ทจ์๋ ์ค๋ฅ ์ฒ๋ฆฌ๊ฐ ์๋ ์ผ๋ฐ์ ์ธ ์ ์ด ํ๋ฆ์ ์ผ๋ถ์ ๋๋ค. -
์ด ์์์๋ ๋ฌธ์์ด์ ์ผ๋ถ๊ฐ ์์ค๋ฉ๋๋ค.
-
tick()
๋ธ๋์น๊ฐ ๋จผ์ ์๋ฃ๋ ๋๋ง๋คnext()
๋ฐbuf
๊ฐ ์ญ์ ๋ฉ๋๋ค. -
๋ค์๊ณผ ๊ฐ์ด
buf
๋ฅผ ๊ตฌ์กฐ์ฒด์ ์ผ๋ถ๋ก ๋ง๋ค์ดLinesReader
๊ฐ ์ทจ์๋์ง ์๋๋ก ํ ์ ์์ต๋๋ค.#![allow(unused)] fn main() { struct LinesReader { stream: DuplexStream, bytes: Vec<u8>, buf: [u8; 1], } impl LinesReader { fn new(stream: DuplexStream) -> Self { Self { stream, bytes: Vec::new(), buf: [0] } } async fn next(&mut self) -> io::Result<Option<String>> { // buf ๋ฐ bytes ์ ๋์ฌ๋ฅผ self๋ก ์ง์ ํฉ๋๋ค. // ... let raw = std::mem::take(&mut self.bytes); let s = String::from_utf8(raw) // ... } } }
-
-
Interval::tick
์ ํฑ์ด โdeliveredโ ๋๋์ง ์ถ์ ํ๋ฏ๋ก ์ทจ์์ ์์ ํฉ๋๋ค. -
AsyncReadExt::read
๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๊ฑฐ๋ ์ฝ์ง ์์ผ๋ฏ๋ก ์ทจ์์ ์์ ํฉ๋๋ค. -
AsyncBufReadExt::read_line
์ ์์ ์ ์ฌํ๋ฉฐ ์ทจ์์ ์์ ํ์ง ์์ต๋๋ค. ์์ธํ ๋ด์ฉ๊ณผ ๋์์ ๊ด๋ จ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์ฐ์ต๋ฌธ์
Async Rust ๊ธฐ์ ์ ์ฐ์ตํ ์ ์๋๋ก ๋ ๊ฐ์ง ์ฐ์ต๋ฌธ์ ๋ฅผ ์ค๋นํ์ต๋๋ค.
-
์์ฌํ๋ ์ฒ ํ์: ์ด ๋ฌธ์ ๋ ์ด๋ฏธ ์ค์ ์ ํ์ธํ์ต๋๋ค. ์ด๋ฒ์๋ Async Rust๋ก ๊ตฌํํฉ๋๋ค.
-
๋ธ๋ก๋์บ์คํธ ์ฑํ ์ ํ๋ฆฌ์ผ์ด์ : ๋ ๋ง์ ๊ณ ๊ธ Async Rust ๊ธฐ๋ฅ์ ์คํํ ์ ์๋ ๋๊ท๋ชจ ํ๋ก์ ํธ์ ๋๋ค.
After looking at the exercises, you can look at the solutions provided.
Dining Philosophers โ Async
๋ฌธ์ ์ ๊ดํ ์ค๋ช ์ ์์ฌํ๋ ์ฒ ํ์๋ฅผ ์ฐธ๊ณ ํ์ธ์.
As before, you will need a local Cargo installation for this exercise. Copy the code below to a file called src/main.rs
, fill out the blanks, and test that cargo run
does not deadlock:
use std::sync::Arc; use tokio::sync::mpsc::{self, Sender}; use tokio::sync::Mutex; use tokio::time; struct Fork; struct Philosopher { name: String, // left_fork: ... // right_fork: ... // thoughts: ... } impl Philosopher { async fn think(&self) { self.thoughts .send(format!("์ ๋ ์นด! {}์ ์๋ก์ด ์์ด๋์ด๊ฐ ์์ต๋๋ค.", &self.name)) .await .unwrap(); } async fn eat(&self) { // Keep trying until we have both forks println!("{}๋์ด ๋จน๊ณ ์์ต๋๋ค...", &self.name); time::sleep(time::Duration::from_millis(5)).await; } } static PHILOSOPHERS: &[&str] = &["Socrates", "ํํํฐ์", "ํ๋ผํค", "์๋ฆฌ์คํ ํ ๋ ์ค", "ํผํ๊ณ ๋ผ์ค"]; #[tokio::main] async fn main() { // ํฌํฌ ๋ง๋ค๊ธฐ // ์ฒ ํ์ ๋ง๋ค๊ธฐ // ์๊ฐํ๊ณ ๋จน๊ฒ ํฉ๋๋ค. // ์๊ฐ์ ์ถ๋ ฅํฉ๋๋ค. }
์ด๋ฒ์๋ Async Rust๋ฅผ ์ฌ์ฉํ๋ฏ๋ก tokio
์ข
์ ํญ๋ชฉ์ด ํ์ํฉ๋๋ค. ๋ค์ Cargo.toml
์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
[package]
name = "dining-philosophers-async-dine"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.26.0", features = ["sync", "time", "macros", "rt-multi-thread"] }
๋ํ ์ด๋ฒ์๋ tokio
ํฌ๋ ์ดํธ์ Mutex
์ mpsc
๋ชจ๋์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
- Can you make your implementation single-threaded?
์ฑํ ์ ํ๋ฆฌ์ผ์ด์
์ด ์ฐ์ต์์๋ ์๋ก์ด ์ง์์ ์ฌ์ฉํ์ฌ ๋ธ๋ก๋์บ์คํธ ์ฑํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌํํด ๋ณด๊ฒ ์ต๋๋ค. ํด๋ผ์ด์ธํธ๊ฐ ์ฐ๊ฒฐํ๊ณ ๋ฉ์์ง๋ฅผ ๊ฒ์ํ๋ ์ฑํ ์๋ฒ๊ฐ ์์ต๋๋ค. ํด๋ผ์ด์ธํธ๋ ํ์ค ์ ๋ ฅ์์ ์ฌ์ฉ์ ๋ฉ์์ง๋ฅผ ์ฝ๊ณ ์๋ฒ๋ก ์ ์กํฉ๋๋ค. ์ฑํ ์๋ฒ๋ ์์ ํ๋ ๊ฐ ๋ฉ์์ง๋ฅผ ๋ชจ๋ ํด๋ผ์ด์ธํธ์ ๋ธ๋ก๋์บ์คํธํฉ๋๋ค.
For this, we use a broadcast channel on the server, and tokio_websockets
for the communication between the client and the server.
์ Cargo ํ๋ก์ ํธ๋ฅผ ๋ง๋ค๊ณ ๋ค์ ์ข ์ ํญ๋ชฉ์ ์ถ๊ฐํฉ๋๋ค.
Cargo.toml:
[package]
name = "chat-async"
version = "0.1.0"
edition = "2021"
[dependencies]
futures-util = { version = "0.3.30", features = ["sink"] }
http = "1.1.0"
tokio = { version = "1.36.0", features = ["full"] }
tokio-websockets = { version = "0.7.0", features = ["client", "fastrand", "server", "sha1_smol"] }
ํ์ API
You are going to need the following functions from tokio
and tokio_websockets
. Spend a few minutes to familiarize yourself with the API.
- StreamExt::next() implemented by
WebSocketStream
: for asynchronously reading messages from a Websocket Stream. - SinkExt::send() implemented by
WebSocketStream
: for asynchronously sending messages on a Websocket Stream. - Lines::next_line()์ ํ์ค ์ ๋ ฅ์์ ์ฌ์ฉ์ ๋ฉ์์ง๋ฅผ ๋น๋๊ธฐ์์ผ๋ก ์ฝ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
- Sender::subscribe()๋ ๋ธ๋ก๋์บ์คํธ ์ฑ๋ ๊ตฌ๋ ์ ์ฌ์ฉ๋ฉ๋๋ค.
Two binaries
Normally in a Cargo project, you can have only one binary, and one src/main.rs
file. In this project, we need two binaries. One for the client, and one for the server. You could potentially make them two separate Cargo projects, but we are going to put them in a single Cargo project with two binaries. For this to work, the client and the server code should go under src/bin
(see the documentation).
Copy the following server and client code into src/bin/server.rs
and src/bin/client.rs
, respectively. Your task is to complete these files as described below.
src/bin/server.rs:
use futures_util::sink::SinkExt; use futures_util::stream::StreamExt; use std::error::Error; use std::net::SocketAddr; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::broadcast::{channel, Sender}; use tokio_websockets::{Message, ServerBuilder, WebSocketStream}; async fn handle_connection( addr: SocketAddr, mut ws_stream: WebSocketStream<TcpStream>, bcast_tx: Sender<String>, ) -> Result<(), Box<dyn Error + Send + Sync>> { // TODO: ํํธ๋ ์๋์ ์์ ์ค๋ช ์ ์ฐธ๊ณ ํ์ธ์. } #[tokio::main] async fn main() -> Result<(), Box<dyn Error + Send + Sync>> { let (bcast_tx, _) = channel(16); let listener = TcpListener::bind("127.0.0.1:2000").await?; println!("ํฌํธ 2000์์ ์์ ๋๊ธฐ"); loop { let (socket, addr) = listener.accept().await?; println!("{addr:?}์ ์ ์ฐ๊ฒฐ"); let bcast_tx = bcast_tx.clone(); tokio::spawn(async move { // ์์ TCP ์คํธ๋ฆผ์ websocket์ ๋ํํฉ๋๋ค. let ws_stream = ServerBuilder::new().accept(socket).await?; handle_connection(addr, ws_stream, bcast_tx).await }); } }
src/bin/client.rs:
use futures_util::stream::StreamExt; use futures_util::SinkExt; use http::Uri; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio_websockets::{ClientBuilder, Message}; #[tokio::main] async fn main() -> Result<(), tokio_websockets::Error> { let (mut ws_stream, _) = ClientBuilder::from_uri(Uri::from_static("ws://127.0.0.1:2000")) .connect() .await?; let stdin = tokio::io::stdin(); let mut stdin = BufReader::new(stdin).lines(); // TODO: ํํธ๋ ์๋์ ์์ ์ค๋ช ์ ์ฐธ๊ณ ํ์ธ์. }
Running the binaries
Run the server with:
cargo run --bin server
and the client with:
cargo run --bin client
ํ์คํฌ
src/bin/server.rs
์์handle_connection
ํจ์๋ฅผ ๊ตฌํํฉ๋๋ค.- ํํธ: ์ฐ์ ๋ฃจํ์์ ๋ ์์
์ ๋์์ ์คํํ๋ ๊ฒฝ์ฐ
tokio::select!
๋ฅผ ์ฌ์ฉํ์ธ์. ํ ์์ ์ ํด๋ผ์ด์ธํธ์์ ๋ฉ์์ง๋ฅผ ์์ ํ์ฌ ๋ธ๋ก๋์บ์คํธํฉ๋๋ค. ๋ค๋ฅธ ํ๋๋ ์๋ฒ๊ฐ ์์ ํ ๋ฉ์์ง๋ฅผ ํด๋ผ์ด์ธํธ๋ก ๋ณด๋ ๋๋ค.
- ํํธ: ์ฐ์ ๋ฃจํ์์ ๋ ์์
์ ๋์์ ์คํํ๋ ๊ฒฝ์ฐ
src/bin/client.rs
์์ main ํจ์๋ฅผ ์๋ฃํฉ๋๋ค.- ํํธ: ์ด์ ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ์ฐ์ ๋ฃจํ์์ ๋ ์์
์ ๋์์ ์คํํ๋ ๊ฒฝ์ฐ
tokio::select!
๋ฅผ ์ฌ์ฉํ์ธ์. (1) ํ์ค ์ ๋ ฅ์์ ์ฌ์ฉ์ ๋ฉ์์ง๋ฅผ ์ฝ๊ณ ์๋ฒ๋ก ๋ณด๋ ๋๋ค. (2) ์๋ฒ์์ ๋ฉ์์ง๋ฅผ ์์ ํ๊ณ ์ฌ์ฉ์์๊ฒ ํ์ํฉ๋๋ค.
- ํํธ: ์ด์ ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ์ฐ์ ๋ฃจํ์์ ๋ ์์
์ ๋์์ ์คํํ๋ ๊ฒฝ์ฐ
- ์ ํ์ฌํญ: ์์ ์ ์๋ฃํ๋ฉด ๋ฉ์์ง ๋ฐ์ ์๋ฅผ ์ ์ธํ ๋ชจ๋ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฉ์์ง๋ฅผ ๋ธ๋ก๋์บ์คํธํ๋๋ก ์ฝ๋๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค.
Concurrency Afternoon Exercise
Dining Philosophers โ Async
use std::sync::Arc; use tokio::sync::mpsc::{self, Sender}; use tokio::sync::Mutex; use tokio::time; struct Fork; struct Philosopher { name: String, left_fork: Arc<Mutex<Fork>>, right_fork: Arc<Mutex<Fork>>, thoughts: Sender<String>, } impl Philosopher { async fn think(&self) { self.thoughts .send(format!("์ ๋ ์นด! {}์ ์๋ก์ด ์์ด๋์ด๊ฐ ์์ต๋๋ค.", &self.name)) .await .unwrap(); } async fn eat(&self) { // Keep trying until we have both forks let (_left_fork, _right_fork) = loop { // ํฌํฌ๋ฅผ ๋์ธ์... let left_fork = self.left_fork.try_lock(); let right_fork = self.right_fork.try_lock(); let Ok(left_fork) = left_fork else { // If we didn't get the left fork, drop the right fork if we // have it and let other tasks make progress. drop(right_fork); time::sleep(time::Duration::from_millis(1)).await; continue; }; let Ok(right_fork) = right_fork else { // If we didn't get the right fork, drop the left fork and let // other tasks make progress. drop(left_fork); time::sleep(time::Duration::from_millis(1)).await; continue; }; break (left_fork, right_fork); }; println!("{}๋์ด ๋จน๊ณ ์์ต๋๋ค...", &self.name); time::sleep(time::Duration::from_millis(5)).await; // ์ฌ๊ธฐ์ ์ ๊ธ์ด ํด์ ๋ฉ๋๋ค. } } static PHILOSOPHERS: &[&str] = &["Socrates", "ํํํฐ์", "ํ๋ผํค", "์๋ฆฌ์คํ ํ ๋ ์ค", "ํผํ๊ณ ๋ผ์ค"]; #[tokio::main] async fn main() { // ํฌํฌ ๋ง๋ค๊ธฐ let mut forks = vec![]; (0..PHILOSOPHERS.len()).for_each(|_| forks.push(Arc::new(Mutex::new(Fork)))); // ์ฒ ํ์ ๋ง๋ค๊ธฐ let (philosophers, mut rx) = { let mut philosophers = vec![]; let (tx, rx) = mpsc::channel(10); for (i, name) in PHILOSOPHERS.iter().enumerate() { let left_fork = Arc::clone(&forks[i]); let right_fork = Arc::clone(&forks[(i + 1) % PHILOSOPHERS.len()]); philosophers.push(Philosopher { name: name.to_string(), left_fork, right_fork, thoughts: tx.clone(), }); } (philosophers, rx) // tx๊ฐ ์ฌ๊ธฐ์์ ์ญ์ ๋๋ฏ๋ก ๋์ค์ ๋ช ์์ ์ผ๋ก ์ญ์ ํ ํ์๊ฐ ์์ต๋๋ค. }; // ์๊ฐํ๊ณ ๋จน๊ฒ ํฉ๋๋ค. for phil in philosophers { tokio::spawn(async move { for _ in 0..100 { phil.think().await; phil.eat().await; } }); } // ์๊ฐ์ ์ถ๋ ฅํฉ๋๋ค. while let Some(thought) = rx.recv().await { println!("์๊ฒฌ ๋ณด๋ด๊ธฐ: {thought}"); } }
์ฑํ ์ ํ๋ฆฌ์ผ์ด์
(์ฐ์ต๋ฌธ์ ๋ก ๋์๊ฐ๊ธฐ)
src/bin/server.rs:
use futures_util::sink::SinkExt; use futures_util::stream::StreamExt; use std::error::Error; use std::net::SocketAddr; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::broadcast::{channel, Sender}; use tokio_websockets::{Message, ServerBuilder, WebSocketStream}; async fn handle_connection( addr: SocketAddr, mut ws_stream: WebSocketStream<TcpStream>, bcast_tx: Sender<String>, ) -> Result<(), Box<dyn Error + Send + Sync>> { ws_stream .send(Message::text("์ฑํ ์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค. ๋ฉ์์ง๋ฅผ ์ ๋ ฅํ์ธ์.".to_string())) .await?; let mut bcast_rx = bcast_tx.subscribe(); // ๋์์ ๋ ์์ ์ ์คํํ๋ ์ฐ์ ๋ฃจํ: (1) `ws_stream`์์ ๋ฉ์์ง๋ฅผ ์์ ํ์ฌ // ๋ธ๋ก๋์บ์คํ ํ๊ณ (2) `bcast_rx`์์ ๋ฉ์์ง๋ฅผ ์์ ํ์ฌ // ํด๋ผ์ด์ธํธ๋ก ์ ์กํฉ๋๋ค. loop { tokio::select! { incoming = ws_stream.next() => { match incoming { Some(Ok(msg)) => { if let Some(text) = msg.as_text() { println!("ํด๋ผ์ด์ธํธ์์: {addr:?} {text:?}"); bcast_tx.send(text.into())?; } } Some(Err(err)) => return Err(err.into()), None => return Ok(()), } } msg = bcast_rx.recv() => { ws_stream.send(Message::text(msg?)).await?; } } } } #[tokio::main] async fn main() -> Result<(), Box<dyn Error + Send + Sync>> { let (bcast_tx, _) = channel(16); let listener = TcpListener::bind("127.0.0.1:2000").await?; println!("ํฌํธ 2000์์ ์์ ๋๊ธฐ"); loop { let (socket, addr) = listener.accept().await?; println!("{addr:?}์ ์ ์ฐ๊ฒฐ"); let bcast_tx = bcast_tx.clone(); tokio::spawn(async move { // ์์ TCP ์คํธ๋ฆผ์ websocket์ ๋ํํฉ๋๋ค. let ws_stream = ServerBuilder::new().accept(socket).await?; handle_connection(addr, ws_stream, bcast_tx).await }); } }
src/bin/client.rs:
use futures_util::stream::StreamExt; use futures_util::SinkExt; use http::Uri; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio_websockets::{ClientBuilder, Message}; #[tokio::main] async fn main() -> Result<(), tokio_websockets::Error> { let (mut ws_stream, _) = ClientBuilder::from_uri(Uri::from_static("ws://127.0.0.1:2000")) .connect() .await?; let stdin = tokio::io::stdin(); let mut stdin = BufReader::new(stdin).lines(); // ๋์์ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ณ ๋ฐ๋ ์ฐ์ ๋ฃจํ loop { tokio::select! { incoming = ws_stream.next() => { match incoming { Some(Ok(msg)) => { if let Some(text) = msg.as_text() { println!("์๋ฒ์์: {}", text); } }, Some(Err(err)) => return Err(err.into()), None => return Ok(()), } } res = stdin.next_line() => { match res { Ok(None) => return Ok(()), Ok(Some(line)) => ws_stream.send(Message::text(line.to_string())).await?, Err(err) => return Err(err.into()), } } } } }
๊ฐ์ฌ์ธ์ฌ
Comprehensive Rust ๐ฆ๋ฅผ ์ด์ฉํด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค. ์ฆ๊ฒ๊ณ ์ ์ตํ ์๊ฐ์ด์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
๊ฐ์๊ฐ ์๋ฒฝํ์ง ์์ผ๋ ์ค์๋ ๊ฐ์ ์ ์ด ์๋ค๋ฉด ์ธ์ ๋ ์ง ๊นํ๋ธ๋ก ์ฐ๋ฝ์ฃผ์ธ์.
์ฉ์ด์ง
๋ค์์ ์ฌ๋ฌ Rust ์ฉ์ด์ ๊ฐ๋จํ ์ ์๋ฅผ ์ ๊ณตํ๋ ์ฉ์ด์ง์ ๋๋ค. ๋ฒ์ญ์ ๊ฒฝ์ฐ ์ฉ์ด๋ฅผ ๋ค์ ์์ด ์๋ณธ์ ์ฐ๊ฒฐํ๋ ์ญํ ๋ ํฉ๋๋ค.
- ํ ๋น:
ํ์ ๋ํ ๋์ ๋ฉ๋ชจ๋ฆฌ ํ ๋น์ ๋๋ค. - ์ธ์:
ํจ์๋ ๋ฉ์๋์ ์ ๋ฌ๋๋ ์ ๋ณด์ ๋๋ค. - Bare-metal Rust:
๋ฎ์ ์์ค์ Rust ๊ฐ๋ฐ๋ก, ์ด์์ฒด์ ๊ฐ ์๋ ์์คํ ์ ๋ฐฐํฌ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. Bare-metal Rust๋ฅผ ์ฐธ๊ณ ํ์ธ์. - ๋ธ๋ก:
๋ธ๋ก ๋ฐ _๋ฒ์_๋ฅผ ์ฐธ๊ณ ํ์ธ์. - ๋น๋ฆผ:
๋น๋ฆผ์ ์ฐธ๊ณ ํ์ธ์. - ๋น๋ฆผ ๊ฒ์ฌ๊ธฐ:
๋ชจ๋ ๋น๋ฆผ์ด ์ ํจํ์ง ํ์ธํ๋ Rust ์ปดํ์ผ๋ฌ์ ๋ถ๋ถ์ ๋๋ค. - ๊ดํธ:
{
and}
. _์ค๊ดํธ_๋ผ๊ณ ๋ ํ๋ฉฐ _๋ธ๋ก_์ ๊ตฌ๋ถํฉ๋๋ค. - ๋น๋:
์์ค ์ฝ๋๋ฅผ ์คํ ๊ฐ๋ฅํ ์ฝ๋ ๋๋ ์ฌ์ฉ ๊ฐ๋ฅํ ํ๋ก๊ทธ๋จ์ผ๋ก ๋ณํํ๋ ํ๋ก์ธ์ค์ ๋๋ค. - ํธ์ถ:
ํจ์ ๋๋ ๋ฉ์๋๋ฅผ ํธ์ถํ๊ฑฐ๋ ์คํํฉ๋๋ค. - ์ฑ๋:
์ค๋ ๋ ๊ฐ์ ๋ฉ์์ง๋ฅผ ์์ ํ๊ฒ ์ ๋ฌํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. - Comprehensive Rust ๐ฆ:
์ด ๊ณผ์ ์ Comprehensive Rust ๐ฆ๋ก ํต์นญ๋ฉ๋๋ค. - ๋์ ์คํ:
์ฌ๋ฌ ์์ ๋๋ ํ๋ก์ธ์ค๋ฅผ ๋์์ ์คํํฉ๋๋ค. - Rust์ ๋์ ์คํ:
Rust์ ๋์ ์คํ์ ์ฐธ๊ณ ํ์ธ์. - ์์:
ํ๋ก๊ทธ๋จ ์คํ ์ค์ ๋ณ๊ฒฝ๋์ง ์๋ ๊ฐ์ ๋๋ค. - ์ ์ด ํ๋ฆ:
ํ๋ก๊ทธ๋จ์์ ๊ฐ๋ณ ๋ฌธ ๋๋ ๋ช ๋ น์ด ์คํ๋๋ ์์์ ๋๋ค. - ๋น์ ์ ์ข
๋ฃ:
ํ๋ก๊ทธ๋จ์ ์๊ธฐ์น ์๊ฑฐ๋ ์ฒ๋ฆฌ๋์ง ์์ ์ค๋ฅ ๋๋ ์ข ๋ฃ์ ๋๋ค. - enumeration:
A data type that holds one of several named constants, possibly with an associated tuple or struct. - ์ค๋ฅ:
์์ ๋์์ ๋ฒ์ด๋๋ ์๊ธฐ์น ๋ชปํ ์ํ๋ ๊ฒฐ๊ณผ์ ๋๋ค. - ์ค๋ฅ ์ฒ๋ฆฌ:
ํ๋ก๊ทธ๋จ ์คํ ์ค์ ๋ฐ์ํ๋ ์ค๋ฅ๋ฅผ ๊ด๋ฆฌํ๊ณ ์ด์ ๋์ํ๋ ํ๋ก์ธ์ค์ ๋๋ค. - ์ฐ์ต:
ํ๋ก๊ทธ๋๋ฐ ๊ธฐ์ ์ ์ฐ์ตํ๊ณ ํ ์คํธํ๊ธฐ ์ํ ๊ณผ์ ๋๋ ๋ฌธ์ ์ ๋๋ค. - ํจ์:
ํน์ ์์ ์ ์คํํ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ฝ๋ ๋ธ๋ก์ ๋๋ค. - ๊ฐ๋น์ง ์ปฌ๋ ํฐ:
๋ ์ด์ ์ฌ์ฉ๋์ง ์๋ ๊ฐ์ฒด๊ฐ ์ฐจ์งํ๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์๋์ผ๋ก ํด์ ํ๋ ๋ฉ์ปค๋์ฆ์ ๋๋ค. - ์ ๋ค๋ฆญ:
ํ์ ์ ๊ดํ ์๋ฆฌํ์์๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ ๊ธฐ๋ฅ์ผ๋ก, ๋ค์ํ ๋ฐ์ดํฐ ํ์ ์ผ๋ก ์ฝ๋๋ฅผ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค. - ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅ:
์์ฑ ํ์๋ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค. - ํตํฉ ํ
์คํธ:
์์คํ ์ ์ฌ๋ฌ ๋ถ๋ถ ๋๋ ๊ตฌ์ฑ์์ ๊ฐ์ ์ํธ์์ฉ์ ํ์ธํ๋ ํ ์คํธ ํ์ ์ ๋๋ค. - ํค์๋:
ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์ ํน์ ์๋ฏธ๋ฅผ ๊ฐ์ง๋ฉฐ ์๋ณ์๋ก ์ฌ์ฉ๋ ์ ์๋ ์์ฝ์ด์ ๋๋ค. - ๋ผ์ด๋ธ๋ฌ๋ฆฌ:
ํ๋ก๊ทธ๋จ์์ ์ฌ์ฉํ ์ ์๋ ์ฌ์ ์ปดํ์ผ๋ ๋ฃจํด ๋๋ ์ฝ๋ ๋ชจ์์ ๋๋ค. - ๋งคํฌ๋ก:
Rust ๋งคํฌ๋ก๋ ์ด๋ฆ์!
๋ก ์ธ์๋ ์ ์์ต๋๋ค. ๋งคํฌ๋ก๋ ์ผ๋ฐ ํจ์๊ฐ ์ถฉ๋ถํ์ง ์์ ๋ ์ฌ์ฉ๋ฉ๋๋ค. ์ผ๋ฐ์ ์ธ ์๋ก๋ ๊ฐ๋ณ์ ์ธ ์ธ์ ์๋ฅผ ์ฌ์ฉํ๋format!
์ด ์๋๋ฐ, ์ด๋ Rust ํจ์์์ ์ง์๋์ง ์์ต๋๋ค. main
ํจ์:
Rust ํ๋ก๊ทธ๋จ์main
ํจ์๋ก ์คํ์ ์์ํฉ๋๋ค.- ์ผ์น:
ํํ์ ๊ฐ์ ๋ํ ํจํด ์ผ์น๋ฅผ ํ์ฉํ๋ Rust์ ์ ์ด ํ๋ฆ ๊ตฌ์ฑ์ ๋๋ค. - ๋ฉ๋ชจ๋ฆฌ ๋์:
ํ๋ก๊ทธ๋จ์ด ๋ ์ด์ ํ์ํ์ง ์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ์ง ๋ชปํด ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ์ ์ฐจ ๋์ด๋๋ ์ํฉ์ ๋๋ค. - ๋ฉ์๋:
Rust์ ๊ฐ์ฒด๋ ํ์ ๊ณผ ๊ด๋ จ๋ ํจ์์ ๋๋ค. - ๋ชจ๋:
Rust์์ ์ฝ๋๋ฅผ ๊ตฌ์ฑํ๊ธฐ ์ํด ํจ์, ํ์ ๋๋ ํธ๋ ์๊ณผ ๊ฐ์ ์ ์๊ฐ ํฌํจ๋ ๋ค์์คํ์ด์ค์ ๋๋ค. - ์ด๋:
Rust์์ ํ ๋ณ์์์ ๋ค๋ฅธ ๋ณ์๋ก ๊ฐ์ ์์ ๊ถ์ ์ด์ ํ๋ ๊ฒ์ ๋๋ค. - mutable:
์ ์ธ๋ ํ ๋ณ์๋ฅผ ์์ ํ ์ ์๋ Rust์ ์์ฑ์ ๋๋ค. - ์์ ๊ถ:
๊ฐ๊ณผ ๊ด๋ จ๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๊ด๋ฆฌํ๋ ์ฝ๋์ ๋ถ๋ถ์ ์ ์ํ๋ Rust์ ๊ฐ๋ ์ ๋๋ค. - ํจ๋:
Rust์์ ๋ณต๊ตฌํ ์ ์๋ ์ค๋ฅ ์ํ๋ก, ํ๋ก๊ทธ๋จ์ด ์ข ๋ฃ๋ฉ๋๋ค. - ๋งค๊ฐ๋ณ์:
ํธ์ถ ์ ํจ์๋ ๋ฉ์๋๋ก ์ ๋ฌ๋๋ ๊ฐ์ ๋๋ค. - ํจํด:
Rust์ ํํ์๊ณผ ์ผ์น์ํฌ ์ ์๋ ๊ฐ, ๋ฆฌํฐ๋ด ๋๋ ๊ตฌ์กฐ์ ์กฐํฉ์ ๋๋ค. - ํ์ด๋ก๋:
๋ฉ์์ง, ์ด๋ฒคํธ ๋๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ ์ํด ์ ๋ฌ๋๋ ๋ฐ์ดํฐ ๋๋ ์ ๋ณด์ ๋๋ค. - ํ๋ก๊ทธ๋จ:
์ปดํจํฐ๊ฐ ํน์ ์์ ์ ์ํํ๊ฑฐ๋ ํน์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์คํํ ์ ์๋ ์ผ๋ จ์ ๋ช ๋ น์ ๋๋ค. - ํ๋ก๊ทธ๋๋ฐ ์ธ์ด: ์ปดํจํฐ์ ๋ช ๋ น์ ์ ๋ฌํ๋ ๋ฐ ์ฌ์ฉ๋๋ ๊ณต์ ์์คํ ์ ๋๋ค(์: Rust).
- ์์ ์:
๋ฉ์๋๊ฐ ํธ์ถ๋๋ ์ธ์คํด์ค๋ฅผ ๋ํ๋ด๋ Rust ๋ฉ์๋์ ์ฒซ ๋ฒ์งธ ๋งค๊ฐ๋ณ์์ ๋๋ค. - ์ฐธ์กฐ ๊ณ์ฐ:
๊ฐ์ฒด์ ๋ํ ์ฐธ์กฐ ์๋ฅผ ์ถ์ ํ๊ณ ๊ฐ์๊ฐ 0์ ๋๋ฌํ๋ฉด ๊ฐ์ฒด์ ํ ๋น์ ํด์ ํ๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ธฐ๋ฒ์ ๋๋ค. - return:
ํจ์์์ ๋ฐํ๋ ๊ฐ์ ๋ํ๋ด๋ ๋ฐ ์ฌ์ฉ๋๋ Rust์ ํค์๋์ ๋๋ค. - Rust:
์์ , ์ฑ๋ฅ, ๋์ ์คํ์ ์ค์ ์ ๋ ์์คํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ๋๋ค. - Rust Fundamentals:
Days 1 to 4 of this course. - Android์ Rust:
Android์ Rust๋ฅผ ์ฐธ๊ณ ํ์ธ์. - Chromium์ Rust:
Chromium์ Rust๋ฅผ ์ฐธ๊ณ ํ์ธ์. - ์์ :
Rust์ ์์ ๊ถ ๋ฐ ๋น๋ฆผ ๊ท์น์ ์ค์ํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ จ ์ค๋ฅ๋ฅผ ๋ฐฉ์งํ๋ ์ฝ๋๋ฅผ ๋ํ๋ ๋๋ค. - ๋ฒ์:
๋ณ์๊ฐ ์ ํจํ์ฌ ์ฌ์ฉํ ์ ์๋ ํ๋ก๊ทธ๋จ์ ์์ญ์ ๋๋ค. - ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ:
Rust์์ ํ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๋ชจ๋ ๋ชจ์์ ๋๋ค. - static:
Rust์์'static
์ ์ฒด ๊ธฐ๊ฐ์ผ๋ก ์ ์ ๋ณ์ ๋๋ ํญ๋ชฉ์ ์ ์ํ๋ ๋ฐ ์ฌ์ฉ๋๋ ํค์๋์ ๋๋ค. - ๋ฌธ์์ด:
ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ๋ฐ์ดํฐ ํ์ ์ ๋๋ค. ์์ธํ ๋ด์ฉ์String
๋ฐstr
์ ์ฐธ๊ณ ํ์ธ์. - ๊ตฌ์กฐ์ฒด:
๋ค์ํ ํ์ ์ ๋ณ์๋ฅผ ๋จ์ผ ์ด๋ฆ์ผ๋ก ๊ทธ๋ฃนํํ๋ Rust์ ๋ณตํฉ ๋ฐ์ดํฐ ํ์ ์ ๋๋ค. - test:
๋ค๋ฅธ ํจ์์ ์ ํ์ฑ์ ํ ์คํธํ๋ ํจ์๊ฐ ํฌํจ๋ Rust ๋ชจ๋์ ๋๋ค. - ์ค๋ ๋:
ํ๋ก๊ทธ๋จ์ ๋ณ๋ ์คํ ์ํ์ค๋ก, ๋์ ์คํ์ ํ์ฉํฉ๋๋ค. - ์ค๋ ๋ ์์ :
๋ค์ค ์ค๋ ๋ ํ๊ฒฝ์์ ์ฌ๋ฐ๋ฅธ ๋์์ ๋ณด์ฅํ๋ ํ๋ก๊ทธ๋จ์ ์์ฑ์ ๋๋ค. - ํธ๋ ์:
์ ์ ์๋ ํ์ ์ ๊ดํด ์ ์๋ ๋ฉ์๋ ๋ชจ์์ผ๋ก, Rust์์ ๋คํ์ฑ์ ๋ฌ์ฑํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. - trait bound:
An abstraction where you can require types to implement some traits of your interest. - tuple:
A composite data type that contains variables of different types. Tuple fields have no names, and are accessed by their ordinal numbers. - ํ์
:
Rust์์ ํน์ ์ข ๋ฅ์ ๊ฐ์ ๋ํด ์ด๋ค ์์ ์ ์คํํ ์ ์๋์ง ์ง์ ํ๋ ๋ถ๋ฅ์ ๋๋ค. - ํ์
์ถ๋ก :
๋ณ์๋ ํํ์์ ํ์ ์ ์ถ๋ก ํ๋ Rust ์ปดํ์ผ๋ฌ์ ๊ธฐ๋ฅ์ ๋๋ค. - ์ ์๋์ง ์์ ๋์:
์ง์ ๋ ๊ฒฐ๊ณผ๊ฐ ์๋ Rust์ ์์ ๋๋ ์กฐ๊ฑด์ผ๋ก, ์ข ์ข ์์ธกํ ์ ์๋ ํ๋ก๊ทธ๋จ ๋์์ ์ด๋ํฉ๋๋ค. - union:
ํ ๋ฒ์ ํ๋์ฉ๋ง ์ฌ๋ฌ ํ์ ์ ๊ฐ์ ๋ณด์ ํ ์ ์๋ ๋ฐ์ดํฐ ํ์ ์ ๋๋ค. - ๋จ์ ํ
์คํธ:
Rust์๋ ์์ ๋จ์ ํ ์คํธ์ ๋๊ท๋ชจ ํตํฉ ํ ์คํธ๋ฅผ ์คํํ ์ ์๋ ์ง์ ๊ธฐ๋ฅ์ด ๋ด์ฅ๋์ด ์์ต๋๋ค. ๋จ์ ํ ์คํธ๋ฅผ ์ฐธ๊ณ ํ์ธ์. - unit type:
Type that holds no data, written as a tuple with no members. - ์์ ํ์ง ์์:
_์ ์๋์ง ์์ ๋์_์ ํธ๋ฆฌ๊ฑฐํ ์ ์๋ Rust์ ํ์ ์งํฉ์ ๋๋ค. ์์ ํ์ง ์์ Rust๋ฅผ ์ฐธ๊ณ ํ์ธ์. - variable:
A memory location storing data. Variables are valid in a scope.
๋ฌ์คํธ ์ฐธ๊ณ ์๋ฃ
๋ฌ์คํธ ์ปค๋ฎค๋ํฐ๋ ์จ๋ผ์ธ์์ ๊ณ ํ์ง์ ๋ฌด๋ฃ ์์ค๋ฅผ ๋ง๋ค์์ต๋๋ค.
๊ณต์ ๋ฌธ์๋ค
๋ฌ์คํธ ํ๋ก์ ํธ์๋ ์ฐธ์กฐํ ๋งํ ์๋ฃ๊ฐ ๋ง์ต๋๋ค. ์ผ๋ฐ์ ์ธ ๋ด์ฉ์ ๋ค๋ฃจ๋ ๋ช๊ฐ์ง ์ฐธ๊ณ ๋ฌธ์๋ค์ ๋๋ค:
- The Rust Programming Language: ๋ฌ์คํธ์ ๋ํ ๋ฌด๋ฃ ํ์ค ์์ ์ ๋๋ค. ์ธ์ด์ ๋ํ ์์ธํ ์ค๋ช ๊ณผ ์ฌ๋๋ค์ด ๋น๋ ํ ์ ์๋ ๋ช๊ฐ์ง ํ๋ก์ ํธ๋ฅผ ํฌํจํฉ๋๋ค.
- Rust By Example: ์ฌ๋ฌ ์์ ๋ฅผ ํตํด ๋ฌ์คํธ์ ๋ฌธ๋ฒ์ ๋ณด์ฌ์ฃผ๋ฉฐ ๋๋๋ก ์ฝ๋๋ฅผ ํ์ฅํ๋ ์ฝ๊ฐ์ ์ฐ์ต๋ฌธ์ ๋ค์ด ํฌํจ๋์ด ์์ต๋๋ค.
- Rust Standard Library: ๋ฌ์คํธ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ์ฒด ๋ฌธ์์ ๋๋ค.
- The Rust Reference: ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ธ๋ง๊ณผ ๋ฌ์คํธ ๋ฌธ๋ฒ์ ์ค๋ช ํ๋ ๋ฌธ์์ ๋๋ค.(์์ง ๋ถ์์ ํ๋คํจ)
์ข ๋ ์ ๋ฌธ์ ์ธ ๊ณต์ ๊ฐ์ด๋์ ๋๋ค:
- The Rustonomicon: ์์ ํ์ง ์์ ๋ฌ์คํธ, FFI, rawํฌ์ธํฐ ์์ ์ ๋ค๋ฃน๋๋ค.
- Asynchronous Programming in Rust: ๋ฌ์คํธ ๋ถ์ด ์์ฑ ๋ ์ดํ ๋์ ๋ ์๋ก์ด ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ์ ๋ค๋ฃน๋๋ค.
- The Embedded Rust Book: ์ด์์ฒด์ ๊ฐ ์๋ ์๋ฒ ๋๋ ์ฅ์น์์์ ๋ฌ์คํธ ์ฌ์ฉ๋ฒ์ ์๊ฐํฉ๋๋ค.
๋น๊ณต์์ ํ์ต ์๋ฃ
๋ฌ์คํธ์ ๋ํ ๊ธฐํ ์๋ด์์ ํํ ๋ฆฌ์ผ์ ์ผ๋ถ์ ๋๋ค:
- Learn Rust the Dangerous Way: C์ธ์ด ํ๋ก๊ทธ๋๋จธ ๊ด์ ์์ ๋ฌ์คํธ๋ฅผ ๋ค๋ฃน๋๋ค.
- Rust for Embedded C Programmers: ์๋ฒ ๋๋ C๊ฐ๋ฐ์(ํ์จ์ด ๊ฐ๋ฐ์)๋ฅผ ์ํ ๋ฌ์คํธ ๊ฐ์ด๋์ ๋๋ค.
- Rust for professionals: ๋ค๋ฅธ ์ธ์ด(C/C++, Java, Python, Javascript)์์ ๋ณ๋ ฌ๋น๊ต๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌ์คํธ ๋ฌธ๋ฒ์ ๋ค๋ฃน๋๋ค.
- Rust on Exercism: ๋ฌ์คํธ๋ฅผ ๋ฐฐ์ฐ๋๋ฐ ๋์์ด ๋๋ 100๊ฐ ์ด์์ ์ฐ์ต๋ฌธ์
- Ferrous Teaching Material: ๋ฌ์คํธ ์ธ์ด์ ๊ธฐ๋ณธ๋ถํฐ ๊ณ ๊ธ์ ์ ๋ถ ๋ค๋ฃจ๋ ์ผ๋ จ์ ์์ ํ๋ ์ ํ ์ด์ , ์น ์ด์ ๋ธ๋ฆฌ, async/await ๊ฐ์ ๋ถ๋ถ๋ ํจ๊ป ๋ค๋ฃน๋๋ค.
- Beginnerโs Series to Rust, Take your first steps with Rust: ์ฒซ๋ฒ์งธ๋ 35๊ฐ์ ์๋ฆฌ์ฆ ์์์ด๋ฉฐ ๋๋ฒ์งธ๋ ๋ฌ์คํธ์ ๋ฌธ๋ฒ๊ณผ ๊ตฌ์กฐ๋ฅผ ๋ค๋ฃจ๋ 11๊ฐ์ ๋ชจ๋ ์ธํธ์ ๋๋ค.
- Learn Rust With Entirely Too Many Linked Lists: ๋ช๊ฐ์ง ์ ํ์ ๋ฆฌ์คํธ ์๋ฃ๊ตฌ์กฐ๋ฅผ ๊ตฌํํด๋ณด๋ฉด์ ๋ฌ์คํธ์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๊ท์น๋ค์ ๊น์ด์๊ฒ ํ์ํฉ๋๋ค.
Little Book of Rust Books์์ ๋ ๋ง์ ๋ฌ์คํธ ๋ถ์ ํ์ธํด๋ณด์ธ์.
๋์์ฃผ์ ๋ถ๋ค
์ด ์๋ฃ๋ ๋ง์ ํ๋ฅญํ ๋ฌ์คํธ ๋ฌธ์๋ค์ ๋์์ ๋ฐ์ ์์ฑ๋์์ต๋๋ค. ์ ์ฉํ ์๋ฃ์ ์ ์ฒด ๋ชฉ๋ก์ other resources์์ ์ดํด๋ณด์๊ธฐ ๋ฐ๋๋๋ค.
The material of Comprehensive Rust is licensed under the terms of the Apache 2.0 license, please see LICENSE
for details.
Rust by Example
์ผ๋ถ ์์ ์ ์ฐ์ต๋ฌธ์ ๋ Rust by Example์ ์ฐธ์กฐํ์์ต๋๋ค. ๋ผ์ด์ ์ค ์กฐํญ์ ํฌํจํ์ฌ ์ ์ฅ์์ third_party/rust-by-example/
ํด๋๋ฅผ ์ฐธ์กฐํ์๊ธฐ ๋ฐ๋๋๋ค.
Rust on Exercism
์ผ๋ถ ์ฐ์ต๋ฌธ์ ๋ Rust on Exercism์ ์ฐธ์กฐํ์์ต๋๋ค. ๋ผ์ด์ ์ค ์กฐํญ์ ํฌํจํ์ฌ ์ ์ฅ์์ third_party/rust-on-exercism/
ํด๋๋ฅผ ์ฐธ์กฐํ์๊ธฐ ๋ฐ๋๋๋ค.
CXX
4์ผ์ฐจ ์คํ ๊ฐ์ ์ค Interoperability with C++์์๋ CXX์ ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํ์์ต๋๋ค. ๋ผ์ด์ ์ค ์กฐํญ์ ํฌํจํ์ฌ ์ ์ฅ์์ third_party/cxx/
ํด๋๋ฅผ ์ฐธ์กฐํ์๊ธฐ ๋ฐ๋๋๋ค.