[Outreach] Looking for a simple example of using comptime instead of macros

Or. without a structure, just two separate functions:

const std = @import("std");

fn min(comptime T: type) !T {
    return switch (T) {
        u8 => 0,
        i8 => -128,
        else => error.Unsupported,
    };
}

fn max(comptime T: type) !T {
    return switch (T) {
        u8 => 255,
        i8 => 127,
        else => error.Unsupported,
    };
}

pub fn main() !void {
    std.debug.print("min(u8) = {}\n", .{try min(u8)});
    std.debug.print("max(u8) = {}\n", .{try max(u8)});
    std.debug.print("min(i8) = {}\n", .{try min(i8)});
    std.debug.print("max(i8) = {}\n", .{try max(i8)});
}
1 Like

Maybe not replacing macros, but here I used comptime to build startic tables that the original C lib was using ruby to generate source files for.

1 Like

Here’s an example I recently have used while cleaning up the try/catch logic in my code. This can be done with any assert, but I wanted a cleaner error message if something went wrong:

const std = @import("std");
const builtin = @import("builtin");
const Child = std.meta.Child;
const debug = (builtin.mode == std.builtin.OptimizeMode.Debug);

pub inline fn assertGrads(x: anytype) Child(@TypeOf(x.grads())) {
    if (comptime debug) {
        if (x.grads()) |grd| {
            return grd;   
        } else {
            @panic("Unassigned tensor gradient.");
        }
    } else {
        return x.grads().?;
    }
}

The debug bool is used to conditionally enable code if we’re building in debug. The Child function returns the child of the optional (which is a slice in this case).

You can see here that if we’re in debug and our optional is null, we trip a panic. In release mode, this all goes away. This is a very clear case where I’ve potentially avoided using two or three macros with comptime.

1 Like

Trying to convince C programmers, I would argue about strong typed generics virtues (no need for casting void* anymore, even Go lang designers agreed on that, at last).

Also, I saw some loop unrolling optimization as a nice use case somewhere in docs (not sure, maybe somewhere else).

Oh! You can implement (statically) polymorphic functions with specific implementations for specific parameter types… It’s a lot easier (and cleaner, whatever it means) than trying to do it in C with distinct functions, preprocessor macros magic and implicit naming conventions.

Being able to create types at compile type it’s quite powerful, too. Not that I think it’s a good idea, but you could probably take a grpc service definition and generate customized struct types for a client at compile time… although it wold be probably better, writing Zig structs and assume those are proto messages, analyze them at compile time and get protobuf service and client implementations with no dynamic behaviour.

2 Likes

If you put 10 C programmers in a room and ask them to write a max() macro, I wonder how many would produce a correct implementation (that is, one that does not duplicate side-effects).

Let’s not be disparaging towards C programmers. Some of the most brilliant people I’ve come across in software love C. We can discuss elements of a language without being disparaging towards the adherents of said language.

I’d like to convince as many C programmers as I can to try Zig. I’ve genuinely found that it has upgraded my programming experience. Besides, we could always use more smart people in our community.

That said, I understand your sentiment. I would say that actually speaks more to the problems with macros than it does towards the programmers. If a major tool is still being misused after 51 years of the C programming language being released, I would say that we need to re-examine the tool itself.

3 Likes

My comment wasn’t meant to be disparaging. I can’t write the said macro myself without consulting Google. The belief that C macros are “evil” is pretty wide spread. I’m genuinely curious how many C programmers still remember enough of the dark art to write something like min/max.

3 Likes

Good to know, and thanks for clarifying :slight_smile:

It’s a deeply contested issue, actually. With most of the developers I’ve worked with (and the large projects I’ve actually seen and read) they use macros quite liberally. For instance, the mysql implementations are packed with it and same can be said for NVIDIA.

In general, it looks like the public opinion is much different than the private opinion on the subject. That’s the impression I’ve had from my experience and just reading large codebases.

Perhaps it would be useful to start a “C macro challenge” category here on this forum, where a real-world C macro is posted each week and everyone tries to come up with the best solution? Could be kinda fun.

8 Likes

That’s honestly a fantastic idea.

2 Likes

I nominate BOOST_PP_COUNTER as the inaugural challlenge :smirk:

Since BOOST_PP_COUNTER relies on a non-standard preprocessor (which is stateful), you could probably do this by running a string replacement over a zig file in the build process. Essentially, the build process would do its own preprocessing. If you count the build step as part of “comptime” then this would work.

Ensuring a particular order between the counters would be tricky, but is totally doable in one file alone.

I second the motion. But I wonder if it could be given a wider scope beyond C and C Macros. For example a category called “Translate-X” (in the vein of translate-c) where you post a code snippet or small program from any other language and the community tries to produce the best Zig version? It could have the upvote functionality like StackOverflow too, sorting solutions by most votes?

4 Likes