Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Else clauses for for and while loops #3163

Closed
wants to merge 1 commit into from

Conversation

Starwort
Copy link

@Starwort Starwort commented Aug 19, 2021

Did I do this right?

@lebensterben
Copy link

This would be redundant given RFC rust-lang/rust#63177

@Starwort
Copy link
Author

Perhaps I'm just tired - it's 11:30 PM here - but I fail to see the connection between that pull and this proposal

@lebensterben
Copy link

lebensterben commented Aug 19, 2021

See https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.try_find

Applies function to the elements of iterator and returns the first true result or the first error.

Your motivating example is

let mut found = false;
for value in data {
   if value == target {
       found = true;
       break;
   }
}
if !found {
   println!("Couldn't find {}", target);
   return;
}

Specifically for the motivating example, you only need

let found = if let Some(value) = data.iter().find(|value| *value == target) {
  true
} else {
  println!("Couldn't find {}", target);
  return;
};

If you think this is too complex, which is not at all, you can use the syntax proposed in the recently-merged let-else PR:

let Some(found) = data.iter().find(|value| *value = target) {
  true
} else {
  println!("Couldn't find {}", target);
  return;
}

If you only care about whether it's found, regardless of the actual matched item, it's simply

if data.iter().find(|value| *value =  target).is_none() {
  println!("Couldn't find {}", target);
  return;
}

For a more general case:

