트레이트 문서의 검증 코드다.
//! 15 트레이트: 기본 메서드, 연관 타입과 제네릭 매개변수의 차이, 블랭킷 구현, 슈퍼트레이트, impl Trait,
//! 고아 규칙(E0117)과 뉴타입 우회, 트레이트를 가져오지 않으면 메서드를 못 부르는 것(E0599)을 확인한다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/15_traits_verify.rs"
use std::fmt;
use std::sync::atomic::{AtomicUsize, Ordering};
static SNIPPET: AtomicUsize = AtomicUsize::new(0);
/// 코드 조각을 라이브러리로 컴파일해 stderr를 돌려준다.
fn compile_errors(code: &str) -> String {
let n = SNIPPET.fetch_add(1, Ordering::Relaxed);
let dir = std::env::temp_dir().join(format!("vr15_{}_{}", std::process::id(), n));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("snippet.rs");
std::fs::write(&file, code).unwrap();
let out = std::process::Command::new("rustc")
.args([
"--edition",
"2024",
"--crate-type=lib",
"--emit=metadata",
"--out-dir",
])
.arg(&dir)
.arg(&file)
.output()
.expect("rustc 실행");
String::from_utf8_lossy(&out.stderr).into_owned()
}
/// 오류 코드가 나오는지 확인하고, 첫 오류 줄을 찍는다.
fn expect_error(label: &str, code: &str, err: &str) {
let e = compile_errors(code);
let first = e
.lines()
.find(|l| l.starts_with("error"))
.unwrap_or("(오류 없음)");
println!("[{label}] {first}");
assert!(e.contains(&format!("error[{err}]")), "{label}: {e}");
}
fn expect_ok(label: &str, code: &str) {
let e = compile_errors(code);
println!("[{label}] 컴파일 통과");
assert!(!e.contains("error"), "{label}: {e}");
}
// 1. 기본 메서드: summarize는 몸통이 있어 구현하는 쪽이 고르지 않아도 된다.
trait Summary {
fn author(&self) -> String;
fn summarize(&self) -> String {
format!("({}의 글 더 보기...)", self.author())
}
}
struct Post {
author: String,
}
struct Tweet {
user: String,
text: String,
}
impl Summary for Post {
fn author(&self) -> String {
self.author.clone()
}
}
impl Summary for Tweet {
fn author(&self) -> String {
format!("@{}", self.user)
}
fn summarize(&self) -> String {
format!("{}: {}", self.author(), self.text)
}
}
// 2. 연관 타입: 한 타입에 한 번만 구현한다. Item이 무엇인지 부르는 쪽이 적지 않는다.
struct Countdown(u32);
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.0 == 0 {
None
} else {
self.0 -= 1;
Some(self.0 + 1)
}
}
}
// 제네릭 매개변수: 같은 타입에 여러 번 구현할 수 있다. 부를 때 어느 쪽인지 적어야 한다.
trait ConvertTo<T> {
fn convert(&self) -> T;
}
impl ConvertTo<i64> for i32 {
fn convert(&self) -> i64 {
i64::from(*self) * 1000
}
}
impl ConvertTo<f64> for i32 {
fn convert(&self) -> f64 {
f64::from(*self) / 2.0
}
}
// 3. 블랭킷 구현: Display를 구현한 모든 타입에 Shout를 붙인다.
trait Shout {
fn shout(&self) -> String;
}
impl<T: fmt::Display + ?Sized> Shout for T {
fn shout(&self) -> String {
format!("{}!", self.to_string().to_uppercase())
}
}
// 4. 슈퍼트레이트: Labeled를 구현하려면 Display가 먼저 있어야 한다.
trait Labeled: fmt::Display {
fn label(&self) -> String {
format!("[{self}]")
}
}
struct Code(u16);
impl fmt::Display for Code {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "E{:04}", self.0)
}
}
impl Labeled for Code {}
// 5. impl Trait: 인자 자리와 반환 자리.
fn notify(item: &impl Summary) -> String {
format!("속보! {}", item.summarize())
}
fn notify_long<T: Summary>(item: &T) -> String {
format!("속보! {}", item.summarize())
}
fn tens() -> impl Iterator<Item = u32> {
(1..=3).map(|x| x * 10)
}
// 6. 뉴타입으로 고아 규칙 우회: Vec<i32>에 Display를 붙이고 싶을 때.
struct Csv(Vec<i32>);
impl fmt::Display for Csv {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let parts: Vec<String> = self.0.iter().map(|x| x.to_string()).collect();
write!(f, "{}", parts.join(","))
}
}
fn main() {
// 1. 기본 메서드
let p = Post {
author: "kim".into(),
};
let t = Tweet {
user: "rustlang".into(),
text: "1.99 released".into(),
};
println!("{}", p.summarize());
println!("{}", t.summarize());
assert_eq!(p.summarize(), "(kim의 글 더 보기...)");
assert_eq!(t.summarize(), "@rustlang: 1.99 released");
// 2. 연관 타입과 제네릭 매개변수
let v: Vec<u32> = Countdown(3).collect();
assert_eq!(v, vec![3, 2, 1]);
let a: i64 = 7.convert();
let b: f64 = 7.convert();
println!("Countdown(3) = {v:?}, 7 -> i64 {a}, 7 -> f64 {b}");
assert_eq!((a, b), (7000, 3.5));
expect_error(
"제네릭 트레이트를 타입 표시 없이 부름",
"pub trait ConvertTo<T> { fn convert(&self) -> T; }
impl ConvertTo<i64> for i32 { fn convert(&self) -> i64 { 0 } }
impl ConvertTo<f64> for i32 { fn convert(&self) -> f64 { 0.0 } }
pub fn f() { let _x = 7i32.convert(); }",
"E0283",
);
expect_error(
"연관 타입 트레이트를 같은 타입에 두 번 구현",
"pub struct C;
impl Iterator for C { type Item = u32; fn next(&mut self) -> Option<u32> { None } }
impl Iterator for C { type Item = String; fn next(&mut self) -> Option<String> { None } }",
"E0119",
);
// 3. 블랭킷 구현
println!("{} {} {}", 42.shout(), "hi".shout(), Code(7).shout());
assert_eq!(42.shout(), "42!");
assert_eq!("hi".shout(), "HI!");
// 4. 슈퍼트레이트
assert_eq!(Code(7).label(), "[E0007]");
println!("label = {}", Code(7).label());
expect_error(
"Display 없이 슈퍼트레이트 구현",
"pub trait Labeled: std::fmt::Display { fn label(&self) -> String { format!(\"[{self}]\") } }
pub struct S;
impl Labeled for S {}",
"E0277",
);
// 5. impl Trait
assert_eq!(notify(&t), notify_long(&t));
assert_eq!(tens().collect::<Vec<_>>(), vec![10, 20, 30]);
expect_error(
"반환 자리 impl Trait에서 두 타입을 돌려줌",
"pub trait Summary { fn s(&self) -> u8 { 0 } }
pub struct A; pub struct B;
impl Summary for A {} impl Summary for B {}
pub fn make(flag: bool) -> impl Summary { if flag { A } else { B } }",
"E0308",
);
expect_error(
"인자 자리 impl Trait 함수에 타입을 직접 적음",
"pub trait S { fn s(&self) -> u8; }
impl S for u8 { fn s(&self) -> u8 { *self } }
pub fn notify(item: &impl S) -> u8 { item.s() }
pub fn f() -> u8 { notify::<u8>(&1) }",
"E0107",
);
// 필수 메서드를 빠뜨림
expect_error(
"필수 메서드 빠뜨림",
"pub trait Summary { fn author(&self) -> String; }
pub struct P;
impl Summary for P {}",
"E0046",
);
// 6. 고아 규칙
expect_error(
"남의 트레이트를 남의 타입에 구현 (고아 규칙)",
"impl std::fmt::Display for Vec<i32> {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) }
}",
"E0117",
);
expect_ok(
"내 트레이트를 남의 타입에 구현",
"pub trait Double { fn double(&self) -> Self; }
impl Double for i32 { fn double(&self) -> i32 { self * 2 } }",
);
expect_ok(
"남의 트레이트를 내 타입에 구현",
"pub struct Meters(pub f64);
impl std::fmt::Display for Meters {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, \"{}m\", self.0) }
}",
);
assert_eq!(Csv(vec![1, 2, 3]).to_string(), "1,2,3");
println!("Csv = {}", Csv(vec![1, 2, 3]));
// 7. 트레이트를 스코프로 가져오지 않으면 메서드를 못 부른다.
expect_error(
"트레이트를 use 하지 않고 메서드 호출",
"mod a { pub trait Hello { fn hi(&self) -> i32 { 1 } } impl Hello for i32 {} }
pub fn f() -> i32 { 5i32.hi() }",
"E0599",
);
expect_ok(
"use로 가져온 뒤 호출",
"mod a { pub trait Hello { fn hi(&self) -> i32 { 1 } } impl Hello for i32 {} }
use a::Hello;
pub fn f() -> i32 { 5i32.hi() }",
);
println!("ALL CHECKS PASSED");
}