콘텐츠로 이동

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` parameter
7 | 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가 코드에 붙인 라벨 두 개를 보세요.

  1. 6번째 줄: fn describe()의 괄호가 비어 있어요. rustc는 “this function doesn’t have a self parameter”(이 함수에는 self 매개변수가 없어요)라고 표시해요.
  2. 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는 아무것도 숨기지 않아요. 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: Ferris
▶ 직접 실행해 보기

rustc는 언제나 &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` parameter
8 | 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