先看代码:
trait myTrait{
fn tr1(&self);
fn tr2(&self);
}
struct cat{i:u8,}
struct dog{i:u8,}
impl myTrait for cat {
fn tr1(&self) { }
fn tr2(&self) { }
}
struct bear{i:u8,}
impl myTrait for bear {
fn tr1(&self) { }
fn tr2(&self) { }
}
impl myTrait for dog {
fn tr1(&self) {}
fn tr2(&self) { }
}
fn fun_impl(arg:impl myTrait){ arg.tr1();arg.tr2();}
fn fun_dyn(arg:&dyn myTrait){arg.tr1();arg.tr2();}
fn main() {
let c=cat{i:1};
let d=dog{i:1};
fun_impl(c);
fun_impl(d);
let c1=cat{i:1};
let d1=dog{i:1};
fun_dyn(&c1);
fun_dyn(&d1);
}
一个trait:myTrait,三个struct: dog,bear,cat, 两个函数:fun_impl(静态派发调用),fun_dyn(动态派发调用)
为了说明问题,把这段代码转成汇编,
一、先看静态派发的fun_impl如下图
1、先在main里调用了2次
2、在程序体内分别为dog和cat生成一次
至于没有为bear生成,是因为在main里没有调用,编译器自动忽略了。这就是静态派发,就是把可能用到的trait function全部帮你提前静态生成,好处在于快,通过单态化, 编译器消除了泛型缺点是过多展开可能会导致编译生成的二级制文件体积过大。以上例子是用于函数参数的形式,同样适用于函数返回的情形,类似于:fn do(...) -> impl SomeTrait
二、再看动态派发的fun_dyn如下图
1、同样也在main里调用两次
2、但在程序体内只生成了一次
顺便提一句:callq 这两句,分别是24,32,还记得吗我在Rust基础-关于trait之四-不得不说一下rust fat point探索rust的fat point 原理https://blog.csdn.net/DarcyZ_SSM/article/details/124351982
这篇中提到的前3个是固定生成的第四个开始就是自定义的部分,代码分别定义了tr1()和tr2(),所以是24和32.
小小的深入一下,继续:
其中的unnamed_1 和unnamed_2,就是两次fun_dyn函数调用前的参数压栈,编译器已经帮忙把vtable准备好了。下面的截图就是。rust/src/librustc_codegen_llvm/meth.rs
那动态派发有啥用呢?
考虑下列代码:
fn fun(p1: i32) -> impl View {
if param1 > 10{
return Circle {};
} else {
return Square{};
}
}
以上代码会出错,虽然Circle和Square都实现了View,但rust编译器需要返回大小一致,所以类型不同,大小自然不同。那么把静态的改成动态的指针就Ok了,如下。
fn fun(p1: i32) -> Box {
if param1 > 10{
return Box::new(Circle {});
} else {
return Box::new(Square{});
}
}
相关文章:
Rust基础-关于trait之一_DarcyZ_SSM的博客-CSDN博客
Rust基础-关于trait之三_DarcyZ_SSM的博客-CSDN博客
Rust基础-关于trait之四-不得不说一下rust fat point_DarcyZ_SSM的博客-CSDN博客
Rust基础-关于trait之五_DarcyZ_SSM的博客-CSDN博客