Rust

82 posts
Rust의 Path와 PathBuf: 안전하게 경로 다루기

Rust의 Path와 PathBuf: 안전하게 경로 다루기

std::fs로 파일 다루기에서 봤듯이 Rust 파일 함수는 모두 경로를 받습니다. 그런데 경로를 그냥 &str이나 String으로 다루면 미묘한 함정이 많은데요. /와 가 섞인 윈도우 경로 처리가 빠진다거나, 확장자만 바꾸려는데 문자열 슬라이싱이 필요해진다거나 하는 식이죠. Rust는 경로 전용 타입 두 개를 제공합니다. &Path와 PathBuf인데요. String과 &str이 그렇듯, 이 둘도 짝꿍처럼 한 묶음으로 이해하면 좋습니다. &Path와 PathBuf의 관계 핵심은 한 줄로 정리됩니다. &Path는 빌린 경로 슬라이스

Rust #[non_exhaustive]로 깨지지 않는 API 만들기

Rust #[non_exhaustive]로 깨지지 않는 API 만들기

라이브러리를 만들다 보면 이미 공개한 열거형에 새 배리언트를 추가해야 할 때가 있습니다. 그런데 열거형에 배리언트를 하나 추가하는 순간 그 열거형을 match로 매칭하던 모든 사용자 코드에서 컴파일 에러가 터져요. 구조체도 마찬가지로 새 필드를 추가하면 구조체를 직접 생성하던 코드가 깨지죠. semver를 지키려면 이런 변경은 메이저 버전을 올려야 합니다. 하지만 에러 타입에 새 종류를 추가하거나 설정 구조체에 옵션 하나를 추가하는 게 정말 파괴적 변경일까요? 🤔 Rust는 이 문제를 해결하기 위해 #[non_exhaustive]

Rust 파일 I/O 기초: std::fs로 읽고 쓰기

Rust 파일 I/O 기초: std::fs로 읽고 쓰기

Rust로 코드를 짜다 보면 파일 한두 개는 꼭 만지게 되는데요. 설정 파일을 읽거나 로그를 쓰거나, 임시 파일을 만들거나 하는 일이죠. Rust 표준 라이브러리의 std::fs 모듈이 이런 작업을 위한 도구를 한가득 제공합니다. 이번 글에서는 std::fs의 가장 기본적인 사용법을 살펴보겠습니다. 경로 다루기는 Path와 PathBuf에서, 효율적인 처리는 BufReader/BufWriter에서, 비동기 처리는 tokio::fs에서 별도로 다룹니다. 한 줄 함수로 빠르게 시작하기 std::fs에는 "파일 한 번에 읽고 끝", "파

Rust JSON Schema 자동 생성: schemars 라이브러리 사용법

Rust JSON Schema 자동 생성: schemars 라이브러리 사용법

API 명세서를 작성하거나 외부에서 들어오는 JSON 데이터를 검증해야 할 때 JSON Schema를 직접 손으로 적어본 적 있으신가요? 필드가 몇 개 안 되면 그럭저럭 버틸 만한데, 구조체 안에 또 구조체가 들어가고 열거형까지 섞이기 시작하면 금세 손이 아파옵니다. 게다가 Rust 코드와 스키마를 따로 관리하다 보면 어느 한쪽이 슬그머니 바뀌어 두 문서가 어긋나는 일도 종종 벌어지죠. 이럴 때 Rust 타입을 기반으로 JSON Schema 문서를 자동으로 만들어주는 라이브러리가 바로 schemars입니다. Serde로 직렬화/역직

Rust의 dyn 키워드와 동적 디스패치(Dynamic Dispatch)

Rust의 dyn 키워드와 동적 디스패치(Dynamic Dispatch)

트레이트를 사용하다 보면 어느 순간 벽에 부딪히는 상황이 옵니다. 서로 다른 타입의 값을 하나의 벡터에 담고 싶은데 컴파일러가 허락하지 않는 거죠 😅 Dog과 Cat은 둘 다 Animal을 구현하지만 엄연히 서로 다른 타입입니다. Rust의 벡터는 모든 원소가 같은 타입이어야 하니까 이 코드는 동작하지 않죠. 이 문제를 해결하는 열쇠가 바로 dyn 키워드입니다. 그럼 dyn이 뭔지, 그리고 어떻게 쓰는 건지 알아볼까요? 정적 디스패치 Rust는 기본적으로 정적 디스패치(static dispatch)를 씁니다. dyn을 보기 전에

