Rust ๋ฉด์ ‘ ์งˆ๋ฌธ: 2026๋…„ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

Rust ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ ์ž์ฃผ ์ถœ์ œ๋˜๋Š” 25๊ฐœ ์งˆ๋ฌธ์„ ์™„๋ฒฝํ•˜๊ฒŒ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ์†Œ์œ ๊ถŒ, ๋นŒ๋ฆผ, ๋ผ์ดํ”„ํƒ€์ž„, ํŠธ๋ ˆ์ดํŠธ, async/await, ๋™์‹œ์„ฑ์— ๋Œ€ํ•œ ์ƒ์„ธํ•œ ๋‹ต๋ณ€๊ณผ ์ฝ”๋“œ ์˜ˆ์ œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Rust ๋ฉด์ ‘ ์งˆ๋ฌธ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

Rust ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ๋Š” ์–ธ์–ด ๊ณ ์œ ์˜ ์†Œ์œ ๊ถŒ ์‹œ์Šคํ…œ, ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ ์—†๋Š” ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ, ์•ˆ์ „ํ•œ ๋™์‹œ์„ฑ ์ฝ”๋“œ ์ž‘์„ฑ ๋Šฅ๋ ฅ์ด ํ‰๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์ด ๊ฐ€์ด๋“œ๋Š” ์†Œ์œ ๊ถŒ ๊ธฐ์ดˆ๋ถ€ํ„ฐ ๊ณ ๊ธ‰ async ๋ฐ ๋™์‹œ์„ฑ ํŒจํ„ด๊นŒ์ง€ ํ•„์ˆ˜ ์งˆ๋ฌธ์„ ์ฒด๊ณ„์ ์œผ๋กœ ๋‹ค๋ฃจ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฉด์ ‘ ํŒ

๋ฉด์ ‘๊ด€์€ Rust์˜ ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „์„ฑ ๋ณด์žฅ์— ๋Œ€ํ•œ ์ดํ•ด๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์„ค๋ช…์„ ์ค‘์‹œํ•ฉ๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋ฒ„๊ทธ๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š” ์›๋ฆฌ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด ํ•ฉ๊ฒฉ๊ณผ ๋ถˆํ•ฉ๊ฒฉ์„ ๊ฐ€๋ฆ…๋‹ˆ๋‹ค.

์†Œ์œ ๊ถŒ๊ณผ ๋นŒ๋ฆผ

์งˆ๋ฌธ 1: Rust์˜ ์†Œ์œ ๊ถŒ ์‹œ์Šคํ…œ์„ ์„ค๋ช…ํ•ด ์ฃผ์„ธ์š”

์†Œ์œ ๊ถŒ์€ Rust์˜ ํ•ต์‹ฌ ๊ฐœ๋…์œผ๋กœ, ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ ์—†์ด ์ปดํŒŒ์ผ ํƒ€์ž„ ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ๋ฅผ ์‹คํ˜„ํ•ฉ๋‹ˆ๋‹ค.

ownership_basics.rsrust
// The three fundamental rules of ownership

fn main() {
    // Rule 1: Each value has a single owner
    let s1 = String::from("hello");  // s1 is the owner

    // Rule 2: Only one variable can own a value at a time
    let s2 = s1;  // s1 is MOVED to s2
    // println!("{}", s1);  // ERROR: s1 is no longer valid
    println!("{}", s2);  // OK: s2 is now the owner

    // Rule 3: When the owner goes out of scope, the value is dropped
    {
        let s3 = String::from("world");
        // s3 is valid here
    }  // s3 goes out of scope, memory is automatically freed

    // Copy types: simple types are copied, not moved
    let x = 5;
    let y = x;  // x is COPIED, not moved
    println!("x = {}, y = {}", x, y);  // Both are valid
}

// Move in action with functions
fn take_ownership(s: String) {
    // s takes ownership of the String
    println!("{}", s);
}  // s is dropped here, memory freed

fn makes_copy(i: i32) {
    // i is a copy of the argument
    println!("{}", i);
}  // i goes out of scope, nothing special (Copy type)

fn ownership_with_functions() {
    let s = String::from("hello");
    take_ownership(s);  // s is moved into the function
    // println!("{}", s);  // ERROR: s is no longer valid

    let x = 5;
    makes_copy(x);  // x is copied
    println!("{}", x);  // OK: x is still valid
}

์†Œ์œ ๊ถŒ์„ ํ†ตํ•ด use-after-free, double-free, ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์™€ ๊ฐ™์€ ์ผ๋ฐ˜์ ์ธ ๋ฉ”๋ชจ๋ฆฌ ๋ฒ„๊ทธ๊ฐ€ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ด๋Ÿฌํ•œ ์†์„ฑ์„ ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

์งˆ๋ฌธ 2: ๋ถˆ๋ณ€ ๋นŒ๋ฆผ๊ณผ ๊ฐ€๋ณ€ ๋นŒ๋ฆผ์˜ ์ฐจ์ด์ ์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ

๋นŒ๋ฆผ์€ ์†Œ์œ ๊ถŒ์„ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๊ณ  ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด๋ฉฐ, ๋ฐ์ดํ„ฐ ๊ฒฝํ•ฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์—„๊ฒฉํ•œ ๊ทœ์น™์ด ์žˆ์Šต๋‹ˆ๋‹ค.

borrowing_rules.rsrust
// Immutable and mutable references

fn main() {
    let mut s = String::from("hello");

    // IMMUTABLE REFERENCES (&T)
    // Can coexist in unlimited numbers
    let r1 = &s;  // immutable reference
    let r2 = &s;  // another immutable reference
    println!("{} and {}", r1, r2);  // OK

    // MUTABLE REFERENCE (&mut T)
    // Only one at a time, and no simultaneous immutable references
    let r3 = &mut s;  // mutable reference
    // let r4 = &s;  // ERROR: cannot have both immutable and mutable
    // let r5 = &mut s;  // ERROR: only one mutable reference allowed
    r3.push_str(" world");
    println!("{}", r3);

    // Reference scopes are limited to their last use
    let r6 = &s;  // OK because r3 is no longer used
    println!("{}", r6);
}

// Practical example: modifying a struct
struct User {
    name: String,
    age: u32,
}

impl User {
    // &self: read-only access
    fn get_name(&self) -> &str {
        &self.name
    }

    // &mut self: modification access
    fn set_name(&mut self, name: String) {
        self.name = name;
    }

    // self: takes ownership (consumes the instance)
    fn into_name(self) -> String {
        self.name  // The User instance no longer exists after this
    }
}

fn borrowing_with_structs() {
    let mut user = User {
        name: String::from("Alice"),
        age: 30,
    };

    // Reading
    println!("Name: {}", user.get_name());

    // Modifying
    user.set_name(String::from("Bob"));

    // Consuming
    let name = user.into_name();
    // user.age;  // ERROR: user has been consumed
}

์ด๋Ÿฌํ•œ ๊ทœ์น™์„ ํ†ตํ•ด ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋ฐ์ดํ„ฐ ๊ฒฝํ•ฉ์˜ ๋ถ€์žฌ๊ฐ€ ๋ณด์žฅ๋ฉ๋‹ˆ๋‹ค. ์„ฑ๋Šฅ ํฌ์ƒ ์—†์ด ์ด ๋ณด์žฅ์„ ์ œ๊ณตํ•˜๋Š” ์–ธ์–ด๋Š” ๋‹ค๋ฅธ ๊ณณ์— ์—†์Šต๋‹ˆ๋‹ค.

