Comprehensive Rust์— ์˜ค์‹  ๊ฒƒ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค ๐Ÿฆ€

Build workflow GitHub contributors GitHub stars

์ด ๊ฐ•์˜๋Š” ๋ฌด๋ฃŒ์ด๋ฉฐ, 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๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ˜‘๋ ฅ์ ์ธ ๋ฉ€ํ‹ฐํƒœ์Šคํ‚น)์„ ๋ชจ๋‘ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

์ œ์™ธ์‚ฌํ•ญ

๋Ÿฌ์ŠคํŠธ๋Š” ๋ฉฐ์น ๋งŒ์— ๋ชจ๋“  ๊ฒƒ์„ ๋‹ค๋ฃจ๊ธฐ์—๋Š” ๋„ˆ๋ฌด ํฐ ์–ธ์–ด์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์•„๋ž˜์™€ ๊ฐ™์€๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค:

๋…์ž ์ˆ˜์ค€์— ๋Œ€ํ•œ ๊ฐ€์ •

๋ณธ ๊ฐ•์˜๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ž์ฒด์— ๋Œ€ํ•ด์„œ๋Š” ์•Œ๊ณ  ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ๋Ÿฌ์ŠคํŠธ๋Š” ์ •์ ํƒ€์ž… ์–ธ์–ด์ด๋ฉฐ, ๊ฐ•์ขŒ์—์„œ๋Š” C/C++ ์™€์˜ ๋น„๊ต, ๋Œ€์กฐ๋ฅผ ํ†ตํ•ด ๋Ÿฌ์ŠคํŠธ๋ฅผ ์„ค๋ช…ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

C/C++์„ ๋ชจ๋ฅด๋”๋ผ๋„ ๋™์  ํƒ€์ž… ์–ธ์–ด(Python์ด๋‚˜ JavaScript ๋“ฑ) ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฒฝํ—˜์ด ์žˆ๋‹ค๋ฉด ๋”ฐ๋ผ์˜ค๋Š”๋ฐ ํฐ ๋ฌธ์ œ๋Š” ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ โ€œ๋ฐœํ‘œ์ž ๋…ธํŠธโ€œ์˜ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์„ ์ด์šฉํ•ด์„œ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ฃผ๋กœ ๊ฐ•์˜์‹ค์—์„œ ์ œ๊ธฐ๋˜๋Š” ์ผ๋ฐ˜์ ์ธ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต๋ณ€๊ณผ ๊ฐ•์‚ฌ๊ฐ€ ๋‹ค๋ฃจ์–ด์•ผ ํ•  ํ‚ค ํฌ์ธํŠธ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ•์˜ ์ง„ํ–‰

๊ฐ•์‚ฌ๋ฅผ ์œ„ํ•œ ์•ˆ๋‚ด ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ๊ตฌ๊ธ€ ๋‚ด๋ถ€์—์„œ ์ด ๊ณผ์ •์„ ์–ด๋–ค์‹์œผ๋กœ ์šด์˜ํ•ด์™”๋Š”์ง€์— ๋Œ€ํ•œ ๋ฐฐ๊ฒฝ ์ •๋ณด์ž…๋‹ˆ๋‹ค.

์ˆ˜์—…์€ ๋ณดํ†ต ์˜ค์ „ 9์‹œ๋ถ€ํ„ฐ ์˜คํ›„ 4์‹œ๊นŒ์ง€ ์ง„ํ–‰๋˜๋ฉฐ, ์ค‘๊ฐ„์— 1์‹œ๊ฐ„์˜ ์ ์‹ฌ์‹œ๊ฐ„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์˜ค์ „ ์ˆ˜์—…์ด 3์‹œ๊ฐ„, ์˜คํ›„ ์ˆ˜์—…์ด 3์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค. ๋‘ ์„ธ์…˜์—๋Š” ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ํœด์‹ ์‹œ๊ฐ„ ๋ฐ ํ•™์ƒ๋“ค์ด ์—ฐ์Šต๋ฌธ์ œ๋ฅผ ํ’€ ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ•์˜๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ์ค€๋น„:

  1. ๊ฐ•์˜ ์ž๋ฃŒ๋ฅผ ์ˆ™์ง€ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ์š” ์š”์ ์„ ๊ฐ•์กฐํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ•์˜ ์ฐธ์กฐ ๋…ธํŠธ๋ฅผ ํฌํ•จํ•˜์˜€์Šต๋‹ˆ๋‹ค. (์ถ”๊ฐ€์ ์ธ ๋…ธํŠธ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ œ๊ณตํ•ด ์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.) ๊ฐ•์˜ ์ฐธ์กฐ ๋…ธํŠธ์˜ ๋งํฌ๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋ณ„๋„์˜ ํŒ์—…์œผ๋กœ ๋ถ„๋ฆฌ๊ฐ€ ๋˜๋ฉฐ, ๋ฉ”์ธ ํ™”๋ฉด์—์„œ๋Š” ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ๊น”๋”ํ•œ ํ™”๋ฉด์œผ๋กœ ๊ฐ•์˜๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  2. 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.

  3. ์ถฉ๋ถ„ํ•œ ๊ณต๊ฐ„์„ ํ™•๋ณดํ•ฉ๋‹ˆ๋‹ค. 15์—์„œ 20๋ช… ๊ทœ๋ชจ์˜ ๊ณต๊ฐ„์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค. ์ˆ˜๊ฐ•์ƒ๊ณผ ๊ฐ•์‚ฌ๊ฐ€ ์งˆ์˜๋ฅผ ํ•˜๊ธฐ์— ์ถฉ๋ถ„ํ•œ ์‹œ๊ฐ„๊ณผ ๊ณต๊ฐ„์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ•์‚ฌ๋‚˜ ์ˆ˜๊ฐ•์ƒ ๋ชจ๋‘ _์ฑ…์ƒ_์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•์˜์‹ค์ด๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค. ๊ฐ•์˜ ์ค‘์— ๊ฐ•์‚ฌ๊ฐ€ ๋ผ์ด๋ธŒ ์ฝ”๋”ฉ์„ ํ•˜๊ฒŒ ๋  ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์œผ๋ฉฐ, ์ด๋•Œ ์ž๋ฆฌ์— ์•‰์•„ ๋…ธํŠธ๋ถ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

  4. ๊ฐ•์˜ ๋‹น์ผ ์กฐ๊ธˆ ์ผ์ฐ ์™€์„œ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ•์‚ฌ ๋…ธํŠธ๋ถ์—์„œ mdbook serve๋ฅผ ์ด์šฉํ•ด ์ง์ ‘ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ํ•˜๋ฉด ํŽ˜์ด์ง€ ์ด๋™ ์‹œ์˜ ์ง€์—ฐ์ด ์—†์Šต๋‹ˆ๋‹ค.(์„ค์น˜ ๋ฐฉ๋ฒ•์„ ์ฐธ์กฐํ•˜์„ธ์š”.) ๋˜ํ•œ, ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ•์˜ ๋„์ค‘ ์˜คํƒ€๋ฅผ ๋ฐœ๊ฒฌํ–ˆ์„ ๋•Œ ๊ทธ ์ž๋ฆฌ์—์„œ ๋ฐ”๋กœ ์ˆ˜์ • ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์žฅ์ ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

  5. ์ˆ˜๊ฐ•์ƒ๋“ค์ด ์ง์ ‘ (๊ฐœ๋ณ„ ํ˜น์€ ๊ทธ๋ฃน์œผ๋กœ) ์—ฐ์Šต๋ฌธ์ œ๋ฅผ ํ’€๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋Œ€์ฒด๋กœ ์˜ค์ „, ์˜คํ›„์— ๊ฐ๊ฐ 30-45๋ถ„ ์ •๋„๋ฅผ ์—ฐ์Šต๋ฌธ์ œ์— ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค (์ด๋Š” ํ•ด๋‹ต์„ ๋ณด๊ณ  ์„ค๋ช…ํ•˜๋Š” ์‹œ๊ฐ„๊นŒ์ง€ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค). ๋ง‰ํ˜€ ๋„์›€์„ ํ•„์š”๋กœ ํ•˜๋Š” ์ˆ˜๊ฐ•์ƒ์ด ์—†๋Š”์ง€ ์ˆ˜์‹œ๋กœ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ ์—ฌ๋Ÿฌ ์‚ฌ๋žŒ์ด ๊ฒช๊ณ  ์žˆ๋‹ค๋ฉด, ๊ทธ ๋ฌธ์ œ๋ฅผ ๊ฐ•์˜์‹ค ์ „์ฒด ์ธ์›์—๊ฒŒ ์•Œ๋ฆฌ๊ณ  ํ•ด๊ฒฐ์ฑ…์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์–ด๋””์— ๊ฐ€๋ฉด ๊ทธ ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ•ด๋‹ต์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ๋ ค ์ค๋‹ˆ๋‹ค.

์ด์ œ ์ค€๋น„๋Š” ๋๋‚ฌ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ๊ทธ๋žฌ๋“ฏ์ด ์—ฌ๋Ÿฌ๋ถ„๋“ค๋„ ์ด ๊ฐ•์˜๋ฅผ ์ฆ๊ธฐ์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค!

๊ฐ•์˜๋ฅผ ๊ณ„์† ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ด ์ฃผ์‹ญ์‹œ์˜ค. ์šฐ๋ฆฌ๋Š” ๋ฌด์—‡์ด ์ข‹์•˜๊ณ , ๋ฌด์—‡์ด ๋ชจ์ž๋ž๋Š”์ง€ ๋“ฃ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ์ˆ˜๊ฐ•์ƒ๋“ค๋กœ ๋ถ€ํ„ฐ์˜ ํ”ผ๋“œ๋ฐฑ๋„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค!

๊ฐ•์˜ ๊ตฌ์„ฑ

๊ฐ•์‚ฌ๋ฅผ ์œ„ํ•œ ์•ˆ๋‚ด ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.

Rust ๊ธฐ์ดˆ

์ฒซ 4์ผ์€ Rust ๊ธฐ์ดˆ๋กœ ์ด๋ฃจ์–ด์ง€๋ฉฐ ์งง์€ ์‹œ๊ฐ„ ์•ˆ์— ๋งŽ์€ ๋‚ด์šฉ์„ ๋‹ค๋ฃจ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Course schedule:

  • Day 1 Morning (2 hours and 10 minutes, including breaks)
SegmentDuration
๊ฐœ์š”5 minutes
Hello World!15 minutes
ํƒ€์ž… ๋ฐ ๊ฐ’45 minutes
ํ๋ฆ„ ์ œ์–ด40 minutes
  • Day 1 Afternoon (2 hours and 15 minutes, including breaks)
SegmentDuration
ํŠœํ”Œ ๋ฐ ๋ฐฐ์—ด35 minutes
์ฐธ์กฐ35 minutes
์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž…50 minutes
  • Day 2 Morning (2 hours and 55 minutes, including breaks)
SegmentDuration
๊ฐœ์š”3 minutes
ํŒจํ„ด ๋งค์นญ1 hour
๋ฉ”์†Œ๋“œ์™€ ํŠธ๋ ˆ์ดํŠธ50 minutes
์ œ๋„ค๋ฆญ40 minutes
  • Day 2 Afternoon (3 hours and 10 minutes, including breaks)
SegmentDuration
ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ1 hour and 20 minutes
ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ1 hour and 40 minutes
  • Day 3 Morning (2 hours and 20 minutes, including breaks)
SegmentDuration
๊ฐœ์š”3 minutes
๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ1 hour
์Šค๋งˆํŠธ ํฌ์ธํ„ฐ55 minutes
  • Day 3 Afternoon (2 hours and 10 minutes, including breaks)
SegmentDuration
๋นŒ๋ฆผ50 minutes
์ˆ˜๋ช…1 hour and 10 minutes
  • Day 4 Morning (2 hours and 40 minutes, including breaks)
