malloc·free ↔ 소유권과 Drop 문서의 검증 코드다.
//! malloc·free ↔ 소유권과 Drop 브리지의 주장을 확인한다.
//! C의 네 실수(누수, 해제 뒤 사용, 이중 해제, 지역 변수 주소 돌려주기)가 Rust에서 어떤 컴파일 오류가 되는지,
//! 할당 횟수와 해제 횟수를 세는 전역 할당자로 "보통은 받은 만큼 돌려준다"와 "안전한 코드로도 샐 수 있다"
//! (Rc 순환, mem::forget, Box::leak)를 확인한다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/06_malloc-free-vs-ownership_verify.rs"
use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::RefCell;
use std::process::Command;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering};
/// 받은 횟수와 돌려준 횟수를 세는 할당자. 실제 일은 System에 맡긴다.
struct Counting;
static ALLOCS: AtomicUsize = AtomicUsize::new(0);
static FREES: AtomicUsize = AtomicUsize::new(0);
// SAFETY: 모든 일을 System에 그대로 넘기고 횟수만 센다.
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCS.fetch_add(1, Ordering::SeqCst);
// SAFETY: 호출자가 GlobalAlloc::alloc의 조건을 지킨다.
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
FREES.fetch_add(1, Ordering::SeqCst);
// SAFETY: ptr은 이 할당자가 같은 layout으로 준 것이다.
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static GLOBAL: Counting = Counting;
/// f를 돌리는 동안 (받은 횟수, 돌려준 횟수)를 잰다. f 안에서는 출력하지 않는다.
fn count<F: FnOnce()>(f: F) -> (usize, usize) {
let a0 = ALLOCS.load(Ordering::SeqCst);
let f0 = FREES.load(Ordering::SeqCst);
f();
(
ALLOCS.load(Ordering::SeqCst) - a0,
FREES.load(Ordering::SeqCst) - f0,
)
}
static SNIPPET: AtomicUsize = AtomicUsize::new(0);
fn compile_errors(code: &str) -> String {
let n = SNIPPET.fetch_add(1, Ordering::SeqCst);
let dir = std::env::temp_dir().join(format!("vr06_{}_{n}", std::process::id()));
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, phrase: &str) {
let stderr = compile_errors(code);
assert!(
stderr.contains(&format!("error[{err}]")),
"{err} 기대, 실제:\n{stderr}"
);
assert!(stderr.contains(phrase), "'{phrase}' 기대, 실제:\n{stderr}");
println!("{err} 확인: {phrase}");
}
fn compile_checks() {
// 이중 해제: drop(b) 두 번
expect_error(
r#"pub fn f(){ let b=Box::new(5); drop(b); drop(b); }"#,
"E0382",
"use of moved value: `b`",
);
// 해제 뒤 사용: 주인을 버린 뒤 그 값을 읽기
expect_error(
r#"pub fn f()->i32{ let b=Box::new(5); drop(b); *b }"#,
"E0382",
"use of moved value: `b`",
);
// 해제 뒤 사용: 참조가 살아 있는데 주인을 버리기
expect_error(
r#"pub fn f()->i32{ let b=Box::new(5); let r=&*b; drop(b); *r }"#,
"E0505",
"cannot move out of `b` because it is borrowed",
);
// 함수 안 변수의 주소를 돌려주기
expect_error(
r#"pub fn f<'a>(_x:&'a i32)->&'a i32{ let y=5; &y }"#,
"E0515",
"cannot return reference to local variable `y`",
);
// realloc 뒤 옛 주소: Vec가 커지는 동안 앞 칸의 참조를 들고 있기
expect_error(
r#"pub fn f(){ let mut v=vec![10,20,30,40]; let q=&v[0]; v.push(50); println!("{q}"); }"#,
"E0502",
"cannot borrow `v` as mutable because it is also borrowed as immutable",
);
}
struct Node {
_other: RefCell<Option<Rc<Node>>>,
_payload: Vec<u8>,
}
fn alloc_checks() {
// Box 하나: 받기 1, 돌려주기 1
let (a, f) = count(|| {
let b = Box::new(41);
assert_eq!(*b, 41);
});
assert_eq!((a, f), (1, 1));
// 함수 안에서 만든 Vec를 함수 밖으로 옮겨도 받기와 돌려주기는 한 번씩
fn make(n: usize) -> Vec<f64> {
vec![0.0; n]
}
let (a, f) = count(|| {
let nums = make(8);
assert_eq!(nums.len(), 8);
});
assert_eq!((a, f), (1, 1));
// C 문서의 problem(100)을 세 번: Rust에서는 함수가 끝날 때 돌려준다
fn problem(n: usize) {
let mut nums = vec![0.0f32; n];
nums[0] = 1.0;
assert_eq!(nums[0], 1.0);
}
let (a, f) = count(|| {
for _ in 0..3 {
problem(100);
}
});
assert_eq!((a, f), (3, 3));
// 새 값을 대입하면 옛 버퍼는 그 자리에서 돌려준다
let (a, f) = count(|| {
let mut s = String::from("hello");
assert_eq!(s.len(), 5);
s = String::from("ahoy");
assert_eq!(s.len(), 4);
});
assert_eq!((a, f), (2, 2));
// Rc 순환: 안전한 코드인데 샌다
let (a, f) = count(|| {
let x = Rc::new(Node {
_other: RefCell::new(None),
_payload: vec![1, 2, 3],
});
let y = Rc::new(Node {
_other: RefCell::new(Some(Rc::clone(&x))),
_payload: vec![4, 5, 6],
});
*x._other.borrow_mut() = Some(Rc::clone(&y));
assert_eq!((Rc::strong_count(&x), Rc::strong_count(&y)), (2, 2));
});
assert!(a == 4 && f == 0, "Rc 순환: 받기 {a}, 돌려주기 {f}");
// mem::forget: 안전한 코드인데 샌다
let (a, f) = count(|| {
let v = vec![1u8; 64];
std::mem::forget(v);
});
assert_eq!((a, f), (1, 0));
// Box::leak: 일부러 새게 하고 &'static mut를 받는다
let (a, f) = count(|| {
let r: &'static mut i32 = Box::leak(Box::new(41));
*r += 1;
assert_eq!(*r, 42);
});
assert_eq!((a, f), (1, 0));
// 실패를 값으로 받기
let mut big: Vec<u8> = Vec::new();
assert!(big.try_reserve(usize::MAX).is_err());
println!("counts: box 1/1, problem x3 3/3, reassign 2/2, rc cycle 4/0, forget 1/0, leak 1/0");
}
fn main() {
compile_checks();
alloc_checks();
println!("ALL CHECKS PASSED");
}