Rust 바이블
목차
- Rust 소개
- 개발환경 설정
- 기초 문법
- 소유권 시스템
- 구조체와 열거형
- 패턴 매칭
- 에러 처리
- 제네릭과 트레이트
- 라이프타임
- 모듈과 크레이트
- 동시성 프로그래밍
- 고급 주제
- 실습 프로젝트
- 유용한 리소스
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. 소유권 시스템
소유권 규칙
- Rust의 각 값은 소유자(owner)가 있다
- 한 번에 하나의 소유자만 있을 수 있다
- 소유자가 스코프를 벗어나면 값이 삭제된다
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-2주: 기초 문법, 소유권 시스템
- 3-4주: 구조체, 열거형, 패턴 매칭
- 5-6주: 에러 처리, 제네릭, 트레이트
- 7-8주: 라이프타임, 모듈 시스템
- 9-10주: 동시성 프로그래밍
- 11-12주: 고급 주제, 실습 프로젝트
팁
- 컴파일러 메시지를 꼼꼼히 읽으세요 (Rust 컴파일러는 매우 친절합니다)
cargo clippy
를 자주 실행하여 코드 품질을 향상시키세요cargo fmt
로 코드 포맷팅을 자동화하세요- 테스트 코드를 작성하는 습관을 기르세요
- Rust 커뮤니티에 적극적으로 참여하세요 (Discord, Reddit, 포럼)
이 가이드를 따라 차근차근 학습하시면 Rust를 마스터할 수 있습니다! 🦀