제네릭 구조체
Coddy Rust 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 61개 중 24번째.
지금까지 여러분이 만든 모든 구조체는 구체적이고 특정된 타입의 필드를 가지고 있었습니다. Pet은 String 타입의 이름을 가지고, Rectangle은 f64 타입의 치수를 가집니다. 하지만 어떤 타입의 데이터든 담을 수 있는 구조체를 만들고 싶다면 어떻게 해야 할까요?
이 지점에서 제네릭(generics)이 등장합니다. 제네릭을 사용하면 여러 타입과 함께 작동하는 유연하고 재사용 가능한 코드를 작성할 수 있습니다. 특정 타입을 하드코딩하는 대신, 구조체를 사용할 때 구체적인 타입으로 대체되는 플레이스홀더(일반적으로 T)를 사용합니다.
어떤 타입이든 담을 수 있는 간단한 래퍼(wrapper) 구조체입니다:
struct Wrapper<T> {
value: T,
}
구조체 이름 뒤의 <T>는 제네릭 타입 매개변수를 선언합니다. 구조체 내부에서 T는 실제로 사용하게 될 타입을 대신하는 역할을 합니다. 인스턴스를 생성할 때, Rust는 구체적인 타입을 추론합니다:
let int_wrapper = Wrapper { value: 42 }; // T는 i32입니다
let float_wrapper = Wrapper { value: 3.14 }; // T는 f64입니다
let text_wrapper = Wrapper { value: "hello" }; // T는 &str입니다
이들 각각은 서로 다른 구체적인 타입인 Wrapper<i32>, Wrapper<f64>, 그리고 Wrapper<&str>이지만, 모두 동일한 구조체 정의를 공유합니다. 이로 인해 본질적으로 동일한 작업을 수행하는 별도의 IntWrapper, FloatWrapper, StringWrapper 구조체를 작성할 필요가 없습니다.
문자 T는 단지 관례일 뿐입니다("Type"의 약자). 어떤 유효한 식별자라도 사용할 수 있지만, T는 Rust 및 제네릭을 지원하는 대부분의 다른 언어에서 표준입니다.
챌린지
쉬움어떤 타입의 값이라도 담을 수 있는 제네릭 컨테이너를 만들어 봅시다! 제네릭이 코드를 얼마나 유연하고 재사용 가능하게 만드는지 보여주는 Container 구조체를 생성하게 됩니다.
코드는 두 개의 파일로 구성됩니다:
container.rs:T타입의 단일 공개 필드item을 가진Container<T>라는 공개 제네릭 구조체를 정의합니다. 이 컨테이너는 정수, 부동 소수점, 문자열 또는 기타 모든 타입을 담을 수 있어야 합니다.main.rs: 컨테이너 모듈을 가져와서 서로 다른 타입의 데이터를 담는 컨테이너를 생성함으로써 그 유연성을 증명합니다. 세 개의 컨테이너를 생성하고 각각 무엇을 담고 있는지 표시합니다.
메인 파일에서 다음 컨테이너들을 생성하세요:
- 정수 값을 담는 컨테이너 (첫 번째 입력값,
i32로 파싱됨) - 부동 소수점 값을 담는 컨테이너 (두 번째 입력값,
f64로 파싱됨) - 문자열을 담는 컨테이너 (세 번째 입력값,
String으로 유지됨)
출력은 각 컨테이너의 내용을 다음 형식으로 표시해야 합니다:
Integer container: {value}
Float container: {value}
String container: {value}예를 들어, 입력값이 42, 3.14, hello인 경우:
Integer container: 42
Float container: 3.14
String container: hello동일한 Container<T> 정의가 세 가지 서로 다른 타입 모두에 어떻게 작동하는지 확인해 보세요. 이것이 바로 제네릭의 힘입니다!
정수 값, 부동 소수점 값, 문자열 값의 세 가지 입력을 받게 됩니다.
치트 시트
제네릭(Generics)을 사용하면 타입 매개변수 자리표시자(placeholder)를 사용하여 모든 유형의 데이터를 보유할 수 있는 구조체를 생성할 수 있습니다.
제네릭 구조체를 선언하려면 타입 매개변수(관례적으로 T를 사용)와 함께 꺾쇠괄호를 사용합니다:
struct Wrapper<T> {
value: T,
}
<T>는 제네릭 타입 매개변수를 선언하며, 구조체 내부의 T는 실제 타입을 위한 자리표시자 역할을 합니다.
인스턴스를 생성할 때, Rust는 구체적인 타입을 추론합니다:
let int_wrapper = Wrapper { value: 42 }; // T는 i32입니다
let float_wrapper = Wrapper { value: 3.14 }; // T는 f64입니다
let text_wrapper = Wrapper { value: "hello" }; // T는 &str입니다
각 인스턴스는 동일한 구조체 정의를 공유하면서 서로 다른 구체적인 타입(Wrapper<i32>, Wrapper<f64>, Wrapper<&str>)이 됩니다.
직접 해보기
mod container;
use container::Container;
fn main() {
// 세 개의 입력을 읽습니다
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let int_value: i32 = input1.trim().parse().expect("Failed to parse integer");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let float_value: f64 = input2.trim().parse().expect("Failed to parse float");
let mut input3 = String::new();
std::io::stdin().read_line(&mut input3).expect("Failed to read line");
let string_value = input3.trim().to_string();
// TODO: Container<T>를 사용하여 세 개의 컨테이너를 생성합니다
// 1. int_value를 담는 정수형 컨테이너
// 2. float_value를 담는 실수형 컨테이너
// 3. string_value를 담는 문자열 컨테이너
// TODO: 각 컨테이너의 내용을 요구된 형식으로 출력합니다
// 정수형 컨테이너: {value}
// 실수형 컨테이너: {value}
// 문자열 컨테이너: {value}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.