923 posts
Rust 기초: Borrow와 ToOwned 트레이트

Rust 기초: Borrow와 ToOwned 트레이트

Rust로 HashMap을 다루다 보면 한 번쯤 이런 의문이 생깁니다. 키로 String을 넣었는데, 조회할 때는 &str을 넘겨도 잘 찾아지는 게 신기하지 않나요? 이게 우연이 아니라 Borrow라는 트레이트가 만들어주는 보장 덕분인데요. 그리고 그 반대쪽, 즉 빌린 값을 다시 소유권이 있는 값으로 되돌리는 역할을 하는 게 ToOwned 트레이트입니다. 이 글에서는 이 두 트레이트가 어떤 계약을 맺고 있고, 왜 짝을 이루는지 알아보겠습니다. AsRef 트레이트와 비슷해 보이지만 미묘하게 다른 지점이 핵심입니다. Borrow 트레이

클로드 코드 마켓플레이스: 플러그인 배포하기

클로드 코드 마켓플레이스: 플러그인 배포하기

클로드 코드 플러그인을 설치해서 쓰다 보면 "이걸 우리 팀에 맞게 직접 만들 수 있지 않을까?"라는 생각이 들기 마련입니다. .claude/ 디렉토리에 스킬이나 Hooks를 설정해서 잘 쓰고 있다면 이미 플러그인의 반은 만든 셈이에요. 이 글에서는 간단한 스킬 하나짜리 플러그인부터 시작해서 Hooks, MCP 서버, LSP 서버를 포함하는 완전한 플러그인을 만들고 마켓플레이스를 통해 팀이나 커뮤니티에 배포하는 과정까지 다룹니다. 플러그인을 설치하고 관리하는 방법은 플러그인 사용법을 참고하세요. 첫 플러그인 만들기 간단한 인사 스킬을

클로드 코드 플러그인 사용법

클로드 코드 플러그인 사용법

클로드 코드에 TypeScript LSP를 연결하고, 코드 리뷰 스킬을 추가하고, GitHub MCP 서버까지 설정하려면 건드려야 할 파일이 꽤 많습니다. 스킬, Hooks, MCP 서버를 각각 따로 설정해본 분이라면 이 번거로움을 잘 아실 거예요. 플러그인은 이런 설정을 하나의 패키지로 묶어서 설치 한 번이면 끝나게 해줍니다. 이 글에서는 플러그인을 찾아서 설치하고 관리하는 법을 다루고, 직접 만들어서 배포하는 방법은 플러그인 마켓플레이스 가이드에서 이어집니다. 플러그인이란? 클로드 코드 플러그인은 커스텀 스킬, 에이전트, Hoo

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와 어떻게 다른지, 그리고 어떤 상황에서

Rust std::error::Error 트레이트 이해하기

Rust std::error::Error 트레이트 이해하기

Rust의 오류 처리와 Result 타입이 익숙해지면 자연스럽게 다음 질문이 생깁니다. Result<T, E>의 E에는 어떤 타입(type)이든 들어갈 수 있는데, 표준 라이브러리의 std::error::Error 트레이트(trait)는 왜 따로 필요할까요? 직접 오류 타입을 만들다 보면 impl Error for MyError {}라는 비어 있는 구현도 자주 보입니다. 한편 여러 오류를 한꺼번에 받는 함수는 Box<dyn Error>를 반환하곤 하죠. 하지만 Error는 단순한 표식이 아닙니다. 오류 메시지와 원인 체인을 다루는

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<T, E> 자체는 E가 Error 트레이트를 구현하도록 요구하지 않습니다. 에러 타입의 공통 인터페이스가 필요한 이유는 Rust std::error::Error 트레이트에서 따로 다룹니다. 이번 글에서는 실무에서 자주 만나는 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 에이전트에게 좀 더 구체적으로 작업 방향을 알려줄 수 있지만, 새로운 대화 세션을 시작할 때마다

Discord 버튼, 셀렉트 메뉴, 모달 다루기

Discord 버튼, 셀렉트 메뉴, 모달 다루기

슬래시 커맨드로 사용자 입력을 받는 방법을 봤는데요. 사용자가 매번 명령어를 타이핑하는 대신 버튼을 누르거나 메뉴에서 고르게 하면 훨씬 편하겠죠. Discord의 메시지 컴포넌트가 바로 그걸 가능하게 합니다. 이번 글에서는 버튼, 셀렉트 메뉴, 입력 폼인 모달을 Discord 공식 문서처럼 라이브러리 없이 다뤄볼게요. 버튼이나 모달도 결국 슬래시 커맨드와 같은 인터랙션이라, 서버리스 봇 만들기에서 만든 HTTP 인터랙션 워커에 그대로 얹힙니다. 아래 코드는 모두 그 워커의 fetch 핸들러 안에서 돌아가는 코드예요. 서명 검증 뼈대

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에는 "파일 한 번에 읽고 끝", "파

Discord