This is my response to the call for blog posts on what Rust development focus ought to be during 2020. Much unlike my extremely late post for 2019, which focused entirely on compiler implementation details, the focus is on things that affect the end user experience, specifically the experience past the initial rose-coloured glasses phase of adoption.
Figure out paths towards stabilisation of features
I maintain a couple of Rust libraries. Two of them, libloading and psm, share
a peculiarity: both sacrifice API safety in return for ability to compile on stable versions of
Rust. The two features in particular are never_type
and unboxed_closures
. To give
an example of where such a sacrifice has been made, I’ll reproduce this snippet from psm
:
/// Run the provided non-terminating computation on an entirely new stack.
///
/// <snip>
///
/// `callback` must not return (not enforced by typesystem currently
/// because `!` is unstable), unwind or otherwise return control flow
/// to any of the previous frames.
pub unsafe fn replace_stack<F: FnOnce()>(
: *mut u8, size: usize, callback: F
base-> ! { ... } )
With unstable never_type
compiler prevents me from specifying F: FnOnce() -> !
as the bound.
There is no choice but to document this invariant as free-form text instead, which is not an ideal
way to enforce such a critical safety invariant.
Luckily, never_type
is on its way to be stabilised and, presuming no
complications, should be available in stable compilers early 2020. unboxed_closures
, on the other
hand, are in a long-lasting limbo and might remain unstable for the foreseeable
future.
I would like us to dedicate some of the time during 2020 to go through all features sharing
unboxed_closures
’ unfortunate state of matters and evaluate the impact these features
have on the ecosystem, figure out what a path towards stabilisation might be, etc.
We want a feature-complete type system
Both never_type
and unboxed_closures
are, ultimately, features that make our type system
better, more expressive and more complete. Just a “more complete” does not cut it anymore, we want
a complete type system! To that end, there are at least a couple improvements to be made and
features to be implemented. On top of everyone’s list there are these inevitable items:
- Generic associated types;
- Lazy normalization;
- Constant generics;
- and many more…
I am well aware of the on-going (ground-)work necessary to implement most of these (see e.g. chalk). However, seeing how impactful the lack of these features can be to Rust code targeting stable, I’d like there to be more focus and clarity on progress of these features.
Lets work on Cargo…
Speaking of “feature-complete”, Cargo’s lack of support and polish for many tasks off the beaten path comes to mind.
Cargo is the standard build system for Rust code, and by its nature Cargo will define what
build-time patterns are used by Rust code in the broader ecosystem. For example, many crates
use build.rs
as their tool of choice to link to external libraries, sometimes it is used to
generate code, etc. Alas, build.rs
is also often attributed for major difficulties integrating
Rust code into alternative build systems with reproducibility as their core value. There are a
couple of things we should address with build.rs
specifically: introduce sandboxing and perhaps
make it not Turing-complete; introduce a way to list input dependencies and forbid access to other
resources; perhaps introduce capability-based security model.
There are a fair amount of papercut issues that involve features and weird handling around dependencies, dev-dependencies and build-dependencies. Inability to use features with dev-dependencies and cargo inadvertently conflating features for different kinds of dependencies (1, 2) are easy to accidentally stumble upon. Cargo is the frontend to the Rust compiler. Leaving a bad impression here will affect Rust as a whole. These papercuts must get no less attention than we give to language ones (e.g. borrow checking).
Less universally relevant… with embedded use-cases and new targets like wasm and RISC-V gaining in popularity, Cargo needs to improve and extend its support for cross-compiling Rust code. Obtaining and customizing target sysroots and using them come to mind as a major pain point here.
…and improve debugging/instrumentation
There is more to software development than edit-compile-run cycle. This being Rust, we do deal with a lower bug density, but they still exist and need to be dealt with.
The first tool developers are likely to reach for is a debugger. While our debugger support is not bad per se, it isn’t stellar either. It is possible to print out a sensible stack trace and look at a variable. You might even be able to call a function, much like when debugging C! As long as it is not a method, that is. Or a generic function. Printing out an element in a vector or a slice is also a non-trivial task.
I suspect that a full-featured expression execution in a debugger is a hard problem to solve and will take us more than a single year of focused effort, but we already have some foundations with e.g. miri.
First class support for binary instrumentation is an easier problem to tackle. Fuzzers, sanitizers and similar invasive tools are all examples of binary instrumentation and they have been in active use for long enough to demonstrate their benefits. Both within and outside of the Rust ecosystem. We should make these a first class feature.
Smooth and shiny!
Work done throughout this decade has resulted in an extremely nice and well thought out language. Now, that the adoption rate is soaring, we need to ensure that the first impressions, a major driver of Rust adoption, last throughout the less typical tasks as well.