Initializing const array of structs with sparse runtime information

As the title. I’ve got an array of structs with these properties:

  1. Array has a compile time defined max size.
  2. Content of the array is only known at runtime.
  3. Once filled, the content won’t change.
  4. Typically only a tiny part of the possible max size is used (2-5%).

So far what I’ve come up with is something like this:

fn maybeGetStruct() ?complex_struct {
    //complicated thing
}

fn fillArray() []complex_struct {
    var tmp: [max_size]complex_struct = .{undefined} ** max_size;

    var count: usize = 0;
    for(0..max_size)|i|{
        tmp[count] = maybeGetStruct() orelse continue;
        count += 1;
    }

    return tmp[0..count];
}

Is there a better / more idiomatic way to do this?

Instead of a function you can use a block, but it’s more a matter of preference what you do.

const arr = init: {
  var tmp: [max_size]T = .{undefined} ** max_size;
  break :init tmp;
};

In your example you’re returning a slice to stack allocated memory. Instead, just return the entire array. Also you’re leaving some elements uninitialized. Maybe change the type to [max_size]?complex_struct :slight_smile:

Thank you for the reply.
I don’t think I explained the issue well enough, though.
Sorry about that. :sweat_smile:

you’re leaving some elements uninitialized. Maybe change the type to [max_size]?complex_struct :slight_smile:

I’m not trying to initialize an array of maybe-null values, I want to filter out the null values from something else that is mostly-null, leaving me with a much smaller array without null/undefined values.

I think the orelse continue; clause in the assignment should do that, since count isn’t incremented if maybeGetStruct() returns null?

If I’m wrong about that I think I may have misunderstood how orelse works.

Maybe you can try out a BoundedArray?

It sounds like what you’re describing. You give it some max size at comptime, then initialize it with some (potentially smaller) size at runtime.

3 Likes

The issue how you used orelse continue was just that you skipped initializing some items without storing which.
For example the function could return [a, b, undefined, c] and the consumer would have no way to know which items are bad data.

I’m assuming you only know how many items are null and need to be filtered at runtime - this means the size of the filtered array can only be known at runtime as well.

The easiest would be to allocate whatever space you need on the heap of course but if you don’t want to do that and the max_size isn’t too big, you could return a partially initialized array and a size of initialized values.

fn filterNonNull() struct { [max_size]T, usize } {
  var tmp: [max_size]T = .{undefined} ** max_size;
  var size: usize = 0;

  for(tmp) |*elem {
    elem.* = maybeGetStruct() orelse continue;
    size += 1;
  }

  return .{ tmp, size };
}

// const a, const size = filterNonNull();
// const b = a[0..size];

… Or just use a BoundedArray like @sea-grass suggested, that’s even better

3 Likes

I think you missed that the loop variable and the array index aren’t the same variable.
I don’t need to store which are null if I’m only advancing the indexing variable when I store non-null things, which I am.

If max_size = 4 and maybeGetStruct would return [a, b, null, d] when looped over, they’d get loaded into the array as [a, b, d, undefined]. The final value of count is 3 and tmp[0..3] is [a, b, d].

Either way, I marked this solved. Something like BoundedArray was what I was looking for. Thank you both for the help. :grin:

1 Like