内容简介:最先出现的官方原文介绍:
Rust 2018 edition
虽然实际发布时间还没到(本文开始写的时间是 18 年七月底),但是有些 2018 edition 的特性已经随着 Rust 的新版本发布放出,这些已经进入 stable 版的特性必然是应当了解并学习的。其中就有两个本文所要讨论的关键字 —— impl
和 dyn
。
最先出现的 impl
是大家已经熟悉的关键字,不过这次这个关键字除了用于表示实现一个 Trait
,还有新的意义,即表达一个 既存类型(Existential types)
,我们可以理解为一个实现了一个特征的 具体对象
。
官方原文介绍: impl Trait https://rust-lang-nursery.github.io/edition-guide/2018/transitioning/traits/impl-trait.html
impl Trait
is the new way to specify unnamed but concrete types that implement a specific trait. There are two places you can put it: argument position, and return position.
trait Trait {} // argument position fn foo(arg: impl Trait) { } // return position fn foo() -> impl Trait { }
不过其意义是什么?与我们另一个要介绍的 dyn Trait
又有什么关系?下面我们正式开始。
使用抽象的一些问题
在使用 Rust 时,我们常常带入一些之前其他语言的惯性思维,无论是 Java 、 Go 还是 PHP,我们可以通过定义接口来抽象一个函数或方法的返回值,只要这个返回值是这个接口的实现即可。Rust 有一个和这些语言类似的东西: Trait
。
A trait tells the Rust compiler about functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic can be any type that has certain behavior.
即 trait 允许我们进行另一种抽象:他们让我们可以抽象类型所通用的行为。 trait 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。在使用泛型类型参数的场景中,可以使用 trait bounds 在编译时指定泛型可以是任何实现了某个 trait 的类型,并由此在这个场景下拥有我们希望的功能。
不过这里需要强调,trait 与 interface 是存在差异的
当我们在某些函数需要返回一个 trait 的实现时,我们可能写出如下代码:
// 注:Iterator 是一个迭代器 trait fn get_iter() -> Iterator<Item=u8> { // ... }
这样的代码将会报错,因为 Rust 要求必须返回一个具体的类型而不是一个抽象,因为抽象对于 Rust 是一个模糊的不具名信息,无法在编译期确定很多细节(这其实是可以解决的,不过由于 Rust 目前对于 DST 即动态大小类型的支持还在未来特性中,为保证 Rust 的稳定推进,目前只能这样)。那如何解决呢?可以通过装箱语法实现:
fn build_trait() -> Box<Iterator<Item=u8>> { // ... }
使用装箱语法意味着我们在返回时需要使用 Box::new()
包装,但是使用装箱意味着这一过程属于运行时的动态分派,无法再将对象定于栈上。除此之外,我们可能还有另外的需求,就是返回一个匿名函数,这在当下业务场景中十分常见,根据上面的描述,我们若想要返回一个匿名函数,代码得如下书写:
fn foo<T>(add: u8) -> Box<T> where T: Fn(u8) -> u8 { Box::new(move |origin: u8| { origin + add }) }
因为匿名函数是编译器生成的匿名类型,根本不存在具体对象一说,这意味着它无法有一个明确的 size,所以只能被放置于 堆内存
之中,并取得一个 胖指针
(即除了原始指针以外还包括对指针、指针指向内存的额外描述信息等的 “指针”),我们知道,凡是非静态分派,又和堆内存打交道的(废话),性能开销相对于栈上的工作,都是十分可观的,因此我们的新语法呼之欲出。
impl Trait
我们继续刚刚返回匿名函数的例子,使用新语法后代码如下:
fn foo<T>(add: u8) -> impl Fn(u8) -> u8 { move |origin: u8| { origin + add } }
该语法表示返回值是一个满足其指明的 trait 的约束的具体类型。另外,由于这个实现是该函数返回值自行指定,还解决了某些场景使用泛型时的一些问题,比如上面的代码例子中,我们使用了泛型,而泛型的实际类型是由调用者决定的,这在使用装箱语法时会报错,虽然你在返回时通过 where 指明了泛型 T 的约束,但那并不是指示泛型具体类型的。
而通过 impl Trait
则是一个具体类型,且由返回者指定。
不过我想看了这部分内容的,都可能还有点模糊的地方,就是 调用者指定类型 ,或者说有没有更直观例子来辨别,当然有,我们以官方对于这部分说明的例子来写:
trait Trait {} fn foo<T: Trait>(arg: T) { } fn foo(arg: impl Trait) { }
上述两种实现,前者是泛型,表示 T 泛型需要是一个 Trait 的实现,后者不是泛型但也是要求满足 Trait 约束,这两个乍一看是类似的,实际却大不一样。
使用泛型时我们说,其类型是调用者决定,具体代码上体现就是我们可以这样调用 foo::<usize>(1)
表示我们传入的参数是 usize
类型,亦或 let a: usize = 1
然后 foo(a)
,在编译时,编译器会将泛型转换为实际被调用者传入的类型: usize
,这就是所谓的调用者决定其类型。
而对于 impl Trait
这种形式,则无需调用者指定,仅需要保证满足约束即可。
dyn 来解决另外的歧义
我们在之前例子中,说过在没有 impl Trait
这种语法糖之前,需要靠装箱解决问题。这个地方其实还有一个问题,我们看代码:
fn my_function() -> Box<Foo> { // ... }
上述代码存在一个歧义, Foo
到底是 trait 还是一个具体的类型?这两个是有明显区别的。通过新的关键字来明确两者差异。以下是官方例子:
trait Trait {} impl Trait for i32 {} // old fn function1() -> Box<Trait> { } // new fn function2() -> Box<dyn Trait> { }
对于使用新关键字后的代码则不再存在歧义,且后续可能将不再支持 Box<Trait>
的写法,而是 Box<dyn Trait>
。当然,对于目前而言,这两者似乎并没什么区别,关于这个语法其实还有很多讨论,可以查看 reddit 的这篇内容了解: https://www.reddit.com/r/rust/comments/8su7r3/i_dont_understand_the_purpose_of_dyn/
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Go语言const关键字理解
- 深入理解Java中的volatile关键字
- 深入理解JS中this关键字
- 深入理解Swift中static和class关键字
- [译] 从 JS 对象的内部原理来理解 this 关键字
- 终于讲清楚了:深入理解 Java 应用程序中 final 关键字的各种使用场景
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Algorithms to Live By
Brian Christian、Tom Griffiths / Henry Holt and Co. / 2016-4-19 / USD 30.00
A fascinating exploration of how insights from computer algorithms can be applied to our everyday lives, helping to solve common decision-making problems and illuminate the workings of the human mind ......一起来看看 《Algorithms to Live By》 这本书的介绍吧!