[rust] 7. 패키지 크레이트 모듈

About_work·2024년 6월 29일
0

rust

목록 보기
9/16
  • workspace
    • 여러개의 패키지들로 구성
  • 어떤 세부 정보를 외부에 노출할지, 비공개로 둘지, 프로그램의 스코프 내 어떤 이름이 있는지 등 다양한 기능이 있음. 이를 통틀어 모듈 시스템이라 하며, 다음 기능들이 포함됩니다:
    • 패키지:
      • 크레이트들을 빌드하고, 테스트하고, 공유하는 데 사용하는 카고 기능
    • 크레이트:
      • 컴파일 한 차례에 고려하는 가장 작은 코드 단위
      • 여러 모듈을 품고 있음
      • 라이브러리나 실행 가능한 모듈로 구성된 트리 구조
    • 모듈:
      • 크레이트의 코드를 -> 읽기 쉽고 재사용하기도 쉽게끔 구조화를 할 수 있게 해 줌
      • 구조, 스코프를 제어하고, 조직 세부 경로를 감추는 데 사용
      • namespace 관리를 위한 기본 단위
      • 공개/비공개 조절 가능

  • 한 패키지에는 여러 개의 바이너리 크레이트와 (원할 경우) 하나의 라이브러리 크레이트가 포함될 수 있으므로,
  • 커진 프로젝트의 각 부분을 크레이트로 나눠서 외부 라이브러리처럼 쓸 수 있습니다.
  • TODO: 상호연관된 패키지들로 이루어진 대규모 프로젝트의 경우에는 14장 ‘카고 작업공간’ 절에서 다룰 예정인, 카고에서 제공하는 작업공간 (workspace) 기능을 이용합니다.

  • 또한 세부 구현을 캡슐화하면 더 고수준에서 코드를 재사용할 수 있는 방법에 대해서도 설명
    • 일단 어떤 연산을 구현하면, (그 구현체의 작동 방식을 몰라도) 다른 코드에서 공개 인터페이스를 통해 해당 코드를 호출할 수 있음
    • 코드를 작성하는 방식에 따라 아래 구현 세부 사항이 정의
      • 다른 코드가 사용할 수 있는 공개 부분
      • 변경 권한을 작성자에게 남겨두는 비공개 부분
  • 이는 머릿속에 기억해 둬야 하는 세부 사항의 양을 제한하는 또 다른 방법

1. 패키지와 크레이트

  • 크레이트 (crate) 는 러스트가 컴파일 한 차례에 고려하는 가장 작은 코드 단위
  • 컴파일러는 그 파일이 크레이트라고 생각
  • 크레이트는 여러 모듈을 담을 수 있고, 다음 절에서 곧 알게 되겠지만
    • 모듈은 이 크레이트와 함께 컴파일되는 다른 파일들에 정의되어 있을 수도 있습니다.
  • 크레이트는 바이너리일 수도 있고, 라이브러리일 수도 있습니다.
    • 바이너리 크레이트 (binary crate):
      • 커맨드 라인 프로그램이나 서버처럼
      • 실행 가능한 실행파일로 컴파일할 수 있는 프로그램
      • 실행파일이 실행되면 -> 무슨 일이 일어나는지를 정의한 main 함수를 포함하고 있어야 함

  • 라이브러리 크레이트 (library crate)
    • main 함수를 가지고 있지 않고 실행파일 형태로 컴파일되지 않습니다.
    • 그 대신, 여러 프로젝트에서 공용될 의도로 만들어진 기능들이 정의되어 있음
    • 예를 들어, 2장에서 사용한 rand 크레이트는 난수를 생성하는 기능을 제공
    • (엄격한 정의로썬 틀리나, 보편적으로) crate는 library crate를 말하는 경우가 많으며, library로 더 줄여서 말하기도 한다.

  • 크레이트 루트 (crate root)
    • 러스트 컴파일러가 컴파일을 시작하는 소스 파일
    • 크레이트의 루트 모듈을 구성

  • 패키지 (package)
    • 크레이트들을 빌드하고, 테스트하고, 공유하는 데 사용하는 카고 기능
    • 일련의 기능을 제공하는 여러 크레이트로 구성된 번들
    • 패키지에는 이 크레이트들을 빌드하는 법이 설명된 Cargo.toml 파일이 포함되어 있음
  • Cargo는 package이다.
    • 실제로 코드를 빌드하는 데 사용하는 커맨드 라인 도구의 바이너리 크레이트가 포함된 패키지
    • 카고 패키지에는 또한 이 바이너리 크레이트가 의존하고 있는 라이브러리 패키지도 포함되어 있음
    • 패키지에는 여러 개의 바이너리 크레이트가 원하는 만큼 포함될 수 있지만, 라이브러리 크레이트는 하나만 넣을 수 있습니다.
  • 프로젝트 디렉터리에는 Cargo.toml 파일이 있는데, 이것이 패키지를 만들어 줍니다.
  • 카고는
    • 패키지명과 같은 이름의 바이너리 크레이트는 -> src/main.rs가 크레이트 루트라는 관례를 준수
    • 마찬가지로, 패키지 디렉터리에 src/lib.rs 파일이 존재할 경우, -> 카고는 해당 패키지가 패키지명과 같은 이름의 라이브러리 크레이트를 포함하고 있다고 판단
      • 그리고 그 라이브러리 크레이트의 크레이트 루트는 src/lib.rs
    • 카고는 라이브러리 혹은 바이너리를 빌드할 때, 이 크레이트 루트 파일을 rustc에게 전달
  • src/bin 디렉터리 내에 파일을 배치하면(main.rs 말고도) 각각의 파일이 바이너리 크레이트가 되어,
    • 여러 바이너리 크레이트를 패키지에 포함할 수 있습니다.
  • 현재 패키지는 src/main.rs 만 포함하고 있으므로 이 패키지는 my-project라는 이름의 바이너리 크레이트만 존재합니다.

