Skip to content

Instantly share code, notes, and snippets.

@andrewrk
Last active December 15, 2021 21:09
Show Gist options
  • Save andrewrk/190170bc1441839644c3f15725a22096 to your computer and use it in GitHub Desktop.
Save andrewrk/190170bc1441839644c3f15725a22096 to your computer and use it in GitHub Desktop.
Haiku dev talking about RAII in C++ in relation to Zig
[14:41:12] <waddlesplash> Zig seems cool but you don't seem to be about the RAII that we love so much :-p
[14:47:25] <andrewrk> anyway I won't come in here trying to convince anyone to switch languages or coding styles :)
[14:47:44] <waddlesplash> in Haiku ... C++ virtual inheritance and RAII objects mean that the code can much more clearly communicate what it is trying to do at the same time it is actually doing it
[14:48:02] <andrewrk> I do think that there is a misunderstanding that we don't care about RAII principles in zig though. I mean yeah you have to manually `defer cleanup();` but the coding model is the same
[14:48:15] <waddlesplash> but what happens if you decide you don't want to clean something up?
[14:48:21] <waddlesplash> for instance we have a very common paradigm in Haiku:
[14:49:35] <waddlesplash> thing* whatever = allocate_memory_or_something();
[14:49:35] <waddlesplash> MemoryDeleter<thing> whateverDeleter(whatever);
[14:49:36] <waddlesplash> if (error) return error; /* whatever is deleted */
[14:49:36] <waddlesplash> whateverDeleter.Detach(); return ENOERR; /* success, we want to keep whatever */
[14:50:10] <waddlesplash> you can't undo a "defer cleanup", but you can tell a RAII object that you actually want to keep the thing
[14:50:48] <waddlesplash> and yes, moving unique_ptrs would handle this case ... but only in code we manage. we can use this paradigm even when e.g. implementing POSIX functions that must return raw pointers
[14:51:31] <waddlesplash> similarly, in core kernel code, we may need to lock and unlock some lock a lot of times, so we don't want to just "defer unlock", we want a MutexLocker object that will keep track of whether the thing is locked or unlocked, and act appropriately on "return error"
[14:54:34] <andrewrk> waddlesplash, the pattern looks pretty identical in zig: https://clbin.com/Bv3zx
[14:55:17] <waddlesplash> ah, so you can defer object calls, I guess that is indeed equivalent then
[14:55:36] <andrewrk> yeah it's an extremely useful pattern, which I'm sure I don't need to convince you of
[14:55:59] <waddlesplash> zig doesn't have "templates" though, in the C++ sense, does it?
[14:56:20] <andrewrk> it's effectively the same thing
[14:56:33] <andrewrk> in zig it's parameters that are required to be compile-time known
[14:56:52] <andrewrk> and then any if/switch expressions whose conditions are compile-time known are flattened out, dead branches are not even analyzed
[14:56:54] <waddlesplash> well, last week for example I wrote some code for our ntfs driver that uses libntfs-3g, which is C
[14:57:05] <waddlesplash> and I had to open and close a lot of I nodes, so I wrote...
[14:57:06] <waddlesplash> typedef CObjectDeleter<ntfs_inode, int, ntfs_inode_close> NtfsInodeCloser;
[14:57:06] <nephele> comptime seems like a major advantage zig has, though :)
[14:57:15] <andrewrk> if you show me a simple example code I can show equivalent zig if you're curious
[14:58:05] <waddlesplash> andrewrk: well, the above template for instance allows me to specify an arbitrary function as a template argument, as well as what type it accepts and what return type it has
[14:58:22] <waddlesplash> and now I've got a new "deleter" type that I can liberally sprinkle through the code
[14:58:53] <andrewrk> yeah that is directly translateable to zig, although you might make some people squint their eyes and accuse you of unnecessary abstraction
[14:59:32] <waddlesplash> well, we have quite a lot of these typedefs, because we can and do create them when interacting with random C libraries :)
[15:03:44] <andrewrk> I'm not sure what the types or usage of CObjectDeleter is, but if I guessed correctly it would look something like this: https://clbin.com/EeKTz
[15:04:07] <waddlesplash> yep, that's about how it works
[15:04:11] <waddlesplash> some more nuance, but not a lot
[15:04:45] <waddlesplash> well, if zig had virtual inheritance or an equivalent thereof, maybe it'd be something we'd consider :-p
[15:05:03] <waddlesplash> or maybe if you add support for language extensions or something, someone will invent "Object Zig" :D
[15:05:03] <andrewrk> hahaha well I'm only here to ask for help reviewing a haiku PR, not to try to convince anyone to use zig :)
const Thing = struct {
detached: bool = false,
fn deinit(self: *Thing) void {
if (!self.detached) {
doTheCleanup();
}
}
fn consume(self: *Thing) void {
self.detached = true;
}
};
test "usage code" {
var thing = functionThatReturnsAThing();
defer thing.deinit();
// ...
thing.consume();
}
fn CObjectDeleter(comptime initFn: anytype, comptime T: type, comptime cleanupFn: anytype) type {
return struct {
state: T,
const Self = @This();
fn init(state: T) !Self {
return Self{
.state = try initFn(),
};
}
fn deinit(self: *Self) void {
cleanupFn(self.state);
}
};
}
test "example usage" {
var c_object_deleter = try CObjectDeleter(ntfs_inode, int, ntfs_inode_close).init(1234);
defer c_object_deleter.deinit();
// ...
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment