구조체 정의 및 인스턴스화
이름 있는 필드로 구조체를 정의·생성하고 필드 초기화 축약·업데이트·튜플·유닛 구조체 문법을 익힙니다.
구조체(struct)는 서로 관련된 여러 값을 하나로 묶고 이름을 붙여 의미 있는 묶음을 만드는 사용자 정의 타입입니다. 객체 지향 언어에 익숙하다면, 객체가 가진 데이터 속성(attribute)과 비슷하다고 생각하면 됩니다. 4장에서는 구조체를 정의하고 만드는 방법, 튜플과 비교해 구조체가 더 나은 경우, 그리고 구조체의 데이터와 관련된 동작을 나타내는 메서드와 연관 함수를 다룹니다. 구조체와 5장에서 볼 열거형은 프로그램이 다루는 개념을 새 타입으로 만들어, 러스트의 컴파일 시점 타입 검사를 최대한 활용하게 해 줍니다.
이 글에서는 구조체를 정의하고 인스턴스를 만드는 문법을 살펴보며, 3장에서 본 이동과 Copy가 구조체의 필드마다 어떻게 적용되는지도 확인합니다.
구조체 정의하고 인스턴스 만들기
구조체는 여러 관련 값을 담는다는 점에서 튜플 타입과 비슷하며, 튜플처럼 각 값의 타입이 달라도 됩니다. 다른 점은 구조체의 각 값에 이름을 붙인다는 것입니다. 이름 덕분에 값의 뜻이 분명해지고, 값에 접근할 때 순서에 기대지 않아도 되므로 튜플보다 다루기 편합니다.
구조체를 정의하려면 struct 키워드와 구조체의 이름을 쓰고, 중괄호 안에 필드(field)라고 부르는 각 값의 이름과 타입을 적습니다. 구조체의 이름은 묶을 데이터의 뜻에 맞게 짓습니다. 다음은 사용자 계정 정보를 담는 구조체입니다.
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}정의한 구조체를 쓰려면 각 필드에 구체적인 값을 넣어 그 구조체의 인스턴스(instance)를 만듭니다. 구조체 이름을 쓰고 중괄호 안에 필드 이름: 값 쌍을 적으며, 필드의 순서는 정의할 때와 달라도 됩니다. 구조체 정의는 그 타입의 일반적인 양식이고, 인스턴스는 그 양식에 실제 값을 채운 것이라고 생각하면 됩니다.
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("[email protected]"),
sign_in_count: 1,
};
}구조체의 값은 점 표기법으로 꺼내며, 사용자의 이메일 주소는 user1.email로 읽습니다. 인스턴스가 가변이라면 같은 방법으로 필드의 값을 바꿀 수도 있습니다.
fn main() {
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("[email protected]"),
sign_in_count: 1,
};
user1.email = String::from("[email protected]");
}가변성은 인스턴스 전체에 적용되며, 일부 필드만 가변으로 만들 수는 없습니다. 다른 식처럼 구조체의 새 인스턴스를 만드는 식도 함수 본문의 마지막에 두어 그 인스턴스를 돌려줄 수 있습니다. 다음 build_user는 이메일과 사용자 이름을 받아, active는 true, sign_in_count는 1로 채운 User를 돌려줍니다.
fn build_user(email: String, username: String) -> User {
User {
active: true,
username: username,
email: email,
sign_in_count: 1,
}
}매개변수의 이름을 필드 이름과 같게 짓는 것은 자연스럽지만, email과 username을 두 번씩 적는 것은 번거롭습니다. 필드가 많아질수록 더 그렇습니다.
필드 초기화 축약법 사용하기
변수와 필드의 이름이 같으면 필드 초기화 축약법(field init shorthand)을 써서 이름을 한 번만 적을 수 있습니다. 아래 build_user에서 username과 email은 콜론과 값 없이 이름만 적혀 있고, active와 sign_in_count는 그대로 필드: 값 모양입니다. 이처럼 축약법은 필드마다 골라 쓸 수 있습니다.
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
}
}email: email 대신 email만 적으면 같은 이름의 변수 email의 값이 email 필드에 들어갑니다. 함수는 이전과 똑같이 동작하며, 축약법은 글자만 줄일 뿐 매개변수의 String이 필드로 이동한다는 점은 그대로입니다.
기존 인스턴스를 이용해 새 인스턴스를 만들 때 구조체 업데이트 문법 사용하기
다른 인스턴스의 값 대부분을 그대로 쓰고 일부만 바꾼 새 인스턴스를 만들어야 할 때가 있습니다. 구조체 업데이트 문법(struct update syntax)을 쓰지 않으면, 다음처럼 바꾸지 않을 필드도 모두 직접 적어야 합니다. 이메일만 새 값이고 나머지는 앞에서 만든 user1과 같은 user2입니다.
fn main() {
// --생략--
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("[email protected]"),
sign_in_count: user1.sign_in_count,
};
}구조체 업데이트 문법을 쓰면 더 짧게 쓸 수 있습니다. ..user1은 직접 적지 않은 나머지 필드를 user1의 필드 값으로 채우라는 뜻이며, 반드시 맨 끝에 적습니다. 직접 적는 필드는 정의의 순서와 상관없이 원하는 만큼, 원하는 순서로 적을 수 있습니다.
fn main() {
// --생략--
let user2 = User {
email: String::from("[email protected]"),
..user1
};
}이 코드도 email만 다르고 active, username, sign_in_count는 user1과 같은 user2를 만듭니다. 그런데 구조체 업데이트 문법은 대입처럼 =를 쓰는 것과 같아서, 필드마다 이동하거나 복사합니다. 아래 그림은 이 예제에서 필드마다 무슨 일이 일어나는지 보여 줍니다.
구조체 업데이트의 필드별 복사와 이동 계약
User{email:새 값,..user1} 전후 username만 이동하고 email과 Copy 두 필드는 원본에서 유효한 상태 변화다. 필드 간 시간 순서를 만들지 않고 업데이트 전·한 번의 생성·두 인스턴스의 사용 가능성으로 부분 이동을 확인한다.
STRUCT UPDATE · FIELD TRANSFER
구조체 업데이트는 필드마다 복사·이동 결과가 다르다
..base는 같은 구조체 타입에서 명시하지 않은 필드만 채웁니다. 각 필드의 타입에 따라 복사하거나 이동하며 자동으로 복제하지 않습니다.
let user2 = User {
email: new_email,
..user1
};
-
명시한 필드 표현식을 먼저 평가
여러 필드를 명시했다면 소스에 작성한 순서대로 왼쪽에서 오른쪽으로 평가합니다. 이 예제에서는
new_email이 먼저 평가됩니다. -
마지막에 base 표현식을 평가
..user1은 문법상 마지막이며, 명시하지 않은username,active,sign_in_count만 제공합니다.
email: new_email- 초기화 명시한 새 값으로
user2.email을 만듭니다. - 전달
user1.email은 base에서 읽지 않아 그대로 남습니다. - 이후
user1.email을 계속 사용할 수 있습니다. username: String· 이동- 초기화 생략한 필드이므로
..user1에서 가져옵니다. - 전달
String은 이 예제에서 복사되지 않고user2.username으로 이동합니다. - 이후
user1.username과user1전체는 사용할 수 없습니다. active: bool- 초기화 생략한 필드이므로
..user1에서 가져옵니다. - 전달
bool이Copy를 구현하므로 값이 복사됩니다. - 이후 원래
user1.active도 계속 사용할 수 있습니다. sign_in_count: u64- 초기화 생략한 필드이므로
..user1에서 가져옵니다. - 전달
u64가Copy를 구현하므로 값이 복사됩니다. - 이후 원래
user1.sign_in_count도 계속 사용할 수 있습니다.
User 예제의 partial move 경계는 필드 단위입니다. 이동된 username 때문에 user1 전체는 사용할 수 없지만, 명시해서 건드리지 않은 email과 복사된 두 필드는 남아 있습니다.이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 9노드와 8간선이다. User{email 새 값,..user1}에서 email 새 값/username String 이동/active bool 복사/sign_in_count u64 복사의 네 규칙으로 분기하고 각각 이후 원본 필드의 사용 조건으로 향한다. 원래 email은 base에서 안 가져와 유효하고, username은 이동되어 원래 필드와 user1 전체를 사용할 수 없으며, 두 Copy 필드는 원본에서 유효하다. 네 하위 관계 사이에 시간 간선이 없으며 필드 초기화의 평가 순서는 원래 본문 설명과 별개다. 세로 도표도 같은 네 분기와 사용 조건을 보존한다. 원래 User에는 사용자 Drop 구현이 없다는 본문 사례의 범위다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 기존 User user1의 네 필드가 유효하다. |
|
|
| 한 업데이트 생성 새 email과 나머지 필드로 user2를 만든다. |
|
|
| 남은 사용 가능성 이동한 필드와 남은 원 필드를 구분한다. |
|
|
구조체 업데이트의 필드별 복사와 이동 계약
User{email:새 값,..user1} 전후 username만 이동하고 email과 Copy 두 필드는 원본에서 유효한 상태 변화다. 필드 간 시간 순서를 만들지 않고 업데이트 전·한 번의 생성·두 인스턴스의 사용 가능성으로 부분 이동을 확인한다.
STRUCT UPDATE · FIELD TRANSFER
구조체 업데이트는 필드마다 복사·이동 결과가 다르다
..base는 같은 구조체 타입에서 명시하지 않은 필드만 채웁니다. 각 필드의 타입에 따라 복사하거나 이동하며 자동으로 복제하지 않습니다.
let user2 = User {
email: new_email,
..user1
};
-
명시한 필드 표현식을 먼저 평가
여러 필드를 명시했다면 소스에 작성한 순서대로 왼쪽에서 오른쪽으로 평가합니다. 이 예제에서는
new_email이 먼저 평가됩니다. -
마지막에 base 표현식을 평가
..user1은 문법상 마지막이며, 명시하지 않은username,active,sign_in_count만 제공합니다.
email: new_email- 초기화 명시한 새 값으로
user2.email을 만듭니다. - 전달
user1.email은 base에서 읽지 않아 그대로 남습니다. - 이후
user1.email을 계속 사용할 수 있습니다. username: String· 이동- 초기화 생략한 필드이므로
..user1에서 가져옵니다. - 전달
String은 이 예제에서 복사되지 않고user2.username으로 이동합니다. - 이후
user1.username과user1전체는 사용할 수 없습니다. active: bool- 초기화 생략한 필드이므로
..user1에서 가져옵니다. - 전달
bool이Copy를 구현하므로 값이 복사됩니다. - 이후 원래
user1.active도 계속 사용할 수 있습니다. sign_in_count: u64- 초기화 생략한 필드이므로
..user1에서 가져옵니다. - 전달
u64가Copy를 구현하므로 값이 복사됩니다. - 이후 원래
user1.sign_in_count도 계속 사용할 수 있습니다.
User 예제의 partial move 경계는 필드 단위입니다. 이동된 username 때문에 user1 전체는 사용할 수 없지만, 명시해서 건드리지 않은 email과 복사된 두 필드는 남아 있습니다.이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 9노드와 8간선이다. User{email 새 값,..user1}에서 email 새 값/username String 이동/active bool 복사/sign_in_count u64 복사의 네 규칙으로 분기하고 각각 이후 원본 필드의 사용 조건으로 향한다. 원래 email은 base에서 안 가져와 유효하고, username은 이동되어 원래 필드와 user1 전체를 사용할 수 없으며, 두 Copy 필드는 원본에서 유효하다. 네 하위 관계 사이에 시간 간선이 없으며 필드 초기화의 평가 순서는 원래 본문 설명과 별개다. 세로 도표도 같은 네 분기와 사용 조건을 보존한다. 원래 User에는 사용자 Drop 구현이 없다는 본문 사례의 범위다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 기존 User user1의 네 필드가 유효하다. |
|
|
| 한 업데이트 생성 새 email과 나머지 필드로 user2를 만든다. |
|
|
| 남은 사용 가능성 이동한 필드와 남은 원 필드를 구분한다. |
|
|
email은 새 값을 직접 적었으므로 user1.email은 가져오지 않아 그대로 유효합니다. active와 sign_in_count는 Copy를 구현한 bool과 u64이므로 복사되어 user1에도 그대로 남습니다. 하지만 username의 String은 Copy가 아니므로 user2로 이동하며, 그래서 이후에는 user1.username과, 그 필드를 포함하는 user1 전체를 쓸 수 없습니다. 이동하지 않은 다른 필드는 여전히 따로 쓸 수 있습니다. username도 새 String으로 직접 적고 ..user1에서는 active와 sign_in_count만 가져온다면, 이동하는 필드가 없으므로 user1 전체를 계속 쓸 수 있습니다.
명명된 필드 없는 튜플 구조체를 사용하여 다른 타입 만들기
러스트에는 튜플과 비슷한 튜플 구조체(tuple struct)도 있습니다. 튜플 구조체는 구조체 자체에는 이름을 붙이지만 필드에는 이름 없이 타입만 적습니다. 튜플 전체에 이름을 붙여 다른 튜플과 다른 타입으로 만들고 싶은데, 필드마다 이름을 붙이는 것은 장황하거나 불필요할 때 씁니다. struct 키워드와 이름 뒤에 튜플의 타입들을 적어 정의합니다.
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}black과 origin은 서로 다른 튜플 구조체의 인스턴스이므로 타입이 다릅니다. 필드의 타입 구성이 같아도 구조체는 각각 별개의 타입이기 때문에, Color를 받는 함수에 Point를 넘길 수 없습니다. 그 밖에는 튜플처럼 쓸 수 있어서, .0 같은 위치 번호로 값에 접근하며, 분해할 때는 let Point(x, y, z) = origin;처럼 구조체의 이름을 적어야 합니다.
필드가 없는 유사 유닛 구조체
필드가 하나도 없는 구조체도 정의할 수 있습니다. 튜플 타입에서 본 유닛 ()과 비슷하게 동작하므로 유사 유닛 구조체(unit-like struct)라고 합니다. 어떤 타입에 트레이트를 구현하고 싶지만 그 타입 안에 담을 데이터는 없을 때 쓰며, 트레이트는 트레이트와 공통 동작에서 다룹니다.
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}struct 키워드 뒤에 이름을 쓰고 바로 세미콜론을 붙이면 되며, 중괄호나 괄호는 필요 없습니다. 인스턴스를 만들 때도 이름만 적습니다. 나중에 AlwaysEqual의 인스턴스가 다른 어떤 값과도 같다고 판단하는 동작을 구현하면 테스트에서 언제나 같은 결과를 내는 값으로 쓸 수 있는데, 그런 동작에는 데이터가 필요 없습니다. 지금의 선언은 필드 없는 값을 만들었을 뿐이며, 이름이 AlwaysEqual이라고 해서 비교하는 동작이 생기지는 않습니다.
구조체 데이터의 소유권
앞의 User 정의에서는 문자열 슬라이스 &str 대신 일부러 소유하는 타입인 String을 썼습니다. 그래야 인스턴스가 자기 데이터를 모두 소유하고, 인스턴스가 유효한 동안 그 데이터도 유효하기 때문입니다. 구조체에 다른 곳이 소유한 데이터의 참조를 담을 수도 있지만, 그러려면 라이프타임과 참조 유효성에서 볼 라이프타임(lifetime)이 필요합니다. 라이프타임은 구조체가 유효한 동안 그 안의 참조가 가리키는 데이터도 유효하다는 것을 보장합니다. 라이프타임 없이 참조를 담으려 하면 컴파일되지 않습니다.
struct User {
active: bool,
username: &str,
email: &str,
sign_in_count: u64,
}
fn main() {
let user1 = User {
active: true,
username: "someusername123",
email: "[email protected]",
sign_in_count: 1,
};
}error[E0106]: missing lifetime specifier
--> src/main.rs:3:15
|
3 | username: &str,
| ^ expected named lifetime parameter컴파일러는 라이프타임 지정자가 없다는 E0106 오류를 username과 email 필드에 하나씩 내고, 라이프타임 매개변수를 붙이는 방법을 도움말로 보여 줍니다. 이 오류를 고쳐 구조체에 참조를 담는 방법은 9장에서 다루며, 지금은 &str 대신 String처럼 소유하는 타입을 쓰면 됩니다. 아래 표는 이 글에서 본 세 가지 구조체 형태와, 필드가 데이터를 소유하는지 빌리는지를 따로 고르는 두 기준으로 정리한 것입니다.
TWO INDEPENDENT AXES
구조체의 형태와 필드의 소유 관계는 별도로 고른다
세 형태는 모두 고유한 명목 타입을 만듭니다. 구조체의 형태는 생성·접근 문법을 정하고, 필드 타입은 데이터의 소유·대여 관계를 정하므로 두 선택을 하나의 분류로 섞지 않습니다.
1 · 형태와 생성·접근 문법
- 이름 있는 필드
struct User { email: String }- 생성·접근
User { email }처럼 필드명으로 초기화하고user.email로 읽습니다. 생성식의 필드 순서는 자유지만 필요한 필드는 모두 채워야 합니다. - 선택 각 값의 이름이 읽기 계약을 전달할 때 사용합니다.
- 튜플 구조체
struct Point(i32, i32);- 생성·접근
Point(10, 20)처럼 위치 순서대로 만들고point.0또는Point(x, y)패턴으로 읽습니다. - 선택 필드명은 불필요하지만 일반 튜플과 구별되는 고유 타입이 필요할 때 사용합니다.
- 유사 유닛 구조체
struct AlwaysEqual;- 생성·접근
AlwaysEqual로 값을 만들며 읽을 필드가 없습니다. - 선택 인스턴스별 저장 상태 없이 고유 타입과 트레이트 구현 지점이 필요할 때 사용합니다.
2 · 필드 타입과 소유·대여 관계
이름 있는 구조체와 튜플 구조체 모두 아래 두 관계 중 필요한 필드 타입을 가질 수 있습니다.
String· 소유- 인스턴스가 문자열 값을 소유합니다. 구조체가 유효한 동안 그 필드의 데이터도 함께 관리됩니다.
- 값을 다른 곳으로 넘길 때는 해당 타입의 이동·복사 규칙이 적용됩니다.
&'a str· 대여- 인스턴스는 외부 문자열을 참조하며 소유하지 않습니다.
- 참조가 구조체보다 먼저 무효가 되지 않도록
'a라이프타임 관계를 타입에 표현해야 합니다.
Copy는 별도의 ownership 형태가 아닙니다. 값을 사용할 때 복사할지 이동할지를 정하는 특성이며, 구조체의 named·tuple 형태와도 독립적입니다.
이름 있는 필드, 튜플 구조체, 유사 유닛 구조체는 모두 저마다 새 타입을 만들며, 형태는 값을 만들고 꺼내는 문법을 정합니다. 필드의 타입은 그와 별개로, 그 데이터를 구조체가 소유할지 빌릴지를 정합니다. String 필드는 데이터를 소유하고, &str 필드는 라이프타임으로 빌린 데이터와의 관계를 밝혀야 합니다.
더 깊이: 구조체는 메모리에 어떤 순서로 놓일까
러스트의 기본 표현(representation)은 구조체의 필드를 선언한 순서대로 놓는다고 약속하지 않습니다. Reference의 타입 레이아웃이 기본 표현에 보장하는 것은 각 필드의 오프셋이 그 필드의 정렬(alignment)에 맞고, 필드끼리 겹치지 않으며, 구조체의 정렬이 가장 큰 필드의 정렬 이상이라는 것뿐입니다. 그래서 컴파일러는 패딩(padding)을 줄이도록 필드의 순서를 바꿀 수 있고, 같은 정의라도 배치가 언제나 같다고 기대할 수 없습니다. 필드의 오프셋이나 구조체의 크기에 기대는 코드를 쓰면 안 되는 까닭입니다.
C 언어나 운영체제와 구조체를 주고받아 배치가 정확히 정해져야 한다면 #[repr(C)]를 붙입니다. 이 표현은 필드를 선언한 순서대로 놓되, 각 필드 앞에 그 필드의 정렬을 맞출 만큼 패딩을 넣고, 전체 크기를 구조체의 정렬의 배수로 올립니다. 예를 들어 u8, u32, u64를 이 순서로 선언한 #[repr(C)] 구조체는 첫 필드 뒤에 3바이트의 패딩이 들어가 세 필드의 오프셋이 0, 4, 8이 되고, 전체 크기는 16바이트입니다. 한편 필드가 없는 유사 유닛 구조체는 기본 표현에서 크기가 0인 타입(zero-sized type)이 되도록 보장되므로, AlwaysEqual 같은 값은 메모리를 차지하지 않습니다.
정리
- 구조체는
struct로 정의하며, 이름 붙은 필드에 서로 다른 타입의 값을 담습니다. - 인스턴스는
구조체이름 { 필드: 값, ... }으로 만들고, 필드는 점 표기법으로 읽으며, 가변성은 인스턴스 전체에 적용됩니다. - 변수와 필드의 이름이 같으면 필드 초기화 축약법으로 이름을 한 번만 적습니다.
- 구조체 업데이트 문법
..base는 적지 않은 필드를 base에서 채우며,Copy가 아닌 필드는 이동하므로 base의 그 필드는 더 쓸 수 없습니다. - 튜플 구조체는 필드에 이름 없이 타입만 적으며, 구성이 같아도 서로 다른 타입입니다.
- 유사 유닛 구조체는 필드가 없으며, 데이터 없이 트레이트를 구현할 타입으로 씁니다.
- 구조체에 참조를 담으려면 라이프타임이 필요하므로, 지금은
String같은 소유 타입을 씁니다.
연습 문제
1. 업데이트 뒤에 쓸 수 있는 필드
다음 프로그램은 컴파일되나요? 된다면 무엇을 출력하나요?
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let user1 = User {
active: true,
username: String::from("learner"),
email: String::from("[email protected]"),
sign_in_count: 4,
};
let user2 = User {
email: String::from("[email protected]"),
..user1
};
println!("{} {} {}", user2.username, user2.active, user2.sign_in_count);
println!("{}", user1.email);
println!("{} {}", user1.active, user1.sign_in_count);
}해설 보기
컴파일되며 learner true 4, [email protected], true 4를 차례로 출력합니다. user2는 email만 새 값이고 나머지를 user1에서 가져오므로 username의 String은 user2로 이동하고, active와 sign_in_count는 복사됩니다. user1.email은 가져가지 않았으므로 그대로 쓸 수 있고, 복사된 두 필드도 user1에 남아 있습니다. 다만 이 뒤에 user1.username이나 user1 전체를 쓰면 이동한 값을 쓰는 것이므로 E0382 오류가 납니다.
2. username까지 적었을 때
앞의 업데이트에서 email 아래에 username: String::from("second"),를 더 적으면 첫 출력은 무엇이 되나요? 그 뒤에 let moved_user = user1;이라고 쓸 수 있나요?
해설 보기
첫 출력은 second true 4가 됩니다. 두 String 필드를 모두 새 값으로 적었으므로 ..user1에서는 bool과 u64만 복사하고, 이동하는 필드가 없습니다. 그래서 user1은 온전히 남아 user1.username은 여전히 learner이며, let moved_user = user1;로 user1 전체를 다른 변수로 옮길 수도 있습니다.
3. 같은 모양, 다른 타입
struct Meters(f64);와 struct Feet(f64);를 정의하고 fn print_meters(m: Meters)를 만들었습니다. print_meters(Feet(3.0))은 컴파일되나요? 튜플 구조체를 이렇게 쓰면 어떤 점이 좋은가요?
해설 보기
컴파일되지 않습니다. Meters와 Feet는 둘 다 f64 하나를 담지만 서로 다른 타입이므로, Meters를 받는 함수에 Feet를 넘기면 타입이 맞지 않는다는 E0308 오류가 납니다. 이렇게 단위를 타입으로 구분하면 미터와 피트를 섞어 쓰는 실수를 컴파일할 때 잡을 수 있습니다.
다음 글 구조체를 사용한 예제 프로그램에서는 사각형의 넓이를 구하는 프로그램을 변수에서 튜플, 구조체로 차례로 고쳐 가며 구조체가 언제 도움이 되는지 확인합니다.