Easiest way to replace instance of templated type

I have a templated function for containing a bitmap for a font:

pub fn MonoBitmapFont(Buffer: []const u8) type {
    return struct {
        mixin: MonoBitmapFontMixin(MonoBitmapFont(Buffer)) = .{},

        pub fn setPixel(_: @This(), target: anytype, x: usize, y: usize, on: bool) void {
            target.plot(x, y, on);
        }

I have various public constant ‘instances’ thereof:

pub const SFPRO8 : MonoBitmapFont(&sfPro8Data) = undefined;
pub const MOBILESIGNAL8x16 : MonoBitmapFont(&mobileSignal8x16Data) = undefined;
pub const MESSAGE8x16 : MonoBitmapFont(&message8x16Data) = undefined;
pub const HACK8x16 : MonoBitmapFont(&battery8x16Data) = undefined;

The code works, it prints to various LCDs and displays but I want to make the font used in printing variable. How would I go about having a code that initializes and replace a font e.g.:

// this code is conceptual
var font : *MonoBitmapFont = @constCast(&HACK8x16);
if (hourOfDay > 8) font = @constCast(&SFPRO8) ...

My heart is telling me that it should be simple, my gut is telling me that Zig sees all these as fundamentally different types (while it is one and the same thing)…

I’m curious what changes in the mixin depending on which buffer is passed?

My first instinct is to add a runtime tag which adds the space overhead of the tag and a branch to check which tag is active:

const MonoBitmapFont = union(enum) {
    sf_pro_8: []const u8,
    mobile_signal_8x16: []const u8,
    // ...

    pub fn setPixel(self: MonoBitmapFont, ....) {
        switch (self) {
            .sf_pro_8 => |sp8| {
                // font-specific behavior
            },
        }
    }
};

If you need to set each individual pixel with a single function call, that would need to switch at each pixel, though. You could also use function pointers:

const MonoBitmapFont = struct {
    setPixel: *const fn(x: usize, y: usize, on: bool) void,
    // ...
};

EvaLeek, thanks for responding. If I was to make the MonoBitmapFont a const = struct then I could simply assign the pointer. Changing it to a union, and I may be missing something, does not deter from the question of how to pass a templated struct around… as unless I am missing something in zig, the “interface” of methods and data should still be the same… it is only types and references that differ?

I ask that, for I don’t know what I don’t know - and that might be that Zig cannot determine two structs by the same function, as being the same struct - or I may be missing some syntax magic?

Again, the problem is not in the execution… The code executes perfectly… it draws text etc… the problem comes in if I want to exchange the font for another.

Also, your solution assumes knowledge of all fonts in the MonoBitmapFont… which is exactly the opposite of what I want to do

No problem! I’m understanding what you’re looking for as runtime polymorphism (an interface MonoBitmapFont that we can call setPixel etc. on). In Zig this is directly handled as a vtable, or a struct of function pointers.

I was also going to suggest the tagged union because, depending on how different the behavior is between fonts, it might be easier to simply do some switching within the MonoBitmapFont type, or directly within the struct encode whatever differs between fonts.

But in this case, it sounds like a vtable is what you are looking for. It could be defined like my second example, and then a *const MonoBitmapFont can be passed around, and the functions can be accessed through the fields of the struct.

1 Like

Care to share a bit more of the code to have some more context? Why do you need generics at all, for example?

2 Likes

I was scared that you were going to say that… My head was going towards vtable as well… I however think that it is a lot of work for something that is conceptually a struct that gets a parameter now needing additional magic, while if it was a struct only that it would mingle/interchange without issue.

LUCKILY, for the bitmap I don’t strictly need it parameterized, so I changed it to a struct (usage code below), but the question remains…

BTW: The most important use of template functions for me is to allow me specifying fixed memory for a structure at compile time (on a per use basis), and to hence not do heap allocations. The below works in a struct as I embed the font as a constant array… It still however does not deter from the question; as I am trying to understand what I don’t. Making the code work in one way or another is not the problem.

Also, the StackString is another template function… I have 3 strings, ConstString, StackString and HeapString… all backed by the same mixin. I would have the same problem here, on the same types…


The reference
pub const SFPRO7X13 : MonoBitmapFont = .{._buffer = @constCast(&sfPro8Data)};
pub const HACK6X10: MonoBitmapFont = .{._buffer = @constCast(&hackSize6X10Data)};

    var fontName: utils.string.StackString(255) = undefined;
    var font: *utils.graphics.font.bitmap.MonoBitmapFont = @constCast(&fonts.SFPRO7X13);
    try fontName.str.set("SFPRO8");

    if (cmd.str.eq("fill")) {
        bmp.fillSet();
    } else if (cmd.str.eq("clear")) {
        bmp.fillClear();
    } else if (cmd.str.eq("text")) {
        const fontNameArg = Args.readArg("-f", "--font");

        if (fontNameArg != null) {
            try fontName.str.set(fontNameArg.?);

            if (fontName.str.eq("HACK")) {
                font = @constCast(&fonts.HACK6X10);
            } else if (fontName.str.eq("SFPRO8")) {
                font = @constCast(&fonts.SFPRO7X13);
            } else if (fontName.str.eq("DEJA7X9")) {



// where
const sfPro8Data = [_]  u8 {
    0x20, 0x5F, 0x0D, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x13, 0x00, 0x1E, 0x00, 0x29, 0x00, 0x36,
    0x00, 0x43, 0x00, 0x48, 0x00, 0x4E, 0x00, 0x54, 0x00, 0x5E, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x77,
    0x00, 0x7D, 0x00, 0x83, 0x00, 0x8E, 0x00, 0x96, 0x00, 0xA1, 0x00, 0xAC, 0x00, 0xB7, 0x00, 0xC2,
    0x00,...

@mg979 - the below code shows the usage. There is not much more to it that is applicable to the question… being:

Can I interchange “instances” of a struct created by the same template function.

Any generic type that was created with different parameters will be a different type to the compiler.

But I’m not sure why you need templates in this use case. Where do you encounter a problem if you have:

pub const MonoBitmapFont = struct {
    buffer: []const u8,

    // functions that use buffer ...
};

pub const sf_pro_8 = MonoBitmapFont{ .buffer = &sfPro8Data };
pub const mobile_signal_8x16 = MonoBitmapFont{ .buffer = &mobileSignal8x16 };
// ...
3 Likes

Thanks, that answers my question. As confirmed, that case I can get by without using a generic. I expected it but did not know for sure (contra/covariance?). I however have other places where this is a reality, e.g. on the strings. I guess the trick here is to give the mixin (composite concrete struct) class all the implementation and pass that around? Not sure whether this will work; will have to check.

3 types of string:
A StackString… that takes a template parameter to preallocate a variable sized u8 array on the heap.
A ConstString (can be a normal struct, but is not currently)
A HeapString

All 3 of these use a StringMixin to provide the common functionality for all strings. So what is implied that you cannot have StackString as a parameter to a function unless the type parameters are consistent? Meaning that with template functions, even though the implementation is the same, you lose the option to send it to functions!? It seems quite harsh… since many (or at least my templates) would be safe to cast?

It is what it is, just a lot of things you have to get wrong to get it right.

The cheap way, that I got by with all these strings, is that the library uses [:0] u8 or u8, which I have mixin methods for… so it fits its initial purpose… but as it gets more complicated I already can see that same issue arise.

I think you should pass the pointer to the Mixin around. If it contains an enum describing the outer type (here, which one of the 3 different string types), you can safely cast using the correct outer type using @fieldParentPtr where necessary.

My meta-suggestion is to learn the vocabulary and abstractions which Zig actually uses, rather than jumping straight into implementing ones from other languages.

Zig doesn’t have templates, that’s a whole large and complex feature, which is very prominent in C++ code, and which Zig doesn’t have at all. We’re not missing out! But it calls for a different approach sometimes.

So trying to answer the question “well in C++ I’d use a template here, what’s a good way to do it in Zig?” will go better than trying to implement templates, because you basically can’t do that unless you’re emitting source code, which I infer you aren’t doing.

For instance, in the example you give for strings and mixins: possibly, you can make the first argument of any given function on those strings an anytype, then, just assign the function body as a declaration inside the type.

Or you could pass the type itself to a function which returns a struct type as a container; in the good old days you could then usingnamespace to get the functions into the namespace, but you can get much the same effect through elbow grease in our brave new world.

A topic more like “best way to accomplish this thing I need” would get better answers than this one.

@mnemnion, OK - I hear you, and the fact that various patterns are completely not fit for Zig is becoming more obvious by the day.

@mnemnion - And yes, I did start to use the “zig” interface / abstractions, It is seriously powerful even though it diminishes contextual assistance. That said, I am not a guru there yet, so not all the pennies dropped wrt opportunity of design. As said, the fact that it assumes things until compile time makes me avoid it as, essentially still a noob, I tend to steer towards things that assist me.

@mnemnion , also agree on the templates comment. C++ templates really, not only in its use, but also in its compilation forces one to not only code ugly, but do ugly things, and the complexity gets out of hand without you even realizing…

So apart from using a template style function that creates a struct; how do I create a variable sized StackString, that has a fixed buffer but variable per instance? Is your comment (“For instance, in the example”, it is not obvious to me how that would fit, or as graciously as a template function be?)

On your last comment of the title; yes, there are always better ways and a hundred of them, I honestly want the easiest way, since I am very distracted, and have to support what I do (need to keep things simple in other words, with less code being less opportunity for creating disaster).

1 Like

Thanks @hvbargen - will give that a go

1 Like

If you know you have a closed set of only three string types, you might be happiest making them into a tagged union.

One of the ways Zig’s type-returning functions differ from templates, is that there’s no way to specify that a parameter be “any type returned from my function”, other than to make it an anytype and document which anytype you mean.

It seems that you have a case where there are three kinds of string in play, and you might be using just one of them in some places, but also need a way to represent “any of these strings”. That’s a good candidate for a sum type, in Zig, a tagged union.

That way you have a concrete type, it can be specified as a parameter or a struct field, and you can wrap any of your variants in the union quite cheaply.

1 Like

Thanks @mnemnion - I have a few quiet hours… going to give that a try today hopefully. I have been (from having to rework unions in C days, been avoiding it), but I do see/understand that in Zig it brings opportunity.

My gut is telling me that I have to mingle code blocks by switching blocks of code specific to the union, which is avoidable with templates; whereas in templates, unless you create stub code for the mixin, you sit with an ugly (but manageable)

exampleStr.str.isLeftEq('...'

etc

But yes, the quantity of types of union is manageable; not due to the lack of possible derivatives, but due to the fact that I can keep it manageable/simple.

@mnemnion … Hmmm sorry, going to have to read the thread again. Missing one critical item. How can I make the static string variable in size (opportunity to create on the heap); if not by template function.

Will read up the thread again; if I missed/forgot that reply, then please excuse me.

Quoted:

That way you have a concrete type, it can be specified as a parameter or a struct field, and you can wrap any of your variants in the union quite cheaply.

Will have to think about that… do you have an example?

What I read from this is:

  1. A concrete type, meaning implemented as const A = struct (or union variant)
  2. Can pass around as parameter… implied by concrete type?
  3. Can be used as a struct field… implied by concrete type?
  4. Can implement the various quite cheaply - agreed… little more complexity in one class vs 3 but not an issue…

Still however fail to see how I mingle the concrete class with a template to ensure that I can ensure a fixed (dynamic size buffer) and other variants with either a const* or heap allocated? Maybe by generalizing the data pointer but then, how to ensure that it is allocated from the stack?

Is, what you are describing the same as @hvbargen (which I don’t think it is), his suggestion makes sense to me in getting by the ‘static’ nature of a struct while still being able to pass it around.

I think, without starting a fight. This is where the Zig writers forces you to supply the buffer, as you want, from the stack by allocating it prior to passing it to whatever formatter needs to deal with it… I want to do that in one step for a stack based string… and reuse the same string methods on constants strings and heap allocated strings, wrapped nicely in “classes”, that is preferably interchangeable…

pseudo code... just as example.
if (consStr.eq(stackStr)

I see code for struct parameterization on Google that I really cannot think that it compile (anymore)… going to give it a try.

It’s true that to get generic method dispatch, you will have to write a certain amount of boilerplate, but this can be minimal, using inline switch statements. Mostly you have a wrapper which looks like:

pub fn doSomething(str: *StringUnion, arg: Whatever) void {
    switch (str) { // your variants:
        inline else => |variant, _| { // the _ is the tag
            variant.doSomething(arg);
        },
    }
}

Which you can customize when you need to.

If you make the interface consistent between the variants and the union, then you can use an anytype receiver, and won’t have to wrap variants when you don’t want to. That’s at the cost of monomorphization, but since we’re doing an alternative to templates, that’s to be expected.

I don’t entirely understand the question, but a union type is the size of the largest variant, plus enough room for the tag. So that might be your answer?

1 Like

This is somewhat of a different matter. It’s fine to allocate a buffer for a Writer on the heap, it doesn’t actually matter where it resides in memory. It’s mostly been illustrated using stack buffers, for whatever reason, but that isn’t inherent.

Similarly, if you want to allocate a string on the stack sometimes, you can use the FixedBufferAllocator to do that.

Zig’s approach to memory management is intentionally very explicit, there’s no magic at all. Sometimes this can feel a bit tedious, but the reward is complete control. The way I see it, we have plenty of languages for when that level of control isn’t needed, but when it is, there’s Zig.

Once you get used to it you might well use Zig anyway. It’s really not that bad, just takes some getting used to.

1 Like

@mnemnion , I have no issue with the principles and values you relay, and love that aspect of Zig myself (the total control) but I think we are missing the point.

Let’s take 3 types. A constant string ([] u8), a heap string that reallocates itself as needed, and a stack based string that is constant in size but specific towards the instance, and potentially/most often reside on the stack. All 3 of these are strings and should be interchangeable, as even though their allocation is different (and hence thereby also their mechanism of instantiation - in my mind anyway), they share 99% of the rest of common traits.

These 3 should all encapsulate their own memory - don’ want to go the Zig route of supplying the buffer (unless it is a constant string) - the StackString and HeapString copies data into occupied memory … for mutable further manipulation.

Why not the Zig route… as that is missing the point for me. As strings and string handling should be one liners… it happens a lot, so writing repetitive code should be encapsulated.

So, the only way to do that is to parameterize the struct (specific to StackString only), which boils down to a function struct (similar in concept to C++ template). This forces me not only into a mixin approach (which I don’t mind). This further means that I have to pass the mixin type around, as the containing type is not ‘transferable’ as parameter etc unless using an anytype, which voids the ability to get code insight while editing. This extends to a StackString(100) vs a StackString(255) not being the same type either… which is where the thread started.

So how would you do this differently given what I want to do, or are you saying that I should bite the bullet and preallocate etc as per a soft suggestion from your previous post?

I am failing to see how my way is worse. But want to understand if there is a better approach - if there are example, documented pattern or other then please do share.