Newtype 패턴은 기존 타입을 감싸서 새로운 타입을 만듭니다. 타입 안전성을 높이고 의미를 명확히 합니다.
// 미터와 피트를 혼동하지 않도록!
struct Meters(f64);
struct Feet(f64);
impl Meters {
fn to_feet(&self) -> Feet {
Feet(self.0 * 3.28084)
}
}
impl Feet {
fn to_meters(&self) -> Meters {
Meters(self.0 / 3.28084)
}
}
fn calculate_distance(m: Meters) -> f64 {
m.0
}
fn main() {
let height = Meters(100.0);
let height_feet = height.to_feet();
// 타입 체크로 실수 방지!
// calculate_distance(height_feet); // 컴파일 에러!
calculate_distance(height); // OK
}
use std::fmt;
// Vec<String>에 Display 구현 불가 (orphan rule)
// Newtype으로 해결!
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let w = Wrapper(vec![
String::from("hello"),
String::from("world"),
]);
println!("w = {}", w);
}
복잡한 타입에 간단한 이름을 부여합니다.
// 복잡한 타입 단순화
type Kilometers = i32;
type Thunk = Box<dyn Fn() + Send + 'static>;
// Result 타입 특화
type Result<T> = std::result::Result<T, std::io::Error>;
fn read_file() -> Result<String> {
std::fs::read_to_string("file.txt")
}
// 복잡한 함수 포인터
type FnPointer = fn(i32) -> i32;
fn apply(f: FnPointer, x: i32) -> i32 {
f(x)
}
fn main() {
let distance: Kilometers = 5;
let thunk: Thunk = Box::new(|| println!("Hello!"));
thunk();
let double: FnPointer = |x| x * 2;
println!("{}", apply(double, 5));
}
컴파일 타임에만 존재하고 런타임에는 사라지는 타입 마커입니다.
use std::marker::PhantomData;
// 타입 상태 패턴
struct Locked;
struct Unlocked;
struct Door<State> {
id: u32,
_state: PhantomData<State>,
}
impl Door<Locked> {
fn new(id: u32) -> Self {
Door {
id,
_state: PhantomData,
}
}
fn unlock(self) -> Door<Unlocked> {
println!("Door {} unlocked", self.id);
Door {
id: self.id,
_state: PhantomData,
}
}
}
impl Door<Unlocked> {
fn open(&self) {
println!("Door {} opened", self.id);
}
fn lock(self) -> Door<Locked> {
println!("Door {} locked", self.id);
Door {
id: self.id,
_state: PhantomData,
}
}
}
fn main() {
let door = Door::new(1);
// door.open(); // 컴파일 에러! Locked 상태에서는 불가능
let door = door.unlock();
door.open(); // OK!
let door = door.lock();
// door.open(); // 다시 컴파일 에러!
}
trait Container {
const MAX_SIZE: usize;
type Item;
fn add(&mut self, item: Self::Item);
fn is_full(&self) -> bool;
}
struct Stack<T> {
items: Vec<T>,
}
impl<T> Container for Stack<T> {
const MAX_SIZE: usize = 100;
type Item = T;
fn add(&mut self, item: T) {
if !self.is_full() {
self.items.push(item);
}
}
fn is_full(&self) -> bool {
self.items.len() >= Self::MAX_SIZE
}
}
// nightly에서만 사용 가능
#![feature(specialization)]
trait Print {
fn print(&self);
}
impl<T> Print for T {
default fn print(&self) {
println!("Generic print");
}
}
impl Print for i32 {
fn print(&self) {
println!("i32: {}", self);
}
}
impl Print for String {
fn print(&self) {
println!("String: {}", self);
}
}
enum Message {
Hello { id: i32 },
}
fn main() {
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello {
id: id_variable @ 3..=7,
} => println!("Found an id in range: {}", id_variable),
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Found some other id: {}", id),
}
}
fn main() {
let num = Some(4);
let split = 2;
match num {
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) if x % split == 0 => println!("divisible by {}: {}", split, x),
Some(x) => println!("{}", x),
None => (),
}
}
절대 반환하지 않는 함수의 타입입니다.
// 무한 루프
fn infinite_loop() -> ! {
loop {
println!("Forever!");
}
}
// panic!도 ! 타입을 반환
fn divide(x: i32, y: i32) -> i32 {
if y == 0 {
panic!("Division by zero");
}
x / y
}
// continue도 ! 타입
fn process_numbers(numbers: Vec<i32>) {
for num in numbers {
let result = match num {
0 => continue, // ! 타입
n if n < 0 => panic!("Negative!"), // ! 타입
n => n * 2,
};
println!("{}", result);
}
}
use std::marker::PhantomData;
// 빌더 상태
struct No;
struct Yes;
struct UserBuilder<HasName, HasEmail> {
name: Option<String>,
email: Option<String>,
age: Option<u32>,
_has_name: PhantomData<HasName>,
_has_email: PhantomData<HasEmail>,
}
impl UserBuilder<No, No> {
fn new() -> Self {
UserBuilder {
name: None,
email: None,
age: None,
_has_name: PhantomData,
_has_email: PhantomData,
}
}
}
impl<E> UserBuilder<No, E> {
fn name(self, name: String) -> UserBuilder<Yes, E> {
UserBuilder {
name: Some(name),
email: self.email,
age: self.age,
_has_name: PhantomData,
_has_email: PhantomData,
}
}
}
impl<N> UserBuilder<N, No> {
fn email(self, email: String) -> UserBuilder<N, Yes> {
UserBuilder {
name: self.name,
email: Some(email),
age: self.age,
_has_name: PhantomData,
_has_email: PhantomData,
}
}
}
impl<N, E> UserBuilder<N, E> {
fn age(mut self, age: u32) -> Self {
self.age = Some(age);
self
}
}
// build는 필수 필드가 모두 설정된 경우에만 가능!
impl UserBuilder<Yes, Yes> {
fn build(self) -> User {
User {
name: self.name.unwrap(),
email: self.email.unwrap(),
age: self.age,
}
}
}
struct User {
name: String,
email: String,
age: Option<u32>,
}
fn main() {
let user = UserBuilder::new()
.name("Alice".to_string())
.email("alice@example.com".to_string())
.age(30)
.build(); // OK!
// 컴파일 에러 예제:
// let user = UserBuilder::new()
// .name("Bob".to_string())
// .build(); // 에러! email 필요
}