구조체와 메서드 문서의 검증 코드다.

//! 구조체와 메서드 문서의 주장을 확인한다: 필드 이름 줄여 쓰기, 갱신 문법의 이동, self 세 가지,
//! 연관 함수, 튜플 구조체의 타입 구별, 유닛 구조체 크기, derive 출력, 컴파일 오류 코드와 clippy 린트.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/10_structs-methods_verify.rs"

use std::mem::size_of;

#[derive(Debug, Clone, PartialEq)]
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn build_user(email: String, username: String) -> User {
    // 매개변수 이름과 필드 이름이 같으면 `email: email` 대신 `email`만 쓴다.
    User {
        active: true,
        username,
        email,
        sign_in_count: 1,
    }
}

#[derive(Debug)]
struct Counter {
    count: u32,
}

impl Counter {
    /// 연관 함수(self가 없다). `Counter::new()`로 부른다.
    fn new() -> Self {
        Self { count: 0 }
    }

    /// &self: 읽기만 한다.
    fn get(&self) -> u32 {
        self.count
    }

    /// &mut self: 고친다.
    fn bump(&mut self) -> u32 {
        self.count += 1;
        self.count
    }

    /// self: 값을 통째로 받아 소비한다. 부른 뒤에는 원래 변수를 못 쓴다.
    fn finish(self) -> String {
        format!("최종 {}", self.count)
    }
}

impl Default for Counter {
    fn default() -> Self {
        Self::new()
    }
}

struct Meters(f64);
struct AlwaysEqual;

fn rust_tool(name: &str) -> std::process::Command {
    std::process::Command::new(name)
}

/// 코드 조각을 컴파일하고 stderr를 돌려준다. clippy가 참이면 clippy-driver로 돌린다.
fn compile(code: &str, clippy: bool, extra: &[&str]) -> String {
    let dir = std::env::temp_dir().join(format!(
        "vr10_{}_{}_{}",
        std::process::id(),
        code.len(),
        clippy
    ));
    std::fs::create_dir_all(&dir).unwrap();
    let file = dir.join("snippet.rs");
    std::fs::write(&file, code).unwrap();
    let out = rust_tool(if clippy { "clippy-driver" } else { "rustc" })
        .args([
            "--edition",
            "2024",
            "--crate-type=lib",
            "--emit=metadata",
            "--out-dir",
        ])
        .arg(&dir)
        .args(extra)
        .arg(&file)
        .output()
        .expect("rustc 실행");
    String::from_utf8_lossy(&out.stderr).into_owned()
}

const USER: &str = "pub struct User { pub active: bool, pub username: String, pub email: String, pub sign_in_count: u64 }\n";

