Removal of usingnamespace: namespacing is good, but `std` doesn't namespace methods

The removal of usingnamespace I think is a net good. However, I think there may have been a misunderstanding in the removal.

The changelog, as well as a lot of the justifications for removing usingnamespace, says that there’s an alternative way to implement mixins. However, the mixin shown in all of the examples I’ve seen in migration (that CounterMixin one) doesn’t actually address the core reason that I’ve loved the mixin pattern: automatic implementation of “standard interfaces”.

Specifically, I have a mixin named PackedFlagsMixin(T) which implements the format, jsonStringify, jsonParse, and jsonParseFromValue methods for a given type. For now I can work around the removal of mixins by doing the following:

pub const MyBitFlags = packed struct {
    foo: bool = false,
    bar: bool = false,
    baz: bool = false,

    const Mixin = PackedFlagsMixin(@This());
    pub const format = Mixin.format;
    pub const jsonStringify = Mixin.jsonStringify;
    pub const jsonParse = Mixin.jsonParse;
    pub const jsonParseFromValue = Mixin.jsonParseFromValue;
}

I recognize that verbosity can be good, but this can start to get ridiculous when you also want to start implementing things like format and others.

But perhaps, we could have something like the following:

pub const MyBitFlags = packed struct {
    foo: bool = false,
    bar: bool = false,
    baz: bool = false,

    pub const Fmt = PackedFlagsFmt(@This());
    pub const Json = PackedFlagsJson(@This());
}

which would be short for something like

pub const MyBitFlags = packed struct {
    foo: bool = false,
    bar: bool = false,
    baz: bool = false,

    pub const Fmt = struct {
        pub fn format(self: MyBitFlags, writer: *std.Io.Writer) !void { ... }
    };
    pub const Json = struct {
        pub fn stringify(self: MyBitFlags, jsonWriter: anytype) !void { ... }
        pub fn parse(alloc: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !MyBitFlags { ... }
        pub fn parseFromValue(alloc: std.mem.Allocator, source: std.json.Value, options: std.json.ParseOptions) !MyBitFlags { ... }
    };
}

This would make implementing interfaces much easier IMO. This also has some other miscellaneous DX benefits:

  • It’s easy to tell what a function is meant for, since it’s namespaced by the struct name (i.e. format now has an explicit tie-in to the fmt package)
  • They are no longer methods, since they don’t take the same struct as a parameter. This is a hint to callers that these functions are not meant to be directly called outside of the library functions that are meant to call them.
  • The functions are now indented, offering additional visual clarity that these methods are meant to specifically be called by the fmt/json/etc library fucntions.
4 Likes

I think for format specifically there is no reason to only call that function indirectly through formatted printing, it is easy and useful to pass some writer to the function to directly use the function.

Similar I imagine there could be situations where something like that would make sense with the json functions (for example using stringify with a custom writer, writing your own formatting code, etc.), so I don’t think distinguishing between direct or indirect calls is that useful.

1 Like

Yeah that’s definitely fair. Especially now that the format call is simpler, I can see manually calling it being useful. I still like having the Fmt namespace for consistency, though.

Also, not like it’s impossible to call the methods anyway. MyType.Fmt.format(val, writer) isn’t intrusive by any means.

I am pretty much new to zig, and I was just using “usingnamespace” to re-export all types from a translated c header in my lib, is there an alternative way to do that? or i have to import and export every type manually?

p.s. sorry if i am entertaining this with a slightly different question. I thought the topic is close enough

Why re-export when you can just assign it the name you want in the first place.

pub const mylib = @cImport({
    \\...
});

I translated a c header to zig and fixed some things manually. Then i did some wrapper so I want to export as library all the types and the wrappers i created as one lib to use it in other projects. Inside my lib i can do the const c = … it’s fine the problem is i don’t how to re-export all the types for projects that use my lib

Either re-export them manually or make c pub

ah right, i can make c pub, better than nothing i guess.

I hope this will be addressed in a different way at some point if i want all types at top level it’s insane to me that i have to export each type individually or have one giant root file.

I don’t know maybe i am just used to Swift where i can organize the code in different files as much as i want basically and all it’s exported potentially.