try_findreturns early either on a match (when the closure returns anOk(true)`), or on a error.

For an Iterator<Item = T>, this returns Result<Option<T>>, which contains all the information you possibly need:

  • Whether a failure occured
  • Whether a match was found and if so the matched item

So you can write something like:

if let Ok(Some(needle)) = hay.iter().try_find(|value| Ok(value == needle)) {
  /* do something */
} else {
  /* do something else */
}

@lebensterben
Copy link

lebensterben commented Aug 19, 2021

The PR contains the following examples, which can all be written in a concise and readble way without proposed new syntax:


Proposed syntax:

let x = for i in 0..10 {
    if (...) {
        break i;
    }
} else {
    11 // default value
};

With Iterator::find():

let x = (0..10).find(|v| Ok(v == target)).unwrap_or_else(11);

Proposed syntax:

let x = while (...) {
    // do work
    if (...) {
        break Some(value);
    }
} else {
    Some(42) // default value
};

With std::iter::successors() and Iterator::try_find()

use std::iter::successors;

let x = successors(Some(initial_value), |v| {
  Some(/* do something on v */)
}).try_find(|v| {
  if /* outer conditional */ {
    Ok(/* inner conditional */)
  } else {
    Err()
  }
}).unwrap_or_else(42);

You can use std::iter::successors() with Iterator::last():

use std::iter::successors;

let x = successors(Some(initial_value), |v| {
  if /* check on v */ {
    Some(/* do something on v */)
  } else {
    None
  }
}).last().flatten().unwrap_or_else(42);

@clarfonthey
Copy link
Contributor

clarfonthey commented Aug 20, 2021

I feel like using dedicated syntax for this instead of just iterator adapters does have promise, and could help in a fair few numbers of cases where such adapters aren't possible, e.g. easily returning to the outer function or async code.

I dunno. I like filling in this gap where while and for can't break values, but loop can.

@SoniEx2
Copy link

SoniEx2 commented Aug 20, 2021

Honestly would rather have

#![feature(label_break_value)]
fn main() {
    let x = 'foo: {
        for &a in [1, 2, 3].iter() {
            if a % 2 == 0 {
                break 'foo a;
            }
        }
        0
    };
}

In fact, you can do this today, on nightly: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=51e6caacaf3e6fae2d6dd68dfbc56c77

@burdges
Copy link

burdges commented Aug 20, 2021

Rejected previously in #961 (comment) and #1767 (comment)

@clarfonthey
Copy link
Contributor

Both comments state that they're open to revisiting this.

@Starwort
Copy link
Author

The PR contains the following examples, which can all be written in a concise and readble way without proposed new syntax:

  1. I am already planning to follow this up with some real-world examples, I just have current prior arrangements; you can probably expect them some time in the next few days
  2. I'm all for one-liners, but (in my subjective opinion) your proposed alternatives lose a lot of readability compared to the original, and even knowing my original code I struggle to understand the purpose of your code samples. I see your point now that it's possible to perform the same function with that PR, but it represents such a massive transformation from the original code that it doesn't feel like the 'right' solution to the problem.

I'm open to changing the else keyword to one such as nobreak if it would make the drawback of programmers not understanding the feature small enough to get approved (although my personal opinion is that else is a perfectly fine keyword for this), and it feels like a more natural solution to the issue than transforming a (potentially large) section of imperative code into a functional style. I'll try to add some more motivating examples after I've got back from work today

@Starwort
Copy link
Author

Starwort commented Aug 20, 2021

That said: Here's a piece of code from one of my own projects (a Connect 4 AI). I cannot see a simple way of transforming this into any of the cases you propose; however it uses the exact pattern for/else is supposed to improve (in this case reaching the else clause indicates we've found a diagonal):

let owner = col.pieces[y];
let mut found_line = true;
for offset in 0..4 {
    let col = &self.cols[x + offset];
    if col.count < y + offset {
        found_line = false;
        break;
    }
    if col.pieces[y + offset] != owner {
        found_line = false;
        break;
    }
}
if found_line {
    return Some(match owner {
        Piece::White => GameResult::WhiteWin,
        Piece::Black => GameResult::BlackWin,
    });
}

I'd be interested in seeing how complex the functional approach can get with a real example (although obviously you do not have to write code for me if you do not want to)
EDIT: It occurs to me that my match arms do not work correctly. I will need to figure out a fix for that fixed

@lebensterben
Copy link

lebensterben commented Aug 20, 2021

@Starwort

let owner = col.pieces[y];

let found_line = &self.cols[x..x + 4]
    .iter()
    .enumerate()
    .find(|(offset, col)| col.count < y + offset || col.pieces[y + offset] != owner)
    .is_none();

if found_line {
    return Some(match owner {
        Piece::White => GameResult::WhiteWin,
        Piece::Black => GameResult::BlackWin,
    });
}

You can make it shorter:

let owner = col.pieces[y];

&self.cols[x..x + 4]
    .iter()
    .enumerate()
    .find(|(offset, col)| col.count < y + offset || col.pieces[y + offset] != owner)
    .is_none()
    .then(|| match owner {
        Piece::White => GameResult::WhiteWin,
        Piece::Black => GameResult::BlackWin,
    })

@SoniEx2
Copy link

SoniEx2 commented Aug 20, 2021

If you consider the label-break-value desugaring:

#![feature(label_break_value)]
fn main() {
    let x = 'foo: {
        for &a in [1, 2, 3].iter() {
            if a % 2 == 0 {
                break 'foo a;
            }
        }
        0
    };
}

then a good keyword for it would be "then":

fn main() {
    let x =
        for &a in [1, 2, 3].iter() {
            if a % 2 == 0 {
                break a;
            }
        } then {
            0
        };
}

but do we really want to add a new keyword vs stabilizing an existing, already-implemented feature?

(Also this is impossible to get wrong: if you forget to break the label you get an error ^-^)

@mcarton
Copy link
Member

mcarton commented Aug 20, 2021

The “Prior art” section gives really good reasons not to have this syntax.

This feature is in both Python (with identical semantics) and Guarded Command Language. Additionally, there has been a proposal for this feature in Golang (closed by bot) and in JuliaLang (with many people proposing/preferring the Python-influenced semantics used in the JuliaLang thread, in addition to backing up the motivation).

The Go proposal mentioned in the RFC got 23 👎 and no support vote, while the Julia proposal has been open since 2012 with little activity. This seems to indicate that people generally feel no need for such syntax.

Unfortunately, many Python users are unfamiliar with the syntax; of an informal survey, only around 25% knew the meaning and 55% gave an incorrect meaning.

If only 1 out of 4 Python users know what it means, and 1 out of 2 think it means something else, this indicates to me that copying this syntax with this semantic is a very bad idea. It's too niche for most users to know, and not intuitive for users who don't know.

@Starwort
Copy link
Author

Starwort commented Aug 20, 2021

It's too niche for most users to know, and not intuitive for users who don't know.

As Rust is a systems language, I would expect that more people would have an understanding of the desugar (at its core, a while loop just uses a guard to goto the loop's beginning; therefore an else can be attached to that guard) than Python, which is a very high-level language. Additionally, the point of that survey was that the responders couldn't look at documentation; those who already know the syntax could use it fine, those who don't can use the guard version, and those who encounter it can look at the feature's documentation and join the first group.

then a good keyword for it would be "then":

I believe I mentioned this but the reason then was rejected was due to it reading as if it always occurs after a loop; with label-break-value this makes sense but it is less obvious that the entire block would be skipped (as the previous one closes first)

@lebensterben: It takes me far longer to parse that code than the equivalent for-else construct (which is really the point of the construct) - I can just about manage but it is definitely harder to read, which kinda supports my point. I'd be interested in a poll between the three four options for readability though (please vote for the most readable version, in your opinion)

❤️

let owner = col.pieces[y];
for offset in 0..4 {
    let col = &self.cols[x + offset];
    if col.count < y + offset {
        break;
    }
    if col.pieces[y + offset] != owner {
        break;
    }
} else {
    return Some(match owner {
        Piece::White => GameResult::WhiteWin,
        Piece::Black => GameResult::BlackWin,
    });
}

👀

let owner = col.pieces[y];

let found_line = &self.cols[x..x + 4]
    .iter()
    .enumerate()
    .find(|(offset, col)| col.count < y + offset || col.pieces[y + offset] != owner)
    .is_none();

if found_line {
    return Some(match owner {
        Piece::White => GameResult::WhiteWin,
        Piece::Black => GameResult::BlackWin,
    });
}

🚀

    'found: {
        let owner = col.pieces[y];
        for offset in 0..4 {
            let col = &self.cols[x + offset];
            if col.count < y + offset {
                break 'found;
            }
            if col.pieces[y + offset] != owner {
                break 'found;
            }
        }
        return Some(match owner {
            Piece::White => GameResult::WhiteWin,
            Piece::Black => GameResult::BlackWin,
        });
    }

😕

let owner = col.pieces[y];

&self.cols[x..x + 4]
    .iter()
    .enumerate()
    .find(|(offset, col)| col.count < y + offset || col.pieces[y + offset] != owner)
    .is_none()
    .then(|| match owner {
        Piece::White => GameResult::WhiteWin,
        Piece::Black => GameResult::BlackWin,
    })

ETA: did a quick poll of my (small) office and all of them agreed the first is clearest

@kennytm

This comment has been minimized.

@clarfonthey
Copy link
Contributor

I actually do like the idea of then as a keyword a lot. One of my biggest issues with the Python syntax is that the word "else" implies that the block will be run instead of the loop, instead of unless it breaks. With if-else, we mentally separate out the two branches as separate paths, whereas for-else are combined paths.

A for-then or while-then gets rid of that problem. I don't think that adding in an extra keyword should be seen as that big of a detriment, but we technically could use do instead, although I'd hesitate against it since it would be too similar to do-while loops.

To me, for-then says "do this loop, then that" and it's not too difficult to understand that a break would skip the then. Whereas for-else is "do this loop, otherwise do this if it doesn't break," which is much harder to understand.

@Starwort
Copy link
Author

Starwort commented Aug 20, 2021

What would you think about the following instead:

for i in 0..2 {
    // code
} broke (value) {
    value
} else {
    other_value
}

The broke clause could be omitted for the semantics as above, would not need the (value) if bare breaks were used, and is probably clearer to those opposing for/else
The loop would still need a consistent type for each break-with-value, but it would only have to match the type of the else if the broke clause was removed.

Does that seem like an acceptable compromise?

N.B. The syntax for the broke clause is based on catch clauses from other languages and could be revised prior to formal introduction to the RFC

@Starwort
Copy link
Author

Now that I'm thinking about it more, I actually like while/broke/else even more than my original RFC as common cleanup code could be moved into the broke clause, while still allowing everything the original proposal does

@clarfonthey
Copy link
Contributor

broke feels like it's getting to the point where you're just mimicking try blocks. At that rate, you're replacing break with yeet and catch with broke.

The "else" block avoids storing extra state whereas the "broke" block just calls a function for every broken value.

@Starwort
Copy link
Author

That's the primary reason why it would be optional; in most cases for/else would be used but in cases with complicated exit code the broke clause would be used for DRY purposes

@PatchMixolydic
Copy link
Contributor

PatchMixolydic commented Aug 25, 2021

#3152 suggests nobreak as a replacement for else (also mentioned in passing in this comment), which significantly clarifies when the second branch is run compared to for-else:

let owner = col.pieces[y];

for offset in 0..4 {
    let col = &self.cols[x + offset];
    if col.count < y + offset {
        break;
    }

    if col.pieces[y + offset] != owner {
        break;
    }
} nobreak { // `k#nobreak` before Rust 2024 if RFC 3098 is merged
    return Some(match owner {
        Piece::White => GameResult::WhiteWin,
        Piece::Black => GameResult::BlackWin,
    });
}

