메서드란? 함수와 유사한 개념으로 fn 키워드와 함수명으로 선언하고 매개변수화 반환값을 가집니다.
메서드 문법
메서드는 다른 어딘가로부터 호출될 때 실행됩니다. 하지만 메서드는 함수와 달리 구조체 컨텍스트에 정의되고, 첫번째 매개변수는 메서드를 호출하고 있는 구조체 인스턴스를 나타냅니다.
메서드 정의하기
우선 함수를 구조체에 정의된 메서드로 변환해봅시다.
Rect 컨텍스트에 함수를 정의하기 위해서 impl 블록을 만듭니다.
// 명시적 동의
#[derive(Debug)]
struct Rect {
width: u32,
height: u32
}
impl Rect {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rect{
width:30,
height:50
};
println!(
"Result: {:#?}", rect1);
}
area 시그니처를 보면 &self라는 매개변수를 가지고 있는데 시그니처의 &self는 사실 self: &Self의 축약형입니다.
- &self: self: &Self의 축약형으로, 구조체 인스턴스를 불변 참조로 가져옵니다.
- Self: impl 블록 내에서 해당 타입(예: Rect)의 별칭입니다.
- 따라서, Rect를 참조하려면 &Self(불변 참조) 또는 Self(소유권 전달)를 사용할 수 있습니다.
이런 특징을 가지고 있습니다. 여기서는 불변참조로 Rect의 소유권을 가져오지 않고 값만 사용하기 위해 &self로 사용합니다.
물론 소유권을 가져올 필요가 있으면 self로 써도 됩니다.
하지만 지금은 데이터를 쓰는것도 아니고 단순히 읽고 연산한 결과를 반환하기 때문에 소유권을 가져올 필요가 없어서 불변참조 형태로 매개변수를 지정했습니다.
그냥 함수로 만들어서 써도 되는거 아닌가?
이런 생각이 들 수도 있습니다. 함수 대신 메서드를 사용하는 주된 이유는 아래와 같습니다
- 코드를 구조화
- 관련 데이터와 동작을 논리적으로 묶는다
- 중복을 제거하며, 관련 동작을 한곳에서 관리 가능하게 하고.
- 데이터 중심적 설계로 직관적이고 유지보수가 용이한 코드를 작성할 수 있기 때문입니다.
우리가 만든 라이브러리를 나중에 누군가 쓴다고 가정해보겠습니다.
이때 관련 코드를 라이브러리 곳곳에서 찾아내야 하는 것 보다는 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);
}
}
메서드 활용
해당 구조체에 새로운 메서드를 하나 더 만들어 보겠습니다. 이 메서드는 두개의 매개변수를 갖습니다.
첫번째 매개변수는 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
}
}
아래와 같은 방법으로 사용합니다
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));
}
여기서는 rect1에서 can_hold 메서드를 호출 했지만 Rectangle 구조체에 can_hold 메서드가 구현되어있기 때문에 rect1,rect2,rect3 모두 can_hold 메서드를 호출 할 수 있습니다. 이런식으로 구조화된 코드를 작성 할 수 있어 유지보수도 편할 것 같습니다.
연관함수
impl 블록 내에 구현된 모든 함수를 연관 함수 (associated function)라고 부릅니다. 이는 impl 뒤에 나오는 타입 ( 이글에서는 Rectangle )과 모두 연관된 함수 이기 때문입니다. 만약 이 연관함수가 동작할 때 해당 타입의 인스턴스가 필요하지 않다면 첫 매개변수를 self로 지정하지 않아도 됩니다.
이 경우 연관함수는 구조체의 새 인스턴스를 반환하는 생성자로 자주 활용 됩니다.
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
함수의 반환 타입 Self 키워드는 impl 키워드 뒤에 있는 Rectangle 타입의 별칭 입니다. 연관 함수를 호출할 땐 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
}
}
자 이렇게 구조체를 사용하여 메서드를 구현하고 이를 구조체 내의 네임스페이스에서 호출 하면 서로 관련 있는 데이터를 하나로 묶어서 관리할 수가 있습니다. 각 데이터 조각에 이름을 붙혀 코드를 더 명확하게 만들 수 있고 도메인별로 명확하게 분리할 수 있습니다.
'Rust' 카테고리의 다른 글
Rust 기본개념 - 열거형의 활용 (0) | 2024.12.09 |
---|---|
Rust 기본개념 - 열거형 (0) | 2024.12.08 |
Rust 기본 개념 - 구조체 (2) | 2024.12.07 |
Rust 기본개념 - 슬라이스 (0) | 2024.12.04 |
Rust 기본 개념 - 참조 및 빌림 (0) | 2024.05.15 |