Post

[RUST] 러스트 프로그래밍 공식 가이드(제2판) 7장 요약

커져가는 프로젝트를 패키지, 크레이트, 모듈로 관리하기

러스트에는 코드 조직화에 필요한 기능이 여럿 있습니다. 어떤 세부 정보를 외부에 노출할지, 비공개로 둘지, 프로그램의 스코프 내 어떤 이름이 있는지 등 다양합니다. 이를 통틀어 모듈 시스템(module system)이라 하며, 다음 기능들이 포함됩니다.

  • 패키지 : 크레이트를 빌드하고, 테스트하고, 공유하는데 사용하는 카고 기능
  • 크레이트 : 라이브러리나 실행 가능한 모듈로 구성된 트리 구조
  • 모듈 및 use : 구조, 스코프를 제어하고, 조직 세부 경로를 감추는 데 사용
  • 경로 : 구조체, 함수, 모듈 등의 이름을 지정

패키지와 크레이트

  • 크레이트 : 러스트가 한 번의 컴파일 시에 고려하는 가장 작은 코드 단위
    • 바이너리 크레이트(binary crate) : 커맨드 라인 프로그램이나 서버 처럼 실행 가능한 실행 파일로 컴파일할 수 있는 프로그램
      • main 함수를 포함하고 있어야 함
    • 라이브러리 크레이트(library crate) : 여러 프로젝트에서 공용될 의도로 만들어진 기능들이 정의되어 있음
      • main 함수를 포함하고 있지 않음
      • 실행 파일 형태로 컴파일되지 않음
  • 크레이트 루트 : 러스트 컴파일러가 컴파일을 시작하는 소스파일이고, 크레이트 모듈을 구성함
  • 패키지 : 일련의 기능을 제공하는 하나 이상의 크레이트로 구성된 번들
    • 크레이트들을 빌드하는 법이 설명된 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:: 는 현재 크레이트의 루트부터 시작하는 절대 경로를 나타냄

경로를 사용하여 모듈 트리의 아이템 참조하기

러스트 모듈 트리에서 아이템을 찾는 방법은 파일 시스템에서 경로를 사용하는 방법과 동일

  • 절대 경로(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);
          }
          
    • 현재 크레이트로부터의 코드에 대해서는 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
  • 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.