> And you would only need Rust — while it excels in the lowest half of the stack, it’s pretty ok everywhere else too.
A younger me, in search of a new language to learn, would have been turned off by something that is only "pretty ok"
But 20 years later, something that is "pretty ok" is exactly what I'm looking for. My experience has been that finding the perfect language (or perfect anything) is futile, and all I want is something that let's me solve my problems, don't get in my way, and don't really surprise me.
For a long while, python was my go-to "pretty ok" language, but eventually it stopped being "pretty ok" for me. Rust has now taken that spot.
It's not popular among the crowd here, but my favourite "pretty ok" language is C#. It's not really the best at anything, but it's pretty good for most use cases.
That being said, because there's no one perfect language, it's handy to have a few in your arsenal. As much as I like C#, I'd still reach for Python for any data or ML type use cases.
I do love C# as a language, and I've been wanting to reach for it more for tooling and backend things at work, now that its cross platform is good. (I've mostly used it for Windows GUI apps previously). I'm more confident that any random developer can work on a C# program than a Rust one.
But it has felt like the FOSS library ecosystem for .NET is not quite as good as it is for Rust. C# is far older than Rust, and NuGet apparently has 3.5x as many packages as crates.io has crates. Despite that, my experience has been that I'm more likely to find a good Rust library for any random thing I want to do. Rustdoc documentation isn't amazing but it's consistent, and venturing into other ecosystems usually reminds me that things can be worse.
The problem is the .NET ecosystem. Most of the truly useful frameworks and libraries are either commercial or freemium, and Microsoft is happy to play along and support this cottage industry instead of releasing free in-house versions and crushing the overpriced third party ecosystem.
Another issue is that Microsoft 's first party libraries are now increasingly designed to funnel you into Azure services. The language is great, but the ecosystem is rotten.
I think this very much depends on what you're doing. If it's the typical web app, pretty much everything you'll want is open-source, and most of the time there's some first-party MS thing for what you need (auth? caching? ORM?). We use it at work and don't use any commercial libraries and don't have problems, although it's your typical microservice/distributed pattern. I'm guessing for developing desktop software there's a lot more gremlins.
It actually helps a lot that everything's so open source, because often companies will spit out a SDK for .NET to check a box and not have good docs around it because most of their customers aren't using .NET except for a few enterprise whales.
> Microsoft 's first party libraries are now increasingly designed to funnel you into Azure services.
Outside of the solution for scaling Blazor being "Azure SignalIR service" I'm having trouble with this one too. Their docs often call out Azure, but there's not a ton of bias in support (again, totally might just be that I don't get in the weeds enough).
It says you can use AAD or IdentityServer as OIDC providers, but the doc goes on to explain how to do it yourself with Entity Framework storing user data in a DB and the open source Microsoft.AspNetCore.Identity hooking it into the app.
It would be odd if they only gave like Cognito as an example (especially considering AAD is free). And I didn’t know people really used IdentityServer as much in new apps, there’s other ways to do that.
In most use cases with a separate client (e.g. in React), it's easier to use a separate OAuth server. Otherwise you will end up having to change the default cookie settings.
Async is a wart, period. It’s an ugly hack around poorly scaling threading abstractions. It basically implements light weight threading but in a way that forces the developer to constantly think about it and clutters up the language with ugly complex async crap.
Go is the only language that gets concurrency right.
Rust async is ugly but it’s ugly because async is ugly and as a systems language there are limits to how much of that ugliness it can hide.
If we could fix threads to scale better at the OS or core library level we’d save insane amounts of developer time.
Go's approach to concurrency exposes Go's lack of memory safety.
When you couple this with a (mostly) bunch of noob devs who have been told Go is super safe because of it's type safety, you have a recipe for disaster.
The combination of language features and surrounding social effects means, once you pursue concurrency, Go abruptly becomes one of the most dangerous languages to use for any industry where subtle errors of value are a problem ... like anything to do with money.
p.s. No slight intended to Elixir, it avoids all these risks.
async (the keyword) is doing fantastic because it’s better than all of the available alternatives. Those alternatives:
- ad hoc callbacks, which had a great Result-ish type signature but really do warrant the “hell” in “callback hell”
- Promise APIs, which are semantically equivalent to async the keyword, unless you care about call stacks, and have a lot of the same hellish problems as the ad hoc callbacks they were meant to address (less nesting! same everything else!)
- Um fibers? Good luck making sense out of whatever that’s doing. It’s a good idea, but it’s also all opaque magic when you try using it.
- Actual threads and child processes… there are valid use cases, and they’re worth pursuing if you have a valid use case, but the facilities for development with them are basically “here’s a bunch of low level concepts that closely mirror their system level counterparts, hope you know/figure out what you’re doing!”
By async I was referring to node's asynchronous event-driven runtime abstraction which the GP refers to as an ugly hack. I'm not sure if this abstraction is better than all of the available alternatives if you compare it to high-level features multi-threaded runtimes offer like thread-safe collections, atomic updates, concurrent hash maps, immutability, structured concurrency, etc... as in Java/Clojure. Most Java programmers don't work with the low-level thread primitives.
The author of esbuild gave up trying to code esbuild with nodejs/worker threads and switched to Go for a less limited/restricted concurrency environment.
> By async I was referring to node's asynchronous event-driven runtime abstraction which the GP refers to as an ugly hack.
Okay with that clarification I can agree it’s a good model, given its constraints. The event loop with async IO in the abstract is a good way to model a single process/thread workload for many use cases that fit it.
> I'm not sure if this abstraction is better than all of the available alternatives if you compare it to high-level features multi-threaded runtimes offer like thread-safe collections, atomic updates, concurrent hash maps, immutability, structured concurrency, etc... as in Java/Clojure.
Clojure’s solution to concurrency is a breath of fresh air, regardless of your execution environment, because its state transactional semantics are great whether your concurrency is in one process/thread or spread across many. I can’t speak to typical Java solutions, but my general sense is they’re higher level and more powerful than Node’s for actually crossing process/thread boundaries, but subject to most of the shared state problems Java has even in a single process/thread.
> The author of esbuild gave up trying to code esbuild with nodejs/worker threads and switched to Go for a less limited/restricted concurrency environment.
After a lot of exploration of Node worker threads, I’d probably similarly look elsewhere if I had a workload suited to it. You can do a lot with Node worker threads with a lot of special tuning for a use case, and I even have some proof of concept code demonstrating that it can be much better than common usage. But I put it on hold because the complexity of making it perform well is very high compared to optimizing the single thread default.
First there was APM https://learn.microsoft.com/en-us/dotnet/standard/asynchrono... then there came TPL (using Task but without async await but ContinueWith) and finally async await with some later refinements like ValueTask. Generally you just use async await nowadays with maybe the complication of when to use Task or ValueTask
I haven't written C# in a while, but I don't remember seeing that one. I skimmed the C# grammar and didn't see it there either, but I only glanced and might have missed an entire section.
I find many new features useful as they usually either reduce boilerplate, increase expressiveness or enable high performance code. Some are also related to AOT compilation.
Do you have any real world experience? I'm working on a Python codebase which is almost 2 million lines of code with almost 100 developers and I haven't seen development speed slow down over the years.
Of course you need to have a structure within this codebase or you'll bump into the typical problems of large codebases. Some languages force you to work this way, we do it by enforcing certain rules in the lower level framework code of the project.
Also you need to keep pace, we are on the latest Python version and code is heavily typed. This project was started in the 2.7 days.
I’ve got a long experience with Ruby which I believe is quite similar, but after about 5 years, an untyped language just falls apart. Updating anything is perilous because libraries will change stuff all the time that violates the types and it won’t be mentioned in the changelogs, your 20,000 unit tests will also constantly miss stuff because you didn’t reimplement a type checker inside rspec. It’s a disaster. Typescript has saved our front end but I don’t see any hope for Rails.
We investigated but the general vibe is that it just isn’t there yet, and it isn’t even moving in the direction to get there. It really needs support from the Rails project, and the rails leadership had indicated it isn’t going to happen.
If you have a pure ruby project and use it from day one, it probably works. But if you are starting a brand new project, just save yourself the pain and use TS.
Are you allowed to say what project/product it is? I'm curious. Do you use type annotations? I've found refactoring sooo much easier in statically typed languages like Go than in Python, even for medium sized projects.
Most code has type annotations but we don't enforce it. A lot of code was written before typing became well supported and we never went back to add it to every old piece of code, we add them when people touch related code sections.
Typing definitely makes it a lot easier to navigate the codebase. But in the end maintaining any large codebase is a question of discipline which starts with simple things like code layout, class and function names, isolation of functionality.
I wanted to say that the per-developer output has not slowed down significantly.
But I admit that there are challenges. For example startup time is ~30 seconds because that's the time it takes to import everything. This automatically means that running even a single test will take at least 30 seconds, to not bang your head against the wall we mostly write new code in a REPL so everything is already loaded.
Worked on Instagram codebase for almost 2.5 years, never wrote a single line of Python, most of the services are hack or c++. Where does python come into picture? Apps are in java/kotlin/ObjectiveC/Swift.
assembly/c/c++/perl/ruby/elixir/elm/js are "pretty ok" to me no matter how long I had written with. python is absolutely not ok to me .. always think "really?" I want to write it in another way, but it can't be.
Not OP but to me the ecosystem is too disjoint. To ship "production python", you need to figure out which of the "current" tools are to cobble together to build, test, and share your code.
`cargo` combines poetry, ruff, pipx, sphinx, pytest, etc. You might expect this to be worse. In some cases, it is (e.g. pytest). The super power is in being able to get a project up and going immediately without wasting time on figuring out which tools to use, scaffolding them together, setting up a host for your documentation, etc.
Then there comes the problem of distributing it where the target system has to have just the right version of Python.
I have been working on a mediumish project (without tests or type hints) where I needed to add functionality including a bit rewiring.
My main problems were:
- I didn't know what data (types) were passed around when looking at parts of the code, so I had to look were the data structures were created, adapted and passed along to actually know more (or just debug it)
- After changing parts of the code, I was 100% certain that I had to run it a dozen times to fix new bugs or things I had forgotten
How it works with Rust:
- I see the types in the function signature
- I know the properties of 'classes' without scanning the whole code of the class
- when it compiles, it works 90+% of the time, because the compiler tells me where I forgot something before, if the IDE hasn't already told me
> I didn't know what data (types) were passed around when looking at parts of the code
For me this is the big one. Types isn't just about writing error-free code, it also aids in making the code self-documenting.
When I read a function signature in an unknown codebase in C, C++, C#, Delphi or similar, I can almost always tell what it does and what I can do (or not). Even types I haven't seen before helps, because the name of the types can be informative.
Thanks to this I've helped countless people fix their code using libraries I've never seen before in mere minutes, simply by glossing over some of the code.
When I work in untyped code, like older Python or similar, it's much harder. Invariably I end up sprinkling dir() or similar just to figure out what the heck is going on. Ok, this so function created... something... what can do with it? Who knows?
Python also has the regrettable property that many type errors aren’t even caught at runtime. Instead you often get incorrect results with no exception.
str vs bytes confusion is a major example. If you use one where you needed the other, the results can be anywhere from inscrutable failures to hilarious. My personal favorite is if you stick bytes into a templating engine. For some reason, iterating bytes in Python yields integers (not bytes-of-length-1 or a separate byte type), and those integers can get stringized and flattened, and your webpage turns into a bunch of digits and you end up with the Matrix.
But Rust puts a unique amount of information into the types. This can get annoying sometimes, but it means typechecking better predicts correctness.
For example, a c header type might have an out parameter and an error code returned. The interaction between those two isn't specified in the type system.
I personally require discriminated unions (i.e. enums where each variant can hold its own type of data) before I consider a type system adequate.
But that's way beyond someone coming from JS or Python, just being excited that there are types on the parameters and the compiler tells you when basic things are wrong.
Manually managing memory has turned out to be error prone (for me) and can result in quite nasty to debug bugs (because the type system is lacking), only to mention the most important thing.
I think there is enough out there on C vs Rust, esp. because of the Linux kernel
That said, it was asked about Python (within the context of Rust), and I have limited experience with Pascal and Java, so I will leave it at that.
> I didn't know what data (types) were passed around when looking at parts of the code, so I had to look were the data structures were created, adapted and passed along to actually know more (or just debug it)
It's even better when all those data structures are just dicts, because it's too cumbersome to make serializable classes, so good luck figuring out what fields are in which.
You should try out deno. TypeScript with zero set up. You can write a .ts file anywhere on your computer and run it with, e.g., `deno run hello-world.ts`. It's my new goto for quick scripts
Yea! I actually wanted to teach using Deno back in 2019! It was a stretch topic for the last couple weeks of class in my syllabus but we were too far behind.
Excited to see where it continues to go and we are writing more scripts in TS at work (without Deno, tho).
One too many TypeError at runtime. I became increasingly annoyed that nothing was catching my silly typos or other mistakes. These days there are some really good python static checkers available, but I'm not sure I really knew about them half a decade ago when I started to move away from Python
I left Python when I found Scala, and while a good type system was a big factor, the groundhog day of "Python dependency management is awful with 4 different tools none of which quite work right, but this new tool has fixed it, for real this time" every few years was the part that really made me despair.
I don't know if I just use Python "wrong" or something. When I worked as a TA, I've written a lot of untyped Python as Blender/Houdini plugins, and I think it worked pretty well for this purpose.
But later I did some backend stuff, I tried to use typed Python and wrote safer code. Then the whole toolchain becomes so cluncky. MyPy, Pylinter, Flake8, etc. They're so slow, and their VSCode extensions are so weird to configure. And I never got the best practice to work with other untyped libraries. It's not that I don't know how to use typed language. Actually I do backend stuff faster in Rust than Python.
In my opinion it's not pretty ok. It's actually superior to many other languages.
I think for web stuff though the borrowing and move semantics is a bit of an overkill. Usually you don't need to thread or handle state in these areas (frameworks and databases handle it for you) so a GC works better here. But everything outside of that is superior to something like ruby, python or go when it comes to safety.
Matklad has hinted at this before, but it's funny how Rust is displacing ML dialects in domains where manual memory management is not at all necessary, simply because no ML has its tooling and deployment story straight.
I’m going to sound like a broken record, but F# is in the ML family and has wonderful tooling and the deployment story is getting dramatically better recently (it’s a little behind C# in its true native AoT compilation support, but it’s coming)
Honestly, the built in tooling has been good enough for me, in the .NET Core era (especially within the last few years).
The MSFT CLI/IDE team has finally had the chance to fix some previous really annoying issues and limitations in the last few years, so the experience has gotten quite a bit smoother.
I know people that looove Paket, especially in the F# community, but I’ve been happy enough with things now to not feel like I need to invest the effort in adopting it.
I think if Rust could integrate a GC pointer elegantly into the ecosystem and put some of the knobs for running the GC the responsibility of user-space libs (ala pluggable GC), add in more optional type inference in places, and make it easy to refactor code to remove the GC, then it could become quite interesting for higher level glue code. As is, it’s not quite as good as TypeScript I think
There is a reference counting GC in rust. It's just not default and you have to be explicit about invoking it. It's similar to shared pointers in C++. See Arc.
For something like python the heap allocation or stack allocation is handled behind the scenes so the entire concept is abstracted away from you.
Arc still requires you to reason about ownership as does this GC.
I think single threaded async w/ easy no copy message passing is more akin to what most people want when thinking about concurrency (eg Go channels) and actually happens to perform exceedingly well when it’s done up and down the stack (eg io_uring). In such a model you don’t even need to worry about ownership so much because you can’t Send anything except for things that need to be so there’s not a lot of value in many of the protections Rust tries to put in place.
It's hard to overestimate how extremely Rust is optimized towards making refactors as easy as possible. This often means a greater cost for initially writing some code, but once that code exists, it's way easier to search around in that code and also way easier to refactor it. Often times, being written is only the start of some piece of code's journey through the years and decades until it is finally deleted.
Editors like VS code just become super powered when using Rust too. Switching back to Ruby feels like crossing a road with my eyes closed in comparison.
+1. Started a new project targeting WASM and decided to learn Rust coming from a Python (hobbyist) background. The VS code experience is like I stepped into a time machine and landed in the future. Oh my god, it is so much better. I don't think I'm going back to Python unless I have to
Tried F#, beautiful language. Read some tutorials and was amazed. Hated getting setup and VS Code felt clunky in comparison.
Tried TypeScript but that felt.. like a dirty afterthought. Maybe just because I generally dislike JavaScript? I am still using both for this project (since WASM+React) but can't say I love it
Rust was just like seeing God.
The Rust book is awesome. Well written, fun, comprehensive. Reminded me of my experience with the Django Book way back when...
Namespaced module imports? Yes, please.
Then cargo "Just Works"! Especially compared to what I had in Python (particularly if you compare to my first days using the language!) or the messiness of Javascript...
When I learned I could use cargo to open docs for a crate or build my own documentation in section I nearly fell off my chair laughing with joy
Besides THE book (Klabnik/Nichols), the ones from O'Reilly, Pragmatic and Nostarch (for Rustaceans, Gjengset, in particular as a 2nd book) are excellent to superb. I might have missed a few, these were the ones from 3 public library systems.
Just look for 2nd eds on THE book and Oreilly's Blandy et al. Course i saw one from Wrox that was a pass.
I haven't and maybe I should, thanks for the tip. I love Django (and have proudly contributed a handful lines of code!) but unless I'm doing simple CRUD type admin-like apps, I probably won't go there again these days.
Why? Give some examples. As someone with little Rust experience, it appears similar to C++, which I have a lot of experience with. C++ is a chore to refactor.
For a C++ -> Rust comparison, I think the best examples would be changing return types. Imagine in C++ if you wanted to change a `Foo*` to a `unique_ptr<Foo>`.
All the rules about when you can safely use/share this thing and who needs to deallocate this thing becomes a pain to figure out throughout the code base.
With rust, because ownership is baked into the type system, those classes of problems just don't exist. Change it from a `Foo&` to a `Box<Foo>`, no problem. The compiler will blow up and tell you exactly where (and how) to fix those things.
This applies to more than just memory allocations. Imagine calling a method from 2 threads that manipulates shared memory. Rust won't allow you to do that without protecting that memory with something like a `Mutex`.
It's the type system. When people say a language is easier to refactor it's usually because the types are designed more like ML, with a little more formal rigor, so the compiler errors can actually help you along as you make changes. Rust's model of mutability adds context to what you're doing - which is also what makes it hard at first(since it adds ceremony and constricts your initial design). In a large system where data can move through a lengthy code path with a lot of indirection, more nuance in types becomes a major advantage to your confidence in making changes.
C++ can never quite get there despite all the innovations made because of its descent from C style types, which are much more simplistic.
It comes down to the type system. In C++ maximal use of the type system is optional. If your C++ code base is C with classes, refactoring will be a mess. If your code base uses modern C++ with maximum type safety, refactoring is straightforward in the same way it is for Rust. The experience in C++ depends on the code base.
Use of maximal type safety in modern C++ is astonishingly robust and expressive. If it compiles it generally works. The majority of developers are a bit lazy about type safety in my experience, which works against you. It depends on the org.
In one sense it's impressive Rust has mediocre refactoring support despite orders of magnitude less investment in tooling. But that isn't worth all that much. It's no worse than any other typed language I've tried.
Rust in theory has a lot of potential here because it's designed to minimize action at a distance.
In addition to what others have said, C and C++ rely on a textual macro preprocessor and text-based #include, which can lead to a lot of annoying code-breakage when refactoring. Automated tools can't parse C/C++ unless they're effectively built on top of a full compiler; even building a complete C preprocessor is an extremely non-trivial task.
(Many newer C++ code-bases discourage relying on the preprocessor, but that only goes so far.)
It's not that it isn't tedious, but the workflow is a lot more reliable. Refactor, `cargo check`, fix the errors, repeat. When there are no more errors, it is very likely the code will still work.
I would say C++ is mostly the same experience, with the exception of anything involving memory safety & lifetimes. It can't guarantee you didn't accidentally introduce a use-after-free/move or anything else of that nature.
That's a huge benefit though - I'm more than happy to slowly and methodically refactor a system if at the end of it I can be nearly 100% certain that I haven't inadvertently broken something in the process. The time spent upfront is multiplied time saved in fixing bugs in production.
In a tortoise and hare race, sprinting off quickly doesn't count if you can't reliably reach the finish line.
It's hard to give concrete examples because the problem is inherent to large, "real" codebases - toy examples can't really do it justice.
That said, there are some general themes:
- The lack of implicit magic in the language. Destructors (specifically the fact that they are called automatically) is implicit. That's it.
- The context-free nature of the language. This exists at several levels:
1. At a function level. There is no cross-function inference and all the information about a function is in its signature, so you don't need to look anywhere else to understand what it does.
2. At a module level. Everything you use from elsewhere is explicitly imported in the module. There's nothing that can change what a piece of code does across a crate (eg. the way you can just redefine `true` to something else in C/C++.).
3. At a file level. The module hierarchy is explicit, it is not inferred based on the layout of files on disk.
Together, these things means you can look at a piece of Rust code and understand it in isolation, without even needing to be familiar with the codebase as a whole. You can move a piece of code from one location to another and (aside from fixing imports) can expect it to continue to function exactly as it did before.
- The amount of information that is available to the compiler. The rich type system and lifetimes means that it's harder to mess up a refactor without the compiler detecting it.
- The fact that it can be used as a safe language. This is not unique to Rust, but Rust is unique in being suited to the lowest-level tasks whilst still being safe. Safety limits the damage a bad refactoring can do (at worst, you break the code being refactored, but at least you won't give the whole program UB or cause a segfault).
- The fact that macros operate on the AST rather than text. This eliminates a class of bugs with C-like macros, where implementation details of the macro have a tendency to leak through the macro definition. Refactorings can often trigger these kinds of bugs because you cannot easily think at that level while doing a large refactor.
- The tooling. Whether it's quickly knowing the type of a variable, swapping out a dependency, or applying broad fixes to a codebase, the tooling works extremely well together.
- Consistency of the language. Everything is an expression. "void" is just a normal type, etc. This level of consistency means that large refactorings require fewer actual changes than they might do in other languages. For example, when I used a bit of C# again after using Rust, it was very noticeable the amount of "pointless" transformations I had to do when refactoring because making an expression conditional may require introducing a new variable because `if` statements cannot be used in an expression. (Yeah there's the ternary operator, but it can't be used in every case, and that's just another example of inconsistency).
> The lack of implicit magic in the language. Destructors (specifically the fact that they are called automatically) is implicit. That's it.
Well, destructors and unwinding. Many, many bugs in unsafe code have been found through unwinding code paths being not fully accounted for. At least in safe code that doesn't use catch_unwind, the worst that can happen is an unexpected state of an object shared across threads.
What's even more fun is the combination of the two. For instance, the catch_unwind function has a big flaw, where it returns an arbitrary Box<dyn Any> to represent the panic payload, and if you aren't very careful about dropping that object, its destructor can panic and start unwinding again. (There are plans to fix this by putting the payload in a shim type that makes the destructor abort on unwind, without actually changing its type_id. I find it quite an ugly hack, but there's not really any other way around it.)
(There are also other ideas to remove language support for unwinding destructors entirely. I'm against that for a few different reasons, but I'm not the one running the show.)
The author talks about http servers. So I will throw a question here.
I am a gopher that wanted to try rust, and I looked into http servers in rust, and it looks like there is like 10 different http servers, and each of them carries with them something that is called “async runtime”, and there are different async runtimes, and you cannot (or can?) use them together, and I realized async rust is not yet finished (or it is?), and I got kind of lost.
What http servers people used? Do the asynch runtimes matter? If I want to “just” build a http server, do I need to study different async runtimes?
Pretty much all the http servers of worth, short of tide, just use Tokio at this point.
For the most part, you really only need to care about Tokio. async-rs is used by some libraries, but it's relatively small in comparison - and many now go to lengths to just support both. There are other runtimes you could tinker with, but really: just use Tokio.
The "easiest" web server/framework to get started with in 2023 is, in my opinion, Axum. It's from the same people who work on Tokio and it does make it somewhat straightforward to dive into as a result.
I have a few actix-web servers in production as well, and it's a fine framework - hard to say where the mindshare falls on it these days.
Rocket is a very nice web framework but effectively has a bus factor of 1 at this point and it's started to show. People coming from e.g Rails may appreciate it though.
Don't use warp (IMO) because http routing is not rocket science and doesn't need the amount of magic it tries to bolt on.
I mean, there's only one http server people actually use, which is hyper. Just like in go where everyone uses net/http, even though fasthttp and so on exist and everyone just ignores them.
Async is really painful and a "not finished yet" thing.
However, in practice, if you use Tokio you should be fine. And usually this decision will be made for you with the framework you pick.
My #1 tip is to decide on exactly what your use case is, then use cargo and look at the recent number of downloads and go with the most popular framework. Doing this for several tags (http, server, framework) is required for categories that are noisy or not tagged well.
Example: Are you writing a web service API, a web server that has server-side templates, a client-side SPA app, or a server-less function?
Usually you will see they are all aligning on the same stack tokio, serde, etc but this is definitely something to be aware of. It is critical that an application uses the same version of dependencies for cross cutting concerns.
It's possible to avoid async contamination in Rust. Game-type programs generally don't use it, because they're compute-bound. The use case for async is a huge number of mostly inactive network connections.
What makes it feel low level to you? Axum has been my favorite way to do HTTP servers for some time. Especially where it is now, everything feels expressive, and I can do whatever I need to. If you're just doing basic stuff it makes it easy and if you're going beyond that it makes it possible.
There are a few parts of the learning curve that come up but the official examples showcase it all beautifully and the community is very responsive if you ask for help.
They're likely talking about Rocket which is higher level than Axum or Actix Web, but it's recommended not to use it as it's not as actively maintained, as well as some other issues. For a Rails-like experience, Nails [0] is one I've seen, although I don't know the status of it as no one seems to talk about it as much as Axum and Actix Web.
- sccache helps with some parts of builds but rustc needs to be caching a whole lot more for rapid TDD iterations. It shouldn't take seconds or many minutes to build things. (Invoke "go does it in milliseconds" card.)
- cargo should migrate away from git and sparse protocols. Both are slow because one abuses git and the other fetches each crate metadata as a file using individual requests. It would be faster and simpler to send either a total compressed sqlite database OR a compressed SQL transaction to migrate between versions. N files on disk representing each crate is asking for fs performance problems in the real world on fses people actual use.
I've found the sparse protocol many times faster than git since Rust 1.68
To use the sparse protocol with crates.io, set the environment variable CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse, or edit your .cargo/config.toml file to add:
On the topic of vertical scalability, I have found that many runtimes require something else to scale. Ruby requires passenger/unicorn/puma/etc. It requires pgbouncer if you're using postgres. It requires redis for background work. Similar situation with Python, or node. You need a process manager to handle forking for concurrency, you need external connection pools etc.
I can see and appreciate the unix philosophy at work, but it's also adding complexity. More things to tune, more things that can go wrong or become a bottleneck.
As we've moved our services to Rust, we've found that all those special tools disappear. Sure, we need to make similar decisions around how our system runs, but (as an example) configuring an in-process db pool is much much simpler than pgbouncer.
And that's a big part of a runtime being scalable. We won't be forced to reach for another tool as our requirements expand. We still might, but we don't have to.
(Certainly golang and the JVM fall into this category as well)
I feel like programming language designers should see themselves as quite beleaguered at the moment.
Even prior to full AI replacement, a properly trained AI will be able to take English language and effectively translate it to machine code, assembly code, or C code.
It doesn't really make much sense to have the human at the this layer for much longer.
Of course it is not "some random" crate; all of them is either in the same repository (when `path` is used) or in other repositories under the `rust-lang` organization with well-known contributors. But the latter is conceptually no more special than other repositories, so Cargo works same.
There are dependencies on other repositories in the rust-lang GitHub organization. The optional dependencies for the backtrace feature include libraries outside the rust-lang GitHub org.
I just peaked at the crates.io dependencies in the standard library.
There's three dependencies to high-quality libraries in the rust-lang GitHub organization: a macro to make conditional compilation easier (cfg_if), a port of Google's SwissTable hashmap library (hashbrown), and bindings to libc. These all have maintainers in common with the standard library.
The rust standard library hashmap is (zero cost) wrapper around a hashbrown hashmap with a subset of hashbrown's methods.
cfg_if is probably used just for the convenience. I guess they're not ready to commit to stabilizing that macro in the standard library for some reason.
libc was originally autogenerated from the c header files, but if I recall correctly updating it involves a bunch of annoying manual processes necessary to ensure perfect compatibility. It's obvious used in the implementation of standard library features.
Then there's a handful of dependenencies for the optional but enabled-by-default backtrace feature like a symbol demangler and a zlib encoder/decoder. I'm not familiar with their quality, but I don't think it's as excellent as the other dependencies.
A younger me, in search of a new language to learn, would have been turned off by something that is only "pretty ok"
But 20 years later, something that is "pretty ok" is exactly what I'm looking for. My experience has been that finding the perfect language (or perfect anything) is futile, and all I want is something that let's me solve my problems, don't get in my way, and don't really surprise me.
For a long while, python was my go-to "pretty ok" language, but eventually it stopped being "pretty ok" for me. Rust has now taken that spot.