
안녕하세요.
오늘은 제네릭, 트레잇에 대해 알아보겠습니다.
📌 Preview
제네릭을 사용하면 타입을 일반화하여 재사용성을 높일 수 있습니다.
트레잇은 공통된 동작을 정의하여 여러 타입에서 사용할 수 있도록 합니다.
- 다른 언어의 Interface와 유사한 개념입니다
📌 제네릭 (Generic)
구체적인 타입을 명시하지 않고 다양한 타입에 대해 동작할 수 있도록 하는 기능입니다.
- 함수, 구조체, 열거형 등을 특정 타입에 의존하지 않고 재사용할 수 있도록 합니다.
제네릭 구조체
다른 언어와 유사한 방식으로 구조체를 사용할 수 있습니다.
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer_point = Point { x: 5, y: 10 };
let float_point = Point { x: 1.2, y: 3.4 };
println!("integer: ({}, {}), float: ({}, {})",
integer_point.x, integer_point.y, float_point.x, float_point.y);
// integer: (5, 10), float: (1.2, 3.4)
}
구조체 함수에서도 사용할 수 있습니다.
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mix_up<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 1, y: 2.5 };
let p2 = Point { x: 'k', y: "Kang" };
let p3 = p1.mix_up(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y); // p3.x = 1, p3.y = Kang
}
제네릭 열거형
열거형에서도 구조체와 똑같이 사용할 수 있습니다.
enum Result<T, E> {
Ok(T),
Err(E),
}
제네릭 함수
제네릭을 사용하면 다양한 타입을 받아 들이는 함수를 만들 수 있습니다.
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let numbers = vec![10, 20, 30, 5, 50];
let chars = vec!['a', 'z', 'm', 'd'];
println!("가장 큰 숫자: {}", largest(&numbers)); // 50
println!("가장 큰 문자: {}", largest(&chars)); // z
}
- T: PartialOrd + Copy → T는 PartialOrd, Copy를 구현하는 타입만 올 수 있음
- 제네릭 제약 (→ 트레잇 바운드에서 자세히 살펴볼 예정입니다)
- PartialOrd: 비교 가능
- Copy: 복사 가능
📌 트레잇 (Trait)
다른 언어에서 인터페이스(Interface) 나 추상 클래스(Abstract Class) 와 유사한 개념입니다.
- 구조체나 열거형이 공통적으로 가져야 할 동작을 정의합니다.
trait Speak {
fn speak(&self);
}
struct Dog;
struct Cat;
impl Speak for Dog {
fn speak(&self) {
println!("멍멍!");
}
}
impl Speak for Cat {
fn speak(&self) {
println!("야옹!");
}
}
fn main() {
let dog = Dog;
let cat = Cat;
dog.speak(); // 멍멍!
cat.speak(); // 야옹!
}
Default 구현도 가능합니다.
trait Animal {
fn make_sound(&self) {
println!("(조용함...)");
}
}
struct Cat;
impl Animal for Cat {} // 기본 구현을 사용
fn main() {
let cat = Cat;
cat.make_sound(); // (조용함...)
}
📌 트레잇 바운드
다른 언어의 제네릭 제약과 같은 기능입니다.
- 제네릭 타입이 특정 트레잇을 구현해야 한다는 조건을 설정합니다
use std::fmt::Display;
fn print_info<T: Display>(item: T) {
println!("출력값: {}", item);
}
fn main() {
print_info(42); // 정수(i32) 출력 가능
print_info("Hello"); // 문자열 출력 가능
print_info(vec![1, 2, 3]); // ❌ 컴파일 에러 (Vec<T>는 Display를 구현하지 않음)
}
- T는 반드시 Display 트레잇을 구현해야 합니다

