16

Is there any technical reason Rust is designed to use dot notation for tuples instead of using index notation (t[2])?

let t = (20u32, true, 'b')
t.2 // -> 'b'

Dot notation seems natural in accessing struct's and object's properties. I couldn't find a resource or explanation online.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Pandemonium
  • 7,724
  • 3
  • 32
  • 51
  • 7
    Why not? Since tuples are fixed sized and can have different types of elements, it's more similar to struct properties than array indices. – Colonel Thirty Two Aug 16 '15 at 00:56

4 Answers4

15

I had no part in the design decisions, but here's my perspective:

Tuples contain mixed types. That is, the property type_of(t[i]) == type_of(t[j]) cannot be guaranteed.

However, conventional indexing works on the premise that the i in t[i] need not be a compile-time constant, which in turn means that the type of t[i] needs to be uniform for all possible i. This is true in all other rust collections that implement indexing. Specifically, rust types are made indexable through implementing the Index trait, defined as below:

pub trait Index<Idx> where Idx: ?Sized {
    type Output: ?Sized;
    fn index(&'a self, index: Idx) -> &'a Self::Output;
}

So if you wanted a tuple to implement indexing, what type should Self::Output be? The only way to pull this off would be to make Self::Output an enum, which means that element accesses would have to be wrapped around a useless match t[i] clause (or something similar) on the programmer's side, and you'll be catching type errors at runtime instead of compile-time.

Furthermore, you now have to implement bounds-checking, which is again a runtime error, unless you're clever in your tuple implementation.

You could bypass these issues by requiring that the index by a compile-time constant, but at that point tuple item accesses are pretending to behave like a normal index operation while actually behaving inconsistently with respect to all other rust containers, and there's nothing good about that.

Ponkadoodle
  • 5,777
  • 5
  • 38
  • 62
  • Thank you @Ponkadoodle. Could you please elaborate on this sentence: `However, conventional indexing works on the premise that the i in t[i] need not be a compile-time constant, which in turn means that the type of t[i] needs to be uniform for all possible i.` By "`i` need not be a compile-time constant", do you mean in situations where it is used as a loop variable to index into a collection and therefore cannot be evaluated at compile-time? – user51462 Jun 16 '23 at 04:59
9

This decision was made in RFC 184. The Motivation section has details:

Right now accessing fields of tuples and tuple structs is incredibly painful—one must rely on pattern-matching alone to extract values. This became such a problem that twelve traits were created in the standard library (core::tuple::Tuple*) to make tuple value accesses easier, adding .valN(), .refN(), and .mutN() methods to help this. But this is not a very nice solution—it requires the traits to be implemented in the standard library, not the language, and for those traits to be imported on use. On the whole this is not a problem, because most of the time std::prelude::* is imported, but this is still a hack which is not a real solution to the problem at hand. It also only supports tuples of length up to twelve, which is normally not a problem but emphasises how bad the current situation is.

The discussion in the associated pull request is also useful.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Steve Klabnik
  • 14,521
  • 4
  • 58
  • 99
  • Does your use of the word "we" imply that you were involved in the decision? And does "different syntax" refer to the `t.2` syntax (that doesn't seem entirely clear). – Keith Thompson Aug 16 '15 at 02:21
  • 1
    I say 'we' because I'm on the core team, but this decision was made, as far as I can remember, before I was involved, which puts it before 2012 at least. (And yes, that's the difference I refer to) – Steve Klabnik Aug 16 '15 at 02:22
  • 2
    Your answer would be improved by including a brief summary of the justification rather than just linking to information on another site. You might also want to mention in the answer that you're on the core team. – Keith Thompson Aug 16 '15 at 02:33
  • I'd argue that the membership on the core team is moot for the purposes of this answer. Steve wasn't a member of the team at the point in time, and even if he was, it wouldn't matter unless he personally helped with this RFC. However, summarizing the information would be in line with SO's rules against link-only answers. – Shepmaster Aug 16 '15 at 02:44
  • Thanks everyone and @Keith Thompson for the link! Although it is odd at first coming from duck-typing language like Python in which you have no need to care much about the differences of each Iterator, it actually makes sense to the Rust way of "being careful". – Pandemonium Aug 16 '15 at 12:02
7

The reason for using t.2 syntax instead of t[2] is best explained in this comment:

Indexing syntax everywhere else has a consistent type, but a tuple is heterogenous so a[0] and a[1] would have different types.

rom1v
  • 2,752
  • 3
  • 21
  • 47
1

I want to provide an answer from my experience using a functional language (Ocaml) for the while since I've posted this question.

Apart from @rom1v reference, indexing syntax like a[0] everywhere else also used in some kind of sequence structure, of which tuples aren't. In Ocaml, for instance, a tuple (1, "one") is said to have type int * string, which conforms to the Cartesian product in mathematics (i.e., the plane is R^2 = R * R). Plus, accessing a tuple by nth index is considered unidiomatic.

Due to its polymorphic nature, a tuple can almost be thought of as a record / object, which often prefer dot notation like a.fieldName as a convention to access its field (except in language like Javascript, which treats objects like dictionaries and allows string literal access like a["fieldname"]. The only language I'm aware of that's using indexing syntax to access a field is Lua.

Personally, I think syntax like a.(0) tends to look better than a.0, but this may be intentionally (or not) awkward considering in most functional languages it is ideal to pattern-match a tuple instead of accessing it by its index. Since Rust is also imperative, syntax like a.10 can be a good reminder to pattern-match or "go use a struct" already.

Pandemonium
  • 7,724
  • 3
  • 32
  • 51
  • "The only language I'm aware of that's using indexing syntax to access a field is Lua." Also Python indexes tuples. But Python is dynamically typed, so it has no problem getting data of a different type for different tuple index values. – Craig McQueen Nov 08 '19 at 02:49