831 posts
Rust tokio::fs: 비동기 파일 I/O와 그 한계

Rust tokio::fs: 비동기 파일 I/O와 그 한계

std::fs 함수는 모두 동기적입니다. 호출하면 그 자리에서 디스크를 기다리며 멈춰 있죠. Tokio 같은 비동기 런타임 위에서 이 함수를 그냥 부르면, 파일을 기다리는 동안 같은 스레드에서 돌아야 할 다른 태스크가 같이 멈춰버립니다. 이번 글에서는 이 문제를 풀기 위한 tokio::fs 모듈을 살펴보고, 한 가지 흥미로운 사실(사실은 진짜 비동기가 아님)을 짚어보겠습니다. tokio::fs는 std::fs의 비동기 버전 tokio::fs는 std::fs와 거의 같은 인터페이스를 비동기 버전으로 제공하는데요. 함수 이름과 시그니처

Rust의 let else: 패턴 매칭 실패 시 깔끔하게 조기 반환하기

Rust의 let else: 패턴 매칭 실패 시 깔끔하게 조기 반환하기

Option이나 Result를 다루다 보면 비슷한 코드를 자꾸 반복해서 쓰게 되는 순간이 있습니다. 값이 들어 있으면 꺼내서 계속 진행하고, 그렇지 않으면 일찍 함수를 빠져나가는 패턴이죠. 값을 꺼내려는 게 코드의 본 의도인데, 정작 화면에서 가장 눈에 띄는 건 if let과 그 뒤에 따라붙는 두 갈래의 분기죠. Rust 1.65에서 안정화된 let ... else 구문은 바로 이 패턴을 한 줄로 정리해주는 도구입니다. 이번 글에서는 let else가 어떤 모양이고, if let이나 match와 어떻게 다른지, 그리고 어떤 상황에서

GraphQL 명세 ③: 스키마를 떠받치는 타입 시스템(Type System)

GraphQL 명세 ③: 스키마를 떠받치는 타입 시스템(Type System)

지난 편에서 우리가 작성하는 쿼리 문서의 문법을 살펴봤는데요. 그런데 쿼리는 혼자서는 아무 의미가 없습니다. user(id: "1") { name }이라는 요청이 말이 되려면, 서버 쪽에 "User라는 타입이 있고, 거기에 name이라는 필드가 있다"는 약속이 미리 정의되어 있어야 하니까요. 그 약속이 바로 스키마(schema) 이고, 스키마를 이루는 재료가 이번 편의 주제인 타입 시스템(Type System) 입니다. 명세 제3장은 GraphQL에서 가장 분량이 많고 중요한 장입니다. 서버가 어떤 타입들을 조합해 스키마를 짤 수

Rust의 에러 처리: 예외 대신 값으로 다루는 법

Rust의 에러 처리: 예외 대신 값으로 다루는 법

자바스크립트로 파일을 읽거나 파이썬으로 API를 호출해본 경험이 있으시다면, 아마 try/catch나 try/except로 예외를 잡는 방식이 익숙하실 겁니다. 그런데 Rust 코드를 처음 보면 이상한 점이 눈에 들어옵니다. 분명히 파일을 여는데 try도 없고, 함수 시그니처에 throws도 없고, 대신 Result<T, E>라는 낯선 타입이 반환됩니다. 🤔 이번 글에서는 다른 언어와 비교했을 때 Rust 에러 처리가 가지는 가장 특이한 지점들과, 일상적인 실패와 버그를 Rust가 어떻게 갈라놓는지를 정리해보겠습니다. 예외가 아니

Rust BufReader와 BufWriter: 버퍼링된 파일 I/O로 효율 챙기기

Rust BufReader와 BufWriter: 버퍼링된 파일 I/O로 효율 챙기기

std::fs 기초에서 본 read_to_string은 편합니다. 한 줄로 파일을 통째로 읽어 String으로 돌려주죠. 하지만 5GB짜리 로그 파일에 같은 함수를 쓴다면? 메모리에 5GB가 그대로 올라갑니다. 이번 글에서는 대용량 파일을 효율적으로 다루는 도구인 BufReader/BufWriter를 정리하고, 줄 단위 처리, 적절한 버퍼링, flush 잊지 않기 같은 실무 패턴을 살펴보겠습니다. 왜 버퍼링이 필요한가 파일 시스템 호출(syscall)은 비싼 연산입니다. 한 바이트씩 읽고 쓰면 매번 syscall이 발생해서 성능이

Zellij: Rust로 만든 차세대 터미널 멀티플렉서

Zellij: Rust로 만든 차세대 터미널 멀티플렉서

