22 ms·
C Macro Reflection in Zig
- skywal_l 2y ago@cImport is on the chopping block though [0]. You will still be able to import c files but it will require a little more work. This is because they want this functionality out of the language so they can remove libclang dependency. [0]: https://github.com/ziglang/zig/issues/20630 https://github.com/ziglang/zig/issues/20630
- flohofwoe 2y agoIt would actually be nice if the translate-c build system step would also allow some restricted symbol transformation (at least stripping the 'namespace prefix' from C library symbols). For instance Odin allows to define a 'link_prefix' for C APIs which is then stripped from the imported symbols: @(default_calling_convention="c", link_prefix="sg_") This causes name transformations like: sg_setup() => setup() sg_shutdown() => shutdown() ...maybe even convert from common case-conventions (like snake-, camel-, pascal- case etc...) to Zig's naming convention so that imported C interfaces don't look so alien relative to native Zig interfaces.
- Cloudef 2y agoFrom the issue > As a consolation prize, the TranslateC build step can be enhanced with advanced settings, such as namespace stripping and validation of existing bindings. Since changes to this don't require changing the language, it's OK if the scope & complexity increase to some extent.
- o11c 2y ago> common case-conventions Unfortunately, we must not forget forget ABOMINATIONCase and NAMESPACED_PascalCase (we could also generalize this to any switch from one convention to another after the first word). I've found they usually are, in fact, identifiable and roundtrippable. I've found the following usually works (it fails for multi-word prefixes or non-leading exceptions, and of course single-word identifiers are ambiguous): count and strip all leading, then trailing, symbols (in some languages this is not limited to underscore but said languages usually need special handling anyway) for every chunk separated by underscore, space, or hyphen (this may be nothing): if the chunk either has no uppercase or has no lowercase, simply use it as a word. Otherwise: for every sliding-window pair of letters (c, d) in the chunk: if c is uppercase: if d is lowercase, or d is a digit and there is lowercase elsewhere: start a new word before c Then for the words-to-convention direction: space and kebab case don't have any good answer for affixes AFAIK. Otherwise, restore at least the leading underscores (trailing underscores are usually keyword-avoiding) for snake and screaming case, be sure to prepend an underscore if the *result* would start with a digit for camel variants, prepend an underscore to each *word* that starts with a digit. But if there were originally more than 1 leading underscores, use those instead for the first word.
- flohofwoe 2y agoI mean, there can always be a function on the TranslateC step which maps 'special case' names (or even translate them to an entirely different name, for instance when there's collisions with Zig reserved keywords): translateSpecialCaseNames(.{ .{ .src = "ABOMINATIONCase", .dst = "case" }, .{ .src = "NAMESPACED_PascalCase", .dst = "pascalCase" }, }); ...in my own language bindings generator, being able to define such special case mappings is actually quite important, mainly for handling that situation where an automatic mapping would result in a reserved keyword.
- o11c 2y agoReserved words really shouldn't require manual care; "list of keywords in X language" is really easy to handle so you can just append an underscore. I do think that namespaces need to be semi-manually managed though (likely only at the project level), since often there are things that look like a namespace but shouldn't be treated like one, and it's not always obvious from a single identifier how many words should be treated as the namespace in some styles. One special case is that sometimes C-ish libraries have class-likes like {Foo, FooBar, FooBaz} where the desired mapping is {foo.Foo, foo.Bar, foo.Baz}.
- jay-barronville 2y agoWhile I understand the reasoning, I think this is one of the most disappointing decisions by the Zig team. One of the main reasons I took Zig seriously was their C interop story—as someone who loves C and dislikes almost every implementation of C interop and FFI I’ve used in other languages (Rust is a notable exception to this), I was pretty much sold on Zig when I was able to, in a total of < 3-ish hours, (1) implement a complete Zig wrapper over a C library I wrote without introducing any overhead, (2) build a Zig program using the wrapper, and (3) cross-compile the program, statically, producing single binaries for 5 different targets…Linux (amd64, arm64), macOS (amd64, arm64), and Windows (amd64), ALL from one laptop using a single Zig executable and some simple flags. This was C interop and productivity I’ve never experienced before. I respect Andrew a lot (I think he’s pretty brilliant), so I hope this turns out to not be as bad as I think it’ll be for those of us who love Zig for what brings to the table for programmers who are biased toward C.
- flohofwoe 2y agoThe translate-c build system step and wrapping the output in a Zig module is currently about 10 lines in build.zig, and I guess this could be reduced further by merging those two steps into a single step. I think that's an acceptable compromise. Especially for C libraries which require configuration via preprocessor defines or compilation flags, the build.zig way is a lot cleaner than the @-builtins that are currently used (IMHO).
- Cloudef 2y agoHow I see is that the only thing that changes is that you can't do @importC anymore. You'll instead do something in build.zig that produces a module which you can then addImport("my-c-lib", generated_module); which you then @import("my-c-lib"); in your zig code as you would with @cImport. This does not seem bad in paper. One thing that does worsen with this is that in @cImport you could also comptime define preprocessor macros which was really cool, now that would have to be handled by build.zig I guess.
- jay-barronville 2y agoThis makes the experience more similar to Rust (which I don’t think is bad—it’s just not as unique, smooth, and impressive as the current Zig experience). I’ve been able to convince C programmers to try out and use Zig just due to this unique ability alone, and to be clear, getting C programmers to seriously consider any language other than C is generally very difficult! Having to consider another build system (with its own semantics), which the current Zig experience doesn’t require, changes the experience much more substantially than I think the Zig team realizes.
- ajnin 2y ago> remove libclang So `zig cc` will have to go as well ? I was under the impression Zig as a drop-in C (cross-)compiler was one of its main selling points.
- flohofwoe 2y agoSee: https://github.com/ziglang/zig/issues/16270#issuecomment-1616115039 https://github.com/ziglang/zig/issues/16270#issuecomment-161... The ability to seamlessly compile C/C++/ObjC code within a Zig project is extremely important for me as well, but I'm fine with that job being delegated to a Zig package and the Zig build system.
- ajnin 2y agoI read that commment as well but if you need to use the Zig build system it means you won't be able to integrate Zig into an existing project as easily, you'll have to integrate the Zig build system or even to integrate your project into the Zig build system, and that's an another level of complexity entirely.
- mst 2y agoThere are enough people who care about adding zig to existing projects being easy (including the zig BDFL) that I would hope that this approach won't be stabilised until they're confident that it isn't adding enough extra complexity to spoil the experience. I mean, we'll have to wait and see, and there may be a transition period during which things kinda suck, but zig has a pretty good track record here so I'm more optimistic than I would be in most cases.
- pta2002 2y agoI think for better or for worse it makes sense that this is delegated to a separate package. For me it always seemed like a weird tangential thing that Zig did that is not really related to Zig The Language, and more to Zig The Build System. It made more sense when they were depending on LLVM either way, but now that they're closer to getting rid of it, it does not make sense to keep the dependency just for something that always seemed more like a 'neat thing' than a core language feature.
- deagle50 2y agoIt seems the build system has gotten enough traction and Andrew is going for broke to enshrine its place in the C space. I wouldn't bet against him.
- samatman 2y agoIt will require a little more work in a trivial way, but not in a meaningful way. What's happening is that C imports are moving to the build system, instead of being a compiler builtin. It's part of making LLVM and libclang optional for programs which don't use it, but the use case of building C programs, and integrating C libraries with Zig programs, remains a central design goal. The build system is relatively new, and a lot of things which were originally independent subcommands are being consolidated into the build system. There's a sort of ambient impression that "Zig won't support C anymore" floating around, I'm not sure from your post whether you have that impression or not, but it isn't even vaguely true. It just means that importing C libraries will be a build step, and not something you write directly into the relevant source code. This is not a big deal.
- jay-barronville 2y ago> It just means that importing C libraries will be a build step, and not something you write directly into the relevant source code. This is not a big deal. It 100% is a big deal. I explained this in another comment [0]. [0]: https://news.ycombinator.com/item?id=41111445 https://news.ycombinator.com/item?id=41111445
- throwawaymaths 2y agoI think you are operating from a mistaken understanding, as responded to in the linked comment.
- jay-barronville 2y ago> I think you are operating from a mistaken understanding, as responded to in the linked comment. No, I’m not. Respectfully, you’re responding to my point while admitting in your comment [0] that you don’t actually know. If you read the relevant discussions, the conclusion, last time I checked, was that there’s going to be a build system requirement. [0]: https://news.ycombinator.com/item?id=41111542 https://news.ycombinator.com/item?id=41111542
- whitehexagon 2y agoDo we know if this means the Reflection as mentioned in the article via @typeInfo will no longer work? or was it anyway comptime info? My Zig so far has been SoC level stuff, but using something like raylib or glfw is somewhere on my todo list, and this example sounds mighty useful. Anyway seems a strange thing to remove when Zig has the potential to be the new C. Hopefully also as stable for the next 30 years :)
- pbaam 2y agoIt will still work. If you look at the type signature of @cImport in the language reference[1], it returns a type just as @import. So you can call @typeInfo on it. But instead of writing const win32 = @cImport({ @cInclude("windows.h"); @cInclude("winuser.h"); }); You will write: const win32 = @import("win32"); Where the module "win32" is declared in build.zig. [1] https://ziglang.org/documentation/master/#cImport https://ziglang.org/documentation/master/#cImport
- eska 2y agoWouldn’t this add at least UINT16_MAX*sizeof(intptr_t) bytes into the executable per enum?
- flohofwoe 2y agoI think the executable will essentially contain a lookup table with 2^16 slots and the string data for each macro name matching WM_* in Windows.h. But it's hard to come up with a better solution since the actually required names are unpredictable at compile time. The size of the lookup table could be reduced though if the WM_* values occupy a much smaller range.
- Cloudef 2y agoIt adds 65536 pointers to the binary. Alternative would be to use a hash map. I think if they made function that used inline for instead it would optimize to a switch. No need for a LUT. fn stringFromMsg(umsg: c_int) [:0]const u8 { @setEvalBranchQuota(1000000); inline for (@typeInfo(win32).Struct.decls) |field| { if (field.name.len >= 3 and std.mem.eql(u8, field.name[0..3], "WM_")) { if (umsg == @field(win32, field.name)) { return field.name; } } } unreachable; // umsg is not valid, programming mistake } godbolt: https://zig.godbolt.org/z/7b73aoosf https://zig.godbolt.org/z/7b73aoosf In zig-budoux, I also do comptime reflection on cImport struct to assert compile time that we won't produce broken runtime code https://github.com/Cloudef/zig-budoux/blob/master/src/c.zig#L8-L18 https://github.com/Cloudef/zig-budoux/blob/master/src/c.zig#...
- flohofwoe 2y ago> ...instead it would optimize to a switch. No need for a LUT. IME there's is no difference in (optimized) code generation between an if-else chain, a switch or (like in your example) an unrolled for-loop with an if inside. All those high level constructs will be optimized into a single lookup table, or a combination of multiple lookup tables and binary search to select between those. Only if there are absolutely no consecutive ranges in the switch-set, a binary search without jump tables will be used.
- Joker_vD 2y agoClang's preprocessor is actually not implemented as a separate compilation pre-pass, it's essentially a part of the lexer and I would be willing to bet that gcc uses a similar scheme. So there is nothing technically impossible about having the access to macro names as a compiler-specific extension, it's just that there is no much demand for it.
- JonChesterfield 2y agoClang prints out things about macro instantiations on the semantic error reporting paths. That probably means it has all the macro information available already. It's not probably reflected to the language because C++ fears reflection in general and hates macros in particular.
- g15jv2dp 2y ago> C++ fears reflection in general and hates macros in particular. Macros aren't a particular case of reflection... And at least in the way they're done in C++, they're a big source of bugs / spaghetti.
- formerly_proven 2y ago> C++ fears reflection and hates macros But only wicked gods would banish us from paradise Why do they fear our power? Because evil is what they are.
- bluGill 2y agoReflection is on track for C++26 so it is completely wrong to say C++ fears reflection.
- Joker_vD 2y agoReflection proposals have been around at least since 2014, so I'd say it's exactly right to say that C++ fears it — otherwise it'd have arrived much sooner.
- 2y ago
- voidUpdate 2y agoI really want to like zig, but I've just had some annoying problems with it, most of which I think are just a result of it not being in 1.0 yet. For example, the recommended way to start a project, with `zig init`, has a load of code that I really don't need when I just want a bare project to get started. I only recently found out that you can just `zig build-exe filename.zig` and skip the whole init part. Also I've had a lot of issues getting editor integration to work correctly. I've installed the VSCode extension but I don't seem to be getting autocomplete etc. It is quite possibly just an ID-10T problem though, so I'll probably take another look at it some weekend
- flohofwoe 2y agoTbf, when coming from the C/C++ ecosystem, these types of problems are 'just another Tuesday' (especially for cross-platform projects and the official VSCode C/C++ extension).
- voidUpdate 2y agoYeah, that's one of the reasons I dislike the C++ ecosystem so much and want to like Zig haha
- jeroenhd 2y agoThis is part of the reason why I don't use VSCode for C(++). Jetbrains Clion seems to be the best IDE for those languages, with Visual Studio (the full fat one, costing a grand, as the free version lacks a bunch of features) as a close second, depending on what platform you're developing for. VSCode is great when it works, but in cases like these it quickly becomes obvious that it's a hodge-podge of tools glued together via extensions and command lines rather than a purpose-built IDE.
- markus_zhang 2y agoI have never used Clion. Can you please share why you believe it is the top of the crop? I'm writing a C++/SDL2 game engine in Visual Studio but I think the IDE is really slow and error prone even for a small project. Everything just runs so slow. Maybe I need a better machine though.
- your-username 2y ago[flagged]
- Uptrenda 2y agoThose function definitions really look amazingly readable. I've seen this done before in other languages and its usually quite horrible. Maybe Zig is worth learning? This is a killer feature.
- montyanderson 2y agoi like your site! seems like zig is really taking off.
- WalterBright 2y agoExample from the article: const win32 = @cImport({ @cInclude("windows.h"); @cInclude("winuser.h"); }); pub fn main() !void { _ = win32.MessageBoxA(null, "world!", "Hello", 0); } Equivalent D: import windows, winuser; void main() { MessageBoxA(null, "world!", "Hello", 0); } In essence pared it down to the essentials. The compiler figures out the rest. Sometimes people ask for a special syntax for importing C files, but I like this simplicity so much better.
- throwawaymaths 2y agoSome of us like explicit invocations, not being unsure if something comes from a .h filenor some other importing mechanism (what if a .h file collides with another import mechanism) Naked imports are also annoying. Which import did that MessageBoxA come from? windows? or winuser? Is it in the language kernel? Explicit is better than implicit. The utter pain for the code writer of four or five keystrokes here and there is not worth confounding the code reader.
- WalterBright 2y agoImports are found along the search path (just like .h files are searched for by the C preprocessor). The first one found it the one selected (just like the C preprocessor does). > Which import did that MessageBoxA come from? If it exists in two or more imports, the compiler will give an ambiguity error. To resolve the ambiguity error, qualify the call with the name of the import: windows.MessageBoxA(null, "world!", "Hello", 0); or, one can do this: import windows : MessageBoxA; or this: import windows, winuser; alias MessageBoxA = windows.MessageBoxA; You can be as explicit as you like, and don't need to worry about ambiguity because the compiler will issue an error for that. It works the same way for D imports.
- throwawaymaths 2y agoCompiler giving a compiler error still favors the writer, not the reader of code. It seems like in its design in general D favors the writer of code with all its complicated bells and whistles that one must keep in mind as a reader. I think decades of experience has shown us that it is way better to favor the reader.
- david2ndaccount 2y agoI wrote a blogpost showing how you could do a similar thing in D with ImportC. https://www.davidpriver.com/C-macro-reflection-in-D.html https://www.davidpriver.com/C-macro-reflection-in-D.html
- classified 2y agoThank Apple for Reader View in Safari. If you are as incompetent in visual design as the author of that page, you should really stay away from dark mode. Your readers will thank you.
- flohofwoe 2y agoWhat's wrong with green-on-black? I'll take that over amber-on-black any day even if amber is apparently better for CRT burn-in.
- rk06 2y agoIt is jarring when you use light mode. There is also issue with contrast. The red const gave me enough pain that I switched to reader view
- classified 2y agoIf you have to ask that, please don't make dark-mode pages.
- mst 2y agoI tend to use green on black for daytime hacking and amber on black in the evenings to reduce blue light issues. But I also habitually run with jacked up contrast and nerfed brightness, so I make no claims that what works for me would be a good idea for anybody else.