Rust Reference of Tuple and Tuple of Reference

519 Views Asked by At

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;

To 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, &A and &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?

Following question: Tuple of Reference and Reference of Tuple for type level operation

3

There are 3 best solutions below

2
Jmb On BEST ANSWER

If you do that a lot, you can make it easier with a trait:

trait TupleOfRefs<'a> {
    type Output: 'a;
    fn as_tuple_of_refs (&'a self) -> Self::Output;
}

impl<'a, A: 'a> TupleOfRefs<'a> for (A,) {
    type Output = (&'a A,);
    fn as_tuple_of_refs (&'a self) -> Self::Output {
        (&self.0,)
    }
}

impl<'a, A: 'a, B: 'a> TupleOfRefs<'a> for (A, B) {
    type Output = (&'a A, &'a B);
    fn as_tuple_of_refs (&'a self) -> Self::Output {
        (&self.0, &self.1)
    }
}

fn main() {
    let a = 1;
    let b = 2;
    
    let t = (a, b);
    let r = t.as_tuple_of_refs();
    
    println!("{r:?}");
}

Playground

The downside is that you first need to implement the trait for all possible tuple sizes (although it can be simplified with a macro):

macro_rules! make_tuple_of_refs {
    ($($t:ident $i:tt),*) => {
        impl <'a, $($t: 'a),*> TupleOfRefs<'a> for ($($t,)*) {
            type Output = ($(&'a $t,)*);
            fn as_tuple_of_refs (&'a self) -> Self::Output {
                ($(&self.$i,)*)
            }
        }
    }
}

trait TupleOfRefs<'a> {
    type Output: 'a;
    fn as_tuple_of_refs (&'a self) -> Self::Output;
}
make_tuple_of_refs!(A 0);
make_tuple_of_refs!(A 0, B 1);

Playground

3
etchesketch On

While &(a, b) destructures to two variables that are both &i32 that is not the same thing as having a tuple of type (&i32, &i32).

You can make the second example work by using let f: TupleOfRef = ( &e.0, &e.1 );

5
cyqsimon On

The simple answer is &(i32, i32) and (&i32, &i32) are not the same type, but this is probably not the level of detail you're looking for.


It's perhaps easier to understand why if you think of it in terms of memory layout. cheats.rs has some excellent illustrations that I will shamelessly steal here:

Tuple memory layout

What's important here, is that whatever you store in your tuple, the elements are always contiguous* see comment in memory. That's true for owned types and references, it's just that when the element is a reference, the memory it points to is not necessarily contiguous.

This is why conversion between &(T, U) and (&T, &U) is non-trivial. In the former case, you have a T and U "bundle" (meaning they are physically together in memory), and a reference points to the "bundle". In the latter case, you have an owned "bundle" of &T and &U (meaning the references are physically together in memory, but they may each point to wherever they want).


Amendments:

Yes, you can create a (&T, &U) from &(T, U), since you can of course obtain &T and &U from &(T, U). The tuples crate provides the TupleAsRef trait, which is implemented for tuples with up to 32 elements:

use tuples::TupleAsRef;

let t = (69, 420);
assert_eq!(t.as_ref(), (&69, &420));