Java 개발자를 위한 Rust 정리

Java 개발자를 위한 단계별 Rust 전환 가이드  |  Java 21 · Rust 2021 (rustc 1.75) · Go 1.21 · C++17
모든 예제 복사 즉시 컴파일·실행 가능  |  Rust Book · JLS SE21 · JVM Spec SE21

Java 개발자를 위한 Rust 가이드

전체 구성 — 탭별 학습 순서

섹션핵심 내용Java 대응
기초 문법 ① 기본 문법변수·불변성·함수·struct·impl·접근제한자클래스·필드·메서드
② enum · Option · Result대수적 타입, null 대체, 예외 없는 에러 처리enum, Optional, throws
③ 타입 시스템타입 추론·turbofish·as/From/Into·숫자타입·String·matchvar, 캐스팅, parseInt
④ 소유권 · 빌림move / & / &mut · Drop · CloneGC, 참조 전달
⑤ Box · Rc · Arc힙 할당, 공유 소유권new, GC 공유
⑥ 클로저 · 람다Fn / FnMut / FnOnce · 캡처 방식람다, Function<T,R>
⑦ 컬렉션 · 함수형Vec · HashMap · Iterator 체인ArrayList, HashMap, Stream
⑧ 제네릭 · trait bound단형화 vs 타입 소거 · struct 메서드<T extends Sound>
⑨ 라이프타임참조 유효 범위 · dangling 차단 · 생략 규칙GC (개념 없음)
제네릭 · 템플릿 ① 왜 필요한가타입별 중복 코드 문제 · 세 언어 비교없음 (타입마다 클래스)
② Box<T> 핵심 차이Template Instantiation · Type Erasure · Monomorphization제네릭 타입 소거
③ 함수 템플릿제네릭 메서드 · static_assert · trait bound<T> 제네릭 메서드
④ 타입 제한static_assert · extends · trait boundT extends Number
⑤ Primitive · 메모리Boxing 비용 · 스택/힙 구조 비교Integer Autoboxing
⑥ 런타임 타입 정보typeid · TypeId · Type ErasuregetClass() · 리플렉션
⑦ Stack<T> 실전완전 구현 · Java/C++/Rust 비교Stack<T> 구현
⑧ 정적 vs 동적Fat Pointer · &dyn · vtable인터페이스 동적 디스패치
문자열 · 라이프타임 ① String vs &str소유권과 뷰 · 근본 차이String (뷰 없음)
② 메모리 구조ptr·len·cap · &str fat pointerObject Header + byte[]
③ 함수 파라미터&str vs String 선택 기준 · Deref coercion항상 String
④ 구조체 필드라이프타임 파라미터 'a · Parser<'a>GC 자동 관리
⑤ 라이프타임 파라미터longest<'a> · 컴파일러와의 계약없음 (GC)
⑥ 실전 — 트레이트 조합where 절 · 라이프타임 + 트레이트 바운드제네릭 + 인터페이스
trait · 디스패치 상속 없는 Rust컴포지션 · trait 기본 구현 · 4개 언어 비교extends, implements
impl vs dyn정적 vs 동적 디스패치 5가지 비교인터페이스 다형성
정적 디스패치단형화 — 타입별 코드 생성, 0 오버헤드없음 (Java는 항상 동적)
동적 디스패치dyn Trait · vtable · fat pointer 16B인터페이스 변수 = 자동
메모리 비교JVM vs Rust 스택/힙 레이아웃Object Header 12~16B
전체 예제Main.java / main.rs동물원 예제 — 전체 개념 총정리완전한 Java↔Rust 대응
레퍼런스용어 사전 · 핵심 정리Rust 용어 ↔ Java 대응 · 공식 출처

Java vs Rust — 핵심 차이 5가지

#주제☕ Java Rust
1메모리 관리 GC — 런타임, 비결정적, pause 있음 소유권 + Drop — 컴파일타임, 결정적, 0 비용
2null 처리 null — NPE는 런타임에야 발견 Option<T> — 컴파일타임에 처리 강제
3에러 처리 throws Exception — 무시 가능 Result<T,E> — 무시하면 컴파일 경고
4힙 vs 스택 new = 항상 힙 (Object Header 포함) 기본 스택, Box::new()만 힙
5다형성 인터페이스 변수 → 자동 동적 디스패치 impl Trait = 정적, dyn Trait = 동적 (명시 필수)

추천 학습 경로

경험추천 순서
Rust 처음기초 문법 ① → ② → ③ → ④ → ⑥ → ⑦ → 전체 예제
소유권이 헷갈림기초 문법 ④ → ⑤ → ⑨ → trait 탭
문자열/라이프타임 심화문자열·라이프타임 탭 ① → ② → ③ → ④ → ⑤
제네릭/템플릿 비교제네릭·템플릿 탭 ① → ② → ③ → ④ → ⑦
trait/인터페이스 비교trait·디스패치 탭 전체
빠른 참조레퍼런스 탭 — 용어 사전 · 핵심 정리

Rust 기본 문법 — Java 대응

출처: Rust Book §3JLS SE21 §4

변수 · 상수 · 불변성

☕ Java
1public class Main {
2    public static void main(String[] args) {
3        // Java: 기본적으로 가변
4        int count = 0;
5        count = 5;           // 재할당 가능
6        final int MAX = 100; // 상수 (Rust const)
7        // MAX = 200;        // 컴파일 에러
8        var name = "Clean Code"; // 타입 추론 (Java 10+)
9        System.out.println(count + " " + MAX + " " + name);
10    }
11}
Rust
 1fn main() {
 2    let count = 0i32;
 3    // count = 5; // E0384
 4    let mut count2 = 0i32;
 5    count2 = 5;
 6    const MAX: u32 = 100;
 7    let name = "Clean Code";
 8    println!("{} {} {} {}", count, count2, MAX, name);
 9}
10
🔍 스택 메모리 레이아웃 — let vs let mut
🔍 스택 메모리 — let · let mut · const선언메모리설명let count = 0i320x00 (4B)불변 — 재할당 불가let mut count2 = 0i320x04 (4B)가변 — 재할당 가능const MAX = 100u32(인라인)컴파일타임 상수✦ 스택: 함수 호출 시 할당, 종료 시 자동 해제 (GC 없음)
💡 불변이 기본인 이유
let x=5; 불변. let mut x=5; 가변.
실수 방지 + data race 컴파일타임 차단.
출처: Rust Book §3.1

기본 타입

☕ Java
 1public class Main {
 2    public static void main(String[] args) {
 3        boolean b = true;
 4        int    i = 42;           // 32bit
 5        long   l = 9999999999L;  // 64bit
 6        double d = 3.14;         // 64bit
 7        char   c = 'A';          // UTF-16 2B
 8        String s = "hello";      // 힙 객체
 9        int    x = (int) 3.9;    // 캐스트→3
10        System.out.println(
11            b + " " + i + " " + l
12            + " " + d + " " + c
13            + " " + s + " " + x);
14    }
15}
Rust
 1fn main() {
 2    let b: bool = true;
 3    let i: i32  = 42;
 4    let l: i64  = 9999999999;
 5    let d: f64  = 3.14;
 6    let c: char = 'A';
 7    let s: &str = "hello";
 8    let x = 3.9f64 as i32;
 9    println!("{} {} {} {} {} {} {}", b, i, l, d, c, s, x);
10}
11
타입☕ Java Rust크기
불리언booleanbool1B
정수(32bit)inti324B
정수(64bit)longi648B
부동소수doublef648B
문자char (UTF-16 2B)char (Unicode 4B)다름!
소유 문자열StringString (24B)
문자열 참조없음&str (16B)스택
부호 없는 정수없음u8~u128, usizeRust만

함수 정의

☕ Java
 1// 반환 타입이 앞에
 2static int add(int a, int b) {
 3    return a + b;
 4}
 5static void greet(String name) {
 6    System.out.println("Hello, " + name);
 7}
 8public static void main(String[] args) {
 9    int r = add(2, 3);
10    greet("Rust");
11    System.out.println(r); // 5
12}
Rust
1fn add(a: i32, b: i32) -> i32 { a + b }
2fn greet(name: &str) { println!("Hello, {}", name); }
3fn main() {
4    let r = add(2, 3);
5    greet("Rust");
6    println!("{}", r);
7}
8
🔍 함수 호출 스택 — Java vs Rust 반환값 비교
🔍 함수 호출 스택 — Java vs Rust☕ Javamain()r = add(2, 3)add(a, b)return a + b; ← 명시적void = 반환값 없음 Rustmain()let r = add(2, 3);add(a, b)a + b ← ; 없음 = 반환() = unit type | return = 명시적 조기 반환vs
⚠️ 세미콜론 없음 = 반환값
a + b → 반환  |  a + b;() (void 대응)
출처: Rust Reference — Block expressions

조건문 — if는 표현식

☕ Java
 1public class Main {
 2    public static void main(String[] args) {
 3        int score = 85;
 4        // if는 문장 — 값 반환 불가
 5        if (score >= 90) System.out.println("A");
 6        else if (score >= 80) System.out.println("B");
 7        else System.out.println("C");
 8
 9        // switch expression (Java 14+)
10        String grade = switch (score / 10) {
11            case 10, 9 -> "A";
12            case 8     -> "B";
13            case 7     -> "C";
14            default    -> "F";
15        };
16        System.out.println(grade); // B
17
18        // for-each
19        int[] nums = {1, 2, 3};
20        for (int n : nums) System.out.println(n);
21    }
22}
Rust
 1fn main() {
 2    let score = 85i32;
 3
 4    // if는 표현식 — 값을 변수에 대입 가능
 5    // Java: if는 문장, 값 반환 불가
 6    let grade = if score >= 90 {
 7        "A"
 8    } else if score >= 80 {
 9        "B"
10    } else {
11        "C"
12    };
13    println!("{}", grade); // "B"
14
15    // for in — 이터레이터 기반
16    let nums = [1i32, 2, 3];
17    for n in &nums {
18        println!("{}", n);
19    }
20
21    // while
22    let mut i = 0i32;
23    while i < 3 {
24        i += 1;
25    }
26
27    // loop — 값 반환 가능 (Java에 없음)
28    let mut j = 0i32;
29    let result = loop {
30        j += 1;
31        if j >= 3 { break j * 10; }
32    };
33    println!("i={}, j={}, loop결과={}", i, j, result);
34}
35

loop · while · for

☕ Java
 1public class Main {
 2    public static void main(String[] args) {
 3        // while
 4        int n = 0;
 5        while (n < 3) {
 6            System.out.print(n + " ");
 7            n++;
 8        }
 9        System.out.println();
10
11        // do-while (Rust에 없음 → loop로 대체)
12        int count = 0;
13        do { count++; } while (count < 5);
14        System.out.println("count: " + count);
15
16        // for (index) — Rust: for i in 0..5
17        for (int i = 0; i < 5; i++) {
18            System.out.print(i + " ");
19        }
20        System.out.println();
21
22        // for-each — Rust: for x in &list
23        String[] names = { "Simba", "Donald", "Nagini"};
24        for (String name : names) {
25            System.out.println("Animal: " + name);
26        }
27
28        // labeled break (Rust도 지원)
29        outer:
30        for (int x = 0; x < 3; x++) {
31            for (int y = 0; y < 3; y++) {
32                if (x == 1 && y == 1) break outer;
33                System.out.print(
34                    "(" + x + "," + y + ") ");
35            }
36        }
37        System.out.println();
38        // Java loop는 값 반환 불가
39        // Rust: let x = loop { break 42; };
40    }
41}
42
Rust
 1fn main() {
 2    // ─── loop — 무한 루프 + 값 반환 ──────────────
 3    let mut count = 0u32;
 4    let result = loop {
 5        count += 1;
 6        if count == 5 { break count * 2; } // break로 값 반환!
 7        // Java: while(true) + return/변수 → Rust만의 기능
 8    };
 9    println!("loop 결과: {}", result); // 10
10
11    // ─── while ────────────────────────────────────
12    let mut n = 0u32;
13    while n < 3 {
14        print!("{} ", n); // 0 1 2
15        n += 1;
16    }
17    println!();
18
19    // ─── for in — 이터레이터 기반 ─────────────────
20    for i in 0..5 {        // 0,1,2,3,4
21        print!("{} ", i);
22    }
23    println!();
24
25    for i in 0..=5 {       // 0,1,2,3,4,5 (inclusive)
26        print!("{} ", i);
27    }
28    println!();
29
30    // ─── for in 컬렉션 ────────────────────────────
31    let names = [ "Simba", "Donald", "Nagini"];
32    for name in &names {
33        println!("동물: {}", name);
34    }
35
36    // ─── enumerate — 인덱스 + 값 ──────────────────
37    for (i, name) in names.iter().enumerate() {
38        println!("[{}] {}", i, name);
39    }
40
41    // ─── 중첩 루프 레이블 (Java에도 있음) ──────────
42    'outer: for x in 0..3 {
43        for y in 0..3 {
44            if x == 1 && y == 1 {
45                break 'outer; // 외부 루프 탈출
46            }
47            print!("({},{}) ", x, y);
48        }
49    }
50    println!();
51}
52
구문☕ Java Rust차이
무한 루프while(true)loopRust: break value로 값 반환
whilewhile(n<3)while n<3동일
do-whiledo{}while(c)없음loop로 대체
for(index)for(int i=0;i<5;i++)for i in 0..5range 기반
for-eachfor(T x:list)for x in &list유사
labeled breaklabel: for'label: loop둘 다 지원

⑧ #[derive] — Java 어노테이션과 비교

☕ Java
 1import java.util.HashSet;
 2import java.util.Objects;
 3// #[derive] 대응 — Java는 직접 구현 or Lombok
 4public class Main {
 5    static class Book {
 6        final String title;
 7        final int pages;
 8        Book(String t, int p) { title=t; pages=p; }
 9
10        // #[derive(Debug)] → toString()
11        // #[derive(Debug)] → toString()
12        @Override
        public String toString() {
13            return "Book{'"
14                + title + "', "
15                + pages + "}";
16        }
17        @Override public boolean equals(Object o) {
18            if (!(o instanceof Book b)) return false;
19            return Objects.equals(title, b.title)
20                && pages == b.pages;
21        }
22        // #[derive(Hash)] → hashCode()
23        @Override public int hashCode() {
24            return Objects.hash(title, pages);
25        }
26    }
27    public static void main(String[] args) {
28        Book b1 =
            new Book("Clean Code", 431);
29        Book b2 = new Book(b1.title, b1.pages);
30        System.out.println(b1);           // toString
31        System.out.println(b1.equals(b2)); // equals
32        HashSet<Book> set =
            new HashSet<>();
33        set.add(b1); // hashCode 필요
34        System.out.println(set.size());    // 1
35        // Java: 직접 구현 or Lombok @Data
36        // Rust: #[derive(Debug, Clone,
        //         PartialEq, Hash)] 한 줄!
37    }
38}
39
Rust
 1#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 2struct Book {
 3    title: String,
 4    pages: u32,
 5}
 6impl Book {
 7    fn new(t: &str, p: u32) -> Self {
 8        Book { title: t.to_string(), pages: p }
 9    }
10}
11
12#[derive(Debug, Default)]
13struct Config {
14    timeout: u32,
15    verbose: bool,
16    name:    String,
17}
18
19fn main() {
20    let b1 = Book::new("Clean Code", 431);
21    let b2 = b1.clone();
22    println!("{:?}", b1);
23    println!("{}", b1 == b2); // true
24
25    use std::collections::HashSet;
26    let mut set = HashSet::new();
27    set.insert(b1);
28    println!("set: {}", set.len()); // 1
29
30    let cfg = Config::default();
31    println!("{:?}", cfg);
32}
33
#[derive]자동 구현Java 대응
Debug{:?} 출력toString()
Clone.clone()clone()
PartialEq== 비교equals()
HashHashMap 키hashCode()
DefaultType::default()기본 생성자
Copy비트 복사 (move 없음)없음
Ord완전 순서Comparable

struct · impl — 데이터와 메서드 분리

💡 struct vs class
Java class는 데이터 + 메서드를 한 곳에. Rust는 데이터(struct)메서드(impl)를 분리. 상속 없음 — 대신 trait으로 공통 행동 정의.
☕ Java
 1public class Main {
 2    private String title;
 3    private int    pages;
 4
 5    public Main(String title, int pages) {
 6        this.title = title;
 7        this.pages = pages;
 8    }
 9    public String summary() {
10        return title + " (" + pages + "p)";
11    }
12    public boolean isLong() { return pages > 400; }
13    public static void main(String[] args) {
14        Main b = new Main("Clean Code", 431);
15        System.out.println(b.summary()); // Clean Code (431p)
16        System.out.println(b.isLong());  // true
17    }
18}
Rust
 1// 데이터만 정의
 2struct Book {
 3    title: String,
 4    pages: u32,
 5}
 6// 메서드는 impl 블록에 분리
 7impl Book {
 8    // 연관 함수 (Java 생성자/static)
 9    fn new(title: &str, pages: u32) -> Book {
10        Book { title: title.to_string(), pages }
11    }
12    // 인스턴스 메서드 (&self = Java this)
13    fn summary(&self) -> String {
14        format!("{} ({}p)", self.title, self.pages)
15    }
16    fn is_long(&self) -> bool { self.pages > 400 }
17}
18fn main() {
19    let b = Book::new("Clean Code", 431);
20    println!("{}", b.summary()); // Clean Code (431p)
21    println!("{}", b.is_long());  // true
22}
☕ Java Rust설명
class Book { ... }struct Book { ... }데이터 정의
클래스 내부 메서드impl Book { ... }메서드는 별도 블록
new Book(...)Book::new(...)생성 관례 (강제 아님)
this&self / &mut self인스턴스 참조
static 메서드self 없는 연관 함수Book::new() 패턴

튜플 · 슬라이스 — Java에 없는 타입

Rust
 1fn min_max(nums: &[i32]) -> (i32, i32) {
 2    let mut mn = nums[0]; let mut mx = nums[0];
 3    for &n in nums {
 4        if n < mn { mn = n; } if n > mx { mx = n; }
 5    }
 6    (mn, mx) // 튜플로 여러 값 반환
 7}
 8fn main() {
 9    let (lo, hi) = min_max(&[3,1,4,1,5]); // 구조 분해
10    println!("min={lo} max={hi}");
11    let pair: (i32, String) = (42, "hi".to_string());
12    println!("{} {}", pair.0, pair.1); // 인덱스 접근
13}
Rust
 1fn sum(nums: &[i32]) -> i32 { // &[T] = 슬라이스 뷰
 2    nums.iter().sum()
 3}
 4fn main() {
 5    let v = vec![1, 2, 3, 4, 5];
 6    println!("{}", sum(&v));       // 15
 7    println!("{}", sum(&v[1..4])); // 9
 8    let arr = [10, 20, 30];
 9    println!("{}", sum(&arr));     // 60
10    let s: &[i32] = &v[0..3];
11    println!("{:?}", s);           // [1, 2, 3]
12}
💡 &[T] vs Java 배열
&[T]는 메모리 연속 구간의 뷰(view). Vec, 배열 어디서든 만들 수 있음.
함수 인자로 &Vec<T> 대신 &[T]를 받으면 더 유연 — Java의 List vs int[]보다 통합된 방식.

접근 제한자 — public/private vs pub

💡 Rust의 기본은 private
Java는 생략하면 package-private. Rust는 생략하면 완전 private. 외부 공개는 반드시 pub 필요.
☕ Java
 1public class Main {
 2    static class Person {
 3        public    String name;   // 어디서든
 4        protected int    age;    // 패키지 + 서브클래스
 5                  String dept;   // package-private (기본)
 6        private   String secret; // 이 클래스만
 7        public String getName() { return name; }
 8    }
 9    public static void main(String[] args) {
10        Person p = new Person();
11        p.name = "Simba";
12        System.out.println(p.getName()); // Simba
13    }
14}
Rust
 1pub struct Person {
 2    pub name: String,    // 공개
 3        age:  u32,       // private (기본)
 4        secret: String, // private
 5}
 6impl Person {
 7    pub fn name(&self) -> &str { &self.name }
 8    fn helper(&self) { } // private
 9}
10pub(crate) fn internal() {} // crate 내부만
11fn main() {
12    let p = Person {
13        name: "Simba".to_string(),
14        age: 5, secret: "roar".to_string(),
15    };
16    println!("{}", p.name()); // Simba
17}
☕ Java Rust범위
publicpub어디서든
package-private (기본)pub(crate)같은 크레이트 내
protected없음 (상속 없음)
private생략 = private현재 모듈만

📁 파일 분리로 이해하는 pub / private / pub(crate)

같은 파일 안에서는 private 필드에도 접근 가능하기 때문에 pub/private 차이가 잘 보이지 않습니다. 파일을 나누면 person.rs는 정의/캡슐화, main.rs는 사용하는 쪽이 되어 무엇이 공개되고 숨겨졌는지 명확히 보입니다.

my_app/src/
├─ main.rs    ← 사용하는 쪽
└─ person.rs   ← 정의/캡슐화하는 쪽
Rust — person.rs
 1pub struct Person {
 2    pub name: String,  // 외부 공개
 3    age: u32,          // 외부 비공개 (private)
 4    secret: String,    // 외부 비공개 (private)
 5}
 6
 7impl Person {
 8    // 공개 생성자 — private 필드가 있으므로 필수
 9    pub fn new(name: &str, age: u32) -> Self {
10        Self {
11            name: name.to_string(),
12            age,
13            secret: String::new(),
14        }
15    }
16
17    // 공개 getter
18    pub fn name(&self) -> &str { &self.name }
19    pub fn age(&self) -> u32   { self.age }
20
21    // private 메서드 — 이 파일 안에서만 호출 가능
22    fn helper(&self) {
23        println!("helper 실행");
24    }
25
26    // 공개 메서드이지만 내부에서 private 사용 가능
27    pub fn print_info(&self) {
28        self.helper();
29        println!("name={}, age={}",
30            self.name, self.age);
31    }
32}
33
34// crate 내부에서만 호출 가능 — 외부 비공개
35pub(crate) fn internal_logic() {
36    println!("crate 내부 전용 함수");
37}
Rust — main.rs
 1mod person;
 2use person::Person;
 3
 4fn main() {
 5    let p = Person::new("Simba", 5);
 6
 7    // ✅ pub 필드 — 직접 접근 가능
 8    println!("name = {}", p.name);
 9
10    // ✅ pub 메서드 — 호출 가능
11    println!("name() = {}", p.name());
12    println!("age()  = {}", p.age());
13
14    // ✅ pub(crate) — 같은 crate 안이므로 가능
15    person::internal_logic();
16
17    // ✅ pub 메서드 (내부에서 private 사용)
18    p.print_info();
19
20    // ❌ private 필드 — 직접 접근 불가
21    // println!("{}", p.age);
22    // println!("{}", p.secret);
23    // ❌ private 메서드 — 호출 불가
24    // p.helper();
25}
💡 pub / private / pub(crate) — 공개 범위 정리
키워드 접근 범위 용도
pub 어디서든 공개 API · 타입 · 생성자
생략 (private) 현재 모듈 안에서만 내부 구현 · 보조 함수
pub(crate) 같은 crate 안 내부 공통 로직 · 외부 비공개
핵심: 파일을 나누면 person.rs정의/캡슐화, main.rs사용하는 쪽이 되어 무엇이 공개되고 숨겨졌는지 명확히 보임.

pub struct가 없으면 외부에서 타입 자체를 쓸 수 없음 — use person::Person 불가.
private 필드는 공개 getter(pub fn age())로 우회 접근.
pub(crate)는 라이브러리 내부 공통 로직 — 외부 사용자에게 숨김.

배열 · 리스트 초기화 비교

☕ Java
 1import java.util.*;
 2public class Main {
 3    public static void main(String[] args) {
 4        int[] arr  = {1, 2, 3};
 5        int[] arr2 = new int[5]; // 0으로 초기화
 6        List<Integer> list = new ArrayList<>();
 7        list.add(1); list.add(2);
 8        List<String> names =
 9            List.of("Alice", "Bob"); // 불변
10        int[][] grid = new int[3][3];
11        System.out.println(
12            arr.length + " " + list.size()); // 3 2
13    }
14}
Rust
 1fn main() {
 2    let arr  = [1, 2, 3];     // [i32; 3] 고정
 3    let arr2 = [0; 5];         // [0,0,0,0,0]
 4    let mut list: Vec<i32> = Vec::new();
 5    list.push(1); list.push(2);
 6    let names = vec!["Alice", "Bob"]; // 매크로
 7    let grid: Vec<Vec<i32>> =
 8        (0..3).map(|_| vec![0; 3]).collect();
 9    println!("{} {}", arr.len(), list.len()); // 3 2
10    let _ = (arr2, names, grid);
11}

String 메서드 비교 — 가장 자주 쓰는 것들

☕ Java
 1public class Main {
 2    public static void main(String[] args) {
 3        String s = "  Hello, Rust!  ";
 4        s.length();             // 16
 5        s.trim();               // "Hello, Rust!"
 6        s.toUpperCase();        // "  HELLO, RUST!  "
 7        s.contains("Rust");    // true
 8        s.replace("Rust","Java");
 9        s.split(",");
10        s.substring(2, 7);     // "Hello"
11        s.indexOf("Rust");     // 9
12        String.valueOf(42);     // "42"
13        Integer.parseInt("42"); // 42
14        System.out.println(s.trim()); // Hello, Rust!
15    }
16}
Rust
 1fn main() {
 2    let s = "  Hello, Rust!  ";
 3    println!("{}", s.len());          // 16 (바이트 수)
 4    println!("{}", s.trim());         // Hello, Rust!
 5    println!("{}", s.to_uppercase()); //   HELLO, RUST!  
 6    println!("{}", s.contains("Rust")); // true
 7    let r = s.replace("Rust", "Java");
 8    println!("{}", r); //   Hello, Java!  
 9    let parts: Vec<_> = s.split(',').collect();
10    println!("{:?}", parts); // ["  Hello", " Rust!  "]
11    println!("{}", &s[2..7]);  // Hello (슬라이스)
12    println!("{:?}", s.find("Rust")); // Some(9)
13    println!("{}", 42.to_string());  // 42
14    let n: i32 = "42".parse().unwrap();
15    println!("{}", n);  // 42
16}
☕ Java Rust차이
length()len()Rust는 바이트 수 (UTF-8 주의)
toUpperCase()to_uppercase()snake_case
substring(s,e)&s[s..e]Rust는 바이트 인덱스 슬라이스
indexOf()find()Rust는 Option<usize> 반환
String.valueOf(n)n.to_string()모든 타입 가능
Integer.parseInt(s)s.parse::<i32>()Rust는 Result 반환

if let · while let — Optional.ifPresent 대응

💡 if let — match의 단축형
Java Optional.ifPresent()와 유사하지만 Rust는 언어 레벨에서 지원.
if let Some(x) = opt { } = match opt { Some(x) => { }, _ => {} }의 축약
☕ Java
 1import java.util.Optional;
 2public class Main {
 3    public static void main(String[] args) {
 4        Optional<String> name =
 5            Optional.of("Simba");
 6        // ifPresent = if let Some()
 7        name.ifPresent(n ->
 8            System.out.println("Name: " + n));
 9        // ifPresentOrElse = if let ... else
10        name.ifPresentOrElse(
11            n -> System.out.println(n),
12            () -> System.out.println("none"));
13        // while let 대응: 없음 (Iterator로 대체)
14        Optional<Integer> val =
15            Optional.of(3);
16        while (val.isPresent()) {
17            System.out.println(val.get());
18            val = Optional.empty();
19        }
20    }
21}
Rust
 1fn main() {
 2    let name: Option<String> =
 3        Some("Simba".to_string());
 4    // if let — Some일 때만 실행
 5    if let Some(n) = &name {
 6        println!("이름: {}", n); // Simba
 7    }
 8    // if let ... else
 9    if let Some(n) = &name {
10        println!("{}", n);
11    } else {
12        println!("없음");
13    }
14    // while let — None이 될 때까지 반복
15    let mut stack = vec![1, 2, 3];
16    while let Some(top) = stack.pop() {
17        println!("{}", top); // 3, 2, 1
18    }
19}
20
☕ Java Rust설명
opt.ifPresent(f)if let Some(x) = opt값 있을 때만 실행
opt.ifPresentOrElse(f,g)if let Some(x) = opt { } else { }없을 때 분기
없음while let Some(x) = iter.next()None까지 반복

switch vs match — 패턴 매칭 비교

☕ Java
 1public class Main {
 2    public static void main(String[] args) {
 3        int day = 3;
 4        String name = switch (day) {
 5            case 1 -> "Mon";
 6            case 2 -> "Tue";
 7            case 3 -> "Wed";
 8            default -> "Other"; // 생략 가능 → 런타임 버그 위험
 9        };
10        System.out.println(name); // 수
11    }
12}
Rust
 1fn main() {
 2    let day = 3u32;
 3    let name = match day {
 4        1 => "월",
 5        2 => "화",
 6        3 => "수",
 7        _ => "기타", // 누락 시 컴파일 에러
 8    };
 9    println!("{}", name); // 수
10}
💡 match 완전성 보장
Java switchdefault 생략 가능 → 런타임 버그.
Rust match는 모든 경우 커버 강제 — 컴파일 타임에 완전성 보장.