터미널에서 개발하다 보면 창이 부족해지는 순간이 오죠. 개발 서버 하나 돌리고, 로그 보면서, 또 다른 창에서 Git 작업하고, 테스트도 돌려야 하고... 결국 터미널 탭이 열 개쯤 열려 있는 자신을 발견하게 됩니다. 😅 이런 문제를 해결하려고 tmux 같은 터미널 멀티플렉서를 써보신 분들도 계실 텐데요. 솔직히 tmux는 진입 장벽이 꽤 높습니다. 키 바인딩을 외워야 하고, 설정 파일도 복잡하고, 처음 켜면 뭘 어떻게 해야 하는지 막막하죠. Zellij는 이런 고민을 깔끔하게 해결해주는 차세대 터미널 멀티플렉서입니다. Rust로

Rust Result 메서드 정리: ?, map_err, and_then, 콤비네이터

Rust Result 메서드 정리: ?, map_err, and_then, 콤비네이터

Rust의 에러 처리에서 봤듯이, Rust는 실패 가능성을 Result<T, E> 타입으로 반환 타입에 박아 넣는 언어입니다. 다만 매번 match로 풀어내기엔 코드가 너무 장황해지죠. 그래서 표준 라이브러리는 Result를 다루는 다양한 메서드를 갖춰두고 있습니다. 이번 글에서는 실무에서 자주 만나는 Result 메서드를 용도별로 정리해보겠습니다. 조기 반환에 쓰는 ? 연산자부터, 에러를 변환하는 map_err, 값을 갈아 끼우는 map/and_then, 그리고 회복용 unwrap_or 패밀리까지 한 번에 살펴봅시다. Result

GraphQL 명세 ②: 우리가 작성하는 쿼리 언어(Language)

GraphQL 명세 ②: 우리가 작성하는 쿼리 언어(Language)

지난 편에서 GraphQL 명세가 어떤 문서이고 왜 읽어야 하는지 함께 살펴봤는데요. 이번 편부터는 본격적으로 명세 본문으로 들어갑니다. 첫 번째 본문 장은 제2장 Language, 그러니까 우리가 클라이언트에서 작성해 서버로 보내는 그 쿼리 텍스트의 문법을 다루는 장입니다. 평소에 별생각 없이 { user { name } } 같은 쿼리를 작성하셨겠지만, 이 짧은 텍스트 안에도 명세가 엄밀하게 정의한 문법 규칙이 빼곡히 들어 있는데요. 이번 편에서는 GraphQL 문서가 어떤 부품들로 조립되는지, 연산부터 디렉티브까지 하나씩 분해해

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

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

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

AI를 위한 프로젝트 안내서: AGENTS.md와 CLAUDE.md

AI를 위한 프로젝트 안내서: AGENTS.md와 CLAUDE.md

요즘 Cursor나 Claude Code, Codex와 같은 AI 도구로 소프트웨어 개발을 많이 하시죠? 그런데 AI 코딩 에이전트에게 작업을 시키다 보면 프로젝트에 대한 전반적인 지식이 부족하여 엉뚱하게 작업을 진행할 때가 있습니다. 예를 들어, 테스트 파일을 엉뚱한 위치에 생성하거나, 프로젝트의 코딩 컨벤션을 무시하거나, 마음대로 이미 쓰고 있는 라이브러리랑 비슷한 기능을 하는 다른 라이브러리를 설치해버리는 식이죠. 이럴 때는 AI 에이전트에게 좀 더 구체적으로 작업 방향을 알려줄 수 있지만, 새로운 대화 세션을 시작할 때마다

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

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

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

.claude/rules로 규칙 체계화하기: CLAUDE.md를 주제별로 쪼개는 법

.claude/rules로 규칙 체계화하기: CLAUDE.md를 주제별로 쪼개는 법

클로드 코드에 프로젝트 규칙을 알려주는 가장 기본적인 방법은 CLAUDE.md입니다. 그런데 프로젝트가 커지면 이 파일 하나가 점점 비대해지죠. 프론트엔드 규칙, 백엔드 규칙, 테스트 컨벤션, 보안 요구사항... 온갖 내용이 한 파일에 뒤섞이면 읽기도 어렵고, 매 세션 전부 로드되니 컨텍스트도 부담스러워집니다. .claude/rules/ 디렉토리는 이 문제를 푸는 방법입니다. 규칙을 주제별로 여러 파일에 나눠 담고, 필요한 순간에만 로드되도록 만들 수 있어요. 이 글에서는 .claude/rules/를 어떻게 구성하는지, 그리고 CL

GraphQL 명세 함께 읽기: 왜 스펙을 읽어야 할까

GraphQL 명세 함께 읽기: 왜 스펙을 읽어야 할까

GraphQL로 API를 몇 번 만들어보셨다면 쿼리를 작성하고, 스키마를 정의하고, 리졸버를 붙이는 일에는 꽤 익숙하실 텐데요. 그런데 혹시 흔히 "스펙"이라고도 부르는 GraphQL 공식 명세(specification) 를 직접 펼쳐본 적 있으신가요? 아마 대부분은 "그런 게 있다는 건 아는데, 실무에서 Apollo만 잘 쓰면 되지 않나?"라고 생각하셨을 것 같습니다. 😅 저도 오랫동안 그랬습니다. 그런데 막상 명세를 읽어보니, 평소에 "왜 이렇게 동작하지?" 하고 넘어갔던 수많은 질문에 대한 답이 거기 다 적혀 있더라고요. 마

Playwright MCP로 AI 에이전트에게 브라우저 자동화 맡기기

Playwright MCP로 AI 에이전트에게 브라우저 자동화 맡기기

AI 코딩 에이전트를 쓰다 보면 "이 웹 페이지 좀 열어서 확인해줘"라고 말하고 싶을 때가 종종 있지 않나요? 배포한 사이트에 버그가 있는 것 같을 때, 특정 폼이 제대로 동작하는지 테스트하고 싶을 때, 혹은 경쟁사 사이트의 UI를 참고하고 싶을 때... 하지만 대부분의 AI 에이전트는 터미널 안에서만 동작하기 때문에 브라우저를 직접 다룰 수가 없습니다. 이 문제를 해결하는 것이 바로 Playwright MCP입니다. Microsoft에서 만든 이 MCP(Model Context Protocol) 서버를 AI 에이전트에 연결하면 브

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

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

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

검색엔진과 AI 크롤러를 안내하는 robots.txt 사용법

검색엔진과 AI 크롤러를 안내하는 robots.txt 사용법

웹사이트를 개발하고 배포하다 보면 한 번쯤 robots.txt라는 파일에 대해서 듣게 됩니다. 저도 처음에는 검색 엔진이 크롤링할 때 필요한 정보를 제공해주는 수단으로 이해했었죠. 하지만 개인 블로그를 운영하면서 느낀 건, 이 작고 단순한 파일이 생각보다 많은 역할을 한다는 겁니다. 검색 결과에 어떤 페이지가 보이고 안 보이는지, 트래픽 낭비를 줄일 수 있는지 등, 의외로 중요한 결정들이 이 한 장짜리 텍스트 파일에 달려 있습니다. 특히 요즘처럼 데이터 학습을 위해서 AI 크롤러가 우후죽순 등장하는 시대에는, 단순한 SEO 도구를

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을 보기 전에

CLAUDE.md 작성 가이드: 프로젝트 규칙을 AI에게 알려주는 법

CLAUDE.md 작성 가이드: 프로젝트 규칙을 AI에게 알려주는 법

클로드 코드로 작업하다 보면 새 세션을 시작할 때마다 AI가 프로젝트에 대해 아무것도 모른다는 걸 느끼게 됩니다. "테스트는 bun run test로 돌려", "커밋 메시지는 영어로 써줘", "이 프로젝트는 Prettier를 쓰고 있어"... 매번 같은 말을 반복하는 건 꽤 피곤한 일이죠. CLAUDE.md는 이 문제를 해결하는 가장 직접적인 방법입니다. 프로젝트의 빌드 명령어, 코딩 스타일, 아키텍처 결정 같은 규칙을 마크다운으로 적어두면 Claude가 매 세션 시작 시 읽어들이거든요. Git에 커밋하면 팀 전체가 공유할 수 있고

클로드 코드 Rewind: 실수를 두려워하지 않는 코딩

클로드 코드 Rewind: 실수를 두려워하지 않는 코딩

클로드 코드로 작업하다가 "아, 이 방향이 아닌데…"라고 느낀 적 있으신가요? 리팩토링을 요청했더니 파일 10개를 고쳐놓았는데 원래 구조가 나았다거나, 라이브러리를 교체하라고 했다가 오히려 빌드가 깨진다거나. 이런 상황에서 할 수 있는 선택지가 그리 많지 않았습니다. git stash로 코드를 되돌린다 해도 Claude의 대화 컨텍스트에는 잘못된 지시가 그대로 남아 있으니까요. 새 세션을 열면 코드는 원래대로 돌아가겠지만 그동안 쌓아온 맥락이 통째로 날아갑니다. 수동으로 "방금 한 거 취소하고 다시 해줘"라고 설명하자니 토큰만 낭비

Discord