RUST

[RUST] 제네릭 (Generics)

ssh9308 2024. 1. 22. 10:38
반응형

제네릭 (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());
    
}

반응형