try/catch/finally vs Result · ? 연산자

☕ Java
 1import java.io.*;
 2import java.nio.file.*;
 3public class Main {
 4    static String readFile(String path)
 5            throws IOException {
 6        try {
 7            return new String(
 8                Files.readAllBytes(Path.of(path)));
 9        } catch (IOException e) {
10            System.err.println("Error: " + e.getMessage());
11            throw e;
12        } finally {
13            System.out.println("finally: always runs");
14        }
15    }
16    public static void main(String[] args) {
17        try {
18            String content = readFile("test.txt");
19            System.out.println(content);
20        } catch (IOException e) {
21            System.out.println("File not found: " + e.getMessage());
22        }
23    }
24}
Rust
 1use std::{fs, io};
 2fn read_file(path: &str)
 3    -> Result<String, io::Error> {
 4    // ? = 에러 즉시 상위로 전파
 5    let content = fs::read_to_string(path)?;
 6    Ok(content)
 7}
 8fn main() {
 9    // 파일이 없으면 Err, 있으면 Ok
10    match read_file("test.txt") {
11        Ok(c)  => println!("{}", c),
12        Err(e) => println!("파일 없음: {}", e),
13    }
14    // finally 대응: Drop (스코프 끝 자동 실행)
15}
☕ Java Rust설명
throws IOException-> Result<T, E>에러 가능성 시그니처 명시
catch(E e) { throw e; }? 연산자에러 즉시 상위 전파
finally { }Drop trait스코프 종료 시 자동 실행
unchecked (RuntimeException)panic!()복구 불가 오류

interface default 메서드 vs trait 기본 구현

☕ Java
 1public class Main {
 2    interface Drawable {
 3        String shape();           // 필수 구현
 4        default void draw() {    // 선택적 오버라이드
 5            System.out.println("Draw: " + shape());
 6        }
 7    }
 8    static class Circle implements Drawable {
 9        public String shape() { return "Circle"; }
10        // draw()는 default 사용
11    }
12    public static void main(String[] args) {
13        Drawable c = new Circle();
14        c.draw(); // 그리기: 원
15    }
16}
Rust
 1trait Drawable {
 2    fn shape(&self) -> &str;    // 필수 구현
 3    fn draw(&self) {              // 기본 구현
 4        println!("그리기: {}", self.shape());
 5    }
 6}
 7struct Circle;
 8impl Drawable for Circle {
 9    fn shape(&self) -> &str { "원" }
10    // draw()는 기본 구현 사용
11}
12fn main() {
13    let c = Circle;
14    c.draw(); // 그리기: 원
15}

null 처리 실전 패턴 — Optional vs Option

☕ Java
 1import java.util.Optional;
 2public class Main {
 3    static Optional<String> findUser(int id) {
 4        return id == 42
 5            ? Optional.of("Alice") : Optional.empty();
 6    }
 7    public static void main(String[] args) {
 8        Optional<String> opt = findUser(42);
 9        opt.orElse("unknown");        // default
10        opt.orElseGet(() -> "lazy");  // lazy default
11        opt.orElseThrow();             // throw if empty
12        opt.map(String::toUpperCase)
13           .filter(s -> s.length() > 3)
14           .ifPresent(System.out::println); // ALICE
15    }
16}
Rust
 1fn find_user(id: u32) -> Option<String> {
 2    if id == 42 {
 3        Some("Alice".to_string())
 4    } else { None }
 5}
 6fn main() {
 7    let opt = find_user(42);
 8    opt.as_deref()
 9        .unwrap_or("unknown");   // Alice
10    opt.map(|s| s.to_uppercase())
11       .filter(|s| s.len() > 3)
12       .map(|s| println!("{}", s));
13}
Java Optional Rust Option
Optional.of(v)Some(v)
Optional.empty()None
opt.orElse(v)opt.unwrap_or(v)
opt.orElseGet(f)opt.unwrap_or_else(f)
opt.map(f)opt.map(f)
opt.isPresent()opt.is_some()
opt.ifPresent(f)if let Some(v) = opt { }

instanceof 패턴 매칭 (Java 16+) vs if let · match

💡 Java 16+ Pattern Matching → Rust if let / match
Java 16+ instanceof Pattern은 타입 검사 + 캐스트를 한 번에 처리합니다.
Rust는 if let / match로 동일한 패턴을 더 강력하게 지원합니다.
☕ Java
 1public class Main {
 2    sealed interface Shape permits Circle, Rect {}
 3    record Circle(double r) implements Shape {}
 4    record Rect(double w, double h) implements Shape {}
 5    static double area(Shape s) {
 6        if (s instanceof Circle c)
 7            return Math.PI * c.r() * c.r();
 8        else if (s instanceof Rect rc)
 9            return rc.w() * rc.h();
10        throw new IllegalArgumentException();
11    }
12    public static void main(String[] args) {
13        System.out.println(area(new Circle(3.0)));
14        System.out.println(area(new Rect(4.0, 5.0)));
15    }
16}
Rust
 1enum Shape {
 2    Circle { r: f64 },
 3    Rect   { w: f64, h: f64 },
 4}
 5fn area(s: &Shape) -> f64 {
 6    // match: 완전성 강제, 타입 분해 동시에
 7    match s {
 8        Shape::Circle { r }    => std::f64::consts::PI * r * r,
 9        Shape::Rect   { w, h } => w * h,
10    }
11}
12fn main() {
13    let c = Shape::Circle { r: 3.0 };
14    let r = Shape::Rect { w: 4.0, h: 5.0 };
15    println!("{:.2}", area(&c)); // 28.27
16    println!("{:.2}", area(&r)); // 20.00
17}
18
Java (21+) Rust설명
instanceof Circle cif let Shape::Circle{r} = s타입 검사 + 분해
switch(s){ case Circle c -> }match s { Circle{r} => }완전성 강제
sealed interfaceenum닫힌 타입 계층
record (불변 데이터 클래스)struct + #[derive]데이터 보유 타입

trait · impl Trait for Type — interface + implements 대응

💡 읽는 순서 표의 핵심 — Java→Rust 1:1 대응
interface Soundtrait Sound  |  class Lion implements Soundimpl Sound for Lion
가장 큰 차이: Rust는 struct와 impl이 분리, 상속 없음, 외부 타입에도 trait 구현 가능.
☕ Java
 1public class Main {
 2    // 1. interface 정의
 3    interface Sound {
 4        String makeSound();
 5        default void loud() {
 6            System.out.println(makeSound().toUpperCase());
 7        }
 8    }
 9    // 2. class에서 implements
10    static class Lion implements Sound {
11        private String name;
12        public Lion(String n) { this.name = n; }
13        public String makeSound() { return "Roar"; }
14    }
15    // 3. 사용
16    public static void main(String[] args) {
17        Sound a = new Lion("Simba"); // 자동 업캐스팅
18        System.out.println(a.makeSound()); // Roar
19    }
20}
Rust
 1// 1. trait 정의
 2trait Sound {
 3    fn make_sound(&self) -> String;
 4    fn loud(&self) { // 기본 구현
 5        println!("{}", self.make_sound().to_uppercase());
 6    }
 7}
 8// 2. struct 선언 (데이터)
 9struct Lion { name: String }
10// 3. impl Trait for Type (계약 이행)
11impl Sound for Lion {
12    fn make_sound(&self) -> String {
13        format!("{}: Roar", self.name)
14    }
15}
16fn main() {
17    let a = Lion { name: "Simba".to_string() };
18    println!("{}", a.make_sound()); // Simba: Roar
19    a.loud(); // SIMBA: ROAR
20}

&dyn Trait · Vec<&dyn Trait> — 동적 디스패치 기본

💡 읽는 순서 표의 핵심 — Sound a = new Lion() → &dyn Sound
Java는 인터페이스 변수에 대입하면 자동으로 동적 디스패치.
Rust는 &dyn Sound 또는 Box<dyn Sound>명시해야 동적 디스패치.
☕ Java
 1import java.util.*;
 2public class Main {
 3    interface Sound { String makeSound(); }
 4    static class Lion implements Sound {
 5        String name; Lion(String n){name=n;}
 6        public String makeSound(){return name+": Roar~";}
 7    }
 8    static class Duck implements Sound {
 9        String name; Duck(String n){name=n;}
10        public String makeSound(){return name+": Quack!";}
11    }
12    public static void main(String[] args) {
13        // Java: 인터페이스 타입으로 쓰면 자동 동적 디스패치
14        Sound a = new Lion("Simba");  // 동적 디스패치
15        Sound b = new Duck("Donald"); // 동적 디스패치
16        System.out.println(a.makeSound()); // vtable 조회
17        // 이종 컬렉션
18        List<Sound> zoo = new ArrayList<>();
19        zoo.add(new Lion("Simba"));
20        zoo.add(new Duck("Donald"));
21        for (Sound s : zoo) System.out.println(s.makeSound());
22    }
23}
Rust
 1trait Sound { fn make_sound(&self) -> String; }
 2struct Lion { name: String }
 3struct Duck { name: String }
 4impl Sound for Lion {
 5    fn make_sound(&self) -> String {
 6        format!("{}: 으르렁~", self.name) }
 7}
 8impl Sound for Duck {
 9    fn make_sound(&self) -> String {
10        format!("{}: 꽥꽥!", self.name) }
11}
12fn main() {
13    // &dyn: 참조로 동적 디스패치 (fat pointer)
14    let lion = Lion { name: "Simba".to_string() };
15    let a: &dyn Sound = &lion; // 명시 필수!
16    a.make_sound(); // vtable 조회
17    // 이종 컬렉션 — Box<dyn> 소유
18    let zoo: Vec<Box<dyn Sound>> = vec![
19        Box::new(Lion { name: "Simba".to_string() }),
20        Box::new(Duck { name: "Donald".to_string() }),
21    ];
22    for s in &zoo {
23        println!("{}", s.make_sound());
24    }
25}
☕ Java Rust설명
Sound a = new Lion()let a: &dyn Sound = &lion동적 디스패치 — Rust는 명시 필수
List<Sound>Vec<&dyn Sound> / Vec<Box<dyn Sound>>이종 컬렉션
참조 8Bfat pointer 16B (data + vtable)Rust dyn은 포인터가 2배
자동dyn 키워드 명시Rust는 정적/동적 구분 명시적

제네릭 기본 — <T extends Sound> vs <T: Sound>

💡 읽는 순서 표의 핵심 — Java=타입소거(동적) vs Rust=단형화(정적)
Java <T extends Sound>: 컴파일 후 타입 소거 → 런타임에 Sound로 처리 (동적)
Rust <T: Sound>: 컴파일 시 타입별 전용 코드 생성 (단형화) → 런타임 오버헤드 없음
☕ Java
 1import java.util.*;
 2public class Main {
 3    interface Sound { String makeSound(); }
 4    static class Lion implements Sound {
 5        final String name; Lion(String n){name=n;}
 6        public String makeSound(){return name+": Roar~";}
 7    }
 8    static class Duck implements Sound {
 9        final String name; Duck(String n){name=n;}
10        public String makeSound(){return name+": Quack!";}
11    }
12    // <T extends Sound> — 컴파일 후 타입 소거
13    static <T extends Sound> void play(T animal) {
14        System.out.println(animal.makeSound());
15    }
16    // 호출 — 런타임에 vtable 조회
17    public static void main(String[] args) {
18        play(new Lion("Simba"));  // play(Sound) 1개만
19        play(new Duck("Donald")); // 같은 바이트코드
20    }
21}
Rust
 1trait Sound { fn make_sound(&self) -> String; }
 2struct Lion { name: String }
 3struct Duck { name: String }
 4impl Sound for Lion {
 5    fn make_sound(&self) -> String {
 6        format!("{}: 으르렁~", self.name)
 7    }
 8}
 9impl Sound for Duck {
10    fn make_sound(&self) -> String {
11        format!("{}: 꽥꽥!", self.name)
12    }
13}
14// <T: Sound> — 컴파일 시 타입별 코드 생성
15fn play<T: Sound>(animal: &T) {
16    println!("{}", animal.make_sound());
17}
18// 컴파일러 자동 생성: play__Lion, play__Duck ...
19fn main() {
20    let l = Lion { name: "Simba".to_string() };
21    let d = Duck { name: "Donald".to_string() };
22    play(&l); play(&d);
23}
항목☕ Java <T extends Sound> Rust <T: Sound>
컴파일 방식타입 소거 → Sound로 통일단형화 → 타입별 코드 복제
런타임 디스패치vtable 조회 (동적)직접 CALL (정적, 0 오버헤드)
여러 boundT extends A & BT: A + B 또는 where
바이너리 크기작음 (코드 1개)클 수 있음 (타입별 코드)

enum — Option · Result · 대수적 타입

출처: Rust Book §6 · std::option · std::result

💡 Java vs Rust enum 핵심 차이
Java enum: 상수 집합. 데이터를 담으려면 필드+생성자 필요 (타입 불안전).
Rust enum: 각 variant가 다른 타입의 데이터를 직접 담음 — 대수적 데이터 타입(ADT).
Option<T> = null의 안전한 대체  |  Result<T,E> = 예외 없는 에러 처리

enum 기본 — 데이터를 담는 열거형

☕ Java
 1public class Main {
 2    // Java enum: 상수 + 데이터 (필드+생성자 필요)
 3    enum Shape {
 4        CIRCLE, RECT, TRIANGLE;
 5        public boolean hasCorners() {
 6            return this != CIRCLE;
 7        }
 8    }
 9    // Java: 데이터를 담으려면 필드+생성자 필요
10    enum Option2 {
11        SOME, NONE;
12        Object value;  // 타입 불안전!
13    }
14    public static void main(String[] args) {
15        Shape s = Shape.CIRCLE;
16        String msg = switch (s) {
17            case CIRCLE   -> "Circle";
18            case RECT     -> "Rect";
19            case TRIANGLE -> "Triangle";
20        };
21        System.out.println(msg);
22    }
23}
Rust
 1enum Shape {
 2    Circle { radius: f64 },
 3    Rect   { w: f64, h: f64 },
 4    Triangle(f64, f64, f64),
 5    Point,
 6}
 7fn area(s: &Shape) -> f64 {
 8    match s {
 9        Shape::Circle { radius } =>
10            std::f64::consts::PI * radius * radius,
11        Shape::Rect { w, h } => w * h,
12        Shape::Triangle(a, b, c) => {
13            let s = (a + b + c) / 2.0;
14            (s*(s-a)*(s-b)*(s-c)).sqrt()
15        }
16        Shape::Point => 0.0,
17    }
18}
19fn main() {
20    let shapes = vec![
21        Shape::Circle { radius: 3.0 },
22        Shape::Rect { w: 4.0, h: 5.0 },
23        Shape::Triangle(3.0, 4.0, 5.0),
24    ];
25    for s in &shapes {
26        println!("{:.2}", area(s));
27    }
28}
항목☕ Java enum Rust enum
데이터 담기필드+생성자 필요variant에 직접 담음
variant 종류동일한 타입만각각 다른 타입 가능
패턴 매칭switch (Java 21+)match (완전성 강제)
대표 enum없음Option<T>, Result<T,E>

Option<T> — null의 안전한 대체

💡 Option<T> 구조
enum Option<T> { Some(T), None }
값이 있을 수도 없을 수도 있을 때 사용. Java의 null을 컴파일타임에 강제 처리.
☕ Java
 1import java.util.Optional;
 2public class Main {
 3    // Java: null 또는 Optional — 강제 아님
 4    static Optional<Double> divide(double a, double b) {
 5        return b == 0 ? Optional.empty()
 6                       : Optional.of(a / b);
 7    }
 8    public static void main(String[] args) {
 9        divide(10, 3)
10            .ifPresentOrElse(
11                v -> System.out.println(String.format("%.2f", v)),
12                () -> System.out.println("Division error"));
13        // Java: null 직접 반환도 가능 → NullPointerException 위험
14        Double unsafe = null; // 컴파일 OK, 런타임 NPE 위험
15    }
16}
Rust
 1// Option: null 없는 Rust의 핵심
 2// enum Option {{ Some(T), None }}
 3fn divide(a: f64, b: f64) -> Option<f64> {
 4    if b == 0.0 { None } else { Some(a / b) }
 5}
 6fn main() {
 7    // 안전한 나눗셈
 8    match divide(10.0, 3.0) {
 9        Some(v) => println!("결과: {:.2}", v),
10        None    => println!("나누기 불가"),
11    }
12    // ? 연산자로 체이닝
13    let result = divide(10.0, 2.0)
14        .map(|v| v * 2.0)
15        .filter(|&v| v > 5.0)
16        .unwrap_or(0.0);
17    println!("체이닝: {}", result); // 10
18}

Result<T, E> — 예외 없는 에러 처리

💡 Result<T,E> 구조
enum Result<T,E> { Ok(T), Err(E) }
에러를 반환값으로 표현. 컴파일러가 처리를 강제 — 무시하면 컴파일 경고/에러.
☕ Java
 1import java.io.*;
 2public class Main {
 3    // Java: checked exception으로 에러 표현
 4    static int parse(String s) throws NumberFormatException {
 5        return Integer.parseInt(s);
 6    }
 7    public static void main(String[] args) {
 8        try {
 9            int n = parse("42");
10            System.out.println(n * 2); // 84
11        } catch (NumberFormatException e) {
12            System.err.println("Error: " + e.getMessage());
13        }
14        // Java: 예외 무시 가능 → 런타임 폭발
15        int risky = Integer.parseInt("abc");
16    }
17}
Rust
 1// Result: 에러를 타입으로 표현
 2// enum Result {{ Ok(T), Err(E) }}
 3fn parse_double(s: &str) -> Result<i32, String> {
 4    s.parse::<i32>()
 5        .map_err(|e| format!("파싱 실패: {}", e))
 6        .map(|n| n * 2)
 7}
 8fn main() {
 9    // match로 명시적 처리 — 무시 불가
10    match parse_double("42") {
11        Ok(n)  => println!("결과: {}", n), // 84
12        Err(e) => eprintln!("에러: {}", e),
13    }
14    // ? 연산자: Err이면 즉시 반환
15    let n = parse_double("abc").unwrap_or(0);
16    println!("기본값: {}", n); // 0
17}
항목☕ Java (throws) Rust (Result)
에러 표현throws Exception반환 타입 Result<T,E>
에러 무시가능 → 런타임 폭발컴파일 경고/에러
에러 전파throws 재선언? 한 글자
finally 대응finally { }Drop (스코프 끝)

enum 상세 — BookStatus 예제 · Java switch 비교

💡 핵심 차이
Java enum: 상수 집합. 데이터 담으려면 필드+생성자 별도 필요 (타입 불안전)
Rust enum: 각 variant가 서로 다른 타입의 데이터를 직접 담음 (대수적 데이터 타입, ADT)
☕ Java
 1public class Main {
 2    enum BookStatus { AVAILABLE, BORROWED, LOST;
 3        public boolean isAvailable() { return this == AVAILABLE; }
 4    }
 5    public static void main(String[] args) {
 6        BookStatus s = BookStatus.AVAILABLE;
 7        System.out.println(s.isAvailable()); // true
 8        String msg = switch (s) {
 9            case AVAILABLE -> "Available";
10            case BORROWED  -> "Borrowed";
11            case LOST      -> "Lost";
12        };
13        System.out.println(msg);
14    }
15}
Rust
 1enum BookStatus {
 2    Available,
 3    Borrowed { due_date: String },
 4    Lost(String),
 5}
 6
 7fn main() {
 8    let s = BookStatus::Borrowed {
 9        due_date: "2025-01-01".to_string(),
10    };
11    match s {
12        BookStatus::Available => {
13            println!("대출 가능");
14        }
15        BookStatus::Borrowed { due_date } => {
16            println!("반납일: {}", due_date);
17        }
18        BookStatus::Lost(reason) => {
19            println!("분실: {}", reason);
20        }
21    }
22}
23
처리 방법Java Optional Rust Option
패턴 매칭없음match Some(v) / None
간결한 처리없음if let Some(v) = opt
값 추출get().unwrap()
기본값orElse(val).unwrap_or(val)
변환map(fn).map(|v|..)
체이닝flatMap(fn).and_then(|v|..)
조건 필터없음.filter(|v|..)
? 연산자없음? (None → 즉시 반환)
기본 타입값없음.unwrap_or_default()

Option 상세 — 6가지 처리 패턴 · Java Optional 전체 비교

💡 enum Option<T> { Some(T), None }
Java null은 런타임에야 NPE 발생. Rust Option<T>컴파일타임에 처리 강제.
무시하면 컴파일 에러 → NPE 원천 차단.
☕ Java
 1import java.util.Optional;
 2class Main {
 3    static class Book {
 4        String title; Book(String t) { title = t; }
 5    }
 6    static Optional<Book> findBook(String t) {
 7        return "Clean Code".equals(t)
 8            ? Optional.of(new Book(t))
 9            : Optional.empty();
10    }
11    public static void main(String[] args) {
12        findBook("Clean Code").ifPresent(
              b -> System.out.println(b.title));
13        Book b = findBook("Unknown").orElseGet(
              () -> new Book("Default"));
14        System.out.println(b.title);
15        String t = findBook("Unknown").map(
              bk -> bk.title).orElse("none");
16        System.out.println(t);
17        System.out.println(
              findBook("Clean Code").isPresent());
18        System.out.println(
              findBook("Unknown").isEmpty());
19    }
20}
Rust
 1struct Book {
 2    title: String,
 3}
 4impl Book {
 5    fn new(t: &str) -> Self {
 6        Book { title: t.to_string() }
 7    }
 8}
 9
10fn find(t: &str) -> Option<Book> {
11    if t == "Clean Code" {
12        Some(Book::new(t))
13    } else {
14        None
15    }
16}
17
18fn main() {
19    // 1. match
20    match find("Clean Code") {
21        Some(b) => println!("match: {}", b.title),
22        None    => println!("없음"),
23    }
24
25    // 2. if let
26    if let Some(b) = find("Clean Code") {
27        println!("if let: {}", b.title);
28    }
29
30    // 3. unwrap (None → panic)
31    let b = find("Clean Code").unwrap();
32    println!("unwrap: {}", b.title);
33
34    // 4. unwrap_or_else
35    let title = find("없는책")
36        .map(|b| b.title)
37        .unwrap_or_else(|| "기본값".to_string());
38    println!("unwrap_or: {}", title);
39
40    // 5. map
41    let len: Option<usize> = find("Clean Code")
42        .map(|b| b.title.len());
43    println!("map: {:?}", len); // Some(10)
44
45    // 6. is_some / is_none
46    println!("{}", find("Clean Code").is_some());
47    println!("{}", find("없는책").is_none());
48}
49

Option 처리 방법 9가지

Rust
 1fn find(t: &str) -> Option<String> {
 2    if t == "Clean Code" {
 3        Some(t.to_string())
 4    } else {
 5        None
 6    }
 7}
 8
 9fn main() {
10    // 1. unwrap_or — 기본값 직접 지정
11    let t = find("없는책")
12        .unwrap_or("기본값".to_string());
13    println!("unwrap_or: {}", t);
14
15    // 2. map_or — map + 기본값 한번에
16    let len = find("Clean Code")
17        .map_or(0, |s| s.len());
18    println!("map_or: {}", len); // 10
19
20    // 3. map_or_else — 기본값도 클로저로
21    let msg = find("없는책")
22        .map_or_else(
23            || "없음".to_string(),
24            |s| format!("찾음: {}", s),
25        );
26    println!("map_or_else: {}", msg);
27
28    // 4. and_then — Option 체이닝
29    let upper = find("Clean Code")
30        .and_then(|s| {
31            if s.len() > 5 {
32                Some(s.to_uppercase())
33            } else { None }
34        });
35    println!("and_then: {:?}", upper);
36
37    // 5. or_else — None일 때 대체 Option
38    let fallback = find("없는책")
39        .or_else(|| find("Clean Code"));
40    println!("or_else: {:?}", fallback);
41
42    // 6. filter — 조건 불만족 시 None
43    let filtered = find("Clean Code")
44        .filter(|s| s.starts_with("Clean"));
45    println!("filter: {:?}", filtered);
46
47    // 7. ? 연산자 — None이면 즉시 None 반환
48    fn get_upper(t: &str) -> Option<String> {
49        let s = find(t)?;
50        Some(s.to_uppercase())
51    }
52    // Some("CLEAN CODE") / None
53    println!("{:?}", get_upper("Clean Code"));
54    println!("{:?}", get_upper("없는책"));
55
56    // 8. as_ref — 소유권 유지하며 참조
57    let opt: Option<String> = find("Clean Code");
58    let opt_ref: Option<&String> = opt.as_ref();
59    println!("as_ref: {:?}", opt_ref);
60    println!("원본: {:?}", opt); // 소유권 유지
61
62    // 9. take — 값 꺼내고 None으로
63    let mut opt2 = find("Clean Code");
64    let taken = opt2.take();
65    println!("take: {:?}", taken);
66    println!("after: {:?}", opt2); // None
67}

Result 상세 — 커스텀 에러 · ? 연산자 · 체이닝

💡 enum Result<T,E> { Ok(T), Err(E) }
Java 예외: throws 선언 무시 가능, unchecked는 전파 안 됨.
Rust Result: 반환값으로 에러 표현, 무시하면 컴파일 경고/에러, ?로 즉시 전파.
☕ Java
 1import java.io.*;
 2import java.nio.file.*;
 3public class Main {
 4    static String readFile(String path)
 5            throws IOException {
 6        return Files.readString(Path.of(path));
 7    }
 8    static int divide(int a, int b) {
 9        if (b == 0)
10            throw new ArithmeticException("Division by zero");
11        return a / b;
12    }
13    public static void main(String[] args) {
14        try {
15            String s = readFile("book.txt");
16            System.out.println(s);
17        } catch (IOException e) {
18            System.err.println("Error: " + e.getMessage());
19        } finally {
20            System.out.println("always runs");
21        }
22    }
23}
Rust
 1use std::num::ParseIntError;
 2use std::fmt;
 3
 4// 커스텀 에러 타입
 5#[derive(Debug)]
 6enum AppError {
 7    Parse(ParseIntError),
 8    NegativeNumber(i32),
 9}
10impl fmt::Display for AppError {
11    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
12        match self {
13            AppError::Parse(e)  =>
                  write!(f, "Parse error: {}", e),
14            AppError::NegativeNumber(n) =>
15                write!(f, "Negative: {}", n),
16        }
17    }
18}
19impl From<ParseIntError> for AppError {
20    fn from(e: ParseIntError) -> Self { AppError::Parse(e) }
21}
22
23// ? 연산자 — Err이면 즉시 반환
24fn parse_positive(s: &str) -> Result<u32, AppError> {
25    let n: i32 = s.parse()?; // ParseIntError → AppError::Parse
26    if n < 0 {
27        return Err(AppError::NegativeNumber(n));
28    }
29    Ok(n as u32)
30}
31
32fn main() {
33    // match로 명시적 처리
34    match parse_positive("42") {
35        Ok(n)  => println!("OK: {}", n),
36        Err(e) => println!("Error: {}", e),
37    }
38
39    // unwrap_or_else
40    let n = parse_positive("abc")
41        .unwrap_or_else(|e| {
42            println!("Recover: {}", e);
43            0u32
44        });
45    println!("Recovered: {}", n);
46
47    // map + and_then 체이닝
48    let doubled = parse_positive("5")
49        .map(|n| n * 2);
50    println!("doubled: {:?}", doubled); // Ok(10)
51
52    // 에러 변환
53    let result = parse_positive("-3");
54    println!("{:?}", result); // Err(NegativeNumber(-3))
55    println!("is_ok: {}", result.is_ok());
56    println!("is_err: {}", result.is_err());
57}
58
항목☕ Java Rust
에러 표현throws Exception반환 타입 Result<T,E>
에러 무시가능 (런타임 오류)컴파일 에러
에러 전파throws 재선언? 한 글자
커스텀 에러extends Exceptionenum MyError

타입 시스템 — 추론 · 변환 · 숫자 · String · match

출처: Rust Book §3.2 · Rust Reference — Type System

타입 추론 · turbofish — Java var 비교