Rust 기초: HashMap으로 키-값 데이터 다루기

Rust 기초: HashMap으로 키-값 데이터 다루기

전화번호부를 떠올려보세요. 이름을 알면 전화번호를 바로 찾을 수 있죠. 프로그래밍에서도 이런 식으로 어떤 키를 가지고 그에 대응하는 값을 빠르게 찾고 싶을 때가 많습니다. 다른 언어에서는 딕셔너리(dictionary)나 맵(map)이라고 부르는 이 자료 구조를 Rust에서는 HashMap이라고 합니다. 이 글에서는 HashMap을 생성하고, 데이터를 넣고 꺼내고, 수정하고 삭제하는 기본 연산부터 Entry API와 소유권 이슈까지 정리해보겠습니다. HashMap 생성 HashMap은 표준 라이브러리의 std::collections

액터 모델(Actor Model): 메시지로 소통하는 동시성 패러다임

액터 모델(Actor Model): 메시지로 소통하는 동시성 패러다임

멀티스레드 코드를 디버깅하다가 머리를 쥐어뜯어 본 적 있으신가요? 락이 어디서 잡혔다가 풀리는지, 어떤 스레드가 어떤 변수를 먼저 건드렸는지 추적하다 보면 어느새 날이 밝아 있곤 하죠. 사실 동시성 코드가 어려운 건 우리가 못 짠 게 아니라 공유 메모리 모델 자체가 사람의 머리로 추론하기 까다롭기 때문입니다. 그래서 오래전부터 "공유하지 말고 메시지로만 대화하자"는 다른 결의 모델을 제안한 사람들이 있었습니다. 그중에서도 1973년에 제안되어 Erlang과 함께 산업 현장에서 검증된 액터 모델(Actor Model)을 이번 글에서

Rust HTTP 클라이언트: reqwest 크레이트 사용법

Rust HTTP 클라이언트: reqwest 크레이트 사용법

웹 API를 호출하거나 외부 서비스와 통신해야 하는 상황은 어떤 언어로 개발하든 빈번하게 마주치게 됩니다. Rust에서는 이런 HTTP 통신을 위해 reqwest라는 크레이트가 사실상 표준처럼 사용되고 있는데요. Python의 requests 라이브러리처럼 직관적인 API를 제공하면서도 Rust답게 타입 안전성과 비동기 처리를 지원하는 것이 특징입니다. 이 글에서는 reqwest 크레이트의 기본적인 사용법부터 실무에서 자주 쓰이는 패턴까지 예제와 함께 살펴보겠습니다. reqwest란? reqwest는 Rust 생태계에서 가장 널리

Rust http 크레이트: HTTP 생태계의 공용 어휘

Rust http 크레이트: HTTP 생태계의 공용 어휘

Rust로 HTTP를 다뤄본 분이라면 한 가지 묘한 사실을 눈치채셨을 거예요. reqwest로 요청을 보내든, hyper로 저수준 서버를 짜든, axum으로 핸들러를 만들든 같은 타입 이름이 자꾸 등장합니다. StatusCode, HeaderMap, Method, Uri 같은 것들이요. 이건 우연이 아닙니다. 이 타입들은 모두 http라는 별도의 크레이트에 정의되어 있고, Rust HTTP 생태계 전체가 이 어휘를 공유하고 있는 거죠. 덕분에 reqwest로 받은 응답의 헤더를 axum 응답에 그대로 옮길 수 있고, tower 미들

asciinema로 터미널 녹화하기: 텍스트 기반 경량 스크린캐스트

asciinema로 터미널 녹화하기: 텍스트 기반 경량 스크린캐스트

CLI 도구 사용법을 누군가에게 설명할 때 어떻게 하시나요? 스크린샷을 여러 장 찍어서 붙이거나 OBS로 화면을 녹화해서 영상 파일을 만들 수도 있겠죠. 근데 솔직히 터미널 화면 녹화하자고 동영상 편집 프로그램까지 꺼내는 건 좀 과하잖아요. asciinema(아스키네마)는 이 고민을 깔끔하게 풀어줍니다. 터미널에서 일어나는 입출력을 텍스트로 녹화해서 MP4 같은 무거운 동영상 파일 대신 몇 KB짜리 텍스트 파일을 만들어주거든요. 녹화된 내용에서 텍스트를 복사할 수도 있고 웹 페이지에 임베드하거나 GIF로 변환하는 것도 간단합니다.

