메소드 문법
메소드는 함수와 유사하다.fn키워드와 메소드 이름으로 선언되고, 파라미터와 리턴값을 가지고 있다.
함수와의 차이점은 크게 두가지이다.
- 메소드는 구조체의 내용 안에 정의된다.
- 첫번 째 파라미터는 항상
self이다.
여기서self는 구조체의 인스턴스를 가리키고 있다.
메소드를 정의하기
앞에서 공부했던 Rectangle 구조체를 재활용해 area 메소드를 만들어 보자.
이렇게 메소드로 구현하는 것이 아마도 함수로 구현하는 것보다 더 우아할 것이다!
#[derive(Debug)]
struct Rectangle {
length: u32,
width: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.length * self.width
}
}
fn main() {
let rectangle = Rectangle {
length: 50,
width: 50
};
println!("Square is {:#?}", rectangle.area());
}
Compiling rust v0.1.0 (/Users/seunghye/Documents/juneun/rust)
Finished dev [unoptimized + debuginfo] target(s) in 1.08s
Running `target/debug/rust`
Square is 2500
먼저 impl (implementation - 구현) 이라는 키워드가 눈에 띈다.impl 키워드의 이름으로는 Rectangle로 구조체의 이름이 사용되었다.
그래서 단순히 해석해 보자면 Rectangle 구조체의 구현이 되겠다.
그리고 impl 키워드의 scope안쪽에 area 메소드가 자리잡고 있다.
앞서 설명했듯이 첫번째 파라미터로 self가 들어가는데, 이건 Rectangle 구조체의 인스턴스를 의미한다.
그래서 self + . 을 이용해 해당 구조체 인스턴스의 필드값들에 접근할 수 있다.area 메소드에서는 width 와 lenght에 접근해 넓이를 구했다.
여기서 중요한점은 self 에도 &가 사용 되었다는 점이다.
만약 &를 사용하지 않는다면, 구조체 인스턴스의 소유권이 area 메소드로 이동하기 때문에 메소드가 종료된 이후 인스턴스가 할당 해제된다.
따라서 구조체 인스턴스를 계속 쓰고싶다면 반드시 &를 붙여주어야 한다.
또한, 구조체 필드의 값을 변경하고 싶다면 &mut self로 사용해야 한다.
역시 기본에 충실해야 한다!
그럼 왜 함수 대신 메소드를 사용하는 걸까?
그건 코드를 조금 더 조직화해서 사용하기 위함이다.
지금은 작고 보잘 것 없는 예제지만, 큰 프로그램을 작성할 경우 수많은 코드 중에서 함수를 찾고 사용하는 것 보다 Rectangle 구조체의 인스턴스가 사용할 수 있는 메소드를 모아 놓는것이 더 편리하다.
또한 구조체의 필드값을 함수로 전달하기 위해 일일이 작성하는 것 보다 self를 통해 편하게 접근하는게 가능해 진다.
더 많은 파라미터를 가진 메소드
이제 Rectangle의 두번째 메소드를 구현해 보자.
이번에 구현할 메소드는 Rectangle의 인스턴스가 다른 Rectangle의 인스턴스를 포함할 수 있는지 확인하는 메소드이다.
이름은 can_hold로 하자.
#[derive(Debug)]
struct Rectangle {
length: u32,
width: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.length * self.width
}
fn can_hold(&self, rect: &Rectangle) -> bool {
self.length >= rect.length && self.width >= rect.width
}
}
fn main() {
let rectangle1 = Rectangle {
length: 50,
width: 50
};
let rectangle2 = Rectangle {
length: 100,
width: 50
};
let rectangle3 = Rectangle {
length: 40,
width: 40
};
println!("Rectangle1 can hold Rectangle2 {}", rectangle1.can_hold(&rectangle2));
println!("Rectangle1 can hold Rectangle3 {}", rectangle1.can_hold(&rectangle3));
}
Rectangle1 can hold Rectangle2 false
Rectangle1 can hold Rectangle3 true
1, 2, 3 을 선언했다.
코드를 보면 1은 2를 안에 넣을 수 없지만 3은 안에 넣을 수 있다.
결과값도 false, true로 정상적으로 출력 되었다.
여기서도 소유권을 넘기지 않고 빌림하는 것을 알 수 있다.
빌린 다음 쓸 필요는 없기 때문에 mut 키워드는 사용하지 않았다.
이제 크게 어렵지 않다! 예제를 보지 않고도 작성이 가능하다!
연관함수
impl 블록에는 self를 갖지 않는 함수도 선언할 수 있다.
그렇다 함수 인것에 주목할 필요가 있다. 왜냐면 self를 사용하지 않기 때문이다.
이를 associated functions, 연관함수라고 부르는데, 이는 구조체의 인스턴스를 인자로 받지 않기 때문에 메소드라고 부르지 않는다.
생소해 보일 수 있겠지만, 앞에서 습관처럼 사용했던 String:;from()이 바로 연관함수이다.
연관 함수는 새로운 구조체의 인스턴스를 리턴해 주는 생성자로 주로 사용된다.
예를들어, 우리가 정사각형의 인스턴스를 생성하고 싶다면, 굳이 가로 세로 길이를 적을 필요는 없을 것이다.
러스트에선 연관 함수가 이를 가능하게 해 준다!
struct Rectangle {
length: u32,
width: u32,
}
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {length: size, width: size}
}
}
fn main() {
let rectangle = Rectangle::square(100);
println!("square width: {} / length: {}", rectangle.width, rectangle.length);
}
square width: 100 / length: 100
연관 함수를 호출하기 위해선 :: 문법을 이용한다.
알다시피, :: 문법은 네임스페이스에서 사용된다.
따라서, Rectangle::square() 라는 코드는, Rectangle 구조체 네임스페이스에 있는 square 연관 함수를 호출하라! 라는 코드가 된다.
이런 네임스페이스 문법은 뒤에서 더 공부해 보자.
정리
구조체는 프로그래머가 원하는 데이터 타입을 만들수 있게 해준다.
러스트에서는 여기에 메소드, 연관 함수 같은 기능들을 통해 그 구조체를 가공하고 다루는데 도움을 준다.
하지만 언제나 구조체가 최선이 되는건 아니다.
러스트는 enum (열거형)을 통해 또 다른 많은 기능들을 제공하니 한번 공부해 보도록 하자
'⚙️ Rust > 👀 Tutorial' 카테고리의 다른 글
| [Rust / 튜토리얼] 15. 러스트에서 match 를 이용한 분기. (0) | 2022.06.06 |
|---|---|
| [Rust / 튜토리얼] 13. 러스트의 열거형과 Option, None 그리고 Some (0) | 2022.06.03 |
| [Rust / 튜토리얼] 12. 러스트 구조체를 이용한 예제 프로그래밍 (0) | 2022.05.29 |
| [Rust / 튜토리얼] 11. 러스트에서 구조체를 정의하고 생성하기 (0) | 2022.05.27 |
| [Rust / 튜토리얼] 10. 러스트의 슬라이스 (slice) (0) | 2022.05.25 |