💡 Java var vs Rust let 결정적 차이
Java var: 지역변수만, 필드·매개변수 불가, 우측 값에서만 추론
Rust let: 클로저 인수·제네릭 포함 전방위 추론, 지연 추론 가능
turbofish ::<>: 추론 불가할 때 "parse::<i32>()" 처럼 타입 명시
☕ Java
 1public class Main {
 2    public static void main(String[] args) {
 3        // Java 10+: var — 지역변수만, 필드/매개변수 불가
 4        var x = 42;                  // int 추론
 5        var list = new java.util.ArrayList<String>();
 6        // Java: 타입별 메서드 이름으로 해결
 7        int n = Integer.parseInt("42");
 8        double d = Double.parseDouble("3.14");
 9        // Java에는 turbofish 없음 — 제네릭 메서드 명시
10        java.util.Collections.<String>emptyList();
11        System.out.println(n + " " + d);
12    }
13}
Rust
 1use std::collections::HashMap;
 2fn main() {
 3    // 기본 추론 — 컴파일러가 타입 결정
 4    let x = 42;           // i32 기본
 5    let x = 42u8;         // suffix로 명시
 6    let x: u8 = 42;      // 어노테이션으로 명시
 7    // _ placeholder — 일부만 명시
 8    let v: Vec<_> = (0..).take(5)
 9        .map(|n| n * 2).collect();
10    // turbofish ::<> — 추론 불가할 때 타입 명시
11    let n = "42".parse::<i32>().unwrap();
12    let v = (0..).take(5).collect::<Vec<i32>>();
13    let sz = std::mem::size_of::<String>(); // 반드시 turbofish
14    // 지연 추론 — 나중에 확정
15    let mut map = HashMap::new(); // 타입 미확정
16    map.insert("key", 42i32); // HashMap<&str,i32> 확정
17    println!("{} {} {:?}", n, sz, v);
18}
상황☕ Java Rust
지역 변수 추론var x = 42let x = 42
필드/매개변수불가 (타입 명시 필수)불가 (타입 명시 필수)
부분 명시없음Vec<_> — _ 자리만 추론
turbofishCollections.<String>emptyList() (드묾)parse::<i32>() (자주 사용)
지연 추론없음나중 사용 시점에서 확정

타입 변환 — as · From · Into · TryFrom

💡 3가지 변환 방식
as: Java (int)x 대응 — 항상 성공, 데이터 손실 가능
From/Into: 안전한 변환 (실패 없음) — String::from(), .into()
TryFrom/TryInto: 실패 가능 변환 — Result 반환
☕ Java
 1public class Main {
 2    public static void main(String[] args) {
 3        // Java: 명시적 캐스팅 (데이터 손실 가능)
 4        double d = 3.99;
 5        int n = (int) d;      // 3 (소수점 버림)
 6        long l = n;            // 암묵적 확장
 7        // 문자열 변환
 8        String s = String.valueOf(42);
 9        int i = Integer.parseInt("42");
10        // 실패 가능 변환
11        try {
12            int x = Integer.parseInt("abc");
13        } catch (NumberFormatException e) {
14            System.out.println("failed");
15        }
16        System.out.println(n + " " + s + " " + i);
17    }
18}
Rust
 1use std::convert::TryFrom;
 2fn main() -> Result<(), Box<dyn std::error::Error>> {
 3    // as — 명시적 캐스팅 (항상 성공, 손실 가능)
 4    let d: f64 = 3.99;
 5    let n = d as i32;          // 3 (소수점 버림)
 6    let l = n as i64;          // 확장
 7    let b = 200u32 as u8;  // 200 (wrap)
 8    // From / Into — 안전한 변환
 9    let _s = String::from("hello");
10    let _s: String = "hello".into();
11    let _n = i64::from(42i32);
12    // TryFrom — 실패 가능 변환
13    let ok  = i8::try_from(100i32);
14    let err = i8::try_from(300i32);
15    // 문자열 변환
16    let _s = 42.to_string();
17    let m: i32 = "42".parse()?;
18    println!("{} {} {} {:?} {:?}", n, l, b, ok, err);
19    println!("parse: {}", m);
20    Ok(())
21}
방식☕ Java Rust실패 시
명시적 캐스트(int)xx as i32wrap/truncate
안전 변환없음From/Into컴파일 에러
실패 가능Integer.parseInt() + catchTryFrom/TryIntoErr(e)
문자열→수Integer.parseInt(s)s.parse::<i32>()각각 예외/Err
수→문자열String.valueOf(n)n.to_string()항상 성공

숫자 타입 — i8~i128 · u8~u128 · f32/f64 · 박싱 없음

💡 Java 대비 핵심 차이
Java: int/Integer 박싱 구분, 오버플로 조용히 wrap
Rust: 박싱 없음 (항상 값 타입), 오버플로 debug=panic / release=wrap
suffix: 42u8, 3.14f32 — Java의 L, f보다 더 세분화
☕ Java
 1public class Main {
 2    public static void main(String[] args) {
 3        // Java: 원시 타입 8개 + 박싱
 4        byte   b = 127;       // 8bit
 5        short  s = 32767;     // 16bit
 6        int    i = 2_147_483_647; // 32bit
 7        long   l = 9_999_999_999L; // 64bit L필수
 8        float  f = 3.14f;     // 32bit f필수
 9        double d = 3.14;      // 64bit 기본
10        // Java: int/Integer 박싱 구분
11        Integer boxed = 42; // 자동 박싱 → 힙
12        int unboxed = boxed;  // 자동 언박싱
13        // 오버플로: 조용히 wrap
14        int overflow = Integer.MAX_VALUE + 1; // -2147483648
15        System.out.println(overflow);
16    }
17}
Rust
 1fn main() {
 2    // Rust: 부호있는 정수 i8~i128, 없는 u8~u128
 3    let b: i8   = 127;         // 8bit  -128..127
 4    let s: i16  = 32_767;      // 16bit
 5    let i: i32  = 2_147_483_647; // 32bit 기본
 6    let l: i64  = 9_999_999_999; // 64bit
 7    let u: usize = 42;         // 포인터 크기
 8    let f: f32  = 3.14f32;     // suffix 필수
 9    let d: f64  = 3.14;        // 64bit 기본
10    // 박싱 없음 — int/Integer 구분 없음
11    let x: i32 = 42;           // 항상 스택값
12    // 오버플로: debug=panic, release=wrap
13    // 명시적 처리
14    let wrapped = i32::MAX.wrapping_add(1); // -2147483648
15    let checked = i32::MAX.checked_add(1); // None
16    println!("{} {:?}", wrapped, checked);
17}
☕ Java Rust크기범위
bytei88bit-128 ~ 127
shorti1616bit-32768 ~ 32767
inti3232bit±2억
longi6464bit±9경
없음u8/u16/u32/u648~64bit부호 없음
없음usize/isize포인터 크기인덱스에 사용
floatf3232bitIEEE 754
doublef6464bitIEEE 754 (기본)
int/Integer 구분없음 (항상 값)박싱 오버헤드 없음

String vs &str

☕ Java
 1class Main {
 2    public static void main(String[] args) {
 3        // Java: String은 힙, 불변
 4        String title = "Clean Code";
 5        System.out.println(title.length()); // 10
 6        // 가변 문자열
 7        StringBuilder sb = new StringBuilder("Clean");
 8        sb.append(" Code");
 9        System.out.println(sb);       // Clean Code
10        System.out.println(sb.length()); // 10
11    }
12}
Rust
1fn main() {
2    let slice: &str = "Clean Code";
3    let mut owned: String = String::from("Clean");
4    owned.push_str(" Code");
5    let r: &str = &owned;
6    println!("{} {} {}", slice, owned, r);
7}
8
Java String Rust String Rust &str
위치힙 (항상)힙 (ptr+len+cap)어디서든 (참조)
소유권GC 관리소유 (Drop)없음 (빌림)
크기참조 8B24B16B (ptr+len)

match — 패턴 7가지

☕ Java
 1// Java switch — default 없어도 컴파일됨
 2int pages = 450;
 3switch (pages / 100) {
 4    case 0: System.out.println("Thin");   break;
 5    case 1:
 6    case 2: System.out.println("Normal");   break;
 7    case 3:
 8    case 4: System.out.println("Thick"); break;
 9    // default 없음 → 컴파일 OK, 버그 위험!
10}
11// Java 14+ switch expression
12String size = switch (pages / 100) {
13    case 0     -> "Thin";
14    case 1, 2  -> "Normal";
15    case 3, 4  -> "Thick";
16    default    -> "Very Thick"; // default 필수
17};
18System.out.println(size); // 두꺼움
19// OR 패턴
20String day = "Sat";
21String kind = switch (day) {
22    case "Sat", "Sun" -> "Weekend";
23    default           -> "Weekday";
24};
25// if (null check) — Rust if let Some에 대응
26String title = "Clean Code"; // 직접 값 사용
27if (title != null) {
28    System.out.println("Found: " + title);
29}
Rust
 1fn main() {
 2    let pages: u32 = 450;
 3    let size = match pages {
 4        0..=100   => "얇음",
 5        101..=300 => "보통",
 6        301..=500 => "두꺼움",
 7        _         => "매우 두꺼움",
 8    };
 9    println!("{}", size);
10    let grade = match 85u32 {
11        90..=100 => "A",
12        80..=89  => "B",
13        70..=79  => "C",
14        _        => "F",
15    };
16    println!("{}", grade);
17    enum Msg { Move { x: i32, y: i32 }, Write(String), Quit }
18    let msg = Msg::Move { x: 10, y: 20 };
19    match msg {
20        Msg::Move { x, y } => println!("Move: ({}, {})", x, y),
21        Msg::Write(t) => println!("Write: {}", t),
22        Msg::Quit  => println!("Quit"),
23    }
24    match 7i32 {
25        x if x < 0       => println!("neg: {}", x),
26        x if x == 0      => println!("zero"),
27        x if x % 2 == 0 => println!("even: {}", x),
28        x               => println!("odd: {}", x),
29    }
30    let kind = match "Sat" {
31        "Sat" | "Sun" => "Weekend",
32        _ => "Weekday",
33    };
34    println!("{}", kind);
35    match (0, -2) {
36        (0, 0)         => println!("origin"),
37        (x, 0) | (0, x) => println!("axis: {}", x),
38        (x, y)         => println!("({}, {})", x, y),
39    }
40    if let Some(v) = Some(42) {
41        println!("val: {}", v);
42    }
43    let mut stack = vec![1i32, 2, 3];
44    while let Some(top) = stack.pop() {
45        println!("pop: {}", top);
46    }
47}
패턴 Rust☕ Java차이
범위0..=100 =>없음Rust만
완전성컴파일 에러없음Rust가 안전
값 반환항상Java 14+만Rust는 표현식
if 가드x if x>0없음Rust만
튜플 매칭(0,y)없음Rust만
if letif let Some(v)=opt없음Rust만
while letwhile let Some(v)=opt없음Rust만

소유권 · 빌림 · 라이프타임

출처: Rust Book §4Rust Reference — Memory

💡 핵심 규칙
값은 하나의 소유자. 스코프 끝 → 자동 Drop. & 빌림은 소유권 없이 접근.
출처: Rust Book §4

소유권 이동 vs 참조 복사

Java — GC 방식 a (ref) b (ref) Book 객체 "Clean Code" GC가 관리 a → OK ✓ b → OK ✓ 둘 다 같은 객체 참조 가능 Rust — 소유권 이동 (move) a: Book b: Book move → a → ✗ 무효! b → OK ✓ error[E0382]: use of moved value: `a` 소유권은 하나뿐 — 이동 후 원본 무효 b 스코프 종료 → 즉시 Drop (GC 없음)
☕ Java
 1public class Main {
 2    static class Book {
 3        String title;
 4        Book(String t) { title = t; }
 5        Book deepCopy() { return new Book(title); }
 6    }
 7    public static void main(String[] args) {
 8        Book a = new Book("Clean Code");
 9        Book b = a;  // 참조 복사 — 같은 객체!
10        b.title = "Modified";
11        // Rust: let b = a; → a 무효 (E0382)
12        System.out.println(a.title); // "Modified"
13
14        // 깊은 복사
15        Book c = a.deepCopy();
16        c.title = "Another";
17        System.out.println(a.title); // "Modified"
18    }
19}
Rust
 1struct Book { title: String }
 2impl Book {
 3    fn new(t: &str) -> Self {
 4        Book { title: t.to_string() }
 5    }
 6}
 7
 8// 불변 빌림 — &Book
 9fn read(book: &Book) {
10    println!("{}", book.title);
11    // book.title = "X"; // 컴파일 에러! 수정 불가
12}
13
14// 가변 빌림 — &mut Book
15fn stamp(book: &mut Book) {
16    book.title.push_str(" [도장]");
17}
18
19fn main() {
20    // ── 소유권 이동 ────────────────────────────
21    let a = Book::new("Clean Code");
22    let b = a; // 소유권 이동 (move)
23    // println!("{}", a.title); // E0382: 이미 이동됨!
24    println!("{}", b.title); // OK
25
26    // ── 불변 빌림 ─────────────────────────────
27    let c = Book::new("Refactoring");
28    read(&c); // &c = c를 빌림
29    read(&c); // OK — 여러 번 빌릴 수 있음
30    println!("{}", c.title); // c는 여전히 유효
31
32    // ── 가변 빌림 ─────────────────────────────
33    let mut d = Book::new("SICP");
34    stamp(&mut d);
35    println!("{}", d.title); // "SICP [도장]"
36    // &mut 빌림 중에는 다른 빌림 불가 (안전 보장)
37}
38

스코프 종료 시 자동 Drop

스코프 종료 시 자동 Drop블록 종료 시 Dropfn main() {{ ← 블록 시작 let book = Book::new(..); println!("{}", book.title);} ← book Drop!// book 없음 → 에러!}함수 종료 시 Dropfn read_book(book: Book) { println!("{}", book.title);} ← 함수 끝. book Drop!fn main() { let b = Book::new(..); read_book(b);힙의 title(String) 해제 → book 메모리 해제
✅ GC 없이 안전한 해제
컴파일타임에 Drop 위치가 확정됩니다. GC pause 없음 · use-after-free 없음 · double-free 없음.
출처: std::ops::Drop

빌림 규칙

빌림(&) — 소유권은 원본에 유지, 읽기만 가능 스택 b: Book (소유자) title.ptr | len | cap "Clean Code" read(&b) 함수 호출 book: &Book (빌림) b의 주소를 가리킴 (8B 포인터) 빌림 함수 종료 → 빌림 반환 b는 여전히 유효 ✓ 여러 번 빌릴 수 있음 ✓ b 여전히 유효 ✓ 소유권 그대로
규칙 1&T 불변 빌림 — 동시에 여럿 가능
규칙 2&mut T 가변 빌림 — 동시에 1개만
규칙 3&T&mut T는 동시 불가

라이프타임

라이프타임 — 참조는 원본보다 오래 살 수 없다 Java — GC가 살려둠 (OK) Book reference = null; Book book = new Book(..); reference = book; } // GC가 reference 때문에 살려둠 reference.title → OK ✓ GC가 나중에 언젠가 해제 Rust — 컴파일 에러로 차단 (안전!) let reference: &Book; let book = Book::new(..); reference = &book; } // book Drop! (book 먼저 사라짐) E0597: `book` does not live long enough 컴파일 에러 → 런타임 NPE 원천 차단

Clone · Copy

☕ Java
 1public class Main {
 2    static class Book implements Cloneable {
 3        String title;
 4        int pages;
 5
 6        Book(String t, int p) {
 7            title = t;
 8            pages = p;
 9        }
10        @Override
11        public Book clone() {
12            try {
13                return (Book) super.clone();
14            } catch (CloneNotSupportedException e) {
15                throw new RuntimeException(e);
16            }
17        }
18        @Override
19        public String toString() {
20            return title + "(" + pages + "p)";
21        }
22    }
23    public static void main(String[] args) {
24        Book b1 = new Book("Clean Code", 431);
25        Book b2 = b1;         // 참조 복사 → 같은 객체
26        Book b3 = b1.clone(); // 깊은 복사 → 새 객체
27        b2.title = "Modified";
28        // b1, b2 같은 객체
29        System.out.println(b1.title); // "Modified"
30        // b3는 별도 객체
31        System.out.println(b3.title); // "Clean Code"
32        // Rust: #[derive(Clone)] 한 줄로 해결
33    }
34}
35
Rust
 1#[derive(Debug, Clone)]
 2struct Book {
 3    title: String,
 4    pages: u32,
 5}
 6
 7#[derive(Debug, Clone, Copy)]
 8struct Point {
 9    x: i32,
10    y: i32,
11}
12
13fn main() {
14    // Clone — 명시적 깊은 복사
15    let b1 = Book {
16        title: "Clean Code".to_string(),
17        pages: 431,
18    };
19    let b2 = b1.clone(); // 힙 데이터 복사
20    println!("{:?}", b1); // b1 여전히 유효
21    println!("{:?}", b2);
22
23    // Copy — 자동 비트 복사
24    let p1 = Point { x: 1, y: 2 };
25    let p2 = p1; // Copy → p1도 유효!
26    println!("{:?}", p1); // OK
27    println!("{:?}", p2);
28
29    // String은 Copy 불가 (힙 소유)
30    // let s1 = String::from("hello");
31    // let s2 = s1; // move → s1 무효
32}
33
항목☕ Java Rust
변수 기본가변불변 (mut 명시)
함수 전달참조 복사소유권 이동 or 빌림
동시 수정제한 없음가변 빌림 1개만
메모리 해제GC (런타임)Drop (컴파일타임)
nullNPE 가능null 없음 — Option<T>
힙 할당new 항상 힙Box::new() 명시 시만

가변 빌림 &mut — Java에 없는 명시적 구분

불변 빌림(&T) vs 가변 빌림(&mut T) vs 소유권 이동 &T — 불변 빌림 원본 data ref1 ref2 ref3 동시에 여럿 OK ✅ 읽기만 가능 &mut T — 가변 빌림 원본 data mut_ref (1개) 동시에 1개만 ⚠️ 읽기 + 쓰기 가능 &T와 동시 불가 ❌ move — 소유권 이동 원본 ❌ 새 소유자 원본 사용 불가 소유권 완전 이전
☕ Java
 1import java.util.*;
 2public class Main {
 3    // Java: 참조로 전달하면 내부 수정 가능
 4    // 불변/가변 구분 없음 (final은 재할당만 막음)
 5    static void addItem(List<String> list) {
 6        list.add("new item"); // 수정 OK — 컴파일러 무관
 7    }
 8    static void readOnly(final List<String> list) {
 9        list.add("item"); // final이어도 내부 수정 가능!
10        // list = new ArrayList<>(); // 이것만 에러
11    }
12    public static void main(String[] args) {
13        List<String> v = new ArrayList<>();
14        v.add("a");
15        addItem(v);
16        System.out.println(v); // [a, new item]
17    }
18}
Rust
 1// &mut: 수정 허용, &: 읽기만
 2fn add_item(list: &mut Vec<String>) {
 3    list.push("new item".to_string());
 4}
 5fn read_only(list: &Vec<String>) {
 6    // list.push(...); // 컴파일 에러! &는 수정 불가
 7    println!("{:?}", list);
 8}
 9fn main() {
10    let mut v = vec!["a".to_string()];
11    add_item(&mut v); // &mut 명시 필요
12    read_only(&v);    // &만으로 읽기
13}
🚨 빌림 규칙 위반 — 컴파일 에러 예시
 1fn main() {
 2    let mut s = String::from("hello");
 3    let r1 = &s;          // 불변 빌림 1
 4    let r2 = &s;          // 불변 빌림 2 — OK
 5    let r3 = &mut s;      // ❌ error[E0502]: &와 &mut 동시 불가
 6    println!("{} {} {}", r1, r2, r3);
 7}
 8// error[E0502]: cannot borrow `s` as mutable
 9// because it is also borrowed as immutable

Box<T> · Rc · Arc — Java new = 항상 힙, Rust = 기본 스택

출처: Rust Book §15.1Rust Book §15.4JVM Spec §2.5

💡 핵심
Java: new는 항상 힙. 언어 차원에서 스택 배치 불가.
Rust: struct는 기본 스택. Box::new()로 명시해야만 힙.
출처: JVM Spec §2.5 · Rust Book §15.1
☕ Java
 1public class Main {
 2    static class Book {
 3        String title;
 4        int pages;
 5        Book(String t, int p) {
 6            title = t;
 7            pages = p;
 8        }
 9        @Override
10        public String toString() {
11            return "'" + title
12                + "' (" + pages + "p)";
13        }
14    }
15    public static void main(String[] args) {
16        // 기본 타입 → 스택 (값 직접 저장)
17        int     x = 42;    // 스택: 4B
18        boolean b = true;  // 스택: 1B
19
20        // 객체 → 항상 힙 (언어 차원 선택 불가)
21        Book book = new Book("Clean Code", 431);
22        // Object Header(12~16B)
23        // + title(ref 8B)
24        // + pages(int 4B)
25
26        System.out.println("Stack: " + x + ", " + b);
27        System.out.println("Heap(always): " + book);
28        // Rust: struct Book → 스택 (기본)
29        // Rust: Box::new(Book{..}) → 힙 (명시)
30    }
31}
32
Rust
 1#[derive(Debug)]
 2struct Book {
 3    title: String,
 4    pages: u32,
 5}
 6impl Book {
 7    fn new(t: &str, p: u32) -> Self {
 8        Book {
 9            title: t.to_string(),
10            pages: p,
11        }
12    }
13}
14
15trait Printable {
16    fn print_info(&self);
17}
18impl Printable for Book {
19    fn print_info(&self) {
20        println!(
21            "'{}' ({}p)",
22            self.title,
23            self.pages,
24        );
25    }
26}
27
28fn main() {
29    // 스택에 직접 (기본)
30    let book = Book::new("Clean Code", 431);
31    println!("스택: {:?}", book);
32
33    // Box::new() → 힙에 명시적 할당
34    let boxed = Box::new(
35        Book::new("Refactoring", 448)
36    );
37    println!("힙: {:?}", boxed);
38    boxed.print_info(); // 자동 역참조
39
40    // Box<dyn Trait> — 동적 디스패치
41    let d: Box<dyn Printable> = Box::new(
42        Book::new("SICP", 657)
43    );
44    d.print_info();
45
46    println!(
47        "Box 포인터: {}B, Book 직접: {}B",
48        std::mem::size_of::<Box<Book>>(),
49        std::mem::size_of::<Book>(),
50    );
51    // 스코프 끝 → 자동 Drop
52}
53
항목☕ Java Rust
기본 타입스택 (값 직접)스택 (값 직접)
객체/struct항상 힙 (new)기본 스택
힙 강제new Book()Box::new(Book{{..}})
Object Header12~16B (강제)없음
메모리 해제GCDrop (스코프 끝)
Java vs Rust 스택/힙 배치 비교 Java 스택 int x = 42 (4B) ref (주소값 8B) 메서드 프레임 힙 (GC 관리) Book 객체 (항상!) Object Header 16B title ref + pages new → 항상 힙. 스택에 객체 생성 불가. GC가 힙 해제 (비결정적) Escape Analysis: JVM 내부 최적화만 Rust 스택 (기본!) x: i32 = 42 (4B) book: Book (32B) vptr 없음! boxed: Box (8B ptr) 힙 (Box만) Book 데이터 (32B) Box::new()만 힙 기본 = 스택. Box::new()만 힙. 스코프 끝 → 즉시 Drop (GC 없음)

Box가 필요한 3가지 상황

상황예시이유
dyn Trait 반환fn f() -> Box<dyn Sound>크기를 컴파일타임에 모름
재귀 자료구조struct Node {{ next: Box<Node> }}무한 크기 방지
큰 데이터 이동Box::new(large_data)스택 오버플로 방지

Rc<T> · Arc<T> — 공유 소유권 (Java GC 대응)

출처: Rust Book §15.4 Rcstd::sync::Arc

💡 Java vs Rust — 공유 소유권
Java는 GC가 참조 카운트를 추적해 모든 객체를 자동 공유합니다. Rust는 소유권이 하나뿐이므로, 여러 곳에서 같은 데이터를 소유하려면 Rc 또는 Arc를 명시적으로 사용해야 합니다.
상황☕ Java Rust
힙에 하나, 단독 소유Sound a = new Lion()Box<dyn Sound>
여러 곳이 공유 (단일 스레드)Sound a = new Lion() (GC 공유)Rc<dyn Sound>
여러 스레드가 공유volatile / synchronizedArc<dyn Sound>
빌림 (소유권 없음)없음 (Java는 항상 참조)&dyn Sound
☕ Java
 1import java.util.*;
 2public class Main {
 3    public static void main(String[] args) {
 4        // Java: GC가 알아서 공유 관리
 5        String lion = "Simba";
 6        List<String> zoo1 = new ArrayList<>();
 7        List<String> zoo2 = new ArrayList<>();
 8        zoo1.add(lion);  // 두 리스트가 같은 객체 참조
 9        zoo2.add(lion);  // GC가 수명 관리
10        System.out.println(zoo1.get(0)); // Simba
11    }
12}
Rust
 1use std::rc::Rc;
 2fn main() {
 3    let lion = Rc::new("Simba");
 4    let zoo1 = Rc::clone(&lion); // 참조 카운트 +1
 5    let zoo2 = Rc::clone(&lion); // 참조 카운트 +1
 6    // 카운트 = 3. 모두 drop 되면 해제.
 7    println!("{} refs", Rc::strong_count(&lion)); // 3
 8} // 스코프 끝 → 카운트 0 → Drop
Rust
 1use std::sync::Arc;
 2use std::thread;
 3fn main() {
 4    let lion = Arc::new("Simba");
 5    let lion2 = Arc::clone(&lion);
 6    let handle = thread::spawn(move || {
 7        println!("thread: {}", lion2); // 다른 스레드에서 접근
 8    });
 9    handle.join().unwrap();
10    println!("main: {}", lion);
11}
⚠️ Rc vs Arc
Rc는 단일 스레드 전용 — 멀티 스레드에서 사용 시 컴파일 에러.
Arc는 원자적(atomic) 참조 카운트로 스레드 안전. 약간의 성능 오버헤드 있음.
Java는 GC가 두 경우를 모두 투명하게 처리하지만, Rust는 명시적 선택이 필요.

클로저 · 람다

출처: Rust Book §13.1

☕ Java
 1import java.util.function.*;
 2public static void main(String[] args) {
 3    Function<Integer, Integer> doubler = x -> x * 2;
 4    Function<Integer, Integer> addOne  = x -> x + 1;
 5    System.out.println(doubler.apply(5)); // 10
 6    System.out.println(addOne.apply(5));  // 6
 7
 8    Predicate<String> isLong = s -> s.length() > 5;
 9    System.out.println(isLong.test("Rust")); // false
10
11    BiFunction<Integer, Integer, Integer> add =
12        (a, b) -> a + b;
13    System.out.println(add.apply(3, 4)); // 7
14
15    // 캡처 (effectively final만 가능)
16    String prefix = "Book: ";
17    Function<String, String> label = t -> prefix + t;
18    System.out.println(label.apply("Clean Code"));
19    // prefix = "X"; // 에러
20}
Rust
 1fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }
 2fn main() {
 3    let double = |x| x * 2;
 4    let add_one = |x: i32| x + 1;
 5    println!("{}", apply(double, 5));
 6    println!("{}", apply(add_one, 5));
 7    let mut count = 0i32;
 8    let mut inc = || { count += 1; count };
 9    println!("{} {}", inc(), inc());
10}
11
트레이트호출 횟수캡처 방식Java 대응
Fn여러 번불변 참조Function<T,R>
FnMut여러 번가변 참조Consumer<T>
FnOnce1번만소유권 이동없음
⚠️ effectively final
Java 람다는 effectively final 변수만 캡처 가능 (JLS §15.27.2).
Rust 클로저는 &(불변), &mut(가변), move(소유권) 세 가지로 명시 가능.

캡처 방식 3가지 — SVG로 이해

Rust 클로저 캡처 방식 3가지 외부 변수 ① &x — 불변 캡처 (Fn) x = 5 |y| x + y x 읽기만 여러 번 호출 OK x 여전히 사용 가능 → Fn 구현 ② &mut x — 가변 캡처 (FnMut) count=0 || count+=1 count 수정 여러 번 호출 OK 클로저 선언 중 x 사용 불가 → FnMut 구현 ③ move — 소유권 이동 (FnOnce) s ❌ 무효 move || use s s 소유권 획득 1번만 호출 가능 스레드 전달에 필요 → FnOnce 구현

Fn · FnMut · FnOnce — 3종 완전 비교

