Open alexrp opened 2 months ago
Disclaimer: I have no idea what happens if you're not making an ELF file.
It is possible to register code to run before/after main (including if std.process.exit()
is called, but not if a panic occurs, or it exits due to an unhandled signal) when linking libc by placing an array of function pointers into the .init_array
and .fini_array
sections. I'm not sure if this is exactly equivalent to the constructor
/destructor
but it may work for you.
Here's a minimal example:
pub fn main() !void {
try std.io.getStdOut().writeAll("this is main\n");
std.process.exit(2);
}
fn constructor1() callconv(.C) void {
std.io.getStdOut().writeAll("this is constructor1\n") catch @panic("constructor1 failed");
}
fn constructor2() callconv(.C) void {
std.io.getStdOut().writeAll("this is constructor2\n") catch @panic("constructor2 failed");
}
fn destructor() callconv(.C) void {
std.io.getStdOut().writeAll("this is destructor\n") catch @panic("destructor failed");
}
export const init_array: [2]*const fn () callconv(.C) void linksection(".init_array") = .{&constructor1, &constructor2};
export const fini_array: [1]*const fn () callconv(.C) void linksection(".fini_array") = .{&destructor};
const std = @import("std");
Which produces the following:
> zig run init_fini.zig -lc
this is constructor1
this is constructor2
this is main
this is destructor
I think this is basically how the constructor
/destructor
attributes work though someone could correct me.
I think this is basically how the
constructor
/destructor
attributes work though someone could correct me.
I believe you're right.
The thing is, I compile this library for Windows, macOS, and Linux, so doing this for each OS would get a bit hairy. I think it's also tricky in the more general case because the linker is supposed to have "append semantics" (if memory serves) for these arrays. It's not clear how that interacts with Zig's language semantics.
(Note: This is not a proposal at this stage; I know there's little desire for language proposals at this time. I just wanted to indicate that this is a use case where pure Zig is currently unable to replace C. This can evolve into a concrete proposal later.)
I have a C# library that effectively replaces
System.Console
and instead centers console interaction around a terminal (i.e. VT100+). Among other concerns, this requires some platform-specific code to configure the terminal -termios
on Unix, Win32 console on Windows - and to restore its configuration on process exit.That last part turns out to be quite tricky. It's basically not possible to do reliably from C#. So I ended up writing a dynamically-loaded helper library in C that abstracts away the platform-specific bits and also performs the terminal configuration. Here's how configuration restoration looks, using the GCC/Clang
__attribute__((destructor))
feature:https://github.com/vezel-dev/cathode/blob/6bd76d8836c1988d96c58283de037de63f5e8ab9/src/native/driver-unix.c#L39-L46
I'm compiling that library with
zig cc
right now, but I'd like to take the extra step and rewrite it in Zig. Unfortunately, it doesn't seem like Zig has the ability to express theconstructor
/destructor
attributes right now.To my knowledge, there is no other feature (whether it's
atexit()
or what have you) that is as reliable at running cleanup code as__attribute__((destructor))
. And it really is important that this code runs: If the terminal is configured in raw mode and the C# application exits abnormally due to an unhandled exception, the terminal could be left in an unusable state if this destructor doesn't run.