Rust가 푸는 문제 문서의 검증 코드다.

//! 01 Rust가 푸는 문제: 배열 밖 읽기는 쓰레기 값 대신 panic이 되고, 해제 뒤 사용·이동 뒤 사용·데이터 경쟁은 컴파일 단계에서 막힌다는 것을 확인한다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/01_why-rust_verify.rs"

use std::hint::black_box;
use std::panic;

/// 클로저를 실행해 panic 메시지를 돌려준다. panic이 없으면 None.
fn panic_message<F: FnOnce() -> R + panic::UnwindSafe, R>(f: F) -> Option<String> {
    let old = panic::take_hook();
    panic::set_hook(Box::new(|_| {}));
    let result = panic::catch_unwind(f);
    panic::set_hook(old);
    match result {
        Ok(_) => None,
        Err(payload) => Some(
            payload
                .downcast_ref::<String>()
                .cloned()
                .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
                .unwrap_or_default(),
        ),
    }
}

/// 코드 조각을 라이브러리로 컴파일해 stderr를 돌려준다.
fn compile_errors(code: &str) -> String {
    let dir = std::env::temp_dir().join(format!("vr_{}_{}", std::process::id(), code.len()));
    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 main() {
    // 1. 배열 밖 읽기: 실행 중에 정해지는 번호로 읽으면 panic이 난다.
    let v = [10, 20, 30];
    let idx = black_box(10usize);
    let msg = panic_message(|| v[idx]).expect("panic이 나야 한다");
    println!("v[10] -> panic: {msg}");
    assert_eq!(msg, "index out of bounds: the len is 3 but the index is 10");

    // 2. 실패할 수 있다는 것을 타입으로 받는 방법: get은 Option을 돌려준다.
    assert_eq!(v.get(idx), None);
    assert_eq!(v.get(1), Some(&20));
    println!("v.get(10) = {:?}, v.get(1) = {:?}", v.get(idx), v.get(1));

    // 3. 해제된 값을 가리키는 참조(C의 use-after-free)는 컴파일되지 않는다.
    let dangle = compile_errors(
        "pub fn f() {\n    let r;\n    {\n        let s = String::from(\"hi\");\n        r = &s;\n    }\n    println!(\"{r}\");\n}\n",
    );
    assert!(dangle.contains("error[E0597]"), "{dangle}");
    assert!(dangle.contains("`s` does not live long enough"), "{dangle}");
    println!("dangling reference -> E0597");

    // 4. 넘겨준 값을 다시 쓰기(이중 해제의 씨앗)는 컴파일되지 않는다.
    let moved = compile_errors(
        "pub fn f() {\n    let s = String::from(\"hi\");\n    let t = s;\n    println!(\"{s} {t}\");\n}\n",
    );
    assert!(moved.contains("error[E0382]"), "{moved}");
    assert!(moved.contains("borrow of moved value: `s`"), "{moved}");
    println!("use after move -> E0382");

    // 5. 두 스레드가 잠금 없이 같은 Vec을 고치는 코드(데이터 경쟁)는 컴파일되지 않는다.
    let race = compile_errors(
        "pub fn f() {\n    let mut v = vec![1];\n    let h = std::thread::spawn(|| v.push(2));\n    v.push(3);\n    h.join().unwrap();\n}\n",
    );
    assert!(race.contains("error[E0373]"), "{race}");
    assert!(race.contains("error[E0499]"), "{race}");
    println!("data race -> E0373, E0499");

    // 6. 같은 일을 안전하게: Mutex로 감싸고 scope 스레드로 빌려 쓰면 컴파일되고 결과가 정해진다.
    let shared = std::sync::Mutex::new(vec![1]);
    std::thread::scope(|s| {
        s.spawn(|| shared.lock().unwrap().push(2));
        s.spawn(|| shared.lock().unwrap().push(3));
    });
    let mut got = shared.into_inner().unwrap();
    got.sort_unstable();
    assert_eq!(got, vec![1, 2, 3]);
    println!("Mutex + scope -> {got:?}");

    println!("ALL CHECKS PASSED");
}