← back to home

Zinc language

· 778 words · 4 min read

Zinc Language Design#

I've spent a lot of time thinking about the gap between how I solve a problem in my head and how I have to express that solution in code. Most modern languages are reactions to their predecessors—C++ reacting to C, Rust reacting to C++, Go reacting to Java. They are improvements, but they still carry the DNA of the problems they were trying to solve.

Zinc is not an attempt to "fix" other languages. It's an experiment to see what happens when I build a tool that maps directly to my own idiosyncratic mental model, ignoring the "industry standard" way of doing things.

The Mental Translation Tax#

I am Italian, and I've noticed that programming always involves a layer of translation. Not just from Italian to English keywords, but from a fluid concept of "data and flow" into rigid structures like Class Hierarchies or Borrow Checkers, I prefer the freedom of C instead.

The Sapir-Whorf hypothesis suggests that the language we speak shapes how we think. In programming, this is undeniably true. If I use a language with heavy OOP requirements, I start seeing everything as an Object, even when it's just a simple transformation. This "translation tax" is mental energy spent on the language rather than the problem.

Zinc is my attempt to lower that tax for myself.

Mapping My Own Model#

When I look at a problem, I don't see objects or monads. I see:

To keep Zinc aligned with this, I followed a subtractive process. I only kept what I felt I absolutely needed to understand my own code six months later.

Personal Design Choices#

A few specific choices illustrate why I built this for myself:

Receiver Functions I like the readability of point.Distance(), but I find the baggage of Classes (constructors, inheritance, hidden this pointers) to be more noise than help. In Zinc, a receiver is just a named parameter that allows dot-notation:

f64 Distance() Point p {
    return sqrt(p.x * p.x + p.y * p.y)
}

It's a cosmetic choice that matches how I read, without adding the complexity of an object system.

Logical Clarity I used and, or, and not instead of symbols. Symbols like && are fine for math, but logic feels like prose to me. Writing if valid and ready is simply one less micro-step of translation in my head.

Visual Consistency in Types I chose square brackets for generics (Vec[T]) because it matches how I see arrays ([T]). To me, both represent a "container" or a "parameter" of a type. It keeps the visual language of the code consistent.

What This Isn't#

It is important to note what Zinc isn't trying to be. It isn't "better" than the tools we have; it just has different priorities:

A Small Implementation#

This is a snippet of how I handle a dynamic array in Zinc. It feels comfortable to me because the data is transparent and the logic is manual.

struct Vec[T] {
    *T data
    u64 len
    u64 cap
}

void Push[T](*T item) Vec[T] self {
    if self.len >= self.cap {
        self.Grow()
    }
    self.data[self.len] = *item
    self.len = self.len + 1
}

void Main() {
    numbers := Vec[i32] { malloc(64), 0, 8 }
    defer numbers.Free()

    for i := 0, i < 10, i = i + 1 {
        numbers.Push(&i)
    }
}

Final Thoughts#

The "perfect language" is a myth because every programmer's brain is wired differently. Zinc is simply a mirror of mine.

Building it hasn't necessarily made me a better programmer, but it has made me more aware of how I think. If there is any value in sharing this, it's not to encourage others to use Zinc, but to encourage others to ask: If you could stop translating your thoughts, what would your code actually look like?


Note that the code of the Zinc compiler is private and still in development. I'll publish the code when i make it self-hosted. For now the compiler is written in C and uses LLVM IR.


← back to home · rss