Five Years of Rust
blog.rust-lang.orgThe progress on the error messages truly is worth highlighting, and kudos to the team for all the hard work there. It's one of the hardest things to get right in a programming language, particularly if that language has a fussy compiler.
It is great. Rust 1.0 with the old borrow checker required knowing where to strategically place extra curly brackets, and when to add `ref` or `.as_ref()` incantations.
Now with the smarter borrow checker and ergonomic improvements most of it is unnecessary, and the error messages know how to suggest the rest.
Still quite a lot of circular gotchas, so it's not a panacea for studying the docs!
I’ve been using the anyhow crate and it’s made error handling almost painless.
You are talking about different things, the grandparent was talking about the compiler error messages.
Just a note, I agree with both of you :)
Just to point out for other readers, nowadays to make a custom error type you just need :
- an enum (or struct)
- its Display implementation
- its Error implementation, now just one function
And a few `impl From<OtherError> for MyError` to make the try? operator work.
For a library it's really not that much work. And even that can be simplified further to a few derive macros with another library : thiserror.
To simplify even more, you don't actually need `Display` or `Error` impls. I use Error enums frequently without display or Error impls. Though, with better backtrace support coming it'll be handy to have Error and the Backtrace related APIs implemented.
(Sidenote, I don't use `impl Error` because I never use `Box<dyn Error>`. Which isn't to say that's correct, just to say that I've never had the need to implement it)
Even though GP meant to highlight compiler error messages, I also have to take my hat off for the library/application error-handling story in Rust.
Using a crate such as thiserror[1] combined with displaydoc[2] makes handling errors in a structured manner a great experience. I love how the error handling story has evolved over the last couple of years in Rust, and it really feels like we're entering into the final stretch of fine-tuning to get the best possible experience.
And yes, anyhow[3] is also great, it serves a different purpose, but I frequently reach for all three crates.
[1]: https://crates.io/crates/thiserror [2]: https://crates.io/crates/displaydoc [3]: https://crates.io/crates/anyhow
Anyhow isn't really about error handling, but about error reporting. With an ordinary enum error, I can match and easily handle the error if I want to handle some errors in special ways. If I want to handle an anyhow error, I have to do string matching on the message, or try to downcast it, which is cumbersome.
What anyhow makes easier is to collect many types of errors together and reporting the errors to the user.
Now that there's been 5 years since v1.0, is there any consensus on design mistakes that Rust made? Any mistakes that people wish they could turn back time and do differently but can't because it would break compatibility with too much existing code out there? That's the more interesting list to me.
There isn't much. Rust has deprecated some mistakes (like Error::cause). The editions mechanism allowed fixing some issues (like unintuitive module paths).
https://github.com/rust-lang/rust/issues?q=label%3Arust-2-br...
• Struct literal syntax should have used C99 syntax. The language uses `name:type` everywhere except struct literals which use `name:value`, and this gets in the way of adding new syntax (type ascriptions).
• `Box` is semi-magical. Maybe it could have been a regular struct. Or maybe magical all the way to allow placement new and destructuring.
• Types are in borrowed, owned-fixed-size and owned-growable variants, but naming of them is a bit ad-hoc. There's str/String, but Path/PathBuf (instead of e.g. String/StringBuf or path/Path).
• Split between libcore and libstd is awkward to manage and not a good fit for WASM. It could have been one libstd with feature toggles (this might still happen).
• Some people think split between Eq and PartialEq is an overkill, and just makes floats annoying.
There are things that are still unsolved in Rust, like umovable types and self-referential structs. But it's hard to say they're a mistake — as far as we know, they're a necessary limitation to make other useful features work.
Box being non-magical might still happen too.
(I disagree personally on struct literal syntax but you're not alone, it's true.)
The "2018 edition" of Rust made breaking changes to the syntax (but the core stayed compatible, so 2015 edition and 2018 edition Rust can be used simultaneously on the same project).
They then said that they'd probably do the same thing in 2021.
Now they're debating whether a 2021 edition is needed since there aren't any breaking changes with broad support except for the removal of deprecated syntax and APIs.
This is strong evidence that the answer to your question is "no".
I don’t think there’s consensus around very large things, but there are some regrets that are commonly expressed about smaller stuff. For example, lots of people think the PartialEq/Eq split was a mistake. I like to half-joke that String should have been StrBuf. Macros have several flaws and are under-developed, etc.
There are also some thoughts about Rust-like languages with some differences, see https://boats.gitlab.io/blog/post/notes-on-a-smaller-rust/ as a prominent example.
I saw that the Piston developers created Dyon which could be seen as a Lua in the Rust spirit. No garbage collection, only lifetimes.
One way to reason about what could have been done better from the beginning is looking at stuff that is marked as [deprecated] in the standard library. E.g. how the "try!()" macro was deprecated in favour of the "?" operator.
I would say the try!() macro is an exception to that rule: if the ? operator existed since the beginning, it would be seen as "too much magic" (in a language that already used up most of its "strangeness budget" in lifetimes/borrowing), while try!() is just a very simple macro you could write yourself, with no special compiler support. Only later, after people got used to try!() everywhere, the ? operator became viable, as "just a shortcut to try!() with better precedence (and it also works on Option)".
That is, there's a path dependence, where the existence of try!() made the ? operator viable.
>if the ? operator existed since the beginning, it would be seen as "too much magic" [...] That is, there's a path dependence, where the existence of try!() made the ? operator viable.
Yes, C++ creator Bjarne Stroustrup made a similar observation:
- For new features, people insist on LOUD explicit syntax.
- For established features, people want terse notation.
There seems to be an invisible "Overton Window" of evolving programming language features and syntax.
Having both the terse and the verbose is so valuable for onboarding.
I feel like the reason pointers as a mechanism in native languages is such a barrier to cross for a newbie is because they are this magical star symbol * just floating around doing... something?
If people started out writing pointer<string> foo instead of string* foo and had to use explicit derefrencing via name rather than by magical glyphs onboarding would be so much easier.
I think it applies to almost all programming concepts too. Starting a language with a baseline grammar of just function invocation -f(x) = y as <noun>.<verb>(<noun>...) - and extrapolating from there and introducing terse grammar progressively as shortcuts would serve much better to get concepts in heads rather than arcane ritualistic glyphs. Introduce x.add(y) then say x + y is the shorthand.
Rust does a really great job "functionizing" almost everything in the language - it has the add trait after all https://doc.rust-lang.org/std/ops/trait.Add.html. You can actually write tons of Rust in just function call form and make it look like Lisp.
I think more interesting would be something we're truly stuck with (at least until Rust 2.0, which may never come).
(Stuff that’s deprecated in the standard library is stuff we’re stuck with; they cannot be removed in editions.)
I've seen this said many times, but I don't really understand why.
Why couldn't a new Rust edition create a new std2021 (for instance), and automagically use it when importing a std submodule in a project using the new edition (and in the prelude)? The old implementation would still be imported using std in crates using prior edition, and could still be imported in the new edition, but named std2018.
If all the implementation (except the now deleted stuff) is put in the std2021 module and the old std2018 () simply re-export it (and re-implement the old stuff), you wouldn't break anything would you?
I'm probably missing something, but so far I don't know what and I'd be really happy if somebody enlightened me.
> Why couldn't
There may be possible ways of doing this, but you're approaching this from the wrong angle. Right now, this is not possible, due to policy. This policy is informed by the technical restrictions right now. There may be possible ways in the future to handle this, but as of right now, there are not.
The reason this is true right now is that there is one copy of the standard library for every program. So it needs to support all editions, because code from multiple editions may call standard library functions.
If we had multiple copies of the standard library, you may end up with issues where two different programs can't interoperate because they'd be using different versions of the same crate, and given that one of the most important features of the standard library is interoperation, this is a huge drawback for the stdlib specifically.
(There may be other technical issues, I am not an expert here, but that's the biggest hurdle as far as I know.)
This isn't the answer I wanted, but it's a good answer. Thank you
You’re welcome. (I imagine that you’d get the same version conflict situation with your idea btw: std::2018::Option is not the same type as std::2021::Option.)
Even if std::2018 is just doing: `pub use std::2021::Option`?
Ehh you're right, actually.
I still think you run into other issues, though.
Sure but you don't have to use them, is the point I'm trying to make. They don't affect modern code and while it's annoying they are used in older code there is at least a path forward to "upgrade" the code to the modern equivalents.
Could a lint check for them and warn if some code uses them?
This way slowly the ecosystem could move and stuff could be eventually removed.
If you use something that's deprecated, you get a warning, yes.
> could be eventually removed.
It can not be used according to our stability policy. There's a lot of closed source Rust out there.
There has been talk on the internals forum of "gating" deprecated parts of the std on a new edition. So the deprecated feature will be hidden for crates that declare `edition = '2030'` but will be available for crates using an older edition. Essentially turning the warning into an error.
But this is just talk at the moment. Currently there is no mechanism to implement this. It would also be a challenge for documentation.
Yes; this is so far off, and is not even at the RFC stage, that I didn't mention it. But you're right that maybe, someday, we could make it even harder for folks to call these things in newer editions.
No, this was intentional. The point was to see how the user base used the language before adding permanent language features.
The planned const generics syntax is going to look pretty weird, because it builds on type generics. If Rust had chosen a different syntax for type generics, const generics might look less weird.
The futures design was built with epoll in mind, and now people are trying to wrap it round io_uring, they are feeling some pain. Would a different design have worked better, without massive drawbacks?
> The planned const generics syntax is going to look pretty weird, because it builds on type generics. If Rust had chosen a different syntax for type generics, const generics might look less weird.
Could you provide an example? Because I don't really see it, except by requiring that non-const generics also be explicitly annotated?
I'm thinking of the need to have blocks wrapping expressions in types [1]:
Simple cases are fine, but i suspect a lot of real-world use of const generics are going to require blocks.const X: usize = 7; let x: RectangularArray<i32, 2, 4>; let y: RectangularArray<i32, X, {2 * 2}>;AIUI, this is only because <> was chosen as the container for type arguments, and > is also a legal operator in expressions. So, for example, if Rust had used Scala-esque [] for type arguments, this would not be a problem.
[1] https://rust-lang.github.io/rfcs/2000-const-generics.html#ap...
While I would prefer brackets, that would not be enought to solve all the problems since, there would be conflict with array syntax too.
But the square brackets used in array access nest, so the parsing is never ambiguous.
The reason > needs braces is that without them, if you're parsing and so far you've seen these characters:
You don't know if that final > is ending the argument list, or is a greater-than operator, with the rest of an expression coming after it.let y: RectangularArray<i32, X, 2>But if it was square brackets, then a closing square bracket on its own can only ever be the end of the list:
Because if it was closing an array access, there would have to have been an opening square bracket:let y: RectangularArray[i32, X, 2]let y: RectangularArray[i32, X, a[1]In case anyone ever stumbles across this thread, a couple of posts about this:
https://keleshev.com/parsing-ambiguity-type-argument-v-less-...
That's what I originally thought about, but the thing is `]` in and of itself is not an operator while `>` is, so
"is" valid syntax whilefoo<bar, qux>corge>
is not. The former requires some sort of disambiguation, while the latter is not. Put an other way, in terms of syntax `[]` always parses the same way (with `[` is infix and `]` terminates it), there can be ambiguity between indexing and generics but it doesn't really matter for the original parsing.foo[bar, qux]corge]For `<>` however, the parsing itself can be ambiguous as `>` could either be the terminator of an earlier `<` or it could be the infix `>` operator. That is where the issue lies, you can't know how to build the AST without either explicit disambiguation, or infinite lookaheads.
I've been complaining for a long time that `Drop` is fundamentally wrong. Rust's "just write a new function" fixed C++'s "construction is mutation of location", but Rust's Drop makes the same mistakes as C++: destruction needs to be for consuming data, not borrowing it and mutating it.
Now, we can't just do
because of DSTs, so we we'll need a new type of consuming reference. And the dual to that, an initializing reference, would also solve the problem of creating DSTs with preallocated memory.drop<T>(T)Now both could use MaybeUninit, but it would be better to just have types that vary with the CFG, so one can insure that no matter how one get to point b, the memory is now initialized.
> but Rust's Drop makes the same mistakes as C++: destruction needs to be for consuming data, not borrowing it and mutating it.
but Rust's drop isn't for that purpose. It's not for the actual cleanup of the struct and its children it is for additional cleanup before the children are deleted. So it has to be mutable. The compiler synthesizes the "delete children" code.
(I don't understand your point about drop<T>)
> It's not for the actual cleanup of the struct and its children it is for additional cleanup
I think that purpose is the tail wagging the dog, an explanation of the current method rather than an actual requirement.
The simpler thing to do is just have drop on the aggregate calls drop on the fields, just as new on the aggregate can call new on the fields.
This does not seem simpler to me, this seems easier to mess up. The most common use case is dropping all fields.
Furthermore, you still have to special-case Drop because now you have to support destructive destructuring for Drop types because it isn't allowed anywhere else.
And plus, if you forget to do this, the failure mode is a stack overflow.
The current design is absolutely based on practical requirements here, it is not a retroactive justification. This is by and large how destructors work, for good reason.
> (I don't understand your point about drop<T>)
Ah I meant to write the function signature:
> fn drop<T>(T);
contrasted with the:
> fn drop<T>(&mut T);
that we have today
Incompatibility between C enums and Rust enums forces to use integers instead, which leads to errors. (Rust doesn't allow to enum variables to be forward compatible, i.e. it cannot have a value outside of enum).
I think that's a good thing. A C "enum" is just a shorthand for declaring an int alias and some constants. You can do that in Rust easily enough.
A Rust enum is an actual enumeration type, which C does not have. This is far more powerful.
It's good thing, but it leads to error in code which must talk to C or network, where enum's are not cast in stone.
You really should not be casting data structures sent over the network directly to local data structures, unless you are using capnproto or another zero copy protocol that does that safely.
You can assign any value to a C enum, they literally are just integers.
> Rust doesn't allow to enum variables to be forward compatible, i.e. it cannot have a value outside of enum
AKA Rust's enums are type-safe, not aliases for integers with some named constants.
Rather than Rust's, I'd say the mistake is C's enums. If you don't want enums, don't have them.
I guess that's one place where Go did something good: they didn't want to improve on C's enums with proper ADTs so they just stripped out the entire thing, an "enum" is an integer and a bunch of constants. Which you can also use to represent these non-enums in Rust though it doesn't have the iota / step convenience. A simple recursive macro might be able to handle it though.
To future proof an enum in rust you use the #[non_exhaustive] annotation.
For C compatibility you have lots of options like #[repr(C)] or #[repr(i32)] to be C compatible. So not sure what you are referring to?
The problem is that #[repr(C)] enum is not compatible with C, even with #[non_exhaustive] annotation, and will never be, because C enum can be described in Rust terms as:
which is not possible to define in current version of Rust.#[repr(C)] enum Foo { A = 1, B = 2, C = 3, UNKNOWN(i32), }
Those topics come up on reddit.com/r/rust quite frequently, along the lines of "What are the biggest mistakes in Rust" etc, you can find a bunch by searching something similar.
Three chars limit for keywords and some std types, e.g. `Vec`, `len`, `str`.
I respect your opinion, but I tend to disagree. The terseness is a feature I like.
Is now the time to start learning rust? In your estimation, are there going to be lots of job opportunities for people who have 15 years experience with rust?
I primarily live in the .Net world, but rust seems extremely close to f# in terms of compiler safety, and I'm trying to decide what programming language to learn next.
I learned it a few years ago, just for fun. I have it in my back pocket for a personal project. The language has some odd corners to learn (and they are what make it special), but it's not so difficult you can't forget about it for a year and then come back to your project, as I have.
There's some highly specialist aspects you _could_ learn (like anything I suppose), but I've got enough to get by and be as productive as I'd like without needing to get to that level.
So there's no harm in learning enough to get by for fun.
Compare to e.g. Haskell, which I learned at university, I have a memory that you'd have to invest quite heavily to make it do something useful.
My job is about 50% Rust, 40% Typescript, 10% Python with regards to language.
I would say Rust is the best day to day experience out of these 3 except for glitchy editor tooling and the long compile times. We have 1000 line services that take close to 10 minutes for a release build which is not ideal.
I would not necessarily suggest learning Rust to get a job though. We mostly stopped mentioning it in our job ads because we don’t want people applying for the “hip stack”. Usually, competent programmers with some basic understanding of how manual memory management works have no trouble picking it up as they go.
> We mostly stopped mentioning it in our job ads because we don’t want people applying for the “hip stack”.
Can you elaborate on that? I'm maybe thinking of changing jobs soon and my #1 want is that I can commonly program in Rust as opposed to C. Why would you explicitly avoid mentioning Rust?
Yeah, sure. What I have seen a lot when hiring, is "magpie developers". These guys always have the hippest CV - if it's on the HN front page regularly, you bet it's on there.
We found in practice, these people tend not to be the best additions to a team. They are mostly interested in the new thing, and only for so long as it is new. When something new comes along, that's what they want to flock to. They'll be an endless source of rewrite suggestions, but tend to struggle when they need to actually dive in and solve complex problems with these tools.
In contrast, we are not really using Rust because it's the new thing on the block. We are using it because we write complex software that needs to run fast for the business to be viable, and Rust's got the best tradeoffs for that right now. If Rust wasn't a thing, we'd likely be using C++.
Also, a word of advice: the language in use should be one of the last things you're considering when looking for jobs. It really doesn't make a difference in your day-to-day happiness.
Instead, try to look for a team you fit in with, a manager you can trust, growth opportunities, and the chance to work on interesting projects that have impact. Case in point, one of my most enjoyable (and beneficial to my career) jobs was mostly Java 8 - and that's certainly near the bottom of my "good programming languages" list.
This is all very good advice.
Now is a great time to learn it, because there's nothing huge on the horizon that will make your life easier or harder.
I started learning just as the async/await and futures features started causing churn and confusion in the ecosystem - it was slightly irritating at the time, but in hindsight, I'm still glad I started learning then.
Plenty of big name companies (discord, amazon) and even universities (georgia tech) are using and teaching rust. That's enough for me to say you should jump on the train.
Another example would be Dropbox which quite recently published this great blog article about "Rewriting the heart of our sync engine" in Rust [1].
[1] https://dropbox.tech/infrastructure/rewriting-the-heart-of-o...
I started a company while also learning Rust. It seemed like the right choice given that we needed something which emphasized memory safety and was also blazingly quick, but I was essentially forced onto it while dealing with the pressure of having to deliver on tight deadlines.
Our core tech is in pure Rust and I have to admit that coming from Javaland it's refreshing to deeply trust your code. It's hard to convey this idea of 'if it compiles it works' but I no longer worry about showing off prototypes to people. If they compile they work.
There is a learning curve as you are introduced to new ideas and design patterns but it's worth it! Rust has made me a better programmer.
Non-Lexical Lifetimes was huge. Impressive list.
I hope the next 5 years bring compiler speed ups.
It probably will. Every release has a number of performance improvements although the release notes don't mention them. Great work like [1] [2] [3] weren't mentioned in the release notes because those notes mostly focused on feature improvements.
This work seems to be gaining momentum, if anything. The next version (1.44) will significantly improve the performance of programs that use async [4]. The next version of LLVM will be faster [5] (hopefully reversing the perf regressions in LLVM 10).
[1] - https://blog.mozilla.org/nnethercote/2020/04/24/how-to-speed...
[2] - https://blog.mozilla.org/nnethercote/2019/12/11/how-to-speed...
[3] - https://blog.mozilla.org/nnethercote/2019/10/11/how-to-speed...
[4] - https://ferrous-systems.com/blog/stable-async-on-embedded/
[5] - https://nikic.github.io/2020/05/10/Make-LLVM-fast-again.html
One of my favorite things with Rust is how other things are starting to use it. Just yesterday I started playing with Deno and was anticipating a severe lack of database bindings. Some people used the built-in Typescript->Rust message passing to make a tiny shim around the Rust bindings for things like MongoDB: https://github.com/manyuanrong/deno_mongo
I hope Rust adds the major anticipated features (GATs, const generics, specialization) soon, or alternatively decides to not implement them altogether. I'm a rather new Rust developer, but still I very quickly ran into the issue of needing a nightly version of rustc because one of my dependencies (PyO3) relied on one of these features. It would be awesome to have some periodic updates from the compiler team on the progress thereof.
There are many places to follow:
https://github.com/rust-lang/wg-traits/tree/master/minutes
https://github.com/rust-lang/lang-team/tree/master/minutes
https://rust-lang.github.io/compiler-team/
There's a youtube channel too. (Also start here: https://blog.rust-lang.org/inside-rust/2020/03/28/traits-spr... )
The important thing is the `chalk` work. And basically the rust-analyzer approach drives that.
Hah, this branch PR got merged 6 days ago into rust master: https://github.com/rust-lang/rust/pull/69406
Technically speaking, those are implemented, just not stabilized, if you’re using them in the nightly, they’re just disabled in stable.
So I would anticipate they will eventually stabilize like many other features have and become part of stable.
Just want to clarify that an implementation being present in nightly doesn't ensure a timeline for stabilization or even that it will happen, as unexpected bugs in the implementation or design of the original RFC might crop up once it's actually used that makes us take the decision to push the stabilization back. This has happened multiple times (from a single version delay to things that are perma-unstable). That being said, the majority of the unstable features you're waiting for "only" need baking time.
GAT and specialization still needs a lot of magic, mostly chalk/polonius integration, doesn't it?
We need more volunteers to help implement those features!
Is there a meaningful help that a Rust beginner can provide here?
Many tickets are scored with a difficulty, and sometimes comes with a mentor: https://github.com/rust-lang/rust/labels?q=E-
I think there are meaningful things a beginner can do. I'd be surprised if improving error messages would be terribly hard. And working on the small issues will help you become familiar enough to help with the bigger things.
Edit: Also perhaps a way to get some exposure to the rust compiler is to help implement lints (in clippy) - it's basically a compiler plugin and relies on the same things available in the compiler.
The D-papercut diagnostics tickets are in my mind a great source of these kind of newcomers tasks, as some are quite small and don't require full context of the codebase.
The killer feature of rust is rapid incremental improvement of the language. Every quarter something gets better.
That's a feature of several languages right now/at times.
I agree. That's how I've felt about Python with every release since 3.5
And yet there are people complaining it's too slow, or too fast.
I don't think there is any pace of language evolution that will never be criticized.
Rust has been really great at incremental language upgrades because of clippy. Stuff get deprecated, clippy issues warnings, buy the time support is dropped your code has been patched.
It feels like we are in some new weird software engineering world where the code is only alive while its actively maintained and worked on. The libraries that are shipped in the OS seem as constant and fixed in time as x86, basically completely abstracted by the constantly evolving software on top of it.
Rust is on track to soon--within ten years--become an industrially important language. I see no other language on the horizon that could take Rust's place alongside C++, in the places where they both excel.
That is not to say it will certainly become industrially important. It depends entirely on the numbers. Today, the total number of working Rust coders may be less than the number who start coding C++ in any given week. (This is a simple consequence of the difference between a base of thousands vs. millions.) But if it can sustain exponential growth long enough, and nothing else comes up in the meantime to take the wind from its sails, it should get there.
What’s the best resource to get started with Rust and make a desktop app?
https://areweguiyet.com/ gives a broad overview, but is slightly outdated i believe. I recommend all raph linus' research on this topic, fairly recent blog: https://raphlinus.github.io/rust/druid/2019/10/31/rust-2020....
Personally I used iced [1] a bit and found it very pleasant to use. iced is cross platform, sponsored and very active.
Well, at least read the small prints and footnotes of this GUI library.
> Iced moves fast and the master branch can contain breaking changes!
Even in general, this crate is not even 1.0 or stable for production use. I'd rather wait until it is mature before touching it. Until then, Qt is the way to go.
Search for the following in your browser: “the rust book” for the official free online rust book. “rustlings” for a set of code exercises to get you used to fixing your code when the compiler shows you an error. “iced github” for a cross platform gui library and, lastly, “rust by example book” for a different angle on learning the language.
I personally started with the rust book, then went from there.
There's always Electron, Wasm, and the few smatterings of decent GUI frameworks being worked on. Also https://github.com/tauri-apps/tauri which claims to be a... Light, more native Electron?
Just depends what you want. A common pattern is essentially to build your app as a Rust library or CLI binary that your GUI wraps in whatever is most convenient for the GUI
I've been working on postgres-extension.rs[1]. The idea is that, instead of writing a PostgreSQL extension in C (which is the only option for interesting extensions), you can also write one in pure rust.
I've eagerly awaited many features to make this work reasonably well, and I've been very pleased how much rust has helped my use case over the last few years.
Although lots of languages have a C FFI, that's really not enough to extend a complex codebase. Postgres has it's own setjmp/longjmp-based error handling, it's own system of allocators, it needs a way to find the right functions in an extension and call them the right way, its own way of dealing with signals, etc. There are zillions of internal structs, and the extension needs to be able to read/modify them without copying/translation. Oh, and also, postgres doesn't like threads at all, so the extension better not make any (at least not ones that call back into postgres APIs).
The only language even close to getting all of this right is rust:
* it has no runtime that causes problems with threading, scheduling, signal handling, or garbage collection
* typically GC'd languages can't operate very well on unmodified C structs; rust doesn't have a GC so it can
* rust goes out of its way to support C-compatible structs without any copying/translation, and can even treat some plain C representations as more interesting types in a binary-compatible way (like a nullable pointer in C could be treated as an Option<&MyStruct> in rust)
* the tokio library allows nice concurrency without creating threads if you use the CurrentThread runtime
* it supports changing the global allocator to be the postgres allocator
* rust has good procedural macro support, which is important because a lot of postgres APIs heavily use macros, so making the rust version ergonomic requires similar macro magic
Areas rust could be more helpful, but which I'm trying to address on my own to the extent that I can:
* Support for setjmp/longjmp. I know this is not easy, but important for interacting with C code that already uses it. I realize it would be unsafe, and that it can't be wrapped up in a safe way directly, and it would be delicate to create safe APIs that use it at all. But I still want it. I made a crate[2] that adds support, but it has a few issues that can't be resolved without compiler support.
* Better support for cdylib shared libraries that might call back into the host program that loads the library. Right now, you have to pass platform-specific flags to get it to link without complaining about undefined symbols (because they won't be resolve until the library is loaded into the host program). Also, it's difficult to test the shared library, because you have to guess at the location of the built library to be able to tell the host program where to find it before you can begin your test. I made a crate[3] to help with these things also, but it would be nice to have better support.
Oh, and one more thing on my wishlist not directly related to this project is that it would be nice to have support for datastructure-specific allocators (like a mini-allocator just for a hash table). Then, you'd be able to monitor and control memory usage by inspecting that allocator; and when you destroy or reset the hash table, you can know that the memory is freed/cleared as well (without worrying about fragmentation). Maybe these mini-allocators could also be used in other contexts, too, but it would probably be easiest to get the lifetimes to work out if it was tied to a data structure.
[1] https://github.com/jeff-davis/postgres-extension.rs [2] https://github.com/jeff-davis/setjmp.rs [3] https://github.com/jeff-davis/cdylib-plugin.rs
Is there a roadmap for the next 1/2/5 years?
One of compiler devs Niko Matsakis wrote a couple of posts about this
* Splitting the compiler into libraries so it's easier to iterate on them, while also making it easier to develop static analysis tools - http://smallcultfollowing.com/babysteps/blog/2020/04/09/libr...
* Improving the async experience - http://smallcultfollowing.com/babysteps/blog/2020/04/30/asyn...
We only plan out per calendar year, so we have a 2020 plan, but that’s it.
Rust mods have to stop listening to the elite language intelligentsia.
Successful eco systems are pragmatic and idiomatically straightforward.
Everything & the kitchen sink in a language is not a recipe for success.
Every language that has a long lifespan spent a long time in feature minimal stasis too. The world won't learn a moving target.
Rust is not particularly adventerous as far as PL theory is concerned: all of the ideas in it are actually quite old (just newer than the ideas in established languages, like C). It's fundamentally quite pragmatic and you see that in most of the decisions it makes. I also don't agree that minimal features is necessary for success (even though it may be a desireable attribute of a language). Many extremely successful languages have piled on features over time, some in a far less reasoned way than rust (C actually a big outlier in how slowly it has evolved: C++, Java, C#, python, perl, PHP, etc are all large languages, with complexity in terms of number of features similar to Rust).
Rust is barely a moving target since 1.0. If you only read the version releases it might seem so, but for the pragmatic programmer not much is changing. Many of the changes concern very special features that only a few libraries make use of. As a library user you don't need to learn them.
I learned Rust a few years ago and without keeping up with the latest changes too much I still feel confident I can work on current code.
Having these "very special features" means that there are some things with are added to the language but rarely used. That means that you might come across code in a library that you don't understand, especially if you're not using those features in your own code.
So, I think the argument that "very special features" shouldn't be counted toward language complexity/growth is wrong, IMO. I would even say that there needs to be even more focus on those features, since they tend to be not widely known, not familar, and often there is less documentation about them, so the likelyhood that they make code hard to understand is even higher.
This is not to say that those features are unnecessary. I just don't think the justification "they are not what a pragmatic programmer will see" is good.
If you look at the changes and cathegorize them they all fall in one if three camps:
- sugar for quality of life improvements that benefits everyone (like ? or NLL)
- new feature that lets you do something that couldn't be done before (like impl Trait or global allocators)
- new feature that removes a special case of the semantics that can be extrapolated from already present features (like associated consts and subslice patterns)
I'd say having special features is ok, as long as these criteria are met:
1) It's obvious the feature is being used.
2) The feature is easy to look up without knowing what it's called, just based on how it's been used.
3) It's easy to understand what the feature actually does, with the appropriate context.
The ? operator doesn't fulfill at least two of the three points, yet it's way more loved than hated. As a general guideline I agree with your points, but it is not to be taken as rigid gospel either.
The ecosystem _is_ one of my favorite parts of Rust.
Building C++ (or C) projects is such a clusterfuck that it's given rise to header-only libraries.
In Rust, everything is "cargo build", and adding a dependency is one line. This has only failed for me when there's a dependency on a C system library that I can't satisfy.
Is it bad to have too many dependencies? Sure, maybe. Is that an excuse to have artificial friction? No. I eagerly await the day when meson or conan or whatever becomes The C++ Dependency And Package Manager.
My other favorite part of Rust is the elitist language features like iterators, immutable borrows, functional programming, etc.
Related to this is how easy it is when looking at someone's code on say GitHub to (a) see how the whole project is structured - because it's basically always the same with the familiar landmarks of src/, cargo.toml etc, and (b) to track down the definition / implementation of a type or function (just by applying knowledge of the module system + looking at the mod and use statements). This is a completely different experience from trying to pick apart some C or C++, where you are, essentially, at the mercy of the implementor's idiosyncratic view of how code should be structured, and really need either an IDE or vast reserves of patience to help navigate it.
While I agree with you, I think you underestimate the number of features that help Rust be great in the face of a Lifetimes and Generics. One feature tends to lead to another. For example, Traits with Associated Types are amazing, but they then lead to a desire for GATs. GATs aren't (in this context) some fancy feature of its own, it fills in a noticeable void in an existing feature, Traits.
So many of these features Rust adds just fill in holes in an already extensive ecosystem.
I agree some feature development could be toned down to mitigate the moving target problem.. hell I think last years poll expressed roughly that. But, I think there's still a ton of features yet to come that merely complete what we already have.
Is the current status of Rust that it is slower than Go ? According to this previous post - https://news.ycombinator.com/item?id=23058147
I don’t think that was the conclusion.
“ as some keen HN commenters have pointed out, it looks like the rust program is not actually equivalent to the go program. The go program parses the string once, while the rust program parses it repeatedly inside every loop.”
I don't think it is. The top comment in that thread indicates why.
In general for most long lived workloads, I would expect Go to be approximately 15-25% slower than an equivalent C/C++/Rust program because of the CPU overhead of the Go GC. The Go team have done a lot of great work to optimize pause times and memory consumption though.
While Go may be faster somewhere, that post was not a good comparison.
See the discussion around it and the patches made by the community and the final results.
See the individual benchmarks: https://github.com/christianscott/levenshtein-distance-bench...
And just to help the lazy people, after making the benchmarked loads equivalent, the results are:
hyperfine go/out 'node javascript/main.js' rust/target/release/rust 1 Benchmark #1: go/out
Benchmark #2: node javascript/main.jsTime (mean ± σ): 1.888 s ± 0.013 s [User: 2.040 s, System: 0.045 s] Range (min … max): 1.875 s … 1.918 s 10 runs
Benchmark #3: rust/target/release/rustTime (mean ± σ): 4.257 s ± 0.033 s [User: 4.295 s, System: 0.042 s] Range (min … max): 4.221 s … 4.338 s 10 runs
SummaryTime (mean ± σ): 874.1 ms ± 50.8 ms [User: 5.688 s, System: 0.830 s] Range (min … max): 813.5 ms … 1001.9 ms 10 runs'rust/target/release/rust' ran 2.16 ± 0.13 times faster than 'go/out' 4.87 ± 0.29 times faster than 'node javascript/main.js'It looks like you got that from this PR: https://github.com/christianscott/levenshtein-distance-bench...
That PR does not make the benchmark loads "equivalent." Please look at the diff. That PR adds parallelism to the Rust program. The Go program does not have parallelism.
The Go and Rust program in that benchmark are really not equivalent. Rust is using threads.
Rust is usually faster than Go. Note the edit in the previous post.
Well, I would say it's faster than Go almost every time.
As someone who is not a particularly good programmer who has done quite a bit of coding in both at this point, it seems to be easier to accidentally write extremely slow code in rust than it is in go, but once you start optimizing, rust will generally be faster, sometimes by quite a lot. I’ve also run into fewer pathological cases as I’ve learned more of the idiomatic rust patterns.
And yet, a lot of experienced programmers have noted that even when they write their first time naïve Rust, it turns out to be really fast.
For example, Bryan Cantrill wrote about his experience in "The relative performance of C and Rust". I found the post fascinating.
http://dtrace.org/blogs/bmc/2018/09/28/the-relative-performa...
I have the opposite feeling. Every time I do something that could be expensive or incorrect, the language makes sure that I notice, which I find extremely helpful in writing fast code the first time around.
>it seems to be easier to accidentally write extremely slow code in rust than it is in go
Never felt about it this way.
likely depends on your background; if you come from GC languages and are used to everything-is-a-reference, you might end up surprised that Copy types are a thing. right after that you might want to box everything so .clone() works everywhere when you find that some types aren't Copy.
Yeah it’s definitely around using clones everywhere.
I don't know that clones are problems in and of themselves rather than what they do behind the scenes: allocations. Cloning an Rc is completely innocent, cloning a HashMap<String, Vec<String>> is deadly in a way you may not expect if you're used to shallow-copying hashmaps (or even deep-copying them).
And it's true that if you think of allocations as being no big deal unless you're aiming for very high performance, your Rust is going to be slow (also your C++ and your C).