I love building a startup in Rust but wouldn't pick it again
propelauth.com
propelauth.com
If your answer is something like Go or Node.js, then Rust is probably not the right choice.
If your answer is C or C++ or something similar, then Rust is very likely the right choice.
Obv, there are always exceptions here, but this helps you work through things a bit more objectively. Rust can be a fantastic language for many purposes, but it has a very high development cost.
I've been saying that for a while. If you're building web backends, Rust is not a good choice. Go is so much easier. The green thread system gets rid of the thread/async distinction, garbage collection means you don't have to obsess over memory management, and the libraries for web backend stuff are the same things Google uses internally, so they're well-tested.
A big problem with Rust, long-term, is that the kind of programs that really need it are somewhat out of today's mainstream. It's not that useful for webcrap. It's not that useful for phone apps. The AI people use Jupyter notebooks and Python to drive code on GPUs.
Where do you really need Rust? Heavy-duty multi-threaded programming. Operating systems. Compilers. Routers and network infrastructure. Robotics, maybe. Hard real time. It ought to be used more for high-performance games, but the game infrastructure isn't there yet. Unreal Engine is C++ and Unity is C#. Rust has Bevy and Rend3, but they're not AAA title ready.
Perhaps Rust is fighting the last war - the mess inside C++.
One thing this is missing is that Rust is useful for libraries callable by many different languages. You may or may not want to use it to build an actual Web app (I personally think it's a solid choice, but reasonable people can disagree). But for building, say, the Python cryptography library [1], which is used as a part of "webcrap", Jupyter notebooks, and in many other domains, Rust is clearly an excellent option. Nobody is going to build core Python infrastructure in Go or Node, and without the plumbing libraries none of the higher-level applications can function.
Adding another language in the middle will only complicate our toolchain.
Regarding Python, with the pressure from Ruby, PHP, JS and Julia JIT compilers, they will eventually get more serious about JIT adoption, and then there is ctypes of OS APIs.
This heuristic wouldn't work for my department because we build web backends in C++. I keep telling the most senior devs here that nobody does this and for good reasons (velocity etc), and their response is "who cares what the rest of the world does, they're just bad at C++."
Go authors always disliked C++, and got a manager that allowed them to work on their toy language, which eventually became Go.
You may be underestimating the amount of need there is for performant code though. Its everywhere.
The thing here is that most web backends are basic CRUD bullshit, and you fundamentally will not hit the thread/async distinction, nor need to care about whether there's garbage collection or not. Microsoft has used actix-web in production as far as I know, it's not like the Rust web stack isn't battle tested.
>Where do you really need Rust? Heavy-duty multi-threaded programming. Operating systems. Compilers. Routers and network infrastructure. Robotics, maybe. Hard real time.
These are all the things that sit adjacent to a web framework, and once you step outside that CRUD happy path, Rust fits very well - and in this case just "bolts on".
>It's not that useful for phone apps.
This is more tangential but I know of more than a few companies who have written or are writing their cross-platform logic in Rust instead of C++. This is really akin to how some projects back Python modules with Rust: it's easy to have it backing things and it beats the hell out of dealing with C++.
And look, I'm not even saying don't use Go/Python/JS/<insert your preferred language here>. You do you, your startup will live or die by so many other things than choice of programming language.
[0]: https://www.usenix.org/system/files/1311_05-08_mickens.pdf
In any case, if you are trying to optimize microsecond usage in AWS lambda I hope you have a truly gargantuan amount of traffic and/or have incredibly cheap engineers or the money saved will barely match up to the cost of the engineering hours sunk into them.
Rust async support was only released in late 2018. Not very long at all, especially if compared to Golang.
Most of my data, cost and performance requirements aren’t suited to the likes of FiveTran/etc, and whilst I could theoretically use Python, it’s far too slow, and the propensity to crash unexpectedly (meaning I have to spend extra time debugging) means the development velocity is actually worse than just writing it in Rust. Plus, most of the libs and tooling I’d use from Python are available in Rust.
I’ve also written Rest and GRPC API’s in Rust (mostly for serving up data and being a query layer), and found it a far more pleasant experience than Python/Typescript/C#. Admittedly a somewhat ”smaller” use-case than a lot of what people would consider web-API’s but still.
If you’re building a large complex app that needs to be shared across multiple platforms, C++ is a common choice. Not talking about games here, think Office, Facebook, Zoom, WebEx etc. It easy to see Rust replacing C++ in that stack.
But as Rust is so much nicer that C++, I could see it becoming popular for smaller projects that want to share common logic, with a native UI on top.
Internally Google uses no web and most of the backends are in C++.
One strength of C++ is that it is far more established than rust - and that comes with a lot of advantages:
* It has a larger number of people who know how to work with it
* It has a huge catalog of established, fully functional libraries for everything you could imagine from UI to game development to embedded systems to simulation to anything else
* It has broad support in developer toolsets in general like editors and IDEs, static analysis tools, formatters, pre-commit hooks, etc
If I'm starting a new project with C++, I can immediately know that there's a huge landscape of programming already carved up and ready to work with. I can't do that as easily in Rust. The language, the libraries, the tools are all younger. Some of it isn't as fully featured, some of it isn't nearly as stable.
That will improve with time, but it's a huge advantage to C++ right now.
They didn't say Rust is almost always better than C/C++.
There's perhaps the implication there, but it's certainly not explicit in the GP's comments.
I'm painfully aware of this. Typical Rust problem, from a reply I made to a posting on Reddit:
* WebGPU dev: WGPU updated to 0.15!
* Me: Might want to hold off on upgrading for a bit. See (bug report on related package)
* WebGPU dev: Good to know. I'll keep this in mind if someone has any issues when following my tutorial
* Me: I'm using Egui/rend3/wgpu/winit/vulkan cross platform on Linux and Windows, with cross-compiling. Getting all those crates to play well together is not easy. Every time something in that stack changes, it's days or weeks of trouble.
Sadly, it's not really an advantage anywhere that hasn't already eaten the grief and doesn't already use C++.
Both C and C++ infrastructure are so horribly terrible that Zig is gaining traction simply by creating a better compiling infastructure totally indepdent of whether the language is better or not.
That's one hell of a downvote.
One the one hand, Go/Node/Python/etc doesn't scratch my itch for the strong type system (sum types/tagged enums mostly) and on the other hand even though I like prototyping in Rust I really miss things like a REPL, more terse syntax, and a bit more expressiveness.
I think OCaml is closer to my ideal but the ecosystem isn't quite there. Maybe all it needs is time.
I've found that the basic NodeJS or Python works for most problems I've chosen. Not even with strong typing.
This might help with cleanup: https://github.com/tc39/proposal-explicit-resource-managemen...
But I'm not sure anything will help with the type system. For example, this drives me absolutely insane: https://www.typescriptlang.org/play#code/MYewdgziA2CmB00QHMA...
I've never really had a problem with it, but I isolate such resources behind a wrapper, which makes cleanup easy. I just create a little higher-order function:
function doSomethingThatNeedsCleanup(fn) {
const thing = createTheThing();
try {
return fn(thing);
}
finally {
cleanUpTheThing(thing);
}
}
> this drives me absolutely insane (console.log(["10", "10", "10"].map(parseInt) outputs [10, NaN, 2])That's not really an issue with the type system, that's just coincidence and bad luck.
The signature of parseInt is:
parseInt(string: string, radix?: number | undefined): number
And Array<string>.map(fn) takes a function with the signature: fn(element: string, index?: number | undefined, array?: string[] | undefined): any
So parseInt coincidentally matches the signature Array<string>.map() is looking for.I'm not sure what you expect the type system to do here. It works just fine at catching an actual type error, such as this:
console.log([10, 10, 10].map(parseInt)
...which correctly complains: Argument of type '(string: string, radix?: number | undefined) => number' is not assignable to parameter of type '(value: number, index: number, array: number[]) => number'.
Types of parameters 'string' and 'value' are incompatible.
Type 'number' is not assignable to type 'string'.
(As I'm sure you know, this is the correct code: `console.log(["10", "10", "10"].map(s => parseInt(s))` .) > But I'm not sure anything will help with the type system. For example, this drives me absolutely insane: https://www.typescriptlang.org/play#code/MYewdgziA2CmB00QHMA...
You just gave me CPTSD, thank you. This motivated me enough so that I will leave JavaScript and TypeScript for good now.I’m currently rewriting what I wrote in Rust to see how the prototypes compare and… I just feel so much at peace knowing that the type system and the borrow checker have my back and that I won’t encounter unexpected nil pointers or zero values. And I write this quickly as well.
(Yes, I do have experience with both languages, so none of these exercises were new to me. But I’ve been favoring Rust over the last couple of years.)
In Go you have a garbage collector, no need for the borrow checker or reference counting on your side. What unexpected nil pointers or zeros? It’s all easy and straightforward in Go. If you’re referring to the handling of materialized interfaces, which one may encounter in form of concrete error types, and which is one of Go’s idiosyncrasies, then it might help to look deeper into learning how to handle Go’s interfaces.
Maybe compared to C, Go is quite concise, but Go is nowhere near Rust's level of expressiveness, especially with poor generics support, much more verbose error handling etc.
> no need for the borrow checker or reference counting on your side.
I think that people that mention borrow checker in context of backend dev haven't done much backend dev in Rust. The nature of a web backend is to (most of the time) get data from the client, process, return a response. In this context you very rarely have to think about borrow checker and almost never use explicit lifetimes.
> What unexpected nil pointers or zeros? It’s all easy and straightforward in Go
If you forget to initialize a struct, you may end up with a nil pointer and you might not catch it until runtime.
I think Rust will see a lot of adoption in web services that are glorified CRUD APIs. It would have been a poor choice to do many of these workloads in this in C or C++ (despite the data point of Amazon 1.0 LOLz).
I really don't see how anyone choses nodejs/deno to anything.
Java imo gives you much less trouble, is more stable, has a working (!!!) Unit testing setup and exceptional runtime.
Next on my list is to give rust a go, since I'm intrigued by its features, but if I was to go fast I'd go with java any day.
This is going to sound mean but I don't really know how to phrase it more nicely. People building backends in js/ts are doing so because either they, or a critical mass of the people they expect to code in it, don't know any better backend languages.
I don't mean for this to be judge-y. People have different skillsets. A nodejs backend can be the right choice if you're a full-stack dev or a solo dev and you mainly know js and you get a lot of agility and correctness by using what you know instead of trying to use a "better" choice but one that you yourself are less likely to use correctly or quickly in your own use case.
It can also be the right choice if you're a big backend java/etc guru but you know you want to set this thing up but then just monitor/oversee frontend/fullstack devs being the ones who do incremental modifications to it.
But yeah - without additional outside constraints and everything else being equal, it doesn't make a lot of sense to use nodejs as opposed to basically anything more boring - be it java, rails, etc.
The thing that surprises me the most about node based backends is that they forego both the platform maturity and static-safety of java AND the ridiculous amount of batteries included into django and rails, just to be able to use javascript, of all things. What you could rails-g in a minute you could instead spend a day looking for an npm package that won't be abandoned in a month or spend eternity rolling your own everything and then maintaining it.
They accomplish things like built-time code generation, static server-side rendering, and other kinds of code transformation that are difficult and flaky without a back end that can understand JS.
I have looked for non-tinkertoy solutions in Java and Go. There aren't any, and senior people in those communities are hostile to the very idea. (Maybe remembering Java Nashorn?) Maybe the only people friendly to the idea of interoperable back ends are Rust people (where you have libraries like Yew/Sycamore/Dioxus/Leptos that actively imitate JS frameworks).
But yes, your claim is that the only reason to write JS back ends is ignorance and enfeeblement is ignorant and repugnant.
And for all of its faults, NodeJS can be fun compared to something like Java which feels more like doing taxes. Maybe it's the danger. Maybe it's the fact there are so many packages and libraries out there - everyone can contribute! Who knows, but I won't waste my time learning say Rails just to use a "better" backend framework. It's good enough even though I know better. There are way more important things.
Spring boot is great but it does have its quirks. Config and plumbing for instance compare to Nest or Rails doesn’t makes it the best and fastest dev experience. And all the Java extra stuff and it’s ecosystem makes it solid but also old compare to what you can see happening in other languages.
I have Quarkus in my mind though. It looks amazing in dev experience with lib like panache, resteasy etc… and performance are amazing too which comes as a bonus. But there is no dev out there. And I feel that in Java people want to work on Spring Boot because spending X years working on Spring Boot are more valuable in their career than X years on something else that is not really the de facto standard of the industry. So ya not great for hiring talented people…
But NestJS with the JS/TS ecosystem start to be very close to what Rails was in the past. Stuff like Prisma for instance makes everything a breeze. There is almost always a package for the thing you want. Great at websocket too. Can do GraphQL if it’s your thing. Same language as the front end, Nextjs, etc. One of the most performant dynamic languages. And interesting things happening like Deno or using Rust with it. So there are quite some perks to it.
Can’t speak about Django though. But I am wondering how is the dev experience like when we use it for real projects.
I am starting a new project/startup and wondering which one to start it with between all these technologies. And after looking for a long time I can see that they really all comes with their pros and cons.
I hate configuring typescript and having to deal with bundlers, package.json, esbuild, etc.
But Deno and Bun are just around the corner. What we need is to get rid of this a la carte mentality around tooling and just give everyone Typescript, batteries included, and make it fast.
Then Node really isn’t that bad anymore.
All languages come with tradeoffs in terms of performance, development/maintenance cost and ease of hiring. Most businesses only care superficially about performance, which leaves developer productivity and ease of hiring. Node/typescript isn't terrible in terms of developer productivity, and it's got the best hiring story of any language. Python is good in both those ways too, but since you already need a javascript hiring pipeline for the client it's easier just to use node and hire full stack devs.
I went back and prototyped a slightly similar app in Python w/ a gui toolkit and I felt like I was driving through exception city. I have made my career writing in dynamic languages so I forgot just how pleasant it was to write in a static one!
Sorry, this is always the wrong choice, which is why it felt so bad.
Writing a UI in TypeScript with any of the major frontend frameworks will not require you to wade through run-time exceptions
Java native GUI toolkits are also far more mature
Python and TKinter and similar stuff is always just awful, I've never seen someone put together anything half decent with that stack
To do the same in java means making a billion model files with a billion transformation functions on them
Time to iterate is also only much faster in certain situations, e.g. local development; if you have to e.g. build a container image, push to a registry, and redeploy to a k8s cluster somewhere, those savings become somewhere between less significant and nonexistent.
Can you expand on what you mean here? I know you're not implying Rust is faster to move thru a CICD pipeline, so can you tell me what you do mean? I seem to be unable to make a different reading
If Rust is helping you make prototypes and iterate quickly, I’d love to hear how you’re using the language.
But if Rust exists, suddenly you have a nicer-to-use high performance language, and you have a choice to use it.
I think if you would choose Java or Python or C#, then Rust might not be the right choice.
I've done a bit of Rust in my job, and there are some basic things that Rust doesn't have going for it:
- steep learning curve (this means for the first 6 months, you or your colleagues are unproductive, write bad Rust which your company then builds upon over time).
- bad error messages (even though that was a focus for the rust team!)
- frustratingly complex for setting up test coverage
- Slow analyzer speed (*super laggy* on Clion, though this might be a jetbrains issue)
- Slow compilation times (I heard somewhere that "Go just goes". I've also written some Go in my free time, and compilation is fast. Well IMHO, "rust will rust" - it's very slow. Generics can make compilation event slower.)
- Verbose. I've seen a just few lines of JS get replaced with hundreds and thousands of Rust.
"What kind of startup would fail because it chose Rust ?"
Then don't be.
Seeing Rust as a system programming language only is missing the bigger picture (IMO).
Iteration time hasn’t been an issue, but compile times can be annoying. Though obviously compile time is related to iteration time.
Of course, all of these things are anecdotal. Collecting anecdotes is how you develop evidence, of course…
For context: most if not all of our team has developed calibration tooling similar to what we're doing now in the past, just at different startups and very specific to certain robotic or sensing configurations.
If anything, once we got CI sorted and started using our own internal registry I would argue that we are significantly faster in terms of iteration time. This is partly because the team is small, but also because most of our tooling is consistent and easy to keep in lockstep. Pulling libraries is done uniformly across platforms and architectures, and our CI runs (through GitLab) stay up-to-date with the latest tooling without issue. Having a stronger type system to detect errors early and a compiler that actually tries to give human-readable messages (looking at you C++ linker errors) using that type system makes everything so much easier.
Compile time seems like it would be an obvious bit that slows one down, but in practice sccache [1] does what it ought to and we barely notice it (at least, I don't and I haven't seen team members complaining about build times). Mostly I'd argue that the real thing holding us back is tooling extant to the rest of the wider Rust ecosystem. Debugging and perf tools are great in Unix land, but if you're making anything cross-platform you need to know more than just perf. That might just be my opinion though, I'll admit I'm still learning how best to apply BPF-based tooling even in Linux alone.
I also realize I'm responding to steveklabnik, so I suspect most of what I'm saying is well-known and that this comment is really more directed at TFA.
And yeah while I've had similar experiences, I'm glad to hear that it's so good for others. And, for example, sccache isn't something I have much experience with, so repeatedly hearing "it works well" is nice.
(Also, secret of replying to people on the internet: you are often replying to the anonymous reader of the discussion as much, if not more, than to the person you're replying to.)
That makes “Rust at a startup” a different equation.
I also just work there, the all-Rust bit started well before I joined!
That said, you are right that it's a point worth considering.
We have only your word for this.
Happy to talk about anything!
"While it still makes sense to write system programs and time-critical kernels of applications in C or C++, the world has changed a great deal since these languages came to prominence in the 1980s. In 2003, processors are a thousand times faster, memories are a thousand times larger, and disks are a factor of ten thousand larger, for roughly constant dollars.
These plunging costs change the economics of programming in a fundamental way. Under most circumstances it no longer makes sense to try to be as sparing of machine resources as C permits. Instead, the economically optimal choice is to minimize debugging time and maximize the long-term maintainability of the code by human beings. Most sorts of implementation (including application prototyping) are therefore better served by the newer generation of interpreted and scripting languages. This transition exactly parallels the conditions that, last time around the wheel, led to the rise of C/C++ and the eclipse of assembler programming."
Assuming that my application scales by the same amount (big assumption, yes), I could cut AWS costs by 7.7 times (!!!) by using the C++ implementation.
I'm pretty sure that maintaining a C++ codebase is less than 7.7 times more expensive than Node, even if you throw in extra development time etc. This also ignores the decades worth of excellent tooling we've built up for C++ (static analyzers, fuzzers, etc).
At a startup, when building things fast matters more than costs, sure. I buy the argument for Node or Python or any other interpreted backend. But once you start to scale, things change after some threshold. Unless you're facebook[1].
[0]. https://www.techempower.com/benchmarks/#section=data-r21
[1]. https://developers.facebook.com/blog/post/2010/02/02/hiphop-...
The problem here is assuming that a 7.7x less expensive AWS bill is the same value as a 7.7x more expensive development time. Imagine an app that is maintained by one programmer costing $10k a month and running in AWS for $1k a month. It is not worth dividing that AWS cost by 10x if it means a 10% development time hit. Actual numbers may vary, but development often costs much more than running in AWS. (For startups it's even more of a difference, because you pay AWS as you get more traffic, but you pay developers before you have an MVP.)
What portion of the bandwidth is end-user facing and what portion of it is connecting all the servers? Another way of asking this is if I need fewer servers because code runs faster, how much less bandwidth do I need?
(Yes, I know, it's just a silly example, but compute costs are only one aspect, and the bandwidth pricing in the on-brand public cloud actually tilts overall costs towards being bandwidth-dominated for many applications.)
One time, I met a guy who wrote firmware for Seagate hard drives. Any new feature he added had a budget measured in microseconds. Obviously he wrote nothing but C++.
Sure, if you're going to throw away your early prototype there are better languages. But nobody ever does that.
Instead your prototype evolves into your product and early expedient decisions you made that were appropriate for a prototype aren't appropriate for your product and you have a significant refactor.
And Rust is the best language I have ever encountered for refactoring. Just bang away changing the code until it compiles, and it's quite likely that once it compiles it actually works. That's not an experience I've had in any other language. Usually a significant refactor exposes some foot-guns that don't fire until significantly later. So you avoid refactoring and your code ends up a right mess.
The problem is that TypeScript is an even better language for early prototypes.
zeroxfe has the right answer:
> If your answer is something like Go or Node.js, then Rust is probably not the right choice.
> If your answer is C or C++ or something similar, then Rust is very likely the right choice.
The only reason one would choose Rust over TypeScript is if one would have chosen C/C++ instead.
Then if you need higher perf/throughput: Go, Java, and C# in particular are all options that I'd consider before C/C++ or Rust. C# in particular is highly congruous to TypeScript [0].
JavaScript, TypeScript, and C# have been converging, IMO (and that's a good thing). Seems really natural that if you're a startup finding PMF, start with TypeScript for iteration speed. If you need higher throughput, C# is a stone's throw away from TypeScript syntactically and it's pretty easy to hire for (compared to Rust). [1]
Pick Rust if you're building something highly performance and memory sensitive. Pick TypeScript and C#/Go/Java for almost all other cases.
[0] https://github.com/CharlieDigital/js-ts-csharp
[1] https://raw.githubusercontent.com/CharlieDigital/js-ts-cshar...
Please don't report your own experience samples as plain universal facts. It is one of the more common means by which falsehoods spread. I have no doubt that's not your intention, and you believe what you write, but you cannot have a basis for such a bald statement, and it happens to be false.
I have seen prototypes built and discarded very frequently. Indeed in an earlier incarnation it was my own professional focus.
I have no idea how common it is in your country, or globally, or in specific industrial sectors. Usable stats in tech are hard to come by (in large part because of its ubiquity). But your statement is just false, and commonly repeated.
You can’t be serious. I’ve seen prototypes get thrown away all the time, especially at startups. In fact, I’d say that’s more common than not.
It takes too long to do these basic steps and then copy them to new projects. And generators only work once, if then, which means they are most useful in languages that have stopped iterating, which is not that compelling.
I’m hoping the next version control system solves the rerere problem and we can build our projects by forking a template project, then pull commits from upstream as necessary.
TDD is great if you can get it working in a tight interview time schedule - they can also reveal any misunderstandings of requirements before the actual solution is implemented!
On the flip side however, many interviewers have experienced countless folks who spend the majority of the interview time on tests and setup, only to run out of time on delivering the solution to the actual presented problem. When I feel like somebody's spending too long on these things I'll try to nudge them towards wrapping up the tests and moving onto the solution. You would be surprised though by just how frequently it's met with open hostility, by candidates with only 10-15 mins left in an hour-long interview and no solution started!
What's happening in interview loops is that we're implicitly or explicitly selecting for people who prefer to YOLO instead of writing tests, and then we are surprised how hard it is to get new and existing hires on board with mature testing processes. The people you need don't work here, they work somewhere else.
In the bad old days of C there were two kinds of programs: programs without correct error handling, and programs where half the loc are unhappy paths that do what exceptions do... with a huge amount of work.
Today people are repeating the same mistakes of the past, putting a "?" on everything is a lot better than what you had to do in C, but why do that when you can just use a language with exceptions?
It is like somebody showed cavemen fire (exceptions) and they decided it wasn't worth anything and went to go screw around with other things.
Note that people now consider that as a mistake, people prefer having Exceptions be hidden instead of explicit and requiring handling like that.
I actually think this would be a fun one. How to make scrambled eggs, but where all failure cases are covered. Would be the "Hal fixes a lightbulb" in prose.
Furthermore, handling errors has little to do with where the error is actually caused. In general, you can only do two things with errors: log and kill the operation or retry the operation. Neither of these has anything to do with the leaf function 20 items down in the stack that actually made the network call that failed.
Yours is an approach which is likely to ensure your code is discarded and has to be rewritten relatively quickly.
Result and ? force you to think about - or at least acknowledge - the edge cases. For a throwaway script or small scale easily tested program, that might be a drawback. For MLOC+ codebases where link times alone are sufficient to start impeding testing iteration times, it can be a big help for correctness and stability, while still being relatively lightweight compared to other manual error handling.
Finally - Rust has exceptions. They're called panics. They can be configured to abort instead of unwind. This helps set the tone - they're really meant for bugs, and exceptionally exceptional circumstances. They cause all the problems of exceptions, too - unconsidered edge cases, undefined behavior unwinding past C ABIs, the works. Fortunately, it's reasonable in Rust to aim to eliminate all panics but bugs.
https://gen5.info/q/2008/07/31/stop-catching-exceptions/
and
https://gen5.info/q/2008/08/27/what-do-you-do-when-youve-cau...
It's very important to minimize the burden of handling errors in code with simple control flow. Frequently I see people try very hard to handle errors with monads in languages like Scala at the micro level and they are so burned out by this that they don't put any effort into handling errors properly at the macro level.
If you make the micro level as automatic as you can it is possible devs will address the macro level, and what is necessary at the micro level is not dealing with a crisis that prevents the compiler from building your code, but rather cleaning up the environment consistently in both normal in error conditions and giving the macro level sufficient context for the error that it can do the right thing.
Fundamentally it is difficult to impossible to fix bugs without knowing what code caused it. Java-style exceptions give you a backtrace for free, which is a huge head start. With Rust you have to do a lot of manual plumbing with something like error_stack to get similar functionality, out-of-the-box Errs do NOT capture this.
Far more productive to work in an environment that does the right thing "for free" vs having to do it manually.
With crates like anyhow and eyre you also get backtraces "for free" nowadays, without needing to do manual plumbing (all you need to do is toggle on a feature flag).
fn foo () -> Result<(), E1> { .. }
fn bar () -> Result<(), E2> {
foo()?;
}
This requires `bar` to have a function signature that notes it may error, `E2` must implement `From<E1>`, and the caller of `bar` must use the result or explicitly silence the warning. Meaning if a program creates a Result the error must be handled - you can't silently let errors bubble up through the call stack.`Result` implements some common combinators like `.ok()` to convert to `Option`, `map`, `map_err`, `or_else`, etc to reflect the common cases of error handling.
And finally, since Result doesn't require non-local control flow like exceptions you know that `drop` will run as the functions return back up the callstack.
And if you want to use Result like exceptions... you can. But you can't hide it from callers, and callers are still free to handle them elegantly.
What ? does in rust is to unwrap the result check if it is err and return from the function with an error result. On top of that it will auto-convert the error type (if the type has the from/into traits implemented)
So it would do:
try { //the code that may fail } catch (error) { //do we just throw the same error? //or convert the exception to a custom other exception }
If I see an API that throws me an low level exception without context I go mad. Like an file not found exception etc when executing an API that does multiple file IO operations.
Error union return values make a lot more sense, the rest is just syntax sugar details (and that's where opinions differ I guess).
I'm not terribly familiar with either language, but I don't see any particular difference between swift and rust error handling for example, swift will also mark fallible function calls with try, similarly to ? in rust.
For what is worth the author of the swift standard library believes that try is a mistake: as most functions can fail in practice it just becomes noise. It might be more useful to mark can't fail regions.
* C# uses exceptions, and when I code it feels exactly the right choice
* Go uses error return values, and when I code it feels exactly the right choice
For some reason both feel very much ideal in use. Maybe it is because in each case the language syntax/ethos fits very well with the choice made, and so is frictionless when developing in the flow (and if used properly of course)? Maybe some other reason. Hey ho.
Personally, I much prefer Rusts solution, being both more up front and at the same time more terse.
The metaphor is kinda stupid though, the "cavemen" in our scenario knows very well that exceptions exist.
Oh, it absolutely can not be that the Rust way is more powerful and you didn't understand it yet. No way. It's all those other people that don't understand the old concept that almost all of them know.
Even types of exceptions are rarely useful results outside of reading the logs or sometimes in libraries outside of your control.
The types can be useful for communicating this
I think there's a pretty good rule of thumb in modern systems-ish language design: If Go and Rust and Zig all do a certain thing, that thing is probably a great idea. These languages have very different priorities, but often they overlap.
https://ziglang.org/documentation/master/#Error-Return-Trace...
I think the first C program I saw was a type-in terminal emulator from Byte magazine around 1985 and I was struck by the akwardness of the error handling in the C stdlib, spent a lot of time looking at the code when I realized the author had "spaced it" at one point such that the error handling was wrong and thought "this sucks" but learned how to write C programs with 3x the LOC because of all the alternate paths I had to put in to handle errors.
When I saw exceptions for the first time I felt strongly liberated because I got for free what I was working for so hard in C so I got to spend more time thinking about algorithms, the needs of the customer, things like that.
This made me laugh harder than makes sense. I'm sure I've been guilty of doing said code, as well.
My experience doing Java, Go and Rust has been completely the opposite. Exception stack traces in Java are amazingly wonderful things - they exactly pin-point the failure points in the code. The amount of hunting I need to do to find out where something failed in the call stack in Go/Rust is tedious. You need a module/crate for error tracing or you up waddling against a strong current of despair.
The only thing is that Rust rightfully uses the ? to return early system on option as well, which removed the ability to have None coalescing with "?". This was the right choice from a language point of view, but I wish there would be a None coalescing syntax in Rust.
1. Exceptions have very high performance costs (equivalent to a longjmp which is very slow), so if you expect to have exceptional cases, it's probably a lot more efficient to not use exceptions.
2. Exceptions break the linear flow of the code when you read it, so now you have to read a lot more code to figure out what the exception paths are and where and how they are handled.
2. So do returns, but we have long settled that SESE is undesirable.
On the sad path. On the happy path they are faster than explicit error checking.
Could you elaborate on why they are so slow, compared to passing around/returning error objects explicitly?
Read up on how exceptions work in C++ implementation-wise. It's not pretty.
Meanwhile, they manage to encode the concept of "can fail" into not merely the type signature of a function but into the syntax used to access it, when--like other monadic behaviors, including "requires scoped allocation"--this is the kind of thing you tend to need to refactor into a codebase at a later time: instead, the code should always be typed as if everything can fail and everything can allocate (not just memory, but any resource); languages that get this right--such as C++ and Python--thereby deserve their stickiness.
Exceptions? Which exceptions? How do you know which exceptions you're supposed to be handling and where they come from or when they happen?
I prefer the control flow of the program and the exact types of errors I'm handling to be explicit.
Just because Rust pretty much forces you to be explicit about your allocations doesn't mean you have to avoid them at all costs.
I had almost this exact scenario, and yes there is pain in writing it with explicit lifetimes. But I can't agree the performance improvement is negligible; maybe in isolation, but I saw about a 100x speed increase for my application when I switched away from Strings. For me it was because I was doing many of those extra String allocations in a loop, so it killed my performance.
1. takes an immutable reference to a vector of strings and
2. returns an immutable reference to a string,
3. with the constraint that the strings in the input vector must not be freed before the result is freed.
It’s a reasonably concise way of saying all that.
At the end of the day, you can't understand a language without, well, learning it.
I think in general that the Rust hype has outgrown what it's good for. If you're writing a web app in Rust then you may want to ask yourself if you're making the right choice.
The performance difference between a Rust server and other languages are incredible, especially in terms of RAM usage and concurrent connections per second.
That said, if your program is going to need tons of entities stored in a database, I wouldn't even consider a language or framework without a solid ORM. Rust has some ORM-lite libraries but I'd end up picking a garbage collected language in practice just because of the difficulties that low level programming bring to such middleware.
Iterating in Rust isn't that hard as long as you don't try to cheat your way out. Instead of returning null for methods that you haven't implemented, add a todo!, etcetera. You have to do things somewhat right the first time. I think that's good, because there's nothing as permanent as a temporary proof of concept. You can clone/copy your way out of most annoying Rust restrictions at the cost of performance you'd otherwise sacrifice by picking a higher level language anyway.
If your startup doesn't know what it's building, you have bigger problems than the language you choose.
Really depends on what "other languages" are here. If you're comparing against Python then sure, but if you're comparing against Go then the difference isn't that incredible.
> That said, if your program is going to need tons of entities stored in a database, I wouldn't even consider a language or framework without a solid ORM.
This is actually what I used Rust for recently and honestly the ORM situation is pretty good. The language itself is just too rigid for this kind of work for too little payoff.
> If your startup doesn't know what it's building, you have bigger problems than the language you choose.
That's true at a high-level, but iterating on small features/changes fast is what makes or breaks most startups.
ok, I did ask myself this, still going with Rust there ;-)
Good question... Maybe...
Because with exceptions it's easy to end up with missing cases or unhelpful catch-all exceptions.
Typically with optional values I find that this is not the case for some reason.
Other interesting links I've yet to consume that may help us get closer to an answer this:
https://news.ycombinator.com/item?id=22225170 - "You're better off using exceptions"
https://softwareengineering.stackexchange.com/questions/4050...
https://dannyvanheumen.nl/post/why-i-prefer-error-values-ove...
Don't use `anyhow` for libraries, though. You want to provide your consumers the ability to `match`.
Proposals to make the existing syntax and semantics even look more like exceptions were met with lots of hostility.
Some people simply live in denial, believing untruths that confirm their worldview.
This is the same situation as with Go and generics. The maintainers were in denial for a very long time, before finally admitting what was obvious to some of us long before [1]
Would I do a startup in rust? Maybe. It’d depend on the idea. But I’d take measures to avoid the natural orthodoxy of Rust.
I think devs get burned when the MVP turns into forever code and somehow are not given room to refactor/rewrite once validated... or they are surrounded by devs who are used to pain/bug-cycle and they (or the business) will accept doing things haphazardly as a cost of doing business. Fred Brook's Second-System effect comes to mind.
Ah ha! Rust! Now we HAVE to write good code because rust has so many protections!!
This has nothing to do with Rust. I've seen the exact same thing happening with golang in a C++ only environment. Long project, took forever, failed slowly, took a week to rewrite in C++.
If you're writing a program in Rust, hire Rust devs or invest heavily in educating the devs you do have first.
Avoiding allocations in Rust, on purpose, and being uncomfortable with how to solve design challenges caused by hyper optimizing your code .. is not a Rust problem. Or an any language problem. Rust gave you rope and you hung yourself with it.
If you're stubborn and you want to use Rust but aren't familiar with the ways to avoid this; Allocate. Use Arc, Rc, Clone, etc. It won't hurt, it won't be terribly slow, and it almost assuredly won't be slower than your prototype languages.
Some might reply "Well then why use Rust!?", to which i would reply because i like it! I love Rust, but if i'm prototyping code i'm not going to write insanely abstract generics either. Why would i? I don't know the problem i'm solving yet, so how can i write truly generic abstractions to solve said problems? Performance is similar. I'll use lifetimes will prototyping in the simple cases, which is most to be honest, but beyond that don't hyper optimize.
To summarize: Choose your favorite language at crunch time. Even if your favorite language gives you rope to hang yourself with you probably don't need to.
I very much doubt that. It's likely true for straight up wasteful languages but very unlikely for more optimized runtimes.
What are you comparing it to? I believe parent meant that even if you clone everywhere, your code is still more likely to be faster than Node/Python, and potentially Go/C#/Java.
One thing to note is that C++ code tends to allocate all over the place from copy assignment and constructors, and it's still very fast. Rust only forces you to be explicit when cloning, but memcpy is still a fast operation, unless you're cloning large structs.
If you're building an operating system or a software platform, to for it; the robustness will pay for itself in time to fix hard-to-find errors.
But if you're iterating fast to find out what the program should be about to begin with, use a prototype-friendly language instead. With garbage collection.
My favorite part of the job is coding in Rust and we do a lot of cool things in that backend code. Unfortunately, most often the Rust code is the fastest and easiest part of a change, which means that I spend most of my time solving problems either on the front end with Typescript or on CI and infra type things rather than the Rust part.
It's a bit sad: if something just works, you spend less time on it than on the hairier things.
> Backend code is simpler
I hear this every once in awhile and think it's mostly a front end happy hour misrepresentation that makes everyone feel good so it gets repeated. The service layer of an application is very often far more complicated than, or to be fair, at least as complicated as, the user interface. Front end devs just typically aren't good at chopping up their problem into nice interfaces and therefore struggle to test it reliably or make large broad changes efficiently. This is where the complexity comes in. That's not a stab at FE devs, it's just not a skill that often gets rewarded in FE work so it's not very prevalent, which I find sad.
The service layer has to deal with enforcing the correctness of business logic despite the infinite ways the meat monkeys can interact with it. It does this by defining clear boundaries on the outside and by ensuring the transactional correctness of logic on the inside. While front end folks have to figure out the correct UX to use to successfully communicate with with a user, service layer folks have to figure out all the implications of a single action the user wishes to take and make sure it happens correctly. Data validation, data modeling, transactions, errors, queuing, retries, scaling, monitoring, etc. are all things that would probably make the average FE dev explode if thrust upon them.
If you compare these two surfaces, the sequence of mouse and keyboard events is very much unpredictable and the number of possible states you should think of is much larger. It's not by chance you spend more time with hairier things, it's because they are harder. In fact, it's a definition of "harder".
If you are writing a typical business application use Python. (Fast development times, low performance)
Or if you want to be clever do a hybrid of both. Create Rust modules for your Python code.
This is just about selecting the right tool for the right job.
If you are writing high performance code where a tracing GC isn't an option, and there are SDKs available use Rust.
Otherwise an AOT compiled language is a better option, and if not, and there are only C and C++ SDKs available, also factor in the development cost of creating wrappers in Rust, before doing the actual development activities.
If there is too much money being burned in wrapper libraries, maybe that isn't the best option as well.
Do you think you will need a major rewrite soon?
Go pick OCaml, Haskell, Scala or Kotlin with GraalVM or OpenJ9, F# with NativeAOT, Swift, Nim, D, whatever.
Spinup time of new engineers is like 6mo+, some devs who can churn out normal CRUD product work just fine in Rails or React ~never become productive with Rust, and hiring skilled Rust devs is just crazy hard (though maybe now that Blockchain/Solidity things have cooled there may be more supply).
Sidenote, we hire Rust at a small shop (~30 devs?). Ironically i've found it _easier_ to hire for Rust. You're totally not wrong, BUT, the quality of the candidates that apply is quite high in our experience. I suspect it's because we get a lot of passionate people. We don't have to weed out as many candidates.
With that said we don't aim for super senior devs. We're happy to hire a junior, etc. I care much more about the quality of the person than raw experience.
With that said traditional hiring avenues have not been fruitful for us. Word of mouth, Rust community job posting, etc have been most fruitful by far. Probably due to exactly what you said.
Being young and cheap is a good quality, I suppose. Experience is overrated.
(We were specifically looking for remote folks near EU time zones, maybe local in SF is harder?)
But here is a claim: Most business-level programmers are not ready for dealing with the borrowing and ownership concept. They don't want to care about reference vs. value types. They can't do memory management efficiently, because most of them have never used a language without GC.
With Rust you would need to care more about memory which is not necessary for most use cases in startups.
That is, the free money, in credits, that allows you to not worry about performance for a while.
The obvious problem being... "What happens when the free money runs out?"
It's not addressed, but the implied answer seems to be "Well, by then it'll be six months later, and..."
And? And what? It's still a problem! It didn't go away.
The money deflected the perf concerns for some time, maybe a year - but then it's back, bigger and badder than ever.
Rust solves that by not requiring that outlay ever, by being more efficient.
Seems like a very poor reason to not choose Rust, to me.
Depending what you do but it’s usually the case that performance won’t matter until mid late game. I saw a startup at +300M valuation still not having to worry about performance for a long time. And a cut in the AWS bill thanks to using a more performant language like Rust won’t make that much a difference compare to how much they had to pay devs. So you just want a language where you can ship features fast.
Also the hiring pool is still very small.
Yes, the claim is "fearless concurrency" but you'll still deadlocking mutexes and once you're heavily into async you need to start using language constructs that feel REALLY awkward, like pinning, runtime checks like RefCell, and so on.
IMO if Rust could make that whole aspect of the language more elegant, it'd be much easier to scale up to a larger org.
IMO there is merit in making a language that is equivalent to garbage collected rust by default with ownership rules similar to python. Then the classic rust based ownership and allocation schemes are all opt-in syntax-wise in the same way Box is opt-in.
A 2x gain is certainly not worth it. A 10x gain? Maybe. But that is hard to achieve when much of your "compute" is spent on the DB side of things. How many startups actually scale out of needing a few non-db instances?
A better statement is that it doesn’t get 10x improvement over Go, or other ergonomic compiled languages.
We average ~80 "instances" to maintain this level of performance. I have very little doubt that, if given the opportunity to rewrite this in rust, we could smash 10x perf improvements. Could we also get more perf out of tuning our python code better? Definitely. Do I think there's 10-20x improvement waiting to be uncovered? No.
Unfortunately (fortunately?) we're at a stage that it makes more sense to throw ludicrous sums of money at it than it does to ground up rewrite.
I get that the point of this article is that launching > sustaining.
But I have a horror story about AWS credits for startups: a friend's startup got their account suspended for a couple days when their Credits ran out, they started getting billed 5 digits (as they expected), and Amazon's fraud detection detected this as an anomalous billing pattern and suspended them! For transitioning from AWS credits to billing!?
Considering all their operations were running in AWS, and they were providing a HW-critical service to their customers, it was bad.
This was late last year. This was despite getting reassurances from their AWS rep that the transition would be smooth.
1. typescript - I already had 6 years of full stack js experience. The ecosystemis full of issues but its all issues I'm used to. 2. rust - new kid on the block. the type system and speed were compelling but it was still being developed and the learning curve was brutal. Plus I was under the gun to get the mvp up. we needed to get a working piece of software up and test out business assumptions.
In the end I went with elixir. Realtime sync was a huge killer feature we were aiming for and phoenix came with the best out of the box support for it. Overall productivity was on par with javascript while the functional aspects made certain types of bugs non issues in elixir. runtime performance has been more that adequate. after 3 years in production, we have only recently rolled out rate limiting and caching and only as a precaution as we've been expanding quickly.
Echoing the author's sentiments, I can definitely see places where rust could be better and thanks to tools like rustler, we'll be able to bring those in piecemeal as needed.
I'm sure our product would be even faster and more efficient on resources if we did it in rust but I'm pretty sure we would have more likely run out of runway before that happenned.
1. learning Rust, and 2. using Rust.
If you take away "Rust made us slow because our team had to learn how to use it and thus we had slow iterations and it's harder to find hires with Rust knowledge" from the equation, then you aren't left with much argument against using Rust early.
The iteration time issue with Rust is solved by experience. We use Rust and our iteration times are average. What we gain is not performance, that's not a reason we use Rust (for an early stage startup, totally agree you burn credits until you can afford to care). We gain correctness. And at an early stage, correctness without paying for a massive QA team is a huge boon.
There are definitely more mature tools for quickly standing up CRUD APIs. If you want a framework that can bootstrap you into an OpenAPI with docgen, swagger, all the bells and whistles, Rust doesn't do that. But Rust will help force you to write correct code that never crashes and handles edge cases it's easy to forget about when moving fast in a duck typed language.
The only language we seriously considered over Rust was Swift. But Swift's just wasn't quite there yet. It might be today. If I was starting something from ground zero today, I'd probably lean towards Swift and need to be argued down back to Rust or Python.
Conversely if you are looking to hire Java folks, you'll have an enormous pool to pick from, or if you need to train somebody in eg Go - you can do that significantly quicker than you could with Rust.
What I'm saying is that, disregarding the externalities, I've found Rust to be quite a boon and not inherently a bad fit for an early stage. YMMV.
Compared to what? Java, C# and Go?
I'm not sure why you feel Rust code would be more correct than Python code but it certainly isn't true.
The reasons are types and rusts multi threading guarantees, which become even more helpful when doing refactorings
A more effective strategy for business is to instead prototype in something like Python—that's what it's for. This was known back in the 90s, and been somewhat forgotten. Django too. Like a flexible clay to rapidly sculpt to a first approximation. Then:
1) Get to product market fit, keep iterating until you do. Do not go to step 2 until that happens.
2) Get the fundamental data models right, get your fundamental software design right. Keep iterating until you do. Do not go to step 3 until that happens.
This stuff is easier in Python as it gets out of your way. Yes use pyflakes, a few tests, and a code formatter to keep you honest. But not much more that that. Pycharm for example, if you need a helping hand.
3) When step 1 and 2 are a looking good, then rebuild the foundation of your gleaming skyscraper with the steel girders of rust, java, and/or other bdsm languages with an already good product and design.
Step three may not even be needed if you have a CRUDdy project. Complete the typing at that time.
So yeah, better to deal with complexities in the beginning and save on switching later, than not to use it all.
First, if you love Rust uniquely, I'd argue it might still make sense to use Rust to build your first product iterations. If your initial team commonly loves Rust, and can't agree on "love" for another common language, perhaps Rust is the best language. Burnout will happen exponentially faster if developers are lamenting the language (n.b. Rust is commonly lamented). I think there are enough language/platform options these days for this to be an unlikely scenario, but this is to say don't discount your passion for a language because it doesn't iterate fast enough.
Second, if the problem your startup is trying to solve is solidly in the performance and security realm, it makes sense to start in Rust. If your pitch is something like "pandas but fast and memory efficient" it also makes sense to start your project in something like Rust.
e.g python+cffi, or python+pyo3(for rust), or even lua+capi?
do we really need code everything in compiled language these days? the cold path can be dealt with by scripting languages, and let the c/c++/rust/etc to handle the performance critical path instead.
That is how AOLServer used to be, and all the other scripting languages developed as Apache plugins, back in the 2000's .com wave, like mod_perl and PHP.
point here is that to restrict script languages to glue logic for the most part, and always remember to use ffi for heavy lifting, not sure how to balance both yet.
This is more about "async Rust" and the way that the most common web frameworks for Rust are utilizing it. It should be totally possible to write much easier to understand frameworks and code - potentially with the limitation of using a classical thread per request architecture which avoids most of the async/lifetime/pinning pitfalls. The main drawback seems that such frameworks seem out of favor in Rust and thereby not available or not very well maintained.
There are other safe languages.
I think it's a really useful thing to have from day one, but it doesn't particularly point you to rust.
Also, performance is really about learning what your bottle-necks are, profiling them and optimizing them. You probably have no idea what those are when you start, so it's not really the right time to try to solve it. (There's a decent chance, e.g., that your inner loops won't even be in code you write, like the database.) Probably the best thing you can do for long-term performance up-front is to try to stay flexible, and try to keep your architecture simple.
Otherwise Rust just has semantics that allow more control over memory, as is often needed in lower level programs, while preventing pointer aliasing. The majority of languages in existence are memory safe--some even more so than Rust. They're just not as flexible.
Not everything is a CRUD mobile app trying to be the next tinder for cats.
Haskell. I know it best and know at least 10 people I could hire that also know it.
I can be as safe or as unsafe as needed where it's needed and get good performance without thinking about it most of the time.
Python has match, is this the same thing?
Interesting: Rust save money but that mean effort!
Unfortunately, the cloud isn't a very good environment for mixed-languages deployments (unless you stick to the most basic services), so you have to make a decision on the very beginning and stay with it.
I know the hype around Rust, but this is really exaggerated: you build a startup for creating a product or giving a service, not to have something written in Rust. Your customers should care about what you are offering, much more than about which language do you use.
Glad Rust is working for others, and I find it interesting to read about. but I don’t know if I could or would ever use it myself.