ZIG programming language.

Electronic Computer Control Unit -> Instruction Cycle -> Instruction Decoder -> Control Signals -> ...

CODE EnCODE DeCODE
First we need CODE, then CODE based EnCODE is possible, then CODE based DeCODE of EnCODE is possible.

Layer 1 : RISC-V Machine Code, ..., (ARM, x86_?, POWER, ... Machine codes)
Layer 2 : RISC-V Assembly, ...(ARM, x86_?, POWER, ... Assembly codes)
Layer 3 : RISC-V Portable Assembly GCC IR gimple, LLVM IR, ...
Layer 4 : c, c--, cmm, c-like subsets, fortran, ...
Layer 5 : c++, objective-c, objective-c++, d, rust, ... , Nim, Crystal, Zig, V, ..., go, ada, ...
Layer 6 : Based on previous layers language apps like JVM, BEAM, CLR(.net), MoarVM, WebAssembly, JS, Python, PHP, Perl, Ruby,...

so from layer4,5 layer languages need to be ordered as per coherent higher order
 
I think Zig’s home page does a great job of explaining the bullet point reasons why you might want to use it.

I haven’t written any zig yet. One thing I’m curious about regarding allocators being explicitly passed in: is that by convention, or language enforced? Because you need to initialize the allocator somewhere, in which case it’s not being passed in. Though I could see how the compiler could check to make sure that the allocator is released in the same scope it’s created in, which would be good.

I’m pretty interested in. I also hope that we see a language between Zig and Rust. Memory safety is explicitly not a goal of Zig - its strategy is to reduce memory errors by making it easier to do the right thing. Rust on the other hand guarantees memory safety as much as it can, but is pretty complex. I don’t know if it’s possible to enforce memory safety with a less complex language, but that’s what I’d like to see.
 
By convention.
See this topic on Zig's forum.
Cool, thanks for that.

I wonder if it's something a linter could catch. Rust has (at least) two mechanisms for easing its error checking: unwrap() and the try operator. They are convenient when writing new code and I don't yet want to have to think about all possible error cases. But I want to handle those properly at some point, so I can use the linter to check for those usages and replace them with robust error handling.

Presumably zig could do something similar and check for allocators outside of main, or otherwise blessed functions.
 
How zig looks like,
Code:
cat main.zig 
const std = @import("std");
const deb = std.debug;
const io = std.io;
const pri = deb.print;
const stdoutf = io.getStdOut().writer();
var bw = io.bufferedWriter(stdoutf);
const stdout = bw.writer();

const arrayList = std.ArrayList;
const sheap = std.heap;
const config=.{.safety=true};
var gpa = sheap.GeneralPurposeAllocator(config){};
const gpaallocator = gpa.allocator();

const Vec = struct {
    x: f32,
    y: f32,

    pub fn init(xa:f32,ya:f32) Vec {
        return Vec { .x=xa + 0.1 , .y=ya + 0.2};
    }

    pub fn printx(v:Vec) void {
        pri("{e}\n",.{v.x});
    }

};

const Month = enum {
    January,
    February,
    March,
};

fn myadd(a:i32,b:i32) i32 {
return a+b;
}

fn myprint(s:[] const u8) void {
    pri("string:{s}",.{s});
}

pub fn range(len: usize) []const u0 {
    return @as([*]u0, undefined)[0..len];
}

const  myunion = union(enum) {
    i:i32,
    f:f32,
};

fn printunion(u:myunion)  void {
    switch(u) {
        .i => |i| pri("Integer : {d}\n",.{i}),
        .f => |f| pri("Float : {e}\n",.{f}),
    }
}

const myerrors= error { MyError,MyError2};

fn testerror(succeed:bool) myerrors!bool {
    if(!succeed) {
        return myerrors.MyError; 
    }
    else {
        return true;
    }

}