2. 모듈을 정의하여 스코프 및 공개 여부 제어하기

  • 경로 (path)
    • 모듈, 아이템의 이름을 지정
  • use
    • 스코프에 경로를 가져옴
  • pub키워드
    • 아이템을 공개하는 데 사용하는 pub 키워드

  • 크레이트
    • 컴파일 한 차례에 고려하는 가장 작은 코드 단위
    • 여러 모듈을 품고 있음
    • 라이브러리나 실행 가능한 모듈로 구성된 트리 구조
  • 크레이트 루트 (crate root)
    • 러스트 컴파일러가 컴파일을 시작하는 소스 파일
    • 크레이트의 루트 모듈을 구성
    • src/lib.rs / src/main.rs: 맞음
    • src/garden.rs 아님
    • 이 두 파일이 그런 이름을 갖게 된 이유는
      • 모듈 트리 (module tree) 라고 불리는 크레이트 모듈 구조에서
        • 최상위에 crate라는 이름을 갖는 일종의 모듈로 형성되기 때문입니다.
  • module
    • 구조, 스코프를 제어하고, 조직 세부 경로를 감추는 데 사용
    • namespace 관리를 위한 기본 단위
    • 공개/비공개 조절 가능

2.1. 모듈 치트 시트

  • 크레이트 루트부터 시작:
    • 크레이트를 컴파일할 때, 컴파일러는 먼저 크레이트 루트 파일을 봅니다.
      • 보통은 라이브러리 크레이트의 경우 src/lib.rs
      • 바이너리 크레이트의 경우 src/main.rs
  • 모듈 선언:
    • 크레이트 루트 파일에는 새로운 모듈을 선언할 수 있습니다;
    • mod garden;이라는 코드로 ‘garden’ 모듈을 선언할 수 있음
    • 컴파일러는 아래의 장소에서 이 모듈의 코드가 있는지 살펴볼 것입니다:
      • mod garden 뒤에 세미콜론 대신 중괄호를 써서 안쪽에 코드를 적은 인라인
      • src/garden.rs 파일 안
      • src/garden/mod.rs 파일 안
  • 서브모듈 선언:
    • 크레이트 루트(src/lib.rs / src/main.rs)가 아닌 다른 파일(src/garden.rs)에서는 서브모듈 (submodule) 을 선언할 수 있습니다.
    • 예를 들면 src/garden.rs 안에 mod vegetables;를 선언할 수도 있습니다.
    • 컴파일러는 부모 모듈 이름(garden)의 디렉터리 안쪽에 위치한 아래의 장소들에서 이 서브모듈의 코드가 있는지 살펴볼 것입니다:
      • 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 키워드는 긴 경로의 반복을 줄이기 위한 어떤 아이템으로의 단축경로를 만들어 줍니다.
    • crate::garden::vegetables::Asparagus를 참조할 수 있는 모든 스코프에서 use crate::garden::vegetables::Asparagus;로 단축경로를 만들 수 있으며,
    • 그 이후부터는 스코프에서 이 타입을 사용하려면 Asparagus만 작성해주면 됩니다.

backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden
    │   └── vegetables.rs
    ├── garden.rs
    └── main.rs
  • 크레이트 루트 파일은 src/main.rs
  • src/main.rs
use crate::garden::vegetables::Asparagus;

pub mod garden; // 모듈선언 -> src/garden.rs 를 봄. vegetables 모듈이 정의되어 있을 것임

fn main() {
    let plant = Asparagus {};
    println!("I'm growing {:?}!", plant);
}
  • src/garden.rs
pub mod vegetables; // pub 모듈 선언 -> src/garden/vegetables.rs 를 봄
  • src/garden/vegetables.rs
#[derive(Debug)]
pub struct Asparagus {}

2.2. 모듈로 관련된 코드 묶기


  • 크레이트 루트 (crate root)
    • 러스트 컴파일러가 컴파일을 시작하는 소스 파일
    • 크레이트의 루트 모듈을 구성
    • src/lib.rs / src/main.rs: 맞음
    • src/garden.rs 아님
    • 이 두 파일이 그런 이름을 갖게 된 이유는
      • 모듈 트리 (module tree) 라고 불리는 크레이트 모듈 구조에서
        • 최상위에 crate라는 이름을 갖는 일종의 모듈로 형성되기 때문입니다.
  • 크레이트
    • 컴파일 한 차례에 고려하는 가장 작은 코드 단위
    • 여러 모듈을 품고 있음
    • 라이브러리나 실행 가능한 모듈로 구성된 트리 구조
  • module
    • 크레이트의 코드를 -> 읽기 쉽고 재사용하기도 쉽게끔 구조화를 할 수 있게 해 줌
    • 구조, 스코프를 제어하고, 조직 세부 경로를 감추는 데 사용
    • namespace 관리를 위한 기본 단위
    • 공개/비공개 조절 가능

  • 모듈 내의 코드는 기본적으로 비공개이므로, 모듈은 아이템의 공개 여부 (privacy) 를 제어하도록 해주기도 합니다.
  • 비공개 아이템은 외부에서의 사용이 허용되지 않는 내부의 세부 구현입니다.
  • 모듈과 모듈 내 아이템을 선택적으로 공개할 수 있는데,
    • 이렇게 하여 외부의 코드가 모듈 및 아이템을 의존하고 사용할 수 있도록 노출해 줌

  • 중첩 (nested) 모듈 안에 함수를 집어넣어 구성하면 크레이트 구조를 실제 레스토랑이 일하는 방식과 동일하게 구성할 수 있습니다.
  • src/lib.rs
    • src/front_of_house.rs 가 있어야하고
    • src/front_of_house/hosting.rs 와 src/front_of_house/serving.rs 가 있어야 함.
mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}

        fn seat_at_table() {}
    }

    mod serving {
        fn take_order() {}

        fn serve_order() {}

        fn take_payment() {}
    }
}
  • 모듈의 본문은 중괄호로 감싸져 있습니다.
  • hosting, serving 모듈처럼, 모듈 내에는 다른 모듈을 넣을 수 있습니다.
  • 모듈에는 구조체, 열거형, 상수, 트레이트, 함수(예제 7-1처럼) 등의 아이템 정의 또한 가질 수 있습니다.

  • 모듈을 사용함으로써 관련된 정의들을 하나로 묶고 어떤 연관성이 있는지 이름을 지어줄 수 있습니다.
  • 모듈화된 코드를 사용하는 프로그래머가 자신에게 필요한 어떠한 정의를 찾을 때,
    • 모든 정의를 읽어 내릴 필요 없이 그룹 기반으로 탐색할 수 있으므로 훨씬 쉽게 찾아낼 수 있죠.
  • 코드에 새로운 기능을 추가하려는 프로그래머도, 자신이 어디에 코드를 작성해야 프로그램 구조가 그대로 유지되는지 파악할 수 있음

  • 모듈 트리 (module tree) = 크레이트 모듈 구조