Rust 데이터 직렬화: Serde 라이브러리 사용법

Rust 데이터 직렬화: Serde 라이브러리 사용법

Rust는 시스템 프로그래밍 언어이기 때문에 데이터를 외부로 안전하게 내보내거나 받아오는 일이 많은데요. 이러한 데이터 직렬화/역직렬화를 위해서 사실상 표준처럼 사용되는 라이브러리가 Serde입니다. 거의 모든 Rust 프로젝트가 사용되는 크레이트(Crate)라고 봐도 과언이 아니죠. 이 글에서는 왜 Serde 라이브러를 어떻게 사용하는지 예제와 함께 살펴보겠습니다. Serde란? 직렬화는 데이터를 Rust 자료형에서 JSON이나 YAML, TOML 등의 형식으로 변화하는 과정을 의미하고, 역직렬화는 변환된 데이터를 다시 원래대로

Rust 기초: Iterator 트레이트로 컬렉션 순회하기

Rust 기초: Iterator 트레이트로 컬렉션 순회하기

Rust로 컬렉션을 다루다 보면 iter(), map(), filter(), collect() 같은 메서드를 자연스럽게 쓰게 됩니다. 이 메서드들 뒤에 있는 게 바로 Iterator 트레이트인데요. Iterator는 Rust의 함수형 코드를 떠받치는 핵심 추상화입니다. 한 번 익숙해지면 for 루프와 임시 변수를 늘어놓던 코드가 짧고 우아하게 바뀌어요. 이 글에서는 Iterator 트레이트가 어떻게 동작하는지, 어댑터와 소비자의 차이가 무엇인지, collect()의 다양한 활용까지 알아보겠습니다. Vec을 먼저 익혀두면 예제를 따라

Rust 기초: Vec으로 동적 배열 다루기

Rust 기초: Vec으로 동적 배열 다루기

Rust에서 배열([T; N])은 크기가 컴파일 시점에 고정됩니다. [i32; 5]라고 선언하면 딱 5개만 담을 수 있고, 나중에 6번째 요소를 추가할 수가 없죠. 사용자 입력을 모으거나 파일에서 데이터를 읽어오거나 API 응답을 파싱하는 상황처럼 실행 시점에 데이터 개수가 정해지는 경우에는 고정 크기 배열로 해결이 안 됩니다. 이럴 때 필요한 것이 Vec<T>입니다. Vec<T>는 힙에 데이터를 저장하는 동적 배열로, 요소를 자유롭게 추가하고 제거할 수 있습니다. Rust에서 가장 많이 쓰이는 컬렉션 타입이기도 하고요. 이 글에서

Rust 오류 자료형: thiserror 라이브러리 사용법

Rust 오류 자료형: thiserror 라이브러리 사용법

Rust는 명시적이고 안전한 오류 처리를 중시하는 프로그래밍 언어입니다. 대표적으로 Result 타입과 ? 연산자를 통해 다양한 에러 상황을 타입 시스템으로 포착할 수 있죠. 하지만 실무에서 직접 오류 자료형을 정의하고 Error 트레이트를 구현하다 보면, 반복적인 보일러플레이트 코드 작성에 지치는 경우가 많습니다. 이럴 때 thiserror 라이브러리가 여러분의 구세주가 될 수 있습니다. Error 트레이트 우선 표준 라이브러리의 Error 트레이트를 직접 구현하는데 필요한 최소한의 코드를 보여드리겠습니다. 아래 Validatio

Rust 기초: 원자적 타입과 메모리 오더링

Rust 기초: 원자적 타입과 메모리 오더링

여러 스레드에서 카운터 하나를 안전하게 올리고 싶을 때 가장 먼저 떠오르는 게 Mutex<i32>인데요. 그런데 막상 짜놓고 보면 "고작 정수 하나 더하자고 잠금을 잡았다 풀었다 한다고?" 싶어집니다. 실제로 이 패턴은 Sync 트레이트 글에서도 잠깐 언급했던 원자적 타입을 쓰면 훨씬 가볍게 해결할 수 있어요. Mutex도 Arc도 안 보이는데 5,000이 정확히 출력됩니다. 그런데 이 코드를 처음 보면 두 가지가 좀 거슬리는데요. &counter로 변경이 되는 게 어떻게 가능한지, 그리고 저 Ordering::Relaxed는 도대

