Rust에서 복잡한 데이터를 다루기 위해 사용하는 기본 단위가 바로 구조체(struct)입니다. 구조체는 여러 개의 관련된 데이터를 하나의 타입으로 묶어 표현할 수 있도록 해줍니다. Rust는 구조체와 메서드(impl 블록)를 통해 모듈화, 캡슐화, 데이터 모델링이 가능합니다.
1. 기본 구조체 정의 및 사용
가장 기본적인 구조체는 struct 다음에 구조체 이름을 쓰고, 중괄호 안에 필드의 이름과 타입을 :으로 연결해 선언합니다.
Rust의 구조체 이름 규칙은 대문자 카멜 케이스(Camel case)입니다. 예를 들어 User, MySruct와 같이 단어 시작을 대문자로 하고, _를 사용할 수 있으며 숫자로 시작할 수 없고, 공백이 포함되면 안됩니다.
struct User {
username: String,
email: String,
active: bool,
}
사용할땐 struct의 인스턴스를 생성합니다.
일반 변수 선언할 때와 마찬가지로 let 키워드를 사용하고, 그 다음에 인스턴스 이름을 적고, = 구조체 이름을 적은 다음 중괄호안에 필드의 이름과 값을, key: value 쌍으로 아래와 같이 입력합니다. 구조체는 모든 필드의 타입이 명확해야 합니다.
fn main() {
let user1 = User {
username: String::from("alice"),
email: String::from("alice@example.com"),
active: true,
};
println!("username: {}", user1.username);
}
- 문자열(String)은 “alice”와 같이 큰따옴표 안에 입력한다고 되는 것이 아니며, String::from(“alice”)라고 하거나, “alice”.to_string()으로 입력해야 합니다.
- bool(논리값)도 true, false와 같이 모두 소문자로 표기합니다.
- 필드 값을 끝까지 입력하고, 쉼표가 있어도 문제가 없습니다.
- 구조체 인스턴스는 tuple과 마찬가지로 . 연산자(notation)로 접근할 수 있습니다.

- Rust는 사용하는 기호도 여러가지가 사용돼서 복잡합니다.
지금까지 나온 것이
변수의 형식은 : 다음에 표시하고,
println다음에 !를 붙여야 하며,
match 패턴의 경우 => 을 사용해서 실행 코드를 지정하고, else를 _로 표시하며,
숫자 입력시 천단위 구분 기호로 _를 사용하고,
char를 입력할 때는 작은 따옴표, String을 입력할 때는 큰따옴표,
반환 값의 타입을 지정할 때는 ->,
loop label은 ‘로 시작하며,
참조를 표시할 때는 &를 사용하고,
튜플과 구조체의 값을 지정할 때는 .을 사용합니다.
2. 구조체는 소유권을 가진다
Rust에서 구조체는 일반 변수처럼 소유권을 가집니다. 즉, 구조체를 다른 변수로 이동시키면 원래 변수는 더 이상 사용할 수 없습니다.
let user2 = user1; // user1의 소유권이 user2로 이동
// println!("{}", user1.email); // 오류!
필드 하나만 이동하는 경우도 마찬가지입니다.
let username = user1.username; // 소유권 이동 (user1.username에 대한 소유권은 종료됨)
// user1.username은 더 이상 유효하지 않음, username 변수가 소유권을 갖게 됨
println!("username: {}", username);
일부 필드를 참조로 처리하거나 클론(clone)을 사용해야 합니다.
let username = &user1.username;
또는
let username = user1.username.clone();
3. 기존 구조체 인스턴스로 새 구조체 인스턴스 생성하기
구조체 인스턴스를 만들 때 기존 구조체를 기반으로 일부 필드만 바꾸고 싶은 경우, 다음과 같이 .. 문법을 사용하여 나머지는 (user2와) 동일하다고 할 수 있습니다:
let user3 = User {
email: String::from("bob@example.com"),
..user2
};
단, user2는 이후 더 이상 사용할 수 없습니다. 그 이유는 username, email과 active 필드의 소유권이 user3에게로 넘어갔기 때문입니다.
또한 ..user2라고 나머지 필드는 똑같다고 할 때 맨 뒤에 ,를 붙이면 안됩니다. 구조체 정의할 때는 ,로 끝나도 되는 것과 구분됩니다.
4. 튜플 구조체 (Tuple Struct)
필드의 이름이 없고 형식만 있는 구조체도 정의할 수 있습니다. 이를 튜플 구조체라고 하며, 단순한 데이터 묶음에 유용합니다. 구조체 이름 다음이 중괄호가 아니라 소괄호인 것도 다릅니다.
struct Color(i32, i32, i32);
fn main() {
let red = Color(255, 0, 0);
println!("Red: {}, {}, {}", red.0, red.1, red.2);
}
5. 유사 유닛 구조체 (Unit-like Struct)
필드가 없는 구조체도 정의할 수 있습니다. 이를 유닛 구조체라고 하며, 마치 빈 enum처럼 동작하거나 타입 태깅 등에 사용됩니다.
struct Marker;
fn main() {
let _m = Marker;
}
이런 구조체는 메모리를 차지하지 않으며, 값 자체보다 타입에 의미를 둘 때 사용됩니다.
6. 구조체에 메서드 구현
Rust는 구조체에 메서드(method)를 추가할 수 있습니다. impl 블록을 통해 구조체에 동작을 부여할 수 있습니다.
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
&self는 해당 메서드가 구조체 인스턴스를 참조로 빌려서 사용한다는 뜻입니다.
fn main() {
let rect = Rectangle { width: 30, height: 50 };
println!("면적: {}", rect.area());
}
impl 블록 안에는 여러 메서드(함수)를 정의할 수 있으며, 정적 메서드(fn new, 생성자 역할)는 다음처럼 작성합니다:
impl Rectangle {
fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
}
위와 같이 생성자를 선언한 경우 아래와 같이 Rectangle::new 다음의 괄호 안에 필드 이름을 입력할 필요 없이 너비와 높이만을 입력해서 인스턴스를 만들 수 있으며 , 면적을 계산하는 것은 같습니다.
let rect1 = Rectangle::new(10, 20);
println!("rect1 면적: {}", rect1.area());
7. 디버깅을위한 #[derive(Debug)]
구조체를 println!으로 출력하려면 Debug 트레이트를 구현해야 합니다.
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 3, y: 7 };
println!("{:?}", p);
}
위에서 {:?} 포맷은 Debug 형식 출력을 의미하며, 결과는 Point { x: 3, y: 7 }처럼 구조체의 필드 이름과 값을 포함한 형태로 출력됩니다.
그러나, 아래와 같이 #[derive(Debug)]를 주석 처리하고 실행하면 “Point가 {:?}를 사용해서 포맷될 수 없다”는 에러 메시지가 표시됩니다.

마무리
Rust의 구조체는 단순한 데이터 묶음을 넘어서, 로직과 상태를 함께 표현할 수 있는 강력한 도구입니다. 구조체를 메서드와 함께 사용하면 객체지향적 모델도 자연스럽게 구현할 수 있으며, 안전하고 구조화된 데이터 설계가 가능합니다.