My Rust 2021 Wishlist for the 2021 Roadmap

The Rust team has put out a call for 2021 Roadmap. So, I was thinking I would bring up a few things that I would love to see in the next edition of Rust:

  1. Argument dependant lookup in match
  2. unwrap_all()
  3. Coupling split() and join()
  4. Backwards compatibility

Argument dependant lookup in match

Consider this code for talking to a secondary processor:

#[derive(Debug)]
pub enum SecondaryProcessor {
    Opening(String),
    Ready,
    Writing(String),
    Reading(String),
    Closing,
}

When we match on it today, we have to do this:

let s = SecondaryProcessor::Ready;
match s {
    SecondaryProcessor::Opening(string) => { /* do something */ }
    SecondaryProcessor::Ready           => { /* do something */ }
    SecondaryProcessor::Writing(string) => { /* do something */ }
    SecondaryProcessor::Reading(string) => { /* do something */ }
    SecondaryProcessor::Closing         => { /* do something */ }
}

This is a lot of text. What can we do today? Well we can use use:

use SecondaryProcessor::*;

let s = Ready;
match s {
    Opening(string) => { /* do something */ }
    Ready | Closing => { /* do something */ }
    others          => { /* do something */ }
}

Now we add another connection to the system, for example a network connection and we want the same ergonomics:

pub enum TlsServerConnection {
    Connecting(String),
    Ready(Socket),
}
use TlsServerConnection::*;

But now let s = Ready; is no longer clearly defined.

With ADL-proposal

Argument dependant lookup would solve that problem inside match, if let and while let nicely.

let s = SecondaryProcessor::Ready;
let t = TlsServerConnection::Ready(...);

match s {
    Opening(string) => { /* do something */ }
    Ready | Closing => { /* do something */ }
    others          => { /* do something */ }
}
match t {
    Connecting(string) => { /* do something */ }
    Ready(Socket)      => { /* do something */ }
}

if let Closing = s {
    /* do something */
}

while let Ready(socket) = t {
    /* do something */
}

Upgrade unreachable_patterns warning to hard error

Currently this code only emits a warning:

match s {
    Opening(string) => { println!("Opening: {}", string); }
    Ready | Closing => { println!("Ready or Closing"); }
    others => { println!("{:?}", others); }
    _ => { println!("/* do something */"); }
}

With this proposal that warning must become a hard error so no ambiguiets rise:

error: unreachable pattern
  --> src/main.rs:18:9
   |
17 |         others => { println!("{:?}", others); }
   |         ------ matches any value
18 |         _ => { println!("/* do something */"); }
   |         ^ unreachable pattern
   |
   = note: `#[error(unreachable_patterns)]` on by default

Update 3: Allow macro creators to generate too many branches

A macro could generate code like this:

match s {
    #[allow(unreachable_patterns)]
    Opening(string) => { println!("Opening: {}", string); }
    Ready | Closing => { println!("Ready or Closing"); }
    others => { println!("{:?}", others); }
    _ => { println!("/* catch all from macro */"); }
}

That would allow the expansion to have a catch all for cases where a macro might not cover all cases but knows that the default is unreachable or unlikely.


Unwrap_all()

There are some APIs that return Option<T> or Result<T, E> and accessing nodes in a tree is usually riddled with these return types.

Querying can result in a type like this: Option<Result<Option<Leaf>, TreeError>>

So it would be nice to have an {Option,Result}::unwrap_all(self) or a macro unwrap_all!(Option<T>) that can handle both types similar to {Option, Result}::flatten(self).

Update 2: I implemented this partially: checkout unwrap_all on crates.io

Update: Added reference to flatten and prototype unwrap_all!

Considerations

One problem is that sometimes the use might not want to unwrap all levels, that would lead to code like this:

fn unpack_leaf(result: Option<Option<Result<Leaf, TreeError>>>) -> Option<Leaf> {
    Some(result.unwrap_all())
}

That one could be solved by a macro with a depth parameter like this:

fn unpack_leaf(result: Option<Option<Option<Leaf>>>) -> Option<Leaf> {
    unwrap_levels!(2, result)
}

Coupling split() and join()

One of the big steps forward in my productivity in Rust was when I fully understood Iterator.

I expected a join() operation over any Iterator such as Split:

println!("direct: {}", "ab,cd,ed".split(",").join::<String>("-"));

But that is currently not possible.

Workaround: collect()

println!("collect: {}", "ab,cd,ed".split(",").collect::<Vec<_>>().join("-"));

Future plans

Since this issues has been around, there is a tracking issue.


Backwards compatibility

This last one is a little more subtle. Most people compile their software on the same operating system as they intend to run it on.

However, sometimes one has to support an older version. As of today the two most popular options are:

1. Virtualisation or secondary machine.

Essentially revert

2. Containers

This one has gained a lot of popularity, since it solves the problem with very little overhead on most operating systems:

sudo docker run --rm --user "$(id -u)":"$(id -g)" -v "$PWD":/usr/src/myapp -w /usr/src/myapp rust:latest cargo build --release

This is a great solution for a developer building projects locally.

3. Continous Integration (CI/CD)

This one builds on containers. Now you can select an old enough container and store the resulting artefacts to a webserver.

links

social