Fortifying the process against feature bloat


#1

This topic originates from a sibling thread Pre-RFC: flexible `try fn`. If you want to catch up on that but don’t want to read it whole (it is long and sometimes a bit heated), I try to sum it up here (but I’m necessarily biased) as well as expand and propose some actual possible steps (not all proposals are fully mine and they generally come out of discussions either directly or indirectly as inspiration). I also tried to pick up a list of relevant comments at the end of this post.

I believe each topic deserves its own thread, and as my comment started the somewhat off-topic discussion there, I feel moral obligation to try to move the discussion here and stop polluting the original topic.

The problem

Programming languages naturally accumulate bloat over their life time. This is because once a feature gets into the language, it’s really hard or impossible to get it out again.

I haven’t seen anybody arguing that having a lot of bloat in the language is a good thing so it might be worthwhile exploring ways how to try avoiding or at least slowing down this kind of degeneration. At least watching this forum, it I feel there’s a lot of proposals that would add to that bloat without much benefit if they got in. (I won’t mention specifics in an attempt to avoid off-topic flame wars. I also don’t claim that some of them did get in.)

The obvious solution to not introduce any new changes isn’t really a viable way, especially with a language as young as Rust.

Current state

There are two mitigation mechanisms I know of. The first one is the RFC process and Rust’s teams which tries to block the bloat from getting in.

The other one are editions which have limited abilities to remove some features from the language.

Limitations of the current state

It is not possible to remove some features with the editions mechanism. With the others, while not tried yet, I have a feeling removing a feature once in the language would prove much harder than getting it in. So in general, most features are going to stay in once they get there. But even if we took only the features that are not possible to remove, it would still be a limitation.

As for the RFC process, the problem is similar to airport security. First, it needs constant vigilance. This requires energy that could be better used somewhere else. If the team has to pay attention to every RFC that is not going to get in, if only to decide to close it, it still takes time. Second, it needs to „defend“ the language many times, but failing once has dire consequences. No matter how good the defenders are, they are going to let something slip from time to time. Furthermore (unlike the airport security), the current implementation of this defence has somewhat low visibility (compared to high visibility of proposals on this forum, for example) and people with somewhat pessimistic life viewpoint (like me :innocent:) worry needlessly.

Proposals

Negative RFCs

Edit: Let’s scratch this one. As pointed out below, it’s heavy-weight and doesn’t really effectively solve the problem of repeated proposals. Something like including them in the FAQ, if they happen often enough, or an easily searched index of refused ideas might be better.

First, I think it would make sense to remove the one-sideness of the process. The process has a formalized way to let a feature in forever. But if a proposal is not accepted, it doesn’t stop anyone from proposing it again only painted in a different colour. Let’s extend it so there’s also a way to ban a feature forever. There probably are features that are never going to be let into Rust, but this is only informal folklore. These crop up from time to time. Having a place where all the arguments against are summed up and can be linked to could both put up ones mind at ease and save time explaining it every time. If a feature is desired, it gets implemented and stabilized. If a feature is explicitly not desired, it gets on a no-feature list. Of course, there would be still the current way of closing a proposal with no implications for similar ones.

It has been hinted that this is already possible in the current process. I’m not familiar with the RFC process enough to know for sure if writing an RFC „Never include feature X“ would really work correctly and there’s no precedent. So maybe only a precedent is what is needed ‒ something obvious, like „We are never going to include C++ style move constructors, move is memcpy on purpose“? This one appears from time to time and it is argued this would break a lot of unsafe code, etc.

Be a bit more explicit about some properties of a good RFC

Edit: Including the points in the template was just my first idea. Basically, some place where a potential author can find the suggestions is good. Maybe creating some kind of guide for new authors (which would include other recommendations too, if the current authors were willing to share them) might make sense.

I guess it implicitly is the authors responsibility to prove why the RFC is needed, useful and worth it (of course in both cases, positive and negative). I’d like to just be more explicit about that, to both remind the authors about it and to make it easier to notice when the proposal isn’t actually needed.

I would also like to promote fact-based discussions over guessing where possible. I believe it is quite hard to predict how users might react to a feature, how it will interact with other features, etc. Having at least some guidance usually helps.

The things I propose here are often supposed to have psychological effect, so the wording might need some care (I know I’m not particularly good at that thing, so if you are or know a psychologist or someone with the expertise, I’ll welcome the help).

