Nascent Idea: allow `.0`, `.1`, etc on arrays

No. The “variadic generics problem” is how Rust will permit users to write generics with a variable number of type parameters, as C++ does:

template<typename T> T adder(T v) { return v; } 

template<typename T, typename... Args> T adder(T first, Args... args) { 
    return first + adder(args...); 
}

“Heterogeneous” means that the type parameters may be different types.

Arrays cannot solve the variadic generics problem, and cannot be heterogeneous.

Edit: the reasons we wouldn’t just adopt something like the C++ solution are that, first, it cannot work without either function overloading (to provide a base-case and terminate the recursion) or const-if (to manually handle the base case within the same function); and, second, it’s just not very clean or easy to work with. (C++ has some fairly ugly standard library features to emulate iteration over the types, but it’s…still not wonderful to work with.)

1 Like

:-1: I think having two syntaxes for the same thing would be confusing to new users. It’s sort of like NLL. Given a choice between:

  • requiring explicit syntax to express disjointness, in order to make the borrow checker simpler to describe (pre-NLL, adding extra {} scopes to express disjointness in time; in this idea, using a new syntax to express disjointness in space); or
  • just making the obvious thing work, at the cost of a more complex borrow checker

…the former has an intuitive appeal, but it turns out the latter is more ergonomic.

3 Likes

I think having .0 kind of syntax on arrays is a workaround and what we really want is to have the compiler not complain when we mutably borrow a provable-at-compile-time-to-be-safe piece of array. I’d rather not see any workarounds in the language.

7 Likes

What the OP’s request for .0-like tuple-field-access notation for arrays shows is that there should be a way to view an array as a struct (and maybe also the other way around, when the tuple is obviously homogeneous but also with the good #[repr].

I’d imagine an (ideally no-op) function/macro to transform a [T; n] into a (T, ..., T) (with the right #[repr]) so that statically disjoint mutable access is possible, only to change ot back afterwards to enable array-only patterns like iteration.

Another option/pattern would be to have the following method for [T; n] forall n [e.g. n=3]:

fn with_tuple_view<R> (
    self: &mut [T; 3],
    f: impl FnOnce(&mut (T, T, T)) -> R,
) -> R;

The idea being that, provided that transmuting &mut [T; 3] into a &mut (T, T, T) is sound (I don’t know the #[repr] stuff or what the compiler constraints are regarding tuples (c.f. above comment about tuple’s order being not having to be well-defined (e.g. when optimizing for size))), this API would allow to do such cheap transmutation wihtin a safe API that allows to go back and forth.

EDIT: forall n being, at the moment, something like forall n < 128, since we don’t yet have type-level integers

2 Likes

I think that we could guarantee that the representation of homogeneous tuples will have the same representation as arrays of the same length.

@RustyYato For discussion of such things, see

1 Like

Transmuting between (T, T, T) and [T; 3] is an entirely reasonable thing to want, however it requires the compiler to provide guarantees about the layout of tuples. AFAIK rustc has intentionally not committed to anything in order to be future evolution proof in the light of backwards and forwards compatibility.

But to be honest, I’d much rather see some new ABI guarantees (that’s extremely useful for FFI too!) and the transmute when a piece of code does weird/sneaky things, rather than making it a commodity tool. Tuples and arrays are very different on the highest, conceptual level, and conflating them (worse yet, making it easier to conflate them) would be a step back.

1 Like

What about the following PoC featuring:

fn main ()
{
    let mut array: [u8; 3] = [0, 1, 2];
    {
        let tuple = array.as_tuple_mut();
        ::std::mem::swap(
            &mut tuple._0,
            &mut tuple._2,
        );
    }
    assert_eq!(array, [2, 1, 0]);
}
2 Likes

Wow, now that’s what I call effort, I hope you generated all those impls programmatically :smile: In all seriousness, that is really nice.

I don’t feel qualified to judge whether it’s safe after just skimming through the code, but yeah, basically if this is safe, then it can be done. And again, I would be totally fine even with compiler magic permitting transmute between [T; n] and (T, T, …, T) too – because they are both built-in types.

(My point wrt. semantics was only that it should require some sort of a conversion, not too painful, just something visible, and the two types shouldn’t just act identically. I like to be able to tell based on indexing/field access which type I’m dealing with.)

That's exactly what I meant : instead of "allow[ing] .0, .1, etc on arrays", we could simply allow a (safe) cast-transmute between arrays and tuples to switch between tuple/struct-specific logic (statically disjoint indexing) and array logic (iteration)

Aside:

meta ^^ I've grown quite fond of code-generating macros (only when needed ofc, cough type-level (generic) integers cough), and the funny thing, with examples such as the above, is that the argument fed to the macro is also programmatically generated (usually w/ Python). So, I ended up writing (Python) code that generates input for a (rust) macro that generates rust code (impls)

1 Like

That's technically UB right now. It's not certain that it'll ever work officially either, since there's at least one proposal (Layout of homogeneous structs · Issue #36 · rust-lang/unsafe-code-guidelines · GitHub) in which it'd be unsound.

1 Like

Thanks for the info, I thought of some potential inner padding leading to UB (hence my size sanity checks with ::std::mem::transmute), but I didn’t think that a homogeneous tuple could have a different alignment than an array.

It should then be doable using a union, but we will then need a Union with non Copy types (unstable #[feature(untagged_unions)]).

In the meantime, I have been adding static guards against given sizes for T and those guards are never failing (not even for alignment): https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4e50997e0548456f61c793cb8b84d3b3

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