Rust 기초 강좌
러스트의 모든 문법을 설명하면 너무 길어지므로, 가장 기초적인 내용만 작성합니다.
1. 시작하기
1.1 Hello World
fn main() {
println!("Hello World!");
}
첫 번째 Rust 프로그램입니다. main
함수는 프로그램의 진입점이며, println!
매크로를 사용해 텍스트를 출력합니다.
1.2 주석
// 한 줄 주석
/* 여러 줄
주석 */
1.3 서식화된 출력
println!("{} 더하기 {} 은 {}", 1, 2, 1 + 2);
println!("{:?}", (3, 4)); // 디버그 출력
println!("{0}야 얘는 내 친구 {1}야. {1}야 이쪽은 {0}야", "영희", "철수");
2. 기본 자료형
2.1 숫자형
let i: i32 = 42; // 정수
let f: f64 = 3.14; // 부동소수점
let b: bool = true; // 불리언
2.2 복합 자료형
// 튜플
let tuple = (1, "hello", 3.14);
// 배열
let array = [1, 2, 3, 4, 5];
2.3 캐스팅
let decimal = 65.4321_f32;
let integer = decimal as u8; // f32를 u8로 캐스팅
let character = integer as char; // u8을 char로 캐스팅
// 숫자형 간의 캐스팅
let a = 15; // i32
let b = a as i64; // i32를 i64로 캐스팅
let c = 256.3_f32 as i32; // f32를 i32로 캐스팅
2.4 타입 접미사
let x = 42u8; // u8 타입
let y = 123_456i32; // i32 타입
let z = 3.14f64; // f64 타입
let big = 1_000_000; // 가독성을 위한 언더스코어 사용
2.5 타입 추론
let inferred_type = 42; // i32로 자동 추론
let float = 3.0; // f64로 자동 추론
let v = vec![1, 2, 3]; // Vec<i32>로 자동 추론
// 문맥 기반 타입 추론
let elem = v[0]; // v의 요소 타입에 따라 추론
2.6 타입 알리어싱
type Distance = i32;
type Coordinates = (i32, i32);
// 타입 알리어스 사용
let distance: Distance = 5;
let position: Coordinates = (10, 20);
// 복잡한 타입에 대한 알리어싱
type Result<T> = std::result::Result<T, std::io::Error>;
type PlayerMap = HashMap<String, Vec<(i32, i32)>>;
3. 변수와 가변성
let x = 5; // 불변 변수
let mut y = 10; // 가변 변수
y = 15; // 값 변경 가능
4. 함수
표현식 (expression)
표현식은 값을 반환하는 코드입니다.
표현식은 세미콜론(;)으로 끝나지 않습니다.
마지막 표현식의 값이 함수의 반환값이 됩니다.
함수 기초
fn add(a: i32, b: i32) -> i32 {
a + b // 암시적 반환
// return a + b; // 명시적 반환
}
fn main() {
let result = add(5, 3);
println!("결과: {}", result);
}
5. 제어문
5.1 if 표현식
let number = 5;
if number < 10 {
println!("10보다 작음");
} else {
println!("10보다 크거나 같음");
}
5.2 let if 표현식
let condition = true;
let number = if condition {
5
} else {
6
}; // 세미콜론 필요
// 모든 분기가 같은 타입이어야 함
let number = if condition {
5 // i32
} else {
6 // i32
};
// 다른 타입을 반환하면 컴파일 에러
let number = if condition {
5 // i32
} else {
"six" // &str - 컴파일 에러!
};
5.2 if let 표현식
// Option 값을 처리할 때 유용
let some_value = Some(3);
if let Some(x) = some_value {
println!("값은 {}", x);
} else {
println!("값이 없음");
}
// Result 값을 처리할 때도 사용 가능
let result = Ok(5);
if let Ok(value) = result {
println!("성공: {}", value);
}
// 열거형(enum) 매칭에도 유용
enum Color {
Red,
Blue,
Green
}
let color = Color::Red;
if let Color::Red = color {
println!("빨간색입니다");
}
5.3 반복문
// for 반복문
for i in 0..5 {
println!("{}", i);
}
// while 반복문
let mut n = 0;
while n < 5 {
println!("{}", n);
n += 1;
}
// loop 반복문
loop { ... } // while true { ... } 과 같음
6. 소유권과 대여
6.1 소유권 규칙
복사는 Copy/Clone 트레이트 참조
let s1 = String::from("hello");
let s2 = s1; // s1의 소유권이 s2로 이동
// println!("{}", s1); // 컴파일 에러!
6.2 참조
참조는 소유권 이동이 발생하지 않습니다.
가변참조의 경우 역참조(*d)
를 해야 합니다.
let a = 1;
let mut b = 2;
let c = &a;
println!("{}", a);
println!("{}", c);
let d = &mut b;
*d = 3;
println!("{}", b);
7. 구조체
7.1 구조체의 종류
// C 스타일 구조체
struct Point {
x: f32,
y: f32,
}
// 유닛 구조체
struct Unit;
// 튜플 구조체
struct Pair(i32, f32);
let pair = Pair(1, 0.1);
println!("pair contains {:?} and {:?}", pair.0, pair.1);
struct User {
username: String,
email: String,
active: bool,
}
let user = User {
username: String::from("rust_user"),
email: String::from("user@example.com"),
active: true,
};
7.2 유닛 구조체
struct Logger;
trait Logging {
fn log(&self, message: &str);
}
impl Logging for Logger {
fn log(&self, message: &str) {
println!("Log: {}", message);
}
}
let logger = Logger;
logger.log("테스트 메시지");
7.3 구조체 메소드
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 인스턴스 메소드 - &self를 첫 번째 매개변수로 받음
fn area(&self) -> u32 {
self.width * self.height
}
// 연관 함수(Associated Function) - self를 매개변수로 받지 않음
// 주로 생성자로 사용됨 (예: String::from())
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
}
fn main() {
// 연관 함수 호출
let rect = Rectangle::new(30, 50);
// 메소드 호출
println!("사각형의 넓이: {}", rect.area());
}
7.4 라이프타임 파라미터
라이프타임 파라미터 'a
는 구조체가 유효한 동안
참조형이 바라보는 값의 유효성을 보장합니다.
컴파일 시점에 유효성을 체크해서,
오류를 발생시키기 위한 것으로,
생명주기를 변경하거나 하는 것은 아닙니다.
struct User<'a> {
username: &'a String,
email: &'a String,
active: bool,
}
let username = String::from("rust_user");
let email = String::from("user@example.com");
let user = User {
username: &username,
email: &email,
active: true,
};
8. 열거형과 패턴 매칭
// 1. Option 열거형
enum Option<T> {
Some(T),
None,
}
let some_number = Some(5);
match some_number {
Some(n) => println!("숫자: {}", n),
None => println!("값이 없음"),
}
// 2. 다른 타입을 포함하는 열거형
enum Shape {
Circle(f64), // 반지름
Rectangle(f64, f64), // 가로, 세로
Triangle{a: f64, b: f64} // 밑변, 높이
}
let shape = Shape::Rectangle(10.0, 20.0);
match shape {
Shape::Circle(r) => println!("원: 반지름 = {}", r),
Shape::Rectangle(w, h) => println!("사각형: {}x{}", w, h),
Shape::Triangle{a, b} => println!("삼각형: 밑변={}, 높이={}", a, b),
}
// 3. impl로 메서드 구현
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle{a, b} => a * b / 2.0,
}
}
}
9. 에러 처리
use std::fs::File;
fn main() {
let file_result = File::open("hello.txt");
match file_result {
Ok(file) => println!("파일 열기 성공"),
Err(error) => println!("에러: {}", error),
}
}
10. 트레이트
트레이트는 다른 언어의 인터페이스와 유사합니다.
10.1 주요 트레이트
Copy와 Clone
// Copy는 값의 비트 단위 복사를 수행
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 1, y: 2 };
let p2 = p1;
println!("{}, {}", p1.x, p1.y);
println!("{}, {}", p2.x, p2.y);
// String 은 Copy 사용불가
#[derive(Clone)]
struct User {
username: String,
email: String,
active: bool,
}
let u1 = User { username: String::from("someone"), email: String::from("<EMAIL>"), active: true};
let u2 = u1.clone();
println!("{}, {}, {}", u1.username, u1.email, u1.active);
println!("{}, {}, {}", u2.username, u2.email, u2.active);
// Copy 를 구현하지 않았으므로 소유권이 이동합니다.
let u3 = u1;
println!("{}, {}, {}", u3.username, u3.email, u3.active);
// println!("{}, {}, {}", u1.username, u1.email, u1.active); // 컴파일러 오류
Display와 Debug
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
let rectangle = Rectangle { width: 30, height: 50 };
// Debug 출력
println!("{:?}", rectangle);
// Rectangle { width: 30, height: 50 }
println!("{:#?}", rectangle);
// Rectangle {
// width: 30,
// height: 50,
// }
// Display 구현
impl std::fmt::Display for Rectangle {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}x{}", self.width, self.height)
}
}
// Display 출력
println!("{}", rectangle);
// 30x50
From과 Into
명시적 캐스팅(casting) 을 작성합니다.
암시적 캐스팅(Into) 는 자동생성됩니다.
struct User {
username: String,
email: String,
active: bool,
}
struct UserWithAge {
username: String,
email: String,
age: i32,
active: bool,
}
impl From<User> for UserWithAge {
fn from(user: User) -> Self {
UserWithAge {
username: user.username,
email: user.email,
active: user.active,
age: 0, // Default age value
}
}
}
impl From<UserWithAge> for User {
fn from(user: UserWithAge) -> Self {
User {
username: user.username,
email: user.email,
active: user.active,
}
}
}
let user = User {
username: "john".to_string(),
email: "john@example.com".to_string(),
active: true,
};
// From 사용
let user_with_age: UserWithAge = UserWithAge::from(user);
// Into 사용
let user: User = user_with_age.into();
TryFrom과 TryInto
From과 Into와 유사하지만 변환이 실패할 수 있는 경우에 사용됩니다.
Result 타입을 반환하여 실패 가능성을 명시적으로 처리합니다.
use std::convert::TryFrom;
struct NonNegativeNumber(i32);
impl TryFrom<i32> for NonNegativeNumber {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value >= 0 {
Ok(NonNegativeNumber(value))
} else {
Err("음수는 허용되지 않습니다.".to_string())
}
}
}
impl TryInto<i32> for NonNegativeNumber {
type Error = String;
fn try_into(self) -> Result<i32, Self::Error> {
Ok(self.0)
}
}
// TryFrom 사용
let positive = NonNegativeNumber::try_from(42);
let negative = NonNegativeNumber::try_from(-42);
match positive {
Ok(value) => {
println!("success: {}", value.0);
// TryInto 타입을 명시적으로 지정
let val: Result<i32, String> = value.try_into();
match val {
Ok(v) => println!("original value: {}", v),
Err(e) => println!("conversion failed: {}", e)
}
}
Err(e) => {
println!("failed: {}", e);
}
}
match negative {
Ok(value) => {
println!("success: {}", value.0);
}
Err(e) => {
println!("failed: {}", e);
}
}
Default
숫자형의 디폴트값은 0 또는 0.0 입니다.
String 의 디폴트값은 "" 입니다.
#[derive(Default)]
struct Settings {
timeout: u32,
port: u16,
}
let settings = Settings::default();
PartialEq와 Eq
PartialEq 는 ==
, !=
연산자를 사용 가능하게 해줍니다.
Eq 는 완전 동등성(반사성, 대칭성, 추이성)이 보장될 때 사용됩니다.
// PartialEq와 Eq를 derive하여 구조체의 동등성 비교가 가능하게 됩니다
#[derive(PartialEq, Eq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 1, y: 2 };
let p3 = Point { x: 3, y: 4 };
// == 연산자를 사용하여 비교 가능
println!("p1 == p2: {}", p1 == p2); // true
println!("p1 == p3: {}", p1 == p3); // false
// != 연산자도 사용 가능
println!("p1 != p3: {}", p1 != p3); // true
// Vec 등의 컬렉션에서 contains 메서드 사용 가능
let points = vec![p1, p3];
println!("Contains p2: {}", points.contains(&p2)); // true (p2는 p1과 같으므로)
}
// 열거형(enum)에도 적용 가능
#[derive(PartialEq, Eq)]
enum Status {
Active,
Inactive,
Pending,
}
fn check_status() {
let status1 = Status::Active;
let status2 = Status::Active;
let status3 = Status::Pending;
println!("status1 == status2: {}", status1 == status2); // true
println!("status1 == status3: {}", status1 == status3); // false
}
부동소수점 타입(f32, f64)은 NaN 때문에 Eq를 구현하지 않으며,
PartialEq만 구현합니다:
let x = f64::NAN;
assert!(x != x); // true - NaN은 자기 자신과도 같지 않습니다
Drop
struct Resource {
data: String,
}
impl Drop for Resource {
fn drop(&mut self) {
// Resource 가 메모리 해제될 때 호출됨
println!("리소스 해제: {}", self.data);
}
}
10.2 트레이트 구현
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
headline: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}: {}", self.headline, self.content)
}
}
let article = NewsArticle {
headline: String::from("Newspaper headline"),
content: String::from("Newspaper content"),
};
println!("1 new article was created: {}", article.summarize());
10.3 트레이트 바운드
기본 트레이트 바운드
// T는 Display와 Clone 트레이트를 구현해야 함
fn print_and_clone<T: Display + Clone>(t: T) {
println!("{}", t);
let _cloned = t.clone();
}
where 절을 사용한 트레이트 바운드
fn complex_function<T, U>(t: T, u: U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
println!("{}", t);
println!("{:?}", u);
42
}
10.4 트레이트 객체 동적 디스패치
trait Draw {
fn draw(&self);
}
struct Button {
label: String,
}
impl Draw for Button {
fn draw(&self) {
println!("그리기: {}", self.label);
}
}
// 트레이트 객체를 사용한 동적 디스패치
// Draw 트레이트를 구현한 모든 타입을 받을 수 있습니다.
// 컴파일 타임에 형이 확정되지 않으므로 `dyn` 을 붙여주어야 합니다.
fn draw_ui(ui_element: &dyn Draw) {
ui_element.draw();
}
let button = Button {label: "button".to_string()};
draw_ui(&button);
10.5 제네릭 트레이트 바운드
use std::ops::Add;
// 컴파일러 오류
// + 연산자는 Add 트레이트가 구현되어 있어야 하는데
// 제네릭을 적용하면 Add 트레이트가 구현되지 않는 경우도 있어
// 컴파일러 오류가 발생한다.
// fn sum<T>(a: T, b: T) -> T {
// a + b
// }
// Add 트레이트가 구현된 타입만 받도록 제한
fn sum<T: Add<Output = T>>(a: T, b: T) -> T {
a + b
}
// 사용 예시
let int_sum = sum(5, 10);
let float_sum = sum(3.14, 2.86);
impl Add for Point {
type Output = Point;
fn add(self, rhs: Self) -> Self::Output {
Point {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
let point_sum = sum(Point { x: 1, y: 2 }, Point { x: 3, y: 4 });
11. 클로저
11.1 기본 문법
// 기본 클로저 (타입 추론)
// 컴파일 시점에 타입이 확정되므로 제네릭이 아님.
let add = |x, y| x + y;
let result = add(5, 3);
let result2 = add(5.0, 3.0); // 컴파일 오류!
// 타입 명시
let multiply: fn(i32, i32) -> i32 = |x, y| x * y;
11.2 클로저 캡처
주변 변수를 클로저 내부로 캡쳐합니다.
let mut list = vec![1, 2, 3];
let mut borrows = || list.push(4);
println!("{:?}", &list);
// move 키워드로 소유권 이전
let list = vec![1, 2, 3];
let owns = move || println!("소유: {:?}", list);
11.3 클로저 트레이트 (캡쳐 구현방식)
Fn/FnMut/FnOnce 는 컴파일러에 의해 자동으로 결정됨
// Fn - 불변 참조로 캡처 (값을 변경시키지 않음)
let text = String::from("Hello");
let print = || println!("{}", text);
// FnMut - 가변 참조로 캡처 (값을 변경)
let mut counter = 0;
let mut increment = || {
counter += 1;
println!("{}", counter);
};
// FnOnce - 소유권 획득
let text = String::from("Hello");
let consume = move || {
println!("{}", text);
// 값을 해제
};
// move 를 명시하지 않아도 FnOnce 으로 작동
let mut list = vec![1, 2, 3];
let mut borrows = || drop(list); // drop은 소유권을 필요로 함
borrows();
11.4 Iterator와 클로저
.iter() 는 언제나 원본 데이터를 변경시키지 않습니다.
let numbers = vec![1, 2, 3, 4, 5];
// map
let doubled: Vec<_> = numbers.iter().map(|x| x * 2).collect();
// doubled = [2, 4, 6, 8, 10]
// filter
let evens: Vec<_> = numbers.iter().filter(|x| *x % 2 == 0).collect();
// evens = [2, 4]
// fold
let sum = numbers.iter()
.fold(0, // 초기값 0으로 시작
|acc, x| // acc: 누적값, x: 현재 요소
acc + x);
// sum = 15
// chain
let more_numbers = vec![6, 7, 8];
let combined: Vec<_> = numbers.iter()
.chain(more_numbers.iter())
.collect();
// combined = [1, 2, 3, 4, 5, 6, 7, 8]
// .iter_mut() : 가변 참조(mutable reference)
let mut numbers = vec![1, 2, 3];
numbers.iter_mut().for_each(|x| *x *= 2);
// numbers = [2, 4, 6]
// .into_iter() : 소유권 이동
let numbers = vec![1, 2, 3];
let doubled: Vec<_> = numbers.into_iter().map(|x| x * 2).collect();
// numbers는 이제 사용할 수 없습니다
11.5 고차 함수
fn apply<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32
{
f(x)
}
let double = |x| x * 2;
let result = apply(double, 5);
// 클로저 반환
fn create_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n
}
let add_five = create_adder(5);
let result = add_five(10); // 15
12. 모듈과 패키지 시스템
12.1 모듈 기본
// lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
fn seat_at_table() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
// main.rs
use RustTest::eat_at_restaurant;
fn main() {
eat_at_restaurant();
}
12.2 파일 시스템 기반 모듈
my_project/
├── Cargo.toml
└── src/
├── main.rs
├── lib.rs
└── models/
├── mod.rs
├── user.rs
└── order.rs
// models/mod.rs
pub mod user;
pub mod order;
// models/user.rs
pub struct User {
pub name: String,
pub age: u32,
}
// main.rs
use crate::models::user::User;
12.3 가시성과 프라이버시
mod back_of_house {
#[derive(Debug)]
pub struct Breakfast {
pub (crate) toast: String, // 프로젝트 한정 public
seasonal_fruit: String, // private 필드
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast { // 전체 public
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("복숭아"),
}
}
}
}
use back_of_house::Breakfast;
fn main() {
let breakfast = Breakfast::summer("toast");
println!("{:#?}", &breakfast);;
}
12.4 use 키워드
use std::collections::HashMap;
// use std::io;
// use std::io::Write;
use std::io::{self, Write};
// 다시 내보내기
pub use crate::front_of_house::hosting;
// as 키워드로 이름 변경
use std::io::Error as IoError;
// glob 연산자 (모든 공개 항목 가져오기)
use std::collections::*;
12.5 패키지와 Cargo.toml
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = "1.0"
[dev-dependencies]
pretty_assertions = "1.0"
[workspace]
members = [
"core",
"cli",
"web",
]
12.6 작업공간(Workspace)
workspace/
├── Cargo.toml
├── core/
│ ├── Cargo.toml
│ └── src/
├── cli/
│ ├── Cargo.toml
│ └── src/
└── web/
├── Cargo.toml
└── src/
12.7 외부 크레이트 사용
// Cargo.toml에 의존성 추가 후
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Point {
x: i32,
y: i32,
}
13. 스마트 포인터
-
힙 메모리에 할당
대용량 데이터에 대해 메모리 할당이 가능해집니다.
메모리 해제가 자동으로 이루어집니다. -
재귀적 데이터 구조 지원
데이타의 크기가 컴파일 시점에 고정될 수 없는 재귀적인 데이타 구조를 지원합니다.
-
Deref 트레이트 구현
역참조를 하지 않아도 됩니다.
- 자동 역참조는 메소드 호출 또는 적절한 트레이트가 구현되어 있을때 호출됨
-
메모리 누수 방지
메모리 누수 같은 문제를 컴파일 시점에 방지합니다.
13.1 Box<T>
#[derive(Debug)]
struct Person {
name: String,
age: u8,
email: String,
}
fn main() {
// 힙에 Person 구조체 할당
let person = Box::new(Person {
name: String::from("Alice"),
age: 30,
email: String::from("alice@example.com"),
});
// 할당된 데이터 출력
println!("Person: {:?}", person);
// 구조체 필드 접근
println!("Name: {}", person.name);
println!("Age: {}", person.age);
println!("Email: {}", person.email);
// Box를 통해 소유권 이동
let moved_person = person; // person의 소유권이 moved_person으로 이동
println!("Moved Person: {:?}", moved_person);
// person은 더 이상 사용할 수 없음 (소유권 이동으로 인해)
// println!("{:?}", person); // 컴파일 에러 발생
}
13.2 Rc<T>
(Reference Counting)
동일한 데이타를 참조합니다.
기본적으로 데이타 수정이 불가능합니다.
데이타를 수정하려면 Rc<RefCell<Vec<i32>>>
와 같이 RefCell
를 사용해야 합니다.
use std::rc::Rc;
// 여러 소유자
let data = Rc::new(vec![1, 2, 3]);
let data2 = Rc::clone(&data);
let data3 = Rc::clone(&data);
println!("참조 카운트: {}", Rc::strong_count(&data));
// 순환 참조 방지
struct Node {
next: Option<Rc<RefCell<Node>>>,
value: i32,
}
let node1 = Rc::new(RefCell::new(Node { next: None, value: 1 }));
let node2 = Rc::new(RefCell::new(Node { next: Some(Rc::clone(&node1)), value: 2 }));
node1.borrow_mut().next = Some(Rc::clone(&node2)); // 순환 참조 발생
// 순환 참조 해결은 아래에서 설명 (Weak)
13.3 RefCell<T>
borrow_mut()
를 이용해 가변 참조를 가져올 수 있습니다.
use std::cell::RefCell;
// 내부 가변성
let data = RefCell::new(5);
// 런타임 borrowing 규칙 체크
let mut mut_ref = data.borrow_mut();
*mut_ref += 1;
drop(mut_ref); // 명시적 반환
let ref_one = data.borrow();
println!("데이터: {}", *ref_one);
13.4 Arc<T>
(Atomic Reference Counting)
Arc는 여러 스레드가 동일한 데이터를 소유하고 공유할 수 있도록 해줍니다.
use std::sync::Arc;
use std::thread;
// 스레드 간 공유
let data = Arc::new(vec![1, 2, 3, 4]);
let mut handles = vec![];
for i in 0..3 {
let data_clone = Arc::clone(&data);
handles.push(thread::spawn(move || {
// Arc 값의 소유권을 이동시킵니다.
println!("스레드 {}: {:?}", i, *data_clone);
i
}));
}
for handle in handles {
// handle.join() 은 스레드가 종료하기를 기다립니다.
let result = handle.join();
match result {
Ok(result) => println!("스레드 {} 종료", result),
Err(e) => println!("{:?}", e),
}
}
13.5 Mutex<T>
와 Arc<T>
조합
Mutex는 한 번에 하나의 스레드만 데이터에 접근할 수 있도록 보장합니다.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
match counter.lock() {
Ok(mut num) => {
*num += 1;
}
Err(poisoned) => {
// 다른 스레드가 값을 잠근 상태에서 패닉이 발생한 경우
eprintln!("뮤텍스가 독화되었습니다. 복구를 시도합니다.");
let mut num = poisoned.into_inner();
*num += 1; // 독화된 뮤텍스를 복구한 후 값을 증가시킴
}
}
});
handles.push(handle);
}
for handle in handles {
if let Err(e) = handle.join() {
eprintln!("스레드가 패닉을 발생시켰습니다: {:?}", e);
}
}
match counter.lock() {
Ok(num) => println!("Result: {}", *num),
Err(poisoned) => {
let num = poisoned.into_inner();
println!("Result: {}", *num);
}
};
}
13.6 Weak<T>
use std::rc::{Rc, Weak};
use std::cell::RefCell;
// 순환 참조 해결
struct Node {
next: Option<Rc<RefCell<Node>>>,
parent: RefCell<Weak<RefCell<Node>>>,
value: i32,
}
impl Node {
fn new(value: i32) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Node {
next: None,
parent: RefCell::new(Weak::new()),
value,
}))
}
}
// 사용 예시
let leaf = Node::new(3);
let branch = Node::new(5);
leaf.borrow_mut().parent = RefCell::new(Rc::downgrade(&branch));
branch.borrow_mut().next = Some(Rc::clone(&leaf));
13.7 Custom Smart Pointer
struct CustomSmartPointer<T> {
data: T,
}
impl<T> Drop for CustomSmartPointer<T> {
fn drop(&mut self) {
println!("CustomSmartPointer 메모리 해제!");
}
}
impl<T> Deref for CustomSmartPointer<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
// 사용
let c = CustomSmartPointer { data: String::from("데이터") };
// Display 트레이트가 구현되어 있지 않아 자동 역참조 불가
println!("포인터: {}", *c);
14. 비동기 프로그래밍
14.1 기본 개념
use tokio;
#[tokio::main]
async fn main() {
println!("비동기 시작");
let result = do_something().await;
println!("result 수신 후 실행");
println!("결과: {}", result);
println!("result 수신 후 실행");
}
async fn do_something() -> String {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
String::from("완료")
}
14.2 Future 트레이트
let result = timer.await;
-
await 키워드를 만나면 현재 Future가 실행기(executor)에 등록됨
-
실행기는 Future를 실행 큐에 추가하고 폴링을 시작합니다.
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
struct MyFuture {
count: u32,
}
impl Future for MyFuture {
type Output = u32;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.count >= 10 {
Poll::Ready(self.count)
} else {
cx.waker().wake_by_ref();
self.count += 1;
Poll::Pending
}
}
}
#[tokio::main]
async fn main() {
println!("Timer started!");
let timer = MyFuture { count: 0 };
let result = timer.await;
println!("Result: {}", result);
}
14.3 동시성 처리
use tokio;
#[tokio::main]
async fn main() {
// 병렬 작업
let handles: Vec<_> = (0..3)
.map(|i| {
// tokio::spawn
// 태스크가 즉시 생성되고 실행 큐에 들어갑니다 (즉시 실행)
tokio::spawn(async move {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
println!("작업 {} 완료", i);
i
})
})
.collect();
// 결과 수집
for handle in handles {
// 실행결과 대기
match handle.await {
Ok(result) => println!("결과: {}", result),
Err(e) => eprintln!("작업 실행 중 오류 발생: {}", e),
}
}
}
14.4 Stream
use tokio;
use futures::stream::{self, StreamExt};
async fn process_stream() {
let mut stream = stream::iter(1..=5)
.map(|x| async move { x * 2 })
.buffer_unordered(2); // 최대 2개의 작업을 동시에 실행
// 한 번에 최대 2개의 작업만 동시에 실행 (stream.next().await)
while let Some(n) = stream.next().await {
println!("값: {}", n);
}
}
#[tokio::main]
async fn main() {
process_stream().await;
}
14.5 Select와 Join
use tokio::select;
async fn race_tasks() {
let t1 = some_async_task();
let t2 = another_async_task();
// 여러 비동기 작업 중 가장 먼저 완료되는 작업을 처리 (select!)
// 두 번째 이후의 작업은 처리안함
select! {
val = t1 => println!("task 1 완료: {}", val),
val = t2 => println!("task 2 완료: {}", val),
}
// join으로 모든 작업 완료 대기
let (result1, result2) = tokio::join!(t1, t2);
}
14.6 채널 통신
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
// 버퍼가 가득 찼을 때 블로킹 (전송 대기)
let (tx, mut rx) = mpsc::channel(32);
// 송신자 작업
let sender = tokio::spawn(async move {
for i in 0..5 {
// 순차실행
tx.send(i).await.unwrap();
}
});
// 수신자 작업
// 채널(mpsc)의 특성상 FIFO(First In First Out) 방식으로 동작
while let Some(value) = rx.recv().await {
println!("받은 값: {}", value);
}
}
14.7 오류 처리
use std::io;
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
match async_operation().await {
Ok(result) => println!("성공: {}", result),
Err(e) => eprintln!("에러: {}", e),
}
Ok(())
}
async fn async_operation() -> io::Result<String> {
// 비동기 작업 시뮬레이션
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
Ok(String::from("작업 완료"))
}
14.8 비동기 락과 동기화
- Mutex: 한 번에 하나의 접근만 허용
- RwLock: 다수의 동시 읽기 또는 단일 쓰기를 허용
use tokio::sync::{Mutex, RwLock};
#[tokio::main]
async fn main() {
// 비동기 뮤텍스
let mutex = Arc::new(Mutex::new(0));
// 읽기-쓰기 락
let rwlock = Arc::new(RwLock::new(String::new()));
// 뮤텍스 사용
let mut lock = mutex.lock().await;
*lock += 1;
drop(lock);
// 읽기-쓰기 락 사용
// 쓰기 락이 걸려있으면 아무도 읽기 불가
let mut write = rwlock.write().await;
write.push_str("데이터");
// 쓰기 락 해제
drop(write);
let read = rwlock.read().await;
println!("데이터: {}", *read);
}
15. 매크로 시스템
15.1 선언적 매크로 (Declarative Macros)
// 기본 매크로 정의
macro_rules! say_hello {
() => {
println!("Hello!");
};
}
// 매개변수를 받는 매크로
macro_rules! print_ex {
// $는 매크로 안에서 변수를 선언하거나 사용할 때 필요한 접두사
($x:expr) => {
println!("{:?}", $x);
};
}
// 여러 패턴 매칭
macro_rules! vec_strs {
// 빈 벡터
() => {
Vec::new()
};
// ($(...),*) 의 형태
// 쉼표로 구분된 괄호안의것(표현식) (0 개 이상)
// ($($x:expr),+) => 한개 이상
($($x:expr),*) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x.to_string());
)*
temp_vec
}
};
}
say_hello!();
print_ex!(10);
print_ex!("Hello");
print_ex!(vec_strs!("a", "b", "c"));
15.2 절차적 매크로 (Procedural Macros)
절차적 매크로 크레이트(프로젝트)는 반드시 별도의 크레이트여야 합니다.
(같은 크레이트 내에서 정의할 수 없음)
- 파생 매크로: 구조체나 열거형에 대한 구현을 자동화할 때 사용
- 속성 매크로: 함수나 구조체에 메타데이터나 추가 기능을 부여할 때 사용
- 함수형 매크로: 임의의 토큰을 입력받아 새로운 코드를 생성할 때 사용
폴더 및 파일
기존 프로젝트에 절차적 매크로를 생성하기 위해 아래와 같이 폴더 및 파일을 생성합니다.
my_project/
├── Cargo.toml
├── src/
│ └── main.rs (또는 lib.rs)
└── crates/
└── my_macro/
├── Cargo.toml
└── src/
└── lib.rs
crates/my_macro/Cargo.toml
[package]
name = "my_macro"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
syn = { version = "2.0", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"
crates/my_macro/src/lib.rs
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn;
use syn::{parse_macro_input, LitStr};
// 파생 매크로 (Derive Macros)
#[proc_macro_derive(HelloWorld)]
pub fn hello_world_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_hello_world(&ast)
}
fn impl_hello_world(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl #name {
fn hello_world() {
println!("Hello, World! I'm {}", stringify!(#name));
}
}
};
gen.into()
}
// 속성 매크로 (Attribute Macros)
#[proc_macro_attribute]
pub fn my_route(attr: TokenStream, item: TokenStream) -> TokenStream {
// 경로 문자열을 파싱
let path = parse_macro_input!(attr as LitStr).value();
let input = parse_macro_input!(item as syn::ItemFn);
let func_ident = &input.sig.ident;
let expanded = quote! {
#[doc = #path]
#input
};
expanded.into()
}
// 함수형 매크로 (Function-like Macros)
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
let input_str = input.to_string();
// SQL 쿼리를 파싱하고 검증하는 로직
let output = format!(
r#"
String::from("{}")
"#,
input_str
);
output.parse().unwrap()
}
#[derive(HelloWorld)]
struct MyStruct;
fn main() {
MyStruct::hello_world();
}
#[my_route("/hello")]
sql!(SELECT * FROM users)
기존 프로젝트 설정
루트 Cargo.toml에 workspace와 의존성을 추가합니다.
[dependencies]
my_macro = { path = "crates/my_macro" }
[workspace]
members = [
"crates/my_macro"
]
루트 프로젝트
use my_macro::HelloWorld;
use my_macro::my_route;
use my_macro::sql;
#[derive(HelloWorld)]
struct Greeter;
// "/api/users" 호출시 사용됨
#[my_route("/api/users")]
fn handle_users() {
// 처리 로직
}
fn main() {
Greeter::hello_world();
let query = sql!(SELECT * FROM users WHERE id = 1);
println!("Generated query: {}", query);
}
15.3 매크로 규칙과 지정자
macro_rules! match_type {
// 1. ($x:expr) => { ... };
// 표현식(expression)을 매칭합니다.
// 예: 1 + 2, foo(), vec![1, 2, 3] 등
// 2. ($x:ident) => { ... };
// 식별자(identifier)를 매칭합니다.
// 예: foo, bar, my_variable 등의 변수명이나 함수명
// 3. ($x:ty) => { ... };
// 타입(type)을 매칭합니다.
// 예: i32, String, Vec<T> 등
// 4. ($x:path) => { ... };
// 경로(path)를 매칭합니다.
// 예: std::vec::Vec, my_module::MyStruct 등
// 5. ($x:tt) => { ... };
// 토큰 트리(token tree)를 매칭합니다.
// 단일 토큰이나 괄호로 묶인 토큰 시퀀스를 매칭
// 예: 1, (1 + 2), [1, 2, 3] 등
// 6. ($x:item) => { ... };
// 아이템(item)을 매칭합니다.
// 예: 함수 정의, 구조체 정의, 모듈 등
}
15.4 매크로 반복과 조건
macro_rules! vector {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
// 조건부 매크로
macro_rules! conditional {
($x:expr, $y:expr, if $condition:expr) => {
if $condition {
$x
} else {
$y
}
};
}
15.5 매크로 내보내기와 가져오기
// 매크로 내보내기
#[macro_export]
macro_rules! public_macro {
() => {
println!("공개 매크로");
};
}
// 매크로 가져오기
use my_crate::public_macro;
// 또는
#[macro_use]
extern crate my_crate;
15.6 디버깅과 오류 처리
macro_rules! debug_print {
($($arg:tt)*) => {{
#[cfg(debug_assertions)]
println!("디버그: {}", format!($($arg)*));
}};
}
// 매크로 확장 추적
#[rustc_macro_transparency = "semitransparent"]
macro_rules! trace_macro {
($name:ident) => {
println!("매크로 {} 실행", stringify!($name));
};
}