10玩rust_C++工程师的Rust迁移之路(5)- 继承与组合 - 下
2020-11-25 更新:
在上一篇文章 https://zhuanlan.zhihu.com/p/76740667 中,我介紹多態(tài)、靜態(tài)分發(fā)和動態(tài)分發(fā)的概念,以及他們各自在C++和Rust中的實現方式。
在本文中,我會重點講Rust中的Trait實現的靜態(tài)分發(fā)與C++ 20(準確的說,現在還叫做C++ 2a)中的concepts的區(qū)別。
在具體介紹這個區(qū)別之前,我想跟大家介紹一個概念,叫做duck typing(鴨子類型)。
鴨子類型
呃……你沒有看錯,這個鴨子就是你平常理解的那個鴨子,我也沒有翻譯錯……
鴨子類型[1]是鴨子測試的一個應用:
如果它走起來像鴨子,也跟鴨子一樣發(fā)出嘎嘎的叫聲,那么它就是鴨子聽起來似乎非常無厘頭,但這個模式實際上被廣泛的應用于多種語言。
在C++中的應用
template <typename T> concept Stream = requires(T a, std::uint8_t* mut_buffer, size_t size, const std::uint8_t* buffer) {{ a.read(mut_buffer, size) } -> std::convertible_to<size_t>;{ a.write(buffer, size) } -> std::convertible_to<size_t>; };class Console { ... }; class FileStream { ... };在Golang中的應用
type Stream interface {Read(uint32) []byteWrite([]byte) uint32 }type Console struct { ... } type FileStream struct { ... }func (c Console) Read(size uint32) []byte {... }func (c Console) Write(data []byte) uint32 {... }在上面的兩個例子中,我們可以注意到,Console和FileStream這兩個類型都沒有顯示的聲明自己兼容Stream concept(interface),但在編譯階段,編譯器可以根據他們實現的方法來判斷他們支持Stream要求的操作,從而實現多態(tài)。
這個功能看似非常誘人,省去了顯式聲明的麻煩,但也帶來了問題。
鴨子類型的局限性
程序員的造詞能力通常是非常匱乏的(大家每次要給變量命名時的抓耳撓腮可以證明這一點),所以非常容易在方法名上重復,但在兩個語境中又可能具有完全不同的語義。
舉個例子:
template <typename T> concept Thread = requires(T a, int signal) {{ a.kill(signal) }; };class DuckFlock { public:void kill(int amount); };void nofity_thread(Thread& t) {t.kill(SIGUSR1); }原本我以為給鴨群發(fā)了一個信號,讓它們打印一下狀態(tài),結果一不小心就殺掉了10只鴨子[2],真的只能召喚華農兄弟了。
Rust的設計
在Rust中,是不允許這種情況出現的,必須顯式的生命類型實現的是哪個trait:
trait Thread {fn kill(&mut self, signal:i32); }trait Flock {fn kill(&mut self, amount:i32); }struct DuckFlock {ducks: i32 }impl DuckFlock {pub fn new(amount: i32) -> DuckFlock {DuckFlock{ ducks: amount }} }impl Thread for DuckFlock {fn kill(&mut self, signal: i32) {if signal == 10 {println!("We have {} ducks", self.ducks);} else {println!("Unknown signal {}", signal);}} }impl Flock for DuckFlock {fn kill(&mut self, amount: i32) {self.ducks -= amount;println!("{} ducks killed", amount);} }fn main() {let mut flock = DuckFlock::new(100);{let thread:&mut Thread = &mut flock;thread.kill(10);}{let flock:&mut Flock = &mut flock;flock.kill(10);}{let thread:&mut Thread = &mut flock;thread.kill(10);} }同樣的,這個例子我也放到Rust Playground,歡迎大家前去玩耍。
Markers
在Rust中,由于實現Trait必須要顯式聲明,這就衍生出了一種特殊類型的trait,它不包含任何的函數要求:
trait TonyFavorite {} trait Food {fn name(&self) -> String; }struct PeikingDuck;impl Food for PeikingDuck {fn name(&self) -> String {"Peiking Duck".to_owned()} }impl TonyFavorite for PeikingDuck {}struct Liver;impl Food for Liver {fn name(&self) -> String {"Liver".to_owned()} }fn eat<T: Food + TonyFavorite>(food: T) {println!("Tony only eat his favorite food like {}", food.name()); }fn main() {eat(PeikingDuck);// eat(Liver); // compile error }這里例子的Playground在此。
事實上,在Rust中,類似的Marker還有非常多,比如Copy、Sync、Send等等。在后續(xù)的文章中,再跟大家逐一解釋這些trait的含義與妙用。
在下一節(jié)的文章中,我會介紹Rust類型系統和C++類型系統最大的不同之一:Rust結構體不能繼承,以及為什么。敬請期待。
延伸閱讀
上一篇
黃玨珅:C++工程師的Rust遷移之路(4)- 繼承與組合 - 中?zhuanlan.zhihu.com下一篇
黃玨珅:C++工程師的Rust遷移之路(6)- 繼承與組合 - 后?zhuanlan.zhihu.com參考
總結
以上是生活随笔為你收集整理的10玩rust_C++工程师的Rust迁移之路(5)- 继承与组合 - 下的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 交行小额免密支付刷卡金怎么用
- 下一篇: 加时间水印_如何在手机照片上添加时间和日