I'm not sure I agree with the article's premises. Rust can be difficult, yes, but it can also heighten developer productivity above other languages. In Go, I'd have to worry about whether I checked for exceptions via `if err != nil` everywhere, while with Rust, I can depend on the compiler telling me if I haven't done so exhaustively, via the Result type. Same for having algebraic data types or, well, generics in general.
I will also push back on other commentors here saying Rust is not good for web apps and APIs, and I have found that to be the opposite of true. I read Zero To Production In Rust [0] (a great book by the way, all about creating a web API with actix-web and Postgres) and deployed an API that has not gone down a single time since deploying near the beginning of this year. It's as ergonomic as (and sometimes even more so than) any NodeJS or Go API I've made (as even though Rust doesn't have function overloading, actix-web and others have some macro magic they do behind the scenes to make it appear as if it does), and there were very few times I had to contend with the borrow checker. If there had been and if I were really going for speed, I would also have cloned everywhere that was needed.
When I write something in Rust and it compiles, I can depend on it to work, and to continue working. At the end of the day, use the tool that makes sense for your business, but I don't think Rust was necessarily a bad choice based on my personal experience with it.
God I get tired of the if err criticism with Go. I truthfully don't even notice it when I write Go, I don't understand why folks get so bent out of shape over it.
That just means you have written a lot of go. I truthfully don't notice lifetimes or the borrow checker most of the time when I write rust.
I actually think that, ironically, the if err problem with go is worse for reading code than writing code. If you are used to the pattern it's not that difficult to add the `if err != nil` after a fallible function call, and as is mentioned elsewhere, linters can help catch if you miss it. However, if you are trying to figure out what a function is doing, it can be very difficult to follow the flow, when 3/4 of the code is
if err != null {
return err
}
especially if you don't have a lot of experience reading go code. And that 3/4 is not an exaggeration. I have had to read functions where literally every line of normal logic is followed by those three lines to propagate the error from the other function. A function that would otherwise fit on a single screen now takes up over three screens worth of scrolling.
I don't see how that's a specific problem of the Go programming language though. Rust still has option types and the cases where an Option could be None still need to be handled. Maybe the big difference is the compiler forcing the handling of this case but it seems like a small thing for experienced programmers.
Rust's default paradigm for memory management seems more significant of a language feature in my opinion and is what I can imagine most people don't like. A lot of the programs I write don't fall into the class of problems Rust is trying to prevent which makes the restrictions it enforces bothersome for me.
> Rust still has option types and the cases where an Option could be None still need to be handled
And rust has the `?` operator, as well as combinators like `map`, `and_then`, `unwrap_or_else`, etc. which IMO make the flow much easier to follow than say `if option.is_none() { return None }`.
> Rust's default paradigm for memory management seems more significant of a language feature in my opinion and is what I can imagine most people don't like
I never said it wasn't. My point is that if you use a language enough you get used to things that are difficult for people less experienced with that language.
`map` and its cousins make Rust error handling code easier to write. It doesn't make error handling easier to follow. And Rust introduces the problem of "thicket of different error types". And that's before we get to the sync/async boundary. There's a lot not to like about both Rust and Go's error handling. People need to stop pretending that either language has this answered perfectly.
People complain about Go's wordy error handling, but systems programming is error programming. The places where Go's error handling is most annoying is in application code, where you'd ordinarily EAFP instead of LBYL. But that's also the code Rust is least convenient for.
Go vs. Rust is truly the dumbest programming language slapfight in the entire industry.
Having used exceptions, like in java and python, checking error return values like in go and to some extent c, and using ADTs to represent error conditions like in rust, haskell, and scala (scala has exceptions too, but it's arguably more idiomatic to use Option, Try, etc.), primarily in application code, I by far prefer using ADTs. IMO it is a good balance between explicitness and verbosity. And ensuring you handle errors is built in to the type system.
This reminds me of the same response regarding generics from Go users:
> God I get tired of the generics criticism with Go. I truthfully don't even notice it when I write Go, I don't understand why folks get so bent out of shape over it.
Your profile link doesn't work. Regarding `if err`, it's one thing to add it everywhere, it's another to forget and then have something break. If Go also checked for exhaustive error handling, I wouldn't mind it either.
This is pretty trivial to accomplish with linters. Where I work your build will fail if you have unchecked err's hanging around, along with a lot of other sorts of issues. No, it's not built into the language by default or anything, but is that dealbreaking?
It is dealbreaking. These days I gravitate more and more towards strongly statically typed languages over weakly dynamically typed ones, which I definitely used to use when younger. Fact is, linters are simply not the same as something built-in from the ground up. Trust me, I have written many thousands of lines of Python and Ruby, and even with linters, they don't hold a candle to even something like TypeScript in terms of type ergonomics and refactorability, not to mention even stronger languages like Haskell or OCaml.
Anecdotally this is the same argument many C++ devs make in regards to Rust safety guarantees. The difference is just...different.
Sure, I did not mean Go specifically when I said that, just stating how when I used linters in other such languages, it was not comprehensive enough to replace a language with types and other niceties built-in.
I also don't use much Go because it lacks good algebraic data type support, honestly can't imagine going back to a language that doesn't have them as I like to define all my business logic in types and then build the actual application from there. Go simply doesn't have that capability.
I'm not familiar with algebraic data types, what's missing from Go that makes that impossible? Sounds about like defining structs/other custom types before writing any method bodies, which is what I'd typically do in a Go project.
In general I agree with you, but Go's simple syntax means that there are extremely few (if any? I've never run into one) edge cases with checking that an error was at least considered. The guarantees are the same as what you get with e.g. Rust. The only difference is that you have to run a third party's code, which I don't like, either.
> The guarantees are the same as what you get with e.g. Rust.
I'm not sure how they could be, given that Rust checks exhaustively and Go doesn't. If one programmer messes up, then that's it, an unhandled exception might occur in the future. I would rather rely on the computer telling me when it should be handled over humans.
It's hard catching bugs in go when your eyes tend to glaze over tens of thousands of 'if err != nil' statements causing bad signal-to-noise ratio.
I actually like Go over Rust, but really, really wish the language provided some more ergonomics and fixed several of its terrible gotchas. (the for loop issue for example)
Many convenient shorthands that you all have to be familiar with before being able to write (and read) Rust competently. I guess that's what they mean with the steep learning curve...
It's nice to read that someone else has had a positive experience with the zero2prod book. I'm working my way through it and my impression so far (even after reading the official "book") is that Rust is hard; I enjoy the challenges and the eventual realizations, but working my way through some of the chapters that involve implementations, traits, and macros makes me wonder: Would I actually be able to do this myself in a reasonable amount of time, in a professional setting?
I thought so too when first starting it, but I realized it's in-depth by design. There are a lot of tutorials that teach you the vague basics but they leave out things you (ostensibly) might not need currently, like logging and tests. In contrast, Z2P covers a lot of areas that are traditionally missed in such tutorials, and it even teaches design patterns for high performance web APIs. With that in mind, it felt like reading two books worth of information in one.
After going through the book, I've found that I simply use the same boilerplate for every new project, so it's more of a `write-once, use again` type of deal. In that case, I don't worry about the time it took to get to the point where I could do that reusing.
Agreed! And personally, I've found that "Zero To Production Rust" is absolutely phenomenal! I feel like it's the equivalent of "The Book" for using Rust on back-end apps!
I was surprised at just how detailed it was. I thought it would be like those NodeJS tutorials like "build a full-fledged backend API in 5 hours" but no, it covers everything you might think of for production, so its title is very accurate. It also teaches you system design along the way, which is quite a notable benefit over many other types of tutorials.
Thank you for linking to zero2prod. I just bought it after looking at the repo for the example code and scanning the sample. I wish I’d read this book a year ago.
I’ve worked my way through a couple of rust books, but this was the first one that actually talked not just about the language, but the entire development flow. Git workflows, CI/CD, docker, comprehensive testing.
Even after clearing the initial rust fundamentals, there are so many standard libraries like anyhow, chrono, serde, thiserror, and tokio that are almost required knowledge, but aren’t covered in most books.
> It's as ergonomic as (and sometimes even more so than) any NodeJS or Go API I've made (as even though Rust doesn't have function overloading, actix-web and others have some macro magic they do behind the scenes to make it appear as if it does)
Not that this takes away from your point, but does Go have function overloading? I had assumed it hadn't given the discussions I had heard about what it would take to retrofit `Context` parameters on everything when they added that type, but I guess maybe this could be one of those "functions can but methods can't" things (which I think I heard is how they do generics as well?)
I'm so convinced that there exceptions are the superior article, that basically there is no hill worth dying on that can even be identified. It would be like saying that I'm going to personally defend a civilized continent against some fringe bushmen armed with flint arrowheads. I've got an army for that, funded by my taxes.
The only difference between exceptions and Result are that you need to change your return type to Result, and that you need to add a ? after calls that might fail.
There's also a bit of annoying typing stuff involved, but generally with something like the anyhow crate you can handwave that away too.
It's the only difference if you're only considering blub exceptions, and not a nice exception system in which raising an exception doesn't immediately unwind, so that you can have remote handlers choosing local restart points and such.
> you need to change
Say I can't change it. I want to pass the exception through a third party library I don't control. I don't want to fork it, or can't.
Sum types with pattern matching are definitely nicer than some integer error codes or what have you, but for error handling, they are just a lipstick on error codes.
People outside of the Rust bubble do know what these concepts are, and about languages like OCaml.
Moreover, everyone who ever had anything to do with the concept of the development of exceptions was not an idiot who just didn't see the light of returning a sum type.
I agree with you about Rust and the web - it's a great fit for a performance-oriented web backend.
Personally, I actually think it's a worse fit for things like kernel/embedded development than for web backends. The ergonomics of Rust are so Javascript-y that the web is natural, and other environments where C is used are a lot messier and trickier.
I wish ReasonML took off more. It's an OCaml dialect which reminds me of Rust with a garbage collector, which is unsurprising given the Rust compiler's history being initially written in OCaml and where a lot of its ideas stem from, such as exhaustive pattern matching, the Result type, functional programming being as performant as imperative programming ("zero cost abstractions") and so on. ReasonML was developed at Facebook by the creator of React and it was supposed to be used as an alternative language than JS for writing React.
However, it didn't have much support after TypeScript took the JS world by storm. If it had, we might have seen the same great Rust-like benefits on the frontend as well, and perhaps the backend too instead of NodeJS.
The productiveness, safety, and beauty (seriously!) of the surprisingly numerous Rust kernel and embedded projects suggests otherwise. That use case took off very quickly in Rust world.
7 years ago I’ve deployed the first version of the REST API service written in Rust. We had multiple deployments in consecutive 5 years. For 2 years this service runs without any updates.
There were just a couple of failures in the first 2 months.
I would say that using Rust for backend APIs seems a bit off. I like Rust, but introducing languages that can be made unsafe / can at a minimum memory leak seems irresponsible.
Its dumb when the C++ community refuses rust when it guarantees memory safety, so why would you bring in a language you prefer to an area that doesn't need it if it introduces that issue?
If you like Result<> or concurrency, use a GC language with those things. Go may not have them, but your choice isn't just Go.
The big win with Rust is that it's not only memory-safe but also thread-safe, which is hugely important in a web application.
IMHO C# is also an excellent choice, but there's a lot going on behind the covers to keep it working properly in a multi-threaded web environment. This mostly doesn't concern you as a developer, until it bites you.
Can doesn't mean will. I have not once used unsafe in my API code, the API acts just as it would in a GC language. In return I get more throughput, more speed, less memory used. The borrow checker ensures that I don't leak memory when not using unsafe, so I don't worry about it.
You can 100% leak memory in pretty much every language though: just use a bad caching/memoisation strategy, and you'll have memory leaks for days. Leaking memory isn't in of itself a problem, and it can be a very valid strategy for short-lived programs where you can rely on the OS to do the ultimate garbage collection run anyway. It's only a problem in the sense that uncontrolled memory acquisition will inevitably lead to the system running out of memory to acquire, but that's also true if you're accumulating memory for perfectly legitimate reasons.
Yes, you're right. However, it's much harder than in other languages, and the likelihood of doing so therefore is lower. I can also leak memory in GC languages too, just to note.
What would you say to a C++ dev refusing to use rust because their code is memory safe and they have never had a memory safety issue?
"Can" is the whole thing. A rust dev risking can is like a C++ dev risking can.
I like rust and do what you want in personal projects, but introducing it to a GC space when there are langauges that "can't" be unsafe is irresponsible when a GC language can offer all the upside of rust.
GCs prevent memory leaks, which safe rust can do. So even if you somehow make sure no unsafe is ever used, you can still introduce a hard to debug issue just because you want to use rust.
The difference is that it's fairly easy to prevent unsafe code from occurring: a "forbid(unsafe_code)" directive prevents unsafe code from even being accepted by the compiler. In C++ there is no equivalent - good linting can prevent some obvious errors, but a lot of valid code is inherently unsafe (in the Rust sense) if used incorrectly or under the wrong assumptions.
It's also worth pointing out that most GC languages can have the same issues as a Rust program (if not more) just by virtue of having FFI. In Python, I can quite happily load a C extension and ignore all the rules. Whereas in Rust, if I want to use a C library, I still need to explicitly declare it as unsafe code.
In practice, I tend to find that I have to think more about how I approach parallel/multi-threaded code in Rust than I do in other languages - not because it's dangerous, but because the compiler doesn't allow code that would break memory safety guarantees. In some ways, that's obviously a bad thing if I'm having to think more, but in my experience, the thoughts often lead to a better design anyway. So in the end, I'm more confident in my Rust code than I am in equivalent code in a GC language.
That's true, but again, it's also true in most other languages that your dependencies can do anything they like, including shipping untrusted, compiled C code.
The value of something like "unsafe" is not that your code is magically protected if it isn't there, but rather that it provides a warning sign as to where dangerous code might lie. So if something goes go wrong - or you're just worried something _might_ go wrong - you know roughly where to look. In the same way, if your Python code includes a C extension and you start getting weird segfaults, you can reasonably guess where to start looking.
Except that, because unsafe is so well integrated in the language, you can often significantly reduce the impact of unsafe to only a few lines, and in most cases you won't need to use it at all.
> "Can" is the whole thing. A rust dev risking can is like a C++ dev risking can.
They're not the same because how C++ does memory safety and how Rust does it is unequivocal. C++ is unsafe by default, Rust is not, and you are ensured it is not, as I mentioned, via things like the borrow checker. To equivocate them is to fall into the same trap you yourself mention.
There are no GC vs non-GC spaces. The point of Rust is to be able to have memory safety without a garbage collector, that's literally why it was made. Thus, it is expected based on that premise that we be able to use Rust wherever a GC could be used. The fact that unsafe exists does matter, as long as unsafe isn't used. I too can turn a GC language like Java into an unsafe monstrosity, by for example doing raw bytecode tinkering, does that mean that Java is now unsafe? No, unsafe is merely an escape hatch.
Rust comes with additional risk. It is easier to leak memory, it is easier to be unsafe (especially since you can't guarantee what future other devs will do), but you gain nothing.
You get all that risk, but fearless concurrency can be done in GC languages (like Elixir) and many people have created the Result type before. So you have added risk for no benefit. Not to mention its easier to hire devs (and train devs) for other GC languages cause not everyone knows or understands the borrow checker.
I mentioned the benefits elsewhere. Faster, more throughput, uses far less memory, more ergonomic developer experience (Elixir for example is not statically typed), rock solid stability (my API and anecdotally those of others I hear have never crashed). The risk is small compared to the benefits. I'm not sure why you keep saying there aren't benefits because there are. Now you might not agree they're good enough for you, but they exist.
Fair, but again I think you can get all those things with a GC language.
Speed is the only one you may possibly not, but I think speed is an non-issue. Speed in programming never matters except in the systems space. No one is gonna be able to tell Rust vs Go in a web API and depending on how you do the benchmark, I think many GC languages can probably tie Rust.
> Speed in programming never matters except in the systems space.
This kind of attitude is how we get bloated Electron apps that HN (including me) loves to complain about. Speed should be a first class consideration, not something to throw away for later.
> No one is gonna be able to tell Rust vs Go in a web API
Debatable, Discord moved from Go to Rust [0] because they were getting latency and CPU spikes from Go's GC, and I'd assume that's the same with any GC language because, well, the GC has to run sometime. Now you might say that we're not all Discord scale, to which I'd say, like above, there are benefits to Rust that are more than just speed. Cargo alone is nigh unbeatable compared to some other languages. I was trying to get Python to work the other day and was pulling my hair out over venv, virtualenv, pip, conda etc.
Bloated is the problem, not language speed. I may have misspoke, what I meant was speed in language never matters.
And the discord instance was because it was systems/performance critical software they rewrote. They didn’t rewrite there web APIs. Discord mentioned the rewrite is now rust with C++ glueing it together. No way you are using C++ unless you have to.
I’ll agree with you on python venv/pip. Ugh what a mess. I like cargo, but honestly I think you could argue cargo also is an issue. When you bring in 100 crates, you just gotta hope all the unsafe code in them are safe.
The problem with Cargo is it only builds rust. I agree it's the best language-specific build tool, but I love the monorepo pattern and future proofing insurances that modern language agnostic build systems like Bazel enable / provide.
> Speed in programming never matters except in the systems space. No one is gonna be able to tell Rust vs Go in a web API
Speed equals money. The same app running at twice the speed uses half the compute resources. You may also gain a lot of simplicity if you don't need to distribute your architecture so early on.
Don't believe the hype: moving from Go to Rust will not make your app (web or other) twice as fast! It might make your developers twice as slow though (as the author of the article noticed)...
I'm not convinced about this at all. I've had far more issues with memory leaks in JavaScript programs than I have with Rust. The only way you're really likely to leak memory in Rust is creating cycles of Rc and Arc types. But I don't think any of my programs have even contained Rc, and only trivial usages of Arc for global resources, which I absolutely wouldn't want to point to each other. JavaScript will let you create the same problem without making it obvious with type signatures. And while you might be lucky and have the GC sort out your mess, it doesn't always manage to.
If you're going to use Rust in web anything at all, the backend is the first place you'd think of. You can use Rust in the frontend but it's not exactly a smooth experience. Rust is a systems language.
> With Rust, though, one needs to learn entirely new ideas — things like lifetimes, ownership, and the borrow checker.
This is really the main reason not to use Rust when you need to move fast: if you don't know the language yet, it will take you time to learn it. But that's true of any language. You probably shouldn't do your startup in Java, either, if your team isn't familiar with it.
Yes, Rust's learning curve is steeper than many other languages, but still... if you're an early-stage startup and you need to move fast, write in a language that you and your team already knows.
> You probably shouldn't do your startup in Java, either, if your team isn't familiar with it.
Java is wildly easier to pick up than Rust, though. Hordes of productive enterprise java programmers are employed today who frankly don't even understand what memory safety even means, much less the tradeoffs their language runtime (or software) needs to make to ensure it.
It's certainly a truism that you should tailor your development environment to the talents of your staff, but that doesn't mean that all tools are equally applicable. Would you do a startup today using APL or COBOL?
> Java is wildly easier to pick up than Rust, though. Hordes of productive enterprise java programmers are employed today who frankly don't even understand what memory safety even means, much less the tradeoffs their language runtime (or software) needs to make to ensure it.
I think Java is a little easier to pick up than Rust, but not much. And this is especially true if one is not already familiar with Object Oriented programming. Classes are weird - conflating a number of concepts that really ought to be orthogonal to each other (and I remember thinking so as a beginner, although I couldn't articulate why then). IMO, structs, enums, and traits are much easier to explain to a beginner.
Rust does have the overhead of needing to care about ownership and memory, but I don't think that's as significant as you might think (and Java also leaks some of these details in for example the difference between objects and primitive types), and it partially makes up for it in other areas.
> We hired a ton of people during my time at this company, but only about two or three of the 60+ people that joined the engineering team had previous experience with Rust. This was not for want of trying to find Rust devs — they just aren’t out there.
As an experienced Rust dev, I had the opposite problem. I looked for a good Rust job, but couldn't find one. Took an Elixir job instead.
I also don't get the complaint about lack of productivity. I've worked in a dozen languages, and Rust is still the most productive language for me by far. I feel like they're speaking from inexperience here. I'm slow whenever I start a new language too, and Rust was no exception. Keep at it, however, and it'll pay off in the long run.
> My primary experience from Rust comes from working with it for a little more than 2 years at a previous startup. This project was a cloud-based SaaS product that is, more-or-less, a conventional CRUD app
I think this should be at the top of the post. Rust seems like an obvious bad choice for this. For something not performance-sensitive that doesn't require most of Rust's power just use languages/frameworks that are made for this kind of thing. Of course you can do this with Rust, but you're probably picking the wrong tool for the job. The Rust ecosystem around building typical webservices isn't nearly as good as it is for other languages, and the benefit you get from Rust's safety and performance is worth less than the iteration speed you gain from the standard Node/Python/etc approach.
That is the main point of the article. It was a poor choice for what they used it for and most startups aren't writing system (or even desktop) software but are building web apps.
A CRUD/SaaS web team should only be using it for high performance areas with a few microservices. At least until the web side of Rust significantly matures (mostly in the people/community side not just code).
But I've used Rust mostly for performance-critical CLI tools, or highly specialized servers. Typically, these have a narrowly defined job that must be done reliably and efficiently. Rust shines at this. These tools run reliably with almost no changes, often for years at a time.
But I have learned that it's often less useful putting policy and frequently-changed business logic in Rust. That kind of code is often accessible to more developers in a strongly-typed scripting language.
> That kind of code is often accessible to more developers in a strongly-typed scripting language.
I assume that you’re thinking of TypeScript or Python with type hints.
Do you mean that these languages are more accessible because they are more widely known by developers or because codebases in these languages are easier for some reason to change frequently than codebases in Rust?
Mostly because TypeScript and typed Python are often more accessible to more developers.
One of the tradeoffs of programming in Rust is that you need to distinguish between value vs. reference, stack vs. heap, and a number of other choices like that. This helps performance tremendously. But unless most of your team is comfortable making these decisions, they add a modest tax to all Rust code written. At least compared to TypeScript.
Rust is a fantastically productive language now that I know it well. There's definitely a point on the learning curve where Rust can be competitive with several popular scripting languages. But getting an entire team of programmers to that point takes a concerted effort.
So for many teams, Rust is the biggest win for problems like:
- "I need a server that handles a few kinds of specialized requests at high speed."
- "I need a CLI tool that can process 60 GB of input data on a regular basis."
- "I need to parse this file format that involves 197 special cases that I must keep carefully straight."
Interesting. By contrast, I wish I had used less Python and more Rust for my company's product because Rust is considerably more productive. We were building gRPC and web services.
I just haven't found it to be the case that developers have a very hard time learning it, but we haven't grown to the point where that would maybe be the case. We also lean heavily into microservices so "oh there's no library in Rust but there's one in Python" is low cost, we can just write a service in Python.
In my experience, Python is one of the least productive programming languages for projects with more than 3 people. If you have a big project, you are going to spend a lot more time reading code than writing it. Python is write-optimized.
By contrast, using Rust makes it easy to force a readable coding style on yourself and others.
Python is not readable to you? The beginner friendly, whitespace-enforcing, almost-like-pseudocode-in-English language - that Python? - is not readable enough, but Rust, where you liberally sprinkle ', {}, !, &, :: or #[] everywhere _is_ readable to you? You must be trolling.
You know what Rust looks like to me? Perl without the dollar signs. There's your write-only language, you just have it backwards.
Being able to understand the syntax and by implication being able to understand a single piece of code in front of your eyes doesn't mean you will understand what it actually does. If you're talking about a small piece of code mainly using well known APIs, maybe you will understand it perfectly. But with code calling custom functions in a big codebase? Even when looking at a simple function call in a class:
- if it calls a function on `self`, where is it actually implemented?
- if you pass arguments, can they be mutated?
- can the function throw an exception?
you can't really answer this questions easily and I would argue answering them in Rust is much easier. Also because tooling for statically typed languages can derive much more than for dynamically typed languages.
Surface syntax is the most subjective aspect of a programming language, but Rust uses just a few symbols in a very consistent way. It's nothing like Perl. The indentation rule is nice for small examples, but it tends to break down in cases that are even slightly complex. Which is why, e.g. Haskell can use semicolons and curly braces as an alternative.
Wait what? Python, if anything, has a reputation for being readable! Perhaps this is more of a criticism of dynamically typed languages in general? As the team grows, adding mypy types can help a lot.
Personally, it's the scoping that drives me nuts more than the weird version of dynamic typing (which is awful, but can be fixed). A lot of "pythonic" syntax is also very python-specific and hard to read if you aren't a python person. There is also the implicit type conversion and the runtime errors for things a linter or a compiler would catch in another language.
If you only work on <1000 line scripts, Python is a blast. If you work on projects with >1M LoC, it gets very hard to keep everything you need in your head if you want to understand code that other people have written.
Absolutely agree. I've wasted hours trying to deduce the data types being passed around in Python when this takes milliseconds in other languages. My steadfast rule: if a team is using Python, I don't join that team.
I think that most of why Rust is popular is just that it's a very modern programming language. It has all the nice things people want and surprisingly little of what they don't. So it's no surprise that for some people it's more productive.
But the article pretty clearly outlined a super simple CRUD app. These days that's a problem space so well defined you can practically specify the whole thing in command line flags. Rust really has little to add there. For other stuff, yeah Rust can be super cool.
I can. I work on a ~20k loc Python service as well as a ~10k loc Rust service. The Rust service is only half the size, but the problem space is many times more complex.
Whenever I work on the Python service I feel like I'm working in clay. Like, everything kind of sort of works. It won't at first, but then you just poke at it with a stick until it does. In Rust, it works or you're told exactly why it doesn't and then you fix it.
I change a struct and the compiler provides me with a list of places that need updating.
I have many reasons (mypy, type system, testing, venv, python 2), but really the big one is rust's superior type system as well as general tooling (lsp is way better, cargo and clippy are phenomenal).
I've read and written a huge amount of python and rust, also a fan of rust and don't find it particular difficult to read.
As long as there is proper linting, both should be readable.
That being said, python is about the simplest language there is to read.
Of course if you have many times nested and improperly indented list comprehensions- sure that gets confusing. But that should be fixed with proper linting (black on its own should do it)
Curious about the examples you've seen that were difficult to parse!
> Rust was used primarily because a couple of the founders of the company were Rust experts
It's remarkable how significantly the first few months of a project impact the first few years of a company. How quickly casual & spontaneous decisions, largely based on convenience and familiarity, ossify into legacy drag.
I've noticed that Rust is in an interesting spot - it has a lot of overlapping appeal with various groups.
- C/C++ developers like it as an alternative for low level languages
- Functional programmers like it because of it's functional features
- "Cargo Culters" / "Hot Language" / "Flavor of the Month" followers like it, as it's in vogue at the moment (this is the crowd that has likely shifted from various web/API scripting languages over the last decade - (php|ruby|python) -> nodejs -> rust)
However, as the article alludes to, for the third crowd, Rust is just likely not a great fit (even if it has a lot of neat features and introduces some great concepts others may not stumble on of their own accord) when your implementation doesn't do much beyond web dev/API/scripting purposes.
I've written a few APIs in Rust and I'm kinda baffled on what the author is trying to convey. On the one hand he mentions that it's a simple CRUD application, on the other hand he mentions complexity that arose from using Rust. I wish he gave some concrete examples of problems that they have. From my experience, when writing web APIs, most of the problems with Rust are not really that visible. Most of the time you get a request, process some data or interact with a database (or both) and return the response. In this scenario you don't often have to deal with lifetimes or any of the advanced stuff.
While I respect the author's anecdote, this doesn't match my experience. I've been programming for 20+ years across a wide array of languages and I'm by far the most productive in Rust.
With a competent teacher, experienced devs should be able to pick up on the memory model pretty quickly, and that is really the only initial blocker to productivity. After that a dev can essentially write procedural code if they want, like other common languages, while refining their thinking and practices towards idiomatic Rust.
I agree mostly with this, however, ownership can be strange in Rust if you’re not familiar with it. Something simple like a linked list can be challenging to implement.
To expand this a little, the perception that linked lists are easy comes from the fact that the garbage collector does all the heavy lifting on the background. Using a non-GC language reveals the complexities involved.
how can you be more productive in rust, where you do have to worry about object lifetimes than in languages where you don't, and where you can focus more on algorithms, say java?
1. I'm familiar with lifetimes and they don't slow me down anymore. Also, I usually structure my projects such that I don't need to explicitly express them.
2. I spend a lot less time fixing bugs because Rust makes them impossible.
Because the time you spend thinking about lifetimes is insignificant compared to the time you save by not having to fix bugs that lifetimes prevented from ever existing.
Isn’t it about time for a language with a runtime with an HTTP server and SQL database? There’s literally millions of us writing basically the same code over and over again: listen on port 80, parse and transform text more times than necessary to make a SQL call to then parse and transform the returned rows into text more times than necessary to return some JSON or HTML.
The wasted clock cycles and developer hours more than makes up for the slow down in productivity rates we’ve seen across advanced economies lately!
I mean, I’ve written C that listens on port 80 and parses HTTP requests directly into SQLite op code but that is definitely not how it is working when you glue together a bunch of npm modules.
Just like how SQL gives you a faster way to not only store and search through binary trees but also to write the instructions to do so means that we should be able to do the same for web applications.
I’m not sure that most parts of a web application need to be Turing complete. User permissions? Data validation? Can’t we have GUI tools with a 1:1 mapping to more declarative code, ala SwiftUI? Why are we using a garbage collector for an HTTP request when we could just be freeing up memory after the response is sent?
I haven't tried this, but I think you can get fairly close by generating the HTTP/SQL serialization code. When I get time I want to write an openapi schema amd generate the server code, write a db schema and generate the db code (I want to try https://github.com/ariga/atlas with Ent for this), then write the business logic between them and see how well it all works
If you squint your eyes, sure, but if you’re looking at the details of something like Django or Rails you’re still having to wire up your own analytics, A/B testing, user permissions, headless browser testing… first, why not make all of these language features?
Second, when you wire up a web server module with a SQLite module with a JSON module and translate between them in Ruby or Python you’re taking a really big performance hit. Just think about how many times something is being translated into an unused intermediate state because that just happens to be how the ready-made pipes are sized.
I’m saying, let’s build a bunch of ready-made pipes that are built specifically for SQL backed HTTP servers and with the features that we all seem to use on every project.
The end result should make it easier and faster to build a more performant web application.
It's not a language, because why do you need language for that. But it's called Hasura, written in Haskell. It's a server which translates Postgres into GraphQL.
Modern OpenJDK comes with a built-in HTTP server, and you can also embed a relational database like H2 or of course SQLite. You can use it from many different languages, of course. Actually it sounds from the article like Kotlin or Java would have been a better fit than Rust for what they were doing. There are also things like Postgrest which turns postgres tables into REST APIs automatically.
But here's something to really chew on. Do we need the HTTP server at all?
Consider: why exactly do we need so much middleware plumbing for ordinary database driven web apps? A lot of this is to do with a winding evolutionary path rather than what you'd design with a clean slate.
Something I've been experimenting with lately is writing apps that connect directly to an RDBMS using its native protocol, over the internet, from a desktop app. Obvs the next step after is to try the same with a mobile app. Historically we've relied on web browsers and written translation layers for a few different reasons, but tech has been improving over time and some of those reasons are becoming obsolete:
1. Free relational databases were really rough back then and the commercial DBs often had weird limitations around distributing DB drivers. These days it's easier and postgres is a lot better.
2. You needed to slather lots of caches and other stuff in front of the RDBMS if you had high traffic. Modern DBs are a lot better at materialized views, cached query plans, read replicas etc. Postgres has a variety of load balancers that can sit in front of it and work around its per-connection overhead.
3. Security wasn't good enough. Nowadays you have features like row level security, security-definer views etc in the free DBs.
4. People translate everything to HTML/HTTP partly because it was too hard to distribute and update apps to Windows desktops/laptops in the 2000s, due to MS just generally losing the ability to execute and being distracted by stuff like Longhorn/WinFS. Browsers solved the basics of caching and refreshing of code for you. Now there's https://conveyor.hydraulic.dev/ which makes distributing desktop apps way easier. It's a lot more feasible now to just knock out a quick desktop app and upload to an S3 bucket, with built in online updates. Also the next release will support force-update-on-start, so you can get a web like experience where the user is always up to date so the schemas and client can be evolved in parallel.
If you could just write your UI using a 'real' UI toolkit and invoke directly to the DB from the UI code, you wouldn't need a lot of the microservices plumbing anymore, you wouldn't need to serialize stuff to JSON or HTML. You could just use the underlying DB protocol as an RPC layer and let the DB enforce business logic. It can be done for common kinds of enterprise apps today: there are only a few problems to solve, none of them especially hard. For instance a little service that turned OAuth SSO into client side certificates would make it easy to connect to a DB without needing to manage usernames or passwords. A tunnelling solution to get through awkward firewalls would also be useful (websockets etc).
For consumer apps there's more complexity. You wouldn't try to implement twitter that way for instance. But still, for the kind of SaaS-y thing that the article is about it could have saved a lot of complexity and improved iteration speed.
Fully agree with you that in a perfect world desktop apps would be the best for end users and programmers. In that world we would be still writing code on Delphi or Jbuilder. One major problem with desktop apps is still distribution. Entry point for majority of modern desktops is still a browser, through which you search, discover and download. Just like with mobile apps, older versions will remain in use and require support for various reasons (imagine IT department that maintains images of their devices — their update cycle will not be what you expect it to be). Another one is that you cannot really put all logic on client and trust that client to manipulate your data in DB, so you will have significant amount of code on the backend. Stored procedures can technically solve the problem, but developer productivity will suffer terribly if you try to build your middleware inside the db. The tooling for Oracle DB or Postgres and tooling for Java are like stone hammer vs a toolkit from Bosch Professional.
> This project was a cloud-based SaaS product that is, more-or-less, a conventional CRUD app: it is a set of microservices that provide a REST and gRPC API endpoint in front of a database, as well as some other back-end microservices
Of course.
Use Java/Kotlin or Node.js/Typescript or Go or Python for your basic web services.
Easy test: Would you at least seriously consider using C/C++ for it? Only then should you use Rust.
The difference between C++ and Rust here is that Rust has a large variety of easily-accessible third-party libraries you can drop in with a one-line change to your Cargo.toml. I think the assessment is no longer that straight-forward. There's some solid crates for building these types of endpoints now, but an honest self-assessment is required to see if it really meets your needs from a staffing perspective.
I'm a Rust-stan for sure, but I probably wouldn't use it for this task either as it'll be hard to find other people who know how to use it - and also for this task. Here's hoping that changes, though.
[edit] I might also add it's not a good fit for making these types of endpoints at a startup as the goal of a startup isn't really to write perfect/correct/safe code, but rather to ship a product as fast as you can so you can iterate on the product-market fit. Quick and dirty in another language is probably a better fit for the business case. Rewrite it later.
Right, but you're company that needs to hire people to write that. It's entirely fine if you're already a team that knows Rust, but if you have a need for an app you need to hire more devs to build, picking the "obvious" stack usually comes with benefit of being able to recruit people more easily.
> 1. The risk of future unsafety (a GC language will always safer than rust)
[citation needed]
> 2. Faster development that's easier to change on a dime.
Yes, development can be faster at the expense of safety, which is a trade-off that favors the use of other languages at early company stages. Agreed here.
> 3. No risk of memory leaks (which can happen in safe rust)
... at the expense of making collection non-deterministic and significantly more resource intensive. Not to mention that the non-determinism means you generally end up grafting your own resource management scheme on top. I've never seen a company that hasn't at some point had someone crying over GC tuning. Leaks are safe (which is why they're allowed in safe Rust) and generally speaking both a non-issue - and very easy to track down.
Rust has been my daily driver at home since like 2015, and I have to say, I've never once gone "man you know what this thing needs? Shenandoah." I write a bunch of Kotlin too, for what it's worth.
These trade-offs are well known and there are pros and cons to both.
1. No citation needed. I can take rust and do unsafe things in it. I can’t in a GC language. Rust allows you to use unsafe.
2. No. It is not at the expense of safety. Again, Rust allows you to do unsafe things using the “unsafe” keyword. GC languages simply will not allow it.
3. Strong reference counting memory leaks is not easy to track down. Many embedded systems have been taken down by them.
Those were all memory safety violations in Unsafe Rust. Safe Rust can't have them. And doesn't Elixir only guarantee its safety at runtime, which is way less useful?
Right but if you imported one of those crates, then your “safe” rust would have memory safety issues.
So bam, you just introduced memory safety issues into your web API. This is like a C++ dev using C++ and causing memory issues unnecessarily just because they don’t want to use a safe language.
And Elixir has concurrency and memory safety and it’s guaranteed, so no package you import can violate it. Unlike rust, where you can use safe rust and think there’s no violation, but then use a crate and suddenly have memory unsafety and concurrency violations.
These crates worries are wayyy better than normal C++, but they are terrible compared to any actually safe language.
My program might also crash if there's a bug in the kernel's filesystem driver, but that's not my bug. And Elixir has FFI, which definitely lets you do unsafe stuff.
It’s not about crashing, it’s about exposing memory that could lead to security vulnerabilities.
And FFI? As in the Unix extensions? That’s the operating systems calls, not the language. By that logic, python is as dangerous as C++.
It’s crazy how so many people on this thread say rust is the only choice because of memory safety, and are now bending over backwards trying to claim memory safety is no big deal. So much so FFI is now being brought up smh.
Those sorts of security vulnerabilities are possible in Python/Elixir/etc. In both Rust and Python/Elixir/etc., they're only possible when you do certain unusual things. You are claiming that the Rust way does count, but that the other languages' ways don't count, with no justification.
Memory safety does matter. Rust is memory-safe and C++ is not, so your conclusion doesn't follow.
> 1. No citation needed. I can take rust and do unsafe things in it. I can’t in a GC language. Rust allows you to use unsafe.
That's not what you originally claimed though. You made some claims about 'risk of future unsafety' and I'm not sure what that means?
For the second half of your claim, Rust's safety guarantees extend well past memory safety - and a collector doesn't guarantee memory safety at all. Yes, most GC'd languages are memory safe too. However, you can just strap the Boehm GC to C or C++ [2] and that doesn't suddenly, magically, make it memory safe.
> 2. No. It is not at the expense of safety. Again, Rust allows you to do unsafe things using the “unsafe” keyword. GC languages simply will not allow it.
Safety means a lot of different things. GC isn't a replacement for safe Rust. Rust offers many kinds of safety, memory safety is just one. And yeah, unsafe is a tool for implementing certain things that cannot be expressed in safe Rust, but it's extremely rare that you would dip into it in production code. It's more for library authors who wrap unsafe APIs in safe ones.
If you're curious what 'safe' rust really means and why the GC doesn't subsume all its features - and the difference between safe and unsafe Rust - I'd recommend [1].
> 3. Strong reference counting memory leaks is not easy to track down. Many embedded systems have been taken down by them.
Sure in embedded it can be hard, but on a PC you can just use valgrind or the leaks tool. If you're writing embedded code in Rust just don't use Rc or Arc boxes and you can't form a cycle. Remember there's no reference counting in Rust at runtime unless you opt into it with a shared-ownership reference-counting box. All Rust's reference counting happens in the compiler and once the compiler determines your object is no longer referenced, it drops it. If it cannot make that determination statically your build will fail.
Reference counting is generally how resources are managed in GC'd languages since you're explicitly not to rely on the finalizer ever being called. Files, sockets, etc. It's just trading off one kind of problem for another.
[edit] Note that you can also leak memory in a GC'd language by keeping a reference to objects you no longer need - for instance, in some long-lived dictionary or array, or in a static. [3]
Right, but to my knowledge the other “safety” of rust is concurrency, which is easily attainable with something like Elixir.
I’m not saying a GC guarantees all safety, I am saying you can find a GC language that gives all the safety (and technically more) of rust.
My quote of “future unsafety” is that even if your code currently uses only safe rust, their will always be the chance someone adds unsafe rust, increasing the safety issues.
It just seems you can find a GC alternative that will never run into the issues rust can cause, and it will be faster to develope/change (because no borrow checker), and there is no downside.
> I am saying you can find a GC language that gives all the safety (and technically more) of rust.
Giving "all the safety" is theoretically possible, but "technically more" is not. And even if you did have "all the safety", you'd still have the big performance disadvantage with a GC language.
> My quote of “future unsafety” is that even if your code currently uses only safe rust, their will always be the chance someone adds unsafe rust, increasing the safety issues.
No there won't. This is what #![forbid(unsafe_code)] is for.
> Easy test: Would you at least seriously consider using C/C++ for it? Only then should you use Rust.
This is a great point. I understand liking a language, but don't bring Rust into a GC space (in industry, personal projects can be what you like!) You can find a GC language with all the features of rust you like.
I agree with you 100%. It's the thing I miss most when not writing Rust. I will say that it as a feature alone isn't a good enough reason for me to write Rust, though!
I've used Go for almost two years on a side project and its type system is exactly why I'm doing Rust now. In my book, it is not okay that I add a new field to a struct and then nothing happens. No compile warnings, nothing. It's just assumed that I then wanted the zeroth value whenever it's created. ... And no adts. You just can't make something as simple as
enum Foo {
Bar(String),
Baz(i32),
}
Why? It's such a fundamental thing to be able to say "this piece of data is either this or that.. and then have the compiler tell you if you missed a case.
Ada is on my list of languages to look at. I'm cautiously optimistic about that one. But would you pick that over Rust as the simpler alternative? "Look guys! We're not moving fast enough with Rust because nobody seems to be proficient in it. Let's go with Ada instead!" .. I jest, but I will check it out and I really hope it hits the sweet spot for me
The rest all are missing basic things. Like, I love TS, but it's absolutely bonkers because js is js. I once worked on a 250k loc project of js/ts, and we had nothing but trouble
I didn't see it here, but one of the points that doesn't seem to be expanded on too well is that despite liking Rust, I wouldn't use it for a basic web server in a startup environment either, mainly because the whole environment around web stuff is too immature in my experience.
Ruby on Rails certainly isn't perfect, but it's a highly battle-tested web framework. It's been so widely used for so long that you can have high confidence that you won't run into any bugs that will require digging into the guts of it. You can write code against it and be highly confident that any bugs or misbehaviors are in your code, with difficulty of finding and fixing dependent only on how good the code you've written is.
With Rust, I don't think that's the case. The DB and web server related libs just don't seem to have that much manpower behind them, and haven't had big complex services built on them in production for years. If you want to do anything mildly complex, there's a good chance you'll have to dig into the guts of these to figure out what's really happening or to resolve some weird bug or add a feature. That'll require a lot of skill, even if the code you're actually writing is mostly straightforward.
The arguing about Rust seems to miss that Rust protects against more than just security bugs.
If you've ever generated some object out of some factory and then tossed it on a list of things and then gone back to generate a new object out of the factory and found that the first object you put on the list got scribbled over because you forgot to clone it, then that's a category of bugs that rust entirely prevents.
And I thought I was pretty good about reasoning about data ownership in other languages, but the borrow checker is teaching me how to be a lot more careful.
You can also use immutable/persistent data structures and GC, which gives the same safety from bugs, but is much cheaper from the perspective of precious time spent.
It took me almost a year to be as productive (and my code as canonical) in Rust as I was after over two decades of C++.
After a month of Rust I was still fighting the borrow checker and grokking basic concepts of the language. So no surprise in what you wrote.
However, after that year I am probably a factor of two to ten more productive in Rust.
The speedup is about two for everyday code (if it compiles it runs and doesn't crash). Ten (or more) when I need to bring in a dependency.
To qualify the latter: if a crate with same functionality or a Rust wrapper for a C/C++ lib exists, the factor can be much, much higher than ten since it is just adding a line to Cargo.toml.
If the dependency is a C/C++ lib it is as slow as using it from C++. It needs to be integrated with the build and that takes about the same time plus writing/generating a wrapper.
All that said: I had to write code in C++ the other day again. It almost felt painful after three years of only Rust.
That is simply not true. Lots of build systems assume certain things in certain places and output to be something (what if you want a .so but the build only spits out .a etc.). In any case you may have to resort to shell scrips or launching shell scrips from your own build system. In short: it sucks.
And there is also the case when the dependency has dependencies itself. Adding a dependency is one of the biggest PITAs in any C/C++ project. For these reasons:
1. No standardized build system.
2. No standardized use/configuration of common build systems (everyone uses CMake/SCons/whatever slightly differently)
3. Dependencies may have other dependencies, which use yet another build system (see 1. & 2.)
This is the reason why 'single header library' is a 'feature' C/C++ devs look out for when deciding on taking on a dependency.
It makes adding such 'libraries' as deps a no brainer as far as the above is concerned. I.e. your average Rust crate is to your Rust project as is a single header C/C++ to your C/C++ project. Every other kind of dependency is potential pain.
Although I like Rust, C++ keeps being my systems language when coding outside Java, .NET and Web.
Why?
Those ecosystems are built alongside C++ tooling, so no need to add an extra layer of indirection.
I already have the safety from those ecosytems, if I am reaching out to C++ is exactly because I need to do some unsafe stuff, or binding libraries that are anyway written in C++.
Well if you had 10 yoe with C++, you would certainly not achieve the same level of productivity with Rust for a while. Your point is valid in the sense that you should use the tool that you know if you need results, but it's not great criticism towards Rust.
"I would much rather have my team sink time into debugging the occasional memory leak or type error for code written in, say, Python or Go, than have everyone on the team suffer a 4x productivity hit for using a language designed to avoid these problems entirely."
I'm not sure if I agree with this. In fact, I'm pretty sure that I don't. These "occasional" nuisances become big blockers after a while and not only decrease productivity but hurt customer goodwill as well when they're not discovered in time.
There's a reason why we see so many articles like "we rewrote x in rust." Rust makes sense when you've scaled to the point that you're seriously considering performance. Sure, starting with rust can potentially save you time, money, and refactoring down the line but only after you've reached a point that few startups ever hit. Otherwise you're limiting yourself with slow development times (compared to, say, Python) and difficulty hiring for very little benefit. I'm sure this startup's founders love rust (and so do I), but ultimately you need to prioritize the company first and that probably means using the most popular, boring tech stack imaginable in the beginning. The more you deviate from that, the harder the early days will be. It's far too easy to be swayed by exciting tech and forget that all that really matters is that you solve someone's problem.
Trying too hard to write good code can be a deadly mistake for an early-stage startup regardless of the language you use.
If you're really a startup, looking for a product-market fit, then you don't know exactly what you're building yet. There's a high change you're going to throw away a lot of code.
e.g. you spend effort caring about scalability, flexibility, proper architecture, full test coverage — only to hand it to users who will say they won't buy it, it's not actually the feature they wanted. You can learn such lessons with crappy copy-pasted code, and then worry about code quality once you've figured out what to actually build.
Sure, but good code saves time even on really short timeframes.
Every time I've worked on—or seen other people work on—a "we must hurry at all costs project", enough time is wasted on debugging, firefighting and friction that just building a decently clean system from the get-go would have been faster. I've seen this unambiguously on "throw away" data science code meant to produce a single report (or normal science code meant for a single paper), and that's a much shorter-term effort than anything at a startup. The tradeoff is even clearer once you start working in weeks and months, much less quarters or years.
It all comes down to whether the team is faster at writing safe code in a tight language or debugging a loose language. My experience has been even with the lossage due to initial errors, teams I'm on have been better debugging errors, especially when the task is so small that the errors are obvious.
I've seen a pretty clear difference in success with less popular technologies based more on management philosophy and culture than the technologies themselves. Management seeing individual developers as fungible—"lack of fungibility in the engineering team can be a real liability"—is a massive red flag in this regard. I am not surprised to hear Rust didn't work out in an environment like that!
... but especially for a small team, having people able to cross-operate on adjacent pieces of the tech is vital. You can't just not develop if all your Rustaceans catch COVID at the same time.
So "there's a huge bifurcation between people who know this language and people who don't" is a good reason to pick another language. As with so many such techs, network effect can dominate other benefits.
> Management seeing individual developers as fungible—"lack of fungibility in the engineering team can be a real liability"—is a massive red flag in this regard
Yeah, the infamous tin soldier approach that's often used to excuse using worse but more entrenched languages.
While the author's anecdotal evidence is coherent, it's not enough to establish causation. Especially if the measure of productivity is feelings of sluggishness.
Rust or not, there's an argument to be made for statically typed languages improving productivity over the long run [0].
All development can feel sluggish depending on the work hours, estimates, business timelines, engineering skills, and the task at hand.
Programming languages are common targets because
- we use them so much
- there are so many
- and all developers, at some point, must choose to dedicate their time to one over the others.
Finally, productivity itself is only one performance characteristic. To focus only on that (without even a good definition of effect or measure) makes content precisely what the author claims to avoid; flame bait.
As usual, very sad to see how little C# is mentioned as an alternative, having very robust, easy to use and performant stack of standard and third-party libraries for building the exact scenario outlined in the article.
Especially that is has first-class support for gRPC, runs on every cloud/on-prem host you can think of and doesn't force you to go out of your way to get most performance out of your implementation. People should not be mentioning Java first as a GC/JIT-based language for cloud given how competitive C# stack is and how much more you get straight out of the box.
Java just has an incredibly long, deep, relationship with the open source/FOSS world that is going to take C# a lot of time to catch up with. Java's also consistently had a much better story with backwards/forwards compatibility and versioning.
I haven't used it in years, but I didn't find the anywhere near as full an ecosystem around C#, it's tooling, it's libraries, etc. It didn't help that there was a lot of confusion around what ran on which runtimes/versions.
I'm in the middle of a rewrite of my finance analytics library in Rust. I chose it for all the compelling reasons: speed, safety and a great package manager.
About 2 weeks in, I hit a blocker, where the most performance critical layer was running 30x slower than C#. It's two weeks since then, and I'm still blocked. Experienced programmers on the language Discord and Reddit have been stumped as well, and I get the impression this is what one should expect from an immature ecosystem, but also makes me fear that it will never actually catch on.
Point is, I think even the selling point of speed is inappropriate.
I can only give you some guesses, but I think there's some false sharing type of bug going on. There's also an issue with the fact that LLVM hasn't released a target for my CPU yet (I use a Zen 4), and who knows what bugs are caused by targeting the wrong CPU.
In a single threaded version, it beats C#, though not by as much as I would have expected. The essence is that I have to run the same calculation on a large array of doubles that spits out another array of doubles, so I parallelize it with SIMD and threads. In C# I max out at AVX-2 for instruction level parallelism, but for Rust, I use AVX-512, and it's not even 2x faster, though it is faster--it should be more than 2x faster because AVX-512 has better instructions to work with. But when I combine this with doing the calculation in threaded parallel chunks on the array, it goes far slower than it should.
> I use AVX-512, and it's not even 2x faster, though it is faster--it should be more than 2x faster because AVX-512 has better instructions to work with. But when I combine this with doing the calculation in threaded parallel chunks on the array, it goes far slower than it should.
You might be saturating your memory bandwidth to the point where it just can't go any faster. Since it seems your problem is easy to parallelize, you might want to experiment with the rust-gpu ecosystem.
I will say that when I do the same parallelization scheme using non-avx operations, it accelerates properly and goes far faster than the avx versions. One interesting caveat is when the compiler autovectorizes non-intrinsic code, the problem persists.
> I’ve worked in dozens of languages in my career, and with few exceptions most modern, procedural languages (C++, Go, Python, Java, etc.) all very similar in terms of their basic concepts. Each language has its differences but usually it’s a matter of learning a few key patterns that differ across languages and then one can be productive pretty quickly. With Rust, though, one needs to learn entirely new ideas
I'd argue C++ is not anywhere easier, if you are using its features or need to analyze the code that does. It won't be anywhere quick.
> I don’t know about anyone else, but at least for me, when I’m building a new feature I usually don’t have all the data types, APIs, and other fine details worked out up front. I’m often just farting out code trying to get some basic idea working and checking whether my assumptions about how things should work are more-or-less correct. Doing this in, say, Python is extremely easy, because you can play fast and loose with things like typing and not worry if certain code paths are broken while you rough out your idea. You can go back later and make it all tidy and fix all the type errors and write all the tests.
> In Rust, this kind of “draft coding” is very difficult, because the compiler can and will complain about every goddamn thing that does not pass type and lifetime checking — as it is explicitly designed to do. This makes perfect sense when you need to build your final, production-ready implementation, but absolutely sucks when you’re trying to cruft something together to test an idea or get a basic foundation in place. The unimplemented! macro is helpful to a point, but still requires that everything typechecks up and down the stack before you can even compile.
This rings so true for me. I could "mock up" entire apps using interfaces in Java, without having to actually write impl code. I could be sloppy as hell around the edges, but that didn't matter, because I could get the large design right without the compiler screaming.
In Rust, there is the chasm between no code and anything that works, feels so draggy.
I'd say the author brought a good point of why you should use Rust even for a CRUD app. (and yeah, I am a Rust fanatic and quite biased too). But hear me out.
> Over time, we grew the team considerably (increasing the engineering headcount by nearly 10x), and the size and complexity of the codebase grew considerably as well.
At this point, Rust is providing security and protection from technical debt. You have lots of new comers to the company/project and each one of them is adding or changing something. Developers being normal human beings have the tendency not to read documentation, old code or discuss with 100 engineers before building something.
Rust forces that on them, to a certain degree. Sure, you can move faster without Rust for now; but you'll pay the price later on. I am working on a company where we are doing microservices both in Rust and TypeScript. It's faster to get the job done in TypeScript; however, the cost is the maintenance later. The TS microservices breaks easily, regress much faster when someone modifies them, and are prone to invisible bugs. Rust is more solid in that front.
But in a world where speed of delivery and delivery itself (just deliver and deal with it later), Rust will definitively not shine. This is kicking the problem down to somebody else.
Another point: Because of that, Rust libraries are usually pretty solid. Comparing to the untangled mess in NPM, Rust will break down much less frequently.
Sweet, well the large feature we just spent 9 months building at a company that has yet to turn a profit is not anything that anyone wants to use so we’re throwing your beautiful porcelain dollhouse down the basement stairs and building something else.
> Sure, you can move faster without Rust for now; but you'll pay the price later on.
The flip side argument for an early stage company is that if they can’t move fast they may not survive. Not having tech debt is great, but it’s better to have tech debt and be alive than no tech debt and out of business.
To be clear, I’m not personally taking a hardline position on either side here. I think these kinds of choices are always a balancing act. Moving too fast can kill your business just as effectively as moving too slow: it’s just as hard to ship changes to a permanently on fire ball of spaghetti as it is a pristinely typed piece of clockwork, and sometimes harder.
> What really bites is when you need to change the type signature of a load-bearing > interface and find yourself spending hours changing every place where the type is > used only to see if your initial stab at something is feasible
Editor/IDE should be able to do this in a few seconds.
> With Rust, though, one needs to learn entirely new ideas — things like lifetimes, ownership, and the borrow checker.
Those three things are actually just different facets of the same thing: ownership. The bad news is that you must learn Rust's ownership model to use Rust idiomatically. The good news is that you can do a lot without learning Rust ownership model at all. Just clone all your values. Not advisable for production code, but great for getting over the ownership model hump.
And once you've learnt those things and got some experience writing real world software in Rust, you may well find yourself more productive in Rust than, say Ruby on Rails.
Context matters a lot, and you shouldn't be making tech choices based on the way the wind is blowing ("tech radars", what's hot in the blogosphere, etc.). If hiring teams to work on your mostly-CRUD app easily is a high priority, then Rust probably isn't a good choice. If you have a team that already knows Rust, and you need to add some web app / service, then Rust is a perfectly fine choice, on the grounds that support for web stuff is "good enough" now, and the best tool for the job is often the one you already know well and are already supporting.
If I'm building stuff for _myself_ and it's getting too fiddly for a bash script, then I'll always default to Rust just because _personally_ I'm way more productive in it than anything else.
I think you meant “borrow checker” because sure it does. It’s called a const reference. Want a mut borrow? That’s a pointer.
That Rust can check these somewhat more explicitly (rather than via good coding style) and that C++ also allows you to do arbitrary permutations (a la non-const references) is what you’re talking about. But ownership is very very real in C++! Just look at the craziness that is move semantics!
> I think you meant “borrow checker” because sure it does. It’s called a const reference. Want a mut borrow? That’s a pointer.
These have very different semantics. Lexically you can only have either 1 mutable reference or N immutable references at a time to a given object. This is the foundation for a lot of the safety and aliasing [2] guarantees. Just because they both use an '&' doesn't make them equivalent! :)
Don't get my started on `std::move` which doesn't really move, and continues to allow you to use the source object - in whatever condition it may be in. These are also not the same. C++ move semantics are sort of the 'ruined fresco' [1] of Rust move semantics.
I guess nodejs might also be throw into the party, as it is anyway needed for many SPA frameworks, and for better or worse, Go, given its relevance on DevOps space.
If it's complex business logic and will grow bigger: Kotlin + Micronaut/Spring Boot + Krush/Hibernate
If it's quite simple and has a clear outlined border: Go + Gin
"Rust has made the decision that safety is more important than developer productivity. This is the right tradeoff to make in many situations — like building code in an OS kernel, or for memory-constrained embedded systems — but I don’t think it’s the right tradeoff in all cases, especially not in startups where velocity is crucial"
This doesn't match my experience. It took some time to get up to speed, but at this point it's much faster for me to write something in Rust than in other languages that I'm proficient in that are ostensibly faster to develop in (e.g., JavaScript).
Part of this depends on one's bar for quality. In Node.js I could write `JSON.parse(input).foo.bar` really quickly. In Rust, I'd probably write two struct definitions with `#[serde(Deserialize)]` first, then a similar line. It'd take 45 seconds longer, but when I'm done, the Rust program validates the input and prints decent error messages.
FWIW the “parse don’t validate” attitude which has become popular in TypeScript circles does this equally well. In Rust you’ll define the types you expect to deserialize as structs with Deserialize trait implementations, in TypeScript you’ll define a basic schema with runtime decoding and corresponding (inferred) static types. Like you say, maybe 45 seconds more typing, with the same value guarantees.
I like that this is enforced in Rust (and that many other things are well beyond what tsc guarantees), and I like it in Node too and I’m glad that a certain corner of the ecosystem takes it seriously.
To me the greatest thing about TypeScript is that there are times that I absolutely know that something doesn't need to be extra safe or to have validation, and I can easily slap a `// @ts-expect-error` on it and either ignore it or come back and fix it later. I know some people think that's a horrible thing and sacrilege, but damn it if it makes it easy to move fast when you need to.
Won’t speak for GP, but I’ll say why I think @ts-expect-error is a useful tool for this. It’s not so much a time saver as a time rearranger. Assuming a few conditions:
- you actually care about type safety so you’re not going to just `as any`
- you have a linter that will block unsafe commits or merges
- you have some type you want to use after it’s established safe, and an idea you want to fill out without breaking flow for even those 45 seconds or whatever to define your schema
- despite your predilection for type safety, you iterate on ideas like that the way people who prefer dynamic languages talk about: damn the torpedoes, I’m in the REPL
Given those assumptions (which fit a lot of TS devs well), the really good thing about @ts-expect-error in particular is it allows you to save an intermediate step for later, catch it in CI or whatevz, and it'll yell at you when, and only when, you leave it unsafe unnecessarily. It’s a good way to have your safety cake and eat your dynamic prototyping cake too, especially if breaking flow for a yak you’d shave anyway is going to sink your flow.
I take your comment to be a jab at JS, which I’m not a particular fan of, but I don’t think it holds much water: the GP’s example is nearly identical in Ruby and Python as well, and would have the same problem in those languages.
Another interpretation would be that Rust and JS cannot possibly cover all or even most programming domains, so there must be other languages out there that need to be in the ring for any given application.
if you also wanted to pull in a node framework for deserilization you could import something like `zod` to have the similar level of nice error messages when invalid input is encountered.
If you were in typescript, zod would also automatically generate your interfaces too.
Does it match the experience of a team and its need to collaboratively deliver software quality across varying levels of engineer experience? If not, then I'm not sure your individual experience is pertinent to the subject matter at hand (using Rust at a startup).
Feel free to weigh my data point as you see fit or ignore it altogether. It differs from the OP’s (which is why I thought it worth mentioning) and their experience is just as valid. To answer your question, I’ve spent the last three years at a startup building with Rust. Before that I spent 9 years at a startup building on Node.js.
Ok, for some perspective here, that 45 seconds longer is about 50-100 times how long it took to do the one liner in javascript. Extrapolate that to generally 50-100x development time for the development of an entire project and you're looking at weeks to months longer development time to solve the same problem. Rust is slower to develop in by a significant factor than js, python, ruby etc.
What I was trying to get at with the (silly) example is that the time-to-first-execution of a working program on good input might be much faster with something like JS, but the time to have a debugged version that fails gracefully (and clearly) on operational errors may not be. It’s okay to disagree, and it’s also okay to agree and still feel Rust makes the wrong tradeoffs. I just mention it because I think people sometimes overweight the time-to-first-run.
I don't really agree with that framing. Most of the languages people use to write web apps are safe! The actual tradeoff rust is making is that performance is more important than developer productivity. It's not willing to sacrifice safety for productivity, but fundamentally the reason to use rust is that it's fast.
If you don't need performance, you might be better off using any number of safe, GC languages. (That said, I actually find the ergonomics of Rust nicer than most other languages. A rust-like language with a GC would be very nice for web backends imo.)
The problem is that, while the benefits of trading safety vs. velocity go to the company, the costs go to the user, as it is the user whose data or identity will be stolen. And this goes well beyond Rust and "mere" memory safety: this extends to every kind of taking things slow and being careful in your coding rather than just throwing something together and later finding out you've made a serious error. This is why we need regulation: startups that take the time or put in the effort to play it safe hate at a disadvantage to ones that, frankly, "don't give a shit", and so the world remains an insecure mess. (I do think the calculus works out differently for large companies that are operating at a fundamentally different economy of scale--and so I do feel like colonies the like of Google or Apple are willing to take things a bit more slowly to ensure a more reasonable result, even though they sometimes do fail--but I almost feel that makes the problem worse, not better :/.)
Rust is essentially not doing anything to prevent the kinds of attacks you're talking about that other mainstream languages aren't already doing. It's a clear win over C and C++! But to a first approximation zero startups are shipping ordinary applicatioins in C/C++. Rust simply isn't meaningfully more secure than Java.
I would argue that a stronger type system will generally reduce bugs, and some of those bugs will impact users (whether by corrupting their data, or breaking a feature, or even causing a security issue).
Most bugs are from the programmer having a misunderstanding about how the system works as they change it. The stronger the type system, the more potential misunderstandings can be verified statically by the compiler before they reach the user.
Other, dynamically typed languages, will require either having unit tests, or running it in prod to find that user who has a null email or whatever, and results in things going awry. Since we're talking about startups, there are of course no unit tests or users, so this is all largely academic. Like, if there are 0 users to trigger bugs, and a bug that could be statically detected by a good type system exists, does it make a sound?
Stronger type systems do reduce bugs. But not all bugs are equal. When we talk about "velocity vs. safety" tradeoffs in SAAS software, we're virtually always talking about security vulnerabilities. There, it's much less clear that stronger type systems reduce vulnerabilities; in fact, the evidence mostly cuts the other way. As far as security is concerned, the major win is memory safety, and you can get that with plain 'ol Java or Python.
I'm bringing this up because in discussions like this, people tend to play fast and loose with the definitions of "safety". The kind of safety we're talking about in a decision between Rust and Java is mostly an externality to the SRE team, not to customers --- in other words, not an externality at all. An internality, if you will.
Most vulnerabilities in boring web apps are not memory safety issues.
I've decided to pick an arbitrary list of security issues where the fixes will be visible to gain some small anecdotal evidence. The top result for "gitlab CVEs" is this august release announcement, let's look at the first three vulns on it: https://about.gitlab.com/releases/2022/08/30/critical-securi...
1. Remote Command Execution via GitHub import
This one was a typeing issue where an object with special keys resulted in dynamic code execution. That could not happen in rust. See the hn discussion here - https://news.ycombinator.com/item?id=33155527
2. Stored XSS via labels color
This can be made into a type-system issue with a good enough type system, text and html should be different types. Arguably this could happen with rust, but honestly, probably would not.
3. Content injection via Incidents Timeline description
This one is also arguably a typing issue for the same reason as above.
----
Hey, look, 3 errors that type systems would help with and which had security implications.
> it's much less clear that stronger type systems reduce vulnerabilities; in fact, the evidence mostly cuts the other way.
What do you mean by that? In what case is a worse type-system (like java or go) going to make it harder to write vulnerabilities than a stronger type system (like haskell or rust)
I'm simply going to say this again: Rust's type system doesn't meaningfully mitigate XSS, RCE, or metacharacter injection. You are equally likely to write an SQLI or an SSRF in Rust as you are in Java.
There are exactly two types of vulnerabilities Rust (and some other modern memory-safe languages) mitigate beyond memory corruption:
1. Java, Python, and Ruby have deserialization libraries that can easily be misused to create RCE vulnerabilities.
2. Python, Ruby, and Javascript have eval and eval-equivalents (which is essentially what your first example is).
You can pretend anything is a type system issue, but neither of these two vulnerabilities are properly understood as type safety errors. Java has a sharply more prescriptive and policed type system than Ruby does, but both have deserialization issues; it's just a generational thing.
Deserialization in new Java code is unlikely; deserialization is much less common than SSRF, which plagues Rust code just like everything else. In 2022, there is no meaningful security benefit to Rust over Java.
There are other reasons to use Rust! People should just stop making up fake security reasons to do it.
As a Rust fanboy, I mostly agree with this sentiment. Theoretically, Rust should prevent “security” issues arising from memory safety issues like heartbleed (maybe not actually heartbleed) but a good GC would too. I do think Rust does push a little harder on correctness via Result types and things like Sync/Send traits, but I don’t have much Java experience to fully compare.
However, type correctness won’t necessarily prevent a DDoS, stolen password, specter, timing attack, phishing attack, etc. Type safety is a nice-to-have but not sufficient for security in SAAS products.
I think most of the things that people feel slow them down about using Rust are cases where the language is forcing you to be kind of pedantic about types (and other invariants encoded in the type system). I totally get that. But the idea that compile-time checks don't eliminate important classes of runtime bugs that impact end users is also ridiculous.
There certainly exist languages like F# and OCaml (functional first domain modeling) and also Elixir and Erlang (100% immutable data and failure control) that provide safety but do not get in the way of developer productivity like Rust.
A programming language can not guarantee a program written in it works correctly, even if you are writing in something like Coq or Idris. People who care much more about velocity than about correctness and security--as they are incentivized to do by the shared competitive game they are all playing--can and do routinely code these errors in any number of programming languages.
I see what you are getting at. I think I may have misinterpreted what you originally wrote. Although my comment still stands alone, I can agree that getting people and organizations to move slower, as a whole, would be beneficial. The venture capitalism going on is basically just people trying to make a buck as quick and as much as possible. There's very little art or craft involved or concern for actual customers today.
Could you expand on this? It doesn't really make sense to me, but I'm also a Rust fan. The premise that Rust prioritizes safety (or performance) over developer productivity also doesn't resonate.
As a consumer, I tend to prefer the thing that is available a bit later (or more expensive) and works well, versus the one that is available sooner (or cheaper) and is less reliable. Maybe that preference is a result of years spent fixing things that could've been designed better?
I believe the point TylerE is trying to make is that features that take longer to deliver are a cost to the user. If a user is waiting on a feature, or perhaps a bug fix and that is taking 4x the time to deliver - the cost is right there on the user's clock waiting.
Many people would argue you with you that the "easier" programming languages are no less reliable, context depending. Framing this in the context of the article a CRUD app, there are plenty of reliable frameworks in many languages.
You're just reiterating the author's point. Not every piece of software written faces dire security ramifications like leaking user identity. Not all software is loading or touching user data at all. Not all software is running in trusted environments. The premise is that Rust may be a good fit if security is a key concern, and even then I would argue it's likely possible to partition the domain into security-sensitive and security-insensitive domains. Cherry picking problem spaces where security matters and making claims about Rust goes against the spirit of the argument.
I would claim the author has an extremely narrow view of where safety matters. Hell: he's even using a pretty safe language (Go) as his alternative! I think the most charitable interpretation I have for the article is that he doesn't actually want to say "I refuse to sacrifice safety for velocity" but "I refuse to sacrifice performance for velocity", which just isn't the same thing.
Well, no, the prudent choice for someone concerned with "safety" in this context is any high-level memory managed (GC) language. It's any boring language that has no `unsafe` at all. What you're actually saying is "I'm willing to sacrifice safety for performance," as invariably the objection to a high-level GC language by Rustaceans is that it lacks the necessary lolspeed.
> while the benefits of trading safety vs. velocity go to the company, the costs go to the user
This is a problem in general, but it's not a good reason to use Rust in a startup, it's a good reason to use Java or C# or TypeScript or another well-worn high-level language with static typing.
Rust's niche is solving a set of security problems that most high-level languages don't have. Choosing it for a startup only really improves your security if the other language you're considering is C or C++.
>This is a problem in general, but it's not a good reason to use Rust in a startup,
It's a good reason to not collect/store information in the first place. If your startup depends on the collection of your user's PII, then I'd seriously question if there's a real purpose to your startup. IF your startup 100% does require the use of PII, then yes, slow the fuck down, and do it right.
I think that’s a bit reductive. The health tech industry as a whole fits this bill — do you question if startups in that sector have a real purpose? There are loads of green engineering teams that have to figure out how to comply with really tricky data handling regulations.
I'm saying that if that is your business, then don't move fast and break things. Health sector isn't going anywhere. It's not a first to market kind of situation. Take your job seriously and secure your data.
If you're some other startup that doesn't actually need PII, but realize it is a fast way to make money, then you should really soul search to ask if you're as amoral as you look to others and if you're okay with that. If not, make a better product that doesn't mean selling your soul to make a buck.
FWIW, immediately after leaving my comment and definitely before seeing your response--but maybe after you opened my comment to read and start your reply--I added this:
> And this goes well beyond Rust and "mere" memory safety: this extends to every kind of taking things slow and being careful in your coding rather than just throwing something together and later finding out you've made a serious error.
Rust's safety goes far beyond memory safety. Java or C# or Typescript don't force you to handle errors and don't force you to exhaustively match and don't have an ecosystem that prioritizes safety etc.
Very little --- I'll go ahead and say, to a first approximation, none --- of that safety has anything to do with software security. The same kinds of bugs that hit Java programs hit Rust programs, with maybe the sole exception of deserialization (which has nothing to do with error handling or type safety).
That matters because that's the kind of "safety" we're talking about when we discuss externalities for end-users --- not servers that need extra monitoring because they might crash.
Idiomatic Rust code is just as cavalier regarding NPEs as Java or Python code. Because the language starts to look really gross when you make your code panic-safe. So you have an `expect` here, an `unwrap` there, and now you're going to have the exact same runtime issues as the more expressive managed languages. No Rust programmer thinks their `expect` will panic, just like no Java programmer expects something will be null. Rust does not force you to "exhaustively match".
I don't think you can reasonably draw any of the conclusions you've made from the individual statements you've said. User PII being misappropriated is far more likely to come from poor InfoSec practices than from the application layer. Memory safety issues are far more likely to accrete to application layer instability and crashes more than not; now that isn't materially good, but it's apples and oranges.
Then you talk about why "startups need regulation" and the lack thereof is why "the world remains an insecure mess" which is another non sequitur drawn from statements which, while individually correct, would never combine to imply that conclusion.
The world is an insecure mess because some societies have not collectively agreed that it is more expensive to be insecure than secure.
Startups don't "need" regulation anymore than regulation is a cure for any problem. In practice, poorly drafted regulation exacerbates rather than solves the problem it is scoped to solve because it opens the door for regulatory capture based monopolies.
That said, I do agree with parts of many of your individual statements: more effective regulations would be good not just for citizens but for startups and society in general; memory safety oriented code tends to be higher ROI for everyone; the world remaining an insecure mess is a pox on global societies whereupon well deployed investment into secure infrastructure would produced significant ROI compared to the present state of the world.
Data security in a SaaS application has more to do with proper role based access to databases than with memory safety. There is nothing in Rust that provides superior role-based security for SaaS than existing frameworks in every major backend language.
I don't disagree, but it sounds like you somehow magically think someone who refuses to trade off velocity for safety--which was a genetic premise that doesn't have much to do with Rust in specific--won't also mess up everything else they touch, which seems far fetched.
I think you're excluding too much of the middle here. Exchanging Rust for Java isn't a meaningful concession to safety, but it is a significant boost to velocity. If you can use Java for your problem domain, Java is probably just objectively better suited to your problem than Rust is.
I'm sorry, do you somehow think that is a theoretical concern? I seriously won a $2M bug bounty earlier this year because the CEO of a company wanted to "move fast and break things" their way to a financial product offering :/.
No, some products--like this guy's startup--are providing machine learning services for such devices as cars and drones, an area where safety clearly doesn't matter ;P. I think the issue, FWIW, is that we are actually going to disagree on what those "qualifiers" should be, not merely that they might exist.
I am not sure how those aren't incompatible thoughts... I was explicitly told, by the CEO, that she was channeling that mindset, and the security engineer there I ended up speaking to was clearly not bothering to actually figure out how to filter change sets made by their engineer as he "felt bad" about it... and I kind of don't blame them, as to the winners go the spoils in some sense? They were super lucky I was the one that found the issue and not a black hat, as $2M was nowhere near how much damage I could have caused. And that's in a financial system, where you might expect someone to know better, and yet they have the same incentives to play fast and loose as everyone else :(.
I don't disagree?... The idea that moving fast is more important than correct and safe code--because a startup can't afford to waste time on these matters as their competitors who "don't give a shit" will eat their lunch--goes well beyond Rust and mere memory safety.
> but I don’t think it’s the right tradeoff in all cases, especially not in startups where velocity is crucial
This could be true for C++, Java or maybe C#. Against them, python/ruby run circles.
But Rust change the equation.
Is super-productive... BUT what is important to note is that you need developers that have done the initial climb and go fully rust with it.
After this, things start to click neatly:
- Modeling code is easy & fast
- Composing code, flow!
- Testing (all of it, including benchmarking and integration) flow.
- Refactoring (big!) is super-easy. This one is where the "velocity" is actually need.
But you CAN'T "code fast". You CAN'T skip in good practiques "let's ignore the use of clippy, Result, proper use of traits (like Into/From), etc". (and even write documentation with including doc-test!)
This is where the "I write code FAASSST bro!, not time for pesky good practiques!" get hurt with Rust.
You need to buy the whole package, because Rust is MADE to be used fully in harmony.
And when you finally do it. IS FAST TO CODE.
And even FASTER to refactor.
And probably so minimal post-fixes.
---
P.D: Major blow against Rust? Slow compiling. This is the one factor that blow the dream and where you truly need a decent machine and flow to make it nice. However, if you take the above advice, you are already with Test/DocTest/Clippy/Check flow and that part can be done to be efficient.
The lack of compilation time in Python is a false economy when you consider the cost of tacking on the type checker and the developer time spent wondering whether the type that's been annotated is accurate. Static typing is good for developer velocity.
This really depends on your problem domain. Rust productivity is vastly higher than C++ due to its helpful compiler error message, package manager, integrated tests and bench facility, and language features like algebraic datatypes and a whole lot more. This also hasn't counted the reduced debug time later, which would be a significant time spent by devs in C++. In areas that don't value performance and correctness, or requirements vary a lot faster like a web app, you will spend time trying to get something correct which you may throw away pretty fast later, which will not be very productive. Thus I think it has less to do with startup picking Rust, but more of whether teams have picked right tool for their domain.
>Rust productivity is vastly higher than C++ due to its helpful compiler error message, package manager, integrated tests and bench facility, and language features like algebraic datatypes and a whole lot more
Are you actually a C++ programmer? I work at a firm that uses C++ and Rust and this isn't the case at all. Setting up dependencies, tests and benchmarks is a one-off cost, most C++ compiler errors are quite understandable to experienced devs unless they're doing something really hairy, and C++ has algebraic datatypes, as a library (std::variant) via variadic templates, something Rust doesn't support. For C++ devs comfortable with template metaprogramming Rust is missing a bunch of features, many things that are relatively simple in C++ are literally impossible in Rust (at least without writing macros). Rust also doesn't save much on debug time if writing single-threaded code, because memory errors are a very small proportion of the bugs one encounters in modern C++ written by experienced devs, rather most bugs are logic errors, which Rust doesn't prevent.
I was for several years. We were using git submodules to handle dependencies, but still using third party packages involves copy paste a lot. `std::variant` was the one we use to mimic the rust `enum` but it is much cumbersome to use, and not safe at all, there was no compiler checked `match`. Sure, single threaded code is much better, but this won't help with buffer overrun, or use after free like `String("hello").c_str()`, or iterator invalidation when you loop and delete, you just gradually learn all those gotchas along the way while bearing the learning cost. Trust me, experienced devs appreciate Rust much more if they are actually good at writing memory safe code.
That's not actually the tradeoff in my experience.
If you're willing to blindly follow the suggestions given you by the compiler about references, and clone() things when it doesn't seem to work, you'll be just as productive as a Python/Java ... programmer. You'll just not be as memory efficient as a Rust programmer who actually groks that stuff. That's the tradeoff: coder productivity versus memory efficiency.
Just once I wish I could force a Rust person to use either Ada, or even an Oberon-2 derivative that spits out C code and which compiles in a flash. Then compare the experience...
Speaking as a "Rust person" do you have any code samples or projects I could take a look at to compare? I've always heard good things about Ada but haven't tried it personally.
Are there languages that copy Rust's type system and compiler ergonomics without the memory safety?
I feel like 90% of my struggles with Rust in the real world have to do with the borrow checker. The two pieces don't feel related enough to me that they need to be interlinked.
I am building modelbox right now - https://github.com/tensorland/modelbox I began building this in Rust while also learning the language. It became quickly very complex as I started introducing streams in async traits. I think in a few years things will get a lot better as more people use Rust for building web services, but I had to go back to Go to be more productive and ship this thing out. I loved how the compiler was forcing me to think harder about life times and such.
I personally have a startup and what I think works well is use TypeScript for almost everything, but Rust for anything that has to be fast and/or needs more code safety due to complexity.
I hope more people looking into statically typed languages like Rust also take a look at Nim, as it seems to be striking a good balance between performance and developer productivity.
,,Over time, we grew the team considerably (increasing the engineering headcount by nearly 10x), and the size and complexity of the codebase grew considerably as well.''
This sounds just like all other projects that don't keep the code professional.
The author complains a lot about the quality of the Rust libraries and documentation, but I bet that their code base and documentation and especially testing is worse.
Still, I agree that Rust is not ready for a startup where the requirements change often.
> As mentioned above, 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.
Naturally, there is a reason people opt for higher level languages like Ruby/Python or at least Go or Java/Kotlin to write apps like this.
I really, really, really, don't understand why people want to use Rust for CRUD/web stuff. Every GC language is better than Rust for CRUD/web when you are in the less than 10,000 users phase.
I understand why Rust is trying to shove its way into the CRUD/web space--that's where all the programming is. I just don't understand the converse.
I cast glances at it every time my company's Python services break because of a runtime error that could have been caught with a better type system, or because they ran out of memory and need bigger boxes, or another developer has to spend a week debugging their environment to get the complex web of dependencies aligned for five minutes, or my laptop strains under the weight of a dozen docker containers attempting to shield all those system-level dependencies from each other, or a deployment fails because of bad tooling/dependency-management
Not all languages are as bad as Python, and Rust comes with a lot of other costs. But it's very seductive to think about a world where you build a static binary with a single command, the build always works no matter what system it's on, the binary always works on the target OS regardless of what else is or isn't installed, it will use 1/10 the memory, and most likely it will never have any runtime errors because you were made to handle them all already
(I realize Go has at least some of these benefits, but its type system is hard for me to stomach)
Biggest one is lack of null-safety. The preference for implicit value defaults, sketchy error-handling, and similar choices also rub me the wrong way. The whole thing just feels too fast-and-loose for my tastes
I'm the thousandth person to suggest this, but my ideal language for web servers and lots of other things would be Rust with a GC instead of a borrow-checker. But Rust has its unique killer-feature to thank for a lot of the traction it's gotten, so it's very possible this hypothetical language never would have taken off in the first place. We've got what we've got, languages are social constructs just as much as technical ones, maybe even more so
I've written a few backend APIs with rust and I have to disagree. Not only have the frameworks managed to get the ergonomics similar to your popular GC lang[0][1], the natural lack of shared mutable state of HTTP handlers means you very rarely have to encounter lifetimes and a lot of the language's advanced features. What's more, now when I go back to work with other languages, I can't help but notice the significant number of unit tests I'd not have had to write in Rust. It doesn't have a Rails and Django but it's an easy pick over anything at the language level.
A note on performance, Rust's the only langauge where I haven't had the need to update my unit test harnesses to `TRUNCATE` tables instead of creating/discarding a separate database per test on PostgresSQL.
I'll also like to mention the gem that is SQLx[1]. As someone who's never been satisfied with ORMs, type checked SQL queries that auto-populate your custom types is revolutionary. With the error-prone langauge-SQL boundary covered, I was surprised just how good it can get making use of the builtin PostgreSQL features. Almost to the point that amount of effort the community's put to building great tools like Prisma.js and <insert favorite ORM> feel like a fool's errand (at least so for PosgreSQL).
Rust does have some nice semantics that the last round of popular languages don't, mostly just due to age. I suspect that a decent amount of its popularity is owed to that. But obviously for something CRUDesq all the nice little affordances in the world can't prevent borrowing from putting it way behind any language with a mature framework. That indicates we could use a GC language with Rust's eye towards modern design but without Rust's more difficult bits. Though I haven't really tried Typescript- does it do anything like that?
I feel that this is a click bait to attract employees. The writer links to his startup on his profile (which Medium shows right next to the article), where you can see that he's looking for founding engineers. Rust has been a trending topic lately, which attracts relatively experienced engineers. He mentions that he made these experiences more than 2 years ago, so it's notable that he waited so long to write about it. The slightly noisy "ex-google" and "ex-apple" mentions sound like advertisement.
And, well, the usual, "learning curve" and some largely hypothetical concerns about hiring. I developed a complex app with Rust with very high time pressure and with developers that didn't have any previous Rust experience (I didn't have much of it myself), it was risky but it worked out well, so I know first hand that it's possible. And we are not "ex-google"!
OT, but I thought it was neat how all the splash images were just casually generated by dall-e and the author didn't feel the need to mention it because it's just normal now
(Not /s, I genuinely think it's cool things are like this now)
Sure, didn't notice it. But it wasn't mentioned in the body text- I'm just saying it wasn't that long ago there was an entire blog post about using AI for blog post images, and now it's normalized. That's cool!
Languages like Python can go a long way for a startup and is my first choice. Rewrite in Rust when it's clear that the API is stable and performance starts to matter.
Exactly. Iterating on an idea is much easier to do in Python, because you can just use lists, dictionaries and sets to solve most problems. Python's string manipulation is also a lot easier than Rust, because you don't have to think about allocations. Once you have figured out what works, you can start defining your Rust structs and enums to build up a reliable code base.
Over the past 2 years I've been writing Rust services for my own startup. Some were straightforward CRUD, some advanced language parsing and ML services. Some thoughts:
- Rust tends to push you to make good decisions. In my case, one of these good decisions was to ditch an ORM (which has always slowed me down) and instead write Postgres queries directly via SQLx. The compile time checks against an actual DB helped my speed dramatically
- Free performance can be a really big help when you are trying to figure out an API or algorithm. It's really helpful to know that my unoptimized code won't tank the server, and also helps me save costs on the cloud.
- "It doesn't compile, or it doesn't break" is kinda a mantra for Rust, and really helps me focus on problems at hand, instead of hunting down bugs.
> You will have a hard time hiring Rust developers.
I've found that there is a drastic difference between hiring devs willing to learn Rust, and devs that want to work with you because you are using Rust. The bar of those devs that seek out a Rust position tends to be very high.
> We made a huge mistake early on by adopting Actix as the web framework for our service...(To be fair, this was a few years ago and maybe things have improved by now.)
Actix-web has gone through some version-churn, but it's never been "buggy" in my experience. The experience multiple years ago is vastly different than today, but even my older services written years ago with Actix-web are still running fine.
> Libraries and documentation are immature.
Perhaps in years past, but I've always found the docs for Rust and its libraries to be very good. Folks write entire books on elements of Rust, and the standardization of the display of crate docs keeps things consistent.
> Rust makes roughing out new features very hard.
The "json!" macro can come in handy here. Also Github Copilot is a godsend for this.
> What really bites is when you need to change the type signature of a load-bearing interface and find yourself spending hours changing every place where the type is used only to see if your initial stab at something is feasible. And then redoing all of that work when you realize you need to change it again.
Hours? These type of corrections are spoonfed to you via errors at compile, or via the IDE. I've never had to spend hours on this. Everyone of these changes could be a bug, and having a typesafe language is a huge help here.
I recognize I might be in the minority, but I've really enjoyed using Rust for services for my startup. It's helped me move fast, but maybe I'm a special case. I'm curious to hear other's experiences.
Not special. I couldn’t have said it better myself.
I often reach for Rust when Python could do, but it’s a bit bigger than a one page of code program. I also forget I’ve been doing Rust nearly full time since late 2015. And before that 15 years of C and understanding memory, etc. Maybe we’re just a rare breed.
Jokes aside. This is a thoughtful and interesting article. I was wavering on going down the Rust path for my SaaS start-up. Glad I didn't in the end for exactly the same kind of reasons.
Rule number one at startups: write things that don’t scale and use mature, boring frameworks/languages.
The goal is to deliver products and iterate as fast as possible. Productivity is key to success.
IF (and it is a big IF) your startup ever gets to the point of having to scale up (nice problem to have) then
just throw money at AWS, hire senior engineers and let them figured it out.
What matters is customer experience; nobody cares of what’s behind the scene.
I will also push back on other commentors here saying Rust is not good for web apps and APIs, and I have found that to be the opposite of true. I read Zero To Production In Rust [0] (a great book by the way, all about creating a web API with actix-web and Postgres) and deployed an API that has not gone down a single time since deploying near the beginning of this year. It's as ergonomic as (and sometimes even more so than) any NodeJS or Go API I've made (as even though Rust doesn't have function overloading, actix-web and others have some macro magic they do behind the scenes to make it appear as if it does), and there were very few times I had to contend with the borrow checker. If there had been and if I were really going for speed, I would also have cloned everywhere that was needed.
When I write something in Rust and it compiles, I can depend on it to work, and to continue working. At the end of the day, use the tool that makes sense for your business, but I don't think Rust was necessarily a bad choice based on my personal experience with it.
[0] https://www.zero2prod.com/