Rust 2024를 바라보며, 저자는 “더 많은 것을 요구할 용기”라는 화두로 러스트의 사용성 향상과 근본적 단순화, async/unsafe 영역의 표현력과 인체공학성 개선, 그리고 학습자와 고급 사용자 모두를 위한 획기적인 툴링 강화를 촉구한다.
작년에 우리는 Rust 2021을 내놓았고, 그 변화들이 실제 사용성에서 큰 개선임을 체감하고 있다. 개별 변화는 비교적 소박했지만, 정밀 캡처 클로저와 더 단순한 포맷 문자열 (println("{x:?}") 대신 println!("{:?}", x))의 조합은 내 “일상적인” 개발에 확실히 차이를 만들어 주었다.1 NLL과 Rust 2018의 새 모듈 시스템 때와 마찬가지로, 나는 이 새로운 관례에 금방 적응했다. 예전 코드로 돌아가 대여 검사기 회피 요령이나 포맷 문자열을 보면, 내 마음 한 켠이 조금씩 죽어간다.2
2022년에 접어들며, 내 생각은 점점 다음 러스트 에디션으로 향하고 있다. 앞으로 몇 년 동안 Rust와 커뮤니티에 내가 바라는 것은 무엇일까? 내 머리에 떠오르는 주제는 바로 이거다. 바로, 더 많은 것을 요구할 용기. 지난 몇 년간 Rust는 사용하기 꽤 많이 좋아졌지만, 나는 아직 만족하지 않는다. 오늘날보다 생산성과 사용 편의성이 22배3 더 좋아질 여지가 있다고 믿고, 그것을 신뢰성, 성능, 다재다능함을 의미 있게 희생하지 않고도 이룰 수 있다고 생각한다.
Rust 사용이 계속 늘어나면서, 나는 다양한 배경과 경험을 가진 많은 Rust 사용자들과 이야기를 나눌 수 있었다. 내가 즐겨 묻는 주제 중 하나는 Rust를 배우는 경험이다. 여러 면에서, 이야기는 내가 예상했던 것보다 훨씬 낙관적이다. 대부분의 사람들은 3~6개월 안에 Rust를 학습하고 생산성을 느낄 수 있다. 더 나아가, 일단 익숙해지고 나면 대부분의 사람들은 Rust를 정말 즐기며, 소유권 규칙을 배우는 것이 다른 언어로 코드를 작성할 때에도 영향을 미친다고 말한다(좋은 방향으로). 또한 Rust에서는 다른 언어에 비해 훨씬 적은 버그를 경험한다고 말한다. 이는 C++에 대해서만이 아니라4, Java 같은 다른 언어로 작성된 것과 비교해도 마찬가지다5.
그렇다고 해도, Rust를 사용하는 데에는 상당한 인지적 오버헤드가 분명 존재한다. 진정한 전문가라고 느끼는 Rust 사용자는 많지 않다6. 사람들이 반복해서 혼란스럽다고 언급하는 주제도 있다. “where 절”, “라이프타임” 같은 것들이다. 더 많은 사람들과 대화할수록, 문제는 어느 한 가지만이 아니라 모든 것이라는 느낌을 받는다. 여러 가지 관심사를 한꺼번에 저글링해야 하고, 코드를 실행해 보기 전에 모든 것을 줄 세워 맞춰야 한다는 점이 문제다.
이러한 인터뷰는 인체공학(ergonomics) 이니셔티브와 Rust 2021에서 우리가 했던 작업을 정말 잘 뒷받침해 준다. 내가 이야기했던 누군가는 이렇게 말했다:
되돌아보면, NLL과 match 인체공학은 사람들이 Rust를 배우는 데 큰 개선이었다. 많은 사람들이 갑자기 여러 가지가 훨씬 쉬워졌다고 느꼈다. NLL은 가변성과 관련된 많은 것들을 훨씬 단순하게 만들었다. 아직 남아 있는 하나는 클로저에서 필드의 분리(disjoint) 캡처다. 이 또한 사람들이 이해하지 못했던 예시다. “왜 컴파일러가 나에게 소리를 지르는 거지? 이건 돼야 하는데?”
이러한 성과에 만족스럽지만, 아직 끝이 아니라고 생각한다. 나는 두 가지 차원에서의 진전을 보고 싶다:
• 근본적 단순화: 이는 NLL이나 분리 클로저 캡처처럼 컴파일러가 수용할 수 있는 것의 판도를 바꾸는 변화다. 이러한 변화는 분석을 더 복잡하게 만들기도 하지만, 궁극적으로 언어를 더 단순하게 느끼게 한다. 작동해야 하는 프로그램이 실제로 작동하게 되는 비율이 높아지기 때문이다. 이런 단순화는 대개 큰 논쟁을 일으키지는 않지만, 설계와 구현은 어렵다. 종종 여러 모서리 사례에서 언어 의미가 소폭 바뀌기 때문에 에디션이 필요하다.
여기에서 가장 단순한 개선 중 하나는 polonius를 도입하는 일일 것이다. 이는 내가 꽤 자주 보게 되는 패턴인 #47680을 해결해 줄 것이다. 또한 스코프드 컨텍스트(scoped contexts), 어떤 형태의 뷰 타입(view types), 특수화(specialization), 또는 자기참조 구조체를 다루는 방법 같은 언어 확장도 이 범주에 들어갈 수 있다고 생각한다. 이건 조금 더 까다롭다. 언어가 커지는 것은 단순화가 아니지만, 흔한 패턴들을 훨씬 더 단순하게 만들어 준다면 순이익이 될 수 있다.
• 모서리 다듬기(sanding rough edges): 이는 Rust 코드를 쓰는 일을 그저 더 쉽게 만들어 주는 변화다. 점을 찍고 줄을 긋듯 사소한 의례적 작업이 줄어든다. 좋은 예가 라이프타임 생략(lifetime elision)이다. 여러분이 컴파일러의 제안을 무작정 따라 하거나, 여기저기에 무작위로 &나 *를 붙여서 컴파일러가 만족하길 바라는 자신을 발견한다면, 지금 모서리를 마주하고 있는 것이다.
모서리 다듬기는 모두에게 도움이 되지만, 가장 큰 영향은 신입에게 간다. 숙련자들은 약간의 “생존 편향”이 있다. 요령을 알고 자동으로 적용한다. 신입은 그런 이점이 없어서, 단순한 컴파일 오류를 고치려다 시간을 많이 낭비하거나(아니면 아예 포기하거나) 할 수 있다.
match 인체공학은 이 범주의 최근 변화였다. 개선이었다고 믿지만, 특히 복사 타입에 대한 참조 주변에서 여러 거친 모서리를 낳기도 했다(자세한 논의는 #44619 참조). 나는 그것들을 고치고 싶고, 또한 암시적 바운드(implied bounds) 같은 다른 영역의 “모서리”도 다듬고 싶다.
이전 항목과 함께, Async Rust가 자연스럽게 느껴지기까지 아직 할 일이 꽤 남았다고 본다. Tyler Mandry와 나는 최근 “Inside Rust” 블로그에 Async Rust in 2022라는 글을 써서, 우리가 바라는 async Rust의 느낌(“그냥 async만 붙이면 된다”)과 거기에 이르는 계획을 개괄했다.
고도로 동시성이 높은 애플리케이션은 Rust가 빛을 발하는 핵심 영역이라는 점은 분명해 보인다. 그러니 이 분야에 계속 과감히 투자하는 것은 타당하다. 더 좋은 점은, 그 투자들이 async Rust 사용자에게만 이익이 아니라는 것이다. 많은 투자 항목이 제네릭 연관 타입(GATs)7이나 type alias impl trait8 같은 Rust의 근본적 확장으로, 궁극적으로 모두에게 혜택을 준다.
하지만 정말 뛰어난 async Rust 경험을 제공하려면 언어 확장만으로는 부족하다. tokio console 같은 더 나은 도구와, nrc가 주도하는 이식성·상호운용성 노력 같은 표준화가 또한 필요하다.
이상하게 들릴지 모르지만, Rust를 지금처럼 안전하게 만들어 주는 것의 일부는 Rust가 unsafe 코드를 지원한다는 사실이다. unsafe 코드는 Rust 프로그래머가 기계의 모든 능력에 접근할 수 있게 해 주며, 이는 Rust가 다재다능함을 갖게 하는 근거다. 그런 다음 Rust 프로그래머는 소유권/대여를 통해 그 원시 능력을 안전한 인터페이스 안에 캡슐화하고, 그 라이브러리의 클라이언트는 제대로 동작함을 신뢰할 수 있다.
하지만 unsafe 세계에도 옥에 티가 있다. 현실적으로 올바른 unsafe Rust 코드를 작성하는 일은 꽤 어렵다9. 사실, 우리가 unsafe 코드 작성자가 따라야 할 규칙을 제대로 정의한 적이 없기 때문에, 말 그대로 불가능하다고도 할 수 있다. 아무도 올바름이 무엇인지 정의하지 않았다면, 자신이 올바르게 하고 있는지 알 길이 없기 때문이다.
물론 여기에는 유망한 작업이 많이 있다! 예를 들어 Stacked Borrows는 별칭(aliasing) 규칙에 대한 실용적 접근법에 매우 가까워 보인다. 그 규칙은 miri에 구현되어 있고, 많은 사람들이 자신의 unsafe 코드를 점검하는 데 이를 사용하고 있다. 마지막으로, unsafe code guidelines 노력은 레이아웃 보장과 기타 unsafe 코드의 여러 측면을 문서화하는 데 좋은 진전을 이루었지만, 그 작업이 RFC로 제안되거나 정식 규범이 되지는 못했다(해당 저장소의 이슈에는 훌륭한 논의가 많이 담겨 있다).
이제는 unsafe 코드를 작성하는 전반적 경험에 제대로 주목할 때라고 생각한다. 사람들이 올바른 unsafe Rust 추상화를 작성할 수 있도록 해야 한다. 이는, 그렇다, 그들이 따라야 할 규칙을 정의하는 데 투자해야 함을 뜻한다. 또한 올바른 unsafe Rust 코드를 더 편리하게 작성할 수 있도록 시간 투자를 해야 한다고 생각한다. 오늘날의 unsafe Rust는 종종 코드에 크게 기여하지 않는 많은 애너테이션과 캐스트를 수반한다10. 또한 생 포인터(raw pointer)로의 메서드 디스패치 같은 핵심 기능이 작동하지 않거나, unsafe 보장을 충족하는 데 도움이 될 unsafe 필드 같은 기능이 부재하기도 하다.
도구는 학습자와 파워 유저 모두에게 Rust 사용 경험에 큰 영향을 준다. 나는 rustup과 cargo의 번거로움 없는 경험이 Rust의 채택에 우리 안전성 보장만큼이나—아니 어쩌면 그보다 더—기여했다고 확신한다. 컴파일러의 오류 메시지 품질은 내가 하는 거의 모든 대화에서 언급되고, clippy와 rustfmt를 신입 개발자 온보딩의 핵심 요소로 꼽는 사람은 수도 없이 많다. 게다가 수년 간의 엄청난 노고 끝에, Rust의 IDE 지원은 이제 정말, 정말 좋아지고 있다. rust-analyzer와 IntelliJ Rust 팀 모두에게 큰 박수를 보낸다.
그리고도, 나는 욕심이 많아서, 더 원한다. Rust가 “획기적으로 뛰어난” 툴링의 전통을 이어가길 바란다. cargo test --debug라고만 치면 테스트 실패가 자동으로 “무슨 일이 있었는지”를 쉽게 파악하게 해 주는 전지(omniscient)에 가까운 디버거에서 나타나길 바란다11. CPU를 어디에서 태우고 메모리를 어디에서 할당하는지에 대한 친숙한 분석을 제공하는 프로파일러를 원한다. 코드 커버리지를 분석·개선하거나 퍼저를 사용해 입력을 만들어 보는 등, 신뢰성을 한 단계 끌어올리는 모범 사례들을 적용하는 일이 사소했으면 한다.
나는 특히 Rust 프로그래머와 그들의 프로그램 사이의 “근본적 관계”를 바꾸는 도구에 관심이 많다. 현대적인 Rust IDE에서 컴파일 오류를 고치는 경험과 rustc를 사용하는 경험 사이의 차이는 이를 잘 보여 준다. IDE에서는 어떤 오류를 어떤 순서로 고칠지 자유롭게 고를 수 있고, 요즘 IDE는 이게 꽤 잘 된다. 피드백도 빠르다. 이는 큰 이점이 될 수 있다.
나는 이런 식의 일을 더 할 수 있다고 본다. 대여 검사(borrow check)를 통과하지 못한 코드를 “단계별로 따라가며” 실행해 보고, 만약 그 코드가 실행된다면 어떤 종류의 메모리 안전 오류가 발생하는지를 보면서, 빌림 검사기가 어떻게 작동하는지 배우는 경험을 떠올려 보자. 혹은 트레이트 해석 실패 같은 복잡한 오류를 더 상호작용적인 방식으로 “디버깅”하는 것 말이다. 상상은 무한하다.
특히 중요할 수 있는 영역이 바로 “unsafe” Rust 주변의 개선된 도구다. 정말로 사람들이 실전에서 올바른 unsafe Rust 코드를 쓰길 원한다면—그리고 나는 그렇다!—그들에게는 도움이 필요하다. 모든 Rust 도구와 마찬가지로, 우리는 기본기를 갖춰야 하고, 그 이상으로 나아갈 수도 있다고 본다. 예컨대 새니타이저(sanitizer)가 확실히 필요하다. 그러나 단지 오류를 탐지하는 데 그치지 말고, 그 새니타이저를 디버거와 연결해, 그 오류를 Stacked Borrows가 어떻게 작동하는지를 가르치는 기회로 바꿀 수 있다. 퍼징과 속성 기반 테스트 같은 것을 쉽게 만들어 주는 더 나은 테스트 프레임워크를 구축할 수 있다. 그리고 형식 기법(formal methods)에 강한 지원을 제공해, 시간을 투자하고자 하는 라이브러리가 더 높은 수준의 보증을 줄 수 있도록 도울 수 있다(예컨대 표준 라이브러리는 좋은 후보일 것이다).
Rust가 더 큰 성공을 거둘수록 변화를 만들기는 점점 더 어려워진다. 세상에는 점점 더 많은 Rust 코드가 존재하고, 연속성과 안정성이 때로는 망가진 것을 고치는 것보다 더 중요할 때도 있다. 그리고 변화를 하기로 결정하더라도, 모두가 “다르게 해야 한다”는 의견을 갖게 마련—게다가, 더 나쁜 것은, 때로는 그들이 옳다는 점이다12. “Rust는 이미 충분히 좋고, 하나의 언어로 모든 걸 하려는 건 바람직하지 않다”라고 말하고 그만두고 싶은 유혹이 들 때가 있다.
Rust 2024에서는 그러지 않았으면 한다. Rust는 멋지다. 하지만 더 멋져질 수 있다. 물론 “그냥” 변화를 만들려 해서는 안 된다. 우리가 이전에 해 둔 일을 존중해야 하고, 변동에 따르는 비용에 대해 현실적이어야 한다. 하지만 우리는 현재의 Rust 프로그래머들이 시작에 불과하다고, Rust 프로그램의 대다수는 아직 쓰여지지 않았다고—실제로 그러하므로—생각하며 계획하고 꿈꿔야 한다.
RustConf 2024에서는 사람들이 예전의 고난을 서로 자랑하길 바란다. “그래, 나 말이야, 예전에 async Rust를 썼지. 뭔가 작은 걸 하려면 매번 crates.io에서 랜덤한 크레이트를 주워와야 했거든. 트레이트에서 async fn을 쓰고 싶다고? 크레이트를 가져와. await할 수 있는 이터레이터를 쓰고 싶다고? 크레이트를 가져와. 사람들이 5일 동안 해킹하고 스탠드업에 와서는 ‘드디어 컴파일됐어!’라고 말하곤 했다니까! 그리고 우린 눈 오는 날에도 출근길이 오르막이었지! 올 때도 오르막! 한여름에도!”13
그러니, Rust 2024에서는, 더 많은 것을 요구할 용기를 내자14.
흥미로운 변화 한 가지: 나는 요즘 코드를 더 많이 직접 쓰고 있다. 이것만으로도 내 마음가짐이 크게 달라진다
진짜로 속이 타들어간다니까! 정말로
2022년이니까, 알지?↩︎
저수준 Rust 코드(예전 같으면 C++로 썼을)를 개발한 팀과 이야기를 나눴는데, 그들은 3년이 넘는 기간 동안 단 한 번의 크래시를 경험했다고 보고했다. 그것도 C 라이브러리로의 FFI에서 기원한 것이었다. 놀랍다.↩︎
가장 흔하게, Rust가 Java 같은 언어에 비해 우위를 갖는 이유는 더 강한 동시성 보장 때문이다. 그러나 그것만은 아니다. 다른 언어에서 요구 성능을 충족하려면 종종 “꽤 영리한” 코드를 써야 한다. Rust의 더 높은 성능 덕분에 더 단순한 코드를 대신 쓸 수 있고, 그러면 버그도 그만큼 줄어든다.↩︎
설문에는 사람들이 스스로의 전문성을 10점 만점에 7점 정도로 꼽는 봉우리가 일관되게 나타난다.↩︎
쉼 없이 그 작업을 이끌고 있는 Jack Huey에게 경의를
그 작업을 쉼 없이 이끌고 있는 Oliver Scherer에게도 경의를
Armin이 최근에 쓴 글, Unsafe Rust is Too Hard는 실제로 어떤 어려움을 겪을 수 있는지 생생한 예를 보여 준다.↩︎
…군더더기(보일러플레이트) 말고는.↩︎
Rustc Reading Club에서 Felix가 시연한 pernos.co 데모 녹화를 보며 이 분야에서 무엇이 가능한지 감을 잡아 보라: 영상 보기↩︎
모두가 틀렸을 때가 얼마나 쉬운지
조금 과장했을지도.↩︎
운율도 맞잖아! 시인인 줄도 몰랐네