In other words, I’d like the authors to include these things in their texts (by suggesting it in the RFC template):

  • Problem statement: I think this could replace or enhance the „motivation“ section, as it is a very similar in actual meaning. However, on the subconscious level, this has a bit different connotations. It hints that there should be something broken to fix and not be done „just because“. This could lead the author to think and abandon obviously unneeded RFC even before writing it. Furthermore, having an explicit problem stated (instead of eg. a solution ‒ motivation „Make X more like in language Y“ is a motivation, but not a problem statement) makes it easier for others to come up with a counter-proposal that solves the same problem in a different way, possibly better.
  • Why this one is different: In case there was a similar proposal before that was rejected, an additional section explaining how this one addresses the points against the previous one and why the author believes this one is better. This implicitly asks the author to look for similar previous proposals and take their feedback into consideration. It also helps reviewers get faster up to date and highlights the important differences. I believe the series of module path RFCs did work somewhat on this basis and it did have positive effect (I would have to go read them if the differences were explicitly highlighted, but I’m sure they evolved based on feedback).
  • Benefit vs. cost analysis: Every feature has certain cost on the bloatness budget (in rare cases the cost may be negative, if a feature removes inconsistency). Some approaches are more expensive than others, so authors actively thinking about that are more likely to pick lightweight approaches. It might promote thinking about some 80/20 solution ‒ one where there is a substantial improvement on the problem situation while requiring much smaller feature. It would also encourage gathering some statistics (how much does this problem happen in the wild?) if reasonably easy to get by. Another suggestion could be to try a more conservative approach first ‒ if the proposal can be split in two, one weaker and another that the first can be extended into, it IMO makes sense to try the smaller one first and follow with the second only if the first solution is still inadequate (eg. start small and upscaling as needed, not start big and downscaling during the RFC process).
  • How it fits in the general direction of Rust ‒ probably a part of the reference level explanation? This should discourage „alien looking“ features, or features with expected negative impact on writing correct code.
  • Experiments and research: The goal would be to encourage some previous experimentation, like possibly creating something similar or limited as a proc macro ‒ a great example of this is the futures-await crate. Having something similar would both better show-case the intention and provide the author with feedback much earlier than when it gets implemented in the compiler, potentially leading to higher quality design.

These things are „soft“, so there’s no guarantee they actually would help. On the other hand, it would still keep the RFC process flexible ‒ as like now, omitting a section in case it makes no sense wouldn’t need to be penalized in any way.

Next steps

I would really like to hear what others think about this. On one side I think this has some potential to help without too many drawbacks (the only one I can think of is that this would probably increase the effort needed to write an RFC). Unlike language features, the changes to the template could be reverted if they prove to be bad.

On the other side, the fact that I see very few drawbacks is usually an indicator that I’m blind by bias, so feedback is welcome. Is there a chance this could backfire in some way? But as this is a very fresh and raw idea, I believe more productive approach will be to first discuss the general direction and bikeshed details (like what section each point fits in best) later on.

If the general consensus is this is a good idea, I’d like someone to help me with the wording and such things, both because I’m not native English speaker and because I sometime write things people understand differently than I thought.


The promised links to relevant posts in the other thread (I hope I didn’t miss any and that there are not too many off-by-one errors in the post numbers). When not sure if a post is relevant, I tried to err on the side of including it:


I just hope this does not escalate to another very heated discussion.


Pre-RFC: flexible `try fn`
#2

To do this effectively, I think it would be a good idea that the rules for negative RFC’s are such that the negative RFC must clearly, unambiguously, and provably state why the feature being denied will break some important guarantees of the Rust language. So, a negative RFC must have proof that the thing being denied (like the idea of using C++ style moves will break unsafe code in Rust) will in fact break Rust. In the case of something that is being ruled out, not based on actually breaking some important guarantee of the language, but, that is deemed to be in conflict with the fundamental spirit and mission statement of the language, a clear argument should be presented and agreed to.

In order to override a negative RFC later the bar is that you must PROVE that the proof that the negative RFC gave for why the feature was forever banned is somehow wrong and that your proposal is in fact implementable without breaking the language or that the evidence/argument presented in the negative RFC that the feature violated the spirit or fundamental mission statement of the language was somehow an invalid argument. That would make a negative RFC, once accepted, almost impossible to override and would also prevent the creation of erroneous negative RFC’s.