crate
 └── front_of_house
     ├── hosting
     │   ├── add_to_waitlist
     │   └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment
  • 이 트리는 또한 어떤 모듈이 서로 형제 (sibling) 관계에 있는지 나타내기도 하는데, 이는 동일한 모듈 내에 정의되어 있음을 말합니다;
    • hosting과 serving은 front_of_house 모듈 내에 정의된 형제
  • 모듈 A가 모듈 B 안에 있으면, 모듈 A는 모듈 B의 자식이며, 모듈 B는 모듈 A의 부모라고 말합니다.
  • 디렉터리에서 파일을 찾는 것처럼, 우리는 크레이트 모듈구조에서, 모듈을 찾아낼 방법이 필요하죠.

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

  • 절대 경로 (absolute path)
    • 크레이트 루트로부터 시작되는 전체 경로입니다;
    • 외부 크레이트로부터의 코드에 대해서는
      • 해당 크레이트 이름으로 절대 경로가 시작되고
    • 현재의 크레이트로부터의 코드에 대해서는 crate 리터럴로부터 시작됩니다.
  • 상대 경로 (relative path)
    • 현재의 모듈을 시작점으로 하여
    • self, super 혹은 현재 모듈 내의 식별자를 사용
crate
 └── front_of_house
     ├── hosting
     │   ├── add_to_waitlist
     │   └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment
  • src/lib.rs (crate 모듈을 생성하는 소스파일 / front_of_house모듈이 정의)
mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // 절대 경로
    crate::front_of_house::hosting::add_to_waitlist();

    // 상대 경로
    front_of_house::hosting::add_to_waitlist();
}
  • 위 코드, 절대 경로와 상대 경로를 지정하는 방법을 잘 보여준다.

  • 상대 경로, 절대 경로 중 무엇을 사용할지는 아래에 따라 여러분이 결정해야 할 사항입니다.
      1. 프로젝트에 따라,
      1. 아래 2가지 중 선택 (아이템을 정의하는 코드아이템을 사용하는 코드를)
      • 분리하고 싶은지, -> 절대 경로
      • 혹은 같이 두고 싶은지에 -> 상대 경로
  • 일반적으로 선호하는 경로는 절대 경로입니다.
    • 아이템을 정의하는 코드와 호출하는 코드는 분리되어 있을 가능성이 높기 때문입니다.

  • 하지만, hosting 모듈이 pub 선언되어 있지 않아, 빌드되지 않는다.
  • hosting 모듈과 add_to_waitlist 함수의 경로를 정확히 명시했지만, 해당 영역은 비공개 영역이기 때문에 러스트가 접근을 허용하지 않습니다.
  • 러스트에서는 모든 아이템이 기본적으로 부모 모듈에 대해 비공개입니다.
  • 함수나 구조체 같은 아이템을 비공개로 하고 싶다면 모듈에 넣으면 됩니다.

  • 부모 모듈 내 아이템은 자식 모듈 내 비공개 아이템을 사용할 수 없지만,
  • 자식 모듈 내 아이템은 부모 모듈 내 아이템을 사용할 수 있습니다.
    • 레스토랑 고객들은 내부에서 진행되는 일을 알 수 없지만, 사무실 관리자는 자신이 운영하는 레스토랑의 모든 것을 보고, 행동할 수 있습니다.
  • 러스트에서는 pub 키워드를 사용하여 자식 모듈의 내부 구성 요소를 공개 (public) 함으로써 -> 외부의 상위 모듈로 노출할 방법을 제공

3.1. pub 키워드로 경로 노출하기

  • 모듈은 단순한 컨테이너이기 때문에 모듈을 공개하는 것 만으로 할 수 있는 것은 별로 없으며, 여기에 더해서 모듈이 가지고 있는 아이템도 마찬가지로 공개해야 합니다.
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // 절대 경로
    crate::front_of_house::hosting::add_to_waitlist();

    // 상대 경로
    front_of_house::hosting::add_to_waitlist();
}
  • front_of_house 모듈은 공개가 아니지만, eat_at_restaurant 함수와 front_of_house 모듈은 같은 모듈 내에 정의되어 있으므로 (즉, 서로 형제 관계이므로) eat_at_restaurant 함수에서 front_of_house 모듈을 참조할 수 있습니다.

