Rust has brought so many great ideas into the programming mainstream. It is a shame that it has also attracted such a community around it that seems to project all sorts of hopes and wishes into the language.
I don't get how someone could think making a CRUD app in Rust could ever be a good idea (beyond hobby projects). That is just not playing to the strengths of the languages at all.
If you CAN use a language with automatic garbage collection for your project, do that. Always. If not, sure, Rust or whatever else language is in that space can be a good choice for embedded development or game dev or whatever. That should be common sense. No need for an article.
I hope for the sake of Rust that the hype around it will die soon so that it can become a pragmatic choice for certain projects.
I'm not sure that it's that cut and dry. Rust provides good tools for building high level abstractions and the developer tooling is comparable to something like typescript. Depending on the team, rust can be a perfectly reasonable choice for a CRUD application.
I have a lot of experience writing microservices and batch jobs using C++. At first this seems outrageous, but with a reasonable set of high level utility libraries, C++ can be very expressive. And then you have the option to drop down to whichever level of abstraction is necessary to address performance or concurrency issues.
Beyond platform requirements (e.g. HTML, CSS and JS on the web or python for ML) I think language choice has more to do with business needs than high level problem domain. When you're building an MVP or have an urgent business need where you need to prioritize short term progress over long term flexibility, high level languages with GC are great. If you have an experienced team and you want to build something that will offer flexibility in the long term, Rust is a fine choice.
I agree Rust is a great language (and has many great things besides its safety protections) but I do think that putting it everywhere when it isn’t necessary is dangerous.
If one doesn’t exist already, someone should make a rust-like language with garbage collection.
EDIT: Thanks to reading other comments, I remembered Elixir, which offers a lot of the concurrency safety of rust without the memory risks.
Kotlin is probably also a good choice (but it ties you do the JVM, which does come with some operational headaches of it's own). Swift would be a fantastic language for this kind of work, but unfortunately it's library ecosystem isn't on nearly the same level as Rust's for backend web.
Rust is an interesting mix. OCaml gets pretty close and type inference offers better ergonomics. The fearless concurrency story is still much better in Rust than OCaml.
If you want high level abstractions, well, automatic memory management enables a bunch of abstractions that the Rust developers gave up on long ago. There are languages with many more capabilities than the ones Rust gives you.
If you have a high level problem, you don't want to spend your focus on low level issues.
> automatic memory management enables a bunch of abstractions that the Rust developers gave up on long ago.
These claims should really come with sourcing. Rust devs have been raising the potential for abstraction in Rust since the initial 1.0 release, most recently with GAT's. What's always been quite hard is zero overhead abstraction, which GC languages simply have no equivalent for.
Sourcing for my "water is wet" claim? Just try writing a language parser in Rust and Haskell and you'll see the difference. Or, to be more on topic, write some database to HTML on Rust and note how you have to keep track of your templates' lifetimes.
The Rust developers are a bunch of very smart people working hard on creating a great language, but they can't do impossible things.
Batch jobs seem like a good candidate for C++ because of how much faster a single instance will be. Multiplied across the size of the total job, that can represent enormous savings. On the other hand, using Rust for a web server with low QPS and atop a relatively novel web stack seems unnecessarily risky. The article does comment on Actix being a pain point.
My current job doesn't use Rust at all, but we're experimenting with converting some CLI tools. There should be relatively little library dependence for the things we're targeting.
I worked at a company where a principal developer got tasked with building a CRUD app. He worked in a silo on it, coming back in 3 months with a CRUD app... written in Scala with heavy use of scalaz. It was a CRUD app that was scalaz features to defer the entire execution until runtime. Nobody could understand the code at all. The team ended up replacing the entire functionality in less than a week using Python+Flask.
My conclusion from stories like this and the one in TFA - engineering management is important and hard, especially when you're dealing with smart engineers. You need someone who is technical enough to understand Rust/Scala vs Python, but also has the business sense to know that as cool as it might be, writing a CRUD app in Rust is not moving your business forward.
I worked with the same guy who made a CLI tool in Java + RxJava. Running everything reactively on hundreds of thread schedulers for something that needed basic collection of arguments and to post to an api.
> He worked in a silo on it, coming back in 3 months with a CRUD app...The team ended up replacing the entire functionality in less than a week using Python+Flask.
This is a symptom of resume-driven development.
> writing a CRUD app in Rust is not moving your business forward.
Writing a CRUD app in a systems programming language might be a symptom of management failure.
Anecdotally I've been able to move faster with Rust on CRUD apps professionally than I've previously in other languages with GC built-in (e.g., C#, TypeScript/Node.js). The borrow checker isn't a problem after you get used to it.
There is still a steep learning curve, although the learning curve has improved significantly over the past couple years. Rust's type system, syntax, and standard library are major strengths compared to those in most GC-based languages, so I'd argue it can be a great fit for domains where GC-based languages have been used historically.
Rust has healthy friction and you learn how to avoid that friction. It might take you a bit longer to hash something out but chances are it will work first time, and will have better fundamentals in design.
I have a different experience with Rust these days. The errors that Rust throws are either the ones I had a vague hunch about or the ones I missed. It never feels like friction, but rather like helping guides. The error messages are informative and often very helpful in resolving problems.
The key, I believe is understanding the machine and memory model of low level computing. Rust errors immediately make sense in that context and it becomes easier to find a proper solution without having to work around it.
These days I feel much more comfortable with Rust than I'm with even Python. It feels like Rust does a lot of heavy lifting and thorough checking of a lot of semantics that's not possible in other languages. The worries about unforseen bugs are lesser (not zero, though) with Rust.
> It never feels like friction, but rather like helping guides.
That is exactly what I mean by "healthy friction." Friction can be used to guide users towards better habits. The fact that you encounter friction less often is evidence of having learned from friction in the past.
Have any of you actually written a CRUD app in Rust? I've written a couple that I also wrote in Node and Go. Using Tokio+Actix. It was a breeze, the LoC were actually similar to Node, and the built in observability was great.
Suggesting that Rust isn't a good choice for a backend CRUD is... odd. With that said, one of the particular CRUDs described above: We ultimately went with Go. Only because Rust isn't an officially approved language at our shop yet though.
I think it comes down to how big/experienced the team is, how old the product is, and how many unfortunate decisions where made in the history of the project that are there to stay. To me the point of the article is that rust would not be a robust choice considering all these factors.
That said, the article is a sample of 1. I'm not aware of other similar experiences
> I don't get how someone could think making a CRUD app in Rust could ever be a good idea
It's the same people than have kept saying there is nothing wrong with writing a backend API server in C++.
Of course you can write a CRUD app in Rust. You can also write it in Brainfuck, such is the magic of Turing-complete languages. Is it a good idea? I'd argue there's languages that are much better suited to writing CRUD systems than either.
> I don't get how someone could think making a CRUD app in Rust could ever be a good idea (beyond hobby projects).
We're having a great time with Actix + sqlx. It feels like Go, but with less typing (physical).
With an IDE like Clion + Rust Plugin, the code writes itself. It feels like writing Java or Golang.
Every time someone says "oh no, borrow checker" for *single thread scoped CRUD apps*, I know they're not even writing Rust in this way and that their concerns are hypothetical imaginings. You can't possibly hit the borrow checker in HTTP handlers unless you're very new to the language or doing something incredibly advanced.
I never see the borrow checker for web apps unless I write threaded code that runs on singletons or other shared state - which is an awesome option to have, as it makes async jobs a part of your deployable artifact rather than some 3rd party job system like Sidekiq.
Bonus of using Rust: the person I hired to join me was also able to write a desktop application.
> With an IDE like Clion + Rust Plugin, the code writes itself.
If there is any point to emphasize, it is this one. Writing Rust without a nicely tuned IDE is almost as slow as writing it using pen and paper.
Because abstractions are “zero-cost” in Rust and the type system is expressive, there are generally many more types involved and a lot of converting between them.
The IDE is very helpful in seeing what types are where, navigating to their definition, viewing their documentation, etc
My 2c: Rust + Actix + Diesel + Tera is the ultimate CRUD app stack, especially now that there is the excellent zero2prod[1] book to refer to for people who have not built a significant CRUD app before.
When you're writing a CRUD app the only errors you should have to worry about are logic errors. The above mentioned Rust stack is the only one that I've been able to achieve that experience with in my whole career.
IMO if you would forget about borrow checker, Rust is awesome language by itself.
I'm not sure if it's even possible, but I think that if GC were introduced to Rust as an optional part, it would make Rust suitable for CRUD apps.
Like all low-level libraries are written with borrow checker and you can write your code with borrow-checker or GC, your choice. So you'll still get superior performance compared to any other GC languages, because your standard library is incredibly fast. And your code will use GC because your code does not matter from performance PoV. And if some function matters - you use borrow checker in that function, so it's fast.
Also green threads might be a good addition. It seems that modern computing reinvents it all over again. Golang, Java. Async/await is hard, people want to write blocking code and get good scalability at the same time.
We need a silver bullet programming language. Period. Suitable to write CRUD and MCU firmware at the same time. Until this silver bullet is not found, we will reinvent new languages.
This is the kind of attitude that will send Rust the way of C++.
Features cost, even optional features. No programming language can or should try to be everything to everyone: to attempt it is to serve no one very well at all, because you split the focus of the language development between too many use cases and you complicate the language so thoroughly that onboarding a new programmer becomes an exercise in re-education about which subsets are acceptable in the given organization.
Rust's niche is safely managing memory without a GC. If Rust adds a GC, even an optional one, it will have lost its soul.
Not only that, but the borrow checker also handles safety concerns. Rust's concurrency safety story ("fearless concurrency") is literally built on the back of ownership and borrowing.
Oh, wow, yes. I confess I skimmed the part about green threads.
I think OP doesn't understand that runtime features like GC and green threads have a cost, and Rust is designed around a making it safe to avoid that cost. Most of Rust's features don't make sense in the context of a managed runtime—what you would be left with is basically a subset of OCaml with C-style syntax and optional mutability.
> Most of Rust's features don't make sense in the context of a managed runtime
I don’t think that’s entirely true actually (which is why linearity is explored in languages like haskell).
For instance Go makes it easier to trigger data races, which undermine memory safety. This is a managed langage “built for concurrency” where using concurrency means memory safety is at risk.
True, but linearity/affine types applied carefully in the context of a managed runtime would look quite different from Rust's borrow checker. The reason why Rust's borrow checker is so infamous is because Rust has to prove everything at compile time. Bringing affine types to a GC language would most likely involve making them opt-in (or only triggered in concurrent situations), while making linearity optional in Rust would undermine its foundations.
You can opt into runtime checking in Rust via interior mutability. Cell<>, RefCell<>, Rc<>, Arc<>, Mutex<>, Rwlock<> are constructs that involve varying levels of runtime checking - most of these will be way more efficient than GC. It's even simpler to use .clone() in order to effectively do away with any requirement for linearity.
Technically green threads were removed when rust was moved “down the stack” back before 1.0: green threads would mean requiring a runtime (scheduler & al) which is problematic when targeting foundational libraries and embedded programming, it makes integrating with other systems a lot harder.
Async being opt-in means the people who don’t need it can avoid it, though it’s not always great when e.g. the premier HTTP client of the ecosystem provides a “blocking” interface which iirc just starts a runtime and delegates to the async interface.
A framework for "pluggable" and optional GC could be very helpful for dealing with the rare case of general graph-like data, where there really is no feasible alternative in terms of more explicit memory management. This will probably be done as a natural outcome of stable support for "local" allocators, which is already planned in Rust itself.
I feel like writing code that satisfies the borrow checker isn't that hard if you intuitively understand the lifetimes of the things you're dealing with. In C++ you have to do this anyway, so having the compiler help actually decreases the cognitive burden. I feel like desiring garbage collection is mostly a code smell, because it indicates that lifetimes and ownership are not obvious. Sometimes that'll legitimately be the case due to shared ownership, but there are facilities in both Rust and C++ for that.
Also green threads might be a good addition. It seems that modern computing reinvents it all over again. Golang, Java. Async/await is hard, people want to write blocking code and get good scalability at the same time.
This is the basic win of Go. Go's "goroutines" are preemptable. So there's no need for an async/thread distinction.
> I don't get how someone could think making a CRUD app in Rust could ever be a good idea (beyond hobby projects).
Me either, and I've said that before on HN. There is, however, a Rust lobby for CRUD apps. Those are the people who are pushing hard for "async". "Async" has its uses, but you don't really need it unless the load on your server has many thousands of concurrent connections, most of which are waiting. The OP here points out that his load is nowhere near that high. Python or node.js would work. Maybe even PHP.
If you need to write CRUD apps with high performance, use Go. That's what it's for. The libraries for what you need probably not only exist, but are running in production on warehouse-sized server farms.
I've been writing a metaverse client in Rust for two years, and I'm about 35,000 lines of safe Rust in. Separate threads are rendering, processing incoming messages from the network, updating the world state, fetching assets, and moving objects that are in motion, all in parallel. There's a lot of heavy machinery. Rust is good for this sort of highly concurrent problem. There have been no bugs in my code that required a debugger. (An early version of the rendering library I use, which is unsafe, caused trouble requiring using a debugger back in 2020. Took about 20 minutes to find the problem.) Rust is the right tool for the job for this sort of thing. I'd need a sizable team to do this in C++, and far more debugging.
As the original poster points out, and as some game devs have mentioned, the safety issues do slow development. You can paint yourself into a corner, where no easy fix will satisfy the ownership rules. It can take a week of rewriting to get out of that. Sometimes longer. Anything where an object needs a connection to its parent is unwieldy in Rust. You can do it, but soundness requires you do it through forward reference counted pointers and backwards weak pointers.
I've written web backends in Rust and it's fine for backend apps, especially APIs. For HTML rendering it's a bit lacking at the moment as it doesn't have the whole ecosystem that Rails for example has, but for APIs I haven't found a lot of missing stuff. I'd say for a Rust dev with some experience it's similar to Go, although more pleasant in my personal view.
In the context of web apps you rarely have to deal with the borrow checker and the rest of the Rust code is rather high level.
For me the post is a bit weird, cause on the one hand he mentions a CRUD app and on the other hand he mentions complexity. If your CRUD app is complex it's either not a CRUD app or you screwed up along the way. I've seen dozens of Rails apps where development was painfully slow cause of a "big ball of mud effect": a bunch of "god models" having dependencies across the entire system, crossing all of the boundaries. So it doesn't have to necessarily be only related to Rust.
Rust is perfectly fine to write CRUD apps and the productivity between it and golang is identical. The only issue I would have with growing a rust app in 2022 is the pool of talent to hire from for it. I find the comments about using rust for different projects generally align with what people were saying 10 years ago about golang and with the rise of people being comfortable in golang it's now used in a massive array of projects beyond what outsiders were trying to peg the language to.
tl;dr rust is great for writing all kinds of apps and as the labor pool with rust grows so will it's usage in all aspects of computing.
I am struggling to find any benefit of Rust for startups to be honest. It makes no economical sense to be bogged down by a language that is 3-10x slower to develop. The issues Rust is preventing will pop up earliest in production, likely rarely and at that point in time the company could be profitable, its product somewhat stabilized and actually have a case of using Rust for an internal rewrite.
Where do those numbers come from?
Rust is not very beginner friendly and takes a while to learn, but nowadays I'm just as fast in it as in other languages, except maybe for Python.
That said I also don't think it makes sense for startups.
I'm only a Rust noob, and have importantly only used it for my own hobby projects. But can't you get a lot of the ergonomics from memory managed languages by using "managed" container structures like ARC and being slightly sloppy with your allocations?
For my hobby projects it hasen't felt more cumbersome than e.g C#.
Arc is not a convenience, since the type matters, the type is visible most places, almost no trait impls transparently work the same on T and Arc T and so on. For all its strengths, it must be said that it's not a language of transparent convenience, not when it comes to this central issue of ownership and sharing.
Progamming languages, hosting platforms and non-standard databases aren't ideal places to spend such tokens.
For most apps what you want is JVM or .NET, PostgreSQL or MySQL or SQL Server, k8s or vanilla VMs/containers.
You almost never want different programming languages to these mature stacks, you might think you do, but you don't. Node.js/Python/Ruby etc all promise a fast start but in reality any perceptible velocity benefit is dead within months, usually before anything of value is shipped. Most of it has to with ecosystems (library quality etc, framework maturity) but those also being the 2 most advanced managed runtimes helps a ton.
For databases just standard RDBMS is almost always the way to go, unless you startup is intrinsically a data startup and you know ahead of time you require very special properties you just want one of these.
Keep hosting simple. Don't use any fangdangled "serverless" nonsense. It's not worth it, shipping a week or 2 later because you took the time to setup EKS/GKE/AKS will pay off in spades. k8s reputation for complexity isn't warranted and it's essentially standard now and will remain that way.
So now you have freed up your innovation tokens you can spend them on solutions to your actual business problems instead of on stuff that will only continue to eat your token budget and often yield negative real returns.
For my money, at least Node and Python have absolutely reached the point where they can provide self-feeding velocity. I quite like the JVM as well--I've written a lot of Java and a lot of Kotlin--but TypeScript and modern, typed Python are very defensible options. I know more about Node than Python, so that's easier for me to talk about, but for my money tools like Fastify have a really excellent ecosystem around getting shit done in ways that don't buy you too much technical debt in the future.
Databases, fully agreed. "Use Postgres unless you have a reason not to." When you need a key-value store for caching or whatever...consider Postgres hstore first, and then spin up a Redis only when you need to.
Hosting...depends. With my product hat on instead of my infra hat, I think there's real value in managed serverless options. The various Heroku descendants--I work at Render, but I'm friends with the Fly folks and they're great too--can, if you are in a low-devops environment, provide some real benefits. Or, for a more stripped-down option, AWS Fargate/GCP Cloud Run, but to bootstrap that you're going to be doing somewhere between the work necessary between a Heroku++ option and "run a server somewhere and keep it patched".
Curious to hear more about your experience with Node/TypeScript. Is it really comparable to the maturity of JVM or the .Net ecosystems? I've experienced some problems such as: 1. It's still running JavaScript on server-side 2. Debugging becomes a pain because stacktraces will contain .js files and line numbers 3. Multi threading is still complex.
1. Type checking is slow (this problem scales with the size of your project also which sucks)
2. Lack of proper build system ecosystem (as compared to Maven/Gradle/etc)
3. Ecosystem is poor
4. Packaging and module systems are poor (import side effects, gross)
If you tackle 2. you can also work around 1. Best way to do this at this time is to use the new Bazel rules_js/rules_ts ecosystem. This will result in lowering the amount of time you spend type checking and transpiling things making it tolerable.
3. and 4. however are just things you have to live with if you go with Node.js.
I've worked in pretty large codebases and #1 and #2 have not been problems I've encountered. #1 used to be a problem, but TypeScript incremental compilation has significantly improved things. #2 doesn't parse at all as a problem if you're working in a monorepo, and if you're not you've created other problems that "use Bazel" doesn't fix--and, to be honest, "use Bazel" would be a failure case that would encourage me to use literally anything else that didn't use it anyway.
#3 is also a pretty wild take, in my experience. Maybe you have some pretty particular needs, but I very rarely find that I don't have what I need in Node.
#4, I'll give you--but if you're going to stan for the JVM, I hope you've never run into a static class initializer with side effects, because I've seen them about as often as I've seen side-effecting imports (which is to say, not often). I gather that they used to be a lot more common in Node, but I cannot think of a library in my usual stack, or even that I've used recently, that has side effects in that way (modulo development-environment stuff which leverages that kind of cursedness intentionally).
My experience is that it's fine(tm). "JavaScript on server-side" does not bother me; V8 is fine. It's not as fast as HotSpot, but also, I don't care. If I ever wrote something at extragalactic scale, yeah, sure, whatever, I'd rewrite hot paths in something else. But I absolutely don't care about that until then.
None of my debugging output includes JS lines because I pack source maps, which TypeScript happily includes.
Multi-threading isn't complex, because there isn't multi-threading. There's coprocessing via Promises, and there indeed are a lot of Node developers who think you don't need locking functionality because it's not multi-threaded (there was an absolutely bonkers discussion a few years ago where a JS developer insisted you didn't need locks), but whatever, they think that about other languages too, use async-lock or whatever.
"Maturity" is a word that means different things to different people. There is not a consensus-best-choice framework like Spring Boot in Node. Which leads people to doing things like "I'll use Express!" and now they have interesting problems all their own. But the tools are there and they're excellent. Fastify is perhaps The Best web framework I've ever used (other than perhaps Python's FastAPI, which makes me wish I liked Python enough to write it because that seems like a Right Answer in itself), and it has only gotten better with v4 allowing you to engage with type providers to create an end-to-end, automatically typechecked route declaration framework. It lets you do stuff like this, where you specify a request schema as JSON Schema (encoded via typebox) and it'll statically derive the TypeScript type for you whilst also using it for request schema validation:
The tools are there and you do probably want to invest a little time in understanding what they do and what their tradeoffs are. There's value in that, for the way I write code and the stuff I enjoy building.
Overall, I'll trade some compilation niceties and even some (but to be frank, not much) performance for a vastly more productive environment in day-to-day use. I really like the JVM. I've been using it professionally for twelve years. I also like the CLR. I did Google Summer of Code for the Mono Project in 2008, I've been around. But the day-to-day of writing code in the dominant languages on those platforms for things other than CRUD does frustrate me, and the general-purpose languages on the JVM and CLR present difficulties in using the type system to effectively encode intent makes it much harder for me to write software that can guide other people to not misuse it. So for me it's worse both at library-writing and at getting-things-out-the-door. (I still do gamedev experiments in Java or Kotlin, though, because libgdx is seared into my brain.)
Why would you even bother as an early stage startup? Ship your code on a PaaS like fly.io/Render/Vercel/Heroku/etc until cost/scale is an actual issue. Same for using managed DB solutions. Your time (=money) would be much better spent elsewhere.
As for language, I would optimize for what's easy to hire for.
Because the impact on your code by going with a serverless platform.
Generally speaking if it's the platform you have you will shoehorn the shape of your code into a serverless shape bucket even if it has no business looking like that.
These architectural shortcomings will inevitably come back to haunt you.
Also most of these platforms amount to cgi-bin v2, meaning things like memory leaks and requests that hang get swept under the rug.
Meaning you already have very difficult to diagnose failures and when you do eventually go to migrate you will find your code is full of these smells making it harder to reform into something reasonable.
That is also putting aside the spaghetti nature of intertwined functions of any serverless codebase of scale, the huge IaC overhead if you go with something like AWS Lambda and the massive amount of incidental complexity that serverless advocates try to make you look the other way for (hello API Gateway).
I'm not a fan of serverless/lambdas for this exact reason, I was referring specifically to PaaS solutions, where you can build your services without vendor lock-in, in a way that makes it easy to containerize later on. Things like memory leaks and requests that hang are usually reported by these platforms.
Fair, the ones that let you run containers directly like Cloud Run etc are fairly reasonable and approximate the k8s experience.
I think by going non-k8s you miss out on some of the key benefits though like standardized API and more portability of the surrounding infrastructure like networking and storage that is otherwise proprietary or not available on "simplifed" PaaS platforms.
I guess my argument boils down to cost of using managed k8s is low, low enough it's not worth using non-standard services instead.
Though I would like to address a point in your earlier comment though that is that it implied "k8s is about scale".
K8s has absolutely nothing to do with scale, the fact it can scale horizontally is a side effect of enforcing good separation of concerns and API design in the orchestration layer. You use k8s because the APIs make your applications better, even with they are small. This is the true innovation behind k8s. There were other platforms that made containers accessible (I worked on one called Flynn, Deis, Heroku to an extent, etc) but none of them pushed a better paradigm around orchestration which is why they didn't lead to the same level of success k8s has.
maybe as a compromise, there's some good languages on top of those stacks. F# exists. You get a lot of the good features that people mention about Rust in this thread without the headaches mentioned in the article.
Expressive GC languages within the major ecosystems exist, no need to reach for a systems language for a web app just because you want the modern, high level features.
I generally agree, but with the ecosystems they have today I think Python and Node are both completely acceptable for a general purpose low-risk tech stack. In fact I would choose either of those before I'd go with a monovendor solution like .NET & SQL Server.
> the service we were building was a fairly straightforward CRUD app. The expected load on this service was going to be on the order no more than a few queries per second, max, through the lifetime of this particular system. The service was a frontend to a fairly elaborate data-processing pipeline that could take many hours to run, so the service itself was not expected to be a performance bottleneck. There was no particular concern that a conventional language like Python would have any trouble delivering good performance.
Yeah, that's not the kind of thing you use low level stuff for. Ruby on Rails or Node or Elixir, or whatever Java thing is going to let you iterate faster than a lower level language.
If given the choice between Rails (Ruby) and Phoenix (Elixir) for a low-traffic CRUD app I might have the same sort of question as you - really?
But if the choice is between Elixir and Rust for that same CRUD app, it doesn’t seem strange; Elixir would be a far better choice.
I would presume the commenter is more familiar with ecosystems like Elixir, even though it wouldn’t be the most optimal choice. But certainly more optimal than Rust in that case.
Yes, really. The Phoenix framework is one of the most productive web frameworks around, and Elixir is a very pragmatic functional language that is easier to adopt than others.
Creating a CRUD module, with schema, migration, controller, view, template and data validation is literally one command away with `mix phx.gen.html/phx.gen.json`
> With Rust, though, one needs to learn entirely new ideas — things like lifetimes, ownership, and the borrow checker. These are not familiar concepts to most people working in other common languages ... Some of those “new” ideas are, of course, present in other languages — especially functional ones.
With C++, lifetime and ownership are just about as important but unfortunately no one's got your back. You can ignore lifetimes and ownership but you do so at your own peril. And the compiler won't tell you you're doing it wrong because the language wasn't designed for it to do so.
If you want a taste of rust's "mindset" (with respect to limitations imposed by some types) without jumping ship to a new language, try C++'s Guidelines Support Library [1]. It introduces some of the same benefits/friction as switching to rust but without a new language. Opting-in to some of these guidelines might be a gentler way to get some of the benefits of Rust. But it comes with a similarly higher bar.
I liked the recent article about writing the Apple Silicon GPU Linux kernel driver in Rust. Now there's a perfect application for Rust if I ever heard one. The service described in this article is a great example of an application that Rust is not the best choice for.
When I first heard about Rust I really thought it would be the language to end all languages, but even after just a few days using it I realized that's not the case. Maybe someday someone will come up with that perfect language for everything, but given recent progress in AI I think the more likely long term outcome is that neural nets will start writing all our code for us. I wouldn't start work on a new programming language today given the changes that are clearly coming in the next 20 years or so. At least, not one designed for humans. Maybe there's an opportunity for someone to make the first programming language designed for neural nets to use.
Maybe that neural net language would look like Rust. The downside of lower programmer productivity may not matter when the programmer is a neural net. You can just run more neural nets! And neural nets are anything but perfect so they will still benefit from the safety guarantees Rust provides. Rust is the language that I don't want to write, but I want the software I use to be written in it.
I find it a little odd the author mentions C++ often. I think the use cases for C++ and Rust are pretty interchangeable, and C++ devs would and should probably know about lifetimes anyway. (That said: yeah, don't use Rust for CRUD apps.)
Coming from c++ I had no troubles with lifetimes in rust; they were obvious and moat of the time I would have structured the code in the same way anyway.
The main problem i encountered (beyond the usual learning curve for stdlib and other common libraries) is all the gotchas and rough edges around async
> I think the use cases for C++ and Rust are pretty interchangeable
Hmm... in some ways. I feel like one probably shouldn't be using C++ for anything network facing or security critical, whereas Rust is ideal for those situations.
If you are writing CPP, rust is a godsend. Many pitfalls/code review debates/wtf moments simply don’t happen in the language. There are huge cpp code bases in the wild which need help.
However rust string handling is barely a step above c’s, there are a variety of datastructures c engineers dislike simply because of the memory management constraints. Getting 2x better performance than Java is a marginal gain for many environments.
I really hope rust takes a bent towards ease of use. Stabilizing a rust gc arena would be hugely impactful, or simply revisiting the string api.
Having 3 separate string types commonly used across the standard library is a huge friction.
For beginners, it’s impossible to write a common program for doing some basic string manipulation without reading 3/4 of the Rust book and waiting through dozens of compilation failures.
Are the three types you refer to str/String, CStr/CString, and OsStr/OsString?
There are trade-offs associated with having multiple string types, but the alternative is worse. It's better to force the developer to handle these things up front than to have bugs down the line.
str/String being UTF-8 is great. Is means that the various standard library functions associated with them have well-defined behavior.
CStr/CString are necessary for FFI, but I wouldn't really say they're commonly used in std.
OsStr/OsString are necessary unless you think the standard library should automatically convert whatever random format the OS uses into UTF-8. Rust's decision to not treat everything like Unix is actually a good thing.
> For beginners, it’s impossible to write a common program for doing some basic string manipulation without reading 3/4 of the Rust book and waiting through dozens of compilation failures.
Personally, I think the standard library makes it pretty easy to learn how to convert between the types.
Rust would not be my first choice for a simple CRUD app. There are too many other tools that handle that space well.
I have used Rust at a startup, and it worked out extremely well. But we used it for CLI tools (where it shines), and performance critical code, and for specialized, high performance servers.
> With Rust, even after months of working daily in the language, most people on the team never felt fully competent. A number of devs told me they were often embarrassed that it was taking longer than they expected for their features to land and that they were spending so long trying to wrap their heads around Rust.
Very unsurprising. Rust is a bad choice to use when building a typical CRUD app especially when you are not making any money. So when I saw this about immature libraries:
> Rust, by comparison, has long felt like a work in progress. The docs for a lot of popular libraries are pretty sparse, and one often needs to read the source code of a given library to understand how to use it. This is bad.
I just couldn't help but say that someone has finally cut through the hype, and experienced the reality of the immature state of the library ecosystem in Rust rather than pretending that it is fine; especially for a startup which can result in the failure of feature delivery and of itself.
This cargo culting of programming languages promised by a fan club or hype squad of any new language needs to stop.
> [...] most modern, procedural languages (C++, Go, Python, Java, etc.) all very similar in terms of their basic concepts. [...] With Rust, though, one needs to learn entirely new ideas — things like lifetimes, ownership, [...]
Maybe an oversight, C++ is misplaced in that list. C++ without knowing about ownership and lifetimes is a recipe for disaster.
Even in the other languages lifetimes/ownership are a useful mental tool for managing manual resources (DB connections, transactions, mutexes, file handles, etc).
There's so much code in Go that is documented like "while this object takes a file handle, it does not close it itself even though there is a close function, you will need to close it" that in Rust is just a lifetime annotation.
One note about hiring: if you want to hire like super experienced, senior developers who can architect your rust codebase and tell you definitively how to write Rust, you’re probably out of luck. There just aren’t that many people. You can get senior developers and you can get developers with Rust experience but the intersection of the two is very sparse.
As a result, expect some churn in your codebase. Your team will have to discover and iterate upon best practices. Avoid the impulse to spend too much time code golf refactoring because “oh I can use a proc macro to decrease boilerplate while avoiding the orphan rule and having nice traits!” Rust can definitely nerd snipe you by making you worry about some very small edge case like oh no I’m copying a few too many times.
Basically unless you’re pretty damn sure you have product market fit (PMF) and you’re sure that rust offers an appreciable benefit to said PMF, I would not use it.
I was very familiar with C before starting to do professional work in Rust, and ran into a similar trap as the grandparent comment describes. Since then I've gained professional experience in C++ as well. Without specific Rust experience, I wholly disagree that "any senior developer with C/C++ experience" will have a good sense of optimal Rust project architecture out of the gate. A reasonably good starting point, sure. But it's a different enough language that you can't just reduce architecture concerns to "it's not overly complicated."
> ran into a similar trap as the grandparent comment describes.
The only "trap" I've seen referenced wrt. Rust is the concern that coding for agility and flexibility introduces boilerplate. Such as GP's concern about calling .clone() too much instead of worrying about lifetimes. But every language has its unidiomatic parts; it's a benefit of Rust that they chose to make the most thoroughly refactored style look "clean and natural", and the most open to refactoring "hackish and unidiomatic" rather than the reverse (cough, cough Java cough, cough). Describing that as a pitfall is just not very helpful!
Wasting time on things that don't matter is a trap. It's (IMO) easy to run into this when you're new with Rust and aren't sure of best practice patterns/conventions.
> Avoid the impulse to spend too much time code golf refactoring because “oh I can use a proc macro to decrease boilerplate while avoiding the orphan rule and having nice traits!” Rust can definitely nerd snipe you by making you worry about some very small edge case like oh no I’m copying a few too many times.
You can definitely spend too much time refactoring in other languages, but I found I was more tempted to do so in Rust than in C (perhaps due to familiarity with C).
> how to grok the often arcane error messages from the compiler
This, to me, is a bizarro opinion. Compared to c++, rustc's error messages are (by and large) incredibly helpful and clear, oftentimes showing how the code should have been written.
Great article. I love Rust, but I don't associate it with (development) agility, despite the overpromising of Rust advocates. It requires a tremendous amount of the developer (at first).
In many larger projects, there is a need for efficiency and/or low-level work in _some_ corners, _and_ there is also a need for productivity, rapid development in _other_ parts. And everywhere do we want maintainable and readable code that is easy to understand.
Selecting more than one language sometimes works, in my experience you can have various microservices that talk to each other, then it doesn't matter what language each is implemented in.
However, it isn't optimal to have to master more than one language (most good people do, but we also want to cater to weaker team members; in football, not everyone is a Ronaldo, Pele or Messi either). I don't see why C++ or Rust shouldn't have opt-in garbage collection offered for parts of the codebase where this makes sense (where productivity is worth more than runtime speed), which is probably most part of most projects if we are honest.
As some of you know Rust actually _had_ GC in an earlier version, and some readers may complain saying "but C++ can do GC if you want" (of course you can add a Boehm-style GC in your project). But what I'm referring to is that there should be a "batteries included" GC-enabled part of systems programming languages by default (as a technical and cultural preferred setting), unless you mark up a library/crate/class as performance-relevant. This could be done with a mechanism similar to marking up "unsafe", e.g. "uncritical" (= runtime matters less in this section compared to productivity). Then strings and other objects could be garbage-collected and programmers could focus on solving the actual problem instead of managing memory. Critical sections could be marked as such, and there'd be the much-coveted "zero overhead" abstractions for these.
The advantage is you would not need to stay up to date in two languages, and you could avoid FFI-type integration, which tends to be brittle (JNI? shudder!).
An entirely separate point is the availability of programmers that master a language. I totally agree with the poster that you should use a mainstream language to avoid being hit by a talent shortage.
My devil's advocate take on this article is that they're using Rust as scapegoat to their scaling (team not performance) problems. I use Go at $DAYJOB and we're having similar problems for lead times. OTOH, the novelty of Rust in webdev, means there isn't someone to make a decision like, "we don't need performance, so use threads instead of async and pass everything around in a box". I've sunk plenty of personal time trying to wrangle async on a CRUD app for learning, and I recommend it as an exercise, but I'd avoid async if I was trying to ship something.
I think for most companies, using Rust for security and performance critical areas is best. For other things, Rust can run Python code via FFI so no need to take the productivity hit unnecessarily.
I have been shocked on multiple occasions to see how difficult teams make it to do basic CRUD apps.
Go lang is suggested rather than Rust, but the standard way of using Go is lower-level and less productive (unless you care about performance) than the Rails of 10 years ago (and I am not a fan of Rails).
Haskell can be a great option here- all the high level benefits of Rust and more in a language with a GC and plenty of good frameworks and libraries for CRUD. Unfortunately its one of the only languages that has a greater learning curve than Rust.
The cautionary tale is, no doubt, don't use trendy tech stacks if you want your startup to survive. You need a product, and the correct tech stack is whatever gets you there soonest. If you're wildly successful you might eventually need something better, you can worry about that then, after all your competitors with awesome tech stacks have gone out of business.
> Because we had chosen an “esoteric” programming language for this service, the other engineers in the company who might have otherwise been helpful in building features, debugging production issues, and so forth were largely unable to help because they couldn’t make heads or tails of the Rust codebase.
Then they are bad engineers and should be fired. Maybe it's just my general rage at so-called developers being incapable of understanding undergraduate level recursive functions, but the quality of developer is on the floor in my opinion.
Not very convincing when "git gud" is the common response to Rust's exuberance towards needless complexity in use cases that it doesn't fit well in (like CRUD applications)
I don't think it's a "git gut" kind of response. There are some engineers who keep repeating "oh X is so hard, I've never tried myself or even looked at it, but I've heard that it's hard". X could be anything.
If you are a software engineer and not a code monkey in a web shop, you should have no problem at least trying to expand your knowledge. I worked with people who're afraid of changing a line in k8s manifest file even after you tell them what and where.
There are some languages that look alien to the typical engineer and hard to understand without at least a minor prior knowledge, but Rust isn't one of them.
That also means that "soup of pointers" datastructures have a more fertile ground to sprout because there's no pushback to them, except by the developers themselves. I find that the memory patterns that the borrow checker dislikes are also the ones that tend to confuse humans.
I usually don't think about memory management in Rust, to be fair.
Also GC doesn't remove all resource management... ironically I find most GC-based languages to be more mentally taxing when managing manual resources (file handles, mutex locks, etc).
Haskell programmers says that Haskell is just fine as well, but in the end there aren't many big programs with complex architecture in these languages. Compilers, interpreters, crud servers etc, those things are very simple architecturally so for them these languages are fine, but for more complex cases these pure languages seems to be too cumbersome.
When you try to adapt your program to fit the needs of humans and technology it will become complex no matter what you do. The world is full of problems that can't be decomposed nicely because they are created by millions of people over decades each doing things slightly differently and you need to make a program that fills their needs, if you think those kind of problems don't exist you are just naïve. Those are the programs with the most complex architecture.
Rust has an undeniably steep learning curve which can cause a productivity penalty for beginners. However, there are some statements in this article I simply can't agree with based on personal experience. I'm using Rust to develop a few projects. At this stage, I would go for Rust for general development, irrespective of how useful manual garbage collection is. To put it in context, my other favourite language is Python.
> Rust, though, one needs to learn entirely new ideas — things like lifetimes, ownership, and the borrow checker. These are not familiar concepts to most people working in other common languages, and there is a pretty steep learning curve, even for experienced programmers.
Rust's borrow checker system doesn't exist in isolation. It's designed to avoid problems that can occur with computing and memory model of programming - things like call stack frames (esp constant size), heap memory and other resource management, concurrency paradigms, etc. These are just the tip of low level computing models. There are things like cache coherence that Rust doesn't address directly. These are concepts that programmers must know if they want to do: a) Low level programming b) High performance programming. The problem raised by author may partially belong to the second category. Even if it is not, programmers should probably learn these, because they are likely to face such problems at some point and may gain from that knowledge.
There are two ways to learn the borrow checker rules. The first is to start with the rules itself. It's going to appear rather convoluted. The other approach is to understand the machine/memory model that I mentioned above first. Once you do, the rules are going to make much more sense - especially in the context of specific error messages during coding. These days, I'm able to connect every single error message that I see to some potential memory or concurrency problem, even though they are the result of some seemingly convoluted but simple borrow checker rule.
> Despite being some of the smartest and most experienced developers I had worked with, many people on the team (myself included) struggled to understand the canonical ways to do certain things in Rust, how to grok the often arcane error messages from the compiler, or how to understand how key libraries worked (more on this below).
There were dozens of errors that I made today that were easily understood just by a glance at Rust's error messages. The help messages that accompany these error messages are often the solution that I needed. Even otherwise, the error messages point out potential low-level bugs as I mentioned above - allowing me to make proper corrections. This may just be anecdotal. What is not anecdotal is that the Rust team reworked the error messaging early in the project's life to make it that way. It's generally accepted that Rust's error messaging is top-notch. I find it disrespectful to all those early endeavors to call it arcane. Perhaps it's a good idea to try to learn those error messages. There is an entire index of errors with detailed explanations [1].
> We started having weekly “learn Rust” sessions for the team to help share knowledge and expertise. This was all a significant drain on the team’s productivity and morale as everyone felt the slow rate of development.
Perhaps this is the wrong way to approach the problem. The thing that may need learning is type theory. Type theory is often too esoteric with a theoretical mathematical approach. Perhaps we need an introduction to it that encourages people to see a bunch of bits as a storage for data with a particular meaning (the type). Next step would be to how to track types using software (type system) and then extend the idea all the way to include constant-size stack frames, generics, polymorphism and lifetime tracking.
> Libraries and documentation are immature
Library ecosystem is still growing - especially async programming. But documentation support is a first-class feature of Rust's language ecosystem. I find myself writing documentation much more often in Rust than with other languages. It's also equally easy to browse the documentation of the language, stdlib and other libraries. Sections are marked out clearly and navigation is well thought-out. Every search of a feature or API takes me through half a dozen links to the final target in a matter of minutes, if not less. It's a good investment to learn browsing technique for documentation in any language.
> Rust makes roughing out new features very hard
Somehow, my experience doesn't match here either. I use the same technique for trying out new features in both Python and Rust. Create a scratch project for each experiment. A single project may take up to a dozen such scratch projects. It's marginally easier in Rust than in Python, mainly because project management is another first-class feature of Rust tooling.
But what's really remarkable about Rust compared to most other languages I've encountered is that Rust's discipline gently nudges the scratch project to a proper design. By the end of the scratch project, the experimental code is often in a good enough state to be directly integrated into the main project. It's amazingly friction-less to integrate integrate external code into the main project.
I wouldn't blame the author for having a very different experience as mine. But I sure would like to know what makes Rust 'click' for some, but remain a tough nut to crack for others.