해커 뉴스에서 756포인트. C++ 코루틴에 관한 블로그 포스트 하나가 받은 점수다.
새로운 라이브러리도 아니다. 컴파일러 익스플로잇도 아니다. 특별히 기발한 해킹도 아니다. Unity에서 일하는 Alex라는 개발자가 C++20의 코루틴 메커니즘을 들여다보고 이렇게 말한 것뿐이다. "이거 내가 C#에서 몇 년째 작성해 온 것과 똑같은 건데, 단계만 더 많고 문서화만 더 형편없잖아."
voithos.io에 게시된 이 글은 Unity의 익숙한 IEnumerator 기반 코루틴 패턴이 C++20 코루틴에 거의 직접적으로 대응된다는 것을 차근차근 설명한다. 반응으로 미루어 보건대, 6년간의 cppreference 페이지와 위원회 논문이 한 번도 건드리지 못한 지점을 정확히 찔렀다.
표준 명세는 코루틴을 정의할 수 있다—설명할 수는 없을 뿐
C++20 코루틴은 2020년에 도입되었다. 이 기능은 강력하고, 유연하며, 그리고—도입 이후 거의 모든 개발자 포럼 스레드에 따르면—공식 문서만으로는 배우기가 거의 불가능하다.
C++ 표준은 코루틴을 promise_type, coroutine_handle, co_await, co_yield, co_return이라는 용어로 기술한다. 프로토콜을 외과적 정밀함으로 명세한다. 하지만 이 모든 것이 *왜* 존재하는지, *언제* 필요한 것인지는 절대 알려주지 않는다.
한 해커 뉴스 댓글러의 표현을 빌리자면, "C++ 코루틴 명세를 읽는 것은 운전하는 법만 알고 싶었을 뿐인데 자동차 엔진 설계도를 읽는 것과 같다."
voithos.io 기사는 접근 방식을 완전히 뒤집는다. 프로토콜에서 시작하는 대신, 모든 Unity 개발자가 이미 이해하고 있는 문제에서 시작한다. 매 프레임마다 조금씩 실행되고, 게임 루프에 제어권을 양보하고, 다음 프레임에서 멈춘 지점부터 다시 이어가는 함수를 만들고 싶다는 것이다.
그게 전부다. 그것이 코루틴이다. 그리고 이 프레임으로 한 번 바라보면, C++의 복잡한 장치들이 추상적 타입 이론이 아니라 배관 공사처럼 보이기 시작한다.
로제타 스톤 효과
이 글의 진짜 기여는 C++ 코루틴을 설명하는 데 있지 않다—그건 이미 많은 사람이 해왔다. Unity를 *번역 계층*으로 사용한 것에 있다.
Unity의 코루틴 모델은 프로그래밍에서 가장 널리 이해된 협력적 멀티태스킹 패턴 중 하나다. 수백만 명의 게임 개발자가 한 프레임을 건너뛰기 위해 yield return null을, 실행을 일시 중지하기 위해 yield return new WaitForSeconds(2f)를 작성해 왔다. 이것이 직관적으로 느껴지는 이유는 프레임워크가 구현 세부 사항보다 멘탈 모델을 먼저 건네주기 때문이다.
C++20 코루틴은 내부적으로 같은 일을 하고 있다. promise_type은 Unity의 코루틴 스케줄러다. coroutine_handle은 일시 중지된 함수에 대한 런타임의 참조다. co_yield는 yield return이다. 대응이 완벽하지는 않다—수명 관리와 타입 소거에서 실질적인 차이가 있다—하지만 이해의 토대를 쌓기에는 충분히 가깝다.
해커 뉴스 토론에 따르면, 이것이 바로 숙련된 C++ 개발자들이 그동안 놓치고 있던 것이었다. 수년간 C++ 경험을 가진 여러 댓글러들이 이 글이 코루틴을 처음으로 "이해하게 된" 계기라고 묘사했다.
진짜 이야기는 코루틴이 아니다—언어가 어떻게 도입에 실패하는가의 이야기다
이 글이 바이럴된 것에서 정말 흥미로운 점은 이렇다. 코루틴 자체에 관한 이야기가 아니라는 것이다. 프로그래밍 언어가 기능이 본격적으로 자리 잡아야 할 바로 그 순간에 어떻게 사용자를 실망시키는가에 관한 이야기다.
C++20 코루틴은 *컴파일러 기능*이다. 표준은 컴파일러가 코루틴 함수를 어떻게 변환하는지 정의하지만, 의도적으로 코루틴 라이브러리를 제공하지 않는다. std::task도 없다. std::generator도 없다(C++23까지는 없었고, 그마저도 최소한의 수준이다). 내장 스케줄러도 없다. 위원회의 입장은 커뮤니티가 이런 것들을 만들 것이라는 것이었다.
6년이 지난 지금, 커뮤니티는 여전히 올바른 추상화에 대해 논쟁 중이다. 그 사이에 개별 개발자들은 표준 위원회의 논문 300페이지로 하지 못한 일을 3,000단어의 블로그 포스트로 해내고 있다.
이 패턴은 업계 전반에서 반복된다. Rust의 비동기 이야기도 Tokio 프레임워크가 사람들에게 손에 잡히는 무언가를 줄 때까지 마찬가지로 불투명했다. JavaScript의 Promise도 프레임워크가 개발자들이 추론할 수 있는 패턴으로 감싸줄 때까지 당혹스러운 존재였다.
교훈은—그리고 계속 재확인되는 교훈은—간단하다. 개발자는 명세에서 배우지 않는다. 프레임워크에서 배운다. 명세는 무엇이 합법적인지를 정의한다. 프레임워크는 무엇이 유용한지를 보여준다.
비유가 깨지는 지점—그리고 그래도 작동하는 이유
해커 뉴스 스레드는 단순한 동의 이상의 것을 읽을 가치가 있다. 숙련된 C++ 엔지니어 몇몇이 특정 지점에 반론을 제기했다. Unity 코루틴은 실질적으로 스택풀(전체 콜 스택을 캡처)인 반면, C++20 코루틴은 스택리스(코루틴 프레임만 보존)라는 점을 지적한 사람들이 있었다. 수명 의미론이 상당히 다르다는 점도 지적되었다. C++ 코루틴은 Unity의 가비지 컬렉션 기반 코루틴에서는 발생할 수 없는 방식으로 댕글링될 수 있다.
이것은 타당한 기술적 차이다. 하지만 스레드에서 반복적으로 제기된 반론은, 불완전한 비유가 완벽한 명세보다 여전히 더 잘 가르친다는 것이다. 비유는 나중에 바로잡을 수 있다. 애초에 개념을 이해하지 못한 개발자는 바로잡을 수가 없다.
한 댓글이 뇌리에 남았다. "C++ 코루틴 입문 글을 다섯 개나 읽었다. 이 글이 읽고 나서 기억만으로 코루틴을 작성할 수 있었던 첫 번째 글이다."
이것은 저자에 대한 칭찬이 아니다. 그 이전에 나온 모든 자료에 대한 고발이다.
이걸로 무엇을 해야 하는가
이전에 C++ 코루틴에서 좌절한 적이 있다면, voithos.io 기사를 읽어라. Unity를 한 번도 만져본 적 없더라도 상관없다. 게임 루프 프레이밍은 이벤트 루프를 작성해 본 개발자라면 누구나 따라갈 수 있을 만큼 보편적이다.
라이브러리 저자나 문서 작성자라면, 이 글이 *왜* 효과적인지 연구하라. 이 글은 기능을 단순화하지 않는다—맥락화한다. 프로토콜에 대한 이해를 요구하기 전에 독자에게 작동하는 멘탈 모델을 먼저 제공한다. 이것은 promise_type에서 시작해서 바깥으로 확장하는 것과는 근본적으로 다른 접근 방식이다.
C++ 위원회에 속해 있거나 표준화 작업에 관여하고 있다면, 이것을 데이터로 받아들여라. 블로그 포스트 하나가 명세보다 기능을 더 잘 가르칠 때, 명세에는 UX 문제가 있는 것이다. C++26이 진행 중이다. 코루틴 이야기에는 여전히 안착할 표준 라이브러리가 필요하다.
그리고 이미 Unity 코루틴을 알고 있는 게임 개발자라면—축하한다. 당신은 이미 현대 C++에서 가장 두려운 기능 중 하나를 이해하고 있다. 단지 아직 모르고 있었을 뿐이다.
병목은 컴파일러가 아니었다
프로그래밍 언어의 도입은 기능의 강력함으로 결정되지 않는다. 기능의 이해도로 결정된다. C++ 코루틴은 C#, Python, Kotlin의 대응 기능보다 유연성 면에서 앞선다고 할 수 있다. 하지만 이해도 면에서는 압도적인 격차로 최하위이기도 하다.
6년 된 언어 기능에 대해 블로그 포스트 하나가 756포인트의 열광을 이끌어낼 수 있다는 사실이 진짜 병목이 어디에 있는지 모든 것을 말해준다. 컴파일러가 아니었다. 하드웨어가 아니었다. 언제나 설명이었다.
우리 업계 최고의 문서 중 일부는 언어 설계자가 작성한 것이 아니다. 방금 이해한 사람, 아직 모르던 시절의 감각을 기억하는 사람이 작성한 것이다.