#3

I just want to say thank you for taking the time and effort to write this up.


#4

Another good section that could be added to the positive RFC is a section called, “Why this does not break the language?” In that section, the proofevidence that the new addition to the language does not break any fundamental guarantees of the language should be presented. This section could be left incomplete for an RFC to be accepted, but, would be required for it to be filled in and agreed to, with justification, before the feature could be stabilized.


#5

I’m a bit in doubts here. On one side, it should be probably harder to pass a negative one than a positive one. Positive one goes through an implementation and stabilization process, so there are more checks on the way. So the rules should be somewhat strict.

On the other hand, if I take another example ‒ full-blown unchecked exceptions. Besides the fact we already have panic, which is a bit similar, I think the general consensus would be this would make Rust not being Rust any more. I could imagine a negative RFC for that. So in sense it would break Rust’s spirit, but not some code ‒ and that’s somewhat hard to prove. I’d be careful with calling explicitly for proofs… though proofs are generally pretty strong arguments.


#6

I agree. I’ve updated my comment above to reflect that reality as well.


#7

@vorner It wasn’t clear to me from your post, so maybe you could spell out in a bit more detail: is the problem you’re trying to solve related to:

  • pre-RFC posts on this forum
  • RFC PRs that are rejected
  • RFC PRs that are merged
  • Features that are stabilized

I see the situation for each of these as quite different!

It’s probably worth reiterating that decisions about the language are ultimately made by consensus within the formal Language Design Team. Every language RFC is ultimately reviewed by that team, and must be actively pushed through the process. I bring this up because I’m a bit confused about this:

No matter how good the defenders are, they are going to let something slip from time to time

The Language Design Team thinks very carefully about the overall coherence of the language and its idioms, as well as learnability, maintainability, readability, etc. And generally speaking, one of the great things about the existing process is that RFC threads do an extremely good idea of surfacing a wide array of concerns; it’s pretty rare for a truly new concern to arise late in the process.

Now, for pre-RFCs on this forum, the Language Design Team often doesn’t have the bandwidth to get deeply involved. In my experience, the vast majority of pre-RFCs never “graduate”, partly because this forum does a great job of surfacing issues. But I don’t see that as a need for “constant vigilance”; there are many, many additional points for feedback (and that require active consent from a team of experienced language designers) before a feature ships.

As to the specific suggestions: rather than negative RFCs (which I expect to be often controversial and hard to specify clearly), that we work toward a Language Design FAQ where we can record some of the folklore knowledge about rationale for various decisions. I think such a thing would be very useful.

The suggestions for the actual RFC format seem fine, though IMO most of what’s suggested is already part of the template, and it’s not clear to me that additional text in the template will make much of an impact.


Don't keep complicating the syntax (soft post, maybe off topic, maybe irrelevant)
#8

At the risk of being overly meta, this started with a concern that the try fn proposals were solving a problem was being drastically overstated, and my response to this meta suggestion is also that I think the problem is being overstated. I’ve simply never seen the same idea get RFC’d multiple times, such that it needed a “vigilant” defense as described here. Whenever I’ve seen similar ideas make it as far as a new RFC, there’s always been some meaningful changes that are worth discussing seriously, even when I happened to dislike both the old and new proposals.

Move constructors come up every so often in discussions on this forum, but I can’t recall there ever being an RFC for adding them (I guess if there was one, it must not have lasted very long). I do recall some pre-RFC threads that proposed something morally equivalent to move constructors, and they pretty much all got shot down by the first responses summarizing and/or linking to the “well known” downsides. try blocks and fns have come up in various forms many times but we have made some obvious progress in the last few years, such as rejecting completely implicit Ok-wrapping, and bikeshedding the keyword enough to conclude try beats catch, so the latest proposals are not at all a repeat of what came before. Plus, even if we had a “negative RFC” about move constructors or whatever, would that have prevented people starting new discussions about them? Probably not.

Based on my experience reading this forum and the related RFC threads, whenever someone proposes a “known bad” design, they’re never simply copy-pasting a past proposal in the hopes of sneaking it past us while we’re not looking; they genuinely didn’t know it was a well-known idea with severe problems. Considering RFCs are not a popularity contest and need a real Rust team to achieve consensus, it’s hard to see how anyone ever could pull that off anyway (as aturon just said).

