이 서평은 한빛미디어 <나는 리뷰어다> 활동을 위해서 책을 제공받아 작성된 서평입니다.
러스트는 현재 나와있는 프로그래밍 언어들 사이에서 특별한 위치를 차지하고 있다.
컴파일 방식 언어가 지닌 속도와 가비지 컬렉션이 없는 언어의 효율성을 갖추었으며, 함수형 언어의 타입 안정성과 독자적인 메모리 안정성을 위한 솔루션까지 제공한다.
러스트 타입 시스템이 제공하는 강력한 일관성은 주목할 만하다. 프로그램이 컴파일을 무사히 통과했다면 정상 작동할 확률이 매우 높은데, 이는 하스켈과 같은 고급 학술용 언어에서나 볼 수 있었던 특징이었다.
하지만 이런 안정성에는 대가가 따른다. 러스트는 문서화가 상당히 잘 되어 있지만 진입 장벽이 높다고 악명이 자자하다. 처음 접하는 사람이라면 러스트의 대여 검사기$_{borrow \space checker}$가 쏟아내는 불평에 대처하고, 데이터 구조를 다시 설계하고, 수명$_{lifetime}$이란 개념을 이해하느라 고전하게 될 것이다.
이 책은 C++등 기존 컴파일 언어 경험자조차 어려워하는 부분들을 최대한 명확하게 설명하려 노력했다. 이펙티브 시리즈의 전통을 이어받아, 엄격한 규칙보다는 유연한 가이드라인을 제시한다. 각 근거를 자세하게 제공함으로써 주어진 상황에서 규칙을 벗어나는 것이 바람직한지를 독자 스스로 판단할 수 있게 한다.
이 책도 다른 시리즈처럼 조언마다 근거를 함께 제공하지만 러스트는 위험 요소가 현저히 적기 때문에 러스트 고유의 개념을 설명하는데 더 집중했다. 그래서 ‘~에 익숙해져라’, ‘~를 이해하라’와 같으며 러스트를 유창하고 관용적으로 작성하도록 도와주기 위한 내용을 담았다.
구성은 위 사진과 같이 되어 있다. 예제 코드는 아래 주소로 가서 확인할 수 있다. 코드 외에도 원서로 작성된 책 원문을 볼 수 있다.
Effective Rust - Effective Rust
타입
첫 번째 장에서는 러스트의 타입 시스템에 대해 조언한다. 러스트의 타입 시스템은 다른 주류 언어들을 뛰어넘는 표현력을 지니고 있고, 오캐멀$_{OCaml}$이나 하스켈$_{Haskell}$과 같은 학술 연구용 언어들의 특성을 상당 부분 계승했다.
특히 러스트의 핵심 요소인 enum 타입은 탁월한 표현력을 자랑하고, 대수적 데이터 타입$_{algebraic \space data \space type; \space ADT}$까지 지원한다는 점에서 차별화된다. 이 장은 러스트의 기본 타입을 소개하고 조합해 프로그램의 의도를 정확히 구현하는 데이터 구조의 설계 방법을 상세히 다룬다.
프로그램 동작을 타입 시스템에 인코딩하면 프로그램 오류를 런타임이 아닌 컴파일 타임에 걸러낼 수 있어서 오류를 찾는데 드는 수고를 줄일 수 있다. 러스트의 표준 라이브러리에서 제공하는 데이터 구조 중에서도 특히 자주 사용되는 Option, Result, Error, Iterator에 대해서도 소개한다.
이런 표준 도구를 익혀 두면, 코드를 간결하고 효율적이면서 러스트답게 관용적$_{idiomatic \space Rust}$으로 작성하는데 도움이 된다. 더불어 물음표 연산자를 사용하면 타입 안전성을 유지하면서도 간결한 오류 처리가 가능함을 보여준다.
데이터 구조를 타입 시스템으로 표현하라
C++, 고$_{GO}$, 자바$_{Java}$와 같은 정적 프로그래밍 언어에 익숙하다면 러스트의 타입 시스템에 대한 기본은 쉽게 이해할 수 있다. 러스트도 다양한 크기의 부호 있는 정수 타입과 부호 없는 정수 타입을 제공한다. 여기에 더해 타깃 시스템의 포인터 크기에 맞춘 부호 있는 정수 타입$_{isize}$과 부호 없는 정수 타입$_{usize}$도 제공하지만 러스트에서는 포인터 타입과 정수 타입을 서로 변환할 일이 많지 않아서 큰 의미는 없다.
정수 타입만 봐도 러스트가 C++보다 훨씬 엄격하다는 것을 알 수 있다. 가령 러스트에서는 큰 정수 타입$_{i32}$을 작은 정수 타입$_{i16}$에 넣으려고 하면 컴파일 오류가 발생한다.
let x: i32 = 42;
let y: i16 = x;
error[E0308]: mismatched types
--> src/main.rs:2:17
|
2 | let y: i16 = x;
| --- ^ expected `i16`, found `i32`
| |
| expected due to this
|
= note: expected type `i16`
found type `i32`
help: you can convert an `i32` to an `i16` by calling `try_into()`
|
2 | let y: i16 = x.try_into()?;
| +++++++++++
참 믿음직하다. 위 예시처럼 실제로는 안전한 값 변환조차도 컴파일러는 잠재적 위험을 방지하기 위해 철저히 차단한다. 러스트가 프로그래머의 실수 가능성을 원천적으로 차단하려는 것을 보여준다.
트레이트
러스트 타입 시스템의 또 다른 핵심은 트레이트$_{trait}$를 이용해 서로 다른 타입 사이의 공통 동작을 인코딩하는 것이다.
러스트의 트레이트는 다른 언어의 인터페이스와 유사한 개념이지만, 제네릭과 연계되어 런타임 오버헤드 없이 재사용한다는 점에서 차별화된다.
이 장에서는 컴파일러를 비롯한 러스트 툴체인에서 제공하는 여러 가지 표준 트레이트를 살펴보고, 원하는 동작을 트레이트로 인코딩해서 사용하는 방법에 대해 조언한다.
표준 트레이트를 잘 익혀둬라
러스트가 제공하는 표준 트레이트들은 C++과 유사하다. 복사 생성자$_{copy-constructor}$, 소멸자$_{destructor}$, 동등 연산자$_{equality \space operator}$와 대입 연산자$_{assignment \space operator}$ 등과 개념적으로 맞닿아 있어서 친숙하게 느낄 수 있다.
사용자 정의 타입을 구현할 때 이런 기본 트레이트를 활용하면 좋다. 특정 연산이 없을 때 러스트 컴파일러가 해당 트레이트를 파악해 구체적인 오류 메시지를 출력할 수 있다.
제공하는 트레이트의 수가 너무 많아서 구현하기가 부담스러울 수 있다. 하지만 자주 사용하는 트레이트라면 대부분 derive 매크로 지정만으로도 자동으로 구현된다.
주요 개념
지금까지 두 장에 걸쳐 살펴본 러스트의 타입과 트레이트는 러스트 코드를 작성하기 위한 기본 어휘를 습득하는 과정이었다. 이제 러스트를 특별하게 만드는 핵심 개념들로 나아갈 차례다.
첫 두 단원에 걸쳐 살펴본 러스트 타입과 트레이트는 러스트 코드를 작성하기 위한 기본 어휘를 공부하는 과정이었다. 이제 러스트의 핵심적인 개념을 알아보자.
대여 검사와 수명 검사는 러스트를 독특하게 만드는 요소지만, 러스트를 처음 배울 때 넘어야 할 큰 장벽이기도 하다. 이 중요성을 고려해 14장과 15장에서 두 개념을 집중적으로 살펴본다. 나머지 장(16~20)에서는 개념 자체는 이해하기 쉽지만 다른 언어로 코드를 작성할 때와는 달라지는 개념을 설명한다.
주요 개념에 맞게 코드를 작성하는 것이 좋다. C/C++의 패턴들을 러스트로 그대로 옮기는 것이 가능할 수는 있지만, 그럴 거면 굳이 러스트를 쓸 필요가 없을 것이다. 러스트다운 방식으로 코드를 작성할 때 비로소 러스트가 가진 가치를 경험할 수 있을 것이다.
unsafe 코드 작성을 자제하라
런타임 오버헤드 없이 메모리 안전성을 보장한다는 점은 다른 주류 언어에는 없는 러스트 언어만의 장점이다. 하지만 이런 강력한 이점은 상응하는 비용을 동반한다.
대여 검사기의 엄격한 규칙을 만족시키고, 적절한 레퍼런스 타입을 지정하기 위해서 코드를 상당 부분 재구성해야 하는 경우가 많아진다.
안전하지 않은 러스트$_{unsafe \space Rust}$란 메모리 안전성 제약을 완화한 러스트의 상위 집합이다. unsafe 키워드로 시작하는 코드 블록 안에서는 일반 러스트에서는 불가능한 작업들이 허용된다.
특히 C 스타일의 원시 포인터를 사용할 수 있다. 이러한 원시 포인터는 대여 규칙이 적용되지 않아서 포인터를 역참조할 때마다 해당 포인터가 가리키는 메모리의 유효성은 전적으로 개발자의 책임이 된다.
단순히 C 코드 스타일을 구현하기 위해 러스트를 선택하는 것은 현명하지 않다. 하지만 러스트에서 unsafe 코드가 꼭 필요한 경우가 있다. 저수준 라이브러리 코드를 작성하거나 다른 언어로 작성된 코드와 연동하는 경우와 같이 불가피한 상황에서만 제한적으로 사용해야 한다.
의존성
코드 재사용은 수십 년 동안 뜬구름 같은 개념이었다. 작성된 코드를 라이브러리로 패키징해서 다양한 애플리케이션에서 활용한다는 개념은 몇몇 표준 라이브러리나 사내용 도구에서나 가능한 꿈같은 이야기었다.
하지만 인터넷의 성장과 오픈소스 소프트웨어의 등장으로 마침내 실현됐다. 지금은 거의 모든 언어가 광범위한 오픈소스 라이브러리를 제공하고, 새로운 의존성을 쉽고 빠르게 추가할 수 있도록 패키지 저장소 형태로 운영하고 있다.
하지만 쉽고 빠르고 편리하다는 장점과 함께 새로운 문제가 발생한다. 여전히 기존 코드를 재사용하는 것이 처음부터 새로 작성하는 것보다 쉽지만, 다른 누군가가 작성한 코드에 의존하면 예기치 않은 위험과 함정이 뒤따를 수 있다. 이 장에서는 러스트에서 cargo 도구를 활용하는 방법 위주로 설명하고 언급된 주의 사항과 이슈는 다른 도구 뿐만 아니라 다른 언어에도 적용할 수 있도록 제공한다.
총평
이 책은 다른 언어에서 사용되는 개념들이 러스트에서 어떻게 구현되고 다뤄져야 하는지 코드와 그림으로 친절하고 상세하게 설명한다. 이펙티브 시리즈답게 한 번 읽고 끝낼 수 없다. 모르는 것이 있을 때 다시 돌아와서 발췌독하며 해답을 얻는다면 더 할 나위 없이 좋을 것이다.
이 책의 진정한 가치는 어느 정도 러스트를 경험해본 개발자들에게 있을 것이다. 단순히 러스트 문법 습득을 넘어, 어떻게 하면 최대한 러스트답게 안전하고 효율적인 코드를 작성할 수 있을지 고민하는 개발자에게 도약을 위한 서적으로 사용하면 좋을 것 같다. 입문자를 넘어 중급자에 도달하고 싶은 독자가 보기에 훌륭한 책이다.
'Life > 독서 기록' 카테고리의 다른 글
[서평] 올인원 개발 키트 - 헬로 Bun (8) | 2024.10.27 |
---|---|
[서평] 클라우드 입문서 - 비전공자를 위한 AWS (1) | 2024.10.27 |
[서평] 중요한 내용만 빠르게 - 컴퓨터 구조와 운영체제 핵심 노트 (2) | 2024.09.29 |
[서평] CS 익힘책 - 이것이 취업을 위한 컴퓨터 과학이다 (11) | 2024.09.28 |
[서평] 모던 자바 기능으로 전문가 되기 - 기본기가 탄탄한 자바 개발자 (9) | 2024.09.04 |
컴퓨터 전공 관련, 프론트엔드 개발 지식들을 공유합니다. React, Javascript를 다룰 줄 알며 요즘에는 Typescript에도 관심이 생겨 공부하고 있습니다. 서로 소통하면서 프로젝트 하는 것을 즐기며 많은 대외활동으로 개발 능력과 소프트 스킬을 다듬어나가고 있습니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!