Rust Memory Master
Rust의 가장 어려운 개념들을 시각적으로 학습하세요.
Rust의 메모리 관리 시스템(Ownership, Borrowing, Lifetimes)은 강력하지만 학습 곡선이 가파릅니다. 이 플랫폼은 복잡한 개념을 직관적인 시각화와 실용적인 예제로 쉽게 이해할 수 있도록 돕습니다.
학습 경로
- 시각화 도구 - Ownership, Borrowing, Lifetimes 예제
- Primitives - Copy trait과 원시 타입
- 퀴즈 - 학습 내용 확인
- 초보 어려움 - 흔한 실수와 해결책
특징
- 인터랙티브 시각화: Canvas 기반 실시간 메모리 다이어그램
- 실용적인 예제: 실제 코드와 함께하는 학습
- 즉시 피드백: 퀴즈로 학습 내용 확인
- 반응형 디자인: 모바일과 데스크톱 모두 지원
시작하기: 위 탭에서 원하는 주제를 선택하거나 학습 경로를 따라 진행하세요.
인터랙티브 시각화 도구
Ownership, Borrowing, Lifetimes를 시각적으로 학습하세요!
(Mobile) 모바일에서 단계별로 확인하세요!
예제 선택
1. Ownership 예제
2. Borrowing 예제
3. Lifetimes 예제
핵심 개념
Ownership = 마법 도서관
- 각 책은 한 명의 주인만 있음
- 주인이 도서관을 나가면 책 사라짐
- 빌려줄 수는 있지만 소유권은 없음
Borrowing = 책 빌리기
- 여러 사람이 동시에 읽기 가능 (&T)
- 한 명만 수정 가능 (&mut T)
- 읽는 중에는 수정 불가
Lifetimes = 대여 기간
- 각 참조는 유효 기간이 있음
- 가장 짧은 기간이 적용됨
- 컴파일러가 자동 추론
Primitives & Copy Trait
원시 타입과 Copy trait을 이해하세요!
예제 선택
1. Copy Trait
원시 타입은 Copy 됩니다!
let x = 5;
let y = x; // Copy!
println!("{}", x); // [OK] OK
2. Move vs Copy
Copy와 Move의 차이를 비교해보세요!
// Copy (i32)
let x = 5;
let y = x; // [OK] 둘 다 유효
// Move (String)
let s1 = String::from("Hello");
let s2 = s1; // ⚠️ s1 무효화
3. 모든 원시 타입
Rust의 모든 원시 타입을 만나보세요!
4️⃣ Stack vs Heap
원시 타입은 Stack에, 컬렉션은 Heap에 저장됩니다!
Primitives 핵심 정리
🎁 Copy Trait = 복사기
- 📄 원본을 그대로 복사
- [OK] 복사본과 원본 모두 유효
- ⚡ 매우 빠른 비트 복사
- 💾 Stack에만 저장
원시 타입 목록
- 정수: i8, i16, i32, i64, i128, isize
- 정수: u8, u16, u32, u64, u128, usize
- 부동소수점: f32, f64
- 불리언: bool (true/false)
- 문자: char (유니코드)
- 튜플: (T1, T2, ...)
성능 팁
- [OK] 가능한 원시 타입 사용: 더 빠름
- [X] 불필요한 String 사용: &str이 더 빠름
- Stack이 Heap보다 10-100배 빠름
퀴즈 시스템
Rust 메모리 관리 개념을 퀴즈로 확인해보세요!
Level 1: Foundation
Rust 기본 개념 퀴즈 (8문제)
이전 진도
비유
Level 1 완료!
상세 결과
초보 개발자가 겪는 어려움
Rust 초보 개발자들이 가장 많이 겪는 7가지 어려움을 시각화하고, 해결책을 제시합니다!
1. "왜 use of moved value 에러가 나나요?"
[X] 흔한 실수
let s1 = String::from("Hello");
let s2 = s1;
println!("{}", s1); // [X] Error!
[OK] 해결책
let s1 = String::from("Hello");
let s2 = s1.clone();
println!("{}", s1); // [OK] OK
2. "왜 cannot borrow as mutable 에러가 나나요?"
[X] 흔한 실수
let s = String::from("Hello");
let r1 = &s;
let r2 = &mut s; // [X] Error!
[OK] 해결책
let mut s = String::from("Hello");
let r1 = &s;
drop(r1);
let r2 = &mut s; // [OK] OK
3. "수명 애너테이션('a)이 뭔가요?"
[X] 에러
fn longest(x: &str, y: &str) -> &str {
// [X] Missing lifetime
if x.len() > y.len() { x } else { y }
}
[OK] 해결책
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
// [OK] Lifetime 명시
if x.len() > y.len() { x } else { y }
}
Python → Rust Ecosystem Mapping
Python 패키지를 Rust crate로 찾기
웹 프레임워크
Flask
마이크로 웹 프레임워크
Axum
현대적인 웹 프레임워크
axum = "0.7"
Django
풀스택 웹 프레임워크
Actix-web
강력한 웹 프레임워크
actix-web = "4"
FastAPI
현대적이고 빠른 API
Axum
타입 안전한 API
axum = "0.7"
데이터 처리
pandas
데이터 분석 라이브러리
Polars
빠른 데이터프레임
polars = "0.36"
numpy
수치 계산 라이브러리
nalgebra
선형 대수 라이브러리
nalgebra = "0.32"
HTTP 클라이언트
requests
사람 친화적 HTTP
reqwest
간편한 HTTP 클라이언트
reqwest = { version = "0.11", features = ["json"] }
비동기 런타임
asyncio
비동기 I/O
tokio
인기 비동기 런타임
tokio = { version = "1", features = ["full"] }
테스팅
pytest
테스트 프레임워크
cargo test
내장 테스트 도구
cargo test
패키지 관리
pip
패키지 설치 도구
cargo
패키지 관리자
cargo add crate_name
CLI
click
CLI 생성 도구
clap
CLI 파서 프레임워크
clap = { version = "4.4", features = ["derive"] }
데이터베이스
SQLAlchemy
ORM 및 SQL 툴킷
Diesel
ORM 및 코드 생성
diesel = { version = "2.1", features = ["postgres"] }
로깅
logging
표준 로깅
log
로깅 파사드
log = "0.4"
총 20개 Python 패키지 매핑 완료
Python ↔ Rust 패턴 비교
Python 코드와 Rust 코드를 비교하여 학습하세요
1. 변수 할당 (Variable Assignment)
Python은 동적 타이핑, Rust는 정적 타이핑 + 타입 추론
Python
# 변수 할당 (타입 추론)
x = 10
name = "Rust"
price = 19.99
# 타입 변경 가능
x = "now a string"
Rust
// 변수 할당 (타입 추론)
let x = 10; // i32 추론
let name = "Rust"; // &str 추론
let price = 19.99; // f64 추론
// 명시적 타입
let x: i32 = 10;
let name: &str = "Rust";
// 변경 가능
let mut x = 10;
x = 20; // OK
핵심 차이점
- Python: 변수는 태그 없이 생성, 언제든 타입 변경 가능
- Rust: let 키워드, 기본적으로 불변(immutable), mut 키워드로 변경 가능(immutable)
- Rust: 타입 추론 지원하지만 컴파일 타임에 타입 결정
2. 함수 정의 (Function Definition)
Python은 타입 힌트(선택), Rust는 타입 명시(필수)
Python
# 함수 정의 (타입 힌트 선택)
def greet(name: str) -> str:
return f"Hello, {name}!"
# 호출
result = greet("World")
print(result)
Rust
// 함수 정의 (타입 명시 필수)
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
// 호출
let result = greet("World");
println!("{}", result);
핵심 차이점
- Python: def 키워드, 타입 힌트는 선택적, 런타임에 타입 검사
- Rust: fn 키워드, 파라미터와 반환 타입 필수, 컴파일 타임에 타입 검사
- Rust: 문자열은 &str (빌림) 또는 String (소유)로 구분
3. 구조체/클래스 (Struct/Class)
Python은 클래스와 메서드, Rust는 구조체와 impl 블록
Python
# 클래스 정의
class Person:
def __init__(self, name: str):
self.name = name
def greet(self) -> str:
return f"Hello, {self.name}!"
# 사용
person = Person("Alice")
print(person.greet())
Rust
// 구조체 정의
struct Person {
name: String,
}
impl Person {
fn new(name: String) -> Self {
Self { name }
}
fn greet(&self) -> String {
format!("Hello, {}!", self.name)
}
}
// 사용
let person = Person::new(String::from("Alice"));
println!("{}", person.greet());
핵심 차이점
- Python: class 키워드, __init__ 생성자, self는 첫 파라미터
- Rust: struct 정의, impl 블록에 메서드, &self 참조
- Rust: new()는 관습이 아니 필수는 아님, Self::new()로 호출
4. 리스트/벡터 연산 (List/Vector Operations)
Python은 리스트 컴프리헨션, Rust는 iterator 체인
Python
# 리스트 컴프리헨션
numbers = [1, 2, 3, 4, 5]
# 제곱
squared = [x**2 for x in numbers]
# [1, 4, 9, 16, 25]
# 필터링
evens = [x for x in numbers if x % 2 == 0]
# [2, 4]
# 맵과 필터 결합
result = [x**2 for x in numbers if x % 2 == 0]
# [4, 16]
Rust
// iterator 체인
let numbers = vec![1, 2, 3, 4, 5];
// 제곱
let squared: Vec = numbers.iter()
.map(|x| x * x)
.collect();
// [1, 4, 9, 16, 25]
// 필터링
let evens: Vec<&i32> = numbers.iter()
.filter(|x| x % 2 == 0)
.collect();
// [2, 4]
// 맵과 필터 결합
let result: Vec = numbers.iter()
.filter(|x| x % 2 == 0)
.map(|x| x * x)
.collect();
// [4, 16]
핵심 차이점
- Python: 리스트 컴프리헨션, 간결한 문법
- Rust: iterator, map/filter/collect 패턴, 지연 평가
- Rust: iter()는 불변 참조, into_iter()는 소비
5. 파일 I/O (File Input/Output)
Python은 with 문, Rust는 Result 타입과 ? 연산자
Python
# 파일 읽기
with open('file.txt', 'r') as f:
content = f.read()
print(content)
# 파일 쓰기
with open('output.txt', 'w') as f:
f.write("Hello, World!")
Rust
// 파일 읽기
use std::fs;
use std::io::Read;
fn read_file() -> std::io::Result {
let mut file = fs::File::open("file.txt")?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
// 간단한 읽기
let content = fs::read_to_string("file.txt")?;
// 파일 쓰기
fs::write("output.txt", "Hello, World!")?;
핵심 차이점
- Python: with 문이 자동으로 리소스 정리, 예외는 raise
- Rust: Result
타입, ? 연산자로 에러 전파 - Rust: fs::read_to_string() 간편 메서드, 명시적 에러 처리 필요
Python → Rust 핵심 개념 매핑
Python의 개념이 Rust에서 어떻게 대응되는지 이해하세요
1. Reference Counting → Ownership
Python: Reference Counting
Python은 모든 것이 객체이고, 참조 카운트로 메모리 관리
a = [1, 2, 3] # 참조 카운트 = 1
b = a # 참조 카운트 = 2
del a # 참조 카운트 = 1
del b # 참조 카운트 = 0 -> 메모리 해제
- 여러 변수가 같은 객체 참조 가능
- 참조 카운트가 0이 되면 자동 해제
- Garbage Collector가 순환 참조 처리
Rust: Ownership
Rust는 소유권(Ownership)으로 메모리 안전성 보장
let a = vec![1, 2, 3]; // a가 소유자
let b = a; // 소유권이 b로 이동 (a는 무효)
// a 사용 불가 - 컴파일 에러!
let b = a.clone(); // 명시적 복제
// a와 b 모두 사용 가능
- 각 값은 소유자가 정확히 하나
- 소유권이 이동하면 이전 변수는 무효화
- Clone으로 명시적 복제 가능
- 컴파일 타임에 메모리 안전성 검사
핵심 차이
Python: 참조 카운트는 런타임 비용, 순환 참조 문제
Rust: 소유권은 컴파일 타임에 결정, 제로 비용
2. Garbage Collection → RAII (Drop Trait)
Python: Garbage Collection
Garbage Collector가 사용하지 않는 메모리 자동 회수
# GC가 자동으로 메모리 관리
def process():
data = [1] * 1000000 # 큰 데이터
return data[0] # 함수 끝나면 data 회수
# GC가 언제 돌지 예측 불가
# 일시 정지(stop-the-world) 발생 가능
- 자동 메모리 회수
- GC 실행 타이밍 예측 불가
- 순간적인 성능 저하 가능
Rust: RAII (Resource Acquisition Is Initialization)
변수가 스코프를 벗어나면 자동으로 리소스 해제
// Drop trait으로 자동 해제
{
let data = vec![1; 1000000];
// data 사용
} // 여기서 자동으로 메모리 해제!
// 명시적 구현도 가능
struct CustomResource;
impl Drop for CustomResource {
fn drop(&mut self) {
// 리소스 정리 코드
}
}
- 스코프 벗어나면 즉시 해제
- 예측 가능한 해제 타이밍
- Zero-cost abstraction
- 명시적으로 Drop trait 구현 가능
핵심 차이
Python: GC는 편리하지만 예측 불가
Rust: RAII는 예측 가능하고 제로 비용
3. Dynamic Typing → Static Typing + Type Inference
Python: Dynamic Typing
변수에 타입이 없고, 런타임에 타입 결정
# 타입 없이 변수 생성
x = 10 # int
x = "hello" # str (타입 변경 가능)
# 런타임에 타입 에러 발견
def add(a, b):
return a + b # 런타임까지 에러를 모름
add(1, 2) # OK
add(1, "hello") # 런타임 에러!
- 유연하고 빠른 prototyping
- 런타임에 타입 에러 발견
- IDE 자동완성이 제한적
Rust: Static Typing + Type Inference
컴파일 타임에 타입 결정, 하지만 타입 추론으로 편리함
// 타입 추론
let x = 10; // i32로 추론
let y = "hello"; // &str로 추론
// 컴파일 타임에 타입 검사
fn add>(a: T, b: T) -> T {
a + b
}
add(1, 2); // OK: i32 + i32
add(1, "hello"); // 컴파일 에러!
- 컴파일 타임에 타입 안전성 보장
- 타입 추론으로 편리함
- 강력한 IDE 지원
- 제로 비용 추상화
핵심 차이
Python: 유연하지만 런타임 에러 위험
Rust: 엄격하지만 컴파일 타임에 에러 발견
4. try/except → Result<T, E>
Python: try/except
예외 처리를 위해 try/except 사용
# 예외 처리
def read_file(filename):
try:
with open(filename) as f:
return f.read()
except FileNotFoundError:
return None
except IOError as e:
print(f"Error: {e}")
return None
# 모든 예외는 try로 잡아야 함
- 명시적 예외 처리
- 예외을 놓치면 프로그램 종료
- 어떤 예외가 발생할지 예측 어려움
Rust: Result<T, E>
성공(Result<T>) 또는 실�(Result<E>)을 타입으로 표현
// Result 타입으로 에러 표현
use std::fs::File;
use std::io::Read;
fn read_file(filename: &str) -> Result {
let mut file = File::open(filename)?; // ?로 에러 전파
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content) // 성공 시 반환
}
// 사용
match read_file("file.txt") {
Ok(content) => println!("{}", content),
Err(e) => println!("Error: {}", e),
}
- 에러를 반환값으로 명시
- ? 연산자로 쉬운 에러 전파
- 컴파일러가 에러 처리 강제
- 타입 안전성 보장
핵심 차이
Python: 예외는 처리 안 하면 전파
Rust: Result는 처리 강제, 더 안전
실전 예제 (Practical Examples)
실제 코드로 Python과 Rust를 비교하세요
예제 1: HTTP 클라이언트 (requests → reqwest)
API에서 데이터 가져오기
Python (requests)
import requests
# GET 요청
response = requests.get('https://api.example.com/users')
data = response.json()
print(f"Status: {response.status_code}")
print(f"Users: {data}")
# POST 요청
payload = {"name": "Alice", "age": 30}
response = requests.post(
'https://api.example.com/users',
json=payload
)
print(f"Created: {response.json()}")
- 간단한 API
- 자동 JSON 처리
- 동기식 (asyncio 사용 필요)
Rust (reqwest + tokio)
use reqwest;
use serde_json::Value;
#[tokio::main]
async fn fetch_users() -> Result<(), Box> {
// GET 요청
let response = reqwest::get("https://api.example.com/users").await?;
let data: Value = response.json().await?;
println!("Status: {}", response.status());
println!("Users: {}", data);
// POST 요청
let payload = serde_json::json!({
"name": "Alice",
"age": 30
});
let response = reqwest::Client::new()
.post("https://api.example.com/users")
.json(&payload)
.send()
.await?;
let created: Value = response.json().await?;
println!("Created: {}", created);
Ok(())
}
- 명시적 에러 처리
- 타입 안전한 JSON
- 기본적으로 비동기 (tokio)
- #[tokio::main] 필요
예제 2: 웹 서버 (Flask → Axum)
REST API 서버 만들기
Python (Flask)
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/')
def home():
return jsonify({"message": "Hello, World!"})
@app.route('/users/', methods=['GET'])
def get_user(user_id):
return jsonify({
"id": user_id,
"name": "Alice"
})
@app.route('/users', methods=['POST'])
def create_user():
data = request.get_json()
return jsonify({
"id": 1,
"name": data['name']
}), 201
if __name__ == '__main__':
app.run(debug=True)
- 간단한 데코레이터
- debug=True 자동 리로드
- 동기식 (비동기는 asyncio 필요)
Rust (Axum + tokio)
use axum::{
Json, Router,
routing::{get, post},
extract::Path,
response::Json as ResponseJson,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Serialize)]
struct HomeResponse {
message: String,
}
async fn home() -> ResponseJson {
Json(HomeResponse {
message: "Hello, World!".to_string(),
})
}
#[derive(Deserialize)]
struct UserPath {
user_id: u32,
}
async fn get_user(Path(user_id): Path) -> ResponseJson {
Json(serde_json::json!({
"id": user_id,
"name": "Alice"
}))
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(home))
.route("/users/:user_id", get(get_user));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
- 타입 안전한 라우팅
- 기본적으로 비동기
- 명시적 상태 관리
- 더 높은 성능
예제 3: 데이터 처리 (pandas → Polars)
CSV 파일 읽고 분석하기
Python (pandas)
import pandas as pd
# CSV 읽기
df = pd.read_csv('data.csv')
# 필터링
filtered = df[df['age'] > 25]
# 집계
stats = df.groupby('department')['salary'].agg([
('mean', 'mean'),
('sum', 'sum')
])
# 컬럼 선택
result = df[['name', 'salary']]
print(filtered)
print(stats)
print(result)
- 직관적인 API
- lazy evaluation 없음
- 메모리 낭비 가능
Rust (Polars)
use polars::prelude::*;
fn process_data() -> PolarsResult {
// CSV 읽기
let df = CsvReader::from_path("data.csv")?
.finish()?;
// 필터링
let filtered = df
.lazy()
.filter(col("age").gt(lit(25)))
.collect()?;
// 집계
let stats = df
.lazy()
.group_by([col("department")])
.agg([
col("salary").mean().alias("mean"),
col("salary").sum().alias("sum"),
])
.collect()?;
// 컬럼 선택
let result = df.select([
col("name"),
col("salary"),
]);
println!("{}", filtered);
println!("{}", stats);
println!("{}", result);
Ok(result)
}
- Lazy evaluation (성능)
- 메모리 효율적
- 더 빠른 처리 속도
- 다중 스레드 처리