*ฅ^•ﻌ•^ฅ* ✨✨  HWisnu's blog  ✨✨ о ฅ^•ﻌ•^ฅ

Generics implementation in Rust, Zig and C

Introduction and update

Hello there! (Ben Kenobi salute) It's been a while since my last blog post, I've been busy doing work stuff at the closing of 2024 year. Some update on the previous post about Autograd optimization: it has reached iteration 02 and I will write about it in the near future.

In this post we will take something simple (Generics) and we'll see the different implementation from different languages.

Generics

Generics allow reusable code for multiple data types safely. Modern languages like Rust and Zig have generics built into the language, however there's none as such in C.

The code is actually taken from the Rust book:

generics_rs

The program demonstrates the use of generics in Rust. The use of generics here allows for flexible and type-safe code that can work with different data types, without having to rewrite the code for each type.

Rust

A bit of explanation on the program provided in the previous section:

The program shows the ability to work with different data types via the use of generics.

Zig

The same program written in Zig:

const std = @import("std");
const print = std.debug.print;

pub fn Point(comptime X1: type, comptime Y1: type) type {
    return struct {
        x: X1,
        y: Y1,

        const Self = @This();

        pub fn mixup(
            self: Self, comptime X2: type, 
            comptime Y2: type, other: Point(X2, Y2)
            ) Point(X1, Y2) {
            return .{
                .x = self.x,
                .y = other.y,
            };
        }
    };
}

pub fn main() void {
    const p1 = Point(i32, f64){ .x = 5, .y = 10.4 };
    const p2 = Point([]const u8, u8){ .x = "Hello", .y = 'c' };
    const p3 = p1.mixup([]const u8, u8, p2);
    print("p3.x = {}, p3.y = {c}\n", .{ p3.x, p3.y });
}

It has the same output but as you can see a bit different in implementation:

This demonstrates the program successfully combined the x and y values from different points even though the types are different.

C

Even though C does not have generics built-in, we can write our own generics to simulate the same outcome we got in the Rust and Zig versions.

Determine the types manually

#include <stdio.h>

typedef enum {
    INT,
    DOUBLE,
    CHAR,
    STRING
} DataType;

typedef struct {
    union {
        int i;
        double d;
        char c;
        char* s;
    } data;
    DataType type;
} Data;

typedef struct {
    Data x;
    Data y;
} Point;

Next the print functions:

void print_data(Data data) {
    switch (data.type) {
        case INT:
            printf("%d", data.data.i);
            break;
        case DOUBLE:
            printf("%f", data.data.d);
            break;
        case CHAR:
            printf("%c", data.data.c);
            break;
        case STRING:
            printf("%s", data.data.s);
            break;
        default:
            printf("Unknown type");
    }
}

void print_point(Point p) {
    printf("p.x = ");
    print_data(p.x);
    printf(", p.y = ");
    print_data(p.y);
    printf("\n");
}

The mixup function:

Point mixup(Point self, Point other) {
    Point result;
    result.x = self.x;
    result.y = other.y;
    return result;
}

Finally the main function:

int main() {
    Point p1;
    p1.x.data.i = 5;
    p1.x.type = INT;
    p1.y.data.d = 10.4;
    p1.y.type = DOUBLE;

    Point p2;
    p2.x.data.s = "Hello";
    p2.x.type = STRING;
    p2.y.data.c = 'c';
    p2.y.type = CHAR;

    Point p3 = mixup(p1, p2);
    print_point(p3);
    return 0;
}

Auto detect types

There is a different version of the program that will auto detect the types. In the previous code we need to determine the types manually which can be inconvenient or downright unusable if we are running an automated system.

I put the auto detect version here.

Next up

I might explore other modern programming language features and see the different implementation between them, especially features that are not available in C.