본문으로 건너뛰기

Rust 바이블

목차

  1. Rust 소개
  2. 개발환경 설정
  3. 기초 문법
  4. 소유권 시스템
  5. 구조체와 열거형
  6. 패턴 매칭
  7. 에러 처리
  8. 제네릭과 트레이트
  9. 라이프타임
  10. 모듈과 크레이트
  11. 동시성 프로그래밍
  12. 고급 주제
  13. 실습 프로젝트
  14. 유용한 리소스

1. Rust 소개

Rust란?

  • 시스템 프로그래밍 언어: 메모리 안전성과 성능을 동시에 보장
  • 메모리 안전: 가비지 컬렉터 없이도 메모리 안전성 제공
  • 제로코스트 추상화: 고수준 기능을 저수준 성능으로 구현
  • 동시성: 안전한 멀티스레딩 지원

Rust의 장점

  • 메모리 안전성 (버퍼 오버플로우, 메모리 누수 방지)
  • 스레드 안전성 (데이터 레이스 방지)
  • 뛰어난 성능 (C/C++과 비슷한 속도)
  • 강력한 타입 시스템
  • 우수한 패키지 관리 (Cargo)

2. 개발환경 설정

Rust 설치

# rustup 설치 (권장)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 환경변수 적용
source $HOME/.cargo/env

# 설치 확인
rustc --version
cargo --version

개발 도구

  • 에디터: VS Code + rust-analyzer 확장
  • IDE: IntelliJ IDEA + Rust 플러그인
  • 디버거: rust-gdb, rust-lldb

첫 프로젝트 생성

# 새 프로젝트 생성
cargo new hello_world
cd hello_world

# 프로젝트 실행
cargo run

# 빌드만 하기
cargo build

# 릴리스 빌드
cargo build --release

3. 기초 문법

변수와 상수

fn main() {
// 불변 변수 (기본)
let x = 5;

// 가변 변수
let mut y = 10;
y = 15;

// 상수
const MAX_POINTS: u32 = 100_000;

// 변수 섀도잉
let x = x + 1; // 새로운 x 생성
}

데이터 타입

fn main() {
// 정수형
let a: i32 = 42;
let b: u64 = 100;

// 부동소수점
let f: f64 = 3.14;

// 불린
let is_true: bool = true;

// 문자
let c: char = 'A';

// 문자열
let s: &str = "Hello"; // 문자열 슬라이스
let string: String = String::from("World"); // 소유된 문자열

// 배열
let arr: [i32; 5] = [1, 2, 3, 4, 5];

// 튜플
let tup: (i32, f64, char) = (42, 3.14, 'A');
let (x, y, z) = tup; // 튜플 디스트럭처링
}

함수

fn main() {
let result = add(5, 3);
println!("Result: {}", result);
}

fn add(a: i32, b: i32) -> i32 {
a + b // 세미콜론 없음 = 반환값
}

// 조기 반환
fn divide(a: f64, b: f64) -> f64 {
if b == 0.0 {
return 0.0; // 조기 반환
}
a / b
}

제어 구조

fn main() {
// if 표현식
let number = 6;
let result = if number % 2 == 0 {
"even"
} else {
"odd"
};

// loop
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // loop에서 값 반환
}
};

// while
let mut n = 3;
while n != 0 {
println!("{}!", n);
n -= 1;
}

// for
let arr = [10, 20, 30, 40, 50];
for element in arr.iter() {
println!("값: {}", element);
}

for i in 0..5 {
println!("인덱스: {}", i);
}
}

4. 소유권 시스템

소유권 규칙

  1. Rust의 각 값은 소유자(owner)가 있다
  2. 한 번에 하나의 소유자만 있을 수 있다
  3. 소유자가 스코프를 벗어나면 값이 삭제된다