3.2. 바이너리와 라이브러리가 함꼐 있는 패키지를 위한 최고의 예제

  • 통상적으로 이렇게 라이브러리와 바이너리 크레이트 모두를 가지는 패턴의 패키지들은,
    • 라이브러리 크레이트에 있는 코드를 호출하여 실행파일을 시작하기 위한 양만큼의 코드가 바이너리 크레이트에 담긴 형태가 됩니다.
  • 모듈 트리는 src/lib.rs 내에 정의되어야 합니다.
    • 그러면 바이너리 크레이트 내에서는 패키지 이름으로 시작하는 경로를 사용함으로써 모든 공개 아이템을 사용할 수 있습니다.
  • 바이너리 크레이트는 (완전히 외부에 있는 다른 크레이트가 이 라이브러리 크레이트를 사용하는 식과 동일하게) 이 라이브러리 크레이트의 사용자가 됩니다: 즉 공개 API만 사용할 수 있습니다.

3.3. super로 시작하는 상대 경로

  • super로 시작하면, (현재 모듈 혹은 크레이트 루트 대신) 자기 부모 모듈부터 시작되는 상대 경로를 만들 수 있습니다.
    • 이는 파일시스템 경로에서 ..로 시작하는 것과 동일합니다.
  • 예제 7-8은 셰프가 잘못된 주문을 수정하여 고객에게 직접 전달하는 상황을 묘사한 코드
  • back_of_house 모듈에 정의된 fix_incorrect_order 함수는
    • super로 시작하는 deliver_order로의 경로를 특정하는 것으로 부모 모듈에 정의된 deliver_order 함수를 호출합니다:
  • src/lib.rs
fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order();
    }

    fn cook_order() {}
}
  • back_of_house 모듈과 deliver_order 함수는 크레이트 모듈 구조 변경 시 서로의 관계를 유지한 채 함께 이동될 가능성이 높습니다.
  • 그러므로 super를 사용하면, 차후에 다른 모듈에 이동시키더라도 수정해야 할 코드를 줄일 수 있습니다.

3.4. 구조체/열거형을 공개하기

  • 구조체(struct) 정의에 pub를 쓰면 구조체는 공개되지만, 구조체의 필드는 비공개로 유지됩니다.
  • 공개 여부는 각 필드마다 정할 수 있습니다.
  • src/lib.rs
mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}

pub fn eat_at_restaurant() {
    // 호밀 (Rye) 토스트를 곁들인 여름철 조식 주문하기
    let mut meal = back_of_house::Breakfast::summer("Rye");
    // 먹고 싶은 빵 바꾸기
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);

    // 다음 라인의 주석을 해제하면 컴파일되지 않습니다; 식사와 함께
    // 제공되는 계절 과일은 조회나 수정이 허용되지 않습니다
    // meal.seasonal_fruit = String::from("blueberries");
}
  • 또한, back_of_house::Breakfast 구조체는 비공개 필드를 갖고 있기 때문에, Breakfast 인스턴스를 생성할 공개 연관 함수(예제에서는 summer 함수입니다)를 반드시 제공해야 합니다.
  • 만약 Breakfast 구조체에 그런 함수가 존재하지 않을 경우, eat_at_restaurant 함수에서 Breakfast 인스턴스를 생성할 수 없습니다.

  • 반대로, 열거형은 공개로 지정할 경우 모든 배리언트가 공개됩니다.
  • 열거형은 그 배리언트가 공개되지 않는다면 큰 쓸모가 없습니다;
    • 열거형의 모든 배리언트에 대해 전부 pub을 붙이는 것은 귀찮은 일이 될 것이므로, 열거형의 배리언트는 기본적으로 공개
  • 구조체의 경우 필드를 공개로 하지 않는 것이 종종 유용하므로, 구조체 필드는 pub을 명시하지 않는 한 기본적으로 모든 것이 비공개라는 일반적인 규칙을 따릅니다.

4. use 키워드로 경로를 스코프 안으로 가져오기

  • 함수 호출을 위해서 경로를 작성하는 것은 불편하고 반복적인 느낌을 줄 수 있음
  • use 키워드를 한번 사용하여 어떤 경로의 단축경로 (shortcut) 를 만들 수 있고, 그러면 스코프 안쪽 어디서라도 짧은 이름을 사용할 수 있음
  • src/lib.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}
  • use가 사용된 특정한 스코프에서만 단축경로가 만들어진다는 점을 주의하세요.
    • 아래 예제에서는 eat_at_restaurant 함수를 새로운 자식 모듈 customer로 옮겼는데, 이러면 use 구문과 다른 스코프가 되므로, 이 함수는 컴파일 되지 않습니다:
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

mod customer {
    pub fn eat_at_restaurant() {
        hosting::add_to_waitlist();
    }
}

