My Goals for Rust in 2018
The Rust project has requested blog posts about the project’s goals for 2018. I found myself in pretty much complete agreement with Nick Cameron’s post, so I thought instead I would write about my own personal goals for Rust in 2018. I am fortunate enough to work on Rust full-time; modulated by the work that needs to get done to accomplish larger team goals, these are some things that I’m individually very motivated to make progress on in 2018.
That is to say, while this does not make for a complete vision of a roadmap for 2018, I would be very eager to have a roadmap which could be inclusive of these smaller goals.
Close out the ergonomics initiative
I’d like to see the ergonomics initiative from 2017 wrapped up. This means a few things.
First, there are a small handful of remaining RFCs which were postponed. In particular, right now I’d like to revisit these possible extensions in more detail:
- Making error handling a bit more ergonomic, along the lines of extending the
system around
?
. I’ve posted a pre-RFC sketch of my own viewpoint of how this should change. - Considering a zero-cost coercion from
&'static str
toString
. The weirdness around string literals is currently a major stumbling block for new users, who have to learn about having multiple string types before they have learned the motivation for them. Details about how this could be zero-cost, and some trade-offs. - Revisit the question of absolute path syntax. In the finally accepted modules RFC, there is one remaining open question about the syntax of absolute paths. I’d like to try to find a settled answer to that. I’m not otherwise interested in moving further with changes to the module system other than what is in the RFC.
On the other side, I’d like to reach a final decision to ship or drop all of the ergonomics RFCs that were accepted. I hope to see this completed over the next several months. During the RFC phase, one of the most common bits of feedback from community members was that too many ergonomics RFCs were in flight at once. This was also very stressful on team members as well. Now that we’re going to begin considering stabilizing some of these features, I hope we can have a clearer (and more spaced out) schedule for this so that their FCPs are staggered.
Write libraries to support network services
I think network services are a great opportunity for Rust in several respects. On the one hand, Rust is well suited for services at the extreme end of the performance spectrum: when you need complete control to manage the trade offs between memory usage, throughput, latency and up-time, Rust has the potential to be a great fit.
On the other hand, I personally believe that Rust’s type system is great for building “habitable” software - software that is easy to maintain, refactor, and grow over time. Since so many software applications are implemented as network services nowadays, it would be great to be able to bring these benefits to more users, even if they don’t need such fine grained control over performance.
Lastly, it is just the case that many users learning a new language reach for implementing an HTTP service as one of their first experiments. These educational projects have neither of the concerns I just laid out - they are neither performance critical nor huge code bases - but we should still have a pleasant onboarding experience for these users as they try to learn Rust.
A lot of people have put a lot of work into building out great building blocks for Rust network services. I want to focus my attention on libraries which provide the user-experience polish on top of these fundamentals, to help users more ergonomically assemble those libraries into a complete application.
Improve support for dynamic types
Last year, we made great progress in the design of several important language features that I hope will be fully implemented and stabilized this year:
- Non-lexical lifetimes
- Generics in associated types
- Const generics
impl Trait
and existentials
Noticeably, all of these are about the static type system - every one of them is something that gets fully resolved at compile time. There are several ideas to move even further with these features (higher kinded polymorphism! dependent types!). But realistically I think the design has already pulled too far ahead of the implementation, and we’re unlikely to keep extending the type system in the same manner in 2018. We have to get more experience building systems with the features we’ve already accepted.
On the other hand, our support for dynamic features of the type system has
lagged beyond, seeing very little update since its original design before 1.0.
We’ve got this rather unfortunate situation in which there is a steep cliff as
soon as you start using either trait objects or dynamically sized types,
because of things like object safety and ?Sized
.
I don’t have a concrete plan, but I’d be really eager to explore the design space for how we could make the dynamic parts of our type system more ergonomic and more expressive. Dynamic dispatch and dynamically sized values are both very useful language features, and they should be first class citizens of Rust just like its static features.
Several very vague potential design directions:
- Allowing trait object values that are directly owned, instead of indirected through a reference type, possibly using alloca.
- Downcasting trait objects using a pattern matching type of syntax,
deprecating the methods of
Any
. - Implementing custom DSTs using an unsafe trait that allows users to specify their representation.
- Make (some) object-unsafe traits object-safe by providing an implementation for the trait object type.
Several of these already have RFCs, but what I’d like to do is revisit the entire space of possibilities and try to find how these different components could interlock to create a cohesive design which is easier to use than the current system, rather than just adding new features to it piecemeal.
Integrate more workflows into cargo
One of the great things about cargo is that it creates a standard workflow across almost the entire ecosystem. When you start a new project, no matter what application domain that project is in, you don’t have to worry about how you add dependencies, compile your code, or run your tests - you just use cargo.
I’d like to see that functionality extended into more areas of the workflow. We’ve seen several frameworks and projects that introduce their own CLI to accomplish things that cargo doesn’t have functionality to support - what I’d like to do is make it so that cargo can support more of that space, so every time you start using a new framework or working in a new domain, you don’t need to learn new workflows.
Two examples:
- Many frameworks will have a CLI which generates new projects or stubs for new
components of your project, so that you can avoid the boilerplate. It’d be
great if cargo were able to perform this stub generation for you, using stubs
that were distributes through a registry. Imagine just having to write
something like
cargo new rocket/app my-website
, for example. - We’ve seen several projects like xargo and wargo which download and build the toolchains you need to target different platforms. Consistent with Nick’s suggestion that cargo integrate rustup’s functionality, it would be great if it also supported a way to download even the more unusual components of the toolchain you might use.
The more that the user experience of different frameworks operating in different domains is the same, the easier it is for users to transfer their knowledge from one domain. It would be great for cargo to enable a world in which Rust can be a bridge from one domain for another - imagine a web developer who writes a web app to learn Rust, but then wants to try writing an embedded project - this shift is much smaller the more the toolchain and language can be the same in both cases.
Stabilize generators and async/await
This is the biggest and most impactful project that I want to accomplish this year, and I’m very excited about the payoff it could have. I think it’s very important - especially for network programming - to see generators and async/await stabilized in Rust.
The ergonomics issues with async programming in Rust right now have been amply
documented. Some of these issues have to do with the specific APIs exposed by
tokio (which are being revised), but some are fundamental to the
way the current futures combinators work. Users often experience having to put
everything into Arc
s and clone them when they have call a future in the
middle of their function, all of which feels unnecessary and silly.
The issue underlying this is deep to how Rust’s ownership system interacts with
async code, and the current implementation of generators doesn’t resolve it. In
the current, unstable generator system, users cannot use references across
yield points. This results in the same sort of Arc
and clone everything mess
that futures sees today. Before we can consider generators and async/await
shipped, I think it is imperative that we solve this problem. Users must be
able to program in generators/async functions using the same patterns they use
in regular functions.
Unfortunately, fully resolving the type system questions to enable this has seemed quite distant, though there have been some exploratory designs that solve part of the problem. Its been very unclear what the path to fully shipping generators is. This post isn’t the place to go into details, but during the Mozilla All Hands in Austin, several of us discussed a path toward stabilizing generators - including ones with references in them - by the end of 2018. If this intrigues you, you’re in luck: I plan to write more about how this could happen over the rest of January.
I think having stable generators and async functions would be a game changer for Rust, because it would put our async IO story on a level footing with languages like JavaScript, C#, and Python. I believe that we’ll end up with code samples that look remarkably similar to code in those languages, but with much better performance. In other words, async functions would democratize the performance wins that futures demonstrated initially.
My overall goal with Rust
All of these goals for 2018 tie together with my larger goal for my own work on Rust. Fundamentally, I believe that Rust has the potential to be an exceptionally empowering technology for people writing programs. I am trying to focus on providing the ’last mile’ of user experience to take the core technological achievements of Rust and make them generally ergonomic and usable by working programmers.
Over the last few years, Rust has developed a bit of a reputation for being hard to use. To some extent, I think this reputation has been unfair, but there is a kernel of truth to it. The inherent complexity of using Rust - of learning about ownership and borrowing, primarily - is much greater than the inherent complexity of using most other languages. I want to smooth out this learning curve and make Rust as nice as possible to use so that this idea that Rust is “difficult, only use it when you need it” can be supplanted by a notion that Rust enables users to accomplish things they couldn’t in other languages, with little downside.
I want to see the barrier to entry for “systems programming” to fall dramatically. Rust is not just a language that forces you to follow best practices (“eat your vegetables”), it is an automated assistant that helps you to organize and manage the complexity of the project that you’re working on. When a programmer with experience in higher level languages begins to use Rust, the space of programs that they now have the technology to write expands dramatically. I want to see the kinds of programs that emerge when systems programming knowledge is widespread and easy to acquire, when anyone with an interest can take the skills they already have and use it to start tinkering in domains that once might have seemed inaccessible to them, such as operating systems, network protocols, cryptography, or compilers.