是否有任何技术原因Rust被设计为使用点符号而不是使用索引符号(t[2]
)元组?
let t = (20u32, true, 'b')
t.2 // -> 'b'
点符号在访问结构体和对象的属性时似乎很自然。我在网上找不到资源或解释
我没有参与设计决策,但这是我的观点:
元组包含混合类型。也就是说,不能保证属性type_of(t[i]) == type_of(t[j])
。
然而,传统索引工作的前提是t[i]
中的i
不需要是编译时常数,这反过来意味着t[i]
的类型需要对所有可能的i
保持一致。在所有其他实现索引的rust集合中都是如此。具体来说,通过实现Index trait, rust类型可以被索引,定义如下:
pub trait Index<Idx> where Idx: ?Sized {
type Output: ?Sized;
fn index(&'a self, index: Idx) -> &'a Self::Output;
}
所以如果你想要一个元组来实现索引,Self::Output
应该是什么类型?实现这一点的唯一方法是使Self::Output
成为一个enum,这意味着在程序员方面,元素访问将不得不封装在一个无用的match t[i]
子句(或类似的东西)中,并且您将在运行时而不是编译时捕获类型错误。
此外,您现在必须实现边界检查,这也是一个运行时错误,除非您在元组实现中很聪明。
您可以通过编译时常量来要求索引来绕过这些问题,但是此时元组项访问假装表现得像一个正常的索引操作,而实际上与所有其他rust容器的行为不一致,这没有什么好处。
这个决定是在RFC 184中做出的。Motivation部分有详细信息:
现在访问元组和元组结构的字段是非常痛苦的——必须单独依靠模式匹配来提取值。这成为了一个问题,因此在标准库
(core::tuple::Tuple*)
中创建了12个特征,以使元组值访问更容易,并添加了.valN()
、.refN()
和.mutN()
方法来帮助解决这个问题。但这并不是一个很好的解决方案——它要求在标准库中实现这些特征,而不是在语言中实现,并且需要在使用时导入这些特征。总的来说,这不是一个问题,因为大多数时候std::prelude::*
是导入的,但这仍然是一个hack,并不是真正解决手头问题的方法。它还只支持长度不超过12的元组,这通常不是问题,但强调了当前情况有多糟糕。
使用t.2
语法而不是t[2]
语法的原因在下面的注释中得到了最好的解释:
索引语法其他地方具有一致的类型,但是元组是异构的,因此
a[0]
和a[1]
将具有不同的类型。
我想从我使用函数式语言(Ocaml)的经验中提供一个答案,因为我已经发布了这个问题。
除了@rom1v引用之外,像a[0]
这样的索引语法在其他地方也用于某种序列结构,而元组则不是。例如,在Ocaml中,元组(1, "one")
被认为具有类型int * string
,这符合数学中的笛卡尔积(即平面为R^2 = R * R),另外,通过nth
索引访问元组被认为是惟一的。
由于其多态性质,元组几乎可以被认为是一个记录/对象,通常更喜欢像a.fieldName
这样的点表示法作为访问其字段的约定(除了像Javascript这样的语言,它将对象视为字典,并允许像a["fieldname"]
这样的字符串字面量访问)。我所知道的唯一使用索引语法来访问字段的语言是Lua。
就我个人而言,我认为像a.(0)
这样的语法往往比a.0
看起来更好,但这可能是有意(或无意)尴尬的,考虑到在大多数函数式语言中,理想的模式匹配元组而不是通过索引访问它。由于Rust也是命令式的,所以像a.10
这样的语法可以很好地提醒您模式匹配或"使用结构体"。