Zinc - A pythonic take on Rust

Zinc [WIP]

The Itch

After taking a compiler course in my graduate program, I was surprised at how easy it was to design a working syntax. While opcode generation, register allocation, and compiler optimizations can be quite difficult, designing a syntax that is easy to read and write is not.

I got this strange “itch”. The itch started while taking compilers at GaTech. Rather than design a programming language and immediately have to deal with very hard problems, I wanted to start by writing a language that compiles into another one—similar to how Zig compiles into C. I’m a huge fan of Rust, its borrow checker, and its safety guarantees. I personally believe Rust represents one of the most important steps forward in computing.

So I decided to create a language called Zinc, which compiles into Rust.

The Philosophy of Zinc

The three influences behind Zinc are Python, Rust, and Go. This is reflected in three somewhat controversial design choices of mine.

The most important principle of Zinc is readability. Let’s be honest: if you’re interested in using a language like this, you’re probably not in a “performance at all costs” situation. That said, I will never carelessly sacrifice performance or introduce wrappers or non-zero-overhead abstractions. These are, admittedly, easy words to speak for a toy language that doesn’t do much yet.

As LLMs get better at writing code and we inevitably become bonsai gardeners, I believe the importance of readability will only increase. We need to start thinking about what reading code looks like for a generation of engineers who may regard writing code the way we now regard writing assembly.

Talk is cheap, Show me the code

Here we will go through some examples of Zinc code and the Rust code it generates.

Hello World

Our classic hello world program in Zinc is given below. The only difference from rust here is the use of print instead of println!.

fn main() {
    print("Hello, world!");
}

Unlike python, we declare functions with fn and use curly braces for code blocks. I am strongly considering adding significant whitespace as an option in the future so that functions can be defined like so:

fn main():
    print("Hello, world!")

Variables and Types

In Zinc, we use type inference heavily, similar to Python. However, we can also explicitly declare types when we cannot rely on inference such as in struct fields. Though I suspect it might be possible to infer field types from first usage, this seems a little too magical for my taste.

fn main() {

    x = 1 // inferred as integer
    print("x: {x}")
    
    x = 3.14 // inferred as float
    print("x: {x}")

    x = "zinc" // inferred as string
    print("x: {x}")

    x = true // inferred as bool
    print("x: {x}")
}

The above Zinc code is actually very close to valid Rust code. Rust allows type inference for local variables and shadowing of variables. The generated Rust code is as follows:

fn main() {
    let x = 1;
    println!("x: {}", x);

    let x = 3.14;
    println!("x: {}", x);

    let x = "zinc";
    println!("x: {}", x);

    let x = true;
    println!("x: {}", x);
}