fn main() {
// 소유권 이동 (move)
let s1 = String::from("hello");
let s2 = s1; // s1의 소유권이 s2로 이동
// println!("{}", s1); // 에러! s1은 더 이상 유효하지 않음

// 복사 (Copy 트레이트를 구현한 타입)
let x = 5;
let y = x; // i32는 Copy 트레이트 구현, 값이 복사됨
println!("x: {}, y: {}", x, y); // 둘 다 사용 가능

// 함수와 소유권
let s = String::from("world");
takes_ownership(s); // s의 소유권이 함수로 이동
// println!("{}", s); // 에러!

let x = 5;
makes_copy(x); // i32는 복사됨
println!("{}", x); // 여전히 사용 가능
}

fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string이 스코프를 벗어나 drop 호출

fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // some_integer가 스코프를 벗어나지만 별일 없음

참조와 빌림

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

// 불변 참조
let len = calculate_length(&s1);
println!("'{}' 의 길이는 {} 입니다.", s1, len);

// 가변 참조
let mut s = String::from("hello");
change(&mut s);
println!("{}", s);

// 가변 참조 규칙
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // 에러! 가변 참조는 하나만 가능

// 불변 참조와 가변 참조 동시 사용 불가
let mut s = String::from("hello");
let r1 = &s; // 문제없음
let r2 = &s; // 문제없음
// let r3 = &mut s; // 에러!
}

fn calculate_length(s: &String) -> usize {
s.len()
} // s는 참조이므로 drop되지 않음

fn change(some_string: &mut String) {
some_string.push_str(", world");
}

슬라이스

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

// 문자열 슬라이스
let hello = &s[0..5];
let world = &s[6..11];
let hello = &s[..5]; // 처음부터
let world = &s[6..]; // 끝까지
let whole = &s[..]; // 전체

// 배열 슬라이스
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // [2, 3]
}

fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

5. 구조체와 열거형

구조체

// 구조체 정의
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}

// 튜플 구조체
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

// 유닛 구조체
struct AlwaysEqual;

impl User {
// 연관 함수 (생성자)
fn new(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}

// 메서드
fn is_active(&self) -> bool {
self.active
}

// 가변 메서드
fn deactivate(&mut self) {
self.active = false;
}

// 소유권을 가져가는 메서드
fn delete(self) -> String {
format!("사용자 {}가 삭제되었습니다", self.username)
}
}

fn main() {
// 구조체 인스턴스 생성
let mut user1 = User::new(
String::from("user@example.com"),
String::from("user123"),
);

// 필드 접근
user1.email = String::from("new@example.com");

// 메서드 호출
println!("활성: {}", user1.is_active());
user1.deactivate();

// 구조체 업데이트 문법
let user2 = User {
email: String::from("another@example.com"),
..user1 // 나머지 필드는 user1에서 복사
};
}

열거형

// 기본 열거형
enum IpAddrKind {
V4,
V6,
}

// 데이터를 포함하는 열거형
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}

// 복잡한 열거형
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

impl Message {
fn call(&self) {
match self {
Message::Quit => println!("종료"),
Message::Move { x, y } => println!("이동: ({}, {})", x, y),
Message::Write(text) => println!("쓰기: {}", text),
Message::ChangeColor(r, g, b) => println!("색상 변경: ({}, {}, {})", r, g, b),
}
}
}

// Option 열거형 활용
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;

// Option 값 사용
if let Some(i) = some_number {
println!("값: {}", i);
}
}

6. 패턴 매칭

match 표현식

enum Coin {
Penny,
Nickel,
Dime,
Quarter(String), // 주 이름 포함
}

fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("행운의 페니!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("{}주 쿼터!", state);
25
},
}
}

fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}

fn main() {
// match with 범위
let x = 5;
match x {
1..=5 => println!("1부터 5"),
6..=10 => println!("6부터 10"),
_ => println!("기타"),
}

// match with 가드
let num = Some(4);
match num {
Some(x) if x < 5 => println!("5보다 작음: {}", x),
Some(x) => println!("{}", x),
None => (),
}
}

if let

fn main() {
let some_u8_value = Some(3);

// match 대신 if let 사용
if let Some(3) = some_u8_value {
println!("3입니다!");
}

// else와 함께
let mut count = 0;
let coin = Coin::Quarter(String::from("Alaska"));

if let Coin::Quarter(state) = coin {
println!("{}주 쿼터!", state);
} else {
count += 1;
}
}