SegmentDuration
๊ฐœ์š”3 minutes
Iterators45 minutes
๋ชจ๋“ˆ40 minutes
ํ…Œ์ŠคํŠธ45 minutes
  • Day 4 Afternoon (2 hours and 10 minutes, including breaks)
SegmentDuration
์˜ค๋ฅ˜์ฒ˜๋ฆฌ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 ๊ธฐ์ค€ ์˜์–ด๋กœ๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.)

๋‹ค๋ฅธ ์–ธ์–ด๋“ค

์ด ๊ณผ์ •์€ ๋‹ค๋ฅธ ์–ธ์–ด๋กœ๋„ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ๊ด„ํ˜ธ ์•ˆ์€ ๋ฒˆ์—ญ์— ๋„์›€ ์ฃผ์‹  ๋ถ„๋“ค์ž…๋‹ˆ๋‹ค:

ํŽ˜์ด์ง€ ์˜ค๋ฅธ์ชฝ ์œ„์˜ ๋ฉ”๋‰ด๋ฅผ ํ†ตํ•ด ๋‹ค๋ฅธ ์–ธ์–ด๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฒˆ์—ญ ๋ฌธ์ œ

์ง„ํ–‰ ์ค‘์ธ ๋ฒˆ์—ญ์ด ๋งŽ์Šต๋‹ˆ๋‹ค. ์ตœ๊ทผ์— ์—…๋ฐ์ดํŠธ๋œ ๋ฒˆ์—ญ๋ณธ์œผ๋กœ ์—ฐ๊ฒฐ๋˜๋Š” ๋งํฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

์ด ๊ณผ์ •์˜ ๋ฒˆ์—ญ ์ž‘์—…์— ๋„์›€์„ ์ฃผ๊ณ  ์‹ถ๋‹ค๋ฉด ์—ฌ๊ธฐ ์„ค๋ช…๋œ ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”. ์ง„ํ–‰ ์ค‘์ธ ๋ฒˆ์—ญ ์ž‘์—…์— ๋Œ€ํ•œ ๋‚ด์šฉ์€ ์ด์Šˆ ํŠธ๋ž˜์ปค๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

์นด๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ

๋Ÿฌ์ŠคํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๋ ค๊ณ ํ•˜๋ฉด ๋‹น์‹ ์€ ๊ณง 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 update rustc and cargo 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 and rustup 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 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)

์ด ๋ฒ„์ „๋ณด๋‹ค ๋” ์ตœ์‹ ์˜ ๋ฒ„์ „์ด์–ด๋„ ์ƒ๊ด€ ์—†์Šต๋‹ˆ๋‹ค. ๋Ÿฌ์ŠคํŠธ๋Š” ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์ •์ƒ์ ์œผ๋กœ ์„ค์น˜๊ฐ€ ๋˜์—ˆ์œผ๋ฉด, ๊ฐ•์˜ ์˜ˆ์ œ์ค‘ ํ•˜๋‚˜๋ฅผ ๋Ÿฌ์ŠคํŠธ ๋ฐ”์ด๋„ˆ๋ฆฌ๋กœ ๋นŒ๋“œํ•ด ๋ด…์‹œ๋‹ค:

  1. ์˜ˆ์‹œ ๋ธ”๋ก์— ์žˆ๋Š” โ€œCopy to clipboardโ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด์„œ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค.

  2. ํ„ฐ๋ฏธ๋„์—์„œ cargo new exercise๋ฅผ ์ž…๋ ฅํ•ด์„œ ์ƒˆ๋กœ์šด exercise/ ํด๋”๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค:

    $ cargo new exercise
         Created binary (application) `exercise` package
    
  3. 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!
    
  4. src/main.rs์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ด์ „ ํŽ˜์ด์ง€์˜ ์†Œ์Šค๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด src/main.rs์— ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค

    fn main() {
        println!("์ˆ˜์ •ํ•ด ์ฃผ์„ธ์š”!");
    }
  5. 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!
    
  6. cargo check์ปค๋งจ๋“œ๋Š” ๋น ๋ฅด๊ฒŒ ์—๋Ÿฌ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. cargo build๋Š” ์‹คํ–‰์—†์ด ์ปดํŒŒ์ผ๋งŒ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ์— target/debug/ํด๋”์—์„œ output์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. cargo build --release์ปค๋งจ๋“œ๋Š” ๋ฆด๋ฆฌ์ฆˆ ๋ฒ„์ „์šฉ ์ตœ์ ํ™”๋ฅผ ์ผœ์„œ ์ปดํŒŒ์ผํ•˜๋ฉฐ target/release/ํด๋”์—์„œ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  7. 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:

SegmentDuration
๊ฐœ์š”5 minutes
Hello World!15 minutes
ํƒ€์ž… ๋ฐ ๊ฐ’45 minutes
ํ๋ฆ„ ์ œ์–ด40 minutes
This slide should take about 5 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:

SlideDuration
๋Ÿฌ์ŠคํŠธ๋ž€?10 minutes
Rust์˜ ์ด์ 3 minutes
ํ”Œ๋ ˆ์ด๊ทธ๋ผ์šด๋“œ2 minutes

๋Ÿฌ์ŠคํŠธ๋ž€?

๋Ÿฌ์ŠคํŠธ๋Š” 2015๋…„์— ๋ฒ„์ „ 1.0์„ ๋ฆด๋ฆฌ์ฆˆ ํ•œ ์ƒˆ๋กœ์šด ํ”„๋กœ๊ทธ๋žจ ์–ธ์–ด์ž…๋‹ˆ๋‹ค:

  • ๋Ÿฌ์ŠคํŠธ๋Š” C++์™€ ์œ ์‚ฌํ•œ ์ •์  ์ปดํŒŒ์ผ ์–ธ์–ด์ž…๋‹ˆ๋‹ค
    • rustc๋Š” LLVM์„ ๋ฐฑ์—”๋“œ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๋Ÿฌ์ŠคํŠธ๋Š” ๋‹ค์–‘ํ•œ ํ”Œ๋žซํผ๊ณผ ์•„ํ‚คํ…์ณ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค:
    • x86, ARM, WebAssembly, โ€ฆ
    • Linux, Mac, Windows, โ€ฆ
  • ๋Ÿฌ์ŠคํŠธ๋Š” ๋‹ค์–‘ํ•œ ์žฅ์น˜์—์„œ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
    • ํŽŒ์›จ์–ด์™€ ๋ถ€ํŠธ๋กœ๋”(์ž„๋ฒ ๋””๋“œ)
    • ์Šค๋งˆํŠธ ๋””์Šคํ”Œ๋ ˆ์ด,
    • ์Šค๋งˆํŠธํฐ,
    • ๋ฐ์Šคํฌํƒ‘,
    • ์„œ๋ฒ„.
This slide should take about 10 minutes.

๋Ÿฌ์ŠคํŠธ๋Š” C++๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ๋Œ€๋ถ€๋ถ„์˜ ๊ณณ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค:

  • ๋†’์€ ์œ ์—ฐ์„ฑ.
  • ๋†’์€ ์ˆ˜์ค€์˜ ์ œ์–ด.
  • ๋งˆ์ดํฌ๋กœ์ปจํŠธ๋กค๋Ÿฌ ๊ฐ™์€ ๋งค์šฐ ์ œํ•œ๋œ ์žฅ์น˜๋กœ ์Šค์ผ€์ผ ๋‹ค์šด ๊ฐ€๋Šฅ.
  • ๋ณ„๋„์˜ ๋Ÿฐํƒ€์ž„์„ ํ•„์š”๋กœ ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜๋„ ์—†์Œ.
  • ์„ฑ๋Šฅ์„ ํƒ€ํ˜‘ํ•˜์ง€ ์•Š์œผ๋ฉด์„œ๋„ ์•ˆ์ •์„ฑ๊ณผ ์•ˆ์ „์— ์ค‘์ ์„ ๋‘ .

Rust์˜ ์ด์ 

๋Ÿฌ์ŠคํŠธ๋งŒ์˜ ๋…ํŠนํ•œ ์„ธ์ผ์ฆˆ ํฌ์ธํŠธ(์žฅ์ ):

  • ์ปดํŒŒ์ผ ์‹œ๊ฐ„ ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „ - ๋ฉ”๋ชจ๋ฆฌ ๋ฒ„๊ทธ์˜ ์ „์ฒด ํด๋ž˜์Šค๊ฐ€ ์ปดํŒŒ์ผ ์‹œ๊ฐ„์— ๋ฐฉ์ง€๋ฉ๋‹ˆ๋‹ค.

    • ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š๋Š” ๋ณ€์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
    • ๋ฉ”๋ชจ๋ฆฌ ์ด์ค‘ ํ•ด์ œ๊ฐ€ ์›์ฒœ์ ์œผ๋กœ ๋ถˆ๊ฐ€๋Šฅ ํ•ฉ๋‹ˆ๋‹ค.
    • ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ ํ›„ ์‚ฌ์šฉ์ด ์›์ฒœ์ ์œผ๋กœ ๋ถˆ๊ฐ€๋Šฅ ํ•ฉ๋‹ˆ๋‹ค.
    • NULLํฌ์ธํ„ฐ๋Š” ์—†์Šต๋‹ˆ๋‹ค.
    • ๋ฎคํ…์Šค๋ฅผ ์ž ๊ถˆ ๋†“๊ณ  ์—ฌ๋Š” ๊ฒƒ์„ ์žŠ๋Š” ์‹ค์ˆ˜๋ฅผ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
    • ์Šค๋ ˆ๋“œ๊ฐ„ ๋ฐ์ดํ„ฐ ๋ ˆ์ด์Šค๋ฅผ ๋ง‰์•„์ค๋‹ˆ๋‹ค.
    • ๋ฐ˜๋ณต์ž๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋ฌดํšจํ™” ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  • ์ •์˜๋˜์ง€ ์•Š์€ ๋Ÿฐํƒ€์ž„ ๋™์ž‘ ์—†์Œ - Rust ๋ฌธ์ด ์‹คํ–‰ํ•˜๋Š” ์ž‘์—…์€ ์ง€์ •๋˜์ง€ ์•Š์€ ์ƒํƒœ๋กœ ๋‘์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

    • ๋ฐฐ์—ด ์ ‘๊ทผ์‹œ ๊ฒฝ๊ณ„ ์ฒดํฌ.
    • ์ •์ˆ˜ํ˜• ํƒ€์ž…์˜ ๋ณ€์ˆ˜์—์„œ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ ๋ฐœ์ƒ์‹œ ๋™์ž‘์ด ์ž˜ ์ •์˜๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ตœ์‹  ์–ธ์–ด ๊ธฐ๋Šฅ - ์ƒ์œ„ ์ˆ˜์ค€ ์–ธ์–ด๋งŒํผ ํ‘œํ˜„๋ ฅ์ด ๋›ฐ์–ด๋‚˜๊ณ  ์ธ์ฒด๊ณตํ•™์ ์ž…๋‹ˆ๋‹ค.

    • ์—ด๊ฑฐํ˜•๊ณผ ํŒจํ„ด ๋งค์นญ.
    • ์ œ๋„ค๋ฆญ.
    • FFI ๋Ÿฐํƒ€์ž„ ์˜ค๋ฒ„ํ—ค๋“œ ์—†์Œ.
    • ๋น„์šฉ์ด ๋“ค์ง€ ์•Š๋Š” ์ถ”์ƒํ™”.
    • ์นœ์ ˆํ•œ ์ปดํŒŒ์ผ๋Ÿฌ ์˜ค๋ฅ˜๋ฉ”์‹œ์ง€.
    • ๋‚ด์žฅ ์ข…์†์„ฑ ๊ด€๋ฆฌ์ž.
    • ๋‚ด์žฅ ํ…Œ์ŠคํŠธ ์ง€์›.
    • LSP (Language Server Protocol, ์–ธ์–ด ์„œ๋ฒ„ ํ”„๋กœํ† ์ฝœ) ์ง€์›์ด ์ž˜๋˜์–ด ์žˆ์Œ.
