Rust Criticism from a Rustacean
jeang3nie.codeberg.page
jeang3nie.codeberg.page
We need to shake the C demon and the only way is to push back on kernel ABIs changing.
Virgil doesn't depend on C and runs happily on Linux and Darwin. And yea, I know, MacOS breaks.
If the IOCTLs are different, then you compile a different binary. What's the big deal? Rust is a compiled language after all.
The fact that this ABI is called "libc" doesn't really mean a whole lot. Neither is the fact that the same library contains a whole bunch of functions primarily useful to programs written in C.
You can do this with syscalls, you can do this with dynamic libraries, or you can do it with IPC. The point is that it's completely language agnostic and not a pain to parse and use.
In principle, it's also worth pointing out that libc is really three separate (conceptual) things. There's libsyscall, which is the bare wrapper around system calls; there's libuss, OS services provided entirely in userspace and not the kernel (dynamic linking and some aspects of threading come to mind here); and finally, libcrt, which is the actual C runtime that implements the C standard library (e.g., fopen). It's really unfortunate that on most OSes, all of these are combined into one library called libc, although Windows actually separates these into different libraries (ntdll, which is not stable; kernel32-ish, which is stable; and msvcrt, which you have historically had to ship yourself!).
If so, that is a bit of a shame. But the solution is to introduce an abstraction layer on top of libc in the stdlib, so there could be a Redox implementation that doesn't have to imitate libc.
Which stdlib? Redox isn't trying to only run Rust programs.
It took a while to implement it on non-Windows oses, but that reality is now here: if you want to run an executable on basically any modern OS and hardware, use the win64 build.
The second worst thing is how difficult to use it is which makes it not worth it for problems where you can use a higher-level garbage-collected language.
That and some of the projects and types of work adopting it; last year the "web3" and "crypto" companies began piling in, which had a markedly corrosive effect on the language and its emphasis. In general, the amount of "webscale" type work being done in Rust right now has shifted the focus in published crates, etc. from "systems" type work to a lot of the "async serving" types of stuff. For someone like myself whose interest (and day job) in Rust is in embedded, "systems" (closer to the metal, storage, virtual machine, database internals, etc), and the like, this is a bit discouraging. In general the list of speakers at RustCon (before the whole fiasco) didn't look on the whole like the list of speakers around a "systems programming language" for example (with some exceptions).
As more "boring" work is done in Rust, this will hopefully shift. The trend hoppers will move on.
This had nothing to do with web3 and crypto, and everything to do with FAANGs and similar large companies not in those industries.
But there's job postings all over the place for either "web3" companies and/or others for tokio/async/web stuff in Rust. Which seems like gratuitous novelty seeking to me, TBH.
* Netflix isn't using it at all, as far as I know
* Google is using it in Fuchsia and Android, but I'm not aware of many web service things. Fucsia does use async/await though. Not sure about Android as I haven't examined it closely.
* Apple is using it as the backend of services, and on some storage team, though for the latter I haven't heard from it in a while. They're absolutely async/await users.
* Facebook/Meta are using it in a number of ways, for services but also as well as like, the SCM, filesystem, and build system. The latter aren't web services but do use async/await.
* Amazon is using it heavily inside of AWS. You don't get more "web services" than that.
But also, I meant "FAANG" in a broader sense. Large, established tech companies. Cloudflare, Microsoft, stuff like that. If you check out who is sponsoring the Rust foundation (not that that means that this gets you more weight for new features, mind you) https://foundation.rust-lang.org/members/ it's very much not dominated by crypto or web3.
When I was on the Rust team, we didn't prioritize web3/crypto use cases, and I haven't seen any evidence of that changing. It's always been kinda awkward, frankly.
After writing this comment, I also wonder if maybe that's also not some of the disconnect here: while async/await is critical for network services in Rust, it's also useful for other things too. So you have a larger coalition than you may think that's interested in directing the language in that way.
FWIW I worked in ad serving for some years (at Google and prior) and much of that work is done in C++ because of millisecond granularity P99, etc. latency concerns. Rust is a natural fit there, even though that is nominally "web serving"; but that's a different domain than serving up database-backed microservices for typical web customer workloads; that stuff is on the whole I/O blocked and not impacted by GC sweeps.
That is not a bad sing, in fact.
That mean the app was write and now is the second(third...) time.
I do this, for work. People pay me to make this stuff several times in the past. And moving langs and other major changes (like move from web to mobile).
Rust make this much better than before: You get some fast, yeah, some safety, great, but much better modeling of logic and great tooling, even errors are much better here! (ie more detailed, better resolutions).
---
Rewriting a system is assumed to be a terrible mistake, and that is true mostly for organizational or political reason, but is VERY profitable if done by technical reasons!
To make it a failure:
- You need something dysfunctional in the org/team, and stay or move anyway will lead to doom. Make the jump to Rust WILL make this more evident!
- You have inexperienced people doing this. Stay or move anyway will lead to doom, but move will be faster!
To make it a success:
- You somehow have a decent org/team
- Budget the time required to make the change, not do it with major time presures
- Do some small test validations, maybe just experiment a little a few weeks/months if the app is truly big.
- Make the jump and use real data ASAP
The rest is obvious work...
I always find it ironic that people say that Rust can't handle criticism when it is constantly being criticized... like in literally this exact post that we're all commenting on. That is a happy Rust user criticizing the language lol but yeah sure "many fans" refuse to listen to criticism.
There, this was your mistake. :p
Why tho? Like, that's how C and C++ do it, right?
> All you can do is live with it and realize that if you are that low on memory, the OS is likely going to be killing processes anyway. That said, I don't like the way Rust is inconsistent here. Rust normally enforces correctness, yet turns around and treats memory as an inexhaustible resource, ignoring or handicapping a lot of potential use cases for the language.
I think it would be more accurate to say that this is an issue of the standard library, not the language. And it's true, if you want to write certain types of software you need to throw away std.
> You can, at least in this particular case, have your cake and eat it, too.
Emphasis on "in this particular case" ? "Macros are a mistake" is a very broad, sweeping statement. I think it would be more accurate to say "Macros should not be a replacement for other forms of metaprogramming".
Having a native, supported language for compile time reflection is a powerful thing. If Rust one day gets it, that will be cool. I don't think macros are going to be a mistake on that day, they have gotten us extremely far at a very low implementation cost.]
> But at this point I just don't see anyone realistically switching to a std-2.0.
Well, to be fair, I think the vast majority of developers using Rust do not care about linking to libc or handling allocation errors and actually prefer things this way.
I agree that comptime would be cool. Personally, I don't think I'd want the other changes - I definitely prefer Rust stays on Github, for example.
I’d say it’s about the deceptive marketing. Rust is talked about as a safe systems language. I don’t think I’ve ever seen it followed by a disclaimer that this only applies to systems with unlimited memory.
It doesn’t of course stop at the marketing slogan. Problems like these aren’t sufficiently promoted in the documentation either.
I think that being more open about these problems would help enable a solution.
So, a Turing machine?
Why Rust doesn't prevent memory leaks - answer is simple. It's too much pain for modicum of gain.
People crow about lifetime being hard, now imagine how much people would complain if you add another orthogonal system to track memory allocation.
Suppose I have decided to model marriage. My Person type has a spouse: Option<Rc<Person>> and when you marry() another Person it fills out both spouse references. Now I make Bob and Alice, and marry them off, so Alice contains a reference to Bob, while Bob has a reference to Alice. Romantic. Next, I throw away my only Alice reference, but that's OK, I have a Bob, and Bob has a spouse field with a reference to Alice, so Alice isn't destroyed. Then I throw away Bob, likewise Alice has a reference to Bob, so Bob isn't destroyed either.
I have now leaked two Persons. I have no way to get them back but they exist forever (well, until program termination).
I would bet money that the majority of C code doesn't check the return code from malloc. What Rust does is strictly better than that. For people that do care, the APIs for fallible allocation are being added over time.
There's a difference between "I was trying to load this 1 pixel = 1mm scale image of central Paris into memory but I don't have 1.3TB of RAM" and "Allocating 4kB of RAM to grow my Vec was impossible". In the latter case you are definitely screwed, and Rust's choice here is fine, any imagined recovery is a fantasy, somewhere something else will also need some RAM and it'll blow up and everything is on fire, aborting now was the right choice unless you're a space probe or something (in which case you should have not been written to go around dynamically allocating memory all over the place)
In the former case it'd be nice to notice OK, that's unreasonable, I don't have that kind of RAM so we shouldn't attempt this. But, wait, is it OK to allocate say, all remaining RAM except 16kB? That wouldn't strictly fail but it sounds like a bad idea too. So maybe we actually want a configurable limit. It might be reasonable to say for example, we're an image loader, we will by default allow up to 50% of our RAM to be image data, in the same way that many thread pools assume it's OK to have about N threads if you have N CPU cores by default.
No, you're not. You just have to handle the case of not having heap memory to spare. If you were out of _stack_ space, now that's being screwed.
> and Rust's choice here is fine
Of course it's fine - for a language that doesn't claim to be safe. So, for Rust, it is not fine.
> any imagined recovery is a fantasy
That is the attitude of people who write non-critical applications where it's ok to be lazy and just let the process die and restart things.
It is not fantasy, it is not imaginary, and it is quite doable (except in corner cases of tiny machines for which 4 KB is a lot, in which case you would probably not do dynamic memory management on a heap anyway).
You have to successfully handle this case. Rust already handles this case by panicking, which you've unilaterally decided isn't safe for some reason. If that's not good enough you need to succeed. You must conjure the 4kB from nowhere. Show us all how it's done, or, and this might be the better choice, slink away to your fantasy land.
You are right at the end though, if you can't accept that running out of memory might happen what you do about that isn't dream up miraculous ways to "handle" it, you don't do dynamic allocations. Which of course works just fine in Rust.
Chrome is a decent example. Some of its processes might crash in an out-of-memory situation, but that only causes some tabs to lose their display state. The browser as a whole keeps running just fine. A similar solution can be done within a single process too.
--
[1] By task I mean some app specific unit of work that makes sense, not some Rust std lib string allocation.
Congratulations on successfully moving the goalposts. You don't get to decide which dynamic allocations fail, if you did obviously you'd choose "none of them".
> You just have to build your app in a way where there is a core that can continue running with no further allocations.
Again Rust - unlike say C++ - is actually defined so that this just works. So whereas C++ std::array can't exist in the standard without also having a heap allocator - yes really, Rust's [T; N] works just fine even including fancy features like select_nth_unstable_by_key() without needing an allocator.
Windows is the most obvious example. Microsoft does not officially document most of their system calls, and makes no promises that they work how you think they do/ how your unofficial documentation claims they do, or that what worked yesterday will keep working, the official documented APIs are to userspace code that you didn't write.
But this is also common on the other Unix systems besides Linux. You define the libc API as what you're promising and then you deliver or don't deliver features in your syscall ABI and the libc handles the slack.
Granted, I've had similar feelings about C++'s template shenanigans, so take it with a grain of salt. Zig's comptime feels like a breath of fresh air in comparison.
<'_>
#[]
stuff::thing::blah
@gloop(gloop)
if(fizz) |buzz|
.Meh => |wtf|
I know, I know... you probably get used to it once you understand the semantics and it's subjective anyway and and and... but still, not all languages I know repel me visually. <'_>
#[foo]
stuff::thing::blah
match gloop(gloop) { x @ _ => x }
if fizz { |buzz| Some(buzz) } else { |_| None }
Meh => |wtf| ()Thanks for correcting me. Still not a joy to read though.
That depends. Rust requires less learning than C++ for writing safe code. There are plenty of C++ developers that feel like they know the language but produce unsafe code. Rust won't let you do that, so these developers complain.
I love Rust and very much like OP it comes very close to my ideal language. I also think it had very good start when it comes to syntax but did a wrong turn when it copied things from C/C++, which in my opinion have horrible syntax for mostly historical and obsolete reasons.
But not all is lost. With the editions mechanism we could do a syntax overhaul. I really, really hope that will happen.
Meanwhile it's the small things everyone can do. One thing I advocate for is to adopt the most common style for identifiers in Java, Javascript, Typescript, Python, and even some parts of the C/C++ world and do away with snake_case.
If we could do away with underscores and double colons we could have a much nicer language already.
snake_case is used a lot in Python:
module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name, function_parameter_name, local_var_name.
(not mandatory, it's just from the style guide)But I don't even think snake_case or CamelCase matter as much as plain keywords vs. symbols or "commonly used" symbols and ones that are not too noisy.
I know compared to Rust strong points this all seems to be petty criticism, but we all read lots of code, probably more so than writing.
+1 for plain keywords. Common separators when necessary (e.g. constants or everything that ends up in the filesystem, bc upper/lowercase is a pain there).
Regular struct, enum, trait variable names: CamelCase hands down, like almost everyone does it.
struct Foo<T> {
t: T
}
vs (assuming we get the pep for inline typevars) class Foo[T]:
t: T
I don't see how Python is meaningfully better at all.Or
use foo::bar::baz;
vs import foo.bar.baz
I just don't get the problem here. "double colons bad" is just an extremely subjective take that I don't really buy at all. I much, much prefer snake case and would hate to see pascalcase in Rust, but also at the end of the day these are such minor things that barely barely impact readability. struct Foo<T> {
t: T
}
Braces... one extra sign (and line) for nothing while indentation works fine. import foo::bar::baz;
import foo.bar.baz
4 times the dots, also useless comma.Objectively less visual noise in Python. Of course the importance of it is subjective, I agree with that. But still, less cruft is good.
> minor things that barely barely impact readability
Of course, I don't agree :-)
If you want to get rid of `::` and `;` and braces you have to look at the fallout of those decisions.
Braces are imo much better at being robust than whitespace.
def Foo[T]:
t: T
size: u32 // invisible error here because someone used tabs by mistake
`;` disambiguate expression vs statement. If you remove it you need way to differentiate between them.Another problem with `import foo.bar.baz` is how will you distinguish between a method call or a type declaration? E.g. `std.option.Option.Some` what is this? Type? Method? Module? What is `std::f64::consts`???
Not to mention you kinda have to have compiler in mind, if syntax is ambigous you're going to have a very bad time.
It's the opposite for the double colon syntax which, apart from Rust, to the best of my knowledge only exists in C++ (for good reasons) and some functional languages for a completely different purpose.
Honestly curious question: What fallout would you see from replacing it with a dot in Rust. I immediately see no disambiguation issues, but I'm not a compiler writer.
would not parse, so no invisible mistake.
That whitespace sensitivity also leads to having to escape newlines, with `\` trailing lines all over the place in Python. I find that far uglier than "4 times the dots" for import statements.
> Of course, I don't agree :-)
For sure, I think that's kind of my point. Like, this all feels very unimportant to me and extremely subjective. I think that rust's syntax fits into its philosophy, however. I would not mind unifying path syntax such that `:` became `.` however.
"Explicit is better than implicit."
;-)It is objectively worse to type shift+; two times than a single non-shifted period.
That's a very simple selling point, what's there not to get?
Even worse is that its use is so similar to the situations where the dot is used that it's just confusing. The Internet is full of questions when to use which. This is in my eyes unnecessary complexity and does not mesh well with the aspiration of Rust wanting to be beginner friendly.
Eh, I don't think that is fair, Rust even at best of times will have concepts like lifetime/references that Python and TS can just ignore and are thus "simpler".
A much better comparison would be something like https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html.
> and do away with snake_case.
That's seem counterproductive - according to: http://www.cs.kent.edu/~jmaletic/papers/ICPC2010-CamelCaseUn....
I.e. you would do with most readable part of syntax.
I don't see why we can't have clean, low noise, high signal syntax in a language like Rust. My criticism is not the syntax that makes the language more expressive - that's essential complexity.
I would want the accidental complexity removed, like the somewhat redundant distinction between dot and double colon. And I wish the cognitive load reduced by making the syntax as familiar as possible to as many people as possible. That's the reason I mentioned Javascript and Python - not only is their syntax more concise but also familiar to more people.
When it comes to the snake case study, I'm not surprised at all. They did not control for the size each identifier took on the screen. That a bigger signal enables a faster response is common sense. Longer identifiers are easier to recognize but the downside is that they take away space we could use for other important things. That's why most languages keep the most frequent ones (like variable names) short and free from unnecessary noise like underscores. Also the sample size in the study was extremely small.
[1] Except the Haskell/ML folks, naturally.
As I pointed in Matklad's post you need to add those concepts in or devise a way to infer them, side by side comparisson of Rust/JS is meaningless if one has no concept of lifetime or one abstracts details via GC.
> Before Rust everybody[1] seemed to believe we cannot have a safe and natively compiled language.
I do agree, but you can't avoid all trade-offs. Rust got safe, natively compiled language by increasing type complexity (adding lifetimes and ownership), and adding more sigil noise. If Rust had GC it would be way simpler and way less usefull.
> like the somewhat redundant distinction between dot and double colon
I don't think it's redundant. But I'm not a Rust compiler guy.
> Then there's the issue that the crates.io registry is on GitHub, and only GitHub, and in order to publish on crates.io you have to have a GitHub account
This isn’t true. Crates.io publishes metadata to a GitHub repo, but that’s decoupled from them only supporting Github as a SSO.
https://techcrunch.com/2019/07/29/github-ban-sanctioned-coun...
how can Rust conduct any official business on such a platform?
2. The UN's headquarters are in the US, the same principles should apply, although I realize legally that might not be the case. Then again - maybe projects like a programming language should actually be constituted via the UN, or ISO, to have the appropriate non-partisan legal basis for operations.
Trade/technology embargoes and sanction regimes are just such things. For an example how arbitrary those can be, the US sanctioned a German port for participating in NordStream 2 construction — and that project had zilch to do with the US, even its financing was done in Euro, not USD.
> The UN's headquarters are in the US,
And the US somewhat regularly declines its visas to the UN representatives it doesn't particularly like. So?
In any case, an international organization (and its members) is not automatically outside of the jurisdictions of the country (countries) it's physically in; if such a need arises, the governments have to pass specific laws regarding those specific organizations and its members, granting some sort of immunity or another. Or they just don't, and the organization simple has to function as a non-profit multinational.
> Then there's the issue that the crates.io registry is on GitHub, and only GitHub
There are a number of issues with this. The public crates registry is available in sanctioned countries, and it’s not even used by default anymore.
This is independent of crates.io only supporting GitHub as a sign-up option. People from sanctioned countries can also sign up for GitHub accounts, but cannot access private ones or paid membership features.
The author conflates these two issues in a way that confuses the point they are trying to make, which has nothing to do with the crates.io registry being on GitHub or access from sanctioned countries.
It would really be a better approach to work on getting Rust to adopt the features of Zig you're looking for. The work it goes into developing a language into a mature option is absolutely massive. Developing a full-featured language, building a (healthy) community around it, building dev mindshare/marketshare, building trust that it's sustainable, etc is a many-years effort by many talented and dedicated people. It's a huge amount of work when you could achieve the same output by adding a certain feature or two to rust.
Of course the leadership is a bit broken right now, but they may (or may not) be able to fix it.
There's also the Typescript approach. That is, extend rust with a comptime feature wrapper. Not nearly as elegant, but you don't need to work with broken rust leadership to develop it and, if it gains traction, you may then have the leverage to get it rolled in to the real thing (if you even want to at that point).
I was just pondering the other day about how nice it would be to take to get the same level of quality C/C++ integration that Zig has, but in Rust. My new job has acres and acres of C++ which isn't going to go away. Linking and wrapping against C++ is one thing; build system integration is another. Zig has some serious advantages there.
That and I really want allocator_api and simd to get into stable.
What are the stumbling blocks? Lack of stable ABI in Rust?
So mixing Zig and C/C++ has a lot less friction.
Would that help?
This is not true.
* The standard implementation technique is to box everything (have a uniform in-memory representation for all types). Monomorphization[1], which is the compilation strategy used by Rust, C++, and I guess Zig, requires type knowledge at compile-time.
* Generic functions should not have any knowledge of the concrete types of the values they are passed, as this breaks an abstraction boundary. Theorems for free[2] is about this idea.
[1]: https://en.wikipedia.org/wiki/Monomorphization
[2]: https://www2.cs.sfu.ca/CourseCentral/831/burton/Notes/July14...
In Rust this is something like
fn foo<A>(a: A): A
(which can only have one implementation, the identity, if you don't break the abstraction barrier provided by generic types.)1. I don't think programming languages can "enforce correctness". (Although I will say that my general experience is that functional languages, not Rust, tend make it more difficult to get things trivially incorrect.)
2. I'm not sure that's a good enough reason to use Rust. I mean, sure, Rust is usable, people do things with it, but - my biased experience is that at some point you just go C++ for some reason, and stay there.
Proc macros are particularly awkward because of the requirement that they must be defined in a separate proc macro crate. There are all sorts of things one could do if it were possible to instead make proc macros that generate proc macros _in place_ like one can with decl macros.
Then there is also the issue of macro expansion... the rustc framers openly admit that with an expression like foo!(bar!(..)) it is often preferable to have bar!(..) expand _before_ foo!(..) expands, because they have hacked in this behavior for several of the built-in macros like `concat!`, however this behavior is completely unavailable for custom macros. In fact, this one little issue is the ONLY blocker to all kinds of exotic and powerful behavior, the lack of which is holding the ecosystem back, for example the ability to eagerly expand a series of macro calls would enable the following things:
* the ability to determine the column, line, and source file in which a macro is being called, at compile-time
* the ability to determine the module path of the caller of your macro, at compile time
* the ability to determine the name of the crate in which a macro is being called, at compile-time
* the ability to implement complex compositional behavior purely in proc macros
* (very important) the ability to determine the `CARGO_MANIFEST_DIR` of the crate in which a macro is being called. Without this key ability, it is impossible to reliably determine where we are executing so we can do things like read metadata configuration values from _the caller's_ `Cargo.toml` at compile-time.
So yeah, really hoping at least eager expansion stabilizes sometime soon, but this zig stuff would be even better.
Some of my crates that would benefit from this:
* docify: https://crates.io/crates/docify
* macro_magic: https://crates.io/crates/macro_magic
* crate-settings (non-functional until this comes out): https://crates.io/crates/crate-settings
* proc-utils: https://crates.io/crates/proc-utils
Would it be a major challenge to add it to Rust?
Something like this: @org/axum @user/axum
They should have just copied the Maven Central mode of operation, it has matured over many years and works well, without any drama such as NPM is infamous for.
1) using registered domains as namespace (if one exists)
2) proving that you have access to said domain
This is how Maven does it and it works very well.
I don't see a reason you couldn't use exisiting org/username from Github as namespace, can they squat on it? Not unless they steal it, in which case, Github should be notified.
In that case just yank all versions on Ygg2/stuff, and the any change update from then on references Org/stuff.
* rust-lang/semver
* steveklabnik/sevmer
* dtolnay/semver
That everyone didn't need to do "yank everything, change all cargo.tomls" is a huge advantage of not being tied to this.
If you want a more complex solution, then you would need a more complex index of ownership for cargo. E.g. alias or transfer ownership. It could be as simple as displaying "This crate is deprecated use org/crate instead". Or as complex as what crate is no longer valid.
One needs to know what exactly are cargo namespace requirements?
> Yes. For example, the semver package has lived in these places:
Sure, and squatters squatted over many crate name extorting bribes to release them.
IMO the solution is simpler. Heavily rate limit the creation of namespaces. That's less viable for crate names, but it should be very uncommon to create more than a few namespaces. You could give people a low number that aren't rate limited, like 5, and then rate limit them to 2 a day afterwards.
They can then publish unlimited crates under those namespaces.
This both misunderstands the impediments, and wildly overestimates Rust's position. First, on some platforms like OpenBSD, it's not just a matter of stability per-se; OpenBSD implements some of their security measures in libc, and therefore intentionally forces its use. Second... how exactly is rust going to "force", say, Windows, to change how they present the userspace/OS interface?
> In practice, most kernels have a "fairly stable" ABI just because coordinating kernel changes with userspace libc changes is tricky.
I don't know if you're using a loose enough definition of "fairly stable" that it's meaningless, or mistaken outright, but no, at least NT can and does change its syscall interface often enough that it actually prevents people from using it in any meaningful way (read: sure, you can do it as long as you never update, but then the next time you run Windows Update it will break).