What I do think would be useful (and I swear I had this in mind before @aturon finished posting his post…) is documenting these sorts of “known bad” ideas in a place where newbies might find them. Emphasis on documenting. RFCs are not the best when it comes to documentation. For this purpose, I think it’d be hard to beat the official FAQ page. I know Go’s official FAQ page has been very helpful whenever I forget why they did or didn’t design something a certain way (since I’m not enough of a Go fanboy to just have all the answers memorized, like I am with Rust).

Edit: Oh hey, move constructors already are in the FAQ: https://www.rust-lang.org/en-US/faq.html#does-rust-have-move-constructors Though we could probably expand that answer a little.


#9

WHATWG has IMHO a nice rule for this — closed discussions can’t be reopened unless you add new information.


#10

Indeed! We have a similar stance on RFC PRs, see this one for example.


#11

Well, why not :slight_smile:
We already have a ton of accepted but unimplemented RFCs.
C++ found itself in similar situation in 1998 when the committee released '98 standard and it was largely unimplemented. What they did? They froze the language development (modulo bug-fixing) for 5 years until compilers caught up, then released bug-fix C++ '03 and only then started extending/changing the language again.

Even on the marketing side we can still report compiler speedups, implemented previously accepted RFCs, progressing fundamental “enabler” features like const evaluation, procedural macros or maybe also async (not sure since I don’t personally care about it), third party projects using the language.
The unknown quantity here for me is what Mozilla management wants from the project - perhaps steady stream of new shiny features and following public visibility has importance for funding or something.


#12

This is clearly at least partly in jest, but it’s important to say: Mozilla management has no direct say in Rust’s development, which is led entirely by the various Rust Teams (of which you’re a member). And of course we set out our overall goals each year through the roadmap process, which this year is mostly focused on completing, polishing and shipping in-flight features.


#13

I have little time today, but I’ll at last try to answer some questions or clarify my view a bit.

You’re right it isn’t clear. In fact, when thinking about the problem, I myself didn’t even cross my mind to think there might be a distinction. But when you ask, I think the problem, as I see it, is kind of going through all of them. I’ll try to explain

Let’s say there’s a stabilized feature which I don’t agree with (I have a specific one in mind, but I don’t want to make bad air at picking at one). I accept I don’t have to agree to everything, but here, it seems to have been an unneeded thing. No matter if the perception is correct, it undermines the trust into the leadership ‒ which I don’t want to be undermined. So, either I’m right it was unneeded and it stabilizing was a „slip“ as I call it, or I’m wrong and there was an actual reason for that to pass, which I don’t see. Either a process mistake or a communication problem. I believe in this case, an explicit problem statement and benefit-cost analysis would have helped.

Anyway, for the feature to get stabilized, it needed to pass through all the stages (allright, not through closed one). And I guess the proposed changes might have helped on each level. I see it as something like a sieve ‒ each level filters some bad RFCs out. But feeding the sieve with higher quality RFCs overall and with less bad ones improves the outcome.

The points were brought up during the discussion, but there’s a difference between a point being made during a discussion and having it stated as a fact by the author. There’s a difference to almost everything ‒ there was a study: committee of 5 judges were going through requests to let inmates released sooner. The study found there was a strong correlation between concentration of unfavourable decisions and the time since last coffee break. Indeed the RFC process is less prone to this, because it takes longer, but I believe better decisions are made if better data are stated early. There’s a strong cognitive bias towards first impressions. Should I try looking up references once I get more time?

Actively as in the guard on the airport lifting a gate and allowing the person to pass. Slip as in not noticing the knife in the pocket.

Good point. I guess having negative RFCs just for the show and demonstration the field is balanced on the theoretical level and the FAQ might be a better format in practice.

I had the template open in another window and tried to check it while writing the post. I might have overlooked something, but overall, I did find points that implicitly included the points, I’d like them to be explicit. If I was writing an RFC, it wouldn’t have been clear to me to include all these things. I agree good RFCs actually follow the points I mentioned, I’d like some form of good RFC practices. I’ve also seen RFCs that didn’t include the points.

Maybe it doesn’t have to go into the template, it was the first place I thought of that an author would look into.

