구조체에 대한 자세한 사항이 공식문서와 교재에 워낙 잘 설명이 되어 있기 때문에, 해당 내용들을 참고하면서, 제 기준에서 기록과 함께 제대로 체화시켜야 되겠다 싶은 부분들 위주로 정리해볼까 합니다.
A struct (or structure) is a custom data type that lets you package together and name multiple related values that make up a meaningful group
→ 고려대학교 ‘시스템 프로그래밍’ 강의자료 중 발췌
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
User 라는 예시 구조체 정의
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
}
User 구조체의 인스턴스 생성
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
}
}
위 코드처럼 변수명과 구조체 필드명이 같으면, username: username, 의 형태처럼 두 번 작성할 필요 없이 한 번만 쓰면 OK!
fn main() {
// --생략--
let user2 = User {
email: String::from("another@example.com"),
..user1
};
}
.. 문법은 따로 명시된 필드를 제외한 나머지 필드를 주어진 인스턴스의 필드 값으로 설정합니다.= 를 이용한다는 점을 주목해야 합니다. 이로 인해 username처럼 String 데이터로 구조체를 업데이트하는 경우 소유권 이동이 일어날 수 있다는 점을 명심해야 합니다. Copy 트레이트를 구현한, 스택에만 저장되는 데이터 타입들은 마찬가지로 복사가 일어나므로 소유권에 대한 부분은 크게 신경쓰지 않아도 됩니다.메서드 (method) 는 함수와 유사합니다. fn 키워드와 함수명으로 선언하고, 매개변수와 반환 값을 가지며, 다른 어딘가로부터 호출될 때 실행됩니다. 하지만 메서드는 함수와 달리 구조체 컨텍스트에 정의되고 (열거형이나 트레이트 객체 안에 정의되기도 하며, 이는 각각 6장, 17장에서 알아보겠습니다), 첫 번째 매개변수가 항상 self 라는 차이점이 있습니다. self 매개변수는 메서드를 호출하고 있는 구조체 인스턴스를 나타냅니다.
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("The area of rect1 is {} square pixels", rect1.area());
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.height * rectangle.width
}
기존에 따로 함수로 만들었던 area 함수를 Rectangle 구조체의 메서드로 추가해준 코드입니다. 변경사항을 자세히 뜯어보자면..
impl 블록을 만들어 주었습니다. → area 함수를 블록 내부로 옮기고 함수 시그니처의 첫 번째 매개변수를 self로 고쳐 주었습니다.Rectangle 인스턴스의 area 메서드를 호출할 수 있습니다. 메서드 문법은 차례대로 인스턴스, 점, 메서드명, 괄호 및 인수로 구성됩니다.area 시그니처를 보면, rectangle: &Rectangle 대신 &self를 사용했습니다. &self는 실제로는 self: &Self를 줄인 것입니다. impl 블록 내에서 Self는 impl 블록의 대상이 되는 타입의 별칭입니다.self가 파라미터 이름이고, Self가 파라미터 타입이다 !!rectangle: &Rectangle에서 그랬던 것처럼, 이 메서드가 Self의 인스턴스를 빌려온다는 것을 나타내기 위해서는 self 축약형 앞에 &를 계속 붙여둘 필요가 있음을 주목하세요. 메서드는 다른 매개변수가 그런 것처럼 self의 소유권을 가져올 수도, 지금처럼 self를 불변으로 빌려올 수도, 가변으로 빌려올 수도 있습니다.제가 궁금했던 점은 바로 이 부분이었습니다. 이렇게 구조체의 메서드로 만들어버리면 그냥 따로 함수로 쓰는 것보다 뭔가 더 좋을 것 같긴 한데 … 그게 정확히 무엇일까? 라는 의문이 들었습니다.
교재에서 설명하는 내용은 아래와 같습니다:
함수 대신 메서드를 사용하는 주된 이유는 메서드 구문을 제공하고 모든 메서드 시그니처 내에서
self타입을 반복할 필요가 없다는 것 외에도 코드를 더 조직적으로 만들기 위해서입니다. 향후 우리가 제공한 라이브러리를 사용할 사람들이Rectangle의 기능과 관련된 코드를 라이브러리 곳곳에서 찾아내야 하는 것보다는, 하나의impl블록 내에 이 타입의 인스턴스로 할 수 있는 모든 것들을 모아두는 것이죠.
확실히, 결국 라이브러리의 형태로 코드를 제공해야 한다고 생각했을 때는, 별도의 함수로 뒀을 때보다 메서드로 만들어서 제공하는 것이 이해의 측면에서든, 활용의 측면에서든 훨씬 나을 것이라 짐작됩니다.
impl 블록 내에 구현된 모든 함수를 연관 함수 (associated function) 라고 부르는데, 이는 impl 뒤에 나오는 타입과 모두 연관된 함수이기 때문입니다. 동작하는 데 해당 타입의 인스턴스가 필요하지 않다면 self를 첫 매개변수로 갖지 않는 (따라서 메서드가 아닌) 연관 함수를 정의할 수도 있습니다. 우리는 이미 String 타입에 정의되어 있는 String::from 함수처럼 이런 종류의 함수를 사용해 봤습니다.
→ 교재에서 ‘연관 함수’라고 소개하는 챕터는 정확히는 ‘(메서드가 아닌) 연관 함수’ 라고 봐야 할 듯 합니다.. 예시를 살펴보겠습니다.
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
new라고 명명되는데, new는 이 언어에서 특별한 이름 혹은 키워드가 아닙니다.new로 지어주면, 다른 언어들과 비슷한 메커니즘으로 활용할 수 있을 것 같습니다!Rectangle로 정사각형을 만들 때 너비, 높이에 같은 값을 두 번 지정하지 않고 치수 하나를 매개변수로 받아서 해당 치수로 너비와 높이를 설정하는 연관 함수 square를 구현한 것입니다.Self 키워드는 impl 키워드 뒤에 적혀있는 타입의 별칭으로서, 여기서는 Rectangle이 되겠습니다.let sq = Rectangle::square(3);처럼 구조체 명에 :: 구문을 붙여서 호출합니다. 연관 함수는 구조체의 네임스페이스 안에 있기 때문이죠. :: 구문은 7장에서 알아볼 모듈에 의해 생성되는 네임스페이스에도 사용됩니다.읽어주셔서 감사합니다 🙇♂️
https://doc.rust-kr.org/ch05-01-defining-structs.html
https://doc.rust-kr.org/ch05-02-example-structs.html
https://doc.rust-kr.org/ch05-03-method-syntax.html
Rust 교재의 한국어 버전을 기반으로 작성한 글임을 다시 한 번 알려드립니다. 🙏