Remix.run Logo
YuechenLi 5 hours ago

>Combined with the Rust rewrite, ICU changes, and identical code folding, Bun's binary size shrinks by ~20% on Linux & Windows.

People who are surprised by this probably has not seen what Zig code actually looks like. Zig's explicitness and lack of abstraction have a real cost that it is basically one of the most verbose programming languages I've ever seen, it's somehow even more verbose than Go. Basic features of modern languages like pattern matching and generics, and as you can see, having to manually clean up everything means that if you forget once, it's a memory leak. Having SOME abstraction is actually good if it prevents you from making mistakes.

Ironically, Zig is a programming language that's probably best written by LLMs, since they can actually tolerate the verbosity.

benced 5 hours ago | parent | next [-]

Not a compiler expert - shouldn't language verbosity and binary size be, at best, very loosely related?

steveklabnik 5 hours ago | parent | next [-]

I don't think you can draw the conclusion that source length and binary size are correlated. For example, in Rust:

    #[derive(Copy, Clone)]
    enum Expr {
        Int(i32),
        Add(i32, i32),
        Neg(i32),
    }
    
    fn eval(expr: Expr) -> i32 {
        match expr {
            Expr::Int(x) => x,
            Expr::Add(a, b) => a + b,
            Expr::Neg(x) => -x,
        }
    }
Rust's enums can carry data. You can write the same thing in C, but because it does not have the enum feature, you have to do it yourself. They're sometimes called "tagged unions" for a reason, you use a union + a tag when doing it by hand:

    #include <stdint.h>
    
    typedef enum {
        EXPR_INT,
        EXPR_ADD,
        EXPR_NEG,
    } ExprTag;
    
    typedef struct {
        ExprTag tag;
        union {
            struct {
                int32_t value;
            } Int;
    
            struct {
                int32_t left;
                int32_t right;
            } Add;
    
            struct {
                int32_t value;
            } Neg;
        };
    } Expr;
    
    int32_t eval(Expr expr) {
        switch (expr.tag) {
            case EXPR_INT:
                return expr.Int.value;
    
            case EXPR_ADD:
                return expr.Add.left + expr.Add.right;
    
            case EXPR_NEG:
                return -expr.Neg.value;
        }
    
        __builtin_unreachable();
    }
I haven't actually compiled this, but it should compile to almost the exact same, if not literally the exact same, machine code. Yet one is way more verbose than the other.
pavon 5 hours ago | parent | next [-]

I think you are saying the same thing as benced - just because Zig source code is verbose is no reason to assume the binary should be larger.

steveklabnik 5 hours ago | parent [-]

I read my parent ask asking a question: is there a correlation, or not?

I am saying that I do not believe there is a correlation between source code length and binary length. If that's what benced meant by their question, then yes, I agree :)

esjeon 5 hours ago | parent [-]

I’m quite sure there is a certain amount of correlation unfortunately, mainly because there are micro patterns (e.g. IO, allocator) that can’t be modularized into functions. Lots of manual copy-pasta.

14113 5 hours ago | parent | prev [-]

It required a little bit of messing with optimisation settings and library generation in Rust, but they emit very very similar x86-64 assembly:

https://godbolt.org/z/89W4srz4d

steveklabnik 5 hours ago | parent [-]

Nice, thank you for picking up after my laziness. Surely only a few bytes different in the binary, and much, much smaller of a delta than the source.

aw1621107 3 hours ago | parent [-]

You can further reduce the difference by passing Expr by pointer in the C version. At that point I think the only difference in the assembly is the order in which the cases are handed.

steveklabnik 2 hours ago | parent [-]

Ah yeah, honestly both should probably be passed by pointer anyway. But that makes me wonder about the actual differences here and why... maybe something fun to dig into.

YuechenLi 5 hours ago | parent | prev | next [-]

Fair point, I phrased that too broadly, and you are right about the loose correlation.

What I was gesturing at, badly, was more that Zig’s low-abstraction / explicit-by-default syntax tends to have you write more boilerplate-y code in general that are more annoying to write and maintain, while not buying you enough over a language with better tooling and ecosystem and compiler optimization like Rust.

surajrmal 4 hours ago | parent | prev [-]

Why? Python is terse but has large binaries because of the runtime overhead. C++ is fairly verbose but can make useful binaries in double digit kib.

giancarlostoro 5 hours ago | parent | prev | next [-]

> Ironically, Zig is a programming language that's probably best written by LLMs, since they can tolerate actually tolerate the verbosity.

Rust in my opinion feels the same.

pjjpo 4 hours ago | parent | next [-]

I have found LLMs struggle with Rust's constraints - they are optimized to produce code that passes the tests, not necessarily good code. So instead of working out lifetimes and borrowing, it will be happy to copy a buffer many times without thought. This means I have to still go through line by line to review and often rewrite either by hand or with another LLM iteration.

There may be some prompting that can help with this but I suspect there is a fundamental tension between writing working code vs good code in LLMs. Go is popular for being simple, making it easy to jump in and write something fast and stable - minimizing the gap between working and good code probably helps out the LLMs a lot.

afavour 5 hours ago | parent | prev | next [-]

I don’t feel the verbosity with Rust. Haven’t written it in a while but now in the LLM era I’m looking forward to saying “sort out the lifetime errors for me”.

i_am_a_peasant 5 hours ago | parent [-]

I trust a lot more Rust code generated by an LLM than anything else ngl.

sroussey 5 hours ago | parent | prev | next [-]

Agree. But just because it feels the same doesn’t mean it compiles the same.

honeycrispy 5 hours ago | parent | prev [-]

That can often depend on how you write it.

tapirl 4 hours ago | parent | prev | next [-]

Zig is indeed verbose in some aspects, but not overall. For example, its `try error-union` syntax eliminates a lot of boilerplate code.

The main reason why Zig is verbose in some aspects is the main goal of Zig is program performance. It is a worthy tradeoff.

typ 4 hours ago | parent | prev | next [-]

Abstraction doesn't necessarily lead to a smaller binary. Much of the bloat in modern software is indeed due to (bad) abstractions.

reinitctxoffset 4 hours ago | parent | prev | next [-]

The twenty percent quoted is referring to the size of the compiled artifact (one assumes ELF or Mach-O).

Whether or not a language is verbose or obscure is very much about your coordinate system. Not unlike safety.

I think C is a reasonable zero for both things.

Zig is more succinct and safer than C while still being comparably ergonomic. Rust is (mostly) safer and more succinct than Zig while being dramatically less ergonomic (take it up with Wadler memory chads, no one likes affine types).

I like lean4, which is dramatically safer, more succinct, and more ergonomic than Rust.

But I can see why some would say it's a bit too succinct.

metaltyphoon an hour ago | parent [-]

> Rust is (mostly) safer and more succinct than Zig while being dramatically less ergonomic

This is just your opinion.

bsder 5 hours ago | parent | prev [-]

Naive was 4-9% on the initial pass.

Also note that the larger percentages were against already smaller binaries. That smells like there was a single large constant number that got saved somewhere rather than general improvements.

> After that initial shrinkage, the team explored more opportunities for binary size reduction using linker optimizations like Identical Code Folding, removing unused data from ICU, and lazily decompressing small parts of libicu with a zstd dictionary on-demand.

I'd be VERY interested in seeing what the individual effects of those parts were.