Maybe. But I’m trying to be conservative in the solutions ‒ few suggestions how to make your RFC better (if the points are seen as beneficial) shouldn’t hurt.


#14

Personally, the RFC process is already a bit too bureaucratic for my taste, and I’d like to see it become made lighter-weight, not heavier. But it doesn’t matter that much either way, since the most interesting features are mostly blocked on implementation work rather than design.

I do wish there were some way to do more long-term testing of new features. Right now, since the majority of the userbase is on stable, it seems like there’s a pattern of “we have to get this stabilized so people can try it out”. But feedback from people trying it out is exactly what we need before it’s too late to change anything! It’s like some weird version of “we have to pass the bill so you can find out what’s in it” :slight_smile:


#15

I think a Babel-like Rust-experimental to Rust-stable compiler could solve the problem.


#16

An old post that represents my view on this:


#17

I really like the idea of making prior rejected proposals more visible. It seems like a really common discussion pattern is

Bob: Pre-RFC for feature X
Alice: X was discussed in this thread here <includes link>
Bob: Oh, thanks.
<close>

Currently, postponed or closed RFCs just end up in the “closed PRs” list on GitHub with no good way to find them or learn from them. The problem is even worse for this forum, where closed Pre-RFCs just get lost in the endless flow of new threads.

Perhaps we should just have an index in the RFCs repo of proposed features that were postponed or rejected (on github or here), categorized by type of proposal (e.g. “new sugar”, “new syntax”, “new static analysis type”, etc…).

People could then be encouraged to look through prior discussions before proposing something new.


#18

My goal here is not really a policy or bureaucracy. On a very high level, I’d like to raise the overall quality of proposals. The Rust teams see mostly the better proposals (or, matured ones), but this forum gets the full blast of the less mature. In my view, a good RFC/Pre-RFC/Proposal is not necessarily one that aligns with my views, but includes relevant information and makes it easy to discuss and see pros & cons. Optimized for reading/discussing the RFC instead of writing it.

Let’s scratch the proposal for negative RFCs. As pointed out, this is quite heavy and wouldn’t really solve the problem. The FAQ or indexing of refused proposals might work better.

As for the specific points above, including them in the template was only my initial idea. Overall, looking at the RFC repo readme, it mostly talks about what happens after the RFC is created and a PR submitted. So, in that note, would some kind of „How to write a good RFC“ guide sound like a reasonable thing? Or maybe, if I could ask for a short blog post from a regular RFC author, how a research before writing one usually looks like, which approaches happen to work, which ones less so… Not a policy or process thing, more like help for the proposal authors. There’s the Rust book for programmers, there’s the compiler book for working on the compiler code, but not much help in the design space. I guess if one specifically asks for help, it is available, but something that one could read before committing to anything would also be valuable, and useful before even writing about the idea on this forum for the first time.


#19

I’m trying to understand this thread, and I think a part of the underlying motivation is a particular mental model of the RFC/feature development process. In particular, there are two (inaccurate) beliefs about the RFC that would make this proposal make sense:

  • An RFC is evaluated on its own merits without considering factors outside of the RFC text.
  • Merging RFCs is a goal or a metric for the Rust project; we have some impetus to keep the “RFC merge rate” up.

Neither of these is true. RFCs are closed all the time because they do not cohere with the language design as a whole, or even just because they cannot be fit into our current roadmap. And we don’t consider it important to “keep merging RFCs;” RFCs are a decision making tool for when we have an external need to make a change; if an area needs no improvement, no RFCs will be merged.

The most likely to merge RFCs are those which provide a solution to a previously identified high priority problem. These most often come from within the project contributors, but not always - for example, the ATC RFC that I wrote (before I was on any team or really a contributor to Rust at all) was a solution to the outstanding problem of higher kinded polymorphism. It’s not the case that I just wanted to add a feature to the language, and the team said “huh, why not?” - it was merged because it was the solution to a previously identified problem.


#20

This statement is interesting! This forum is for immature proposals - and it essentially functions as a filter (well, a filter_map); proposals either are improved after leaving this forum or they don’t leave it. And the team sees all of these proposals also (even if we don’t reply to many of them).

I agree that surfacing knowledge about previous design decisions would be helpful, but I’d just emphasize that its helpful to save people time, not prevent “feature bloat”: features won’t be added to the language that are contrary to the decisions we’ve made, even if we haven’t documented them.