Rust 기초 강좌

By | 2025년 1월 26일
Table of Contents

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

답글 남기기