while let

fn main() {
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);

while let Some(top) = stack.pop() {
println!("{}", top);
}
}

7. 에러 처리

Result와 Option

use std::fs::File;
use std::io::ErrorKind;

fn main() {
// Result 처리
let f = File::open("hello.txt");

let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("파일 생성 문제: {:?}", e),
},
other_error => panic!("파일 열기 문제: {:?}", other_error),
},
};
}

// 에러 전파
fn read_username_from_file() -> Result<String, std::io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}

// 더 간단한 버전
fn read_username_simple() -> Result<String, std::io::Error> {
std::fs::read_to_string("hello.txt")
}

커스텀 에러 타입

use std::fmt;

#[derive(Debug)]
enum MathError {
DivisionByZero,
NegativeLogarithm,
}

impl fmt::Display for MathError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MathError::DivisionByZero => write!(f, "0으로 나눌 수 없습니다"),
MathError::NegativeLogarithm => write!(f, "음수의 로그는 계산할 수 없습니다"),
}
}
}

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

fn divide(a: f64, b: f64) -> Result<f64, MathError> {
if b == 0.0 {
Err(MathError::DivisionByZero)
} else {
Ok(a / b)
}
}

fn main() {
match divide(10.0, 0.0) {
Ok(result) => println!("결과: {}", result),
Err(err) => println!("에러: {}", err),
}
}

8. 제네릭과 트레이트

제네릭

// 제네릭 함수
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];

for &item in list {
if item > largest {
largest = item;
}
}

largest
}

// 제네릭 구조체
struct Point<T> {
x: T,
y: T,
}

impl<T> Point<T> {
fn new(x: T, y: T) -> Self {
Point { x, y }
}
}

// 특정 타입에 대한 구현
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}

// 제네릭 열거형
enum Option<T> {
Some(T),
None,
}

enum Result<T, E> {
Ok(T),
Err(E),
}

트레이트

// 트레이트 정의
trait Summary {
fn summarize(&self) -> String;

// 기본 구현
fn summarize_author(&self) -> String {
String::from("Unknown")
}
}

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

// 트레이트를 매개변수로 사용
fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}

// 트레이트 경계 문법
fn notify2<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}

// 여러 트레이트 경계
fn notify3<T: Summary + Display>(item: &T) {
// ...
}

// where 절 사용
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
// ...
}

연관 타입

trait Iterator {
type Item; // 연관 타입

fn next(&mut self) -> Option<Self::Item>;
}

struct Counter {
current: usize,
max: usize,
}

impl Iterator for Counter {
type Item = usize;

fn next(&mut self) -> Option<Self::Item> {
if self.current < self.max {
let current = self.current;
self.current += 1;
Some(current)
} else {
None
}
}
}

9. 라이프타임

라이프타임 기본

// 라이프타임 명시
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}

// 구조체의 라이프타임
struct ImportantExcerpt<'a> {
part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}

// 라이프타임 생략 규칙 적용
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}

fn main() {
let string1 = String::from("long string is long");

{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}

let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let i = ImportantExcerpt {
part: first_sentence,
};
}

정적 라이프타임

// 'static 라이프타임
let s: &'static str = "I have a static lifetime.";

// 제네릭 타입 매개변수, 트레이트 경계, 라이프타임 함께 사용
fn longest_with_an_announcement<'a, T>(
x: &'a str,
y: &'a str,
ann: T,
) -> &'a str
where
T: Display,
{
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}

10. 모듈과 크레이트

모듈 시스템

// src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}

fn seat_at_table() {}
}

mod serving {
fn take_order() {}

fn serve_order() {}

fn take_payment() {}
}
}

pub fn eat_at_restaurant() {
// 절대 경로
crate::front_of_house::hosting::add_to_waitlist();

// 상대 경로
front_of_house::hosting::add_to_waitlist();
}

// use 키워드
use crate::front_of_house::hosting;

pub fn eat_at_restaurant2() {
hosting::add_to_waitlist();
}

// as 키워드로 별칭
use std::fmt::Result;
use std::io::Result as IoResult;

