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.
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.
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.
What I have seen is a lot of good work in the larger ecosystem. And the tooling continues to improve.
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.
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.
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".
// 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. 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!.
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).
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.
A pub quizz with them would be just as fun.
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?
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.)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.
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.
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.
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
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.