Rust는 코드의 재사용성과 타입 안전성을 동시에 보장하기 위해 제네릭(Generic)과 트레잇(Trait)을 사용합니다. 이 개념은 Rust의 함수, 구조체, 열거형 등 다양한 곳에 적용되는데, 제네릭은 일반화된 타입을 의미하고, Trait은 공통된 행위 또는 특성을 의미합니다.
1. 제네릭(Generic)
가. 형식별 별도 함수
제네릭은 “특정 타입에 얽매이지 않고 다양한 타입에 대해 작동할 수 있도록 하는 일반화된 타입”을 의미합니다. 따라서, 타입에 의존하지 않는 유연한 코드를 작성할 수 있도록 도와줍니다.
아래 코드는 정수 타입 i32에 대해서는 largest_i32 함수를 사용하고, char에 대해서는 largest_char 함수를 따로 적용하고 있습니다.
fn largest_i32(list: &[i32]) -> i32 {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn largest_char(list: &[char]) -> char {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
let result = largest_i32(&numbers);
println!("The largest number is {}", result);
let chars = vec!['y', 'm', 'a', 'q'];
let result = largest_char(&chars);
println!("The largest char is {}", result);
}
위 코드를 실행하면 아래와 같이 “가장 큰 수는 100, 가장 큰 문자는 y(번역)”라고 화면에 표시됩니다.

나. 제네릭 타입의 하나의 함수
위와 같이 i32와 char라는 데이터 타입에 따라 다른 함수를 하나의 함수로 합치기 위해 T라는 제네릭을 이용했습니다.
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
let result = largest(&numbers);
println!("The largest number is {}", result);
let chars = vec!['y', 'm', 'a', 'q'];
let result = largest(&chars);
println!("The largest char is {}", result);
}
위 코드를 실행하면
The largest number is 100
The largest char is y
가 화면에 출력되는데,
Generic T를 정의하는 구문에서 : PartialOrd + Copy를 제거하고 실행하면

error[E0369]: binary operation >
cannot be applied to type T
(에러 E0369: 이진 동작인 > 는 T 타입에 적용될 수 없습니다)
란 에러 메시지가 표시되고,
그 아래 T 다음에 “: std:cmp::PartialOrd를 추가하라고 합니다.
PartialOrd 는 두 값을 비교하여 크거나 작거나 같은지 부분적으로만 결정할 수 있는 경우를 위해 설계되었습니다.예를 들어 부동 소수점 숫자(floating-point numbers)의 경우 NaN (Not a Number) 값이 존재하기 때문에 완전한 비교가 불가능합니다. Ord 는 두 값을 항상 비교할 수 있는 경우를 위해 설계되었습니다. |
그래서 T 다음에 PartialOrd만 추가하고 Copy는 빼고 실행하면

let mut largest = list[0];의 list[0]에서
error[E0508]: cannot move out of type [T]
, a non-copy slice
(복사할 수 없는 슬라이스인 `[T]` 유형에서 이동할 수 없습니다)
란 에러가 발생하고,
let mut largest = &list[0];에서는
type ‘T’가 Copy trait를 구현하지 않았기 때문에 move가 일어났는데, 여기서 data가 move될 수 없다고 합니다.
정수나 문자 타입에 공통적으로 적용하기 위해 제네릭 타입을 적용하는 것은 좋은데, 적용하기 위해 추가해야 할 요소들이 많습니다. 그리고, 두 개의 Trait을 연결하기 위해서는 +를 사용헸습니다.
다. 구조체에 한 가지 제네릭 적용
struct Point<T> {
x: T,
y: T,
}
fn main() {
let int_point = Point { x: 1, y: 2 };
let float_point = Point { x: 1.0, y: 2.0 };
println!("({:.1}, {:.1})", int_point.x, int_point.y);
println!("({:.1}, {:.1})", float_point.x, float_point.y);
}
- 위 코드의 경우 타입이 T 하나뿐이 없기 때문에, 타입이 한 가지인 경우만 적용가능합니다. 따라서 Point 구조체를 정의할 때 i32 또는 f64 타입 한 가지로만 지정했습니다.
- 그리고, {}라고 하면 실수인 경우에도 1로 출력이 돼서 {:.1}로 출력 형식을 변경했습니다.
라. 구조체에 두 가지 제네릭 적용
- 아래와 같이 하나의 구조체에 여러 타입, T와 U를 지정할 수 있습니다. 이 경우 T나 U는 특별한 의미가 있는 것이 아니고, 타입이 다르다는 것을 의미합니다.
struct Mixed<T, U> {
x: T,
y: U,
}
위에서 두 가지 타입을 지정했으므로 아래와 같이 i32와 f64를 같이하거나 달리해서 지정할 수 있습니다.