This slide should take about 3 minutes.

์—ฌ๊ธฐ์—์„œ ๋งŽ์€ ์‹œ๊ฐ„์„ ๋ณด๋‚ด์ง€ ๋งˆ์„ธ์š”. ์ด ๋ชจ๋“  ์‚ฌํ•ญ์€ ๋‚˜์ค‘์— ์ž์„ธํžˆ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

์ˆ˜๊ฐ•์ƒ๋“ค์—๊ฒŒ ์–ด๋–ค ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š”์ง€ ๋ฌผ์–ด๋ณด์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ์–ด๋–ค ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š๋ƒ์— ๋”ฐ๋ผ ๋Ÿฌ์ŠคํŠธ์—์„œ ์–ด๋–ค ์ ์„ ๊ฐ•์กฐํ•ด์•ผ ํ• ์ง€๋ฅผ ๊ณ ๋ฏผํ•ด ๋ณด์„ธ์š”:

  • 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โ€™์„ ์‚ฌ์šฉํ•ด ์ƒ์„ฑ๋œ ์–ด์…ˆ๋ธ”๋ฆฌ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

This slide should take about 2 minutes.

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:

SlideDuration
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 should take about 5 minutes.

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}");
}
This slide should take about 5 minutes.
  • x = 20์˜ ์ฃผ์„ ์ฒ˜๋ฆฌ๋ฅผ ์‚ญ์ œํ•˜์—ฌ ๋ณ€์ˆ˜๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ถˆ๋ณ€์ž„์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. mut ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ณ€๊ฒฝ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • ์—ฌ๊ธฐ์„œ โ€™i32โ€™๋Š” ๋ณ€์ˆ˜์˜ ํƒ€์ž…์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์ปดํŒŒ์ผ ์‹œ๊ฐ„์— ์•Œ๋ ค์ ธ์•ผ ํ•˜์ง€๋งŒ, ํƒ€์ž… ์ถ”๋ก (๋‚˜์ค‘์— ์„ค๋ช…)์„ ์‚ฌ์šฉํ•˜๋ฉด ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ์ด๋ฅผ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.

๊ฐ’

๋‹ค์Œ์€ ๋ช‡ ๊ฐ€์ง€ ๊ธฐ๋ณธ ๋‚ด์žฅ ํƒ€์ž…๊ณผ ๊ฐ ํƒ€์ž…์˜ ๋ฆฌํ„ฐ๋Ÿด ๊ฐ’ ๋ฌธ๋ฒ•์ž…๋‹ˆ๋‹ค.

ํƒ€์ž…๋ฆฌํ„ฐ๋Ÿด ๊ฐ’
๋ถ€ํ˜ธ์žˆ๋Š” ์ •์ˆ˜i8, i16, i32, i64, i128, isize-10, 0, 1_000, 123_i64
๋ถ€ํ˜ธ์—†๋Š” ์ •์ˆ˜u8, u16, u32, u64, u128, usize0, 123, 10_u16
๋ถ€๋™์†Œ์ˆ˜f32, f643.14, -10.0e20, 2_f32
์œ ๋‹ˆ์ฝ”๋“œ ๋ฌธ์žchar'a', 'ฮฑ', 'โˆž'
๋ถˆ๋ฆฌ์–ธbooltrue, false

๊ฐ ํƒ€์ž…์˜ ํฌ๊ธฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  • iN, uN, fN์€ ๋ชจ๋‘ _N_๋น„ํŠธ ์ž…๋‹ˆ๋‹ค.
  • isize ์™€ usize ๋Š” ํฌ์ธํ„ฐ์™€ ๊ฐ™์€ ํฌ๊ธฐ์ž…๋‹ˆ๋‹ค,
  • char 32 ๋น„ํŠธ ์ž…๋‹ˆ๋‹ค,
  • bool์€ 8 ๋น„ํŠธ ์ž…๋‹ˆ๋‹ค.
This slide should take about 5 minutes.

์œ„์— ํ‘œ์‹œ๋˜์ง€ ์•Š์€ ๋ช‡ ๊ฐ€์ง€ ๋ฌธ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  • All underscores in numbers can be left out, they are for legibility only. So 1_000 can be written as 1000 (or 10_00), and 123_i64 can be written as 123i64.

์—ฐ์‚ฐ

fn interproduct(a: i32, b: i32, c: i32) -> i32 {
    return a * b + b * c + c * a;
}

fn main() {
    println!("๊ฒฐ๊ณผ: {}", interproduct(120, 100, 248));
}
This slide should take about 3 minutes.

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]);
}
This slide should take about 5 minutes.

์ด ์Šฌ๋ผ์ด๋“œ๋Š” ๋ฌธ์ž์—ด์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์— ์žˆ๋Š” ๋ชจ๋“  ๋‚ด์šฉ์€ ๋‚˜์ค‘์— ์ž์„ธํžˆ ๋‹ค๋ฃจ๊ฒ ์ง€๋งŒ ํ›„์† ์Šฌ๋ผ์ด๋“œ์™€ ์—ฐ์Šต๋ฌธ์ œ์—์„œ ๋ฌธ์ž์—ด์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ๋Š” ์ด๊ฒƒ์œผ๋กœ ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ฌธ์ž์—ด ๋‚ด์— ์ž˜๋ชป๋œ 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);
}
This slide should take about 3 minutes.

์ด ์Šฌ๋ผ์ด๋“œ๋Š”, ๋Ÿฌ์ŠคํŠธ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ณ€์ˆ˜๊ฐ€ ์–ด๋–ป๊ฒŒ ์„ ์–ธ๋˜์–ด ์žˆ๊ณ , ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ๋˜๋Š”์ง€๋ฅผ ์ œ์•ฝ ์กฐ๊ฑด์œผ๋กœ ์‚ผ์•„์„œ ๋ณ€์ˆ˜์˜ ํƒ€์ž…์„ ์ถ”๋ก ํ•˜๋Š” ๋ชจ์Šต์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€, ์ด๋ ‡๊ฒŒ ๋ช…์‹œ์ ์ธ ํƒ€์ž…์„ ์ƒ๋žตํ•˜๊ณ  ์„ ์–ธ๋˜์—ˆ๋‹ค๊ณ  ํ•ด์„œ โ€œ์–ด๋–ค ํƒ€์ž…โ€œ์ด๋ผ๋„ ๋‹ค ๋‹ด์„ ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์ด ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋ผ๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๋ช…์‹œ์ ์ธ ํƒ€์ž… ์„ ์–ธ์ด ์žˆ๋˜ ์—†๋˜, ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ƒ์„ฑํ•œ ๋จธ์‹ ์ฝ”๋“œ๋Š” ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ๋‹จ์ง€ ํƒ€์ž… ์„ ์–ธ์„ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์„œ ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ๋” ๊ฐ„๊ฒฐํ•œ ์ฝ”๋“œ๋ฅผ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค„ ๋ฟ์ž…๋‹ˆ๋‹ค.

์•„๋ฌด๊ฒƒ๋„ ์ •์ˆ˜ ๋ฆฌํ„ฐ๋Ÿด์˜ ํƒ€์ž…์„ ์ œํ•œํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ 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:

SlideDuration
if ํ‘œํ˜„์‹4 minutes
๋ฐฐ์—ด๊ณผ for ๋ฐ˜๋ณต๋ฌธ5 minutes
break์™€ continue4 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);
}
This slide should take about 4 minutes.

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 ํ•จ์ˆ˜๋Š” ๋งˆ์ง€๋ง‰ ํ‘œํ˜„์‹์ด ;๋กœ ๋๋‚˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ’๊ณผ ํƒ€์ž…์ด ()์ž…๋‹ˆ๋‹ค.

This slide and its sub-slides should take about 5 minutes.
  • ๋ธ”๋ก ๋งˆ์ง€๋ง‰ ์ค„์„ ์ˆ˜์ •ํ•˜๋ฉด์„œ ๋ธ”๋ก์˜ ๊ฐ’์ด ์–ด๋–ป๊ฒŒ ๋ฐ”๋€Œ๋Š”์ง€ ๋ณด์—ฌ์ฃผ์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ์„ธ๋ฏธ์ฝœ๋ก ์„ ๋„ฃ๊ฑฐ๋‚˜ ๋บ€๋‹ค๋“ ์ง€, ์•„๋‹ˆ๋ฉด 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));
}
This slide should take about 3 minutes.
  • ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ๋•Œ์—๋Š” ์ด๋ฆ„์„ ๋จผ์ € ์“ฐ๊ณ , ํƒ€์ž…์„ ๋‚˜์ค‘์— ์”๋‹ˆ๋‹ค. ์ด๋ฆ„๊ณผ ํƒ€์ž…์€ : ๋กœ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ผ๋ถ€ ์–ธ์–ด(์˜ˆ๋ฅผ ๋“ค์–ด C)์™€ ๋ฐ˜๋Œ€์ž„์— ์œ ์˜ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๋ฆฌํ„ด ํƒ€์ž…๋„ ํ•จ์ˆ˜์˜ ์‹œ์ž‘์ด ์•„๋‹Œ ๊ฐ€์žฅ ๋’ท๋ถ€๋ถ„์— ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.
  • The last expression in a function body (or any block) becomes the return value. Simply omit the ; at the end of the expression. The return keyword can be used for early return, but the โ€œbare valueโ€ form is idiomatic at the end of a function (refactor gcd to use a return).
  • ๋ฐ˜ํ™˜๊ฐ’์ด ์—†๋Š” ํ•จ์ˆ˜์˜ ๊ฒฝ์šฐ, ์œ ๋‹› ํƒ€์ž… ()์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. -> ()๊ฐ€ ์ƒ๋žต๋œ ๊ฒฝ์šฐ ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ์ด๋ฅผ ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • 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 in std::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));
}
This slide should take about 2 minutes.

์ด ์„น์…˜์—์„œ๋Š” ์ด๋Ÿฌํ•œ ์ผ๋ฐ˜์ ์ธ ํŽธ์˜ ๊ธฐ๋Šฅ์ด ์žˆ์œผ๋ฉฐ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ธฐ์–ตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋งคํฌ๋กœ๋กœ ์ •์˜๋˜๋Š” ์ด์œ ์™€ ํ™•์žฅ ๋Œ€์ƒ์€ ํŠน๋ณ„ํžˆ ์ค‘์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด ๊ณผ์ •์—์„œ๋Š” ๋งคํฌ๋กœ ์ •์˜๋ฅผ ๋‹ค๋ฃจ์ง€ ์•Š์ง€๋งŒ ์ดํ›„ ์„น์…˜์—์„œ๋Š” ํŒŒ์ƒ ๋งคํฌ๋กœ์˜ ์‚ฌ์šฉ์— ๊ด€ํ•ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

์—ฐ์Šต๋ฌธ์ œ: ์ฝœ๋ผ์ธ  ์ˆ˜์—ด

์ฝœ๋ผ์ธ  ์ˆ˜์—ด์€ ์ž„์˜์˜ 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:

SegmentDuration
ํŠœํ”Œ ๋ฐ ๋ฐฐ์—ด35 minutes
์ฐธ์กฐ35 minutes
์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž…50 minutes

ํŠœํ”Œ ๋ฐ ๋ฐฐ์—ด

This segment should take about 35 minutes. It contains:

SlideDuration
๋ฐฐ์—ด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:?}");
}
This slide should take about 5 minutes.
  • A value of the array type [T; N] holds N (a compile-time constant) elements of the same type T. 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);
}
This slide should take about 5 minutes.
  • ๋ฐฐ์—ด๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํŠœํ”Œ์€ ๊ณ ์ • ๊ธธ์ด๋ฅผ ๊ฐ–์Šต๋‹ˆ๋‹ค.

  • ํŠœํ”Œ์€ ์„œ๋กœ ๋‹ค๋ฅธ ํƒ€์ž…์˜ ๊ฐ’๋“ค์„ ํ•˜๋‚˜์˜ ๋ณตํ•ฉ ํƒ€์ž…์œผ๋กœ ๋ฌถ์Šต๋‹ˆ๋‹ค.

  • ํŠœํ”Œ์— ์†ํ•œ ๊ฐ’์€ t.0, t.1๊ณผ ๊ฐ™์ด ์ธ๋ฑ์Šค๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • The empty tuple () is referred to as the โ€œunit typeโ€ and signifies absence of a return value, akin to void 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);
        }
    }
}
This slide should take about 3 minutes.

