자바 클래스, 인터페이스와 러스트의 구조체, 메소드, 트레이트 방식 비교

자바(Java)는 모든 메소드를 클래스 내부에 정의합니다. 그러나, 러스트(Rust)는 데이터 구조(Struct)와 메소드(Impl block)가 물리적으로 분리되어 있습니다. 구조체 안에는 데이터만 정의하고, 별도의 impl 블록에서 그 구조체에 대한 메소드를 구현합니다.

Ⅰ. 데이터 구조, 메소드 구현 방식 비교

1. 자바: 클래스 내부에 메소드

자바(Java)는 모든 메소드를 클래스 내부에 정의합니다. 이는 객체 지향 프로그래밍(OOP)의 특징으로, 데이터(필드)와 행동(메소드)이 하나의 단위(클래스)에 밀접하게 결합(cohesion)되어 있습니다.

2. 러스트: 구조체와 메소드의 분리

러스트(Rust)에서는 데이터 구조(Struct)와 메소드(Impl block)가 물리적으로 분리되어 있습니다. 구조체 안에는 데이터만 정의하고, 별도의 impl 블록에서 그 구조체에 대한 메소드를 구현합니다.

3. 장단점

구분자바(메소드=클래스 내부)러스트(메소드=구조체 외부)
응집성데이터와 행동이 하나에 묶임데이터와 행동이 분리되어 명확
확장성상속 등 OOP 구조 확장 용이Trait, 여러 impl로 유연하게 확장
가독성클래스 안에서 한 번에 파악 가능데이터 정의와 메소드 구현 분리
유연성하나의 클래스가 하나의 메소드 집합같은 구조체에 여러 impl, trait 구현 가능
재사용성상속, 인터페이스로 구현Trait 등으로 다양한 방식의 재사용
관리의 용이성대형 클래스에서 복잡해질 수 있음관련 없는 메소드를 구조체와 별도 구현 가능
코드 조직화OOP 방식(클래스-중심)데이터 중심(struct), 행동 분리(impl, trait)
  • 자바는 객체 중심의 응집력, 개발 및 이해 용이성이 강점이나, 큰 클래스가 복잡해지거나 상속 구조의 한계 등이 있습니다.
  • 러스트는 데이터와 행동의 분리로 역할이 명확하며, trait 기반의 확장성과 안전성이 강점이지만, 초보자에겐 코드 연관성 파악이 다소 불편할 수 있습니다.

Ⅱ. 예제를 통한 비교

1. 자바(Java)

자바에서는 데이터(필드)와 메소드가 한 클래스 내부에 정의되어 객체 지향 프로그래밍 패러다임에 맞춰 응집되어 있습니다.

public class Point {
private int x;
private int y;

// 생성자
public Point(int x, int y) {
this.x = x;
this.y = y;
}

// 메소드: 두 점 사이 거리 계산
public double distance(Point other) {
int dx = this.x - other.x;
int dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}

// getter 메소드
public int getX() { return x; }
public int getY() { return y; }
}

// 사용
public class Main {
public static void main(String[] args) {
Point p1 = new Point(0, 0);
Point p2 = new Point(3, 4);
System.out.println(p1.distance(p2)); // 출력: 5.0
}
}
  • 징: Point 클래스 내부에 데이터(x, y)와 동작(거리 계산 메소드)을 모두 포함.
  • 장점: 객체 지향적 응집성(cohesion) 강화, 한 곳에서 모든 관련 기능 파악 가능.
  • 단점: 클래스가 커질수록 복잡성 증가, 상속 구조 제한 등.

2. 러스트(Rust)

러스트는 데이터 구조체(struct)와 메소드 구현부(impl 블록)를 분리해서 작성합니다. 이로써 역할 분리가 명확해지고, 여러 impl 블록과 trait을 통해 유연하게 확장할 수 있습니다.

// 데이터 정의: 구조체는 필드만 가짐
struct Point {
x: i32,
y: i32,
}

// 메소드 구현: impl 블록에서 정의
impl Point {
// 연관 함수(정적 메서드와 유사)
fn new(x: i32, y: i32) -> Point {
Point { x, y }
}

// 메소드: 두 점 사이 거리 계산
fn distance(&self, other: &Point) -> f64 {
let dx = (self.x - other.x) as f64;
let dy = (self.y - other.y) as f64;
(dx.powi(2) + dy.powi(2)).sqrt()
}
}

