In The Rust Programming Language book, in the Structs chapter we are introduced to tuple structs.
Under which cases should I use tuple structs over normal tuples (other than the example mentioned in the book)?
Use tuple structs when:
You will find that this is not too often the case. If it is, you are usually just trying to wrap exactly one other type into a new type to give it different behavior. This is a known pattern, called 'newtype' pattern. When you have multiple fields in your struct, you usually want to name them.
As a quick reminder:
Let's take a look at [T]::split_at()
:
fn split_at(&self, mid: usize) -> (&[T], &[T])
They authors of this function choose to return a tuple here. Let's see, would we gain anything from...
SplitSlice
, SliceParts
, ...? Every name we could give is superfluous, because the function is already named appropriately.left
and right
would make it more clear what part is what side. But here we assume that programmers have the correct intuition. English is written from left to right, in English cultures arrays are usually drawn from left to right ([0 | 1 | 2 | 3 ]
), so it kind of makes sense for most people.⇒ just a tuple is fine!
Another example: imagine you are writing an application that somehow works with a text file (compiler, text editor, ...). When you are talking about a region in the text file (for example, a search result), you want to specify that region by giving byte offsets. Let's see if tuples work out for us:
fn find_first_occurence(file: &TextFile, needle: &str) -> (usize, usize)
Does the return type speak for itself? Rather not... even if you know that regions in the file are specified by byte offsets, the return value is still ambiguous: either it's (start, end)
or it's (start, stuff)
, where stuff can be any other search metric (the function doesn't need to return end
, since we already know the length of needle
and thus could calculate it). So, I hope you agree, we want to name the return type. Let's call it Span
-- that's the name used in the Rust compiler.
Next question: struct or tuple struct? Does it make sense to name the fields? Again, there is no clear answer, but I'd argue that we do want to name the fields. What is easier to read: span.1 - span.0
or span.high - span.low
? Additionally, we can write documentation for the named fields; for example, to document that high
is exclusive.
⇒ struct
Now imagine you want to report line numbers to the user. Especially important is a function that returns the corresponding line number for a given span (for simplicity's sake, we assume this span never spans more than one line).
fn get_line_number(file: &TextFile, span: Span) -> ???
So what do we return? In the context of this function a simple u32
is probably fine! There is no doubt what this u32
represents. Although: does the line number counting starts with 0 or 1? sigh
Sure, we could document this property on the function ... and every other function working with line numbers. So how about creating a new type and document it there? This will also help with functions taking multiple numbers, including line numbers:
print_snippet(&file, 57, 63, 80);
Wait, what are line numbers now? Exactly: instead of taking u32
s, it would take LineNumber
s -- the type system is the documentation.
We now agreed on creating a new type. But: struct or tuple struct? Let's try struct:
struct LineNumber {
line_number: u32, // uhm...
}
Well, how to call the field? The only fitting names fall into two categories:
line_number
, number
, line
, ...inner
, value
, data
, ...There is not really a benefit in naming the field. So let's don't and use a ...
⇒ tuple struct 🎉
The decision to make it an own type has some nice consequences: we can use 0-based numbers in our source code but never need to worry about printing it incorrectly:
impl fmt::Display for LineNumber {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
(self.0 + 1).fmt(f)
}
}
We can adjust the 0-based number to a 1-based number (for those damn humanz!) in a single place!
Also note that we were talking about byte offsets above. Instead of using usize
, we should also create a new type to distinguish it from char offsets, for example!
Tuple structures are less common. Use them when you only have a few members and it's clear enough which are which that they don't need a name.
One common use of tuple structures is newtypes. This is a tuple structure with only one member. This is useful to make simple wrappers around existing types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With