In place initialization of an optional stuct

?struct { std.compress.flate.Decompress, [256]u8, Io.Reader.Limited, [256]u8 } I want to initialize this struct in place, and it is self referential so I can’t make a temporary value. Is it possible to get a pointer to the non-null value?

i’m not sure i totally follow, but the following appears to run ok

const std = @import("std");

const T = struct {
    opt_field: ?struct {
        decompress: std.compress.flate.Decompress,
        decompress_buff: [1024 * 1024]u8,
        reader: std.Io.Reader.Limited,
        reader_buff: [256]u8,
    },
};

test "it works fine" {
    const instance = try std.testing.allocator.create(T);
    defer std.testing.allocator.destroy(instance);

    var unlimited: std.Io.Reader = .failing;

    instance.* = .{ .opt_field = null };
    instance.opt_field = .{
        .reader_buff = undefined,
        .reader = .init(&unlimited, .limited(256), &instance.opt_field.?.reader_buff),
        .decompress_buff = undefined,
        .decompress = .init(&instance.opt_field.?.reader.interface, .raw, &instance.opt_field.?.decompress_buff),
    };
    _ = instance.opt_field.?;
}

Thanks for you help

no need for a wrapper struct, nor heap allocation

const T = struct {
    decompress: std.compress.flate.Decompress,
    decompress_buff: [1024 * 1024]u8,
    reader: std.Io.Reader.Limited,
    reader_buff: [256]u8,
};

test "it works fine" {
    var unlimited: std.Io.Reader = .failing;

    var instance: ?T = null;
    instance = .{
        .reader_buff = undefined,
        .reader = .init(&unlimited, .limited(256), &instance.?.reader_buff),
        .decompress_buff = undefined,
        .decompress = .init(&instance.?.reader.interface, .raw, &instance.?.decompress_buff),
    };
    _ = instance.?;
    std.debug.print("{any}", .{instance});
}

the trick is just to get the memory then initialise in seperate statements, that way you can refer to the memory during initialisation.

but I doubt you need a self referential type in the first place. Just making each field its on variable simplifies this a lot, and if you need to you can bundle pointers to them up in a struct after the fact.

2 Likes

There is no need to have an additional struct as in alanza’s answer, but you can still have the optionality in the type (and not in the instance as in that of vulpesx’ ) :

const std = @import("std");

const T = ?struct {
    decompress: std.compress.flate.Decompress,
    decompress_buffer: [1024 * 1024]u8,
    reader: std.Io.Reader.Limited,
    reader_buffer: [256]u8,
};

test "it works fine" {
    var unlimited: std.Io.Reader = .failing;

    var instance: T = null;
    instance = .{
        .reader_buffer = undefined,
        .reader = .init(&unlimited, .limited(256), &instance.?.reader_buffer),
        .decompress_buffer = undefined,
        .decompress = .init(&instance.?.reader.interface, .raw, &instance.?.decompress_buffer),
    };
    _ = instance.?;
    std.debug.print("{any}\n", .{instance});
}

What is unclear to me is why you want this to be optional.

Because of alignment the optionality adds 8 (eight) bytes to the size of instance (admittedly not that much more if you have a 1Mb decompression buffer). If that is because you want to keep track of initialisation, and/or clearing, of the struct, you may want to consider using additional (boolean) struct members instead:

const T1 = struct {
    decompress: std.compress.flate.Decompress,
    decompress_buffer: [1024 * 1024]u8,
    reader: std.Io.Reader.Limited,
    reader_buffer: [256]u8,
};

const T2 = struct {
    initialised: bool,
    used: bool,
    cleared: bool,
    decompress: std.compress.flate.Decompress,
    decompress_buffer: [1024 * 1024]u8,
    reader: std.Io.Reader.Limited,
    reader_buffer: [256]u8,
};

test "compare sizes" {
    try std.testing.expect(@sizeOf(T) == @sizeOf(T1) + 8);
    try std.testing.expect(@sizeOf(T) == @sizeOf(T2));
}

This gives you multiple testable options with the same 8 bytes overhead.