핵심
Rust에서 impl 블록 안에 정의되는 모든 함수를 associated function이라고 부른다. 그중 첫 인자가 self인 것을 특별히 method라고 분리해서 부른다.
impl Rectangle {
// associated function (self 없음) — 흔히 생성자로 사용
fn square(size: u32) -> Self {
Self { width: size, height: size }
}
// method (self 있음)
fn area(&self) -> u32 {
self.width * self.height
}
}
호출 syntax도 다르다. method는 점 표기, associated function은 :: 표기.
let sq = Rectangle::square(3);
let a = sq.area();
method 호출은 syntactic sugar
rect1.area()는 컴파일러가 내부적으로 다음과 같이 푼다.
Rectangle::area(&rect1)
실제로 위처럼 직접 호출하는 것도 합법이다. 즉 method는 "타입 이름으로 namespacing되고 첫 인자가 self인 함수"일 뿐이다. instance가 호출에 필요한 이유는 단순하다 — method 본문이 첫 인자로 인스턴스를 받기 때문이다. self가 없는 associated function은 인스턴스 없이도 호출 가능하다.
self의 세 형태
method의 첫 인자는 ownership과 직결된다.
| 형태 | 의미 | 사용 시점 |
|---|---|---|
&self | immutable borrow | 인스턴스를 읽기만 함 |
&mut self | mutable borrow | 인스턴스 내부를 수정 |
self | ownership 이전 | 인스턴스를 변환·소비 |
&self는 self: &Self의 줄임말이고 Self는 impl 블록의 대상 타입에 대한 alias다. method가 결국 &Rectangle을 첫 인자로 받는 함수와 동등하다는 사실이 여기서 명확해진다. → borrowing-rules, copy-vs-move 참고.
automatic referencing
C/C++에는 .과 -> 두 호출 연산자가 있다. Rust는 . 하나로 통일했다. 비결은 컴파일러가 method signature를 보고 호출 측에 필요한 &, &mut, *를 자동으로 끼워넣는 automatic referencing and dereferencing 기능이다.
p1.distance(&p2); // 이 둘은 동일하게 컴파일된다
(&p1).distance(&p2);
이게 가능한 이유는 method의 receiver 타입(&self 등)이 한 가지로 명확하게 정해져 있기 때문이다. 컴파일러가 호출 측의 borrow 형태를 deterministic하게 결정할 수 있다.
필드와 같은 이름의 method
impl Rectangle {
fn width(&self) -> bool { self.width > 0 }
}
필드 width와 method width가 같은 이름이어도 충돌이 없다. rect1.width는 필드, rect1.width()는 method — 괄호 유무로 구분된다. 필드를 private으로 막고 method만 public으로 노출해 read-only 접근을 만드는 getter 패턴에 자주 쓰인다. Rust는 자동 getter를 만들어주지 않는다.
associated function의 호출 — :: 표기
self가 없는 associated function은 인스턴스가 없어도 부를 수 있다. 주로 생성자 역할로 쓰이며, 이름은 관습적으로 new지만 키워드는 아니다.
let sq = Rectangle::square(3);
::은 module namespacing에도 동일하게 쓰인다 — String::from, Vec::new 같은 패턴이 모두 이 메커니즘이다.
리턴 타입의 Self는 impl 대상 타입의 alias이므로 Rectangle이라고 적어도 동일하다.
다중 impl 블록
한 struct에 impl 블록을 여러 개 둬도 컴파일 결과는 동일하다.
impl Rectangle { fn area(&self) -> u32 { ... } }
impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { ... } }
지금은 의미가 없지만 generic + trait 단계에서 — generic 파라미터에 따라 일부 method를 특정 trait 구현자에게만 노출하는 식의 — 분리가 자연스럽게 의미를 가진다.
다른 언어의 static과의 매칭
| Rust | 대응 개념 |
|---|---|
| associated function (self 없음) | C 함수 / C++ static member / Java static method / Python @staticmethod |
method (&self 등) | C에서 첫 인자로 포인터 받는 함수 / C++ non-virtual member function |
Java instance method와 직접 대응되지 않는 점에 주의한다 — Java instance method는 기본이 virtual이라 vtable을 거치는 dynamic dispatch이고, Rust method는 기본이 정적 dispatch다. 자세한 메커니즘은 how-method-dispatch-works 참고.
결론
method는 결국 첫 인자가 self인 함수다. Rust가 추가하는 것은 namespacing(Type::name)과 점 호출 syntactic sugar뿐이다. self를 어떻게 받느냐가 ownership과 borrow 동작을 결정하고, self가 없는 변형이 associated function이다 — 다른 언어의 static method와 같은 개념.