콘텐츠로 이동

Rust 에러 E0603: function, struct or module is private (비공개 항목)

E0603은 알고 보면 좋은 소식이에요. Rust가 내가 원한 함수, 구조체, 모듈을 찾기는 했어요. 다만 그 항목이 비공개(private)라서 건네줄 수가 없는 거예요. Rust 모듈 안의 모든 것은 따로 말하지 않는 한 비공개라서, 코드를 처음 모듈로 나눌 때 거의 모든 사람이 이 에러를 만나요. 해결책은 보통 pub 한 단어예요. 이 페이지는 Rust 컴파일러 에러 해설의 일부예요.

error[E0603]: function `apply_discount` is private
--> src/main.rs:8:26
|
8 | let total = billing::apply_discount(200); // ❌ 비공개
| ^^^^^^^^^^^^^^ private function
|
note: the function `apply_discount` is defined here
--> src/main.rs:2:5
|
2 | fn apply_discount(price: u32) -> u32 { // `pub`이 없어요
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

“함수 apply_discount는 비공개”라는 뜻이에요. note는 “함수 apply_discount는 여기에 정의되어 있다”며 정의된 자리를 보여 줘요.

이 에러를 내는 프로그램은 다음과 같아요.

mod billing {
fn apply_discount(price: u32) -> u32 { // `pub`이 없어요
price * 90 / 100
}
}
fn main() {
let total = billing::apply_discount(200); // ❌ 비공개
println!("합계: {total}");
}

첫 줄에 항목의 종류가 나오기 때문에, 같은 에러가 몇 가지 모양으로 나타나요.

첫 줄에 나오는 문구 보통 무엇이 잘못됐나요 해결
function `apply_discount` is private fn에 pub이 없음 방법 1 또는 방법 2
struct `Order` is private struct에 pub이 없음 방법 1
module `prices` is private 경로에 있는 mod에 pub이 없음 방법 1 또는 방법 3
function import `price_of` is private 다른 모듈 안의 일반 use를 거쳐 항목에 접근함 방법 3
tuple struct constructor `Percent` is private 구조체는 pub이지만 필드가 pub이 아님 방법 4

두 화살표를 읽어 보세요. 첫 번째(8번째 줄)는 항목을 쓰는 곳이에요. note:의 화살표(2번째 줄)는 항목이 정의된 곳이에요. Rust는 이렇게 말하고 있어요. “저기 apply_discount가 보이긴 하는데, 공유한다고 표시한 적이 없으니 billing 밖의 코드는 호출할 수 없어요.”

파일은 상관없어요. billing이 src/billing.rs에 있어도 같은 에러가 나고, note:가 대신 src/billing.rs:1:1을 가리킬 뿐이에요.

사무실 건물을 떠올려 보세요. 각 모듈은 문이 달린 방이에요. 비공개 항목은 방 안에만 있는 도구예요. 방 안에 있는 사람들(그리고 그 안의 더 작은 방에 있는 사람들)은 쓸 수 있지만, 복도에 있는 사람은 아무도 못 써요. pub은 그 도구를 복도 선반에 올려 두는 거예요.

Rust는 모든 것을 기본적으로 비공개로 만들어요. 함수, 구조체, 구조체 필드, 모듈, 심지어 use로 가져온 이름까지요. 공유하고 싶을 때 pub으로 직접 선택해요. 모듈 강좌에서도 처음부터 이 내용을 다뤄요.

이유는 미래의 나를 지키기 위해서예요. pub인 것은 하나의 약속이 돼요. 다른 코드가 거기에 기댈 수 있으니, 바꾸면 그 코드가 망가질 수 있어요. 비공개 항목은 마음대로 바꾸고, 이름을 고치고, 지워도 돼요. 밖에서 아무도 그것에 기대지 않는다는 걸 컴파일러가 보장해 주거든요. 작은 도우미 함수는 도우미로 남고, 구조체는 아무도 필드를 직접 건드릴 수 없으니 자기 규칙(예를 들어 “잔액은 절대 음수가 아니다”)을 지킬 수 있어요.

하나 더 있어요. 공개 여부는 경로의 모든 단계에서 확인해요. shop::prices::price_of는 prices가 나에게 보이고 그리고 price_of도 나에게 보일 때만 동작해요. 비공개 모듈 안에 있는 pub fn은 여전히 손이 닿지 않아요.

방법 1: 항목에 pub 붙이기(경로의 모든 모듈에도)

섹션 제목: “방법 1: 항목에 pub 붙이기(경로의 모든 모듈에도)”

항목을 모듈 밖에서 쓰도록 만든 거라면 이 방법을 고르세요. 대부분은 이게 정답이에요.

mod billing {
pub fn apply_discount(price: u32) -> u32 {
price * 90 / 100
}
}
fn main() {
let total = billing::apply_discount(200);
println!("합계: {total}");
}

실행 결과:

합계: 180
▶ 직접 실행해 보기

에러가 module `prices` is private(모듈 prices는 비공개)라고 말한다면, 빠진 pub은 모듈에 있어요. pub mod prices { ... }라고 쓰세요. 이 경우 rustc는 function `price_of` is not publicly re-exported(함수 price_of가 공개적으로 다시 내보내지지 않음)라는 두 번째 라벨로도 알려 줘요. 파일을 따로 나눴다면 pub은 선언 줄에 붙여요. pub mod prices;처럼요.

구조체라면 pub struct Order는 타입을 공개할 뿐이에요. 밖의 코드가 필드를 읽거나 바꿔야 한다면 필드마다 따로 pub이 필요해요. 이건 방법 4에서 더 다룰게요.

방법 2: “내 코드에서만” 쓰려면 pub(crate)나 pub(super) 쓰기

섹션 제목: “방법 2: “내 코드에서만” 쓰려면 pub(crate)나 pub(super) 쓰기”

항목을 공유해야 하지만 온 세상과 공유할 필요는 없을 때 이 방법을 고르세요. pub에는 몇 가지 단계가 있어요. 좁은 것부터 넓은 것 순서예요.

이렇게 쓰면 항목을 쓸 수 있는 곳
아무것도 안 씀(비공개) 자기 모듈과 그 안의 모듈들
pub(super) 여기에 부모 모듈까지
pub(crate) 이 크레이트의 모든 코드
pub 모두. 내 크레이트에 의존하는 다른 크레이트까지

프로그램(바이너리)에서는 pub(crate)와 pub이 거의 똑같이 동작해요. 차이는 라이브러리에서 드러나요. pub(crate)를 쓰면 도우미 함수를 라이브러리의 공개 API로 만들지 않고도 내 모듈끼리 공유할 수 있어요.

mod shop {
// 이 크레이트의 어떤 코드든 호출할 수 있어요.
pub(crate) fn store_name() -> &'static str {
"Ferris Fruits"
}
pub mod receipt {
// 이 도우미는 부모 모듈인 `shop`만 호출할 수 있어요.
pub(super) fn line(item: &str, price: u32) -> String {
format!("{item}: {price}")
}
}
pub fn print_receipt(items: &[(&str, u32)]) {
println!("== {} ==", store_name());
for (item, price) in items {
println!("{}", receipt::line(item, *price));
}
}
}
fn main() {
println!("{}에 오신 걸 환영해요", shop::store_name());
shop::print_receipt(&[("apple", 3), ("pear", 5)]);
// shop::receipt::line("apple", 3); // ❌ E0603: `line`은 `shop` 전용이에요
}

