주석
코드의 이유와 제약을 남기는 Rust 주석 문법을 익히고 구현과 어긋나지 않는 설명을 작성·점검하는 기준을 세웁니다.
프로그래머는 읽기 쉬운 코드를 쓰려고 애쓰지만, 코드만으로는 왜 그렇게 썼는지 드러나지 않을 때가 있습니다. 그럴 때 코드에 남기는 설명이 주석(comment)입니다. 컴파일러는 일반 주석을 무시하지만, 코드를 읽는 사람에게는 코드만으로 알 수 없는 맥락을 알려 줍니다. 이 글에서는 러스트의 주석 문법과, 문서를 만드는 문서화 주석, 그리고 쓸모 있는 주석을 쓰는 기준을 살펴봅니다.
일반 주석
러스트의 한 줄 주석은 슬래시 두 개 //로 시작해 그 줄 끝까지 이어집니다. 컴파일러는 //부터 줄 끝까지를 모두 무시하므로, 주석에는 한국어 문장이든 코드처럼 보이는 글이든 자유롭게 쓸 수 있습니다.
// hello, world여러 줄에 걸친 주석은 줄마다 //를 붙입니다. // 하나는 그 줄에만 효과가 있으므로, 둘째 줄부터 //를 빼면 그 줄은 코드로 읽혀 컴파일 오류가 납니다.
// 여기서는 여러 줄에 걸쳐 설명해야 할 만큼
// 복잡한 일을 합니다. 이 주석이 무슨 일이
// 일어나는지 설명해 줍니다.주석은 코드 줄의 끝에 붙일 수도 있습니다. 이때도 // 앞의 코드는 그대로 컴파일되고, //부터 줄 끝까지만 주석이 됩니다.
fn main() {
let lucky_number = 7; // 오늘은 운이 좋은 것 같아요
}하지만 설명할 코드의 바로 윗줄에 따로 쓰는 경우가 더 많습니다. 윗줄에 두면 코드 줄이 길어도 주석까지 한 줄에 몰리지 않고, 주석이 바로 아래 줄을 설명한다는 것도 한눈에 보입니다.
fn main() {
// 오늘은 운이 좋은 것 같아요
let lucky_number = 7;
}블록 주석은 /*와 */ 사이에 씁니다. 러스트의 블록 주석은 중첩할 수 있어서, 블록 주석 안에 또 다른 블록 주석을 넣어도 바깥 주석이 중간에 끝나지 않습니다.
/* 바깥 주석
/* 안쪽 주석 */
다시 바깥 주석
*/C처럼 블록 주석의 중첩을 허용하지 않는 언어라면 안쪽의 */에서 주석이 끝나 다시 바깥 주석이 코드로 읽힙니다. 러스트는 여는 /*와 닫는 */의 짝을 세므로 마지막 */까지가 하나의 주석이며, 그래서 블록 주석이 이미 들어 있는 코드도 통째로 /*와 */로 감싸 잠시 꺼 둘 수 있습니다.
문서화 주석과 속성
러스트에는 일반 주석과 달리 버려지지 않는 문서화 주석(documentation comment)도 있습니다. 문서화 주석은 컴파일러가 문서를 나타내는 doc 속성(attribute)으로 바꾸어 기록하며, rustdoc이 이것을 모아 HTML 문서를 만듭니다. 아래 그림은 일반 주석과 두 가지 문서화 주석, 그리고 파일 첫 줄의 shebang이 각각 어떻게 처리되는지 비교합니다.
RUST · COMMENT FORMS
일반 주석과 문서화 주석은 처리 결과가 다르다
일반 주석은 공백처럼 처리되고, 문서화 주석은 doc 속성이 됩니다. 바깥쪽 문서는 다음 대상에, 안쪽 문서는 포함하는 대상에 적용됩니다.
네 형식은 서로 다른 입력 경로
일반 주석 · 공백으로 취급
// 줄 바꿈 또는 EOF까지
/* 바깥 /* 중첩 */ 계속 */
// 한 줄 주석과 /* ... */ 블록 주석은 토큰화 과정에서 공백처럼 해석됩니다. 블록 주석은 중첩할 수 있습니다.
////와 /***…*/는 문서화 주석이 아니라 일반 주석입니다.
바깥쪽 문서 · 다음 대상
/// 실행 계약
/** 추가 설명 */
pub fn run() {}
#[doc = "..."]
바깥쪽 doc 속성으로 해석되어 뒤따르는 항목 등 바깥쪽 속성을 받을 수 있는 대상에 적용됩니다.
안쪽 문서 · 포함하는 대상
//! 크레이트 안내
/*! 모듈 안내 */
#![doc = "..."]
안쪽 doc 속성으로 해석되어 자신을 포함하는 크레이트나 모듈 같은 대상에 적용됩니다.
문서화 주석은 Markdown 문서 입력이다
컴파일러는 문서화 주석을 단순히 버리지 않고 doc 속성으로 처리합니다. 그 텍스트는 관례상 Markdown이며 rustdoc이 렌더링합니다. 그러나 주석 문법 자체는 Markdown을 해석하지 않으므로 Markdown 코드 안에서도 */는 블록 주석의 닫힘 기호로 처리됩니다.
#![...]는 안쪽 속성입니다. 반면 파일 선두(선택적 BOM 바로 뒤)의 shebang은 주석이 아니며, #! 뒤의 다음 비공백·비일반-주석 토큰이 [가 아니면 해당 첫 줄을 토큰화 전에 제거합니다. 작성할 주석에는 코드가 드러내지 못한 이유와 제약을 남기고 구현과 함께 갱신합니다.일반 주석 //와 /* ... */는 컴파일러가 공백처럼 다루므로 프로그램에 아무 영향도 주지 않습니다. 슬래시 세 개로 시작하는 ///와 /** ... */는 바깥쪽 문서화 주석으로, #[doc = "..."] 속성이 되어 바로 뒤에 오는 함수나 구조체 같은 항목을 설명합니다. 슬래시가 네 개인 ////와 별이 세 개인 /*** ... */는 문서화 주석이 아니라 일반 주석으로 처리됩니다.
//!와 /*! ... */는 안쪽 문서화 주석으로, #![doc = "..."] 속성이 되어 그 주석을 담고 있는 크레이트나 모듈 자체를 설명합니다. 그래서 크레이트 전체의 설명은 보통 src/lib.rs 맨 앞에 //!로 씁니다. 문서화 주석의 내용은 관례상 Markdown으로 쓰며, 문서화 주석으로 문서를 만들고 그 안의 예제를 테스트로 실행하는 방법은 Crates.io 배포에서 다룹니다.
이 규칙에는 두 가지 주의할 점이 있습니다. 첫째, 주석의 문법은 Markdown을 모르므로, 블록 문서화 주석 안의 Markdown 코드 조각에 */를 쓰면 거기서 주석이 끝나 버립니다. 둘째, 파일 맨 앞의 #!로 시작하는 줄은 주석이 아닙니다. 실행할 프로그램을 알려 주는 shebang 줄은 토큰으로 나누기 전에 입력에서 빠지며, #! 다음에 [가 오는 #![...]는 shebang이 아니라 안쪽 속성으로 처리됩니다.
쓸모 있는 주석
주석은 코드가 하는 일을 다시 말하기보다, 코드만 보아서는 알 수 없는 의도와 제약을 남길 때 쓸모가 있습니다. 여러 방법 가운데 왜 이 방법을 골랐는지, 경계값을 어떻게 처리하기로 했는지, 어떤 전제가 지켜져야 안전한지, 성능을 위해 무엇을 양보했는지가 그런 내용입니다. 다음 코드의 주석은 곱셈 자체가 아니라, 왜 60을 곱하는지를 설명합니다.
fn main() {
let minutes = 3;
// 저장 형식이 초 단위이므로 분을 초로 바꾼다.
let seconds = minutes * 60;
println!("{seconds}");
}180minutes * 60이 곱셈이라는 것은 코드만 보아도 알 수 있으므로, "60을 곱한다"는 주석은 아무것도 더해 주지 않습니다. 반면 저장 형식이 초 단위라는 사실은 코드에 드러나지 않으며, 이것을 알아야 나중에 누군가 60을 다른 수로 바꾸려 할 때 그래도 되는지 판단할 수 있습니다.
주석은 컴파일러가 검사하지 않으므로, 코드가 바뀌었는데 주석은 예전 동작을 설명하는 일이 쉽게 생깁니다. 그래서 코드를 고칠 때는 주석도 함께 고치고, 코드 리뷰에서는 주석이 있는지보다 주석이 지금의 코드와 맞는지를 먼저 봅니다. 구현을 그대로 되풀이하는 주석은 이유를 설명하는 주석으로 바꾸거나 지우고, 함수가 실패하는 조건이나 입력의 제약, 안전성의 전제처럼 그 함수를 쓰는 사람이 알아야 할 내용이 빠지지 않았는지 확인합니다.
정리
- 한 줄 주석은
//로 시작해 줄 끝까지 이어지고, 블록 주석은/*와*/사이에 쓰며 중첩할 수 있습니다. - 일반 주석은 공백처럼 처리되어 프로그램에 영향을 주지 않습니다.
///와/** */는 뒤에 오는 항목을,//!와/*! */는 그 주석을 담은 크레이트나 모듈을 설명하는 문서화 주석이며,doc속성이 됩니다.- 블록 주석 안의
*/는 Markdown 코드 조각 안에 있어도 주석을 끝내며, 파일 맨 앞의 shebang은 주석이 아닙니다. - 주석에는 코드가 하는 일보다 코드만으로 알 수 없는 의도와 제약을 쓰고, 코드를 고칠 때 함께 고칩니다.
연습 문제
1. 코드와 주석이 어긋날 때
분을 초로 바꾸는 예제에서 (가) minutes만 4로 바꾸면 출력과 주석은 어떻게 되나요? (나) (가)에 이어 곱하는 수만 100으로 바꾸고 주석은 그대로 두었더니 400이 출력되었습니다. 이 출력은 주석이 맞다는 뜻인가요?
해설 보기
(가) 출력은 240이 되며, 저장 형식이 초 단위라 분을 초로 바꾼다는 주석은 여전히 맞습니다. (나) 400이 출력된다는 것은 프로그램이 실행되었다는 뜻일 뿐, 주석이 맞다는 뜻은 아닙니다. 저장 형식이 여전히 초 단위라면 100을 곱하는 코드가 틀린 것이므로 60으로 되돌려야 하고, 저장 형식이 바뀌었다면 주석을 고쳐야 합니다. 컴파일러는 주석을 검사하지 않으므로 이런 불일치는 사람이 찾아야 합니다.
2. 어떤 주석이 될까
다음 주석은 각각 일반 주석인가요, 문서화 주석인가요? 문서화 주석이라면 무엇을 설명하나요? (가) /// 두 수를 더한다. (나) //// 구분선 (다) //! 이 크레이트는 단위 변환을 제공한다.
해설 보기
(가)는 바깥쪽 문서화 주석으로, 바로 뒤에 오는 함수 같은 항목을 설명합니다. (나)는 슬래시가 네 개이므로 일반 주석이며 문서에 나타나지 않습니다. (다)는 안쪽 문서화 주석으로, 이 주석을 담고 있는 크레이트를 설명하므로 보통 src/lib.rs의 맨 앞에 둡니다.
다음 글 제어 흐름문에서는 조건에 따라 코드를 실행하는 if와, 코드를 되풀이하는 loop, while, for를 살펴봅니다.