Ask HN: What's the best “higher level Rust” these days?

61 points by losvedir ↗ HN
After playing with rust, I really like it. However, I generally don't need to write the low level code that justifies the increased complexity of the borrow checker. I would rather just pay the small price for a garbage collector. What language is most like rust, but at a higher level?

To me, these are the things that I like about rust that I'd want to see:

* Getting nullability right, not making the "billion dollar mistake". An integer shouldn't be able to be "null" unless it's part of the type.

* "Algebraic" (Sum and Product) data types. I love Typescript's "integer | null" syntax, but Option<int> or something like that is fine, too. Typed structs/records or tuples.

* Errors as values. Goes naturally with the ADTs, and it feels very robust having the compiler enforce handling all the "Ok(..) | Err(..)" results.

* Focus on the data, defining structs and functions that work with structs. "Traits" as a means of abstraction. Not much in the way of inheritance.

* A nice development experience with a good package manager or other way of finding and using open source code.

* Vibrant and growing community.

* Can develop comfortably on Mac, deploy to Linux. Windows support a "nice to have".

Rust just nails all these things so well, it makes me want to use it even for things that don't seem like a good fit to me, like command line tools or web servers or scripts.

Here are the languages I can think of that might work, but I think they all have some issues:

* Ocaml - Rust's heritage, so it makes sense. But I feel like it's been around forever and hasn't really taken off. And doesn't it still have some sort of concurrency issue?

* Swift - So close to the ideal language, but does it work well outside of the Mac ecosystem?

* F# - Conversely, another great language, but does it work well outside of Windows?

* Java - The last time I used Java in earnest (many, many years ago) it still had the nullability issue, and was too class/inheritance focused for my tastes. I'm not sure if modern Java has improved on this yet.

* Typescript - Is this it? As a language, it actually ticks all the boxes, I think, which surprises me. But as a runtime it's nonexistent, and so you have to compile to Javascript and run on node and all that. I use it and really like it on the frontend. But how is it on the backend for ordinary non-web applications? How's its performance? It feels fiddly to get all the build stuff running, but maybe Deno or Bun is an option here.

* Nim - Kind of off the wall here, but it looks like it might actually be a contender from perusing its docs. But its community is pretty niche, and I don't have a good sense on if its growing or not.

62 comments

[ 2.7 ms ] story [ 115 ms ] thread
Golang is continuing to grow to serve this use case. Just like explicitly handling errors though- you'd be expected to explicitly handle null values if you want to be safe from them
In many ways Golang is much lower level then Rust. So I wouldn't say it handles this case.
I'm not very familiar with rust, would you be able to share an example?
I’m not the parent poster, but I would assume they are referring to Go’s typical usage of for-loops over something like Streams/Iterators/Comprehensions. (And other cases where idiomatic Go uses the simpler option)
I like the idea of Go, except I don't think it quite has the type system that I like. I saw it got generics recently, but that actually wasn't very high on my priorities, compared to being able to say "this value is an integer and definitely not nil", OR, saying "this value is an integer OR it could be nil, and I want the compiler to make sure I'm considering the nil case."

And I appreciate that it has the "errors as values" like I was asking about, though, the types don't really force you to handle them, I don't think?

IIRC, you can't have a nil value, just a nil pointer. I like that because it makes you avoid using nil/null/none as a magic value as a habit (not completely, because yeah, pointers).

But it's definitely no Rust. It's a very small language by comparison. That's a positive for me, but you can't have all the Rust things and that at the same time.

I'm not a "1 language for everything" guy. I tend to choose for the task if given the choice or choose for the team so I'm not stuck being the only one that can maintain it. And for anything more than a toy for myself, I always pick something mainstream.

If I'm slapping something together as a 1-off, I tend to go with Python. If I'm writing a CLI, I tend to use Go and I'm using it more for backend stuff. I'll use Java if I have to, because of the team thing. But I won't like it. For toy stuff, I'll pick a language out of a hat just to learn something new. Every few years I'll use a LISP/Scheme.

In my dayjob I use Haskell + Rust and in many ways I feel like Haskell is a higher level Rust.
Oh, of course Haskell should be on the list, at least from the language/types perspective! What about packages? Last I looked into Haskell (years ago), there was some light controversy over two competing package ecosystems, I think. Has that settled down?
There's never been competing ecosystems. There is and has been one package manager: cabal.

