콘텐츠로 이동

Rust 에러 E0615: attempted to take value of method (메서드를 필드처럼 사용)

E0615는 value.something이라고 괄호 없이 썼는데, something이 필드가 아니라 메서드라는 뜻이에요. Rust에서 메서드는 인자가 하나도 없더라도 ()를 붙여 호출해야만 실행돼요. C#, JavaScript, Python에서 온 분들이 특히 자주 만나는 에러예요. 그런 언어에서는 .Count, .length나 프로퍼티가 평범한 필드처럼 보이니까요. 고치는 건 거의 항상 두 글자면 끝나요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러 해설에서 볼 수 있어요.

error[E0615]: attempted to take value of method `len` on type `String`
--> src/main.rs:3:46
|
3 | println!("{} has {} letters", name, name.len); // ❌ `len`은 메서드예요
| ^^^ method, not a field
|
help: use parentheses to call the method
|
3 | println!("{} has {} letters", name, name.len()); // ❌ `len`은 메서드예요
| ++

“attempted to take value of method len on type String“은 “String 타입의 len 메서드를 값처럼 가져가려고 했다”는 뜻이에요. 라벨 method, not a field는 “필드가 아니라 메서드”, help는 “괄호를 써서 메서드를 호출하라”는 뜻이에요.

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

fn main() {
let name = String::from("Ferris");
println!("{} has {} letters", name, name.len); // ❌ `len`은 메서드예요
}

첫 줄을 뒤에서부터 읽어 보세요. on type `String` 부분은 Rust가 살펴본 타입이고, method `len` 부분은 Rust가 거기서 찾은 것이에요. 그러니까 Rust는 len을 찾긴 찾았어요. 다만 그건 필드처럼 읽을 수 있는 값이 아니라, 실행해야 하는 코드예요.

라벨 method, not a field와 help 속 () 아래의 ++가 정확히 그 말을 하고 있어요. 괄호를 붙이면 끝이에요.

자판기를 떠올려 보세요. 필드는 자판기에 붙어 있는 라벨(“12번 자판기”)이에요. 그냥 읽으면 돼요. 메서드는 버튼(“거스름돈 보기”)이에요. 누르기 전까지는 아무 일도 일어나지 않아요. name.len은 버튼을 가리키기만 하고, name.len()은 버튼을 눌러요.

Rust는 필드와 메서드를 엄격하게 구분해요. 필드는 구조체 안에 저장된 데이터예요. 메서드는 impl 블록 안에 정의된, self를 받는 함수예요. 필드는 value.field로 읽고, 메서드는 value.method()로 호출해요. 메서드로 동작 추가하기를 참고하세요.

많은 언어가 프로퍼티(property)로 이 경계를 흐려요. C#의 list.Count나 JavaScript의 text.length는 필드처럼 보이지만, 뒤에서 코드가 실행돼요. Rust에는 일부러 프로퍼티가 없어요. ()가 보이면 코드가 실행되고 시간이 걸릴 수도 있다는 걸 알 수 있고, ()가 없으면 저장된 데이터를 그냥 읽는 거라는 걸 알 수 있어요. 숨겨진 게 없어요.

구조체에는 심지어 이름이 같은 필드와 메서드가 함께 있을 수도 있어요. 비공개 balance 필드와 공개 balance() 메서드처럼요. 이게 가능한 건 Rust가 어느 쪽을 말하는지 절대 추측하지 않기 때문이에요. 괄호가 없으면 필드, 괄호가 있으면 메서드예요.

거의 언제나 이 방법이에요. help: 줄대로 메서드를 호출하세요. 직접 만든 메서드(장바구니의 total 같은)든, len, is_empty, trim, count 같은 표준 메서드든 똑같아요.

struct Item {
price: u32, // 센트 단위
qty: u32,
}
struct Cart {
items: Vec<Item>,
}
impl Cart {
fn total(&self) -> u32 {
self.items.iter().map(|i| i.price * i.qty).sum()
}
}
fn main() {
let name = String::from("Ferris");
println!("{}의 글자 수: {}", name, name.len()); // ✅ 호출했어요
let cart = Cart {
items: vec![Item { price: 250, qty: 2 }, Item { price: 100, qty: 1 }],
};
if !cart.items.is_empty() {
println!("합계: {}센트", cart.total()); // ✅ 호출했어요
}
}

실행 결과:

Ferris의 글자 수: 6
합계: 600센트
▶ 직접 실행해 보기

메서드에 인자가 필요하면, rustc가 _ 자리 표시자로 인자가 들어갈 곳을 보여 줘요. s.contains라면 help에 s.contains(_)라고 나와요. 찾고 싶은 걸 채워 넣으세요. s.contains("world")처럼요. 메서드에 무엇이 필요한지 모르겠다면, rustc의 다음 에러(E0061)가 인자가 몇 개 필요한지 알려 줄 거예요.

방법 2: value.method 대신 메서드를 함수로 넘기기

섹션 제목: “방법 2: value.method 대신 메서드를 함수로 넘기기”

메서드를 지금 바로 호출하려던 게 아니라, .map()이나 sort_by_key 같은 곳에 넘기고 싶을 때 이 방법을 고르세요. .map(words[0].len)이라고 써도 E0615가 나요. 좋은 방법이 두 가지 있어요. 메서드를 호출하는 짧은 클로저를 쓰거나, 함수 이름처럼 쓸 수 있는 메서드의 전체 경로 String::len을 쓰는 거예요.

fn main() {
let words = vec![String::from("crab"), String::from("ferris"), String::from("rust")];
// 각 항목에 대해 메서드를 호출하는 클로저
let lengths: Vec<usize> = words.iter().map(|w| w.len()).collect();
println!("{:?}", lengths);
// 메서드의 경로는 함수 이름처럼 쓸 수 있어요
let same: Vec<usize> = words.iter().map(String::len).collect();
println!("{:?}", same);
let mut sorted = words.clone();
sorted.sort_by_key(|w| w.len());
println!("{:?}", sorted);
}

