写这篇文章的时候我只是用打印函数地址的方式来测试的,这个测试方法可能并不是很妥当,可能会引起编译器在某些情况下放弃优化(个人猜测),目前想到的方法:应该在release模式下大量impl一个实现了静态方法的trait看文件膨胀的速度,这个我目前还在想办法测试。所以目前来说下面的结论暂时还是存疑的,希望大家也能想办法测试或者能直接找到官方的实际做法的文章。
目前了解来看rust泛型的概念很好,比c++多了很多类型限定和编译时检查,出错提示相对c++的模板来说也特别特别的友好。但任何事物总是有两面性,过度的使用泛型会使得代码极度的膨胀,这个膨胀的速度在我看来比c++的模板快多了
1 trait里的默认函数和方法引起的代码膨胀
首先来看一段简单的代码:
trait Foo{
fn foo(){
println!("foo");
}
}
struct Bar1;
struct Bar2;
impl Foo for Bar1{}
impl Foo for Bar2{}
fn main(){
println!("{:p}",&Bar1::foo);
println!("{:p}",&Bar2::foo);
}
看看这个运行结果:
0x7f8147412150
0x7f8147412178
结果可以看到:Bar1::foo和Bar2::foo是两个地址并不一样的函数,这样就意味着虽然这两个函数的二进制代码完全一样,但是rust还是给复制了两份。
从rust语言本身的特性来看这么做有他的理由,比如默认函数可以被覆盖等,但从结果上来看你就必须很小心了,如果你的trait Foo有很多默认函数,并且不巧你有很多类型都实现了Foo,那么代码的膨胀速度估计会出乎意料的
2 保存closure引起的代码膨胀
rust的closure与c++类似,都是生成一个匿名的结构体包装你用到的“环境”里所有的值,然后类似c++重载operator ()来实现调用,这也就意味着两个完全相同的closure,比如:|i|i+100 ,你写两次他就是两个不同的类型,这样你用泛型保存closure的时候就要注意了
下面看一个稍微复杂一点的例子:
trait Foo{
fn foo(){
println!("addr of Foo::foo:{:p}",&Self::foo);
}
fn print_foo_addr(&self);
}
//保存closure
struct Bar<F:Fn(i32)->i32>{
func:F,
i:i32,
}
impl <F> Bar<F> where F:Fn(i32)->i32{
fn new(f:F)->Bar<F>{
Bar{
func:f,
i:100
}
}
fn print_new_addr(&self){
println!("addr of Bar::new :{:p}",&Self::new);
println!("addr of Bar::print :{:p}",&Self::print_new_addr);
}
}
impl<F>Foo for Bar<F> where F:Fn(i32)->i32 {
fn print_foo_addr(&self){
Self::foo();
println!("addr of Foo::print:{:p}",&Self::print_foo_addr);
}
}
fn main(){
let b = Bar::new(|i|{
i+100
});
let b2 = Bar::new(|i|{
i+100
});
b.print_foo_addr();
b.print_new_addr();
println!("");
b2.print_foo_addr();
b2.print_new_addr();
}
Play地址:http://is.gd/CM8Vnj
无论是debug,release,nightly输出结果都差不多:
addr of Foo::foo:0x7ffbe66e5180
addr of Foo::print:0x7ffbe66e5190
addr of Bar::new :0x7ffbe66e51a0
addr of Bar::print :0x7ffbe66e51b0
addr of Foo::foo:0x7ffbe66e51c0
addr of Foo::print:0x7ffbe66e51d0
addr of Bar::new :0x7ffbe66e51e0
addr of Bar::print :0x7ffbe66e51f0
可以看到所有的方法的代码全部被复制了一遍
如果你只是用某个函数或者方法接收一个closure做回调那就好很多了,rust不是整个类型的全部函数方法都复制,rust只会把你接收closure的那个函数重新实现一下
附带一个列子强化下即使看起来一样的closure都是不一样的:
struct Foo<F:Fn(i32)->i32>{
f:F,
}
impl<F:Fn(i32)->i32> Foo<F>{
fn new(f:F)->Foo<F>{
Foo{
f:f,
}
}
}
fn main(){
let b = Foo::new(|i|{i+100});
(b.f)(100);
}
上面这个例子保存了一个closure,编译和运行都正常,play地址:http://is.gd/JVCd00
我们对其做一些修改,再添加一个成员:f2:F
struct Foo<F:Fn(i32)->i32>{
f:F,
f2:F,
}
impl<F:Fn(i32)->i32> Foo<F>{
fn new(f:F,f2:F)->Foo<F>{
Foo{
f:f,
f2:f2,
}
}
}
fn main(){
let b = Foo::new(|i|{i+100},|i|{i+100});
(b.f)(100);
(b.f2)(100);
}
Play地址:http://is.gd/GSTQbq
这个列子是不能正确编译的的,看看编译结果:
<anon>:20:33: 20:43 error: mismatched types:
expected `[closure <anon>:20:22: 20:32]`,
found `[closure <anon>:20:33: 20:43]`
(expected closure,
found a different closure) [E0308]
<anon>:20 let b = Foo::new(|i|{i+100},|i|{i+100});
^~~~~~~~~~
note: in expansion of closure expansion
<anon>:20:33: 20:43 note: expansion site
<anon>:20:33: 20:43 help: see the detailed explanation for E0308
<anon>:20:33: 20:43 note: no two closures, even if identical, have the same type
<anon>:20 let b = Foo::new(|i|{i+100},|i|{i+100});
^~~~~~~~~~
note: in expansion of closure expansion
<anon>:20:33: 20:43 note: expansion site
<anon>:20:33: 20:43 help: consider boxing your closure and/or using it as a trait object
<anon>:20 let b = Foo::new(|i|{i+100},|i|{i+100});
^~~~~~~~~~
note: in expansion of closure expansion
<anon>:20:33: 20:43 note: expansion site
error: aborting due to previous error
playpen: application terminated with error code 101
Compilation failed.
也就是说我们看起来两个完全一样的closure编译器认为是两种类型
3 lifetime参数并不会引起代码的复制
lifetime也放在了<>
里,测试下看看:
struct Foo<'a>{
i:&'a i32,
}
impl<'a> Foo<'a>{
fn print_addr(&self){
println!("i={},{:p}",self.i,&Self::print_addr);
}
}
fn main(){
let i=1i32;
let f1 = Foo{i:&i};
f1.print_addr();
{
{
let k=2i32;
let f3 = Foo{i:&k};
f3.print_addr();
}
let j=3i32;
let f2 = Foo{i:&j};
f2.print_addr();
{
let f4 = Foo{i:&i};
f4.print_addr();
}
}
}
Play地址:http://is.gd/lepdI2
结果可以看出lifetime并不会引起代码的复制
i=1,0x7f7ca62b1140
i=2,0x7f7ca62b1140
i=3,0x7f7ca62b1140
i=1,0x7f7ca62b1140