뉴타입 패턴
Coddy Rust 여정의 Object Oriented Programming 섹션에 포함된 레슨. 61개 중 54번째.
때로는 같은 타입의 두 값이 완전히 다른 것을 나타내기도 합니다. 사용자의 이메일과 비밀번호는 둘 다 String 값일 수 있지만, 함수 호출에서 실수로 서로 바꾸면 심각한 버그가 발생합니다. Newtype 패턴은 기본 타입을 감싸는 서로 구별되는 타입을 만들어 이 문제를 해결합니다.
newtype는 단일 필드를 가진 튜플 구조체일 뿐입니다:
struct Password(String);
struct Email(String);
둘 다 String을 감싸지만, 컴파일러는 이를 완전히 다른 타입으로 처리합니다. Email이 필요한 곳에 실수로 Password를 전달할 수 없습니다. 컴파일러가 그 실수를 잡아냅니다.
fn send_reset_email(email: Email, password: Password) {
// ...
}
let email = Email(String::from("user@example.com"));
let pass = Password(String::from("secret123"));
send_reset_email(email, pass); // 타입이 올바른 순서를 강제함
// send_reset_email(pass, email); // 컴파일되지 않음!
내부 값에 접근하려면 튜플 구조체이므로 .0을 사용하세요:
let pass = Password(String::from("secret123"));
println!("Length: {}", pass.0.len());
또한 newtype에 메서드를 추가하여, 일반적인 String에는 의미가 없지만 newtype이 나타내는 대상에 특화된 유효성 검사나 서식 지정과 같은 동작을 부여할 수도 있습니다.
챌린지
쉬움Newtype 패턴을 사용하여 타입 안전한 사용자 등록 시스템을 만들어 봅시다! 두 값 모두 단순한 값을 감싸고 있지만, 실수로 서로 혼용하는 것을 방지하기 위해 Username과 UserId에 대한 서로 다른 타입을 만들 것입니다.
코드를 두 파일에 나누어 구성합니다:
user_types.rs: 두 개의 public 뉴타입 struct를 Define합니다. 하나는String을 감싸는Username이고, 다른 하나는u32를 감싸는UserId입니다. 각각에value라는 method를 추가하여 inner 데이터에 대한 reference를 returns하도록 합니다(Username의 경우&String을 returns하고, UserId의 경우&u32를 returns합니다). 이를 통해 감싸진 값에 대한 제어된 access를 제공합니다.main.rs: user_types module을 가져오고, 첫 번째 parameter로UserId를, 두 번째 parameter로Username을 takes하는display_userfunction을 만듭니다. function은 user 정보를 출력해야 합니다. 그런 다음 제공된 inputs를 사용하여 두 types의 instances를 Create하고 function을 Call합니다.
여기서 중요한 insight는 UserId와 Username이 단순한 wrapper일지라도 compiler가 이들을 완전히 서로 다른 types로 취급한다는 점입니다. ID가 필요한 곳에 username을 실수로 pass할 수 없습니다!
display_user function은 다음 format으로 출력해야 합니다:
User #{id}: {username}예를 들어, inputs가 42와 alice_dev인 경우:
User #42: alice_devinputs가 1001과 bob_smith인 경우:
User #1001: bob_smith두 개의 inputs를 받습니다. user ID는 u32로 parse하고, 두 번째 input은 username String입니다.
직접 해보기
mod user_types;
use user_types::{Username, UserId};
// TODO: `display_user`라는 함수를 생성하세요. 이 함수는 다음을 받습니다:
// - 첫 번째 매개변수: UserId
// - 두 번째 매개변수: Username
// 함수는 다음을 출력해야 합니다: "User #{id}: {username}"
// 내부 값에 접근하려면 .value() 메서드를 사용하세요
fn main() {
// 입력 읽기
let mut id_input = String::new();
std::io::stdin().read_line(&mut id_input).expect("Failed to read line");
let id: u32 = id_input.trim().parse().expect("Invalid number");
let mut username_input = String::new();
std::io::stdin().read_line(&mut username_input).expect("Failed to read line");
let username = username_input.trim().to_string();
// TODO: 입력을 사용하여 UserId와 Username의 인스턴스를 생성하세요
// TODO: 올바른 인수로 display_user를 호출하세요
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
Object Oriented Programming의 모든 레슨
직접 연습해 보세요: 온라인 Rust 컴파일러