Skip to main content

Rust in 2020

Wow what a year for Rust. 2019 has flown by (in real life as well), with so many developments since the beginning of 2019. The 2018 edition has brought so many quality of life improvements to day to day Rust development, particularly the more intuitive module system, NLL, and proc-macros.

Rust 2020 has a lot to live up to!

Well…

My thoughts for Rust 2020 are perhaps a little less aimed at new features I want to see than some other roadmaps, but more towards continuing the quality of life improvements, and expanding the ecosystem. My thoughts can be grouped into:

  1. Simplicity
  2. A Greater Ecosystem
  3. Compile Time
  4. Misc

Simplicity

Rust has never been a very simple language. Not like go or C. Certainly not as bad as C++. I enjoyed being a beginner in Rust, being helped1 by the compiler at every step. The language for a beginner was relatively simple: functions, structs, traits and enums. This made for a very powerful language (particularly the enums which are my favourite), yet a small surface area.

As I moved on I started looking more at generics (coming from C++ this was not too complex for me) and traits in greater detail, but it was still not unsurmountable.

When I look at Rust code today, I see quite a lot of intimidating complexity. For example this function definition from diesel:

impl<T, U, Op, Ret, DB> QueryFragment<DB> for InsertStatement<T, U, Op, Ret>
where
    DB: Backend,
    T: Table,
    T::FromClause: QueryFragment<DB>,
    U: QueryFragment<DB> + CanInsertInSingleQuery<DB>,
    Op: QueryFragment<DB>,
    Ret: QueryFragment<DB>,
    {
    // ...

Or this seemingly magic incantation for command line parsing from cargo-edit:

#[derive(Debug, StructOpt)]
struct Args {
    /// Crates to be upgraded.
    dependency: Vec<String>,

    /// Path to the manifest to upgrade
    #[structopt(long = "manifest-path", value_name = "path")]
    manifest_path: Option<PathBuf>,

    /// Upgrade all packages in the workspace.
    #[structopt(long = "all")]
    all: bool,

    /// Include prerelease versions when fetching from crates.io (e.g. 0.6.0-alpha').
    #[structopt(long = "allow-prerelease")]
    allow_prerelease: bool,

    /// Print changes to be made without making them.
    #[structopt(long = "dry-run")]
    dry_run: bool,

    /// Run without accessing the network
    #[structopt(long = "offline")]
    pub offline: bool,

    /// Upgrade all packages to the version in the lockfile.
    #[structopt(long = "to-lockfile", conflicts_with = "dependency")]
    pub to_lockfile: bool,
}

I’m not singling these crates out by any stretch. I also understand that the trait bounds in the diesel case enable a great amount of power, and I admire the amount of clever code that is going on behind the scenes of a crate like structopt, but it is a lot of additional boilerplate or magic.

I think Rust inhabits a nice space in that it’s not too simplistic to fail to describe common problem domains in the type system in a satisfying and reassuring way2, and it’s not so complicated that is painful to read a page of Rust code. I think for 2020 I’d like to ensure that this last point doesn’t happen. I’d like to see Rust be cautious in adding new sigils. The process from explicit error handling -> try! -> ? was excellent in my opinion3. It was quite slow, and a lot of input was gathered from the community4.

My worry is that Rust will start including magic items and follow the path of Haskell, where every library invents new operators (e.g. Parsec has <|> and <?>, lens has %%~, ^.) etc. This hides a lot of information from new users.

One area Rust shines in is documentation. A lot of this “magic” can be overcome with good documentation and extensive examples.

A Greater Ecosystem

I come from a Python background. Due to it’s long life has had a great ecosystem developed around it, with stable mature packages. My background is in scientific computing so it’s great to have the pydata ecosystem to play with.

I recognise that these packages have taken a long time to formalise. I started working seriously with numerical Python around the time of numpy's unification of the numerical Python libraries.

I would love Rust to gain the stable and mature crates that it needs to be a world-beating language. I love the way that Rust interfaces with C and has really gained a lot of momentum wrapping existing code quickly.

Part of this maturity and stability must come from consensus from the community. I hate to see the fragmentation between different implementations of the same thing (e.g. async-std/tokio, or grpcio/grpc). From what I’ve seen, the usual progression is at least:

  1. sync crate
  2. async crate with futures 0.1
  3. async crate with async/await5

Again this must occur early as people experiment with different implementations (and as mentioned during the latest Rustacean Station episode, Rust as a low-level systems language often cannot force an implementation on people as users have different constraints), but I’m looking forward to the time when I’m looking for a crate and there is an obvious well-tested option that interfaces with the rest of the crates well. I suppose this means my wish for Rust 2020 is for it to skip 10 years. Should I just wait?

Compile Time

I am a huge fan of pushing “things” (functionality or error detection) to compile time. Yes this adds to the compile time of Rust crates (which is a sore point for many people, myself included) but the extra confidence gained from knowing that if my code compiles it probably works is a huge relief. I would prefer to have a long compilation process in exchange for the constant threat of runtime errors.

I am a great fan of the compile time checks Rust already performs, and the type systems which make modelling complex scenarios a breeze. I am very happy that more and more is able to be const, so calculated at compile time. I’ve been watching Jonathan Blow’s screencasts on his new language with great interest. He is aiming for different trade-offs to Rust. His language is6 completely interpretable so extremely capable at compile time code generation. The ability to push computation to compile time and save valuable runtime resources is a great benefit. I can see that benefiting systems built in Rust as well.

I think, and will always think that the more effort that is put into compiler warnings, lints and errors the happier I will feel. I’m really happy that this is considered so highly within the Rust community (as is documentation generally).

Misc

Here we have a grab-bag of items that are not worth separate headings:

  • I’d love command-line documentation of items, ideally the same interface for stdlib and crates (as per pydoc/godoc). I’d love to work on this feature myself as it’s so close to my heart.
  • As mentioned before, compile times are always a problem.
  • I would love to see some of the new error handling libraries make it into the standard library. I can see progress with e.g. anyhow/thiserror.

Wrap up

I know this post has been very contradictory. I want to have faster compile times, yet push more complexity to the compiler and code analysis. I want to have a stable ecosystem, but with a wide variety of functionality. I want to have a simple yet extremely expressive and powerful language.

I think this completely summarises engineering. Engineering is about choosing the best trade-off between multiple solutions. Finding a middle ground.

If any community can do this, the Rust community can.

- Simon


  1. helped is one way of putting it ↩︎

  2. https://golang.org/ ↩︎

  3. https://thefeedbackloop.xyz/stroustrups-rule-and-layering-over-time/ ↩︎

  4. another thing Rust is excellent with: getting community input on big ↩︎

  5. ok so this may be because of the excitement around async/await, hopefully we will be able to skip step 2 in the not-too-distant future. ↩︎

  6. afaik decisions. ↩︎