Hi everyone, I'm happy to announce the release of Aura 4.
Aura is a package manager for Arch Linux. Its original purpose was in supplementing Pacman to support the building of AUR packages, but since its creation in 2012 it has evolved to enable a variety of use cases.
Aura 4 represents a signicant body of work to port Aura from Haskell to Rust. The full motivations for this rewrite are discussed here. Overall, Aura is now much more performant, has a 4x smaller binary, and is much easier to install.
If you're an Arch Linux user and interested in trying it, see this AUR package: AUR (en) - aura
Otherwise, I'm happy to answer any questions that anyone has regarding porting a project like this from Haskell. Cheers!
Binary size does concern me, and I'm happy that I was able to reduce it by so much. cargo bloat and cargo deny are great tools for finding naughty dependencies.
rayon continues to be excellent. One of the reasons I was able to keep binary size so low is the complete avoidance of async / tokio, which is a massive source of bloat if you don't actually need it.
clap is a big dependency but absolutely paid off: the output of --help is vastly improved.
I do continue to miss Haskell's type holes feature, and I must say that where code beauty is concerned, Haskell is the clear winner.
Rust makes functional programming somewhat difficult. You mostly have to satisfy yourself with iterators, and you can't get too fancy with higher-order functions, function composition, etc.
While porting any software should generally be a last resort, in this case I believe it was worth it.
Sorry, what do you mean? Rust has partial inference, too, and you can also use the same trick to ask the compiler for types as you would in Haskell.
Oh, no, I have to fundamentally disagree on this one. Many features of Haskell are simply not properly designed and feel ad-hoc, especially the lack of coherence (in the Rust sense) in the typeclass system. Not to mention the myriad of {-# LANGUAGE #-} pragmas without which many useful/necessary features are not even supported in ghc.
This is quite the claim. It would help if you could prove your point with specific examples – I never found that functions don't compose in Rust, or that higher-order functions would be any more difficult to use than in Haskell. In fact Rust has the most uniform and easiest to compose typesystem of all the languages I have ever used.
As of the more recent Haskell2021 spec, most of the most commonly used extensions are now included by default.
Rust is not (syntactically) optimized for frequent usage of function composition. In general it is much cleaner and more terse in Haskell.
I'm a professional Rust developer and still need to look up the difference between Fn, FnMut, and FnOnce every time it comes up.
Lifetimes complicate the matter further.
Deref types (like &String -> &str) work automatically for normal function calls but not when passed as "function pointers" (i.e. you need a wrapping lambda).
I'm confused – how is that a disadvantage over Haskell? Haskell doesn't even have a comparable implicit conversion anywhere.
I'm sorry to say but that's not the fault of the language. It's as simple as calling by shared reference, mutable reference, and value.
"More terse" is not in itself an advantage, but yeah, Rust doesn't have the equivalent of a . operator. However, this is not a real problem in practice.
Yes, but they are necessary. They are not a limitation. Haskell doesn't have lifetimes because it's garbage collected – this discussion has been had too many times.
It was the second part: "but not when passed as "function pointers" (i.e. you need a wrapping lambda)" that is a point of friction. Rust has methods as well as top-level functions, but there's no way to call methods in a "point free" way. In this case, you can't call as_ref() (etc.) on their own, you need to call a wrapping lambda and this is extra verbosity.
Having written both languages professionally, I will professionally disagree.
I agree, this was not a criticism of lifetimes per se. I enjoy the control lifetimes offer in most cases. However, I have had trouble in the past with taming the compiler when trying to craft legal function signatures that involve borrowed data in higher-order functions / associated types / impl Foo in return position / etc. Am I just an idiot? Perhaps, but maybe certain formulations in the current form of Rust are also harder than they need to be.
Interesting! I've never thought about replacing tokio with rayon because I thought it was specifically designed for data parallelism, which is less common in the type of work I do. I will take a look at Aura's code for usages ^.^
Rust functions don't compose well and you have to resort to data composition like the iterators do. At work, we built a streaming library for remote data access using the same technique. It is a lot of work when you mix async code.
To me, making abstractions in Rust is less idiomatic because references and lifetimes get in your way. Paradoxically, this is something I like about the language because somehow limits the complexity of your code. I found too many Haskell projects that are unnecessarily difficult to maintain do to the use of language features.
My usage of it is: "Just do these things in parallel based on the number of CPUs I have". No need to get async involved, which is better for long-lived processes and ones where you need massively more Futures than CPUs you have.
I was one of the "Simple Haskell" proponents and can recall two distinct views I held at the time:
You probably don't need lenses.
The only useful monad transformer combination is ReaderT IO.
And this is coming from someone who went down the Extensible Effects rabbit-hole and came back out again.
Rust goes a bit further, making the IO implicit and instead giving Result / Option prominence as the only available Monads. The nom parsing library is Rust is very Monadic, but the monadic binding is entirely manual, making you pass along the parse progress by hand.
I can see it's mostly about packages checking, deps verification and downloads, where rayon makes a lot of sense.
My experience with nom has been day and night compared to megaparsec exactly because of this. It's a price I am willing to pay in exchange of simplicity in a professional setting.
I can see how it would be more effort than eg in Haskell, and how that can be annoying at times.
But I don't think it can be validly argued that it's "less idiomatic", since any idioms used in code written in - and by users of - some language L are in fact defined by L.
When you say "idiomatic" there I'm going to assume you mean "more difficult".
Anyway I'm curious as to what you mean. Can you give a short and sweet example?
Aside: Personally I loath this word "idiomatic" as usually used in the context of programming languages. I always thought of "idiomatic" as the dictionary defines it:
a group of words established by usage as having a meaning not deducible from those of the individual words
For example "You are teaching your grandmother how to suck eggs".
Which is, as far as I can tell is not the intended meaning when used wrt programming language use. In programs I'd rather people wrote what they actually mean in their code. Anything else is obfuscation.
In the context of software development, "idioms" are the patterns in the code that come up "naturally" because they help to keep the code tidy and organised, so are more frequently used.
Idiomatic is in the context of a particular programming language.
Idiomatic C++ is different to idiomatic rust.
While one would hope that what is idiomatic for a particular language ends up also being ergonomic, elegant, better. This isn't necessarily so. It's defined as what is most commonly used by programmers familiar with the language.
To use an idiom: When in Rome, do as the Romans do.
OED and other dictionaries are descriptive rather than prescriptive. If enough people start using "idiom" to mean "boiled egg" the OED would add that as well.
Bottom line is that I still don't know what it means w.r.t. Rust. I had a little idea with C where you see things like arrays being iterated through by incrementing pointers rather than what might be the more obvious array indexing and so on. These things can be a bit harder to grok at first but one sees them so often the intent becomes clear.
So, if clippy shuts up about my code that is idiomatic enough Rust for me
Clippy's raison d'être is to enforce an idiomatic style of Rust on people; it's lints are all things where the other style is valid Rust, but it's not idiomatic for one reason or another (anything from matters of taste through to usually wrong, even though it's valid code).
I have a wee bit of a downer on the discussions/suggestions about "idiomatic Rust" (clippy aside) because so often I have seen here that ten posters can post ten different idiomatic solutions to such questions. If so many people see it so differently where is the idiom?
Also a few years back when new to Rust people would have many different idiomatic solutions to my questions, many of which I thought were way over complicated and carrying too much conceptual package for they do. Worse still they often turned out to be detrimental to performance.
I'd argue that this is where Clippy's allow-by-default lints really shine. You can have an allow-by-default lint (with or without an automatic fix) that pushes you down a specific "idiomatic" route; if there's general agreement that this is a good thing, that lint can become warn-by-default, while it can stay allow-by-default if there's a "hard core" group complaining that the rest of us get Rust wrong.
For example, clippy::semicolon_if_nothing_returned qualifies as a "possible future idiom" in this view; it's an allow-by-default lint, for something that's semantically and syntactically valid, but if people agree that it's a bad idea in "good" Rust code, it can become "warn" or "deny" by default.
And such lints can be left to wither gently; take clippy::implicit_return, which is generally accepted to be linting against idiomatically correct code. That's still there, but will remain at allow-by-default forever if not removed.