I have a couple of things I'm wondering about though:
- Node.js is pretty good at IO-bound workloads, but I wonder if this holds up as well when comparing e.g. Go or PHP. I have run into embarrassing situations where my RiiR adventure ended with less performance against even PHP, which makes some sense: PHP has tons of relatively fast C modules for doing some heavy lifting like image processing, so it's not quite so clear-cut.
- The "caveman" approach is a nice one just to show off that it still works, but it obviously has a lot of overhead just because of all of the forking and whatnot. You can do a lot better by not spawning a new process each time. Even a rudimentary approach like having requests and responses stream synchronously and spawning N workers would probably work pretty well. For computationally expensive stuff, this might be a worthwhile approach because it is so relatively simple compared to approaches that reach for native code binding.
7 lines of rust, 1 small JS change. It looks like napi-rs supports Buffer so that JS change could be easily eliminated too.
And with just a tiny bit of extra work you can give the worker an http interface.... Wait a minute.,.
Disclaimer: I'm one of the maintainers
i've been a user of uwebsockets.js, uwebsockets is used underneath by bun.
i hope encore does benchmark compared to encore, uwsjs, bun, and fastify.
express is just so damn slow.
Beyond performance, Rust also brings a high level of portability and these examples show just how versatile a pice of code can be. Even beyond the server, running this on iOS or Android is also straightforward.
Rust is definitely a happy path.
My favorite thing about Rust, however, is Rust dependency management. Cargo is a dream, coming from C++ land.
Even self-hosting on an rpi becomes viable.
At least with Rust it is safer.
Yes, for most languages. For example, in Zig (https://ziglang.org/documentation/master/#WebAssembly) or in C (https://developer.mozilla.org/en-US/docs/WebAssembly/C_to_Wa...)
> Relatedly, is it as easy to generate a QR code in C as it is in Rust (11 LoC)?
Yes, there are plenty of easy to use QR-code libraries available, for pretty much every relevant language. Buffer in, buffer out.
(Obviously there are other advantages to Rust)
Writing Rust for web (Actix, Axum) is no different than writing Go, Jetty, Flask, etc. in terms of developer productivity. It's super easy to write server code in Rust.
Unlike writing Python HTTP backends, the Rust code is so much more defect free.
I've absorbed 10,000+ qps on a couple of cheap tiny VPS instances. My server bill is practically non-existent and I'm serving up crazy volumes without effort.
> Safety : The code you write in a Rust NIF should never be able to crash the BEAM.
I tried to find some documentation stating how it works but couldn't. I think they use a dirty scheduler, and catch panics at the boundaries or something? wasn't able to find a clear reference.
Super surprised that shelling out was nearly as good any any other method.
Why is the average bytes smaller? Shouldn't it be the same size file? And if not, it's a different alorithm so not necessarily better?
This would entail zero network hops, probably 100,000+ QRs per second.
IF it is 100,000+ QRs per second, isn't most of the thing we're measuring here dominated by network calls?
Not a bad idea for an internal office network where every computer is hooked up with a gigabit or better, but not great for cloud hosted web applications.
The article is mostly about exemplifying the various leve of optimisation you can get by moving “hot code paths” to native code (irrespective whether you write that code in rust/c++/c.
Worth noting that if you’re optimising for memory usage, rust (or some other native code) might not help you very much until you throw away your whole codebase, which might not be always feasible.
But assuming that the hypothetical C and C++ versions would be using generators and compressors of similar quality, it performance characteristics should be similar.
The big plus(es) to using Rust over C/C++ are a) the C and C++ versions would not be memory-safe, and b) it looks like Rust's WASM tooling (if that's the approach you were to use) is excellent.
(As someone who has written C code for more than 20 years, and used to write older-standard C++ code, I would never ever write an internet-facing server in either of those languages. But I would feel just as confident about the security properties of my Rust code as I would for my Java code.)
It runs on any JVM and has a couple flavors of "ahead-of-time" bytecode compilation.
I didn’t notice this on the front page, what JVM versions is this compatible with?
On Linux, fork() is actually reasonably fast, and if you're exec()ing a binary that's fairly small and doesn't need to do a lot of shared library loading, relocations, or initialization, that part of the cost is also fairly low (for a Rust program, this will usually be the case, as they are mostly-statically-linked). Won't be as low as crossing a FFI boundary in the same process (or not having a FFI boundary and doing it all in the same process) of course, but it's not as bad as you might think.
Or, in other words, it's the unavoidable result of insisting on using a language created for the frontend to write everything else.
You don't need to rewrite your code in Rust to get that saving. Any other language will do.
(Personally, I'm surprised all the gains are so small. Looks like it's a very well optimized code path.)
As I said in another comment, the most likely cause is that temporary garbage is not collected immediately in JavaScript, while garbage is collected immediately in Rust. See https://doc.rust-lang.org/nomicon/ownership.html for the key idea behind how Rust manages this.
If you truly believe that it is somehow due to data isolation, then I would appreciate a reference to where JavaScript's design causes it to behave differently.
I don't think this is an educated take.
The whole selling point of JavaScript in the backend has nothing to do with "frontend" things. The primary selling point is what makes Node.js take over half the world: it's async architecture.
And by the way, benchmarks such as Tech Empower Web Framework still features JavaScript frameworks that outperform Rust frameworks. How do you explain that?
This also means that you cannot "simply measure memory usage" (e.g. using `time` or `htop`) without already having a relatively deep understanding of the underlying mechanisms.
Most importantly:
libc / malloc implementation:
glibc by default has heavy memory fragmentation, especially in multi-threaded programs. It means it will not return `malloc()`ed memory back to the OS when the application `free()`s it, keeping it instead for the next allocation, because that's faster. Its default settings will e.g. favour 10x increased RESident memory usage for 2% speed gain. Some of this can be turned off in glibc using e.g. the env var `MALLOC_MMAP_THRESHOLD_=65536` -- for many applications I've looked at, this instantaneously reduced RES fro 7 GiB to 1 GiB. Some other issues cannot be addressed, because the corresponding glibc tunables are bugged [2]. For jemalloc `MALLOC_CONF=dirty_decay_ms:0,muzzy_decay_ms:0` helps to return memory to the OS immediately.
Linux:
Memory is generally allocated from the OS using `mmap()`, and returned using `munmap()`. But that can be a bit slow. So some applications and programming language runtimes use instead `madvise(MADV_FREE)`; this effectively returns the memory to the OS, but the OS does not actually do costly mapping table changes unless it's under memory pressure. As a result, one observes hugely increased memory usage in `time` or `htop`. [2]
The above means that people are completely unware what actually eats their memory and what the actual resource usage is, easily "measuring wrong" by factor 10x.
For example, I've seen people switch between Haskell and Go (both directions) because they thought the other one used less memory. It actually was just the glibc/Linux flags that made the actual difference. Nobody made the effort to really understand what's going on.
Same thing for C++. You think without GC you have tight memory control, but in fact your memory is often not returned to the OS when the destructor is called, for the above reason.
This also means that the numbers for Rust or JS may easily be wrong (in either direction, or both).
So it's quite important to measure memory usage also with the tools above malloc(), otherwise you may just measure the wrong thing.
[1]: https://sourceware.org/bugzilla/show_bug.cgi?id=14827
[2]: https://downloads.haskell.org/ghc/latest/docs/users_guide/ru...
People write bad-performing code not because it's easier, it's because they don't know how to do it better or don't care.
Repeating things like "premature optimization is the root of all evil" and "it's cheaper to get a bigger machine than dev time" are bad because people stop caring about it and stop doing it and, if we don't do it, it's always going to be a hard and time-consuming task.
Sometimes it means spending a couple extra minutes here or there to teach a junior about freeing memory on their PR.
No one is suggesting it has to be a zero-sum game, but it would be nice to bring some care for the engineering of the craft back into a field that is increasingly dominated by business case demands over all.
From my casual dabbling in python and rust they feel like they’re in similar ballpark. Especially if I want the python code to be similarly robust as what rust tends to produce. Edge cases in python are much more gnarly
Adding Rust into your build pipeline also takes planning and very careful upfront design decisions. `cargo build` works great from your command line, but you can't just throw that into any pre-existing build system and expect it to just work.
The same will happen on any function where you're calling functions over and over again that create transient data which later gets discarded.
The problem is that most developers are not capable of optimizing for efficiency and performance.
Having more powerful hardware has allowed us to make software frameworks/libraries that make programming a lot more accessible. At the same time lowering the quality of said software.
Doesn't mean that all software is bad. Most software is bad, that's all.
> Regarding the abnormally high memory usage, it's because I'm running Node.js in "cluster mode", which spawns 12 processes for each of the 12 CPU cores on my test machine, and each process is a standalone Node.js instance which is why it takes up 1300+ MB of memory even though we have a very simple server. JS is single-threaded so this is what we have to do if we want a Node.js server to make full use of a multi-core CPU.
On a Raspberry Pi you would certainly not need so many workers even if you did care about peak throughput, I don't think any of them have >4 CPU threads. In practice I do run Node.JS and JVM-based servers on Raspberry Pi (although not Node.JS software that I personally have written.)
The bigger challenge to a decentralized Internet where everyone self-hosts everything is, well, everything else. Being able to manage servers is awesome. Actually managing servers is less glorious, though:
- Keeping up with the constant race of security patching.
- Managing hardware. Which, sometimes, fails.
- Setting up and testing backup solutions. Which can be expensive.
- Observability and alerting; You probably want some monitoring so that the first time you find out your drives are dying isn't months after SMART would've warned you. Likewise, you probably don't want to find out you have been compromised after your ISP warns you about abuse months into helping carry out criminal operations.
- Availability. If your home internet or power goes out, self-hosting makes it a bigger issue than it normally would be. I love the idea of a world where everyone runs their own systems at home, but this is by far the worst consequence. Imagine if all of your e-mails bounced while the power was out.
Some of these problems are actually somewhat tractable to improve on but the Internet and computers in general marched on in a different more centralized direction. At this point I think being able to write self-hostable servers that are efficient and fast is actually not the major problem with self-hosting.
I still think people should strive to make more efficient servers of course, because some of us are going to self-host anyways, and Raspberry Pis run longer on battery than large rack servers do. If Rust is the language people choose to do that, I'm perfectly content with that. However, it's worth noting that it doesn't have to be the only one. I'd be just as happy with efficient servers in Zig or Go. Or Node.JS/alternative JS-based runtimes, which can certainly do a fine job too, especially when the compute-intensive tasks are not inside of the event loop.
Retry for a while until the destination becomes reachable again. That's how email was originally designed.
I had fun writing it, learned some new stuff along the way, and ended up with an API that could serve 80K RPS (according to the venerable ab command) on my laptop with almost no optimization effort. I will absolutely reach for Rust+Actix again for my next project.
(And I found, fixed, and PR’d a bug in a popular rate limiter, so I got to play in the broader Rust ecosystem along the way. It was a fun project!)
I would definitely disagree with this after building a micro service (url shortener) in rust. Rust requires you to rethink your design in unique ways, so that you generally cant do things in the 'dumbest way possible' as your v1. I found myself really having to rework my design-brain to fit rusts model to please the compiler.
Maybe once that relearning has occurred you can move faster, but it definitely took a lot longer to write an extremely simple service than I would have liked. And scaling that to a full api application would likely be even slower.
Caveat that this was years ago right when actix 2 was coming out I believe, so the framework was in a high amount of flux in addition to needing to get my head around rust itself.
This has been my experience. I have about a year of rust experience under my belt, working with an existing codebase (~50K loc). I started writing the toy/throwaway programs i normally write, now in rust instead of go halfway through this stretch. Hard to say when it clicked, maybe about 7-8 months through this experience, so that i didn't struggle with the structure of the program and the fights with the borrow checker, but it did to the point where i don't really have to think about it much anymore.
What is it about Rust that makes it so appealing to people to use for web backend development? From what I can tell, one of the selling points of Rust is its borrow checker/lifetime management system. But if you're making a web backend, then you really only need to care about two lifetimes: the lifetime of the program, and the lifetime of a given request/response. If you want to write a web backend in C, then it's not too difficult to set up a simple system that makes a temporary memory arena for each request/response, and, once the response is sent, marks this memory for reuse (and probably zeroes it, for maximum security), instead of freeing it.
Again, I don't really have any experience with Rust whatsoever, but how does the borrow checker/lifetime system help you with this? It seems to me (as a naïve, outside observer) that these language features would get in the way more than they would help.
> Again, I don't really have any experience with Rust whatsoever, but how does the borrow checker/lifetime system help you with this? It seems to me (as a naïve, outside observer) that these language features would get in the way more than they would help.
You're absolutely right that the borrow checker would get in the way. But it's mostly irrelevant in Rust web development. Backend request flow code almost never shares references or changes ownership, so you don't need to think about ownership much in Rust webdev. And since most of the time Rust can infer the lifetimes of variables, you can almost entirely ignore the system and not even annotate lifetimes in your types.
So what you are left with is a language with an incredible type system, extremely modern semantics and ergonomics, zero cost functional abstractions that have no overhead, trait-based OO instead of classes, sum types (Rust enums) and fantastic syntax around matching [1], option and result types (themselves sum types) with fantastic ergonomics, syntax and error handling designed to result in fewer defects in your code, incredible package manager, incredible build system, single binary build targets, the best compiler error messages and lints in the world currently, cross compilation for a wide variety of systems, bare metal performance with no garbage collection.
It's a phenomenal language and offers so much.
And it's insane that you get bare metal / C performance in web code without even having to think about it.
Rust never set out to be a backend web development language, but because the borrow checker disappears when doing web development, you get so many free things from the language that you don't have to pay for. This post [2] explains it pretty well.
[1] One of the best things about the language
This metric doesn't convey any meaningful information. Performance metrics need context of the type of work completed and server resources used.
Oh jeez, hard disagree. I absolutely love Rust, but spinning up something in Flask is so so so much easier than in Rust (warp and axum are where I have experience). Certainly some of this is just a part of the learning curve of figuring out a Rust crate you haven't used before. But still, I don't think it's credible that Rust web development is just as productive as the others you mention.
The content being encoded in the PNG was different ("https://www.reddit.com/r/rustjerk/top/?t=all" for the first, "https://youtu.be/cE0wfjsybIQ?t=74" for the second example - not sure whether the benchmark used different things?), so I'd expect the PNG buffer pixels to be different between those two images and thus the compressed image size to be a bit different, even if the compression levels of DEFLATE within the PNG were the same).
[0]: https://github.com/pretzelhammer/using-rust-in-non-rust-serv...
[1]: https://zlib.net/manual.html#Advanced:~:text=The%20strategy%...
[2]: https://github.com/rust-lang/flate2-rs/blob/1a28821dc116dac1...
Edit: Nevermind. If you look at the actual generated files, they're 594 and 577 bytes respectively. This is mostly HTTP headers.
[3]: https://github.com/pretzelhammer/rust-blog/blob/master/asset...
[4]: https://github.com/pretzelhammer/rust-blog/blob/master/asset...
It may be just additional HTTP headers added to the response, but then it's hardly fair to use that as a point of comparison and treat smaller as "better".
Average response size also halved from 1506 bytes to 778 bytes, the compression algo in the Rust library must be better than the one in the JS library
It is the availability of the developers who know the language (JavaScript) (aka cheaper available workforce).
Node.js is popular because js is popular. You pretty much guarantee an infinite pool of developers for, like, ever. And you can even use those developers across the entire stack with greater velocity and much less onboarding.
async is cool, but not that cool. CGI was doing basically that a long time ago, and it was even more automagical.
> Tech Empower Web Framework still features JavaScript frameworks that outperform Rust frameworks. How do you explain that?
The benchmarks are constructed in such a way that highlights the strengths of the particular JS JIT implementation. JS is good at a lot of things, so if you just do those things, it might appear that it has okay performance.
People do the same thing with C# vs C++; this has been a problem forever. Sure, C# is about as fast or close if you have 16 gigs allocated to the GC and your app is using 100 megs. Now run at 95% memory usage with lots of churning and the order of magnitude differences come out. It's just a fundamental problem with GC langs.
C# has excellent async for asp.net and has for a long time. I haven't touched Java in ages so cannot comment on the JVM ecosystem's async support. So there are other excellent options for async backends that don't have the drawbacks of javascript.
The trick is getting everyone to switch over and ensure correct security and correctness for the newer software. A good example may be openssh. It is very well established, so many will use it - but it has had some issues over the years, and due to that, it is actually _very_ difficult now to know what the _correct_ way to configure it for the best, modern, performant, and _secure_ operation. There are hundreds of different options for it, almost all of them existing for 'legacy reasons' (in other words no one should ever use in any circumstance that requires any security).
Then along comes things like mosh or dropbear, which seem like they _may_ improve security, but still basically do the same thing as openssh, so it is unclear if they have a the same security problems and simply don't get reported due to lower use, or if they aren't vulnerable.
While simultaneously, things like quicssh-rs rewrite the idea but completely differently, such that it is likely far, far more secure (and importantly simpler!), but getting more eyes on it for security is still important.
So effectively, having things like Linux move to Rust (but as the proper foundation rather than some new and untrusted entity) can be great when considering any 'rewrite' of software, not only for removing the cruft that we now know shouldn't be used due to having better solutions (enforce using only best and modern crypto or filesystems, and so on), but also to remodel the software to be more simple, cleaner, concise, and correct.
I don't see why. People will just discover they rewrote something slower.
In python land, uv (for project) and pipx (for CLI tools).
Package management for languages owes its heritage to CPAN, which then, in turn, owes its lineage to StopAlop the first package manager written about 1992, which inspired dpkg. Now there is nix which cuts across system package and configuration management. Perhaps in the future or soon LLMs will be able to rewrite hot sections in other languages and repeatedly benchmark various implementation approaches in a generative manner.
That includes users of low-level languages. They assume free() means free when it doesn't.
And assumption- and hope-driven development are less bothersome to the mind!
It's annoying to have to fact-check every sane assumption, but unfortunately it's required. Of course for anything that exists, somebody somewhere built a cache around it for average-case performance gains that destroys simplicity and adds pathological edge cases.
Most people learn this only when they try to run a real-world system that inexplicably runs out of RAM, or if they see unreasonably large number and actually start digging instead of just accepting it.
Sure, the SMTP email protocol states guidelines for "retries" but senders don't waste resources retrying forever. E.g. max of 5 days: https://serverfault.com/questions/756086/whats-the-usual-re-...
So gp's point is that if your home email server is down for an extended power outage (maybe like a week from a bad hurricane) ... and you miss important emails (job interview appointments, bank fraud notifications, etc) ... then that's one of the risks of running an email server on the Raspberry Pi at home.
Switching to a more energy-efficient language like Rust for server apps so it can run on RPi still doesn't alter the risk calculation above. In other words, many users would still prioritize email reliability of Gmail in the cloud over the self-hosted autonomy of a RPi at home.
I don't think it's an obstacle that's absolutely insurmountable, but it feels like something where we would need to organize the entire Internet around solving problems like these. My personal preference would be to have devices act more independently. e.g. It's possible to sync your KeepassXC with SyncThing at which point any node is equal and thus only if you lose all of your devices simultaneously (e.g. including your mobile computer(s)) are you at risk of any serious trouble. (And it's easy to add new devices to back things up if you are especially worried about that.) I would like it if that sort of functionality could be generalized and integrated into software.
For something like e-mail, the only way I can envision this working is if any of your devices could act as a destination in the event of a serious outage. I suspect this would be possible to accomplish to some degree today, but it is probably made a lot harder by two independent problems (IPv4 exhaustion/not having directly routable IPs on devices, mobile devices "roaming" through different IP addresses) which force you to rely on some centralized infrastructure anyways (e.g. something like Tailscale Funnels.)
I for one welcome whoever wants to take on the challenge of making it possible to do reliable, durable self-hosting of all of my services without the pain. I would be an early adopter without question.
It is very hard know if your software is going to be popular enough for costs to be factor at all and even if it would be, it is hard to know whether you can survive as a entity long enough for the extra delay, a competitor might ship a inferior but earlier product or you may run out money.
You rather ship and see with the quick and dirty and see if there demand for it to worth the cleaner effort .
There is no limit to that, more optimization keeps becoming a good idea as you scale at say Meta or Google levels it makes sense to spend building your own ASICs for example we won’t dream of doing that today
If you're running a web server, it definitely is. Compute and memory literally translate into money.
Vast majority of applications both in enterprise and consumers space that get built do not close to even say 10k monthly active users.
You can stick several of those apps in crappy PHP or NodeJS code on to the cheapest $50 / month VPS and nobody will know the difference ,or do it even cheaper on run them on serverless stacks such as firebase, lambda etc.
There is a threshold for usage of code under which the significant cost driver is developer time and skill levels needed. 95%+ professional developers never write code that will exceed this threshold.
This economics is what drives
- So much poorly written plugins for CMSes or apps in easy to start languages like PHP or nodeJS,
- No code solutions as diverse as Retool or Shopify get so much revenue and valuations
- Copilot style AI assistance would have a market even when they were not good enough for skilled developers.
This economics works for startups too, all of here use the cloud, and it will keep making sense both technically and economically until we hit perhaps 10s if not 100s of millions of users. We don't care about somebody else DC with network mounted disk with shitty I/O performance or be bothered about paying for metered bandwidth or the language we use.
There are only few pieces of code that truly cross the threshold where cost of performance of infra(RAM, Disk, Memory, Bandwidth, CPU etc) costs are much greater than cost of developer time so it makes sense to optimize it.
Facebook fiddled with Hack run time before optimizing out their PHP stack, Twitter famously rewrote out their ruby stack after they kept hitting limits. Products have to only worry about this problem IF they scale, most don't.
Don't you end up paying for it with compile times? Because the borrow checker has to check all your lifetime annotations and do a bunch of work, just to come to the conclusion that your simple two-lifetime (or whatever) setup is in fact valid?
Vegeta, the tool I used for benchmarking, iterates through all those targets round-robin style while attacking the server and then averages the results when reporting the average response size in bytes (and it only measures the size of the response body, it doesn't include other things like headers).
Even using the same library and same compression algorithm not all 200px by 200px QR code PNGs will compress to the same size. How well they can be compressed depends a lot on the encoded piece of text as that determines the visual complexity of the generated QR code.
I'm not totally happy with sqlx and the logging situation, but most issues that come up are the "solve once and never worry about it again" type.
If I had to do some of my projects over again, I'd probably just stick with synchronous Rust and thread pools.
The concept of async isn't that bad, but it's implementation in Rust feels rushed and incomplete.
For a language that puts so much emphasis on compile time checks to avoid runtime footguns, it's way too easy to clog the async runtime with blocking calls and not realize it.
If you know the machine and platform ahead of time, not really. For frontend JS this isn't the case. But for backend code it absolutely is the case.
Sure, theoretically the JIT can sit in the background, see which functions are called the most and how they're call and then re-JIT pieces of code. In practice, I'm not sure how often this is done and if you even gain much performance. You MIGHT in a dynamically typed lang like JS because you can find out a bunch of info at runtime. In something like C# though? You already know a bunch at compile-time.
How does a Java style com.foo.bar or Golang style URL help e.g. mitigate supply chain attacks? For Golang, if you search pkg.go.dev for "jwt" there's 8 packages named that. I'm not sure how they are sorted; it doesn't seem to be by import count. Yes, you can see the URL directly, but crates.io also shows the maintainers. Is "github.com/golang-jwt/jwt/v5" "better" than "golang.org/x/oauth2/jwt"? Hard to say at a glance.
On the flip side, there have been several instances where Cargo packages were started by an individual, but later moved to a team or adopted. The GitHub project may be transferred, but the name stays the same. This generally seems good.
I honestly can't quite see what the issue is, but I have been wrong many a time before.
I truly like rust as a performance language but I would rather like real tangible results (admittedly slow is okay) than imagination within the rust / performance land.
I don't want to learn rust to feel like I am doing something "good" / "learning" where I can learn golang at a way way faster rate and do the stuff that I like for which I am learning programming.
Also just because you haven't learned rust doesn't make you inferior to anybody.
You should learn because you want to think differently , try different things. Not for performance.
Performance is fickle minded.
Like I was seeing a native benchmark of rust and zig (rust won) and then I was seeing benchmark of deno and bun (bun won) (bun is written in zig and deno in bun)
The reason I suppose is that deno doesn't use actix and non actix servers are rather slower than even zig.
It's weird .
It's just Rust is somehow more accessible to them? Maybe it's that pointers and memory just was an inaccessible / overburdensom transition?
In Go instead of having a value that can be one of two different types, you have to have two values one of which you set to the zero value. It feels prehistoric.
Going to lower level languages can be scary. What is 'fighting the borrow-checker' for some, may be 'guard rails' for others.
When Facebook started PHP was a good choice. These days though you'd probably be better off going for Springboot or .NET. These are more performant and much more "batteries included". I would say the same thing goes for Node.
Node.js is... usable. It's certainly not nice to write JS on the backend and you need a LOT of libraries to make it work. Then the problem is they don't all interop perfectly together and you won't have great tooling. I think most people, even startups, would be better off going with a backend framework with good tooling. If you're able to do a bunch of codegen + you have all the batteries, I would imagine developer velocity would be faster.
They balanced developer velocity over time and the learning curve needed to use them. Learning curve is important because steeper the curve, more experienced/skilled developers are needed and that translates to more $/hr cost of dev time. Simpler learning + with codegen tools was the pitch that RoR or .NET and all the frameworks inspired by them had in late 2000s.
Today it has shifted, to models like Firebase, Supabase or Hasura, NextJs or similar stacks using GraphQL, gRPC or occasionally RESTful APIs generation workflows instead of boilerplating tools .NET, Springboot et al provided . These frameworks come with hosting services and typically language agnostic, however TypeScript/ JavaScript is dominant choice in this model the developer now only focuses on business logic and not worry about organizing code or running it or about standard components like auth, so frontend teams are more likely to own this now and they will write TS/JS more often than not.
Even runtimes like Deno are getting into the game, instead of just writing the runtime code and make money with consulting, Deno wants to make DX for running code in their managed runtime so simple that a lot of small teams would just use that out of the box.
Until the app is at 10s of million scale - non NodeJS + unmanaged stacks won't make economic sense. People will build software in any system of course, because it is what they know not because it is the rational decision.
It really depends on what you mean by "memory usage".
The fundamental principle of any garbage collection system is that you allocate objects in the heap at will without freeing them until you really need to, and when that time comes you rely on garbage collection strategies to free and move objects. What this means is that processes end up allocating more data that the one being used, just because there is no need to free it. Consequently, with garbage collecting languages you configure processes with a specific memory budget. The larger the budget, the rarer these garbage collection strategies kick in.
I run a service written with a garbage collected language. It barely uses more than 100MB of memory to handle a couple hundred requests per minute. The process takes over as much as 2GB of RAM before triggering generation 0 garbage collection events. These events trigger around 2 or 3 times per month. A simplistic critic would argue the service is wasting 10x the memory. That critic would be manifesting his ignorance, because there is absolutely nothing to gain by lowering the memory budget.
Given that compute is often priced proportional to (maximum) memory usage, there is potentially a lot to be gained: dramatically cheaper hosting costs. Of course if your hosting costs are small to be begin with then this likely isn't worthwhile.
Let's look at numbers.
Hetzner sells vCPUs with 4GB of RAM for less than 5$/month, and 8GB of RAM for less than $10/month.
https://www.hetzner.com/cloud/
In my example, the cost of having garbage collection generation 0 events triggering twice a year would be an extra $5. If I wanted the frequency of these events to double, in theory I would save perhaps $2/month.
If I ran a web-scale service with 10 times the nodes as-is, we're talking about a $50/month price tag difference.
How much does a company charge for an engineer's hourly labor? How many years would it take to recover the cost of having an engineer tune a service's garbage collection strategy?
People need to thing things through before discussing technical merits.
Well, that depends on information you haven't provided. Maybe your system does have an extra 900 MB of memory hanging around; I've certainly seem systems where the minimum provisionable memory[1] is more than what the system will use for program memory + a full cache of the disk. If that's the case, then yeah, there's nothing to gain. In most systems though, 900 MB of free memory could go towards caching more things from disk, or larger network buffers, or something more than absolutely nothing.
Even with all that, lowering your memory budget might mean more of your working memory fits in L1/L2/L3 cache, which could be a gain, although probably pretty small, since garbage isn't usually accessed. Absolutely nothing is a pretty low barrier though, so I'm sure we could measure something. Probably not worth the engineering cost though.
There are also environments where you can get rather cheap freeing by setting up your garbage to be easily collected. PHP does a per-request garbage collection by (more or less) resetting to the pre-request state after the request is finished; this avoids accumulating garbage across requests, without spending a lot of effort on analysis. An Erlang system that spawns short lived BEAM processes to handle requests can drop the process heap in one fell swoop when the process dies; if you configure the initial heap size so no GCs are triggered during the lifetime of the process, there's very little processing overhead. If something like that fits your environment and model, it can keep your memory usage lower without a lot of cost.
[1] Clouds usually have a minimum memory per vCPU; if you need a lot of CPUs and not a lot of memory, too bad. I don't think you can buy DDR4 SIMMs of less than 4GB, or DDR5 of less than 8GB. Etc
That's not how it works. You cannot make sweeping statements over how something is bad when you fail to consider how it's used and what are the actual real world constraints.
For example, you're arguing that minimizing memory consumption is somehow desirable, and if you're making that claim you need to actually make a case. I clearly refuted your point by clarifying how things work in the real world. If you feel you can come up with a corner case that refutes it, just do it. So far you haven't, but that didn't stopped you from making sweeping statements.
If you rebuild Linux from the ground up with isolation in mind, you will be able to do it more efficiently. People are indeed in the process of rewriting it, but it's far from complete (and moving back and forward, as not every Linux dev cares about it).
What is it specifically about JavaScript's implementation of data isolation that, in your mind, helps cause the excessive memory usage?
Even zoom, used to be very efficient, but has gradually got worse over time :-(
How many software projects have you seen fail because it couldn't run fast enough or used too many resources? Personally, I've never seen it. I'm sure it exists, but I can't imagine it's a common occurrence. I've rewritten systems because they grew and needed perf upgrades to continue working, but this was always something the business knew, planned for and accepted as a strategy for success. The project may have been less successful if it had been written with performance in mind from the beginning.
With that in mind, I can't think of many things less appropriate to keep in your mind as a first class concern when building software than performance and optimization. Sure, as you gain experience in your software stack you'll naturally be able to optimize, but since it will possibly never be the reason your projects fail and presumably your job is to ensure success of some project, then it follows that you should prioritize other things strongly over optimization.
This is doing a lot of heavy lifting. Just because an app is slow doesn't mean Rust would've made it faster. It may just be slow because of a bad query or otherwise poor architecture, especially in web development. The commenter is asking what projects you've seen fail because the language itself hit a performance limit that couldn't be worked around.
I'll take a fast food job for 40 hours any day of the week over most of the options in poor countries. Sure, nothing but the best, fulfilling jobs for everyone is ideal, but until I see that exist I'm not informed enough to know whether it's possible outside of someones political ideals.
This has always been a poor argument.
I am not saying you're wrong, I just don't find it any better than C++ concurrent code, you just have many different lock types that correspond to the borrow-checker's expectations, vs C++'s primitives / lock types.
Channels are nicer, but that's doable easily in C++ and native to Go.
Which is quite good, but leaves out shared memory with other processes, or threads having data races with external resources, while corner cases they are quite common in distributed computing scenarios.
Yes and yes...
Rust statically enforces that you don't have data races, i.e. it's not possible in Rust (without unsafe hacks) to forget to guard access to something with a mutex. In every other language this is enforced with code comments and programmer memory.
Unless you can come up with a specific reference, it seems unlikely that this would explain the large memory efficiency difference. By contrast it is simple and straightforward to understand why keeping temporary garbage until garbage collection could result in tying up a lot of memory while continually running code that allocates memory and lets it go out of scope. If you search, you'll find lots of references to this happening in a variety of languages.
What large piece of software with a user interface do you work with that is actually fast and stays fast? For me, its probably just Chrome / Firefox. Everything else seems to get slower over time.
So "Rust" means "Not JavaScript, and also a bunch of other constraints that mean that Rust is pretty much the only sensible choice."
Hum, no. The point is exactly that it would help a great deal if you moved to Python or Ruby or PHP.
Of course, Rust will give you even better memory efficiency. But Javascript is a particularly bad option there, and almost anything else would be an improvement. ("Almost", because if you push it enough and move to something like MathLab, you'll get worse results.)
Also, I don't know what Node is doing exactly, but if you take a lot of these dynamic languages and just fork them into multiple processes, which they still largely need to do to effectively use all the CPUs, you will generally see high per-process memory consumption just like Node. Any memory page that has a reference counter in it that is used by your code ends up Copied-On-Write in practice by every process in the steady state because all you need to do to end up copying the page is looking at any one reference it happens to contain in such a language. At least in my experience memory sharing gains were always minimal to effectively zero in such cases.
[1] https://www.techempower.com/benchmarks/#hw=ph&test=composite...
What leads you to believe in that?
There are some cases where C++ makes sense:
* You have a large existing C++ codebase you need to talk to via a large API surface (C++/Rust FFI is not great)
* You have a C++ library that's core to your project and doesn't have a good Rust alternative (i.e. Qt)
* You don't like learning (and are therefore in completely the wrong industry!)
Also, it has so few footguns compared to C or C++ even modestly experienced developers can safely use it.
Basically the best place where Rust can work is one where all variables, all requirements and all edgecases are known ahead of time or cases where manual memory safety is a necessity vis-a-vis accepting a minor performance hike from things like the garbage collector. This works well in some spaces (notably; systems programming, embedded and Browser Engines and I wouldn't consider the latter a valid target), but webserver development is probably one of the furthest places where you are looking for Rust.
In a lot of languages you're working with a hammer and nail (metaphorically speaking) and when you move to a different language its just a slightly different hammer and nail. Rust is a screwdriver and screw though, and once I stopped trying to pound the screw in with the screwdriver, but rather use the one to turn the other, it was a lot easier. Greenfield projects with a lot of iteration are just as fast as doing it in python (although a bit more front-loaded rather than debugging), working new features into existing code - same thing.
They are comfortable with runtimes
Misconception.
You will encounter the borrow checker almost never when writing backend web code in Rust. You only encounter it the first time when you're learning how to write backend code in Rust. Once you've gotten used to it, you will literally never hit it.
Sometimes when I write super advanced endpoints that mutate global state or leverage worker threads I'll encounter it. But I'm intentionally doing stuff I could never do in Python or Javascript. Stuff like tabulating running statistics on health check information, batching up information to send to analytics services, maintaining in-memory caches that talk to other workers, etc.
This tends to work well for most crud api servers, since you allocate “context, request, and response” data at the start of the handler function, and deallocate at the end. Most helper data can also be tied to the request lifecycle. And data is mainly isolated per-request. Meaning there isn’t much data sharing across multiple request.
This means that the borrow checker “just works”, and you probably won’t even need lifetime annotations or even any special instructions for the borrow checkers. It’s the idealized use case the borrow checker was designed for.
This is also the property which most GC languages like Java, Go, and C# exploit with generational garbage collectors. The reason it “works” in Java happens to be the same reason it works in Rust.
If your server does need some shared in-memory data, you can start by just handing out copies. If you truly need something more complicated, and we are talking about less than 10% of crud api servers here, then you need to know a thing or two about the borrow checker.
I’m not saying to rewrite web servers in Rust, or even advocating for it as a language. I’m just pointing out that a crud api server is the idealized use case for a borrow checker.
No
Once you learn to surrender to the borrow checker it becomes friend, not foe
You must submit
The performance benefits of Rust were supposed to be a non-penalty: "Look, you can please please use this where you'd use C or C++ I promise it won't impact your performance!" Performance and GC overhead was the rejection de jure of every other C replacement.
But here we are: All friends are javascript front-enders-turned-backenders and are wondering if they should pick up Rust. It fits into a pattern of "new shiny" but it's good, don't get me wrong, if everyone experiences compiled languages and starts writing their headless code in sensible languages.
Repeating myself, but I'm just wondering why not Go? Why now?
If you have sufficient experience, that's not really the case. Certainly compared to "comparable" languages like C++ where that time fighting the borrow checker might instead have been spent chasing random crashes.
But, if you want a dynamically typed experience, look no further than PHP or Perl. Also significantly faster, and, if I had to bet, you could probably iterate much faster in Perl. It wouldn't be fun, but honestly, I doubt that Perl is more footgunny than JS.
func f() (SomeType, error) {
// ...
}
In Rust you would return one value: fn f() -> anyhow::Result<SomeType> {
// ...
}
In Go (and similar languages like C) nothing enforces that you actually set exactly one value, and nothing enforces that you actually handle the values that are returned.It's even worse if you need to add a variant, because then it's easy to make a mistake and not update some site that consumes it.
This reads like a parody of Rust's fandom.
enum Animal {
case dog(name: String)
case cat(name: String)
case bird
func sound() {
switch self {
case .dog(let name):
print("\(name) says Woof!")
case .cat(let name):
print("\(name) says Meow!")
case .bird:
print("Tweet!")
}
}
}
and another with nesting enum Thing {
case first(x: Int)
case second
}
enum Outer {
case ok(Thing?)
}
let value: Outer = .ok(.some(.first(x: 42)))
switch value {
case .ok(.some(.first(let x))):
print("Matched with x = \(x)")
case .ok(.some(.second)):
print("Matched .second")
case .ok(.none):
print("Matched .none")
}In my experience, languages like Ruby and Python are slower than languages like Javascript, which are slower than languages like C#/Java, which are slower than languages like C++/Rust, which are slower than languages like C and Fortran. Assembly isn't always the fastest approach these days, but well-placed assembly can blow C out of the water too.
The ease of use and maintainability scale in reverse in my experience, though. I wouldn't want to maintain the equivalent of a quick and dirty RoR server reimplemented in C or assembly, especially after it's grown organically for a few years. Writing Rust can be very annoying when you can't take the normal programming shortcuts because of lifetimes or the borrow checker, in a way that JIT'ed languages allow.
Everything is a scale and faster does not necessarily mean better if the code becomes unreadable.
Right, but then I'd have to write C++. Shallow dismissal aside (I really do not enjoy writing C++), the bigger issue is safety: I am almost certain to write several exploitable bugs in a language like C++ were I to use it to build an internet-facing web app. The likelihood of that happening with Rust, Java, C#, or any other memory-safe language is much lower. Sure, logic errors can result in security issues too, and no language can save you from those, but that's in part the point: when it comes to the possibility of logic errors, we're in "all things being equal" territory. When it comes to memory safety, we very much are not.
So that pretty much leaves me with Rust, if I've decided that the memory footprint or performance of Java or C# isn't sufficient for my needs. (Or something like Go, but I personally do not enjoy writing Go, so I wouldn't choose it.)
> Everything is a scale and faster does not necessarily mean better if the code becomes unreadable.
True, but unreadable-over-time has not been my experience with Rust. You can write some very plain-vanilla, not-"cleverly"-optimized code in Rust, and still have great performance characteristics. If I ever have to drop into 'unsafe' in a Rust code base for something like a web app, most likely I'm doing it wrong.
Even the basics, nobody is calling Rust's [T]::sort_unstable without knowing it is an unstable sort. Even if you've no idea what "stability" means in this context you are cued to go find out. But in C++ that is just called "sort". Hope you don't mind that it's unstable...
[Edited because I can't remember the correct order of words apparently.]
Very well summed. I'll remember this exact quote. Thank you.
You probably want the apples-to-apples comparison but this looks an artificially limiting comparison; people are shilling, ahem, sorry, advocating for their languages in most areas, especially web / API servers. If somebody is making grandiose claims about their pet language then it's very fair to slap them with C++ or Rust or anything else that's actually mega ultra fast.
So there's no "better" comparison here. It's a fair game to compare everything to everything if people use all languages for the same kinds of tasks. And they do.
perhaps you get 1300MB to 20 MB with C# or Java or go, and 13MB with rust . Rust’s design is not the reason for bulk of the reduction is the point
“Less languages features, but a better compiler” was originally the aspirational selling point of Go.
And even though there were some hiccups, at least 10 years ago, I remember that mainly being true for typical web servers. Go programs did tend to use less memory, have less GC pauses (in the context of a normal api web server), and faster startup time.
But I know Java has put a ton of work in to catch up to Go. So I wonder if that’s still true today?
Also you can do that same thing in Rust or C++ too. Very common in C++, speeds up programs quite a bit.
CoreCLR itself doesn't take much memory - GC might decide on a large heap size however. Do give .NET 9 a try with Server GC which has enabled DATAS by default. It prioritizes smaller memory footprint much more heavily and uses a much more advanced tuning algorithm to balance out memory consumption, allocation throughput and % of time spent in GC.
Are you hiring developers that are 100% fully conscious of concurrency and starvation or people that are only concerned with rest and vest and TC?
For either case Go is better.
* For people that are aware of concurrency, they will select Go because they appreciate its out-of-the-box preemptive concurrency model with work stealing.
* For people that are not aware of concurrency, then you should definitely use Go because they are not qualified to safely use anything else.
I remember this being true 10 years ago. Java web servers I maintained had a huge problem with tail latency. Maybe if you were working on a 1 qps service it didn’t matter. But for those of us working on high qps systems, this was a huge problem.
But like I said, I know the Java people have put a ton of work in to try to close the gap with Go. So maybe this isn’t true anymore.
A faster compiler was the aspirational selling point. As legend has it, Go was conceived while waiting for a C++ program to compile.
Before what was called "Go 2" transitioned the project away from Google and into community direction there was some talk of adding no more features, instead focusing on improving the compiler... But since the community transition took place, the community has shown that they'd rather have new features.
The "Go 1" project is no longer with us (at least publicly; perhaps it lives on inside Google?)
I don’t think Java has any edge when it comes to deployment.
The language never set out to solve this problem. It wasn't an intentional design goal. The language design and problem space just happen to overlap more or less perfectly.
Complete serendipity.
This contrasts significantly with effort and adoption of NativeAOT in .NET. Well, besides CLI, scenarios where it shines aren’t those which Go is capable of addressing properly in the first place like GUI applications.
Hazelcast has a good blog [0] on their benchmarks between 8 and some of the more modern runtimes, here is one of their conclusions:
> JDK 8 is an antiquated runtime. The default Parallel collector enters huge Full GC pauses and the G1, although having less frequent Full GCs, is stuck in an old version that uses just one thread to perform it, resulting in even longer pauses. Even on a moderate heap of 12 GB, the pauses were exceeding 20 seconds for Parallel and a full minute for G1. The ConcurrentMarkSweep collector is strictly worse than G1 in all scenarios, and its failure mode are multi-minute Full GC pause
[0] https://hazelcast.com/blog/performance-of-modern-java-on-dat...
Go tends to perform better at "leaner" microservices, but if you are judging this only by comparing it to the state of Java many years ago, ignoring numerous alternative stacks, it's going to be a completely unproductive way to look at the situation. Let's not move the goalposts.
Although making a Rust monolith would be great!
I still think it's a meaningful reason for Rust's popularity, though, given that Swift isn't used much outside of the Apple ecosystem.
If this is a meaningful reason for popularity, why is the Rust the only popular one with it (aside from Swift's popularity within the Apple ecosystem)? Shouldn't we expect other languages, those which have been relegated to the non-mainstream (including Swift outside of the Apple ecosystem), with the same feature to also be popular?
I expect Rust is popular simply because it did well in its marketing. You can't go anywhere in tech circles without seeing an advertisement for it. Which plants the seed for when the next time someone is "I think I'll try a new language"; Rust is first in mind. Swift is a great language. It would be perfectly suitable option for someone to pick up as a new language technically, but since it is effectively never advertised outside of certain Apple developer-focused venues... Case in point: You didn't even think to think of it here, and understandably so.
I have not found this to be generally true. It depends heavily on whether your code is limited by pure high level language code[1] and culture makes comparisons harder if you’re not just switching languages but also abstraction models and a big stack of optimizations. In theory Java beats Python but in practice I’ve seen multiple times where a Java program was replaced by Python seeing whole number multiple improvements in performance and reductions in memory consumption because what was really happening is that a bunch of super complicated, optimization-resistant Java framework code was being replaced with much simpler code which was easier to optimize. Node is closer to that side of Java culturally, I think in both cases because people reacted to the limited language functionality by building tons of abstractions which are still there even after the languages improved so even though it’s possible to do much better a lot of programmers are still pushing around a lot of code with 2000s-era workarounds buried in the middle.
1. I’m thinking of someone I saw spend months trying to beat Python in Go and eking out a 10% edge because the bulk of the work devolved to stdlib C code.
While I fully believe that a Python program with a superior O()-complexity class can beat Java (or, indeed, any language), and that a simpler Python program can hypothetically beat a Java program that is just too complicated, it would also be the case that taking that faster Python program and then porting that into Java would then see order of magnitude+ speed increases. Python is slow. When comparing languages I generally add the caveat "with some non-zero and comparable amount of time dedicated to optimization" to try to build a reasonable comparison, because most programs that have had no effort done on optimization at all will have their performance dominated by something stupid that the programmer didn't even realize they wrote.
The speed increases aren't relevant if the old Java was "too slow" and the new Python is "fast enough". Every program I've ever written could be made faster... but they're all fast enough now.
Pure Python with some non-trivial optimization effort can not beat a Java program with some non-trivial optimization effort, and that's before the Java code starts using multiple CPUs, if the problem is amenable to that.
This is not cheerleading, dumping on Python, or promoting Java, as if anything my personal biases are in fact the other way (tbh I don't particularly like either at this point but I'd much rather be using Python). This is just engineering stuff that good engineers should know: https://jerf.org/iri/post/2024/not_about_python/
I'll quote "Technology Holy Wars are Coordination Problems": [1]
> The enduring phenomenon of holy wars in computing, such as the bitterness around the prolonged Python 2 to Python 3 migration, is not due to mere pettiness or love of conflict, but because they are a coordination problem: the problem is not getting everyone to make a good decision, but making the same decision.
I agree that you can make better software engineering decisions if you avoid thinking like a fan and that a poor choice of language can hinder a project from the start. In light of what is at stake, though, "engineers should never be fans" is an unrealistic call for peace. It reminds me of this dialogue: [2]
> There’s a passage in the Principia Discordia where Malaclypse complains to the Goddess about the evils of human society. “Everyone is hurting each other, the planet is rampant with injustices, whole societies plunder groups of their own people, mothers imprison sons, children perish while brothers war.”
> The Goddess answers: “What is the matter with that, if it’s what you want to do?”
> Malaclypse: “But nobody wants it! Everybody hates it!”
> Goddess: “Oh. Well, then stop.”
[1] https://gwern.net/holy-war
[2] https://slatestarcodex.com/2014/07/30/meditations-on-moloch/
My guess is that if you were to rewrite this same app in straight Python (no Rust at all), it would probably already give you "Tier 3" performance.
But sure, I bet there are a bunch of use cases where nodejs would be faster than Python.
So its supremacy above anything else is kind of relative, and always left of out context.
Humans can't reason about data races because they're contrary to our normal understanding of the world, but an ordinary race condition isn't like that. Earlier I saw there was the nice cheese at the grocery store, but I wasn't sure if they had fresh bread so... I kept walking, when I reached the bread aisle I bought bread and then I went back but nope, somebody else bought the last of the nice cheese. Oh well. Race condition.
It's a function of popularity and widespread use. The only languages that do not feature CVEs are the ones that are not used.
Eve Rust started to feature in CVEs, including memory safety problems in it's standard library. Somehow that fact is omitted from these discussions.
> (...) even experts failing to use those languages correctly (...)
I couldn't help noticing you felt the need to resort to weasel words like "correctly" to add color to an unsubstantiated personal assertion.
What's the best example you can come up with to support your opinion?
> C++ isn’t horrible but it’s harder to use, harder to find good developers (...)
This personal assertion is comical, as recruiters are systematically targeting C++ developers for Rust positions, and Rust is notoriously bad for newbies to onboard onto.
I'd prefer these debates were kept at an objective and substantiated level, but it seems that's too much to ask. It seems it's easier to throw unsubstantiated claims around and wait to see if half the bullshit sticks.
Oh, please. Nobody is saying that Rust is perfect, only that the defect rate in normal usage is considerably lower and tend to be concentrated in areas like “unsafe” blocks rather than spread randomly around the code base.
> I couldn't help noticing you felt the need to resort to weasel words like "correctly" to add color to an unsubstantiated personal assertion. … This personal assertion is comical, as recruiters are systematically targeting C++ developers for Rust positions, and Rust is notoriously bad for newbies to onboard onto.
“Correctly” isn’t a weasel word, especially not in the context of describing how a program functions. I was referring to the common excuse that has cropped up over decades where language proponents try to blame problems on the user rather than acknowledging that certain features are hard to use safely.
I’ve been hearing people say that C/C++ are fine and you just need better programmers since the 90s, which has not been an effective strategy in reducing the number of security vulnerabilities. My comment about easier to learn was written in the context of reaching the level needed to reliably write safe code, not just producing a compilable program which doesn’t immediately crash since even large, elite teams with enormous resources struggle with memory safety bugs in large C/C++ code bases.
For example, Android reports halving their code rollback rate and a significant reduction in the number of vulnerabilities by switching to memory-safe languages. Clearly relying on programmer vigilance and testing was not as effective as picking tools which made certain classes of error much harder.
https://security.googleblog.com/2024/09/eliminating-memory-s...
This is the kind of fallacies that dominate Rust fanboy's discourse.
You start off by mindlessly commenting on "constant stream of CVEs", but when you're faced with the reality that Rust also piles up CVEs then you start to try to move goalposts around. Odd how you switched from CVE talk to vague allusions of "perfection", as if now CVEs don't matter.
That's the problem with your type of fanaticism: you stop makint technical claims and instead resort to sweeping baseless accusations,as if that was a positive trait on a language and it's community.
> “Correctly” isn’t a weasel word, especially not in the context of describing how a program functions.
It is. There is no way around it.
> My comment about easier to learn was written in the context of reaching the level needed to reliably write safe code (...)
Again with the goalpost-moving/weasel word combo.
Rust is notoriously unfriendly to beginners and imposes an unparalleled learning curve. Around a quarter of new developers outright give up and quit over how unusable it is to them. This is acknowledged by the Rust community itself as demonstrated by the last annual Rust survey. There is no way around it. I don't know why anyone would try to waste time handwaving over this.
> For example, Android reports halving their code rollback rate and a significant reduction in the number of vulnerabilities by (...)
Here's the problem with this sort of specious reasoning. You are cherry-picking an example of how a project invested heavily in memory safety and therefore ended up lowering vulnerabilities. You ignore how much work was invested into processes and prioritizing specific types of problems. You instead decide to ignore everything and anything, and opt to go the simplistic path of pretending that the only step required to achieve these gains is onboarding a magical tool, as if nothing else was a factor.
Do you understand how this blend of cargo cult mentality is silly and unproductive?
I get it that you feel the need to promote a tool you like. That's fine. But if you had a case you wouldn't feel compelled to frame all your arguments on artificial scenarios you try to pin on all other tools, would you?
Take a chill pill, you have completely derailed you argument with personal attacks in place of substance. You would have to be willfully ignorant to think Rust isn't safer than C++, and I say that as someone who refuses to use Rust.
The extensive compile-time metaprogramming facilities in C++ give it unique performance advantages relative to other performance languages, and is the reason it tends to be faster in practice.
For example, compare the speed and implementation of std::sort and qsort (it's almost an order of magnitude difference in run time for big N!)
Also, sorting is something where algorithmic improvement makes a sizeable difference so you need to be sure you're either measuring apples vs apples or that you've decided up front what your criteria are (e.g. lazy people will use the stdlib so only test that; or nobody sorts non-integer types so I only test those)
For some inputs if you're willing to use a specialist sort the best option today is C. If you care enough to spend resources on specialising the sort for your purpose that's a real option. Or alternatively if you can't be bothered to do more than reach for the standard library of course Rust has significantly faster sort (stable and unstable) than any of the three C++ stdlibs. Or maybe you want a specialized vector sort that Intel came up with and they wrote it for C++. Hope portability wasn't an issue 'cos unsurprisingly Intel only care if it works on Intel CPUs.
Sure, if you write all the code. If you're writing a library or more generic functions, you don't have that power.
And, even then, while you can do this it's going to be much more code and more prone to bugs. C++ is complex, but that complexity can often bring simplicity. I don't need to specialize for int, double, float, etc because the compiler can do it for me. And I know the implementation will be correct. If I specialize by hand, I can make mistakes.
In addition, this isn't something where C "shines". You can do the exact same thing in C++, if you want. Many templates have hand-rolled specializations for some types.
> apples vs apples
It is, they're both qsort. When every single comparison requires multiple dereferences + a function call it adds up.
> For some inputs if you're willing to use a specialist sort the best option today is C
I don't understand how. Even if this is the case, which I doubt, you could just include the C headers in a C++ application. So, C++ is equally as good of a choice + you get whatever else you want/need.
> Rust has significantly faster sort (stable and unstable) than any of the three C++ stdlibs
Maybe, but there's a new std::sort implementation in LLVM 17. Regardless, the Rust implementations are very fast for the same reason the C++ implementations are fast - encoding information in types at compile-time and aggressively inlining the comparison function. Rust has a very similar generic methodology to C++.
Yes, but not if you pass in void *. For libraries this matters. If you're both writing the producer and consumer then sure, you can do it manually.
> code bloat caused by monomorphization
This is true and a real problem, but I would argue in most scenarios extra codegen will be more performant than dynamic allocation + redirection. Because that's the alternative, like how swift or C# or Java do it.
- https://pyo3.rs/
- https://github.com/neon-bindings/neon
- https://github.com/mre/rust-language-bindingsIt's Ocaml
Beautiful
... And yet the fact that most of us know we're reinventing Lisp, and still doing it anyway, says something. I guess it says that we're just trying to get our jobs done.
In modern times the network is the computer.
Again, no computers involved in my food shopping (well, not the part I described anyway) but there's a race condition, because that's how the universe works. Rust can't magically change how the universe works. Whereas Data Races are something very strange, and Rust can just outlaw those.
Oh! No, that's not a thing. What's happened there is you saw that the libc function was named qsort and you went "I am smart, I know that means Tony Hoare's Quicksort algorithm from the 1960s" but that's not what it means, it is named that way but it's only defined as an unstable sort, the libc does not promise any particular algorithm.
Over in C++ land they also don't specify the sort algorithm used but in C++ 11 they mandated that the provided function must have worst case O(n log n) performance. This is awkward for Quicksort because although Tony's algorithm is very fast on average, its worst case is O(n squared) which is very slow
Thus, conforming C++ libraries are definitely not a Quicksort. Now, conformance to the C++ ISO standard is basically a minor curiosity and nobody cares, so Clang for example just didn't bother and shipped a Quicksort anyway until relatively recently, but already we can see that we're by no means guaranteed these are "both qsort" nor that they're both anything in particular.
The thing you should do is an introspective sort or "Introsort". There are a lot of these, for some time the best general purpose algorithm was PDQsort, the Pattern Defeating Quicksort by Orson. But even though that word "Quicksort" is in there this is not just "Well it's qsort so it's the same anyway" any more than a Cayenne is the same as a road legal 911 is the same as Porsche's 963 track car.
Decades ago a friend wrote a software "Time Machine" (this was long before Apple's backup software) which just buffers all the audio inputs so you could do the same thing with the computer in their studio as on their Sony Mini Disc recorder - if you hit "record" it records the sound you just heard, even though that sound happened before you hit the button, because it's a mains powered piece of kit it can just buffer a few seconds of PCM samples in a loop and so it does.
Sony invented that tech because it's necessary to how the device actually works anyway (it has lossy audio compression inside it, which needs a buffer) and it's useful, so, why not. The "Time Machine" software is of course not actually a time machine, but never did any users or prospective users say "Hey, that's misleading, it's not actually a time machine, those would require a change to the laws of physics". It's just audio software, duh.
The fact fearless concurrency doesn't protect you from the dangers of race conditions is no different from how it doesn't protect you from say mob violence. Those both sound bad, but I wasn't expecting the programming language to magically fix either of them.
Mainstream Lisp dialects have had objects other than lists for many, many decades. The LISP-1 programmer's manual from 1960, referencing the original language which started it all, describes zero-based arrays already.
In some Lisp-like languages, the syntax processing itself is based on arrays, like Janet. The parenthesized notation turns into a nested array, not a nested linked list.
In Lisps where the syntax is based on lists, that doesn't imply that your program has to work with list at run-time. The code-transformations (macros) which happen at compile time will be working with linked lists.
Budding computer scientists and engineers like to write toy Lisp dialects (sometimes in one weekend). Often, those languages only work with linked lists, and are interpreted, meaning that the linked lists representing the code structure are traversed to execute the program, and repeatedly traversed in the case of loops.
(If you're making remarks about an important historic language family based on familiarity with someone's toy Lisp project on github, or even some dialect with an immature implementation, that is a gross intellectual mistake. You wouldn't do that, would you?)
Linked lists may "kind of suck" on cached hardware with prefetch, but that doesn't prevent them from being widely used in kernels, system libraries, utilities, language run-times (internally, even in the run-times of languages not known for exposing linked lists to the programmer), ... C programmers use linked lists like they are going out of style.
No need to be pedantic. Obviously I’m not talking about a random toy lisp someone hacked together.
Linked lisps have their uses, obviously, but being the core data abstraction for your entire language kinda sucks nowadays.
I’m talking about lisp the language, not the philosophical concept. When people just say “lisp” referring to a specific language you can safely guess either scheme or Common Lisp.
You say you're not talking about a random toy Lisp someone threw together. Yet those kind of projects are the ones that have lists as the core or perhaps the only data abstraction for the entire language. If we search for the Lisps that make your remarks correct, that's mainly what we find.
I think this is a rare exception in production Lisps. One notable one is something called Pico Lisp. People take this seriously and use it, so we can't call it a toy. Yet it does almost everything with lists.
When people say Lisp nowadays no you cannot guess that it's Scheme or Common Lisp. It could be Clojure, or Fennel or others.
Scheme and Common Lisp are very different languages.
You may want to check the Common Lisp standard (a dialect, where its development goes back to 1982).
https://www.lispworks.com/documentation/HyperSpec/Front/Cont...
From the table of contents you can see that the language spec prominently describes: CLOS objects, structures (records), condition objects (-> errors), symbols, packages (namespaces for symbols), multi-dimensional arrays, strings, hash tables, files, streams, ...
None of these standard data structures are linked list based.
For example when I write a Lisp form do define a structure, a record-like data structure:
(defstruct packet
sender
receiver
header
payload)
then the SOURCE is an s-expression, a linked list.DEFSTRUCT is a macro, which defines a record data structure and a bunch of functions for it (accessors, getters, creater, type predicate, ...).
The Lisp compiler will expand the macro form into a much larger s-expression -> again a nested list.
The compiler will then process lists and a lot of other data structures (see above) and create MACHINE CODE for code defined by above record definition.
Structures themselves are by default VECTOR-like objects, with static access into its components. A getter will access the fixed offset into a record and the code for that will usually be inlined in the using code.
So we have two aspects:
* processing with linked lists on current CPUs is several orders of magnitude faster, than on the machines where Lisp was originally defined. It does not matter for most use cases on modern machines. For example any Apple Silicon is great for running Lisp.
* Lisp offers many other data structures, which are widely used in Lisp applications.
For example if I would need a bit vector, I would not use a linked list of numbers, but a real bitvector:
CL-USER 1 > (describe #*0000010010000011000000000)
#*0000010010000011000000000 is a (SIMPLE-ARRAY (UNSIGNED-BYTE 1) (25))
CL-USER 2 > (sbit #*0000010010000011000000000 5)
; get the fifth bit, using zero-based indexing
1
Here the operations are written as lists, but they operate on real vectors of bits.The result then is that optimizing Common Lisp compilers can generate code, which is fast enough for many applications.
So is in Common Lisp the linked list the "core data abstraction for your entire language"?
That's misleading. The "entire language" has many more data structures, which are not built on top of linked lists. For example arrays (strings, vectors, bitvectors, multidimensional arrays) are a part of the language, are widely used and are not made of linked lists.