// pub use로 재내보내기
pub use crate::front_of_house::hosting;

// 중첩 경로
use std::{cmp::Ordering, io};
use std::io::{self, Write};

// glob 연산자
use std::collections::*;

별도 파일로 모듈 분리

// src/lib.rs
mod front_of_house;

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}

// src/front_of_house.rs
pub mod hosting {
pub fn add_to_waitlist() {}
}

// 또는 src/front_of_house/mod.rs
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}

11. 동시성 프로그래밍

스레드

use std::thread;
use std::time::Duration;

fn main() {
// 기본 스레드 생성
let handle = thread::spawn(|| {
for i in 1..10 {
println!("spawned thread: {}", i);
thread::sleep(Duration::from_millis(1));
}
});

for i in 1..5 {
println!("main thread: {}", i);
thread::sleep(Duration::from_millis(1));
}

// 스레드 완료 대기
handle.join().unwrap();

// move 클로저
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("벡터: {:?}", v);
});

handle.join().unwrap();
}

메시지 패싱

use std::sync::mpsc;
use std::thread;

fn main() {
let (tx, rx) = mpsc::channel();

thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
});

let received = rx.recv().unwrap();
println!("받음: {}", received);

// 여러 값 전송
let (tx, rx) = mpsc::channel();

thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];

for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});

for received in rx {
println!("받음: {}", received);
}

// 여러 생산자
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();

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

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

for received in rx {
println!("받음: {}", received);
}
}

공유 상태

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

fn main() {
// Mutex 사용
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!("결과: {}", *counter.lock().unwrap());
}

async/await

// Cargo.toml에 tokio 추가 필요
// tokio = { version = "1", features = ["full"] }

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

#[tokio::main]
async fn main() {
let handle1 = tokio::spawn(async {
for i in 1..5 {
println!("Task 1: {}", i);
sleep(Duration::from_millis(100)).await;
}
});

let handle2 = tokio::spawn(async {
for i in 1..5 {
println!("Task 2: {}", i);
sleep(Duration::from_millis(150)).await;
}
});

// 두 태스크 완료 대기
let _ = tokio::try_join!(handle1, handle2);
}

async fn fetch_data() -> Result<String, Box<dyn std::error::Error>> {
// 비동기 HTTP 요청 등
sleep(Duration::from_secs(1)).await;
Ok("Data fetched".to_string())
}

12. 고급 주제

스마트 포인터

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

// Box<T>
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}

// Rc<T> - 참조 카운팅
#[derive(Debug)]
enum List {
Cons(i32, Rc<List>),
Nil,
}

use List::{Cons, Nil};

fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));

let b = Cons(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a));

{
let c = Cons(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
}

println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}

// RefCell<T> - 내부 가변성
fn main() {
let value = Rc::new(RefCell::new(5));

let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));

*value.borrow_mut() += 10;

println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}

매크로

// 선언적 매크로
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}

// 사용자 정의 매크로
macro_rules! my_print {
($($arg:tt)*) => {
println!("DEBUG: {}", format!($($arg)*));
};
}

fn main() {
my_print!("Hello, {}!", "world");

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

// 절차적 매크로 (별도 crate 필요)
// use proc_macro::TokenStream;
// use quote::quote;
// use syn;

// #[proc_macro_derive(HelloMacro)]
// pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// let ast = syn::parse(input).unwrap();
// impl_hello_macro(&ast)
// }

unsafe Rust

fn main() {
// 원시 포인터
let mut num = 5;

let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

unsafe {
println!("r1: {}", *r1);
println!("r2: {}", *r2);
}

// unsafe 함수 호출
unsafe {
dangerous();
}
}

unsafe fn dangerous() {}

// C 함수 호출
extern "C" {
fn abs(input: i32) -> i32;
}

fn main() {
unsafe {
println!("C abs(-3): {}", abs(-3));
}
}

// 전역 변수
static mut COUNTER: usize = 0;

fn add_to_count(inc: usize) {
unsafe {
COUNTER += inc;
}
}

13. 실습 프로젝트

프로젝트 1: 추측 게임

use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {
println!("숫자를 맞춰보세요!");

let secret_number = rand::thread_rng().gen_range(1..=100);

loop {
println!("추측한 수를 입력하세요.");

let mut guess = String::new();

io::stdin()
.read_line(&mut guess)
.expect("입력 읽기 실패");

let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};

println!("당신의 추측: {}", guess);

match guess.cmp(&secret_number) {
Ordering::Less => println!("너무 작습니다!"),
Ordering::Greater => println!("너무 큽니다!"),
Ordering::Equal => {
println!("정답입니다!");
break;
}
}
}
}

