Meetup 2024: Rust Design Goals

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.

Tyler's draft

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.

Correctness by construction

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.

Clarity of purpose

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.

Natural performance, nuanced control

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.

Power through composition

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.

Flexibility to change

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.

Josh's notes on Rust's effectiveness

(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.)

Niko's design axioms take 3

Including for reference

  • If it compiles, it works. Rust's design emphasizes reliability and long-term maintenance. Memory safety in particular is not negotiable.
  • Leave nothing on the table. Rust lets you take full advantage of the capabilities of your machine.
  • {Performance, portability} by default. Rust code written in the natural way performs well and works across all common environments (operating systems, architectures, etc). So yes, you can have nice things even in your inner loop.
  • Empower the ecosystem. Rust is not a language that bakes in all its choices. We aim to build general mechanisms that the greater library ecosystem can make use of to build their own abstractions.
  • Good first impressions, supportive tooling. We try to make the easy thing easy with sensible defaults and quality tooling. We can't always make it easy, but we can support you in figuring out your problem.
  • Not afraid to do the right thing. It's always good to follow precedent, but Rust is not afraid to chart a difference course if we think it's the right one.

Prior version for Josh =) https://nikomatsakis.github.io/rust-design-axioms/#what-we-strive-for

TC: Some thoughts

(These are extracted from an earlier document, Thoughts on Rust Beliefs.)

Belief: Industrial but advanced, powerful, and principled

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: notentirely 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.

Belief: Two kinds of language power

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.

Belief: No runtime

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.

Belief: Stability

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.

Belief: Rust is its own successor

Rust isn't waiting for a successor language. We're designing the successor language to Rust for every release.

More drafts/notes here

Prior art

Questions and Discussion

High-level takeaways

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:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
, love that phrase

Niko: I think the audience is ourselves and people who write RFCs.

joshtriplett:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
, and also people who want to understand the design philosophy of Rust.

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

  • What is the structure
  • Positive is better than negative; would rather define ourselves in terms of what we are trying to accomplish rather than what we avoid
  • How much do you lump together things that are in tension? Example, "good defaults vs explicit control", these are clearly in tension, it's nice to talk about how we aim to resolve it (simple at first, but let you do whatever you need to do)

"If we use a construct from other ecosystems, we don't make it surprisingly different."

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.

"Start smooth, grows with you"

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?

"Slow is smooth, smooth is fast"

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.

"in it for the long haul" or "extensible and adaptable"

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.

Some space for Niko to think "out loud"

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.

Thoughts on merging these into a coherent consensus

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.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
for reframing any hard lines or "negative" properties into positives where possible, at least for the pithy versions, though I think the descriptions should also reference what that means won't happen. This is particularly true for properties that preserve the ability for Rust to go anywhere and do anything.

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.

What stability means

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.

"Strategies for success"

niko: sometimes use this phrase to describe patterns we commonly follow.

TC: "Editions are meant to be adopted" is a good example of this.

Reviewing the set

Tyler:

Niko: Like the customer-focus. Want it to be actionable.

  • Correctness by construction
    • Seems well aligned.
    • Tension, if any, is going to be with "eliminating every bug". Rust is meant to prevent bugs in practice, not to make you prove everything up front nor to jump through hoops "just because".
    • TC: The correctness by construction brings to mind the similar idea of "parse, don't validate".
  • Clarity of purpose
    • Josh: Wasn't clear on this one from the phrase, seemed ambiguous. About the Rust language vs about people's Rust code?
    • Niko: Let you pay attention to what's important and just what's important.
    • Felix: Might force you to pay attention.
    • Josh: Design patterns a sign that you're working around limitations in your ability to abstract; if you're using the pattern a lot it can make it hard to see through.
    • Niko: Example of not achieveing: .as_ref()?
    • Scott: Don't want to have to call .into() on your params, etc.
    • Tyler: You want the "noise that matters", i.e. telling you you're going to blow your stack etc.
    • Josh: Shouldn't have to write .get(&5).
    • Niko: High signal-to-noise.
    • Niko: Wish the phrase was more obvious
    • Tyler: Need to wordsmith a bit.
    • Scott: Like signal-to-noise phrasing.
      • Josh: +1. Like the principle.
    • Scott: You might not understand what specifically every single line of code is doing but you understand why it's there and what it's for.
      • Tyler: That's interesting, I like that.
      • Josh: What you don't know can't hurt you.
  • Natural performance, nuanced control
    • Proposal: "smooth start, grows with you" I guess that's property of the language not the code
    • Scott: Liked the phrasing "you write the Rust that's in the book", from a Microsoft blog post. Compared to C# or Java if you need to make them perform.
    • Niko: High performance Rust is just Rust.
    • Josh: "if you write it in the most obvious way it's fast" is closely related to "if you write it in the most obvious way it's correct"
  • Power through composition
    • Josh:
      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →
    • Josh: Also, give a lot of power to library authors. Lets them be compositional and provide multiplicative benefit.
    • Niko: Liked "we're a library-oriented language" from C++, I've always found it clear and actionable.
    • TC: "The ecosystem is first-class".
      • This leans on the idea of what "first class" means in programming languages, that what users or library authors can write is the same in important ways ergonomics, performance, etc. as what the language itself can do.
      • That is, you're not giving much up by providing something as a crate rather than us providing that as language designers, or that being provided by libs-api.
    • Scott: Interacts with correctness by construction; doesn't compromise it.
    • Niko: Anti-Go in this respect.
  • Flexibility to change
    • Proposal: "extensible and adaptable"
    • Niko: Thinking of "rebalancing the orphan rules" RFC
    • Josh: See TC's point here:
      • 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.
    • Josh: 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.
    • Josh: Should this just be "stability without stagnation"?
    • Niko: For some "to change" as sort of "resistant to change"
    • TC: "Rust is its own successor" is related to this.
    • Niko: I want to capture fearless refactoring.
    • Scott: Hearing three things: , Code doesn't break, Willing to let junior engineers check into my post and not worry about it.
    • Josh: See Jane's post about impostor syndrome, and how we make sure you don't have to be perfect to contribute to Rust itself; we design our processes so that we don't expect perfection.
    • TC: Reminds me of the cliche "slow is smooth, smooth is fast". It may seem slower in some ways, but ultimately you get to the result you want faster, particularly if that result is having reliable code.
    • Niko: New lifetime capture defaults are an example of this value. Another is ..
    • Josh: Also assuming ordering of enum variants. other cases where it's too easy to write a one-way door. We're very careful about one-way doors when designing the language; we also try to be careful not to inflict one-way doors on our users, where it's too easy to accidentally write a one-way door.

Josh:

  • If it compiles, it's probably correct
    • more-or-less "correctness by construction", should be able to merge this in
  • Rust is predictable
  • Rust keeps getting better
    • Related: "editions are meant to be adopted", Rust will not be "done"
  • Rust is Rust, everywhere
    • May be mergeable with things related to transparency/control
  • Nobody puts Rust in the corner - We treat "impossible" as a challenge.
  • Rust is for everyone
  • Rust is a part of its own community
  • Orthogonality
  • Rust tooling is first-class

TC:

  • Industrial but advanced, powerful, and principled
  • Two kinds of language power
  • No runtime
  • Stability
  • Rust is its own successor

Niko: Leave nothing on the table captured?
Maybe nuanced control.