Split View: Rust 프로그래밍 입문 가이드 — 소유권, 라이프타임, 안전한 시스템 프로그래밍
Rust 프로그래밍 입문 가이드 — 소유권, 라이프타임, 안전한 시스템 프로그래밍
목차
- 왜 Rust인가
- 소유권(Ownership)
- 라이프타임
- 열거형과 패턴 매칭
- 에러 처리
- 구조체와 트레이트
- 비동기 프로그래밍
- Cargo 패키지 매니저
- 실전 Rust
- Rust in 2026
1. 왜 Rust인가
1.1 C/C++의 문제점
시스템 프로그래밍 분야에서 C와 C++은 수십 년간 지배적인 위치를 차지해 왔습니다. 하지만 이 언어들은 근본적인 문제를 안고 있습니다.
| 문제 유형 | 설명 | 결과 |
|---|---|---|
| 버퍼 오버플로 | 배열 경계를 넘는 접근 | 보안 취약점 |
| 댕글링 포인터 | 해제된 메모리를 참조 | 정의되지 않은 동작 |
| 이중 해제 | 같은 메모리를 두 번 해제 | 크래시 또는 보안 위협 |
| 데이터 레이스 | 동시 접근 시 동기화 부재 | 비결정적 버그 |
| 널 포인터 역참조 | null 값을 역참조 | 프로그램 크래시 |
Microsoft에 따르면, Windows 보안 취약점의 약 70%가 메모리 안전성 문제에서 기인합니다. Google의 Chrome 팀 역시 유사한 수치를 보고했습니다.
1.2 Rust의 해답
Rust는 컴파일 타임에 메모리 안전성을 보장하면서도 가비지 컬렉터(GC) 없이 동작합니다. 이것이 Rust를 독특하게 만드는 핵심입니다.
- 제로 코스트 추상화: 런타임 오버헤드 없는 고수준 추상화
- 소유권 시스템: 컴파일러가 메모리를 추적
- 스레드 안전성: 데이터 레이스를 컴파일 타임에 방지
- 풍부한 타입 시스템: 많은 버그를 컴파일 타임에 잡음
1.3 Rust의 인기 상승
Stack Overflow 설문조사에서 Rust는 2016년부터 2025년까지 10년 연속 "가장 사랑받는 언어" 1위를 차지했습니다. 2024~2026년 사이에는 실제 산업 채택률도 크게 증가했습니다.
- Linux 커널: Rust를 공식 제2 언어로 채택
- Android: 새로운 시스템 컴포넌트에 Rust 사용
- AWS: Firecracker, Bottlerocket 등 핵심 인프라에 Rust 활용
- Microsoft: Windows 커널 코드를 Rust로 재작성 시작
- Cloudflare: 네트워크 프록시를 Rust로 전환
2. 소유권(Ownership)
2.1 소유권의 3가지 규칙
Rust의 핵심 개념인 소유권은 3가지 규칙으로 요약됩니다.
- Rust의 모든 값은 소유자(owner) 가 있다
- 한 시점에 소유자는 단 하나뿐이다
- 소유자가 스코프를 벗어나면 값은 자동으로 해제된다
fn main() {
let s1 = String::from("hello"); // s1이 String의 소유자
let s2 = s1; // 소유권이 s2로 이동(move)
// println!("{}", s1); // 컴파일 에러! s1은 더 이상 유효하지 않음
println!("{}", s2); // OK
} // s2가 스코프를 벗어나면 메모리 해제
2.2 이동(Move)과 복사(Copy)
기본적으로 Rust는 값을 이동(move) 합니다. 단, Copy 트레이트를 구현한 타입은 복사됩니다.
// 정수형은 Copy 트레이트 구현 - 복사됨
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y); // 둘 다 사용 가능
// String은 Copy 미구현 - 이동됨
let s1 = String::from("hello");
let s2 = s1; // 이동!
// s1은 사용 불가
// 명시적 복사가 필요하면 clone 사용
let s3 = String::from("world");
let s4 = s3.clone();
println!("s3 = {}, s4 = {}", s3, s4); // 둘 다 사용 가능
Copy를 구현하는 타입들: 정수(i32, u64 등), 부동소수점(f32, f64), 불리언(bool), 문자(char), 이들로만 구성된 튜플.
2.3 빌림(Borrowing)과 참조
소유권을 넘기지 않고 값을 사용하려면 참조(reference) 를 사용합니다.
fn calculate_length(s: &String) -> usize {
s.len()
} // s는 참조이므로 여기서 해제되지 않음
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 빌림 (불변 참조)
println!("'{}' 의 길이: {}", s1, len); // s1 여전히 유효
}
2.4 가변 참조와 불변 참조
Rust는 참조에 대해 엄격한 규칙을 적용합니다.
fn main() {
let mut s = String::from("hello");
// 불변 참조는 여러 개 동시에 가능
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// 가변 참조는 하나만 가능
let r3 = &mut s;
r3.push_str(", world");
println!("{}", r3);
// 불변 참조와 가변 참조를 동시에 가질 수 없음
// let r4 = &s;
// let r5 = &mut s; // 컴파일 에러!
}
이 규칙 덕분에 데이터 레이스가 컴파일 타임에 방지됩니다.
2.5 슬라이스
슬라이스는 컬렉션의 일부를 참조하는 방법입니다.
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let s = String::from("hello world");
let word = first_word(&s);
println!("첫 번째 단어: {}", word); // "hello"
}
3. 라이프타임
3.1 라이프타임이 필요한 이유
라이프타임은 참조가 유효한 범위를 나타냅니다. 컴파일러는 참조가 가리키는 데이터보다 오래 살아남지 않도록 보장해야 합니다.
// 이 코드는 컴파일되지 않음 - 댕글링 참조 방지
// fn dangle() -> &String {
// let s = String::from("hello");
// &s // s가 함수 끝에서 해제되므로 참조가 무효
// }
// 올바른 방법: 값을 직접 반환
fn no_dangle() -> String {
let s = String::from("hello");
s // 소유권이 호출자에게 이동
}
3.2 라이프타임 어노테이션
컴파일러가 참조의 유효 기간을 추론할 수 없을 때, 명시적 라이프타임 어노테이션이 필요합니다.
// 두 문자열 슬라이스 중 긴 것을 반환
// 반환값의 라이프타임은 두 인자 중 짧은 쪽을 따름
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
println!("더 긴 문자열: {}", result); // 여기서는 OK
}
// println!("{}", result); // string2가 해제되어 에러 가능
}
3.3 구조체의 라이프타임
구조체가 참조를 포함할 때도 라이프타임을 명시해야 합니다.
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("주목! {}", announcement);
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence;
{
let i = novel.find('.').unwrap_or(novel.len());
first_sentence = &novel[..i];
}
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("인용: {}", excerpt.part);
}
3.4 정적 라이프타임
'static 라이프타임은 프로그램 전체 실행 기간 동안 유효한 참조를 의미합니다.
// 문자열 리터럴은 항상 'static
let s: &'static str = "이 문자열은 프로그램 전체 수명 동안 유효합니다";
// 주의: 'static을 남용하지 마세요
// 대부분의 경우 더 짧은 라이프타임이면 충분합니다
3.5 라이프타임 생략 규칙
Rust 컴파일러는 3가지 규칙으로 라이프타임을 자동 추론합니다.
- 각 참조 매개변수는 고유한 라이프타임을 받는다
- 참조 매개변수가 하나뿐이면, 반환 타입의 라이프타임은 그것을 따른다
- 메서드에서
&self또는&mut self가 있으면, 반환 타입의 라이프타임은self를 따른다
// 라이프타임 생략 적용 - 규칙 2
fn first_word(s: &str) -> &str {
// 컴파일러가 fn first_word<'a>(s: &'a str) -> &'a str 로 추론
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
4. 열거형과 패턴 매칭
4.1 열거형(enum)
Rust의 열거형은 다른 언어보다 훨씬 강력합니다. 각 변형(variant)에 데이터를 포함할 수 있습니다.
// 기본 열거형
enum Direction {
Up,
Down,
Left,
Right,
}
// 데이터를 포함하는 열거형
enum Message {
Quit, // 데이터 없음
Move { x: i32, y: i32 }, // 이름 있는 필드
Write(String), // 단일 String
ChangeColor(i32, i32, i32), // 세 개의 i32
}
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("종료"),
Message::Move { x, y } => println!("이동: ({}, {})", x, y),
Message::Write(text) => println!("메시지: {}", text),
Message::ChangeColor(r, g, b) => {
println!("색상 변경: ({}, {}, {})", r, g, b)
}
}
}
}
4.2 Option 타입
Rust에는 null이 없습니다. 대신 Option 열거형을 사용합니다.
enum Option<T> {
None,
Some(T),
}
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
if denominator == 0.0 {
None
} else {
Some(numerator / denominator)
}
}
fn main() {
let result = divide(10.0, 3.0);
// match로 처리
match result {
Some(value) => println!("결과: {:.2}", value),
None => println!("0으로 나눌 수 없습니다"),
}
// if let으로 간결하게
if let Some(value) = divide(10.0, 0.0) {
println!("결과: {}", value);
} else {
println!("나눗셈 실패");
}
// unwrap_or로 기본값 지정
let safe_result = divide(10.0, 0.0).unwrap_or(0.0);
println!("안전한 결과: {}", safe_result);
}
4.3 패턴 매칭(match)
match는 Rust에서 가장 강력한 제어 흐름 구문 중 하나입니다.
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
enum UsState {
Alabama,
Alaska,
Arizona,
// ...
}
fn value_in_cents(coin: &Coin) -> u8 {
match coin {
Coin::Penny => {
println!("행운의 동전!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("주 쿼터: {:?}", state);
25
}
}
}
4.4 고급 패턴 매칭
fn classify_number(n: i32) {
match n {
// 범위 매칭
1..=12 => println!("{}월", n),
13..=19 => println!("십대"),
// 가드 조건
x if x % 2 == 0 => println!("짝수: {}", x),
x if x < 0 => println!("음수: {}", x),
// 다중 패턴
20 | 30 | 40 => println!("10의 배수"),
// 나머지 모두
_ => println!("기타: {}", n),
}
}
// 구조체 디스트럭처링
struct Point {
x: f64,
y: f64,
}
fn describe_point(point: &Point) {
match point {
Point { x: 0.0, y: 0.0 } => println!("원점"),
Point { x, y: 0.0 } => println!("x축 위: {}", x),
Point { x: 0.0, y } => println!("y축 위: {}", y),
Point { x, y } => println!("({}, {})", x, y),
}
}
5. 에러 처리
5.1 Result 타입
Rust는 복구 가능한 에러에 Result를 사용합니다.
use std::fs::File;
use std::io::{self, Read};
enum Result<T, E> {
Ok(T),
Err(E),
}
fn read_file_content(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn main() {
match read_file_content("config.toml") {
Ok(content) => println!("파일 내용: {}", content),
Err(e) => eprintln!("파일 읽기 실패: {}", e),
}
}
5.2 ? 연산자
? 연산자는 에러 전파를 간결하게 만듭니다.
use std::fs;
use std::io;
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
IoError(io::Error),
ParseError(ParseIntError),
}
impl From<io::Error> for AppError {
fn from(e: io::Error) -> Self {
AppError::IoError(e)
}
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self {
AppError::ParseError(e)
}
}
fn read_and_parse(path: &str) -> Result<i32, AppError> {
let content = fs::read_to_string(path)?; // io::Error -> AppError
let number = content.trim().parse::<i32>()?; // ParseIntError -> AppError
Ok(number * 2)
}
5.3 anyhow와 thiserror
실전에서는 anyhow와 thiserror 크레이트를 많이 사용합니다.
// thiserror - 라이브러리용 구체적인 에러 타입 정의
use thiserror::Error;
#[derive(Error, Debug)]
enum DatabaseError {
#[error("연결 실패: {0}")]
ConnectionFailed(String),
#[error("쿼리 실행 에러: {0}")]
QueryError(String),
#[error("레코드를 찾을 수 없음: {id}")]
NotFound { id: u64 },
}
// anyhow - 애플리케이션용 간편한 에러 처리
use anyhow::{Context, Result};
fn load_config() -> Result<Config> {
let content = fs::read_to_string("config.toml")
.context("설정 파일을 읽을 수 없습니다")?;
let config: Config = toml::from_str(&content)
.context("설정 파일 파싱에 실패했습니다")?;
Ok(config)
}
5.4 panic과 복구 불가능한 에러
// panic!은 프로그램을 즉시 중단
fn check_positive(n: i32) {
if n < 0 {
panic!("음수가 입력되었습니다: {}", n);
}
}
// 배열 인덱스 범위 초과도 panic
let v = vec![1, 2, 3];
// let element = v[99]; // panic!
// 안전한 접근
match v.get(99) {
Some(value) => println!("값: {}", value),
None => println!("인덱스 범위 초과"),
}
6. 구조체와 트레이트
6.1 구조체(struct) 정의
#[derive(Debug, Clone)]
struct User {
username: String,
email: String,
age: u32,
active: bool,
}
impl User {
// 생성자 패턴 (관례적으로 new)
fn new(username: String, email: String, age: u32) -> Self {
User {
username,
email,
age,
active: true,
}
}
// 메서드 (&self로 불변 빌림)
fn is_adult(&self) -> bool {
self.age >= 18
}
// 가변 메서드 (&mut self)
fn deactivate(&mut self) {
self.active = false;
}
// 소유권을 가져가는 메서드
fn into_username(self) -> String {
self.username
}
}
fn main() {
let mut user = User::new(
"rustacean".to_string(),
"rust@example.com".to_string(),
25,
);
println!("성인 여부: {}", user.is_adult());
user.deactivate();
println!("사용자: {:?}", user);
}
6.2 트레이트(Trait)
트레이트는 공유 동작을 정의하는 방법입니다. 다른 언어의 인터페이스와 유사합니다.
trait Summary {
fn summarize_author(&self) -> String;
// 기본 구현 제공 가능
fn summarize(&self) -> String {
format!("{}님의 글 더 읽기...", self.summarize_author())
}
}
struct Article {
title: String,
author: String,
content: String,
}
impl Summary for Article {
fn summarize_author(&self) -> String {
self.author.clone()
}
fn summarize(&self) -> String {
format!("{}, by {} - {}...",
self.title, self.author,
&self.content[..50.min(self.content.len())]
)
}
}
struct Tweet {
username: String,
content: String,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
// summarize는 기본 구현 사용
}
6.3 제네릭과 Trait Bound
// 제네릭 함수 + 트레이트 바운드
fn notify(item: &impl Summary) {
println!("속보! {}", item.summarize());
}
// 동일한 표현 (트레이트 바운드 문법)
fn notify_verbose<T: Summary>(item: &T) {
println!("속보! {}", item.summarize());
}
// 복수의 트레이트 바운드
fn display_and_summarize<T: Summary + std::fmt::Display>(item: &T) {
println!("표시: {}", item);
println!("요약: {}", item.summarize());
}
// where 절로 가독성 향상
fn complex_function<T, U>(t: &T, u: &U) -> String
where
T: Summary + Clone,
U: std::fmt::Display + std::fmt::Debug,
{
format!("{} - {:?}", t.summarize(), u)
}
6.4 제네릭 구조체
struct Stack<T> {
elements: Vec<T>,
}
impl<T> Stack<T> {
fn new() -> Self {
Stack {
elements: Vec::new(),
}
}
fn push(&mut self, item: T) {
self.elements.push(item);
}
fn pop(&mut self) -> Option<T> {
self.elements.pop()
}
fn peek(&self) -> Option<&T> {
self.elements.last()
}
fn is_empty(&self) -> bool {
self.elements.is_empty()
}
fn size(&self) -> usize {
self.elements.len()
}
}
// 특정 타입에 대한 추가 구현
impl<T: std::fmt::Display> Stack<T> {
fn print_top(&self) {
if let Some(top) = self.peek() {
println!("최상단: {}", top);
}
}
}
7. 비동기 프로그래밍
7.1 async/await 기초
Rust의 비동기 프로그래밍은 async/await 키워드를 사용합니다.
// async fn은 Future를 반환
async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
let response = reqwest::get(url).await?;
let body = response.text().await?;
Ok(body)
}
// async 블록
let future = async {
let data = fetch_data("https://api.example.com/data").await;
println!("데이터 수신 완료");
data
};
7.2 Tokio 런타임
Rust의 가장 인기 있는 비동기 런타임인 Tokio를 사용하는 방법입니다.
use tokio;
use std::time::Duration;
#[tokio::main]
async fn main() {
// 동시에 여러 작업 실행
let (result1, result2) = tokio::join!(
async_task("작업1", 2),
async_task("작업2", 1),
);
println!("결과: {:?}, {:?}", result1, result2);
}
async fn async_task(name: &str, seconds: u64) -> String {
println!("{} 시작", name);
tokio::time::sleep(Duration::from_secs(seconds)).await;
println!("{} 완료", name);
format!("{} 결과", name)
}
7.3 채널과 동시성
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
// 다중 생산자, 단일 소비자 채널
let (tx, mut rx) = mpsc::channel::<String>(100);
// 여러 생산자 생성
for i in 0..5 {
let tx_clone = tx.clone();
tokio::spawn(async move {
let msg = format!("메시지 {}", i);
tx_clone.send(msg).await.unwrap();
});
}
// 원본 tx 드롭 (모든 생산자가 끝나면 채널 종료)
drop(tx);
// 소비자
while let Some(message) = rx.recv().await {
println!("수신: {}", message);
}
}
7.4 Mutex와 공유 상태
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = tokio::spawn(async move {
let mut num = counter.lock().await;
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
println!("최종 카운터: {}", *counter.lock().await);
}
8. Cargo 패키지 매니저
8.1 Cargo.toml 구조
[package]
name = "my-project"
version = "0.1.0"
edition = "2021"
authors = ["Developer <dev@example.com>"]
description = "A sample Rust project"
license = "MIT"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
anyhow = "1.0"
thiserror = "2.0"
clap = { version = "4", features = ["derive"] }
[dev-dependencies]
criterion = "0.5"
mockall = "0.13"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
8.2 주요 Cargo 명령어
# 프로젝트 생성
cargo new my-project # 바이너리 프로젝트
cargo new my-lib --lib # 라이브러리 프로젝트
# 빌드와 실행
cargo build # 디버그 빌드
cargo build --release # 릴리스 빌드
cargo run # 빌드 후 실행
cargo run -- --arg1 value # 인자 전달
# 테스트
cargo test # 모든 테스트 실행
cargo test test_name # 특정 테스트만
cargo test -- --nocapture # 출력 표시
# 검사와 분석
cargo check # 컴파일 확인 (빌드보다 빠름)
cargo clippy # 린트 검사
cargo fmt # 코드 포매팅
# 문서
cargo doc --open # 문서 생성 후 브라우저 열기
# 의존성 관리
cargo add serde # 의존성 추가
cargo update # 의존성 업데이트
cargo tree # 의존성 트리 확인
8.3 워크스페이스
대규모 프로젝트는 워크스페이스로 관리합니다.
# 루트 Cargo.toml
[workspace]
members = [
"crates/core",
"crates/api",
"crates/cli",
]
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
# crates/core/Cargo.toml
[package]
name = "my-core"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
8.4 Crate 배포
# crates.io 계정 설정
cargo login
# 배포 전 검사
cargo publish --dry-run
# 실제 배포
cargo publish
9. 실전 Rust
9.1 CLI 도구 (clap)
use clap::Parser;
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(name = "file-analyzer")]
#[command(about = "파일 분석 도구", long_about = None)]
struct Cli {
/// 분석할 파일 경로
#[arg(short, long)]
path: PathBuf,
/// 출력 형식
#[arg(short, long, default_value = "text")]
format: String,
/// 상세 출력
#[arg(short, long)]
verbose: bool,
/// 최대 깊이
#[arg(short, long, default_value_t = 3)]
depth: u32,
}
fn main() {
let cli = Cli::parse();
if cli.verbose {
println!("파일 경로: {:?}", cli.path);
println!("출력 형식: {}", cli.format);
println!("최대 깊이: {}", cli.depth);
}
analyze_file(&cli.path, cli.depth);
}
fn analyze_file(path: &PathBuf, depth: u32) {
println!("분석 중: {:?} (깊이: {})", path, depth);
// 파일 분석 로직...
}
9.2 웹 서버 (Axum)
use axum::{
extract::{Path, State},
http::StatusCode,
response::Json,
routing::{get, post},
Router,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Todo {
id: u64,
title: String,
completed: bool,
}
type AppState = Arc<Mutex<Vec<Todo>>>;
#[tokio::main]
async fn main() {
let state: AppState = Arc::new(Mutex::new(Vec::new()));
let app = Router::new()
.route("/todos", get(list_todos).post(create_todo))
.route("/todos/:id", get(get_todo))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("서버 실행: http://localhost:3000");
axum::serve(listener, app).await.unwrap();
}
async fn list_todos(
State(state): State<AppState>,
) -> Json<Vec<Todo>> {
let todos = state.lock().await;
Json(todos.clone())
}
async fn create_todo(
State(state): State<AppState>,
Json(payload): Json<CreateTodo>,
) -> (StatusCode, Json<Todo>) {
let mut todos = state.lock().await;
let todo = Todo {
id: todos.len() as u64 + 1,
title: payload.title,
completed: false,
};
todos.push(todo.clone());
(StatusCode::CREATED, Json(todo))
}
#[derive(Deserialize)]
struct CreateTodo {
title: String,
}
async fn get_todo(
State(state): State<AppState>,
Path(id): Path<u64>,
) -> Result<Json<Todo>, StatusCode> {
let todos = state.lock().await;
todos
.iter()
.find(|t| t.id == id)
.cloned()
.map(Json)
.ok_or(StatusCode::NOT_FOUND)
}
9.3 WebAssembly (WASM)
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
if n <= 1 {
return n as u64;
}
let mut a: u64 = 0;
let mut b: u64 = 1;
for _ in 2..=n {
let temp = b;
b = a + b;
a = temp;
}
b
}
#[wasm_bindgen]
pub struct ImageProcessor {
width: u32,
height: u32,
pixels: Vec<u8>,
}
#[wasm_bindgen]
impl ImageProcessor {
#[wasm_bindgen(constructor)]
pub fn new(width: u32, height: u32) -> Self {
ImageProcessor {
width,
height,
pixels: vec![0; (width * height * 4) as usize],
}
}
pub fn grayscale(&mut self) {
for chunk in self.pixels.chunks_mut(4) {
let avg = ((chunk[0] as u16 + chunk[1] as u16 + chunk[2] as u16) / 3) as u8;
chunk[0] = avg;
chunk[1] = avg;
chunk[2] = avg;
}
}
pub fn pixels(&self) -> *const u8 {
self.pixels.as_ptr()
}
}
10. Rust in 2026
10.1 Linux 커널의 Rust
Linux 6.1(2022)부터 Rust가 공식적으로 커널에 도입되었고, 2026년 현재 드라이버, 파일시스템, 네트워크 서브시스템 등에서 Rust 코드가 확대되고 있습니다.
- 드라이버: GPU, NVMe 등 새로운 드라이버를 Rust로 작성
- 바인딩: 기존 C 커널 API에 대한 안전한 Rust 바인딩 확충
- 도구:
rustfmt,clippy등 커널 Rust 툴체인 안정화
10.2 Android와 모바일
Google은 Android의 새로운 시스템 컴포넌트를 Rust로 작성하고 있습니다.
- Bluetooth 스택, 키스토어, DNS 리졸버 등에 Rust 적용
- 메모리 안전 취약점이 크게 감소
- Android의 Rust 코드 비율이 지속적으로 증가
10.3 클라우드 인프라
AWS, Azure, GCP 모두 핵심 인프라에 Rust를 활용하고 있습니다.
| 조직 | 프로젝트 | 설명 |
|---|---|---|
| AWS | Firecracker | 서버리스 VM 관리 |
| AWS | Bottlerocket | 컨테이너 전용 OS |
| Cloudflare | Pingora | HTTP 프록시 엔진 |
| Discord | 메시지 인프라 | 읽기 상태 서비스 재작성 |
| Figma | 서버 사이드 | 멀티플레이어 서버 |
| 1Password | 핵심 엔진 | 크로스 플랫폼 코어 |
10.4 Rust의 미래 전망
2026년 기준으로 Rust의 미래는 밝습니다.
성장 영역:
- 임베디드/IoT:
no_std환경에서의 안전한 프로그래밍 - 게임 개발: Bevy 엔진의 성숙
- AI/ML 인프라: PyTorch, Hugging Face 등에서 핵심 컴포넌트로 Rust 사용
- 블록체인: Solana, Polkadot 등 주요 블록체인이 Rust 기반
언어 발전:
- 비동기 개선: async trait 안정화, async closure
- 에디션: Rust 2024 에디션의 안정화
- GAT: 제네릭 연관 타입의 활용 확대
- 컴파일 속도: 빌드 시간 지속 개선
마무리
Rust는 단순히 C/C++의 대체제가 아닙니다. 소유권 시스템과 타입 시스템을 통해 안전성과 성능을 동시에 달성하는 새로운 패러다임을 제시합니다.
Rust를 배우는 과정에서 "소유권 싸움(fighting the borrow checker)"을 경험하게 되지만, 이는 컴파일러가 런타임에 발생할 버그를 미리 잡아주는 것입니다. 시간이 지나면 소유권 시스템이 자연스러워지고, 다른 언어에서도 더 안전한 코드를 작성하게 됩니다.
Rust 입문자에게 권장하는 학습 순서:
- The Rust Programming Language (The Book) 읽기
- Rustlings 연습 문제 풀기
- 소규모 CLI 도구 만들기
- 웹 서버 또는 API 구축
- 오픈소스 프로젝트 기여
Rust의 커뮤니티는 친절하고 활발합니다. 공식 포럼, Discord, 그리고 Reddit의 r/rust에서 도움을 받을 수 있습니다. 2026년, Rust는 시스템 프로그래밍의 미래를 이끌어가고 있습니다.
Rust Programming Beginner Guide — Ownership, Lifetimes, and Safe Systems Programming
Table of Contents
- Why Rust
- Ownership
- Lifetimes
- Enums and Pattern Matching
- Error Handling
- Structs and Traits
- Async Programming
- Cargo Package Manager
- Rust in Practice
- Rust in 2026
1. Why Rust
1.1 The Problems with C/C++
C and C++ have dominated systems programming for decades. However, these languages carry fundamental problems.
| Issue Type | Description | Consequence |
|---|---|---|
| Buffer Overflow | Accessing beyond array bounds | Security vulnerabilities |
| Dangling Pointer | Referencing freed memory | Undefined behavior |
| Double Free | Freeing the same memory twice | Crashes or security threats |
| Data Race | Lack of synchronization on concurrent access | Non-deterministic bugs |
| Null Pointer Dereference | Dereferencing a null value | Program crash |
According to Microsoft, approximately 70% of Windows security vulnerabilities stem from memory safety issues. Google's Chrome team has reported similar figures.
1.2 Rust's Answer
Rust guarantees memory safety at compile time while operating without a garbage collector (GC). This is what makes Rust unique.
- Zero-cost abstractions: High-level abstractions with no runtime overhead
- Ownership system: The compiler tracks memory
- Thread safety: Data races prevented at compile time
- Rich type system: Many bugs caught at compile time
1.3 Rust's Rising Popularity
In Stack Overflow surveys, Rust was the "most loved language" for 10 consecutive years from 2016 to 2025. Between 2024 and 2026, industry adoption has also increased significantly.
- Linux Kernel: Adopted Rust as its official second language
- Android: Using Rust for new system components
- AWS: Core infrastructure like Firecracker and Bottlerocket built with Rust
- Microsoft: Began rewriting Windows kernel code in Rust
- Cloudflare: Transitioning network proxies to Rust
2. Ownership
2.1 The Three Rules of Ownership
Rust's core concept of ownership is summarized by three rules:
- Every value in Rust has an owner
- There can only be one owner at a time
- When the owner goes out of scope, the value is automatically dropped
fn main() {
let s1 = String::from("hello"); // s1 owns the String
let s2 = s1; // Ownership moves to s2
// println!("{}", s1); // Compile error! s1 is no longer valid
println!("{}", s2); // OK
} // s2 goes out of scope, memory is freed
2.2 Move and Copy
By default, Rust moves values. However, types that implement the Copy trait are copied instead.
// Integers implement Copy trait - they are copied
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y); // Both usable
// String does not implement Copy - it is moved
let s1 = String::from("hello");
let s2 = s1; // Move!
// s1 is no longer usable
// Use clone for explicit copies
let s3 = String::from("world");
let s4 = s3.clone();
println!("s3 = {}, s4 = {}", s3, s4); // Both usable
Types that implement Copy: integers (i32, u64, etc.), floating-point numbers (f32, f64), booleans (bool), characters (char), and tuples composed only of these types.
2.3 Borrowing and References
To use a value without transferring ownership, use a reference.
fn calculate_length(s: &String) -> usize {
s.len()
} // s is a reference, so it is not dropped here
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // Borrowing (immutable reference)
println!("The length of '{}' is {}", s1, len); // s1 still valid
}
2.4 Mutable and Immutable References
Rust enforces strict rules on references.
fn main() {
let mut s = String::from("hello");
// Multiple immutable references are allowed simultaneously
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// Only one mutable reference is allowed
let r3 = &mut s;
r3.push_str(", world");
println!("{}", r3);
// Cannot have immutable and mutable references simultaneously
// let r4 = &s;
// let r5 = &mut s; // Compile error!
}
Thanks to these rules, data races are prevented at compile time.
2.5 Slices
Slices are a way to reference a portion of a collection.
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let s = String::from("hello world");
let word = first_word(&s);
println!("First word: {}", word); // "hello"
}
3. Lifetimes
3.1 Why Lifetimes Are Needed
Lifetimes indicate the scope for which a reference is valid. The compiler must ensure that a reference does not outlive the data it points to.
// This code won't compile - prevents dangling references
// fn dangle() -> &String {
// let s = String::from("hello");
// &s // s is freed at the end of the function, reference becomes invalid
// }
// Correct approach: return the value directly
fn no_dangle() -> String {
let s = String::from("hello");
s // Ownership moves to the caller
}
3.2 Lifetime Annotations
When the compiler cannot infer the validity period of references, explicit lifetime annotations are required.
// Returns the longer of two string slices
// The return value's lifetime follows the shorter of the two arguments
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
println!("The longer string is: {}", result); // OK here
}
// println!("{}", result); // Could error since string2 has been dropped
}
3.3 Lifetimes in Structs
When a struct contains references, lifetimes must be specified.
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention! {}", announcement);
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence;
{
let i = novel.find('.').unwrap_or(novel.len());
first_sentence = &novel[..i];
}
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("Quote: {}", excerpt.part);
}
3.4 Static Lifetime
The 'static lifetime indicates a reference that is valid for the entire duration of the program.
// String literals are always 'static
let s: &'static str = "This string is valid for the entire program lifetime";
// Warning: Don't overuse 'static
// In most cases a shorter lifetime is sufficient
3.5 Lifetime Elision Rules
The Rust compiler automatically infers lifetimes using three rules:
- Each reference parameter gets its own unique lifetime
- If there is only one reference parameter, the return type's lifetime follows it
- In methods, if
&selfor&mut selfexists, the return type's lifetime followsself
// Lifetime elision applied - Rule 2
fn first_word(s: &str) -> &str {
// Compiler infers: fn first_word<'a>(s: &'a str) -> &'a str
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
4. Enums and Pattern Matching
4.1 Enums
Rust's enums are far more powerful than those in other languages. Each variant can contain data.
// Basic enum
enum Direction {
Up,
Down,
Left,
Right,
}
// Enum with data
enum Message {
Quit, // No data
Move { x: i32, y: i32 }, // Named fields
Write(String), // Single String
ChangeColor(i32, i32, i32), // Three i32 values
}
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move: ({}, {})", x, y),
Message::Write(text) => println!("Message: {}", text),
Message::ChangeColor(r, g, b) => {
println!("Change color: ({}, {}, {})", r, g, b)
}
}
}
}
4.2 The Option Type
Rust has no null. Instead, it uses the Option enum.
enum Option<T> {
None,
Some(T),
}
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
if denominator == 0.0 {
None
} else {
Some(numerator / denominator)
}
}
fn main() {
let result = divide(10.0, 3.0);
// Handle with match
match result {
Some(value) => println!("Result: {:.2}", value),
None => println!("Cannot divide by zero"),
}
// Concise with if let
if let Some(value) = divide(10.0, 0.0) {
println!("Result: {}", value);
} else {
println!("Division failed");
}
// Default value with unwrap_or
let safe_result = divide(10.0, 0.0).unwrap_or(0.0);
println!("Safe result: {}", safe_result);
}
4.3 Pattern Matching (match)
match is one of the most powerful control flow constructs in Rust.
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
enum UsState {
Alabama,
Alaska,
Arizona,
// ...
}
fn value_in_cents(coin: &Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter: {:?}", state);
25
}
}
}
4.4 Advanced Pattern Matching
fn classify_number(n: i32) {
match n {
// Range matching
1..=12 => println!("Month {}", n),
13..=19 => println!("Teenager"),
// Guard conditions
x if x % 2 == 0 => println!("Even: {}", x),
x if x < 0 => println!("Negative: {}", x),
// Multiple patterns
20 | 30 | 40 => println!("Multiple of 10"),
// Catch all
_ => println!("Other: {}", n),
}
}
// Struct destructuring
struct Point {
x: f64,
y: f64,
}
fn describe_point(point: &Point) {
match point {
Point { x: 0.0, y: 0.0 } => println!("Origin"),
Point { x, y: 0.0 } => println!("On x-axis: {}", x),
Point { x: 0.0, y } => println!("On y-axis: {}", y),
Point { x, y } => println!("({}, {})", x, y),
}
}
5. Error Handling
5.1 The Result Type
Rust uses Result for recoverable errors.
use std::fs::File;
use std::io::{self, Read};
enum Result<T, E> {
Ok(T),
Err(E),
}
fn read_file_content(path: &str) -> Result<String, io::Error> {
let mut file = File::open(path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
fn main() {
match read_file_content("config.toml") {
Ok(content) => println!("File content: {}", content),
Err(e) => eprintln!("Failed to read file: {}", e),
}
}
5.2 The ? Operator
The ? operator makes error propagation concise.
use std::fs;
use std::io;
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
IoError(io::Error),
ParseError(ParseIntError),
}
impl From<io::Error> for AppError {
fn from(e: io::Error) -> Self {
AppError::IoError(e)
}
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self {
AppError::ParseError(e)
}
}
fn read_and_parse(path: &str) -> Result<i32, AppError> {
let content = fs::read_to_string(path)?; // io::Error -> AppError
let number = content.trim().parse::<i32>()?; // ParseIntError -> AppError
Ok(number * 2)
}
5.3 anyhow and thiserror
In practice, the anyhow and thiserror crates are widely used.
// thiserror - Define specific error types for libraries
use thiserror::Error;
#[derive(Error, Debug)]
enum DatabaseError {
#[error("Connection failed: {0}")]
ConnectionFailed(String),
#[error("Query execution error: {0}")]
QueryError(String),
#[error("Record not found: {id}")]
NotFound { id: u64 },
}
// anyhow - Convenient error handling for applications
use anyhow::{Context, Result};
fn load_config() -> Result<Config> {
let content = fs::read_to_string("config.toml")
.context("Failed to read configuration file")?;
let config: Config = toml::from_str(&content)
.context("Failed to parse configuration file")?;
Ok(config)
}
5.4 panic and Unrecoverable Errors
// panic! immediately terminates the program
fn check_positive(n: i32) {
if n < 0 {
panic!("Negative number received: {}", n);
}
}
// Array index out of bounds also panics
let v = vec![1, 2, 3];
// let element = v[99]; // panic!
// Safe access
match v.get(99) {
Some(value) => println!("Value: {}", value),
None => println!("Index out of bounds"),
}
6. Structs and Traits
6.1 Defining Structs
#[derive(Debug, Clone)]
struct User {
username: String,
email: String,
age: u32,
active: bool,
}
impl User {
// Constructor pattern (conventionally named new)
fn new(username: String, email: String, age: u32) -> Self {
User {
username,
email,
age,
active: true,
}
}
// Method (borrows &self immutably)
fn is_adult(&self) -> bool {
self.age >= 18
}
// Mutable method (&mut self)
fn deactivate(&mut self) {
self.active = false;
}
// Method that takes ownership
fn into_username(self) -> String {
self.username
}
}
fn main() {
let mut user = User::new(
"rustacean".to_string(),
"rust@example.com".to_string(),
25,
);
println!("Is adult: {}", user.is_adult());
user.deactivate();
println!("User: {:?}", user);
}
6.2 Traits
Traits define shared behavior. They are similar to interfaces in other languages.
trait Summary {
fn summarize_author(&self) -> String;
// Can provide a default implementation
fn summarize(&self) -> String {
format!("Read more from {}...", self.summarize_author())
}
}
struct Article {
title: String,
author: String,
content: String,
}
impl Summary for Article {
fn summarize_author(&self) -> String {
self.author.clone()
}
fn summarize(&self) -> String {
format!("{}, by {} - {}...",
self.title, self.author,
&self.content[..50.min(self.content.len())]
)
}
}
struct Tweet {
username: String,
content: String,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
// Uses default implementation for summarize
}
6.3 Generics and Trait Bounds
// Generic function + trait bounds
fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
// Equivalent expression (trait bound syntax)
fn notify_verbose<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
// Multiple trait bounds
fn display_and_summarize<T: Summary + std::fmt::Display>(item: &T) {
println!("Display: {}", item);
println!("Summary: {}", item.summarize());
}
// where clause for readability
fn complex_function<T, U>(t: &T, u: &U) -> String
where
T: Summary + Clone,
U: std::fmt::Display + std::fmt::Debug,
{
format!("{} - {:?}", t.summarize(), u)
}
6.4 Generic Structs
struct Stack<T> {
elements: Vec<T>,
}
impl<T> Stack<T> {
fn new() -> Self {
Stack {
elements: Vec::new(),
}
}
fn push(&mut self, item: T) {
self.elements.push(item);
}
fn pop(&mut self) -> Option<T> {
self.elements.pop()
}
fn peek(&self) -> Option<&T> {
self.elements.last()
}
fn is_empty(&self) -> bool {
self.elements.is_empty()
}
fn size(&self) -> usize {
self.elements.len()
}
}
// Additional implementation for specific types
impl<T: std::fmt::Display> Stack<T> {
fn print_top(&self) {
if let Some(top) = self.peek() {
println!("Top: {}", top);
}
}
}
7. Async Programming
7.1 async/await Basics
Rust's async programming uses the async/await keywords.
// async fn returns a Future
async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
let response = reqwest::get(url).await?;
let body = response.text().await?;
Ok(body)
}
// async blocks
let future = async {
let data = fetch_data("https://api.example.com/data").await;
println!("Data received");
data
};
7.2 The Tokio Runtime
Here is how to use Tokio, Rust's most popular async runtime.
use tokio;
use std::time::Duration;
#[tokio::main]
async fn main() {
// Run multiple tasks concurrently
let (result1, result2) = tokio::join!(
async_task("Task 1", 2),
async_task("Task 2", 1),
);
println!("Results: {:?}, {:?}", result1, result2);
}
async fn async_task(name: &str, seconds: u64) -> String {
println!("{} started", name);
tokio::time::sleep(Duration::from_secs(seconds)).await;
println!("{} completed", name);
format!("{} result", name)
}
7.3 Channels and Concurrency
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
// Multi-producer, single-consumer channel
let (tx, mut rx) = mpsc::channel::<String>(100);
// Create multiple producers
for i in 0..5 {
let tx_clone = tx.clone();
tokio::spawn(async move {
let msg = format!("Message {}", i);
tx_clone.send(msg).await.unwrap();
});
}
// Drop original tx (channel closes when all producers are done)
drop(tx);
// Consumer
while let Some(message) = rx.recv().await {
println!("Received: {}", message);
}
}
7.4 Mutex and Shared State
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = tokio::spawn(async move {
let mut num = counter.lock().await;
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
println!("Final counter: {}", *counter.lock().await);
}
8. Cargo Package Manager
8.1 Cargo.toml Structure
[package]
name = "my-project"
version = "0.1.0"
edition = "2021"
authors = ["Developer <dev@example.com>"]
description = "A sample Rust project"
license = "MIT"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
anyhow = "1.0"
thiserror = "2.0"
clap = { version = "4", features = ["derive"] }
[dev-dependencies]
criterion = "0.5"
mockall = "0.13"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
8.2 Essential Cargo Commands
# Create a project
cargo new my-project # Binary project
cargo new my-lib --lib # Library project
# Build and run
cargo build # Debug build
cargo build --release # Release build
cargo run # Build and run
cargo run -- --arg1 value # Pass arguments
# Testing
cargo test # Run all tests
cargo test test_name # Run specific test
cargo test -- --nocapture # Show output
# Inspection and analysis
cargo check # Check compilation (faster than build)
cargo clippy # Lint checks
cargo fmt # Code formatting
# Documentation
cargo doc --open # Generate docs and open in browser
# Dependency management
cargo add serde # Add dependency
cargo update # Update dependencies
cargo tree # Show dependency tree
8.3 Workspaces
Large projects are managed using workspaces.
# Root Cargo.toml
[workspace]
members = [
"crates/core",
"crates/api",
"crates/cli",
]
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
# crates/core/Cargo.toml
[package]
name = "my-core"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { workspace = true }
8.4 Publishing a Crate
# Set up crates.io account
cargo login
# Pre-publish check
cargo publish --dry-run
# Publish
cargo publish
9. Rust in Practice
9.1 CLI Tools (clap)
use clap::Parser;
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(name = "file-analyzer")]
#[command(about = "File analysis tool", long_about = None)]
struct Cli {
/// File path to analyze
#[arg(short, long)]
path: PathBuf,
/// Output format
#[arg(short, long, default_value = "text")]
format: String,
/// Verbose output
#[arg(short, long)]
verbose: bool,
/// Maximum depth
#[arg(short, long, default_value_t = 3)]
depth: u32,
}
fn main() {
let cli = Cli::parse();
if cli.verbose {
println!("File path: {:?}", cli.path);
println!("Output format: {}", cli.format);
println!("Max depth: {}", cli.depth);
}
analyze_file(&cli.path, cli.depth);
}
fn analyze_file(path: &PathBuf, depth: u32) {
println!("Analyzing: {:?} (depth: {})", path, depth);
// File analysis logic...
}
9.2 Web Server (Axum)
use axum::{
extract::{Path, State},
http::StatusCode,
response::Json,
routing::{get, post},
Router,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Todo {
id: u64,
title: String,
completed: bool,
}
type AppState = Arc<Mutex<Vec<Todo>>>;
#[tokio::main]
async fn main() {
let state: AppState = Arc::new(Mutex::new(Vec::new()));
let app = Router::new()
.route("/todos", get(list_todos).post(create_todo))
.route("/todos/:id", get(get_todo))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("Server running: http://localhost:3000");
axum::serve(listener, app).await.unwrap();
}
async fn list_todos(
State(state): State<AppState>,
) -> Json<Vec<Todo>> {
let todos = state.lock().await;
Json(todos.clone())
}
async fn create_todo(
State(state): State<AppState>,
Json(payload): Json<CreateTodo>,
) -> (StatusCode, Json<Todo>) {
let mut todos = state.lock().await;
let todo = Todo {
id: todos.len() as u64 + 1,
title: payload.title,
completed: false,
};
todos.push(todo.clone());
(StatusCode::CREATED, Json(todo))
}
#[derive(Deserialize)]
struct CreateTodo {
title: String,
}
async fn get_todo(
State(state): State<AppState>,
Path(id): Path<u64>,
) -> Result<Json<Todo>, StatusCode> {
let todos = state.lock().await;
todos
.iter()
.find(|t| t.id == id)
.cloned()
.map(Json)
.ok_or(StatusCode::NOT_FOUND)
}
9.3 WebAssembly (WASM)
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u64 {
if n <= 1 {
return n as u64;
}
let mut a: u64 = 0;
let mut b: u64 = 1;
for _ in 2..=n {
let temp = b;
b = a + b;
a = temp;
}
b
}
#[wasm_bindgen]
pub struct ImageProcessor {
width: u32,
height: u32,
pixels: Vec<u8>,
}
#[wasm_bindgen]
impl ImageProcessor {
#[wasm_bindgen(constructor)]
pub fn new(width: u32, height: u32) -> Self {
ImageProcessor {
width,
height,
pixels: vec![0; (width * height * 4) as usize],
}
}
pub fn grayscale(&mut self) {
for chunk in self.pixels.chunks_mut(4) {
let avg = ((chunk[0] as u16 + chunk[1] as u16 + chunk[2] as u16) / 3) as u8;
chunk[0] = avg;
chunk[1] = avg;
chunk[2] = avg;
}
}
pub fn pixels(&self) -> *const u8 {
self.pixels.as_ptr()
}
}
10. Rust in 2026
10.1 Rust in the Linux Kernel
Since Linux 6.1 (2022), Rust has been officially integrated into the kernel. As of 2026, Rust code is expanding across drivers, filesystems, and network subsystems.
- Drivers: New drivers for GPU, NVMe, etc. written in Rust
- Bindings: Safe Rust bindings for existing C kernel APIs expanding
- Tooling: Kernel Rust toolchain with
rustfmt,clippystabilized
10.2 Android and Mobile
Google is writing new Android system components in Rust.
- Rust applied to Bluetooth stack, keystore, DNS resolver, and more
- Memory safety vulnerabilities significantly reduced
- The proportion of Rust code in Android continues to grow
10.3 Cloud Infrastructure
AWS, Azure, and GCP all leverage Rust for core infrastructure.
| Organization | Project | Description |
|---|---|---|
| AWS | Firecracker | Serverless VM management |
| AWS | Bottlerocket | Container-optimized OS |
| Cloudflare | Pingora | HTTP proxy engine |
| Discord | Message infra | Read state service rewrite |
| Figma | Server-side | Multiplayer server |
| 1Password | Core engine | Cross-platform core |
10.4 The Future of Rust
As of 2026, the future of Rust looks bright.
Growth Areas:
- Embedded/IoT: Safe programming in
no_stdenvironments - Game Development: Maturation of the Bevy engine
- AI/ML Infrastructure: Rust used as core components in PyTorch, Hugging Face, and more
- Blockchain: Major blockchains like Solana and Polkadot are Rust-based
Language Evolution:
- Async improvements: Stabilization of async traits and async closures
- Editions: Stabilization of the Rust 2024 edition
- GAT: Expanded use of Generic Associated Types
- Compile speed: Continuous improvements to build times
Conclusion
Rust is more than just a replacement for C/C++. Through its ownership system and type system, it presents a new paradigm that achieves safety and performance simultaneously.
While learning Rust, you will experience "fighting the borrow checker," but this is the compiler catching bugs at compile time that would otherwise appear at runtime. Over time, the ownership system becomes natural, and you will find yourself writing safer code in other languages as well.
Recommended learning path for Rust beginners:
- Read The Rust Programming Language (The Book)
- Work through Rustlings exercises
- Build a small CLI tool
- Construct a web server or API
- Contribute to open-source projects
Rust's community is friendly and active. You can get help on the official forums, Discord, and Reddit's r/rust. In 2026, Rust is leading the future of systems programming.