# Reference of Tuple and Tuple of Reference

What is the relationship between reference of tuple and tuple of reference as types?
Why does the first works but the second doesn't?

let a = 1;
let b = 2;
// This works, c: &i32, d:&i32
let (c, d) = &(a, b);

type TupleOfRef<'a> = (&'a i32, &'a i32);
let e = (a, b);
// This doesn't
let f: TupleOfRef = &e;

EDIT 23.03.30

I should make my point of question more clear. It is more about the relation of type (&'a A, &'a B) with (A, B).

Thought the memory layout of tuple is not guaranteed, it is clear that can't make &(A, B) out of &A and &B without cloning, since there is no one memory address holding A and B.

However, making (&A, &B) out of (A, B) makes some sense, since we have not only the address of tuple, (namely, &(A, B)), but also at the addresses of its elements, (namely, &Aand &B, as @etchesketch mentioned). And this seems work in the first case of the example above, but not in the second.

Actually the second is what I want. Is there anyway to (&A, &B, ..) out of owned (A, B, ..)in general? Or is there any good way to express these 'matchability' in trait bound?

You're witnessing something called binding modes or "match ergonomics". In this code:

let (c, d) = &(a, b);

(c, d) is a pattern matching against a &(i32, i32) expression, so the default binding mode for c and d is to take a reference to a and b.

But when you add an ascription, you're saying what you expect the type of the expression is. So this doesn't compile either:

let (c, d): (&i32, &i32) = &(a, b);

You can write the above without utilizing binding modes like so:

let &(ref c, ref d) = &(a, b);

Here the & in the pattern "strips away" the & in the expression, making the binding mode by-value. The ref means "bind by taking a reference", instead of binding by-value. (Read more about patterns here.)

Sometimes binding modes can be confusing. If you want to see where they're taking place so you can be more sure of what's going on, you can use Clippy with the pattern_type_mismatch lint. Here's an example (you can run Clippy from the Tools menu in the top-left).

5 Likes

What's happening here is that match ergonomics allow you to use a pattern that looks like it expects a tuple, like (c, d) to be matched against a reference to a tuple. The expanded actual pattern this â€śtranslatesâ€ť into would look like

let &(ref c, ref d) = &(a, b);

This binds c to a reference to the first field of the tuple, and d to a reference to the second field.

Note that your pattern gives you two variables of type &i32, not a single tuple of two &i32s. If you want a single tuple combining both, youâ€™ll need to create it as a subsequent step.

let (c, d) = &(a, b);
let tup = (c, d);

While this point may seem pedantic, itâ€™s useful to understand important aspects of why the second piece of code cannot work. Creating (&i32, &i32) from &(i32, i32) requires two steps: first, you need to create references to each of the fields, then these two references need to be combined into a new tuple; and pattern matching can only do the first half of this. Hence

let f: TupleOfRef = &e;

cannot possibly work to do the same in a single step. The pattern f also is a variable pattern, not a tuple pattern, so match ergonomics donâ€™t apply either in the first place, and it really only does a simple move/copy.

Letâ€™s discuss some equivalent operations to

let (c, d) = &(a, b);

wellâ€¦ letâ€™s give the thing a name perhaps

let r = &(a, b);
let (c, d) = r;

As mentioned above, the more explicit equivalent alternative looks like

let r = &(a, b);
let &(ref c, ref d) = r;

Patterns are duals to expressions, and each part of this pattern has a specific meaning:

• starting with r: &(i32, i32), the first & in the pattern will match the reference, i.e. essentially dereferences r to *r of type (i32, i32), and matches the result against the inner pattern (ref c, ref d).
• the tuple pattern then accesses both fields of the tuple and matches each against the respective sub-pattern
• the first field of *r, i.e. (*r).0 is matched against ref c
• the second field of *r, i.e. (*r).1 is matched against ref d
• the ref foo pattern binds by reference, e.g. let ref foo = bar; is like let foo = &bar
• so for the first field, c is bound to &((*r).0)
• and for the second field, d is bound to &((*r).1)

This gives us the equivalent code

let r = &(a, b);
let c = &((*r).0);
let d = &((*r).1);

which can be syntactically slightly simplified:

let r = &(a, b);
let c = &r.0;
let d = &r.1;

You might have mistyped the date.

The instability of layout in Rust refers to the fact that the layout may change between different compiler versions or targets (or possibly even between different compilations). Within a single program however, all instances of a specific tuple type like (i32, i32) have the same layout, and while you the programmer cannot guess that layout in advance, the compiler is allowed to know the layout and use it to get &i32 references to the first and second field in a (i32, i32) tuple from any given &(i32, i32) reference r, via the pattern matching, or expressions like &r.0, as discussed above, without any problem.

6 Likes

Not sure if I have asked before, but if I did, then it was a while ago and I forgot the answer:

With match ergononics, is there still a need to use ref or ref mut in patterns sometimes? If yes, could someone come up with an example?

It happened to me a couple of times that I had some tuple in the form &(CopyType, NonCopyType) and I wanted to get a CopyType and a &NonCopyType out of it. Unfortunately using a reference pattern for the CopyType doesn't work, so I had to use a reference pattern outside the tuple and a ref pattern for the NonCopyType.

For example:

let tuple = &(1, "foo".to_string());

let &(n, ref s) = tuple;
// let (&n, s) = tuple // Doesn't work

// Assert that the types are what I expect
let _: i32 = n;
let _: &String = s;

This also happens when you are pattern matching over a slice of Copy types and you want to get an owned instance of the first and a slice reference to the tail. In that case you also can't use a reference pattern for the element, but you also can't just put a reference pattern in front of the slice pattern because it would make the tail an unsized slice, which you can only exist behind a reference. In this case the working pattern would be &[elem, ref rest @ ..]

