홈강좌Rust 입문·중급 47편 중 23편째
Rust 메서드 — impl 블록에 구조체의 함수를 단다
Rust 메서드는 impl 타입 { } 블록 안에 만드는, 구조체에 딸린 함수다. 첫 매개변수가 &self 면 읽기만 하고, &mut self 면 고친다. 고치는 메서드를 mut 아닌 변수에 부르면 E0596 이 난다. self 가 없는 함수는 Rect::new 처럼 부르고, 새 값을 만들 때 쓴다.
01 impl 블록에 메서드를 만든다
사각형 구조체에 넓이를 구하는 메서드를 달아 보자.
struct Rect {
width: u32,
height: u32,
}
impl Rect {
fn area(&self) -> u32 {
self.width * self.height
}
fn is_square(&self) -> bool {
self.width == self.height
}
}
fn main() {
let r = Rect { width: 30, height: 20 };
println!("area: {}", r.area());
println!("square: {}", r.is_square());
} Compiling rect v0.1.0 (~/projects/rect)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/rect`
area: 600
square: falseimpl Rect { … }는 "Rect에 딸린 함수들" 을 담는 블록이다.- 메서드의 첫 매개변수
&self는 "이 메서드를 부른 그 사각형" 을 빌린 것이다.&self는self: &Rect를 줄여 쓴 것이라, 18편의 읽기 참조와 같다. - 안에서는
self.width처럼 칸을 읽는다. - 부를 때는
r.area()처럼 점 뒤에 쓴다.&self자리에r이 저절로 들어간다. 9편의scores.len()이 이런 메서드였다.
함수 area(r: &Rect) 로 만들어도 같은 일을 한다. 메서드로 두면 "사각형이 할 수 있는 일" 이 impl Rect 한곳에 모여서 찾기 쉽다.
02 고치는 메서드는 &mut self
크기를 두 배로 키우는 메서드는 칸을 고친다. 그래서 &mut self 다. 그런데 mut 가 아닌 변수에 불러 보면 막힌다.
struct Rect {
width: u32,
height: u32,
}
impl Rect {
fn double(&mut self) {
self.width = self.width * 2;
self.height = self.height * 2;
}
}
fn main() {
let r = Rect { width: 30, height: 20 };
r.double();
println!("{} x {}", r.width, r.height);
}error[E0596]: cannot borrow `r` as mutable, as it is not declared as mutable
--> src/main.rs:15:5
|
15 | r.double();
| ^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
14 | let mut r = Rect { width: 30, height: 20 };
| +++- 무엇 —
r을 고칠 수 있게(mutable) 빌릴 수 없다.mut로 선언하지 않았기 때문이다. - 어디 — 15번째 줄의
r.double().double이&mut self라서, 부르는 순간r을&mut로 빌린다. - 고치는 법 —
help:가 14번째 줄을let mut r로 바꾸라고 한다.
19편의 규칙이 그대로다. 바꿀 수 없는 값을 고치라고 빌려줄 수는 없다.
| 첫 매개변수 | 뜻 | 부르는 변수 |
|---|---|---|
&self | 읽기만 한다 | let 이면 된다 |
&mut self | 칸을 고친다 | let mut 이어야 한다 |
03 실습 — self 없는 함수로 만들기
impl 안의 함수가 self 를 받지 않으면, 값 없이 타입 이름으로 부른다. 새 값을 만드는 함수가 대개 이렇다.
struct Rect {
width: u32,
height: u32,
}
impl Rect {
fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
fn square(size: u32) -> Self {
Self { width: size, height: size }
}
fn double(&mut self) {
self.width = self.width * 2;
self.height = self.height * 2;
}
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let mut r = Rect::new(30, 20);
r.double();
let s = Rect::square(10);
println!("{} x {} = {}", r.width, r.height, r.area());
println!("square area: {}", s.area());
} Compiling rect v0.1.0 (~/projects/rect)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.15s
Running `target/debug/rect`
60 x 40 = 2400
square area: 100Rect::new(30, 20)은Rect에 딸린new를 부른다. 5편의i8::MAX처럼::는 "그 타입에 딸린 것" 이다. 15편부터 쓴String::from도 이런 함수다.-> Self의Self는impl하는 그 타입, 여기서는Rect를 가리킨다. 이름이 바뀌어도impl안은 고칠 필요가 없다.Self { width, height }는 22편에서 본 줄여 쓰기다.
new 라는 이름은 정해진 규칙이 아니라 관례다. 정사각형을 만드는 square 처럼 뜻이 드러나는 이름을 더 만들어도 된다. Rect 에 둘레를 구하는 perimeter(&self) -> u32 를 더해 보자. 다음 편은 "정해진 몇 가지 중 하나" 를 나타내는 열거형이다.