💡 핵심 규칙
Fn: 불변 캡처 — 몇 번이든 호출 가능. Java Function<T,R> 대응
FnMut: 가변 캡처 — 몇 번이든 호출 가능하지만 mut 필요. Java Consumer<T> 대응
FnOnce: 소유권 이동 — 단 1번만 호출. Java에 없음 (move 의미론)
☕ Java
 1import java.util.function.*;
 2public class Main {
 3    // Fn 대응: Function<T,R> — 여러 번 호출
 4    static int callFn(Function<Integer,Integer> f, int x) {
 5        return f.apply(x);
 6    }
 7    // FnMut 대응: Consumer<T> — 내부 상태 변경
 8    static void callFnMut(Runnable f) { f.run(); }
 9    // FnOnce 대응: Java에 없음 — 1번만 실행 보장 불가
10    public static void main(String[] args) {
11        int base = 10;
12        // effectively final만 캡처 가능
13        Function<Integer,Integer> add =
14            x -> x + base;
15        System.out.println(callFn(add, 5));  // 15
16        System.out.println(callFn(add, 3));  // 13
17        // 내부 상태 변경: int[]로 우회
18        int[] cnt = {0};
19        Runnable inc = () -> cnt[0]++;
20        inc.run(); inc.run();
21        System.out.println(cnt[0]); // 2
22    }
23}
Rust
 1// Fn — 불변 캡처, 여러 번 호출 가능
 2fn call_fn<F: Fn(i32) -> i32>(f: &F, x: i32) -> i32 {
 3    f(x)
 4}
 5// FnMut — 가변 캡처, 여러 번 호출 가능
 6fn call_fn_mut<F: FnMut() -> i32>(mut f: F) -> i32 {
 7    f() + f() // 두 번 호출
 8}
 9// FnOnce — 소유권 이동, 1번만 호출
10fn call_once<F: FnOnce() -> String>(f: F) -> String {
11    f() // 1번만 — 이후 f 사용 불가
12}
13fn main() {
14    let base = 10i32;
15    // Fn: 불변 참조 캡처
16    let add = |x| x + base;
17    println!("{}", call_fn(&add, 5));   // 15
18    println!("{}", call_fn(&add, 3));   // 13
19    // FnMut: 가변 참조 캡처 (mut 필수)
20    let mut count = 0i32;
21    let inc = || { count += 1; count };
22    println!("{}", call_fn_mut(inc)); // 1+2=3
23    // FnOnce: 소유권 이동 (move)
24    let name = String::from("Rust");
25    let greet = move || format!("Hello, {}!", name);
26    println!("{}", call_once(greet)); // Hello, Rust!
27    // greet(greet); // ❌ 이미 소비됨
28}
29
트레이트캡처 방식호출 횟수mut 필요Java 대응
Fn&T 불변 참조무제한불필요Function<T,R>
FnMut&mut T 가변 참조무제한필요Consumer<T> (우회)
FnOnce소유권 이동1번불필요없음
🔍 Fn / FnMut / FnOnce 포함 관계
🔍 Fn / FnMut / FnOnce 포함 관계FnOnce소유권 이동 — 1번만 호출FnMut가변 참조 — 여러 번 호출Fn불변 참조 — 여러 번 호출Fn ⊂ FnMut ⊂ FnOnce · Fn 구현 → FnMut, FnOnce 자동 구현 · FnOnce가 가장 범용
Rust
 1fn main() {
 2    let mut count = 0;
 3    // count를 &mut 캡처 → FnMut
 4    let mut inc = || {
 5        count += 1;
 6        count
 7    };
 8    println!("{}", inc()); // 1
 9    println!("{}", inc()); // 2
10    println!("{}", inc()); // 3
11}
Rust
 1fn consume<F: FnOnce() -> String>(f: F) -> String {
 2    f() // 1번만 호출 가능
 3}
 4fn main() {
 5    let name = String::from("Rust");
 6    // move: name 소유권이 클로저 안으로
 7    let greet = move || format!("Hello, {}!", name);
 8    // println!("{}", name); // ❌ name 이미 이동됨
 9    println!("{}", consume(greet));
10}

고차 함수 — 클로저를 인자/반환값으로

☕ Java
 1import java.util.function.*;
 2import java.util.*;
 3import java.util.stream.*;
 4public class Main {
 5    // 함수를 인자로 받는 메서드
 6    static <T> List<T> myFilter(
 7        List<T> list, Predicate<T> pred) {
 8        return list.stream()
 9            .filter(pred)
10            .collect(Collectors.toList());
11    }
12    // 함수를 반환하는 메서드
13    static Function<Integer,Integer> multiplier(int n) {
14        return x -> x * n;
15    }
16    public static void main(String[] args) {
17        List<Integer> nums = List.of(1,2,3,4,5);
18        var evens = myFilter(nums, x -> x > 3);
  System.out.println(evens); // [4, 5]
19        Function<Integer,Integer> x3 = multiplier(3);
20        System.out.println(x3.apply(7)); // 21
21    }
22}
Rust
 1// impl Fn: 클로저를 인자로
 2fn my_filter<T, F>(v: Vec<T>, pred: F) -> Vec<T>
 3where F: Fn(&T) -> bool {
 4    v.into_iter().filter(|x| pred(x)).collect()
 5}
 6// impl Fn: 클로저를 반환
 7fn multiplier(n: i32) -> impl Fn(i32) -> i32 {
 8    move |x| x * n  // n을 move 캡처
 9}
10fn main() {
11    let nums = vec![1,2,3,4,5];
12    let evens = my_filter(nums, |x| x % 2 == 0);
13    let triple = multiplier(3);
14    println!("{:?}", evens);      // [2, 4]
15    println!("{}", triple(7));   // 21
16}
트레이트호출캡처Java 대응주요 용도
Fn여러 번&T (불변)Function<T,R>일반 변환, 필터
FnMut여러 번&mut T (가변)Consumer<T>누적, 카운터
FnOnce1번만T (소유권)없음스레드 전달, 소비
💡 Fn 계층 구조
FnFnMutFnOnceFn을 구현하면 FnMut, FnOnce도 자동 구현.
함수 인자로 FnOnce를 받으면 세 가지 모두 수용 가능 (가장 넓은 계약).
출처: Rust Reference — Closure Types

컬렉션 · 함수형

출처: Rust Book §8Rust Book §13.2

⑬ Vec — Java ArrayList

☕ Java
 1import java.util.*;
 2import java.util.stream.*;
 3public class Main {
 4    public static void main(String[] args) {
 5        List<String> v = new ArrayList<>(
 6            List.of(
 7                "Clean Code",
  "Refactoring", "SICP"));
 8        v.add("DDIA");           // push
 9        v.add(0, "DDD");         // insert(0)
10        String last = v.get(v.size()-1);
11        v.remove(v.size()-1);  // pop()
12        v.stream()
13         .filter(s -> s.length() > 4)
14         .map(String::toUpperCase)
15         .forEach(System.out::println);
16        System.out.println(last);
17    }
18}
Rust
 1fn main() {
 2    // ─── 생성 ──────────────────────────────────────
 3    let mut shelf: Vec<String> = Vec::new();
 4    // vec! 매크로로 초기화
 5    let mut v = vec![
 6        "Clean Code".to_string(),
 7        "Refactoring".to_string(),
 8        "SICP".to_string(),
 9    ];
10
11    // ─── 추가 / 삭제 ──────────────────────────────
12    v.push("DDIA".to_string());      // add(e) — 끝에 추가
13    v.insert(0, "DDD".to_string()); // add(0, e) — 앞에 삽입
14    let last = v.pop();            // remove(n-1) — 끝 제거
15    println!("pop: {:?}", last);
16    println!("크기: {}", v.len()); // size()
17
18    // ─── 접근 ──────────────────────────────────────
19    println!("첫 번째: {}", v[0]); // get(0) 대응
20    println!("안전 접근: {:?}", v.get(100)); // None 반환
21
22    // ─── 순회 ──────────────────────────────────────
23    for title in &v {                    // for (String t : list)
24        print!("{} / ", title);
25    }
26    println!();
27
28    // 인덱스와 함께
29    for (i, title) in v.iter().enumerate() {
30        println!("[{}] {}", i, title);
31    }
32
33    // ─── 함수형 체인 ──────────────────────────────
34    let long_titles: Vec<&str> = v.iter()
35        .filter(|t| t.len() > 4)
36        .map(|t| t.as_str())
37        .collect();
38    println!("긴 제목: {:?}", long_titles);
39
40    // ─── 정렬 ──────────────────────────────────────
41    v.sort();
42    println!("정렬: {:?}", v);
43    v.sort_by(|a, b| b.cmp(a)); // 역순
44    println!("역순: {:?}", v);
45
46    // ─── 불변 컬렉션 패턴 ─────────────────────────
47    let fixed = vec![1i32, 2, 3]; // mut 없음 → 수정 불가
48    println!("contains 3: {}", fixed.contains(&3));
49}
50

⑭ HashMap

☕ Java
 1import java.util.*;
 2public class Main {
 3    public static void main(String[] args) {
 4        Map<String,String> lib = new HashMap<>();
 5        lib.put("isbn-001", "Clean Code");
 6        lib.put("isbn-002", "Refactoring");
 7        lib.getOrDefault("isbn-999", "none");
 8        lib.putIfAbsent("isbn-003", "DDIA");
 9        lib.forEach((k, v) -> System.out
10            .println(k + " → " + v));
11        // 빈도 계산
12        Map<String,Integer> cnt = new HashMap<>();
13        String[] ws = {"a","b","a"};
  for (String w : ws)
14            cnt.merge(w, 1, Integer::sum);
15        System.out.println(cnt);
16    }
17}
Rust
 1use std::collections::HashMap;
 2
 3#[derive(Debug, Clone)]
 4struct Book {
 5    title: String,
 6    pages: u32,
 7}
 8impl Book {
 9    fn new(t: &str, p: u32) -> Self {
10        Book { title: t.to_string(), pages: p }
11    }
12}
13
14fn main() {
15    let mut library: HashMap<String, Book> = HashMap::new();
16
17    // ─── 추가 ──────────────────────────────────────
18    library.insert(
19        "isbn-001".to_string(),
20        Book::new("Clean Code", 431),
21    );
22    library.insert(
23        "isbn-002".to_string(),
24        Book::new("Refactoring", 448),
25    );
26
27    // ─── 조회 ──────────────────────────────────────
28    // get → Option<&V>
29    if let Some(book) = library.get("isbn-001") {
30        println!("찾음: {}", book.title);
31    }
32    // get_or_insert — 없으면 삽입 (Entry API)
33    library.entry("isbn-003".to_string())
34           .or_insert_with(|| Book::new("SICP", 657));
35
36    // ─── 삭제 / 존재 확인 ──────────────────────────
37    println!("exists: {}", library.contains_key("isbn-001"));
38    library.remove("isbn-001");
39    println!("after remove: {}", library.len()); // 2
40
41    // ─── 순회 ──────────────────────────────────────
42    for (isbn, book) in &library {
43        println!("{} → {}", isbn, book.title);
44    }
45
46    // ─── 함수형 ────────────────────────────────────
47    let titles: Vec<&str> = library.values()
48        .map(|b| b.title.as_str())
49        .collect();
50    println!("제목들: {:?}", titles);
51
52    // ─── 카운터 패턴 ──────────────────────────────
53    let text = "hello world hello rust world";
54    let mut counts: HashMap<&str, u32> = HashMap::new();
55    for word in text.split_whitespace() {
56        *counts.entry(word).or_insert(0) += 1;
57    }
58    println!("word counts: {:?}", counts);
59}
60

⑮ Iterator 체인 비교

☕ Java
 1import java.util.*;
 2import java.util.stream.*;
 3
 4public static void main(String[] args) {
 5    List<Integer> nums = List.of(1, 2, 3, 4, 5);
 6
 7    // Stream API — 지연 평가 (Rust Iterator)
 8    int sum = nums.stream()
 9        .filter(n -> n % 2 == 0)
10        .map(n -> n * 2)
11        .reduce(0, Integer::sum);
12    System.out.println(sum); // 12
13
14    List<String> names = nums.stream()
15        .map(n -> "book-" + n)
16        .collect(Collectors.toList());
17    System.out.println(names);
18
19    boolean any = nums.stream()
20        .anyMatch(n -> n > 4);
21    long cnt = nums.stream()
22        .filter(n -> n > 2)
23        .count();
24    System.out.println(any + " " + cnt);
25}
Rust
 1fn main() {
 2    let nums = [1i32, 2, 3, 4, 5];
 3
 4    // filter → map → sum (지연 평가)
 5    let sum: i32 = nums.iter()
 6        .filter(|&&n| n % 2 == 0)
 7        .map(|&n| n * 2)
 8        .sum();
 9    println!("{}", sum); // 12
10
11    // collect → Vec<String>
12    let names: Vec<String> = nums.iter()
13        .map(|n| format!("book-{}", n))
14        .collect();
15    println!("{:?}", names);
16
17    // any / count
18    let any_big = nums.iter().any(|&n| n > 4);
19    let cnt = nums.iter()
20        .filter(|&&n| n > 2)
21        .count();
22    println!("any={} cnt={}", any_big, cnt);
23}
24

iter() · iter_mut() · into_iter() — Java for-each 비교

💡 3가지 Iterator 메서드 핵심
iter(): &T — 불변 참조 반복, 원본 유지
iter_mut(): &mut T — 가변 참조 반복, 원본 수정 가능
into_iter(): T — 소유권 이동, 반복 후 원본 소비
☕ Java
 1import java.util.*;
 2public class Main {
 3    public static void main(String[] args) {
 4        List<String> names = new ArrayList<>(
 5            List.of("Simba", "Donald", "Nagini"));
 6        // iter() 대응: for-each (읽기만)
 7        for (String n : names)
 8            System.out.println(n);
 9        // iter_mut() 대응: ListIterator (수정)
10        ListIterator<String> it =
11            names.listIterator();
12        while (it.hasNext()) {
13            String s = it.next();
14            it.set(s.toUpperCase()); // 수정
15        }
16        System.out.println(names);
17        // into_iter() 대응: 없음 (Java는 항상 참조)
18        // Java는 소유권 이동 개념 없음 → GC가 관리
19        List<String> consumed = names;
20        System.out.println(consumed);
21        System.out.println(names); // 여전히 OK
22    }
23}
Rust
 1fn main() {
 2    let mut names = vec![
 3        "Simba".to_string(),
 4        "Donald".to_string(),
 5        "Nagini".to_string(),
 6    ];
 7    // iter() → &String, 원본 유지
 8    for n in names.iter() {  // n: &String
 9        print!("{} ", n);
10    }
11    println!();
12    // iter_mut() → &mut String, 원본 수정
13    for n in names.iter_mut() { // n: &mut String
14        *n = n.to_uppercase();
15    }
16    println!("{:?}", names);
17    // into_iter() → String, 소유권 이동
18    for n in names.into_iter() { // n: String
19        println!("{}", n);
20    }
21    // println!("{:?}", names); // ❌ 소유권 이동됨
22}
23
메서드반환 타입원본 상태Java 대응
iter()&T유지for (T x : list)
iter_mut()&mut T수정 가능ListIterator.set()
into_iter()T소비됨없음 (GC가 관리)
🔍 iter 종류별 메모리 흐름
🔍 iter 종류별 메모리 흐름 — Vec<String>Vec<String>"Simba""Donald""Nagini"iter()→ &T 읽기만 원본 유지iter_mut()→ &mut T 수정가능 원본 유지into_iter()→ T 소유이동 원본 소멸for x in &v → iter() · &mut v → iter_mut() · v → into_iter()
Java Stream Rust Iterator설명
stream().iter()불변 이터레이터
filter(pred).filter(|x|..)조건 필터링
map(fn).map(|x|..)변환
collect(toList()).collect()소비→컬렉션
reduce(0,sum).sum()합산
anyMatch().any(|x|..)하나라도 참
count().count()개수
flatMap().flat_map()중첩 해제
💡 지연 평가
Java Stream과 Rust Iterator 모두 소비 연산(collect(), sum())이 호출될 때까지 실제 연산이 실행되지 않습니다.

제네릭 · trait bound · struct 메서드

출처: Rust Book §10.1Rust Book §10.2

struct 메서드

☕ Java
 1public class Main {
 2    static class Rectangle {
 3        private double width, height;
 4
 5        static Rectangle of(double w, double h) {
 6            Rectangle r = new Rectangle();
 7            r.width = w;
 8            r.height = h;
 9            return r;
10        }
11        static Rectangle square(double s) {
12            return of(s, s);
13        }
14        double area() {
15            return width * height;
16        }
17        double perimeter() {
18            return 2 * (width + height);
19        }
20        boolean isSquare() {
21            return Math.abs(width - height)
22                < 1e-10;
23        }
24        // &mut self 대응 — Java는 구분 없음
25        Rectangle scale(double f) {
26            width  *= f;
27            height *= f;
28            return this;
29        }
30        @Override
31        public String toString() {
32            return String.format(
33                "%.1fx%.1f (area=%.1f)",
34                width, height, area());
35        }
36    }
37    public static void main(String[] args) {
38        var r = Rectangle.of(3.0, 4.0);
39        System.out.println(r.area());      // 12
40        r.scale(2.0);
41        System.out.println(r);             // 6.0x8.0
42        System.out.println(
43            Rectangle.square(5.0));        // 5.0x5.0
44    }
45}
46
Rust
 1#[derive(Debug)]
 2struct Rectangle {
 3    width:  f64,
 4    height: f64,
 5}
 6
 7impl Rectangle {
 8    // 연관 함수 (Java static factory)
 9    fn new(w: f64, h: f64) -> Self {
10        Rectangle { width: w, height: h }
11    }
12    fn square(s: f64) -> Self {
13        Rectangle { width: s, height: s }
14    }
15
16    // &self — 불변 메서드 (Java 일반 메서드)
17    fn area(&self) -> f64 {
18        self.width * self.height
19    }
20    fn perimeter(&self) -> f64 {
21        2.0 * (self.width + self.height)
22    }
23    fn is_square(&self) -> bool {
24        (self.width - self.height).abs()
25            < f64::EPSILON
26    }
27
28    // &mut self — 가변 메서드 (Java는 구분 없음)
29    fn scale(&mut self, f: f64) {
30        self.width  *= f;
31        self.height *= f;
32    }
33
34    // self — 소유권 소비 (Java에 없음)
35    fn describe(self) -> String {
36        format!(
37            "{:.1}x{:.1} (area={:.1})",
38            self.width,
39            self.height,
40            self.width * self.height,
41        )
42    }
43}
44
45fn main() {
46    let mut r = Rectangle::new(3.0, 4.0);
47    println!("{}", r.area());       // 12
48    println!("{}", r.perimeter());  // 14
49    println!("{}", r.is_square());  // false
50    r.scale(2.0);
51    println!("{:?}", r);
52    let s = Rectangle::square(5.0);
53    println!("{}", s.describe());
54}
55
수신자 RustJava 대응
없음fn new()static T of()
&self읽기 전용일반 메서드
&mut self수정 가능일반 메서드 (구분 없음)
self소유권 소비 (1회만)없음

제네릭 (Generics)

☕ Java
 1import java.util.*;
 2public class Main {
 3    // 제네릭 — 타입 소거 → 런타임 동적 디스패치
 4    static <T extends Comparable<T>> T largest(
 5            List<T> list) {
 6        T max = list.get(0);
 7        for (T item : list) {
 8            if (item.compareTo(max) > 0) max = item;
 9        }
10        return max;
11    }
12    static class Pair<T extends Comparable<T>> {
13        final T first, second;
14        Pair(T f, T s) { first=f; second=s; }
15        T larger() {
16            return first.compareTo(second) >= 0
17                ? first : second;
18        }
19    }
20    public static void main(String[] args) {
21        System.out.println(
22            largest(List.of(34,50,25,100,65))); // 100
23        var p = new Pair<>(5, 10);
24        System.out.println(p.larger()); // 10
25        // Java 제네릭 = 타입 소거 → 동적 디스패치
26        // Rust 제네릭 = 단형화 → 정적 디스패치
27    }
28}
Rust
 1fn largest<T: PartialOrd>(list: &[T]) -> &T {
 2    let mut l = &list[0];
 3    for item in list {
 4        if item > l { l = item; }
 5    }
 6    l
 7}
 8
 9struct Pair<T> {
10    first:  T,
11    second: T,
12}
13impl<T: std::fmt::Display + PartialOrd> Pair<T> {
14    fn new(f: T, s: T) -> Self {
15        Pair { first: f, second: s }
16    }
17    fn cmp_display(&self) {
18        if self.first >= self.second {
19            println!("first={}", self.first);
20        } else {
21            println!("second={}", self.second);
22        }
23    }
24}
25
26fn main() {
27    let nums = vec![34i32, 50, 25, 100, 65];
28    println!("{}", largest(&nums));   // 100
29
30    let p = Pair::new(5i32, 10);
31    p.cmp_display(); // "second=10"
32}
33
항목Java 제네릭 Rust 제네릭
구현타입 소거 → 런타임 Object단형화 → 컴파일타임 구체 함수
vtable있음 (동적)없음 (직접 CALL)
bound 문법<T extends Sound><T: Sound>

trait bound · where · impl Trait 반환

☕ Java
 1import java.util.*;
 2public class Main {
 3    // 제네릭 — 타입 소거 → 런타임 동적 디스패치
 4    static <T extends Comparable<T>> T largest(
 5            List<T> list) {
 6        T max = list.get(0);
 7        for (T item : list) {
 8            if (item.compareTo(max) > 0) max = item;
 9        }
10        return max;
11    }
12    static class Pair<T extends Comparable<T>> {
13        final T first, second;
14        Pair(T f, T s) { first=f; second=s; }
15        T larger() {
16            return first.compareTo(second) >= 0
17                ? first : second;
18        }
19    }
20    public static void main(String[] args) {
21        System.out.println(
22            largest(List.of(34,50,25,100,65))); // 100
23        var p = new Pair<>(5, 10);
24        System.out.println(p.larger()); // 10
25        // Java 제네릭 = 타입 소거 → 동적 디스패치
26        // Rust 제네릭 = 단형화 → 정적 디스패치
27    }
28}
Rust
 1use std::fmt;
 2fn print_info<T: fmt::Display + fmt::Debug>(item: &T) {
 3    println!("Display: {}", item);
 4    println!("Debug:   {:?}", item);
 5}
 6fn complex<T, U>(t: &T, u: &U) -> String
 7where T: fmt::Display + Clone, U: fmt::Debug + Clone {
 8    format!("{} {:?}", t, u)
 9}
10fn make_adder(x: i32) -> impl Fn(i32) -> i32 { move |y| x + y }
11fn main() {
12    print_info(&42i32);
13    println!("{}", complex(&"hello", &vec![1,2,3]));
14    let add5 = make_adder(5);
15    println!("{}", add5(3));
16}
💡 impl Trait 반환
fn make_adder(x: i32) -> impl Fn(i32) -> i32
구체 타입을 숨기면서 정적 디스패치 유지. vtable 없음.
출처: Rust Reference — impl Trait

라이프타임 (Lifetimes) — Java 비교

출처: Rust Book §10.3Rust Reference — ElisionRust Book — Elision Rules

💡 라이프타임이란?
Rust에서 모든 참조는 라이프타임(lifetime)을 갖습니다.
라이프타임은 참조가 유효한 범위를 나타내며, 컴파일러가 자동으로 추론합니다.
Java는 GC가 참조를 관리하므로 이 개념이 없습니다.
출처: Rust Book §10.3 — Lifetime Syntax

핵심 규칙 — dangling pointer 차단

라이프타임 — 참조는 원본보다 오래 살 수 없다☕ Java — GC가 살려둠Book reference = null;{ Book book = new Book(..); reference = book;} // GC가 살려둠reference.title → OK ✓GC가 나중에 해제✦ 참조가 살아있는 한 GC는 해제 안함✦ GC pause · 오버헤드 · NPE 위험✦ 런타임, 비결정적 Rust — 컴파일타임 차단let r: &str;{ let s = String::from(..); r = &s; // r 수명 > s!} // s Drop → r dangling!// 컴파일 에러 E0597!dangling 원천 차단 ✓✦ 참조는 원본보다 오래 살 수 없음✦ 위반 시 E0597 컴파일 에러✦ 컴파일타임, 결정적, 0 런타임 비용GC 없이 dangling pointer 원천 차단 — 컴파일타임에 검증
☕ Java — GC가 참조를 살려둠

참조 변수가 가리키고 있는 동안 GC는 객체를 해제하지 않습니다.
개발자가 라이프타임을 신경 쓸 필요 없지만,
GC pause · 메모리 오버헤드 · NPE 위험이 있습니다.

Rust — 컴파일타임에 검증

참조는 원본 데이터보다 오래 살 수 없습니다.
이를 어기면 E0597 컴파일 에러.
GC 없이 dangling pointer를 원천 차단합니다.

라이프타임 명시가 필요한 경우

⚠️ 언제 명시가 필요한가?
대부분의 경우 컴파일러가 라이프타임 생략(elision)으로 자동 추론합니다.
명시가 필요한 경우: 여러 참조를 받아 참조를 반환하는 함수 struct에 참조 필드를 저장할 때
출처: Rust Reference — Lifetime Elision
☕ Java
 1public class Main {
 2    // Java: GC가 참조를 살려두므로
 3    // 라이프타임 개념이 없음
 4    static String dangling() {
 5        StringBuilder sb = new StringBuilder("local");
 6        return sb.toString(); // String 반환 (복사)
 7        // Rust: 참조(&str) 반환 시 컴파일 에러!
 8        // dangling pointer 원천 차단
 9    }
10
11    // Java: 참조가 살아있는 한 GC가 보존
12    static String outer;
13
14    static void lifetime_demo() {
15        String local = "hello world";
16        outer = local; // OK — GC가 살려둠
17        // Rust: outer = &local; 은 에러!
18        // local의 라이프타임이 함수 끝에 끝남
19    }
20
21    // Java: 메서드 간 참조 전달 — 자동
22    static String firstWord(String s) {
23        int space = s.indexOf(' ');
24        if (space < 0) return s;
25        return s.substring(0, space);
26        // Rust: &str 반환 시 라이프타임 명시 필요
27        // fn first_word(s: &str) -> &str
28        // (여기선 생략 가능)
29    }
30
31    public static void main(String[] args) {
32        System.out.println(dangling());
33        lifetime_demo();
34        System.out.println(outer);
35        System.out.println(
36            firstWord("hello world"));
37        // Java: 라이프타임 신경 쓸 필요 없음
38        // 대신 GC pause + NPE 위험
39    }
40}
41
Rust
 1// ── 1. 라이프타임이 필요한 이유
 2// 컴파일러가 둘 중 어느 참조를 반환할지 모름
 3// → 'a 로 "둘 다 최소한 'a 동안 유효" 명시
 4fn longest<'a>(
 5    x: &'a str,
 6    y: &'a str,
 7) -> &'a str {
 8    if x.len() > y.len() { x } else { y }
 9}
10
11// ── 2. struct에 참조 저장
12struct Excerpt<'a> {
13    // 'a: Excerpt가 살아있는 동안
14    //    part 원본도 살아있어야 함
15    part: &'a str,
16}
17impl<'a> Excerpt<'a> {
18    fn announce(
19        &self,
20        ann: &str,
21    ) -> &str {
22        println!("주의: {}", ann);
23        self.part
24    }
25}
26
27// ── 3. 생략(elision) — 컴파일러가 자동 추론
28// 아래 두 함수는 라이프타임이 동일
29fn first_word(s: &str) -> &str {
30    match s.find(' ') {
31        Some(i) => &s[..i],
32        None    => s,
33    }
34}
35// 명시 버전 (위와 완전히 동일)
36fn first_word_explicit<'a>(
37    s: &'a str,
38) -> &'a str {
39    match s.find(' ') {
40        Some(i) => &s[..i],
41        None    => s,
42    }
43}
44
45// ── 4. 'static — 프로그램 전체 수명
46fn static_demo() {
47    // 문자열 리터럴 → 바이너리에 포함 → 'static
48    let s: &'static str = "항상 살아있음";
49    println!("{}", s);
50}
51
52fn main() {
53    // longest 예제
54    let s1 = String::from("long string");
55    {
56        let s2 = String::from("xyz");
57        let result = longest(&s1, &s2);
58        println!("longest: {}", result);
59    } // s2 Drop → result도 여기까지만 유효
60
61    // struct 예제
62    let novel = String::from(
63        "Call me Ishmael. Some years ago...",
64    );
65    let first = novel.split('.').next().unwrap();
66    let e = Excerpt { part: first };
67    println!("{}", e.announce("중요!"));
68
69    // 생략 예제
70    let sentence = String::from("hello world");
71    println!("{}", first_word(&sentence));
72    println!("{}", first_word_explicit(&sentence));
73
74    static_demo();
75}
76

라이프타임 생략(Elision) — 컴파일러 자동 추론