๋น„๋ ‰์‹œ์ปฌ ๋ผ์ดํ”„ํƒ€์ž„

Rust 2018 ์ดํ›„, ์ปดํŒŒ์ผ๋Ÿฌ๋Š” NLL(Non-Lexical Lifetimes)์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฐธ์กฐ๊ฐ€ ๋” ์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์‹œ์ ์„ ๋” ์ •ํ™•ํ•˜๊ฒŒ ํŒ๋‹จํ•˜๋ฉฐ, ์œ ์—ฐ์„ฑ์ด ํ–ฅ์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์งˆ๋ฌธ 3: ๋ผ์ดํ”„ํƒ€์ž„์ด๋ž€ ๋ฌด์—‡์ด๋ฉฐ, ์–ธ์ œ ์–ด๋…ธํ…Œ์ด์…˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๊นŒ

๋ผ์ดํ”„ํƒ€์ž„์€ ์ฐธ์กฐ์˜ ์œ ํšจ ๊ธฐ๊ฐ„์„ ์ปดํŒŒ์ผ๋Ÿฌ์—๊ฒŒ ์•Œ๋ ค์ฃผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ, ๋Œ•๊ธ€๋ง ์ฐธ์กฐ๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

lifetimes.rsrust
// Understanding and annotating lifetimes

// ERROR: dangling reference
// fn dangling() -> &String {
//     let s = String::from("hello");
//     &s  // s is dropped at function end, reference invalid
// }

// The compiler often infers lifetimes automatically
fn first_word(s: &str) -> &str {
    // Elided lifetime: compiler understands the return
    // has the same lifetime as the input
    match s.find(' ') {
        Some(i) => &s[..i],
        None => s,
    }
}

// Explicit annotation needed with multiple references
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    // 'a means: the return lives at least as long
    // as the shorter of the two inputs
    if x.len() > y.len() { x } else { y }
}

fn lifetime_example() {
    let string1 = String::from("long string");
    let result;

    {
        let string2 = String::from("xyz");
        result = longest(&string1, &string2);
        println!("Longest: {}", result);  // OK here
    }
    // println!("{}", result);  // ERROR: string2 is dropped
}

// Lifetimes in structs
struct ImportantExcerpt<'a> {
    part: &'a str,  // Struct cannot outlive part
}

impl<'a> ImportantExcerpt<'a> {
    // Method returning a reference with the same lifetime
    fn level(&self) -> i32 {
        3
    }

    // Elided lifetime for &self returning a new reference
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention: {}", announcement);
        self.part  // Returns with lifetime 'a
    }
}

// Static lifetime: lives for the entire program duration
fn static_lifetime() {
    let s: &'static str = "hello";  // Stored in the binary

    // Constants have implicit 'static lifetime
    const MAX_POINTS: u32 = 100_000;
}

// Combining lifetimes and generics
fn longest_with_announcement<'a, T>(
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: std::fmt::Display,
{
    println!("Announcement: {}", ann);
    if x.len() > y.len() { x } else { y }
}

๋ผ์ดํ”„ํƒ€์ž„์€ ์ปดํŒŒ์ผ ํƒ€์ž„์— ๊ฒ€์ฆ๋ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ๊ฐ€ ์ปดํŒŒ์ผ๋˜๋ฉด, ์ฐธ์กฐ์˜ ์œ ํšจ์„ฑ์ด ๋ณด์žฅ๋ฉ๋‹ˆ๋‹ค.

ํŠธ๋ ˆ์ดํŠธ์™€ ์ œ๋„ค๋ฆญ

์งˆ๋ฌธ 4: Rust์˜ ํŠธ๋ ˆ์ดํŠธ๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๊นŒ

ํŠธ๋ ˆ์ดํŠธ๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ํƒ€์ž… ๊ฐ„์— ๊ณต์œ ๋˜๋Š” ๋™์ž‘์„ ์ •์˜ํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ์ธํ„ฐํŽ˜์ด์Šค์™€ ์œ ์‚ฌํ•˜๋ฉด์„œ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ”๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

traits_basics.rsrust
// Defining and implementing traits

// Trait definition
trait Summary {
    // Required method (no body)
    fn summarize(&self) -> String;

    // Method with default implementation
    fn summarize_author(&self) -> String {
        String::from("(Anonymous)")
    }

    // Default method that calls a required method
    fn full_summary(&self) -> String {
        format!("By {} - {}", self.summarize_author(), self.summarize())
    }
}

// Implementation for different types
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }

    fn summarize_author(&self) -> String {
        format!("@{}", self.author)
    }
}

struct Tweet {
    username: String,
    content: String,
    reply: bool,
    retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

// Trait bounds: constraining generics
fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

// Alternative syntax with where
fn notify_verbose<T>(item: &T)
where
    T: Summary,
{
    println!("Breaking news! {}", item.summarize());
}

// Multiple trait bounds
fn notify_complex<T: Summary + Clone + std::fmt::Display>(item: &T) {
    println!("{}", item);
}

// Return a type that implements a trait
fn create_summarizable() -> impl Summary {
    Tweet {
        username: String::from("rust_lang"),
        content: String::from("Rust 2026 is amazing!"),
        reply: false,
        retweet: false,
    }
}

ํŠธ๋ ˆ์ดํŠธ๋Š” ํด๋ž˜์Šค ์ƒ์† ์—†์ด ๋‹คํ˜•์„ฑ์„ ์‹คํ˜„ํ•˜๋ฉฐ, ์ƒ์†๋ณด๋‹ค ํ•ฉ์„ฑ์„ ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค.

์งˆ๋ฌธ 5: ์ •์  ๋””์ŠคํŒจ์น˜์™€ ๋™์  ๋””์ŠคํŒจ์น˜์˜ ์ฐจ์ด์ ์„ ์„ค๋ช…ํ•ด ์ฃผ์„ธ์š”

Rust๋Š” ๋‹คํ˜•์„ฑ์— ๋Œ€ํ•ด ๋‹จํ˜•ํ™”(์ •์ )์™€ ํŠธ๋ ˆ์ดํŠธ ๊ฐ์ฒด(๋™์ )์˜ ๋‘ ๊ฐ€์ง€ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

static_vs_dynamic_dispatch.rsrust
// Static vs dynamic dispatch

trait Animal {
    fn speak(&self) -> String;
    fn name(&self) -> &str;
}

struct Dog { name: String }
struct Cat { name: String }

impl Animal for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
    fn name(&self) -> &str { &self.name }
}

impl Animal for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
    fn name(&self) -> &str { &self.name }
}

// STATIC DISPATCH (monomorphization)
// Compiler generates a version for each concrete type
fn make_speak_static<T: Animal>(animal: &T) {
    // At compile time, becomes make_speak_Dog and make_speak_Cat
    println!("{} says {}", animal.name(), animal.speak());
}

// Advantages: inlining possible, no runtime overhead
// Disadvantages: larger binary, type must be known at compile time

