Rust Shenanigans:返回类型多态性

2021-04-14 06:15:10

在本文中,我将描述生锈返回类型多态性(A.K.A.通用返回),这是我最近发现的功能,并且我一直很感兴趣。

我在编程语言中第一次看到这个功能,乍一看,它似乎是某种内置编译器魔法,仅在标准库中可用。实际上,它是一般性的功能,您可以每天使用您自己的代码。

请记住,我仍然是一个初学者的生锈,所以我的描述可能不是最准确的,但我会尝试说明为什么我喜欢这个功能以及它如何使用一些示例。希望你会发现这个话题像我一样有趣!

在尝试解释实际上是什么返回类型的多态性之前,请让我向您展示一些有趣的例子,如果您已经逃离生锈,您可能会熟悉:

用 ; fn main(){让nums = [2,17,22,48,1997,2,22];让nums_square_no_dups:< I32> = nums。 iter()。地图(| x | x * x)。搜集 ( ) ; println! (" {:?}",nums_square_no_dups); // {2304,484,3988009,4,289}}}}}}}}}

好的,你看到了吗?我的意思是,世界上如何收集()弄清楚我想从迭代器中“收集”值到整数的哈希集?的确,它给了我一个整数的哈希集!

不相信?我们也可以轻微更改这个例子,看看会发生什么......让我们尝试使用VEC:

fn main(){让nums = [2,17,22,48,1997,2,22];让nums_square:< I32> = nums。 iter()。地图(| x | x * x)。搜集 ( ) ; println! (" {:?}" nums_square); // [4,289,484,2304,3988009,4,484]}

好的,没有再删除重复的重复,它保留了元素的顺序,但这不是重点!重点是收集()仍在给我们我们想要的东西。这次我们要求VEC,事实上,我们得到了一个VEC而不是散列画面。

请注意,此代码与前一个代码几乎相同。除了类型声明(以及变量名称,而是无关紧要),我们没有更改以外的任何内容!

此外,请记住,虽然生锈可以推断您的类型,但大多数情况下,您无需提供显式类型定义,当使用Collect()所需的类型定义时。

fn main(){让nums = [2,17,22,48,1997,2,22];让nums_square = nums。 iter()。地图(| x | x * x)。搜集 ( ) ; }

此示例不会编译,因为收集不知道是什么是返回类型,因此我们得到了这个漂亮的错误:

错误[e0282]:所需的型注释 - > src / main.rs:3:9 | |假设nums_square = nums.iter()。地图(| x | x * x).collect(); | ^^^^^^^^^^^^考虑给出`nums_square`一种类型

我希望你开始搞定这一点。 recoll()等一些功能可以根据预期的回报类型表现不同!返回类型需要显式,因此Rust编译器可以为您选择合适的行为。

好的,这是在编码挑战时我在生锈中学到的第一个东西之一,我一直在想一段时间“这只是一些迭代器魔法,它可能只适用于几种标准类型”......

然后,最近我看到了一些这样做的代码是这样做的:

fn main(){让答:u8 = :: default();让B:i64 = :: default();让C:= :: default();让D:(U16,USIZIZE)=(:: default(),:: default()); DBG! (一种 ) ; // a = 0 dbg! (b); // b = 0 dbg! (C ) ; // c ="" DBG! (d); // d =(0,0)}

是的,没令人印象深刻,我知道......但是再次,我们总是称之为相同的函数,这次默认:: default(),它为我们提供了我们需要的特定类型的默认值!

在所有诚实中,我几乎瞥了一眼这个思考它是一个其他漂亮的铁锈编译时间魔法,适用于内置类型。直到我注意到一小笔记“顺便说一下,如果要”,您可以为您的自定义类型带来默认值“。

struct {x:f64,y:f64,z:f64,} //它' syeas pal,只需实现`默认的{fn default() - >> self {{x:0_f64,y:0_f64,z:0_f64,}} fn main(){let origin:= :: default(); println! (" {:?}",起源); // point3d {x:0.0,y:0.0,z:0.0}}

默认特性允许您定义自定义类型的默认值。特性迫使您实现一个默认()方法,该方法必须返回给定类型(self)的实例。

再一次,没有什么非常令人兴奋的......除了这一点,我的头部点击了,我开始问自己“所以这是每个人都可以使用的一些概括的功能......”。

我的怀疑是默认:: default()函数可以以某种方式以某种方式推断出预期的返回类型,如果该类型实现默认特征,那么它只需调用该类型的默认值()函数。

当然,我立即去了,寻找默认的实际实现:: default()来验证我的猜测。

现在,我不会要求我理解100%(那里有一些可爱的宏,我的大脑无法编译那些人),但我有一种感觉,我猜测了我是正确的道路。

T有一个约束:只要它实现默认特征,它可以是任何类型

最后(这是重要点),t是默认:: default()必须返回的类型

这就是魔法来自的地方。 实现实际上是返回类型的泛型。 此外,通过使用默认的特征约束,我们可以具有可扩展的定义:任何人都可以实现使用默认值:: default()使用的新类型。 更稍后更多...... 现在,这是摘要的足够的理论,让我们试着用它做点什么! 多年来,我了解到,当我可以建立使用它们时,我的大脑可以欣赏新的编程概念。 所以,我卷起了我的袖子,开始思考“好的,我能用这可以建立什么?” 我想,这里的一个好问题是“我什么时候想根据不同的预期回报类型做不同的东西?” 我想出了一个与棋盘游戏相关的简单想法。 像D& D的东西,你有不同的骰子(不同数量的面)。 我们可以使用返回型多态性能够滚动不同类型的骰子吗? 我想我们可以! 这是越来越多的我用我的第一次尝试做了什么:

使用{thread_rng,};特质< > {fn roll() - > ; }结构(U8);结构(U8); struct {} impl< D6和GT;对于{fn roll() - > d6 {d6 {0:thread_rng()。 gen_range(1 .. = 6),}}} IMPL< D8>对于{fn roll() - > d8 {d8 {0:thread_rng()。 gen_range(1 .. = 8),}}}}}}}}} fn main(){//' s roll a d6让roll:d6 = :: roll(); println! (" {:?}",卷); // d6(3)//让' s卷d8让roll:d8 = :: roll(); println! (" {:?}",卷); // d8(3)}

为简单起见,我只显示了我实现了D6和D8的方式。如果您想要完整版本,请查看我的D& D骰子生锈游乐场。

此实现的有趣部分是我有2个不同的实现对于函数Die :: roll(),返回D6(6面板)滚动,另一个返回D8(8面向芯片)卷。

Rust将根据该调用的预期返回类型自动调用正确的实现。

我的意思是,如果我将这段代码转换为库,有人需要使用100个面孔的自定义模具?或者如果他们想要一个总是滚动100的伎俩怎么办?

好吧,这几乎是默认的::默认()是这样的,所以它应该是可能的,对吧?

现在,这种roll()的实现只需要在t的当前类型上使用旋转性状

这个想法不是真正的原创。我们几乎模仿默认:: default()对特性默认值的默认值,除了它们的命名选择可能稍微令人困惑,因为模块,功能和特征都被称为默认值😰...

使用{thread_rng,}; ///这是每个骰子需要实施的特质......" rollable",吧?酒吧特征{///滚动模具fn roll() - >自己 ; ///从最新的卷FN Val(& self) - > U8; } ///滚动给定芯片的通用功能。 PUB FN ROLL< :> () - > {:: roll()//< - 请注意,这里的rollable`是给定呼叫的当前类型! } /// D6模具(6个面):卷会给你一个“U8”在“1”中。酒吧结构(U8); ichar for d6 {fn roll() - > d6 {d6 {0:thread_rng()。 gen_range(1 .. = 6),}} Fn Val(& self) - > U8 {self。 0}} ///一个D8模具(8个面):滚动会给你一个“U8”在“1”中。= 8号码。酒吧结构(U8); ichar for d8 {fn roll() - > d8 {d8 {0:thread_rng()。 gen_range(1 .. = 8),}} fn val(& self) - > U8 {self。 0}}

这几乎是它,现在我们可以尽可能地使用我们的D6和D8:

fn main(){//' s roll a d6让R:D6 = roll(); println! (" {:?}",r); // d6(3)//让' s roll a d8设r:d8 = roll(); println! (" {:?}",r); // d8(3)}

结构(U8); ichar for {fn roll() - > {{0:100} //< - 强制它滚动100} fn val(& self) - > U8 {self。 0}} fn main(){println! ("我敢打赌我'这次得到100百岁!");假设d:= roll(); println! ("看看我得到了什么:{}!",d。val())//< - 是的,这将永远是100}

我实际上确实最终将此愚蠢的示例发布为库。一探究竟。谁知道,也许你真的想要实施某种棋盘游戏!

我们已经看到,当函数使用返回类型多态性时,我们需要明确声明预期的类型。

如果您想调用实现返回类型多态性的函数,但您不想分配它?让我们说我们要立即使用返回的值,也许是另一个函数调用的参数。

事实上,它没有!如果您尝试编译该代码,您将获得一个美丽的错误消息:

错误[e0282]:所需的型注释 - > SRC / MAIN.RS:47:23 | | Println!(" {:?}",roll()); | ^^^^无法在函数`t`声明的类型参数`t`中的类型

编译器还建议运行rustc - 例如如何解决如何解决此问题的详细指南。你必须把它交给铁锈队,他们在提供了伟大的文件方面做了如此巨大的工作!

现在,如果您患者足够运行该命令并阅读指南,您会发现我们可以用所谓的涡轮类鱼类语法来解决这个问题!

Turbo-Fish语法看起来像......鱼:::< ......是的,有一定程度的想象力!

因此,这是我们如何实际使用它来指定通用参数的类型:

无论如何,通过这种语法,参数t的类型不再含糊不清:我们明确说我们想要D6! 实际上,Turbo Fish是具有通用类型的函数的扩展语法,除了我们使用良好定义的类型进行分配时,Rust编译器足够智能以推断通用参数的类型,使我们的生活更轻松。 我希望你找到这个主题(和这篇文章!)尽可能有趣,并且感觉不仅仅是欢迎您在评论中告诉我,如果您已经了解了此功能。 您是否已经在某处使用它? 我真的很想知道你的用例,所以请与我分享♥ 如果您想要一个更加抛光和详细的解释,请查看此杰出博客的杰出博客,詹姆斯··················克拉兰返回生锈返回。