Box와 힙 문서의 검증 코드다.

//! Box와 힙: 재귀 타입은 Box 없이 컴파일되지 않는다(E0072, Option으로 감싸도), 포인터 크기(얇은 8바이트, 넓은 16바이트),
//! Option<Box<T>>가 Box<T>와 같은 크기, Box<dyn Trait>로 서로 다른 타입 담기, Box를 옮겨도 힙 주소는 그대로,
//! Drop이 한 번씩 도는 것, 쓸데없는 Box를 잡는 clippy 린트 네 개가 기본으로 켜져 있는 것을 확인한다.
//! (크기는 64비트 기계 기준)
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/27_box-heap_verify.rs"

use std::cell::Cell;
use std::fmt::Display;
use std::mem::size_of;

// ---------- 재귀 타입 ----------

#[derive(Debug)]
enum List {
    Cons(i32, Box<List>),
    Nil,
}

use List::{Cons, Nil};

impl List {
    fn sum(&self) -> i32 {
        match self {
            Cons(v, rest) => v + rest.sum(),
            Nil => 0,
        }
    }
}

/// 이진 트리: 자식이 없을 수 있으므로 Option<Box<Node>>
struct Node {
    key: i32,
    left: Option<Box<Node>>,
    right: Option<Box<Node>>,
}

fn insert(slot: &mut Option<Box<Node>>, key: i32) {
    match slot {
        None => {
            *slot = Some(Box::new(Node {
                key,
                left: None,
                right: None,
            }))
        }
        Some(n) if key < n.key => insert(&mut n.left, key),
        Some(n) => insert(&mut n.right, key),
    }
}

fn in_order(slot: &Option<Box<Node>>, out: &mut Vec<i32>) {
    if let Some(n) = slot {
        in_order(&n.left, out);
        out.push(n.key);
        in_order(&n.right, out);
    }
}

// ---------- Box<dyn Trait> ----------

trait Shape {
    fn area(&self) -> f64;
    fn name(&self) -> String;
}
struct Square(f64);
struct Circle(f64);
impl Shape for Square {
    fn area(&self) -> f64 {
        self.0 * self.0
    }
    fn name(&self) -> String {
        format!("square {}", self.0)
    }
}
impl Shape for Circle {
    fn area(&self) -> f64 {
        3.0 * self.0 * self.0 // 계산을 단순하게: π 대신 3
    }
    fn name(&self) -> String {
        format!("circle {}", self.0)
    }
}

// ---------- Drop 세기 ----------

thread_local! {
    static DROPS: Cell<u32> = const { Cell::new(0) };
}
struct Loud;
impl Drop for Loud {
    fn drop(&mut self) {
        DROPS.with(|d| d.set(d.get() + 1));
    }
}

fn take_box(b: Box<[u64; 1024]>) -> *const u64 {
    b.as_ptr()
}

fn compile_errors(code: &str) -> String {
    compile_with("rustc", "rustc", code)
}

/// 같은 조각을 clippy-driver로 컴파일한다(추가 -W 없이, 즉 기본 설정 그대로).
fn clippy(code: &str) -> String {
    compile_with("clippy-driver", "clippy", code)
}

fn compile_with(tool: &str, tag: &str, code: &str) -> String {
    let dir = std::env::temp_dir().join(format!("vr27_{}_{tag}", std::process::id()));
    std::fs::create_dir_all(&dir).expect("임시 폴더");
    let file = dir.join("snippet.rs");
    std::fs::write(&file, code).expect("쓰기");
    let out = std::process::Command::new(tool)
        .args([
            "--edition",
            "2024",
            "--crate-type=lib",
            "--emit=metadata",
            "--out-dir",
        ])
        .arg(&dir)
        .arg(&file)
        .output()
        .expect("컴파일러 실행");
    String::from_utf8_lossy(&out.stderr).into_owned()
}

