寫這篇文章的時候我只是用列印函數地址的方式來測試的,這個測試方法可能並不是很妥當,可能會引起編譯器在某些情況下放棄最佳化(個人猜測),目前想到的方法:應該在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);}
看看這個運行結果:
0x7f81474121500x7f8147412178
結果可以看到: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);}//儲存closurestruct 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:0x7ffbe66e5180addr of Foo::print:0x7ffbe66e5190addr of Bar::new :0x7ffbe66e51a0addr of Bar::print :0x7ffbe66e51b0addr of Foo::foo:0x7ffbe66e51c0addr of Foo::print:0x7ffbe66e51d0addr of Bar::new :0x7ffbe66e51e0addr 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 siteerror: aborting due to previous errorplaypen: application terminated with error code 101Compilation 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,0x7f7ca62b1140i=2,0x7f7ca62b1140i=3,0x7f7ca62b1140i=1,0x7f7ca62b1140