fn main() {
let p1 = Point::new(0, 0);
let p2 = Point::new(3, 4);
println!("{}", p1.distance(&p2)); // 출력: 5.0
}
  • 특징: struct는 데이터만 선언, 메소드는 별도의 impl 블록에서 구현.
  • 장점: 데이터와 행동이 분리되어 역할 명확, 여러 impl이나 trait로 기능 확장 유리, 컴파일타임 안전성 증대.
  • 단점: 관련 데이터와 메소드가 코드상 분리되어 있어 한눈에 파악하기 어려울 수 있음, 전통적인 OOP 방식과 차이 있음.

3. 비교 표

항목자바(Java)러스트(Rust)
구조클래스 내부에 데이터와 메소드가 함께 있음구조체(데이터)와 impl 블록(메소드)로 분리
작성 방식한 클래스 파일 내에서 모든 정의struct로 데이터 정의, 별도 impl로 메소드 구현
확장성상속과 인터페이스 기반 확장 (단일 상속)여러 impl 블록과 trait 조합으로 유연하고 다중 확장 가능
가독성관련 데이터와 메소드가 한 곳에 있어 파악 용이데이터와 메소드가 분리되어 코드가 흩어질 수 있음
안전성런타임 검사 및 가비지 컬렉션컴파일 타임 소유권 및 빌림 검사로 메모리 안전성 강화
메모리참조 타입 중심, 힙 할당 및 가비지 컬렉션 필요값 타입 중심, 명확한 메모리 제어 및 성능 최적화 가능
객체 지향전통적인 OOP 완전 지원클래스는 없으나 trait로 인터페이스 역할 및 객체지향 유사 기능 제공

러스트의 구조체+impl 방식은 자바와는 다르게 데이터와 메소드가 분리되어 있지만, impl 블록 내에서 메소드를 묶어 객체 지향적 프로그래밍의 많은 특징을 흉내 낼 수 있습니다. trait를 활용하면 인터페이스 역할도 하며, 상속 대신 다중 trait 구현으로 유연하게 기능을 확장할 수 있습니다.

Ⅲ . 자바의 상속과 인터페이스 구조와 러스트의 고급 Trait(트레이트) 및 패턴 비교

자바의 상속과 인터페이스 구조와 러스트의 고급 Trait(트레이트) 및 패턴을 심도 있게 비교 설명하면 다음과 같습니다.

1. 자바의 상속과 인터페이스 구조

가. 상속 (Inheritance)

  • 클래스 간에 “is-a” 관계를 표현하는 가장 기본적인 메커니즘입니다.
  • 한 클래스가 다른 클래스(부모 클래스)를 상속받아 멤버 변수와 메소드를 재사용하거나 오버라이드할 수 있습니다.
  • 단일 상속만 지원하여 다중 상속의 복잡성을 회피합니다.
  • 상속 구조가 깊어지면 유지보수가 어려워지고, 부모 클래스 변경 시 자식 클래스에 의도치 않은 영향이 발생할 수 있음.

나. 인터페이스 (Interface)

  • 여러 클래스가 구현해야 하는 메소드의 명세(계약)를 정의합니다.
  • 자바 8부터는 디폴트 메소드 구현도 지원하여 일부 기능적 확장 가능.
  • 인터페이스의 다중 구현이 가능해 상속 단점을 보완하고 다형성 제공.
  • 인터페이스는 구현 메소드가 없거나 기본 구현만 제공하므로, 설계 시 유연성을 줌.
interface Flyer {
void fly();
}
class Bird implements Flyer {
public void fly() {
System.out.println("Bird is flying");
}
}
class Airplane implements Flyer {
public void fly() {
System.out.println("Airplane is flying");
}
}

2. 러스트의 고급 Trait 및 패턴

가. Trait(트레이트) 개념

  • 러스트의 트레이트는 특정 기능을 구현하도록 강제하는 인터페이스 역할을 합니다.
  • 타입에 따라 여러 트레이트를 다중으로 구현할 수 있어 매우 유연합니다.
  • 트레이트 내에 메소드 기본 구현(default method)을 제공해 부분 구현도 가능.
  • Trait는 다중 상속을 대체하며, 조합(composition) 및 다형성을 지원합니다.

나. 고급 트레이트 특징

