Obviously i'm biased, but i quite enjoy it. I find i am more efficient now than before, because it manages to give me the ease of the "easier" languages quite often with a rich set of tooling when i need to go deeper.
Personally i feel the concern over the borrow checker is way overblown. 95% of the time the borrow checker is very simple. The common worst case i experience is "oh, i'm borrowing all of `foo` when i really want `foo.bar` - which is quite easily solved by borrowing just the `.bar` in a higher scope.
The lifetime jazz is rarely even worth using when compared to "easier" languages. Throw a reference count around the data and you get similar behavior to that of them and you never had to worry about a single lifetime. Same goes for clones/etc.
I often advocate this. For beginners to use the language like it was a GC'd language. A clone or reference count is often not a big deal and can significantly simplify your life. Then, when you're deeper into the language you can test the waters of lifetimes.
So yea. Your mileage will vary i'm sure. But i find Rust to be closer to GC'd languages than actual manual languages, in UX at least. You won't screw up and leak or introduce undefined behavior, which is quite a big UX concern imo.
- The borrow checker is one of the easier parts of Rust to grok, it's just as you say, not that complicated in the end.
- Traits are more annoying to understand and find in source code when they can get added from anywhere, and suddenly you code gets extra functionality, or it's missing the right one unless you import the right crate but there is no "back-reference" so you're not clear what crate the code actually comes from.
- Crates/libraries are harder to grok with their mod.rs/lib.rs files and whatnot, in order to structure your application over many files.
- Macros are truly horrible in Rust, both to write and debug, but then my only experience with macros are with Clojure, where writing macros is basically just writing normal code and works basically the same way
- Compilation times when you start using it for larger projects tend to be kind of awful. Some crates makes this even worse (like tauri, bevy) and you need to be careful on what you add as some can have a dramatic impact on compilation speed
- The async ecosystem is not as mature as it seems on first look. I'm really looking forward to it all stabilizing in the future. Some libraries support only sync code, others only async but only via tokio, others using other async libraries. Read somewhere that eventually it'll be possible to interop between them, time will tell.
- Control flow with Option/Result and everything that comes with it is surprisingly nice and something I'm replicating when using other languages (mainly a Clojure developer by day) now.
My development journey was PHP -> JavaScript -> Ruby -> Golang -> Clojure with doing them all the capacity of backend/frontend/infrastructure/everything in-between, freelancing/consulting/working full-time at long term companies, so take my perspective with that in mind. Rust would be my first "low-level" language that I've used in larger capacity.
I have been using Rust professionally as well and had a different experience. For anything singlethreaded I agree with you. For any asynchronous behavior, whether it's threads or async or callbacks, the borrow checker gets very stubborn. As a response, people just Arc-Mutex everything, at which point you might as well have used a GC language and saved yourself the headache.
However, enums with pattern matching and the trait system is still unbeatable and I miss it in other languages.
I know dealing with the borrow checker can feel like a dead-end sometimes, but dealing with it is something that you can learn. Things that it can't handle fall into a handful of common patterns (like self-referential structs). Once you learn what not to do, you can recognize and avoid the issues even before you write the code.
There's very little allocation since it supports returning VLAs (like strings) from functions via a secondary stack. Its Alire tool does the toolchain install and provides package management, so trying the language out is super easy. I've done a few bindings to things in C with it, which is ridiculously easy.
> Is it worth learning
Languages are easy to pick up once you understand fundamentals. The borrow checker is intuitive if you have an understanding of stack frames, heap/data segment, references, moved types, shared memory.
You then should be asking "Is it worth using?", then evaluate use cases.. pros/cons.. etc.
For CLI, Rust is likely the easiest given it's macros, but if you struggle with the borrow checker then it won't be. You will be fighting the compiler instead of developing something.
Depending what your CLI program is doing, you might want to evaluate what libraries are available, how they handle I/O, and parallelism.
JavaScript has incredibly easy and fast concurrent I/O thanks to libuv and v8.
> Languages are easy to pick up once you understand fundamentals. The borrow checker is intuitive if you have an understanding of stack frames, heap/data segment, references, moved types, shared memory.
I see this sentiment often. In the last 10 years, I have come up a level in raw C, learned Kotlin, Swift, Python, Elixir/Erlang, and a smattering of JavaScript, all coming from a background that included Fortran and Smalltalk.
My problem with the dialogue is what is meant by “learn.” I have architected, implemented, and maintain different components of our products in all these languages currently. I think that demonstrates I have “learned” these languages, at least at this level of “picked up.” But I can’t write Python the way Brett Canon does. Or Elixir the way Jose Valium does. Or any of their peers. And in that regard I still very much feel I have not “learned”.
I spent a couple days playing with Zig a month or so ago. I became familiar with the way it worked. I could spend another month or so in that phase, and then could probably comfortably accomplish things with it. But I don’t think I’d feel like I’d “learned” it.
It reminds me of my experience learning Norwegian. I lived in Norway for 2 years and did my best to speak as much Norwegian as I could. At six months I could definitely get by. At 13 months, as I embraced the northern dialect, I was beginning to surprise Norwegians that I was from the states. I started dreaming in Norwegian at some point after that. But even at 24 months, able to carry on a fluid conversation, I realized I still could “learn” the language better than I currently knew it.
So I guess, it always seems there needs to be more context, from both the asker and the answerer, when this “should I learn X” discussion is had. Learning is not a merit badge.
For a language that is around 8 years old, this may be a serious problem, since the surrounding ecosystem has been probably written without parallelism in mind, and it may take a very long time to be updated (if ever).
They do, but it is hidden inside a compiler flag, if you compile your prject with `Dpreview_mt` then it will come with multi-threaded support. This has been an experimental feature for a few year though, and there is not much improvement since it first got introduced.
Personally I don't use crystal for this kind of feature, and it runs stable enough when I use it for some cpu intensive tasks when I rarely need it.
Crystal really shines when you need something that you usually write a python/ruby script to do, especially for tasks that run for hours. Converting some script from ruby to crystal and run it in production mode typically reduce the time consumed to 1/5 or even 1/10 of the original depends on the job. As someone who have to read gigabytes of text files regularly, Crystal is currently the best one for the task.
The compilation time for released binary is something need much improvement though. And I'm not sure if they can even achieve incremental compilation.
It depends on the domain. From a production perspective, an flag-gated functionality that has been experimental for two or more years, is not "built-in". Plus, as explained, the ecosystem (I think I've read even the stdlib) doesn't give guarantees about thread safety
For small-scall scripting, then sure, it could be useful - but anything will do. I've evaluated for use at my company, and discarded it, because of the lack of libraries. Sadly, this is a chicken-and-egg situation. I've also evaluated contributing to it, but I won't until multithreading is stable.
Well this might be the problem. In corporate environment you can't afford to be too adventurous.
Personally I solve the "lack of libraries" problem by using more than one language, then connect them via child process call or some persistent storage like database or plain text files.
But it's entirely a different matter when the code need to be used by a lot of people.
For the cases mentioned, I think crystal is immensely helpful: - Reading/writing files are easy, usually a single method will give you the result you want. - Working with directories are nice, things like `Dir.mkdir_p`, `Dir.each_child`, `File.exists?`... all existed to make your life easier. - Like ruby, you can invoke shell command easily using backticks - There are some useful libraries to for console app, like `colorize` or `option_parser`. Crystal is a battery included language, so the standard library is filled with useful libraries. - Working with lists and hashmaps is a breeze, since the Enumerable and Iterable modules are filled with useful methods, mostly inspired from ruby land. - Concurrent is built in, so you can trivially write performant IO-bounded tasks like web crawlers.
For a project that made by a handful of people, I just can't praise the dev team enough for making a language this practical.
When I tried to work with Rust a few years ago I found it utterly impenetrable. I just had no idea what the borrow checker was doing, did not understand what the error messages meant, and honestly couldn't even understand the documentation or the tutorials on the subject. Understanding what is happening in C or Zig is pretty easy; in Rust it's always been a nightmare for me. I just really don't grok the "lifetime" concept at all, it feels like I'm trying to learn academic computer science instead of a programming language.
Rust feels to me like a powerful, expressive language for professional programmers at the top of their game. That's a complement for any language. But it comes at the cost of mind-numbing complexity for anyone who's not an expert.
The official Rust book is targeted at novices with some programming experience. There's also Rustlings https://github.com/rust-lang/rustlings for a more practical approach.
> When I tried to work with Rust a few years ago I found it utterly impenetrable. I just had no idea what the borrow checker was doing, did not understand what the error messages meant, and honestly couldn't even understand the documentation or the tutorials on the subject
The compiler diagnostics have improved a lot over time. It's quite possible that some of the examples you have in mind return better error messages.
> in Rust it's always been a nightmare for me. I just really don't grok the "lifetime" concept at all, it feels like I'm trying to learn academic computer science instead of a programming language.
Academic computer science calls lifetimes "regions", which is perhaps a clearer term. It's a fairly clean extension of the notion of scope that you'd also find in languages like C or Zig. It's really not that complex, even though the Rust community sometimes finds it difficult to convey the right intuitions.
The language team has done a great job rounding rough edges, and this next roadmap is slated for even more polishing. They heavily prioritize dev experience which is why i think people like myself (a GC'd language person historically) use and love Rust so much.
Any programming language is good enough for their own use cases :) It's a matter of understanding which the use cases are.
I'm a big Rust fan, but I nonetheless believe that the use cases for programming languages with manual memory management are comparatively small, in particular, since GC has been improved a lot in the last decade.
For undecided people, I conventionally suggest Golang. Those who at some point need deep control, will recognize it and change accordingly.
My question could be further constrained then to be, is learning Rust or Zig despite its manual memory management worth it for applications that are normally already garbage collected in their current implementations? Or are languages like Nim and Crystal enough? Does Rust and Zig have other benefits despite manual memory management?
Zig has no garbage collection btw, but makes it easier than C to handle that. Another language without garbage collection that helps a lot to avoid memory issues is Ada (Looks a bit like Pascal). So there are alternatives to Rust.
But most software are throughput-oriented.
Nim’s ARC system also doesn’t use atomic or locks which means it’s runtime overhead is very low. I use it successfully on embedded devices for microsecond timed events with no large tail latencies.
I wouldn't buy into much Apple marketing regarding its performance though,
https://github.com/ixy-languages/ixy-languages
It makes sense in the context of tracing GC having been a failure in Objective-C due to its C semantics, while automating Cocoa's retain/release calls was much safer approach. Swift naturally built on top of that due to interoperability with Objective-C frameworks.
Nim has taken other optimizations into consideration, however only in the new ORC implementation.
Still, all of them are much better than managing memory manually.
I wouldn’t make claims on Swifts overall performance, but just it’s memory usage (really Obj-Cs) and particularly for GUI development. Java’s GCs have always been very memory hungry, usually to the tune of 2x. Same with .Net. Though to be fair Go’s and Erlang’s GCs have much better memory footprints. Erlang’s actor model benefits it there.
Agreed, they’re all better than manual memory management.
Nim also can double as a web language by transpiling to JS.
Then you can automate code generation with the sublime macros, which are just standard Nim code to create Nim code. No new syntax or special rules required - any Nim code can be run at compile time or run time, so you can use standard/3rd party libraries at compile time to write macros and give the user a slick syntax whilst removing boilerplate.
I really miss languages without straight forward metaprogramming after using Nim. It's something that multiplies the power of a language, rather than just adds to it.
There are no fast languages, only fast language implementations.
NodeJS/V8 is pretty fast (even faster than you probably think)—particularly if you're already doing things like making the sort of compromises where you limit yourself to writing only programs that can be expressed under the TypeScript regime. It's usually the case that it's not the NodeJS runtime that is the problem but rather the NodeJS programming style that is the source of the discomfort with "speed" that you will have experienced.
https://coalton-lang.github.io/reference/
I don’t think it has sum types, but it offers a rather sophisticated and very functionally oriented type system that compiles back to SBCL. It looks neat, haven’t found time to play yet…
In addition, such detractors were acting like a brand new programming language would be a finished polished product from day 1, when that was not the case for their far older languages. For instance, Odin and Crystal are older than Vlang, but has been surpassed by it in various respects.
And don't get me wrong, I like Odin. That's because Odin is among the newer breed of languages that have continued the trend in which Go started of non class-based OOP and more generalized OO, that are contenders to be alternatives to C/C++ (like Zig).
In the case of Vlang, it has clearly been developing rapidly and consistently, and continually gaining in popularity. Simply looking at their releases (and release schedule) along with their documentation (which various newer contenders are lacking in even that), will show a lot of the controversy is without merit or distortions of language development reality.
https://github.com/vlang/v/releases https://github.com/vlang/v/blob/master/doc/docs.md https://modules.vlang.io/
If a person does their research, then they would have clarity on the subjects. This is partly why I think it is better to refer people to the website or advise them to do their own evaluation, as oppose to reference critics or 3rd party websites who may have hidden motives and are advocates for a competing language. I think the competition between the newer languages has come to that point.
Are you sure about this? Let's look at their first feature, "No null". Ironically every vlang project including compiler itself is full of `isnil`[0] check.
This is just trailer for their lies; once start digging you'd find misrepresentation in every major features they advertise.
Vlang has both the options of Autofree and GC, or you can do it manually. As the language is still developing (like other languages such as Crystal, Odin, and Zig), users should know these memory management options are experimental and still being refined.
However, since you're just interested in writing a fast command-line app, what about the JVM or .NET? Those have a startup time issue, but once they're running they're very fast, less than an order of magnitude slower than C/Rust/Zig/etc.
When using C, if you want something from a higher-level language, usually you'd just do what that language's runtime does internally. So for a vector of heterogeneous objects, you could do a linked-list of pointers to the data objects. Something like:
struct VecItm { struct VecItm next; void *data; size_t data_len; char objTypeId; };
Is it more work? Yes. But at this point I would stop and consider if I really need a heterogeneous collection. Do we really need to store a list of objects with arbitrary sizes? What if we have 3 types of objects, in 3 arrays, and we store index into those arrays? struct Foo { unsigned byte x; };
struct Bar { unsigned word x; };
struct Baz { unsigned long x; };
struct Foo foos[256];
struct Bar bars[256];
struct Baz bazzes[256];
// objTypeId: 0 = foo, 1 = bar, 2 = baz
struct VecItm { struct VecItm next; unsigned int idx; char objTypeId; };
Or what if we can do something even better? What if Foo, Bar, and Baz all have a max size that isn't too wildly different? Can we store them all in an array directly? // objTypeId: 0 = foo (x contains 8 bits), 1 = bar (x contains 16 bits), 2 = baz (x contains 32 bits)
struct VecItm { unsigned long x; char objTypeId; };
struct VecItm vecItms[256];
> Its “macro” system is just a disgusting hackIt's really not that bad. It can result in spaghetti-code where your macros are using other macros and they're spread all over a header file. But if you use them surgically, only when needed, they don't cause much trouble.
> I could just as well write a sed script
The benefit of C macros over sed is if you use some language-aware tooling like an IDE (such as CLion), it will syntax-check and type-check your macros.
Your second example misses the auto-growing part. Sure, I want an array, but if that becomes too small, I automatically want to realloc the underlying data to a larger array. And I want to use it for Foo, for int and for any sized objects, generally.
C++ can create the proper zero-overhead abstraction over it by generating code for each type I use it for, C to my knowledge can’t do so without copy-pasting code or doing things with runtime overhead, resulting in (often) much slower code.
Is it worth learning Rust or Zig and dealing with the borrow checker or manual memory management in general, or are GC languages like Nim or Crystal good enough? I'm not doing any embedded programming by the way, just interested in command line apps and their speed as compared to, say, TypeScript, which is what I usually write command line apps in.