Rust
 1// 라이프타임 생략(elision) — 컴파일러 자동 추론
 2// 아래 세 함수는 모두 동일한 라이프타임을 가짐
 3
 4// 생략 버전 (컴파일러가 'a 자동 추론)
 5fn first_word(s: &str) -> &str {
 6    let bytes = s.as_bytes();
 7    for (i, &b) in bytes.iter().enumerate() {
 8        if b == b' ' {
 9            return &s[0..i];
10        }
11    }
12    &s[..]
13}
14
15// 명시 버전 (위와 동일)
16fn first_word_explicit<'a>(
17    s: &'a str,
18) -> &'a str {
19    let bytes = s.as_bytes();
20    for (i, &b) in bytes.iter().enumerate() {
21        if b == b' ' {
22            return &s[0..i];
23        }
24    }
25    &s[..]
26}
27
28fn main() {
29    let s = String::from("hello world");
30    // 생략 버전
31    let w1 = first_word(&s);
32    println!("{}", w1); // "hello"
33    // 명시 버전
34    let w2 = first_word_explicit(&s);
35    println!("{}", w2); // "hello"
36}
37
규칙설명예시
규칙 1입력 참조가 하나면 출력에 같은 라이프타임fn f(s: &str) -> &str
규칙 2&self/&mut self가 있으면 출력에 self 라이프타임fn f(&self) -> &str
나머지위 규칙 미적용 시 명시 필요fn longest<'a>(x: &'a str, ...) -> &'a str

'static — 프로그램 전체 수명

종류예시설명
문자열 리터럴let s: &'static str = "hello";바이너리에 포함 → 항상 유효
정적 변수static MAX: u32 = 100;프로그램 전체 수명
trait boundT: 'static소유권 있는 타입 또는 &'static만 허용
✅ Java vs Rust 라이프타임 비교
항목☕ Java Rust
메모리 관리GC (런타임)라이프타임 (컴파일타임)
dangling pointerGC가 방지컴파일 에러로 방지
런타임 비용GC pause 있음없음 (0 비용)
null 참조NPE 가능Option<T> (강제)

struct에 참조 필드 — 라이프타임 명시 필수

struct 라이프타임 — 참조 필드의 유효 범위 보장 원본 String (소유자) novel: String "Call me Ishmael..." 수명: 블록 전체 ('a) ImportantExcerpt<'a> part: &'a str "Call me" 수명 ≤ 'a (원본보다 짧거나 같음) &'a 참조 ❌ 위반 — 댕글링 원본 drop됨 (블록 종료) struct 사용 시도 ❌ 컴파일 에러!
☕ Java
 1public class Main {
 2    // Java: GC가 참조 수명을 자동 관리
 3    // 원본이 GC되면 참조도 무효 — 런타임에서야 알 수 있음
 4    static class Excerpt {
 5        private final String part; // String 자체를 복사해 저장
 6        public Excerpt(String text) {
 7            this.part = text.split("\\\\.")[0];
 8        }
 9        public String getPart() { return part; }
10    }
11    // Java는 &str 슬라이스 개념이 없어
12    // 항상 String 복사본을 만들어야 함
13    public static void main(String[] args) {
14        Excerpt e = new Excerpt("Hello. World.");
15        System.out.println(e.getPart()); // Hello
16    }
17}
Rust
 1// 참조 필드를 가진 struct는 라이프타임 필수
 2struct Excerpt<'a> {
 3    part: &'a str, // 복사 없이 슬라이스 참조
 4}
 5impl<'a> Excerpt<'a> {
 6    fn announce(&self) {
 7        println!("발췌: {}", self.part);
 8    }
 9}
10fn main() {
11    let novel = String::from("Call me Ishmael. Some years ago...");
12    let first = novel.split('.').next().unwrap();
13    let ex = Excerpt { part: first }; // 복사 없음!
14    ex.announce(); // novel보다 ex가 먼저 drop됨 → OK
15} // novel drop → ex는 이미 없으므로 안전
💡 struct 라이프타임 핵심
참조 필드를 가진 struct는 반드시 라이프타임 파라미터 선언 필요.
Excerpt<'a>의 의미: "이 struct 인스턴스는 'a가 유효한 동안만 존재 가능"
Java는 항상 String을 복사해 저장 — Rust는 복사 없이 원본 슬라이스를 안전하게 참조 가능.

제네릭 · 템플릿 — Java · C++ · Rust 비교

C++17 기준 · Java 21 기준 · Rust 1.82 기준 — Template Instantiation / Type Erasure / Monomorphization 완전 비교

왜 제네릭/템플릿이 필요한가

타입마다 같은 로직을 반복하는 문제를 해결하기 위해 등장했다. 세 언어 모두 <T> 문법을 사용하지만 컴파일 타임 처리 방식이 근본적으로 다르다.

☕ Java
 1class PrintInt {
 2    static void print(int v) {
 3        System.out.println("int: " + v);
 4    }
 5}
 6class PrintStr {
 7    static void print(String v) {
 8        System.out.println("str: " + v);
 9    }
10}
11public class Main {
12    public static void main(String[] args) {
13        PrintInt.print(42);
14        PrintStr.print("hello");
15        // Problem: duplicate code per type
16        // Solution: generics
17    }
18}
C++
 1#include <iostream>
 2#include <string>
 3
 4void print_int(int v) {
 5    std::cout << "int: " << v << "\n";
 6}
 7void print_str(const std::string& v) {
 8    std::cout << "str: " << v << "\n";
 9}
10
11// Solution: template
12template<typename T>
13void print_any(const T& v) {
14    std::cout << "val: " << v << "\n";
15}
16
17int main() {
18    print_int(42);
19    print_str("hello");
20    print_any(42);
21    print_any(std::string("hello"));
22    return 0;
23}
Rust
 1fn print_i32(v: i32) {
 2    println!("i32: {}", v);
 3}
 4fn print_str(v: &str) {
 5    println!("str: {}", v);
 6}
 7// Solution: generic function
 8fn print_any<T: std::fmt::Display>(v: T) {
 9    println!("val: {}", v);
10}
11fn main() {
12    print_i32(42);
13    print_str("hello");
14    print_any(42);
15    print_any("hello");
16    print_any(3.14f64);
17}

Box<T> — Template Instantiation vs Type Erasure vs Monomorphization

Template Instantiation · Type Erasure · Monomorphization개요• 같은 Box<T> 문법 — 세 언어의 컴파일 방식은 완전히 다르다• C++/Rust: 타입마다 별도 코드 생성 | Java: 하나의 코드로 처리• 핵심: Stack<int>와 Stack<string>이 C++/Rust에서는 완전히 다른 코드① Java — Type Erasure (JLS §4.6)소스 코드class Box<T> {T value;Box(T v) {this.value = v;}T get() {return value;}}// T = 타입 파라미터바이트코드 (T 소거)// T → Object 로 소거됨class Box {Object value; // T→ObjectBox(Object v) {this.value = v;}Object get() {return value;}}사용 시 자동 캐스팅 삽입// 개발자 코드:Box<Integer> a = new Box<>(42);Integer x = a.get();// 컴파일러 실제 변환:Integer x =(Integer) a.get();// 캐스팅 자동 삽입!// 런타임 확인:Java Type Erasure — Stack(Object) 핵심 설명• Stack<Integer> 와 Stack<String> 은 런타임에 동일한 클래스 → a.getClass()==b.getClass() = true• Stack(Object): T 소거 후 내부 배열이 Object[] → 각 요소가 Integer 힙 객체를 가리키는 참조• push(10) 내부: int 10 → Integer.valueOf(10) 힙 객체 생성 → 참조 저장 = Autoboxing 비용• 메모리: 참조 8B×3 + Integer 객체 16B×3 = 약 72B (vs C++/Rust: int×3 = 12B)• .class 파일: Box.class 단 하나 → 바이너리 크기 불변, 하위 호환성 ✓✓ 장점 & 핵심 개념✓ 바이너리 크기 불변 — 타입 수 늘어도 Box.class 하나만✓ 기존 Java 코드 하위 호환✓ 컴파일 속도 빠름• 공장(클래스 코드) = 1개만 존재• 상자(객체) = new 할 때마다 생성 (여러 개 가능)✗ 제약✗ 런타임 T 정보 없음 (instanceof T 불가)✗ Stack<int> 불가 → Stack<Integer> 강제✗ new T[10] 불가✗ Autoboxing 오버헤드② C++17 — Template Instantiation (cppreference §Template instantiation)소스 코드template<typename T>class Box {public:T value;Box(T v) : value(v) {}T get() const {return value;}};// 사용: Box<int> a(42);Box<int> 전용 코드// 개념적 생성 코드:// (실제 이름 아님)class Box_int {public:int value;Box_int(int v): value(v) {}int get() const {return value;}Box<string> 전용 코드// 개념적 생성 코드:// (실제 이름 아님)class Box_str {public:string value;Box_str(string v): value(v) {}string get() const {return value;}C++ Template Instantiation 핵심• Box_int, Box_str = 개념 설명용 이름. 실제로는 컴파일러 내부에서 맹글링(name mangling) 처리됨• .text 영역: Box<int> 전용 기계어 코드 + Box<string> 전용 기계어 코드 = 타입 수만큼 증가• 런타임: typeid(Box<int>) != typeid(Box<string>) → 타입 정보 완전 보존• 직접 CALL 명령어 → vtable 없음 → 인라인 최적화 가능 → 최고 성능③ Rust — Monomorphization (The Rust Book §10.1)소스 코드struct Box<T> {value: T,}impl<T: Display>Box<T> {fn new(v: T) -> Self {Box { value: v }}fn get(&self) -> &T {&self.value}}Box<i32> 전용// Monomorphization 결과// (개념 설명용 이름)struct Box_i32 {value: i32,}impl Box_i32 {fn get(&self) -> &i32 {&self.value}}// i32 전용 최적화 코드// .text 에 별도 존재Box<String> 전용// Monomorphization 결과// (개념 설명용 이름)struct Box_str {value: String,}impl Box_str {fn get(&self)->&String {&self.value}}// String 전용 최적화 코드// .text 에 별도 존재Rust Monomorphization 핵심• C++와 동일: 타입마다 별도 코드 생성 → .text 영역에 i32 전용 + String 전용 코드 별도 존재• C++와 차이: rustc 컴파일러가 처리, 소유권 시스템으로 GC 없이 안전한 메모리 관리• TypeId::of::<Vec<i32>>() != TypeId::of::<Vec<String>>() → 타입 정보 보존• primitive 직접 사용 (Stack<i32> ✓), Boxing 없음, 연속 메모리, 최고 성능
💡 공장(클래스 코드) vs 상자(객체) — 핵심 구분
공장 = 클래스 코드 (컴파일 결과물, 바이너리 .text 영역에 존재)
상자 = 실제 생성된 객체 (new 할 때마다 힙에 생성)

C++: Stack<int>용 공장 따로 + Stack<string>용 공장 따로 → 타입마다 공장이 다름
Java: Stack(Object) 공장 하나만 → 공장은 하나, 상자는 여러 개 생성 가능
Rust: Stack<i32>용 공장 따로 + Stack<String>용 공장 따로 → C++와 동일

오해 주의: Java도 객체(상자)는 여러 개 만든다. 차이는 공장 코드가 몇 개냐의 차이다.
💡 C++ '코드를 찍어낸다'의 정확한 의미
찍어낸다 = 공장(클래스 코드)을 만드는 것 — 객체를 찍어내는 것이 아님

Stack<int> 첫 사용 → int 전용 공장 생성 (바이너리에 1회만)
Stack<int> 두번째 new → 공장 재사용, 객체만 새로 생성
Stack<string> 첫 사용 → string 전용 공장 별도 생성

공장(클래스 코드) = 타입당 1개    상자(객체) = new 할 때마다 1개

C++17 Template Instantiation: Box<int> 사용 시 컴파일러가 int 전용 독립 코드를 생성한다. Box<string>은 또 다른 독립 코드다 (cppreference §Template instantiation). 개념 설명용으로 Box_int라 부르지만 실제 이름은 아니다.

Java Type Erasure: Box<Integer>Box<String>은 컴파일 후 하나의 Box(Object)로 합쳐진다 (JLS §4.6). 런타임에 T 정보는 사라진다.

Rust Monomorphization: C++와 동일하게 컴파일 타임에 타입별 독립 코드를 생성한다 (The Rust Book §10.1). 런타임 오버헤드 없음.

☕ Java
 1class Box<T> {
 2    private T value;
 3    Box(T v) { this.value = v; }
 4    T get() { return value; }
 5    @Override
 6    public String toString() {
 7        return "Box(" + value + ")";
 8    }
 9}
10public class Main {
11    public static void main(String[] args) {
12        Box<Integer> a = new Box<>(42);
13        Box<String>  b = new Box<>("hello");
14        // Type Erasure: both become Box(Object)
15        // at runtime - no separate classes
16        System.out.println(a.get()); // 42
17        System.out.println(b.get()); // hello
18        System.out.println(
19            a.getClass() == b.getClass()); // true
20    }
21}
C++
 1#include <iostream>
 2#include <string>
 3
 4template<typename T>
 5class Box {
 6public:
 7    T value;
 8    Box(T v) : value(v) {}
 9    T get() const { return value; }
10};
11// Template Instantiation:
12// Box<int>    -> separate int-specific code
13// Box<string> -> separate string-specific code
14
15int main() {
16    Box<int>         a(42);
17    Box<std::string> b("hello");
18    std::cout << a.get() << "\n"; // 42
19    std::cout << b.get() << "\n"; // hello
20    // a and b are completely different types
21    return 0;
22}
Rust
 1struct Box<T> {
 2    value: T,
 3}
 4impl<T: std::fmt::Display> Box<T> {
 5    fn new(v: T) -> Self { Box { value: v } }
 6    fn get(&self) -> &T { &self.value }
 7}
 8// Monomorphization:
 9// Box<i32>    -> i32-specific code at compile time
10// Box<String> -> String-specific code at compile time
11// Same result as C++ Template Instantiation
12fn main() {
13    let a = Box::new(42i32);
14    let b = Box::new(String::from("hello"));
15    println!("{}", a.get()); // 42
16    println!("{}", b.get()); // hello
17}
💡 핵심 차이
C++/Rust → 타입별 독립 코드 생성 (바이너리 크기 증가, 런타임 오버헤드 없음)
Java → 하나의 클래스로 처리 (바이너리 크기 불변, boxing/casting 비용 발생)
💡 Java Box<T> 동작 원리 — 5단계
① 소스: class Box<T> { T value; } → T = 타입 파라미터 (컴파일 타임 체크용)
② 컴파일: javac가 T를 소거 → Box { Object value; } 로 변환 (Type Erasure)
③ JVM Method Area: Box.push(Object), Box.pop() 코드 딱 1개씩만 존재
④ 런타임 new: Box<Integer> a = new Box<>(42) → 힙에 Box 객체 생성 { value: Object }
⑤ get() 호출: Object 반환 → javac가 자동으로 (Integer) 캐스팅 삽입

핵심: T는 컴파일 타임 타입 체크 도구. 런타임에는 T 정보가 존재하지 않음
💡 JVM 내부 — 코드 1개, 객체는 여러 개
Stack<Integer> s1 = new Stack<>();  Stack<String> s2 = new Stack<>();

JVM Method Area (공장): Stack.push(Object) ← 하나만 존재 / push(Integer) → 없음
힙 (Heap): s1 → Stack 객체 #1    s2 → Stack 객체 #2

결론: 코드(공장) 1개, 객체(상자) 2개 → Java Type Erasure의 핵심

함수 템플릿 · 제네릭 메서드

함수/메서드에도 타입 파라미터를 적용할 수 있다. Java는 객체에 > 연산자를 사용할 수 없어 Comparable 제약이 필요하다. Rust는 PartialOrd trait bound로 명시한다.

☕ Java
 1class Util {
 2    // Generic method: <T extends Comparable<T>>
 3    // Cannot use > on objects, must use compareTo
 4    public static <T extends Comparable<T>>
 5    T maxValue(T a, T b) {
 6        return (a.compareTo(b) > 0) ? a : b;
 7    }
 8}
 9public class Main {
10    public static void main(String[] args) {
11        System.out.println(
12            Util.maxValue(3, 7));       // 7
13        System.out.println(
14            Util.maxValue(2.1, 5.4));   // 5.4
15        System.out.println(
16            Util.maxValue("apple",
17                          "banana")); // banana
18    }
19}
C++
 1#include <iostream>
 2#include <string>
 3
 4// Template function: T must support operator>
 5// No explicit constraint in C++17
 6// Compile error occurs at point of use if T
 7// does not support operator>
 8template<typename T>
 9T max_value(T a, T b) {
10    return (a > b) ? a : b;
11}
12
13int main() {
14    std::cout << max_value(3, 7)
15              << "\n";        // 7
16    std::cout << max_value(2.1, 5.4)
17              << "\n";        // 5.4
18    std::cout << max_value(
19        std::string("apple"),
20        std::string("banana"))
21              << "\n";        // banana
22    return 0;
23}
Rust
 1use std::fmt::Display;
 2
 3// Rust: trait bound PartialOrd + Display
 4// Explicit constraint at declaration site
 5fn max_value<T: PartialOrd + Display>(
 6    a: T, b: T
 7) -> T {
 8    if a > b { a } else { b }
 9}
10
11fn main() {
12    println!("{}", max_value(3i32, 7));
13    println!("{}", max_value(2.1f64, 5.4));
14    println!("{}", max_value("apple", "banana"));
15    // Monomorphization: separate code per type
16    // Zero runtime overhead
17}

타입 제한 — C++17 static_assert · Java extends · Rust trait bound

타입 제한 방식 비교 — static_assert · extends · trait bound개요• T에 제약을 거는 방식 — 세 언어 모두 컴파일 타임 검사, 문법과 동작은 다름• Java: 선언 시점 상한 경계 C++17: static_assert (사용 시점) Rust: trait bound (impl 블록)① Java — T extends (JLS §4.4)소스 코드class NumberBox<T extends Number> {T value;NumberBox(T v) {this.value = v;}double asDouble() {return value.doubleValue();}}사용 예// ✓ OK — Number 하위NumberBox<Integer> a =new NumberBox<>(10);NumberBox<Double> b =new NumberBox<>(3.14);// ✗ 컴파일 에러NumberBox<String> c =new NumberBox<>("x");// String은 Number 아님!T 소거 후 바이트코드// T extends Number 이므로// 소거 후:class NumberBox {Number value;// T → Number 로 소거double asDouble() {return value.doubleValue();}}핵심 개념• T extends Number: Number와 하위 타입(Integer, Double, Long...) 만 허용• bounded T → Number로 소거 / unbounded T → Object로 소거 (JLS §4.6)• 소거 후에도 Number가 제공하는 메서드(doubleValue 등)를 T 위치에서 호출 가능② C++17 — static_assert (cppreference §static_assert)소스 코드template<typename T>class NumberBox {static_assert(std::is_arithmetic<T>::value,"T must be numeric");T value;NumberBox(T v): value(v) {}double asDouble() const {return (double)value;}};사용 예 & if constexpr// ✓ OKNumberBox<int> a(10);NumberBox<double> b(3.14);// ✗ static_assert 실패NumberBox<string> c("x");// → T must be numeric// if constexpr (C++17 신규):#include <iostream>#include <type_traits>template<typename T>void print(T v) {if constexpr (std::is_arithmetic<T>::value) {std::cout << "num: " << v;} else {std::cout << "other: " << v;}}핵심 개념• concept 키워드는 C++20부터. C++17에서는 static_assert + type_traits 조합• static_assert 실패 시 컴파일 에러 — 사용 시점 검사 (선언 시점 아님)• if constexpr: 컴파일 타임 분기 — 조건 맞는 분기만 컴파일됨 (C++17 신규)• C++20부터는 concept NumberBox<T: std::arithmetic> 처럼 선언 가능③ Rust — trait bound (The Rust Book §10.2)소스 코드use std::ops::Add;use std::fmt::Display;struct NumberBox<T> {value: T,}impl<T:Add<Output=T>+ Display+ Copy>NumberBox<T> {fn doubled(&self) -> T {self.value + self.value}}사용 예 & where 절// ✓ OKlet a = NumberBox {value: 10i32};println!("{}", a.doubled());// → 20// ✗ 컴파일 에러// &str 은 Add 없음// where 절 동일 표현:impl<T> NumberBox<T>where T: Add<Output=T>+ Display + Copy{ ... }핵심 개념• trait bound: T가 반드시 특정 trait을 구현해야 함. + 로 복수 trait 조합• where 절: 긴 bound를 분리해서 가독성 향상. 의미는 동일• Monomorphization 시 bound 위반 → 컴파일 에러 (선언 시점 + 인스턴스화 시점)

C++17: concept은 C++20부터. C++17에서는 static_assert + std::is_arithmetic으로 타입을 제한한다. 제약 위반 시 컴파일 에러.

Java: T extends Number로 선언 시점에 상한 경계를 지정한다 (JLS §4.4). unbounded TObject로 소거, bounded T extends NumberNumber로 소거.

Rust: T: Add + Display + Copy처럼 trait bound를 impl 블록에 명시한다 (The Rust Book §10.2).

☕ Java
 1// Java: T extends Number constrains at declaration
 2class NumberBox<T extends Number> {
 3    private T value;
 4    NumberBox(T v) { this.value = v; }
 5    double asDouble() {
 6        return value.doubleValue();
 7    }
 8    @Override
 9    public String toString() {
10        return "NumberBox(" + value + ")";
11    }
12}
13public class Main {
14    public static void main(String[] args) {
15        NumberBox<Integer> a =
16            new NumberBox<>(10);
17        NumberBox<Double>  b =
18            new NumberBox<>(3.14);
19        System.out.println(a.asDouble()); // 10.0
20        System.out.println(b.asDouble()); // 3.14
21        // NumberBox<String> c =
22        //   new NumberBox<>("x"); // compile error
23    }
24}
C++
 1#include <iostream>
 2#include <type_traits>
 3
 4// C++17: static_assert for type constraint
 5// (concept available in C++20 only)
 6template<typename T>
 7class NumberBox {
 8    static_assert(
 9        std::is_arithmetic<T>::value,
10        "T must be arithmetic type");
11public:
12    T value;
13    NumberBox(T v) : value(v) {}
14    double asDouble() const {
15        return static_cast<double>(value);
16    }
17};
18
19int main() {
20    NumberBox<int>    a(10);
21    NumberBox<double> b(3.14);
22    std::cout << a.asDouble() << "\n"; // 10
23    std::cout << b.asDouble() << "\n"; // 3.14
24    // NumberBox<std::string> c("x");
25    // -> static_assert fails at compile time
26    return 0;
27}
Rust
 1// Rust: trait bound constrains at declaration
 2// std::ops::Add ensures T supports + operator
 3use std::ops::Add;
 4use std::fmt::Display;
 5
 6struct NumberBox<T> {
 7    value: T,
 8}
 9impl<T: Add<Output = T> + Display + Copy>
10NumberBox<T> {
11    fn new(v: T) -> Self {
12        NumberBox { value: v }
13    }
14    fn doubled(&self) -> T {
15        self.value + self.value
16    }
17}
18fn main() {
19    let a = NumberBox::new(10i32);
20    let b = NumberBox::new(3.14f64);
21    println!("{}", a.doubled()); // 20
22    println!("{}", b.doubled()); // 6.28
23    // Constraint checked at compile time
24}

Primitive 타입 처리 · 메모리 구조

Primitive 타입 처리 비교개요• Java만 primitive를 제네릭에 직접 못 씀 — int 대신 Integer(Wrapper 클래스) 사용 강제• C++/Rust: 값 직접 저장 (Boxing 없음) Java: Integer 힙 객체 생성 (Autoboxing 비용)① Java — Wrapper 클래스 필수 (JLS §4.5.1)코드// ✗ 컴파일 에러Stack<int> s1;Stack<double> s2;// ✓ Wrapper 클래스 사용Stack<Integer> s1;Stack<Double> s2;// Autoboxing — 자동 변환s1.push(42);// 내부: Integer.valueOf(42)// → 힙에 Integer 객체 생성힙 메모리 구조// 힙 메모리 구조:ArrayList 내부 Object[]:ref → Integer(10) // 힙 객체ref → Integer(20) // 힙 객체ref → Integer(30) // 힙 객체// 각각 비연속 힙 객체!// 메모리 비교:// Java: 참조 8B × 3// + 객체 16B × 3 = 72B// C++: int × 3 = 12B// Rust: i32 × 3 = 12B핵심 개념• Autoboxing: push(42) → Integer.valueOf(42) 힙 객체 생성 → ArrayList에 참조 저장• Unboxing: get() → Integer 객체 → intValue() → int (역변환도 비용 발생)• integer 캐시: -128~127 범위는 JVM이 캐싱. 그 외 범위는 매번 새 객체 생성② C++17 — primitive 직접 사용코드// ✓ 모두 가능Stack<int> s1;Stack<double> s2;Stack<char> s3;s1.push(10);s1.push(20);// 힙: [10][20] 4B씩// int 값 직접 연속 저장// Boxing 없음특성• vector<int> 힙 버퍼에 int 값 직접 저장 — 연속 메모리, CPU cache 친화적• Integer 힙 객체 없음 → GC 부담 없음, 메모리 효율 최고• Template Instantiation으로 int 전용 최적화 코드 생성③ Rust — primitive 직접 사용코드// ✓ 모두 가능let mut s1: Stack<i32>= Stack::new();let mut s2: Stack<f64>= Stack::new();s1.push(10i32);// Vec<i32> 버퍼:// [10][20] 4B씩 연속// Boxing 없음 — C++와 동일특성• Vec<i32> 버퍼에 i32 값 직접 저장 — boxing 없음, 연속 메모리• GC 없음 — Drop trait으로 스코프 종료 시 힙 버퍼 자동 해제• Monomorphization으로 i32 전용 최적화 코드 생성 — C++와 동일한 성능 메모리 구조 비교 — Stack (스택 선언 기준)개요 — 스택 선언 기준, 요소 [10, 20, 30]• 공통: 구조체/객체 자체는 스택, 동적 배열(vector/Vec/ArrayList) 버퍼는 힙• 차이: Java는 Integer 힙 객체 생성(boxing), C++/Rust는 값 직접 저장(boxing 없음)① Java — Stack<Integer>스택 + 힙 구조[ 스택 메모리 ]Stack s (참조변수)→ 4/8 bytes (주소만)→ 힙의 Stack 객체 가리킴[ 힙 메모리 ]Stack 객체→ ArrayList 참조ArrayList 내부 Object[][ref1][ref2][ref3]↓ ↓ ↓Integer(10)Integer(20)Integer(30)← 각 Integer = 별도 힙 객체Stack(Object) 핵심 설명• Stack(Object): ArrayList 내부 배열이 Object[] — 각 요소가 Integer 객체를 가리키는 참조• Integer 객체는 각각 힙에 비연속으로 위치 — cache miss 발생 가능• 메모리: 참조 8B×3 + Integer 객체 16B×3 = 약 72B (vs C++/Rust: int×3 = 12B)• GC 대상: Integer 객체 3개 + ArrayList + Stack 객체 모두 힙 → GC 부담공장(클래스 코드) vs 상자(객체)// 공장(클래스 코드) vs 상자(객체)Stack<Integer> s1 = new Stack<>();Stack<String> s2 = new Stack<>();// JVM Method Area (공장) — 1개만 존재Stack.push(Object) ← 하나만 존재Stack.pop() ← 하나만 존재// 힙(Heap) — 상자는 각각 생성s1 → Stack 객체 #1 { value: Object }s2 → Stack 객체 #2 { value: Object }// 공장 1개, 상자 2개pop() 자동 캐스팅// pop() 시 자동 캐스팅 발생s1.push(10); // int→Integer boxings2.push("hello"); // String 그대로// 개발자 코드:Integer x = s1.pop();String y = s2.pop();// javac 실제 변환:Integer x = (Integer) s1.pop();String y = (String) s2.pop();// 캐스팅 자동 삽입!② C++17 — Stack<int> (std::vector 기반)스택 + 힙 구조[ 스택 메모리 ]Stack s (지역변수)vector<int> {ptr → 힙 버퍼 주소,len = 3,cap = 4}[ 힙 메모리 ] vector 버퍼+----+----+----+----+| 10 | 20 | 30 | .. |+----+----+----+----+ 4B 4B 4B 4B← int 값 직접 연속 저장← 총 12 bytes핵심 설명• ptr이 힙 버퍼를 가리킴. ptr/len/cap 3필드가 스택의 Stack 객체에 저장됨• int 값이 힙 버퍼에 직접 연속 저장 — boxing 없음, CPU cache 친화적• Stack 소멸 시 소멸자(~Stack)가 vector 힙 버퍼 자동 해제 — GC 불필요③ Rust — Stack<i32> (Vec<T> 기반)스택 + 힙 구조[ 스택 메모리 ]let mut s: Stack<i32>Vec<i32> {ptr: *mut i32, // 힙 주소len: usize, // = 3cap: usize, // = 4}[ 힙 메모리 ] Vec 버퍼+----+----+----+----+| 10 | 20 | 30 | .. |+----+----+----+----+ 4B 4B 4B 4B← i32 값 직접 연속 저장← 총 12 bytes핵심 설명• C++와 동일한 메모리 구조: ptr/len/cap 스택, 버퍼 힙, 값 직접 저장• i32 값이 힙 버퍼에 직접 연속 저장 — boxing 없음, GC 없음• 소유권: Stack이 스코프 벗어나면 Drop trait 자동 호출 → 힙 버퍼 해제