You are probably referring to stack, which is a just wrapper around cabal that makes some decisions for you. For instance, it pins a set of packages to known-to-work-together versions. It also provides a different CLI (cabal's is a bit verbose).

Today, cabal has improved quite a bit (Nix-style builds, for instance), so the pain stack was invented to help with isn't really there anymore. If you invented stack today, it probably wouldn't get the same level of adoption.

But there are plenty of good packages out there. There often are many different packages for the same thing with different approaches (best part of Haskell!), but for core infrastructure the ecosystem tends to settle and rally around one.

Can you elaborate a bit further how you combine both?
C#/F# being Windows languages is plain false; they have been cross-platform FOSS for just under a decade. TypeScript has a good number of features you're talking about, but I still wouldn't use it myself, because it is only a static checker on top of a much worse underlying system, and the static checker has plenty of holes big enough to fit serious problems through. Your best option as far as I can tell is Swift. It checks all the boxes, and in fact cribs quite a bit directly from Rust; its package repository is serviceable, if light on more niche use-cases; and it has supported Linux for quite some time, and recently started supporting Windows (though not the package manager, so not really).

But if Windows support is more important than a little bit of cognitive overhead? Just stick with Rust. It works, it's all the good things with few of the bad, it's more maintainable than anything mentioned, and the best language to write anything in is the language you already know.

I tried to write a little Linux utility that would walk the filesystem in C# a while back.

It convinced me the language doesn't really support Posix systems after all.

(Obviously, Linux software that's been written in C# exists; maybe I was using the wrong standard library or something. I went with whatever Microsoft's documentation suggested.)

Yeah, this is mistaken. At my last job we deployed tons of c# on Linux. The .net ecosystem is a little complicated because of the slow convergence of its std library and runtime (.net core, .net standard, .net framework, .net), but as of a few years ago a bog-standard .net app did everything you’d expect on any computer.
I'd rather believe hedora who has given a concrete example, sorry.
We've been running a production web service on .NET core on Linux for over a year. We develop, compile, and test this service in macs, build the docker containers on linux, and run on Linux.
I define 'concrete' a bit more strictly than 'I tried it and it doesn't work'. No description of what kind of strange behavior was observed. That ticks over between the likelihood Microsoft got their code wrong and the likelihood insert random HN user got their code wrong.
I can't really directly answer your question as I'm looking for pretty much the same—a language like Rust, just easier to use. I could imagine that being a language that transpiles to Rust so I sometimes head over to https://lib.rs to see what's new in the space.

Other than that I'm these days only mildly interested in languages that do not compile to WASM or transpile to JS because NodeJS and the browser have become my main platforms. IMO browsers offer an unprecedented way to develop GUIs for applications. People are unhappy with the perceived bloat of it but there's a web browser on almost each single consumer-facing (i.e. non-backend / server) machine these days and those things have become very, very capable. I just feel deprived of something when I go back to, say, Python.

I'm not very interested in TypeScript anymore b/c for the troubles and the benefits that typing gives you I want to have a solution that works at run time and ideally at compile time, too—I'd rather forgo the latter but not the first b/c of user input validation. Having to entertain that whole fancy and complex beast that is a good type system only to not be able to use it for run time checks is not what I want.

Just a few thoughts.

I’m not sure that any language meets these criteria yet, but I’m placing my bets on Swift. There’s been a lot of activity in Swift’s various cross-platform working groups recently… whether or not that pans out remains to be seen, but within the community there’s absolutely a desire/will for Swift’s cross platform story to improve.

Personally speaking, if Swift’s Windows/Linux support improved and a cross platform UI framework were built for it, I’d have a hard time imagining using anything else for cross platform desktop development. Swift gets a lot right for that use case and strikes a good balance between ergonomics and safety.

Sadly I think swift has missed the boat. Rust has eclipsed it in terms of cross platform and widely use crates. Swift was never big into the cross platform scene and now I think it's missed the boat.

Furthermore swift's lack of macros means you're at apple's behest to have important changes to the language made. Rust didn't come up with serde, arguably one of Rust's "killer apps"; it's a third party package that the language merely makes possible. But in swift, such a thing would be impossible to do at compile time without explicit support from apple. This means that language can only provide functionality blessed by apple.

Swift is a neat language but it's got a huge uphill battle if it wants to become widespread and I just don't see it happening when all it offers over rust is that you don't have to worry about lifetimes.

The biggest issue with Rust is its lack of mature UI frameworks. While several are being developed, they all have major shortcomings of one type or another, and if you want a more traditional "boring" retain-mode cross platform framework your options are even more limited.

And as alluded to in my post, Rust isn't particularly pleasant to use for a UI use case anyway.

Of course there is no cross-platform UI framework for Swift either, but if such a thing were to come into existence I think it'd quickly come to enjoy decent popularity for desktop application development.

I’m with the GP that there’s still a chance for Swift on other platforms. The server-side workgroup seems to be stabling SwiftNIO; so it’s at least being worked on.

I don’t really agree that macros make or break a language though. And swift’s codable generation was a community driven proposal; like all language evolutions have been

Scala

* Getting nullability right - Tick.

* "Algebraic" (Sum and Product) data types. Tick.

* Errors as values - Tick.

* Focus on the data - Tick.

* A nice development experience - Tick (has got a lot better in recent years).

* Vibrant and growing community - Tick. Hard to know if it's growing, but it feels like it has a very stable niche imho. I see a lot of dynamism in the communities I follow like Typelevel.

* Can develop comfortably on Mac, deploy to Linux. Windows support a "nice to have. Tick. I've developed on all of these environments.

Is it a "higher level Rust?". It's a very different beast but, based on your checklist at least, it fits the items them than most other mainstream languages imho.

Doesn't scala inherit type erasure from Java / the JVM though? That means nullability and sum and product types can never be sound in it, and every line can throw some unexpected exception (just like Java), right?

On top of that, the classloader makes an open world assumption, at least with the JVM, so the compiler can never prove me wrong on any of the above points, regardless of what program it's been handed.

Anyway, I've been there and done that with Java, and I'm hoping to never put up with that ecosystem again. I'd much rather fight the borrow checker than deal with null pointer and class cast exceptions in production.

It does, but scalafix helps to avoid some of these issues. I’m not sure how important a very opinionated compiler is to you, but a compiler that does what it can + a good linter isn’t the worst combination.

Once you tap into the Java ecosystem, you can often wrap things into the typelevel ecosystem and work with effects. Generally, Scala with cats/cats-effect is quite pleasant.

Last I used Deno it was about half the speed of Node.
(comment deleted)
> However, I generally don't need to write the low level code that justifies the increased complexity of the borrow checker.

Your criteria are so specific that it will be easier to just learn how the borrow checker works.

Nim is a great language with some killer features, but if the community is growing, it's doing it very slowly.

TypeScript would have my vote. I have my eye on Deno and Bun but they aren't there yet, Node gets the job done every time.

IMHO swift has one of the most beautiful type systems and is more high level than rust. Of course youre limited on what platforms you can use it with but server side swift is getting a lot more attention. Kotlin is pretty comparable though so maybe that’s your best bet. Haskell is also great but if you want the slightly more imperative style then I think kotlin or swift are the best fits. I find all to be the times I have the most fun programming. You can also get part of the way there with typescript and the fp-Ts ecosystem but of course the type system has a lot less overlap with rust
Have you tried wrapping everything in an Rc<> (or Arc<Mutex<>>) and keeping everything synchronous and single threaded? I've seen people report good success with that for low-performance stuff.

Reference counted languages provide 99% of the benefit of full GCs for simple programs, and I think those two suggestions are enough to mindlessly appease the borrow checker in most circumstances.

This is my approach to "higher level rust" - When the borrow-checker and lifetimes start getting too complicated, there's usually an easier but less-optimal fallback. (RC/ARC + mutexes, clones, Strings, Box dyn)

Rust can be a simple(r) language if you're willing to set aside the idea that you need to write performance-critical systems code from day one - just use the basics and optimize if/when needed.

I wish somebody would make a language that looked like ruby/python but all it did was transpile the source file into a rust file with any necessary Rc<>/Arc<Mutex<>> stuff and then run the normal rust compiler.

If you wanted to optimize a file, then just run the "eject" command on a file and then replace the extraneous Rc<>/Arc<Mutex<>> stuff with smart people borrow checker stuff yourself.

I like crystal and nim, but it would be better to have such a language deeply just actually be rust under the hood and use rust libs natively and use crate literally instead of creating a whole new ecosystem.

F# is not a windows language. It's been open source from the start and there are many compiler implementations that does not depend on Windows at all, like Fable.

That said I think it has many parallels with Rust and I enjoyed reading this comparison between the two https://github.com/Dhghomon/rust-fsharp

It shows how strikingly similar the two languages are, at least on the surface.

Unfortunately I have to agree with your inner voice that there's no perfect language out there.

I'm building my first library in OCaml for a number of great reasons: 1. the type system we all want 2. multicore is here (but in RC) 3. algebraic effects is here (but in basically RC) 4. build system is pretty good after you warm yourself up a bit. i ran into some weird dep issues recently but overcame it by vendoring deps directly.

A hard requirement for me is to be able to generate native executables, and only OCaml / Rust / Swift has this with a more modern type system. If I wasn't so focused on learning in an fp language, I'd use Rust for my needs.

If you asked for a vote, I'd say give 2 weeks for each of your top 3 languages. Opinions and anecdotal experience will always be beaten by personal experience.

F# and C# also support native executables
I'd recommend going full steam ahead with Rust.

Companies like Azure and Meta now recommend Rust as either one of several viable choices, or THE recommended option, for the use cases you describe, including CLI and web servers.

Personally at this point, just about anything that's not a frontend (android, ios, web), I think Rust is the best language for the job. It's the language I prefer, and feel most productive in, for the use cases you're describing (web server, CLI, small scripts and utils).

There's a learning curve, but at this point, I almost entirely forget about the lack of GC. It feels like writing code in any other language - except of course with all the benefits you call out (no null, ADT, typed error values, trait-based OO system, the best tooling of any language I've used, expansive 3rd party libraries).

