Skip to content
Chung Leong edited this page Apr 12, 2024 · 4 revisions

An opaque is a notional type in the Zig language. It's used to create pointers that point to "mystery" data, whose meaning is only known to the code that created them.

const std = @import("std");

const Context = struct {
    number1: i32,
    number2: i32,
    number3: i32,
};
const OpaquePtr = *align(@alignOf(Context)) opaque {};

pub fn startContext(allocator: std.mem.Allocator) !OpaquePtr {
    const ctx = try allocator.create(Context);
    ctx.* = .{ .number1 = 10, .number2 = 20, .number3 = 30 };
    return @ptrCast(ctx);
}

pub fn showContext(opaque_ptr: OpaquePtr) void {
    const ctx: *Context = @ptrCast(opaque_ptr);
    std.debug.print("{any}\n", .{ctx.*});
}
import { showContext, startContext } from './opaque-example-1.zig';

const ctx = startContext();
console.log(ctx.valueOf());
showContext(ctx);
{}
opaque-example-1.Context{ .number1 = 10, .number2 = 20, .number3 = 30 }

Potential pitfall

The example above uses an allocator provided by Zigar, which obtains memory from the JavaScript engine. This memory is garbage-collected. It also gets moved around (when a buffer graduates from V8's young-generation heap to its old-generation heap, for instance). You cannot place pointers to this relocatable memory into a struct then hide its implementation behind an opaque pointer. Zigar must be aware of their existance in order to update them.

The following code demonstrates the problem:

const std = @import("std");

const Context = struct {
    string: []const u8,
};
const OpaquePtr = *align(@alignOf(Context)) opaque {};

pub fn startContext(allocator: std.mem.Allocator) !OpaquePtr {
    const ctx = try allocator.create(Context);
    ctx.string = try allocator.dupe(u8, "This is a test");
    return @ptrCast(ctx);
}

pub fn showContext(opaque_ptr: OpaquePtr) void {
    const ctx: *Context = @ptrCast(opaque_ptr);
    std.debug.print("{s}\n", .{ctx.string});
}
import { showContext, startContext } from './opaque-example-2.zig';

const ctx = startContext();
gc(); // force immediate garbage collection; requires --expose-gc
showContext(ctx);
�B,��Ʊ/4

Without the call to gc, the code would actually produce the expected output. As garbage collection happens at unpredictable intervals, this could lead to the most insideous of bugs--ones that only pop up on rare occasions.

You need to use your own allocator if you're going to keep pointers hidden behind an opaque pointer. You'll also need to provide a clean-up function.

Clone this wiki locally