(1) 동일 메소드명을 가진 다중 트레이트 구현 (충돌 해결)

  • 예를 들어, 같은 이름 fly() 메소드를 가진 서로 다른 트레이트 Pilot, Wizard를 Human 타입에 모두 구현 가능.
  • 호출 시 Pilot::fly(&human), Wizard::fly(&human)처럼 트레이트 이름을 명시해 충돌 해결.
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
struct Human;

impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}

impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}

impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}

fn main() {
let person = Human;
Pilot::fly(&person); // This is your captain speaking.
Wizard::fly(&person); // Up!
person.fly(); // *waving arms furiously*
}

(2) 슈퍼 트레이트 (Super-traits)

  • 한 트레이트가 다른 트레이트의 구현을 전제로 하는 경우 사용.
  • 예: OutlinePrint 트레이트가 Display 트레이트를 반드시 구현한 경우에만 구현 가능하도록 제한.
use std::fmt::Display;

trait OutlinePrint: Display {
fn outline_print(&self) {
// Display 기능 이용해 출력하는 구현
println!("*{}*", self);
}
}

(3) 제네릭과 트레이트 바운드 (Trait Bounds)

  • 함수, 구조체, 열거형 등에서 트레이트를 타입 매개변수 조건으로 지정하여 유연한 재사용성 제공.
fn print_info<T: Display + Clone>(item: T) {
println!("{}", item);
}

(4) Newtype 패턴

  • 외부 타입에 대해 새로운 타입을 만든 뒤 트레이트를 구현해 동작을 확장하거나 수정하는 패턴.
  • 원래 타입의 구현과 격리돼 안전성과 캡슐화 유지에 도움.

다. 자바 상속-인터페이스와 러스트 Trait 패턴 비교

특징자바 (상속, 인터페이스)러스트 (고급 Trait 패턴)
다중 상속 지원 여부클래스는 단일 상속, 인터페이스 다중 구현 가능다중 트레이트 구현 완전 지원
메소드 충돌 처리인터페이스 디폴트 메소드 충돌은 명시적 오버라이딩 필요트레이트별로 명시적 호출(TraitName::method)로 충돌 처리
구현 분리 여부상속 기반으로 구현 내용 일부 공유, 부모-자식 직접 연관데이터와 행동 완전 분리, 여러 impl 블록과 트레이트로 유연한 구현
확장성 및 유연성인터페이스와 추상 클래스 활용 가능, 런타임 다형성 제공트레이트 조합 및 바운드로 더 강력하고 유연한 제네릭 기반 다형성 가능
안전성런타임에 예외 발생 가능, 컴파일 타임 타입 검사 제한컴파일 타임 엄격한 타입 검사 및 소유권 규칙으로 높은 안전성 보장
메모리 구조클래스는 힙에 할당, 가비지 컬렉션 필요구조체는 값 타입(STACK 기반), 트레이트 객체는 런타임에 크기를 모르는 타입 표현 지원

라. 러스트 Trait를 활용한 고급 설계 패턴 예시

(1) Trait 객체 (Trait Objects)

  • 런타임에 다양한 타입을 동적으로 처리하고자 할 때 사용.
  • Box<dyn Trait> 형태로 포인터에 담아 추상화 제공.
trait Animal {
fn speak(&self);
}

struct Dog;
struct Cat;

impl Animal for Dog {
fn speak(&self) {
println!("Woof!");
}
}

impl Animal for Cat {
fn speak(&self) {
println!("Meow!");
}
}

fn make_animal_speak(animal: Box<dyn Animal>) {
animal.speak();
}

fn main() {
let dog = Box::new(Dog);
let cat = Box::new(Cat);

make_animal_speak(dog);
make_animal_speak(cat);
}

(2) 제네릭과 Trait Bound를 이용한 추상화

fn print_animal_speak<T: Animal>(animal: T) {
animal.speak();
}

(3) Newtype 패턴

struct Wrapper(Vec<String>);

impl std::fmt::Display for Wrapper {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}

5. 러스트 Trait 객체와 동적 디스패치(Dynamic Dispatch)

가. 기본 개념

  • 러스트의 Trait 객체는 런타임에 다양한 타입들을 추상화하고 동적으로 호출할 수 있게 하는 기능입니다.
  • 구체적인 타입을 명시하지 않고 특정 트레이트를 구현한 여러 객체를 하나의 타입으로 사용할 때 유용합니다.
  • dyn Trait 키워드로 표현하며, 이때 메소드 호출은 컴파일 타임이 아닌 런타임에 결정(동적 디스패치)됩니다.

