오늘은 Rust의 메서드(method)에 대해 알아보고자 합니다.
메서드 문법
메서드(method)는 함수와 유사합니다.
fn 키워드와 함수명으로 선언하고, 매개변수와 반환값을 가지며, 다른 어딘가로부터 호출될 때 실행되지만, 함수와 달리 구조체 컨텍스트에 정의되고, 첫 번째 매개변수가 항상 self라는 차이점이 있습니다.
메서드 정의하기
#[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()
);
}
Rectangle의 컨텍스트에 함수를 정의하기 위해, Rectangle에 대한 impl(implementation, 구현) 블록을 만듭니다.
메서드 문법(method syntax)을 이용해서 Rectangle 인스턴스의 area 메서드를 호출할 수 있습니다.
area 시그니처에서는 rectangle: &Rectangle 대신 self:&Self를 줄인 &self를 사용했습니다.
rectangle: &Rectangle에서 그랬던 것처럼 Self 메서드가 Self의 인스턴스를 빌려온다는 것을 나타내기 위해서는 self 축약형 앞에 &를 계속 붙여둘 필요가 있습니다.
메서드는 다른 매개변수가 그러는 것처럼 self의 소유권을 가져올 수도, 지금처럼 self를 불변으로 빌려올 수도, 가변으로 빌려올 수도 있습니다.
만약 메서드에서 작업 중 호출한 인스턴스를 변경하고 싶다면 &mut self를 사용하면 됩니다.
이러한 기법은 보통 해당 메서드가 self를 다른 무언가로 변환하고, 그 이후에는 원본 인스턴스의 사용을 막고자 할 떄 사용됩니다.
함수 대신 메서드를 사용하는 주된 이유는 메서드 구문을 제공하고 모든 메서드 기스니처 내에서 self 타입을 반복할 필요가 없다는 것외에도 코드를 더 조직적으로 만들기 위해서입니다.
Rectangle의 기능과 관련된 코드를 라이브러리 곳곳에서 찾아내야 하는 것보다는, 하나의 impl 블록 내에 이 타입의 인스턴스로 할 수 있는 모든 것들을 모아둔 것입니다.
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);
}
}
여기서는 인스턴스의 width 필드가 0보다 크면 true를 반환하고 0이면 false를 반환하는 메서드의 이름으로 width를 사용했습니다.
main에서 rect1.width 뒤에 괄호를 붙여 width()을 만들면 rust는 width 메서드를 의도한다는 것을 인지합니다.
괄호를 사용하지 않으면 rust는 width 필드를 의미한다는 것으로 봅니다.
필드와 동일한 이름의 메서드를 만드는 경우는 해당 필드의 값을 얻어오는 것 말고는 안하는 경우가 대부분입니다.
이러한 메서드를 게터(getter)라고 부르는데, rust는 다른 언어들처럼 구조체 필드에 대한 게터를 자동으로 만들지 않습니다.
더 많은 매개변수를 가진 메서드
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
}
}
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));
}
이처럼 메서드는 self 매개변수 뒤에 여러 매개변수를 가질 수 있으며, 이 매개변수는 함수에서의 매개변수와 동일하게 기능합니다.
연관 함수
impl 블록 내에 구현된 모든 함수를 연관 함수(associated function) 이라고 부르는데, 이는 impl 뒤에 나오는 타입과 모두 연관된 함수이기 때문입니다.
메서드가 아닌 연관 함수는 구조체의 새 인스턴스를 반환하는 생성자로 자주 활용됩니다.
이 함수들은 new라고 명명되는데, new는 이 언어에서 특별한 이름 혹은 키워드가 아닙니다.
생성자의 예시로 width와 heigth가 같은 정사각형을 만들 때, width와 height를 두 번 지정하지 않고 차수 하나를 매개변수로 받아서 너비와 높이를 설정하는 함수를 만들 수 있습니다.
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
해당 연관 함수를 호출할 때는 let sq = Rectangle::square(3);처럼 구조체 명에 :: 구문을 붙여서 호출합니다.
여러 개의 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
}
}
impl 블록을 여러 개로 나눠야 할 이유는 전혀 없지만, impl 블록을 반드시 하나만 작성해야할 필요는 없습니다.
정리
- impl 블록을 활용하여 구조체 타입에 대한 연관 함수들을 만들 수 있습니다.
- self를 활용하여 특정 구조체의 값을 활용할 수 있습니다.
- 특정 구조체에 대한 impl은 여러개 만들어서 활용할 수 있습니다.
'Rust' 카테고리의 다른 글
Rust 설치부터 실행까지 (match, if let) - 14 (0) | 2025.01.12 |
---|---|
Rust 설치부터 실행까지 (열거형, Option) - 13 (0) | 2025.01.12 |
Rust 설치부터 실행까지 (구조체, 디버깅) - 11 (0) | 2025.01.07 |
Rust 설치부터 실행까지 (슬라이스) - 10 (0) | 2025.01.06 |
Rust 설치부터 실행까지 (참조자) - 9 (0) | 2025.01.05 |
댓글