수명 문서의 검증 코드다.
//! 수명 문서의 주장을 확인한다: 지역 변수의 참조를 돌려줄 때의 오류(E0106·E0515), 짧게 사는 값의 참조(E0597),
//! 수명 생략 세 규칙, 'static 경계(E0597·E0373), 2021과 2024 에디션의 impl Trait 수명 캡처 차이(E0700, use<>),
//! clippy의 needless_lifetimes. 실행 부분은 longest, 구조체 수명, 메서드의 수명 생략, Box::leak을 확인한다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/08_lifetimes_verify.rs"
use std::process::Command;
use std::sync::atomic::{AtomicUsize, Ordering};
static SNIPPET: AtomicUsize = AtomicUsize::new(0);
/// 코드 조각을 tool(rustc 또는 clippy-driver)로 컴파일해 stderr를 돌려준다.
fn compile_with(tool: &str, edition: &str, code: &str, extra: &[&str]) -> String {
let n = SNIPPET.fetch_add(1, Ordering::SeqCst);
let dir = std::env::temp_dir().join(format!("vr08_{}_{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(tool)
.args([
"--edition",
edition,
"--crate-type=lib",
"--emit=metadata",
"--out-dir",
])
.arg(&dir)
.args(extra)
.arg(&file)
.output()
.expect("컴파일러 실행");
let _ = std::fs::remove_dir_all(&dir);
String::from_utf8_lossy(&out.stderr).into_owned()
}
fn expect_error_ed(edition: &str, code: &str, err: &str, phrase: &str) {
let stderr = compile_with("rustc", edition, code, &[]);
assert!(
stderr.contains(&format!("error[{err}]")),
"{err} 기대({edition}), 실제:\n{stderr}"
);
assert!(stderr.contains(phrase), "'{phrase}' 기대, 실제:\n{stderr}");
println!("{err} 확인 ({edition}): {phrase}");
}
fn expect_error(code: &str, err: &str, phrase: &str) {
expect_error_ed("2024", code, err, phrase);
}
fn expect_ok_ed(edition: &str, code: &str) {
let stderr = compile_with("rustc", edition, code, &[]);
assert!(
!stderr.contains("error"),
"컴파일 성공 기대({edition}), 실제:\n{stderr}"
);
}
fn expect_ok(code: &str) {
expect_ok_ed("2024", code);
}
const EVENS: &str =
r#"pub fn evens(v:&[i32])->impl Iterator<Item=i32>{ v.iter().copied().filter(|x| x%2==0) }"#;
const LEN_OF: &str = r#"pub fn len_of(x:&str)->impl Fn()->usize{ let n=x.len(); move || n }
pub fn g()->usize{ let f={ let s=String::from("hi"); len_of(&s) }; f() }"#;
const LEN_OF_USE: &str = r#"pub fn len_of(x:&str)->impl Fn()->usize + use<>{ let n=x.len(); move || n }
pub fn g()->usize{ let f={ let s=String::from("hi"); len_of(&s) }; f() }"#;
fn compile_checks() {
// 지역 String의 참조를 돌려주려 함: 입력 참조가 없으니 수명을 정할 수 없다
expect_error(
r#"pub fn dangle() -> &String { let s=String::from("hi"); &s }"#,
"E0106",
"there is no value for it to be borrowed from",
);
// 입력 참조가 둘이면 어느 쪽인지 모른다
expect_error(
r#"pub fn longest(x:&str,y:&str)->&str{ if x.len()>y.len(){x}else{y} }"#,
"E0106",
"but the signature does not say whether it is borrowed from `x` or `y`",
);
// 수명을 적어도 지역 변수를 가리키면 E0515
expect_error(
r#"pub fn f<'a>(_x:&'a str)->&'a str{ let s=String::from("hi"); &s }"#,
"E0515",
"cannot return reference to local variable `s`",
);
// 'static을 붙여도 같다
expect_error(
r#"pub fn f()->&'static str{ let s=String::from("a"); &s }"#,
"E0515",
"returns a reference to data owned by the current function",
);
// 안쪽 블록의 x를 바깥 r가 가리킨다
expect_error(
r#"pub fn f(){ let r; { let x=5; r=&x; } println!("{r}"); }"#,
"E0597",
"`x` does not live long enough",
);
// longest의 결과를 string2보다 오래 쓴다
expect_error(
r#"fn longest<'a>(x:&'a str,y:&'a str)->&'a str{ if x.len()>y.len(){x}else{y} }
pub fn f(){ let s1=String::from("long string"); let result; { let s2=String::from("xyz"); result=longest(s1.as_str(),s2.as_str()); } println!("{result}"); }"#,
"E0597",
"`s2` does not live long enough",
);
// 참조를 담는 구조체에는 수명 매개변수가 필요하다
expect_error(
r#"pub struct Excerpt{ pub part:&str }"#,
"E0106",
"missing lifetime specifier",
);
// 규칙 1·2: 입력 참조가 하나면 출력도 그 수명
expect_ok(r#"pub fn first_word(s:&str)->&str{ s.split(' ').next().unwrap_or("") }"#);
// 규칙 3: &self가 있으면 출력은 self의 수명 (다른 입력 참조가 있어도)
expect_ok(
r#"pub struct W{ v:Vec<i32> } impl W{ pub fn get(&self, _k:&str)->&i32{ &self.v[0] } }"#,
);
// 규칙 3이 원하는 것과 다르면 컴파일러가 막는다: 인자의 참조를 돌려주려 할 때
let pick = compile_with(
"rustc",
"2024",
r#"pub struct W; impl W{ pub fn pick(&self, k:&str)->&str{ k } }"#,
&[],
);
assert!(
pick.contains("error: lifetime may not live long enough"),
"{pick}"
);
println!("규칙 3 확인: pick은 k를 돌려줄 수 없다");
// 출력이 한쪽에서만 오면 그쪽에만 수명을 단다
expect_ok(r#"pub fn first<'a>(a:&'a str,_b:&str)->&'a str{ a }"#);
// T: 'static 경계에 지역 변수의 참조를 넣으면 E0597
expect_error(
r#"fn keep<T:'static>(_t:T){} pub fn f(){ let x=5; keep(&x); }"#,
"E0597",
"argument requires that `x` is borrowed for `'static`",
);
// thread::spawn은 'static을 요구한다
expect_error(
r#"pub fn f(){ let v=vec![1]; let h=std::thread::spawn(|| v.len()); h.join().unwrap(); }"#,
"E0373",
"closure may outlive the current function, but it borrows `v`",
);
// impl Trait 수명 캡처: 2021은 E0700, 2024는 통과
expect_error_ed(
"2021",
EVENS,
"E0700",
"captures lifetime that does not appear in bounds",
);
expect_ok_ed("2024", EVENS);
// 2024는 모든 수명을 캡처하므로 필요 없는 캡처가 생길 수 있다
expect_ok_ed("2021", LEN_OF);
expect_error_ed(
"2024",
LEN_OF,
"E0597",
"Rust 2024 has adjusted the `impl Trait` lifetime capture rules",
);
// use<>로 아무것도 캡처하지 않는다고 밝히면 통과
expect_ok_ed("2024", LEN_OF_USE);
}
fn lint_checks() {
let out = compile_with(
"clippy-driver",
"2024",
r#"pub fn same<'a>(s:&'a str)->&'a str{ s }"#,
&[],
);
assert!(out.contains("clippy::needless_lifetimes"), "{out}");
println!("clippy needless_lifetimes 기본 경고 확인");
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
struct Excerpt<'a> {
part: &'a str,
}
impl Excerpt<'_> {
// 규칙 3: 돌려주는 &str은 self의 수명을 받는다
fn announce_and_return(&self, announcement: &str) -> &str {
assert!(!announcement.is_empty());
self.part
}
}
fn runtime_checks() {
let s1 = String::from("long string is long");
let r;
{
let s2 = String::from("xyz");
let inner = longest(s1.as_str(), s2.as_str());
assert_eq!(inner, "long string is long");
r = longest(s1.as_str(), "literal is 'static");
}
assert_eq!(r, "long string is long");
let novel = String::from("Call me Ishmael. Some years ago...");
let first = novel.split('.').next().unwrap();
let e = Excerpt { part: first };
assert_eq!(e.announce_and_return("hi"), "Call me Ishmael");
let s: &'static str = "I have a static lifetime.";
let leaked: &'static mut Vec<i32> = Box::leak(Box::new(vec![1, 2]));
leaked.push(3);
assert_eq!((s.len(), leaked.len()), (25, 3));
println!("runtime: longest, Excerpt, 'static ok");
}
fn main() {
compile_checks();
lint_checks();
runtime_checks();
println!("ALL CHECKS PASSED");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn longest_picks_longer() {
assert_eq!(longest("ab", "abc"), "abc");
assert_eq!(longest("abc", "ab"), "abc");
assert_eq!(longest("ab", "cd"), "cd");
}
}