소개
모듈 트리 내 항목 참조 경로에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서는 Rust 에서 모듈 트리 내 항목을 참조하기 위해 경로를 사용하며, 절대 경로 또는 상대 경로 형식을 가질 수 있음을 배웁니다.
모듈 트리 내 항목 참조 경로에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서는 Rust 에서 모듈 트리 내 항목을 참조하기 위해 경로를 사용하며, 절대 경로 또는 상대 경로 형식을 가질 수 있음을 배웁니다.
Rust 가 모듈 트리 내에서 항목을 찾을 위치를 표시하기 위해, 파일 시스템을 탐색할 때 경로를 사용하는 방식과 동일한 방식으로 경로를 사용합니다. 함수를 호출하려면 해당 경로를 알아야 합니다.
경로는 두 가지 형식을 가질 수 있습니다.
crate로 시작합니다.self, super 또는 현재 모듈의 식별자를 사용합니다.절대 경로와 상대 경로는 모두 이중 콜론 (::) 으로 구분된 하나 이상의 식별자를 따릅니다.
Listing 7-1 로 돌아가서, add_to_waitlist 함수를 호출하려는 경우를 가정해 봅시다. 이는 다음과 같은 질문과 같습니다: add_to_waitlist 함수의 경로는 무엇입니까? Listing 7-3 에는 Listing 7-1 에서 일부 모듈과 함수가 제거된 내용이 포함되어 있습니다.
크레이트 루트에서 정의된 새로운 함수 eat_at_restaurant에서 add_to_waitlist 함수를 호출하는 두 가지 방법을 보여드리겠습니다. 이 경로는 올바르지만, 이 예제가 현재 상태로 컴파일되지 않도록 하는 또 다른 문제가 남아 있습니다. 잠시 후에 그 이유를 설명하겠습니다.
eat_at_restaurant 함수는 라이브러리 크레이트의 공개 API 의 일부이므로 pub 키워드로 표시합니다. "pub 키워드로 경로 노출"에서 pub에 대해 자세히 알아보겠습니다.
파일 이름: src/lib.rs
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
Listing 7-3: 절대 및 상대 경로를 사용하여 add_to_waitlist 함수 호출
eat_at_restaurant에서 add_to_waitlist 함수를 처음 호출할 때 절대 경로를 사용합니다. add_to_waitlist 함수는 eat_at_restaurant과 동일한 크레이트에서 정의되므로 crate 키워드를 사용하여 절대 경로를 시작할 수 있습니다. 그런 다음 add_to_waitlist에 도달할 때까지 연속적인 각 모듈을 포함합니다. 동일한 구조의 파일 시스템을 상상할 수 있습니다: add_to_waitlist 프로그램을 실행하기 위해 /front_of_house/hosting/add_to_waitlist 경로를 지정합니다. crate 이름을 사용하여 크레이트 루트에서 시작하는 것은 셸에서 /를 사용하여 파일 시스템 루트에서 시작하는 것과 같습니다.
eat_at_restaurant에서 add_to_waitlist를 두 번째로 호출할 때 상대 경로를 사용합니다. 경로는 eat_at_restaurant과 동일한 모듈 트리에 정의된 모듈의 이름인 front_of_house로 시작합니다. 여기서 파일 시스템에 해당하는 것은 front_of_house/hosting/add_to_waitlist 경로를 사용하는 것입니다. 모듈 이름으로 시작하는 것은 경로가 상대적임을 의미합니다.
상대 경로 또는 절대 경로를 사용할지 여부를 선택하는 것은 프로젝트에 따라 결정되며, 항목 정의 코드를 항목을 사용하는 코드와 별도로 이동할지 또는 함께 이동할지에 따라 달라집니다. 예를 들어, front_of_house 모듈과 eat_at_restaurant 함수를 customer_experience라는 모듈로 이동하면 add_to_waitlist에 대한 절대 경로를 업데이트해야 하지만 상대 경로는 여전히 유효합니다. 그러나 eat_at_restaurant 함수를 별도로 dining이라는 모듈로 이동하면 add_to_waitlist 호출에 대한 절대 경로는 동일하게 유지되지만 상대 경로는 업데이트해야 합니다. 일반적으로 코드 정의와 항목 호출을 서로 독립적으로 이동하려는 경우가 많으므로 절대 경로를 지정하는 것을 선호합니다.
Listing 7-3 을 컴파일하고 아직 컴파일되지 않는 이유를 알아보겠습니다! 얻는 오류는 Listing 7-4 에 표시됩니다.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
error[E0603]: module `hosting` is private
--> src/lib.rs:12:21
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
Listing 7-4: Listing 7-3 의 코드를 빌드할 때의 컴파일러 오류
오류 메시지는 hosting 모듈이 private 이라고 말합니다. 즉, hosting 모듈과 add_to_waitlist 함수에 대한 올바른 경로가 있지만 Rust 는 private 섹션에 액세스할 수 없으므로 해당 경로를 사용할 수 없습니다. Rust 에서 모든 항목 (함수, 메서드, 구조체, 열거형, 모듈 및 상수) 은 기본적으로 상위 모듈에 대해 private 입니다. 함수 또는 구조체와 같은 항목을 private 으로 만들려면 모듈에 넣습니다.
상위 모듈의 항목은 하위 모듈 내부의 private 항목을 사용할 수 없지만 하위 모듈의 항목은 상위 모듈의 항목을 사용할 수 있습니다. 이는 하위 모듈이 구현 세부 정보를 래핑하고 숨기지만 하위 모듈은 정의된 컨텍스트를 볼 수 있기 때문입니다. 비유를 계속하자면, 개인 정보 보호 규칙을 레스토랑의 뒷방과 같다고 생각하십시오: 그곳에서 일어나는 일은 레스토랑 고객에게는 private 이지만, 사무실 관리자는 운영하는 레스토랑의 모든 것을 보고 할 수 있습니다.
Rust 는 내부 구현 세부 정보를 숨기는 것이 기본값이 되도록 모듈 시스템이 이러한 방식으로 작동하도록 선택했습니다. 그렇게 하면 외부 코드를 손상시키지 않고 내부 코드의 어떤 부분을 변경할 수 있는지 알 수 있습니다. 그러나 Rust 는 pub 키워드를 사용하여 항목을 public 으로 만들어 하위 모듈 코드의 내부 부분을 외부 상위 모듈에 노출하는 옵션을 제공합니다.
pub 키워드로 경로 노출Listing 7-4 에서 hosting 모듈이 private 이라고 알려준 오류로 돌아가 보겠습니다. 상위 모듈의 eat_at_restaurant 함수가 하위 모듈의 add_to_waitlist 함수에 접근할 수 있도록 하려면, Listing 7-5 에 표시된 것처럼 hosting 모듈을 pub 키워드로 표시합니다.
파일 이름: src/lib.rs
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
--snip--
Listing 7-5: eat_at_restaurant에서 사용하기 위해 hosting 모듈을 pub으로 선언
불행히도, Listing 7-5 의 코드는 Listing 7-6 에 표시된 것처럼 여전히 컴파일러 오류를 발생시킵니다.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:9:37
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:12:30
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
Listing 7-6: Listing 7-5 의 코드를 빌드할 때의 컴파일러 오류
무슨 일이 일어났을까요? mod hosting 앞에 pub 키워드를 추가하면 모듈이 public 이 됩니다. 이 변경으로 front_of_house에 접근할 수 있다면 hosting에도 접근할 수 있습니다. 그러나 hosting의 내용은 여전히 private 입니다. 모듈을 public 으로 만든다고 해서 그 내용이 public 이 되는 것은 아닙니다. 모듈의 pub 키워드는 상위 모듈의 코드만 해당 모듈을 참조할 수 있도록 허용하며, 내부 코드에 접근하는 것은 허용하지 않습니다. 모듈은 컨테이너이므로 모듈만 public 으로 만드는 것으로는 할 수 있는 일이 많지 않습니다. 더 나아가 모듈 내의 하나 이상의 항목을 public 으로 만들도록 선택해야 합니다.
Listing 7-6 의 오류는 add_to_waitlist 함수가 private 이라고 말합니다. 개인 정보 보호 규칙은 모듈뿐만 아니라 구조체, 열거형, 함수 및 메서드에도 적용됩니다.
Listing 7-7 과 같이 정의 앞에 pub 키워드를 추가하여 add_to_waitlist 함수도 public 으로 만들어 보겠습니다.
파일 이름: src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
--snip--
Listing 7-7: mod hosting 및 fn add_to_waitlist에 pub 키워드를 추가하면 eat_at_restaurant에서 함수를 호출할 수 있습니다.
이제 코드가 컴파일됩니다! pub 키워드를 추가하면 개인 정보 보호 규칙에 따라 add_to_waitlist에서 이러한 경로를 사용할 수 있는 이유를 확인하기 위해 절대 경로와 상대 경로를 살펴보겠습니다.
절대 경로에서 크레이트 모듈 트리의 루트인 crate로 시작합니다. front_of_house 모듈은 크레이트 루트에 정의되어 있습니다. front_of_house는 public 이 아니지만, eat_at_restaurant 함수가 front_of_house와 동일한 모듈에 정의되어 있으므로 (eat_at_restaurant와 front_of_house는 형제 관계임), eat_at_restaurant에서 front_of_house를 참조할 수 있습니다. 다음은 pub으로 표시된 hosting 모듈입니다. hosting의 상위 모듈에 접근할 수 있으므로 hosting에 접근할 수 있습니다. 마지막으로, add_to_waitlist 함수는 pub으로 표시되어 있으며 해당 상위 모듈에 접근할 수 있으므로 이 함수 호출이 작동합니다!
상대 경로에서 로직은 첫 번째 단계를 제외하고 절대 경로와 동일합니다. 크레이트 루트에서 시작하는 대신 경로가 front_of_house에서 시작합니다. front_of_house 모듈은 eat_at_restaurant과 동일한 모듈 내에 정의되어 있으므로 eat_at_restaurant이 정의된 모듈에서 시작하는 상대 경로가 작동합니다. 그런 다음 hosting과 add_to_waitlist가 pub으로 표시되어 있으므로 나머지 경로가 작동하고 이 함수 호출이 유효합니다!
다른 프로젝트에서 코드를 사용할 수 있도록 라이브러리 크레이트를 공유하려는 경우, public API 는 크레이트 사용자가 코드와 상호 작용할 수 있는 방식을 결정하는 계약입니다. 크레이트에 의존하는 사람들을 위해 public API 에 대한 변경 사항을 관리하는 데에는 많은 고려 사항이 있습니다. 이러한 고려 사항은 이 책의 범위를 벗어납니다. 이 주제에 관심이 있다면 *https://rust-lang.github.io/api-guidelines*에서 Rust API 가이드라인을 참조하십시오.
바이너리 및 라이브러리가 있는 패키지에 대한 모범 사례
패키지에는
src/main.rs바이너리 크레이트 루트와src/lib.rs라이브러리 크레이트 루트가 모두 포함될 수 있으며, 두 크레이트 모두 기본적으로 패키지 이름을 갖는다고 언급했습니다. 일반적으로 라이브러리와 바이너리 크레이트를 모두 포함하는 이 패턴의 패키지는 라이브러리 크레이트의 코드를 호출하는 실행 파일을 시작하기에 충분한 코드를 바이너리 크레이트에만 갖습니다. 이렇게 하면 다른 프로젝트에서 패키지가 제공하는 대부분의 기능을 활용할 수 있습니다. 라이브러리 크레이트의 코드를 공유할 수 있기 때문입니다.모듈 트리는
src/lib.rs에 정의되어야 합니다. 그런 다음, 모든 public 항목은 패키지 이름으로 경로를 시작하여 바이너리 크레이트에서 사용할 수 있습니다. 바이너리 크레이트는 완전히 외부 크레이트가 라이브러리 크레이트를 사용하는 것과 마찬가지로 라이브러리 크레이트의 사용자가 됩니다. 즉, public API 만 사용할 수 있습니다. 이렇게 하면 좋은 API 를 설계하는 데 도움이 됩니다. 작성자일 뿐만 아니라 클라이언트이기도 합니다!12 장에서 바이너리 크레이트와 라이브러리 크레이트를 모두 포함하는 명령줄 프로그램을 사용하여 이 조직적 관행을 시연합니다.
super로 상대 경로 시작하기경로의 시작 부분에 super를 사용하여 현재 모듈 또는 크레이트 루트가 아닌 상위 모듈에서 시작하는 상대 경로를 구성할 수 있습니다. 이는 파일 시스템 경로를 .. 구문으로 시작하는 것과 같습니다. super를 사용하면 상위 모듈에 있는 항목을 참조할 수 있으므로, 모듈이 상위 모듈과 밀접하게 관련되어 있지만 상위 모듈이 언젠가 모듈 트리의 다른 곳으로 이동할 수 있는 경우 모듈 트리를 더 쉽게 재정렬할 수 있습니다.
Listing 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() {}
}
Listing 7-8: super로 시작하는 상대 경로를 사용하여 함수 호출
fix_incorrect_order 함수는 back_of_house 모듈에 있으므로 super를 사용하여 back_of_house의 상위 모듈로 이동할 수 있습니다. 이 경우 루트인 crate입니다. 거기에서 deliver_order를 찾고 찾습니다. 성공! back_of_house 모듈과 deliver_order 함수는 서로 동일한 관계를 유지하고 크레이트의 모듈 트리를 재구성하기로 결정하면 함께 이동할 가능성이 높다고 생각합니다. 따라서 이 코드가 다른 모듈로 이동하는 경우 향후 코드 업데이트할 위치를 줄이기 위해 super를 사용했습니다.
pub을 사용하여 구조체와 열거형을 public 으로 지정할 수도 있지만, 구조체 및 열거형과 함께 pub을 사용하는 데 몇 가지 추가 세부 사항이 있습니다. 구조체 정의 앞에 pub을 사용하면 구조체를 public 으로 만들지만 구조체의 필드는 여전히 private 으로 유지됩니다. 각 필드를 개별적으로 public 으로 만들거나 그렇지 않을 수 있습니다. Listing 7-9 에서 public toast 필드가 있지만 private seasonal_fruit 필드가 있는 public back_of_house::Breakfast 구조체를 정의했습니다. 이는 고객이 식사와 함께 제공되는 빵의 종류를 선택할 수 있지만 셰프가 계절과 재고에 따라 식사에 어떤 과일을 곁들일지 결정하는 레스토랑의 경우를 모델링합니다. 사용 가능한 과일은 빠르게 변경되므로 고객은 과일을 선택하거나 어떤 과일을 받게 될지 볼 수도 없습니다.
파일 이름: 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() {
// Order a breakfast in the summer with Rye toast
let mut meal = back_of_house::Breakfast::summer("Rye");
// Change our mind about what bread we'd like
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// The next line won't compile if we uncomment it; we're not
// allowed to see or modify the seasonal fruit that comes
// with the meal
// meal.seasonal_fruit = String::from("blueberries");
}
Listing 7-9: 일부 public 필드와 일부 private 필드가 있는 구조체
back_of_house::Breakfast 구조체의 toast 필드가 public 이므로 eat_at_restaurant에서 점 표기법을 사용하여 toast 필드에 쓰고 읽을 수 있습니다. seasonal_fruit이 private 이므로 eat_at_restaurant에서 seasonal_fruit 필드를 사용할 수 없다는 점에 유의하세요. seasonal_fruit 필드 값을 수정하는 줄의 주석 처리를 해제하여 어떤 오류가 발생하는지 확인해 보세요!
또한 back_of_house::Breakfast에 private 필드가 있으므로 구조체는 Breakfast의 인스턴스를 생성하는 public 연관 함수를 제공해야 합니다 (여기서는 summer라고 명명했습니다). Breakfast에 그러한 함수가 없으면 eat_at_restaurant에서 private seasonal_fruit 필드의 값을 설정할 수 없으므로 eat_at_restaurant에서 Breakfast의 인스턴스를 만들 수 없습니다.
반대로, 열거형을 public 으로 만들면 모든 변형이 public 이 됩니다. Listing 7-10 과 같이 enum 키워드 앞에 pub만 있으면 됩니다.
파일 이름: src/lib.rs
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
Listing 7-10: 열거형을 public 으로 지정하면 모든 변형이 public 이 됩니다.
Appetizer 열거형을 public 으로 만들었으므로 eat_at_restaurant에서 Soup 및 Salad 변형을 사용할 수 있습니다.
열거형 변형이 public 이 아니면 열거형은 그다지 유용하지 않습니다. 모든 경우에 모든 열거형 변형에 pub을 주석 처리해야 하는 것은 번거로울 것이므로 열거형 변형의 기본값은 public 입니다. 구조체는 필드가 public 이 아니어도 유용한 경우가 많으므로 구조체 필드는 pub으로 주석 처리되지 않는 한 기본적으로 모든 것이 private 이라는 일반적인 규칙을 따릅니다.
아직 다루지 않은 pub과 관련된 상황이 하나 더 있으며, 이는 마지막 모듈 시스템 기능인 use 키워드입니다. 먼저 use 자체를 다루고, 그 다음 pub과 use를 결합하는 방법을 보여드리겠습니다.
축하합니다! 모듈 트리에서 항목을 참조하기 위한 경로 (Paths for Referring to an Item in the Module Tree) 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.