[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.