트레이트와 반복자 연습 문서의 문제 1 풀이 코드다.
//! 22 트레이트와 반복자 연습 p1: 도형 트레이트와 트레이트 객체로 섞인 도형을 넓이 순으로 정렬한다.
//! 변형: f64 넓이를 sort_by_key의 키로 쓰면 f64가 Ord가 아니라서 E0277로 막히는 것을 확인한다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/4.연습문제/22_traits-iterators-practice_p1.rs"
use std::f64::consts::PI;
trait Shape {
fn area(&self) -> f64;
fn name(&self) -> String;
fn describe(&self) -> String {
format!("{} {:.2}", self.name(), self.area())
}
}
struct Circle {
r: f64,
}
struct Rect {
w: f64,
h: f64,
}
struct Triangle {
base: f64,
height: f64,
}
impl Shape for Circle {
fn area(&self) -> f64 {
PI * self.r * self.r
}
fn name(&self) -> String {
"원".into()
}
}
impl Shape for Rect {
fn area(&self) -> f64 {
self.w * self.h
}
fn name(&self) -> String {
"직사각형".into()
}
}
impl Shape for Triangle {
fn area(&self) -> f64 {
self.base * self.height / 2.0
}
fn name(&self) -> String {
"삼각형".into()
}
}
/// 넓이가 큰 것부터 설명 문자열을 돌려준다.
fn ranked(shapes: &mut [Box<dyn Shape>]) -> Vec<String> {
shapes.sort_by(|a, b| b.area().total_cmp(&a.area()));
shapes.iter().map(|s| s.describe()).collect()
}
fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
shapes.iter().map(|s| s.area()).sum()
}
fn example() -> Vec<Box<dyn Shape>> {
vec![
Box::new(Circle { r: 1.0 }),
Box::new(Rect { w: 2.0, h: 3.0 }),
Box::new(Triangle {
base: 4.0,
height: 2.0,
}),
]
}
const SORT_BY_KEY: &str = "pub trait Shape { fn area(&self) -> f64; }
pub fn f(v: &mut [Box<dyn Shape>]) { v.sort_by_key(|s| s.area()); }";
fn compile_stderr(code: &str) -> String {
let dir = std::env::temp_dir().join(format!("vr22p1_{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("snippet.rs");
std::fs::write(&file, code).unwrap();
let out = std::process::Command::new("rustc")
.args([
"--edition",
"2024",
"--crate-type=lib",
"--emit=metadata",
"--out-dir",
])
.arg(&dir)
.arg(&file)
.output()
.expect("rustc 실행");
String::from_utf8_lossy(&out.stderr).into_owned()
}
fn main() {
let mut shapes = example();
let out = ranked(&mut shapes);
let total = total_area(&shapes);
println!("{out:?}");
println!("합 {total:.5}");
assert_eq!(out, ["직사각형 6.00", "삼각형 4.00", "원 3.14"]);
assert!((total - (PI + 10.0)).abs() < 1e-12);
// 변형: sort_by_key(|s| s.area())는 f64가 Ord가 아니라서 컴파일되지 않는다.
let e = compile_stderr(SORT_BY_KEY);
let first = e.lines().find(|l| l.starts_with("error")).unwrap();
println!("[변형] {first}");
assert!(e.contains("error[E0277]: the trait bound `f64: Ord` is not satisfied"));
// total_cmp는 NaN까지 줄 세운다: 양수 NaN은 가장 큰 쪽에 온다.
let mut xs = [2.0, f64::NAN, -1.0];
xs.sort_by(f64::total_cmp);
assert_eq!(xs[0], -1.0);
assert!(xs[2].is_nan());
println!("ALL CHECKS PASSED");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn example_order() {
let mut s = example();
assert_eq!(ranked(&mut s)[0], "직사각형 6.00");
}
#[test]
fn empty_is_zero() {
let s: Vec<Box<dyn Shape>> = Vec::new();
assert_eq!(total_area(&s), 0.0);
}
}