빌림 오류 고치기 예제 사다리 문서의 문제 4 풀이 코드다.
//! 빌림 오류 고치기 예제 사다리 문제 4: self.items를 도는 동안 &mut self 메서드를 부르면 E0502.
//! 필드를 따로 빌리면 고쳐진다. 번호로 도는 반복문은 원소를 clone해야만 컴파일된다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/4.연습문제/09_borrow-errors-ladder_p4.rs"
use std::process::Command;
/// 코드 조각을 rustc로 컴파일해 stderr를 돌려준다.
fn compile_errors(code: &str) -> String {
let dir = std::env::temp_dir().join(format!("vrL4_{}_{}", 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 struct Inventory { items: Vec<String>, log: Vec<String> }
impl Inventory {
fn record(&mut self, msg: &str) { self.log.push(msg.to_string()); }
pub fn audit(&mut self) {
for item in &self.items {
self.record(item);
}
}
}"#;
const FIXED: &str = r#"pub struct Inventory { items: Vec<String>, log: Vec<String> }
impl Inventory {
pub fn audit(&mut self) {
for item in &self.items {
self.log.push(item.clone());
}
}
}"#;
struct Inventory {
items: Vec<String>,
log: Vec<String>,
}
/// 기록하는 일을 log 필드만 받는 함수로 뺀다.
fn record(log: &mut Vec<String>, msg: &str) {
log.push(format!("checked {msg}"));
}
impl Inventory {
fn audit(&mut self) {
// self.items(공유)와 self.log(가변)는 서로 다른 필드라 함께 빌릴 수 있다
for item in &self.items {
record(&mut self.log, item);
}
}
}
fn main() {
expect_error(
BROKEN,
"E0502",
&[
"cannot borrow `*self` as mutable because it is also borrowed as immutable",
"this for loop borrows `*self` immutably, preventing mutation within its body",
"immutable borrow occurs here",
"immutable borrow later used here",
"mutable borrow occurs here",
"consider using an index-based loop instead, or collecting modifications into a separate collection",
],
);
expect_ok(FIXED);
// 도움말대로 번호로 돌아도, 원소를 빌린 채 &mut self 메서드를 부르면 여전히 E0502다
expect_error(
r#"pub struct Inventory { items: Vec<String>, log: Vec<String> }
impl Inventory {
fn record(&mut self, msg: &str) { self.log.push(msg.to_string()); }
pub fn audit(&mut self) {
for i in 0..self.items.len() {
self.record(&self.items[i]);
}
}
}"#,
"E0502",
&[
"cannot borrow `*self` as mutable because it is also borrowed as immutable",
"immutable borrow later used by call",
],
);
expect_ok(
r#"pub struct Inventory { items: Vec<String>, log: Vec<String> }
impl Inventory {
fn record(&mut self, msg: &str) { self.log.push(msg.to_string()); }
pub fn audit(&mut self) {
for i in 0..self.items.len() {
let item = self.items[i].clone();
self.record(&item);
}
}
}"#,
);
let mut inv = Inventory {
items: vec!["apple".to_string(), "pear".to_string()],
log: Vec::new(),
};
inv.audit();
assert_eq!(inv.log, ["checked apple", "checked pear"]);
assert_eq!(inv.items.len(), 2);
println!("ALL CHECKS PASSED");
}