값·변수·타입 문서의 검증 코드다.
//! 03 값·변수·타입: 기본 불변, 가림(shadowing), 기본 타입, 정수 넘침(dev panic / overflow-checks 끄면 감기),
//! checked_·wrapping_·saturating_·overflowing_ 메서드, as 변환의 잘림과 포화, char 4바이트를 확인한다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/3.개념집/03_values-variables-types_verify.rs"
use std::any::type_name_of_val;
use std::hint::black_box;
use std::panic;
use std::path::PathBuf;
use std::process::Command;
fn panic_message<F: FnOnce() -> R + panic::UnwindSafe, R>(f: F) -> Option<String> {
let old = panic::take_hook();
panic::set_hook(Box::new(|_| {}));
let result = panic::catch_unwind(f);
panic::set_hook(old);
result.err().map(|p| {
p.downcast_ref::<String>()
.cloned()
.or_else(|| p.downcast_ref::<&str>().map(|s| s.to_string()))
.unwrap_or_default()
})
}
fn snippet_dir(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("vr_{}_{}", std::process::id(), tag));
std::fs::create_dir_all(&dir).unwrap();
dir
}
/// 코드 조각을 라이브러리로 컴파일해 stderr를 돌려준다. emit은 "metadata"(검사만) 또는 "link"(끝까지).
fn compile_errors(tag: &str, code: &str, emit: &str) -> String {
let dir = snippet_dir(tag);
let file = dir.join("snippet.rs");
std::fs::write(&file, code).unwrap();
let out = Command::new("rustc")
.args(["--edition", "2024", "--crate-type=lib"])
.arg(format!("--emit={emit}"))
.arg("--out-dir")
.arg(&dir)
.arg(&file)
.output()
.expect("rustc 실행");
String::from_utf8_lossy(&out.stderr).into_owned()
}
/// 실행 파일로 컴파일해 돌린다. (성공 여부, stdout, stderr)
fn compile_and_run(tag: &str, code: &str, flags: &[&str]) -> (bool, String, String) {
let dir = snippet_dir(tag);
let file = dir.join("main.rs");
let exe = dir.join("prog");
std::fs::write(&file, code).unwrap();
let build = Command::new("rustc")
.args(["--edition", "2024"])
.args(flags)
.arg("-o")
.arg(&exe)
.arg(&file)
.output()
.expect("rustc 실행");
assert!(
build.status.success(),
"{}",
String::from_utf8_lossy(&build.stderr)
);
let out = Command::new(&exe).output().expect("실행");
(
out.status.success(),
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
)
}
const OVERFLOW_PROG: &str = r#"use std::hint::black_box;
fn main() {
let x: u8 = black_box(255);
println!("{}", x + 1);
let y: i32 = black_box(i32::MAX);
println!("{}", y + 1);
}
"#;
fn main() {
// 1. 기본 불변: 다시 대입하면 E0384.
let e = compile_errors(
"imm",
"pub fn f() -> i32 {\n let x = 5;\n x = 6;\n x\n}\n",
"metadata",
);
assert!(e.contains("error[E0384]: cannot assign twice to immutable variable `x`"));
println!("불변 변수에 다시 대입 -> E0384");
// 2. 가림: 같은 이름으로 새 변수를 만든다. 타입이 바뀌어도 된다.
let spaces = " ";
let spaces = spaces.len();
assert_eq!(spaces, 3);
assert_eq!(type_name_of_val(&spaces), "usize");
// mut로는 타입을 바꿀 수 없다: E0308.
let e = compile_errors(
"mutty",
"pub fn f() {\n let mut spaces = \" \";\n spaces = spaces.len();\n let _ = spaces;\n}\n",
"metadata",
);
assert!(e.contains("error[E0308]: mismatched types"));
assert!(e.contains("expected `&str`, found `usize`"));
println!("가림으로 &str -> usize: {spaces}, mut로 바꾸면 -> E0308");
// 가림은 안쪽 블록에서만 유효하다.
let x = 5;
let x = x + 1;
{
let x = x * 2;
assert_eq!(x, 12);
println!("가림 블록: 안쪽 x = {x}");
}
assert_eq!(x, 6);
println!("가림 블록: 바깥 x = {x}");
// 3. 타입을 적지 않은 리터럴의 기본 타입.
let i = 5;
let f = 2.0;
assert_eq!(type_name_of_val(&i), "i32");
assert_eq!(type_name_of_val(&f), "f64");
println!(
"5 -> {}, 2.0 -> {}",
type_name_of_val(&i),
type_name_of_val(&f)
);
// 4. 크기.
assert_eq!(size_of::<char>(), 4);
assert_eq!(size_of::<bool>(), 1);
assert_eq!(size_of::<usize>(), 8); // 이 기계는 64비트
assert_eq!(size_of::<i128>(), 16);
assert_eq!('가' as u32, 0xAC00);
assert_eq!("가".len(), 3); // 문자열은 UTF-8 바이트로 센다
assert_eq!("가".chars().count(), 1);
println!(
"char {}B, '가'=U+{:04X}, \"가\".len()={}",
size_of::<char>(),
'가' as u32,
"가".len()
);
// 5. 서로 다른 정수 타입은 자동으로 섞이지 않는다.
let e = compile_errors(
"mix",
"pub fn f() -> u64 {\n let a: u32 = 5;\n let b: u64 = 7;\n a + b\n}\n",
"metadata",
);
assert!(e.contains("error[E0308]"));
let a: u32 = 5;
let b: u64 = 7;
assert_eq!(u64::from(a) + b, 12);
println!("u32 + u64 -> E0308, u64::from(a) + b = 12");
// 6. 넘침: 이 파일은 dev 프로필이라 넘침 검사가 켜져 있다.
let x: u8 = black_box(255);
let msg = panic_message(|| x + 1).unwrap();
assert_eq!(msg, "attempt to add with overflow");
println!("dev: 255u8 + 1 -> panic: {msg}");
// 컴파일러가 값을 아는 넘침은 deny 린트로 막힌다. 다만 검사만 하는 단계(--emit=metadata, cargo check)에서는 안 보인다.
let lit = "pub fn f() -> u8 {\n let x: u8 = 255 + 1;\n x\n}\n";
let full = compile_errors("lit_full", lit, "link");
let meta = compile_errors("lit_meta", lit, "metadata");
assert!(full.contains("error: this arithmetic operation will overflow"));
assert!(!meta.contains("error"));
println!("상수 255 + 1: 끝까지 컴파일 -> 오류, 검사만 -> 오류 없음");
let e = compile_errors("lit256", "pub fn f() -> u8 {\n 256\n}\n", "metadata");
assert!(e.contains("error: literal out of range for `u8`"));
// overflow-checks를 끄면(=release 기본값) 감아 돈다. 켜면 panic.
let (ok, out, _) = compile_and_run("off", OVERFLOW_PROG, &["-C", "overflow-checks=off"]);
assert!(ok);
assert_eq!(out, "0\n-2147483648\n");
println!("overflow-checks=off -> {}", out.replace('\n', " "));
let (ok, out, err) = compile_and_run("on", OVERFLOW_PROG, &["-C", "overflow-checks=on"]);
assert!(!ok);
assert_eq!(out, "");
assert!(err.contains("attempt to add with overflow"));
println!("overflow-checks=on -> panic");
// -O(최적화)만 주고 overflow-checks를 따로 정하지 않아도 꺼진다(debug-assertions가 꺼지므로).
let (ok, out, _) = compile_and_run("opt", OVERFLOW_PROG, &["-O"]);
assert!(ok);
assert_eq!(out, "0\n-2147483648\n");
// 0으로 나누기와 i32::MIN / -1은 overflow-checks를 꺼도 panic이다.
let div = r#"use std::hint::black_box;
fn main() {
let m: i32 = black_box(i32::MIN);
let d: i32 = black_box(-1);
println!("{}", m / d);
}
"#;
let (ok, _, err) = compile_and_run("div", div, &["-C", "overflow-checks=off"]);
assert!(!ok);
assert!(err.contains("attempt to divide with overflow"));
let zero_prog = "use std::hint::black_box;\nfn main() {\n let z: i32 = black_box(0);\n println!(\"{}\", 1 / z);\n}\n";
let (ok, _, err) = compile_and_run("zero", zero_prog, &["-C", "overflow-checks=off"]);
assert!(!ok);
assert!(err.contains("attempt to divide by zero"));
let z: i32 = black_box(0);
let msg = panic_message(|| 1 / z).unwrap();
assert_eq!(msg, "attempt to divide by zero");
println!("i32::MIN / -1 (checks off) -> panic, 1 / 0 -> panic: {msg}");
// 7. 넘침을 직접 다루는 네 메서드.
let x: u8 = black_box(250);
assert_eq!(x.checked_add(10), None);
assert_eq!(x.checked_add(5), Some(255));
assert_eq!(x.wrapping_add(10), 4);
assert_eq!(x.saturating_add(10), 255);
assert_eq!(x.overflowing_add(10), (4, true));
assert_eq!(0u8.saturating_sub(1), 0);
println!(
"250u8 + 10: checked {:?}, wrapping {}, saturating {}, overflowing {:?}",
x.checked_add(10),
x.wrapping_add(10),
x.saturating_add(10),
x.overflowing_add(10)
);
// 8. as 변환: 조용히 자르거나 포화시킨다. try_from은 실패를 알려 준다.
let big: i32 = black_box(300);
let neg: i32 = black_box(-1);
assert_eq!(big as u8, 44); // 300 = 256 + 44, 아래 8비트만 남는다
assert_eq!(neg as u32, 4_294_967_295);
assert_eq!(neg as u8, 255);
assert_eq!(black_box(3.99f64) as i32, 3); // 0 쪽으로 버림
assert_eq!(black_box(-3.99f64) as i32, -3);
assert_eq!(black_box(-1.5f64) as u8, 0); // 포화
assert_eq!(black_box(1e10f64) as i32, i32::MAX);
assert_eq!(black_box(f64::NAN) as i32, 0);
assert!(u8::try_from(big).is_err());
assert_eq!(u8::try_from(black_box(200i32)), Ok(200u8));
println!(
"300 as u8 = {}, -1 as u32 = {}, 3.99 as i32 = {}, -1.5 as u8 = {}, 1e10 as i32 = {}, NaN as i32 = {}",
big as u8,
neg as u32,
black_box(3.99f64) as i32,
black_box(-1.5f64) as u8,
black_box(1e10f64) as i32,
black_box(f64::NAN) as i32
);
// 9. 부동소수점: 0.1 + 0.2는 0.3이 아니다.
let s = black_box(0.1f64) + black_box(0.2f64);
assert_ne!(s, 0.3);
assert!((s - 0.3).abs() < 1e-12);
println!("0.1 + 0.2 = {s}");
// 10. const는 타입을 꼭 적고, 컴파일할 때 값이 정해진다.
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
assert_eq!(THREE_HOURS_IN_SECONDS, 10_800);
let e = compile_errors("constty", "const N = 5;\n", "metadata");
assert!(e.contains("missing type for `const` item"), "{e}");
println!("const 타입 생략 -> 오류");
// 11. 확인 문제 C1: 가림은 되고, mut에 다른 타입을 넣으면 E0308.
let a = compile_errors(
"c1a",
"pub fn f() -> &'static str {\n let x = 5;\n let _ = x;\n let x = \"five\";\n x\n}\n",
"metadata",
);
assert!(!a.contains("error"), "{a}");
let b = compile_errors(
"c1b",
"pub fn f() {\n let mut x = 5;\n let _ = x;\n x = \"five\";\n let _ = x;\n}\n",
"metadata",
);
assert!(b.contains("error[E0308]"), "{b}");
println!("C1: 가림 -> 컴파일됨, mut에 &str -> E0308");
println!("ALL CHECKS PASSED");
}