트레이트 객체 문서의 검증 코드다.

//! 18 트레이트 객체: 포인터 크기(데이터 포인터 + vtable 포인터), 서로 다른 타입을 한 Vec에 담기,
//! dyn 호환성(E0038) 규칙과 `where Self: Sized` 탈출구, Box<dyn Error>와 ?, 기본 수명 'static,
//! dyn 값은 포인터 뒤에서만 쓴다는 것(E0277), Send 없는 Box<dyn Error>를 스레드에서 돌려받지 못하는 것을 확인한다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/18_trait-objects_verify.rs"

use std::error::Error;
use std::fmt;
use std::mem::{size_of, size_of_val};
use std::sync::atomic::{AtomicUsize, Ordering};

static SNIPPET: AtomicUsize = AtomicUsize::new(0);

fn compile_errors(code: &str) -> String {
    let n = SNIPPET.fetch_add(1, Ordering::Relaxed);
    let dir = std::env::temp_dir().join(format!("vr18_{}_{}", 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(label: &str, code: &str, needle: &str) {
    let e = compile_errors(code);
    let first = e
        .lines()
        .find(|l| l.starts_with("error"))
        .unwrap_or("(오류 없음)");
    println!("[{label}] {first}");
    if needle.is_empty() {
        assert!(!e.contains("error"), "{label}: {e}");
    } else {
        assert!(e.contains(needle), "{label}: {e}");
    }
}

trait Shape {
    fn area(&self) -> f64;
    fn name(&self) -> String {
        "도형".to_string()
    }
    // Self: Sized가 붙은 메서드는 vtable에 들어가지 않는다. 그래서 트레이트는 dyn 호환을 유지한다.
    fn boxed(self) -> Box<dyn Shape>
    where
        Self: Sized + 'static,
    {
        Box::new(self)
    }
}

struct Circle {
    r: f64,
}

struct Rect {
    w: f64,
    h: f64,
}

struct Unit;

impl Shape for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.r * self.r
    }
    fn name(&self) -> String {
        format!("원(r={})", self.r)
    }
}

impl Shape for Rect {
    fn area(&self) -> f64 {
        self.w * self.h
    }
}

impl Shape for Unit {
    fn area(&self) -> f64 {
        1.0
    }
}

#[derive(Debug)]
struct ConfigError {
    key: String,
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "설정 키 {} 가 없다", self.key)
    }
}

impl Error for ConfigError {}

fn parse_port(s: &str) -> Result<u16, Box<dyn Error + Send + Sync>> {
    if s.is_empty() {
        return Err(Box::new(ConfigError { key: "port".into() }));
    }
    let n: u16 = s.parse()?; // ParseIntError가 Box<dyn Error>로 바뀐다
    Ok(n)
}