It's crazy how many good GUI libraries there are as well, I've played around migrating some heavier applications and the experience was absolutely painless.
Which ones do you mean? That is the first time I've had someone express a positive sentiment regarding the state of GUIs in Rust.

I would say that I've tried most of them and they are all varying levels of aweful right now.

I'd recommend that only for those who know that they are in the top 5% or so of programmers (as HN is almost by definition a self-selected DK hothouse, I realise this can be a hard judgement to make, but a bit of self-reflection can go a long way). I'm a below to average programmer, and don't believe I'll ever be able to understand Rust well enough to use it productively. I have my reasons to nonetheless still be teaching myself Rust, but expected fluency is not among those reasons. Rust is just too hard for most programmers, and literally every non-'star' I've known (other than myself!) has given up on it.

If, self-deception aside, you really are a superior programmer, it's an excellent tool, a joy to use, and is going to be a good choice for an increasingly wide spread of domains.

Nice thread. The comment about "full steam ahead with Rust" was nice.

Like you I've been on a basically identical search, but instead of coming up from Rust I've come down from dynamically typed languages on the backend. I just couldn't bear another run-time error that would have been easily prevented with Typescript.

I've come to the conclusion that the palette of ergonomic and statically typed languages for the backend is surprisingly quite sparse (when one includes the requirement of a relatively sizable community) if you don't include Java/Go.

