Thoughts on tuples

The problem with this is the memory layout where the tuple slice/sub tuple may not have the same memory layout as it's parent so creating a new tuple may not be what's wanted here.

I think that it can be solved by marking which tuple element/offset remains accessible through a variable. Not sure if such metadata can exist in the compiler (if it's possible I definetely will write an rfc).

If marking which offsets/members are accessible/not accessible (by move/ref/mut) this can also work with structs

Pseudo code to illustrate this

fn takes_refs(string: &mut String, nb: &mut i32);
fn takes_tuple(t: (&mut String, &mut i32));

let (_, rest @ .., _) = &mut (1, String::new("hello"), 12, 13);

takes_refs(rest); // rest is an alias to a mutable string and a mutable i32
takes_tuple((..rest)); // tuple created with members of rest

// only 2 fields of the original tuple can be used by rest
let (a, b): (&mut String, &mut i32) = rest;
// or
let { 0: a, 1: b } = rest;

Similar functionality for structs, member names can be used (this could be an evolution after full tuple bindings)

struct Foo { field1: u8, field2: u8, field3: String }
struct Bar<'a> { field2: &'a u8, field3: &'a String }

fn takes_refs(field2: &u8, field3: &String);
fn takes_bar<'a>(bar: Bar<'a>);

let Foo {_field1, rest.. } = &Foo { ... };
takes_refs(rest);
takes_bar(Bar{..rest})
// only field2 and field3 are marked as accessible through rest
let { field2, field3 } = rest;

I understand that it has a different memory layout, but I fail to see how it is a problem when it produces a tuple with references to the individual elements.

You can think of my example above as being syntactic sugar for

let (_, ref rest_1, ref rest_2, _) = (1, 3u8, 3, 7);
let rest = (rest_1, rest_2);
// rest: (&3u8, &3i32)

That seems pretty intuitive to me, and is much easier than introducing a kind of "tuple slice". And it will likely be more performant, since a bunch of references is easier to optimize than a special metadata section. It is also trivial for borrowcheck to reason about. It doesn't require any new, complicated concepts that will make the language harder to learn.

1 Like

To be honest that's what I think I expressed in this thread but I got this answer: Thoughts on tuples - #10 by digama0

Either I've been misunderstood or this causes a problem I don't really know.

My previous post (Thoughts on tuples - #21 by Massou) is a speculation of how it could work without creating a new tuple.

Having insights from people who know compiler internals and clarify what is possible/not possible regarding tuple patterns would be awesome.

That is only a problem if you expect to get a single reference:

let (_, ref rest, _) = (1, 3u8, 3, 7);
// rest == &(3u8, 3i32)

I suggested to make it a tuple of references instead, so rest == (&3u8, &3i32). This is possible. Whether it is a good idea is another question, because it isn't entirely consistent with how patterns work today.

This is, btw, similar to how currently mut doesn’t distribute over tuple patterns so you can’t write mut (a, b) (which I at least try to do every once in a while) but have to write (mut a, mut b).

If one wants to bind a tuple of non-references to a tuple of references, it’s currently possible via tup @ (ref _0, ref _1) (unfortunately you can't use _ here for some reason). So, should people be against changing the semantics of ref, maybe a good syntax to bind a rest-tuple to a tuple of res would be

(a, rest @ ref.., b)

and the same should work for mut and ref mut, too. Note that

(a, rest @ (ref..), b) 

would then automatically also work, and bind a tuple within a tuple rather than a rest-tuple.

rest @ (ref _0, ref _1) doesn't do what you think it does (which is why you can't use ref on an _ pattern) (playground):

#![feature(type_name_of_val)]

fn main() {
    let foo @ (ref _0, ref _1) = (1, 2);
    dbg!(std::any::type_name_of_val(&foo));
}
[src/main.rs:5] std::any::type_name_of_val(&foo) = "(i32, i32)"
2 Likes

Ough, thanks. I thought I had double-checked it on the playground, but I had a brain fart in my test program… And yeah, it wouldn't even make much sense if ref _0 somehow also modified the binding mode of rest :frowning:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.