Rust Survey 2021 Results
blog.rust-lang.org
blog.rust-lang.org
Rust is already a very large language and for intrinsic reasons pile on a load more complexity than Haskell (just look at the iterators).
I write this as a fan and advocate of Rust.
What I have seen is a lot of good work in the larger ecosystem. And the tooling continues to improve.
I don't think that's true at all. Little by little, ergonomic changes make it in, that are actually quality of life improvements for the end user. Better error messages, smarter faster compiler, better idioms etc. These are all small, but they add up. For someone coming into the language cold, it can be the difference between a terrible experience and a decent one. No language was born perfect, so let's not pretend Rust is there, or even close.
* there are language and tooling improvements all the time, that make learning the language easier
* waiting to learn the language can make it easier due to the above
* if you learn Rust today the amount of things you need to learn going forward are few to none, the evolution of the language doesn't make your old knowledge useless
Haskell Prime, essentially the Haskell 98 successor, is supposed to fix that. The first thing I do in almost any Haskell project is enable a bunch of extensions that I consider absolutely essential[1]. Most or all of those should likely be incorporated into the new standard, and then the truly experimental stuff stands out as experimental again.
Unfortunately it seems that Haskell Prime efforts have slowed down, despite a very active Haskell community in total. I have not looked into details there, though.
[1] For example ScopedTypeVariables, where it's hard to imagine for me at least why this would not be the default.
https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/cont...
And among other things, it also enables pattern signatures.
The official docs at https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/scop... are generally worth a read, they start out with an example of what lexically scoped type variables allow, and follow with the design principles for the extension.
Take generic associated types, for example: they’re a major new feature… but are conceptually just removing a potentially surprising limitation.
Or const generics: they really are a major new feature, but in general the code that can use them was a good deal more complex and inconsistent before.
You’d have a stronger argument about complexity creep on async—there was lots of demand for it, but it’s not clear that the design that is now frozen was in fact ideal.
But maybe this would be better fixed through Rustdoc, by supporting topics / sections, and being able to “globally” set which standard topics you’re interested in e.g. I don’t do embedded so generally unsafe and explicit allocations I’m uninterested in, that stuff could be folded away / hidden in both the sidebar & main text.
Wrt examples I feel like the examples are mostly obvious / uninteresting and thus have little value, but I’ve been in the field a while so that may well be an experience bias.
Alternatively some examples show the neat bits but not clearly because they’re artificial e.g. HashSet::insert demonstrates that it returns a bool via an assert_eq, you have to figure out how cool and useful that is (then gripe that other langages don’t give this info) separately.
My guiding principle for the absolute minimum of API documentation is "If I read the documentation and want to use the thing I read, I should be able to copy/paste something from right there to get started with."
This isn't perfect (trait implementations...) but it's nice to see that it's been as helpful as I hoped it would be.
It’s always been that tho, one of the motivations behind Haskell was to have a unified basis for FP language research: at the time (late 80s) there were a dozen half-assed half-implemented languages in the space, and the main and most stable platform (Miranda) was proprietary software.
A pub quizz with them would be just as fun.
Adding ".await" means when I teach Rust we now need to say "x.y is member access. Unless y is 'await'. Then it's something totally different". That's the type of strange rule that C++ has, and if you gather enough of them, languages become very hard to teach.
However, async seems to be the only big language feature which effected many things which has been added.
An await macro or function would have sufficed but because Rust is still mainly a hipster language with a hipster community, they had to choose a hipster syntax after years of bikeshedding about how to make it absolutely perfect.
You can even await the same future multiple times in a select statement keeping it executing a bit further each time until it returns, if you do it by mutable reference. Really quite magic.
Now we just need async closures and some nice async style iterators. Something like FuturesOrdered or even FuturesUnordered in an inline iterator style allowing efficient nice composability. Without any of the pitfalls and gotchas those two currently have.
I think that might be what throws many people off? The regular style of method chaining and having the cold futures just be a dead piece of memory you can pass around until actually passed to the runtime?
We tried, and it literally did not suffice.
This stuff was discussed by the community for a long time - bikeshedding about syntax is a well-known "trap" in programming language design. It has turned out to be (IMHO) reasonably intuitive, and preferable to the alternatives.
// with "?"
let v = x?;
// without "?"
let v = match x {
Ok(o) => Ok(o),
Err(e) => return Err(e), // exit current scope due to the return
}.unwrap();
That feels like something that could be done with a macro.But yes it is a hidden control flow, and taking in account hidden control flows is inherently more complicated (or at least has a steeper learning curve).
let v = match x {
Ok(o) => o,
Err(e) => return Err(e.into()),
};
> That feels like something that could be done with a macro.Indeed. It used to be a macro called try!.
Seeing a '?' is obviously a "new thing", so people go look up what it is (in practice, I would expect any Rust programmer to learn '?' really early, but if they somehow miss learning it, they will know to go look it up).
I've seen beginners assume they were "missing the await member". Of course, once you know it's something new you can learn it. I would have prefered something like .!await, just to make clear it's a "new thing".
The module system, the macro system(s), the core language, the borrow checker. It's all different micro-languages without a shared foundation.
I think it's easier to when you compare Rust to simpler languages like Carp or Zig.
Carp due to its homoiconicity and lisp heritage allows you to write macros in regular old Carp. Zig doesn't really have Macros, but comptime (compile time) code evaluation, which results in something with a lot of the benefits of a macro system without the drawback of unreadable DSLs, because it too is plain old Zig annotated with `comptime`. Zigs module system is build on Struct name-spacing, so if you know Structs, you know the module system. Its build system is also build on Zig code.
My guess is that Rusts origins are partially to be blamed for this, the web is build the same way, disjoint standards that are developed by different groups of people. It's just that just like the web, the sum of the parts is less elegant and usable than each constituent.
Where this could be a problem - I write a comptime function, only using other functions that I’ve verified can be executed at compile time. But now the implementation details of those functions (that they’re comptime) has leaked to their definition. Now a change to the impl of those functions could break my code, without the authors of those functions realising it.
Perhaps this is not a problem in practice?
I would say that more in general the problem between public interface vs implementation details is much bigger than any type system and requires humans to negotiate what should be considered part of the public interface through other means (eg: tests, comments). Comptime is one example, another is ABI stability (eg the layout of a struct, or just its size), another could be speed or memory consumption.
Zig doesn't have an official package manager yet, we'll see what happens once we get one and people in the community have to start communicating stability guarantees to their users.
And yes the problem you describe could exists, but I've not encountered anything like it in practice. But the idea you had there captures the difference between Zigs and Rusts type-system/generics quite nicely:
> Rust tries to proof universally qualified correctness and behaviour of code, i.e. regardless of its context, while zig only proofs you the correctness of specific instantiations.
While this may sound like a bug, I'd argue that it's a feature, as it's YAGNI applied at a type level, and for example allows you to do things like:
switch (builtin.target.cpu.arch) {
.x86_64 => //do intel stuff
.aarch64 => //do arm stuff
else => @panic("unknown arch!");
}
With the switch arms that don't apply simply being ignored and never type checked.
This allows you to use a basic language construct where C requires a preprocessor and Rust requires macros or compiler magic. #[cfg(target_arch = "x86_64")]
…
#[cfg(target_arch = "aarch64")]
…
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
compile_error!("unknown arch!");
(If you got to much more complex cfg branches, cfg-if could be worthwhile, which lets you write `cfg_if! { if #[cfg(…)] { … } else if #[cfg(…)] { … } else { … } }`. But for only a few simple ones, I prefer to keep it out.)Microsoft is also shipping pre-compiled projections for Rust/WinRT.
Then we have examples like image, that compile rayon twice, with different versions, because we just love to watch third party crates being compiled.
However I should also add that compile times have improved greatly, my travel netbook can finally handle small Rust projects without killing the battery.
Why do you think Apple has spent such a big effort making Swift work for shipping binary libraries?
Most devs would just stick with Objective-C, C and C++ otherwise.
Eh, sort of. They're basically shipping the Rust equivalent of C header files and import libraries. Whereas before they would parse metadata in a build script which would then generate the Rust code on the fly (which was hell for a number of reasons). And the installed Windows SDK was used for the import libraries.
https://github.com/microsoft/windows-rs/tree/23ff38bbbf46fb5...
Apparently since last November they decided to replace it with another approach, so my information is outdated.
Pros:
- Sum types. I am an OO apologist, but trying to use classes in C++ is an exercise in frustration. Sum types map very well to union types and are a better fit for systems programming.
- Unit tests built right into the language - it seems like a small thing but it's a hassle in most languages to choose a library, set it up etc.
- The above, combined with cargo-watch [0]. With the right flags, you hit save, and your tests all run. It's a god-send.
- Ecosystem is pretty big. I can find most things I need.
- Discord channel is nice. I was put off a bit by the Rust Evangelism Strikeforce back in the day, but so far everyone is pretty chill.
- Options & Results in the standard library. All languages should have this
Cons:
- I find it hard to transfer knowledge of C to rust, because they use different terminology.
- Docs can be confusing to read because every method on collections like `map` or `filter` returns its own Trait. People have explained why this is to me a couple of times but I still haven't gotten it through my head.
- You still have to think about memory and pointers. Not always a bad thing, but it is an extra dimension when solving a problem.
Overall I'm powering through. I'm hoping to get to a point where Rust is an obvious choice for anything that involves finer control of memory.
It's a tool for encapsulation: you can get into trouble with it, but I'd argue the same is possible with many things - it's how well you use it. You can easily use state composition in C++ if you want, and generally (multiple inheritance can cause issues in some cases) you can use interface classes as well.
And as someone who's been learning Rust for the past 6+ months, I've found that of the three new projects I started which I started in Rust instead of C++/Python over the past 4 months, Composition actually worked quite less well (it's quite verbose due to all the plumbing needed to connect things up) than if I could have used OO interitance in many situations where I wanted specialisation - i.e. hierarchy behaviour traits that had both common shared state and functionality AND specialised state and functionality. Thankfully Traits can at least have default implementations - if not, things would have been a bit worse.
To be clear: there's a lot to like in Rust though.
Two things get the enterprise devs of the mid 2000s going:
Badmouthing Oracle. And complaining about refactoring someone's mess of an abstract factory.
Especially I would say since microkernels just became incredibly widespread After Docker. Encapsulation is just not as necessary as it used to be.
In C++ the whole encapsulated structs thingo works fine.
It's there for when you need it, and you don't need it when you don't.
Gang of 4-esque programming is okay when it's necessary. Sometimes you are making something with a 100 different buttons and you just gotta do it that way.
However, for every project that needs all those factories and such, there's probably 5-10 where straight procedural/functional programming is perfectly fine.
Or in the case of many data oriented applications, or when formal verification is needed, probably just straight better.
Just do your abstraction with subroutine signatures and scope.
Well, where should I begin:
std::cout << "Hello world";
Is just a nice way of doing things...As the other commenter said, it's not so much OO, but more GoF and how the 2000's OO went.
Please tell me why any basic library adds 3 or 4 levels of inheritance just to do something basic like vector or array. "Oh but this is to make things more generic" and yet nobody uses all that generic stuff. STL is often replaced by more "serious" users.
C++ (and Java) are 2nd systems syndrome gone to 11
I've just taken a look at the Vector implementation at my current system and it's an exercise in frustration. Meanwhile I can understand most of this https://doc.rust-lang.org/src/alloc/vec/mod.rs.html#400
Where is the level of indirection in std::vector implementations? With templates, c++ really doesn’t use virtual dispatch all that much.
It is an example of how they fumbled the implementation of APIs, because this one has very poor usability.
> With templates, c++ really doesn’t use virtual dispatch all that much.
Good, but try to understand why your multiple-inherited vector is not compiling by reading a 3 line error message.
I bet with a couple of macros it would be possible to automate what languages on Windows can somehow do magically with COM interfaces.
[1]: https://play.rust-lang.org/?version=stable&mode=debug&editio...
I think you're remembering the various iterator adaptors, but they aren't methods on collections, they work on Iterators as their name implies. Somebody else explained why these methods can't all just return "Iterator" (that isn't a type) but just thought I'd mention you won't find map and filter in Rust's Vec or HashMap types, those methods live in the Iterator trait.
struct Map { closure, original_iterator }
This fact could have been hidden by making these methods return `impl Iterator`, but it would be strictly less useful/performant in case you needed to store the iterator somewhere, because then you couldn't name the actual type, and would need to work with an abstract one.But I do agree that the way documentation presents traits and their implementations can be overwhelming, especially the Iterator which is a pretty large trait.
I'm sure you know this, but `type Alias = impl Trait;` is on its way which would make naming those possible.
I don't think that's what your parent was talking about when they say "couldn't name the actual type".
They're saying, suppose I have a function that says it return an Iterator and I realise ah, it's not really returning an Iterator, but some subtype of Iterator - or, in Rust where subtypes don't exist, a type which merely implements Iterator. I now can't talk about the actual type being returned, because that's hidden from me, and it needn't be.
I found this to be the case: Rust is equally hard to learn, for C programmers and for Java programmers. But C programmers get frustrated more, because while Java programmers don't expect their knowledge to transfer, C programmers have unrealistic and unjustified expectation of knowledge transfer. Actual learning curve is same, just expectations differ.
I wrote about this in 2016: https://sanxiyn.blogspot.com/2016/06/problem-in-rust-adoptio....
I don't think there's anything particularly unusual about it - were I to seriously learn haskell, I'd hope whatever knowledge I had of Ocaml would put me in a good position.
Your blog was spot on - with the caveat that people who know C++ can easily "learn" stuff like Java and C# but tend to write very C++ flavoured Java and C#.
While it’s a legitimate concern that many jobs appear to be from the crypto industry, I wouldn’t worry too much. Whether they’re scams or not, the entire community benefits from the software they sponsor and open source.
See https://medium.com/concordium/the-devx-initiative-rust-ecosy... for details.
Rust is particularly good for crypto because 1) it is very efficient and 2) ownership semantics let you model things in a way that prevents many of the issues plaguing solidity and etherum.
There will be a crash though, and a lot of the money on Rust will evaporate, but I think this early push will ensure the language survives well into the future.
"Rust can now safely be classified as a language used by people in professional settings. Of those respondents using Rust, 59% use it at least occasionally at work with 23% using Rust for the majority of their coding. This is a large increase over last year where only 42% of respondents used Rust at work."
Has anyone actually tried to teach/learn Rust as a first programming language? People did it with C++ (and even C) at some point, is Rust that much more problematic? (And the attempt might even inform new convenience features: the proc macro system gives Rust a lot of inherent flexibility there.)
Why not take it for granted like you do for C vs ASM?
So I think it would be interesting to teach users Rust's point of view first, before they learn the "misconceptions" from other languages.
But in practice something like JS is better, because students can make something engaging appear on screen before they lose patience.
If you really need a general 'long enough' strategy and can't just pick a sensible place to drop the object statically, that's what Rc<> and Arc<> provide - built from the ground up via refcounting.
> Users coming from C are surprised that references aren't like pointers.
For good reason. General "pointers" don't have a simple, compositional semantics that preserves modularity (Yes, I know about separation logic; that's not practically reasonable in a language like Rust - or C/C++ for that matter - that's not built around expressing complex logical invariants in code); references do. A piece of code that uses pointers in non-reference-like ways must be understood as a unit. Which is why Rust strives to reduce such code to a minimum, marked with a scary "unsafe" keyword.
I'd say Rust is quite a bit easier to learn than C++, while being a step up from Java and two steps up from Python or JavaScript.
Or - you could describe that as biased, at least. Alternatively, maybe the intent of these language surveys is exactly to find out what committed power-users think. Either way, interpreting results from language surveys is tricky business.
(Or more succinctly: the absolute numbers are probably meaningless, but the trends are maybe worth watching.)
How was the sample selected?
On topic, i’d like to try out rust, but can’t find good cause in my daily webdev needs.
Rust is for complicated problems where both performance and safety matter. Web browsers. Databases. Operating systems. Industrial control. I'm writing a client for a virtual world in Rust. It's much easier to do complex concurrency in Rust than in C++.
Use the right tool for the job.
Makes me long for some Pascal. :)
As far as development experience, cargo clippy / cargo check / rust-analyzer give you the feedback you need without involving codegen (except for any proc-macros in your dependency tree), and incremental compilation makes codegen faster after the first build. Work is also ongoing[2] to make debug-quality codegen faster.
Here is a quality article on the art of keeping a Rust project fast to build: https://matklad.github.io/2021/09/04/fast-rust-builds.html
The overall series on organizing a large project is worth reading as well: https://matklad.github.io/2021/09/05/Rust100k.html
[1]: https://nnethercote.github.io/2021/11/12/the-rust-compiler-h...
I'm also in this camp, although I would go further to say that Rust is already "too complex." Or maybe not. Go has already done an excellent job of being a very simple and effective language. Rust's complexity makes it very useful for some cool stuff. Like building DSL's.
But because of its complexity, Rust is not the language I would reach for in most cases when building almost anything pretty standard, like a basic web app backend. For that, I would choose Go over Rust every time. The reason being: the cognitive load of Go is far, far lower, and that is super important for maintainability and being able to collaborate with others.
Basically, in the Venn diagram of what Rust can do and what Go can do, I'd mostly use Rust only for what it can do well and Go can't. For example, a real time system that can't have GC overhead. Or sometimes I'll choose it anyway for my hobby projects, just 'cuz I think it's cool and want to use it. But if I put on my pragmatism/business hat and want to build anything seriously, that's the way it will go.
Take that for what it's worth since I'm very new to Rust (and I quite like it actually!). Although I have a lot of experience with a lot of other languages including C/C++, Java, Go, Perl, JS/TS, Python, and more. Rust is up there as one of the more complex languages I've used. I've really noticed it on the learning curve so far. Reminds me a lot of Scala, actually.
The onboarding experience of Go vs Rust is night and day. You can legitimately go from reading "A tour of Go" to understanding most Go projects within a day (if you're already an experienced programmer). And it's not because Rust is lacking good learning resources. It has amazing docs and so many amazing free resources. I'm honestly enjoying and learning a lot about it rather quickly, I think. But it's still taking a long time and I can see I'm in for a ride and will be going "oh wtf is that" in Rust source code for a very long time!
It’s verbose and quite ugly as it tries to appeal to C family programmers while being expression and pattern based.
When trying to resolve ownership issues with closures the first time, you‘ll be doubting whether Rust is in fact a language or just a sick elaborate joke that tries to mock you for even considering to use it.
But with every concept learned you feel accomplished and start to develop Stockholm syndrome. The Rust compiler beats you to pieces and puts you together again.
As a new programmer, you rise from the ashes of your past inadequacies and delusions:
„Rust is actually a pretty productive language.“
On a more serious note: there is a book „Programming Rust“ and likely others that help with a structured introduction to the language, which I feel is a warranted approach, especially if the memory management concepts seem alien to you.
aka fix provably buggy code before it makes it to production?