구조체와 열거형
Rust에서 struct는 데이터 필드를 그룹화하는 데 사용되며, 단순한 데이터 컨테이너 역할을 합니다. 반면 enum은 특정 값들의 집합을 정의하는 데 적합합니다.
struct Person {
full_name: String,
sex: Gender,
}
enum Gender {
Unknown = 0,
Female = 1,
Male = 2,
}
연관 함수와 메서드
impl 블록 내부에서 정의되는 함수는 두 가지 유형으로 나뉩니다. 첫 번째는 연관 함수로, 타입 자체에 바인딩된 함수입니다. 호출 시 타입::함수명() 형식을 사용합니다.
impl Person {
fn create() -> Self {
Person {
full_name: "홍길동".into(),
sex: Gender::Male,
}
}
fn calculate_age(_a: i32, _b: i32) -> i32 {
_a + _b
}
}
두 번째는 메서드로, 첫 번째 인자가 &self 또는 &mut self 형태인 함수입니다. 이들은 인스턴스를 통해 호출되며, 문법적으로 인스턴스.메서드() 형태로 사용됩니다.
impl Person {
fn rest(&self) {
println!("잠자기 시작");
}
fn eat(&mut self, food: &str) {
println!("먹는 중: {}", food);
}
}
fn main() {
let mut person = Person::create();
person.rest();
person.eat("김치찌개");
}
실제로 person.rest()는 내부적으로 Person::rest(&person)로 변환됩니다. 즉, 메서드는 연관 함수의 특수한 형태이며, 문법적 설탕일 뿐입니다. 마찬가지로 &self는 self: &Person의 약어이고, &mut self는 self: &mut Person을 의미합니다.
그러나 Person::rest(&person)처럼 직접 호출하는 경우는, 특정 트레이트의 동일한 이름의 메서드를 명시적으로 호출해야 할 때 필요합니다.
트레이트(Tratis): 인터페이스 정의
트레이트는 다른 언어에서의 인터페이스나 추상 클래스와 유사하며, 일정한 행동을 보장하기 위한 계약입니다. 이를 통해 다형성과 코드 재사용을 가능하게 합니다.
trait Sound {
fn make_sound(&self) -> &str;
}
struct Sheep;
struct Cow;
struct Human;
impl Sound for Sheep {
fn make_sound(&self) -> &str {
"메리메리"
}
}
impl Sound for Cow {
fn make_sound(&self) -> &str {
"음머머"
}
}
impl Sound for Human {
fn make_sound(&self) -> &str {
"안녕하세요!"
}
}
// 메서드 오버라이드 예시
impl Human {
fn make_sound(&self) -> &str {
"하하하"
}
}
fn main() {
let sheep = Sheep;
let cow = Cow;
let human = Human;
println!("{}", sheep.make_sound());
println!("{}", cow.make_sound());
println!("{}", human.make_sound());
// 인스턴스 메서드 호출
println!("{}", Human::make_sound(&human));
// 명시적인 트레이트 메서드 호출
println!("{}", Sound::make_sound(&human));
}
트레이트를 파라미터로 사용
함수 매개변수로 트레이트를 지정하면, 해당 트레이트를 구현한 모든 타입의 인스턴스를 전달할 수 있습니다.
fn trigger_sound(animal: &impl Sound) {
println!("{}", animal.make_sound());
}
여러 트레이트를 동시에 요구할 경우, + 기호로 연결합니다.
fn process(item: &T) { ... }
트레이트를 반환값으로 사용
함수의 반환값으로 트레이트를 지정할 수도 있습니다. 이는 구현을 숨기고, 유연한 타입 반환을 가능하게 합니다.
fn get_animal() -> impl Sound {
Human
}
제네릭 (Generics)
제네릭은 타입을 일반화하여 다양한 타입에 대해 동일한 로직을 적용할 수 있게 해줍니다.
struct Point<T> {
x: T,
y: T,
}
fn type_of<T>(_value: &T) -> &'static str {
std::any::type_name::<T>()
}
fn main() {
println!("{}", type_of(&42));
println!("{}", type_of(&"Hello".to_string()));
println!("{}", type_of(&Point { x: 1.0, y: 2.0 }));
}
트레이트와 제네릭의 결합
트레이트 제약 조건을 제네릭과 함께 사용하면, 특정 행동을 보장하는 타입만 허용할 수 있습니다.
fn call_speak(item: &T) {
println!("{}", item.make_sound());
}
또는 where 키워드를 사용해 더 복잡한 제약 조건을 표현할 수 있습니다.
fn call_speak<T>(item: &T)
where
T: Sound,
{
println!("{}", item.make_sound());
}
여러 트레이트를 동시에 요구할 때도 +를 사용합니다.
struct Container {
data: T,
}
fn process_item(input: T) { ... }
어트리뷰트 (Attributes)
Rust는 컴파일러에게 추가 정보를 제공하는 어트리뷰트를 지원합니다. 예를 들어, 함수의 동작 방식이나 문서화, 테스트 설정 등을 제어할 수 있습니다.
공식 문서 참조: Rust Reference - Attributes