Zig: Allow declarations in @Type for Struct/Union/Enum/Opaque

Created on 17 Oct 2020  Â·  2Comments  Â·  Source: ziglang/zig

The compiler currently errors when the .decls slice is non-empty.

More context at https://github.com/ziglang/zig/issues/383.

proposal

Most helpful comment

One (imho strong) argument to allow this is that we can make really convenient interface implementations:

// Definition of an interface:
const Allocator = std.meta.Interface(struct {
    pub const Error = error{OutOfMemory};
    alloc: fn (self: *std.meta.Self, len: usize) Error![]u8,
    free: fn(self: *std.meta.Self, ptr: []u8) void,
});

// Usage is just taking the interface type, it's a "fat pointer":
fn process(allocator: Allocator, items: []const u8) !void {
    // Just call functions on the interface like on normal objects.
    // this is provided via created functions from .decls
    const buf = try allocator.alloc(items.len);
    defer allocator.free(buf);

    …
}

// Implementing interfaces is still duck typing:
const FixedBufferAllocator = struct {
    const Self = @This();

    buffer: []u8,
    allocated: bool = false,

    pub fn init(buf: []u8) Self {
        return Self { .buffer = buf };
    }

                                          // access public symbols of the struct we pass to std.meta.Interface
                                          // as we can usingnamespace them
    pub fn alloc(self: *Self, size: usize) Allocator.Error!void {
        if(self.allocated)
            return error.OutOfMemory;
        if(size > self.buffer.len)
            return error.OutOfMemory;
        self.allocated = true;
        return self.buffer[0..size];
    }

    pub fn free(self: *Self, ptr: []u8) void {
        std.debug.assert(ptr.ptr == self.buffer.ptr);
        self.allocated = false;
    }
};

fn main() !void {
    var fba = FixedBufferAllocator.init(&some_array);

    // Interface().get(ptr) will duck-type match the interface into a fat pointer
    try process(Allocator.get(&fba), "Hello, Zig");
}

To make the convenient function calls like allocator.free(…) we need TypeInfo.Struct.decls.

The neat thing is: The example code above does feel native, like if interfaces would be a part of the language. But they are just a clever use of comptime. This would kinda also close #130 as "implemented"

All 2 comments

One (imho strong) argument to allow this is that we can make really convenient interface implementations:

// Definition of an interface:
const Allocator = std.meta.Interface(struct {
    pub const Error = error{OutOfMemory};
    alloc: fn (self: *std.meta.Self, len: usize) Error![]u8,
    free: fn(self: *std.meta.Self, ptr: []u8) void,
});

// Usage is just taking the interface type, it's a "fat pointer":
fn process(allocator: Allocator, items: []const u8) !void {
    // Just call functions on the interface like on normal objects.
    // this is provided via created functions from .decls
    const buf = try allocator.alloc(items.len);
    defer allocator.free(buf);

    …
}

// Implementing interfaces is still duck typing:
const FixedBufferAllocator = struct {
    const Self = @This();

    buffer: []u8,
    allocated: bool = false,

    pub fn init(buf: []u8) Self {
        return Self { .buffer = buf };
    }

                                          // access public symbols of the struct we pass to std.meta.Interface
                                          // as we can usingnamespace them
    pub fn alloc(self: *Self, size: usize) Allocator.Error!void {
        if(self.allocated)
            return error.OutOfMemory;
        if(size > self.buffer.len)
            return error.OutOfMemory;
        self.allocated = true;
        return self.buffer[0..size];
    }

    pub fn free(self: *Self, ptr: []u8) void {
        std.debug.assert(ptr.ptr == self.buffer.ptr);
        self.allocated = false;
    }
};

fn main() !void {
    var fba = FixedBufferAllocator.init(&some_array);

    // Interface().get(ptr) will duck-type match the interface into a fat pointer
    try process(Allocator.get(&fba), "Hello, Zig");
}

To make the convenient function calls like allocator.free(…) we need TypeInfo.Struct.decls.

The neat thing is: The example code above does feel native, like if interfaces would be a part of the language. But they are just a clever use of comptime. This would kinda also close #130 as "implemented"

A minor usecase - when translate-c can't handle something I make a wrapper function instead:

[nix-shell:~/bluetron/blinky]$ cat wrapper.c
#include "stdbool.h"
#include "stdint.h"
#include "nrf_delay.h"
#include "boards.h"

void bluetron_nrf_delay_ms(uint32_t ms_time)
{
  nrf_delay_ms(ms_time);
}

It would be nice to be able to wrap my @cImport to automatically rename bluetron_foo to foo, replacing the original declaration.

This is kind of similar to --wrap=foo in ld.

Was this page helpful?
0 / 5 - 0 ratings