Personally, I'd still prefer the iterator or label_break_value approaches. Neither of them would require reserving a unique keyword or utilizing a strange take on an existing keyword, and the label_break_value solution seems much more readable in my eyes, especially to those unfamiliar with for-else constructs.

@ssokolow
Copy link

ssokolow commented Aug 27, 2021

As Rust is a systems language, I would expect that more people would have an understanding of the desugar (at its core, a while loop just uses a guard to goto the loop's beginning; therefore an else can be attached to that guard) than Python, which is a very high-level language. Additionally, the point of that survey was that the responders couldn't look at documentation; those who already know the syntax could use it fine, those who don't can use the guard version, and those who encounter it can look at the feature's documentation and join the first group.

As someone who has been programming in Python for ~18 years and is familiar with the Python equivalent to this proposal, I'd say that Rust being a systems language is more reason not to implement such a feature.

Rust is already a fairly complex language and we don't want to become C++. This doesn't feel like it benefits enough to be worth what it costs to the language's complexity budget.

Heck, as someone who was introduced to this kind of else right at the beginning by the O'Reilly Python 2.3 book I learned from, someone who spent a fair amount of time being "too clever for my own good" as far as maintainability goes, and who's gone through just about every programming style Python allows over the last two decades (I still have some codebases that I need to refactor away from "enterprise overload" OOP-style), I think I used for/else maybe once or twice and I'm not sure I ever used while/else.

In my experience, they're just too niche to be worth it.

@joshtriplett joshtriplett added the T-lang Relevant to the language team, which will review and decide on the RFC. label Aug 27, 2021
@homersimpsons
Copy link

For the record, I would expect a while/else and for/else to trigger the else if the loop does not loop.

In python, it has this rfc behavior: https://book.pythontips.com/en/latest/for_-_else.html (which seems controversial)
But in Twig, it has an "empty loop behavior" https://twig.symfony.com/doc/2.x/tags/for.html#the-else-clause

The else also forces to check the in-loop behavior and should not be confused with a parent if.

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Aug 27, 2021

@rfcbot fcp close

I thank the author for taking the time to prepare the RFC, but I tend to agree with those who have said that the history of this feature in Python is actually evidence against its inclusion. It's just a bit too niche and confusing, and the meaning of else is not obvious or intuitive (as evidenced by 55% of folks in the survey assigning it the wrong meaning), even if it can obviously be useful. I therefore move to close.

@rfcbot
Copy link
Collaborator

rfcbot commented Aug 27, 2021

Team member @nikomatsakis has proposed to close this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. disposition-close This RFC is in PFCP or FCP with a disposition to close it. labels Aug 27, 2021
@durka
Copy link
Contributor

durka commented Aug 29, 2021

In Python I find this feature both useful (as this RFC says at the beginning, it's a common pattern that is cumbersome to solve with a boolean) and also very confusing.

I can never remember that else means "loop did not break" because, to me, break is sort of an exceptional thing to do, so I first think that should jump to the else clause, the same way exiting early from try ends up in catch... but wait, that's not how for/else works, is it... unless... oh dear, better Google it this time just like every time.

So yeah, I agree this is a problem, but I do not support using else as the solution. Perhaps some macro crates can experiment with other ideas like broke suggested in previous comments.

@joshtriplett
Copy link
Member

@durka I agree entirely with that characterization: we need a recommended, idiomatic, Rustic solution to the underlying problem (handling loops that don't find what they're looking for), but not for ... else.

@rfcbot
Copy link
Collaborator

rfcbot commented Aug 30, 2021

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. labels Aug 30, 2021
@fstirlitz
Copy link

It’s a shame that something so apparently hastily written was submitted here. I had been drafting a much more elaborate proposal that actually addresses the Python objection. But now that this is being rejected, it makes me doubt it’s going to have a fair hearing.

@ssokolow
Copy link

ssokolow commented Sep 1, 2021

@fstirlitz I don't hold prejudice against a concept based on prior proposals and I don't get the impression Rust team members do either.

Your idea is certainly better, with exhausted being less confusing than else... but, being perfectly honest, I'm still not convinced having a special exhausted case brings enough value to justify itself. That was always half my concern.

(eg. What about empty like how else is used in templating languages? It feels questionable to privilege this specific case but not that one. ...and, if you address both, how do we go about minimizing the sense of added complexity?)

@lebensterben
Copy link

@fstirlitz
Your draft has the same problem as this PR, that they are lack of meaningful example.

@fstirlitz
Copy link

fstirlitz commented Sep 1, 2021

@lebensterben Fair, I might need to look for something more elaborate than find.

@ssokolow I would hope so, but I do recognise proceeding RFCs comes with its own fatigue. At some point people may get tired of the whole subject.

The primary motivation for an exhausted block is to facilitate assigning a return value to loops, by covering the case in which the loop did not hit a break expression that would determine that returned value. Singling-out the no-iterations case as special doesn’t help with this at all: the loop may still be exhausted after a non-zero number of iterations, at which point the returned value will be indeterminate.

Also, adding a loop-exhaustion block is a pretty natural modification of the current loop desugaring, in a way in which singling out no-iterations is not: where while $condition { $body } is currently equivalent to loop { if $condition { $body; } else { break; } }, adding an exhaustion block would simply put it after the break in the desugaring. Special-casing no-iterations would be much more involved.*

If there is a burning need, one may attempt to use macros to build an equivalent of the for loop with the no-iterations case singled out.


* Footnote

Although i would have been pretty easy to express with the non-reducible/state-machine loop construct I discussed once:

let mut it = /* obtain iterator */;

fsm_loop First(it.next()) {
	First(Some(item)) => {
		continue Next(Some(item));
	},
	
	First(None) => {
		/* no iterations */
		break;
	},
	
	Next(Some(item)) => {
		/* got an item */
		continue Next(it.next());
	},
	
	Next(None) => {
		/* exhausted after non-zero iterations */
		break;
	}
}

@scottmcm
Copy link
Member

scottmcm commented Sep 1, 2021

Note that ControlFlow+try_fold+try gives some pure-library phrasings of these things, which can help.

For example, that "you can write your own Iterator::find" example becomes

let found = haystack.into_iter().try_for_each(|item| try { 
    if predicate(item) {
        return ControlFlow::Break(item);
    }
}).break_value();

And in general the else { FOO } can, using that, often become .break_value().or_else(|| FOO ).

So for-else feels like it's a strange intermediate, to me. It's more complex than the basic structured control flow stuff, but also not particularly generalizable to more complex situations. I think I'd be inclined more towards leaving it out, but having either label-break-value or the more elaborate custom-fsm construct instead.

The loop-break-value version is

let found = 'found: {
    for item in haystack {
        if predicate(item) {
            break 'found Some(item);
        }
    }
    None
};

which is nearly exactly the same length as the else version, and personally I think it's better at avoiding the "what did else mean again?" confusion.

(And you can make a stable version of that by just adding a loop and break in the appropriate places.)

it makes me doubt it’s going to have a fair hearing.

I wouldn't worry too much about that. Take #1603, for example, which was one of the more heavily 👎'd RFCs on this repo and was not accepted. But the same idea came back a year and a bit later as #2113 where it was well-received, and it's soon going to be required, with edition 2021.

If there's a change that's a good-enough fit, it'll happen, even if similar things were rejected many times before.

@Treeniks
Copy link

Treeniks commented Sep 3, 2021

I specifically remember being overwhelmed by the amount of options Rust has for loops when I first learned the language. With the existence of standard for and while, the rather unusal loop (compared to other languages) and the while let variant, Rust is already a lot more complicated when it comes to simple loops than most languages.

for and while is standard in pretty much every C-like language, and they also function the exact same across all those languages, including Rust. while let is a natural extension of while with Rust's Enum system, whereas loop is something I haven't seen in other languages yet (not that I know many), but also naturally integrates with Rust's "everything is an expression" philosophy. But the proposed exhausted syntax personally doesn't seem to be quite such a natural extension (without looking at the underlying desugaring, which is something I would rather not have to think about while programming) and also doesn't exist widely across C-like languages, thus also the confusion with the python survey I assume. I feel adding features that don't exist in most programming languages to syntax that does exist in most programming languages will add unneeded confusion for anyone either coming from another language and trying to learn Rust, or for anyone who has to switch between different languages regularly.

While I personally find the proposed alternatives using Iterators to be very unreadable, the label-break-value alternative looks very similar to a for ... exhausted syntax, while using a natural extension of Rust's labels and leaving no room for confusion. I feel as though, if we were to give a programmer, who has never seen or written Rust code before, a snippet of code with the for ... exhausted syntax and a snippet with the lable-break-value syntax (the latter with specific return keywords), they would have a far easier time understanding the label-break-value syntax (in terms of Control Flow at least), not to mention they could be 100% sure about the Control Flow, whereas the for ... exhausted syntax, while the exhausted keyword is certainly more accurate than else, could still potentially leave open questions. With the else keyword, there would exist multiple interpretations of what it could mean, all of which exist in some other language, so they would have to pick one at random. With the exhausted keyword, they could guess what that keyword probably means, but it would be a keyword that is exclusive to Rust and thus it would still be a guess of what it means exactly. I feel as though there is something beautiful in a solution for which you don't need to know the language to be able to understand it, and the label-break-value syntax certainly achieves this better (although still not perfect) in my opinion.

@fstirlitz
Copy link

That’s 461 words spent on an argument that boils down to ‘but it’s not very familiar to the average programmer’. To which I reply: unfamiliarity is temporary. This is not a difficult concept to grasp once explained, and it can be explained in less than two minutes. Someone who has never seen Rust before is going to spend much more time on lifetime annotations than on this relative triviality. You’d have to be pretty selective with your snippets already if you want to find something that will be immediately obvious to someone entirely unfamiliar with Rust. (And even then you’re making assumptions about them being familiar with something else. To someone unfamiliar with any programming, all code is incomprehensible.)

The same argument could have been made about the match statement a while ago: until quite recently, pattern-matching was known only from relatively obscure languages like Haskell, Scala and ML. Now languages like Java and Python have gotten one, and there are C++ and PHP proposals in the works, even though it doesn’t add any expressive power; you could do the same things with switch or if chains in those languages.

@jhpratt
Copy link
Member

jhpratt commented Sep 6, 2021

Without reading the reference-level explanation (which shouldn't be necessary to understand your proposal), I sincerely have no idea what is even being proposed. I am aware that other languages have for/else & where/else, but have never seen it used in the wild (probably because nobody knows what it does). The comparison to pattern matching with match is a stretch given that any (non-beginner) programmer could immediately think of situations where it's useful.

You stated five days ago that "now that this is being rejected, it makes me doubt it’s going to have a fair hearing." It has not yet been rejected, as FCP has not yet completed. There is a ten day period for a reason. If there are clear and obvious reasons to include this, then it should be so stated. But when an experienced programmer has no idea what's even being proposed, that by itself is reason to close in my opinion.

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this RFC. to-announce and removed final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. labels Sep 9, 2021
@rfcbot
Copy link
Collaborator

rfcbot commented Sep 9, 2021

The final comment period, with a disposition to close, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

The RFC is now closed.

@rfcbot rfcbot added closed This FCP has been closed (as opposed to postponed) and removed disposition-close This RFC is in PFCP or FCP with a disposition to close it. labels Sep 9, 2021
@rfcbot rfcbot closed this Sep 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed This FCP has been closed (as opposed to postponed) finished-final-comment-period The final comment period is finished for this RFC. T-lang Relevant to the language team, which will review and decide on the RFC. to-announce
Projects
None yet
Development

Successfully merging this pull request may close these issues.