7 Likes

I tried this:

fn main() {
let tuple = &(1, "foo".to_string());

// let &(n, ref s) = tuple;
// let (&n, s) = tuple // Doesn't work
let (&n, s) = tuple; // Doesn't seem to work either

// Assert that the types are what I expect
let _: i32 = n;
let _: &String = s;
}

But it doesn't work:

Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/main.rs:6:10
|
6 |     let (&n, s) = tuple;
|          ^^       ----- this expression has type `&({integer}, String)`
|          |
|          expected integer, found reference
|
= note:   expected type `{integer}`
found reference `&_`
help: consider removing `&` from the pattern
|
6 -     let (&n, s) = tuple;
6 +     let (n, s) = tuple;
|

error: could not compile `playground` due to previous error

I always thought I could add & in a pattern to "undo" something being a reference, i.e. dereferencing it. But apparently that doesn't hold in cases where I (would) have a reference only because of match ergonomics.

So I guess I can need it (more often?) in case of Copy types because there will no partial moves that would be problematic.

1 Like

Sure, just take references, as you would with anything else:

let tup = (42_u64, String::new());
let (a, b) = (&tup.0, &tup.1);

Wrt. binding modes: others have answered why &(T, U) is sometimes turned into (&T, &U) by the compiler. This is undoubtedly confusing and goes against strong typing. While you are learning the language (and even later), it's advisable to turn off this misfeature completely, using Clippy and #![deny(clippy::pattern_type_mismatch)].

2 Likes

Right, match ergonomics is a bit different from what I imagine you might be imagining here:

What you probably imagine is that let (PAT1, PAT2) = r; with r: &(S, T) will create a &S and match it against PAT1; as well as creating a &T and matching it against PAT2. Thatâ€™s not what happens. Instead itâ€™s just that the pattern is re-written in precisely two ways:

• a dereferencing step is introduced implicitly, so the pattern becomes a &(PAT1', PAT2') pattern
• the sub-patterns PAT1 and PAT2 would be re-written using the concept of â€śdefault binding modesâ€ť which (only) affects variable patterns without an explicit binding mode, and thus patterns like x inside of PAT1 would become ref x in PAT1'[1]

This has effects like the one you described that you cannot simply write &x instead of x to turn it into an owned value again. This will simply give a type failure instead, since we are still matching against an owned i32, itâ€™s just that the pattern x was implicitly re-written to ref x due to default binding modes. This also has the effect that writing ref x explicitly still works and suddenly is equivalent to writing x, and writing mut x will also remove the effects of default binding modes, because it is considered an explicit binding mode, resulting in a hacky way of achieving binding to an owned i32 after all, though thatâ€™s only really â€śniceâ€ť if you happen to want a mutable variable, too, and I suppose it can be confusing to readers. (Some demo playground.)

1. there seems to be some underdocumented action of resetting default binding modes inside of any &mut â€¦ or &â€¦ patterns â†©ď¸Ž

2 Likes

I need a general solution works for general n-ary tuple,, can't index the element manually.

Rust doesn't yet support being generic over tuple arity; this would require something like variadic generics. In the meantime, you can create and implement a trait for tuples up to a fixed size, which allows such conversions. (You'll probably want to implement it using a macro to avoid boilerplate.)

1 Like

I'm not sure if I understand what you need exactly. I migt have done something the other way around: Allowing to pass (&T, &U) instead of &(T, U). I solved this with some manual implementations that got horribly complex here:

mmtkvdb::storable::StorableRef

I decided to only support tuples up to size 4. I guess using macros could help me support longer tuples, but implementation has been already pretty complex and confusing.

For example:

trait RefElements {
type Refs<'a> where Self:'a;
fn ref_elements(&self)->Self::Refs<'_>;
}

/* Template impl
impl<A> RefElements for (A,) {
type Refs<'a> = (&'a A,) where Self:'a;
fn ref_elements(&self)->Self::Refs<'_> {
#[allow(non_snake_case)]
let &(ref A,) = self;
(A,)
}
} */

macro_rules! impl_ref_elements {
() => {};
(\$T0:ident \$(\$T:ident)*) => {
impl<\$T0, \$(\$T,)*> RefElements for (\$T0,\$(\$T,)*) {
type Refs<'a> = (&'a \$T0, \$(&'a \$T,)*) where Self:'a;
fn ref_elements(&self)->Self::Refs<'_> {
#[allow(non_snake_case)]
let &(ref \$T0,\$(ref \$T,)*) = self;
(\$T0,\$(\$T,)*)
}
}

impl_ref_elements!{\$(\$T)*}
}
}

impl_ref_elements!{A B C D E F G H I J K L M}

impl RefElements for () {
type Refs<'a> = ();
fn ref_elements(&self)->() { () }
}
3 Likes

It does seem the closest solution so far.
However, it does not work for non-tuple type, unfortunately. I am looking for any other solution.

By the way, @Jmb suggested quite similar solution.

And here is a new question post for it.

This is the first mention I've seen about non-tuple types from you, and I don't understand how it relates to your question, which explicitly mentions tuples. Can you elaborate on the behavior you're looking for for non-tuples?

1 Like

Yes, I did not mention the non-tuple type and it is something I figured out while testing the trait solution.

It is something regarding its purpose of it; Implementing the operation of function at the type level.
Please refer to the second Stack Overflow question for detailed motivation.

Are there plans (a GitHub issue/PR/RFC) to tweak match ergonomics to match OP's intuition?

1 Like

I am not aware of existing plans or proposals, but also I havenâ€™t searched for it. First place to look for it would probably be the original discussions of match ergonomics themself, where people might have discussed alternative approaches, and in case they did, they might even have found disadvantages that I havenâ€™t thought of yet.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.