#programminglanguage #rust >[!introduction] > 구조체*Struct*는 여러 값을 묶을 수 있게 해주는 문법입니다. C에도 구조체가 있지만, 러스트의 구조체는 메서드와 연관함수까지 포괄한다는 점에서 다른 프로그래밍 언어의 클래스에 해당하는 개념에 더 가깝습니다. 이번 글에서는 러스트의 구조체에 대해 알아봅니다. ## 구조체 만들기 ```rust struct User { active: bool, username: String, email: String, sign_in_count: u64, } ``` 구조체를 정의하기 위한 키워드는 `struct`이며, 다음으로 구조체의 이름을 정하고 중괄호`{}` 안에 구조체의 구성 요소인 필드*field*의 이름과 타입을 정의하면 된다. ### 튜플 구조체 구조체 자체에 이름을 짓지만 필드에는 이름을 짓지 않을 수 있는데, 이런 구조체를 튜플 구조체*tuple structs*라고 부른다. 아래와 같이 선언하며, 구조체이기 때문에 필드가 동일하더라도 이름이 다르면 타입도 다르다는 점에 유의해야 한다. ```rust struct Color(i32, i32, i32); struct Point(i32, i32, i32); fn main() { let black = Color(0, 0, 0); let origin = Point(0, 0, 0); } ``` 이렇게 튜플 구조체를 선언하고 인스턴스를 만들면 필드 별로 변수를 만들 수도 있고, `.`과 인덱스로 개별 값에 접근할 수도 있다. ```rust let (r, g, b) = black; let red = black.0; ``` ### 유사 유닛 구조체 한발 더 나아가, 필드가 없는 구조체를 정의할 수도 있다. 이런 구조체를 유사 유닛 구조체*unit-like structs*라고 부르는데, 아무것도 없는 빈 구조체라고 생각하면 된다. ```rust struct AlwaysEqual; fn main() { let subject = AlwaysEqual; } ``` ## 인스턴스 만들기 ### 인스턴스 선언 이렇게 정의한 구조체를 사용하기 위해서는 구조체의 인스턴스*Instance*를 생성해야 한다. 사용하고자 하는 구역에서 구조체의 이름을 적고 중괄호 안에 각 필드의 이름과 값을 아래와 같이 지정해주면 된다. 구조체가 인스턴스를 만들기 위한 양식인 셈이다. 이때 필드의 순서는 구조체와 동일할 필요는 없다. ```rust fn main() { let user1 = User { active: true, username: String::from("someusername123"), email: String::from("[email protected]"), sign_in_count: 1, }; } ``` 이렇게 생성된 인스턴스에서 특정 값을 얻는 방법은 `인스턴스.필드`로 표기하면 된다. 이를테면 `user1.username`이나 `user1.sign_in_count`처럼 사용하면 `User` 구조체의 인스턴스 `user1`에 저장된 필드의 값을 가져올 수 있는 것이다. 만약 인스턴스의 어떤 필드 값을 바꾸고자 한다면, 인스턴스의 가변성이나 불변성은 인스턴스 전체에 일괄적으로 적용되기 때문에 모든 필드가 가변이거나 모든 필드가 불변이라는 것을 아는 것이 중요하다. 일부 필드만 가변일 수는 없다. ### 구조체 업데이트 인스턴스를 만들 때 흔히 사용하는 방법 중 하나는 다른 인스턴스에서 일부 필드의 값만 바꿔 새로운 인스턴스를 생성하는 것인데, 이럴 때 유용하게 쓸 수 있는 것이 바로 러스트의 구조체 업데이트 문법*struct update syntax*다. ```rust struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { let user1 = User { active: true, username: String::from("someusername123"), email: String::from("[email protected]"), sign_in_count: 1, }; let user2 = User { active: user1.active, username: user1.username, email: String::from("[email protected]"), sign_in_count: user1.sign_in_count, }; } ``` 새로운 인스턴스 `user2`를 만들 때 `user1`에서 필드를 가져온 경우, 구조체 문법을 쓰지 않는다면 위와 같이 적게 되는데, 구조체 업데이트 문법을 사용하면 코드를 줄일 수 있다. ```rust fn main() { // --생략-- let user2 = User { email: String::from("[email protected]"), ..user1 }; } ``` 이렇게 적기만 해도 명시된 `email` 필드를 제외한 나머지 필드는 `user1`에서 가져온 것과 동일하다. 이때, 새로 선언할 필드를 미리 선언하고 `..user1`은 맨 마지막에 사용해 나머지 케이스를 포괄하는 느낌으로 사용해야 한다. 이때 기존 인스턴스를 사용하지 못하는 경우도 있는데, 이는 필드의 특성에 따라 좌우된다. ### 필드 초기화 축약법 인스턴스를 만들 때 사용하는 또 한가지 방법은 [[프로그래밍 언어/Rust/함수\|함수]]를 이용하는 것이다. 전달하고자 하는 필드를 매개변수로 하고 구조체를 반환하는 함수를 이용해 호출하는 쪽에서 인스턴스 생성을 함수 호출로 해결하는 방식이다. ```rust fn build_user(email: String, username: String) -> User { User { active: true, username: username, email: email, sign_in_count: 1, } } ``` 필드의 이름과 매개변수의 이름이 같은 경우, 아래와 같이 필드 초기화 축약법*field init shorthand*를 사용해 반복되는 입력을 줄일 수 있다. ```rust fn build_user(email: String, username: String) -> User { User { active: true, username, email, sign_in_count: 1, } } ``` ## 메서드 { #eed5bb} 러스트의 구조체는 메서드*method*라는 자체 함수를 가진다. 엄밀히 따지자면 메서드는 함수와 유사하되 다른 개념이지만, 함수와 여러 공통점을 가지고 있기 때문에 실제로 코드를 작성하고 보는 입장에서는 구조체에 딸린 함수라고 봐도 무방하다. ### 메서드 정의 어떤 구조체에 대한 메서드는 특정 블록 안에서 정의되어야 하는데, 이 블록을 나타내는 키워드가 바로 `impl`이다. `impl` 뒤에 붙는 구조체의 이름은 이 블록 안의 내용이 해당 구조체에 대한 구현을 담고 있다는 것을 나타낸다. ```rust struct Rectangle { width: u32, height: u32, } impl Rectangle { // &self는 self: &Self를 줄인 것이다. fn area(&self) -> u32 { self.width * self.height } } ``` 메서드를 정의하는 방법은 함수와 동일하다. 다만 차이점이 있다면 첫번째 매개변수가 항상 `self`라는 점인데, 여기서 `self`는 메서드를 호출하고 있는 구조체 인스턴스를 나타낸다. 일반적으로 `self`는 `Self`타입의 매개변수[^1]며, `Self`는 `impl` 블록의 대상이 되는 타입의 별칭이다. 함수의 매개변수에 대하여 언제나 타입을 명시해야 하지만, 상황에 따라 위와 같은 축약을 허용하기도 한다. [^1]: 정확히는 `Self`타입을 참조하는 참조자 타입이다. 이에 대한 내용은 [[소유권\|소유권]]정의된 메서드를 사용하기 위해서는 구조체의 인스턴스를 만든 다음, 인스턴스를 통해 메서드를 호출하면 된다. ```rust fn main() { let rect1 = Rectangle { width: 30, height: 50, }; println!( "The area of the rectangle is {} square pixels.", rect1.area() // 메서드 호출 ); } ``` ## 연관 함수 `impl` 블록에 메서드만 구현할 수 있는 것은 아니다. `impl` 블록 안에 구현되는 모든 함수를 연관 함수*associated function*이라 부르고, 그 중 첫번째 매개변수가 `self`로 고정되는 함수들을 메서드라 부른다. 그래서 `self`를 첫 매개변수로 가지지 않는 연관함수도 얼마든지 구현할 수 있고, 객체를 생성하는 함수인 생성자*constructor*가 일반적으로 연관 함수로 구현된다. ```rust impl Rectangle { fn square(size: u32) -> Self { Self { width: size, height: size, } } } ``` --- ## 참고 자료 & 더보기 + [The Rust Programming Language(한국어판), 5. 구조체로 연관된 데이터를 구조화하기](https://doc.rust-kr.org/ch05-00-structs.html) + [[프로그래밍 언어/Rust/함수\|함수]] + [[소유권\|소유권]]