Defining new variables programmatically in inline for loops and extending the @ syntax

Is there a way to define variables programmatically in an inline for loop where the number of such variables is known at comptime? The use case is inlining a for loop with intermediate variables that have different types. Since I can’t redefine variables with the same name and different type, I am thinking I could maybe define a different variable in each iteration. If this is not currently possible, would it be reasonable to extend the @"xyz" syntax to support comptime strings?

Can you provide some pseudo code so we can see exactly what you’re talking about?

1 Like

Ideally, I would like to do something like this but for a comptime known number of times.

const a1 = .{};
const a2 = a1 ++ .{1};
const a3 = a2 ++ .{2};
const a4 = a3 ++ .{3};
...

Show us more code so we can suggest something helpful, this is confusing, we need more context.

Without seeing more, I am thinking you may want to build up a custom struct type using comptime and then just use a single variable of that struct type in your loop, accessing its various fields.

I am not sure what more code to show. The above comment shows the pattern I would like to produce at compile time. My “naive” attempt is shown below.

fn loop_n(comptime n: u8) ??? {
  var a = .{};
  inline for (0..n) |i| {
    var a = a ++ .{i};
  }
  return a;
}

It obviously doesn’t work. First, I cannot redefine a different variable a (with different type) that shadows an existing variable a. Second, it is not clear to me how to infer the output type programmatically, perhaps I can use @Type for that in cases where I can easily infer the element types.

In this particular case, I can probably come up with a better solution but I was trying to do the above pattern in Zig. In theory, it could work if either we have a way to programmatically change the names of the variables a inside the loop in every iteration or if shadowing was a thing in Zig.

@Type to infer the output type programmatically and inline recursion might be my best bet instead of an inline for loop.

You aren’t explaining at all what you are actually trying to do, only how you want to do it.
Do you want to create an array or a slice for some type, build a bigger tuple from smaller ones, etc.?
How are you going to use this? / Where is it called?
Why do you need it?

All these things are valuable to know, else there are many different functions that could be written here that produce something, that may not be what you want, for example:

const std = @import("std");

fn buildString(comptime n: u8) []const u8 {
    comptime var a: []const u8 = "";
    inline for (0..n) |i| {
        const last = i + 1 == n;
        const sep = if (last) "" else ", ";
        a = a ++ std.fmt.comptimePrint("{d}{s}", .{ i, sep });
    }
    return a;
}

fn buildArray(comptime n: u8) [n]u8 {
    comptime var a: [n]u8 = undefined;
    inline for (0..n) |i| {
        a[i] = i;
    }
    return a;
}

fn BuildTuple(comptime n: u8) type {
    const types_array = [1]type{u8} ** n;
    return std.meta.Tuple(&types_array);
}

fn buildTuple(comptime n: u8) BuildTuple(n) {
    var a: BuildTuple(n) = undefined;
    inline for (0..n) |i| {
        a[i] = i;
    }
    return a;
}

pub fn main() !void {
    std.debug.print("build string: '{s}'\n", .{buildString(10)});
    std.debug.print("build array: {any}\n", .{buildArray(10)});
    std.debug.print("build tuple: {}\n", .{buildTuple(10)});
}

Output:

build string: '0, 1, 2, 3, 4, 5, 6, 7, 8, 9'
build array: { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
build tuple: { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }

Maybe you are looking for @field(T, field_name)?

1 Like

Thanks for the code suggestions. I will study them. I am really not trying to do anything in particular, I was just curious how to do the above pattern or if it was at all possible. I believe recursion is what I might try next.

I believe what you’re looking for is something that goes like this…

You have a value x of type T and you want to append to it more type-level information. So then, you try to use x in an assignment back to itself. The problem is, the type of the value you want to assign from has changed and is no-longer the same type T. So a simple, strongly typed variable cannot represent this information.

You have a few options here - one is to deduce what the variable is supposed to be off-site and then assign it into place. This is a bit ugly to do and will send you down the road to recursion.

Another possibility is to use dynamic types that deploy erasure. This a small step closer to something like python, actually. The problem in this case is… once you have your variable, how do you use it? There’s a possible workaround here but I’d have to play around with some examples… I happen to have an eraser that may do the trick but I’ll report on that once I’ve done some experimenting.

In general, this type of thing is hard to do in any language that requires variables to have a single type.

2 Likes

I am trying to avoid dynamism but it would still be interesting to learn about that solution.

Why might someone prefer having a tuple of the same types (meaning the buildTuple with u8) instead of simply having an array of them?

I don’t know, maybe in a context where somebody already uses tuples for something and wants to add to that tuple in some compile time function.

I only wrote it here to illustrate the point, that there are many different possibilities here, because the original code used syntax that could mean lots of things, based on concrete types and details.

In most cases, just using an array is the better way.

1 Like