pub fn main() !void {

    var arrx:[10]u32=undefined;
    arrx[2]=4;
    pri("{d}\n",.{arrx[2]});

    var x: i32=321;
    var px: *i32=&x;
    pri("{d}\n",.{px.*});

    var ov: ?u32=null;
    if(ov)|value|{
        pri("Value : {d}\n",.{value});
    }
    else {
        pri("isnull\n",.{});
    }
    ov=88;
    if(ov)|value|{
        pri("Value : {d}\n",.{value});
    }
    else {
        pri("isnull\n",.{});
    }

    const  r:bool = testerror(false) catch |err| blk: {
        if (err == myerrors.MyError) {
            break :blk false;
        }
        else {
            return;
        }
    };
    pri("{any}\n",.{r});

    switch (15) {
        0...10 => pri{"0-10\n",.{}},
        15 => pri("15\n",.{}),
        20 => pri("20\n",.{}),
        else =>  pri("Error\n",.{}),
    }

    pri("Hello World1\n", .{});
    try stdout.print("Hello World2\n", .{});
    try bw.flush();

    const myvec = Vec.init(2.0,3.0);
    pri("{e}\n", .{myvec.x});
    myvec.printx();

    var vv: i32 = 123;
    pri("{d}\n", .{vv});

    var month:Month=.January;
    pri("Month:{}\n",.{month});

    pri("Add : {d}\n", .{myadd(123,456)});

    myprint("Mystring\n");

    const m1:myunion=myunion{.f=2.3};
    const m2:myunion=myunion{.i=5};
    printunion(m1);
    printunion(m2);

    var list = arrayList(u8).init(gpaallocator);
    defer list.deinit();
    try list.append('C');
    try list.append('A');
    try list.append('T');
    _ =list.pop();
    for (list.toOwnedSlice()) |elem,index| {
        pri("by val: {d} : {c} \n", .{index,elem});
    }
 
Oh, btw..

Since this forum is FreeBSD dedicated,
I'd like to note just one little feature
I like in FreeBSD API (as opposed to Linux API).

It's about adding a fd (file descriptor) to a set of file descriptors
being watched by an application program.

In FreeBSD when adding a fd to "watched fds' set"
in case of it's already there FreeBSD kernel just accepts it.

In Linux such a behavior of an app is an error,
and when I tried to construct an abstraction
above epoll/kqueue in Zig, I came up with this workaround:


Code:
fn enableEventSource(self: *EventQueue, es: *EventSource, ek: EventKind) !void {

        const FdAlreadyInSet = os.EpollCtlError.FileDescriptorAlreadyPresentInSet;
        var em: u32 = if (.can_read == ek) (EPOLL.IN | EPOLL.RDHUP) else EPOLL.OUT;
        em |= EPOLL.ONESHOT;

        var ee = EpollEvent {
            .events = em,
            .data = EpollData{.ptr = @ptrToInt(es)},
        };

        // emulate FreeBSD kqueue behavior
        epollCtl(self.fd, EPOLL.CTL_ADD, es.id, &ee) catch |err| {
            return switch (err) {
                FdAlreadyInSet => try epollCtl(self.fd, EPOLL.CTL_MOD, es.id, &ee),
                else => err,
            };
        };
    }


I did not explore Zig in FreeBSD,
I only had some some little experience with D in this OS,
but I hope that will be useful for someone anyhow.

And still I think that a language with GC (no matter, ARC or "true") is not a C competitor.
 
Nope, it is much more useful to learn FORTH.
Now that's a name I've not heard in a long time. A long time. Instead of adding Rust (or Zig) to base, let's rewrite the kernel and utilities in Forth. Oh wait, BTDT, got the T-shirt. I vaguely remember having an EPROM (or was it a tape?) that allowed booting Forth on a 6809-based microcomputer. It was sort of fun.
 
A Warm Acknowledgment and an Objective Conclusion from an Enthusiast


As someone who started this thread with a lot of enthusiasm about exploring alternative languages for OS development, I now feel it's important — and honest — to offer a concluding thought.


With time, reflection, and especially thanks to the feedback and shared experience of more seasoned members here, I’ve come to the realization that, at least for me, there is currently no better language than C for low-level development on 32-bit Intel machines.


While I remain genuinely fascinated by new languages like Zig and Rust — and deeply appreciate the energy and innovation they bring — the reality is that these languages are still relatively unstable: syntax changes frequently, features are added or removed, and toolchains evolve quickly. All of this introduces friction when working close to the hardware. And in the end, they all rely on LLVM as a backend, which, in my experience, often results in overly padded binaries (NOP gaps, alignment artifacts, etc.) that aren’t ideal for precise control.


By contrast, GCC (and C) gives me more direct influence over code generation, better control over inline assembly, and produces more compact and predictable output. BSD systems still supporting 32-bit architectures further reinforce the practicality of this setup.


I also must admit: I haven’t yet “mastered” C, and I’m not ashamed of that. I’m still learning every day. But I’ve developed a way of working that includes a lot of inline assembly within C — something I find both expressive and necessary. In this environment, rapid changes and instability from newer languages can make the process more confusing, rather than empowering.


This discussion has helped me focus, refine my direction, and understand better what suits me and this stage of my OS project. While I still cheer for the evolution of modern languages, I’m personally continuing on a path where C remains the most suitable and reliable foundation.


So I don’t see this as an end to the topic, but rather as a milestone — a step forward in clarity and confidence. I'm grateful for the exchange and for the thoughtful replies I received. They truly helped shape this perspective.


Thank you all.


P.S. I apologize for not explicitly mentioning other languages that some members have pointed out here — I do appreciate the suggestions and insights. My reflections were limited to the ones I had personally explored in more depth.
 
zig has native backends for x86_64 and aarch64 (not sure about arm64) which are significantly faster at compiling. 20 seconds for the compiler (compared to 75 seconds with llvm backend). Contrast that with just how long it takes to compile llvm itself (easily adds an hour to make buildworld). Also, zig is much better at cross compilation -- try "zig targets". Pipe it through less as the list is very long.
 
I’ve developed a way of working that includes a lot of inline assembly within C — something I find both expressive and necessary.
This is usually ends up being a mistake. Use well defined interfaces and separate assembly code in assembly files so that you can more easily target multiple architectures. inline assembly will make a port very painful.
 
Back
Top