Community코딩 & 개발github.com

tenequm/rust-dev

첫날부터 Rust를 잘 활용하기 위한 가이드 - 소유권, 오류를 값으로 다루기, String vs &str, Box/Rc/Arc, anyhow vs thiserror, 그리고 필수 크레이트 목록(tokio, serde, axum, sqlx). Rust 프로젝트를 시작하거나, 빌림 검사기와 싸우거나, 크레이트를 선택할 때 사용하십시오.

rust-dev란 무엇인가요?

이 스킬은 Rust 개발을 처음 시작하는 개발자가 프로젝트 첫 커밋부터 올바른 결정을 내릴 수 있도록 실용적인 기초를 제공합니다. 소유권, 빌림, 오류 처리, 타입 설계 등 Rust 고유의 개념을 다른 언어(파이썬, 자바스크립트, Go, Java/C#, C++)와의 차이점을 중심으로 설명하며, `String` vs `&str`, `Box`/`Rc`/`Arc` 선택 기준, `anyhow` vs `thiserror` 같은 당면한 결정 사항에 대한 명확한 기준을 제시합니다. 또한 CLI, 서비스, 라이브러리 등 대부분의 실제 애플리케이션을 구축하는 데 필요한 필수 크레이트 목록(tokio, serde, clap, axum, sqlx, tracing 등)과 초보자가 흔히 빠지는 함정을 피하는 방법을 상세히 다룹니다. 이 스킬은 Claude Code, Cursor, Codex와 같은 AI 에이전트와 함께 Rust 프로젝트를 시작할 때 가장 유용하며, SKILL.md 문서는 실제 Rust 코드베이스에서 바로 적용 가능한 모범 사례와 의사 결정 테이블을 제공합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/tenequm/skills/tree/HEAD/skills/rust-dev

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Rust 개발 - 첫날

Rust 애플리케이션을 첫 커밋부터 잘 작성하기 위한 실용적인 기초 가이드입니다. 교과서가 아닙니다. 다른 언어와의 차이점, 모든 것을 결정짓는 첫날의 선택, 그리고 대부분의 실제 애플리케이션을 포괄하는 적은 수의 크레이트에 초점을 맞춥니다.

사용 시기

  • 새로운 Rust 프로젝트(CLI, 서비스, 라이브러리)를 시작할 때
  • Python, JavaScript, Go, Java/C#, C++에서 Rust로 전환할 때
  • 소유/빌림 타입, 스마트 포인터, 트레이트 객체 vs 제네릭 중에서 선택할 때
  • 오류 처리 전략(anyhow vs thiserror)을 결정할 때
  • 어떤 크레이트를 사용할지 결정할 때
  • 최소한의 but 의견이 반영된 Cargo.toml, clippy, rustfmt을 구성할 때

첫날 설정

# 1. 도구 체인 설치 (rustup은 도구 체인 관리자)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 2. 구성 요소 확인 (rustfmt와 clippy는 stable과 함께 제공되며, rust-src는 IDE 기능을 활성화합니다)
rustup component add rustfmt clippy rust-src

# 3. 프로젝트 생성
cargo new my-app          # 바이너리 (src/main.rs)
cargo new --lib my-lib    # 라이브러리 (src/lib.rs)

# 4. 개발 루프 (이 네 가지를 외우세요)
cargo check     # 빠른 타입 검사, 코드 생성 없음
cargo run       # 빌드 및 실행 (바이너리)
cargo test      # 빌드 및 테스트 실행 (doctest 포함)
cargo clippy --all-targets   # 린트, 테스트/ 및 benches/ 포함 (푸시 전 실행)
cargo fmt       # 포맷팅

# 5. Cargo.toml을 직접 수정하지 않고 의존성 관리
cargo add tokio --features full
cargo remove tokio
cargo update                 # 기존 semver 범위 내에서 Cargo.lock 재계산

cargo update는 Cargo.toml에 이미 명시된 버전 범위 내에서만 이동합니다. 주 버전(1.x에서 2.0)을 넘으려면 Cargo.toml을 직접 수정하거나 cargo add <crate>@2를 사용해야 합니다.

rust-analyzer는 필수입니다. 이는 모든 편집기(VS Code, Zed, Neovim, Helix, RustRover는 자체 엔진을 사용하지만 유사함)가 사용하는 언어 서버입니다. VS Code에서는 rust-analyzer 확장을 설치하고 rust-analyzer.check.command를 "clippy"로 설정하여 저장 시 린트 피드백을 받도록 하십시오.

파일 감시자가 필요하신가요? cargo install --locked bacon을 실행한 후 프로젝트에서 bacon을 실행하십시오. 첫날에는 필요하지 않습니다.

5가지 아이디어로 보는 Rust 정신 모델

Rust는 대부분의 언어에서 당연시하는 두 가지(가비지 컬렉터와 예외)를 메모리, 데이터 경합, 오류 처리에 대한 컴파일 타임 보장과 맞바꿉니다. 언어의 형태는 이러한 거래에서 비롯됩니다.

1. 소유권: 모든 값은 정확히 하나의 소유자를 가집니다

값을 물리적 객체(책, 파일, 네트워크 연결)로 생각하십시오. 어떤 순간에 하나의 변수가 그것을 소유합니다. 다음을 할 수 있습니다:

  • 이동: let b = a;는 소유권을 b에 넘깁니다. a는 사라집니다.
  • 불변으로 빌리기: &a는 다른 사람이 볼 수 있게 합니다. 여러 읽기 권한자 허용.
  • 가변으로 빌리기: &mut a는 한 사람이 수정할 수 있게 합니다. 배타적 접근.
  • 복제: a.clone()은 깊은 복사본을 만듭니다. 둘 다 각자의 것을 유지합니다.

소유자가 범위를 벗어나면 값은 해제됩니다(메모리 해제, 파일 닫힘, 잠금 해제). GC도, 수동 free도 없습니다. 이것이 RAII이며, 컴파일러가 강제합니다.

2. 별칭 XOR 가변성

어떤 순간에 데이터는 다음 중 하나를 가집니다:

  • 하나의 가변 참조(&mut T), 또는
  • 임의 개수의 불변 참조(&T),

둘 다 동시에는 불가능합니다. 이 하나의 규칙이 데이터 경합과 대부분의 use-after-free 버그를 제거합니다. 빌림 검사기가 이를 강제합니다. 검사기가 불평할 때, 이는 언어가 어렵다는 것이 아니라 데이터 소유권 구조가 불분명하다는 것을 알려주는 것입니다.

3. 오류는 값이지 예외가 아닙니다

try/catch가 없습니다. 실패할 수 있는 함수는 Result<T, E>를 반환합니다. 유용한 값을 반환하지 않을 수 있는 함수는 Option<T>를 반환합니다. 컴파일러는 둘 다 처리하도록 강제합니다. ? 연산자는 한 문자로 오류를 호출 스택 위로 전파합니다:

fn read_config() -> Result<Config, anyhow::Error> {
    let text = std::fs::read_to_string("config.toml")?;  // ? = Err 시 조기 반환
    let config = toml::from_str(&text)?;
    Ok(config)
}

null은 없습니다. Option<T>는 None 또는 Some(value)입니다. 컴파일러가 None 경우를 잊는 것을 허용하지 않습니다.

4. 트레이트는 Java 인터페이스가 아닙니다

trait는 동작을 정의합니다. 타입은 트레이트를 impl합니다. 여기까지는 익숙합니다. 차이점은:

  • 정적 디스패치가 기본값입니다. fn f<T: Display>(x: T)를 작성하면 컴파일러는 호출하는 각 구체적인 T에 대해 f의 별도 복사본을 생성합니다(단형화, C++ 템플릿과 유사). 런타임 오버헤드가 전혀 없습니다.
  • 동적 디스패치는 선택 사항이며 dyn Trait(일반적으로 Box<dyn Trait> 또는 &dyn Trait)를 통해 사용합니다. 호출당 하나의 vtable 조회.
  • 상속이 없습니다. 트레이트는 구성됩니다. Deref를 사용하여 타입을 "확장"하려는 유혹이 든다면 멈추고 구성 또는 열거형을 사용하십시오.
  • 고아 규칙: YourTrait을 SomeoneElsesType에 대해 impl하거나 SomeoneElsesTrait를 YourType에 대해 impl할 수 있지만, 둘 다 외부인 것은 안 됩니다. 이는 의존성 해결을 건전하게 유지합니다.

5. 빌림 검사기는 설계 신탁입니다

초보자의 가장 흔한 실수는 컴파일러 오류를 침묵시켜야 할 장애물로 취급하는 것입니다. 그렇지 않습니다. 거의 모든 빌림-검사 오류는 누가 무엇을 소유하는지에 대한 실제 문제를 드러냅니다. 막혔을 때, 물어야 할 질문은 "이걸 어떻게 컴파일하게 만들지"가 거의 항상 아니고 "내가 여기서 원하는 실제 소유권 관계는 무엇인가?"입니다. 오류를 읽으십시오. 컴파일러는 비정상적으로 유익합니다.

모든 함수 시그니처를 위한 3가지 질문

함수를 작성하기 전에, 입력을 소유, 읽기, 또는 수정해야 합니까?

fn consume(s: String)        // 소유: 함수가 책임을 지고, 호출자는 잃음
fn read(s: &str)             // 읽기: 함수가 보고, 호출자는 유지
fn modify(s: &mut String)    // 수정: 함수가 제자리에서 변경

90%의 경우에 통하는 기본값:

  • 함수 매개변수: String보다 &str, Vec<T>보다 &[T]를 선호합니다(이들은 슬라이스이며, 소유 및 빌림 호출자 모두를 받습니다).
  • 함수 반환: 소유 타입(String, Vec<T>)을 반환합니다. 참조 반환은 수명을 의미합니다. 필요할 때까지 피하십시오.
  • 구조체 필드: 소유 타입(String, Vec<T>)을 선호합니다. 구조체에 &str을 저장하는 것은 가장 흔한 초보자 함정이며, 구조체를 보유하는 모든 타입에 수명 주석이 계단식으로 퍼집니다.

첫날 결정 표

처음에 직면하는 선택에 대한 한 줄 답변입니다.

결정기본값다른 것을 선택해야 할 때
String vs &str (구조체 필드)String실제 이유가 있고 수명을 이해하기 전까지는 거의 항상 &str 안 됨
String vs &str (함수 매개변수)&str내부에서 소유/저장해야 하는 경우에만 String 사용
Vec<T> vs &[T] (매개변수)&[T]소유해야 하는 경우에만 Vec<T>
Box<T> vs Rc<T> vs Arc<T>Box<T> (단일 소유자, 힙)스레드 간 공유 소유권에는 Arc<T>. 기본값으로 Rc<T>는 피하십시오; 비동기로 전환할 때 리팩터링하지 않도록 Arc<T>를 사용하십시오
RefCell<T> vs Mutex<T>Mutex<T> (또는 RwLock<T>)같은 이유: async/스레드에서 작동하는 반면 RefCell은 작동하지 않음
Option<T> vs Result<T, E>"값 없음"에는 Option<T>, "실패한 이유"에는 Result<T, E>부재가 호출자가 처리해야 할 의미를 지닌다면 Result
dyn Trait vs impl Trait / <T: Trait>제네릭 (<T: Trait> 또는 impl Trait) - 정적 디스패치이종 컬렉션(Vec<Box<dyn Animal>>)이 필요할 때 Box<dyn Trait>
애플리케이션 코드의 오류모든 곳에 anyhow::Result<T>-
라이브러리 코드의 오류thiserror에서 파생된 열거형공개 라이브러리 API에서는 절대 Box<dyn Error>를 사용하지 마십시오 - 호출자가 다운캐스트해야 함
&self vs &mut self vs selfgetter에는 &self, setter에는 &mut self, 빌더/소비 연산에는 self-
모듈 레이아웃파일이 길어질 때까지 인라인 모듈, 그 다음 분할하나의 모듈 = 하나의 파일은 Java/C#의 본능이지 Rust의 본능이 아님

초기에 내재화해야 할 관용구

이것들은 거의 모든 Rust 프로그램에 등장합니다. 첫 주 안에 배우십시오.

오류 전파를 위한 ?. 9줄의 match를 한 문자로 대체합니다.

let body = reqwest::get(url).await?.text().await?;

수동 루프 대신 반복자 체인. 수동 루프와 동일한 기계어 코드로 컴파일됩니다(LLVM이 클로저를 인라인화). 관용적인 Rust는 함수형 스타일입니다.

let active_emails: Vec<String> = users
    .iter()
    .filter(|u| u.active)
    .map(|u| u.email.clone())
    .collect();

match의 완전성. 열거형에 새 변형을 추가하면 이를 처리하지 않는 모든 match가 컴파일 오류가 됩니다. 이것을 사용하십시오. 이는 모든 언어에서 가장 강력한 리팩터링 도구 중 하나입니다.

컴파일러가 리팩터링을 주도하도록 하십시오. 동일한 트릭이 구조체에서도 작동합니다: Default가 없는 필수 필드를 추가하면 모든 기존 생성자가 컴파일 오류가 됩니다 - 업데이트해야 할 모든 사이트의 무료 완전 작업 목록입니다.

if let과 let else 는 일반적인 단일 팔 match를 위해 사용합니다.

if let Some(name) = user.name { println!("hi {name}"); }

let Some(name) = user.name else { return Err(anyhow!("no name")); };
// 여기서부터 `name`은 범위 내에 있으며, 중첩 없음

타입 변환을 위한 From / Into. From을 구현하면 Into를 무료로 얻습니다. ?는 From을 사용하여 오류 타입을 자동으로 변환합니다.

Option / Result에 대한 조합자. match에 손을 대기 전에 .map, .and_then, .unwrap_or, .unwrap_or_else, .ok_or를 먼저 사용하십시오.

파생 매크로. #[derive(Debug, Clone, PartialEq)]는 상용구의 80%를 무료로 제공합니다. JSON을 위해 #[derive(Serialize, Deserialize)]를 추가하십시오.

최근 문법. 알아둘 가치가 있는 안정화된 기능, 최신순: bool::ok_or 및 ok_or_else (1.98)는 조건을 직접 Result로 바꿉니다 (ready.ok_or(Error::NotReady)?). cfg_select!는 cfg 조건자에 대한 컴파일 타임 match로, cfg-if 크레이트의 대부분의 사용을 대체하며, if let 가드는 match 팔에서 작동합니다 (match x { Some(v) if let Ok(n) = v.parse::<i32>() => ... }) - 둘 다 1.95. assert_matches! 및 debug_assert_matches! (1.96)는 동등성이 아닌 패턴에 대해 단언하며, 이는 열거형에 자연스러운 단언입니다. 계속해서 볼 수 있고 드물다고 생각해서는 안 되는 두 가지 오래된 기능: let-체인 (if let Some(x) = a && x > 3 { ... }), 1.88부터 2024 에디션에서 안정화, 및 비동기 클로저 (async || { ... }, AsyncFn 계열 바운드 포함), 1.85부터 안정화.

X에서 왔다면, 여기에 걸려들 것입니다

Python 또는 JavaScript에서 온 경우:

  • 힙 값(예: String, Vec)에 대한 let b = a;는 그것을 이동시킵니다. a는 더 이상 사용할 수 없습니다. 빌리려면 &a를, 복사하려면 a.clone()을 사용하십시오.
  • null이 없습니다. Option<T>가 강제됩니다.
  • 예외가 없습니다. Result<T, E>와 ?. 컴파일러가 오류를 무시하는 것을 허용하지 않습니다.
  • 상속이 없습니다. 구성 + 트레이트 + 열거형.
  • 변수는 기본적으로 불변입니다. 변경하려면 mut를 추가하십시오. 참조도 마찬가지: & vs &mut.
  • 이름 재선언이 허용되며 관용적입니다: let input = input.trim();은 이전 바인딩을 새 바인딩(다른 타입일 수 있음)으로 가립니다. 이는 변이가 아니며, Rust 코드가 input2를 만들지 않고 값을 제자리에서 변환하는 방법입니다.
  • 정수 타입은 명시적이며 인덱싱에는 usize가 필요합니다. 또한 빌드에 따라 오버플로우 방식이 다릅니다: 디버그 "는 프로그램이 _런타임에 패닉_을 일으키는 정수 오버플로우 검사를 포함"하는 반면, --release는 이를 생략하고 "2의 보수 래핑"을 수행합니다. 통과하는 테스트 스위트도 프로덕션에서 래핑될 수 있으므로, 디버그 패닉에 의존하기보다는 산술이 실제로 경계에 도달할 수 있는 곳에서 checked_*/saturating_*/wrapping_*을 사용하십시오. strict_add와 그 친구들(1.91부터 안정화)은 모든 빌드에서 오버플로우 시 패닉을 일으키고, [profile.release] 아래 overflow-checks = true는 프로덕션에서 디버그 동작을 유지합니다. 그리고 정수 타입 간의 as는 자동으로 잘립니다(300_u32 as u8은 44) - 값이 맞지 않을 수 있는 곳에서는 u8::try_from(x)?를 사용하십시오.
  • String은 인덱싱할 수 없습니다. s[0]은 컴파일되지 않습니다 - 대신 무엇을 사용해야 하는지 references/ownership-and-types.md를 참조하십시오.

Go에서 온 경우:

  • 오류를 값으로 - 같은 본능이지만, if err != nil 대신 ?를 사용하십시오.
  • nil이 없습니다. Option<T>.
  • GC와 고루틴이 없습니다: 소유권 + 빌림, tokio를 사용한 async/await. 비동기 모델은 협력적입니다(await는 명시적인 양보 지점), 선점형이 아닙니다.
  • interface{}는 트레이트가 됩니다. 정적 디스패치를 위해 기본적으로 제네릭을 사용하며, 필요할 때만 Box<dyn Trait>를 사용하십시오.
  • 정적 링킹이 기본값입니다. 바이너리는 더 크지만 자체 포함됩니다.
  • panic!은 애플리케이션 코드에서 복구 불가능한 버그를 위해 예약되어야 합니다; Go 스타일의 "로그 및 계속"으로 사용하지 마십시오.

Java 또는 C#에서 온 경우:

  • 트레이트는 기본적으로 가상 디스패치가 있는 인터페이스가 아닙니다. <T: Trait>는 단형화됩니다. dyn Trait는 선택적인 동적 버전입니다.
  • null 참조가 없습니다. Option<T>.
  • 예외가 없습니다. Result<T, E>와 ?.
  • 클래스 상속이 없습니다. 합 타입에는 열거형을, 공유 동작에는 트레이트를 사용하십시오.
  • GC 없음: 소유권과 빌림이 수명을 결정합니다. Arc<T>는 Java 참조에 가장 가깝습니다.
  • 제네릭은 단형화되며, 타입 소거되지 않습니다.

C++에서 온 경우:

  • RAII와 같지만, 빌림 검사기가 컴파일 타임에 강제합니다.
  • 복사/이동 생성자가 없습니다. Clone은 명시적이며 Copy는 저렴한 비트별 복사를 위한 마커 트레이트입니다.
  • 안전한 코드에서는 정의되지 않은 동작이 없습니다(이론상).
  • &는 컴파일 타임에 확인된 빌림이지, 원시 포인터가 아닙니다. 원시 포인터(*const T, *mut T)는 존재하지만 역참조하려면 unsafe가 필요합니다.
  • 스마트 포인터는 Box<T> (unique_ptr), Rc<T> (shared_ptr, 단일 스레드), Arc<T> (shared_ptr, 스레드 안전)입니다.
  • 매크로는 위생적입니다. 절차적 매크로(derive, attribute, 함수형)는 serde, tokio::main 등이 작동하는 방식입니다.

크레이트 쇼트리스트

이것들은 대부분의 실제 애플리케이션을 포괄합니다. 필요에 따라 추가하십시오; 모두 필수는 아닙니다.

크레이트제공하는 것
serde + serde_json직렬화. #[derive(Serialize, Deserialize)]로 끝
tokio비동기 런타임. #[tokio::main], tokio::spawn, 비동기 I/O
anyhow애플리케이션 오류 타입. anyhow::Result<T>, bail!, context()
thiserror라이브러리 오류 열거형. #[derive(thiserror::Error)]
clapCLI 인수 파싱. #[derive(Parser)]로 구조체 기반 파싱
axumHTTP 서버. tokio 위에 구축, 타워 미들웨어, 추출기
sqlx데이터베이스 드라이버. 컴파일 타임 SQL 검사 (sqlx::query!), 비동기, PostgreSQL/MySQL/SQLite
tracing로깅/계측. tracing-subscriber로 설정, #[instrument]으로 함수 계측
reqwestHTTP 클라이언트. 비동기, TLS 지원
tokio-utiltokio를 위한 어댑터: CancellationToken, Semaphore, PollSender, 프레임 기반 I/O를 위한 Framed

프로젝트 구성 빠른 시작

다음은 균형 잡힌 첫날 Cargo.toml입니다:

[package]
name = "my-app"
version = "0.1.0"
edition = "2021"

[dependencies]
# 비동기 I/O
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
anyhow = "1"
clap = { version = "4", features = ["derive"] }

[dev-dependencies]
# 테스트 전용

[lints.clippy]
# 당신의 팀 규칙을 여기에 추가하십시오
cargo = "warn"
pedantic = "allow"  # pedantic 린트는 종종 너무 까다롭습니다; 원하면 활성화

.rustfmt.toml (선택 사항, 권장):

max_width = 100
use_field_init_shorthand = true

더 많은 Rust 학습 자료

온라인 리소스:

  • Rust 공식 문서 (https://www.rust-lang.org/learn): The Book은 필수, Rust by Example은 실제 코드와 함께 배우기에 좋습니다, The Edition Guide는 언어 에디션의 차이점을 설명합니다.
  • Rust API 지침 (https://rust-lang.github.io/api-guidelines/): Rust 라이브러리 설계에 대한 공식 권장 사항. 트레이트, 명명 규칙, 안전 등을 다룹니다.
  • This Week in Rust (https://this-week-in-rust.org/): Rust 생태계의 최신 뉴스, 크레이트 릴리스 및 블로그 게시물을 요약한 주간 뉴스레터.
  • The Embedded Rust Book: no_std 환경 및 임베디드 시스템에서의 Rust에 대한 공식 가이드.
  • Rust Cookbook: 일반적인 프로그래밍 작업(파일 I/O, 날짜/시간, 정렬, 암호화 등)을 위한 레시피 모음.

Rust 참고 자료 (레퍼런스):

도구:

  • cargo-edit (cargo install cargo-edit): Cargo.toml 편집을 위한 cargo add/remove/upgrade 명령어.
  • cargo-watch (cargo install cargo-watch): 파일 변경 시 명령어를 다시 실행하는 파일 감시자.
  • cargo-audit (cargo install cargo-audit): Cargo.lock에서 알려진 취약점을 검사합니다.
  • cargo-expand (cargo install cargo-expand): 매크로 확장 결과를 확인합니다.
  • cargo-udeps (cargo install cargo-udeps): 사용되지 않은 의존성을 찾습니다.
  • cargo-deny (cargo install cargo-deny): 의존성 라이선스, 취약점 등을 확인합니다.

고급 주제 (첫 주 이후):

CLI 앱을 위한 clap 빠른 참조

다음은 clap의 #[derive(Parser)] API를 사용하여 CLI 앱을 구성하는 방법입니다:

use clap::Parser;

/// 간단한 CLI 도구
#[derive(Parser)]
#[command(name = "my-app")]
#[command(about = "유용한 작업을 수행", long_about = None)]
struct Cli {
    /// 입력 파일
    input: String,

    /// 출력 파일 (선택 사항)
    #[arg(short, long, default_value = "output.txt")]
    output: String,

    /// 상세 모드 (-v, -vv, -vvv)
    #[arg(short, long, action = clap::ArgAction::Count)]
    verbose: u8,

    /// 설정 파일 경로
    #[arg(short = 'c', long = "config")]
    config: Option<String>,
}

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

    println!("입력: {}", cli.input);
    println!("출력: {}", cli.output);
    println!("상세 레벨: {}", cli.verbose);
}

피해야 할 일반적인 함정

  1. &str을 구조체 필드에 저장: 트레이트 객체 참조(&dyn Trait)와 마찬가지로 수명 주석이 필요하며 모든 소비자에게 전염됩니다. 거의 항상 String을 사용하십시오.

  2. 라이브러리에서 Box<dyn Error> 반환: 호출자가 다운캐스트하도록 강제합니다. 대신 thiserror를 사용하십시오.

  3. 구체적인 오류 타입을 모든 함수 시그니처에 전파: 애플리케이션 코드는 anyhow::Result<T>를 사용하십시오. 구체적인 오류는 라이브러리 경계에서만 중요합니다.

  4. .clone()의 과도한 사용: 때로는 필요하지만, &를 먼저 사용할 수 있는지 확인하십시오.

  5. Rc를 기본값으로 사용: Rc는 스레드 안전하지 않습니다. 비동기로 전환할 때 변경할 필요가 없도록 Arc를 사용하십시오. Rc는 단일 스레드 참조 카운팅이 성능상의 이유로 정당화되는 특정 경우에만 사용하십시오.

  6. 기본값으로 RefCell 사용: RefCell은 스레드 안전하지 않습니다. 대신 Mutex 또는 RwLock을 사용하십시오.

  7. 릴리스 빌드에서 패닉에 의존한 정수 오버플로우 포착: 오버플로우될 수 있는 산술에는 checked_*/wrapping_*/saturating_*를 사용하거나 프로필에 overflow-checks = true를 설정하십시오.

  8. 라이브러리 API에서 &str 반환: 이는 수명에 묶여 호출자의 유연성을 제한합니다. 대신 String을 반환하십시오.

라이브러리나 서비스를 작성할 때 고려할 사항

  • no_std 호환성: 임베디드 또는 커널 수준 코드를 대상으로 한다면, 크레이트가 #![no_std]를 지원하는지 확인하십시오.
  • Send + Sync 경계: 라이브러리 타입이 기본적으로 Send 및 Sync를 구현하는지 확인하십시오. 그렇지 않으면 사용자가 비동기 컨텍스트에서 사용할 수 없습니다.
  • feature 플래그: 원한다면 크레이트에 선택적 기능(serde 지원, tokio 통합 등)을 추가할 수 있습니다.
  • 문서 테스트: /// 주석의 코드 예제는 cargo test로 테스트됩니다. 문서를 최신 상태로 유지하는 좋은 방법입니다.
  • CI/CD: GitHub Actions + actions-rs/toolchain을 사용하여 여러 Rust 버전 및 cargo test / cargo clippy로 테스트하십시오.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

관련 스킬