Rust 2026 기본기 정리
Edition 2024 기준 최신 문법과 표준 라이브러리, 비동기, 동시성, 패턴까지 한 페이지로 모은 입문~중급 핵심 노트.
2026-05 기준 stable Rust(1.87+)에서 동작하는 코드만 수록.
1. 왜 Rust 2026인가
- 안전 + 성능 — GC 없이 메모리 안전(소유권), C/C++ 수준의 단일 바이너리.
- 현대 문법 — 합타입(
enum), 패턴 매칭, 트레이트 기반 제네릭, 표현식 중심.
- Edition 2024(2024-11 stable) — async closure, RPITIT, AFIT, gen 블록, let-chains 등이 모두 안정화된 첫 에디션.
- 2026 시점 — async fn in trait(AFIT),
impl Trait in trait return(RPITIT), async closure가 표준이고, async iterator/gen 블록이 일반 라이브러리에서 활용되기 시작.
2. 설치 & 툴체인
# macOS / Linux
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 버전 / 업데이트
rustup update
rustc --version # rustc 1.87.x (stable)
cargo --version
# 컴포넌트
rustup component add clippy rustfmt rust-analyzer rust-src
# 툴체인 핀(프로젝트 루트)
echo '[toolchain]\nchannel = "1.87.0"\ncomponents = ["rustfmt","clippy"]' > rust-toolchain.toml
| 도구 | 역할 |
rustup | 툴체인 매니저 (stable/beta/nightly, 컴포넌트, 타깃) |
cargo | 빌드/패키지/테스트/배포 통합 CLI |
rustc | 실제 컴파일러 |
rustfmt | 포매터 (cargo fmt) |
clippy | 린터 (cargo clippy -- -D warnings) |
rust-analyzer | LSP — VS Code/Zed/Helix에 필수 |
3. Cargo & 프로젝트 구조
cargo new hello # 바이너리
cargo new mylib --lib # 라이브러리
cargo run
cargo build --release
cargo test
cargo check # 타입체크만 (빠름)
cargo add tokio --features full
cargo remove anyhow
Cargo.toml (Edition 2024)
[package]
name = "myapp"
version = "0.1.0"
edition = "2024"
rust-version = "1.85"
[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
thiserror = "2"
[profile.release]
lto = "fat"
codegen-units = 1
strip = "symbols"
워크스페이스
[workspace]
members = ["crates/*"]
resolver = "3" # Edition 2024 기본값
[workspace.package]
edition = "2024"
rust-version = "1.85"
[workspace.dependencies]
serde = "1" # 멤버에서 serde.workspace = true
4. 기본 문법
변수 · 가변성 · 섀도잉
let x = 5; // 불변 (기본)
let mut y = 5; // 가변
let x: i32 = 10; // 섀도잉 — 같은 이름 재선언, 타입 변경 가능
const MAX: u32 = 100; // 컴파일타임 상수, 항상 SCREAMING
static NAME: &str = "rinda"; // 정적 (수명 'static)
기본 타입
| 분류 | 타입 |
| 정수 | i8/i16/i32/i64/i128/isize, u8/...usize |
| 실수 | f32, f64 |
| 불리언/문자 | bool, char (4바이트 유니코드 스칼라) |
| 문자열 | &str (뷰), String (소유) |
| 복합 | 튜플 (T1, T2), 배열 [T; N], 슬라이스 &[T], Vec<T> |
| 유닛 | () — "값 없음" |
함수 · 표현식
fn add(a: i32, b: i32) -> i32 {
a + b // 마지막 표현식이 반환값(세미콜론 X)
}
fn classify(n: i32) -> &'static str {
if n > 0 { "양수" } // if도 표현식
else if n < 0 { "음수" }
else { "영" }
}
제어 흐름
// loop / break with value
let answer = loop {
break 42;
};
// while let
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{top}");
}
// for + range
for i in 0..5 { println!("{i}"); }
for (i, v) in ["a", "b"].iter().enumerate() { … }
// let-else (1.65+) — 조기 반환에 최적
let Some(name) = lookup() else { return Err(...); };
// let-chains (Edition 2024 stable)
if let Some(u) = user && u.active && let Some(p) = u.plan {
println!("{}", p.tier);
}
5. 소유권 · 빌림 · 수명 ★ 핵심
Rust의 정체성. 컴파일러가 "메모리를 누가 책임지나"를 추적해 GC 없이 안전을 보장한다.
3대 규칙
- 모든 값은 정확히 하나의 소유자(owner)를 가진다.
- 소유자가 스코프를 벗어나면 값은 즉시
drop된다.
- 참조(
&T)는 동시에 여러 개의 불변 참조 또는 단 하나의 가변 참조만 허용된다.
let s1 = String::from("hi");
let s2 = s1; // 소유권 이동(move). 이후 s1 사용 불가.
// println!("{s1}"); // ← 컴파일 에러
let s3 = s2.clone(); // 깊은 복제. 소유권 두 개로 분리.
fn len(s: &String) -> usize { s.len() } // 빌림 — 소유권 안 가져감
fn push_x(s: &mut String) { s.push('x'); } // 가변 빌림
수명(lifetime)
// 입력 두 참조 중 더 짧은 수명을 반환에 매칭
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
대부분의 수명은 생략(elision)된다
- 입력 참조 1개 → 모든 출력에 같은 수명.
- 메서드(
&self) → 출력 수명 = &self.
- 그 외엔 명시. 컴파일러 메시지가 정확히 알려준다.
Copy vs Move
i32·bool·char·고정 크기 배열 등 Copy 트레이트 구현 타입은 대입 시 복제된다. String·Vec·Box는 이동.
6. 구조체 · 열거형 · 패턴 매칭
구조체
#[derive(Debug, Clone, PartialEq)]
struct User { id: u64, name: String, active: bool }
impl User {
fn new(id: u64, name: impl Into<String>) -> Self {
Self { id, name: name.into(), active: true }
}
fn deactivate(&mut self) { self.active = false; }
}
// 튜플 구조체 / 유닛 구조체
struct UserId(u64);
struct Marker;
열거형 (대수적 데이터 타입)
enum Shape {
Circle { radius: f64 },
Rect(f64, f64),
None,
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle { radius } => 3.14159 * radius * radius,
Shape::Rect(w, h) => w * h,
Shape::None => 0.0,
}
}
}
패턴 매칭
match n {
0 => "zero",
1..=9 => "single digit",
x if x < 0 => "negative",
_ => "big",
}
// 구조 분해
let User { name, .. } = user;
let (a, b, c) = (1, 2, 3);
// or 패턴 / @ 바인딩
match n {
n @ 1 | n @ 3 | n @ 5 => println!("odd small {n}"),
_ => {}
}
Option / Result
let some_n: Option<i32> = Some(5);
let got = some_n.unwrap_or(0);
let doubled = some_n.map(|x| x * 2);
fn parse(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse::<i32>() // 그대로 반환
}
fn double(s: &str) -> Result<i32, _> {
Ok(parse(s)? * 2) // `?` — 실패 시 조기 반환
}
7. 트레이트 · 제네릭
트레이트 정의 / 구현
trait Greet {
fn hello(&self) -> String;
fn shout(&self) -> String { // 기본 구현
format!("{}!!", self.hello())
}
}
impl Greet for User {
fn hello(&self) -> String { format!("hi, {}", self.name) }
}
제네릭 + 트레이트 바운드
// where 절로 가독성
fn sum<T>(items: &[T]) -> T
where
T: Copy + std::ops::Add<Output = T> + Default,
{
let mut acc = T::default();
for &x in items { acc = acc + x; }
acc
}
// `impl Trait` — 익명 반환 타입
fn make_iter() -> impl Iterator<Item = u32> {
(0..).filter(|n| n % 3 == 0)
}
정적 디스패치 vs 동적 디스패치
| 정적 (제네릭/impl Trait) | 동적 (dyn Trait) |
| 비용 | 0 (모노모피화) | vtable 1단계 인다이렉션 |
| 이종 컬렉션 | 불가 | 가능 (Vec<Box<dyn T>>) |
| 코드 크기 | 커짐 | 작음 |
RPITIT / AFIT 2024+
// trait 메서드가 impl Trait을 반환 (Edition 2024 stable)
trait Repository {
fn all(&self) -> impl Iterator<Item = User>;
// async fn in trait — 동적 디스패치 시엔 trait 객체 wrapper 필요
async fn find(&self, id: u64) -> Option<User>;
}
8. 에러 처리
표준 패턴
- panic! — 회복 불가능. 보통 버그.
- Result<T, E> — 회복 가능. 모든 IO/파싱/네트워크에 사용.
? 연산자 — Err면 즉시 반환, From으로 자동 변환.
thiserror — 라이브러리 에러 정의
use thiserror::Error;
#[derive(Debug, Error)]
pub enum DbError {
#[error("connection failed: {0}")]
Conn(#[from] std::io::Error),
#[error("row {id} not found")]
NotFound { id: u64 },
#[error("deserialize: {0}")]
De(#[from] serde_json::Error),
}
anyhow — 애플리케이션 에러
use anyhow::{Context, Result};
fn load(path: &str) -> Result<Config> {
let data = std::fs::read_to_string(path)
.with_context(|| format!("reading {path}"))?;
let cfg = serde_json::from_str(&data)
.context("parse config")?;
Ok(cfg)
}
언제 anyhow vs thiserror?
라이브러리(공개 API)에서는 호출자가 분기할 수 있도록 thiserror로 enum. 애플리케이션(바이너리)에서는 컨텍스트 체인으로 충분하니 anyhow.
9. 컬렉션 · 이터레이터
| 타입 | 용도 | 핵심 API |
Vec<T> | 가변 배열 | push/pop/iter/extend |
String | UTF-8 가변 문자열 | push_str/format!/chars |
HashMap<K,V> | 해시 맵 | insert/get/entry |
BTreeMap | 정렬 맵 | 범위 쿼리 |
HashSet | 중복 없는 집합 | insert/contains |
VecDeque | 양방향 큐 | push_front/back |
이터레이터 — Rust의 정수
let nums = vec![1, 2, 3, 4, 5];
let sum_of_squares: i32 = nums.iter()
.filter(|&&x| x % 2 == 1)
.map(|&x| x * x)
.sum(); // 9 + 1 + 25 = 35
let grouped: HashMap<bool, Vec<i32>> = nums.into_iter()
.fold(HashMap::new(), |mut acc, n| {
acc.entry(n % 2 == 0).or_default().push(n);
acc
});
3대 변환 메서드
| 메서드 | 의미 |
iter() | 불변 참조 &T |
iter_mut() | 가변 참조 &mut T |
into_iter() | 소유권 이동 T |
gen 블록 Edition 2024
// 게으른 이터레이터를 yield 문법으로 작성
fn fib() -> impl Iterator<Item = u64> {
gen {
let (mut a, mut b) = (0u64, 1);
loop {
yield a;
(a, b) = (b, a + b);
}
}
}
10. 클로저 & Fn 트레이트
let y = 10;
let add_y = |x| x + y; // 캡처: y 빌림
let moved = move |x: i32| x + y; // y 소유권 이동
| 트레이트 | 호출 방식 | 특성 |
Fn | 여러 번 호출, 환경을 빌림 | 가장 제약 적음 |
FnMut | 여러 번 호출, 환경을 가변 빌림 | 상태 변경 |
FnOnce | 한 번만 호출, 환경을 소비 | 가장 강력 |
async closure 1.85+
async fn retry<F>(mut f: F) -> Result<()>
where F: AsyncFnMut() -> Result<()>,
{
for _ in 0..3 {
if f().await.is_ok() { return Ok(()); }
}
Err(anyhow!("3 attempts failed"))
}
11. 모듈 · 크레이트 · 가시성
// src/lib.rs
pub mod users; // src/users.rs 또는 src/users/mod.rs
pub mod billing { pub mod tier; }
// 가시성 단계
pub // 어디서나
pub(crate) // 같은 크레이트만
pub(super) // 부모 모듈만
pub(in path) // 특정 경로만
// (지정 없음) // 같은 모듈만 (private 기본)
// use 별칭
use std::collections::HashMap as Map;
use crate::users::{User, UserId};
12. 스마트 포인터
| 타입 | 의미 | 언제 |
Box<T> | 힙 할당, 단일 소유 | 재귀 타입, trait 객체, 큰 값 |
Rc<T> | 참조 카운팅 (싱글스레드) | 그래프, 공유 소유 |
Arc<T> | 원자적 RC (스레드 안전) | 스레드 간 공유 |
Cell/RefCell | 내부 가변성 (싱글스레드) | 불변 외형, 가변 내부 |
Mutex/RwLock | 동기 잠금 | 스레드 공유 가변 |
Cow<T> | Clone-on-Write | 대부분 빌림, 가끔 소유 |
use std::sync::Arc;
use tokio::sync::Mutex; // async에선 tokio::sync 사용
let shared = Arc::new(Mutex::new(vec![0; 10]));
let shared2 = Arc::clone(&shared); // 카운트 +1
13. 동시성
스레드
use std::thread;
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
for i in 0..4 {
let tx = tx.clone();
thread::spawn(move || {
tx.send(i * i).unwrap();
});
}
drop(tx);
for v in rx { println!("{v}"); }
scoped threads 1.63+
let data = vec![1, 2, 3];
thread::scope(|s| {
s.spawn(|| println!("{:?}", &data)); // 빌림 OK
s.spawn(|| println!("len={}", data.len()));
}); // 모든 스레드 join 후 반환
Send / Sync
- Send — 다른 스레드로 이동해도 안전.
- Sync — 다른 스레드에서 참조해도 안전(
&T: Send).
- 대부분 자동 도출.
Rc·RefCell는 Sync 아님 → 멀티스레드에선 Arc<Mutex<T>>.
14. async / await
최소 예제 (tokio)
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let body = reqwest::get("https://example.com")
.await?
.text().await?;
println!("{} bytes", body.len());
Ok(())
}
spawn / join / select
use tokio::{join, select, time::{sleep, Duration}};
let a = tokio::spawn(async { 1 });
let b = tokio::spawn(async { 2 });
let (ra, rb) = join!(a, b);
select! {
_ = sleep(Duration::from_secs(5)) => println!("timeout"),
msg = rx.recv() => println!("got {msg:?}"),
}
async fn in trait 2024 stable
trait Mailer {
async fn send(&self, to: &str, body: &str) -> Result<()>;
}
struct SmtpMailer;
impl Mailer for SmtpMailer {
async fn send(&self, to: &str, body: &str) -> Result<()> { … }
}
주의 — std::sync::Mutex를 await 하지 마라
async 컨텍스트에서 lock을 들고 있는 동안 .await 하면 worker thread가 점유된다. 그 경우엔 tokio::sync::Mutex(또는 RwLock) 사용.
15. 2024 Edition · 2026 신문법 모음
| 기능 | 요지 | 버전 |
| let-else | 단일 패턴 + else 조기 반환 | 1.65 |
| GAT | 제네릭 연관 타입 | 1.65 |
| scoped threads | borrow 가능한 스레드 풀 | 1.63 |
| RPITIT / AFIT | trait이 impl Trait · async fn 반환 | 1.75 |
async closure / AsyncFn* | 고차 async 함수 정의 | 1.85 |
| let-chains | if let … && … 합성 | 2024 ed. |
gen 블록 | 이터레이터를 yield 문법으로 | 2024 ed. |
| RPIT 캡처 단순화 | impl Trait + use<…> 명시 캡처 | 2024 ed. |
tail call 보장 become | 실험적, 일부 nightly | — |
| const generics 확장 | 표현식 일반화 진행 중 | — |
RPIT 캡처 명시 — use<…>
// 2024 이전: lifetime 자동 캡처가 과도해 빌림 충돌 잦음
// 2024+: + use<...> 로 어떤 제네릭/수명을 "결과 타입에 가둘지" 명시
fn filter_long<'a>(xs: &'a [String])
-> impl Iterator<Item = &'a str> + use<'a>
{
xs.iter().filter(|s| s.len() > 5).map(|s| s.as_str())
}
Edition 2024 마이그레이션
cargo fix --edition --edition-idioms
# Cargo.toml: edition = "2024"
16. unsafe 최소 가이드
unsafe는 "컴파일러가 검증 못 하는 5가지"를 직접 책임지는 키워드다.
- raw pointer 역참조
- 가변 static 접근/수정
- FFI 호출
- 안전하지 않은 트레이트 구현
- 유니온 필드 접근
실무 가이드: unsafe 블록은 좁게, 안전한 wrapper로 감싸 외부엔 안전 API만 노출. // SAFETY: 주석으로 invariant를 문서화. cargo miri로 UB 검출.
17. 실전 패턴
Builder
#[derive(Default)]
struct QueryBuilder { table: String, limit: Option<u32> }
impl QueryBuilder {
fn table(mut self, t: &str) -> Self { self.table = t.into(); self }
fn limit(mut self, n: u32) -> Self { self.limit = Some(n); self }
fn build(self) -> String {
let mut q = format!("SELECT * FROM {}", self.table);
if let Some(n) = self.limit { q.push_str(&format!(" LIMIT {n}")); }
q
}
}
Newtype — 타입 안정성
struct UserId(u64);
struct CompanyId(u64);
// fn fetch(id: UserId) -- CompanyId 넣으면 컴파일 에러
Typestate
struct Conn<S>(PhantomData<S>);
struct Disconnected;
struct Connected;
impl Conn<Disconnected> {
fn connect(self) -> Conn<Connected> { Conn(PhantomData) }
}
impl Conn<Connected> {
fn query(&self) {}
fn close(self) -> Conn<Disconnected> { Conn(PhantomData) }
}
// disconnected 상태에서 query() 호출하면 컴파일 에러
RAII 가드
struct Timer(Instant);
impl Drop for Timer {
fn drop(&mut self) {
println!("elapsed {:?}", self.0.elapsed());
}
}
{ let _t = Timer(Instant::now()); work(); } // 스코프 끝나면 자동 출력
18. 테스트 · 벤치 · 도큐먼테이션
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adds_two() {
assert_eq!(add(2, 3), 5);
}
#[tokio::test]
async fn fetches() {
let r = fetch().await.unwrap();
assert!(!r.is_empty());
}
}
/// 두 수를 더한다.
///
/// # Examples
/// ```
/// use myapp::add;
/// assert_eq!(add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }
cargo test — 단위 + 통합 + 도큐 테스트 모두 실행.
cargo bench — criterion 크레이트 권장.
cargo doc --open — 코드에서 HTML 문서 생성.
19. 다음 학습 경로
- The Rust Programming Language (TRPL) — 공식 책, 무료.
rustup doc --book
- Rust by Example — 짧은 코드 예제 모음.
- Rustlings — 인터랙티브 문제집 (
cargo install rustlings).
- Tokio Tutorial — async 실전.
- The Rustonomicon — unsafe / FFI 깊이.
- Jon Gjengset 유튜브 — 라이브 코딩으로 내부 구조 학습.
이 페이지는 빠르게 훑는 레퍼런스다. 실제로 손에 익히는 가장 빠른 방법은 작은 CLI 하나(예: 파일 검색기, JSON 파서, HTTP 캐시 프록시)를 처음부터 끝까지 만드는 것.
Last updated 2026-05 · Rust stable 1.87 · Edition 2024 · 작성: Claude Code