C++/Rust: primitive 타입을 제네릭 파라미터로 직접 사용 가능. vector<int>/Vec<i32>는 값을 힙 버퍼에 직접 저장한다 (boxing 없음).

Java: Box<int> 불가 (JLS §4.5.1). Wrapper class 필수. Integer 각각이 힙 객체 → boxing overhead 발생.

메모리 도식에서 Stack 구조체/객체는 스택에 선언한 경우 기준. 내부 버퍼(vector/Vec/ArrayList)는 힙에 위치.

☕ Java
 1import java.util.ArrayList;
 2import java.util.List;
 3
 4// Java: cannot use primitive types directly
 5// int, double -> must use wrapper classes
 6class Container<T> {
 7    private List<T> items = new ArrayList<>();
 8    void add(T v) { items.add(v); }
 9    T get(int i) { return items.get(i); }
10    int size() { return items.size(); }
11}
12public class Main {
13    public static void main(String[] args) {
14        // Container<int> -> compile error
15        Container<Integer> a = new Container<>();
16        Container<Double>  b = new Container<>();
17        a.add(10);   // autoboxing: int -> Integer
18        a.add(20);
19        b.add(3.14); // autoboxing: double -> Double
20        System.out.println(a.get(0)); // 10
21        System.out.println(b.get(0)); // 3.14
22        // Integer objects stored on heap (boxing)
23    }
24}
C++
 1#include <iostream>
 2#include <vector>
 3
 4// C++: primitive types work directly
 5// No boxing overhead
 6template<typename T>
 7class Container {
 8    std::vector<T> items;
 9public:
10    void add(T v) { items.push_back(v); }
11    T get(int i) const { return items[i]; }
12    size_t size() const { return items.size(); }
13};
14
15int main() {
16    Container<int>    a; // int stored directly
17    Container<double> b; // double stored directly
18    a.add(10);
19    a.add(20);
20    b.add(3.14);
21    std::cout << a.get(0) << "\n"; // 10
22    std::cout << b.get(0) << "\n"; // 3.14
23    // Values stored directly in vector buffer
24    // No boxing, no heap allocation per element
25    return 0;
26}
Rust
 1// Rust: primitive types work directly
 2// No boxing overhead (same as C++)
 3struct Container<T> {
 4    items: Vec<T>,
 5}
 6impl<T: std::fmt::Display> Container<T> {
 7    fn new() -> Self {
 8        Container { items: Vec::new() }
 9    }
10    fn add(&mut self, v: T) {
11        self.items.push(v);
12    }
13    fn get(&self, i: usize) -> &T {
14        &self.items[i]
15    }
16}
17fn main() {
18    let mut a: Container<i32> = Container::new();
19    let mut b: Container<f64> = Container::new();
20    a.add(10i32); // i32 stored directly in Vec
21    a.add(20i32);
22    b.add(3.14f64);
23    println!("{}", a.get(0)); // 10
24    println!("{}", b.get(0)); // 3.14
25    // No boxing, values in heap buffer directly
26}

런타임 타입 정보

컴파일 파이프라인 — 런타임 타입 정보컴파일 파이프라인 비교• Java: T 소거 후 단일 바이트코드 → JVM 실행 | C++/Rust: 타입별 독립 기계어 → CPU 직접 실행① Java소스Stack<Integer>javac 컴파일Type Erasure 적용T → ObjectStack(Object) 변환바이트코드Box.class 단 하나JVM Method Areapush(Object) 코드 1개JVM 실행캐스팅 자동 처리런타임T 정보 없음 ✗② C++17소스Stack<int>g++ -std=c++17Template Instantiationint 전용 코드 생성별도 기계어 작성.text 영역int 전용 코드 존재CPU 직접 실행직접 CALL 명령어런타임typeid 보존 ✓③ Rust소스Stack<i32>rustcMonomorphizationi32 전용 코드 생성별도 기계어 작성.text 영역i32 전용 코드 존재CPU 직접 실행직접 CALL 명령어런타임TypeId 보존 ✓결과 비교항목JavaC++17Rust바이너리 코드단 하나 (소거)타입별 독립타입별 독립런타임 T 정보없음 ✗typeid 보존 ✓TypeId 보존 ✓.text 크기타입 수 무관타입 수만큼 증가타입 수만큼 증가캐스팅 삽입컴파일러 자동없음없음인라인 최적화제한적가능가능실행 방식JVM 바이트코드CPU 직접 실행CPU 직접 실행

Java: Type Erasure로 List<String>List<Integer>는 런타임에 같은 클래스 (getClass() == getClass()true). T가 무엇인지 런타임에 알 수 없다.

C++: Template Instantiation으로 타입별 독립 코드 → typeid(Stack<int>) != typeid(Stack<string>).

Rust: Monomorphization으로 C++와 동일. TypeId::of::<Vec<i32>>() != TypeId::of::<Vec<String>>().

☕ Java
 1import java.util.ArrayList;
 2import java.util.List;
 3
 4public class Main {
 5    public static void main(String[] args) {
 6        List<String>  a = new ArrayList<>();
 7        List<Integer> b = new ArrayList<>();
 8        a.add("hello");
 9        b.add(42);
10        // Type Erasure: at runtime both are
11        // just ArrayList - T info is gone
12        System.out.println(
13            a.getClass() == b.getClass()); // true
14        System.out.println(
15            a.getClass().getName());
16            // java.util.ArrayList
17        // Cannot do: if (a instanceof List<String>)
18        // T is erased at runtime
19    }
20}
C++
 1#include <iostream>
 2#include <typeinfo>
 3#include <vector>
 4#include <string>
 5
 6int main() {
 7    std::vector<int>         a;
 8    std::vector<std::string> b;
 9    a.push_back(42);
10    b.push_back("hello");
11    // Template Instantiation: each type generates
12    // separate code -> truly different types
13    bool same = (typeid(a) == typeid(b));
14    std::cout << std::boolalpha
15              << same << "\n"; // false
16    std::cout << typeid(a).name() << "\n";
17    std::cout << typeid(b).name() << "\n";
18    return 0;
19}
Rust
 1use std::any::TypeId;
 2
 3fn main() {
 4    // Monomorphization: each type generates
 5    // separate code -> different TypeIds
 6    let id_i32 = TypeId::of::<Vec<i32>>();
 7    let id_str = TypeId::of::<Vec<String>>();
 8    println!("{}", id_i32 == id_str); // false
 9
10    // Unlike Java, type info is preserved
11    // at compile time (no type erasure)
12    println!("Vec<i32> != Vec<String>");
13    // Each has separate monomorphized code
14    // in the binary, just like C++
15}
💡 pop() 자동 캐스팅 — 왜 ClassCastException이 발생하나
개발자 코드: Integer x = s1.pop();
javac 실제 변환: Integer x = (Integer) s1.pop(); ← 캐스팅 자동 삽입

정상: s1에 Integer push → (Integer) 캐스팅 성공
에러: s1에 String push 후 Integer로 꺼내면 → ClassCastException

컴파일 타임 타입 체크로 런타임 에러를 방지하는 것이 Java 제네릭의 핵심 목적.

실전 — Stack<T> 완전 구현

앞에서 배운 개념을 모두 적용한 완전한 Stack 구현. C++는 std::move로 불필요한 복사를 제거. Rust는 pop()Option<T>를 반환해 빈 스택 접근을 컴파일 타임에 강제 처리한다.

☕ Java
 1import java.util.ArrayList;
 2import java.util.List;
 3import java.util.EmptyStackException;
 4
 5class Stack<T> {
 6    private List<T> data = new ArrayList<>();
 7    void push(T v) { data.add(v); }
 8    T pop() {
 9        if (data.isEmpty())
10            throw new EmptyStackException();
11        return data.remove(data.size() - 1);
12    }
13    T peek() {
14        if (data.isEmpty())
15            throw new EmptyStackException();
16        return data.get(data.size() - 1);
17    }
18    boolean isEmpty() { return data.isEmpty(); }
19    int size() { return data.size(); }
20}
21public class Main {
22    public static void main(String[] args) {
23        Stack<Integer> s1 = new Stack<>();
24        s1.push(10); s1.push(20); s1.push(30);
25        System.out.println(s1.pop()); // 30
26        System.out.println(s1.peek()); // 20
27        Stack<String> s2 = new Stack<>();
28        s2.push("a"); s2.push("b");
29        System.out.println(s2.pop()); // b
30    }
31}
C++
 1#include <iostream>
 2#include <vector>
 3#include <string>
 4#include <stdexcept>
 5
 6template<typename T>
 7class Stack {
 8    std::vector<T> data;
 9public:
10    void push(T v) {
11        data.push_back(std::move(v));
12    }
13    T pop() {
14        if (data.empty())
15            throw std::runtime_error(
16                "Stack is empty");
17        T v = std::move(data.back());
18        data.pop_back();
19        return v;
20    }
21    const T& peek() const {
22        if (data.empty())
23            throw std::runtime_error(
24                "Stack is empty");
25        return data.back();
26    }
27    bool empty() const { return data.empty(); }
28    size_t size() const { return data.size(); }
29};
30
31int main() {
32    Stack<int> s1;
33    s1.push(10); s1.push(20); s1.push(30);
34    std::cout << s1.pop()  << "\n"; // 30
35    std::cout << s1.peek() << "\n"; // 20
36    Stack<std::string> s2;
37    s2.push("a"); s2.push("b");
38    std::cout << s2.pop() << "\n";  // b
39    return 0;
40}
Rust
 1struct Stack<T> {
 2    data: Vec<T>,
 3}
 4impl<T: std::fmt::Display> Stack<T> {
 5    fn new() -> Self {
 6        Stack { data: Vec::new() }
 7    }
 8    fn push(&mut self, v: T) {
 9        self.data.push(v);
10    }
11    fn pop(&mut self) -> Option<T> {
12        self.data.pop()
13    }
14    fn peek(&self) -> Option<&T> {
15        self.data.last()
16    }
17    fn is_empty(&self) -> bool {
18        self.data.is_empty()
19    }
20    fn size(&self) -> usize {
21        self.data.len()
22    }
23}
24fn main() {
25    let mut s1: Stack<i32> = Stack::new();
26    s1.push(10); s1.push(20); s1.push(30);
27    println!("{}", s1.pop().unwrap());  // 30
28    println!("{}", s1.peek().unwrap()); // 20
29    let mut s2: Stack<String> = Stack::new();
30    s2.push(String::from("a"));
31    s2.push(String::from("b"));
32    println!("{}", s2.pop().unwrap());  // b
33}

Rust — 정적 디스패치 vs 동적 디스패치 (&dyn · Fat Pointer)

Rust — 정적 디스패치 vs 동적 디스패치 (Fat Pointer)개요• 동일한 Circle, Rect 구조체를 정적(&T) 또는 동적(&dyn) 으로 다루는 방법• 정적: 컴파일 타임 결정 — Monomorphization, 직접 CALL, 이종 컬렉션 불가• 동적: 런타임 결정 — Fat Pointer 16B, vtable 간접 참조, 이종 컬렉션 가능공통 코드 — trait Shape, struct Circle, struct Recttrait & 구조체 정의trait Shape {fn area(&self) -> f64;fn name(&self) -> &str;}struct Circle { radius: f64 }struct Rect { w: f64, h: f64 }Circle 구현impl Shape for Circle {fn area(&self) -> f64 {std::f64::consts::PI* self.radius * self.radius}fn name(&self) -> &str {"Circle"}}// impl Shape for Rect { ... }① 정적 디스패치 — fn draw<T: Shape>(s: &T)코드// 정적 디스패치 함수fn draw<T: Shape>(s: &T) {println!("{}: {:.2}",s.name(), s.area());}// 사용let c = Circle { radius: 3.0 };let r = Rect { w: 4.0, h: 5.0 };draw(&c); // ✓ 정적draw(&r); // ✓ 정적컴파일 결과 (개념)// Monomorphization 결과 (개념):fn draw_Circle(s: &Circle) {println!("{}: {:.2}",s.name(), s.area());}fn draw_Rect(s: &Rect) {println!("{}: {:.2}",s.name(), s.area());}// 직접 CALL draw_Circle// vtable 없음, 인라인 가능정적 디스패치 특성• &c: thin pointer (8B) — Circle 객체 주소만 담음• CALL draw_Circle 직접 — vtable 조회 없음, 최고 성능• 이종 컬렉션 불가: Vec<&T>는 하나의 타입만 → Circle과 Rect 함께 못 넣음• 컴파일 타임에 T=Circle 확정 → 인라인 최적화, zero-cost abstraction② 동적 디스패치 — fn draw(s: &dyn Shape) → Fat Pointer코드 — 이종 컬렉션// 동적 디스패치 함수fn draw(s: &dyn Shape) {// &dyn Shape = Fat Pointer 16B// runtime vtable 조회println!("{}: {:.2}",s.name(), s.area());}// 이종 컬렉션!let shapes: Vec<Box<dyn Shape>>= vec![Box::new(Circle{radius:3.0}),Box::new(Rect{w:4.0,h:5.0}),];for s in &shapes {draw(s.as_ref()); // 동적}Fat Pointer 구조 상세// &dyn Shape 구조 (16 bytes):+------------------+| data ptr (8B) || → Circle 실제 주소|+------------------+| vtable ptr (8B) || → vtable 주소 |+------------------+vtable for Circle:+-------------------+| drop ptr || size_of::<Circle> || align_of::<Circle>|| area() fn ptr ← ─|| name() fn ptr ← ─|+-------------------+// runtime: vtable→area()→CALL동적 디스패치 & Fat Pointer 핵심• data ptr (8B): 실제 객체 (Circle 또는 Rect) 메모리 주소• vtable ptr (8B): 해당 타입의 vtable 주소 → drop/size/align/메서드 포인터 테이블• 런타임: s.area() → vtable ptr → vtable → area() fn ptr → CALL (간접 참조 2회)• 이종 컬렉션 가능: Vec<Box<dyn Shape>>에 Circle, Rect 함께 저장 가능• vtable 간접 참조 비용 발생, 인라인 최적화 불가 → 정적 디스패치보다 느림• The Rust Reference §"Trait Objects"정적 vs 동적 비교항목정적 디스패치 fn<T: Shape>동적 디스패치 &dyn Shape포인터 크기thin ptr 8Bfat ptr 16B타입 결정컴파일 타임런타임함수 호출직접 CALLvtable 간접 참조성능최고 (인라인 가능)vtable 오버헤드이종 컬렉션불가 ✗가능 ✓코드 크기타입마다 증가단일 함수

Rust는 C++처럼 정적 디스패치(Monomorphization)가 기본이지만, &dyn Trait을 통해 Java처럼 동적 디스패치도 선택 가능하다.

&dyn TraitFat Pointer로 구현된다 (The Rust Reference §Trait Objects): data pointer(8B) + vtable pointer(8B) = 16B. vtable에는 destructor, size, alignment, 메서드 포인터가 저장된다.

이종 컬렉션(Vec<Box<dyn Shape>>)은 동적 디스패치로만 가능하다. 개발자가 명시적으로 선택한다.

Rust
 1use std::fmt::Display;
 2
 3trait Shape {
 4    fn area(&self) -> f64;
 5    fn name(&self) -> &str;
 6}
 7struct Circle { radius: f64 }
 8struct Rect   { width: f64, height: f64 }
 9
10impl Shape for Circle {
11    fn area(&self) -> f64 {
12        std::f64::consts::PI * self.radius
13            * self.radius
14    }
15    fn name(&self) -> &str { "Circle" }
16}
17impl Shape for Rect {
18    fn area(&self) -> f64 {
19        self.width * self.height
20    }
21    fn name(&self) -> &str { "Rect" }
22}
23
24// Static dispatch: monomorphization
25// Compiler generates separate code per type
26// Zero runtime overhead
27fn print_area<T: Shape>(shape: &T) {
28    println!("{}: area = {:.2}",
29             shape.name(), shape.area());
30}
31
32fn main() {
33    let c = Circle { radius: 3.0 };
34    let r = Rect { width: 4.0, height: 5.0 };
35    print_area(&c); // Circle: area = 28.27
36    print_area(&r); // Rect:   area = 20.00
37}
Rust
 1trait Shape {
 2    fn area(&self) -> f64;
 3    fn name(&self) -> &str;
 4}
 5struct Circle { radius: f64 }
 6struct Rect   { width: f64, height: f64 }
 7
 8impl Shape for Circle {
 9    fn area(&self) -> f64 {
10        std::f64::consts::PI * self.radius
11            * self.radius
12    }
13    fn name(&self) -> &str { "Circle" }
14}
15impl Shape for Rect {
16    fn area(&self) -> f64 {
17        self.width * self.height
18    }
19    fn name(&self) -> &str { "Rect" }
20}
21
22// Dynamic dispatch: fat pointer (&dyn Trait)
23// data ptr (8B) + vtable ptr (8B) = 16B
24// vtable resolved at runtime
25fn print_area_dyn(shape: &dyn Shape) {
26    println!("{}: area = {:.2}",
27             shape.name(), shape.area());
28}
29
30fn main() {
31    // Heterogeneous collection: only with dyn
32    let shapes: Vec<Box<dyn Shape>> = vec![
33        Box::new(Circle { radius: 3.0 }),
34        Box::new(Rect { width: 4.0,
35                        height: 5.0 }),
36    ];
37    for s in &shapes {
38        print_area_dyn(s.as_ref());
39    }
40    // Circle: area = 28.27
41    // Rect:   area = 20.00
42}
💡 정적 vs 동적 선택 기준
정적 (<T: Trait>): 타입이 컴파일 타임에 확정, 이종 컬렉션 불필요 → 성능 최우선
동적 (&dyn Trait): 런타임에 타입 결정, 이종 컬렉션 필요 → 유연성 우선
C++는 virtual으로 동적, Rust는 명시적 dyn으로 동적을 선택

String vs &str — 소유권과 뷰

Java는 String 하나뿐이지만 Rust는 소유하는 String빌리는 &str이 명확히 구분된다. C++17의 string_view와 유사하지만 Rust는 잘못된 사용을 컴파일 타임에 차단한다.

💡 String vs &str 핵심
String: 힙 할당 · 소유권 있음 · 가변 · String::from("hello")
&str: 슬라이스(뷰) · 소유권 없음 · 불변 · "hello" 또는 &owned

Java의 String은 Rust의 String에 대응 — 단 Java는 GC, Rust는 소유권
Java에는 &str에 해당하는 개념이 없음 — 모든 String이 힙 객체
C++의 string_view&str과 유사하지만 C++은 dangling을 런타임에서야 발견
☕ Java
 1// Java: String 하나만 존재, GC 관리
 2// 문자열 리터럴은 String Pool에 캐시됨
 3public class Main {
 4    public static void main(String[] args) {
 5        // 리터럴 → String Pool (같은 객체)
 6        String s1 = "hello";
 7        String s2 = "hello";
 8        System.out.println(s1 == s2); // true (Pool)
 9
10        // new → 힙에 새 객체
11        String s3 = new String("hello");
12        System.out.println(s1 == s3); // false (다른 주소)
13        System.out.println(
14            s1.equals(s3)); // true
15
16        // 연결 → 새 String 생성
17        String s4 = s1 + " world";
18        System.out.println(s4); // hello world
19    }
20}
C++
 1#include <iostream>
 2#include <string>
 3#include <string_view>
 4using namespace std;
 5
 6int main() {
 7    // std::string: 힙 할당, 소유권 있음
 8    string owned = "hello";
 9
10    // string_view: 뷰, 소유권 없음 (C++17)
11    string_view view1 = owned;     // String → 뷰
12    string_view view2 = "world";   // 리터럴 → 뷰
13
14    cout << owned << endl;  // hello
15    cout << view1 << endl;  // hello
16    cout << view2 << endl;  // world
17
18    // owned 변경 → view1 무효화 위험!
19    // (Rust는 컴파일 타임에 차단)
20    owned += "!";
21    // view1 사용 = UB (미정의 동작)
22    return 0;
23}
Rust
 1fn main() {
 2    // String: 힙 할당, 소유권 있음, 가변
 3    let mut owned: String = String::from("hello");
 4
 5    // &str: 슬라이스(뷰), 소유권 없음, 불변
 6    let literal: &str = "world";        // 정적 메모리
 7    let view: &str    = &owned;         // owned의 뷰
 8
 9    println!("{} {}", owned, literal);  // hello world
10    println!("view: {}", view);         // hello
11
12    // owned 변경 시 컴파일러가 view 사용 차단
13    owned.push_str("!");
14    // println!("{}", view); // 컴파일 에러!
15    // owned 변경됨 → view 무효
16
17    println!("owned: {}", owned);       // hello!
18}

메모리 구조 — 실제로 어떻게 저장되나

String과 &str의 내부 구조를 이해하면 언제 복사가 발생하고 언제 발생하지 않는지, 왜 &str이 더 효율적인 파라미터인지 알 수 있다.

💡 메모리 구조 비교
Java String: Object Header(12B) + 필드 + 내부 byte[] → 힙 객체, 슬라이스 없음
C++ string: SSO — 15자 이하 스택 저장, 초과 시 ptr+len+cap 힙 할당
Rust String: ptr(8B)+len(8B)+cap(8B) = 24B 스택 | 실제 데이터는 힙
Rust &str: ptr(8B)+len(8B) = 16B fat pointer | 소유권 없음, 복사 없음

핵심: &str 슬라이스는 복사 없이 기존 메모리를 가리키는 뷰 — 매우 효율적
☕ Java
 1// Java String 메모리 구조
 2// Object Header(12B) + 필드 + 내부 byte[] 참조
 3public class Main {
 4    public static void main(String[] args) {
 5        String s = "hello";
 6        // 힙: [ Header 12B | coder | hash | value → ]
 7        //                               ↓
 8        //                         byte[] {h,e,l,l,o}
 9
10        System.out.println(s.length());    // 5
11        System.out.println(s.charAt(0));   // h
12
13        // Java 9+: Latin-1(1B/char) 또는 UTF-16(2B/char)
14        // 슬라이스 없음 — 항상 새 String 생성
15        String sub = s.substring(1, 3);    // "el" 새 객체
16        System.out.println(sub);           // el
17    }
18}
C++
 1#include <iostream>
 2#include <string>
 3#include <string_view>
 4using namespace std;
 5
 6int main() {
 7    // SSO(Small String Optimization): 15자 이하 스택
 8    string small = "hi";      // 스택 직접 저장
 9    string large = "hello world this is long string";
10
11    // string: ptr(8B)+len(8B)+cap(8B) 또는 SSO 버퍼
12    cout << small.size() << endl;   // 2
13    cout << large.size() << endl;   // 31
14
15    // string_view: ptr(8B)+len(8B) = 16B 고정
16    string_view sv = large;
17    cout << sv.size()   << endl;    // 31
18    cout << sv.substr(0, 5) << endl; // hello (뷰)
19    return 0;
20}
Rust
 1use std::mem;
 2
 3fn main() {
 4    let owned  = String::from("hello");
 5    let slice: &str = "hello";
 6
 7    // String: { ptr(8B), len(8B), cap(8B) } = 24B
 8    // 데이터는 힙에 저장
 9    println!("String 크기: {}B",
10        mem::size_of::<String>());     // 24
11
12    // &str: { ptr(8B), len(8B) } = 16B (fat pointer)
13    // 데이터를 소유하지 않음 — 어딘가를 가리킬 뿐
14    println!("&str 크기: {}B",
15        mem::size_of::<&str>());       // 16
16
17    // 슬라이스: 복사 없이 뷰 생성
18    let hello: &str = &owned[0..5];   // 뷰 (0비용)
19    println!("슬라이스: {}", hello);   // hello
20
21    // String → &str: Deref coercion (0비용)
22    let s: &str = &owned;
23    println!("뷰: {}", s);            // hello
24}

함수 파라미터 — &str vs String 선택 기준

Rust 함수 설계에서 파라미터를 &str로 받으면 String · &str · 슬라이스 모두 인자로 전달할 수 있다 (Deref coercion). 새 문자열을 생성해서 반환할 때는 String을 반환한다.

💡 파라미터 선택 기준
파라미터: &str 권장 — String · &str · 슬라이스 모두 수용, 복사 없음
파라미터: String — 함수 내부에서 소유권 필요 시 (저장, 수정, 이동)
반환: String — 새로 생성한 문자열 반환 (format! 등)
반환: &str — 입력의 일부를 반환 시 (라이프타임 자동 추론 or 명시)

Java는 항상 String 파라미터 — 내부적으로 참조 전달이지만 슬라이스 불가
☕ Java
 1// Java: 항상 String — &str 개념 없음
 2public class Main {
 3    // String 파라미터 — 참조 전달 (복사 없음)
 4    static int countChar(String s, char c) {
 5        int count = 0;
 6        for (char ch : s.toCharArray()) {
 7            if (ch == c) count++;
 8        }
 9        return count;
10    }
11
12    // String 반환 — 새 객체 또는 기존 참조
13    static String greet(String name) {
14        return "Hello, " + name + "!"; // 새 String
15    }
16
17    public static void main(String[] args) {
18        String s = "hello world";
19        System.out.println(countChar(s, 'l')); // 3
20        System.out.println(
21            greet("Rust")); // Hello, Rust!
22    }
23}
C++
 1#include <iostream>
 2#include <string>
 3#include <string_view>
 4using namespace std;
 5
 6// string_view 권장: String, 리터럴, 슬라이스 모두 수용
 7int countChar(string_view s, char c) {
 8    int count = 0;
 9    for (char ch : s) {
10        if (ch == c) count++;
11    }
12    return count;
13}
14
15// 새 문자열 생성 시 string 반환
16string greet(string_view name) {
17    return string("Hello, ") + string(name) + "!";
18}
19
20int main() {
21    string owned = "hello world";
22    cout << countChar(owned, 'l') << endl;    // 3
23    cout << countChar("hello", 'l')
24         << endl;  // 2 (리터럴)
25    cout << greet("Rust") << endl; // Hello, Rust!
26    return 0;
27}
Rust
 1// Rust: 파라미터는 &str 권장 (가장 유연)
 2fn count_char(s: &str, c: char) -> usize {
 3    s.chars().filter(|&ch| ch == c).count()
 4}
 5
 6// 새 문자열 생성 시 String 반환
 7fn greet(name: &str) -> String {
 8    format!("Hello, {}!", name)
 9}
10
11fn main() {
12    let owned = String::from("hello world");
13    let literal = "hello";
14
15    // String → &str 자동 변환 (Deref coercion)
16    println!("{}", count_char(&owned, 'l'));    // 3
17    // &str 그대로 전달
18    println!("{}", count_char(literal, 'l'));   // 2
19    // 슬라이스도 전달 가능
20    println!("{}", count_char(&owned[0..5], 'l')); // 2
21
22    println!("{}", greet("Rust")); // Hello, Rust!
23    println!("{}", greet(&owned));
24}

구조체 필드와 라이프타임

구조체에 문자열 참조(&str)를 필드로 저장하면 라이프타임 파라미터가 필요하다. Java는 GC가 처리하고 C++은 dangling 위험이 있지만 Rust는 컴파일 타임에 안전을 보장한다.

💡 구조체에 &str 필드를 쓰면
struct Parser<'a> { input: &'a str }
'a = 'input이 Parser보다 오래 살아야 한다'는 컴파일러와의 계약

C++의 string_view 필드와 동일한 개념 — 단 C++은 위반 시 UB, Rust는 컴파일 에러
간단하게 하려면 필드를 String으로 — 소유권 가져오면 라이프타임 불필요
Java는 GC가 자동 관리 — 수명 걱정 없음
☕ Java
 1// Java: 구조체 필드에 String — GC 자동 관리
 2public class Parser {
 3    private String input;  // 소유, GC 관리
 4
 5    public Parser(String input) {
 6        this.input = input; // 참조 저장
 7    }
 8
 9    // substring → 새 String (항상 안전)
10    public String getWord(int start, int end) {
11        return input.substring(start, end);
12    }
13
14    public static void main(String[] args) {
15        Parser p = new Parser("hello world");
16        System.out.println(p.getWord(0, 5));  // hello
17        System.out.println(p.getWord(6, 11)); // world
18        // GC가 모두 처리 — 수명 걱정 없음
19    }
20}
C++
 1#include <iostream>
 2#include <string>
 3#include <string_view>
 4using namespace std;
 5
 6// C++: string_view 필드 — 원본 수명 의존 (위험!)
 7struct Parser {
 8    string_view input; // 뷰 저장 — 원본이 살아있어야 함
 9
10    Parser(string_view s) : input(s) {}
11
12    string_view getWord(size_t start, size_t end) {
13        return input.substr(start, end - start);
14    }
15};
16
17int main() {
18    string text = "hello world";
19    Parser p(text);  // text가 p보다 오래 살아야 함
20    cout << p.getWord(0, 5)  << endl; // hello
21    cout << p.getWord(6, 11) << endl; // world
22    // text 소멸 후 p 사용 = dangling (UB)
23    return 0;
24}
Rust
 1// Rust: &str 필드 → 라이프타임 명시 필수
 2struct Parser<'a> {
 3    input: &'a str,  // 'a: input이 Parser보다 오래 살아야
 4}
 5
 6impl<'a> Parser<'a> {
 7    fn new(input: &'a str) -> Self {
 8        Parser { input }
 9    }
10
11    // 반환값도 'a 수명 — input의 일부를 반환
12    fn get_word(&self,
13        start: usize, end: usize) -> &str {
14        &self.input[start..end]
15    }
16}
17
18fn main() {
19    let text = String::from("hello world");
20    let p = Parser::new(&text);
21
22    println!("{}", p.get_word(0, 5));   // hello
23    println!("{}", p.get_word(6, 11));  // world
24    // text보다 p가 먼저 소멸 → 안전
25    // text 소멸 후 p 사용 → 컴파일 에러로 차단
26}