์ด ๊ธฐ๋Šฅ์€ 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}");
}
This slide should take about 5 minutes.
  • 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):

2584567โŽค8โŽฅ9โŽฆtranspose==โŽ›โŽก1โŽœโŽข4โŽโŽฃ73โŽคโŽž6โŽฅโŽŸ9โŽฆโŽ โŽก1โŽข2โŽฃ3

๋‘ ํ•จ์ˆ˜ ๋ชจ๋‘ ํ–‰๋ ฌ์˜ ํฌ๊ธฐ๋Š” 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:

SlideDuration
๊ณต์œ  ์ฐธ์กฐ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;
}
This slide should take about 10 minutes.
  • ์ฐธ์กฐ๋Š” ์ฐธ์กฐ๋˜๋Š” ๊ฐ’์„ โ€™๋นŒ๋ฆฐ๋‹คโ€™๊ณ  ํ•˜๋ฉฐ, ์ด๋Š” ํฌ์ธํ„ฐ์— ์ต์ˆ™ํ•˜์ง€ ์•Š์€ ํ•™์ƒ๋“ค์—๊ฒŒ ์ข‹์€ ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ์—์„œ๋Š” ์ฐธ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ’์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์—ฌ์ „ํžˆ ์›๋ž˜ ๋ณ€์ˆ˜๊ฐ€ ๊ฐ’์„ โ€™์†Œ์œ โ€™ํ•ฉ๋‹ˆ๋‹ค. ์†Œ์œ ๊ถŒ์— ๊ด€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ 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:?}");
}
This slide should take about 10 minutes.

ํ‚ค ํฌ์ธํŠธ:

  • โ€™๋ฐฐํƒ€์ โ€™์ด๋ž€ ๊ฐ’์— ์•ก์„ธ์Šคํ•˜๋Š” ๋ฐ ์ด ์ฐธ์กฐ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋™์‹œ์— ๋‹ค๋ฅธ ์ฐธ์กฐ(๊ณต์œ  ๋˜๋Š” ๋ฐฐํƒ€์ )๊ฐ€ ์กด์žฌํ•  ์ˆ˜ ์—†์œผ๋ฉฐ, ๋ฐฐํƒ€์  ์ฐธ์กฐ๊ฐ€ ์กด์žฌํ•˜๋Š” ๋™์•ˆ์—๋Š” ์ฐธ์กฐ๋œ ๊ฐ’์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. x_coord๊ฐ€ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋Š” ๋™์•ˆ &point.0์„ ๋งŒ๋“ค๊ฑฐ๋‚˜ point.0์„ ๋ณ€๊ฒฝํ•ด ๋ณด์„ธ์š”.

  • Be sure to note the difference between let mut x_coord: &i32 and let 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:

SlideDuration
๊ตฌ์กฐ์ฒด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);
}
This slide should take about 10 minutes.

ํ‚ค ํฌ์ธํŠธ:

  • ๊ตฌ์กฐ์ฒด๋Š” 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.
    • ๋‹ค์Œ ์Šฌ๋ผ์ด๋“œ์—์„œ๋Š” ํ•„๋“œ ์ด๋ฆ„์ด ๋œ ์ค‘์š”ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํŠœํ”Œ ๊ตฌ์กฐ์ฒด๋ฅผ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.
  • 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);
}
This slide should take about 10 minutes.
  • ๋‰ดํƒ€์ž…์€ ๊ธฐ๋ณธ ํƒ€์ž…์— ๋ถ€๊ฐ€์ ์ธ ์˜๋ฏธ๋ฅผ ๋”ํ•˜๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:
    • ์ˆซ์ž๊ฐ’์— ๋‹จ์œ„๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Œ: ์œ„์—์„œ Newtons์ด ๊ทธ ์˜ˆ์ž…๋‹ˆ๋‹ค.
    • The value passed some validation when it was created, so you no longer have to validate it again at every use: PhoneNumber(String) or OddNumber(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);
}
This slide should take about 5 minutes.

ํ‚ค ํฌ์ธํŠธ:

  • 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 a PlayerMove 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.

This slide should take about 5 minutes.
  • ๋Ÿฌ์ŠคํŠธ์˜ 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>>>>;
This slide should take about 2 minutes.

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:

SegmentDuration
๊ฐœ์š”3 minutes
ํŒจํ„ด ๋งค์นญ1 hour
๋ฉ”์†Œ๋“œ์™€ ํŠธ๋ ˆ์ดํŠธ50 minutes
์ œ๋„ค๋ฆญ40 minutes

ํŒจํ„ด ๋งค์นญ

This segment should take about 1 hour. It contains:

SlideDuration
Matching Values10 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)๋Š” ์ผ์น˜ ๋ถ€๋ฌธ ๋‚ด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ”์ธ๋”ฉ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

์ผ์น˜ ๊ฐ€๋“œ๋Š” ์กฐ๊ฑด์ด ์ฐธ์ธ ๊ฒฝ์šฐ์—๋งŒ ๋ถ€๋ถ„์ด ์ผ์น˜ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

This slide should take about 10 minutes.

ํ‚ค ํฌ์ธํŠธ:

  • ํŒจํ„ด์—์„œ ์‚ฌ์šฉ๋˜๋Š” ํŠน์ˆ˜ ๋ฌธ์ž๋“ค์„ ์•Œ๋ ค์ฃผ์„ธ์š”

    • |: 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๋Š” ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋ฌธ์ž์—ด์— ๋ฐ”์ธ๋”ฉ๋ฉ๋‹ˆ๋‹ค.

This slide should take about 8 minutes.

๊ตฌ์กฐ์ฒด

  • 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 the result variable and match it in a loop. That wonโ€™t compile because msg is consumed when matched. To fix it, match &result instead of result. That will make msg a reference so it wonโ€™t be consumed. This โ€œmatch ergonomicsโ€ appeared in Rust 2018. If you want to support older Rust, replace msg with ref msg in the pattern.

ํ๋ฆ„ ์ œ์–ด

Rust์—๋Š” ๋‹ค๋ฅธ ์–ธ์–ด์™€๋Š” ๋‹ค๋ฅธ ๋ช‡ ๊ฐ€์ง€ ์ œ์–ด ํ๋ฆ„ ๊ตฌ์กฐ๊ฐ€ ์žˆ์œผ๋ฉฐ ํŒจํ„ด ์ผ์น˜์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • if let ํ‘œํ˜„์‹
  • while let expressions
  • match ํ‘œํ˜„์‹

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.

This slide should take about 10 minutes.

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 for name.pop(). The while 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:

SlideDuration
๋ฉ”์„œ๋“œ10 minutes
ํŠธ๋ ˆ์ž‡(Trait)15 minutes
ํŠธ๋ ˆ์ž‡ ์ƒ์†ํ•˜๊ธฐ3 minutes
์—ฐ์Šต๋ฌธ์ œ: ์ผ๋ฐ˜ min20 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๋ผ๊ณ  ์ด๋ฆ„๋ถ™์ž…๋‹ˆ๋‹ค.
This slide should take about 8 minutes.

ํ‚ค ํฌ์ธํŠธ:

  • ๋ฉ”์„œ๋“œ๋ฅผ ํ•จ์ˆ˜์™€ ๋น„๊ตํ•˜์—ฌ ์†Œ๊ฐœํ•˜๋Š” ๊ฒƒ๋„ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋ฉ”์„œ๋“œ๋Š” ๊ตฌ์กฐ์ฒด๋‚˜ ์—ด๊ฑฐํ˜•๊ณผ ๊ฐ™์€ ํƒ€์ž…์˜ ์ธ์Šคํ„ด์Šค์—์„œ ํ˜ธ์ถœ ๋˜๋ฉฐ, ์ฒซ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜(ํŒŒ๋ผ๋ฉ”ํ„ฐ)๋Š” ์ธ์Šคํ„ด์Šค๋ฅผ self๋กœ ํ‘œ๊ธฐํ•ฉ๋‹ˆ๋‹ค.
    • ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด receiver ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ  ์ฝ”๋“œ๋ฅผ ์ข€๋” ์ฒด๊ณ„์ ์œผ๋กœ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฉ”์„œ๋“œ๋“ค์ด ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์œ„์น˜์— ๋ชจ์—ฌ ์žˆ์œผ๋‹ˆ ์ฐพ๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
  • ๋ฉ”์„œ๋“œ receiver์ธ self ํ‚ค์›Œ๋“œ ์‚ฌ์šฉ์„ ์–ธ๊ธ‰ํ•ด ์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.
    • ์˜ˆ์ œ์˜ ๊ฒฝ์šฐ self: &Self์˜ ์ค„์ธ ๋ฒ„์ „์ž„์„ ์•Œ๋ ค์ฃผ๊ณ , ๊ตฌ์กฐ์ฒด์˜ ์ด๋ฆ„์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋˜๋Š”์ง€ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๋„ ์ข‹์Šต๋‹ˆ๋‹ค.
    • impl ๋ธ”๋ก ๋‚ด๋ถ€์—์„œ๋Š” Self๊ฐ€ ํ•ด๋‹น ํƒ€์ž…์˜ ์ด๋ฆ„ ๋Œ€์šฉ์œผ๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Œ์„ ์•Œ๋ ค์ฃผ์„ธ์š”.
    • ๊ตฌ์กฐ์ฒด์˜ ํ•„๋“œ๋ฅผ ์ ‘๊ทผํ•  ๋•Œ ์  ํ‘œ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๋“ฏ์ด self์— ์  ํ‘œ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐœ๋ณ„ ํ•„๋“œ๋“ค์„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • This might be a good time to demonstrate how the &self differs from self by trying to run finish 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);
}
This slide and its sub-slides should take about 15 minutes.
  • ํŠธ๋ ˆ์ž‡์€ ํ•ด๋‹น ํŠธ๋ ˆ์ž‡์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ํƒ€์ž…์ด ๊ฐ€์ ธ์•ผ ํ•˜๋Š” ์—ฌ๋Ÿฌ ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

  • 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 for Type, you use an impl Trait for Type { .. } block.

  • Unlike Go interfaces, just having matching methods is not enough: a Cat type with a talk() method would not automatically satisfy Pet unless it is in an impl 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 on talk.

ํŠธ๋ ˆ์ž‡(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);
}
This slide should take about 3 minutes.

์ƒ์†์€ ๋งคํฌ๋กœ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„๋˜๋ฉฐ ๋งŽ์€ ํฌ๋ ˆ์ดํŠธ๊ฐ€ ์œ ์šฉํ•œ ์ƒ์† ๋งคํฌ๋กœ๋ฅผ ์ œ๊ณตํ•˜์—ฌ ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด 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:

SlideDuration
์™ธ๋ถ€(๋‹ค๋ฅธ์–ธ์–ด) ํ•จ์ˆ˜๋“ค5 minutes
์ œ๋„ค๋ฆญ ๋ฐ์ดํ„ฐ ํƒ€์ž…10 minutes
์ œ๋„ค๋ฆญ ํƒ€์ž… ์ œํ•œ(ํŠธ๋ ˆ์ž‡ ๊ฒฝ๊ณ„)10 minutes
ํŠธ๋ ˆ์ž‡ ๊ตฌํ˜„ํ•˜๊ธฐ(impl Trait)5 minutes
์—ฐ์Šต๋ฌธ์ œ: ์ผ๋ฐ˜ min10 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)));
}
This slide should take about 5 minutes.
  • 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());
}
This slide should take about 10 minutes.
  • ์งˆ๋ฌธ: 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 the std 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 no From<&str> implementation for Foo.

  • 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:?}");
}
This slide should take about 8 minutes.
  • 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:?}");
}
This slide should take about 5 minutes.

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");
}
This slide and its sub-slides should take about 10 minutes.