나. 장점

  • 다양한 타입에 대해 동일 인터페이스 역할을 수행할 수 있게 하며, 실행 시점에 적절한 메소드가 호출됩니다.
  • 유연한 설계와 다형성 제공.

다. 단점

  • 동적 디스패치로 인해 약간의 성능 오버헤드 발생.
  • 컴파일 타임에 타입 크기를 알 수 없으므로 보통 Box<dyn Trait>, &dyn Trait 형태로 사용.

예제 코드

trait Animal {
fn speak(&self);
}

struct Dog;

impl Animal for Dog {
fn speak(&self) {
println!("Woof!");
}
}

struct Cat;

impl Animal for Cat {
fn speak(&self) {
println!("Meow!");
}
}

fn animal_speak(animal: &dyn Animal) {
animal.speak();
}

fn main() {
let dog = Dog;
let cat = Cat;

animal_speak(&dog); // Woof!
animal_speak(&cat); // Meow!
}
  • animal_speak 함수는 타입이 &dyn Animal로, 호출하는 객체가 Dog이든 Cat이든 런타임에 올바른 speak 메서드를 호출합니다.

요약

  • 자바의 상속과 인터페이스는 클래스 중심의 단일 상속과 다중 인터페이스 구현으로 객체 지향 프로그래밍 패러다임을 완성하며, 런타임 다형성을 제공합니다.
  • 러스트의 트레이트는 다중 상속을 대체하고, 유연한 기능 확장과 컴파일 타임 안전성을 제공하며, 타입별로 여러 트레이트를 조합해 강력한 추상화와 다형성을 구현합니다.
  • 러스트에서 트레이트는 충돌 해결, 슈퍼 트레이트, 제네릭 바운드, 트레이트 객체 등 다양한 고급 패턴으로 복잡한 설계를 수행할 수 있습니다.
  • 두 언어 모두 장단점이 있으므로, 팀의 경험과 프로젝트 요구사항에 따라 적합한 방식을 선택하는 것이 중요합니다.

다른 언어와 비교한 Rust의 동시성(concurrency) 장,단점

Rust의 동시성(concurrency)은 안전성과 성능을 모두 고려한 설계로, 데이터 경쟁(data race)을 컴파일 타임에 방지하고, 성능 저하 없이 병렬 처리를 가능하게 합니다. C++, Java, Python, Go 등 타 언어와 비교해 장단점을 알아보겠습니다.

1. Rust의 동시성 개념

Rust는 동시성을 다음 세 가지 방식으로 지원합니다:

  1. 스레드 기반 동시성 (std::thread)
    • OS 스레드를 생성하여 병렬 작업 수행.
    • thread:spawn을 통해 새로운 스레드를 실행.
  2. 메시지 기반 통신 (std::sync::mpsc)
    • 채널을 통해 스레드 간 데이터 교환.
    • 데이터 소유권을 안전하게 이동.
  3. 비동기 프로그래밍 (async/await, tokio, async-std)
    • 효율적인 I/O 처리.
    • 싱글 스레드에서 수천 개의 작업을 동시에 처리할 수 있음.
    • Future를이용한 논블로킹 방식.

2. Rust 동시성의 핵심 특징

특징설명
데이터 레이스 방지컴파일 타임에 mut, &mut, Send, Sync 등을 통해 공유 자원에 대한 안전성 확보
제로 코스트 추상화고급 추상화를 사용해도 런타임 오버헤드 없음
fearless concurrency안전하게 동시성을 구현할 수 있어 “두려움 없는 동시성”이라고도 불림

3. 타 언어와의 비교

가. Rust vs C++

항목RustC++
안전성컴파일 타임 데이터 레이스 방지런타임에서 버그 발견 가능
메모리 모델소유권 시스템수동 메모리 관리
쓰레드 API안전하고 모던한 추상화복잡하고 안전하지 않은 경우 많음

🔹 Rust는 안전하고 버그 없는 병렬 처리를 제공
🔸 C++은 성능은 뛰어나지만 관리 책임이 개발자에게 있음 (예: 뮤텍스 실수 → 데이터 손상)

나. Rust vs Java

항목RustJava
런타임없음 (네이티브 실행)JVM 기반
동기화Mutex, RwLock, channel 등 명시적synchronized, volatile, ExecutorService 등
성능시스템 수준 고성능GC와 JVM 오버헤드 존재

