구조체는 튜플과 유사하게 구성요소들이 각자 다른 타입을 지닐 수 있습니다. 하지만 튜플과는 달리 각 구성요소들을 명명할 수 있어 값이 의미하는 바를 명확하게 인지할 수 있습니다. 구조체 내의 특정 요소 데이터 명세를 기술하거나 접근 시 순서에 의존할 필요가 없어 튜플보다 유연하게 다룰 수 있습니다.
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
구조체 중괄호 안에서는 필드(field)라 불리는 각 구성요소들의 타입과 접근할 수 있는 이름을 정의합니다.
정의한 구조체를 사용하려면 각 필드의 값을 명세한 인스턴스를 생성해야 합니다. 구조체를 정의할 때 필드들의 순서가 정의한 필드의 순서와 같을 필요는 없습니다.
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
인스턴스의 필드 값을 변경하고 싶다면 인스턴스가 변경 가능(mutable) 해야합니다.
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
아래의 코드는 사용자의 이메일과 이름을 받아 User 구조체의 인스턴스를 반환하는 함수입니다.
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
구조체 필드와 동일한 이름으로 함수 매개변수의 이름을 지정하는 것은 합리적이지만, 필드 이름과 변수를 반복해야하는 것은 비효율적입니다. 위의 코드를 더 간단하게 변경할 수 있습니다.
앞서 살펴본 코드를 축약하기 위해 아래와 같이 필드 초기화 축약법(field init shorthand)을 이용할 수 있습니다.
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
존재하는 인스턴스에서 기존 값의 대부분을 재사용하고 몇몇 값만 바꿔 새로운 인스턴스를 정의할 수 있습니다.
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
구조체명을 통해 의미를 부여할 수 있으나 필드의 타입만 정의할 수 있고 명명은 할 수 없는 튜플 구조체라 불리는 튜플과 유사한 형태의 구조체를 정의할 수 있습니다.
일반적인 구조체 정의 방법과 같이 struct
키워드를 통해 정의 가능합니다. 튜플 타입 정의는 키워드 뒤에서 이루어집니다.
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
구조체 내의 타입이 모두 동일하지만 다른 튜플 구조체이기 때문에 black
과 origin
이 서로 다른 타입임을 유의해야합니다.
어떠한 필드도 없는 구조체 역시 정의 가능합니다. 이는 유닛타입인 ( ) 와 비슷하게 동작하며, 유사 유닛 구조체(unit-like structs)라 불립니다. 유사 유닛 구조체는 특정 타입에 트레잇(trait)을 구현해야하지만 타입 자체게 데이터를 저장하지 않는 경우에 유용합니다.
첫 예제였던 User
구조체 정의에서는 &str
문자 슬라이스 타입 대신 String
타입을 사용했습니다. 이는 의도적으로 구조체 전체가 유효한 동안 구조체가 그 데이터를 소유하게 하고자함입니다.
구조체가 소유권이 없는 데이터의 참조를 저장할 수는 있지만, 라이프타임의 사용을 전제로 합니다. 라이프타임은 구조체가 존재하는 동안 참조하는 데이터를 계속 존재할 수 있도록 합니다. 라이프타임을 사용하지 않고 참조를 저장하고자 하면 아래와 같은 일이 발생합니다.
struct User {
username: &str,
email: &str,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: "someone@example.com",
username: "someusername123",
active: true,
sign_in_count: 1,
};
}
error[E0106]: missing lifetime specifier
-->
|
2 | username: &str,
| ^ expected lifetime parameter
error[E0106]: missing lifetime specifier
-->
|
3 | email: &str,
| ^ expected lifetime parameter