라이프타임 파라미터 — 컴파일러와의 계약

여러 참조를 받아 그 중 하나를 반환하는 함수에서 라이프타임 파라미터가 필수다. 컴파일러는 반환값이 어떤 입력에서 왔는지 알아야 메모리 안전을 보장할 수 있다. C++은 이를 추적하지 않아 dangling 위험이 항상 존재한다.

💡 라이프타임 파라미터가 필요한 이유
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
→ 반환값의 수명 = x와 y 중 짧은 것 — 컴파일러에게 '이 참조는 이만큼 산다' 알림

Java: String 반환 → 새 객체 또는 GC 관리 → 수명 문제 없음
C++: string_view 반환 → 수명 추적 안 함 → dangling 위험 (런타임 버그)
Rust: 잘못된 수명 → 컴파일 에러 → 런타임 버그 불가능
☕ Java
 1// Java: 반환값이 String이므로 수명 문제 없음
 2public class Main {
 3    // 더 긴 문자열 반환
 4    static String longest(String x, String y) {
 5        return x.length() >= y.length() ? x : y;
 6    }
 7
 8    public static void main(String[] args) {
 9        String s1 = "long string is here";
10        String result;
11        {
12            String s2 = "short";
13            // s2가 스코프 벗어나도 GC가 보호
14            result = longest(s1, s2);
15        }
16        // 안전 — GC가 result 유지
17        System.out.println(result);
18    }
19}
C++
 1#include <iostream>
 2#include <string_view>
 3using namespace std;
 4
 5// C++: 반환 string_view — 수명 추적 안 함!
 6string_view longest(string_view x, string_view y) {
 7    return x.size() >= y.size() ? x : y;
 8}
 9
10int main() {
11    string s1 = "long string is here";
12    string_view result;
13    {
14        string s2 = "short";
15        result = longest(s1, s2);
16    } // s2 소멸!
17    // result가 s2를 가리킬 경우 dangling
18    // 컴파일러가 감지하지 못함 → 런타임 버그
19    cout << result << endl; // UB 가능
20    return 0;
21}
Rust
 1// Rust: 라이프타임으로 컴파일 타임에 안전 보장
 2// 'a = x와 y 중 짧은 수명, 반환값도 그 수명
 3fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
 4    if x.len() >= y.len() { x } else { y }
 5}
 6
 7fn main() {
 8    let s1 = String::from("long string is here");
 9    let result;
10    {
11        let s2 = String::from("short");
12        result = longest(&s1, &s2);
13        println!("{}", result); // OK — s2 살아있음
14    } // s2 소멸
15
16    // println!("{}", result); // 컴파일 에러!
17    // s2 소멸 → result 사용 불가 (컴파일 타임 차단)
18
19    // s1만 비교 시 안전
20    let r2 = longest(&s1, "tiny");
21    println!("{}", r2); // long string is here
22}

실전 — 라이프타임 + 트레이트 바운드

라이프타임과 트레이트 바운드를 조합하면 강력하고 안전한 제네릭 함수를 만들 수 있다. where 절을 사용하면 복잡한 바운드를 분리해서 가독성을 높일 수 있다.

💡 where 절 & 라이프타임 + 트레이트 조합
fn announce<'a, T, M>(item: &'a T, msg: M) -> &'a str where T: Summarize, M: Display

where 절: 복잡한 바운드를 함수 시그니처에서 분리 → 가독성 향상
라이프타임 + 트레이트: T: Display + 'a = T가 Display를 구현하고 'a보다 오래 살아야 함
반환값 &'a str: item에서 빌린 참조 → item이 살아있는 동안만 유효
Java는 String 반환으로 항상 안전 — 단 슬라이스 반환 불가 (새 객체만)
☕ Java
 1// Java: 제네릭 + 인터페이스 + String 반환
 2interface Summarize {
 3    String summary();
 4}
 5
 6class Article implements Summarize {
 7    private String title;
 8    private String content;
 9
10    Article(String title, String content) {
11        this.title = title;
12        this.content = content;
13    }
14
15    public String summary() {
16        return title + ": " + content.substring(0, 20);
17    }
18}
19
20public class Main {
21    static <T extends Summarize>
22    void announce(T item, String msg) {
23        System.out.println(
24            "[" + msg + "] " + item.summary());
25    }
26
27    public static void main(String[] args) {
28        Article a = new Article(
29                "Rust", "Fast and safe language!");
30        announce(a, "공지");
31    }
32}
C++
 1#include <iostream>
 2#include <string>
 3#include <string_view>
 4using namespace std;
 5
 6struct Article {
 7    string title;
 8    string content;
 9
10    string_view summary() const {
11        // 첫 20자 반환 (뷰)
12        size_t len = min(content.size(), (size_t)20);
13        return string_view(content).substr(0, len);
14    }
15};
16
17// C++20 concept + string_view
18template<typename T, typename Msg>
19void announce(const T& item, Msg msg) {
20    cout << "[" << msg << "] "
21         << item.title << ": "
22         << item.summary() << endl;
23}
24
25int main() {
26    Article a{"Rust", "Fast and safe language!"};
27    announce(a, "공지");
28    return 0;
29}
Rust
 1use std::fmt::Display;
 2
 3trait Summarize {
 4    fn summary(&self) -> &str;
 5}
 6
 7struct Article {
 8    title:   String,
 9    content: String,
10}
11
12impl Summarize for Article {
13    fn summary(&self) -> &str {
14        // content의 일부를 뷰로 반환 (0비용)
15        let end = self.content.len().min(20);
16        &self.content[..end]
17    }
18}
19
20// 라이프타임 + 트레이트 바운드 + where 절
21fn announce<'a, T, M>(item: &'a T, msg: M) -> &'a str
22where
23    T: Summarize,
24    M: Display,
25{
26    println!("[{}] {}: ", msg, item.summary());
27    item.summary()
28}
29
30fn main() {
31    let a = Article {
32        title:   String::from("Rust"),
33        content: String::from(
34            "Fast and safe language!"),
35    };
36    let summary = announce(&a, "공지");
37    println!("반환된 요약: {}", summary);
38}

상속 없는 Rust

출처: Rust Book §10.2Rust Reference — Traits

① 상속이 하는 일 → Rust · Go · C++ 대체 방법

💡 핵심 관점
Java 상속이 하는 3가지 역할을 Rust는 모두 다른 방법으로 분리해서 처리한다. Go·C++도 함께 보면 패턴이 보인다.
Java 상속의 역할☕ Java Rust Go C++
데이터 공유extends Animal컴포지션 (필드 포함)임베딩 (자동 위임): public Animal
코드 재사용부모 메서드 상속trait 기본 구현임베딩 메서드부모 메서드 상속
다형성 강제abstract 메서드trait 필수 메서드interface 메서드순수 가상 함수
다중 상속불가 (interface만 가능)여러 trait 구현 ✅여러 interface ✅다중 상속 (복잡) ⚠️

② impl 블록 = Go 리시버 = Java 클래스 메서드

💡 3개 언어 메서드 정의 비교
Java는 class 안에 메서드를 넣지만, Rust는 impl 블록으로 분리, Go는 리시버 함수로 분리한다.
&self = 읽기전용 (Go: 값 리시버 b Book)  |  &mut self = 수정가능 (Go: 포인터 리시버 b *Book)  |  self = 소유권 소비 (Go에 없음)
☕ Java
 1public class Main {
 2    static class Book {
 3        private String title;
 4        private int    pages;
 5        public Book(String t, int p) {
 6            title = t; pages = p;
 7        }
 8        public String getTitle() { return title; }
 9        public void addPage()   { pages++; }
10    }
11    public static void main(String[] args) {
12        Book b = new Book("Clean Code", 430);
13        b.addPage();
14        System.out.println(b.getTitle()); // Clean Code
15    }
16}
Rust
 1struct Book { title: String, pages: u32 }
 2impl Book {
 3    // &self = Go 값 리시버 (읽기)
 4    fn title(&self) -> &str {
 5        &self.title
 6    }
 7    // &mut self = Go *T 리시버 (수정)
 8    fn add_page(&mut self) {
 9        self.pages += 1;
10    }
11    // self = 소유권 소비 (Go에 없음)
12    fn destroy(self) {
13        println!("dropped: {}", self.title)
14    }
15}
16fn main() {
17    let mut b = Book {
18        title: "Clean Code".to_string(),
19        pages: 430,
20    };
21    b.add_page();
22    println!("{}", b.title());
23    b.destroy();
24}
수신자☕ Java Rust Go의미
읽기this&selffunc (b Book)불변 접근
수정this&mut selffunc (b *Book)가변 접근
소비없음self없음소유권 이전 후 drop
정적staticself 없는 연관 함수패키지 함수인스턴스 불필요

③ trait = interface + 추가 기능 — why_trait

☕ Java
 1public class Main {
 2    // Java: implements는 클래스 선언 시점에만 가능
 3    interface Sound {
 4        String makeSound();
 5        default void loudSound() {
 6            String s = makeSound()
 7                .toUpperCase() + "!!!";
 8            System.out.println(s);
 9        }
10    }
11    // String에 Sound를 구현하고 싶어도 불가!
12    // String은 java.lang — 수정 불가, 컴파일 에러
13    // Sound s = "hello"; // ✗
14    static class Lion implements Sound {
15        public String makeSound() { return "Roar"; }
16    }
17    public static void main(String[] args) {
18        Sound s = new Lion();
19        s.loudSound(); // 으르렁!!!
20    }
21}
Rust
 1trait Sound {
 2    fn make_sound(&self) -> String;
 3    fn loud_sound(&self) { // 기본 구현
 4        let s = self.make_sound().to_uppercase();
 5        println!("{}!!!", s);
 6    }
 7}
 8// 기존 타입 i32에 나중에 구현 가능!
 9impl Sound for i32 {
10    fn make_sound(&self) -> String {
11        format!("Number: {}", self)
12    }
13}
14fn main() {
15    42.loud_sound(); // 숫자 42!!!
16}
표현의미Java 대응
impl Lion { fn name(&self) }Lion 고유 메서드 블록class 내부 일반 메서드
impl Sound for Lion { ... }Sound trait 계약 이행implements Sound + @Override
⚠️ 고아 규칙 (Orphan Rule)
외부 타입에 외부 trait을 구현하는 것은 불가 — trait 또는 타입 중 하나는 반드시 현재 crate에 있어야 함.
예: 표준 라이브러리의 Display를 표준 라이브러리의 Vec에 별도 구현 ❌
출처: Rust Reference — Orphan Rules
역할☕ Java Rust Go C++
데이터 묶음classstructstructstruct/class
메서드class 내부impl 블록리시버 함수class 내부
인터페이스interfacetraitinterface순수 가상 함수
구현 선언implementsimpl Trait for Type묵시적: public Base
데이터 상속extends없음 (컴포지션)임베딩: public Base
외부 타입 확장불가블랭킷 impl불가불가

4개 언어 비교 — Java · Rust · Go · C++

☕ Java
 1// Java: 데이터 + 메서드가 class 안에
 2public class Book {
 3    private final String title;
 4    private final int pages;
 5    Book(String t, int p) {
 6        this.title = t;
 7        this.pages = p;
 8    }
 9    public String summary() {
10        return title + " (" + pages + "p)";
11    }
12    public static void main(String[] args) {
13        Book b = new Book("Clean Code", 431);
14        System.out.println(b.summary());
15    }
16}
Rust
 1// Rust: 데이터(struct)와 메서드(impl) 분리
 2struct Book { title: String, pages: u32 }
 3
 4impl Book {
 5    fn summary(&self) -> String {
 6        format!("{} ({}p)", self.title, self.pages)
 7    }
 8}
 9fn main() {
10    let b = Book {
11        title: "Clean Code".to_string(),
12        pages: 431,
13    };
14    println!("{}", b.summary());
15}
Go
 1// Go: struct + 리시버 함수 분리
 2package main
 3import "fmt"
 4type Book struct {
 5    title string
 6    pages int
 7}
 8// 값 리시버: (b Book) — &self 대응, 복사본
 9func (b Book) Summary() string {
10    return fmt.Sprintf("%s (%dp)", b.title, b.pages)
11}
12// 포인터 리시버: (b *Book) — &mut self 대응, 수정 가능
13func (b *Book) AddPage() {
14    b.pages++
15}
16// interface 묵시적 구현 (implements 없음!)
17type Summarizer interface {
18    Summary() string
19}
20func main() {
21    b := Book{title: "Clean Code", pages: 431}
22    b.AddPage()
23    fmt.Println(b.Summary()) // Clean Code (432p)
24    var s Summarizer = b
25    fmt.Println(s.Summary())
26}
C++
 1// C++: class 내부 (Java와 유사)
 2// virtual 키워드로 동적 디스패치 선택
 3#include <iostream>
 4#include <string>
 5class Book {
 6private:
 7    std::string title;
 8    int pages;
 9public:
10    Book(std::string t, int p)
          : title(t), pages(p) {}
11    // const = 불변 (Rust &self와 유사)
12    std::string Summary() const {
13        return title + " ("
              + std::to_string(pages) + "p)";
14    }
15};
16int main() {
17    Book b("Clean Code", 431);
18    std::cout << b.Summary() << std::endl;
19    return 0;
20}

Go interface vs Rust dyn

Go iface vs Rust fat pointer Go interface — 16B itab* (8B) data* (8B) itab inter* (interface 타입) _type* (구체 타입) fun[] (메서드 포인터) Rust &dyn Trait — 16B data_ptr (8B) vtable_ptr (8B) vtable drop_ptr size / align fn 포인터들 타입 정보 없음 downcast 불가 (Any trait 필요)

trait vs interface

☕ Java
 1interface Sound {
 2    String makeSound();
 3    default void loudSound() {
 4        System.out.println(
              makeSound().toUpperCase() + "!!!");
 5    }
 6}
 7class Lion implements Sound {
 8    private final String name;
 9    Lion(String n) { this.name = n; }
10    @Override public String makeSound() {
11        return name + ": Roar";
12    }
13}
14public class Main {
15    public static void main(String[] args) {
16        Lion l = new Lion("Simba");
17        System.out.println(l.makeSound());
18        l.loudSound();
19    }
20}
Rust
 1trait Sound {
 2    fn make_sound(&self) -> String;
 3}
 4trait Announce {
 5    fn announce(&self);
 6}
 7
 8// blanket: Sound 구현 → Announce 자동!
 9impl<T: Sound> Announce for T {
10    fn announce(&self) {
11        println!("[Announce] {}", self.make_sound());
12    }
13}
14
15struct Book { title: String }
16impl Book {
17    fn new(t: &str) -> Self {
18        Book { title: t.to_string() }
19    }
20}
21impl Sound for Book {
22    fn make_sound(&self) -> String {
23        format!("Book '{}' sound", self.title)
24    }
25}
26struct Lion { name: String }
27impl Sound for Lion {
28    fn make_sound(&self) -> String {
29        format!("{}: Roar", self.name)
30    }
31}
32
33fn main() {
34    let book = Book::new("Clean Code");
35    let lion = Lion { name: "Simba".into() };
36    // Sound 구현 → Announce 자동 획득
37    book.announce();
38    lion.announce();
39    // 표준 라이브러리 실제 예:
40    // impl<T: Display> ToString for T
41    // Display 구현 → .to_string() 자동
42}
43
💡 블랭킷 구현 (Blanket impl)
Rust에서는 외부 타입에도 trait을 구현할 수 있습니다.
impl<T: Display> ToString for T → Display 구현 시 .to_string() 자동.
출처: Rust Reference — Implementations

상속 대신 컴포지션 — Java extends vs Rust 필드 포함

Java 상속 vs Rust 컴포지션 ☕ Java — 상속 (extends) Animal name: String Dog extends Animal breed: String +name 상속 Cat extends Animal indoor: bool +name 상속 강한 결합 — Animal 변경 시 모두 영향 Rust — 컴포지션 (필드 포함) AnimalInfo name: String Dog info: AnimalInfo breed: String Cat info: AnimalInfo indoor: bool 느슨한 결합 — 각 struct 독립적
☕ Java
 1public class Main {
 2    abstract static class Animal {
 3        protected String name;
 4        public Animal(String name) { this.name = name; }
 5        public abstract String sound();
 6        public String describe() {
 7            return name + ": " + sound();
 8        }
 9    }
10    static class Dog extends Animal {
11        private String breed;
12        public Dog(String name, String breed) {
13            super(name); this.breed = breed;
14        }
15        public String sound() { return "Woof"; }
16    }
17    public static void main(String[] args) {
18        Animal d = new Dog("Rex", "Labs");
19        System.out.println(d.describe()); // Rex: 멍멍
20    }
21}
Rust
 1struct AnimalInfo { name: String }
 2
 3trait Animal {
 4    fn info(&self) -> &AnimalInfo;
 5    fn sound(&self) -> &'static str;
 6    // 기본 구현 (Java default 메서드와 동일)
 7    fn describe(&self) -> String {
 8        format!("{}: {}", self.info().name, self.sound())
 9    }
10}
11struct Dog { info: AnimalInfo, breed: String }
12impl Animal for Dog {
13    fn info(&self) -> &AnimalInfo { &self.info }
14    fn sound(&self) -> &'static str { "Woof" }
15}
16fn main() {
17    let d = Dog {
18        info: AnimalInfo { name: "Rex".to_string() },
19        breed: "Husky".to_string(),
20    };
21    println!("{}", d.describe()); // Rex: Woof
22}
Java (상속) Rust (컴포지션)차이
extends Animal필드로 info: AnimalInfo데이터 재사용 방식
super(name)필드 초기화 직접생성 방식
abstract 메서드trait 필수 메서드계약 정의
default 메서드trait 기본 구현동일 역할
다중 상속 불가여러 trait 구현 가능Rust가 더 유연
⚠️ 왜 상속이 없나?
상속은 데이터 재사용다형성을 하나로 묶어 강한 결합을 만듦.
Rust는 이를 분리: 데이터 재사용 → 컴포지션(필드 포함), 다형성 → trait.
결과적으로 더 유연하고 예측 가능한 코드 구조를 만들 수 있음.
출처: Rust Book §17.1 — OO 특성
비교

impl vs dyn — 5가지 비교 + blanket impl

출처: Rust Book §18.2

☕ Java
 1import java.util.*;
 2public class Main {
 3    interface Sound {
 4        String makeSound();
 5        default void loudSound() {
 6            System.out.println(
 7                makeSound().toUpperCase()
 8                + "!!!");
 9        }
10    }
11    record Lion(String name)
12            implements Sound {
13        public String makeSound() {
14            return name + ": Roar";
15        }
16    }
17    record Duck(String name)
18            implements Sound {
19        public String makeSound() {
20            return name + ": Quack";
21        }
22        public void loudSound() {
23            System.out.println(
24                "(quietly) " + makeSound());
25        }
26    }
27    // 타입 소거 → 컴파일 후 play(Sound a)
28    // → 항상 동적 디스패치!
29    static <T extends Sound> void play(T a) {
30        System.out.println(
31            "[generic] " + a.makeSound());
32    }
33    static void holdConcert(List<Sound> zoo) {
34        System.out.println("=== Chorus ===");
35        for (Sound a : zoo) {
36            a.loudSound(); // vtable 조회
37        }
38    }
39    static Sound makeAnimal(String kind) {
40        return switch (kind) {
41            case "lion" -> new Lion("Simba");
42            default     -> new Duck("Donald");
43        };
44    }
45    public static void main(String[] args) {
46        var lion = new Lion("Simba");
47        var duck = new Duck("Donald");
48        play(lion);
49        play(duck);
50        holdConcert(List.of(lion, duck));
51        Sound a = makeAnimal("lion");
52        System.out.println(a.makeSound());
53    }
54}
55
Rust
 1trait Sound {
 2    fn make_sound(&self) -> String;
 3    fn loud_sound(&self) {
 4        println!(
 5            "{}!!!",
 6            self.make_sound().to_uppercase(),
 7        );
 8    }
 9}
10
11#[derive(Clone)]
12struct Lion { name: String }
13#[derive(Clone)]
14struct Duck { name: String }
15
16impl Sound for Lion {
17    fn make_sound(&self) -> String {
18        format!("{}: Roar", self.name)
19    }
20}
21impl Sound for Duck {
22    fn make_sound(&self) -> String {
23        format!("{}: Quack", self.name)
24    }
25    fn loud_sound(&self) {
26        println!("(quietly) {}", self.make_sound());
27    }
28}
29
30// 정적 — impl Trait
31fn play_impl(a: &impl Sound) {
32    println!("[impl] {}", a.make_sound());
33}
34// 동적 — dyn Trait
35fn play_dyn(a: &dyn Sound) {
36    println!("[dyn]  {}", a.make_sound());
37}
38
39// 이종 컬렉션 — dyn만 가능
40fn hold_concert(zoo: &[Box<dyn Sound>]) {
41    for a in zoo { a.loud_sound(); }
42}
43
44// impl Trait 반환 — 정적
45fn make_static() -> impl Sound {
46    Lion { name: "Simba".to_string() }
47}
48// Box<dyn Trait> 반환 — 동적 필수
49fn make_dynamic(kind: &str) -> Box<dyn Sound> {
50    match kind {
51        "lion" => Box::new(Lion {
52            name: "Simba".into(),
53        }),
54        _ => Box::new(Duck {
55            name: "Donald".into(),
56        }),
57    }
58}
59
60fn main() {
61    let lion = Lion { name: "Simba".into() };
62    let duck = Duck { name: "Donald".into() };
63
64    play_impl(&lion); play_impl(&duck);
65    play_dyn(&lion);  play_dyn(&duck);
66
67    let zoo: Vec<Box<dyn Sound>> = vec![
68        Box::new(lion.clone()),
69        Box::new(duck.clone()),
70    ];
71    hold_concert(&zoo);
72
73    println!("{}", make_static().make_sound());
74    println!("{}", make_dynamic("lion").make_sound());
75}
76
Rust impl (정적) Rust dyn (동적)☕ Java
결정 시점컴파일 타임런타임런타임
vtable없음있음 (16B)있음 (헤더)
인라인가능불가JIT
이종 컬렉션불가가능가능
반환 타입impl TraitBox<dyn Trait>인터페이스 타입

blanket 구현

Rust
 1trait Sound {
 2    fn make_sound(&self) -> String;
 3}
 4trait Announce {
 5    fn announce(&self);
 6}
 7
 8// blanket: Sound 구현 → Announce 자동!
 9impl<T: Sound> Announce for T {
10    fn announce(&self) {
11        println!("[Announce] {}", self.make_sound());
12    }
13}
14
15struct Book { title: String }
16impl Book {
17    fn new(t: &str) -> Self {
18        Book { title: t.to_string() }
19    }
20}
21impl Sound for Book {
22    fn make_sound(&self) -> String {
23        format!("Book '{}' sound", self.title)
24    }
25}
26struct Lion { name: String }
27impl Sound for Lion {
28    fn make_sound(&self) -> String {
29        format!("{}: Roar", self.name)
30    }
31}
32
33fn main() {
34    let book = Book::new("Clean Code");
35    let lion = Lion { name: "Simba".into() };
36    // Sound 구현 → Announce 자동 획득
37    book.announce();
38    lion.announce();
39    // 표준 라이브러리 실제 예:
40    // impl<T: Display> ToString for T
41    // Display 구현 → .to_string() 자동
42}
43

정적 vs 동적 디스패치 — 한눈에

출처: Rust Reference — Trait Objects

정적 디스패치 (Static Dispatch)
  • 컴파일 타임에 호출 함수 확정
  • 문법: impl Trait 또는 <T: Trait>
  • 단형화: 타입별 함수 복사본 생성
  • vtable 없음 → 직접 CALL → 최고 성능
  • 이종 컬렉션 불가
  • Java에 없음 — Rust·C++ 템플릿만
동적 디스패치 (Dynamic Dispatch)
  • 런타임에 vtable 조회 후 호출
  • 문법: dyn Trait
  • fat pointer = data_ptr(8B) + vtable_ptr(8B) = 16B
  • 간접 CALL 오버헤드
  • 이종 컬렉션 가능
  • Java의 모든 인터페이스 호출이 여기에 해당
항목정적 (impl/제네릭)동적 (dyn)☕ Java
결정 시점컴파일 타임런타임런타임
vtable없음있음있음
인라인가능어려움JIT
이종 컬렉션불가가능가능
포인터 크기보통 8Bfat ptr 16Bref 8B (헤더에 vtable)

정적 디스패치 — 단형화 (Java에 없음)

출처: Rust Book §10.1 — 단형화

⚠️ Java에는 정적 디스패치가 없습니다
Java <T extends Sound> → 타입 소거 → play(Sound a) → vtable 조회.
Rust <T: Sound> → 단형화 → play__Lion, play__Duck → 직접 CALL.
☕ Java
 1import java.util.List;
 2// Java 정적 디스패치 대응 예제
 3// Java는 정적 디스패치가 없음 — 항상 vtable 조회
 4public class Main {
 5    interface Sound {
 6        String makeSound();
 7    }
 8    record Lion(String name) implements Sound {
 9        public String makeSound() {
10            return name + ": Roar";
11        }
12    }
13    record Duck(String name) implements Sound {
14        public String makeSound() {
15            return name + ": Quack";
16        }
17    }
18
19    // Java 제네릭 = 타입 소거 → 런타임에 Sound로 변환 → vtable!
20    // Rust <T: Sound>와 달리 단형화가 일어나지 않음
21    static <T extends Sound> void playGeneric(T animal) {
22        // 컴파일 후: play(Sound animal) — T 소거됨
23        System.out.println("[dynamic] " + animal.makeSound());
24    }
25
26    // 인터페이스 타입 — 명시적 동적 디스패치
27    static void playInterface(Sound animal) {
28        System.out.println("[dynamic] " + animal.makeSound());
29    }
30
31    public static void main(String[] args) {
32        var simba  = new Lion("Simba");
33        var donald = new Duck("Donald");
34
35        // Java는 항상 동적 — Lion, Duck 모두 같은 코드 경로
36        playGeneric(simba);    // vtable 조회
37        playGeneric(donald);   // vtable 조회
38        playInterface(simba);  // vtable 조회
39        playInterface(donald); // vtable 조회
40
41        // 이종 컬렉션 — Java는 자동 (Rust dyn과 동일)
42        List<Sound> zoo = List.of(simba, donald);
43        zoo.forEach(a ->
44            System.out.println(a.makeSound()));
45    }
46}
47
Rust
 1trait Sound {
 2    fn make_sound(&self) -> String;
 3}
 4
 5struct Lion { name: String }
 6struct Duck { name: String }
 7
 8impl Sound for Lion {
 9    fn make_sound(&self) -> String {
10        format!("{}: Roar", self.name)
11    }
12}
13impl Sound for Duck {
14    fn make_sound(&self) -> String {
15        format!("{}: Quack", self.name)
16    }
17}
18
19// impl Trait → 단형화 (정적 디스패치)
20// 컴파일 후:
21//   play__Lion(&Lion) 생성
22//   play__Duck(&Duck) 생성
23fn play_impl(animal: &impl Sound) {
24    // 직접 CALL — vtable 없음!
25    println!(
26        "[static] {}",
27        animal.make_sound(),
28    );
29}
30
31// 제네릭 문법 — 위와 완전히 동일
32fn play_generic<T: Sound>(animal: &T) {
33    println!(
34        "[static] {}",
35        animal.make_sound(),
36    );
37}
38
39// dyn — 동적 디스패치 (비교용)
40fn play_dynamic(animal: &dyn Sound) {
41    // vtable 조회 → 간접 CALL
42    println!(
43        "[dynamic] {}",
44        animal.make_sound(),
45    );
46}
47
48fn main() {
49    let simba  = Lion { name: "Simba".into() };
50    let donald = Duck { name: "Donald".into() };
51
52    // 정적: 타입별 별도 함수로 컴파일됨
53    play_impl(&simba);     // play__Lion
54    play_impl(&donald);    // play__Duck
55
56    play_generic(&simba);  // 동일
57    play_generic(&donald);
58
59    // 동적: vtable 조회
60    play_dynamic(&simba);
61    play_dynamic(&donald);
62
63    // 정적은 이종 컬렉션 불가!
64    // let v: Vec<impl Sound> = ... // 에러
65}
66
항목Java 제네릭 Rust impl/제네릭
구현타입 소거 → play(Sound a)단형화 → play__Lion, play__Duck
vtable있음 (동적)없음 (직접 CALL)
인라인JIT만컴파일타임 가능
이종 컬렉션List<Sound> 가능불가 → dyn 사용