fn main() {
    // 1. 재귀 타입
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
    assert_eq!(list.sum(), 6);
    assert_eq!(format!("{list:?}"), "Cons(1, Cons(2, Cons(3, Nil)))");
    let e = compile_errors("pub enum List { Cons(i32, List), Nil }\n");
    assert!(e.contains("error[E0072]"), "{e}");
    assert!(e.contains("recursive type `List` has infinite size"), "{e}");
    assert!(e.contains("insert some indirection"), "{e}");
    // Option으로 감싸도 안에 Node가 통째로 들어 있으므로 같은 오류다
    let e = compile_errors("pub struct Node { pub val: i32, pub next: Option<Node> }\n");
    assert!(e.contains("error[E0072]"), "{e}");
    assert!(e.contains("recursive type `Node` has infinite size"), "{e}");
    println!("1. Box 없는 재귀 타입: E0072 (Option<Node>도 E0072)");

    let mut root = None;
    for k in [5, 3, 8, 1, 4, 9, 7] {
        insert(&mut root, k);
    }
    let mut keys = Vec::new();
    in_order(&root, &mut keys);
    assert_eq!(keys, vec![1, 3, 4, 5, 7, 8, 9]);

    // 2. 크기
    let sizes = [
        ("&i32", size_of::<&i32>()),
        ("Box<i32>", size_of::<Box<i32>>()),
        ("Box<[i32; 1000]>", size_of::<Box<[i32; 1000]>>()),
        ("Option<Box<i32>>", size_of::<Option<Box<i32>>>()),
        ("Box<[i32]>", size_of::<Box<[i32]>>()),
        ("Box<str>", size_of::<Box<str>>()),
        ("Box<dyn Display>", size_of::<Box<dyn Display>>()),
        ("Vec<i32>", size_of::<Vec<i32>>()),
        ("List", size_of::<List>()),
        ("[i32; 1000]", size_of::<[i32; 1000]>()),
    ];
    for (n, s) in sizes {
        println!("   size_of::<{n}>() = {s}");
    }
    assert_eq!(size_of::<usize>(), 8, "64비트 기계에서 확인한다");
    assert_eq!(size_of::<Box<i32>>(), 8);
    assert_eq!(size_of::<Box<[i32; 1000]>>(), 8); // 가리키는 것이 커도 상자는 8바이트
    assert_eq!(size_of::<Option<Box<i32>>>(), 8); // None을 널 주소로 나타낸다
    assert_eq!(size_of::<Box<[i32]>>(), 16); // 주소 + 길이
    assert_eq!(size_of::<Box<str>>(), 16);
    assert_eq!(size_of::<Box<dyn Display>>(), 16); // 주소 + vtable 주소
    assert_eq!(size_of::<Vec<i32>>(), 24); // 주소 + 길이 + 용량
    assert_eq!(size_of::<List>(), 16); // i32 4 + Box 8 → 정렬 맞춰 16
    assert_eq!(size_of::<[i32; 1000]>(), 4000);
    // 더 늘리지 않을 목록은 Box<[T]>로 바꾸면 손잡이가 24 → 16바이트
    let v: Vec<i32> = (1..=5).collect();
    let frozen: Box<[i32]> = v.into_boxed_slice();
    assert_eq!(frozen.len(), 5);
    assert_eq!(&frozen[..], &[1, 2, 3, 4, 5]);
    let name: Box<str> = String::from("kim").into_boxed_str();
    assert_eq!(&*name, "kim");
    println!("2. 크기 확인");

    // 3. Box<dyn Trait>로 다른 타입을 한 Vec에
    let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Square(2.0)), Box::new(Circle(1.0))];
    let total: f64 = shapes.iter().map(|s| s.area()).sum();
    let names: Vec<String> = shapes.iter().map(|s| s.name()).collect();
    assert_eq!(total, 7.0);
    assert_eq!(names, vec!["square 2", "circle 1"]);
    println!("3. Box<dyn Shape>: 넓이 합 {total}");

    // 4. Box를 함수로 넘겨도(이동) 힙의 데이터는 복사되지 않는다: 주소가 같다
    let big = Box::new([7u64; 1024]);
    let before = big.as_ptr();
    let after = take_box(big);
    assert_eq!(before, after);
    println!("4. 8KB 배열을 담은 Box 이동: 힙 주소 그대로");

    // 5. Drop은 정확히 한 번
    {
        let a = Box::new(Loud);
        let b = a; // 이동: 소유자가 b로 바뀐다
        let v: Vec<Box<Loud>> = vec![b, Box::new(Loud)];
        assert_eq!(v.len(), 2);
    }
    assert_eq!(DROPS.with(|d| d.get()), 2);
    println!("5. Box 두 개, Drop 두 번");

    // 6. 쓸데없는 Box를 잡는 clippy 린트(기본 설정에서 경고)
    let e = clippy(
        "pub fn run() -> usize {\n\
         struct S { v: Box<Vec<i32>> }\n\
         fn h(v: Vec<Box<i32>>) -> usize { v.len() }\n\
         fn k(x: Box<std::rc::Rc<i32>>) -> i32 { **x }\n\
         fn f(x: Box<i32>) -> i32 { *x + 1 }\n\
         let s = S { v: Box::new(vec![1]) };\n\
         s.v.len() + h(vec![Box::new(1)]) + k(Box::new(std::rc::Rc::new(1))) as usize + f(Box::new(1)) as usize\n\
         }\n",
    );
    for lint in [
        "box_collection",
        "vec_box",
        "redundant_allocation",
        "boxed_local",
    ] {
        assert!(
            e.contains(&format!("`#[warn(clippy::{lint})]` on by default")),
            "{lint}: {e}"
        );
    }
    assert!(
        e.contains("you seem to be trying to use `Box<Vec<..>>`"),
        "{e}"
    );
    assert!(
        e.contains("`Vec<T>` is already on the heap, the boxing is unnecessary"),
        "{e}"
    );
    println!("6. clippy 기본 경고: box_collection, vec_box, redundant_allocation, boxed_local");

    println!("ALL CHECKS PASSED");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty_list_sums_to_zero() {
        assert_eq!(Nil.sum(), 0);
    }

    #[test]
    fn deref_box() {
        let b = Box::new(41);
        assert_eq!(*b + 1, 42);
    }
}