🔹 Rust는 GC 없는 고성능 동시성
🔸 Java는 GC로 메모리 관리가 쉽지만 지연 가능성 존재

다. Rust vs Python

항목RustPython
성능매우 빠름느림 (인터프리터 기반)
GIL (Global Interpreter Lock)없음있음 (멀티 코어 병렬 처리 불가)
비동기 처리고성능 async/awaitasyncio로 가능하나 성능은 낮음

🔹 Rust는 진짜 병렬 처리 가능
🔸 Python은 GIL 때문에 CPU 병렬처리에 약함 (I/O 병렬만 현실적)

라. 요약

구분Rust의 장점Rust의 단점
성능네이티브 수준의 성능안전성을 위한 빌드 시간 증가
안전성데이터 레이스를 컴파일 타임에 방지초기 진입 장벽 (개념이 복잡함)
표현력async/await, channel, Mutex 등 현대적 추상화도구/라이브러리 생태계가 다른 언어보다 적은 편
병렬성GIL 없음, 진짜 병렬 처리 가능쓰레드 디버깅이 어려울 수 있음

4. Rust 동시성이 특히 유리한 분야

  • 고성능 웹 서버 (예: Actix, Axum)
  • 실시간 시스템 (예: 게임, IoT)
  • 병렬 데이터 처리 (예: 이미지/영상 처리)
  • 시스템 프로그래밍 (드라이버, 임베디드)

5. 1부터 100만까지 숫자의 합을 4개 스레드로 나눠 병렬 계산 비교

가. Rust 버전

use std::thread;

fn main() {
let data: Vec<u64> = (1..=1_000_000).collect();
let chunk_size = data.len() / 4;

let mut handles = vec![];

for i in 0..4 {
let chunk = data[i * chunk_size..(i + 1) * chunk_size].to_vec();
let handle = thread::spawn(move || chunk.iter().sum::<u64>());
handles.push(handle);
}

let total: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum();
println!("합계: {}", total);
}

설명:

use std::thread;
  • Rust의 표준 라이브러리에서 thread 모듈을 가져옵니다. 병렬 처리를 위해 사용됩니다.
let data: Vec<u64> = (1..=1_000_000).collect();
  • (1..=1_000_000)는 표현식이며 RangeInclusive 타입으로, 1부터 1_000_000까지 포함하는 이터레이터입니다.
  • .collect()는 이터레이터(iterator)를 모아서 컬렉션(예: Vec, HashMap, String)으로 변환하는 메서드입니다.
  • 명시적으로 Vec 타입을 선언했기 때문에, collect()는 모든 숫자를 벡터로 수집하게 됩니다.
let chunk_size = data.len() / 4;
  • 데이터를 4개의 스레드로 나눌 것이기 때문에, 각 스레드가 처리할 데이터의 크기를 계산합니다.
  • chunk_size = 250_000
let mut handles = vec![];
  • 스레드 핸들(JoinHandle)들을 저장할 벡터.
  • 각 스레드는 나중에 .join()으로 결과를 수집할 수 있습니다.
for i in 0..4 {
let chunk = data[i * chunk_size..(i + 1) * chunk_size].to_vec();
let handle = thread::spawn(move || chunk.iter().sum::<u64>());
handles.push(handle);
}
  • 루프를 4번 돌며 벡터를 4등분합니다.
let chunk = data[i * chunk_size..(i + 1) * chunk_size].to_vec();
  • data를 4등분하여 각 스레드에 넘길 복사본을 만든 후 chunk에 할당합니다.
  • i가 0~3까지 반복되므로:
    • i = 0 일 때는 data[0..250000]
    • i = 1 일 때는 data[250000..500000]
    • i = 2 일 때는 data[500000..750000]
    • i = 3 일 때는 data[750000..1000000]
  • 이렇게 전체 데이터를 4개의 슬라이스(slice)로 나눕니다. 하지만 슬라이스는 참조(&)이며, 여러 스레드가 같은 데이터를 공유할 때, 데이터 경합(data race)을 막기 위해 컴파일러가 참조의 안전성을 보장해야 하므로, .to_vec()을 사용하여 슬라이스의 복사본을 만들어 소유권을 가지는 새 벡터로 만듭니다. 이제 이 벡터는 독립적 소유권을 가지므로, move를 통해 클로저에 안전하게 넘길 수 있습니다