fn main() {
    // 1. 필드 이름 줄여 쓰기
    let u = build_user("a@x.com".to_string(), "ann".to_string());
    assert_eq!(u.username, "ann");
    assert_eq!(u.sign_in_count, 1);

    // 2. 갱신 문법: 지정하지 않은 필드는 옮겨 오거나 복사한다.
    let user1 = build_user("a@x.com".to_string(), "ann".to_string());
    let user2 = User {
        email: "b@x.com".to_string(),
        ..user1
    };
    assert_eq!(user2.username, "ann");
    // user1.email은 옮겨지지 않았으므로 아직 쓸 수 있다.
    assert_eq!(user1.email, "a@x.com");
    println!("user2 = {user2:?}");

    // user1 전체를 쓰면 E0382 (username이 옮겨 갔다)
    let e = compile(
        &format!(
            "{USER}pub fn f(user1: User) -> usize {{ let user2 = User {{ email: String::new(), ..user1 }}; let _ = user2; let whole = user1; whole.email.len() }}"
        ),
        false,
        &[],
    );
    assert!(e.contains("E0382"), "{e}");
    assert!(e.contains("partially moved"), "{e}");
    println!("갱신 문법 뒤 user1 전체 사용: E0382 (partially moved)");

    // user1.email만 쓰면 컴파일된다.
    let e = compile(
        &format!(
            "{USER}pub fn f(user1: User) -> usize {{ let user2 = User {{ email: String::new(), ..user1 }}; let _ = user2; user1.email.len() }}"
        ),
        false,
        &[],
    );
    assert!(!e.contains("error"), "{e}");

    // String 필드 둘 다 새로 주면 user1은 그대로 쓸 수 있다(Copy 필드만 복사).
    let e = compile(
        &format!(
            "{USER}pub fn f(user1: User) -> usize {{ let user2 = User {{ email: String::new(), username: String::new(), ..user1 }}; let _ = user2; let whole = user1; whole.email.len() }}"
        ),
        false,
        &[],
    );
    assert!(!e.contains("error"), "{e}");

    // 3. self 세 가지
    let mut c = Counter::new();
    assert_eq!(c.get(), 0);
    assert_eq!(c.bump(), 1);
    assert_eq!(c.bump(), 2);
    let s = c.finish();
    assert_eq!(s, "최종 2");
    let d = Counter::default();
    assert_eq!(d.get(), 0);
    println!("{d:?}");

    // finish(self) 뒤에 c를 쓰면 E0382
    let e = compile(
        "pub struct C { n: u32 }\nimpl C { pub fn finish(self) -> u32 { self.n } pub fn get(&self) -> u32 { self.n } }\npub fn f(c: C) -> u32 { let a = c.finish(); a + c.get() }",
        false,
        &[],
    );
    assert!(e.contains("E0382"), "{e}");
    println!("소비하는 메서드 뒤 사용: E0382");

    // 불변 변수에서 &mut self 메서드를 부르면 E0596
    let e = compile(
        "pub struct C { n: u32 }\nimpl C { pub fn bump(&mut self) { self.n += 1; } }\npub fn f() { let c = C { n: 0 }; c.bump(); }",
        false,
        &[],
    );
    assert!(e.contains("E0596"), "{e}");
    println!("let c에서 &mut self 메서드: E0596");

    // 4. 튜플 구조체: 필드 타입이 같아도 서로 다른 타입이다.
    let e = compile(
        "pub struct Meters(pub f64);\npub struct Seconds(pub f64);\npub fn run(d: Meters) -> f64 { d.0 }\npub fn f() -> f64 { run(Seconds(3.0)) }",
        false,
        &[],
    );
    assert!(e.contains("E0308"), "{e}");
    println!("Seconds를 Meters 자리에: E0308");
    let m = Meters(5.0);
    assert_eq!(m.0, 5.0);

    // 5. 유닛 구조체는 크기가 0
    let _a = AlwaysEqual;
    assert_eq!(size_of::<AlwaysEqual>(), 0);
    assert_eq!(size_of::<Meters>(), size_of::<f64>());

    // 5-1. 기본 배치는 순서를 바꿀 수 있다(관찰값), repr(C)는 선언 순서
    struct Loose {
        _a: u8,
        _b: u32,
        _c: u8,
    }
    #[repr(C)]
    struct LooseC {
        _a: u8,
        _b: u32,
        _c: u8,
    }
    println!(
        "Loose {} 바이트, repr(C) {} 바이트",
        size_of::<Loose>(),
        size_of::<LooseC>()
    );
    assert_eq!(size_of::<Loose>(), 8);
    assert_eq!(size_of::<LooseC>(), 12);

    // 6. derive(Debug) 출력 모양
    let p = Counter { count: 3 };
    assert_eq!(format!("{p:?}"), "Counter { count: 3 }");
    assert_eq!(format!("{p:#?}"), "Counter {\n    count: 3,\n}");

    // derive(PartialEq): 필드를 모두 비교한다
    let a = build_user("a".into(), "b".into());
    let b = a.clone();
    assert_eq!(a, b);

    // 7. 자동 참조: c.get()은 Counter::get(&c)와 같다.
    let c2 = Counter { count: 7 };
    assert_eq!(Counter::get(&c2), c2.get());

    // 8. clippy: pub fn new() -> Self 가 있고 Default가 없으면 new_without_default (기본 켜짐)
    let e = compile(
        "pub struct C { n: u32 }\nimpl C { pub fn new() -> Self { C { n: 0 } } pub fn n(&self) -> u32 { self.n } }",
        true,
        &[],
    );
    assert!(e.contains("clippy::new_without_default"), "{e}");
    println!("clippy::new_without_default 기본으로 경고");

    // clippy: Default로 만든 뒤 바로 필드를 다시 넣으면 field_reassign_with_default (기본 켜짐)
    let e = compile(
        "#[derive(Default)] pub struct Cfg { pub a: u32, pub b: u32 }\npub fn f() -> Cfg { let mut c = Cfg::default(); c.a = 1; c }",
        true,
        &[],
    );
    assert!(e.contains("clippy::field_reassign_with_default"), "{e}");
    println!("clippy::field_reassign_with_default 기본으로 경고");

    // 9. 확인 문제 C3의 실행 추적
    let mut t = Counter::new();
    t.bump();
    let snapshot = t.get();
    t.bump();
    t.bump();
    let out = format!("{snapshot} {} {}", t.get(), t.finish());
    println!("C3: {out}");
    assert_eq!(out, "1 3 최종 3");

    println!("ALL CHECKS PASSED");
}