Rust Reference of Tuple and Tuple of Reference
Asked Answered
C

3

5

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

Caresa answered 29/3, 2023 at 16:53 Comment(2)
That's because you haven't specified the type in the first case, so the compiler is smart enough to infer the types. If you mention the type, it will fail like in the latter caseCanonist
I think this is also related to match ergonomicsMatrimony
S
1

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

Sightread answered 30/3, 2023 at 7:2 Comment(2)
Thank you so much. This is the closest thing to what I need. I have tested your method and It works just fine except for the case of non-tuple type. Actually this is for composing operation in type level. e.g. (&A) -> A x (&B) -> B = ((&A, &B)) -> (A, B). I define a function-like trait holding operation as member function with param type with the associated types. Regular ref does not work since the result of Compose<OP::<T=A>, OP::<T=B>> would be (&(A, B)) -> (A, B) rather than (&A, &B) -> (A, B). Do you have any suggestion for this??Caresa
I have posted a separate question for it. #75895644Caresa
S
4

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));
Signorino answered 29/3, 2023 at 17:12 Comment(5)
Note that structs (and tuples) may not store that data contiguously because there may be padding, but other than that your explanation is correct.Tightwad
@ChayimFriedman thanks for the correction. I overlooked that.Signorino
Layout order of tuples is not guaranteed either, to my knowledge. So the layout could be C, then A, then B, for example.Plod
@Signorino Thank you for the kind explanation. I do aware that they are not the same type. I have made my point a bit more clear, please be advised and enlighten me.Caresa
FWIW given the image above it's overwhelmingly likely that the physical layout will be BCA, as (as with structs) the compiler will reoder fields from larger to smaller, in order to limit padding.Adjudge
M
3

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 );

Myosin answered 29/3, 2023 at 17:6 Comment(3)
Thank you for your answer. Actually it is more closer to something I want. But is there any good way to do it for n-ary tuple in general as first case of my example, without indexing the element?Caresa
@Caresa I didn't know the answer to your n-ary question so I looked around and it doesn't look like there is any straightforward solution. Are all of your tuple items of the same type? Why not just use a vector?Myosin
No, they are heterogeneous. Because this is about type-level programming, can't depend on heap-allocated, runtime information.Caresa
S
1

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

Sightread answered 30/3, 2023 at 7:2 Comment(2)
Thank you so much. This is the closest thing to what I need. I have tested your method and It works just fine except for the case of non-tuple type. Actually this is for composing operation in type level. e.g. (&A) -> A x (&B) -> B = ((&A, &B)) -> (A, B). I define a function-like trait holding operation as member function with param type with the associated types. Regular ref does not work since the result of Compose<OP::<T=A>, OP::<T=B>> would be (&(A, B)) -> (A, B) rather than (&A, &B) -> (A, B). Do you have any suggestion for this??Caresa
I have posted a separate question for it. #75895644Caresa

© 2022 - 2024 — McMap. All rights reserved.