← Back to context

Comment by srcreigh

5 days ago

It's just stored as a [256]u8 in the struct.

  // sqlite.zig
  pub const ErrorPayload = struct {
      message: [256]u8,
  
      pub fn init(db: *c.sqlite3) @This() {
          var self = std.mem.zeroes(@This());
          var fw = std.Io.Writer.fixed(self.message[0..]);
          _ = fw.writeAll(std.mem.span(c.sqlite3_errmsg(db))) catch |err| switch (err) {
              error.WriteFailed => return self, // full
          };
          return self;
      }
  };

And what pattern would you recommend if you needed to allocate?

  • It's worth every effort to avoid situations where a function creates extra clean up responsibilities to the caller only on error conditions.

    If I really needed a large error payload, too big to fit on the stack, I'd probably want to do something like this:

      const errbuf = try alloc.alloc(u8, 1024*1024*1024);
      module.setErrorBuf(&errbuf)
      defer {
        module.setErrorBuf(null);
        alloc.free(errbuf);
      }
    
      var func_diag = diagnostics.OfFunction(module.func){};
      module.func(foo, bar, &func_diag) catch |err| switch (err) {
        error.BigPayload => {
          const payload = func_diag.get(error.BigPayload);
    
          // The payload can reference the 1MiB of data safely here,
          // and it's cleaned up automatically.
        }
      }

  • The diagnostic struct could contain a caller-provided allocator field which the callee can use, and a deinit() function on the diagnostic struct which frees everything.