Rust 2019 and beyond: limits to (some) growth.

This is a blog post (as solicited) about my suggestions for the Rust project in 2019 and beyond.I should note that I am speaking only for myself, not anyone else, and not even as a very active participant in Rust anymore. Moreover these suggestions, to a large extent, apply to many projects. Rust is just one case, but one that is currently doing some conscious year-end reflection.I should also note overall that I'm quite pleased by the trajectory of the Rust project and this suggestion is being made only in the spirit of keeping it healthy and on-track, avoiding some problems I observe developing in it, as a mostly-outsider these days.I'm writing to make a recommendation about paying attention to -- acknowledging, planning, making explicit mechanisms and policy around the limits of -- the growth of two things in the project:

  1. Necessarily-shared technical artifacts, specifically the language definition itself.

  2. The strains on people participating in conversations about those artifacts.

In particular, I want to draw attention (as others have done, with other words) to the fact that unlimited growth in both of these things is neither possible nor desirable, that there are natural limits, and that as with all natural systems reaching their growth limits, it will be better for everyone if the encounter with the limits is made in a controlled, planned fashion.

Note that I am not writing about limits to the growth of many other areas of the project. It may be possible to have (say) a package index that is too big, or a website that is too big, or maybe even a user community that is too big; but it's not clear that those are problems yet for Rust, and I'm presently only talking about the two specific areas above.

Every natural system has factors that limit its growth. That is why the universe is not (for example) a single amoeba expanding outwards at the speed of light. A system grows (and often its rate of growth grows too!) until it starts to encounter limiting factors, and then one by one the rate-of-rate-of-change, and then the rate-of-change, and finally the overall size of the system eventually all plateau. Typical growth patterns thus look something like a sigmoid or "S-curve", gradually approaching some asymptote. At least when the limiting factors are encountered in a gradual or controlled fashion.

When a system encounters its limits in an uncontrolled or abrupt fashion, a phenomenon can occur that is more like an overshoot, or even oscillation: the limit still exists but its effect is felt more in the form of collapse or crisis. The S-curve goes up to a peak, followed by a crash on the other side. That is what you don't want.

The Rust project has a few forms of process control -- that are, in essence, limits on rates of change and/or growth -- that I think are very judicious, and in part responsible for the success of the project so far. I'd like to couch the following recommendations by analogy to them. These process controls are:

  • The Bors queue, generally gating changes on program-wide correctness.

  • Crater runs, generally gating releases on ecosystem-wide correctness.

  • Time-based releases, generally avoiding having to consider whether to slip a schedule rather than cut a feature. The decision is made by the clock, and everything not-ready gets cut.

