제네릭 (Generics) 이란?
Rust에서 제네릭(generics)은 다양한 데이터 타입에 대해 동작할 수 있는 함수나
구조체, 열거형(enum), 메서드를 작성할 수 있게 해주는 프로그래밍 기능이다.
제네릭을 사용하면 코드 중복을 줄이고, 타입 안전성을 유지하면서도 유연성을 높일 수 있다.
제네릭의 사용 예시
제네릭을 사용하는 가장 기본적인 예는 함수에서 여러 타입을 처리할 수 있도록 하는 것이다.
예를 들어, 특정 리스트 내의 최대값을 반환하는 함수를 작성하고 싶다고 가정 보자.
다음은 제네릭을 사용하여 어떤 타입의 리스트 값에 대해서도
동작할 수 있는 함수를 만드는 방법을 보여준다.
함수에서의 제네릭 사용
아래의 예제는
i32 배열에서 가장 큰 수를 찾는 함수 largest_i32()
char 배열에서 가장 큰 글짜를 찾는 함수 largest_char()
String 배열에서 가장 큰 문자열을 찾는 함수 largest_string()
함수가 존재한다.
fn largest_i32(list: &[i32]) -> &i32 {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item
}
}
largest
}
fn largest_char(list: &[char]) -> &char {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item
}
}
largest
}
fn largest_string(list: &[String]) -> &String {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item
}
}
largest
}
fn main() {
let numbers = vec![5,43,77,12,190,8];
let result1 = largest_i32(&numbers);
println!("가장 큰 수는 {}",result1);
let chars = vec!['a', 'z', 'c'];
let result2 = largest_char(&chars);
println!("가장 큰 글짜는 {}",result2);
let strings: Vec<String> = vec!["apple".to_string(), "samsung".to_string(), "google".to_string()];
let result3 = largest_string(&strings);
println!("가장 큰 문장은 {}",result3);
}
위의 세개의 함수를 잘 들여다보면,
파라미터로 들어가는 타입의 종류만 다를 뿐, 로직은 똑같은 것을 알 수 있다.
이럴 때 제네릭을 사용해서 세 개의 함수를 하나의 함수로 축소시킬 수 있다.
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item
}
}
largest
}
fn main() {
let numbers = vec![5,43,77,12,190,8];
let result1 = largest(&numbers);
println!("가장 큰 수는 {}",result1);
let chars = vec!['a', 'z', 'c'];
let result2 = largest(&chars);
println!("가장 큰 글짜는 {}",result2);
let strings: Vec<String> = vec!["apple".to_string(), "samsung".to_string(), "google".to_string()];
let result3 = largest(&strings);
println!("가장 큰 문장은 {}",result3);
}
여기서 주의할 점은 제네릭으로 받은 변수 T 가 모든 타입에 대해서 가능한 게 아니고
T 타입이 std::cmp::PartialOrd 트레이트를 구현하고 있을 경우에만 적용가능하다는 뜻이다.
구조체에서의 제네릭 사용
구조체에서도 제네릭을 사용할 수 있다.
예를 들어, 특정 타입의 값으로 구성된 포인트를 나타내는 구조체를 만들고 싶다면
다음과 같이 작성할 수 있다.
#[derive(Debug)]
struct Pointer<T> {
x: T,
y: T,
z: T
}
fn main() {
let p1 = Pointer{ x: 3, y: 10, z: 20};
let p2 = Pointer{ x : 1.4, y: 5.0, z: 15.4};
println!("p1 = {:?}, p2 = {:?}", p1, p2);
}
하지만, 아래와 같이 구조체를 선언하면 문제가 발생한다.
#[derive(Debug)]
struct Pointer<T> {
x: T,
y: T,
z: T
}
fn main() {
let p3 = Pointer{ x: 3, y: 10, z: 20.1};
}
구조체 내부에 있는 변수 모두 T 를 받고 있다.
즉 x,y,z 의 변수 모두 같은 데이터 타입을 가져야 한다는 의미인데,
x, y는 i32 z는 i64 데이터 타입이기 때문에 타입 불일치로 오류가 발생한다.
만약에 다른 타입으로 제네렉을 해주고 싶다면 아래와 같이 제네릭을 추가로 선언해 주면 된다.
#[derive(Debug)]
struct Pointer<T,U> {
x: T,
y: T,
z: U
}
fn main() {
let p3 = Pointer{ x: 3, y: 10, z: 20.1};
}
x, y, 의 제네릭과 z의 제네릭 타입이 다르기 때문에,
위의 예제는 문제가 없는 코드가 된다.
열거형(Enum)에서의 제네릭 사용
열거형(enum)에서도 제네릭을 사용할 수 있으며,
Option과 Result는 Rust의 표준 라이브러리에서 제공하는 제네릭 열거형의 좋은 예이다.
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
메서드 에서의 제네릭 사용
구조체나 열거형의 메서드에서도 제네릭을 사용할 수 있다.
메소드메서드 내에서 구조체의 제네릭 타입을 사용하거나,
메서드 자체에서 제네릭 매개변수를 도입할 수 있다.
#[derive(Debug)]
struct Pointer<T> {
x: T,
y: T,
z: T
}
impl<T> Pointer<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p1 = Pointer{ x: 3, y: 10, z: 20};
println!("p1.x = {:?}",p1.x());
}
'RUST' 카테고리의 다른 글
[RUST] Rc, Arc 란 (1) | 2024.04.30 |
---|---|
[RUST] 트레이트 (Traits) (0) | 2024.01.23 |
[RUST] Error (0) | 2024.01.03 |
[RUST] ENUM (0) | 2024.01.02 |
[RUST] 구조체(structure) (0) | 2023.12.29 |