Remix.run Logo
josephg 10 hours ago

> Go is a better Rust. Rust is an ugly version of C++ with longer compile times and a band of zealous missionaries.

Eh. There's a lot I like about Go. I adore its compilation speed and the focus on language simplicity. But its got plenty of drawbacks too. Default nullability is a huge mistake. And result types (zig, swift, rust) are way better than go's error handling. Sum types in general are missing from Go, and once you start using them its so hard to go back. Go also doesn't have anywhere near as good interop with native code. Mixing C (or any other LLVM langauge) with rust is easy and feels great. You even get LTO across the language barrier.

The big thing I'm growing to dislike about rust is how many transitive dependencies a lot of projects end up pulling in. Its very easy to end up with projects that take a million years to compile & produce huge binaries. Not because they do a lot but simply because everything depends on everything, and the dependency tree takes a long time to bottom out. I don't know what the right answer is. It feels more like a cultural problem than a language / ecosystem problem. But I wish rust projects felt as lightweight and small as most C projects I've worked with. I'm doing some work with the stalwart email server at the moment (written in rust). Stalwart is a relatively new, well written email server. But it somehow pulls in 893 transitive dependencies! I'm not even joking. Compiling stalwart takes about 20 minutes, and the compilation process generates several gigabytes of intermediate build assets. What a mess.

nicoburns 10 hours ago | parent | next [-]

> Compiling stalwart takes about 20 minutes

20 minutes! What hardware is this on? I've worked on Rust projects with similar numbers of dependencies where the compile time (for a clean release build) was 2-4 minutes (on a MacBook M1 Pro)

nicoburns 6 hours ago | parent [-]

UPDATE: tried compiling stalwart on my machine, and it took 14 minutes, with a really weird timing profile:

- 99% of the ~700 crates were done compiling in about a minute or 2 - RocksDB (a C++ dependency) was 2 minutes by itself - And then it took 10 minutes (ten!) just for the final binary at the end.

That's not normal for Rust code at all. Even large ones like Servo or Rustc or Zed.

UPDATE2: turns out they have LTO enabled by default. Disabling that brings the compile time down to 7 minutes. But that's still really unexpectedly slow.

josephg 4 hours ago | parent [-]

Disabling codegen units = 1 speeds up the compilation further. But it’s still too many dependencies and too slow. The binary is pretty huge too.

nicoburns 2 hours ago | parent [-]

> But it’s still too many dependencies and too slow.

I definitely agree that it's too slow. I just don't think the cause is "too many dependencies" because I've compiled Rust codebases with twice as many dependencies in half the time!

It seems to produce a 94MB binary. So it may be partly that there are some very big dependencies. But the amount of compilation time that ends up in the top-level crate (even with LTO disabled) also makes me feel like this must be triggering a compiler bug. Either that or using far too many generics.

9rx 8 hours ago | parent | prev [-]

> Sum types in general are missing from Go

Not quite. It doesn't have tagged unions, which is what I expect you are thinking of, but it does have sum types.

josephg 4 hours ago | parent [-]

Only by abusing interface {}. The result is horrible.

Go doesn’t have sum types as a first class primitive.

9rx 4 hours ago | parent [-]

Using interface as it was designed to be used offers first-class sum types. Although not all interface use equates to sum types.

But they're not tagged unions. I expect that is still where your confusion lies. Tagged unions and sum types are not equivalent. Tagged unions are a subset of sum types.

josephg 2 hours ago | parent | next [-]

This may be a bit too pedantic, but I consider interface {} to be a way to do polymorphism via type classes. Interface defines an open class of types which implement some interface.

Sum types are a type definition defining something as A or B. Not “anything that quacks like a duck”. But concretely “one of this or one of that”. This enables different syntax, like the match expression to be used, in which you exhaustively list all the variants. The compiler doesn’t need to heap allocate enums because it knows the maximum size of a single value. The compiler and programmer can take advantage of the knowledge that there’s a closed set of values the type can hold. It’s not an open type class.

Result and Option are quite beautiful as sum types. But they’re horrible as type classes. Try implementing them using interface{} in Go. It’s not the same.

9rx an hour ago | parent [-]

> Interface defines an open class of types

But can also define a closed set of types, perfectly satisfying "sum types".

> This enables different syntax, like the match expression to be used, in which you exhaustively list all the variants.

Go does not provide this out of the box, but that is not prerequisite for sum types. The mathematical theory says nothing about "the compiler must produce an error if the user doesn't match call cases". There is sufficient information provided by the sum types if you wish to add this to your build chain yourself, of course.

josephg an hour ago | parent [-]

By that definition C has sum types too.

This argument feels like the “we have sum types at home” meme. Ergonomics matter.

I write a lot of rust. Rust has traits which are similar to Go’s interfaces. But the features aren’t the same, and I use enum all the time. (I also use trait all the time, but I use trait and enum for different things).

lock1 3 hours ago | parent | prev [-]

I'm curious, if tagged unions are a subset of sum type, what is your definition of "sum type"?

AFAIK, tagged union is sum type, based on sum type mathematical definition.

9rx 2 hours ago | parent [-]

On second thought, I agree with your definition. So Go does, in fact, have tagged unions.