In addition, the project has grown some less-mechanized but nonetheless important social structures to manage the growth in people participating in the project.

  • The CoC. It's not always remembered, but the CoC does not just talk about social justice and harassment and so forth. It also lays out boundaries around the signal:noise ratio in conversation, the use of other people's attention and time, and the need to accept tradeoffs (I wish I'd had the foresight to use the term "zero-sum" here because I should have: not every decision is non-zero-sum).

  • The RFC process. This includes rules about the form, content, timing, set of participants and permitted and expected forms of discourse when discussing significant changes.

  • The governance model. This includes delineation of areas of responsibility, hierarchical delegation where necessary, roles and expectations of participants and so forth.

All these are essentially acknowledgements that -- absent control -- overshoots can happen and crises (or at least a degree of chaos and dysfunction) may ensue. When possible, process controls are automatic and completely impartial (to minimize ill-will and subjective judgement, the temptation to cut corners, etc.) Where necessarily subjective, the norms and processes are at least clearly articulated in writing, in well-documented, easy-to-find places.Going back to the two areas of concern, then: I want to bring attention to these two areas where the project does not currently have adequate mechanisms or policies in place to control growth, that carry risks of eventual dysfunction or even crisis. In both, it is not clear to me how far from such crisis the project currently is; but either way I think it is worth acting sooner rather than later.

  1. The language itself. Its definition. This is (unlike many parts of the project) a necessarily shared technical artifact. Everyone has a stake in it, and every change potentially effects everyone. Moreover, everyone needs to learn and understand a substantial part of it: people do not have the option to ignore parts they are not interested in. Even parts one wants to ignore will occur in shared contexts: documentation and teaching material, testsuites and validation material, compiler internals, formal models, other people's codebases, overall maintenance burden, etc. etc.

    The limiting factors to the growth of the language as an artifact then, include at least the following:

    • The ability for a beginner to learn the language.

    • The ability for an intermediate user to feel confident, adapt to others' codebases.

    • The ability for an expert or maintainer to know all (or most) of it, evaluate changes.

    • The cost:benefit ratio of each new change, in terms of new and ongoing work that it incurs, vs. the number of people or use-cases that benefit from it. The costs are combinatorial with many dimensions of the project and language size, and almost always increase.

    The risks of overshooting these limits are potentially quite serious:

    • Unsound language changes. Say: failure to maintain a critical safety guarantee.

    • Reputation for overcomplexity, loss of users. Becoming the next C++ or Haskell.

    • Lower-quality features with incomplete definition, testing, documentation.

    • Under-used features that simply absorb effort needed elsewhere.

    • Fragmentation into dialects, unsharable codebases, lower value.

  2. The strains on the people working on the language. Some parts of the project can be delegated, de-synchronized, proceed in parallel with as many hands are available to work on them. Not so the shared technical artifacts. To some extent, many people (and an increasingly-many people) need to be involved in nearly all changes, and that means that there's a lot of pressure both for everyone in that group-of-many to "keep up" with all the discourse occurring, and for the standard of what it means to "keep up" to gradually creep upwards as both more changes are proposed, and more voices contribute to each discussion.

    The limiting factors to the growth of these strains on participating people include at least the following:

    • The number of hours in a day.

    • The number of paid hours in a day.

    • Responsibilities and interests in things outside the project.

    • Reservoirs of mental energy to understand what's being discussed.

    • Trust in the judgement of everyone participating in a conversation.

    • Reservoirs of psychological and emotional energy to read and discuss.

    • The presumption of good faith in everyone participating in a conversation.

    The risks of overshooting these limits are also potentially quite serious:

    • Poor decisions made through exhaustion or attrition.

    • Magnifying inequality: only the most-privileged, available, energetic, well-paid or otherwise well-situated participants can keep up.

    • Narrowing of discourse, from careful consideration to "winning arguments".

    • People acting out, burning out, behaving poorly, departing project.

    • Frustration, accusation of bad faith, grudges, conspiratorial thinking, forks.

I want to emphasize that the limits and risks I've outlined here are quite independent of people, topics, or even the Rust project generally. They will occur to varying degrees anywhere and with anyone. Simply swapping out the current maintainers (say) for your preferred people will not actually solve them: the limits will re-occur. The only solution is to manage the encounter with the limits. Put some controls in place.This is the hard part and the part I'm going to leave least-definite. It's essential, after all, that the project adopt controls of its own volition rather than feeling that they're imposed from outside. I think the project needs to pause, reflect, consider collectively, and put some controls in place. I will therefore only offer a few possible suggestions here, not terribly structured but in the spirit of the holidays: as a bunch of possibly-interesting gifts to unwrap, look over, and decide whether to keep or swap for something more to your liking.

  1. Embrace negative space. Make a process for defining features and concepts out of the future trajectory of the language. Allow (or encourage) RFCs that say "Rust will never have X" for some value of X. While this sounds "negative" -- and it is, the word is written on the label -- it's a one-time thing where objections can be honestly considered with a long-term horizon ("never" is quite some time!) and given fair discussion, but then put to rest rather than being a perennial source of lingering conflict. A few examples where one might want to find and articulate negative spaces: paint certain categories of expressivity permanently out of the type system (eg. dependent types, HKTs, etc.), or out of the grammar (eg. parameter qualifiers, positional or keyword args), or out of the set of kinds of item (eg. anonymous record types), or out of the set of inference obligations in the middle-end (eg. constant value synthesis, implicit arguments). Put some hard limits in place, both to avoid those features themselves, and also to avoid people "putting pieces in place" that exist only to eventually-enable them.

  2. Front-load costs, make them explicit. Taking a page from webassembly's change process, make it clear that moving an RFC past a very early phase is going to require a commensurate investment in implementation, formalization, documentation revision, teaching-materials revision, test-writing, maintenance and so forth. Absent a way to cover the costs, defer changes "nobody has yet been found to pay for" at that stage.

  3. Set a learning-rate and book-length target. Try to work backwards from the amount of time and number of pages it "should take" to learn the language, or become expert in it, then cut things that go beyond that. If "teach yourself Rust in 21 days" isn't going to work, figure out what should. Three months? Six? A year? Think about languages that "definitely take too long", and pick a number that's less than that. Is a thousand page manual healthy? Five hundred? Three hundred?

  4. Set other mechanized limits: lines of code in the compiler, overall time to bootstrap, dollars spent per day on AWS instances, number of productions in the grammar, number of inference rules in the type system, percent test coverage, percent of documents allowed to be marked as "incomplete", etc. Get creative, figure out meaningful things you can measure and then put mechanisms in place to limit them.

  5. Rate-limit activity based on explicit personal-time-budgeting: ballpark-figure how many hours (or paid hours) per person are realistically available without exhaustion or burnout -- including the least-privileged but still necessary participants -- and work backwards to figure out how many hours of participation and review "should be" digested per team, per release cycle, thus how much work gets scheduled. Then cut or defer things that go beyond that.

  6. Allow the moderator team to apply rate limits or cooling-off periods to particular discussions as a whole. Sometimes an outside perspective that a discussion is just too heated overall is an easier way to de-escalate than shining a spotlight on a single person's behaviour.

  7. As with the moderator team: grow an additional cross-project team that does budgeting and auditing of load levels in other teams. This can be effective because the auditing/budgeting team sees their work as helping people to say no to the right things, rather than the default stance most people will have when participating in a team, to say yes to too many things.

These are just some ideas to outline the very general family of thing I am suggesting. I'm sure you can come up with more possible and more plausible ways of controlling the encounter with limits to the language's size and the personal strains of working on it. The Rust community has shown itself to be quite admirably creative, reflective and self-critical over the years. For this you should be commended. I hope this post is taken in the the same vein of constructive criticism.

Happy new year, and good luck!