Rust 如何为 Hindley-Milner 解决可变性问题?
How does Rust solve mutability for Hindley-Milner?
我读到 Rust 使用 Hindley-Milner 有很好的类型推断。 Rust 也有可变变量,据我所知,当 HM 算法处理可变性时必须有一些约束,因为它可能过度泛化。以下代码:
let mut a;
a = 3;
a = 2.5;
不编译,因为在第二行推断出整数并且不能将浮点值分配给整数变量。所以我猜测对于简单变量,一旦推断出非泛型类型,变量就会变成单一类型并且不能再被泛化。
但是像 Vec 这样的模板呢?例如这段代码:
let mut v;
v = Vec::new();
v.push(3);
v.push(2.3);
这又失败了,但是最后一行又失败了。这意味着第二行部分推断出类型 (Vec),第三行推断出容器类型。
规则是什么?是否有我不知道的值限制之类的东西?还是我把事情搞得太复杂了,而 Rust 有更严格的规则(就像根本没有泛化一样)?
如果我没记错的话它会这样做:
let mut a;
a = 3; //here a is already infered as mut int
a = 2.5; //fails because int != float
对于 vec 片段:
let mut v;
v = Vec::new();// now v type is Vec<something>
v.push(3); // v type is Vec<int>
v.push(2.3); // here fails because Vec<int> != Vec<float>
注意我没有使用 rust 类型,只是为了有一个大概的想法。
rustc 在其类型推断中过于急切被认为是一个问题(就诊断质量而言)。
如果我们检查你的第一个例子:
let mut a = 3;
a = 2.5;
那么第一行会推断a
有一个{generic integer type}
,第二行会诊断2.5
不能赋给a
因为它不是通用整数类型。
预计更好的算法会记录冲突,然后指向每种类型的来源。也许我们会用 Chalk.
得到它
注意:泛型整数类型是 Rust 制作整数字面量的技巧"polymorphic",如果没有其他提示它应该是什么具体的整数类型,它将默认为i32
.
第二个例子的发生方式基本相同。
let mut v = Vec::new();
v.push(3);
详情:
v
被分配类型 $T
Vec::new()
生成类型 Vec<$U>
3
生成类型 {integer}
所以,在第一行,我们得到 $T == Vec<$U>
,在第二行,我们得到 $U == {integer}
,所以 v
被推断为类型 Vec<{integer}>
。
如果没有其他来源可以了解确切的整数类型,默认情况下会回退到 i32
。
我想指出,这里的可变性实际上并不影响推理;从类型推断或类型统一的角度来看,以下代码示例是等效的:
// With mutability:
let mut a = 1;
a = 2.5;
// Without mutability:
let a = if <condition> { 1 } else { 2.5 };
Rust 在 HM 方面存在更严重的问题,Deref
并且子类型化也更具挑战性。
我读到 Rust 使用 Hindley-Milner 有很好的类型推断。 Rust 也有可变变量,据我所知,当 HM 算法处理可变性时必须有一些约束,因为它可能过度泛化。以下代码:
let mut a;
a = 3;
a = 2.5;
不编译,因为在第二行推断出整数并且不能将浮点值分配给整数变量。所以我猜测对于简单变量,一旦推断出非泛型类型,变量就会变成单一类型并且不能再被泛化。
但是像 Vec 这样的模板呢?例如这段代码:
let mut v;
v = Vec::new();
v.push(3);
v.push(2.3);
这又失败了,但是最后一行又失败了。这意味着第二行部分推断出类型 (Vec),第三行推断出容器类型。
规则是什么?是否有我不知道的值限制之类的东西?还是我把事情搞得太复杂了,而 Rust 有更严格的规则(就像根本没有泛化一样)?
如果我没记错的话它会这样做:
let mut a;
a = 3; //here a is already infered as mut int
a = 2.5; //fails because int != float
对于 vec 片段:
let mut v;
v = Vec::new();// now v type is Vec<something>
v.push(3); // v type is Vec<int>
v.push(2.3); // here fails because Vec<int> != Vec<float>
注意我没有使用 rust 类型,只是为了有一个大概的想法。
rustc 在其类型推断中过于急切被认为是一个问题(就诊断质量而言)。
如果我们检查你的第一个例子:
let mut a = 3;
a = 2.5;
那么第一行会推断a
有一个{generic integer type}
,第二行会诊断2.5
不能赋给a
因为它不是通用整数类型。
预计更好的算法会记录冲突,然后指向每种类型的来源。也许我们会用 Chalk.
得到它注意:泛型整数类型是 Rust 制作整数字面量的技巧"polymorphic",如果没有其他提示它应该是什么具体的整数类型,它将默认为i32
.
第二个例子的发生方式基本相同。
let mut v = Vec::new();
v.push(3);
详情:
v
被分配类型$T
Vec::new()
生成类型Vec<$U>
3
生成类型{integer}
所以,在第一行,我们得到 $T == Vec<$U>
,在第二行,我们得到 $U == {integer}
,所以 v
被推断为类型 Vec<{integer}>
。
如果没有其他来源可以了解确切的整数类型,默认情况下会回退到 i32
。
我想指出,这里的可变性实际上并不影响推理;从类型推断或类型统一的角度来看,以下代码示例是等效的:
// With mutability:
let mut a = 1;
a = 2.5;
// Without mutability:
let a = if <condition> { 1 } else { 2.5 };
Rust 在 HM 方面存在更严重的问题,Deref
并且子类型化也更具挑战性。