fn main() {
    // 1. 크기: 보통 참조는 주소 하나, 트레이트 객체를 가리키는 참조는 주소 둘.
    let w = size_of::<usize>();
    let sizes = [
        ("&Circle", size_of::<&Circle>()),
        ("&dyn Shape", size_of::<&dyn Shape>()),
        ("Box<Circle>", size_of::<Box<Circle>>()),
        ("Box<dyn Shape>", size_of::<Box<dyn Shape>>()),
        ("&[u8]", size_of::<&[u8]>()),
    ];
    for (n, s) in sizes {
        println!("{n:>15}: {s}바이트");
    }
    assert_eq!(size_of::<&dyn Shape>(), 2 * size_of::<usize>());
    assert_eq!(size_of::<Box<dyn Shape>>(), 2 * w);
    assert_eq!(size_of::<&Circle>(), w);
    assert_eq!(size_of::<Box<Circle>>(), w);

    // 2. 서로 다른 타입을 한 Vec에
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { r: 1.0 }),
        Box::new(Rect { w: 2.0, h: 3.0 }),
        Unit.boxed(),
    ];
    for s in &shapes {
        println!(
            "{} 넓이 {:.2}, 값 크기 {}바이트",
            s.name(),
            s.area(),
            size_of_val(&**s)
        );
    }
    let total: f64 = shapes.iter().map(|s| s.area()).sum();
    assert!((total - (std::f64::consts::PI + 7.0)).abs() < 1e-12);
    // size_of_val은 dyn 뒤에 숨은 실제 타입의 크기를 돌려준다.
    assert_eq!(size_of_val(&*shapes[0]), 8);
    assert_eq!(size_of_val(&*shapes[1]), 16);
    assert_eq!(size_of_val(&*shapes[2]), 0);

    // 2-1. dyn Shape는 크기를 모르므로 값 그대로 받거나 Vec에 바로 담을 수 없다.
    expect(
        "dyn Shape를 값으로 받음",
        "pub trait Shape { fn area(&self) -> f64; }
         pub fn f(x: dyn Shape) -> f64 { x.area() }",
        "error[E0277]",
    );
    expect(
        "Vec<dyn Shape>",
        "pub trait Shape { fn area(&self) -> f64; }
         pub fn f(x: Vec<dyn Shape>) -> usize { x.len() }",
        "error[E0277]",
    );

    // 3. dyn 호환성
    expect(
        "제네릭 메서드가 있는 트레이트",
        "pub trait Store { fn put<T>(&self, x: T); }
         pub fn f(_s: &dyn Store) {}",
        "error[E0038]",
    );
    expect(
        "Self를 돌려주는 메서드",
        "pub trait Cloner { fn dup(&self) -> Self; }
         pub fn f(_s: &dyn Cloner) {}",
        "error[E0038]",
    );
    expect(
        "Self: Sized로 빼낸 메서드",
        "pub trait Cloner { fn dup(&self) -> Self where Self: Sized; fn id(&self) -> u8; }
         pub fn f(s: &dyn Cloner) -> u8 { s.id() }",
        "",
    );
    expect(
        "Self: Sized 메서드를 dyn에서 부름",
        "pub trait Cloner { fn dup(&self) -> Self where Self: Sized; }
         pub fn f(s: &dyn Cloner) { let _ = s.dup(); }",
        "error",
    );
    expect(
        "async fn이 있는 트레이트",
        "pub trait Fetch { async fn get(&self) -> u8; }
         pub fn f(_s: &dyn Fetch) {}",
        "error[E0038]",
    );
    expect(
        "Clone은 dyn 호환이 아니다",
        "pub fn f(_s: &dyn Clone) {}",
        "error[E0038]",
    );

    // 4. Box<dyn Error>와 ?
    assert_eq!(parse_port("8080").unwrap(), 8080);
    let e1 = parse_port("").unwrap_err();
    let e2 = parse_port("80x").unwrap_err();
    println!("에러 1: {e1}");
    println!("에러 2: {e2}");
    assert_eq!(e1.to_string(), "설정 키 port 가 없다");
    assert_eq!(e2.to_string(), "invalid digit found in string");
    assert!(e1.downcast_ref::<ConfigError>().is_some());
    assert!(e2.downcast_ref::<std::num::ParseIntError>().is_some());

    // 5. Box<dyn Trait>의 기본 수명은 'static
    expect(
        "빌린 &str를 Box<dyn Display>로",
        "pub fn f<'a>(s: &'a str) -> Box<dyn std::fmt::Display> { Box::new(s) }",
        "error",
    );
    expect(
        "수명을 적은 Box<dyn Display + 'a>",
        "pub fn f<'a>(s: &'a str) -> Box<dyn std::fmt::Display + 'a> { Box::new(s) }",
        "",
    );

    // 6. 자동 트레이트는 + 로 덧붙인다.
    expect(
        "Send가 아닌 값을 Box<dyn Shape + Send>에",
        "pub trait Shape {}
         pub struct S(std::rc::Rc<u8>);
         impl Shape for S {}
         pub fn f(s: S) -> Box<dyn Shape + Send> { Box::new(s) }",
        "error[E0277]",
    );

    // 7. Send가 없는 Box<dyn Error>는 스레드 밖으로 돌려받을 수 없다.
    expect(
        "Box<dyn Error>를 돌려주는 스레드",
        "pub fn f() {
             let h = std::thread::spawn(|| -> Result<(), Box<dyn std::error::Error>> { Ok(()) });
             let _ = h.join();
         }",
        "error[E0277]",
    );
    expect(
        "Box<dyn Error + Send + Sync>를 돌려주는 스레드",
        "pub fn f() {
             let h = std::thread::spawn(|| -> Result<(), Box<dyn std::error::Error + Send + Sync>> { Ok(()) });
             let _ = h.join();
         }",
        "",
    );

    println!("ALL CHECKS PASSED");
}