이름 있는 필드로 구조체를 정의·생성하고 필드 초기화 축약·업데이트·튜플·유닛 구조체 문법을 익힙니다.
구조체(struct)는 여러 값을 묶고 이름을 지어서 의미 있는 묶음을 정의하는 데에 사용됩니다.
객체 지향 언어에 익숙하신 분들이라면, 구조체란 객체의 데이터 속성(attribute)과 비슷한 것입니다.
이번 장에서는 앞서 배운 튜플과 구조체를 비교 대조하여 어떤 경우에 구조체로 데이터를 묶는 것이 더 좋은지 보여줄 것입니다.
우선 구조체를 정의하고 생성하는 방법을 살펴봅니다.
그리고 구조체의 데이터와 연관된 동작을 표현하는 메서드와 연관 함수(associated functions) 정의 방법을 다루겠습니다.
구조체와 열거형(5장에서 설명할 예정입니다)은 프로그램 도메인에 새로운 타입을 만들어서 러스트의 컴파일 시점 타입 검사 기능을 최대한 활용하기 위한 기본 구성 요소입니다.
이제 구조체를 정의하고 인스턴스를 다루는 흐름을 살펴보겠습니다. 앞 장의 String 이동·Copy·공유 대여를 필드마다 적용합니다. 아래 예제는 src/main.rs를 차례로 바꾸는 발췌이므로 필요한 정의를 함께 둡니다. 예제 4-2·4-3의 main에는 예제 4-1의 User 정의가 필요하고, build_user의 두 버전은 한 번에 하나만 선택합니다. 한 파일에 main 두 개를 붙이지 마세요.
구조체는 여러 개의 연관된 값을 가질 수 있다는 측면에서 볼 때 ‘튜플 타입’ 절에서 살펴본 튜플과 비슷합니다.
튜플처럼 구조체의 구성 요소들은 각각 다른 타입이 될 수 있습니다.
그리고 여기에 더해서, 구조체는 각각의 구성 요소에 이름을 붙일 수 있습니다.
따라서 각 요소가 더 명확한 의미를 갖게 되고, 특정 요소에 접근할 때 순서에 의존할 필요도 사라지게 되어 튜플보다 유연하게 사용할 수 있습니다.
구조체를 정의하려면 struct 키워드와 해당 구조체에 지어줄 이름을 입력합니다.
이때 구조체 이름은 함께 묶을 데이터의 의미에 맞도록 지어주세요.
이후 중괄호 안에서는 필드(field)라고 부르는 각 구성 요소의 이름 및 타입을 정의합니다.
fn build_user(email: String, username: String) -> User { User { active: true, username, email, sign_in_count: 1, }}
이번에는 build_user 함수에서 User 구조체의 인스턴스를 생성할 때 email: email처럼 작성하는 대신, 변수명과 필드명이 같다는 점을 이용해 email로만 작성한 모습입니다.
물론, 함수는 이전과 같이 잘 작동합니다. 축약형은 email: email의 반복을 줄일 뿐 String 전달을 대여나 복제로 바꾸지 않습니다. User 정의와 이 build_user를 둔 파일의 main에서 build_user(String::from("[email protected]"), String::from("learner"))를 호출하면 매개변수로 받은 두 문자열이 반환한 User의 각 필드로 이동합니다. active는 true, sign_in_count는 1입니다.
User{email:새 값,..user1} 전후 username만 이동하고 email과 Copy 두 필드는 원본에서 유효한 상태 변화다. 필드 간 시간 순서를 만들지 않고 업데이트 전·한 번의 생성·두 인스턴스의 사용 가능성으로 부분 이동을 확인한다.
User 구조체 업데이트에서 명시 필드, untouched 필드, Copy 필드, 이동 필드와 업데이트 뒤 user1의 사용 가능성을 대응한 필드 전달 장부
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,username,active,sign_in_count
user1: 전체 사용 가능
변화·이유
예제 User에는 사용자 Drop 구현이 없다.
한 업데이트 생성
새 email과 나머지 필드로 user2를 만든다.
값·보충 정보
username: String 이동
active/count: Copy
변화·이유
명시한 email은 새 값에서 온다. ..user1은 생략한 username을 이동하고 active와 sign_in_count를 Copy하므로 user1이 부분 이동된다.
남은 사용 가능성
이동한 필드와 남은 원 필드를 구분한다.
값·보충 정보
user1.email: 유효
user1 전체/username: 사용 불가
변화·이유
부분 이동 뒤 Copy 필드와 가져오지 않은 email은 개별로 남는다.
구조체 업데이트의 필드별 복사와 이동 계약
User{email:새 값,..user1} 전후 username만 이동하고 email과 Copy 두 필드는 원본에서 유효한 상태 변화다. 필드 간 시간 순서를 만들지 않고 업데이트 전·한 번의 생성·두 인스턴스의 사용 가능성으로 부분 이동을 확인한다.
User 구조체 업데이트에서 명시 필드, untouched 필드, Copy 필드, 이동 필드와 업데이트 뒤 user1의 사용 가능성을 대응한 필드 전달 장부
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,username,active,sign_in_count
user1: 전체 사용 가능
변화·이유
예제 User에는 사용자 Drop 구현이 없다.
한 업데이트 생성
새 email과 나머지 필드로 user2를 만든다.
값·보충 정보
username: String 이동
active/count: Copy
변화·이유
명시한 email은 새 값에서 온다. ..user1은 생략한 username을 이동하고 active와 sign_in_count를 Copy하므로 user1이 부분 이동된다.
남은 사용 가능성
이동한 필드와 남은 원 필드를 구분한다.
값·보충 정보
user1.email: 유효
user1 전체/username: 사용 불가
변화·이유
부분 이동 뒤 Copy 필드와 가져오지 않은 email은 개별로 남는다.
다른 인스턴스에서 대부분의 값을 유지한 채로 몇 개의 값만 바꿔 새로운 인스턴스를 생성하게 되는 경우가 간혹 있습니다.
그럴 때 유용한 게 바로 구조체 업데이트 문법(struct update syntax)입니다.
먼저 예제 4-6에서는 구조체 업데이트 문법을 사용하지 않고 새로운 User 인스턴스 user2를 만드는 방법을 보여줍니다.
email에는 새로운 값을 설정했지만, 나머지 값들은 예제 4-2에서 만들었던 user1의 값과 동일합니다.
예제 4-6: user1의 값 중 하나를 다르게 한
새로운 User 인스턴스 생성하기
src/main.rs
fn main() { // --생략-- let user2 = User { active: user1.active, username: user1.username, email: String::from("[email protected]"), sign_in_count: user1.sign_in_count, };}
구조체 업데이트 문법을 사용하면 다음 예제 4-7처럼 더 적은 코드로 같은 효과를 낼 수 있습니다.
.. 문법은 따로 명시된 필드를 제외한 나머지 필드를 주어진 인스턴스의 필드 값으로 설정합니다.
예제 4-7: 새로운 email 값으로 User 구조체의
인스턴스를 생성하되, 나머지 필드는 구조체 업데이트 문법으로 user1의 필드
값을 사용하기
src/main.rs
fn main() { // --생략-- let user2 = User { email: String::from("[email protected]"), ..user1 };}
예제 4-7의 코드 또한 email 값이 다른 user2 인스턴스를 생성하지만 username, active, sign_in_count는 user1의 필드와 같은 값을 갖게 합니다.
user1의 값들과 동일한 값들로 나머지를 채우려면 ..user1을 제일 끝에 적어야 하지만, 다른 필드들은 구조체의 정의 내에 있는 필드들의 순서와는 상관없이 우리 마음대로 몇 개든 임의 순서로 적을 수 있습니다.
구조체 표현식에서 명시한 필드의 초기화 표현식은 소스에 작성한 순서대로 왼쪽에서 오른쪽으로 평가되고, 마지막의 ..user1 base 표현식은 그다음에 평가됩니다.
base는 새로 만들 구조체와 같은 타입이어야 하며, 명시하지 않은 필드만 base에서 가져옵니다. 이때 필드 타입이 Copy를 구현하면 값이 복사되고, 그렇지 않으면 이동 규칙이 적용됩니다. 구조체 업데이트 문법이 모든 필드를 자동으로 복제하는 것은 아닙니다.
이 예제에서는 명시하지 않은 user1.username의 String이 user2로 이동합니다. 따라서 이후에 user1 전체나 user1.username은 사용할 수 없지만, 새 값으로 명시한 email은 base에서 가져오지 않았으므로 user1.email은 계속 사용할 수 있습니다. active와 sign_in_count도 Copy 트레이트를 구현한 타입이므로 ‘Copy 트레이트를 통한 값 복사’ 절에서 살펴본 것처럼 원래 필드를 계속 사용할 수 있습니다.
user2에 email과 username의 String을 모두 명시하고 ..user1에서는 active와 sign_in_count만 가져온다면, 이동되는 필드가 없으므로 user2를 만든 이후에도 user1 전체가 유효합니다.
예시
생략된 생성 코드를 채운 다음 프로그램을 별도 src/main.rs로 읽어 보세요. 출력 매크로는 여기의 String을 빌려 표시하며, username 이동은 구조체 업데이트에서 일어납니다.
필드별 사용 가능성을 확인하는 완전한 예
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은 [email protected]이고, user1의 email은 이 업데이트에서 가져오지 않았습니다. 부분 이동은 원래 객체의 모든 필드가 한꺼번에 사라졌다는 뜻이 아닙니다.
스스로 확인하기
위 업데이트에서 email 바로 아래에 username: String::from("second"),를 명시하면 첫 출력과 원 user1.username의 값은 무엇인가요? 이 변형에서 let third = user1;로 전체 값을 넘기는 것은 가능한가요? 명시하지 않은 원 버전에서 같은 전체 전달을 하면 왜 다른지 설명하세요. 두 버전은 각각 별도 파일로 판단합니다.
명시한 필드와 base가 제공할 필드의 풀이
첫 출력은 second true 4이고 원 user1.username은 learner입니다. 두 String을 모두 새 값으로 명시했으므로 base에서는 bool·u64만 복사합니다. 따라서 이 시점의 user1 전체를 third로 이동할 수 있습니다. 물론 전체를 넘긴 다음 user1을 다시 읽을 수 있다는 뜻은 아닙니다. 원 버전은 username을 이미 옮겼으므로 user1 전체 전달이 허용되지 않습니다. 구조체 업데이트와 남은 필드의 공식 설명은 이 구분을 같은 User 타입으로 보여 줍니다.
튜플 구조체는 구조체 자체에는 이름을 지어 의미를 주지만 이를 구성하는 필드에는 이름을 붙이지 않고 타입만 적어 넣은 형태입니다.
튜플 구조체는 튜플 전체에 이름을 지어주거나 특정 튜플을 다른 튜플과 구분하고는 싶은데, 그렇다고 각 필드명을 일일이 정해 일반적인 구조체 형태로 만들면 너무 장황하거나 불필요할 경우 유용합니다.
튜플 구조체의 정의는 일반적인 구조체처럼 struct 키워드와 구조체 명으로 시작되나, 그 뒤에는 타입들로 이루어진 튜플이 따라옵니다.
예시로 살펴보겠습니다.
다음은 각각 Color, Point라는 두 개의 튜플 구조체 정의 및 사용 예시입니다.
src/main.rs
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 타입은 둘 다 i32 값 3개로 이루어진 타입이지만, Color 타입을 매개변수로 받는 함수에 Point 타입을 인수로 넘겨주는 건 불가능합니다.
앞서 말한 점을 제외하면 튜플처럼 사용할 수 있습니다.
여러 부분으로 해체할 수도 있고, .과 인덱스로 개별 값에 접근할 수도 있죠. 예를 들어 let Point(x, y, z) = origin;처럼 분해할 때도 타입 이름을 씁니다. Color를 받는 함수에 Point(0, 0, 0)을 전달할 수 없는 이유는 숫자 값이 아니라 서로 다른 명목 타입이기 때문입니다. 한편 Color(i32, i32, i32)라는 선언만으로 각 값이 0..255인지 검사하는 기능까지 생기지는 않습니다.
이는 ‘튜플 타입’에서 다룬 유닛 타입 ()와 비슷하게 동작하므로 유사 유닛 구조체(unit-like structs)라 지칭합니다.
유사 유닛 구조체는 어떤 타입에 대해 트레이트를 구현하고 싶지만 타입 내부에 어떤 데이터를 저장할 필요는 없을 경우 유용합니다.
트레이트에 대해서는 9장에서 자세히 다루겠습니다.
아래는 AlwaysEqual이라는 이름의 유닛 구조체를 선언하고 생성하는 예제입니다.
src/main.rs
struct AlwaysEqual;fn main() { let subject = AlwaysEqual;}
AlwaysEqual을 정의하기 위해서 struct 키워드 뒤에 이름을 쓰고 바로 세미콜론을 붙였습니다.
중괄호나 괄호도 필요 없습니다!
그런 다음 subject 변수에 AlwaysEqual 인스턴스를 만들어 넣을 때도 비슷한 방식을 사용합니다.
정의한 이름을 적어 넣고, 중괄호나 괄호는 안 썼습니다.
나중에 AlwaysEqual의 모든 인스턴스는 언제나 다른 모든 타입의 인스턴스와 같도록 하는 동작을 구현하여, 이미 알고 있는 결괏값의 테스트 용도로 사용한다고 가정해봅시다.
이런 동작에는 데이터가 필요 없을 것입니다! 현재의 선언은 이름과 필드 없는 값을 만든 상태일 뿐, 이름이 AlwaysEqual이라고 해서 비교 연산이나 모든 값에 대한 동등성이 자동 구현된 것은 아닙니다. 여기의 동등성 이야기는 뒤에서 트레이트로 구현할 동작을 가정한 설명입니다.
유사 유닛 구조체를 포함하여 임의의 타입에 대한 트레이트를 정의하고 구현하는 방법은 9장에서 다룰 것입니다.
예제 4-1의 User 구조체 정의에서는 의도적으로
&str 문자열 슬라이스 대신 구조체가 소유권을 갖는 String 타입을 사용했습니다.
구조체 인스턴스가 유효한 동안 각 인스턴스 내의
모든 데이터가 유효하도록 만들기 위해서죠.
참조자를 이용해 구조체가 소유권을 갖지 않는 데이터도 저장할 수는 있지만,
이는 9장에서 배울 라이프타임(lifetime)을 활용해야 합니다.
라이프타임을 사용하면 구조체가 존재하는 동안에
구조체 내 참조자가 가리키는 데이터의 유효함을 보장받을 수 있기 때문이죠.
만약 라이프타임을 명시하지 않고 참조자를 저장하고자 하면 다음처럼 문제가 발생합니다.
src/main.rs
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, };}
이 정의에는 참조자와 구조체 인스턴스의 관계를 나타내는 라이프타임이 없으므로, 컴파일러는 E0106 범주의 오류를 보고합니다. 구체적인 줄 번호와 도움말 표현은 컴파일러 버전에 따라 달라질 수 있습니다.
error[E0106]: missing lifetime specifier
위 에러를 해결하여 구조체에 참조자를 저장하는 방법은 9장에서 알아보겠습니다.
지금 당장은 &str 대신 String을 사용하는 것으로
넘어가도록 하죠.
구조체를 고를 때는 이름 있는 필드, 튜플 구조체, 유사 유닛 구조체 중 어떤 형태가 의도를 가장 잘 드러내는지 함께 판단하면 좋습니다.
구조체 형태는 값을 생성하고 읽는 문법을 정하지만, 데이터의 소유·대여 관계는 각 필드 타입이 정합니다. 이름 있는 구조체와 튜플 구조체 모두 소유한 값이나 참조자를 필드로 가질 수 있습니다.
이름 있는 구조체, 튜플 구조체, 유사 유닛 구조체의 생성과 접근 문법을 비교하고 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 형태와도 독립적입니다.
먼저 필드 이름과 위치가 의도를 어떻게 드러낼지에 따라 형태를 고르고, 그다음 각 필드가 값을 소유할지 외부 값을 빌릴지 타입으로 결정합니다. 유사 유닛 구조체는 필드 데이터가 없습니다.