ํ•ด๋‹ต

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:

SegmentDuration
ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ1 hour and 20 minutes
ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ1 hour and 40 minutes

ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

This segment should take about 1 hour and 20 minutes. It contains:

SlideDuration
ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ3 minutes
๋ฌธ์„œํ™”์ฃผ์„ ํ…Œ์ŠคํŠธ5 minutes
Duration10 minutes
Option, Result10 minutes
String10 minutes
Vec10 minutes
HashMap10 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 on libc, 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 or BinaryHeap.

์‚ฌ์‹ค ์ž์ฒด ์ฝ”๋“œ๋ฅผ ๋ฌธ์„œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

/// ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋ฅผ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
///
/// ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๊ฐ€ 0์ด๋ฉด ๊ฒฐ๊ณผ๋Š” false์ž…๋‹ˆ๋‹ค.
fn is_divisible_by(lhs: u32, rhs: u32) -> bool {
    if rhs == 0 {
        return false;
    }
    lhs % rhs == 0
}

์ฝ˜ํ…์ธ ๋Š” ๋งˆํฌ๋‹ค์šด์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ๊ฒŒ์‹œ๋œ ๋ชจ๋“  Rust ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํฌ๋ ˆ์ดํŠธ๋Š” rustdoc ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ docs.rs์— ์ž๋™์œผ๋กœ ๋ฌธ์„œํ™”๋ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ API์˜ ๋ชจ๋“  ๊ณต๊ฐœ ํ•ญ๋ชฉ์€ ์ด ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌธ์„œํ™”๋ฉ๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ ๋‚ด๋ถ€(์˜ˆ: ๋ชจ๋“ˆ ๋‚ด๋ถ€)์˜ ํ•ญ๋ชฉ์„ ๋ฌธ์„œํ™”ํ•˜๋ ค๋ฉด โ€™๋‚ด๋ถ€ ๋ฌธ์„œ ์ฃผ์„โ€™์ด๋ผ๊ณ  ํ•˜๋Š” //! ํ˜น์€ /*! .. */๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

//! ์ด ๋ชจ๋“ˆ์—๋Š” ์ •์ˆ˜์˜ ๋ถ„ํ•  ๊ฐ€๋Šฅ์„ฑ๊ณผ ๊ด€๋ จ๋œ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
This slide should take about 5 minutes.

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);
}
This slide should take about 10 minutes.
  • 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}");
        }
    }
}
This slide should take about 10 minutes.
  • 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์˜ ๋ชจ๋“  ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ ์ž…๋‹ˆ๋‹ค.

This slide should take about 10 minutes.
  • 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(); and let 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 implement Display, 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์—์„œ ์Šฌ๋ผ์ด์Šค ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

This slide should take about 10 minutes.
  • Vec is a type of collection, along with String and HashMap. 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:#?}");
}
This slide should take about 10 minutes.
  • 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:

SlideDuration
๋น„๊ต10 minutes
Iterators10 minutes
From๊ณผ Into10 minutes
ํ…Œ์ŠคํŠธ5 minutes
Read์™€ Write10 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์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

This slide should take about 10 minutes.

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);
}
This slide should take about 10 minutes.

๋…ผ์˜์ :

  • 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.
  • 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}");
}
This slide should take about 10 minutes.
  • ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž…์˜ ๊ฒฝ์šฐ์—๋„ 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.

This slide should take about 5 minutes.

์ด ์Šฌ๋ผ์ด๋“œ๊ฐ€ ๋๋‚œ ํ›„ ์ž ์‹œ ์‰ฌ์–ด๊ฐ€๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

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());
}
This slide should take about 5 minutes.
  • ํŠธ๋ ˆ์ž‡์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ฑฐ๋‚˜ #[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));
}
This slide should take about 20 minutes.

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:

SegmentDuration
๊ฐœ์š”3 minutes
๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ1 hour
์Šค๋งˆํŠธ ํฌ์ธํ„ฐ55 minutes

๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ

This segment should take about 1 hour. It contains:

SlideDuration
ํ”„๋กœ๊ทธ๋žจ ๋ฉ”๋ชจ๋ฆฌ ๊ฒ€ํ† 5 minutes
์ž๋™ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ10 minutes
์†Œ์œ ๊ถŒ5 minutes
Move ๋ฌธ๋ฒ•5 minutes
Clone2 minutes
๋ณตํ•ฉ ํƒ€์ž…5 minutes
Drop10 minutes
์—ฐ์Šต๋ฌธ์ œ: ๋นŒ๋“œ ํƒ€์ž…20 minutes

ํ”„๋กœ๊ทธ๋žจ ๋ฉ”๋ชจ๋ฆฌ ๊ฒ€ํ† 

ํ”„๋กœ๊ทธ๋žจ์€ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.

  • ์Šคํƒ: ๋กœ์ปฌ ๋ณ€์ˆ˜๋ฅผ ์œ„ํ•œ ์—ฐ์†์ ์ธ ๋ฉ”๋ชจ๋ฆฌ ์˜์—ญ.

    • ์—ฌ๊ธฐ ์ €์žฅ๋˜๋Š” ๊ฐ’์€ ์ปดํŒŒ์ผ ์‹œ ๊ฒฐ์ •๋˜๋Š” ๊ณ ์ • ํฌ๊ธฐ๋ฅผ ๊ฐ–์Šต๋‹ˆ๋‹ค.
    • ๋งค์šฐ ๋น ๋ฆ„: ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น/๋ฐ˜ํ™˜์ด ๋‹จ์ง€ ์Šคํƒ ํฌ์ธํ„ฐ์˜ ์ด๋™๋งŒ์œผ๋กœ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค.
    • ๊ด€๋ฆฌ๊ฐ€ ์‰ฌ์›€: ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ํ• ๋‹น๋˜๊ณ , ๋ฆฌํ„ดํ•˜๋ฉด ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.
    • ์Šคํƒ์— ์žˆ๋Š” ๊ฐ’๋“ค์€ ๋งค์šฐ ๋†’์€ ๋ฉ”๋ชจ๋ฆฌ ์ธ์ ‘์„ฑ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
  • ํž™: ํ•จ์ˆ˜ ํ˜ธ์ถœ/๋ฆฌํ„ด๊ณผ ์ƒ๊ด€ ์—†์ด ์œ ์ง€๋˜๋Š” ๊ฐ’์ด ์ €์žฅ๋˜๋Š” ๊ณณ.

    • ์—ฌ๊ธฐ ์ €์žฅ๋˜๋Š” ๊ฐ’์€ ํ”„๋กœ๊ทธ๋žจ ์ˆ˜ํ–‰์‹œ ๊ทธ ํฌ๊ธฐ๊ฐ€ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค.
    • ์Šคํƒ ๋ณด๋‹ค๋Š” ๋Š๋ฆผ: ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น/๋ฐ˜ํ™˜์‹œ ํ•ด์•ผ ํ•  ์ผ์ด ์ข€ ๋” ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋ฉ”๋ชจ๋ฆฌ ์ธ์ ‘์„ฑ์„ ๋ณด์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์˜ˆ์ œ

String์„ ํ•˜๋‚˜ ๋งŒ๋“ค๊ฒŒ ๋˜๋ฉด, ์Šคํƒ์—๋Š” ๊ณ ์ •๋œ ํฌ๊ธฐ์˜ ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ํž™์—๋Š” ๊ฐ€๋ณ€ ํฌ๊ธฐ์˜ ๋ฐ์ดํ„ฐ, ์ฆ‰, ์‹ค์ œ ๋ฌธ์ž์—ด, ์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค:

fn main() {
    let s1 = String::from("์•ˆ๋…•ํ•˜์„ธ์š”");
}
StackHeaps1capacity5ptrHellolen5
This slide should take about 5 minutes.
  • ๋ฌธ์ž์—ด(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๋กœ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค.

๋Ÿฌ์ŠคํŠธ๋Š” ์ด ๋‘˜์„ ํ˜ผํ•ฉํ•œ ์ƒˆ๋กœ์šด ํ˜•ํƒœ์˜ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๊ธฐ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

์ปดํŒŒ์ผ ์‹œ ์˜ฌ๋ฐ”๋ฅธ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ๋ฅผ ๊ฐ•์ œํ•จ์œผ๋กœ์จ ์™„์ „ํ•œ ํ†ต์ œ์™€ ์•ˆ์ „์„ฑ ๋ชจ๋‘ ์ œ๊ณต.

์ด๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๋Ÿฌ์ŠคํŠธ์˜ ์ปจ์…‰์€ ๋ช…์‹œ์ ์ธ ์†Œ์œ ๊ถŒ์ž…๋‹ˆ๋‹ค.

This slide should take about 10 minutes.

์ด ์Šฌ๋ผ์ด๋“œ๋Š” ๋‹ค๋ฅธ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ•™์ƒ๋“ค์ด ๋งฅ๋ฝ์— ๋”ฐ๋ผ 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.

This slide should take about 5 minutes.

๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ๊ตฌํ˜„์— ์ต์ˆ™ํ•œ ํ•™์ƒ์€ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ๊ฐ€ ์—ฐ๊ฒฐ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•ด โ€˜๋ฃจํŠธโ€™ ์„ธํŠธ๋กœ ์‹œ์ž‘ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. 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๋กœ ์ด๋™ ์ „ ๋ฉ”๋ชจ๋ฆฌ:

StackHeaps1ptrHello!len4capacity4

s2๋กœ ์ด๋™ ํ›„ ๋ฉ”๋ชจ๋ฆฌ:

StackHeaps1ptrHello!len4capacity4s2ptrlen4capacity4(inaccessible)

๊ฐ’์„ ํ•จ์ˆ˜์— ์ „๋‹ฌํ• ๋•Œ, ๊ทธ ๊ฐ’์€ ๋งค๊ฐœ๋ณ€์ˆ˜์— ํ• ๋‹น๋ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ์†Œ์œ ๊ถŒ์˜ ์ด๋™์ด ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค:

fn say_hello(name: String) {
    println!("์•ˆ๋…•ํ•˜์„ธ์š” {name}")
}

fn main() {
    let name = String::from("Alice");
    say_hello(name);
    // say_hello(name);
}
This slide should take about 5 minutes.
  • ์ด๋Š” 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์˜ ์Šค์ฝ”ํ”„๊ฐ€ ์ข…๋ฃŒ๋˜๋ฉด ๊ฐ๊ฐ์˜ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ•ด์ œ๋ฉ๋‹ˆ๋‹ค.

๋ณต์‚ฌ ์ „:

StackHeaps1ptrCpplen3capacity3

๋ณต์‚ฌ ํ›„:

StackHeaps1ptrCpplen3capacity3s2ptrCpplen3capacity3

ํ‚ค ํฌ์ธํŠธ:

  • 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();
    }
}
This slide should take about 2 minutes.

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()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์‚ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
This slide should take about 5 minutes.