위 코드를 실행하면
(1, 2)
(1.0, 2)
식으로 화면에 표시됩니다.
2. 트레잇(Trait)
트레잇은 공통된 동작을 정의하는 인터페이스입니다. 특정 타입이 트레잇을 구현하면, 해당 트레잇의 메서드를 사용할 수 있습니다.
가. 예제 코드
trait Summary {
fn summarize(&self) -> String;
}
struct Article {
title: String,
author: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
fn main() {
let article = Article {
title: String::from("Rust 배우기"),
author: String::from("홍길동"),
};
println!("요약: {}", article.summarize());
}
(1) trait 트레잇 이름 { 메소드 선언 }
- trait 키워드로 트레잇, 여기서는 Summary 트레잇을 정의하는데, 함수는 함수명과 반환 타입만 지정했고, 구체적인 동작은 정의되지 않았습니다.
(2) 구조체 정의
- struct Article { }은 Article이란 구조체를 title과 author 모두 String으로 정의했습니다.
(3) impl 트레잇명 for type명
- Trait을 구현하는 방식은 impl Trait for Type으로서
impl Summary for Article이라고 적은 다음
중괄호 안에서 fn summarize로 메소드를 구현하는데,
인수는 &self로 자기 자신, 여기서는 Article을 받고,
반환 형식은 위에서 지정했듯이 String 타입이며,
구체적으로 실행하는 것은
format!(“{} by {}”, self.title, self.author)으로
self.title by self.author란 문자열을 생성합니다.
(4) main 함수에서 구조체 생성 및 트레잇 메소드 실행
- main 함수에서는 구조체를 생성한 다음 aricle.summarize로 title과 author를 이용한 문자열을 만든(format) 후 println!로 화면에 출력합니다.
- 따라서, 위 코드를 실행하면 아래와 같이 “요약 : Rust 배우기 by 홍길동”이 출력됩니다.

나. 트레잇 바운드(Trait Bound)
함수 인자에서 트레잇을 사용하는 방법은 다음과 같이 세 가지가 있습니다.
(1) 케이스 1
item을 impl Summary로 지정하고, main 함수에서 notify 함수의 인수로 aritcle을 입력하는 것입니다.
fn notify(item: impl Summary) {
println!("알림: {}", item.summarize());
}
예제는 아래와 같습니다.
기존 코드에 위 fn notify를 추가하고,
main함수에서 println!(“요약: {}”, article.summarize());을 주석 처리하고, notify(article);을 추가하면 됩니다.
trait Summary {
fn summarize(&self) -> String;
}
struct Article {
title: String,
author: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
fn notify(item: impl Summary) {
println!("New notification: {}", item.summarize());
}
fn main() {
let article = Article {
title: String::from("Rust 배우기"),
author: String::from("홍길동"),
};
notify(article);
// println!("요약: {}", article.summarize());
}
(2) 케이스 2
아래는 “notify 함수를 정의하는데, 제네릭 타입 T는 Summary 트레잇을 구현한 타입으로 제한하고, 함수의 매개변수 item은 T 타입이다”라는 의미입니다. 가장 많이 사용하는 형식입니다.
fn notify<T: Summary>(item: T) {
println!("알림: {}", item.summarize());
}
위 코드를 활용한 코드는 아래와 같이 Generic에서 설명한 코드와 비슷합니다. 케이스 1과 다른 점은 article을 참조 형식(&article)으로 전달하는 것입니다.
trait Summary {
fn summarize(&self) -> String;
}
struct Article {
title: String,
author: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
fn notify<T: Summary>(item: &T) {
println!("New notification: {}", item.summarize());
}
fn main() {
let article = Article {
title: String::from("Rust 배우기"),
author: String::from("홍길동"),
};
notify(&article);
// println!("요약: {}", article.summarize());
}
(3) 케이스 3
여러 트레잇을 조합할 수도 있습니다.
fn process<T: Summary + Clone>(item: T) {
println!("처리: {}", item.summarize());
}
구조체가 Clone이 가능하도록 Struct 구조체 선언문 위에
#[derive(Clone)] 문이 있어야 하고,
main함수에서 notify의 인수를 aricle로 지정하면 됩니다.
trait Summary {
fn summarize(&self) -> String;
}
#[derive(Clone)]
struct Article {
title: String,
author: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
fn notify<T: Summary + Clone>(item: T) {
println!("New notification: {}", item.summarize());
}
fn main() {
let article = Article {
title: String::from("Rust 배우기"),
author: String::from("홍길동"),
};
notify(article);
// println!("요약: {}", article.summarize());
}
다. 트레잇 객체 (Trait Object)
정확한 타입을 모르더라도 트레잇이 구현된 어떤 타입이든 받아들이고 싶을 때는 다음과 같이 dyn 키워드를 사용합니다.
fn notify(item: &dyn Summary) {
println!("알림: {}", item.summarize());
}
- 런타임 시 어떤 타입인지 결정됩니다 (동적 디스패치).
- 반드시 참조(&) 형태로만 사용 가능합니다.
trait Summary {
fn summarize(&self) -> String;
}
// #[derive(Clone)]
struct Article {
title: String,
author: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
fn notify(item: &dyn Summary) {
println!("New notification: {}", item.summarize());
}
fn main() {
let article = Article {
title: String::from("Rust 배우기"),
author: String::from("홍길동"),
};
notify(&article);
// println!("요약: {}", article.summarize());
}
🧠 요약
개념 | 설명 |
---|---|
Generic | 여러 타입에 대해 재사용 가능한 코드 |
Trait | 공통 기능 정의, 타입에 메서드 제공 |
Trait Bound | 함수나 구조체에 트레잇 구현 타입만 허용 |
impl Trait | 간결하게 바운드 지정 가능 |
dyn Trait | 트레잇 객체, 런타임에 타입이 결정됨 |