빌림 오류 고치기 예제 사다리 문서의 문제 2 풀이 코드다.
//! 빌림 오류 고치기 예제 사다리 문제 2: 한 Vec의 두 칸을 &mut로 동시에 빌리면 E0499. swap이나 split_at_mut로 고친다.
//! 길이 검사를 빼면 빈 슬라이스에서 n - 1이 넘쳐 panic한다(dev 빌드).
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/4.연습문제/09_borrow-errors-ladder_p2.rs"
use std::process::Command;
/// 코드 조각을 rustc로 컴파일해 stderr를 돌려준다.
fn compile_errors(code: &str) -> String {
let dir = std::env::temp_dir().join(format!("vrL2_{}_{}", 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 swap_ends(v: &mut Vec<i32>) {
let n = v.len();
let a = &mut v[0];
let b = &mut v[n - 1];
std::mem::swap(a, b);
}"#;
/// 고친 버전 1: 표준 라이브러리의 swap.
fn swap_ends(v: &mut [i32]) {
if v.len() >= 2 {
let n = v.len();
v.swap(0, n - 1);
}
}
/// 고친 버전 2: split_at_mut로 겹치지 않는 두 조각을 받는다.
fn swap_ends_split(v: &mut [i32]) {
if v.len() >= 2 {
let n = v.len();
let (left, right) = v.split_at_mut(n - 1);
std::mem::swap(&mut left[0], &mut right[0]);
}
}
/// 길이 검사를 뺀 판: 빈 슬라이스에서 n - 1이 넘친다.
fn swap_ends_unguarded(v: &mut [i32]) {
let n = v.len();
v.swap(0, n - 1);
}
fn panic_message(f: impl FnOnce() + std::panic::UnwindSafe) -> String {
let err = std::panic::catch_unwind(f).expect_err("panic 기대");
if let Some(s) = err.downcast_ref::<String>() {
s.clone()
} else {
(*err.downcast_ref::<&str>().unwrap()).to_string()
}
}
fn main() {
expect_error(
BROKEN,
"E0499",
&[
"cannot borrow `*v` as mutable more than once at a time",
"first mutable borrow occurs here",
"second mutable borrow occurs here",
"first borrow later used here",
"use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices",
],
);
expect_ok(
r#"pub fn swap_ends(v: &mut [i32]) { if v.len() >= 2 { let n = v.len(); v.swap(0, n - 1); } }"#,
);
let mut a = vec![1, 2, 3, 4];
swap_ends(&mut a);
assert_eq!(a, [4, 2, 3, 1]);
swap_ends_split(&mut a);
assert_eq!(a, [1, 2, 3, 4]);
let mut one = [9];
swap_ends(&mut one);
swap_ends_split(&mut one);
assert_eq!(one, [9]);
let mut empty: [i32; 0] = [];
swap_ends(&mut empty);
swap_ends_split(&mut empty);
let mut one2 = [9];
swap_ends_unguarded(&mut one2);
assert_eq!(one2, [9]);
std::panic::set_hook(Box::new(|_| {}));
let msg = panic_message(|| {
let mut e: [i32; 0] = [];
swap_ends_unguarded(&mut e);
});
let _ = std::panic::take_hook();
println!("unguarded empty: {msg}");
assert!(msg.contains("attempt to subtract with overflow"));
println!("ALL CHECKS PASSED");
}