실행 결과:

Ferris Fruits에 오신 걸 환영해요
== Ferris Fruits ==
apple: 3
pear: 5
▶ 직접 실행해 보기

마지막 줄 앞의 //를 지우면, pub(super)라고 썼는데도 function `line` is private가 나요. main은 receipt의 부모가 아니라서 문이 닫혀 있는 거예요.

안쪽 모듈은 내부 사정이지만, 그 안의 항목 하나는 쉽게 쓸 수 있어야 할 때 이 방법을 고르세요. 모듈은 비공개로 두고, 항목만 더 짧은 공개 경로에 올려 두는 거예요.

mod shop {
mod prices {
// 비공개로 둬요. 가격을 어떻게 저장하는지는 shop이 알아서 할 일이에요
pub fn price_of(item: &str) -> u32 {
if item == "apple" { 3 } else { 5 }
}
}
pub use prices::price_of; // 이제 shop::price_of로 쓸 수 있어요
}
fn main() {
println!("사과 가격: {}", shop::price_of("apple"));
println!("배 가격: {}", shop::price_of("pear"));
}

실행 결과:

사과 가격: 3
배 가격: 5
▶ 직접 실행해 보기

function import `price_of` is private(가져온 함수 price_of는 비공개)도 이 방법으로 고쳐요. shop 안의 일반 use prices::price_of;는 shop만을 위한 지름길이에요. 다른 모든 것처럼 use도 비공개거든요. pub use는 그 지름길을 공개된 문으로 바꿔 줘요. 큰 크레이트들은 늘 이렇게 해요. 그래서 serde::Serialize가 어느 안쪽 모듈에 있는지 몰라도 그냥 쓸 수 있는 거예요.

방법 4: 필드를 공개하는 대신 pub fn new 추가하기

섹션 제목: “방법 4: 필드를 공개하는 대신 pub fn new 추가하기”

에러가 구조체의 필드나 생성자에 관한 것일 때 이 방법을 고르세요. 여기서는 구조체는 pub이지만, 필드는 아니에요.

mod billing {
pub struct Percent(u8);
}
fn main() {
let discount = billing::Percent(150); // ❌ 비공개 생성자
}
error[E0603]: tuple struct constructor `Percent` is private
--> src/main.rs:6:29
|
2 | pub struct Percent(u8);
| -- a constructor is private if any of the fields is private
...
6 | let discount = billing::Percent(150); // ❌ 비공개 생성자
| ^^^^^^^ private tuple struct constructor
|
help: consider making the field publicly accessible
|
2 | pub struct Percent(pub u8);
| +++

