소개
**Method Syntax (메서드 구문)**에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서 메서드는 fn 키워드와 이름을 사용하여 선언되며, 매개변수와 반환 값을 가질 수 있습니다. 메서드는 구조체 (struct) 의 컨텍스트 내에서 정의되며, 첫 번째 매개변수는 항상 호출되는 구조체의 인스턴스를 나타내는 self입니다.
**Method Syntax (메서드 구문)**에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서 메서드는 fn 키워드와 이름을 사용하여 선언되며, 매개변수와 반환 값을 가질 수 있습니다. 메서드는 구조체 (struct) 의 컨텍스트 내에서 정의되며, 첫 번째 매개변수는 항상 호출되는 구조체의 인스턴스를 나타내는 self입니다.
*Methods (메서드)*는 함수와 유사합니다. fn 키워드와 이름을 사용하여 선언하고, 매개변수와 반환 값을 가질 수 있으며, 메서드가 다른 곳에서 호출될 때 실행되는 코드를 포함합니다. 함수와 달리 메서드는 구조체 (struct) (또는 챕터 6 과 챕터 17 에서 각각 다루는 열거형 (enum) 또는 트레이트 객체 (trait object)) 의 컨텍스트 내에서 정의되며, 첫 번째 매개변수는 항상 self로, 메서드가 호출되는 구조체의 인스턴스를 나타냅니다.
Listing 5-13 에 나와 있는 것처럼, Rectangle 인스턴스를 매개변수로 받는 area 함수를 변경하여 Rectangle 구조체에 정의된 area 메서드를 만들어 보겠습니다.
파일 이름: src/main.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
1 impl Rectangle {
2 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.",
3 rect1.area()
);
}
Listing 5-13: Rectangle 구조체에 area 메서드 정의하기
Rectangle의 컨텍스트 내에서 함수를 정의하려면, Rectangle에 대한 impl (구현) 블록을 시작합니다 [1]. 이 impl 블록 내의 모든 것은 Rectangle 타입과 연관됩니다. 그런 다음 area 함수를 impl 중괄호 내로 옮기고 [2], 첫 번째 (그리고 이 경우 유일한) 매개변수를 시그니처와 본문 내 모든 곳에서 self로 변경합니다. main에서 area 함수를 호출하고 rect1을 인수로 전달했던 곳에서, 대신 메서드 구문을 사용하여 Rectangle 인스턴스에 대한 area 메서드를 호출할 수 있습니다 [3]. 메서드 구문은 인스턴스 뒤에 옵니다: 점 (.) 을 추가하고, 그 뒤에 메서드 이름, 괄호, 그리고 모든 인수를 추가합니다.
area의 시그니처에서 rectangle: &Rectangle 대신 &self를 사용합니다. &self는 실제로 self: &Self의 축약형입니다. impl 블록 내에서 Self 타입은 impl 블록이 속한 타입의 별칭입니다. 메서드는 첫 번째 매개변수로 Self 타입의 self라는 매개변수를 가져야 하므로, Rust 는 첫 번째 매개변수 위치에서 이름 self만 사용하여 이를 줄여 쓸 수 있도록 합니다. 여전히 rectangle: &Rectangle에서 했던 것처럼, 이 메서드가 Self 인스턴스를 빌린다는 것을 나타내기 위해 self 축약형 앞에 &를 사용해야 합니다. 메서드는 다른 매개변수와 마찬가지로 self의 소유권을 가져오거나, 불변으로 self를 빌리거나, 가변으로 self를 빌릴 수 있습니다.
함수 버전에서 &Rectangle을 사용했던 것과 같은 이유로 여기에서 &self를 선택했습니다: 소유권을 가져오고 싶지 않고, 구조체의 데이터를 읽기만 원하며, 쓰기는 원하지 않습니다. 메서드가 수행하는 작업의 일부로 메서드를 호출한 인스턴스를 변경하려면, 첫 번째 매개변수로 &mut self를 사용합니다. 첫 번째 매개변수로 self만 사용하여 인스턴스의 소유권을 가져가는 메서드는 드뭅니다; 이 기술은 일반적으로 메서드가 self를 다른 것으로 변환하고 변환 후 호출자가 원래 인스턴스를 사용하지 못하도록 하려는 경우에 사용됩니다.
메서드 구문을 제공하고 모든 메서드의 시그니처에서 self의 타입을 반복할 필요가 없다는 점 외에도, 메서드를 사용하는 주된 이유는 조직화입니다. 우리는 타입의 인스턴스로 할 수 있는 모든 것을 하나의 impl 블록에 넣었습니다. 이는 코드의 미래 사용자가 우리가 제공하는 라이브러리의 여러 곳에서 Rectangle의 기능을 검색하는 것을 방지합니다.
구조체의 필드와 동일한 이름을 가진 메서드를 지정할 수 있다는 점에 유의하십시오. 예를 들어, width라는 Rectangle에 대한 메서드를 정의할 수 있습니다.
파일 이름: src/main.rs
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 뒤에 괄호를 붙이면, Rust 는 메서드 width를 의미한다는 것을 압니다. 괄호를 사용하지 않으면, Rust 는 필드 width를 의미한다는 것을 압니다.
종종, 항상 그런 것은 아니지만, 필드와 동일한 이름을 가진 메서드를 제공할 때, 필드의 값만 반환하고 다른 작업은 수행하지 않도록 합니다. 이러한 메서드를 *getter (게터)*라고 하며, Rust 는 다른 일부 언어처럼 구조체 필드에 대해 자동으로 구현하지 않습니다. 게터는 필드를 private (비공개) 로 만들고 메서드를 public (공개) 으로 만들 수 있으므로, 해당 필드에 대한 읽기 전용 액세스를 타입의 public API 의 일부로 활성화할 수 있기 때문에 유용합니다. 챕터 7 에서 public 과 private 이 무엇인지, 그리고 필드 또는 메서드를 public 또는 private 으로 지정하는 방법에 대해 논의할 것입니다.
-> 연산자는 어디에 있습니까?
C 와 C++ 에서는 메서드를 호출하는 데 두 개의 다른 연산자가 사용됩니다: 객체에서 직접 메서드를 호출하는 경우
.을 사용하고, 객체에 대한 포인터에서 메서드를 호출하고 먼저 포인터를 역참조해야 하는 경우->를 사용합니다. 즉,object가 포인터인 경우,object->something()은(*object).something()과 유사합니다.Rust 에는
->연산자에 해당하는 것이 없습니다; 대신, Rust 에는 자동 참조 및 역참조라는 기능이 있습니다. 메서드 호출은 Rust 에서 이 동작을 갖는 몇 안 되는 곳 중 하나입니다.작동 방식은 다음과 같습니다:
object.something()으로 메서드를 호출하면, Rust 는 자동으로&,&mut, 또는*를 추가하여object가 메서드의 시그니처와 일치하도록 합니다. 즉, 다음은 동일합니다:p1.distance(&p2); (&p1).distance(&p2);첫 번째는 훨씬 더 깔끔해 보입니다. 이 자동 참조 동작은 메서드가 명확한 수신자 (receiver)---
self의 타입---를 갖기 때문에 작동합니다. 수신자와 메서드 이름이 주어지면, Rust 는 메서드가 읽기 (&self), 변경 (&mut self), 또는 소비 (self) 하는지 확실하게 파악할 수 있습니다. Rust 가 메서드 수신자에 대해 빌림을 암시적으로 만드는 사실은 실제로 소유권을 인체공학적으로 만드는 데 큰 부분을 차지합니다.
Rectangle 구조체에 두 번째 메서드를 구현하여 메서드 사용을 연습해 보겠습니다. 이번에는 Rectangle의 인스턴스가 다른 Rectangle의 인스턴스를 받아들이고 두 번째 Rectangle이 self (첫 번째 Rectangle) 내부에 완전히 들어갈 수 있으면 true를 반환하고, 그렇지 않으면 false를 반환하도록 합니다. 즉, can_hold 메서드를 정의하면 Listing 5-14 에 표시된 프로그램을 작성할 수 있습니다.
파일 이름: src/main.rs
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));
}
Listing 5-14: 아직 작성되지 않은 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를 전달하는데, 이는 Rectangle의 인스턴스인 rect2에 대한 불변 빌림입니다. rect2를 읽기만 하면 되기 때문에 (쓰는 것은 가변 빌림이 필요하므로) 말이 되고, main이 can_hold 메서드를 호출한 후에도 rect2의 소유권을 유지하여 다시 사용할 수 있도록 하려고 합니다. can_hold의 반환 값은 부울 (Boolean) 이 될 것이고, 구현은 self의 너비와 높이가 다른 Rectangle의 너비와 높이보다 각각 큰지 여부를 확인합니다. Listing 5-15 에 표시된 Listing 5-13 의 impl 블록에 새로운 can_hold 메서드를 추가해 보겠습니다.
파일 이름: src/main.rs
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
}
}
Listing 5-15: 다른 Rectangle 인스턴스를 매개변수로 받는 Rectangle에 can_hold 메서드 구현하기
Listing 5-14 의 main 함수로 이 코드를 실행하면 원하는 출력을 얻을 수 있습니다. 메서드는 self 매개변수 뒤의 시그니처에 추가하는 여러 매개변수를 가질 수 있으며, 이러한 매개변수는 함수의 매개변수와 마찬가지로 작동합니다.
impl 블록 내에 정의된 모든 함수는 *연관 함수 (associated functions)*라고 불립니다. 이는 impl 뒤에 이름이 지정된 타입과 연관되어 있기 때문입니다. 첫 번째 매개변수로 self를 갖지 않는 연관 함수 (따라서 메서드가 아님) 를 정의할 수 있습니다. 이러한 함수는 타입의 인스턴스를 사용할 필요가 없기 때문입니다. 우리는 이미 이와 같은 함수를 하나 사용했습니다: String 타입에 정의된 String::from 함수입니다.
메서드가 아닌 연관 함수는 종종 구조체의 새 인스턴스를 반환하는 생성자 (constructor) 로 사용됩니다. 이러한 함수는 종종 new라고 불리지만, new는 특별한 이름이 아니며 언어에 내장되어 있지 않습니다. 예를 들어, 하나의 차원 매개변수를 가지고 이를 너비와 높이로 모두 사용하여, 동일한 값을 두 번 지정할 필요 없이 정사각형 Rectangle을 더 쉽게 만들 수 있는 square라는 연관 함수를 제공하도록 선택할 수 있습니다.
파일 이름: src/main.rs
impl Rectangle {
fn square(size: u32) -> 1 Self {
2 Self {
width: size,
height: size,
}
}
}
반환 타입 [1]과 함수의 본문 [2]에 있는 Self 키워드는 이 경우 Rectangle인 impl 키워드 뒤에 나타나는 타입의 별칭입니다.
이 연관 함수를 호출하려면 구조체 이름과 함께 :: 구문을 사용합니다; let sq = Rectangle::square(3);가 그 예입니다. 이 함수는 구조체에 의해 네임스페이스화됩니다: :: 구문은 연관 함수와 모듈에 의해 생성된 네임스페이스 모두에 사용됩니다. 모듈에 대해서는 챕터 7 에서 논의할 것입니다.
각 구조체는 여러 개의 impl 블록을 가질 수 있습니다. 예를 들어, Listing 5-15 는 Listing 5-16 에 표시된 코드와 동일하며, 각 메서드가 자체 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
}
}
Listing 5-16: 여러 impl 블록을 사용하여 Listing 5-15 다시 작성하기
여기서는 이러한 메서드를 여러 impl 블록으로 분리할 이유가 없지만, 이것은 유효한 구문입니다. 제네릭 타입 (generic types) 과 트레이트 (traits) 에 대해 논의하는 챕터 10 에서 여러 impl 블록이 유용한 경우를 보게 될 것입니다.
축하합니다! 메서드 구문 (Method Syntax) 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.