前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++工程师的Rust迁移之路(5)- 继承与组合 - 下

C++工程师的Rust迁移之路(5)- 继承与组合 - 下

作者头像
MikeLoveRust
发布2019-08-19 12:01:07
9480
发布2019-08-19 12:01:07
举报

本文发表于知乎专栏:https://zhuanlan.zhihu.com/p/78333162

已获得作者授权。

在上一篇文章 zhuanlan.zhihu.com/p/76 中,我介绍多态、静态分发和动态分发的概念,以及他们各自在C++和Rust中的实现方式。

在本文中,我会重点讲Rust中的Trait实现的静态分发与C++ 20(准确的说,现在还叫做C++ 2a)中的concepts的区别。

在具体介绍这个区别之前,我想跟大家介绍一个概念,叫做duck typing(鸭子类型)。

鸭子类型

呃……你没有看错,这个鸭子就是你平常理解的那个鸭子,我也没有翻译错……

鸭子类型[1]是鸭子测试的一个应用:

如果它走起来像鸭子,也跟鸭子一样发出嘎嘎的叫声,那么它就是鸭子

听起来似乎非常无厘头,但这个模式实际上被广泛的应用于多种语言。

在C++中的应用

代码语言:javascript
复制
template <typename T>
concept bool Stream = requires(T a) {
    { a.read(std::uint8_t*, size_t) } -> size_t;
    { a.write(const std::uint8_t*, size_t) } -> size_t;
};

class Console { ... };
class FileStream { ... };

在Golang中的应用

代码语言:javascript
复制
type Stream interface {
    Read(uint32) []byte
    Write([]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要求的操作,从而实现多态。

这个功能看似非常诱人,省去了显式声明的麻烦,但也带来了问题。

鸭子类型的局限性

程序员的造词能力通常是非常匮乏的(大家每次要给变量命名时的抓耳挠腮可以证明这一点),所以非常容易在方法名上重复,但在两个语境中又可能具有完全不同的语义。

举个例子:

代码语言:javascript
复制
template <typename T>
concept bool Thread = requires(T a) {
  { a.kill(int signal) } -> void;
};

class DuckFlock {
public:
    void kill(int amount);
};

void nofity_thread(Thread& t) {
    t.kill(SIGUSR1);
}

原本我以为给鸭群发了一个信号,让它们打印一下状态,结果一不小心就杀掉了10只鸭子[2],真的只能召唤华农兄弟了。

Rust的设计

在Rust中,是不允许这种情况出现的,不许显式的生命类型实现的是哪个trait:

代码语言:javascript
复制
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,欢迎大家前去玩耍。

Makers

在Rust中,由于实现Trait必须要显式声明,这就衍生出了一种特殊类型的trait,它不包含任何的函数要求:

代码语言:javascript
复制
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等等。在后续的文章中,再跟大家逐一解释这些trait的含义与妙用。

在下一节的文章中,我会介绍Rust类型系统和C++类型系统最大的不同之一:Rust结构体不能继承,以及为什么。敬请期待。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 鸭子类型
  • 鸭子类型的局限性
  • Rust的设计
  • Makers
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档