메서드 문법
impl 블록에서 self를 받는 메서드를 정의하고 추가 매개변수·연관 함수·여러 구현 블록으로 구조체 동작을 구성합니다.
메서드(method)는 함수와 비슷합니다. fn 키워드와 이름으로 선언하고, 매개변수와 반환 값을 가지며, 다른 곳에서 부르면 실행됩니다. 다른 점은 메서드가 구조체(또는 열거형이나 트레이트 객체)에 딸려 정의되고, 첫 매개변수가 언제나 그 메서드를 부른 인스턴스를 나타내는 self라는 것입니다. 이 글에서는 구조체를 사용한 예제 프로그램의 area 함수를 Rectangle의 메서드로 바꾸고, 매개변수가 더 있는 메서드, 인스턴스 없이 부르는 연관 함수, 여러 impl 블록을 살펴봅니다.
메서드 정의하기
Rectangle을 매개변수로 받던 area 함수를 Rectangle 구조체에 정의된 area 메서드로 바꿔 봅니다. 앞 글의 코드와 비교하면 함수가 impl Rectangle 블록 안으로 들어갔고, 호출도 area(&rect1)에서 rect1.area()로 바뀌었습니다.
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}The area of the rectangle is 1500 square pixels.Rectangle에 딸린 함수를 정의하려면 먼저 Rectangle의 impl(implementation) 블록을 만듭니다. 이 블록 안의 모든 것은 Rectangle 타입에 딸립니다. 그다음 area 함수를 impl의 중괄호 안으로 옮기고, 첫 매개변수를 self로 바꾸어 본문에서 self.width처럼 씁니다. main에서는 rect1을 인수로 넘겨 area 함수를 부르던 것을, 인스턴스 뒤에 점과 메서드 이름, 괄호를 붙이는 메서드 문법(method syntax) rect1.area()로 바꿉니다.
area의 시그니처에서는 rectangle: &Rectangle 대신 &self를 썼습니다. &self는 self: &Self의 줄임말이며, impl 블록 안에서 Self는 그 블록이 구현하는 타입, 여기서는 Rectangle의 별칭입니다. 메서드는 첫 매개변수가 Self 타입의 self여야 하므로, 러스트는 첫 자리에 self만 적는 줄임말을 허용합니다. 이 메서드가 인스턴스를 빌린다는 것을 나타내려면 &Rectangle에서처럼 self 앞에 &를 붙여야 합니다.
메서드도 다른 매개변수처럼 self의 소유권을 가져오거나, 불변으로 빌리거나, 가변으로 빌릴 수 있습니다. 여기서 &self를 고른 까닭은 함수 버전에서 &Rectangle을 쓴 까닭과 같습니다. 소유권을 가져오거나 데이터를 고칠 필요 없이 읽기만 하면 되기 때문입니다. 메서드가 인스턴스를 고쳐야 한다면 첫 매개변수를 &mut self로 합니다. self만 적어 소유권을 가져오는 메서드는 드문데, 주로 self를 다른 것으로 바꾼 뒤 원래 인스턴스를 더 쓰지 못하게 하려는 경우에 씁니다. 이때도 보통의 이동 규칙이 적용되어, Copy가 아닌 인스턴스는 메서드로 이동하므로 부른 뒤에는 쓸 수 없습니다.
함수 대신 메서드를 쓰는 주된 까닭은, 메서드 문법을 쓸 수 있고 시그니처마다 self의 타입을 되풀이하지 않아도 된다는 것 말고도, 코드를 정리하는 데 있습니다. 한 타입의 인스턴스로 할 수 있는 일을 모두 그 타입의 impl 블록 한곳에 모아 두면, 나중에 이 코드를 쓰는 사람이 Rectangle으로 할 수 있는 일을 여기저기 찾아다니지 않아도 됩니다.
메서드에 필드와 같은 이름을 붙일 수도 있습니다. 예를 들어 Rectangle에 width라는 메서드를 정의할 수 있습니다.
impl Rectangle {
fn width(&self) -> bool {
self.width > 0
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
if rect1.width() {
println!("The rectangle has a nonzero width; it is {}", rect1.width);
}
}The rectangle has a nonzero width; it is 30이 width 메서드는 width 필드가 0보다 크면 true를, 0이면 false를 돌려줍니다. rect1.width 뒤에 괄호를 붙이면 러스트는 width 메서드를 뜻한다고 알고, 괄호가 없으면 width 필드를 뜻한다고 봅니다. 필드와 같은 이름의 메서드는 그 필드의 값을 돌려주기만 하는 경우가 많으며, 이런 메서드를 게터(getter)라고 합니다. 러스트는 다른 언어처럼 게터를 자동으로 만들어 주지 않습니다. 필드는 비공개로 두고 메서드만 공개하면 그 필드를 읽기만 하게 할 수 있어서, 게터는 타입의 공개 API에 쓸모가 있습니다. 공개와 비공개는 pub 키워드로 경로 노출하기에서 다룹니다.
-> 연산자는 없나요?
C와 C++에서는 메서드를 부를 때 두 가지 연산자를 씁니다. 객체로 직접 부를 때는 .을, 객체의 포인터로 부르느라 먼저 역참조해야 할 때는 ->를 쓰며, 포인터 object에 대해 object->something()은 (*object).something()과 같습니다. 러스트에는 ->에 해당하는 연산자가 없습니다. 대신 메서드를 부를 때 자동 참조와 역참조(automatic referencing and dereferencing)가 일어납니다.
object.something()으로 메서드를 부르면, 러스트는 object가 그 메서드의 시그니처에 맞도록 &나 &mut, *를 자동으로 붙입니다. 그래서 다음 두 호출은 같습니다.
#[derive(Debug, Copy, Clone)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn distance(&self, other: &Point) -> f64 {
let x_squared = f64::powi(other.x - self.x, 2);
let y_squared = f64::powi(other.y - self.y, 2);
f64::sqrt(x_squared + y_squared)
}
}
fn main() {
let p1 = Point { x: 0.0, y: 0.0 };
let p2 = Point { x: 3.0, y: 4.0 };
println!("{}", p1.distance(&p2));
println!("{}", (&p1).distance(&p2));
}5
5두 점 사이의 거리는 3과 4의 제곱의 합 25의 제곱근인 5이며, 첫 호출이 훨씬 깔끔합니다. 이런 자동 참조가 가능한 것은 메서드가 받는 쪽, 곧 self의 타입이 분명하기 때문입니다. 받는 쪽과 메서드 이름을 알면 러스트는 그 메서드가 인스턴스를 읽기만 하는지(&self), 고치는지(&mut self), 소비하는지(self)를 알 수 있습니다. 메서드를 받는 쪽을 러스트가 알아서 빌려 주므로, 실제로 소유권을 다루기가 훨씬 편해집니다.
이 자동 보정은 점 앞의 받는 쪽에만 적용되며, 다른 인수까지 자동으로 빌려 주지는 않습니다. 그래서 p1.distance(&p2)에서 p1은 &self에 맞게 자동으로 빌리지만, other: &Point에 넘기는 p2에는 직접 &를 붙였습니다. 아래 그림은 점으로 메서드를 부를 때 러스트가 메서드를 찾는 과정입니다.
점 호출은 수신자 후보를 만든 뒤 메서드를 결정한다
점 메서드의 후보 생성·T/&T/&mut T 후보 순서 탐색·선택 뒤 가변성 확인은 명시된 탐색 순서다. 한 receiver의 후보 장부로 선택을 추적하되 컴파일러 구현 pass나 런타임 탐색으로 만들지 않고 선택 뒤 조건 실패의 재탐색 금지를 보존한다.
METHOD LOOKUP · AUTOREF / AUTODEREF
메서드는 후보 순서대로 찾고 선택 뒤 호출 조건을 검사한다
점 문법은 수신자에 무조건 & 하나를 붙이는 단축 규칙이 아닙니다. 수신자 타입의 역참조 사슬과 대여 후보를 순서대로 만들고 보이는 메서드를 탐색합니다.
receiver.method(args)에서 먼저 수신자 표현식의 타입을 얻습니다.수신자 타입을 반복해서 역참조하며 후보 타입을 만듭니다. 마지막에 배열을 슬라이스로 변환하는 후보도 시도합니다.
각 후보
T바로 뒤에&T와&mut T를 추가합니다.후보 순서대로 inherent 메서드와 보이는 trait 메서드를 탐색합니다. 타입 매개변수에서는 trait bound의 메서드를 우선합니다. 모호함은 해당 탐색 단계에서 판단합니다.
선택한 메서드가 가변성·수명·대여·
unsafe조건상 실제로 호출 가능한지 검사합니다. 실패해도 다음 후보를 다시 찾지 않습니다.
선택이 명확하고 호출 가능
수신자를 맞는 형태로 자동 보정해 메서드를 호출합니다.
오류
후보가 없거나 모호하거나 선택한 메서드의 호출 조건을 만족하지 않으면 컴파일 오류입니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 원래 가로 도표의 7노드와 6간선이다. 점 호출→반복 역참조/최종 배열 슬라이스 후보→각 T 뒤에 &T/&mut T 추가→후보 순서별 inherent/보이는 trait/타입 매개변수 bound 우선 탐색→선택과 호출 조건→성공 호출의 흐름이다. 조건이 아니요인 경우 성공 노드 밖의 우회선으로 컴파일 오류를 향한다. 후보 없음/모호함/선택 뒤 가변성·수명·unsafe 조건 위반을 원래 본문과 동일하게 구분하고 선택 후 실패 시 재탐색하지 않는다. 추가 인수의 대여는 수신자 자동 보정과 별개다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 수신자 후보 rect.area 호출의 Rectangle 후보를 만든다. |
|
|
| 메서드 선택 &self area에 맞는 후보를 찾는다. |
|
|
| 조건 검사·호출 대여 조건이 맞으면 수신자를 보정해 호출한다. |
|
|
점 호출은 수신자 후보를 만든 뒤 메서드를 결정한다
점 메서드의 후보 생성·T/&T/&mut T 후보 순서 탐색·선택 뒤 가변성 확인은 명시된 탐색 순서다. 한 receiver의 후보 장부로 선택을 추적하되 컴파일러 구현 pass나 런타임 탐색으로 만들지 않고 선택 뒤 조건 실패의 재탐색 금지를 보존한다.
METHOD LOOKUP · AUTOREF / AUTODEREF
메서드는 후보 순서대로 찾고 선택 뒤 호출 조건을 검사한다
점 문법은 수신자에 무조건 & 하나를 붙이는 단축 규칙이 아닙니다. 수신자 타입의 역참조 사슬과 대여 후보를 순서대로 만들고 보이는 메서드를 탐색합니다.
receiver.method(args)에서 먼저 수신자 표현식의 타입을 얻습니다.수신자 타입을 반복해서 역참조하며 후보 타입을 만듭니다. 마지막에 배열을 슬라이스로 변환하는 후보도 시도합니다.
각 후보
T바로 뒤에&T와&mut T를 추가합니다.후보 순서대로 inherent 메서드와 보이는 trait 메서드를 탐색합니다. 타입 매개변수에서는 trait bound의 메서드를 우선합니다. 모호함은 해당 탐색 단계에서 판단합니다.
선택한 메서드가 가변성·수명·대여·
unsafe조건상 실제로 호출 가능한지 검사합니다. 실패해도 다음 후보를 다시 찾지 않습니다.
선택이 명확하고 호출 가능
수신자를 맞는 형태로 자동 보정해 메서드를 호출합니다.
오류
후보가 없거나 모호하거나 선택한 메서드의 호출 조건을 만족하지 않으면 컴파일 오류입니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 원래 가로 도표의 7노드와 6간선이다. 점 호출→반복 역참조/최종 배열 슬라이스 후보→각 T 뒤에 &T/&mut T 추가→후보 순서별 inherent/보이는 trait/타입 매개변수 bound 우선 탐색→선택과 호출 조건→성공 호출의 흐름이다. 조건이 아니요인 경우 성공 노드 밖의 우회선으로 컴파일 오류를 향한다. 후보 없음/모호함/선택 뒤 가변성·수명·unsafe 조건 위반을 원래 본문과 동일하게 구분하고 선택 후 실패 시 재탐색하지 않는다. 추가 인수의 대여는 수신자 자동 보정과 별개다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 수신자 후보 rect.area 호출의 Rectangle 후보를 만든다. |
|
|
| 메서드 선택 &self area에 맞는 후보를 찾는다. |
|
|
| 조건 검사·호출 대여 조건이 맞으면 수신자를 보정해 호출한다. |
|
|
rect.area()를 부르면 러스트는 먼저 받는 쪽의 타입 Rectangle로 후보를 만들고, 필요하면 역참조를 되풀이하며 후보를 더 만든 뒤, 각 후보에 &와 &mut을 붙인 형태까지 차례로 살펴 이름이 맞는 메서드를 찾습니다. &self를 받는 area는 &Rectangle 후보에서 찾아지며, 그 대여가 허용되면 받는 쪽을 &rect로 보정해 부릅니다. 맞는 메서드가 없거나 대여 규칙에 맞지 않으면 컴파일 오류가 납니다.
더 깊이: 후보를 차례로 찾기 때문에 생기는 일
Reference에 따르면 컴파일러는 후보 타입마다 먼저 그 타입에 직접 구현된 메서드를 찾고, 그다음 스코프에 있는 트레이트가 그 타입에 제공하는 메서드를 찾습니다. 그리고 앞의 후보에서 메서드를 찾으면 뒤의 후보는 보지 않습니다. 후보는 T, &T, &mut T 순서로 이어지므로, &self를 받는 트레이트 메서드와 같은 이름의 &mut self 고유 메서드가 함께 있다면 &T 후보에서 먼저 찾아진 트레이트 메서드가 불립니다. Reference도 이 순서가 가끔 예상과 다른 결과를 낳는다며 바로 이런 예를 듭니다.
더 자주 만나는 예는 참조에 clone을 부르는 경우입니다. 표준 라이브러리는 모든 &T에 Clone을 구현합니다. 그래서 r: &T에 r.clone()을 부르면, T가 Clone을 구현했을 때는 T의 clone이 먼저 찾아져 값의 복제본이 나오지만, 구현하지 않았을 때는 다음 후보에서 &T의 clone이 찾아져 참조의 복사본이 나옵니다. rustc에는 이런 호출을 기본으로 경고하는 noop_method_call 린트가 있으며, 이 린트는 그 호출이 아무 일도 하지 않는다고 알리고 값을 복제하려던 것이라면 그 타입에 Clone을 구현하라고 제안합니다.
더 많은 매개변수를 가진 메서드
Rectangle에 두 번째 메서드를 만들어 봅니다. 이번 메서드는 다른 Rectangle을 받아, 그 사각형이 self 안에 완전히 들어가면 true를, 아니면 false를 돌려줍니다. can_hold를 정의해 다음 프로그램이 동작하게 만드는 것이 목표입니다.
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
let rect3 = Rectangle {
width: 60,
height: 45,
};
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}아직 can_hold를 정의하지 않았으므로 이 코드는 지금은 컴파일되지 않으며, 아래 출력은 메서드를 완성한 뒤에 나와야 할 결과입니다. rect2는 너비와 높이가 모두 rect1보다 작지만 rect3는 rect1보다 넓으므로, 다음과 같이 출력되어야 합니다.
Can rect1 hold rect2? true
Can rect1 hold rect3? false메서드는 impl Rectangle 블록 안에 두고, 이름은 can_hold로 하며, 다른 Rectangle을 불변 참조로 받습니다. 매개변수의 타입은 부르는 코드를 보면 알 수 있습니다. rect1.can_hold(&rect2)는 rect2의 불변 참조 &rect2를 넘기는데, rect2를 읽기만 하면 되고 main이 그 뒤에도 rect2를 쓸 수 있어야 하므로 불변 참조가 알맞습니다. 반환 값은 불리언이며, self의 너비와 높이가 각각 다른 사각형의 너비와 높이보다 큰지 확인합니다.
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}이제 앞의 main을 실행하면 원하는 결과가 나옵니다. 메서드는 self 뒤에 매개변수를 여러 개 둘 수 있으며, 그 매개변수는 함수의 매개변수와 똑같이 동작합니다. 이 can_hold는 너비와 높이가 각각 엄격하게 더 작아야 들어간다고 보므로, 치수가 같거나 사각형을 돌려 넣는 경우는 들어가지 않는 것으로 판단합니다.
연관 함수
impl 블록 안에 정의한 함수는 모두 impl 뒤에 적은 타입에 딸리므로 연관 함수(associated function)라고 부릅니다. 그 가운데 첫 매개변수로 self를 받지 않는 함수도 정의할 수 있는데, 이런 함수는 인스턴스가 없어도 동작하므로 메서드가 아닙니다. String 타입의 String::from이 이런 함수입니다.
메서드가 아닌 연관 함수는 그 타입의 새 인스턴스를 돌려주는 생성자로 자주 씁니다. 흔히 new라는 이름을 붙이지만, new는 러스트의 키워드도 특별한 이름도 아닙니다. 예를 들어 치수 하나를 받아 너비와 높이가 모두 그 값인 정사각형 Rectangle을 만드는 square를 정의하면, 같은 값을 두 번 적지 않아도 됩니다.
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}반환 타입과 본문의 Self는 impl 뒤에 적은 타입의 별칭이므로, 여기서는 Rectangle입니다. 연관 함수는 let sq = Rectangle::square(3);처럼 구조체 이름 뒤에 ::를 붙여 부릅니다. 이 함수가 구조체의 네임스페이스(namespace) 안에 있기 때문이며, ::는 모듈 스코프와 공개 범위에서 볼 모듈이 만드는 네임스페이스에도 씁니다. 아래 표는 impl 블록에 둘 수 있는 것들을 정리한 것입니다.
ASSOCIATED API · IMPL LAYOUT
메서드와 연관 함수는 호출할 때 수신자를 쓰는지가 다르다
impl Rectangle 안의 함수는 모두 타입에 연관됩니다. 그중 첫 매개변수가 self인 함수가 메서드이고, self가 없는 함수도 연관 함수로 같은 API에 속합니다.
| # | 정의와 호출 | 읽는 기준 |
|---|---|---|
| 1 | 메서드
|
기존 인스턴스를 수신자로 사용합니다. self 형태가 읽기, 변경, 소비 계약을 정하며 추가 매개변수는 그 뒤에 둡니다. |
| 2 | 메서드가 아닌 연관 함수
|
인스턴스 없이 타입 경로에서 호출합니다. Self는 대상 타입의 별칭이며, 생성 함수 이름 new는 관례일 뿐 키워드가 아닙니다. |
| 3 | 여러 inherent 같은 타입에 블록을 둘 이상 정의 |
문법상 유효하며 모든 항목은 같은 타입 API에 합쳐집니다. 블록을 나눠도 호출 문법이나 네임스페이스가 달라지지 않습니다. |
| 4 | 필드와 같은 이름의 메서드
|
괄호 없이 쓰면 필드에 접근하고, 괄호를 붙이면 메서드를 호출합니다. 같은 이름을 써도 두 문법으로 구분됩니다. |
- 메서드 ·
rect.area() - 첫 매개변수
self가 기존 인스턴스를 받고 소유권 계약을 드러냅니다. - 연관 함수 ·
Rectangle::square self없이 타입 경로로 호출합니다. 반환 타입과 생성식의Self는Rectangle을 뜻합니다. 생성 함수 이름new는 관례이며 키워드가 아닙니다.- 여러
impl· 하나의 API - 블록을 나눠도 별도 타입이 생기지 않으며 호출 방식도 그대로입니다.
- 필드와 같은 이름도 가능
rect.width는 필드이고rect.width()는 메서드입니다. 괄호가 둘을 구분합니다.
can_hold처럼 메서드는 self 뒤에 일반 함수와 같은 추가 매개변수를 둘 수 있습니다. 여러 impl은 필요할 때만 책임이나 제네릭 조건을 나누는 수단입니다.area처럼 self를 받는 메서드는 rect.area()처럼 기존 인스턴스로 부르며, self의 형태가 읽기, 고치기, 소비하기를 정합니다. square처럼 self가 없는 연관 함수는 Rectangle::square(3)처럼 타입 이름으로 부르고, 주로 새 인스턴스를 만듭니다. impl 블록 안의 Self는 그 타입의 별칭이며, 필드와 같은 이름의 메서드는 괄호의 유무로 필드와 구분합니다.
여러 개의 impl 블록
구조체 하나에 impl 블록을 여러 개 둘 수도 있습니다. 앞의 코드를 메서드마다 impl 블록을 나누어 쓰면 다음과 같습니다.
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}여기서는 블록을 나눌 까닭이 없지만, 이렇게 써도 올바른 코드입니다. 블록을 나누어도 새 타입이나 네임스페이스가 생기지는 않으며, 두 메서드는 모두 같은 Rectangle의 메서드이고 부르는 방법도 같습니다. 여러 impl 블록이 쓸모 있는 경우는 제네릭 데이터 타입에서 다룹니다.
self의 세 가지 형태
마지막으로, 생성자와 self의 세 가지 형태를 한 프로그램에서 함께 써 봅니다. 아래 그림은 square(3)으로 만든 인스턴스 하나가 세 메서드를 거치며 어떻게 바뀌는지 보여 줍니다.
메서드 수신자는 대여·수정·소비 계약을 정한다
본문의 별도 완전 예 square(3)→area 9에서 3×3 유지→enlarge(2)에서 6×6/36→into_dimensions 소비로 tuple 6,6의 동일 인스턴스 상태가 명시된다. 반환 튜플 사용 가능과 원 rect moved를 함께 보여 줄 가치가 높다.
RECEIVER CONTRACT · OWNERSHIP
수신자 형태가 읽기·변경·소유권 전달을 정한다
첫 매개변수의 형태는 단순한 표기 차이가 아닙니다. &self, &mut self, self가 메서드의 접근 권한과 호출 뒤 값의 상태를 정합니다.
| # | 수신자와 허용되는 동작 | 호출자에게 남는 결과 |
|---|---|---|
| 1 | 공유 대여 ·
|
호출자가 계속 소유합니다. area의 u32 필드 읽기는 Copy라 값을 복사하지만, Copy가 아닌 필드는 공유 참조 밖으로 이동할 수 없습니다. |
| 2 | 가변 대여 ·
|
소유권은 호출자에게 남습니다. 다만 수신자를 가변으로 빌릴 수 있어야 하고, 호출과 충돌하는 다른 대여가 없어야 합니다. |
| 3 | 값으로 받기 ·
|
Copy가 아닌 이 절의 Rectangle은 이동되어 원래 바인딩을 다시 쓸 수 없습니다. 수신자 타입이 Copy라면 복사본이 전달되어 원본도 유효합니다. |
&self· 공유 대여- 읽기 중심 계약입니다. 호출자가 계속 소유하며, 이 예제의
u32필드는Copy라 값이 복사됩니다.Copy가 아닌 필드는 공유 참조 밖으로 이동할 수 없습니다. &mut self· 가변 대여- 읽고 쓸 수 있지만 독점 대여입니다. 수신자를 가변으로 빌릴 수 있고 충돌하는 대여가 없어야 합니다.
self· 값으로 받기Copy가 아닌 값은 이동합니다.Copy타입이라면 복사본이 전달되어 원본도 계속 쓸 수 있습니다.- 추가 매개변수는 별도 계약
can_hold의other:&Rectangle처럼self뒤의 매개변수는 일반 함수 매개변수와 같습니다.
rect1.can_hold(&rect2)의 추가 인수 &rect2는 호출자가 명시합니다.이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 7노드와 6간선이다. 새 본문의 별도 완전한 예인 square 3→rect 3,3→공유 area 반환 9와 원래 상태 유지→가변 enlarge 2로 rect 6,6과 넓이 36→값 self를 받는 into_dimensions 소비→반환 tuple 6,6의 흐름이다. 소비 노드의 다른 간선은 원래 rect 사용 불가로 향하며 반환 tuple 사용 가능과 원본 사용 불가를 동시에 구분한다. 이 Rectangle은 Copy 미구현이고 치수의 곱은 본문의 작은 값 범위다. 세로 도표도 같은 전환과 독립 결과를 보존하며 tuple에서 원본 무효 노드로 시간 간선을 추가하지 않는다. |
| 노드·연결선 라벨 | 넓이 9 · rect는 3 × 3 |
| 노드·연결선 라벨 | 가변 대여: rect 6 × 6 |
| 노드·연결선 라벨 | 넓이 36 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 생성·읽기 square(3)으로 만든 rect를 공유 메서드 area로 읽는다. |
|
|
| 가변 갱신 enlarge(2)가 같은 rect의 치수를 늘린다. |
|
|
| 값 소비 into_dimensions가 self를 소비해 튜플을 준다. |
|
|
메서드 수신자는 대여·수정·소비 계약을 정한다
본문의 별도 완전 예 square(3)→area 9에서 3×3 유지→enlarge(2)에서 6×6/36→into_dimensions 소비로 tuple 6,6의 동일 인스턴스 상태가 명시된다. 반환 튜플 사용 가능과 원 rect moved를 함께 보여 줄 가치가 높다.
RECEIVER CONTRACT · OWNERSHIP
수신자 형태가 읽기·변경·소유권 전달을 정한다
첫 매개변수의 형태는 단순한 표기 차이가 아닙니다. &self, &mut self, self가 메서드의 접근 권한과 호출 뒤 값의 상태를 정합니다.
| # | 수신자와 허용되는 동작 | 호출자에게 남는 결과 |
|---|---|---|
| 1 | 공유 대여 ·
|
호출자가 계속 소유합니다. area의 u32 필드 읽기는 Copy라 값을 복사하지만, Copy가 아닌 필드는 공유 참조 밖으로 이동할 수 없습니다. |
| 2 | 가변 대여 ·
|
소유권은 호출자에게 남습니다. 다만 수신자를 가변으로 빌릴 수 있어야 하고, 호출과 충돌하는 다른 대여가 없어야 합니다. |
| 3 | 값으로 받기 ·
|
Copy가 아닌 이 절의 Rectangle은 이동되어 원래 바인딩을 다시 쓸 수 없습니다. 수신자 타입이 Copy라면 복사본이 전달되어 원본도 유효합니다. |
&self· 공유 대여- 읽기 중심 계약입니다. 호출자가 계속 소유하며, 이 예제의
u32필드는Copy라 값이 복사됩니다.Copy가 아닌 필드는 공유 참조 밖으로 이동할 수 없습니다. &mut self· 가변 대여- 읽고 쓸 수 있지만 독점 대여입니다. 수신자를 가변으로 빌릴 수 있고 충돌하는 대여가 없어야 합니다.
self· 값으로 받기Copy가 아닌 값은 이동합니다.Copy타입이라면 복사본이 전달되어 원본도 계속 쓸 수 있습니다.- 추가 매개변수는 별도 계약
can_hold의other:&Rectangle처럼self뒤의 매개변수는 일반 함수 매개변수와 같습니다.
rect1.can_hold(&rect2)의 추가 인수 &rect2는 호출자가 명시합니다.이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 7노드와 6간선이다. 새 본문의 별도 완전한 예인 square 3→rect 3,3→공유 area 반환 9와 원래 상태 유지→가변 enlarge 2로 rect 6,6과 넓이 36→값 self를 받는 into_dimensions 소비→반환 tuple 6,6의 흐름이다. 소비 노드의 다른 간선은 원래 rect 사용 불가로 향하며 반환 tuple 사용 가능과 원본 사용 불가를 동시에 구분한다. 이 Rectangle은 Copy 미구현이고 치수의 곱은 본문의 작은 값 범위다. 세로 도표도 같은 전환과 독립 결과를 보존하며 tuple에서 원본 무효 노드로 시간 간선을 추가하지 않는다. |
| 노드·연결선 라벨 | 넓이 9 · rect는 3 × 3 |
| 노드·연결선 라벨 | 가변 대여: rect 6 × 6 |
| 노드·연결선 라벨 | 넓이 36 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 생성·읽기 square(3)으로 만든 rect를 공유 메서드 area로 읽는다. |
|
|
| 가변 갱신 enlarge(2)가 같은 rect의 치수를 늘린다. |
|
|
| 값 소비 into_dimensions가 self를 소비해 튜플을 준다. |
|
|
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn square(size: u32) -> Self {
Self { width: size, height: size }
}
fn area(&self) -> u32 {
self.width * self.height
}
fn enlarge(&mut self, factor: u32) {
self.width *= factor;
self.height *= factor;
}
fn into_dimensions(self) -> (u32, u32) {
(self.width, self.height)
}
}
fn main() {
let mut rect = Rectangle::square(3);
println!("{}", rect.area());
rect.enlarge(2);
println!("{}", rect.area());
let dimensions = rect.into_dimensions();
println!("{} {}", dimensions.0, dimensions.1);
}9
36
6 6square(3)이 3 × 3 인스턴스를 만들고, &self를 받는 area는 빌려 읽기만 하므로 넓이 9를 돌려준 뒤에도 rect는 그대로입니다. &mut self를 받는 enlarge는 같은 rect를 가변으로 빌려 6 × 6으로 고치며, 그래서 rect를 let mut으로 선언했습니다. 두 호출 뒤에도 rect의 소유권은 main에 있습니다. self를 받는 into_dimensions는 rect를 소비해 치수의 튜플을 돌려주므로, 그 뒤로는 rect를 쓸 수 없고 돌려받은 dimensions를 씁니다.
정리
- 메서드는
impl블록 안에 정의하며, 첫 매개변수self가 메서드를 부른 인스턴스를 나타냅니다. &self는 읽기만,&mut self는 고치기,self는 소유권을 가져와 소비하기를 뜻하며,Self는 그impl의 타입입니다.- 메서드를 부를 때 러스트는 받는 쪽에
&,&mut,*를 자동으로 붙이므로->연산자가 필요 없지만, 다른 인수는 직접 빌려 넘깁니다. - 메서드는
self뒤에 매개변수를 더 가질 수 있습니다. self가 없는 연관 함수는타입::함수()로 부르며, 주로new같은 생성자로 씁니다.- 한 타입에
impl블록을 여러 개 둘 수 있으며, 모두 같은 타입의 API입니다.
연습 문제
1. 들어가는 사각형
30 × 50인 rect1에 대해 (가) 30 × 20, (나) 40 × 20, (다) 20 × 40인 사각형을 can_hold로 각각 검사하면 결과는 무엇인가요? 넓이가 더 작은 (나)가 들어가지 않는 까닭은 무엇인가요?
해설 보기
(가)는 false, (나)는 false, (다)는 true입니다. (가)는 너비가 30으로 같아 30 > 30이 거짓이고, (나)는 너비 40이 rect1의 30보다 넓어 거짓입니다. (다)는 너비와 높이가 각각 더 작으므로 참입니다. (나)의 넓이 800은 rect1의 1500보다 작지만, can_hold는 넓이가 아니라 너비와 높이를 따로 비교하므로 들어가지 않는 것으로 판단합니다.
2. 세 형태를 바꿔 보기
self의 세 가지 형태 예제에서 square의 인수를 4로, enlarge의 배수를 3으로 바꾸면 세 줄의 출력은 무엇인가요? 첫 줄의 mut만 지우면 어디서 컴파일이 멈추나요? into_dimensions 뒤에 rect.area()를 부르면 어떻게 되나요?
해설 보기
4 × 4의 넓이 16, 12 × 12의 넓이 144, 치수 12 12를 출력합니다. mut을 지우면 불변인 rect를 enlarge가 가변으로 빌릴 수 없으므로, rect.enlarge(3)에서 E0596 오류가 납니다. into_dimensions가 rect를 소비한 뒤에 rect.area()를 부르면 이동한 값을 쓰는 것이므로 E0382 오류가 납니다.
3. 메서드인가 연관 함수인가
다음 가운데 rect.이름()으로 부르는 것과 Rectangle::이름()으로 부르는 것을 고르세요. (가) fn new(width: u32, height: u32) -> Self (나) fn perimeter(&self) -> u32 (다) fn rotate(&mut self)
해설 보기
(가)는 self를 받지 않는 연관 함수이므로 Rectangle::new(30, 50)처럼 타입 이름으로 부릅니다. (나)와 (다)는 self를 받는 메서드이므로 rect.perimeter(), rect.rotate()처럼 인스턴스로 부르며, (다)는 인스턴스를 고치므로 rect가 가변이어야 합니다.
이것으로 4장을 마칩니다. 구조체로 뜻 있는 타입을 만들고 impl 블록에 그 타입의 동작을 모았으며, 다음 장의 열거형 정의에서는 사용자 정의 타입을 만드는 또 다른 방법인 열거형을 살펴봅니다.