동적 디스패치 — dyn Trait · vtable · fat pointer

출처: Rust Reference — Trait ObjectsRust Book §18.2

☕ Java
 1import java.util.List;
 2interface Sound {
 3    String makeSound();
 4    default void loudSound() {
 5        System.out.println(
              "  " + makeSound().toUpperCase() + "!!!");
 6    }
 7}
 8record Lion(String name) implements Sound {
 9    public String makeSound() { return name + ": Roar"; }
10}
11record Duck(String name) implements Sound {
12    public String makeSound() { return name + ": Quack"; }
13    public void loudSound() {
14        System.out.println("  (quietly) " + makeSound());
15    }
16}
17record Snake(String name) implements Sound {
18    public String makeSound() { return name + ": Hiss"; }
19}
20public class Main {
21    static void holdConcert(List<Sound> zoo) {
22        System.out.println("  === Animal Chorus ===");
23        for (Sound a : zoo) { a.loudSound(); }
24    }
25    public static void main(String[] args) {
26        var simba  = new Lion("Simba");
27        var donald = new Duck("Donald");
28        var nagini = new Snake("Nagini");
29        List<Sound> zoo = List.of(simba, donald, nagini);
30        holdConcert(zoo);
31        Sound a = switch ("lion") {
32            case "lion" -> new Lion("Simba");
33            default     -> new Duck("Donald");
34        };
35        System.out.println("  " + a.makeSound());
36    }
37}
Rust
 1trait Sound {
 2    fn make_sound(&self) -> String;
 3    fn loud_sound(&self) {
 4        println!(
 5            "  {}!!!",
 6            self.make_sound().to_uppercase(),
 7        );
 8    }
 9}
10
11struct Lion  { name: String }
12struct Duck  { name: String }
13struct Snake { name: String }
14
15impl Sound for Lion {
16    fn make_sound(&self) -> String {
17        format!("{}: Roar", self.name)
18    }
19}
20impl Sound for Duck {
21    fn make_sound(&self) -> String {
22        format!("{}: Quack", self.name)
23    }
24    fn loud_sound(&self) {
25        println!(
26            "  (quietly) {}",
27            self.make_sound(),
28        );
29    }
30}
31impl Sound for Snake {
32    fn make_sound(&self) -> String {
33        format!("{}: Hiss", self.name)
34    }
35}
36
37// 정적 디스패치 — 단형화
38fn play_impl(a: &impl Sound) {
39    println!("[impl] {}", a.make_sound());
40}
41// 동적 디스패치 — vtable 조회
42fn play_dyn(a: &dyn Sound) {
43    println!("[dyn]  {}", a.make_sound());
44}
45
46// Box<dyn Sound> — 런타임 타입 결정
47fn make_animal(kind: &str) -> Box<dyn Sound> {
48    match kind {
49        "lion" => Box::new(Lion {
50            name: "Simba".into(),
51        }),
52        "duck" => Box::new(Duck {
53            name: "Donald".into(),
54        }),
55        _ => Box::new(Snake {
56            name: "Nagini".into(),
57        }),
58    }
59}
60
61fn hold_concert(zoo: &[Box<dyn Sound>]) {
62    println!("  === Animal Chorus ===");
63    for a in zoo {
64        // fat ptr → vtable → 함수 포인터
65        a.loud_sound();
66    }
67}
68
69fn main() {
70    let lion = Lion  { name: "Simba".into()  };
71    let duck = Duck  { name: "Donald".into() };
72
73    // 정적 (단형화 — 직접 CALL)
74    play_impl(&lion);
75    play_impl(&duck);
76
77    // 동적 (vtable — 간접 CALL)
78    play_dyn(&lion);
79    play_dyn(&duck);
80
81    // 이종 컬렉션 — dyn만 가능
82    let zoo: Vec<Box<dyn Sound>> = vec![
83        make_animal("lion"),
84        make_animal("duck"),
85        make_animal("snake"),
86    ];
87    hold_concert(&zoo);
88}
89

fat pointer 구조 — JVM vs Rust

fat pointer 구조 — JVM 객체 참조 vs Rust &dyn☕ Java — 객체 참조 (8B)ref (8B)힙 객체 (Lion)Object Header 12~16BMark Word (8B)Klass Ptr → vtablename/age/hungry...단일 참조 — vtable은 Object Header 내부모든 객체가 항상 Header(16B) 비용 부담→ Java Sound ref는 8B 1개뿐Rust는 dyn 사용 시에만 fat pointer Rust — fat pointer (16B)&dyn Sound (16B)data_ptr (8B)vtable_ptr (8B)실제 데이터Lion / Duck(vptr 없음!)vtabledrop_ptr / size / alignfn make_soundfn loud_sound데이터/vtable 분리 — struct에 vptr 없음
⚠️ vtable 내부 구조 — ABI 미보장
[0]drop/[1]size/[2]align/메서드 순서는 rustc 내부 기준. 공식 ABI에서 보장하지 않습니다.
출처: Rust Reference — Trait Objects
비교

호출 흐름 비교 — 정적 vs 동적 vs Java

출처: Rust Reference

호출 흐름 비교 — animal.make_sound() 한 줄의 내막 Rust 정적 디스패치 ① 컴파일 시 T=Lion 확정 ② Lion::make_sound 주소 확정 ③ 직접 CALL + 인라인 가능 비용: 0 (컴파일타임 확정) Rust 동적 디스패치 ① fat ptr에서 vtable_ptr 로드 ② vtable[n] 슬롯 로드 ③ 간접 CALL (인라인 불가) 비용: 메모리 로드 2회 + 간접 점프 ☕ Java (항상 동적) ① 참조에서 Klass Word 로드 ② vtable에서 makeSound 로드 ③ 간접 CALL (JIT devirt 가능) 비용: 동일하나 JIT 최적화 가능
💡 Java JIT devirtualize
Java는 런타임 JIT 컴파일러가 자주 호출되는 가상 메서드를 정적화(devirtualize)할 수 있음.
단, 이는 런타임 최적화이고 언어 차원의 보장이 아님. Rust 정적 디스패치는 컴파일 타임에 항상 보장.
단계 Rust 정적 Rust 동적☕ Java
1컴파일 시 T=Lion 확정fat ptr에서 vtable_ptr 로드참조에서 Klass Word 로드
2Lion::make_sound 주소 확정vtable[n] (make_sound) 로드vtable에서 makeSound 로드
3직접 CALL (주소 고정)간접 CALL간접 CALL
4인라인 가능인라인 불가JIT devirtualize
비용0 (컴파일타임)메모리 로드 2회 + 간접 점프메모리 로드 2회 + 간접 점프
항목Java 참조 8B Rust fat pointer 16B
포인터 크기8B (단일)16B (data+vtable)
vtable 위치객체 헤더 (Klass Pointer)fat pointer (vtable_ptr)
객체 오버헤드Header 12~16B (강제)없음 (struct에 vptr 없음)
null 안전NPE 가능Option<Box<dyn Sound>>

메모리 비교 — JVM vs Rust

출처: JVM Spec §2Rust Reference — Memory

① Java JVM 객체 메모리 구조

Java 힙 (Heap) — GC 관리Lion 객체Object Header (12~16B)mark word (8B)klass ptr → vtablename (ref 4~8B)age (int 4B)hungry (bool 1B)Duck 객체Object Header (12~16B)mark word (8B)klass ptr → vtablename (ref)age (int)feathers (int)JVM Method Table (vtable)Lion vtablemakeSound → Lion::makeSoundloudSound → Sound defaultDuck vtablemakeSound → Duck::makeSoundloudSound → Duck::loudSoundSound a = simba → a는 8B 단일 참조. vtable은 Lion Object Header 안에모든 객체가 항상 Object Header(16B) 자동 내장 → 클래스마다 vtable 1개Rust는 dyn 사용 시에만 fat pointer 16B. 아닐 땐 0 비용.※ HotSpot JVM 64bit 기준. 압축 OOP 시 Header=12B.

② Rust 메모리 구조

Rust 스택 — vptr 없음, &dyn 시에만 fat pointer스택 (Stack)simba: Lion (32B) — vptr 없음!name(ptr+len+cap=24B) | age(4B) | hungry(1B+pad)a: &dyn Sound (16B — fat pointer)data_ptr (8B) → simbavtable_ptr (8B) → vtable힙 사용 없음 (&dyn 사용 시에도).rodata (vtable) — 읽기 전용Sound_for_Lion_vtable[0] drop_fn → Lion::drop[1] size = 32[2] align = 8[3] make_sound → Lion::make_soundSound_for_Duck_vtable[0] drop_fn → Duck::drop[1] size = 32[2] align = 8[3] make_sound → Duck::make_sound[4] loud_sound → Duck::loud_soundRust 핵심: struct에 vptr 없음. &dyn 시에만 fat pointer 16B 발생→ Lion은 32B 그대로. Java는 모든 객체에 16B Header 강제⚠ vtable 순서는 rustc 내부 구현 기준. 공개 ABI 미보장.
항목☕ Java (HotSpot 64bit) Rust
스택기본 타입, 참조(8B)기본 타입, struct 전체
모든 객체 (new)Box::new() 명시 시만
객체 헤더12~16B (Mark Word + Klass Ptr)없음
vtable 위치Klass Pointer → Method tablefat pointer vtable_ptr
메모리 해제GC (STW pause)Drop (컴파일타임)
null 안전NPE 가능Option<T>
💡 HotSpot JVM Object Header
Mark Word (8B): GC 마킹, 해시, 락 정보
Klass Pointer (4~8B): 클래스 메타데이터 (vtable 포함)
• Java 17+ 기본: 압축 OOP → Klass 4B → 헤더 합계 12B
출처: JVM Spec §2.5
비교

C++ 비교 (참고)

💡 3개 언어의 vtable 철학
Java는 항상 동적. C++는 기본 정적, virtual 명시 시 동적. Rust는 기본 정적, dyn 명시 시 동적.
Rust는 C++와 같은 방향이지만 키워드를 virtual 대신 dyn으로 통일하고, vtable을 객체 안이 아닌 fat pointer에 분리.
항목☕ JavaC++ Rust
vtable 위치객체 헤더 (Klass)객체 내부 (vptr)fat pointer (struct에 없음)
기본 디스패치동적 (항상)정적 (virtual 시 동적)정적 (dyn 시 동적)
메모리 해제GC수동 / RAII소유권 Drop
null 안전없음 (NPE)없음 (dangling)Option<T>
객체 오버헤드Header 12~16Bvptr 8B없음

전체 예제 — Main.java / main.rs

Main.java: javac 21 검증 완료 (HashMap·Stream·제네릭·feed·status 포함)  |  main.rs: rustc 1.75 검증 완료 (정적+동적·Box<dyn>·HashMap·Iterator·feed·status 포함)

☕ Main.java
  1// Main.java — javac 21 검증 완료
  2import java.util.ArrayList;
  3import java.util.List;
  4import java.util.Map;
  5import java.util.HashMap;
  6import java.util.stream.Collectors;
  7
  8// ── Step 1: 데이터 클래스 ────────────────
  9class Lion {
 10    private final String name;
 11    private final int age;
 12    private boolean hungry;
 13    public Lion(String name, int age) {
 14        this.name = name;
 15        this.age = age;
 16        this.hungry = true;
 17    }
 18    public String getName()   { return name; }
 19    public String status() {
 20        return name + " (" + age + " y.o.) — "
 21            + (hungry ? "Hungry" : "Full");
 22    }
 23    public void feed() {
 24        if (hungry) {
 25            System.out.println(
 26                "  [Lion] " + name + " fed.");
 27            hungry = false;
 28        } else {
 29            System.out.println(
 30                "  [Lion] " + name + " already full.");
 31        }
 32    }
 33    @Override public String toString() {
 34        return "Lion{" + name + "}";
 35    }
 36}
 37class Duck {
 38    private final String name;
 39    private final int age;
 40    private int feathers;
 41    public Duck(String name, int age) {
 42        this.name = name;
 43        this.age = age;
 44        this.feathers = 1000;
 45    }
 46    public String getName() { return name; }
 47    @Override public String toString() {
 48        return "Duck{" + name + "}";
 49    }
 50}
 51class Snake {
 52    private final String name;
 53    private final int age;
 54    private final boolean isVenomous;
 55    public Snake(String name, int age, boolean v) {
 56        this.name = name;
 57        this.age = age;
 58        this.isVenomous = v;
 59    }
 60    public String getName() { return name; }
 61    @Override public String toString() {
 62        return "Snake{" + name + "}";
 63    }
 64}
 65
 66// ── Step 2 & 3: interface + implements ──
 67interface Sound {
 68    String makeSound();
 69    default void loudSound() {
 70        System.out.println(
 71            "  " + makeSound().toUpperCase()
 72            + "!!!");
 73    }
 74    static String describe(Sound s) {
 75        return "Sound: " + s.makeSound();
 76    }
 77}
 78class LionSound extends Lion implements Sound {
 79    public LionSound(String name, int age) {
 80        super(name, age);
 81    }
 82    @Override public String makeSound() {
 83        return getName() + ": Roar~";
 84    }
 85}
 86class DuckSound extends Duck implements Sound {
 87    public DuckSound(String name, int age) {
 88        super(name, age);
 89    }
 90    @Override public String makeSound() {
 91        return getName() + ": Quack!";
 92    }
 93    @Override public void loudSound() {
 94        System.out.println(
 95            "  (quietly) " + makeSound());
 96    }
 97}
 98class SnakeSound extends Snake implements Sound {
 99    public SnakeSound(
100            String name, int age, boolean v) {
101        super(name, age, v);
102    }
103    @Override public String makeSound() {
104        return getName() + ": Hiss!!!";
105    }
106}
107
108// ── 정적 메서드 ──────────────────────────
109class Zoo {
110    // Java 제네릭 = 타입 소거 → 동적 디스패치
111    // Rust <T: Sound> = 단형화 → 정적 디스패치
112    public static <T extends Sound>
113            void playGeneric(T animal) {
114        System.out.println(
115            "  [generic] " + animal.makeSound());
116    }
117    public static void holdConcert(
118            List<Sound> animals) {
119        System.out.println("  === Animal Chorus ===");
120        for (Sound a : animals) {
121            a.loudSound(); // 동적 디스패치
122        }
123    }
124    public static Sound makeAnimal(String kind) {
125        return switch (kind) {
126            case "lion"  -> new LionSound("Simba", 5);
127            case "duck"  -> new DuckSound("Donald", 3);
128            default      ->
129                new SnakeSound("Nagini", 8, false);
130        };
131    }
132    public static Map<String, Sound> buildZoo() {
133        Map<String, Sound> zoo = new HashMap<>();
134        zoo.put("simba",
135            new LionSound("Simba", 5));
136        zoo.put("donald",
137            new DuckSound("Donald", 3));
138        zoo.put("nagini",
139            new SnakeSound("Nagini", 8, false));
140        return zoo;
141    }
142    public static void main(String[] args) {
143        System.out.println("=".repeat(55));
144        System.out.println(
145            " Java Zoo — all calls are dynamic dispatch");
146        System.out.println("=".repeat(55));
147
148        LionSound  simba  = new LionSound("Simba", 5);
149        DuckSound  donald = new DuckSound("Donald", 3);
150        SnakeSound nagini =
151            new SnakeSound("Nagini", 8, false);
152
153        System.out.println(
154            "\n1. Generic <T extends Sound>:");
155        playGeneric(simba);
156        playGeneric(donald);
157        playGeneric(nagini);
158
159        System.out.println(
160            "\n2. Heterogeneous List<Sound>:");
161        List<Sound> zoo = new ArrayList<>();
162        zoo.add(simba);
163        zoo.add(donald);
164        zoo.add(nagini);
165        holdConcert(zoo);
166
167        System.out.println(
168            "\n3. Conditional return makeAnimal():");
169        for (String kind :
170                List.of("lion", "duck", "snake")) {
171            Sound a = makeAnimal(kind);
172            System.out.println("  " + a.makeSound());
173        }
174
175        System.out.println("\n4. HashMap:");
176        Map<String, Sound> zooMap = buildZoo();
177        zooMap.forEach((name, sound) ->
178            System.out.println(
179                "  " + name + " → " + sound.makeSound()));
180
181        System.out.println("\n5. Stream API:");
182        zoo.stream()
183           .filter(a -> a.makeSound().contains("Roar"))
184           .map(Sound::describe)
185           .forEach(s -> System.out.println("  " + s));
186
187        System.out.println("\n6. Feeding:");
188        System.out.println("  Status: " + simba.status());
189        simba.feed();
190        simba.feed();
191
192        System.out.println(
193            "\n  Done: Java GC frees memory");
194        System.out.println("=".repeat(55));
195    }
196}
main.rs
  1// main.rs — rustc 1.75 검증 완료
  2use std::collections::HashMap;
  3
  4#[derive(Debug, Clone)]
  5struct Lion {
  6    name:    String,
  7    age:     u32,
  8    hungry:  bool,
  9}
 10#[derive(Debug, Clone)]
 11struct Duck {
 12    name:     String,
 13    age:      u32,
 14    feathers: u32,
 15}
 16#[derive(Debug, Clone)]
 17struct Snake {
 18    name:        String,
 19    age:         u32,
 20    is_venomous: bool,
 21}
 22
 23impl Lion {
 24    fn new(name: &str, age: u32) -> Self {
 25        Lion { name: name.to_string(), age, hungry: true }
 26    }
 27    fn name(&self) -> &str { &self.name }
 28    fn status(&self) -> String {
 29        format!(
 30            "{} ({} y.o.) — {}",
 31            self.name,
 32            self.age,
 33            if self.hungry { "Hungry" } else { "Full" }
 34        )
 35    }
 36    fn feed(&mut self) {
 37        if self.hungry {
 38            println!("  [Lion] {} fed.", self.name);
 39            self.hungry = false;
 40        } else {
 41            println!("  [Lion] {} already full.", self.name);
 42        }
 43    }
 44}
 45impl Duck {
 46    fn new(name: &str, age: u32) -> Self {
 47        Duck { name: name.to_string(), age, feathers: 1000 }
 48    }
 49    fn name(&self) -> &str { &self.name }
 50}
 51impl Snake {
 52    fn new(name: &str, age: u32, v: bool) -> Self {
 53        Snake { name: name.to_string(), age, is_venomous: v }
 54    }
 55    fn name(&self) -> &str { &self.name }
 56}
 57
 58trait Sound {
 59    fn make_sound(&self) -> String;
 60    fn loud_sound(&self) {
 61        println!("  {}!!!", self.make_sound().to_uppercase());
 62    }
 63}
 64
 65impl Sound for Lion {
 66    fn make_sound(&self) -> String {
 67        format!("{}: Roar~", self.name)
 68    }
 69}
 70impl Sound for Duck {
 71    fn make_sound(&self) -> String {
 72        format!("{}: Quack!", self.name)
 73    }
 74    fn loud_sound(&self) {
 75        println!("  (quietly) {}", self.make_sound());
 76    }
 77}
 78impl Sound for Snake {
 79    fn make_sound(&self) -> String {
 80        if self.is_venomous {
 81            format!("{}: Hiss!!!", self.name)
 82        } else {
 83            format!("{}: Sssss...", self.name)
 84        }
 85    }
 86}
 87
 88// 정적 디스패치 — 단형화 (Java에 없음)
 89fn play_static<T: Sound>(a: &T) {
 90    println!("  {}", a.make_sound());
 91}
 92
 93// &impl Sound — play_static과 동일 (syntactic sugar)
 94fn play_impl(a: &impl Sound) {
 95    println!("  {}", a.make_sound());
 96}
 97
 98// 동적 디스패치 — Java holdConcert와 동일
 99fn hold_concert(animals: &[&dyn Sound]) {
100    println!("  === Animal Chorus ===");
101    for animal in animals {
102        // fat ptr → vtable → 함수 포인터
103        animal.loud_sound();
104    }
105}
106
107fn make_animal(kind: &str) -> Box<dyn Sound> {
108    match kind {
109        "lion"  => Box::new(Lion::new("Simba", 5)),
110        "duck"  => Box::new(Duck::new("Donald", 3)),
111        _       => Box::new(Snake::new("Nagini", 8, false)),
112    }
113}
114
115fn main() {
116    println!("{}", "=".repeat(60));
117    println!(" Rust Zoo — static dispatch by default");
118    println!("{}", "=".repeat(60));
119
120    let simba  = Lion::new("Simba", 5);
121    let donald = Duck::new("Donald", 3);
122    let nagini = Snake::new("Nagini", 8, false);
123
124    // ── 정적 디스패치 ──────────────────────────
125    // Java에 없는 개념: 컴파일타임 단형화
126    println!("\n  Static dispatch (not in Java):");
127    play_static(&simba);    // play__Lion 직접 CALL
128    play_static(&donald);   // play__Duck 직접 CALL
129    play_impl(&nagini);
130
131    // ── 동적 디스패치 ──────────────────────────
132    // Java의 기본 동작과 동일
133    println!("\n  Dynamic dispatch (like Java List<Sound>):");
134    let animals: Vec<&dyn Sound> = vec![
135        &simba, &donald, &nagini,
136    ];
137    hold_concert(&animals); // fat ptr → vtable → 함수
138
139    // ── Box<dyn Sound> ─────────────────────────
140    println!("\n  Box<dyn Sound> (like Java new Lion() similar):");
141    let animal = make_animal("lion");
142    println!("  {}", animal.make_sound());
143
144    // ── HashMap (Java와 동일한 기능) ───────────
145    println!("\n  HashMap:");
146    let mut zoo_map: HashMap<&str, &str> = HashMap::new();
147    zoo_map.insert("simba",  "lion");
148    zoo_map.insert("donald", "duck");
149    zoo_map.insert("nagini", "snake");
150    for (name, kind) in &zoo_map {
151        println!("  {} → {}", name, kind);
152    }
153
154    // ── Iterator 체인 (Java Stream에 대응) ─────
155    println!("\n  Iterator chain (like Java Stream):");
156    let names: Vec<&str> = vec!["simba", "donald", "nagini"];
157    let lions: Vec<&&str> = names.iter()
158        .filter(|&&n| n == "simba")
159        .collect();
160    println!("  Lions: {:?}", lions);
161
162    // ── 먹이 주기 ─────────────────────────────
163    println!("\n  Feeding:");
164    let mut simba2 = simba.clone();
165    println!("  Status: {}", simba2.status());
166    simba2.feed();
167    simba2.feed(); // 이미 배부름
168
169    println!("\n  Done: scope ends → auto Drop (no GC)");
170    println!("{}", "=".repeat(60));
171}
172
☕ Main.java vs main.rs — 핵심 차이점
데이터 + 메서드
☕ Java
class Lion {
  String name;
  String makeSound() { ... }
}
Rust
struct Lion { name: String }
impl Lion {
  fn make_sound(&self) { ... }
}
디스패치 방식
☕ Java — 항상 동적
<T extends Sound> void play(T a)
// 타입 소거 → vtable 조회!
Rust — 선택 가능
fn play(a: &impl Sound) // 정적
fn play(a: &dyn  Sound) // 동적
메모리 해제
// Java — GC (비결정적)
Lion simba = new Lion("Simba");
// 언젠가 GC가 해제 (STW)
// Rust — Drop (즉시)
let simba = Lion::new("Simba");
} // 스코프 끝 → 즉시 Drop!
이종 컬렉션 + null 안전
// Java
List<Sound> zoo = new ArrayList<>();
zoo.add(new Lion(...)); // 자동
Sound a = null; // NPE 위험!
// Rust
let zoo: Vec<Box<dyn Sound>> = vec![...];
// null 없음!
let a: Option<Box<dyn Sound>> = None;
항목 ☕ Main.java main.rs
데이터+메서드class 내부struct + impl 분리
정적 디스패치없음 (타입 소거→동적)&impl Sound (단형화)
동적 디스패치항상 (vtable)&dyn Sound (명시)
메모리 해제GC (런타임)Drop (스코프 끝)
null 안전NPE 가능null 없음, Option<T>
용어

핵심 용어 사전 — Rust 용어 ↔ Java 대응

출처: Rust ReferenceThe Rust BookJLS SE21

Rust 용어☕ Java 대응설명
traitinterface메서드 시그니처 집합. Rust trait은 기존 타입에 외부에서 추가 가능 (Java 불가 — orphan rule)
impl Trait for Typeclass T implements I계약 이행. Rust는 클래스 외부 별도 블록, Java는 클래스 선언부에 명시
dyn Trait(Java 기본 동작)동적 디스패치 명시. Java는 항상 동적이므로 별도 키워드 없음
&dyn TraitInterface ref = new Type()fat pointer (16B) = data_ptr + vtable_ptr. Java 참조는 단순 포인터 8B
Box<dyn Trait>Interface ref = new Type()힙 단독 소유. Java는 GC 해제, Rust는 스코프 끝에 즉시 Drop
Rc<T>GC 참조 공유 (단일 스레드)단일 스레드 공유 소유권. 참조 카운트 방식. Arc와 달리 스레드 안전 아님
Arc<T>GC 참조 공유 (멀티 스레드)원자적 참조 카운트. 멀티 스레드 안전. Java의 volatile 참조와 유사
단형화 (Monomorphization)❌ 없음 (타입 소거가 반대)제네릭을 타입별 전용 코드로 컴파일 타임에 복제. Java는 런타임에 타입 소거
fat pointer (와이드 포인터)❌ 없음data_ptr (8B) + vtable_ptr (8B) = 16B. dyn Trait에만 존재. Java 참조는 8B 단순 포인터
vtableJVM 내부 vtable동일 개념. Java는 객체 헤더(클래스 포인터)에 내장, Rust는 fat pointer에만 존재
DST (!Sized)❌ 없음컴파일 타임에 크기 불확정 타입. Java는 모든 객체가 참조 = 8B 고정
소유권 (Ownership)GC 관리 참조값의 유일한 주인. 스코프 종료 시 즉시 Drop. Java GC는 비결정적 해제
빌림 (&T / &mut T)참조 전달 (Java 기본)소유권 없이 읽기/쓰기 접근. 컴파일러가 유효성 보장 (댕글링 포인터 불가)
라이프타임 ('a)❌ 없음 (GC가 대신)참조의 유효 범위를 컴파일 타임에 명시. GC 없는 메모리 안전의 핵심
Sized traitJava primitive 타입 (크기 확정)컴파일 타임에 크기가 확정되는 타입이 자동 구현하는 marker trait. Rust의 모든 기본 타입은 Sized. Java 참조 타입은 항상 포인터(8B)라 개념이 다름
DST (!Sized)❌ 없음 (Java는 참조로 숨김)크기가 컴파일 타임에 불확정인 타입. dyn Sound, [i32], str이 해당. 반드시 포인터 뒤에서만 사용 가능
trait objectSound a = new Lion() (자동)dyn Sound처럼 dyn 뒤에 trait 이름이 붙은 타입. 구체 타입을 숨기고 trait 메서드만 호출 가능한 불투명 타입. Java는 dyn 없이 자동으로 동작
vtable (가상 메서드 테이블)JVM 내부 vtable (객체 헤더)trait 메서드의 함수 포인터 배열. 컴파일 타임에 확정, 바이너리 .rodata에 저장. Java는 객체 헤더(class word)가 vtable을 가리킴. Rust는 fat pointer의 vtable_ptr이 가리킴
type erasure (타입 소거)Java 제네릭의 동작 방식Java 제네릭은 컴파일 후 타입 파라미터 T가 제거되어 런타임에 T가 무엇인지 모름. Rust <T: Trait>는 반대 — 단형화로 타입별 코드 생성. dyn Trait만 타입 소거와 유사
정리

핵심 정리 — Java ↔ Rust 대응

☕ Java Rust설명
classstruct + impl데이터와 메서드 분리
interfacetrait메서드 계약 + 기본 구현
implementsimpl Trait for Typetrait 구현
new Book()Book {{ .. }}생성 (Rust는 스택 기본)
nullNone / Option<T>없음 표현 — 컴파일타임 강제
try/catchResult<T,E> + ?에러 처리 — 무시 불가
List<Sound>Vec<&dyn Sound>이종 컬렉션
<T extends Sound><T: Sound>Java=타입소거(동적), Rust=단형화(정적)
GC소유권 + Drop메모리 해제 — Rust는 컴파일타임
동적 디스패치 (항상)dyn Trait (명시)vtable 조회
없음impl Trait정적 디스패치 — 단형화
참조 8B (vtable은 헤더)fat pointer 16B (data+vtable)포인터 구조 차이

공식 출처