๋ณต์‚ฌ(copy)์™€ ๋ณต์ œ(clone)๋Š” ๊ฐ™์ง€ ์•Š์Šต๋‹ˆ๋‹ค:

  • ๋ณต์‚ฌ๋Š” ๋ฉ”๋ชจ๋ฆฌ์˜ ๋‚ด์šฉ์„ ๊ทธ๋Œ€๋กœ ํ•œ ๋ฒŒ ๋” ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜๋ฉฐ, ์•„๋ฌด ๊ฐ์ฒด์—์„œ๋‚˜ ๋‹ค ์ง€์›ํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.
  • ๋ณต์‚ฌ๋Š” ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. (C++์—์„œ ๋ณต์‚ฌ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ๋ณต์‚ฌ ๋™์ž‘์„ ์ž„์˜๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๊ณผ ๋น„๊ต๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.)
  • ๋ณต์ œ๋Š” ๋ณด๋‹ค ์ผ๋ฐ˜์ ์ธ ์ž‘์—…์ด๋ฉฐ, CloneํŠธ๋ ˆ์ž‡์„ ๊ตฌํ˜„ํ•˜์—ฌ ๋ณต์ œ์‹œ ๋™์ž‘์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Drop ํŠธ๋ ˆ์ž‡์„ ๊ตฌํ˜„ํ•œ ํƒ€์ž…์€ ๋ณต์‚ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์œ„์˜ ์˜ˆ์‹œ์—์„œ ๋‹ค์Œ์„ ์‹œ๋„ํ•ด ๋ณด์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค:

  • Point๊ตฌ์กฐ์ฒด์— Stringํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์ปดํŒŒ์ผ ๋˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด String์€ CopyํŠธ๋ ˆ์ž‡์„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • Remove Copy from the derive attribute. The compiler error is now in the println! for p1.
  • 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์—์„œ ๋‚˜๊ฐ€๊ธฐ");
}
This slide should take about 8 minutes.
  • 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:

SlideDuration
Box10 minutes
Rc5 minutes
ํŠธ๋ ˆ์ž‡ ๊ฐ์ฒด10 minutes
์—ฐ์Šต๋ฌธ์ œ: ๋ฐ”์ด๋„ˆ๋ฆฌ ํŠธ๋ฆฌ30 minutes

Box<T>

Box๋Š” ํž™ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์†Œ์œ  ํฌ์ธํ„ฐ์ž…๋‹ˆ๋‹ค:

fn main() {
    let five = Box::new(5);
    println!("five: {}", *five);
}
5StackHeapfive

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:?}");
}
StackHeaplistElement1Element2Nil
This slide should take about 8 minutes.
  • Box is like std::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 a List directly into the List, the compiler would not be able to compute a fixed size for the struct in memory (the List 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, a Box 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์ด ์•„๋‹™๋‹ˆ๋‹ค. ์ด๋Š” ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ๋ ˆ์ด์•„์›ƒ์„ ์ตœ์ ํ™” ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค:

StackHeaplistElement1Element2

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 ํฌ์ธํ„ฐ๋กœ _๋‹ค์šด๊ทธ๋ ˆ์ด๋“œ_ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
This slide should take about 5 minutes.
  • 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 with RefCell).

ํŠธ๋ ˆ์ž‡ ๊ฐ์ฒด

ํŠธ๋ ˆ์ž‡ ๊ฐ์ฒด๋Š” ํƒ€์ž…์ด ๋‹ค๋ฅธ ๊ฐ’(์˜ˆ๋ฅผ ๋“ค์–ด ์ปฌ๋ ‰์…˜์— ์†ํ•œ ๊ฐ ๊ฐ’)๋“ค์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

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๋ฅผ ํ• ๋‹นํ•œ ์ดํ›„์˜ ๋ฉ”๋ชจ๋ฆฌ ๋ ˆ์ด์•„์›ƒ:

<Dog as Pet>::talk<Cat as Pet>::talkStackHeapFidoptrlives9len2capacity2data:name,4,4age5vtablevtablepets: Vec<dyn Pet>data: CatDogProgram text
This slide should take about 10 minutes.
  • 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 ํ•„๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์•„๋ž˜ ์ฝ”๋“œ์˜ ๊ฒฐ๊ณผ์™€ ๋น„๊ตํ•ด๋ณด์„ธ์š”:
    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:

SegmentDuration
๋นŒ๋ฆผ50 minutes
์ˆ˜๋ช…1 hour and 10 minutes

๋นŒ๋ฆผ

This segment should take about 50 minutes. It contains:

SlideDuration
๋นŒ๋ฆผ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ํ•จ์ˆ˜)์— ์žˆ์Šต๋‹ˆ๋‹ค.
This slide should take about 10 minutes.

์ด ์Šฌ๋ผ์ด๋“œ๋Š” 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}");
}
This slide should take about 10 minutes.
  • ์ถฉ๋Œํ•˜๋Š” ์ฐธ์กฐ๊ฐ€ ๊ฐ™์€ ์ง€์ ์— _์กด์žฌ_ํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ์ฐธ์กฐ๊ฐ€ ์—ญ์ฐธ์กฐ๋˜๋Š” ์œ„์น˜๋Š” ์ค‘์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์œ„ ์ฝ”๋“œ ์ปดํŒŒ์ผ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด 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.

This slide should take about 10 minutes.

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 a set 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 to subtree.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:

SlideDuration
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]์„ ์ˆ˜์ •ํ•˜๋ฉด ๋ฌด์Šจ ์ผ์ด ์žˆ์–ด๋‚ ๊นŒ์š”?
This slide should take about 10 minutes.
  • ์Šฌ๋ผ์ด์Šค๋Š” ์šฐ์„  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 through a at this point in the execution, but you can read the data from both a and s safely. It works before you created the slice, and again after the println, 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์€ ๋ฌธ์ž์—ด์„ ๋‹ด์„ ์ˆ˜ ์žˆ๋Š” ๋ฒ„ํผ์ž…๋‹ˆ๋‹ค.
This slide should take about 10 minutes.
  • &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 the push() and push_str() methods.

  • The format!() macro is a convenient way to generate an owned string from dynamic values. It accepts the same format specification as println!().

  • You can borrow &str slices from String via & and optionally range selection. If you select a byte range that is not aligned to character boundaries, the expression will panic. The chars iterator iterates over characters and is preferred over trying to get character boundaries right.

  • For C++ programmers: think of &str as std::string_view from C++, but the one that always points to a valid string in memory. Rust String is a rough equivalent of std::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:?}");
}
This slide should take about 10 minutes.

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)
        )
    );
}
This slide should take about 5 minutes.

์ด ์˜ˆ์—์„œ 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:?}");
}
This slide should take about 5 minutes.
  • ์œ„์˜ ์˜ˆ์ œ์—์„œ 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:

SegmentDuration
๊ฐœ์š”3 minutes
Iterators45 minutes
๋ชจ๋“ˆ40 minutes
ํ…Œ์ŠคํŠธ45 minutes

Iterators

This segment should take about 45 minutes. It contains:

SlideDuration
Iterator5 minutes
IntoIterator5 minutes
FromIterator5 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}");
    }
}
This slide should take about 5 minutes.
  • 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}");
    }
}
This slide should take about 5 minutes.

Click through to the docs for IntoIterator. Every implementation of IntoIterator must declare two types:

  • Item: the type to iterate over, such as i8,
  • 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:?}");
}
This slide should take about 5 minutes.

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 the Vec 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:

SlideDuration
๋ชจ๋“ˆ3 minutes
ํŒŒ์ผ์‹œ์Šคํ…œ ๊ณ„์ธต5 minutes
๊ฐ€์‹œ์„ฑ5 minutes
use, super, self10 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();
}
This slide should take about 3 minutes.
  • ํŒจํ‚ค์ง€๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ ํ•˜๋‚˜์˜ ๋Œ€ํ‘œ 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!()
}
This slide should take about 5 minutes.
  • 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();
}
This slide should take about 5 minutes.
  • pub ํ‚ค์›Œ๋“œ๋Š” ๋ชจ๋“ˆ์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ, ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์œผ๋กœ pub(...) ์ง€์ •์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณต๊ฐœ ๋ฒ”์œ„๋ฅผ ์ œํ•œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.
  • pub(crate)๋กœ ๊ฐ€์‹œ์„ฑ์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด ์ž์ฃผ ์“ฐ์ž…๋‹ˆ๋‹ค.
  • ์ž์ฃผ ์“ฐ์ด์ง„ ์•Š์ง€๋งŒ ํŠน์ • ๊ฒฝ๋กœ์— ๋Œ€ํ•ด์„œ๋งŒ ๊ฐ€์‹œ์„ฑ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์–ด๋–ค ๊ฒฝ์šฐ์ด๋“  ๊ฐ€์‹œ์„ฑ์ด ๋ถ€์—ฌ๋˜๋ฉด ํ•ด๋‹น ๋ชจ๋“ˆ์„ ํฌํ•จํ•˜์—ฌ ํ•˜์œ„์˜ ๋ชจ๋“  ๋ชจ๋“ˆ์ด ์ ์šฉ๋ฐ›์Šต๋‹ˆ๋‹ค.

use, super, self

๋ชจ๋“ˆ์€ use๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค๋ฅธ ๋ชจ๋“ˆ์˜ ์‹ฌ๋ณผ์„ ๋‚ด ์Šค์ฝ”ํ”„๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๊ฐ ๋ชจ๋“ˆ์˜ ์ƒ๋‹จ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‚ด์šฉ์ด ์˜ต๋‹ˆ๋‹ค:

use std::collections::HashSet;
use std::process::abort;

๊ฒฝ๋กœ

๊ฒฝ๋กœ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค:

  1. ์ƒ๋Œ€ ๊ฒฝ๋กœ:

    • foo ๋˜๋Š” self::foo๋Š” ํ˜„์žฌ ๋ชจ๋“ˆ ๋‚ด๋ถ€์˜ foo๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค,
    • super::foo๋Š” ๋ถ€๋ชจ ๋ชจ๋“ˆ์˜ foo๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค.
  2. ์ ˆ๋Œ€ ๊ฒฝ๋กœ:

    • crate::foo๋Š” ํ˜„์žฌ ํฌ๋ ˆ์ดํŠธ ๋ฃจํŠธ์˜ foo๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค,
    • bar::foo๋Š” barํฌ๋ ˆ์ดํŠธ์˜ foo๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค.
This slide should take about 8 minutes.
  • ๋” ์งง์€ ๊ฒฝ๋กœ์—์„œ ๊ธฐํ˜ธ๋ฅผ โ€™๋‹ค์‹œ ๋‚ด๋ณด๋‚ด๊ธฐโ€™ํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ํฌ๋ ˆ์ดํŠธ์˜ ์ตœ์ƒ์œ„ 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();
}
This slide and its sub-slides should take about 15 minutes.

ํ•™์ƒ๋“ค์—๊ฒŒ๋Š” ๋ณธ์ธ์—๊ฒŒ ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ฐฉ์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๋‚˜๋ˆ„๋„๋ก ๊ถŒ์žฅํ•˜์„ธ์š”. ๊ทธ๋ฆฌ๊ณ ๋‚˜์„œ ํ•„์š”ํ•œ 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:

SlideDuration
ํ…Œ์ŠคํŠธ ๋ชจ๋“ˆ5 minutes
๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ5 minutes
์ปดํŒŒ์ผ๋Ÿฌ ๋ฆฐํŠธ ๋ฐ Clippy3 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๋ฅผ ์ˆ˜ํ–‰ํ–ˆ์„ ๊ฒฝ์šฐ์—๋งŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
This slide should take about 5 minutes.

ํ”Œ๋ ˆ์ด๊ทธ๋ผ์šด๋“œ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ

ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ํ…Œ์ŠคํŠธ ํ•˜๋ ค๋ฉด, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

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);
}
This slide should take about 3 minutes.

์ฝ”๋“œ ์ƒ˜ํ”Œ์„ ์‹คํ–‰ํ•˜๊ณ  ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋„ ๋ฆฐํŠธ๊ฐ€ ํ‘œ์‹œ๋˜์ง€๋งŒ ์ฝ”๋“œ๊ฐ€ ์ปดํŒŒ์ผ๋˜๊ณ  ๋‚˜๋ฉด ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ”Œ๋ ˆ์ด๊ทธ๋ผ์šด๋“œ ์‚ฌ์ดํŠธ๋กœ ์ „ํ™˜ํ•˜์—ฌ ์ด๋Ÿฌํ•œ ๋ฆฐํŠธ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

๋ฆฐํŠธ๋ฅผ ํ•ด๊ฒฐํ•œ ํ›„ ํ”Œ๋ ˆ์ด๊ทธ๋ผ์šด๋“œ ์‚ฌ์ดํŠธ์—์„œ 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 becomes 14 which becomes 1 + 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:

SegmentDuration
์˜ค๋ฅ˜์ฒ˜๋ฆฌ55 minutes
์•ˆ์ „ํ•˜์ง€ ์•Š์€ ๋Ÿฌ์ŠคํŠธ1 hour and 5 minutes

์˜ค๋ฅ˜์ฒ˜๋ฆฌ

This segment should take about 55 minutes. It contains:

SlideDuration
ํŒจ๋‹‰3 minutes
Iterator5 minutes
๋ฌต์‹œ์  ํ˜•๋ณ€ํ™˜5 minutes
Error5 minutes
From๊ณผ Into5 minutes
Result๋ฅผ ์ด์šฉํ•œ ๊ตฌ์กฐํ™”๋œ ์˜ค๋ฅ˜์ฒ˜๋ฆฌ30 minutes

ํŒจ๋‹‰

Rust๋Š” โ€™ํŒจ๋‹‰โ€™์œผ๋กœ ์น˜๋ช…์ ์ธ ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๋Ÿฌ์ŠคํŠธ๋Š” ์ˆ˜ํ–‰ ์ค‘ ์น˜๋ช…์ ์ธ ์˜ค๋ฅ˜๋ฅผ ๋งŒ๋‚˜๋ฉด ํŒจ๋‹‰์„ ๋ฐœ์ƒํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค:

fn main() {
    let v = vec![10, 20, 30];
    println!("v[100]: {}", v[100]);
}
  • ํŒจ๋‹‰์€ ๋ณต๊ตฌํ•  ์ˆ˜ ์—†๊ณ  ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜์ž…๋‹ˆ๋‹ค.
    • ํŒจ๋‹‰์€ ํ”„๋กœ๊ทธ๋žจ์— ๋ฒ„๊ทธ๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
    • ๊ฒฝ๊ณ„ ๊ฒ€์‚ฌ ์‹คํŒจ์™€ ๊ฐ™์€ ๋Ÿฐํƒ€์ž„ ์‹คํŒจ๋กœ ์ธํ•ด ํŒจ๋‹‰์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์‹คํŒจ ์‹œ ์–ด์„ค์…˜(์˜ˆ: assert!) ํŒจ๋‹‰
    • ๋ชฉ์ ๋ณ„ ํŒจ๋‹‰์€ panic! ๋งคํฌ๋กœ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํŒจ๋‹‰์€ ์Šคํƒ์„ โ€™ํ•ด์ œโ€™ํ•˜์—ฌ ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜๋œ ๊ฒƒ์ฒ˜๋Ÿผ ๊ฐ’์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.
  • ์ถฉ๋Œ(ํฌ๋ž˜์‹œ)์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์•„์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ํŒจ๋‹‰์„ ์œ ๋ฐœํ•˜์ง€ ์•Š๋Š” API(Vec::get๋“ฑ)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
This slide should take about 3 minutes.

๊ธฐ๋ณธ์ ์œผ๋กœ, ํŒจ๋‹‰์ด ๋ฐœ์ƒํ•˜๋ฉด ์Šคํƒ ๋˜๊ฐ๊ธฐ๋ฅผ ํ•ฉ๋‹ˆ๋‹ค. ์Šคํƒ ๋˜๊ฐ๊ธฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์บ์น˜๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค:

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:?}");
}
This slide should take about 5 minutes.

?๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก read_username ํ•จ์ˆ˜๋ฅผ ๋‹จ์ˆœํ™”ํ•ฉ๋‹ˆ๋‹ค.

ํ‚ค ํฌ์ธํŠธ:

  • username ๋ณ€์ˆ˜๋Š” Ok(string)์ด๊ฑฐ๋‚˜ Err(error)์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • fs::write ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ์ด ์—†๊ฑฐ๋‚˜, ๋น„์—ˆ๊ฑฐ๋‚˜, ์ค‘๋ณต๋˜๋Š” ๊ฒฝ์šฐ ๋“ฑ์„ ํ…Œ์ŠคํŠธํ•ด ๋ด…๋‹ˆ๋‹ค.
  • Note that main can return a Result<(), E> as long as it implements std::process::Termination. In practice, this means that E implements Debug. The executable will print the Err 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:?}");
}
This slide should take about 5 minutes.

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}"),
    }
}
This slide should take about 5 minutes.

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 implement From<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:?}"),
    }
}
This slide should take about 5 minutes.

thiserror

  • The Error derive macro is provided by thiserror, 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 the Display 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 standard Result and Option 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:

SlideDuration
์•ˆ์ „ํ•˜์ง€ ์•Š์€ ๋Ÿฌ์ŠคํŠธ5 minutes
์›์‹œ ํฌ์ธํ„ฐ ์—ญ์ฐธ์กฐ(๋”ฐ๋ผ๊ฐ€๊ธฐ)10 minutes
์ •์  ๊ฐ€๋ณ€ ๋ณ€์ˆ˜5 minutes
Unions5 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๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

This slide should take about 5 minutes.

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);
    */
}
This slide should take about 10 minutes.

๋ชจ๋“  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}");
    }
}
This slide should take about 5 minutes.
  • ์ด ํ”„๋กœ๊ทธ๋žจ์€ ๋‹จ์ผ ์Šค๋ ˆ๋“œ์ด๋ฏ€๋กœ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ 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!
}
This slide should take about 5 minutes.

๋Ÿฌ์ŠคํŠธ์—๋Š” ์—ด๊ฑฐํ˜•์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์œ ๋‹ˆ์˜จ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋Š” ๊ทนํžˆ ๋“œ๋ญ…๋‹ˆ๋‹ค. ์œ ๋‹ˆ์˜จ์€ 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);
}
This slide should take about 5 minutes.

์•ˆ์ „ํ•˜์ง€ ์•Š์€ ํ•จ์ˆ˜ ํ˜ธ์ถœ

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 {}
This slide should take about 5 minutes.

์•ˆ์ „ํ•˜์ง€ ์•Š์€ ํŠธ๋ ˆ์ž‡์„ ๋งŒ๋“ค ๋•Œ์—๋Š” ์ฃผ์„์— # 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๊ณผ StringUTF-8๋Ÿฌ์ŠคํŠธ์—์„œ์˜ ๋ฌธ์ž์—ด ์ฒ˜๋ฆฌ
CStr๊ณผ CString๋„(NUL)๋กœ ๋๋‚จCํ•จ์ˆ˜์™€ ์—ฐ๋™ํ•˜๊ธฐ
OsStr์™€ OsStringOS๊ฐ€ ์ •์˜ํ•จ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 ์‚ฌ์šฉ์ด ๋Š˜์–ด๋‚œ ์ ์„ ๊ฐ์•ˆํ•  ๋•Œ ๋ฐœํ‘œ์ž๋Š” ๋‹ค์Œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„ค์น˜

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 TypeDescription
rust_binary๋Ÿฌ์ŠคํŠธ ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
rust_library๋Ÿฌ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(rlibํ˜น์€ dylib)๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
rust_fficc ๋ชจ๋“ˆ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” C library (์ •์  ํ˜น์€ ๋™์ )๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
rust_proc_macroproc-macro๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋Ÿฌ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ์˜ ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ์ƒ๊ฐํ•ด๋„ ์ข‹์Šต๋‹ˆ๋‹ค.
rust_testํ‘œ์ค€ ๋Ÿฌ์ŠคํŠธ ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ…Œ์ŠคํŠธ ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
rust_fuzzlibfuzzer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ fuzz ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
rust_protobufํ”„๋กœํ† ๋ฒ„ํ”„(protobuf) ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋Ÿฌ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
rust_bindgenC ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•œ ๋Ÿฌ์ŠคํŠธ ๋ฐ”์ธ๋”ฉ์„ ์ œ๊ณตํ•˜๋Š” ๋Ÿฌ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ 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 is com.example.birthdayservice and the file is at aidl/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 than String 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.

  1. Create an instance of your service type (BirthdayService).
  2. 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 the BnBinder base class in C++. We donโ€™t have inheritance in Rust, so instead we use composition, putting our BirthdayService within the generated BnBinderService.
  3. Call add_service, giving it a service identifier and your service object (the BnBirthdayService object in the example).
  4. 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 and inout 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, Vecs 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 TypeRust TypeNote
booleanbool
bytei8Note that bytes are signed.
charu16Note the usage of u16, NOT u32.
inti32
longi64
floatf32
doublef64
StringString

๋ฐฐ์—ด

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:

PositionRust Type
in argument&[T]
out/inout argument&mut Vec<T>
ReturnVec<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 as BnBirthdayService 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 an OwnedFd, and so can be created from a File (or any other type that wraps an OwnedFd), and can be used to create a new File 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
This slide should take about 5 minutes.
  • 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);
}
This slide should take about 5 minutes.
  • 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 TypeC++ Type
Stringrust::String
&strrust::Str
CxxStringstd::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 ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๊ทน์†Œ์ˆ˜์ด๋ฏ€๋กœ ์ด๋Ÿฌํ•œ ๊ฑฐ์˜ ๋ชจ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—๋Š” ์†Œ๋Ÿ‰์˜ ํผ์ŠคํŠธ ํŒŒํ‹ฐ ๊ธ€๋ฃจ ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

RustExistingcrateLanguageCrateboundaryAPIExistingChromiumChromiumRustRustC++C++wrapper

ํŠน์ • ์„œ๋“œ ํŒŒํ‹ฐ ํฌ๋ ˆ์ดํŠธ์˜ ํผ์ŠคํŠธ ํŒŒํ‹ฐ 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" ]
}
We'll see that this relationship only works if the Rust code exposes plain C APIs which can be called from C++, or if we use a C++/Rust interop tool.

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
Example screenshot from VSCode

๋ˆ„๊ตฐ๊ฐ€๊ฐ€ 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!์—์„œ โ€™์ •์˜๋กœ ์ด๋™โ€™์„ ๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ์œผ๋กœ ํด๋ฆญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๊ณณ

It's really important that students get this running, because future exercises will build on it.