Rust 기초: AsRef 트레이트로 유연한 함수 만들기

Rust 기초: AsRef 트레이트로 유연한 함수 만들기

Rust로 함수를 작성하다 보면 이런 고민이 생깁니다. &str을 받는 함수를 만들었는데, 호출하는 쪽에서 String을 넘기려면 매번 &를 붙이거나 .as_str()을 호출해야 하죠. 역참조 강제 덕분에 &name만으로도 잘 되긴 하지만, 직접 라이브러리 API를 설계할 때는 "이 함수가 어떤 타입을 받을 수 있는지"를 시그니처에서 명확히 드러내고 싶을 때가 있습니다. AsRef<T> 트레이트가 바로 이런 상황을 위해 존재합니다. AsRef 트레이트란? AsRef<T>는 어떤 값에서 &T를 저비용으로 얻을 수 있다는 것을 나타내는

Rust 기초: Sync 트레이트로 스레드 간 안전한 참조 공유하기

Rust 기초: Sync 트레이트로 스레드 간 안전한 참조 공유하기

Send 트레이트를 공부하다 보면 자연스럽게 따라오는 질문이 있는데요. "값을 스레드로 옮기지 않고, 여러 스레드에서 동시에 참조만 하고 싶으면 어떻게 하지?" 예를 들어 Arc로 RefCell을 감싸서 여러 스레드에서 접근하려고 하면 이런 컴파일 에러가 납니다. "스레드 간에 안전하게 공유할 수 없다"는 이 에러의 핵심이 바로 Sync 트레이트입니다. Send가 소유권 이동의 안전성을 보장한다면, Sync는 참조 공유의 안전성을 보장하는 건데요. 이 글에서 Sync가 어떤 역할을 하고 어떤 타입이 Sync이고 어떤 타입은 아닌지,

Rust 기초: Option과 Result에서 as_deref() 활용하기

Rust 기초: Option과 Result에서 as_deref() 활용하기

Rust에서 Option<String>을 다루다 보면 꽤 답답한 순간이 찾아옵니다. Option 안에 들어있는 String을 &str과 비교하고 싶은데 타입이 맞지 않아 컴파일러가 거부하는 상황이죠. Option<String>과 Option<&str>은 서로 다른 타입이라 직접 비교할 수 없습니다. 이런 상황에서 as_deref()를 알고 있으면 아주 깔끔하게 해결할 수 있는데요. 이 글에서는 as_ref()와 비교하면서 as_deref()가 왜 필요하고 어떻게 동작하는지 살펴보겠습니다. Option에서 소유와 참조 문제의 근본 원

Rust 기초: Send 트레이트로 스레드 안전성 보장하기

Rust 기초: Send 트레이트로 스레드 안전성 보장하기

Rust로 멀티스레드 프로그래밍을 하다 보면 이런 컴파일 에러를 만나게 되는 경우가 있는데요. "스레드 간에 안전하게 보낼 수 없다"는 말은 대체 무슨 뜻일까요? 그리고 Send 트레이트는 뭘까요? 🤔 사실 이 에러 메시지 안에 Rust가 동시성 프로그래밍에서 데이터 경합(data race)을 원천 차단하는 메커니즘이 담겨 있습니다. Send 트레이트가 어떻게 동작하고, 어떤 상황에서 우리를 보호해 주는지 살펴볼게요. Send 트레이트란? Send는 Rust 표준 라이브러리의 std::marker 모듈에 정의된 마커 트레이트(ma

Rust 기초: Arc로 스레드 간 데이터 공유하기

Rust 기초: Arc로 스레드 간 데이터 공유하기

Rust의 소유권 시스템은 메모리 안전성을 보장해주지만, 여러 스레드에서 같은 데이터를 공유하려고 하면 컴파일러가 허용하지 않습니다. "한 번에 하나의 소유자만 존재할 수 있다"는 규칙 때문입니다. 소유권을 이동하면 다른 스레드에서 사용할 수 없고, 참조를 전달하려고 하면 수명(lifetime) 문제로 컴파일 오류가 발생합니다. 실제로는 여러 스레드가 같은 설정 값을 읽거나, 공유 데이터를 참조해야 하는 상황이 많습니다. 예를 들어 웹 서버에서 모든 워커 스레드가 동일한 설정 파일을 읽어야 하거나, 여러 스레드가 같은 캐시 데이터를

Discord