Adding semantic information to declarations

back in my java/c# days, one could “annotate” declarations with a special syntax that looked like @MyAttr(params…)…. these are often used by supporting tools (eg., stub generators for RPC)….

i have a similar need, and was wondering if there is some variation of a comment i could use for this purpose…. like a doc comment, it would generally precede a declaration….

is there any examples of this approach in zig???

My understanding is that by design zig does not allow decorators or special attributes.
But you can call compile time functions passing the type or the function.

comptime {
    myAttr(func, params...);
}
fun func() void {
    ...

There is a proposal Proposal: code annotations · Issue #14656 · ziglang/zig · GitHub

Personally I am not a fan of putting semantic meaning into comments.
I also don’t know whether I like the idea of having annotations in general, seems like a way to hide a bunch of switch statements behind magic.

I also think that in many cases it can be avoided by structuring your program differently / making it more explicit (which helps make it more understandable).
Or said another way: I think annotations are often used to hide ugly code from the user, instead of working on the code to make it less ugly, so it doesn’t need hiding.

I also think that this type of magic, typically found in frameworks, is a illusion of simplicity cast on the user, first everything seems so simple and straightforward, but as soon as you want to do something that wasn’t intended by the framework, you realize that the price for that quick on ramp is a lack of flexibility, certain things are easy to do, while others are incredible difficult, because now you suddenly need to understand a big stack of things that were hidden from you, so that you are able to add your own variation of magic to the framework, sometimes having to fight several layers of abstractions that just didn’t have your use-case in mind.

Which is why I don’t like frameworks, I was disappointed by them too often.
Simple libraries and writing code, is better then making things overly cute.

4 Likes

There are times when annotations work really well. For example, declaring struct field names for JSON serialization in Golang is very handy:

type User struct {
	Name          string    `json:"name"`
	Password      string    `json:"password"`
	PreferredFish []string  `json:"preferred_fish"`
	CreatedAt     time.Time `json:"created_at"`
}

This way the field names in Golang code can follow the usual Go conventions while still being easy to declare how to call them in the serialized JSON output/input.

IMO this type of control is not easy at all in the Zig standard JSON library.

go really required something like that because starting an identifier with lower or upper case means private or public. There is no need for that in zig.
You can define the user struct as:

const User = struct {
    name: []const u8,
    password: []const u8,
    preferred_fish: [][]const u8,
    created_at: i64,
}

Adding a json mapping might look an interesting idea but it is not:

  • why not add a relational database mapping also, and an xml one, and a second json mapping because someone else needs a different json file, etc.
  • this requires another parser that probably results to calling a comptime function that maintains the mapping. And the point is that you can call the comptime function now without any syntactic sugar.

Yes, I am also against the proposal for code annotations.

2 Likes

thanks for all the suggestions… i can see why this topic is contentious, given the philosphy of zig… and of course, there are so many different use-cases that folks want to address…

the suggestion by @dimdin is a possible direction that could work for me…

again, drawing an example for java/c#, it was common practice to define “empty” interfaces (eg., ISerializable) that one could (easily) associate with classes – in effect annotating the type with some “aspect” (eg., serializability)…

my use-case would involve my reflecting of certain struct/contain types to determine whether they have a particular “feature”… i’m already using @hasDecl to look for certain methods by name; i suppose i could follow a convention where a void const of some “well-known” name could do the trick:

pub const SomeAspectName = {};

i suppose this is no worse than saying (in java/c#) that i implement the SomeAspectName interface – both are “well-known” names… and i suppose this is not uncommon when you’re duck-typing???

I have used something like that, it is useful and simple sometimes.
I think this also can be useful: Tags · Issue #1099 · ziglang/zig · GitHub

2 Likes

I don’t like this in Go either but I still maintain that being able to name JSON field name like in my example is very handy. It’s not uncommon for the field name to be named differently from how it’s in the serialization format. F.ex. when the JSON schema is defined by some remote service which I have no control over.

I don’t care to argue for the exact same feature for Zig, I just think it’s useful to enumerate language features to inform design discussions. Somehow being able to communicate field names to the json library in zig would imo be very useful and readable.

EDIT: But I guess the above could be pretty much done with the tags pattern.

Thank you for linking to the TAGS pattern; it is so simple that feels magic.

This could be used for functions:

pub const func1_TAGS = .{ .args = 0 };
pub fn func1() void {

vs

comptime {
    args(func1, 0);
}
pub fn func1() void {
2 Likes

Yes, it is feasible. As an example I am reversing the names:

const User = struct {
    const GO_NAMES = [_][]const u8{"Name", "Password", "PreferredFish", "CreatedAt"};
    name: []const u8,
    password: []const u8,
    preferred_fish: [][]const u8,
    created_at: i64,
}
2 Likes

I think using ziggy would be a good solution, just ditch the json :wink:

You could have a secondary struct that contains the same field names that are strings that map to the json tag or whatever else you were trying to do. Then have some comptime code in the serializer that looked up the data structs member in the tag tag name struct. It isn’t the easiest, but you would probably only need to write the code once. I did something similar that removed items from one struct to form a second.

const Data = struct { first: u32};
const Data_Tag = struct { first: []const u8 = @as([]u8, "First")};

Its wordy, but it works.

Instead of \\:, why not using # ?