rust为什么显示不了国服_捋捋 Rust 中的 impl Trait 和 dyn Trait

e29e8863d7b2ae64bf865759783deb4c.png

缘起

一切都要从年末换工作碰上特殊时期, 在家闲着无聊又读了几首诗, 突然想写一个可以浏览和背诵诗词的 TUI 程序说起. 我选择了 Cursive 这个 Rust TUI 库. 在实现时有这么一个函数, 它会根据参数的不同返回某个组件(如 Button, TextView 等). 在 Cursive 中, 每个组件都实现了 View 这个 trait, 最初这个函数只会返回某个确定的组件, 所以函数签名可以这样写

fn some_fn(param: SomeType) -> Button

随着开发进度增加, 这个函数需要返回 Button, TextView 等组件中的一个, 我下意识地写出了类似于下面的代码

fn some_fn(param1: i32, param2: i32) -> impl View {if param1 > param2 {// do something...return Button {};} else {// do something...return TextView {};}
}

可惜 Rust 编译器一如既往地打脸, Rust 编译器报错如下

--> srcmain.rs:19:16|
13 | fn some_fn(param1: i32, param2: i32) -> impl View {|                                         --------- expected because this return type...
...
16 |         return Button {};|                --------- ...is found to be `Button` here
...
19 |         return TextView {};|                ^^^^^^^^^^^ expected struct `Button`, found struct `TextView`error: aborting due to previous errorFor more information about this error, try `rustc --explain E0308`.

从编译器报错信息看函数返回值虽然是 impl View 但其从 if 分支推断返回值类型为 Button 就不再接受 else 分支返回的 TextView. 这与 Rust 要求 if else 两个分支的返回值类型相同的特性一致. 那能不能让函数返回多种类型呢? Rust 之所以要求函数不能返回多种类型是因为 Rust 在需要在 编译期确定返回值占用的内存大小, 显然不同类型的返回值其内存大小不一定相同. 既然如此, 把返回值装箱, 返回一个胖指针, 这样我们的返回值大小可以确定了, 这样也许就可以了吧. 尝试把函数修改成如下形式:

fn some_fn(param1: i32, param2: i32) -> Box<View> {if param1 > param2 {// do something...return Box::new(Button {});} else {// do something...return Box::new(TextView {});}
}

现在代码通过编译了, 但如果使用 Rust 2018, 你会发现编译器会抛出警告:

warning: trait objects without an explicit `dyn` are deprecated--> srcmain.rs:13:45|
13 | fn some_fn(param1: i32, param2: i32) -> Box<View> {|                                             ^^^^ help: use `dyn`: `dyn View`|= note: `#[warn(bare_trait_objects)]` on by default

编译器告诉我们使用 trait object 时不使用 dyn 的形式已经被废弃了, 并且还贴心的提示我们把 Box<View> 改成 Box<dyn View>, 按编译器的提示修改代码, 此时代码 no warning, no error, 完美.

impl TraitBox<dyn Trait> 除了允许多种返回值类型的之外还有什么区别吗? trait object 又是什么? 为什么 Box<Trait> 形式的返回值会被废弃而引入了新的 dyn 关键字呢?

埋坑

impl Traitdyn Trait 在 Rust 分别被称为静态分发和动态分发. 在第一版的 Rust Book 这样解释分发(dispatch)

When code involves polymorphism, there needs to be a mechanism to determine which specific version is actually run. This is called ‘dispatch’. There are two major forms of dispatch: static dispatch and dynamic dispatch. While Rust favors static dispatch, it also supports dynamic dispatch through a mechanism called ‘trait objects’.

即当代码涉及多态时, 需要某种机制决定实际调用类型. Rust 的 Trait 可以看作某些具有通过特性类型的集合, 以上面代码为例, 在写代码时我们不关心具体类型, 但在编译或运行时必须确定 Button 还是 TextView. 静态分发, 正如静态类型语言的"静态"一词说明的, 在编译期就确定了具体调用类型. Rust 编译器会通过单态化(Monomorphization) 将泛型函数展开.

假设 FooBar 都实现了 Noop 特性, Rust 会把函数

fn x(...) -> impl Noop

展开为

fn x_for_foo(...) -> Foo
fn x_for_bar(...) -> Bar

(仅作原理说明, 不保证编译会这样展开函数名).

通过单态化, 编译器消除了泛型, 而且没有性能损耗, 这也是 Rust 提倡的形式, 缺点是过多展开可能会导致编译生成的二级制文件体积过大, 这时候可能需要重构代码.

静态分发虽然有很高的性能, 但在文章开头其另一个缺点也有所体现, 那就是无法让函数返回多种类型, 因此 Rust 也支持通过 trait object 实现动态分发. 既然 Trait 是具有某种特性的类型的集合, 那我们可以把 Trait 也看作某种类型, 但它是"抽象的", 就像 OOP 中的抽象类或基类, 不能直接实例化.

Rust 的 trait object 使用了与 c++ 类似的 vtable 实现, trait object 含有1个指向实际类型的 data 指针, 和一个指向实际类型实现 trait 函数的 vtable, 以此实现动态分发. 更加详细的介绍可以在

Exploring Dynamic Dispatch in Rust​alschwalm.com

看到. 既然 trait object 在实现时可以确定大小, 那为什么不用 fn x() -> Trait 的形式呢? 虽然 trait object 在实现上可以确定大小, 但在逻辑上, 因为 Trait 代表类型的集合, 其大小无法确定. 允许 fn x() -> Trait 会导致语义上的不和谐. 那 fn x() -> &Trait 呢? 当然可以! 但鉴于这种场景下都是在函数中创建然后返回该值的引用, 显然需要加上生命周期:

fn some_fn(param1: i32, param2: i32) -> &'static View {if param1 > param2 {// do something...return &Button {};} else {// do something...return &TextView {};}
}

我不喜欢添加额外的生命周期说明, 想必各位也一样. 所以我们可以用拥有所有权的 Box 智能指针避免烦人的生命周期说明. 至此 Box<Trait> 终于出现了. 那么问题来了, 为什么编译器会提示 Box<Trait> 会被废弃, 特地引入了 dyn 关键字呢? 答案可以在 RFC-2113 中找到.

RFC-2113 明确说明了引入 dyn 的原因, 即语义模糊, 令人困惑, 原因在于没有 dyn 让 Trait 和 trait objects 看起来完全一样, RFC 列举了3个例子说明.

第一个例子, 加入你看到下面的代码, 你知道作者要干什么吗?

impl SomeTrait for AnotherTrait impl<T> SomeTrait for T where T: Another

你看懂了吗? 说实话我也看不懂 : ) PASS

第二个例子, impl MyTrait {} 是正确的语法, 不过这样会让人以为这会在 Trait 上添加默认实现, 扩展方法或其他 Trait 自身的一些操作. 实际上这是在 trait object 上添加方法.

如在下面代码说明的, Trait 默认实现的正确定义方法是在定义 Trait 时指定, 而不应该在 impl Trait {} 语句块中.

trait Foo {fn default_impl(&self) {println!("correct impl!");}
}impl Foo {fn trait_object() {println!("trait object impl");}
}struct Bar {}impl Foo for Bar {}fn main() {let b = Bar{};b.default_impl();// b.trait_object();Foo::trait_object();
}

Bar 在实现了 Foo 后可以通过 b.default_impl 调用, 无需额外实现, 但 b.trait_object 则不行, 因为 trait_object 方法是 Foo 的 trait object 上的方法.

如果是 Rust 2018 编译器应该还会显示一条警告, 告诉我们应该使用 impl dyn Foo {}

第三个例子则以函数类型和函数 trait 作对比, 两者差别只在于首字母是否大写(Fn代表函数trait object, fn则是函数类型), 难免会把两者弄混.

更加详细的说明可以移步

RFC-2113​github.com

.

总结

impl traitdyn trait 区别在于静态分发于动态分发, 静态分发性能 好, 但大量使用有可能造成二进制文件膨胀; 动态分发以 trait object 的概念通过虚表实现, 会带来一些运行时开销. 又因 trait object 与 Trait 在不引入 dyn 的情况下经常导致语义混淆, 所以 Rust 特地引入 dyn 关键字, 在 Rust 2018 中已经稳定.

引用

以下是本文参考的资料

impl Trait for returning complex types with ease​doc.rust-lang.orgimpl trait 社区跟踪​github.comrust-lang/rfcs​github.com
ece4098c3643cec5f44a0c056f8ecd63.png
Traits and Trait Objects in Rust​joshleeb.comDynamic vs. Static Dispatch​lukasatkinson.deExploring Dynamic Dispatch in Rust​alschwalm.com

PS: 题图为卢浦大桥, 全上海我最喜欢的大桥, 没有之一~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/335027.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sublime Text for Mac的快捷键

文章目录选择文本移动光标编辑文本查找/替换窗口显示书签和标记其它选择文本 快捷键说明CommandD先选中文本&#xff0c;再按CommandD&#xff0c;会选中下一个相同的文本&#xff0c;再继续按D(Command不放)则会选中下一个相同的文本&#xff0c;可以同时编辑被选中的文本Con…

zip unzip_zip和unzip上的Java要点

zip unzip压缩是编写文件时可以在我们的代码中发出的主要动作之一。 因此&#xff0c;我发现在zip和unzip上必不可少的简单Java代码段&#xff0c;并且必须易于访问。 要点是纯Java语言&#xff0c;并以zip格式存储两个文件。 完成后&#xff0c;打开打开的拉链并评估其内容。…

mysql不支持子查询_MySQL不支持子查询优化一例

一创建表 create table tt1(id int primary key, c1 INT);create table tt2(id int primary key, c2 INT);insert into tt1 value一创建表create table tt1(id int primary key, c1 INT);create table tt2(id int primary key, c2 INT);insert into tt1 values(1,1),(2,2),(3,3…

nginx配合python_人生苦短我用python[0x02] nginx与python结合

原标题&#xff1a;人生苦短我用python[0x02] nginx与python结合**文章内容为原创&#xff0c;欢迎转载请注明出处**背景nginx是一款高性能的http服务器&#xff0c;python是一门无论做系统开发还是业务逻辑开发都是非常不错的动态语言&#xff0c;现在流行微服务&#xff0c;微…

Linux 文件颜色含义

目录文件&#xff1a;蓝色 一般文件&#xff1a;白色 符号链接&#xff1a;紫色 设备文件&#xff1a;黄色 可执行文件&#xff1a;绿色 链接文件&#xff1a;青色 图片文件&#xff1a;粉红色 压缩文件&#xff1a;红色 其它文件&#xff1a;灰色 链接文件&#xff1…

Mysql索引使用情况_介绍mysql索引失效的情况

mysql视频教程栏目索引失效的情况。索引对于MySQL而言&#xff0c;是非常重要的篇章。索引知识点也巨多&#xff0c;要想掌握透彻&#xff0c;需要逐个知识点一一击破&#xff0c;今天来先来聊聊哪些情况下会导致索引失效。图片总结版相关免费学习推荐&#xff1a;mysql视频教程…

java8根据某个id删选_Java 8可选

java8根据某个id删选在编程时&#xff0c;我们都面临着&#xff08;最&#xff09; 臭名昭著的NullPointerException 。 而且我相信我们所有人都同意&#xff0c;遇到NullPointerException也是一种痛苦。 为了使读者了解最新情况&#xff0c;著名的计算机科学家Tony Hoare引入了…

emacs python ide_Emacs Python IDE win7 x64

安装平台 win7 x64 &#xff0c;emacs 23.3.1snippet工具&#xff0c;可自定义一些模板&#xff1a;.emacs 配置如下(add-to-list load-path "~/.emacs.d/yasnippet")(require yasnippet) ;; not yasnippet-bundle(yas/global-mode 1)自动完成工具&#xff0c;其实只…

解决阿里云 ssh 远程连接短时间没操作就会断掉的问题

在本地主机&#xff08;例如&#xff1a;你自己的 MacBook&#xff09;中打开 sshd 的配置文件&#xff0c;命令语句如下&#xff1a; sudo vim /etc/ssh/sshd_config注意&#xff1a;使用用户 root 编辑配置文件&#xff0c;否则会报错。 找到下面这个两行&#xff08;/Clie…

oracle查看jdk文档_Oracle JDK 9 Early Access文档已更新

oracle查看jdk文档Raymond Gallardo于2017年4月4日发布的针对Oracle JDK 9的抢先 访问文档已更新&#xff0c;今天宣布对Oracle JDK9文档的抢先访问页面进行了更新。 Gallardo重点介绍了一些更新的部分&#xff0c;包括Oracle JDK 9的新增功能 &#xff0c; Oracle JDK 9迁移指…

python3 selenium_Python3+Selenium3自动化测试-(准备)

Python3Selenium3自动化测试-(准备)最近在学习selenium自动化测试相关的内容&#xff0c;所以将实际准备情况做一记录&#xff0c;# 系统&#xff1a;win10(64位)# 浏览器&#xff1a;Chrome(67.0)、Firefox(61.0)、IE# python版本&#xff1a;3.6.5# Selenium&#xff1a;3.13…

MacBook如何通过键盘快捷键输入特殊字符_特殊符号

符号快捷键说明–[ Option ][ - ]破折号[ Option ] [ / ]除号≠[ Option ][ ]不等号≤[ Option ][ < ]≥[ Option ][ > ][ Option ][ \ ]左指双角引号[ Option ][ ’ ]拉丁文小写字母AE…[ Option ][ ; ]水平省略号≈[ Option ][ X ]约等于Ω[ Option ][ Z ]希腊文大写…

java 类 null_深入理解java中的null“类型”

本文研究的主要是java中的null“类型”的相关实例&#xff0c;具体介绍如下。先给出一道简单的null相关的题目&#xff0c;引发我们对null的探讨,后面会根据官方语言手册对null“类型”进行解读。题目&#xff1a;下面程序能正确运行吗&#xff1f;解析&#xff1a;输出应该为 …

wcg总决赛_关于总决赛

wcg总决赛可以将变量声明为final。 最终变量只能分配一次。 如果分配了最终变量&#xff0c;则将导致编译时错误&#xff0c;除非在分配前立即将其明确取消分配。 分配任何最终变量后&#xff0c;将永远无法对其进行更改。 如果变量引用任何对象的任何实例&#xff0c;它将继续…

python dump函数用法_Python中json库的load和dump函数

相信很多朋友都对python里面的json库非常熟悉&#xff0c;json这个东西简明易懂&#xff0c;还能储存数据&#xff0c;实在是非常方便。我一开始使用json是从爬虫接触的&#xff0c;那个时候只知道json.loads和json.dumps&#xff0c;当时ide确实会自动填充load函数和dump函数&…

如何彻底禁用Chrome浏览器的缓存功能

禁止Chrome浏览器的缓存功能有几种方式&#xff1a; 使用Shift F5强制刷新缓存。实际使用中我们发现&#xff0c;这种方法对于部分网页的更改并不能做到释放缓存的作用&#xff0c;尤其是无法释放DNS缓存。 使用隐身模式Shift Control N. 这种方法只能在打开的页面上消除之…

java int integer内存_java中一个integer对象的内存占用是多少?可以通过java方法输出吗?...

今天翻记录看到了自己15年3月份提的问题&#xff0c;现在已经18年6月份了&#xff1b;去年线上项目出现内存瓶颈&#xff0c;原因是缓存的玩家角色数据过多&#xff0c;在长时间不停服的情况下&#xff0c;导致数据越来越膨胀&#xff0c;之前没有清除无用缓存的机制&#xff0…

Linux命令行中的特殊符号_特殊字符

常用的特殊符号 符号说明#1.表示注释&#xff1b;2.命令提示符~表示用户主目录。切换到用户主目录下&#xff1a;cd ~&#xff0c;切换到用户主目录下的bin目录&#xff1a;cd ~/bin~表示当前目录。切换到当前目录下wwwroot目录&#xff1a;cd ~/wwwroot~-表示上次的工作目录。…

微信小程序css 华文琥珀_琥珀项目:较小的,面向生产力的Java语言功能

微信小程序css 华文琥珀Brian Goetz最近的消息欢迎来到琥珀&#xff01; 介绍Project Amber &#xff08; OpenJDK的一部分&#xff0c; 最初于1月提出 &#xff09;。 Goetz通过介绍“欢迎使用Amber项目&#xff0c;这是我们面向特定生产力的Java语言JEP的孵化场”的介绍打开了…

windows查看器无法打开图片_关于windows微软商城无法打开 错误代码0x80004003问题...

我本来是商城可以进 但是一打开里面的东西就无法打开 按下面的刷新就闪退 如下图操作桌面winr 运行 services.msc然后找到windows update打开就行 但是开启了系统会自动更新若是修改时显示拒绝访问桌面winr 输入regedit打开注册表 注册表找到HKEY_LOCAL_MACHINESYSTEMCurrentCo…