빌림 오류 고치기 예제 사다리 문서의 문제 1 풀이 코드다.

//! 빌림 오류 고치기 예제 사다리 문제 1: 앞 칸의 참조를 쥔 채 push하면 E0502. 값을 복사해 두면 고쳐진다.
//! 순서를 바꿔도 고쳐지고, 원소가 String이면 복사가 안 되어(E0507) clone이 필요하다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/4.연습문제/09_borrow-errors-ladder_p1.rs"

use std::process::Command;

/// 코드 조각을 rustc로 컴파일해 stderr를 돌려준다.
fn compile_errors(code: &str) -> String {
    let dir = std::env::temp_dir().join(format!("vrL1_{}_{}", 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 = Command::new("rustc")
        .args([
            "--edition",
            "2024",
            "--crate-type=lib",
            "--emit=metadata",
            "--out-dir",
        ])
        .arg(&dir)
        .arg(&file)
        .output()
        .expect("rustc 실행");
    let _ = std::fs::remove_dir_all(&dir);
    String::from_utf8_lossy(&out.stderr).into_owned()
}

/// 오류 코드와, 문서에 옮긴 메시지 조각(본문·라벨·도움말)이 모두 나오는지 확인한다.
fn expect_error(code: &str, err: &str, phrases: &[&str]) {
    let stderr = compile_errors(code);
    assert!(
        stderr.contains(&format!("error[{err}]")),
        "{err} 기대, 실제:\n{stderr}"
    );
    for phrase in phrases {
        assert!(stderr.contains(phrase), "'{phrase}' 기대, 실제:\n{stderr}");
        println!("{err} 확인: {phrase}");
    }
}

fn expect_ok(code: &str) {
    let stderr = compile_errors(code);
    assert!(
        !stderr.contains("error"),
        "컴파일 성공 기대, 실제:\n{stderr}"
    );
}

const BROKEN: &str = r#"pub fn push_and_report(v: &mut Vec<i32>, x: i32) -> i32 {
    let first = &v[0];
    v.push(x);
    *first
}"#;

const FIXED: &str = r#"pub fn push_and_report(v: &mut Vec<i32>, x: i32) -> i32 {
    let first = v[0];
    v.push(x);
    first
}"#;

/// 고친 버전: 첫 값을 복사해 두고 push한다.
fn push_and_report(v: &mut Vec<i32>, x: i32) -> i32 {
    let first = v[0];
    v.push(x);
    first
}

fn main() {
    expect_error(
        BROKEN,
        "E0502",
        &[
            "cannot borrow `*v` as mutable because it is also borrowed as immutable",
            "immutable borrow occurs here",
            "mutable borrow occurs here",
            "immutable borrow later used here",
        ],
    );
    expect_ok(FIXED);
    // 다른 고침: push를 먼저 하고 그 뒤에 읽는다
    expect_ok(r#"pub fn push_and_report(v: &mut Vec<i32>, x: i32) -> i32 { v.push(x); v[0] }"#);
    // 원소가 String이면 복사해 꺼낼 수 없다. 도움말의 첫 제안(&v[0])은 처음 오류로 돌아간다
    expect_error(
        r#"pub fn push_and_report(v: &mut Vec<String>, x: String) -> String {
    let first = v[0];
    v.push(x);
    first
}"#,
        "E0507",
        &[
            "cannot move out of index of `Vec<String>`",
            "consider borrowing here",
            "consider cloning the value if the performance cost is acceptable",
        ],
    );
    expect_ok(
        r#"pub fn push_and_report(v: &mut Vec<String>, x: String) -> String {
    let first = v[0].clone();
    v.push(x);
    first
}"#,
    );
    let mut v = vec![10, 20, 30, 40];
    assert_eq!(push_and_report(&mut v, 50), 10);
    assert_eq!(v, [10, 20, 30, 40, 50]);
    // push가 버퍼를 옮겨도 복사해 둔 값은 그대로다
    let mut w = Vec::with_capacity(1);
    w.push(7);
    let before = w.as_ptr();
    assert_eq!(push_and_report(&mut w, 8), 7);
    println!("buffer moved: {}", before != w.as_ptr());
    println!("ALL CHECKS PASSED");
}