Rust 에러 E0424: expected value, found module `self` (self 매개변수 없음)
E0424는 self 매개변수가 없는 함수 안에서 self를 썼다는 뜻이에요. 그래서 Rust 입장에서는 self가 가리킬 대상이 없어요. 아주 흔한 실수예요. 특히 this가 언제나 있는 Java, C#, JavaScript를 쓰다 왔다면 더 그래요. 해결은 보통 한 단어면 끝나요. 함수의 매개변수 목록에 &self를 추가하면 돼요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러 해설에서 볼 수 있어요.
에러 메시지
섹션 제목: “에러 메시지”error[E0424]: expected value, found module `self` --> src/main.rs:7:32 |6 | fn describe() { | -------- this function doesn't have a `self` parameter7 | println!("Player: {}", self.name); // ❌ 어떤 플레이어요? | ^^^^ `self` value is a keyword only available in methods with a `self` parameter |help: add a `self` receiver parameter to make the associated `fn` a method |6 | fn describe(&self) { | +++++
error[E0599]: no method named `describe` found for struct `Player` in the current scope --> src/main.rs:13:10 |13 | hero.describe(); | ^^^^^^^^ this is an associated function, not a method“expected value, found module self“는 “값이 와야 하는데 self 모듈을 찾았다”는 뜻이에요. 라벨은 “이 함수에는 self 매개변수가 없다”, “self 값은 self 매개변수가 있는 메서드에서만 쓸 수 있는 키워드다”라고 알려 주고, help는 “self 리시버(receiver) 매개변수를 추가해서 연관 함수를 메서드로 만들라”고 제안해요. 두 번째 E0599는 “현재 스코프에서 Player에 describe라는 메서드가 없다, 이건 메서드가 아니라 연관 함수다”라는 뜻이에요.
이 에러를 내는 프로그램은 다음과 같아요.
struct Player { name: String,}
impl Player { fn describe() { println!("Player: {}", self.name); // ❌ 어떤 플레이어요? }}
fn main() { let hero = Player { name: String::from("Ferris") }; hero.describe();}두 번째 에러인 E0599는 첫 번째 에러 때문에 덩달아 생긴 거예요. E0424를 고치면 같이 사라져요.
무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”rustc가 코드에 붙인 라벨 두 개를 보세요.
- 6번째 줄:
fn describe()의 괄호가 비어 있어요. rustc는 “this function doesn’t have aselfparameter”(이 함수에는self매개변수가 없어요)라고 표시해요. - 7번째 줄:
self.name은 “이 메서드를 호출한 그 플레이어”의 이름을 달라는 뜻이에요. 그런데describe는 플레이어를 받은 적이 없으니, 여기서self는 아무것도 가리키지 않아요.
impl 블록 안에 있지만 self 매개변수가 없는 함수는 연관 함수(associated function)예요. 특정한 값 하나가 아니라 타입 자체에 속해요(Player::describe()). self 매개변수가 있는 함수는 메서드(method)이고, self라고 쓸 수 있는 건 메서드뿐이에요.
“여기에 본인 이름을 쓰세요”라고 적힌 신청서를 떠올려 보세요. 누군가 그 신청서를 들고 있을 때만 말이 되죠. describe()는 아무도 들고 있지 않은 신청서예요.
그런데 왜 “found module self”(self 모듈을 찾았다)라고 할까요? Rust에서 self에는 뜻이 하나 더 있어요. use self::helpers;처럼 쓰는 “현재 모듈”이라는 뜻이에요. self 값이 없으면 rustc는 그 뜻으로 해석한 다음, 모듈은 값이 아니라고 불평하는 거예요. “module”이라는 단어는 신경 쓰지 않아도 돼요. 진짜 문제는 빠진 매개변수예요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”Rust는 아무것도 숨기지 않아요. Java와 C#은 static이 아닌 모든 메서드에 눈에 보이지 않는 this를 넣어 줘요. Rust는 self를 첫 번째 매개변수로 직접 쓰게 하고, 메서드가 값을 어떻게 받는지도 함께 밝히게 해요. 보기만 하는지, 바꾸는지, 소유권을 가져가는지요. 이 선택은 대여(borrowing) 규칙의 일부라서 눈에 보여야 해요.
덕분에 코드를 읽는 사람과 컴파일러 모두 메서드가 값에 무슨 일을 하는지 정확히 알 수 있어요. fn summary(&self)는 아무것도 바꾸지 않겠다는 약속이에요. fn add(&mut self, ...)는 값을 바꾼다고 알려 줘요. fn checkout(self)는 값을 다 써 버려요. 그리고 컴파일러가 이 약속들을 대신 검사해 줘요. 메서드와 impl 블록은 구조체에서 설명해요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: &self, &mut self, self 추가하기
섹션 제목: “방법 1: &self, &mut self, self 추가하기”함수가 이미 있는 값 하나를 다뤄야 할 때 이 방법을 고르세요. 대부분은 이게 정답이에요. rustc가 제안하는 매개변수를 추가하고, 점(.)으로 호출하면 돼요.
struct Player { name: String,}
impl Player { fn describe(&self) { println!("Player: {}", self.name); }}
fn main() { let hero = Player { name: String::from("Ferris") }; hero.describe();}실행 결과:
Player: Ferrisrustc는 언제나 &self를 제안하지만, 사실 세 가지 종류가 있어요. 메서드가 하는 일에 맞는 걸 고르세요.
| 매개변수 | 메서드가 하는 일 | 흔한 이름 |
|---|---|---|
&self |
값을 읽기만 해요 | summary, len, is_empty, describe |
&mut self |
값을 바꿔요 | add, push, reset, level_up |
self |
값을 다 써 버려요(소유권을 가져가요) | into_..., finish, build, checkout |
세 가지를 한 타입에 모두 써 보면 이래요.
struct Order { items: Vec<String>,}
impl Order { fn summary(&self) -> String { format!("상품 {}개: {}", self.items.len(), self.items.join(", ")) }
fn add(&mut self, item: &str) { self.items.push(item.to_string()); }
fn checkout(self) -> Vec<String> { self.items }}
fn main() { let mut order = Order { items: Vec::new() }; order.add("커피"); order.add("베이글"); println!("{}", order.summary()); let packed = order.checkout(); // 여기서 order를 다 써 버려요 println!("포장 완료: {:?}", packed);}실행 결과:
상품 2개: 커피, 베이글포장 완료: ["커피", "베이글"]&self를 골랐는데 메서드가 필드를 바꾸면 컴파일러가 바로 알려 줘요. “cannot assign to self.count, which is behind a & reference”(& 참조 뒤에 있는 self.count에 대입할 수 없다)라는 E0594나, push 같은 호출이라면 E0596이 나와요. 그럴 땐 &mut self로 바꾸세요. 잘 모르겠으면 &self로 시작하세요. self는 드물게 쓰여요.
방법 2: 타입::메서드(&값)으로 호출하기
섹션 제목: “방법 2: 타입::메서드(&값)으로 호출하기”함수에 self 매개변수가 생기면 두 가지 방법으로 호출할 수 있어요. hero.label()과 Player::label(&hero)는 완전히 같은 뜻이에요. 점 앞에 쓰던 값이 첫 번째 인자가 되는 거예요. 평소에는 점 형태를 주로 쓰고, 타입:: 형태는 map 같은 곳에 메서드 자체를 넘기고 싶을 때 편해요.
struct Player { name: String, level: u32,}
impl Player { fn label(&self) -> String { format!("{} (레벨 {})", self.name, self.level) }}
fn main() { let hero = Player { name: String::from("Ferris"), level: 3 }; println!("{}", hero.label()); println!("{}", Player::label(&hero)); // 같은 호출이에요
let team = vec![hero, Player { name: String::from("Corro"), level: 5 }]; let labels: Vec<String> = team.iter().map(Player::label).collect(); println!("{:?}", labels);}실행 결과:
Ferris (레벨 3)Ferris (레벨 3)["Ferris (레벨 3)", "Corro (레벨 5)"]반대로 값 없이 Player::label()이라고 쓰면 안 돼요. 메서드에는 언제나 self가 필요해요. 빠뜨리면 “this function takes 1 argument but 0 arguments were supplied”(이 함수는 인자 1개를 받는데 0개가 주어졌다)라는 E0061이 나와요.
방법 3: 함수에 필요한 걸 넘겨주기
섹션 제목: “방법 3: 함수에 필요한 걸 넘겨주기”아직 값이 없을 때, 또는 함수가 impl 블록 밖에 있을 때 이 방법을 고르세요.
대표적인 예가 생성자예요. new는 값을 만드는 함수라서, 읽어 올 self가 아직 없어요. 처음에 필요한 데이터를 평범한 매개변수로 new에 넘기고, Self { ... }로 구조체를 만드세요.
struct Counter { count: u32, step: u32,}
impl Counter { fn new(step: u32) -> Self { Self { count: 0, step } }
fn tick(&mut self) { self.count += self.step; }}
fn main() { let mut counter = Counter::new(5); counter.tick(); counter.tick(); println!("카운트: {}", counter.count);}실행 결과:
카운트: 10대소문자를 잘 보세요. 대문자 S로 시작하는 Self는 타입이라서 Self { ... }는 새 값을 만들어요. 소문자 s의 self는 메서드를 호출한 값이에요.
impl 블록 밖에 있는 일반 함수(free function)도 같은 방법으로 고쳐요. 이때 rustc의 문구는 조금 달라서 “this function can’t have a self parameter”(이 함수에는 self 매개변수가 있을 수 없다)라고 해요. 대신 구조체를 참조로 넘기세요.
struct Cart { items: Vec<u32>,}
fn total(cart: &Cart) -> u32 { cart.items.iter().sum()}
// 호출할 때: total(&cart)아니면 total을 impl Cart 안으로 옮기고 방법 1처럼 &self를 주세요. 둘 다 괜찮아요. 함수가 분명히 “장바구니에 관한” 일이라면 메서드 쪽이 더 자연스럽게 읽혀요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”new 안에서. fn new() -> Self 안에 self.count = 0;이라고 쓰면 E0424가 나요. 이때 rustc가 제안하는 fn new(&self) -> Self는 말이 안 돼요(카운터를 만들려면 카운터가 있어야 하는 꼴이죠). 해결: 방법 3처럼 Self { count: 0 }으로 값을 만드세요.
연관 함수 안의 클로저에서. 문제는 클로저가 아니라, 클로저를 감싸고 있는 함수예요.
error[E0424]: expected value, found module `self` --> src/main.rs:8:35 |7 | fn sale_prices(prices: &[u32]) -> Vec<u32> { | ----------- this function doesn't have a `self` parameter8 | prices.iter().map(|p| p - self.discount).collect() | ^^^^ `self` value is a keyword only available in methods with a `self` parameter해결: fn sale_prices(&self, prices: &[u32])처럼 메서드로 만들거나, fn sale_prices(prices: &[u32], discount: u32)처럼 숫자를 넘겨주세요. 클로저를 참고하세요.
main이나 도우미 함수에서. impl 블록 밖에서는 self를 절대 쓸 수 없어요. 해결: 구조체를 매개변수로 넘기거나(total(&cart)), 그 구조체의 메서드를 호출하세요(cart.total()).
C#이나 Java의 “static” 메서드에 익숙할 때. self가 없는 연관 함수는 Rust판 static 메서드예요. 인스턴스 메서드를 원한다면 self 매개변수를 직접 추가해야 해요. C# 개발자를 위한 Rust를 참고하세요.
관련 에러
섹션 제목: “관련 에러”- E0425: 반대 방향의 실수예요. 메서드 안에서
self.count대신count라고 썼을 때 나와요. - E0599: “no method named … found”(그런 이름의 메서드가 없다).
self가 없는 함수는 점으로 호출할 수 없어서, E0424 바로 뒤에 자주 함께 나와요. - E0061: 인자 개수가 틀렸어요. 예를 들어 값 없이
Player::label()을 호출할 때 나와요. - E0596:
&self를 추가했지만 메서드가 필드를 바꿔서&mut self가 필요할 때 나와요. 이런 식으로 필드에 대입하면 E0594가 나와요. - 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. impl Player 안에서 fn heal() { self.hp += 10; }이 E0424를 내요. 가장 좋은 해결책은 무엇일까요?
2. fn new() -> Self 안에서 왜 self.name을 쓸 수 없을까요?
3. Player에 fn label(&self) -> String이 정의되어 있어요. hero.label()과 같은 호출은 무엇일까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0424 — Rust error codes index (영어): 이 에러의 공식 설명이에요. 짧은 예제도 있어요.
- 메서드 문법 — The Rust Programming Language 한국어판:
&self,&mut self,self와new같은 연관 함수를 설명해요. - Associated functions and methods — Rust by Example (영어): 두 종류를 나란히 보여 주는 실행 예제예요.
self키워드 — 표준 라이브러리 문서 (영어): 메서드 리시버와 현재 모듈,self의 두 가지 뜻을 모두 다뤄요.- Associated items — The Rust Reference (영어): 메서드, 리시버, 연관 함수에 대한 정확한 규칙이에요.