์ด ์˜ˆ๋Š” ์ตœ์†Œ ๊ณตํ†ต๋ถ„๋ชจ ์ƒํ˜ธ ์šด์šฉ์„ฑ ์–ธ์–ด์ธ 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 (using pub 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 the png 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 to assert_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++ ๋ชจ๋‘์—์„œ ํ•จ์ˆ˜์™€ ์œ ํ˜•์— ๊ด€ํ•œ ์„ ์–ธ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

Overview diagram of cxx, showing that the same interface definition is used to create both C++ and Rust side code which then communicate via a lowest common denominator C API

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 a mod called ffi 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 ๋ฐ”์ธ๋”ฉ ์ดˆ์•ˆ์„ ์ž‘์„ฑํ•˜์—ฌ ์ถฉ๋ถ„ํžˆ ๋‹จ์ˆœํ•˜๊ฒŒ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

In addition, right now, Rust code in one component cannot depend on Rust code in another, due to linking details in our component build. That's another reason to restrict Rust to use in leaf nodes.

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 of Result<T, E> can be:

    • Returned via out parameters (e.g. via &mut T). This requires that T can be passed across the FFI boundary - for example T has to be:
      • A primitive type (like u32 or usize)
      • A type natively supported by cxx (like UniquePtr<T>) that has a suitable default value to use in a failure case (unlike Box<T>).
    • 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 in UniquePtr<T>.
  • The E part of Result<T, E> can be:

    • Returned as a boolean (e.g. true representing success, and false representing failure)
    • Preserving error details is in theory possible, but so far hasnโ€™t been needed in practice.

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 ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์ƒ๊ฐํ•ด ๋ณด์„ธ์š”. ํ•ด๋‹น ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์Šค์ผ€์น˜ํ•ฉ๋‹ˆ๋‹ค.

๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๊ณณ

As students explore Part Two, they're bound to have lots of questions about how to achieve these things, and also how CXX works behind the scenes.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์œ ํ˜• Y๋กœ ์œ ํ˜• X์˜ ๋ณ€์ˆ˜๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๋ฐ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ X์™€ Y๋Š” ๋ชจ๋‘ ํ•จ์ˆ˜ ์œ ํ˜•์ž…๋‹ˆ๋‹ค. ์ด๋Š” C++ ํ•จ์ˆ˜๊ฐ€ cxx::bridge์˜ ์„ ์–ธ๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • C++ ์ฐธ์กฐ๋ฅผ Rust ์ฐธ์กฐ๋กœ ์ž์œ ๋กญ๊ฒŒ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด UB๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์„๊นŒ์š”? CXX์˜ ๋ถˆํˆฌ๋ช… ์œ ํ˜•์˜ ๊ฒฝ์šฐ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํฌ๊ธฐ๊ฐ€ 0์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. CXX ์‚ฌ์†Œํ•œ ์œ ํ˜•์˜ ๊ฒฝ์šฐ UB๋ฅผ ์œ ๋ฐœํ•˜๋Š” ๊ฒƒ์ด _๊ฐ€๋Šฅ_ํ•˜์ง€๋งŒ CXX์˜ ์„ค๊ณ„์ƒ ์ด๋Ÿฌํ•œ ์˜ˆ๋ฅผ ๋งŒ๋“ค๊ธฐ๊ฐ€ ์ƒ๋‹นํžˆ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

์„œ๋“œ ํŒŒํ‹ฐ ํฌ๋ ˆ์ดํŠธ ์ถ”๊ฐ€

Rust ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” โ€™ํฌ๋ ˆ์ดํŠธโ€™๋ผ๊ณ  ํ•˜๋ฉฐ crates.io์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Rust ํฌ๋ ˆ์ดํŠธ๊ฐ€ ์„œ๋กœ ์ข…์†๋˜๋Š” ๊ฒƒ์€ ์•„์ฃผ ์‰ฝ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์„œ๋กœ ์ข…์†๋ฉ๋‹ˆ๋‹ค.

์†์„ฑC++ libraryRust crate
Build system1์–ต+์ผ๊ด€์„ฑ: Cargo.toml
์ผ๋ฐ˜์ ์ธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํฌ๊ธฐํฐ ํŽธ์ž‘๊ฒŒ
๋ชจ๋“  ์ข…์†์„ฑ๋“ค์ ์Œ1์–ต+

Chromium ์—”์ง€๋‹ˆ์–ด์—๊ฒŒ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์žฅ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋ชจ๋“  ํฌ๋ ˆ์ดํŠธ๋Š” ๊ณตํ†ต ๋นŒ๋“œ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ Chromium์— ์ž๋™์œผ๋กœ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ทธ๋Ÿฌ๋‚˜ ํฌ๋ ˆ์ดํŠธ์—๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์ „์ด ์ข…์† ํ•ญ๋ชฉ์ด ์žˆ์œผ๋ฏ€๋กœ ์—ฌ๋Ÿฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋ฃฐ ๋‚ด์šฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • Chromium ์†Œ์Šค ์ฝ”๋“œ ํŠธ๋ฆฌ์— ํฌ๋ ˆ์ดํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•
  • ์ด๋ฅผ ์œ„ํ•ด gn ๋นŒ๋“œ ๊ทœ์น™์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•
  • ์ถฉ๋ถ„ํ•œ ์•ˆ์ „์„ฑ์„ ์œ„ํ•ด ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ๊ฐ์‚ฌํ•˜๋Š” ๋ฐฉ๋ฒ•
All of the things in the table on this slide are generalizations, and counter-examples can be found. But in general it's important for students to understand that most Rust code depends on other Rust libraries, because it's easy to do so, and that this has both benefits and costs.

ํฌ๋ ˆ์ดํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋„๋ก 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/v/:libํฌ๋ ˆ์ดํŠธ์ด๋ฆ„๋ฉ”์ด์ €semver๋ฒ„์ „

์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

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, and
  • uwuify.

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์„ ๋งŒ๋“œ์…จ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Chromium UI screenshot with uwu language
Students will likely need some hints here. Hints include:
  • 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

core

alloc

std

  • Slices, &str, CStr
  • NonZeroU8โ€ฆ
  • Option, Result
  • Display, Debug, write!โ€ฆ
  • Iterator
  • panic!, assert_eq!โ€ฆ
  • NonNull ๋ฐ ๋ชจ๋“  ์ผ๋ฐ˜์ ์ธ ํฌ์ธํ„ฐ ๊ด€๋ จ ํ•จ์ˆ˜
  • Future and async/await
  • fence, AtomicBool, AtomicPtr, AtomicU32โ€ฆ
  • Duration
  • Box, Cow, Arc, Rc
  • Vec, BinaryHeap, BtreeMap, LinkedList, VecDeque
  • String, CString, format!
  • Error
  • HashMap
  • Mutex, Condvar, Barrier, Once, RwLock, mpsc
  • File ๋ฐ ๋‚˜๋จธ์ง€ fs
  • println!, Read, Write, Stdin, Stdout ๋ฐ ๋‚˜๋จธ์ง€ io
  • Path, OsString
  • net
  • Command, Child, ExitCode
  • spawn, sleep ๋ฐ ๋‚˜๋จธ์ง€ thread
  • SystemTime, Instant
  • 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 ์‹คํ–‰์ž
  • 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์—๋Š” ์‹ค์ œ๋กœ ํ›จ์”ฌ ๋” ๋งŽ์€ ๋ ˆ์ง€์Šคํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉฐ, ์ด์— ์•ก์„ธ์Šคํ•  ํฌ์ธํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์˜คํ”„์…‹์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฝ๊ณ  ์ฝ๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ทธ์ค‘ ์ผ๋ถ€๋Š” ๊ตฌ์กฐํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋Š” ๋น„ํŠธ ํ•„๋“œ์ž…๋‹ˆ๋‹ค.

์˜คํ”„์…‹๋ ˆ์ง€์Šคํ„ฐ ์ด๋ฆ„๋„ˆ๋น„
0x00DR12
0x04RSR4
0x18FR9
0x20ILPR8
0x24IBRD16
0x28FBRD6
0x2cLCR_H8
0x30CR16
0x34IFLS6
0x38IMSC11
0x3cRIS11
0x40MIS11
0x44ICR11
0x48DMACR3
  • ๊ฐ„๊ฒฐ์„ฑ์„ ์œ„ํ•ด ์ผ๋ถ€ 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์€ ์บ์‹œ๊ฐ€ ์‚ฌ์šฉ ์ค‘์ง€๋œ ์ƒํƒœ๋กœ ๋ฉ”๋ชจ๋ฆฌ์— ์ง์ ‘ ์•ก์„ธ์Šคํ•˜๋Š” ๋ฐ˜๋ฉด ํ˜ธ์ŠคํŠธ์—๋Š” ๋™์ผํ•œ ๋ฉ”๋ชจ๋ฆฌ์— ๋Œ€ํ•ด ์บ์‹œํ•  ์ˆ˜ ์žˆ๋Š” ๋ณ„์นญ์ด ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ํ˜ธ์ŠคํŠธ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์— ๋ช…์‹œ์ ์œผ๋กœ ์•ก์„ธ์Šคํ•˜์ง€ ์•Š๋”๋ผ๋„ ์ถ”์ธก ์•ก์„ธ์Šค๋Š” ์บ์‹œ ์ฑ„์šฐ๊ธฐ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋‘˜ ์ค‘ ํ•˜๋‚˜์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์†์‹ค๋ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ์—๋„ ํ•˜์ดํผ๋ฐ”์ด์ € ์—†์ด ํ•˜๋“œ์›จ์–ด์—์„œ ์ง์ ‘ ์‹คํ–‰๋˜๋Š” ์ด ํŠน์ • ๊ฒฝ์šฐ์—๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ, ์ผ๋ฐ˜์ ์œผ๋กœ ์ข‹์€ ํŒจํ„ด์€ ์•„๋‹™๋‹ˆ๋‹ค.

์œ ์šฉํ•œ ํฌ๋ ˆ์ดํŠธ

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 of RwLock, Barrier and Once from std::sync; and Lazy 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.

  1. ์ง๋ ฌ ์ฝ˜์†”์— ํ˜„์žฌ ์‹œ๊ฐ„์„ ์ถœ๋ ฅํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‚ ์งœ/์‹œ๊ฐ„ ํ˜•์‹ ์ง€์ •์—๋Š” chrono ํฌ๋ ˆ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ์ผ์น˜ ๋ ˆ์ง€์Šคํ„ฐ์™€ ์›์‹œ ์ธํ„ฐ๋ŸฝํŠธ ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ์‹œ๊ฐ„(์˜ˆ: ํ–ฅํ›„ 3์ดˆ)๊นŒ์ง€ ๋ฐ”์œ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค. ๋ฃจํ”„ ๋‚ด์—์„œ core::hint::spin_loop๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  3. ์‹œ๊ฐ„์ด ์žˆ๋Š” ๊ฒฝ์šฐ ์—ฐ์žฅ: RTC ์ผ์น˜๋กœ ์ƒ์„ฑ๋œ ์ธํ„ฐ๋ŸฝํŠธ๋ฅผ ์‚ฌ์šฉ ์„ค์ •ํ•˜๊ณ  ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. arm-gic ํฌ๋ ˆ์ดํŠธ์— ์ œ๊ณต๋œ ๋“œ๋ผ์ด๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Arm ์ผ๋ฐ˜ ์ธํ„ฐ๋ŸฝํŠธ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • GIC์— IntId::spi(2)๋กœ ์—ฐ๊ฒฐ๋œ RTC ์ธํ„ฐ๋ŸฝํŠธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • ์ธํ„ฐ๋ŸฝํŠธ๋ฅผ ์‚ฌ์šฉ ์„ค์ •ํ•œ ํ›„์—๋Š” arm_gic::wfi()๋ฅผ ํ†ตํ•ด ์ฝ”์–ด๋ฅผ ์ ˆ์ „ ๋ชจ๋“œ๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์ธํ„ฐ๋ŸฝํŠธ๋ฅผ ์ˆ˜์‹ ํ•  ๋•Œ๊นŒ์ง€ ์ฝ”์–ด๊ฐ€ ์ ˆ์ „ ๋ชจ๋“œ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค.

์—ฐ์Šต ํ…œํ”Œ๋ฆฟ์„ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  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 a JoinHandle. Look at the docs.

    • JoinHandle has a .join() method that blocks.
  • Use let handle = thread::spawn(...) and later handle.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:

  • Use the Result return value from handle.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.
  • 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 of Rc that uses atomic operations.
  • Arc<T> implements Clone whether or not T does. It implements Send and Sync if and only if T implements them both.
  • Arc::clone() has the cost of atomic operations that get executed, but after that the use of the T 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. The MutexGuard ensures that the &mut T doesnโ€™t outlive the lock being held.
  • Mutex<T> implements both Send and Sync iff (if and only if) T implements Send.
  • A read-write lock counterpart: RwLock.
  • Why does lock() return a Result?
    • If the thread that held the Mutex panicked, the Mutex becomes โ€œpoisonedโ€ to signal that the data it protected might be in an inconsistent state. Calling lock() on a poisoned mutex fails with a PoisonError. You can call into_inner() on the error to recover the data regardless.

์˜ˆ์ œ

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}");
    }
}

(์—ฐ์Šต๋ฌธ์ œ๋กœ ๋Œ์•„๊ฐ€๊ธฐ)

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 an async 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 an async 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 one send.

  • 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 of select! creates new futures.

    • ๋Œ€์•ˆ์€ future ์ž์ฒด ๋Œ€์‹  &mut future๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(Pinning์„ ๋‹ค๋ฃฐ ๋•Œ ์ž์„ธํžˆ ์„ค๋ช…ํ•  ์˜ˆ์ •์ž„).

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 the Future trait uses Pin<&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 or async are not dyn 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/ํด๋”๋ฅผ ์ฐธ์กฐํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.