Rust 메서드 구문 연습

Beginner

This tutorial is from open-source community. Access the source code

소개

**Method Syntax (메서드 구문)**에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.

이 랩에서 메서드는 fn 키워드와 이름을 사용하여 선언되며, 매개변수와 반환 값을 가질 수 있습니다. 메서드는 구조체 (struct) 의 컨텍스트 내에서 정의되며, 첫 번째 매개변수는 항상 호출되는 구조체의 인스턴스를 나타내는 self입니다.

Method Syntax (메서드 구문)

*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의 인스턴스를 받아들이고 두 번째 Rectangleself (첫 번째 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의 치수보다 작지만, rect3rect1보다 넓기 때문입니다.

Can rect1 hold rect2? true
Can rect1 hold rect3? false

메서드를 정의하려는 것을 알고 있으므로, impl Rectangle 블록 내에 있을 것입니다. 메서드 이름은 can_hold이고, 다른 Rectangle의 불변 빌림을 매개변수로 받습니다. 메서드를 호출하는 코드를 보면 매개변수의 타입을 알 수 있습니다: rect1.can_hold(&rect2)&rect2를 전달하는데, 이는 Rectangle의 인스턴스인 rect2에 대한 불변 빌림입니다. rect2를 읽기만 하면 되기 때문에 (쓰는 것은 가변 빌림이 필요하므로) 말이 되고, maincan_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 인스턴스를 매개변수로 받는 Rectanglecan_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 키워드는 이 경우 Rectangleimpl 키워드 뒤에 나타나는 타입의 별칭입니다.

이 연관 함수를 호출하려면 구조체 이름과 함께 :: 구문을 사용합니다; let sq = Rectangle::square(3);가 그 예입니다. 이 함수는 구조체에 의해 네임스페이스화됩니다: :: 구문은 연관 함수와 모듈에 의해 생성된 네임스페이스 모두에 사용됩니다. 모듈에 대해서는 챕터 7 에서 논의할 것입니다.

여러 impl 블록

각 구조체는 여러 개의 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 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.