let handle = thread::spawn(move || chunk.iter().sum::<u64>());
  • thread::spawn(…) → 새로운 스레드(thread)를 만들어서 주어진 작업을 실행합니다.
  • move || … → 클로저(익명 함수)에서 외부 변수인 chunk의 소유권을 이동시켜 사용합니다.
  • chunk.iter().sum::() → chunk의 모든 원소를 합산하여 u64값을 반환합니다.
  • 반환된 handle은 JoinHandle 타입이고, 이걸 handles 벡터에 저장해 나중에 결과를 수집합니다.
handles.push(handle);
  • thread::spawn(…)의 결과인 JoinHandle을 handles 벡터에 저장합니다.
  • 이 handles는 모든 스레드 작업이 끝난 뒤 결과를 수집하는 데 사용됩니다.
let total: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum();
  • 스레드에서 계산한 4개의 부분합을 모아서 전체 합을 계산한다.
  • handles.into_iter() .into_iter()는 handles 벡터의 소유권을 consuming iterator로 가져옵니다(copy가 아닌 move). 즉, 이후 handles는 더 이상 사용할 수 없습니다.
  • .map(|h| h.join().unwrap())의각 h는 JoinHandle 이고, h.join()은 이 스레드가 끝날 때까지 기다리고, .unwrap()으로 에러 무시하고 강제 추출합니다. .map(…) 부분은 4개의 스레드를 기다리며 각각의 계산된 합을 모아 [u64; 4] 형태로 만듭니다
  • .sum()은 [u64; 4]을 전부 더해서 최종 합계를 구해서, total에 할당합니다.
println!("합계: {}", total);
  • “합계: 500000500000″을 출력합니다.
    500000500000은 1000000 * 1000001 / 2 = 500000500000입니다.

나. Python (threading 사용, CPU 병렬 처리 실패 예)

import threading

data = list(range(1, 1_000_001))
results = [0] * 4

def worker(idx, chunk):
results[idx] = sum(chunk)

threads = []
chunk_size = len(data) // 4

for i in range(4):
t = threading.Thread(target=worker, args=(i, data[i*chunk_size:(i+1)*chunk_size]))
threads.append(t)
t.start()

for t in threads:
t.join()

print("합계:", sum(results))

🔸 설명:

  • Python은 GIL(Global Interpreter Lock) 때문에 진짜 병렬 아님
  • threading은 CPU 병렬 처리 불가 → 오히려 느림
  • multiprocessing을 쓰면 병렬 가능하지만 복잡도 증가

다. Java 버전

import java.util.concurrent.*;

public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(4);
int[] data = new int[1_000_000];
for (int i = 0; i < data.length; i++) data[i] = i + 1;

Future<Long>[] results = new Future[4];
int chunkSize = data.length / 4;

for (int i = 0; i < 4; i++) {
final int start = i * chunkSize;
final int end = (i + 1) * chunkSize;
results[i] = executor.submit(() -> {
long sum = 0;
for (int j = start; j < end; j++) sum += data[j];
return sum;
});
}

long total = 0;
for (Future<Long> result : results) {
total += result.get();
}

executor.shutdown();
System.out.println("합계: " + total);
}
}

🔸 설명:

  • ExecutorService를 통해 병렬 처리
  • 비교적 안전하지만, Future.get()이 blocking이며 예외 처리 필요
  • GC가 있어 런타임 예측 어려움

라. C++ 버전 (C++11 이상)

#include <iostream>
#include <vector>
#include <thread>
#include <numeric>

int main() {
std::vector<uint64_t> data(1'000'000);
std::iota(data.begin(), data.end(), 1);

uint64_t partial_sums[4] = {};
std::vector<std::thread> threads;

size_t chunk_size = data.size() / 4;

for (int i = 0; i < 4; ++i) {
threads.emplace_back([i, chunk_size, &data, &partial_sums]() {
partial_sums[i] = std::accumulate(
data.begin() + i * chunk_size,
data.begin() + (i + 1) * chunk_size,
0ULL
);
});
}

for (auto& t : threads) t.join();

uint64_t total = 0;
for (auto s : partial_sums) total += s;

std::cout << "합계: " << total << std::endl;
}

🔸 설명:

  • 고성능이지만, &data, &partial_sums는 데이터 경쟁 가능성 존재
  • 공유 자원 동기화에 실패하면 잘못된 결과 나올 수 있음
  • 동기화 도구 사용 시 성능 저하 우려