Languages I personally crossed off:

* Java - Just much too verbose for my taste, too mutable, too nullable, and I don't find "Kingdom of Nouns" elegant to program in. (The JVM is quite magnificent though..)

* Golang - I get why people like it, and I have absolutely nothing against it. For me it's not expressive enough.

* Typescript - Pretty much 100% what you said. The language itself is wonderful, but it really shines when confined to the browser. The runtime differences between the Browser/Node cause all sorts of problems. Also compiled binaries are nice, which are out here.

I see why somebody might choose each of those languages, and be highly justified in doing so, but for me they didn't fit the bill.

That leaves:

* Scala - Only have very light experience. Really strong support for functional/immutable style programming, and checks all the boxes as another commenter pointed out. Community seems a bit complex at times. I haven't had time to really dive into it, but it's been on my mind.

* Rust - Seems like it nails a lot of things really well. Haven't ever touched it, so I can't give an educated appraisal of its merits. The only real obvious con is that it's "overkill" for systems dealing primarily with business logic, i.e. where GC isn't a big issue.

* Swift - Seems quite excellent, but I had written it off as unsuitable for general purpose stuff. Perhaps a premature take?

* Nim - The one on the list I have played around with the most. Seems to have a very powerful macro system which I have yet to take full advantage of. Feels like a statically-typed python, with a bent towards an imperative style over a more functional one. I am personally thrilled they went with the decision to make "func" a fist-class keyword, with special compiler significance. Still a bit niche however.