// DYNAMIC DISPATCH (trait objects)
// Uses a vtable to resolve methods at runtime
fn make_speak_dynamic(animal: &dyn Animal) {
    // Resolved via a pointer table (vtable) at runtime
    println!("{} says {}", animal.name(), animal.speak());
}

// Advantages: can store different types, smaller binary
// Disadvantages: indirection overhead, no inlining

fn main() {
    let dog = Dog { name: String::from("Rex") };
    let cat = Cat { name: String::from("Whiskers") };

    // Static: type is known at compile time
    make_speak_static(&dog);
    make_speak_static(&cat);

    // Dynamic: type is resolved at runtime
    make_speak_dynamic(&dog);
    make_speak_dynamic(&cat);

    // Heterogeneous collection (requires dynamic dispatch)
    let animals: Vec<Box<dyn Animal>> = vec![
        Box::new(Dog { name: String::from("Buddy") }),
        Box::new(Cat { name: String::from("Luna") }),
    ];

    for animal in animals.iter() {
        println!("{} says {}", animal.name(), animal.speak());
    }
}

// Object safety: not all traits can become trait objects
trait ObjectSafe {
    fn method(&self);
    // No Self in return type
    // No generic parameters
}

// NOT object safe (cannot be dyn NotObjectSafe)
trait NotObjectSafe {
    fn create() -> Self;  // Self in return
    fn generic<T>(&self, t: T);  // Generic
}

์„ฑ๋Šฅ์„ ์ค‘์‹œํ•˜๋Š” ๊ฒฝ์šฐ ์ •์  ๋””์ŠคํŒจ์น˜๊ฐ€ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ์ด์ข… ์ปฌ๋ ‰์…˜์ด๋‚˜ ์œ ์—ฐ์„ฑ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋™์  ๋””์ŠคํŒจ์น˜๊ฐ€ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

Rust ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

์—๋Ÿฌ ์ฒ˜๋ฆฌ

์งˆ๋ฌธ 6: Result์™€ Option์„ ์‚ฌ์šฉํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์€

Rust์—๋Š” ์˜ˆ์™ธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋Š” Result<T, E>์™€ Option<T> ํƒ€์ž…์„ ํŒจํ„ด ๋งค์นญ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

error_handling.rsrust
// Idiomatic error handling in Rust

use std::fs::File;
use std::io::{self, Read};

// Option<T>: presence or absence of a value
fn find_user(id: u32) -> Option<String> {
    match id {
        1 => Some(String::from("Alice")),
        2 => Some(String::from("Bob")),
        _ => None,  // No user found
    }
}

// Result<T, E>: success or error
fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("Division by zero"))
    } else {
        Ok(a / b)
    }
}

fn option_combinators() {
    let user = find_user(1);

    // Pattern matching
    match user {
        Some(name) => println!("Found: {}", name),
        None => println!("Not found"),
    }

    // unwrap_or: default value
    let name = find_user(99).unwrap_or(String::from("Unknown"));

    // map: transform the value if present
    let upper = find_user(1).map(|n| n.to_uppercase());

    // and_then (flatMap): chain Options
    let first_char = find_user(1).and_then(|n| n.chars().next());

    // if let: simplified pattern matching
    if let Some(name) = find_user(2) {
        println!("User 2 is {}", name);
    }
}

fn result_handling() -> Result<(), Box<dyn std::error::Error>> {
    // The ? operator propagates errors automatically
    let result = divide(10.0, 2.0)?;
    println!("Result: {}", result);

    // Equivalent to:
    // let result = match divide(10.0, 2.0) {
    //     Ok(v) => v,
    //     Err(e) => return Err(e.into()),
    // };

    Ok(())
}

// File reading with error propagation
fn read_file_contents(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;  // Propagates error if failure
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

// Custom errors
#[derive(Debug)]
enum AppError {
    IoError(io::Error),
    ParseError(String),
    NotFound(String),
}

impl std::fmt::Display for AppError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            AppError::IoError(e) => write!(f, "IO error: {}", e),
            AppError::ParseError(s) => write!(f, "Parse error: {}", s),
            AppError::NotFound(s) => write!(f, "Not found: {}", s),
        }
    }
}

impl std::error::Error for AppError {}

// Automatic conversion with From
impl From<io::Error> for AppError {
    fn from(error: io::Error) -> Self {
        AppError::IoError(error)
    }
}

fn complex_operation() -> Result<String, AppError> {
    let contents = std::fs::read_to_string("config.txt")?;  // Auto-convert

    if contents.is_empty() {
        return Err(AppError::NotFound(String::from("Config is empty")));
    }

    Ok(contents)
}

? ์—ฐ์‚ฐ์ž๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•ด์ง€๋ฉด์„œ๋„ ๋ช…์‹œ์ ์ธ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ•์ œ๋ฉ๋‹ˆ๋‹ค. ๋Ÿฐํƒ€์ž„์— ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ๋•์…˜์—์„œ unwrap ์‚ฌ์šฉ์„ ํ”ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

unwrap()๊ณผ expect()๋Š” None ๋˜๋Š” Err์ธ ๊ฒฝ์šฐ ํŒจ๋‹‰์„ ์ผ์œผํ‚ต๋‹ˆ๋‹ค. ํ”„๋กœํ† ํƒ€์ดํ•‘์ด๋‚˜ ์‹คํŒจ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ?๋ฅผ ํ†ตํ•œ ์ „ํŒŒ ๋˜๋Š” ์ปด๋น„๋„ค์ดํ„ฐ ์‚ฌ์šฉ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

์งˆ๋ฌธ 7: thiserror๋ฅผ ์‚ฌ์šฉํ•œ ์ปค์Šคํ…€ ์—๋Ÿฌ ์ƒ์„ฑ ๋ฐฉ๋ฒ•์€

thiserror ํฌ๋ ˆ์ดํŠธ๋Š” ์ธ์ฒด๊ณตํ•™์ ์ธ ์ปค์Šคํ…€ ์—๋Ÿฌ ์ƒ์„ฑ์„ ๊ฐ„์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค.

custom_errors.rsrust
// Custom errors with thiserror

use thiserror::Error;

// Error definition with derive macro
#[derive(Error, Debug)]
pub enum DataStoreError {
    #[error("connection failed: {0}")]
    ConnectionFailed(String),

    #[error("query failed: {query}")]
    QueryFailed { query: String, source: std::io::Error },

    #[error("record not found: id={id}")]
    NotFound { id: u64 },

