为啥我需要“使用 rand::Rng”在 rand::thread_rng() 上调用 gen()?

Posted

技术标签:

【中文标题】为啥我需要“使用 rand::Rng”在 rand::thread_rng() 上调用 gen()?【英文标题】:Why do I need "use rand::Rng" to call gen() on rand::thread_rng()?为什么我需要“使用 rand::Rng”在 rand::thread_rng() 上调用 gen()? 【发布时间】:2022-01-13 22:08:26 【问题描述】:

当我使用 Rust 的 rand crate 时,如果我想生成一个 rand 数,我会这样写:

use rand::self, Rng;
let rand = rand::thread_rng().gen::<usize>();

如果我不use rand::Rng,就会出现错误:

在当前范围内没有为结构 rand::prelude::ThreadRng 找到名为 gen 的方法

这与我习惯的完全不同。通常我会这样对待模组:

import rand from "path";
rand.generate();

一旦我导入了这个模组,我就不需要导入其他东西了,我可以使用它导出的每一种方法。

为什么我必须use rand::Rng 才能在rand::thread_rng() 上启用gen 方法?

【问题讨论】:

附带说明,通过在 cargo.toml 中包含 rand,编译器会自动将符号 rand 引入范围内,因此无需导入它。此外,rand::thread_rngrand::Rng 都包含在 rand 的前奏中,因此推荐使用 use rand::prelude::*; 将符号纳入范围。 【参考方案1】:

我不知道您使用的具体库,但我可以猜到问题所在。我猜Rng 是一个定义gen 的特征。可以认为 Traits 有点像 Java 的接口:它们允许您为不同数据类型上的同一函数定义不同行为,从而实现临时多态性。

然而,Rust 的特征解决了 Java 接口的一个主要问题(嗯,它们解决了几个主要问题,但这里有一个相关问题)。在Java中,如果你定义了一个接口,那么任何写一个类的人都可以实现这个接口,但你不能为其他人实现它。特别是,内置类型 Stringint 等可以永远在下游实现任何新接口。在 Rust 中,trait writer struct/enum writer 都可以实现 trait。

但这带来了另一个问题。现在,如果我有一个Foo 类型的值foo 并且我写foo.bar(),那么bar 可能不是定义的方法on Foo;它可能是一些trait writer 在其他文件中实现的东西。我们无法在您的计算机上搜索每个 Rust 文件以寻找可能的匹配特征,因此 Rust 做出合理的决定,将搜索限制在范围内的特征上。如果你想调用foo.bar() 并且bartrait Bar 上的一个方法,那么trait Bar 必须在你调用它的范围内。否则,Rust 不会看到它。

因此,在您的情况下,thread_rng() 返回一个rand::prelude::ThreadRnggen 方法没有rand::prelude::ThreadRng 上定义。相反,它是在一个名为rand::Rng 的特征上定义的,它由ThreadRng 实现*。该特征必须在范围内才能使用该方法。

【讨论】:

谢谢!看来我得再读一遍特质章节了。【参考方案2】:

这和我以前知道的完全不同。

感觉不同,因为它确实不同。您可能习惯于通过某种虚拟方法表(例如在 C++ 中)进行动态调度,或者在 JS 的情况下,通过查找接收者对象自己的属性或通过 @987654324 查找其祖先来动态调度@-链。在任何情况下,您正在调用方法的对象都带有一些数据,这些数据告诉它如何获取您正在调用的方法。给定被调用方法的签名,接收者对象本身就知道如何获取带有该签名的方法。

不过,这不是唯一的方法。例如,

OCaml 或 SML 中的模块/函子 Haskell 中的类型类 Scala 中的implicits / givens Rust 中的特征

工作在一个相当不同的原则上:方法不依赖于接收者,而是依赖于模块/类型类/给定/特征实例。在每种情况下,这些都是与方法调用的接收者分开的实体。它开启了一些新的可能性,例如它允许你做一些特别的多态性(即在事后定义特征的实例,对于不一定在你控制之下的类型)。同时,编译器通常需要您提供更多信息才能选择正确的实例:它的行为有点像一个小型类型导向搜索引擎,甚至有点像“定理证明器”,为此为了工作,你必须告诉编译器在哪里为那些合成生成的实例寻找合适的构建块。

如果您以前从未使用过任何具有编译器的语言,该编译器具有基于类型信息“搜索实例”的子系统,那么这确实应该感觉很陌生。错误消息和解决方法确实感觉很不一样,因为不是将您的实现与接口进行比较并寻找冲突,您必须通过提供更多提示(例如,通过导入更多特征等)来引导这种实例搜索机制。

在您的特定情况下,rand::thread_rng 返回一个 struct ThreadRng。就其本身而言,该结构对gen 方法一无所知,因为该方法不直接绑定到该结构。相反,它在Rng trait 中定义。但同时,它可以定义为一些完全不相关的特征,并具有一些完全不同的含义。为了消除预期含义的歧义,因此您必须明确指定要使用 Rng 特征。这就是为什么你必须在use-clause 中提及它。

【讨论】:

非常感谢您的回答。就像您说的那样,错误消息最初并没有帮助我找到解决方案。而且我是这种语言的新手,有时会受到过去思维方式的限制。

以上是关于为啥我需要“使用 rand::Rng”在 rand::thread_rng() 上调用 gen()?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 rand() 编译时不包含 cstdlib 或使用命名空间 std?

为啥 rand() + rand() 会产生负数?

为啥我在使用 rand() 时会得到这种特殊的颜色模式?

为啥 rand() 在 Linux 上重复数字的频率远高于 Mac?

为啥新的随机库比 std::rand() 更好?

为啥我总是用 rand() 得到相同的随机数序列?