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 * 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.
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.
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.
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).
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.
* 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.
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.
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.
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.
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
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.
Your criteria are so specific that it will be easier to just learn how the borrow checker works.
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.
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.
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.
* 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.
F# if you want functional C#.
Scala if you are adventurous.