Abstract: Rust needs a language for how to design a language. Some words and phrases have emerged over time, but they can mean different things to different people.
Preparation: Bring notes on what you think makes Rust an effective programming language – and how it can be more effective. We'll read through one another's notes and Tyler will share the draft he's been iterating on.
Artifact: A preliminary set of well-understood pithy phrases that capture the essence of what Rust strives to be, a great programming language. We will try these phrases out throughout the rest of the summit and revisit them at the end.
What I'm aiming to do here is shift the conversation from talking about properties of a great programming language to properties of great code. After all, if we do our job right, the language fades into the background and what people interact with day-to-day is the code of the projects they work in. A great programming language supports and encourages writing great code.
This would ideally be fleshed out with both positive and negative examples of how Rust does/does not support them, but also generally what it feels like to work on a project with/without them.
Great code uses static checking to preserve its invariants and makes invalid states unrepresentable at runtime. A great language supports this with a flexible, strong type system and supportive tooling.
Great code brings only the important characteristics of your application to your attention. It avoids wading through needless complexity to express an idea. Complexity that is needed in some applications will not be needed in others.
Great code can be written and understood iteratively, emphasizing different characteristics at different levels of abstraction. It leverages concepts, ideas, and syntax from other programming paradigms when they are a good fit, but it introduces new ways of expressing a solution when they are not.
Rust code written in the natural way performs well across all common environments (operating systems, architectures, etc). When that isn't enough, Rust lets you take full advantage of the capabilities of your machine by replacing building blocks with your own.
Great code is built from smaller building blocks that are easy to understand. It leverages existing libraries where appropriate, composing them in high-level ways to express its aims.
Great code has the flexibility to accept changes over time, from a diverse array of contributors, without losing confidence in its correctness. A great language allows itself to evolve alongside your code and the libraries you use, without stepping on your toes.
(Note: This isn't intended to be a complete self-contained list of all the most important properties of Rust. In particular, this doesn't attempt to duplicate existing proposed goals. Rather, it's a list of important properties, some of which may be not be conspicuous in their absence. Some of these may be possible to combine into others, and the complete list is definitely too long for a pithy set of design goals. They may still be useful as condensations of common ideas.)
If it compiles, it's probably correct - our static checking and our linting are shockingly good. Relatedly, the most obvious thing is probably the right one, and if it isn't, it shouldn't look correct and compile but be wrong.
Rust is predictable - Rust never surprises you unpleasantly (but sometimes surprises you pleasantly). You're never going to use some construct of Rust and have it behave wildly different than you expect, or end up on a days-long debugging adventure because the language did something "behind your back" or something spooky at a distance. You might not know everything about Rust, but unknown unknowns should be rare, and when you learn something you should feel like "I didn't know that but that's fair enough and now I do", rather than "I shouldn't have had to expect that, that's absurd!". You shouldn't need an adversarial/security mindset to think of things Rust might do in response to things you thought straightforward.
Rust keeps getting better - We don't stop at some point and say we're done (or done trying); as long as our users continue to have new or unmet needs, we consider all options on the table, whether in the language, library, tooling, or ecosystem.
Rust is Rust, everywhere - Rust can go anywhere, and it's the same language everywhere. You might have some added constraints, but Rust tries to help you meet them.
Nobody puts Rust in the corner - We treat "impossible" as a challenge. We adapt advanced research ideas into accessible concepts for everyone, and help people more than people expect a language can help. Editions help us here, so we can preserve compatibility and never have a Rust 2.0 while still fixing issues that would otherwise persist for historical/compatibility reasons.
Rust is for everyone - You shouldn't need to have read research papers to understand how to use Rust, or how to use other people's Rust code. When we make advanced features available, we do so in an accessible way that keeps complexity contained. For instance, while a more complex type system could support writing libraries that catch more incorrect code at runtime, we don't want the result to be excessively hard to work with. "If it compiles, it's probably right", but shaving down "probably" isn't worth leaning too hard on "if" and making the statement feel vacuously true. (Closely related to the next point as well.)
Rust is a part of its own community - The Rust project doesn't hold itself apart from its ecosystem. Rust co-evolves with its ecosystem. We contribute to the ecosystem and it contributes to us.
Orthogonality - Rust is not a twisty maze of special cases. Everything you learn about Rust applies in as many places as it can, as similarly as it can. This makes learning new things about Rust often multiplicative, rather than merely additive; it also means we can cover maximal area with minimal surface. When it doesn't apply, Rust tells you that, and anticipates that you might have expected it to. If we use a construct from other ecosystems, we don't make it surprisingly different. We try to enable libraries to provide orthogonal and multiplicative functionality as well, with features that make it easy to combine X and Y.
Rust tooling is first-class - we don't stop with the language and library. rustdoc, testing, build system, packaging. We don't stop at some point and say "that's not our responsibility", or have excessively strict partitioning. We care about the user's experience and try to make it better, whatever the best way to do that is. (Though we do need to improve cross-team coordination on this front.)
Including for reference…
Prior version for Josh =) https://nikomatsakis.github.io/rust-design-axioms/#what-we-strive-for
(These are extracted from an earlier document, Thoughts on Rust Beliefs.)
We want Rust to be an industrial language. Famously, Haskell has the unofficial motto, "avoid success at all costs". Conversely, Rust has a naked ambition for wide adoption. This too is a belief that is central to what Rust is.
There are, however, some prices that we have not been willing to pay for that.
Some other languages have achieved wide adoption by keeping low their ambitions for advanced language design. It's an entirely valid approach to win by providing a rock solid implementation of only well-trodden and uncontroversial ideas.
Rust has not taken that path. We have embraced an innovative borrow checker, algebraic data types, generics, advanced type inference, closures, expression-orientation (even e.g. for if
), existential types, macros by example, procedural macros, async/await (in our approach, coroutines in disguise), and much more.
(One story, relayed to the author by Eric Holk, is that the Rust team once flew Dan Grossman, the designer of the Cyclone programming language, out to the Mozilla offices to pick his brain about how we might build an effect/capabilities system into an industrial language, and that the borrow checker was an outcome of that.) // niko: not…entirely accurate, but ok.
Many language designers, particularly prior to Rust, would have balked at including even just one of those powerful features in a language meant for industry. We seem to believe that an industrial language can be more.
Relatedly, we tend to look for principled solutions to problems. This is an artifact of being willing to accept general and powerful language mechanisms. A language that is not willing to accept powerful mechanisms has to make do with many special case solutions for various problems. But a language like Rust can adopt something closer to what Guy L Steele wrote in the Scheme reports:
Programming languages should be designed not by piling feature on top of feature, but by removing the weaknesses and restrictions that make additional features appear necessary.
Perhaps we would make some edits or adjustments to this. But judging by our actions, we seem to hold as a belief something to this effect.
Rust is a powerful language on two fronts. One is that Rust allows us to precisely control the machine. This is often the first thing people think of when considering the power of a language.
But Rust has another kind of power: our systems for automatic proof checking.
(Indeed, one way to look at much of Rust's evolution is to see it as us having continued to extend our proof checking systems to cover more of the code that people write.)
One of the unique things about Rust is how far we go on both of these fronts. Many languages with powerful proof checking give up on allowing for precise control of the machine, and most languages that allow for precise control of the machine give up on powerful proof checking.
The fact that we choose both here helps explain both why Rust is so particularly difficult to design and implement (and sometimes, unfortunately, to use), and yet it also explains at least one of the reasons why Rust is so loved.
Our willingness to do this hard thing seems to point to some important set of core beliefs that we must hold.
To find the set of our general beliefs, we may need to work backward from some specific beliefs that we unquestionably hold. One such belief is that Rust should have no runtime. A great number of extremely hard problems in the design and implementation of Rust would become much less hard if we were to accept garbage collection or other significant implicit runtime behaviors. We choose not to do that and to tackle the hard problems instead because that's what Rust is.
Of course, we do have reasons for this. If Rust were to have a runtime, then it couldn't hope to replace C everywhere that C is used. And while we try politely to not speak of our plans for world domination, we do treat as an invariant that anything that would preclude replacing most modern uses of memory-unsafe systems languages must not be done. This is the essence of a core belief.
This belief that Rust should not have a runtime plausibly dominates most other beliefs. Even if we could make Rust programs more reliable or performant by adding a meaningful runtime, we still probably would not do it.
It's easy to take it for granted, but we have particular beliefs about stability that are not commonly held outside of our ecosystem. We've written some of these particulars down, e.g. in RFC 1122. Still, when enumerating our beliefs, it may be worth writing this one down too, as in practice, our belief in stability can override many other outcomes we might want. Of course, our desire for stability is not unbounded, and we do carefully trade it when needed for other things (e.g. soundness). As with most of our beliefs, it is intertwined with them.
When we're discussing stability, it's worth us also being clear about what kinds of things we don't believe in that others might still call stability. This goes beyond the exclusions to our stability guarantee and includes our thinking on e.g. minimum supported Rust versions and the rate and desirability of language evolution.
Rust isn't waiting for a successor language. We're designing the successor language to Rust for every release.
Niko: Like pithy phrases, looking forward to using those.
Josh: +1. Think Niko or Tyler's drafts are good as starting point for that. Think others, including my own, have captured other items that might not be present, but aren't the right starting point for merging into. Would like to see a "standard library" of phrases (Niko: "strategies") that we can cite and have common meaning and understanding, whether or not we consider all of those phrases equally important.
TC: Like the reframing of what we want from a good code base, but not totally sure if we can capture everything. ..
Felix: Not sure what the goal is: Are we trying to help ourselves make decisions or selling Rust?
Does improving over time help with the first goal?
Josh: I think it does
TC: Rust is its own successor
joshtriplett:
, love that phraseImage Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Niko: I think the audience is ourselves and people who write RFCs.
joshtriplett:
Niko: I don't want to adopt a structure that's too inflexible that it isn't helpful to others, e.g. in the libs team.
Felix: One thing I want to see reflected: Interop has to be part of our story. Rust won't always be the best fit for everyone. (See also: "Some were meant for C", a paper by Stephen Kell from Onward 2017, regarding what Felix regards as "a good definition" of systems programming.)
Niko: Two things that would be worth discussing
nikomatsakis: This didn't "ring true" for me, perhaps because it depends a lot on what "surprising" means. I tend to think of it kind of the opposite: we take something you vaguely know, but make it work zero-cost and (often) better. Async functions are an example, as the semantics of calling an async function in JavaScript are very different from the semantics of doing so in Rust.
joshtriplett: "surprisingly" is definitely load-bearing. We aren't afraid to do it differently, if we think we can do substantially better. But we don't take a well-established concept and make it gratuitously different in an uncanny valley way, using terms or constructs strangely that give people incorrect intuitions.
nikomatsakis: I was thinking about Natural performance, nuanced control and wondering about this slight generalization. I think it applies to more than just performance, in particular. Other thoughts I had: good defaults, nuanced control?
TC: Prompted just by the phrasing above, it occurs to me the cliche "slow is smooth, smooth is fast" (popular among e.g. some special forces communities in the US) is applicable to Rust. People talk about Rust not being e.g. as fast as Python to write, but this applies only in the small. In the large, Rust is faster because it's "smoother". A large Rust codebase has fewer sharp edges.
nikomatsakis: never heard this cliche :)
joshtriplett: Seems closely related to "slow and steady wins the race", but without the connotation of actually being slow.
nikomatsakis: I find the phrase Flexibility to change a bit confusing – maybe it's a garden path, I realize now it means "flexibility (in order) to change", but it reads like… "resistant to change" to me (even though that's plainly not what it says when I stop and think about it). Anyway, I was thinking about alternative phrasings, I feel like two important things about Rust are that it prioritizes "long-term maintenance" and the idea of "extensible and adaptable". The latter feels like a property of good code.
nikomatsakis: I'm thinking over what I like from each of the approaches:
I like things being "simple, pithy, and memorable". I used to prefer wordier things but I've come to realize that we needed "catchphrases" we can reference to bring up a shared understanding.
I no longer feel there is a need for strict ordering – and yet I also think ordering has some value. I often think of it in terms of "red lines" – the "no runtime" feels like one of those.
But I generally prefer "positive" statements over "negative" ones. It feels frustrating to rule out an avenue ("no runtime") vs saying the positive outcome we want. e.g. "transparency" or "can be used wherever C can"
Two themes I want to see "come out" somewhere – maybe not in this list – are empowerment and extensibility. I think a key part of Rust's goal is empowering people to do things that they didn't think they could or didn't want to take the time to do. Growing the pool of systems programmers is a bit part of our "mission statement". To some extent I don't think that belongs in this list, as it's more like, the outcome of following our list. But it seems important.
Extensibility still doesn't feel like it's "coming out" from the principles above. It seems like one of Rust's core values that we (aim to be) a "library language" – a language where any library can do most of what we can.
joshtriplett: I think there's a lot of value in us having a coherent set of items that we agree upon. I think the versions from Tyler and Niko seem like the best starting points for that. The others could then be used for additional things we want to capture, while trying not to grow the set too much, and trying to coalesce items where it makes sense.
One area that seems likely to prove difficult to reach consensus on is whether we consider the properties "complete", insofar as having a set of principles that we consider most important, and more important than anything not in the set. I think there'd be a lot of value in capturing a variety of pithy phrases with common meanings that we can cite for reference and common understanding, whether or not all of them are part of a top-level "most important" set.
Josh: From TC's writeup:
When we're discussing stability, it's worth us also being clear about what kinds of things we don't believe in that others might still call stability. This goes beyond the exclusions to our stability guarantee and includes our thinking on e.g. minimum supported Rust versions and the rate and desirability of language evolution.
I think this deserves elevating from unspoken assumptions and semi-shared understanding up to more explicit policy. We've written down things like our stability policy and "allowed breakage". We should write down that we expect to keep evolving the language, as well. There's also a concept of "grease", where we try to avoid breakage but we don't want to let the pathways for coping with certain types of change stagnate and seize up from disuse.
niko: sometimes use this phrase to describe patterns we commonly follow.
TC: "Editions are meant to be adopted" is a good example of this.
Tyler:
Niko: Like the customer-focus. Want it to be actionable.
.as_ref()
?.into()
on your params, etc..get(&5)
.Josh:
TC:
Niko: Leave nothing on the table captured?
Maybe nuanced control.