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:
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:
- Create a Point struct with X1 and Y1 as type parameters.
- Implementing mixup method for Point struct.
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:
- Create a generic function that returns a new struct type with X1 and Y1 as fields.
- The mixup function returns a new Point where it mixed the values from different points.
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
- Defines a set of custom data types and structures to represent different types of data. In the C version we use enums, unions and structs.
- Data struct has two members: a union that store different types of data and an enum that specifies the type of data stored in the union.
#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:
- print_data: takes a Data struct as argument and print its value based on the type member.
- print_point: takes a Point struct and prints out the values.
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:
- Essentially the function swaps the value of two different input points.
Point mixup(Point self, Point other) {
Point result;
result.x = self.x;
result.y = other.y;
return result;
}
Finally the main function:
- Even when the points (p1 and p2) have different data types, the program works well thanks to the use of unions and enums as explained before.
- Output:
p.x = 5, p.y = c
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.