프로젝트 2: 간단한 웹 서버

// Cargo.toml
// [dependencies]
// tokio = { version = "1", features = ["full"] }

use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};
use std::fs;

fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

for stream in listener.incoming() {
let stream = stream.unwrap();

handle_connection(stream);
}
}

fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();

let get = b"GET / HTTP/1.1\r\n";

let (status_line, filename) = if buffer.starts_with(get) {
("HTTP/1.1 200 OK", "hello.html")
} else {
("HTTP/1.1 404 NOT FOUND", "404.html")
};

let contents = fs::read_to_string(filename).unwrap();
let response = format!(
"{}\r\nContent-Length: {}\r\n\r\n{}",
status_line,
contents.len(),
contents
);

stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}

프로젝트 3: CLI 도구

// Cargo.toml
// [dependencies]
// clap = { version = "4.0", features = ["derive"] }

use clap::{Parser, Subcommand};
use std::fs;
use std::io::{self, Write};

#[derive(Parser)]
#[command(name = "mygrep")]
#[command(about = "A simple grep clone")]
struct Cli {
#[command(subcommand)]
command: Commands,
}

#[derive(Subcommand)]
enum Commands {
Search {
pattern: String,
file: String,
},
Count {
pattern: String,
file: String,
},
}

fn main() {
let cli = Cli::parse();

match &cli.command {
Commands::Search { pattern, file } => {
search(pattern, file);
}
Commands::Count { pattern, file } => {
count(pattern, file);
}
}
}

fn search(pattern: &str, filename: &str) {
let contents = fs::read_to_string(filename)
.expect("파일을 읽을 수 없습니다");

for (line_num, line) in contents.lines().enumerate() {
if line.contains(pattern) {
println!("{}: {}", line_num + 1, line);
}
}
}

fn count(pattern: &str, filename: &str) {
let contents = fs::read_to_string(filename)
.expect("파일을 읽을 수 없습니다");

let count = contents.lines()
.filter(|line| line.contains(pattern))
.count();

println!("패턴 '{}' 발견 횟수: {}", pattern, count);
}

14. 유용한 리소스

공식 문서

연습 사이트

유용한 크레이트

  • 웹 개발: axum, warp, actix-web
  • CLI: clap, structopt
  • 비동기: tokio, async-std
  • 시리얼라이제이션: serde, serde_json
  • 에러 처리: anyhow, thiserror
  • 테스팅: proptest, mockall
  • 날짜/시간: chrono, time
  • HTTP 클라이언트: reqwest, hyper

학습 로드맵

  1. 1-2주: 기초 문법, 소유권 시스템
  2. 3-4주: 구조체, 열거형, 패턴 매칭
  3. 5-6주: 에러 처리, 제네릭, 트레이트
  4. 7-8주: 라이프타임, 모듈 시스템
  5. 9-10주: 동시성 프로그래밍
  6. 11-12주: 고급 주제, 실습 프로젝트

  • 컴파일러 메시지를 꼼꼼히 읽으세요 (Rust 컴파일러는 매우 친절합니다)
  • cargo clippy를 자주 실행하여 코드 품질을 향상시키세요
  • cargo fmt로 코드 포맷팅을 자동화하세요
  • 테스트 코드를 작성하는 습관을 기르세요
  • Rust 커뮤니티에 적극적으로 참여하세요 (Discord, Reddit, 포럼)

이 가이드를 따라 차근차근 학습하시면 Rust를 마스터할 수 있습니다! 🦀