Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use case: struct with embedded resizable last field #173

Closed
andrewrk opened this issue Aug 22, 2016 · 10 comments
Closed

use case: struct with embedded resizable last field #173

andrewrk opened this issue Aug 22, 2016 · 10 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@andrewrk
Copy link
Member

In C you can do something like this:

struct Buffer {
    size_t capacity;
    size_t length;
    unsigned char data[];
};

Here, instead of having data be a pointer in the struct, the struct itself is resized, along with data. The data is part of the struct; the data field points to the beginning.

Zig does not currently have a convenient way to represent this concept. How you would do this in status quo land is:

struct Buffer {
    capacity: usize,
    length: usize,
    data: u8,
}

fn getBuffer(wanted_data_len: usize) -> &Buffer {
    const ptr = (%%mem.allocate(u8, @sizeOf(Buffer) - 1 + wanted_data_len)).ptr;
    return (&Buffer)(ptr);
}

fn getSubString(buf: &Buffer, start: usize, end: usize) -> []u8 {
    assert(start <= buf.length);
    assert(end <= buf.length);
    (&buf.data)[start...end]
}

I guess that's not so bad. We could leave status quo as is.

@andrewrk andrewrk added the enhancement Solving this issue will likely involve adding new logic or components to the codebase. label Aug 22, 2016
@andrewrk andrewrk added this to the 0.1.0 milestone Aug 22, 2016
@felipecrv
Copy link

felipecrv commented Sep 3, 2016

Don't overlook the case where you have two (n) arbitrarily sized buffers

struct Model {
   size_t len;
   Vec3 *vertices;
   Vec2 *uv;
}

It would still be nice to allocate it all at once.

This could be solved by passing the sizes in the type declaration just like we do for C arrays (int arr[10]; // sizeof(arr) = 40):

struct Model<static_len> {
  size_t len;
  Vec3 vertices[static_len];
  Vec2 uv[static_len];
}

// You would store the data at the end of the struct and the two pointers to the data

sizeof(Model<10>)  // sizeof(size_t) + 10 * sizeof(Vec3) + 10 * sizeof(Vec2) + sizeof(*Vec3) + sizeof(*Vec2)

They would work like C arrays

struct Model *model = malloc(sizeof(Model<10>));    // analogous to malloc(sizeof(int[10]))
// TODO: figure out how to set the pointers automatically
model->len = 10;

sizeof(*model);  // = sizeof(size_t) + sizeof(*Vec3) + sizeof(*Vec2)
                           // It's not the size of the entire thing just like the size of *int_ptr_to_array
                           // is the size of a single int

// You could have them on the stack
Model<10> model;

And since you store the actual pointers in the struct, you can pass them to other functions

foo(&model);

void foo(Model *model) {
   for (int i = 0; i < model->len; i++) {
      // vertices and uv are pointers inside the struct
      model->vertices[i]; 
      model->uv[i]; 
   }
}

You could afford to store only one pointer here as you at least know where the first buffer starts. In general you would need n - 1 buffers to store n parameterized buffers in the struct.

@andrewrk andrewrk modified the milestones: 0.1.0, 0.2.0 Mar 26, 2017
@kyle-github
Copy link

As Philix says, it is nice to allocate it all at once. However, I find that the extra gymnastics I do to figure out how big to allocate a "variable length" struct in one memory block are worth it for the other end of the lifespan: freeing the memory. I only have one free(). I do not need to know much of anything about the memory, or the type if it is all one block.

Now, I can wrap allocation and deallocation in functions per struct type and call those specifically. That works too (and is probably not a bad thing).

You can still do the variable length structure as one block even if you do not have the empty array at the end. Instead, have the last part be a pointer and point it just past the rest of the object as if it was a separate allocation. But the case with the end element being a zero length array is handy...

@andrewrk andrewrk modified the milestones: 0.2.0, 0.3.0 Oct 20, 2017
@andrewrk andrewrk modified the milestones: 0.3.0, 0.4.0 Feb 28, 2018
@andrewrk andrewrk modified the milestones: 0.4.0, 0.5.0 Feb 3, 2019
@daurnimator
Copy link
Collaborator

How you would do this in status quo land is:

struct Buffer {
    capacity: usize,
    length: usize,
    data: u8,
}

I don't think this is correct: zig gives no assurances on struct layout, and may reorder fields.

@andrewrk
Copy link
Member Author

You are correct. I believe when I originally made that statement struct layout had defined order, but as you note that is no longer the case.

@emekoi
Copy link
Contributor

emekoi commented Apr 28, 2019

this seems to be quite old, did zig ever give guarantees on struct layout?

@tgschultz
Copy link
Contributor

tgschultz commented Apr 28, 2019

simply adding packed to the struct definitions should make the original example still apply... well, that and all the other changes necessary to update the code.

@daurnimator
Copy link
Collaborator

daurnimator commented May 4, 2019

I'm currently using this:

const Block = struct {
    nbytes: usize,
    bytep: *u8,
    fn bytes(self: *Block) [*]u8 {
        return @ptrCast([*]u8, self) + @sizeOf(self); // assumes @alignOf(u8) < @alignOf(self)
    }
};

Allocating one is a bit weird...

const blk = @ptrCast(*Block, try allocator.alignedAlloc(u8, @alignOf(Block), @sizeOf(Block) + len));
blk.* = Block{
    .nbytes = nbytes,
    .bytep = bytep,
};

(and it gets even weirder when I use the VLA as a linkedlist node type)

The @sizeOf call in this code should really be the actual structure size in bytes (not the size in an array); or you end up forcing the variable size element to the alignment of the struct itself.

@andrewrk andrewrk modified the milestones: 0.5.0, 0.6.0 Sep 11, 2019
@andrewrk andrewrk modified the milestones: 0.6.0, 0.7.0 Dec 9, 2019
@andrewrk andrewrk added proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. and removed enhancement Solving this issue will likely involve adding new logic or components to the codebase. labels Oct 9, 2020
@andrewrk andrewrk closed this as completed Oct 9, 2020
@andrewrk
Copy link
Member Author

andrewrk commented Oct 9, 2020

There is no plan to add an explicit language feature for this.

@daurnimator
Copy link
Collaborator

There is no plan to add an explicit language feature for this.

Is there at least a plan for how to conventionally represent them? note that translate-c will need to handle them.

@dsimunic
Copy link

dsimunic commented Jan 1, 2021

One way for translate-c would be to represent a struct as a comptime function that forces one to set the explicit size of the array.

A c struct like this:

typedef struct FunctionCallInfoBaseData
{
 short nargs;
 NullableDatum args[];
} FunctionCallInfoBaseData;

Would translate to:

fn struct_FunctionCallInfoBaseData(comptime args_len: c_ushort) type {
    return extern struct {
        nargs: c_ushort,
        args: [args_len]NullableDatum,
    };
}

All references would get translated as calls with the array length parameter set to 0, making the build fail for any access and forcing the user to review all uses (or alternatively would default to maxInt to allow build without review):

pub export fn add_one(fcinfo: *struct_FunctionCallInfoBaseData(0)) void {
    // This fails the build with 'accessing a zero length array is not allowed',
    // forcing the programmer to review the code
    _ = fcinfo.args[0].value;
}

This works well for cases when one accesses the array explicitly like in the above example.

For cases when one has to rely on the length passed at runtime (nargs value), one can set args_len to maxInt(usize) to pass the compile check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

7 participants