“튜플 구조체 생성자 Percent는 비공개”라는 뜻이에요. 라벨은 “필드 중 하나라도 비공개면 생성자도 비공개”라고 설명하고, help는 “필드를 공개해 보라”고 제안해요.

rustc는 pub u8을 제안하고, 단순한 데이터 타입이라면 그래도 괜찮아요. 그런데 값을 보세요. 150퍼센트예요! 필드가 비공개인 것 자체가 핵심일 때가 많아요. 모듈은 Percent가 만들어지기 전에 값을 확인하고 싶은 거예요. 확인까지 해 주는 공개 생성자를 만들어 주세요.

mod billing {
pub struct Percent(u8);
impl Percent {
pub fn new(value: u8) -> Option<Percent> {
if value <= 100 { Some(Percent(value)) } else { None }
}
pub fn apply(&self, price: u32) -> u32 {
price * (100 - self.0 as u32) / 100
}
}
}
fn main() {
let ten = billing::Percent::new(10).unwrap();
println!("200에서 10% 할인: {}", ten.apply(200));
println!("150%도 되나요? {}", billing::Percent::new(150).is_some());
}

실행 결과:

200에서 10% 할인: 180
150%도 되나요? false
▶ 직접 실행해 보기

이제 프로그램 전체의 모든 Percent는 0에서 100 사이라는 게 보장되고, apply는 절대 언더플로가 나지 않아요. new 자체에도 pub이 필요하다는 걸 잊지 마세요. 비공개 메서드는 다른 에러인 E0624(associated function `new` is private, 연관 함수 new는 비공개)를 내요. 생성자에 대해서는 구조체에서 더 볼 수 있어요.

main.rs와 lib.rs가 둘 다 있는 패키지. 이 둘은 서로 다른 크레이트이고, main.rs는 라이브러리를 바깥에서 써요. lib.rs에 mod inventory;라고만 쓰면, main.rs의 use shop::inventory::count_items;는 실패해요.

error[E0603]: module `inventory` is private
--> src/main.rs:1:11
|
1 | use shop::inventory::count_items;
| ^^^^^^^^^ private module

“모듈 inventory는 비공개”라는 뜻이에요. 해결: lib.rs에 pub mod inventory;라고 쓰고, 모듈 안에서는 pub fn count_items라고 쓰세요. 주의하세요. 여기서는 pub(crate)로는 부족해요. 라이브러리 입장에서 main.rs는 다른 크레이트라서, pub(crate) fn이어도 function `count_items` is private가 나요. Cargo와 크레이트를 참고하세요.

비공개 구조체 필드. 이건 따로 에러 코드가 있어요. 비공개 필드가 있는 구조체를 만들면 E0451, 그 필드를 읽으면 E0616이 나요.

error[E0451]: field `balance` of struct `Account` is private
error[E0616]: field `balance` of struct `Account` is private

둘 다 “구조체 Account의 필드 balance는 비공개”라는 뜻이에요. 해결: 방법 4와 같은 선택이에요. 누구나 바꿔도 된다면 필드에 pub을 붙이고, 아니면 비공개로 두고 pub fn new(...)와 pub fn balance(&self) -> u64 같은 게터를 추가하세요.

크레이트의 소스 코드에서 찾은 함수. GitHub에서 크레이트 코드를 읽다가 딱 맞는 도우미 함수를 찾았는데, 호출하니 E0603이 나요. 크레이트 작성자가 pub으로 만들지 않았다면 쓸 수 없고, 그 함수는 언제든 바뀔 수 있어요. docs.rs의 크레이트 페이지에서 같은 일을 하는 공개된 방법을 찾아보세요. 거기에는 pub 항목만 나와요.

  • E0432: unresolved import. 항목을 아예 찾지 못했어요. E0603은 찾았지만 비공개인 경우예요.
  • E0433: failed to resolve. 코드 안의 경로가 아무 데로도 이어지지 않아요.
  • E0451과 E0616: 구조체 필드가 비공개예요(구조체를 만들 때, 또는 필드를 읽을 때).
  • E0624: 메서드나 연관 함수(new 같은)가 비공개예요.
  • 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.

이해도 점검

1. mod shop { mod prices { pub fn price_of() {} } }이 있고 main이 shop::prices::price_of()를 호출해요. 어떤 에러가 날까요?

2. src/lib.rs의 도우미 함수에 pub(crate)가 붙어 있어요. 같은 패키지의 src/main.rs에서 호출할 수 있을까요?

3. pub struct Email(String);은 일부러 필드를 비공개로 뒀어요. 다른 모듈에서 값을 만들 수 있게 하는 친절한 방법은 무엇일까요?

점수: 0 / 3