    #[error("invalid data: {0}")]
    InvalidData(#[from] serde_json::Error),

    #[error(transparent)]  // Delegates Display to source
    Other(#[from] anyhow::Error),
}

// Implementation with rich context
pub struct DataStore {
    connection_string: String,
}

impl DataStore {
    pub fn connect(conn_str: &str) -> Result<Self, DataStoreError> {
        if conn_str.is_empty() {
            return Err(DataStoreError::ConnectionFailed(
                "Empty connection string".into()
            ));
        }
        Ok(Self { connection_string: conn_str.to_string() })
    }

    pub fn get_record(&self, id: u64) -> Result<Record, DataStoreError> {
        // Query simulation
        if id == 0 {
            return Err(DataStoreError::NotFound { id });
        }
        Ok(Record { id, data: format!("Record {}", id) })
    }
}

pub struct Record {
    pub id: u64,
    pub data: String,
}

// Usage with anyhow for applications
use anyhow::{Context, Result};

fn application_code() -> Result<()> {
    let store = DataStore::connect("postgres://localhost/db")
        .context("Failed to connect to database")?;

    let record = store.get_record(42)
        .context("Failed to fetch user record")?;

    println!("Got: {}", record.data);
    Ok(())
}

// Pattern: converting errors with context
fn read_config() -> Result<Config> {
    let contents = std::fs::read_to_string("config.toml")
        .context("Failed to read config file")?;

    let config: Config = toml::from_str(&contents)
        .context("Failed to parse config file")?;

    Ok(config)
}

#[derive(Debug)]
struct Config {
    // ...
}

thiserror๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(ํƒ€์ž… ์—๋Ÿฌ)์— ์ตœ์ ์ด๋ฉฐ, anyhow๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(์ตœ๋Œ€ํ•œ์˜ ์œ ์—ฐ์„ฑ)์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

์Šค๋งˆํŠธ ํฌ์ธํ„ฐ

์งˆ๋ฌธ 8: Box, Rc, Arc, RefCell์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ด ์ฃผ์„ธ์š”

์Šค๋งˆํŠธ ํฌ์ธํ„ฐ๋Š” ํž™ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , ๋‹จ์ˆœํ•œ ์†Œ์œ ๊ถŒ์œผ๋กœ๋Š” ์‹คํ˜„ํ•  ์ˆ˜ ์—†๋Š” ํŒจํ„ด์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

smart_pointers.rsrust
// Main smart pointers in Rust

use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;

// BOX<T>: heap allocation
// Used for: recursive types, large types, trait objects
fn box_example() {
    // Simple heap allocation
    let b = Box::new(5);
    println!("b = {}", b);

    // Recursive type (impossible without Box)
    #[derive(Debug)]
    enum List {
        Cons(i32, Box<List>),
        Nil,
    }

    let list = List::Cons(1,
        Box::new(List::Cons(2,
            Box::new(List::Cons(3,
                Box::new(List::Nil))))));
    println!("{:?}", list);
}

// RC<T>: Reference Counting (single-threaded)
// Multiple owners for the same data
fn rc_example() {
    let data = Rc::new(vec![1, 2, 3]);

    // Clone increments the reference counter
    let data_clone1 = Rc::clone(&data);  // count = 2
    let data_clone2 = Rc::clone(&data);  // count = 3

    println!("Reference count: {}", Rc::strong_count(&data));  // 3

    // Each clone can read the data
    println!("data_clone1: {:?}", data_clone1);

    // Data is freed when the last Rc is dropped
}

// ARC<T>: Atomic Reference Counting (thread-safe)
// Like Rc but usable across threads
fn arc_example() {
    use std::thread;

    let data = Arc::new(vec![1, 2, 3, 4, 5]);
    let mut handles = vec![];

    for i in 0..3 {
        let data_clone = Arc::clone(&data);
        let handle = thread::spawn(move || {
            // Each thread has its own Arc
            println!("Thread {}: {:?}", i, data_clone);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

// REFCELL<T>: Interior Mutability
// Allows mutation even with an immutable reference
fn refcell_example() {
    let data = RefCell::new(5);

    // borrow() returns an immutable reference
    println!("Value: {}", *data.borrow());

    // borrow_mut() returns a mutable reference
    *data.borrow_mut() += 1;
    println!("After mutation: {}", *data.borrow());

    // Borrowing rules are checked at RUNTIME
    // Panics if rules are violated
    // let r1 = data.borrow();
    // let r2 = data.borrow_mut();  // PANIC: already borrowed
}

// Common combination: Rc<RefCell<T>>
// Multiple owners with possible mutation
fn rc_refcell_example() {
    #[derive(Debug)]
    struct Node {
        value: i32,
        children: Vec<Rc<RefCell<Node>>>,
    }

    let node1 = Rc::new(RefCell::new(Node {
        value: 1,
        children: vec![],
    }));

    let node2 = Rc::new(RefCell::new(Node {
        value: 2,
        children: vec![Rc::clone(&node1)],  // node1 is child of node2
    }));

    // Modify node1 from anywhere
    node1.borrow_mut().value = 10;

    println!("node2 child value: {}",
        node2.borrow().children[0].borrow().value);  // 10
}

// For threads: Arc<Mutex<T>> or Arc<RwLock<T>>
fn arc_mutex_example() {
    use std::sync::Mutex;
    use std::thread;

    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 || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Final count: {}", *counter.lock().unwrap());  // 10
}

์ƒํ™ฉ์— ๋งž๋Š” ์ ์ ˆํ•œ ์Šค๋งˆํŠธ ํฌ์ธํ„ฐ๋ฅผ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœ ํž™ ํ• ๋‹น์—๋Š” Box, ๊ณต์œ ์—๋Š” Rc/Arc, ๋‚ด๋ถ€ ๊ฐ€๋ณ€์„ฑ์—๋Š” RefCell/Mutex๊ฐ€ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

๋™์‹œ์„ฑ

์งˆ๋ฌธ 9: Rust๋Š” ์–ด๋–ป๊ฒŒ ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๊นŒ

Rust์˜ ํƒ€์ž… ์‹œ์Šคํ…œ์€ Send ํŠธ๋ ˆ์ดํŠธ์™€ Sync ํŠธ๋ ˆ์ดํŠธ๋ฅผ ํ†ตํ•ด ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋ฐ์ดํ„ฐ ๊ฒฝํ•ฉ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

thread_safety.rsrust
// Concurrent safety guarantees

use std::thread;
use std::sync::{Arc, Mutex, mpsc};

// SEND: a type can be transferred to another thread
// SYNC: a type can be shared between threads via references

// Most types are Send and Sync automatically
// Exceptions: Rc (not Send/Sync), RefCell (not Sync), raw pointers

fn send_example() {
    let data = vec![1, 2, 3];

    // Vec is Send, so it can be moved to another thread
    let handle = thread::spawn(move || {
        println!("Data in thread: {:?}", data);
    });

    handle.join().unwrap();
}

// The compiler prevents concurrency errors
fn compile_time_safety() {
    // This would NOT compile:
    // let data = std::rc::Rc::new(5);
    // thread::spawn(move || {
    //     println!("{}", data);  // ERROR: Rc is not Send
    // });

    // Solution: use Arc
    let data = Arc::new(5);
    let data_clone = Arc::clone(&data);

    thread::spawn(move || {
        println!("{}", data_clone);  // OK: Arc is Send
    });
}

// Mutex for thread-safe shared mutation
fn mutex_pattern() {
    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 || {
            // lock() blocks until exclusive access is obtained
            let mut num = counter.lock().unwrap();
            *num += 1;
            // MutexGuard is dropped here, releasing the lock
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

// RwLock for multiple reads / exclusive write
fn rwlock_example() {
    use std::sync::RwLock;

    let data = Arc::new(RwLock::new(vec![1, 2, 3]));
    let mut handles = vec![];

    // Multiple simultaneous readers
    for i in 0..3 {
        let data = Arc::clone(&data);
        handles.push(thread::spawn(move || {
            let read = data.read().unwrap();
            println!("Reader {}: {:?}", i, *read);
        }));
    }

    // Only one writer at a time
    {
        let data = Arc::clone(&data);
        handles.push(thread::spawn(move || {
            let mut write = data.write().unwrap();
            write.push(4);
            println!("Writer added 4");
        }));
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

// Channels for inter-thread communication
fn channel_example() {
    let (tx, rx) = mpsc::channel();  // Multi-producer, single-consumer

    // Clone the sender for multiple producers
    let tx1 = tx.clone();
    thread::spawn(move || {
        tx1.send("from thread 1").unwrap();
    });

    thread::spawn(move || {
        tx.send("from thread 2").unwrap();
    });

    // Receive messages
    for received in rx {
        println!("Got: {}", received);
    }
}

"๋‘๋ ค์›€ ์—†๋Š” ๋™์‹œ์„ฑ": ์ฝ”๋“œ๊ฐ€ ์ปดํŒŒ์ผ๋˜๋ฉด, ๋ฐ์ดํ„ฐ ๊ฒฝํ•ฉ์€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ฒซ ๋ฒˆ์งธ ๋ฐฉ์–ด์„  ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

Mutex ํฌ์ด์ฆˆ๋‹

์Šค๋ ˆ๋“œ๊ฐ€ Mutex๋ฅผ ๋ณด์œ ํ•œ ์ƒํƒœ์—์„œ ํŒจ๋‹‰ํ•˜๋ฉด, Mutex๋Š” "ํฌ์ด์ฆŒ" ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์ดํ›„์˜ lock() ํ˜ธ์ถœ์€ ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ, into_inner()๋กœ ๋ณต๊ตฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์งˆ๋ฌธ 10: Rust์˜ async/await๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๊นŒ

Rust์˜ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋Š” ์ œ๋กœ ๋น„์šฉ Future์— ๊ธฐ๋ฐ˜ํ•˜๋ฉฐ, ์–ธ์–ด์— ๋Ÿฐํƒ€์ž„์ด ๋‚ด์žฅ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

async_await.rsrust
// Asynchronous programming in Rust

use tokio::time::{sleep, Duration};

// async fn returns a Future that must be executed
async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
    // await suspends execution without blocking the thread
    let response = reqwest::get(url).await?;
    let body = response.text().await?;
    Ok(body)
}

// Futures are lazy: nothing executes without await or poll
async fn lazy_example() {
    let future = async {
        println!("This won't print yet");
    };
    // Nothing happened

    future.await;  // Now it executes
}

// Parallel execution of futures
async fn parallel_execution() {
    // join! executes multiple futures in parallel
    let (result1, result2) = tokio::join!(
        fetch_data("https://api.example.com/1"),
        fetch_data("https://api.example.com/2"),
    );

    println!("Results: {:?}, {:?}", result1, result2);
}

// select! for the first completed future
async fn race_example() {
    tokio::select! {
        result = fetch_data("https://api1.example.com") => {
            println!("API 1 responded first: {:?}", result);
        }
        result = fetch_data("https://api2.example.com") => {
            println!("API 2 responded first: {:?}", result);
        }
        _ = sleep(Duration::from_secs(5)) => {
            println!("Timeout!");
        }
    }
}

// Streams: asynchronous iterators
use tokio_stream::StreamExt;

async fn stream_example() {
    let mut stream = tokio_stream::iter(vec![1, 2, 3, 4, 5]);

    while let Some(value) = stream.next().await {
        println!("Got: {}", value);
    }
}

// Spawn for background tasks
async fn spawn_tasks() {
    let handle = tokio::spawn(async {
        sleep(Duration::from_secs(1)).await;
        "Task completed"
    });

    println!("Task spawned, doing other work...");

    let result = handle.await.unwrap();
    println!("Result: {}", result);
}

// Entry point with tokio
#[tokio::main]
async fn main() {
    // The tokio runtime executes futures
    parallel_execution().await;
}

// Alternative: multi-threaded or single-threaded runtime
#[tokio::main(flavor = "current_thread")]
async fn main_single_thread() {
    // Everything runs on a single thread
}

#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
async fn main_multi_thread() {
    // Pool of 4 worker threads
}

Rust์˜ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋Š” "๋Ÿฐํƒ€์ž„์„ ์ง์ ‘ ๊ฐ€์ ธ์˜ค๋Š”" ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. tokio, async-std, smol ๋“ฑ ์‚ฌ์šฉ ์‚ฌ๋ก€์— ๋งž๋Š” ์ตœ์ ํ™”๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Rust ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

๊ณ ๊ธ‰ ํŒจํ„ด

์งˆ๋ฌธ 11: Rust์—์„œ Builder ํŒจํ„ด์„ ์„ค๋ช…ํ•ด ์ฃผ์„ธ์š”

Builder ํŒจํ„ด์€ ๋‹ค์ˆ˜์˜ ์„ ํƒ์  ํ•„๋“œ๋ฅผ ๊ฐ€์ง„ ๋ณต์žกํ•œ ๊ตฌ์กฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ Rust์˜ ๊ด€์šฉ์  ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

builder_pattern.rsrust
// Idiomatic Builder pattern in Rust

#[derive(Debug, Clone)]
pub struct Server {
    host: String,
    port: u16,
    max_connections: usize,
    timeout_seconds: u64,
    tls_enabled: bool,
    tls_cert_path: Option<String>,
}

// Builder with consuming approach (ownership)
#[derive(Default)]
pub struct ServerBuilder {
    host: String,
    port: u16,
    max_connections: usize,
    timeout_seconds: u64,
    tls_enabled: bool,
    tls_cert_path: Option<String>,
}

impl ServerBuilder {
    pub fn new() -> Self {
        Self {
            host: String::from("localhost"),
            port: 8080,
            max_connections: 100,
            timeout_seconds: 30,
            tls_enabled: false,
            tls_cert_path: None,
        }
    }

    // Each method takes self and returns Self for chaining
    pub fn host(mut self, host: impl Into<String>) -> Self {
        self.host = host.into();
        self
    }

    pub fn port(mut self, port: u16) -> Self {
        self.port = port;
        self
    }

    pub fn max_connections(mut self, max: usize) -> Self {
        self.max_connections = max;
        self
    }

    pub fn timeout(mut self, seconds: u64) -> Self {
        self.timeout_seconds = seconds;
        self
    }

    pub fn enable_tls(mut self, cert_path: impl Into<String>) -> Self {
        self.tls_enabled = true;
        self.tls_cert_path = Some(cert_path.into());
        self
    }

    // build() consumes the builder and creates the final structure
    pub fn build(self) -> Result<Server, String> {
        if self.tls_enabled && self.tls_cert_path.is_none() {
            return Err("TLS enabled but no certificate path provided".into());
        }

        Ok(Server {
            host: self.host,
            port: self.port,
            max_connections: self.max_connections,
            timeout_seconds: self.timeout_seconds,
            tls_enabled: self.tls_enabled,
            tls_cert_path: self.tls_cert_path,
        })
    }
}

// Fluent usage
fn create_server() -> Result<Server, String> {
    ServerBuilder::new()
        .host("0.0.0.0")
        .port(443)
        .max_connections(1000)
        .timeout(60)
        .enable_tls("/etc/ssl/cert.pem")
        .build()
}

// Alternative with derive macro (typed-builder crate)
// #[derive(TypedBuilder)]
// pub struct Config {
//     #[builder(default = "localhost".to_string())]
//     host: String,
//     #[builder(default = 8080)]
//     port: u16,
// }

// Pattern with type-level validation (typestate pattern)
pub struct Unvalidated;
pub struct Validated;

pub struct Request<State = Unvalidated> {
    url: String,
    method: String,
    headers: Vec<(String, String)>,
    _state: std::marker::PhantomData<State>,
}

impl Request<Unvalidated> {
    pub fn new(url: &str) -> Self {
        Self {
            url: url.to_string(),
            method: "GET".to_string(),
            headers: vec![],
            _state: std::marker::PhantomData,
        }
    }

    pub fn method(mut self, method: &str) -> Self {
        self.method = method.to_string();
        self
    }

    // validate() changes the state type
    pub fn validate(self) -> Result<Request<Validated>, String> {
        if self.url.is_empty() {
            return Err("URL cannot be empty".into());
        }
        Ok(Request {
            url: self.url,
            method: self.method,
            headers: self.headers,
            _state: std::marker::PhantomData,
        })
    }
}

impl Request<Validated> {
    // send() is only available on validated requests
    pub async fn send(self) -> Result<Response, reqwest::Error> {
        // Implementation...
        todo!()
    }
}

struct Response;

Typestate ํŒจํ„ด์„ ํ†ตํ•ด ํŠน์ • ์ž‘์—…์ด ์˜ฌ๋ฐ”๋ฅธ ์ƒํƒœ์—์„œ๋งŒ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ์Œ์ด ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋ณด์žฅ๋ฉ๋‹ˆ๋‹ค.

์งˆ๋ฌธ 12: ์™ธ๋ถ€ ํƒ€์ž…์— ํŠธ๋ ˆ์ดํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์€

"๊ณ ์•„ ๊ทœ์น™"์€ ์™ธ๋ถ€ ํŠธ๋ ˆ์ดํŠธ๋ฅผ ์™ธ๋ถ€ ํƒ€์ž…์— ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ ๊ธˆ์ง€ํ•˜์ง€๋งŒ, ์šฐํšŒ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

newtype_pattern.rsrust
// The Newtype pattern to work around the orphan rule

use std::fmt;

// ORPHAN RULE: cannot implement Display (std) for Vec (std)
// impl fmt::Display for Vec<i32> { ... }  // ERROR

// SOLUTION 1: Newtype wrapper
struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

fn newtype_example() {
    let w = Wrapper(vec![
        String::from("hello"),
        String::from("world"),
    ]);
    println!("{}", w);  // [hello, world]
}

// Transparent access with Deref
use std::ops::Deref;

impl Deref for Wrapper {
    type Target = Vec<String>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn deref_example() {
    let w = Wrapper(vec![String::from("test")]);
    println!("Length: {}", w.len());  // Calls Vec::len via Deref
}

// SOLUTION 2: Extension trait (to add methods)
trait VecExt<T> {
    fn first_or_default(&self) -> Option<&T>;
}

impl<T> VecExt<T> for Vec<T> {
    fn first_or_default(&self) -> Option<&T> {
        self.first()
    }
}

fn extension_trait_example() {
    let v = vec![1, 2, 3];
    println!("First: {:?}", v.first_or_default());
}

// Newtype with domain semantics
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Email(String);

impl Email {
    pub fn new(email: &str) -> Result<Self, &'static str> {
        if email.contains('@') && email.contains('.') {
            Ok(Self(email.to_string()))
        } else {
            Err("Invalid email format")
        }
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for Email {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct UserId(u64);

impl UserId {
    pub fn new(id: u64) -> Self {
        Self(id)
    }
}

// Newtypes add type safety without runtime overhead
fn process_user(id: UserId, email: Email) {
    println!("Processing user {} with email {}", id.0, email);
}

fn type_safety_example() {
    let id = UserId::new(42);
    let email = Email::new("user@example.com").unwrap();

    process_user(id, email);

    // This would not compile:
    // process_user(email, id);  // Types reversed
    // process_user(UserId::new(42), "string");  // String instead of Email
}

Newtype์€ ๋ฉ”๋ชจ๋ฆฌ ํ‘œํ˜„์˜ ๋™์ผ์„ฑ ๋ณด์žฅ ๋•๋ถ„์— ๋Ÿฐํƒ€์ž„ ๋น„์šฉ์ด ์ œ๋กœ์ž…๋‹ˆ๋‹ค.

์งˆ๋ฌธ 13: ํ”„๋กœ์‹œ์ € ๋งคํฌ๋กœ์˜ ์‚ฌ์šฉ๋ฒ•์€

ํ”„๋กœ์‹œ์ € ๋งคํฌ๋กœ๋Š” ์ปดํŒŒ์ผ ํƒ€์ž„์— ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋ฉฐ, ์ปค์Šคํ…€ derive ๋“ฑ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

procedural_macros.rsrust
// Understanding procedural macros

// Proc macros are defined in a separate crate with proc-macro = true

// Crate: my_derive (Cargo.toml: proc-macro = true)
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

// DERIVE MACRO: #[derive(MyTrait)]
#[proc_macro_derive(MyDebug)]
pub fn my_debug_derive(input: TokenStream) -> TokenStream {
    // Parse input as a type definition
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;

    // Generate implementation code
    let expanded = quote! {
        impl std::fmt::Debug for #name {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, stringify!(#name))
            }
        }
    };

    TokenStream::from(expanded)
}

// ATTRIBUTE MACRO: #[my_attribute]
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
    // attr contains the attribute arguments
    // item contains the annotated element (function, struct, etc.)

    let method_path = attr.to_string();  // "GET, /users"
    let input = parse_macro_input!(item as syn::ItemFn);
    let fn_name = &input.sig.ident;

    let expanded = quote! {
        #input

        // Additionally generated code
        fn register_#fn_name() {
            println!("Registered route: {}", #method_path);
        }
    };

    TokenStream::from(expanded)
}

// FUNCTION-LIKE MACRO: my_macro!(...)
#[proc_macro]
pub fn make_answer(_input: TokenStream) -> TokenStream {
    "fn answer() -> u32 { 42 }".parse().unwrap()
}

// --- Usage in client code ---

// Derive macro
#[derive(MyDebug)]
struct Point {
    x: i32,
    y: i32,
}

// Attribute macro
#[route("GET", "/users")]
fn get_users() -> Vec<User> {
    vec![]
}

// Function-like macro
make_answer!();  // Generates fn answer() -> u32 { 42 }

fn main() {
    let p = Point { x: 1, y: 2 };
    println!("{:?}", p);  // Uses our MyDebug

    println!("Answer: {}", answer());  // 42
}

struct User;

ํ”„๋กœ์‹œ์ € ๋งคํฌ๋กœ๋Š” ๋ฐ˜๋ณต์ ์ธ ์ฝ”๋“œ์— ๋Œ€ํ•ด ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์„ ๋ฐœํœ˜ํ•ฉ๋‹ˆ๋‹ค. ์ง๋ ฌํ™”, ์›น ๋ผ์šฐํŒ…, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋“ฑ์ด ๋Œ€ํ‘œ์ ์ธ ์šฉ๋„์ž…๋‹ˆ๋‹ค.

๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „์„ฑ๊ณผ unsafe

์งˆ๋ฌธ 14: unsafe๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์‹œ์ ๊ณผ ๋ฐฉ๋ฒ•์€

unsafe ๋ธ”๋ก์€ ์ €์ˆ˜์ค€ ์ฝ”๋“œ์— ๋Œ€ํ•ด ์ปดํŒŒ์ผ๋Ÿฌ์˜ ํŠน์ • ๊ฒ€์‚ฌ๋ฅผ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.

unsafe_rust.rsrust
// Understanding unsafe and its guarantees

// The 5 superpowers of unsafe:
// 1. Dereference raw pointers
// 2. Call unsafe functions
// 3. Access/modify mutable static variables
// 4. Implement unsafe traits
// 5. Access union fields

// RAW POINTERS
fn raw_pointers() {
    let mut num = 5;

    // Creating raw pointers is safe
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    // Dereferencing requires unsafe
    unsafe {
        println!("r1 is: {}", *r1);
        *r2 = 10;
        println!("r2 is: {}", *r2);
    }
}

// UNSAFE FUNCTION
// The function guarantees safety IF preconditions are met
unsafe fn dangerous() {
    // Code that assumes the caller verified invariants
}

fn call_dangerous() {
    // Must be in an unsafe block
    unsafe {
        dangerous();
    }
}

// SAFE ABSTRACTION over unsafe code
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = values.len();
    let ptr = values.as_mut_ptr();

    assert!(mid <= len);  // Runtime check

    unsafe {
        // We know the two slices don't overlap
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

// FFI: calling C code
extern "C" {
    fn abs(input: i32) -> i32;
}

fn call_c_function() {
    unsafe {
        println!("Absolute value: {}", abs(-3));
    }
}

// Export a function for C
#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Called from C!");
}

// MUTABLE STATIC
static mut COUNTER: u32 = 0;

fn increment_counter() {
    unsafe {
        COUNTER += 1;
        println!("COUNTER: {}", COUNTER);
    }
}

// UNSAFE TRAIT
unsafe trait Dangerous {
    // Implementers guarantee invariants
}

unsafe impl Dangerous for i32 {
    // Implementer asserts respecting the trait's invariants
}

// Practical example: structure with internal pointer
pub struct MyVec<T> {
    ptr: *mut T,
    len: usize,
    capacity: usize,
}

impl<T> MyVec<T> {
    pub fn new() -> Self {
        Self {
            ptr: std::ptr::null_mut(),
            len: 0,
            capacity: 0,
        }
    }

    pub fn push(&mut self, value: T) {
        if self.len == self.capacity {
            self.grow();
        }

        unsafe {
            std::ptr::write(self.ptr.add(self.len), value);
        }
        self.len += 1;
    }

    fn grow(&mut self) {
        // Unsafe allocation/reallocation...
    }
}

impl<T> Drop for MyVec<T> {
    fn drop(&mut self) {
        unsafe {
            // Properly free memory
            for i in 0..self.len {
                std::ptr::drop_in_place(self.ptr.add(i));
            }
            if self.capacity > 0 {
                let layout = std::alloc::Layout::array::<T>(self.capacity).unwrap();
                std::alloc::dealloc(self.ptr as *mut u8, layout);
            }
        }
    }
}
unsafe์˜ ์ฒ ์น™

unsafe ์ฝ”๋“œ์˜ ๋ฒ”์œ„๋ฅผ ์ตœ์†Œํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. unsafe ์ฝ”๋“œ๋Š” ๋ถˆ๋ณ€ ์กฐ๊ฑด์„ ๋ณด์žฅํ•˜๋Š” ์•ˆ์ „ํ•œ ์ถ”์ƒํ™” ์•ˆ์— ์บก์Аํ™”ํ•ฉ๋‹ˆ๋‹ค. unsafe ์ฝ”๋“œ๊ฐ€ ์ฃผ๋ณ€์˜ ์•ˆ์ „ํ•œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์†์ƒ์‹œ์ผœ์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.

์งˆ๋ฌธ 15: ๋นŒ๋ฆผ ๊ฒ€์‚ฌ๊ธฐ๋Š” ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๊นŒ

๋นŒ๋ฆผ ๊ฒ€์‚ฌ๊ธฐ๋Š” Rust ์ปดํŒŒ์ผ๋Ÿฌ์˜ ํ•ต์‹ฌ์œผ๋กœ, ์†Œ์œ ๊ถŒ๊ณผ ๋นŒ๋ฆผ ๊ทœ์น™์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

borrow_checker.rsrust
// Understanding how the borrow checker works

fn borrow_checker_basics() {
    let mut v = vec![1, 2, 3];

    // RULE 1: Either multiple immutable references or one mutable
    let r1 = &v;
    let r2 = &v;
    println!("{:?} {:?}", r1, r2);  // OK: multiple immutable references

    // From here, r1 and r2 are no longer used (NLL)

    let r3 = &mut v;  // OK thanks to Non-Lexical Lifetimes
    r3.push(4);
}

// The borrow checker tracks lifetimes
fn lifetime_tracking() {
    let mut data = String::from("hello");

    let slice = &data[..];  // Immutable borrow starts
    // data.push_str(" world");  // ERROR: cannot mutate during borrow
    println!("{}", slice);  // Last use of slice

    data.push_str(" world");  // OK: borrow ended
}

// Common problems and solutions
mod common_patterns {
    // Problem: borrowing two mutable fields
    struct Data {
        field1: Vec<i32>,
        field2: Vec<i32>,
    }

    fn problem(data: &mut Data) {
        // This sometimes doesn't compile directly:
        // let f1 = &mut data.field1;
        // let f2 = &mut data.field2;

        // Solution: destructuring
        let Data { field1, field2 } = data;
        field1.push(1);
        field2.push(2);
    }

    // Problem: iterate and modify
    fn iterate_and_modify() {
        let mut v = vec![1, 2, 3, 4, 5];

        // Does not compile:
        // for &x in &v {
        //     if x % 2 == 0 {
        //         v.push(x * 2);  // ERROR: borrowed by iterator
        //     }
        // }

        // Solution 1: collect indices first
        let to_add: Vec<i32> = v.iter()
            .filter(|&&x| x % 2 == 0)
            .map(|&x| x * 2)
            .collect();
        v.extend(to_add);

        // Solution 2: use explicit indices
        let len = v.len();
        for i in 0..len {
            if v[i] % 2 == 0 {
                let new_val = v[i] * 2;
                v.push(new_val);
            }
        }
    }

    // Problem: self-referential struct
    // struct SelfRef {
    //     data: String,
    //     slice: &str,  // Reference to data - IMPOSSIBLE
    // }

    // Solution: use indices or crates like ouroboros
    struct SafeSelfRef {
        data: String,
        slice_start: usize,
        slice_end: usize,
    }

    impl SafeSelfRef {
        fn get_slice(&self) -> &str {
            &self.data[self.slice_start..self.slice_end]
        }
    }
}

// Patterns to work around limitations
mod workarounds {
    use std::cell::RefCell;

    // Interior mutability when borrow checker is too restrictive
    struct Graph {
        nodes: RefCell<Vec<Node>>,
    }

    struct Node {
        value: i32,
    }

    impl Graph {
        fn add_node(&self, value: i32) {
            // Mutation possible despite &self
            self.nodes.borrow_mut().push(Node { value });
        }

        fn get_node(&self, index: usize) -> Option<i32> {
            self.nodes.borrow().get(index).map(|n| n.value)
        }
    }
}

๋นŒ๋ฆผ ๊ฒ€์‚ฌ๊ธฐ๋Š” ์ฒ˜์Œ์—๋Š” ์ œ์•ฝ์ด ์—„๊ฒฉํ•˜๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ด๋Ÿฌํ•œ ์ œ์•ฝ์„ ํ†ตํ•ด ๋‹ค๋ฅธ ์–ธ์–ด์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ „์ฒด ๋ฒ„๊ทธ ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

Rust ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ๋Š” ์†Œ์œ ๊ถŒ ์‹œ์Šคํ…œ์— ๋Œ€ํ•œ ๊นŠ์€ ์ดํ•ด, ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „์„ฑ ๋ณด์žฅ, ๋ฐ์ดํ„ฐ ๊ฒฝํ•ฉ ์—†๋Š” ๋™์‹œ์„ฑ ์ฝ”๋“œ์˜ ์ž‘์„ฑ ๋Šฅ๋ ฅ์ด ํ‰๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฐœ๋…์˜ ์ˆ™๋‹ฌ์ด Rust์˜ ๊ณ ์œ ํ•œ ์žฅ์ ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ๋ฐœ์ž๋ฅผ ๊ตฌ๋ณ„ํ•˜๋Š” ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.

๋ฉด์ ‘ ์ค€๋น„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • ์†Œ์œ ๊ถŒ, ๋นŒ๋ฆผ, 3๊ฐ€์ง€ ๊ธฐ๋ณธ ๊ทœ์น™์˜ ์ดํ•ด
  • ๋ผ์ดํ”„ํƒ€์ž„ ์–ด๋…ธํ…Œ์ด์…˜์ด ํ•„์š”ํ•œ ์‹œ์ ๊ณผ ๋ฐฉ๋ฒ• ํŒŒ์•…
  • ํŠธ๋ ˆ์ดํŠธ์™€ ์ •์ /๋™์  ๋””์ŠคํŒจ์น˜ ์ฐจ์ด์  ์ˆ™๋‹ฌ
  • Result์™€ Option์„ ํ™œ์šฉํ•œ ๊ด€์šฉ์  ์—๋Ÿฌ ์ฒ˜๋ฆฌ
  • ์ƒํ™ฉ์— ๋งž๋Š” ์ ์ ˆํ•œ ์Šค๋งˆํŠธ ํฌ์ธํ„ฐ ์„ ํƒ
  • Arc, Mutex, ์ฑ„๋„์„ ์‚ฌ์šฉํ•œ ๋™์‹œ์„ฑ ์ฝ”๋“œ ์ž‘์„ฑ
  • async/await์™€ tokio ๋“ฑ ๋Ÿฐํƒ€์ž„์˜ ์ดํ•ด
  • unsafe์˜ ์•ˆ์ „ํ•œ ์‚ฌ์šฉ ์‹œ์ ๊ณผ ๋ฐฉ๋ฒ• ํŒŒ์•…

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

Rust ๋ฉด์ ‘ ์ค€๋น„์—๋Š” ์–ธ์–ด ๊ณ ์œ ์˜ ์†Œ์œ ๊ถŒ ๊ฐœ๋…์— ๋Œ€ํ•œ ์—ฐ์Šต์ด ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค. Exercism์—์„œ์˜ ์—ฐ์Šต, ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ, Rust ์ƒํƒœ๊ณ„์— ๋Œ€ํ•œ ๊ธฐ์—ฌ๊ฐ€ ๊ฐ€์žฅ ๊นŒ๋‹ค๋กœ์šด ๊ธฐ์ˆ  ๋ฉด์ ‘์— ๋Œ€๋น„ํ•œ ์ง€์‹ ์ •์ฐฉ์— ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.

ํƒœ๊ทธ

#rust
#interview
#systems programming
#ownership
#technical interview

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

Rust์˜ ์†Œ์œ ๊ถŒ๊ณผ ๋นŒ๋ฆผ - ์™„๋ฒฝ ๊ฐ€์ด๋“œ

Rust์˜ ์†Œ์œ ๊ถŒ๊ณผ ๋นŒ๋ฆผ: ์™„๋ฒฝ ๊ฐ€์ด๋“œ

Rust์˜ ์†Œ์œ ๊ถŒ๊ณผ ๋นŒ๋ฆผ ์‹œ์Šคํ…œ์„ ๋งˆ์Šคํ„ฐํ•ฉ๋‹ˆ๋‹ค. ์†Œ์œ ๊ถŒ ๊ทœ์น™, ์ฐธ์กฐ, ๋ผ์ดํ”„ํƒ€์ž„, ๊ณ ๊ธ‰ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ํŒจํ„ด์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

๊ฒฝํ—˜ ์žˆ๋Š” ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ Rust ๊ธฐ์ดˆ ๊ฐ€์ด๋“œ

Rust ๊ธฐ์ดˆ: ๊ฒฝํ—˜ ์žˆ๋Š” ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ 2026๋…„ ๊ฐ€์ด๋“œ

C++, Java, Python ๊ฒฝํ—˜์„ ํ™œ์šฉํ•˜์—ฌ Rust๋ฅผ ํšจ์œจ์ ์œผ๋กœ ํ•™์Šตํ•˜๋Š” ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค. ์†Œ์œ ๊ถŒ, ๋นŒ๋ฆผ, ๋ผ์ดํ”„ํƒ€์ž„ ๋“ฑ Rust ํ•ต์‹ฌ ๊ฐœ๋…์„ ์ฒด๊ณ„์ ์œผ๋กœ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

Rust ์†Œ์œ ๊ถŒ๊ณผ ๋นŒ๋ฆผ ๊ฐœ๋…์„ ์„ค๋ช…ํ•˜๋Š” ๊ธฐ์ˆ  ๊ฐ€์ด๋“œ ์ปค๋ฒ„ ์ด๋ฏธ์ง€

Rust ์†Œ์œ ๊ถŒ๊ณผ ๋นŒ๋ฆผ: ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „์„ฑ์˜ ํ•ต์‹ฌ ์›๋ฆฌ ์™„๋ฒฝ ํ•ด์„ค

Rust์˜ ์†Œ์œ ๊ถŒ(Ownership), ๋นŒ๋ฆผ(Borrowing), ๋ผ์ดํ”„ํƒ€์ž„(Lifetime) ๊ฐœ๋…์„ ์ฒด๊ณ„์ ์œผ๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. ๋นŒ๋ฆผ ๊ฒ€์‚ฌ๊ธฐ์˜ ๋™์ž‘ ์›๋ฆฌ์™€ ์‹ค๋ฌด ํŒจํ„ด, ๊ธฐ์ˆ  ๋ฉด์ ‘ ๋Œ€๋น„ ํ•ต์‹ฌ ํฌ์ธํŠธ๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค.