처리중입니다. 잠시만 기다려주세요.
TTJ 코딩클래스
정규반 단과 자료실 테크 뉴스 코딩 퀴즈
테크 뉴스
Hacker News 2026.06.11 34

Rust 비동기, 도대체 누가 내 Future를 실행하는 걸까?

Hacker News 원문 보기
Rust 비동기, 도대체 누가 내 Future를 실행하는 걸까?

"비동기 코드를 짰는데 아무 일도 안 일어나요"

Rust로 async/await를 처음 써본 사람들이 자주 겪는 황당한 순간이 있어요. 분명 async fn을 만들어서 호출했는데, 코드가 그냥... 아무것도 안 하는 거예요. 다른 언어 같으면 알아서 실행됐을 텐데 말이죠. 처음엔 "버그인가?" 싶지만, 사실 이건 Rust 비동기의 가장 중요한 특징이자 설계 철학이거든요. 오늘은 "그래서 내 비동기 코드는 도대체 누가 실행해주는 거냐"는 질문을 천천히 풀어볼게요.

Future가 뭐냐면

Rust에서 async 함수를 호출하면 그 자리에서 실행되는 게 아니라 'Future'라는 값이 하나 만들어져요. Future는 말 그대로 '미래에 완성될 결과를 담은 약속'인데요. 음식점에서 주문하고 받는 진동벨을 떠올리면 쉬워요. 진동벨 자체는 음식이 아니죠. "음식 준비되면 알려줄게"라는 약속일 뿐이고요. Rust의 Future도 똑같아요. "이 작업 끝나면 결과 줄게"라는 약속만 손에 쥐여주는 거예요.

그런데 결정적으로, Rust의 Future는 '게으르다(lazy)'는 점이 핵심이에요. 누군가 "야, 진행 상황 어때?" 하고 물어보기(이걸 poll이라고 해요) 전까지는 손가락 하나 까딱 안 하거든요. 그래서 some_async_fn()만 호출하고 .await를 안 붙이거나, 만들어진 Future를 아무도 실행해주지 않으면 정말로 아무 일도 안 일어나는 거예요. JavaScript의 Promise가 만들자마자 알아서 굴러가는 것과는 정반대죠.

그래서 누가 실행하느냐 — Executor와 Runtime

이 게으른 Future를 계속 찔러보면서(poll) "끝났어? 아직? 끝났어?" 물어보고, 아직이면 잠깐 재웠다가 준비되면 다시 깨워서 마저 진행시키는 역할을 하는 게 바로 '실행기(executor)'예요. 여기서 Rust만의 재밌는 점이 나오는데요. Rust 표준 라이브러리에는 이 실행기가 들어있지 않아요. async/await라는 문법과 Future라는 약속의 규격(트레이트)만 언어가 제공하고, 정작 그걸 굴려주는 엔진은 외부 라이브러리에서 직접 골라 써야 하거든요.

동작 원리를 조금만 더 들여다보면 이래요. 실행기가 Future를 poll 했는데 아직 결과가 준비 안 됐으면, Future는 Pending(아직)이라고 답하면서 'Waker'라는 호출 버튼 하나를 등록해둬요. 그러고 나면 실행기는 이 Future를 붙잡고 기다리는 대신 다른 일을 하러 가요. 나중에 네트워크 데이터가 도착하는 것처럼 준비가 끝나면, 그 Waker 버튼이 눌려서 "이제 다시 poll 해도 돼!" 하고 실행기에 알려주죠. 이 덕분에 스레드 하나로도 수만 개의 작업을 효율적으로 굴릴 수 있는 거예요. 기다리느라 멈춰있는 시간이 없으니까요.

런타임은 골라 쓰는 거예요 — tokio, async-std, smol

그래서 실무에서는 거의 항상 비동기 '런타임'을 하나 골라요. 가장 널리 쓰이는 건 단연 tokio고요. 웹 프레임워크 axum, HTTP 클라이언트 reqwest 같은 인기 라이브러리들이 대부분 tokio 위에서 돌아가서 사실상 표준처럼 자리 잡았어요. 그 외에 표준 라이브러리와 비슷한 API를 지향했던 async-std, 그리고 가볍고 모듈식으로 조립하는 걸 좋아하는 사람들을 위한 smol도 있어요. 코드에서 #[tokio::main] 같은 매크로를 본 적 있다면, 그게 바로 "이 프로그램의 비동기 작업은 tokio 실행기가 굴려라"라고 지정하는 부분이에요.

업계 맥락 — 왜 이렇게 만들었을까

다른 언어와 비교하면 Rust의 선택이 더 또렷하게 보여요. Go는 런타임과 고루틴 스케줄러가 언어에 기본 내장돼 있어서 go func() 한 줄이면 알아서 돌아가요. 편하지만 런타임을 떼어낼 수가 없죠. JavaScript는 이벤트 루프 하나가 언어 환경에 박혀 있고요. 반면 Rust는 "런타임을 언어에 박아 넣으면 모두에게 그 비용을 강요하게 된다"고 본 거예요. 임베디드 기기처럼 운영체제도 없는 환경부터 초고성능 서버까지 같은 문법으로 쓰되, 실행 엔진은 상황에 맞게 갈아 끼울 수 있게 한 거죠. 자유도와 제로 코스트(쓰지 않는 기능엔 비용을 안 매김)를 위해 약간의 초기 설정 번거로움을 감수한 트레이드오프인 셈이에요.

한국 개발자에게 주는 시사점

실무에서 Rust 비동기를 쓴다면 적어도 두 가지는 꼭 기억해두면 좋아요. 첫째, .await를 빼먹으면 Future는 그냥 잠자고 있다는 것. 컴파일러가 보통 unused must_use 경고를 주니 그 경고를 무시하지 마세요. 둘째, 비동기 실행기 위에서 무거운 동기 작업이나 std::thread::sleep 같은 블로킹 코드를 그냥 돌리면 안 돼요. 실행기 스레드가 거기서 멈춰버려서 다른 모든 작업이 같이 굶거든요. tokio라면 spawn_blocking으로 따로 빼주는 식으로 처리해야 해요. 이 원리만 이해하고 있으면, 멀쩡해 보이는데 응답이 느려지는 미스터리한 성능 문제를 훨씬 빨리 잡을 수 있어요.

마무리

핵심 한 줄로 정리하면, "Rust의 Future는 약속일 뿐이고, 그 약속을 실제로 굴리는 건 내가 직접 고른 런타임이다"예요. 여러분은 Go의 '알아서 다 해주는' 방식과 Rust의 '내가 고르고 조립하는' 방식 중 어느 쪽이 더 끌리세요? 실무에서 tokio 말고 다른 런타임 써본 경험 있으면 공유해주세요.


🔗 출처: Hacker News

이 뉴스가 유용했나요?

이 기술을 직접 배워보세요

AI 도구, 직접 활용해보세요

AI 시대, 코딩으로 수익을 만드는 방법을 배울 수 있습니다.

AI 활용 강의 보기

"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"

실제 수강생 후기
  • 비전공자도 6개월이면 첫 수익
  • 20년 경력 개발자 직강
  • 자동화 프로그램 + 소스코드 제공

매일 AI·개발 뉴스를 받아보세요

주요 테크 뉴스를 매일 아침 이메일로 전해드립니다.

스팸 없이, 언제든 구독 취소 가능합니다.