Rust类型推断的怪异性

4

我之前遇到了一个奇怪的情况。我写了:

if rand::random() < self.food_chance {...}

(self.food_chancef32 类型)

我遇到了类型推断错误:

   |
71 |             if rand::random() < self.food_chance {
   |                ^^^^^^^^^^^^ cannot infer type for `T`

然而,这段代码可以编译。
if self.food_chance > rand::random() {...}

我有疑问,这种行为有原因吗?难道这是期望的行为吗?

我对类型推断理论有一些了解,大多数算法对左侧/右侧都没有偏见,所以我倾向于认为这是预期的行为而不是明显的错误。

我尝试过搜索相关信息,但从未找到过类似的结果。

2个回答

7
基本上这是因为Rust无法推断接收者的类型。必须知道接收者的类型才能执行方法查找,而且由于Rust的设计,接收者的类型必须显式指定。
rand::random() < self.food_chance

等同于

std::cmp::PartialOrd::lt(&rand::random(), &self.food_chance);

接收器是左操作数。
在表达式中,
self.food_chance > rand::random()

另一方面,接收器是已知的,因此Rust可以执行方法查找。它仅会找到一个 f32 的实现作为接收器,即PartialOrd<f32>,然后再由此确定右侧的类型。如果有不同右侧类型的实现,Rust也无法推断出该情况下的类型。
要理解为什么Rust不能推断接收器,请查看Rust方法查找过程如何工作。第一步是根据接收器的类型构建候选类型列表。这显然只在你已经“知道”接收器的类型时才有效。

你是否知道这是语言本身的限制(无法通过其他方式解决)还是当前实现的限制? - Matthieu M.
@MatthieuM。当前的语言定义中根本没有包含推断接收器类型的过程。方法查找过程的定义(请参见答案中的链接)肯定需要事先知道接收器的类型。如果我们想要推断接收器的类型,我们需要为接收器未知的情况发明不同的方法查找语义。我想不出任何合理和一致的方式来定义这个语义。 - Sven Marnach
我认为通过平等地处理所有参数应该是可能的;通过简单名称查找(内在+可见特征)选择候选方法列表,然后使用接收器和参数进行推理以找到哪个候选方法可能匹配,如果存在任何歧义则拒绝。我不确定的是是否存在算法复杂度会爆炸的情况,使其实际上无法解决。 - Matthieu M.
@MatthieuM。你可能可以用这些语义来定义一种语言,但它会与Rust有很大的不同。 - Sven Marnach
我可能有点天真,但我没有看到任何语义上的区别;只是在如何推断类型方面有所不同。 - Matthieu M.
@MatthieuM。有几个问题。首先,特质实现可以是通用的,因此在范围内找到所有方法的实现根本不可能-可能有无限多个实现。第二个问题是当前机制按特定顺序查找候选类型列表上的方法,并使用第一个匹配项。仅当单个类型存在歧义时才会出错。第三个问题是接收器在其他方面与类型推断不同,因此将其视为其他参数相同会更改语义。 - Sven Marnach

3
> 操作符是一个方法的语法糖;它是 partial_cmp 方法,该方法属于 PartialOrd 特征。该方法使用左侧作为 self 参数调用;这决定了使用哪个 PartialOrd 实现。由于您可以使用 f32 作为右侧类型为不同类型实现 PartialOrd 特征,因此只有在了解左侧类型时才能唯一确定 PartialOrd 的实现。反过来,PartialOrd 的这种实现决定了需要从 rand::random 中获取的类型。

你认为右侧可能有不同类型的f32,而左侧只能有一种实现方式,这是不正确的。正如你可以在这个例子中看到的那样,左侧只有一个f32的实现方式,但是右侧也只有一个f32的实现方式,所以我真的看不出这个答案如何解释这种不对称性。 - Sven Marnach
我的观点是,假设 OP 没有手动实现 f32PartialOrd,如果是这样,那么 OP 应该提到这一点,因为这对问题很重要。这也是编译器能够推断类型的原因。我并没有说只能有一个带有 f32 作为左操作数的 PartialOrd 实现。不过,你的例子确实很好地展示了你的答案。 - vandenheuvel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接