[RUST] 러스트 프로그래밍 공식 가이드(제2판) 7장 요약
커져가는 프로젝트를 패키지, 크레이트, 모듈로 관리하기
러스트에는 코드 조직화에 필요한 기능이 여럿 있습니다. 어떤 세부 정보를 외부에 노출할지, 비공개로 둘지, 프로그램의 스코프 내 어떤 이름이 있는지 등 다양합니다. 이를 통틀어 모듈 시스템(module system)이라 하며, 다음 기능들이 포함됩니다.
- 패키지 : 크레이트를 빌드하고, 테스트하고, 공유하는데 사용하는 카고 기능
- 크레이트 : 라이브러리나 실행 가능한 모듈로 구성된 트리 구조
- 모듈 및 use : 구조, 스코프를 제어하고, 조직 세부 경로를 감추는 데 사용
- 경로 : 구조체, 함수, 모듈 등의 이름을 지정
패키지와 크레이트
- 크레이트 : 러스트가 한 번의 컴파일 시에 고려하는 가장 작은 코드 단위
- 바이너리 크레이트(binary crate) : 커맨드 라인 프로그램이나 서버 처럼 실행 가능한 실행 파일로 컴파일할 수 있는 프로그램
main
함수를 포함하고 있어야 함
- 라이브러리 크레이트(library crate) : 여러 프로젝트에서 공용될 의도로 만들어진 기능들이 정의되어 있음
main
함수를 포함하고 있지 않음- 실행 파일 형태로 컴파일되지 않음
- 바이너리 크레이트(binary crate) : 커맨드 라인 프로그램이나 서버 처럼 실행 가능한 실행 파일로 컴파일할 수 있는 프로그램
- 크레이트 루트 : 러스트 컴파일러가 컴파일을 시작하는 소스파일이고, 크레이트 모듈을 구성함
- 패키지 : 일련의 기능을 제공하는 하나 이상의 크레이트로 구성된 번들
- 크레이트들을 빌드하는 법이 설명된
Cargo.toml
파일이 포함되어 있음 - 패키지에는 여러 개의 바이너리 크레이트가 원하는 만큼 포함될 수 있음
- 라이브러리 크레이트는 하나만 넣을 수 있음
- 적어도 하나 이상의 크레이트가 포함되어야 하며, 이는 라이브러리든 바이너리든 상관 없음
- 크레이트들을 빌드하는 법이 설명된
모듈, 경로, use, pub 키워드가 컴파일러에서 작동하는 방법
- 크레이트 루트부터 시작
- 크레이트를 컴파일 할 때 컴파일러는 먼저 크레이트 루트 파일을 봄
- 라이브러리 크레이트의 경우
src/lib.rs
- 바이너리 크레이트의 경우
src/main.rs
- 라이브러리 크레이트의 경우
- 크레이트를 컴파일 할 때 컴파일러는 먼저 크레이트 루트 파일을 봄
- 모듈 선언
- 크레이트 루트 파일에는 새로운 모듈을 선언할 수 있음
mod garden;
이라는 코드로 garden 모듈을 선언할 수 있으며, 컴파일러는 아래의 장소에서 이 모듈의 코드가 있는지 살펴봄mod garden
뒤에 세미콜론 대신 중괄호를 써서 안쪽에 코드를 적은 인라인src/garden.rs
파일 안src/garden/mod.rs
파일 안
- 서브 모듈 선언
- 크레이트 루트가 아닌 다른 파일에서는 서브모듈을 선언할 수 있음
- 예를들면
src/garden.rs
안에mod vegetables;
를 선언할 수 있음 - 컴파일러는 부모 모듈 이름의 디렉터리 안쪽에 위치한 아래의 장소들에서 이 서브모듈의 코드가 있는지 살펴 봄
mod vegetables
뒤에 세미콜론 대신 중괄호를 써서 안쪽에 코드를 적은 인라인src/garden/vegetables.rs
파일 안src/garden/vegetables/mod.rs
파일 안
- 모듈 내 코드로의 경로
- 일단 모듈이 크레이트의 일부로서 구성되면, 공개 규칙이 허용하는 한도 내에서라면 해당 코드의 경로를 사용하여 동일한 크레이트의 어디에서든 이 모듈의 코드를 참조할 수 있게 됨
- 예를들면,
garden vegetables
모듈 안에 있는Asparagus
타입은crate::garden::vegetables::Asparagus
로 쓸 수 있음
- 비공개 vs 공개
- 모듈 내의 코드는 기본적으로 부모 모듈에게 비공개(private)
- 모듈을 공개(public)로 만들려면,
mod
대신pub mod
를 사용하여 선언 - 공개 모듈의 아이템들을 공개로 하려면 마찬가지로 그 선언 앞에
pub
를 사용
use
키워드- 어떤 스코프 내에서 use 키워드는 긴 경로의 반복을 줄이기 위한 어떤 아이템으로의 단축경로를 만들어줌
create::garden::vegetables::Asparagus
를 참조할 수 있는 모든스코프에서use create::garden::vegetables::Asparagus;
로 단축 경로로 만들 수 있음- 단축경로 사용 이후 스코프에서 이 타입을 사용하려면
Asparagus
만 작성해주면 됨
- 단축경로 사용 이후 스코프에서 이 타입을 사용하려면
모듈을 정의하여 스코프 및 공개 여부 제어하기
- 모듈(module) : 크레이트의 코드를 읽기 쉽고 재사용하기도 쉽게끔 구조화를 할 수 있게 해줌
mod
키워드와 모듈이름을 지정하여 모듈을 정의- 모듈의 본문은 중괄호로 감싸져 있음
- 모듈 안에 다른 모듈 및 구조체, 열거형, 상수, 트레이트,함수 등의 아이템 정의를 넣을 수 있음
- 모듈 내의 코드는 기본적으로 비공개
- 모듈은 아이템의 공개 여부(privacy)를 제어하도록 해줌
- 비공개 아이템은 외부에서 사용이 허용되지 않음
- 모듈과 모듈 내 아이템을 선택적으로 공개할 수 있음
- 모듈 트리
- Rust의 모든 크레이트는 트리 구조로 모듈들이 연결됨
- 트리 최상단(root)은 크레이트 루트 파일(
main.rs
또는lib.rs
) - 모듈 트리 구조
- 전체 모듈 최상위에 crate 라는 모듈이 암묵적으로 위치
1 2 3 4 5
crate (암묵적으로 존재) |- network | |- client | |-request |- utils
crate::
는 현재 크레이트의 루트부터 시작하는 절대 경로를 나타냄
- 전체 모듈 최상위에 crate 라는 모듈이 암묵적으로 위치
경로를 사용하여 모듈 트리의 아이템 참조하기
러스트 모듈 트리에서 아이템을 찾는 방법은 파일 시스템에서 경로를 사용하는 방법과 동일
- 절대 경로(absolute path) : 크레이트 루트로부터 시작되는 전체 경로
- 외부 크레이트로부터의 코드에 대해서는 해당 크레이트 이름으로 절대 경로가 시작됨
- Cargo.toml
1 2
[dependencies] rand = "0.8"
- 코드
1 2 3 4 5 6
fn main() { // 절대 경로: rand crate 루트부터 시작 let mut rng = rand::thread_rng(); let n: u32 = rand::Rng::gen_range(&mut rng, 1..=10); println!("Random number: {}", n); }
- Cargo.toml
- 현재 크레이트로부터의 코드에 대해서는
crate
리터럴로부터 시작됨1 2 3 4 5 6 7 8 9 10
mod network { pub fn connect() { println!("Connected!"); } } fn main() { // 절대 경로: crate 루트부터 탐색 crate::network::connect(); }
- 외부 크레이트로부터의 코드에 대해서는 해당 크레이트 이름으로 절대 경로가 시작됨
- 상대 경로(relative path) : 현재 모듈을 시작점으로 하여
self
,super
혹은 현재 모듈 내의 식별자를 사용- 현재 모듈 기준
1 2 3 4 5 6 7 8 9 10
mod network { pub fn connect() { println!("Connected!"); } pub fn start() { // self:: 는 현재 모듈을 의미 self::connect(); } }
- 상위 모듈 기준
1 2 3 4 5 6 7 8 9 10 11 12
mod network { pub fn connect() { println!("Connected!"); } pub mod client { pub fn request() { // super:: 는 network 모듈을 의미 super::connect(); } } }
- 현재 모듈 기준
- 모든 아이템은 기본적으로
private
- 함수, 메서드, 구조체, 열거형, 모듈, 상수 등은
pub
을 붙이지 않으면 부모 모듈 기준으로만 접근 가능 - 부모 모듈 밖에서는 접근할 수 없음
- 부모 모듈 안에 있는 아이템은 자식 모듈 내 비공개 아이템을 사용할 수 없음
- 자식 모듈 내 아이템은 조상 모듈 내 아이템을 사용할 수 있음
- 자식 모듈의 세부 구현은 감싸져서 숨겨져 있지만, 자식 모듈 내에서는 자신이 정의된 콘텍스트를 볼 수 있음
- 함수, 메서드, 구조체, 열거형, 모듈, 상수 등은
pub 키워드로 경로 노출하기
pub
- 상위 모듈이 해당 모듈을 가리킬 수 있도록 해줌
- 내부에 접근할 수 있는 것은 아님
- 즉, 모듈의 아이템을 공개하려면 모듈과 모듈이 가지고 있는 아이템 모두 공개(
pub
)되어야 함
super로 시작하는 상대 경로
super
- 현재 모듈 혹은 크레이트 루트 대신 자기 부모 모듈로부터 시작되는 상대경로를 만들 수 있음
- 부모 모듈에 위치하고 있음을 알고 있는 아이템을 참조하도록 해줌
구조체, 열거형을 공개하기
- 구조체
- 구조체 정의에
pub
을 사용하면 구조체는 공개되지만, 구조체의 필드는 비공개로 유지- 각 필드마다 공개 여부 지정
- 구조체 정의에
- 열거형
- 열거형의 정의에
pub
을 사용하면 열거형은 공개되며, 모든 배리언트가 공개 - 열거형은 그 배리언트가 공개되지 않으면 쓸모가 없음
- 따라서, 열거형 배리언트는 기본적으로 공개
- 열거형의 정의에
use 키워드로 경로를 스코프 안으로 가져오기
함수 호출을 위해서 경로를 작성하는 것은 불편하고 반복적인 느낌을 줄 수 있습니다. use
키워드를 한번 사용하면 어떤 경로의 단축경로(shortcut)를 만들 수 있고, 그러면 스코프 안쪽 어디서라도 짧은 이름을 사용할 수 있습니다.
- 스코프에
use
키워드와 경로를 작성하는 건 파일시스템의 심벌릭 링크를 생성하는 것과 유사 use
키워드로 가져온 경우 비공개 규칙이 적용use
로 불러온다 해도 원래 아이템이 비공개라면 그대로 접근이 불가능
use
가 사용된 특정한 스코프에서만 단축경로가 만들어짐
보편적인 use 경로 작성법
use
경로에 부모 모듈을 특정하면 함수 호출 시 전체 경로를 반복하는 것을 최소화하면서 함수가 로컬에 정의되어 있지 않음을 명백히 보여줄 수 있음 (관용적인 방법)- 여러 모듈에서 같은 이름의 함수가 있을 때, 충돌을 피할 수 있음
- 호출 함수의 모듈 경로가 명시되면 코드 가독성이 향상됨
- 직접 함수를 가져와 호출하면 해당 함수가 어디에 정의되어 있는지가 불분명함
- 구조체나 열거형 등의 타 아이템을 가져올 시에는 보편적으로 전체 경로를 작성
- 이름이 같은 두개의 타입을 동일한 스코프에 가져오려면 부모 모듈을 반드시 명시해야 함 (관용적 인 방법)
- 직접 아이템을 가져와 사용하면 중복된 이름에 대해 어떠한 모듈의 아이템을 사용할 지 알 수 없음
as 키워드로 새로운 이름 제공하기
- use 키워드로 동일한 이름의 타입을 스코프로 여러 개 가져올 경우 경로 뒤에
as
키워드를 작성하고, 새로운 이름이나 타입 별칭을 작성하여 충돌을 방지할 수 있음 (관용적 인 방법)
1
2
3
4
5
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {}
fn function2() -> IoResult {}
pub use로 다시 내보내기
1
2
3
4
5
6
7
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub use
는 우리 코드를 호출하는 코드가 해당 스코프에 정의된 것처럼 해당 이름을 참조할 수 있음- 아이템을 스코프로 가져오는 동시에 다른 곳에서 아이템을 가져갈 수 있도록 만들기 때문에, 다시 내보내기(re-exporting) 라고 함
restaurant::front_of_house::hosting::add_to_waitlist()
를restaurant::hosting::add_to_waitlist()
와 같이 사용할 수 있게 됨- 둘 다 사용이 가능한 경로
외부 패키지 사용하기
Cargo.toml
에 디펜던시를 추가하면 카고가crates.io
에서rand
패키지를 비롯한 모든 디펜던시를 다운로드하고 프로젝트에서 해당 패키지를 사용할 수 있게 됨- use 키워드와 크레이트 이름을 쓰고 가져올 아이템을 나열하여 사용
- 예시 :
rand::thread_rng
- 예시 :
- use 키워드와 크레이트 이름을 쓰고 가져올 아이템을 나열하여 사용
std
표준 라이브러리- “내가 작성 중인 크레이트가 아닌 다른 크레이트”로 외부 크레이트로 분류됨
- 개발중인 패키지로 가져오려면
use
문을 사용 (use std::...
) Cargo.toml
에 추가할 필요 없음- Rust컴파일러(rustc)에 기본 포함된 크레이트이기 때문
중첩 경로를 사용하여 대량의 use 나열을 정리하기
동일한 크레이트나, 동일한 모듈 내에 정의된 아이템을 여러개 사용할 경우, 아이템당 한 줄씩 코드를 나열하면 수직 방향으로 너무 많은 영역을 차지합니다. 이를 해결하기 위해 중첩 경로를 사용하여 동일한 아이템을 한줄로 가져올 수 있는 방법을 설명합니다.
- 경로의 공통된 부분을 작성하고 콜론 두개를 붙인 다음, 중괄호 내에 경로가 다른 부분을 나열
- 경로의 아무 단계에서 사용할 수 있으며, 하위 경로가 동일한 use 구문이 많을 때 빛을 발함
- 사용 예시
- 중첩 경로를 사용해, 경로 앞부분이 같은 여러 아이템을 스코프로 가져오기
use std::{comp::Ordering, io};
- 하위 경로가 같은 두 use 구문 (std::io, std::io::Write)
use std::io::{self, Write};
- 중첩 경로를 사용해, 경로 앞부분이 같은 여러 아이템을 스코프로 가져오기
글롭 연산자
- 경로에 글롭(glob) 연산자
*
를 붙이면 경로 안에 정의된 모든 공개 아이템을 가져올 수 있음use std::collections::*;
와 같이 사용- 글롭 연산자는 코드에 사용된 어떤 이름이 어느 곳에 정의되어 있는지 파악하기 어렵게 만들 수 있으므로 사용에 주의
- 테스트할 모든 아이템을
tests
모듈로 가져오는 용도로 자주 사용됨
별개의 파일로 모듈 분리하기
- 모듈 선언
mod 모듈명;
- 크레이트 루트(
main.rs
또는lib.rs
)에서 모듈을 사용할 때 작성 - 컴파일러에게 “이 모듈이 존재한다(코드를 불러올 팔일이 있다)”를 알려주는 역할
- 이 선언만으로는 실제 구현 내용은 포함되지 않음
- 크레이트 루트(
- 모듈 정의
- 모듈의 실제 코드 구현 파일
mod 모듈명;
선언 후, 컴파일러는src/모듈명.rs
파일에서 구현 내용을 찾음
- 모듈 계층
- 하위 모듈은 디렉터리 구조로 확장 가능
- 예:
crate::front_of_house::hosting
->src/front_of_house/hosting.rs
- 예:
- 모듈 분리는 가독성과 관리성을 높이지만, 컴파일 단위는 여전히 하나의 크레이트
- 하위 모듈은 디렉터리 구조로 확장 가능
This post is licensed under CC BY 4.0 by the author.