Rust 제네릭 소개 [2/2]: 특성 개체(정적 대 동적 디스패치)
15079 단어 tutorialcodenewbierustprogramming
Rust 제네릭 소개:
This post is an excerpt from my course Black Hat Rust
이제 궁금해하실 수 있습니다. 주어진 특성을 만족하는 다양한 구체적인 유형을 포함할 수 있는 컬렉션을 만드는 방법은 무엇입니까? 예를 들어:
trait UsbModule {
// ...
}
struct UsbCamera {
// ...
}
impl UsbModule for UsbCamera {
// ..
}
impl UsbCamera {
// ...
}
struct UsbMicrophone{
// ...
}
impl UsbModule for UsbMicrophone {
// ..
}
impl UsbMicrophone {
// ...
}
let peripheral_devices: Vec<UsbModule> = vec![
UsbCamera::new(),
UsbMicrophone::new(),
];
불행하게도 이것은 Rust에서 그렇게 간단하지 않습니다. 모듈의 메모리 크기가 다를 수 있으므로 컴파일러에서 이러한 컬렉션을 만들 수 없습니다. 벡터의 모든 요소는 같은 모양을 갖지 않습니다.
특성 개체는 런타임에 계약(특성)을 준수하는 다양한 구체적인 유형(다양한 모양)을 사용하려는 경우 이 문제를 정확하게 해결합니다.
개체를 직접 사용하는 대신 컬렉션의 개체에 대한 포인터를 사용할 것입니다. 이번에는 모든 포인터의 크기가 같으므로 컴파일러가 코드를 수락합니다.
실제로 이를 수행하는 방법은 무엇입니까? 스캐너에 모듈을 추가할 때 아래에서 볼 수 있습니다.
정적 대 동적 디스패치
그렇다면 일반 매개변수와 특성 객체의 기술적인 차이점은 무엇입니까?
일반 매개변수를 사용하는 경우(여기서는
process
함수용):ch_04/snippets/dispatch/src/statik.rs
trait Processor {
fn compute(&self, x: i64, y: i64) -> i64;
}
struct Risc {}
impl Processor for Risc {
fn compute(&self, x: i64, y: i64) -> i64 {
x + y
}
}
struct Cisc {}
impl Processor for Cisc {
fn compute(&self, x: i64, y: i64) -> i64 {
x * y
}
}
fn process<P: Processor>(processor: &P, x: i64) {
let result = processor.compute(x, 42);
println!("{}", result);
}
pub fn main() {
let processor1 = Cisc {};
let processor2 = Risc {};
process(&processor1, 1);
process(&processor2, 2);
}
컴파일러는 함수를 호출하는 각 유형에 대한 특수 버전을 생성한 다음 호출 사이트를 이러한 특수 함수에 대한 호출로 바꿉니다.
이것은 단형화로 알려져 있습니다.
예를 들어 위의 코드는 대략 다음과 같습니다.
fn process_Risc(processor: &Risc, x: i64) {
let result = processor.compute(x, 42);
println!("{}", result);
}
fn process_Cisc(processor: &Cisc, x: i64) {
let result = processor.compute(x, 42);
println!("{}", result);
}
이러한 기능을 직접 구현하는 것과 같습니다. 이를 정적 디스패치라고 합니다. 유형 선택은 컴파일 타임에 정적으로 이루어집니다. 최고의 런타임 성능을 제공합니다.
반면에 특성 개체를 사용하는 경우:
ch_04/snippets/dispatch/src/dynamic.rs
trait Processor {
fn compute(&self, x: i64, y: i64) -> i64;
}
struct Risc {}
impl Processor for Risc {
fn compute(&self, x: i64, y: i64) -> i64 {
x + y
}
}
struct Cisc {}
impl Processor for Cisc {
fn compute(&self, x: i64, y: i64) -> i64 {
x * y
}
}
fn process(processor: &dyn Processor, x: i64) {
let result = processor.compute(x, 42);
println!("{}", result);
}
pub fn main() {
let processors: Vec<Box<dyn Processor>> = vec![
Box::new(Cisc {}),
Box::new(Risc {}),
];
for processor in processors {
process(&*processor, 1);
}
}
컴파일러는 1개의 함수
process
만 생성합니다. 런타임에 프로그램은 어떤 종류의 Processor
가 processor
변수인지, 따라서 어떤 compute
메서드를 호출할지 감지합니다. 이것은 알려진 동적 디스패치입니다. 유형 선택은 런타임에 동적으로 이루어집니다.특성 개체
&dyn Processor
의 구문은 특히 덜 장황한 언어에서 오는 경우 약간 무거워 보일 수 있습니다. 나는 그것을 개인적으로 좋아한다! 한 눈에 dyn Processor
덕분에 함수가 특성 개체를 허용한다는 것을 알 수 있습니다.Rust는 각 변수의 정확한 크기를 알아야 하므로 참조
&
가 필요합니다.Processor
특성을 구현하는 구조의 크기가 다를 수 있으므로 유일한 해결책은 참조를 전달하는 것입니다. Box
, Rc
또는 Arc
와 같은 (스마트) 포인터일 수도 있습니다.요점은
processor
변수가 컴파일 시간에 알려진 크기를 가져야 한다는 것입니다.이 특정 예에서는
&*processor
함수에 대한 참조를 전달하기 위해 먼저 Box
를 역참조해야 하기 때문에 process
를 수행합니다. 이것은 process(&(*processor), 1)
와 동일합니다.동적으로 파견된 함수를 컴파일할 때 Rust는 후드 아래에 a vtable라고 하는 것을 만들고 런타임에 이 vtable을 사용하여 호출할 함수를 선택합니다.
This post is an excerpt from my course Black Hat Rust
마무리 생각
절대적인 성능이 필요할 때는 정적 디스패치를 사용하고 더 많은 유연성이 필요하거나 동일한 동작을 공유하는 개체 모음이 필요할 때는 특성 개체를 사용하세요.
Reference
이 문제에 관하여(Rust 제네릭 소개 [2/2]: 특성 개체(정적 대 동적 디스패치)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/sylvainkerkour/introduction-to-rust-generics-22-trait-objects-static-vs-dynamic-dispatch-aoo텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)