Rust는 커튼 뒤에서 무슨 일을 하고 있을까?
원문은 Matthias Endler님이 에 게재했습니다. 이 블로그 구독하기
Rust는 코드를 즐겁게 작성할 수 있도록 다양한 syntactic sugar를 제공한다. 하지만 때로는 커튼 뒤를 들여다보고 컴파일러가 실제로 우리 코드로 무엇을 하고 있는지 파악하기가 어렵다.
2018년 Rust Belt Rust 2018에서 Tshepang Lekhonkhobe가 발표한 Syntax conveniences afforded by the compiler라는 강연을 봤다 (녹화 영상은 여기에서).
초록을 인용하자면 다음과 같다:
Rust 컴파일러는 사용자의 삶을 더 편하게 만드는 여러 가지 편의 기능을 제공한다. 내부에서 무슨 일이 일어나고 있는지 신비에 싸여 있다고 느끼지 않으려면, 이러한 기능이 무엇인지 아는 것이 좋다… 세상에 대해 마법 같은 생각을 덜 할수록 더 좋다.
그는 이어서 이러한 편의 기능의 몇 가지 예를 든다:
- 라이프타임 생략
- 타입 추론
- syntactic sugar
- 암시적 역참조
- 타입 강제 변환
- 숨겨진 코드 (예: prelude)
강연에서 그가 이러한 편의 기능을 적용한 코드와 적용하지 않은 코드를 비교하는 것을 보는 것은 매우 유익하고 재미있었다.
집에 돌아온 뒤 더 알아보고 싶어졌다. 커튼 뒤에서 Rust가 무슨 일을 하고 있는지 보여주는 도구가 있을지 궁금했다.
Reddit에서 desugar된 출력을 생성하는 컴파일러 플래그에 대한 토론을 찾았다. (참고로 여기서는 rustup을 사용해 +nightly 플래그로 nightly 컴파일러를 실행하고 있다.)
rustc +nightly -Zunpretty=hir example.rsHIR은 high-level intermediate representation의 약자다. 이는 기본적으로 컴파일러가 사용하기에 더 적합한 추상 구문 트리(AST)다. 이후 컴파일 단계에서 처리하기 쉬운 기본 구성 요소로 syntactic sugar를 대체한다. 더 자세히 알고 싶다면 Nico Matsakis의 상세한 글을 읽어보라.
어쨌든 출력 결과는 놀라울 정도로 읽기 쉬워 보였다(아래 참조). 여기에 약간의 구문 강조와 포맷팅을 더하면 꽤 유용한 도구가 될 수 있겠다는 생각이 들었다.
rustfmt를 적용해 보니 믿기 어려울 정도로 잘 동작했다. 이 작은 성공에 힘입어 이를 cargo 서브커맨드로 감싸 cargo-inspect라고 이름 붙였다.
실제 코드에 cargo-inspect를 적용해 보자!
예제 - range 표현식 desugar하기
다음 예제들은 프로젝트의 examples 폴더에서도 찾을 수 있다.
입력:
for n in 1..3 {
println!("{}", n);
}cargo-inspect 출력:
이것이 깔끔하게 포맷팅된 터미널 출력이다. prettyprint 덕분에 줄 번호와 색상이 표시되는데, 이는 bat 위에 구축된 라이브러리다. 잘 안 보일 수도 있으니 요점은 다음과 같다:
match ::std::iter::IntoIterator::into_iter(
::std::ops::Range { start: 1, end: 3 })
mut iter => loop {
// ...
},
};1..3이 std::ops::Range { start: 1, end: 3 }로 변환되는 것을 볼 수 있다. 컴파일러 백엔드 입장에서는 이 둘은 완전히 동일하다. 따라서 다음 코드는 성립한다:
assert_eq!((1..3), std::ops::Range { start: 1, end: 3 });예제 - 파일 처리
입력:
use std::fs::File;
use std::io::Error;
fn main() -> Result<(), Error> {
let file = File::open("file.txt")?;
Ok(())
}출력:
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;
use std::fs::File;
use std::io::Error;
fn main() -> Result<(), Error> {
let file = match ::std::ops::Try::into_result(
<File>::open("file.txt")) {
::std::result::Result::Err(err) =>
#[allow(unreachable_code)]
{
#[allow(unreachable_code)]
return ::std::ops::Try::from_error(
::std::convert::From::from(err))
}
::std::result::Result::Ok(val) =>
#[allow(unreachable_code)]
{
#[allow(unreachable_code)]
val
}
};
Ok(())
}? 연산자가 File::open의 Result에 대한 match로 desugar되는 것을 볼 수 있다. 에러가 발생한 경우 std::convert::From::from을 적용해 에러 타입을 변환한다. 그 외의 경우에는 단순히 Ok 값을 반환한다.
강연
벨기에에서 열린 FOSDEM에서 이 프로젝트에 대해 자세히 발표할 기회가 있었다. 영상은 다음과 같다:
향후 계획
여기서 컴파일러를 다시 만들려는 생각은 없다. rustc가 내가 할 수 있는 것보다 훨씬 더 훌륭하게 해내고 있다. 이 모든 기능은 이미 존재하던 것이다. 나는 그저 나와 같은 학습자들이 컴파일러에 더 쉽게 다가갈 수 있도록 만들려는 것뿐이다.
현재 이 도구는 다소 취약하다. 문제가 생기면 보기 좋지 않은 에러 메시지를 출력한다. 작고 독립적인 예제 스니펫에 실행할 때 가장 빛을 발한다.
참여해 보세요!
GitHub에 다른 사람들이 참여할 수 있도록 몇 가지 이슈를 열어 두었다. 구체적으로 다음과 같은 옵션이 있으면 좋겠다:
또한 특히 흥미로운 코드 예제를 찾았다면 주저하지 말고 examples 폴더에 기여해 달라.
글을 무작위로 읽기

댓글
로그인하고 댓글 남기기