4.1. 보편적인 use 경로 작성법

  • src/lib.rs
  • 예제 1
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

mod customer {
    pub fn eat_at_restaurant() {
        hosting::add_to_waitlist();
    }
}
  • 예제 2
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting::add_to_waitlist;

pub fn eat_at_restaurant() {
    add_to_waitlist();
}
  • 함수 가져올 때: 부모 모듈을 특정해라!
    • 예제 1과 2의 동작은 동일하지만, 예제 1 코드가 use 키워드로 스코프에 함수를 가져올 때의 관용적인 코드
    • 함수의 부모 모듈을 use 키워드로 가져오면 함수를 호출할 때 부모 모듈을 특정해야 합니다.
    • 함수 호출 시 부모 모듈을 특정하면 전체 경로를 반복하는 것을 최소화하면서도 함수가 로컬에 정의되어 있지 않음을 명백히 보여주게 됩니다.
    • 예제2의 코드는 add_to_waitlist가 어디에 정의되어 있는지 불분명합니다.
  • 구조체/열거형 가져올 떄: 아이템만 특정하는것이 더 보편적?
    • 한편, use 키워드로 구조체나 열거형 등의 타 아이템을 가져올 시에는 전체 경로를 작성하는 것이 보편적입니다.
    • 아래 코드는 HashMap 표준 라이브러리 구조체를 바이너리 크레이트의 스코프로 가져오는 관용적인 코드 예시입니다.
  • src/main.rs
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}
  • 이러한 관용이 탄생하게 된 명확한 이유는 없습니다. 어쩌다 보니 관습이 생겼고, 사람들이 이 방식대로 러스트 코드를 읽고 쓰는 데에 익숙해졌을 뿐입니다.
  • 하지만, 동일한 이름의 아이템을 여럿 가져오는 경우는 이 방식을 사용하지 않습니다. 러스트가 허용하지 않기 때문이죠.
  • 아래 예시도 보편적 예시
  • src/lib.rs
use std::fmt;
use std::io;

fn function1() -> fmt::Result {
    // --생략--
}

fn function2() -> io::Result<()> {
    // --생략--
}

4.2. as 키워드로 새로운 이름 제공하기

  • use 키워드로 동일한 이름의 타입을 스코프로 여러 개 가져올 경우의 또 다른 해결 방법이 있습니다.
    -또 다른 보편적 예시 2
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --생략--
}

fn function2() -> IoResult<()> {
    // --생략--
}

4.3. pub use로 다시 내보내기

  • use 키워드로 이름을 가져올 경우, 해당 이름은 새 위치의 스코프에서 비공개가 됩니다.
  • pub과 use를 결합하면 우리 코드를 호출하는 코드가 해당 스코프에 정의된 것처럼 해당 이름을 참조할 수 있습니다.
  • 이 기법은 아이템을 스코프로 가져오는 동시에 다른 곳에서 아이템을 가져갈 수 있도록 만들기 때문에, 다시 내보내기 (re-exporting) 라고 합니다.
  • src/lib.rs (다른 스코프의 코드에서 사용할 수 있도록 pub use 사용)
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}
  • 중요!!!
  • 위와 같이 변경하기 전이라면
    • 외부 코드에서는 add_to_waitlist 함수를 호출하기 위해
    • restaurant::front_of_house::hosting::add_to_waitlist()라는 경로를 사용해야 할 것입니다.
  • 위의 pub use가 루트 모듈로부터 hosting 모듈을 다시 내보냈으므로, 이제 외부 코드는 restaurant::hosting::add_to_waitlist() 경로를 대신 사용할 수 있습니다.

  • 중요!!!
  • 다시 내보내기 기법은 작성한 코드의 구조 내부와, 그 코드를 사용할 프로그래머들이 예상할법한 해당 분야의 구조가 서로 다를 때 유용
    • 레스토랑 비유 예제를 예로 들어보죠. 레스토랑을 운영하는 직원들의 머릿속에서는 ‘접객 부서’와 ‘지원 부서’가 나뉘어 있습니다.
    • 하지만 레스토랑을 방문하는 고객들은 레스토랑의 부서를 그런 용어로 나누어 생각하지 않겠죠.
  • pub use를 사용하면 코드를 작성할 때의 구조와, 노출할 때의 구조를 다르게 만들 수 있습니다.
  • 라이브러리를 제작하는 프로그래머와, 라이브러리를 사용하는 프로그래머 모두를 위한 라이브러리를 구성하는 데 큰 도움이 되죠.
  • TODO: pub use에 대한 또 다른 예제, 그리고 이것이 여러분의 크레이트 문서에 어떤 영향을 주는지에 대해서는 14장의 ‘pub use를 사용하여 편리한 공개 API 내보내기’절에서 살펴보겠습니다

