Rust 공식 문서를 참고한 글입니다.
1. 구조체
구조체는 튜플과 유사하게 여러 자료형을 하나의 자료형으로 한 곳에 묶어서 만드는 자료형이다. 튜플과 다른 점은 순서에 의존하지 않고 접근할 수 있다는 장점이 있다.
구조체 정의
구조체를 정의할 때는 struct라는 키워드를 사용한다. 구조체 내부의 자료들은 Field라고 표현하며 이름:자료형으로 표현된다.
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
인스턴스 생성
이렇게 정의한 구조체를 사용하려면, 구조체 인스턴스를 생성해야 한다. 구조체 안에 key:value 쌍을 만들면 된다. 각 필드의 이름을 통해서 구분이 되기 때문에 정의한 순서와 같을 필요가 없다. 선언한 구조체에서 값을 읽으려면 인스턴스 이름 : 필드 이름으로 접근할 수 있다. 가변성이 있는 인스턴스는 값을 대입하면 되는데, 변수와 마찬가지로 mut 키워드를 사용해서 가변성을 부여할 수 있다.
fn main() {
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
}
이때, 구조체 인스턴스는 필드가 아니라 인스턴스 단위로 가변성이 적용된다는 것을 기억해야 한다.
구조체를 반환하는 함수를 생성해서 구조체를 생성할 수 있다. 이때, 인자로 전달된 변수와 대입할 필드 이름이 같으면 생략해도 된다.
// 기본형
fn build_user(email: String, username: String) -> User {
User {
active: true,
username: username,
email: email,
sign_in_count: 1,
}
}
// 축약형
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
}
}
함수를 사용해 구조체를 반환할 때, 이전에 생성된 구조체 인스턴스가 있다면 그것을 사용해서 새로운 인스턴스를 생성할 수도 있다.
fn main() {
// --snip--
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
}
만약에 기존 인스턴스와 겹치는 부분이 많으면, 겹치지 않는 부분은 작성하고 나머지는 일괄적으로 ..를 사용해 작성할 수 있다.
fn main() {
// --snip--
let user2 = User {
email: String::from("another@example.com"),
..user1
};
}
튜플 구조체
Rust에서는 튜플과 유사하게 생긴 튜플 구조체 라는 것을 지원한다. 튜플 구조체는 튜플과 구조체 그 사이 어딘가에 위치한다. 구조체처럼 필드를 그룹으로 만들어서 이름을 붙일 수 있지만, 튜플처럼 각 필드는 이름을 갖지 않는다. 또 각 필드에 접근할 때, 튜플처럼 순서대로 접근할 수 있다. 어떤 튜플을 하나의 자료형처럼 서로 구분할 때 사용한다.
튜플 구조체를 정의할 때는 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);
}
예를 들어, 위 코드에서 Color과 Point는 이름만 다를 뿐 타입이 모두 같다. 그러나 다른 구조체로 구분된다. 그래서 Color에서 Point를 변수로 사용할 수 없다.
Unit-Like 구조체
구조체 안에 필드가 없이 선언되는 자료형도 있다. 이것을 unit-like 구조체라고 하고, 이후에 다시 자세하게 배울 내용이다. 간단하게 말하면, 어떤 자료형에 대한 특색을 저장해야 할 때, 데이터가 없는 경우 사용하는 구조체다.
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
2. 구조체 사용하기
구조체에 대해 이해하기 위해서 실제 프로그램을 작성하고 리팩토링 해보자. 사각형의 면적을 계산하는 프로그램을 작성하기 위해서 rectangles라는 프로젝트를 생성한다.
// main.rs
fn main() {
let width1 = 30;
let height1 = 50;
println!(
"The area of the rectangle is {} square pixels.",
area(width1, height1)
);
}
fn area(width: u32, height: u32) -> u32 {
width * height
}
프로그램을 실행시키면 오류 없이 실행되는 것을 확인할 수 있다.
그러나 이 경우, 한 사각형의 너비와 높이이므로 서로 연관성이 있다. 관리하기 쉽도록 하나의 튜플로 만들어줄 것이다. 아래와 같은 방식을 예로 들면, 하나의 자료형으로 width와 height를 계산한다.
// main.rs
fn main() {
let rect1 = (30, 50);
println!(
"The area of the rectangle is {} square pixels.",
area(rect1)
);
}
fn area(dimensions: (u32, u32)) -> u32 {
dimensions.0 * dimensions.1
}
현재 rect1이라는 자료형은 이름이 존재하지 않기 때문에 모호하다. 여기에서 의미를 조금 더 부여하기 위해서 코드를 수정해보자.
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
area(&rect1)
);
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
Rectangle이라는 구조체를 선언하고 이를 rect1 변수에 바인딩했다. area 함수도 하나의 변수로 묶였다. 지난 4장에서 배웠듯, 이 함수에서 소유권을 가져오는 대신 빌려오고 싶기 때문에, main이 소유권을 가진 채로 area에서 rect1을 사용하는 대신에 &를 사용해야 한다.
이제 rect1을 출력해보자. 우리는 println! 매크로를 사용해서 변수를 출력했다. 이때 구조체는 각각의 필드를 개별적으로 출력해줘야 한다. 우리가 지금까지 출력한 값들은 출력의 형태가 명확하기 때문에 오류 없이 출력되었다. 그러나 구조체는 둘 이상의 출력 형태를 가지기 때문에 Rust 컴파일러는 오류를 발생한다. 예제를 통해 알아보자.
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {}", rect1);
}
위 프로그램은 오류가 발생한다.
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
println! 매크로는 Display라는 형태를 기본으로 하지만, 구조체는 그 형태가 복잡하기 때문이다.
힌트를 참조하면, {:?} 이나 {:#?}를 사용하면 출력할 수 있다고 한다. 이것은 Debug 형태로 출력하는데, std::fmt::Debug 트레이트가 있어야 한다. 그래서 구조체 정의 앞에 #[derive(Debug)]를 추가하여 수정할 수 있다.
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {rect1:?}");
}
'언어 > Rust' 카테고리의 다른 글
[Rust] 6. 열거형과 패턴 매칭 (8) | 2024.10.09 |
---|---|
[Rust] 5-2. 메서드 (0) | 2024.10.09 |
[Rust] 4-3. Slice Type (0) | 2024.09.28 |
[Rust] 4-2. 참조와 대여 (0) | 2024.09.28 |
[Rust] 4-1. 소유권(Ownership) (0) | 2024.09.28 |