마. 언어별 동시성 비교

언어병렬 처리 성능안전성코드 복잡도주의사항
Rust매우 뛰어남컴파일 타임 안전 보장다소 복잡소유권, 라이프타임 이해 필요
Python낮음 (GIL)안전하나 느림간단multiprocessing 사용 시 복잡
Java중간런타임 에러 가능보통예외 처리, GC
C++고성능데이터 레이스 가능복잡직접 동기화 필요

6. Rust vs Go 동시성 비교 요약

항목RustGo
동시성 모델명시적 스레드 + 채널 + async/await경량 고루틴(goroutine) + 채널(channel)
메모리 관리수동 + 소유권 시스템 (GC 없음)GC 있음 (자동 메모리 관리)
안전성컴파일 타임에 데이터 경쟁 차단런타임에 데이터 레이스 가능 (race detector 필요)
런타임없음 (zero-cost abstraction)있음 (스케줄러 + GC)
학습 곡선가파름 (소유권/라이프타임 개념 필요)비교적 완만
성능매우 뛰어남 (GC 없음)빠르지만 GC 오버헤드 존재

7. 10개의 작업을 동시 실행 비교

가. Rust:

use std::thread;

fn main() {
let mut handles = vec![];

for i in 0..10 {
let handle = thread::spawn(move || {
println!("Rust 스레드 {} 실행", i);
});
handles.push(handle);
}

for handle in handles {
handle.join().unwrap();
}
}

✅ 특징:

  • std::thread::spawn으로 OS 스레드 생성
  • 스레드 수 제한 없음 (하지만 무거움)
  • 안전하게 소유권 이동 (move) → 데이터 경쟁 없음

나. Go: 고루틴 10개 실행

package main

import (
"fmt"
"time"
)

func main() {
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Printf("Go 고루틴 %d 실행\n", i)
}(i)
}

time.Sleep(time.Second) // 고루틴이 끝날 시간 대기
}

✅ 특징:

  • go 키워드 하나로 병렬 실행
  • 고루틴은 스레드보다 훨씬 가볍고 수천 개 생성 가능
  • 단, 공유 자원 접근 시 데이터 레이스 가능 (예: i 변수 캡처 문제 발생 가능)

다. 동시성 핵심 차이

항목RustGo
실행 단위OS 스레드 또는 async task고루틴 (경량 스레드)
병렬 처리 수단스레드, 채널, async/await고루틴, 채널
데이터 보호컴파일 타임 소유권 체크뮤텍스, 채널, race detector
동시성 철학“Fearless Concurrency” (두려움 없는 동시성)“Do not communicate by sharing memory…”
GC없음 (직접 메모리 관리)있음 (자동 정리되지만 성능 오버헤드 발생 가능)

라. 성능과 안전성 비교

항목RustGo
성능GC가 없어서 시스템 자원 최대 활용GC와 스케줄러의 오버헤드 존재
안전성데이터 경쟁을 컴파일 타임에 방지기본적으로 가능함 (race detector로 검사해야 함)
스케일링수천 개의 작업 처리 시 async 필요수천 개 고루틴도 가볍게 처리 가능
디버깅 난이도복잡 (라이프타임, borrow checker 등)비교적 단순

마. 상황별 언어 선택

상황Rust 추천Go 추천
고성능 시스템 (e.g. 게임, 실시간 처리, WebAssembly)X
빠른 개발, 유지보수 쉬운 서버 (e.g. 웹 API, 클라우드 백엔드)가능하지만 무겁고 복잡매우 적합
메모리 제어 필요 (e.g. 임베디드, 드라이버)X
초고성능 네트워크 서버 (e.g. Actix, Tokio 기반)GC로 한계 가능
간단한 병렬 작업, CLI 툴한계

바. 결론

항목RustGo
성능최상급좋음 (하지만 GC 존재)
안전성컴파일 타임 보장런타임 race 가능
개발 속도어렵고 장벽 높음빠르고 쉬움
확장성 (스케일)async 사용 시 매우 뛰어남고루틴 덕분에 뛰어남
유지보수성복잡간단하고 명확

사. 요약

  • Rust: 동시성을 정밀하게 제어해야 하거나, 성능과 안전이 최우선인 경우 유리
  • Go: 빠르게 개발하고, 다수의 작업을 단순하게 병렬 처리할 때 탁월