4.4. 외부 패키지 사용하기

  • rand 패키지를 프로젝트에서 사용하기 위해서 Cargo.toml에 다음 줄을 추가했었죠:
    • 러스트 언어에 포함되어 있지 않은 패키지
rand = "0.8.5"
  • Cargo.toml에 rand를 의존성으로 추가하면
    • 카고가 crates.io에서 rand 패키지를 비롯한 모든 의존성을 다운로드하고
    • 프로젝트에서 rand 패키지를 사용할 수 있게 됩니다.
use rand::Rng;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1..=100);
}
  • 알아 두어야 할 것이 있다면 std 표준 라이브러리도 마찬가지로 외부 크레이트
  • 러스트 언어에 포함되어 있기 때문에 Cargo.toml 에 추가할 필요는 없지만,
    • 표준 라이브러리에서 우리가 만든 패키지의 스코프로 가져오려면 use 문을 작성해야 합니다.
use std::collections::HashMap;

4.5. 중첩 경로를 사용하여 대량의 use 나열을 정리하기

  • src/main.rs
// --생략--
use std::cmp::Ordering;
use std::io;
// --생략--
use std::io;
use std::io::Write;
  • src/main.rs
// --생략--
use std::{cmp::Ordering, io};
// --생략--
use std::io::{self, Write};

4.6. glob 연산자

  • 경로에 글롭 (glob) 연산자 *를 붙이면 경로 안에 정의된 모든 공개 아이템을 가져올 수 있습니다.
use std::collections::*;
  • 하지만 글롭 연산자는 아래 용도 말고는 쓰지 말자.
    • 글롭 연산자는 테스트할 모든 아이템을 tests 모듈로 가져오는 용도로 자주 사용됩니다. (11장 ‘테스트 작성 방법’에서 다룰 예정입니다.)
    • TODO: 또한 프렐루드 패턴의 일부로 사용되기도 하며, 자세한 내용은 표준 라이브러리 문서를 참고 바랍니다.
      • 프렐루드: 자주 사용하는 라이브러리의 아이템들을 포함하는 모듈

5. 별개의 파일로 모듈 분리하기

  • 큰 모듈이라면, 정의를 여러 파일로 나누어 코드를 쉽게 찾아갈 수 있도록 만들어야겠죠.
  • 크레이트 루트 파일에 모든 모듈이 정의되는 형태 대신 이 모듈들을 파일로 추출해 보겠습니다.
    • 이 경우 크레이트 루트 파일은 src/lib.rs지만, 이러한 절차는 크레이트 루트 파일이 src/main.rs인 바이너리 크레이트에서도 작동합니다.
  • src/lib.rs
mod front_of_house;

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}
  • src/front_of_house.rs
pub mod hosting {
    pub fn add_to_waitlist() {}
}
  • 모듈 트리에서 mod 선언을 사용하여 파일을 로드하는 것은 한 번만 하면 됩니다. 일단 그 파일이 프로젝트의 일부란 것을 컴파일러가 파악하면 프로젝트의 다른 파일들은 선언된 위치의 경로를 사용하여 로드된 파일의 코드를 참조해야 합니다.
  • 다음으로 hosting 모듈을 파일로 추출하겠습니다. hosting이 루트 모듈이 아니라 front_of_house의 자식 모듈이기 때문에 과정이 약간 다릅니다.
  • src/front_of_house/ 에 위치시키겠습니다.
  • src/fromt_of_house.rs
pub mod hosting;
  • src/fromt_of_house/hosting.rs
pub fn add_to_waitlist() {}

5.1. 대체파일경로

  • src/front_of_house.rs (우리가 지금 다룬 형태)
  • src/front_of_house/mod.rs (예전 스타일, 여전히 지원되는 경로)
  • mod 키워드는 모듈을 선언하고, 러스트는 모듈과 같은 이름의 파일에서 해당 모듈에 들어가는 코드를 찾습니다.
profile
새로운 것이 들어오면 이미 있는 것과 충돌을 시도하라.

0개의 댓글