2020-11-25 更新:

  1. 修正了C++ 20中的concept语法

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

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

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

鸭子类型

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

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

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

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

在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要求的操作,从而实现多态。

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

鸭子类型的局限性

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

举个例子:

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);
}

原本我以为给鸭群发了一个信号,让它们打印一下状态,结果一不小心就杀掉了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等等。在后续的文章中,再跟大家逐一解释这些trait的含义与妙用。

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

延伸阅读

上一篇

黄珏珅:C++工程师的Rust迁移之路(4)- 继承与组合 - 中​zhuanlan.zhihu.com

下一篇

黄珏珅:C++工程师的Rust迁移之路(6)- 继承与组合 - 后​zhuanlan.zhihu.com

参考

  1. ^Duck typing https://en.wikipedia.org/wiki/Duck_typing
  2. ^在Linux下SIGUSR1等于10

10玩rust_C++工程师的Rust迁移之路(5)- 继承与组合 - 下相关推荐

  1. 10玩rust_有趣的 Rust 类型系统: Trait

    也许你已经学习了标准库提供的 String 类型,这是一个 UTF-8 编码的可增长字符串.该类型的结构为: pub struct String {vec: Vec<u8>, } UTF- ...

  2. Cris 玩转 Linux 之 Deepin 迁移全过程记录

    Cris 玩转 Linux 之 Deepin 迁移全过程记录 Author:Cris 文章目录 Cris 玩转 Linux 之 Deepin 迁移全过程记录 Author:Cris 0. 序 1. 磨 ...

  3. 服务迁移之路 | Spring Cloud向Service Mesh转变 | 技术干货

    戳蓝字"CSDN云计算"关注我们哦! 技术头条:干货.简洁.多维全面.更多云计算精华知识尽在眼前,get要点.solve难题,统统不在话下! 作者: 李宁 转自:博云技术社区 Sp ...

  4. 运维工程师打怪升级进阶之路 V2.0

    很多读者伙伴们反应总结的很系统.很全面,无论是0基础初学者,还是有基础的入门者,或者是有经验的职场运维工程师们,都反馈此系列文章非常不错! 命名:<运维工程师打怪升级之路> 版本:V1.0 ...

  5. 考拉海购全面云原生迁移之路

    今年 8 月底,入驻"阿里动物园"一周年的考拉海购首次宣布战略升级,在现有的跨境业务基础上,将重点从以"货"为中心变成以"人"为中心,全面发 ...

  6. [转] Web前端研发工程师编程能力飞升之路

    [转] Web前端研发工程师编程能力飞升之路 分类: Javascript | 转载请注明: 出自 海玉的博客 今天看到这篇文章.写的非常有意思.发现自己还有很长的一段路要走. [背景] 如果你是刚进 ...

  7. 初中计算机课师徒结对活动记录,师徒结对活动记录表10张(师傅尚积东徒弟丁明路)(备课6节听课4节).doc...

    师徒结对活动记录表10张(师傅尚积东徒弟丁明路)(备课6节听课4节).doc 2014年度师徒结对活动记录表(一)活动方式交流探讨活动地点教导处活动时间2014.4.11活动内容帮助徒弟:提高认识青年 ...

  8. 关于硬件工程师的真相:敢问路在何方?

    关于硬件工程师的真相:敢问路在何方? 硬件工程师,曾经有多少人希望从事的职业?在别人眼里好像能够从事硬件设计需要你了解很多东西,可以从事这个职业之后才逐渐发现,硬件工程师处在一种非常难受的困境当中!想 ...

  9. 习题 11.10 将本章11.8节中的程序片段加以补充完善,成为一个完整的程序。在程序中使用继承和组合。在定义Professor类对象prof1时给出所有数据的初值,然后修改prof1的生日数据。。。

    C++程序设计(第三版) 谭浩强 习题11.10 个人设计 习题 11.10 将本章11.8节中的程序片段加以补充完善,成为一个完整的程序.在程序中使用继承和组合.在定义Professor类对象pro ...

最新文章

  1. 使用Powershell将PST导入Exchange 2007
  2. 多视图几何三维重建实战系列- Cascade-MVSNet
  3. netty中的future和promise源码分析(二)
  4. 深入Java中文编码乱码问题及最优解决方法
  5. 站长就是个太监^_^
  6. centos6 nodejs 安装测试
  7. pythonweb啥意思_python-web-guide
  8. 深度技术win11旗舰稳定版v2021.07
  9. epel源mysql版本_centos网络yum源和epel源(2017可用首选)
  10. php案例之后台数据显示-- PDO版(php data object)
  11. 原版Win7注入USB3.0驱动和NVME驱动教程
  12. 答答星球微信答题小程序头脑王者源码带后台手机app开发排位pk
  13. 【Qt象棋游戏】05_象棋走棋规则——象、马、将、兵
  14. 记一次应急-插U盘之后文件夹全变成exe中毒(100%解决)
  15. 如何将ipad作为电脑的第二显示屏
  16. 企业邮箱托管外包后安全吗?企业邮箱安全须知
  17. IE6下图片的浏览剪裁与上传
  18. Java 鸡翁一值钱五Java_Java案例5:斐波那契数列,百钱百鸡
  19. MybatisPlus报错can not find lambda cache for this entity
  20. 大学物理·第7章恒定磁场

热门文章

  1. 如何在ASP.NET Core中自定义Azure Storage File Provider
  2. EF Core 数据库 Provider 一览
  3. 【活动(广州)】MonkeyFest2018 微软最有价值专家讲座
  4. 课程 预编译框架,开发高性能应用 - 微软技术暨生态大会 2018
  5. ASP.NET Core 2.0使用Autofac实现IOC依赖注入竟然能如此的优雅简便
  6. .NET的一点历史故事:招兵买马和聚义山林
  7. 云计算设计模式(四)——消费者的竞争模式
  8. .NET Core 使用 grpc 实现微服务
  9. C语言之strstr函数类似Java字符串的contain函数
  10. Android之用UncaughtExceptionHandler实现保存崩溃日志到sdcard目录下的文件夹