실행 결과:

[4, 6, 4]
[4, 6, 4]
["crab", "rust", "ferris"]
▶ 직접 실행해 보기

여기서 단어는 영어 그대로 두었어요. 길이(바이트 수)를 출력하는 예제라서, 한글 단어로 바꾸면 숫자가 달라지기 때문이에요(한글 한 글자는 3바이트예요).

String::len이 여기서 통하는 건 iter()가 &String 항목을 주고, String::len이 &String을 받기 때문이에요. 타입이 정확히 맞지 않을 때는 클로저 |w| w.len()이 언제나 통하는 쉬운 선택이에요.

방법 3: 정말 저장할 데이터라면 필드로 만들기

섹션 제목: “방법 3: 정말 저장할 데이터라면 필드로 만들기”

에러가 직접 만든 구조체에서 났고, 그 값은 그냥 저장해 두면 된다는 걸 깨달았을 때 이 방법을 고르세요. 이건 설계의 문제예요. 필드로 할까요, 메서드로 할까요?

  • 값이 한 번 주어지고 다른 무엇에도 의존하지 않는다면 필드로 만드세요. id, name, created_at 날짜 같은 것들이요.
  • 값을 다른 데이터로부터 계산한다면 메서드로 만드세요. 항목들로 구하는 total, 너비와 높이로 구하는 area, 내용물로 구하는 len 같은 것들이요.

total을 필드로 저장해 두면, 누군가 항목을 추가하고 값을 고치는 걸 잊는 순간 틀린 값이 돼요. total() 메서드는 매번 새로 계산하니까 틀릴 수가 없어요. 아래에서 id는 필드이고 total은 메서드예요. 주문이 바뀌어도 메서드가 계속 맞는 값을 내는 걸 볼 수 있어요.

mod shop {
pub struct Order {
pub id: u32, // 저장값: 한 번 정하고, order.id로 읽어요
prices: Vec<u32>, // 비공개: 메서드로만 바꿀 수 있어요
}
impl Order {
pub fn new(id: u32) -> Self {
Order { id, prices: Vec::new() }
}
pub fn add(&mut self, price: u32) {
self.prices.push(price);
}
// 계산값: 언제나 prices와 맞아서, 낡은 값이 될 수 없어요
pub fn total(&self) -> u32 {
self.prices.iter().sum()
}
pub fn item_count(&self) -> usize {
self.prices.len()
}
}
}
use shop::Order;
fn main() {
let mut order = Order::new(17);
order.add(250);
order.add(100);
println!("주문 {}: 상품 {}개, {}센트", order.id, order.item_count(), order.total());
order.add(50);
println!("주문 {}: 상품 {}개, {}센트", order.id, order.item_count(), order.total());
}

실행 결과:

주문 17: 상품 2개, 350센트
주문 17: 상품 3개, 400센트
▶ 직접 실행해 보기

많은 라이브러리에서 볼 수 있는 중간 방법도 있어요. 비공개 필드와, 이름이 같은 공개 메서드(게터, getter)를 함께 두는 거예요. 다른 코드는 acc.balance()로 값을 읽을 수는 있지만 직접 바꿀 수는 없어요. 이때 모듈 밖에서 acc.balance라고 쓰면 조금 다른 에러가 나요. E0616 “field balance of struct Account is private”(Account 구조체의 balance 필드는 비공개다)이고, a method `balance` also exists, call it with parentheses(같은 이름의 balance 메서드도 있으니 괄호를 붙여 호출하라)라는 힌트가 붙어요. 고치는 법은 같아요. ()를 붙이세요. 기본은 모두 비공개를 참고하세요.

다른 언어에서 온 습관. name.len, list.is_empty, text.trim, words.iter().count는 모두 E0615를 내요. Rust에서는 전부 메서드라서 전부 ()가 필요해요. 해결: name.len(), list.is_empty()처럼 쓰세요. 유용한 문자열 메서드를 참고하세요.

Vec의 len. scores.len은 이 메시지의 유일한 예외예요. E0616, “field len of struct Vec is private”(Vec 구조체의 len 필드는 비공개다)이 나와요. Vec에 정말로 len이라는 숨은 필드가 있기 때문이에요. 그래도 rustc는 help: a method `len` also exists, call it with parentheses를 붙여 줘요. 해결: scores.len(). (scores.is_empty는 평범한 E0615가 나요.) 벡터를 참고하세요.

무언가를 바꾸는 메서드. 한 줄에 counter.increment;만 써도 E0615가 나요. 값을 원한 게 아니었는데도요. ()가 없으면 아무것도 실행되지 않아요. 해결: counter.increment();.

반대 경우: 필드를 메서드처럼 호출할 때. name이 필드인데 user.name()이라고 쓰면 E0599가 나고, field, not a method(메서드가 아니라 필드)라는 라벨과 help: remove the arguments(인자를 지우라)가 붙어요. 해결: user.name. 필드에 클로저가 들어 있다면, 예를 들어 on_click: Box<dyn Fn() -> String>이라면, rustc는 surround the field access with parentheses(필드 접근을 괄호로 감싸라)라고 알려 줘요. (button.on_click)()처럼 써서 호출하세요.

이해도 점검

1. "attempted to take value of method is_empty on type String"이라는 에러가 났어요. 어떻게 고칠까요?

2. 저장된 필드보다 메서드로 만드는 게 보통 더 좋은 건 무엇일까요?

3. 모든 단어의 길이를 구하고 싶어요: words.iter().map(???). 무엇이 통할까요?

점수: 0 / 3