여러 개의 트레잇 바운드를 적용할 수도 있습니다.
use std::fmt::{Display, Debug};
fn show_info<T: Display + Debug>(item: T) {
println!("출력값: {}", item);
println!("디버그 정보: {:?}", item);
}
fn main() {
show_info(42); // ✅ 정수는 Display + Debug 모두 구현
show_info("Hi"); // ✅ 문자열도 Display + Debug 모두 구현
}
- '+'를 사용하면 됩니다
where
트레잇 바운드가 많아지면 where 문법을 사용하여 가독성을 높일 수 있습니다.
fn example<T, U>(a: T, b: U)
where
T: Display + Clone,
U: Debug + Default,
{
println!("{}", a);
println!("{:?}", b);
}
함수 반환값 타입에도 트레잇 바운드를 설정할 수 있습니다.
use std::fmt::Display;
fn get_message() -> impl Display {
"Hello, Rust!"
}
fn main() {
println!("{}", get_message());
}
- get_message() 반환값은 Display를 구현한 타입만 가능합니다
📌 동적 디스패치 (Dynamic Dispatch)
지금까지 살펴본 디스패치는 정적 디스패치 입니다.
- impl Trait은 정적 디스패치 → 컴파일 타임에 결정됩니다
- dyn Trait은 동적 디스패치 → 런타임에 결정 됩니다
왜 이런 동적 디스패치가 필요할까요?
→ 모든 타입을 컴파일 타임에 결정할 수 없는 경우가 있습니다.
Case1)
아래와 같이 트레잇과 구조체를 선언해 줍니다.
trait Animal {
fn make_sound(&self);
}
struct Dog;
struct Cat;
impl Animal for Dog {
fn make_sound(&self) {
println!("멍멍!");
}
}
impl Animal for Cat {
fn make_sound(&self) {
println!("야옹!");
}
}
Dog와 Cat을 담은 벡터를 사용하고 싶지만, 정적 디스패치를 사용하면 컴파일 에러가 납니다.
fn main() {
let animals: Vec<impl Animal> = vec![Dog, Cat]; // ❌ 불가능
}

동적 디스패치를 사용하면 가능합니다.
fn main() {
let animals: Vec<Box<dyn Animal>> = vec![Box::new(Dog), Box::new(Cat)];
for animal in animals {
animal.make_sound();
}
}
Case2)
정적 디스패치는 반환 타입이 하나로 고정되어야 합니다.
fn get_animal(is_dog: bool) -> impl Animal {
if is_dog {
Dog
} else {
Cat // ❌ 오류: `impl Trait`을 사용하면 반환 타입이 고정되어야 함!
}
}

동적 디스패치를 사용하면 가능합니다.
fn get_animal(is_dog: bool) -> Box<dyn Animal> {
if is_dog {
Box::new(Dog)
} else {
Box::new(Cat)
}
}
fn main() {
let animal = get_animal(true);
animal.make_sound(); // 멍멍!
}
✅ dyn Trait vs impl Trait 비교 정리
특징 | 정적 디스패치 | 동적 디스패치 |
타입 결정 시점 | 컴파일 타임에 결정 | 런타임에 결정 |
성능 | 빠름 (최적화 가능) | 느림 (vtable 사용) |
사용 예시 | 반환 타입이 항상 같은 타입일 때 | 반환 타입이 여러 개일 때 |
메모리 | 스택에 저장 가능 | 힙 할당 필요 (Box 사용) |
메서드 호출 | 직접 호출 (인라인 최적화 가능) | vtable을 통해 호출 |
📌 정리
개념 | 설명 | 예제 |
제네릭 (Generics) | 타입을 일반화하여 재사용 가능 | fn largest<T: PartialOrd + Copy>(list: &[T]) -> T |
제네릭 구조체 | 여러 타입을 다룰 수 있는 구조체 | struct Point<T, U> { x: T, y: U } |
트레잇 (Traits) | 공통된 동작(메서드)을 정의 | trait Speak { fn speak(&self); } |
트레잇 바운드 | 특정 트레잇을 구현한 타입만 사용 가능 | fn print_summary<T: Summary>(item: &T) |
'Rust' 카테고리의 다른 글
[Rust] 에러 처리 (0) | 2025.03.30 |
---|---|
[Rust] 스마트 포인터 (2) - RefCell<T>, Arc<T>, Mutex<T> (0) | 2025.03.24 |
[Rust] 스마트 포인터 (1) - Box<T>, Rc<T> (2) | 2025.03.24 |
[Rust] 소유권, 참조와 빌림, 슬라이스 (0) | 2025.03.03 |
[Rust] 기초 문법 (2) | 2025.02.28 |