프로그래밍러스트 개발자

러스트에서 트레이트는 어떻게 구현되며 동적 및 정적 디스패치란 무엇인가요? 각 접근 방식을 언제 사용해야 하나요?

Hintsage AI 어시스턴트로 면접 통과

답변

러스트의 트레이트는 인터페이스를 정의하는 방식입니다: 트레이트는 타입이 구현해야 하는 메서드 집합을 설명합니다. 트레이트의 구현은 제네릭과 메서드의 동적 디스패치를 사용할 수 있게 해줍니다.

  • 정적 디스패치 (제네릭 및 바운드를 통한): 컴파일 시간에 특정 메서드 구현이 호출됩니다. 제네릭 코드(impl<T: Trait> 또는 fn foo<T: Trait>(t: T))가 사용되며, 컴파일 중 각 타입에 대해 별도의 함수 버전이 생성됩니다(모노모르피제이션).

  • 동적 디스패치: 함수가 트레이트에 대한 객체를 참조로 받을 때(&dyn Trait), 컴파일러는 어떤 타입이 사용될지 미리 알 수 없습니다. 메서드 호출은 실행 시간에 vtable(가상 테이블)을 통해 이루어집니다.

예제:

trait Animal { fn speak(&self); } struct Dog; impl Animal for Dog { fn speak(&self) { println!("Woof!"); } } // 정적 디스패치 def print_animal_static<T: Animal>(a: T) { a.speak(); } // 동적 디스패치 def print_animal_dyn(a: &dyn Animal) { a.speak(); }

사용 시기:

  • 정적 디스패치는 성능이 중요하고 컴파일 시간에 타입이 알려져 있을 때 사용합니다.
  • 동적 디스패치는 이질적인 객체 컬렉션을 다뤄야 하거나 타입이 미리 알려지지 않은 경우에 사용합니다.

속임수 질문

제네릭 타입 트레이트를 Vec<Trait>에 저장할 수 있나요? 그 이유는 무엇인가요?

답변: 아니요, 그렇게 할 수 없습니다. Vec는 사이즈가 있는 타입을 요구하는데, 트레이트는 본질적으로 크기가 없습니다. Box<dyn Trait> 또는 트레이트에 대한 참조를 사용해야 합니다.

// let v: Vec<Trait> = vec![]; // 컴파일 오류 let v: Vec<Box<dyn Animal>> = vec![Box::new(Dog)]; // 정상

주제에 대한 미세한 차이를 알지 못해 발생한 실제 오류의 예들


이야기

Box 또는 Rc를 사용하지 않고 트레이트 객체 컬렉션을 만들려는 시도:

// let coll: Vec<MyTrait> = vec![]; // 오류: the size for values of type ... cannot be known

결과적으로 — 코드의 완전한 리팩토링과 동적 트레이트에 대해 Box 사용의 필요성을 깨닫는 중요한 교훈.

이야기

주니어 개발자가 동적 디스패치를 통해 복잡한 API를 구현했는데, 정적 디스패치가 가능했던 부분이 있었습니다. 이로 인해 성능 저하와 인라인 문제를 초래했으며, 모든 다형성이 과도했습니다.

이야기

데모 클라이언트에서 다양한 프로토콜과 상호 작용하기 위해 임시 객체에 대한 참조(&dyn Trait)를 저장하려고 시도했으나, 이로 인해 dangling references와 runtime panic이 발생했습니다. 명시적인 소유권을 가진 Box<dyn Trait>로 변경해야 했습니다.