You should check out Kotlin if you like Swift (but think it's not general purpose enough) and the JVM!
Huh, Kotlin had almost completely slipped my radar but seems interesting from a quick glance now. I had always pigeonholed it as an "Android" language. Does it have general use?
I’d also be interested in peoples replies. I know there is a creative coding framework built on it (haven’t used it though) https://openrndr.org/ .
Keep in mind kotlin is 100% compatible with java/jvm, so anything Java can do it runs on, kotlin does as well. Kotlin did get it's momentum from Android, but it's matured quite a bit. IMO it's a excellent language.
It runs anywhere the JVM does.

It has a pretty big ecosystem for all sorts of stuff, but on top of that it has native Java interop, giving you access to a huge selection of libraries.

There's also Kotlin Native, which compiles to native code using LLVM and ways to compile it to JS.

Basically, it runs pretty much anywhere.

According to dev surveys, usage is pretty evenly split between mobile and backend, but you could even do desktop or web apps with it if you wanted.

> The runtime differences between the Browser/Node cause all sorts of problems.

Fixing this is pretty much the major pitch of Deno.

I learned F# before I took a dive at rust, while I don't have use cases for rust on my day to day I think these two languages are very similar and you can fare a very long way with what you already know from rust it is worth taking a look at it

F# is cross-platform with excellent tooling from vscode via ionide extension, jetbrains rider, and visual studio, it also ticks several boxes from above :)

Feel free to chime in twitter there's the #fsharp hashtag, The F# Org slack https://fsharp.org/guides/slack/ and the unnoficial F# discord server https://discord.com/servers/fsharp-196693847965696000

C# is a good option.

F# if you want functional C#.

Scala if you are adventurous.

C# is great, if you ignore the lack of algebraic data types and the generally weak type system. Value types in C# are awesome and really make it easy to write super high performance code that doesn't allocate!

F# is awesome if you ignore the fact that it's so easy to shoot yourself in the foot from a performance standpoint with all the lazy constructs. It sure is awesome though to have such a strict type system, though I do sometimes miss features that Typescript has, such as structural typing and mapped types.

In general I do thing the dotnet ecosystem offers a really good set of compromises.

What do you mean by lazy constructs? F# is not lazy by default but opt-in using seq.
Agreed. I really like TypeScript type system. I wish there was a language that used .NET but used TypeScript syntax/semantics. Maybe deno?
Have you looked at Zig? Can’t say if it ticks all the boxes but it looks like a pretty interesting language.
Bit left field, but I think Haxe is a good "higher level rust".

* It's garbage collected

* Has algebraic types (enums similar to rust's)

* Pattern matching as good as rust's

* Null check strictness can be configured with the compiler

* Package manager

It's somewhat OOP geared though.

Personally, Nim fits a sweet spot for me. It has most of the items you listed, and makes a lot of projects interesting to me much more fun to program. It makes writing CLI utils and quick data migration scripts easy and fun. I use it for embedded software for the great balance of productivity vs performance.

I’ve been working to build more libraries and community for embedded software. It’s been slow going but a couple of companies are using it in shipping products. Nim leverages existing C code very well though.

Though Nim does have its downsides, primarily a smaller community. It’s small but active and not going away anytime soon. I think the ecosystem is slowly building a solid foundation of core libraries since the language reached 1.0 and found its feature set. But it doesn’t have a “killer” feature or single library to drive it. So hopefully the slow and steady buildup can keep going. For example, one big investor in Nim ecosystem for example just hired someone to work full time on the language server / tooling. There’s more work going on with numerical processing too (eg native graphing libraries, etc).

For me in many areas the language more than makes up for the smaller community. I can wrap C/C++ libraries quickly and I don’t need to do borrow checker. Though I would say it sounds like Go or Swift might fit some of your preferences too. Rust is great in many ways but I am more productive with “compile time duck typing” while Rust favors traits and fixed predefined abstractions.

> After playing with rust, I really like it. However, I generally don't need to write the low level code that justifies the increased complexity of the borrow checker. I would rather just pay the small price for a garbage collector. What language is most like rust, but at a higher level?

I think you've mixed yourself up somewhere here. A garbage collector doesn't protect you from stale references, it just makes sure that it results in a program crash (or exception of some sort) rather than letting your program run off into fantasy lands where pigs fly and everyone casts magic spells. Rust still protects you by turning these errors into compile time errors, saving debugging time.

I would recommend taking a look at Julia. What it can't do is "get nullability right at compile time" however you can define Integer values which can't be zero and have that changed at run time with more and more of these checks moving to compile time but still raising errors at run time, "force you to look at values" but you can throw arbitrary stuff which is nice. Julia has the rest you wish for though. Julia has nice macros, has multiple dispatch instead of inheritance, a vibrant and growing community. It is mostly used in numerical work however use it on other domains has also shown a lot of promise, at the moment producing standalone binaries is still complicated, it pays compile time every time a function is called with new types.