-
Notifications
You must be signed in to change notification settings - Fork 56
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
Add let else statements #165
Conversation
``` | ||
|
||
If the `else` and opening brace do not fit on the same line as the initializer, | ||
break before `else`. If a line begins with `else`, it should be indented at the same level as `let`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
break before `else`. If a line begins with `else`, it should be indented at the same level as `let`, | |
break before `else`. If a line begins with `else` (potentially preceded by closing braces or parentheses or similar), it should be indented at the same level as the matching `let`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think "line begins with else
" and "preceded by..." are mutually exclusive. Perhaps another paragraph is needed for that case.
|
||
If the `else` and opening brace do not fit on the same line as the initializer, | ||
break before `else`. If a line begins with `else`, it should be indented at the same level as `let`, | ||
and the block should be on the same line if it fits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and the block should be on the same line if it fits. | |
and there should always be a newline after the opening brace so that the block always appears on a subsequent line. This calls attention to the `else` block attached to the `let`, making it easy to visually distinguish a `let`-`else` from a plain `let`. |
Additional justification: this only applies when the else {
doesn't fit on the line with the initializer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This calls attention to the
else
block
The style guide does not need to say "why" right?
I think what you are suggesting here should not be a dependent clause of "if a line begins with else
". How about this?
If the
else
is not on the same line aslet
, theelse
block should not be written in one line.
I'm assuming that we can lean on the "blocks" section of the guide and so "not written in one line" is specific enough.
Regardless of how it's worded, I agree, this is more consistent with if/else.
let MyStruct { foo } = ({ | ||
statement; | ||
fun() | ||
}) else { return }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}) else { return }; | |
}) else { | |
return | |
}; |
}) else { return }; | ||
|
||
let Some(1) = opt | ||
else { return }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else { return }; | |
else { | |
return | |
}; |
Co-authored-by: Josh Triplett <[email protected]>
Thank you for writing this up! Just wanted to acknowledge that I've seen this. I have a couple minor thoughts and questions I'll try to write up soon, but largely just want to park it for a while so the community has ample opportunity to weigh in |
Just to toss in my 2¢ here, I would like to see this usually formatted so that the |
@scottmcm I would prefer to hang the I think the construct will stand out by having a braced block, and we could make it stand out more by never putting the |
I'm not convinced by that. Something like
already exists in various forms, like if that's a Though I guess that gets to the whole semicolon question again. I think it'd stand out more without the |
Hm, I don't agree with the sentiment that we need to go to great lengths to maximize the visibility of the Thus I second scottmcm. There is nobody clamoring to format this: let x = match foo { ...like this: let x =
match foo { ...so I don't see the argument for doing so here. Let's just do what is most consistent, whatever that turns out to be. |
I believe our available options may be summed up as the following:
(Note this is considering shorter cases first. Of course you need a fallback for longer lines.) I would rule out option 4 where let-else is always 4+ lines. I noticed that if/else statements (not necessarily if/else expressions) always have blocks in multiple lines. If we want to be consistent with if/else in that way, then rule out option 3. If you agree that option 4 is bad and that the else block should be multiple lines, then you can't always have |
@camsteffen That's a good summary. I would propose option 2: don't let the else hide on the same line, require a multi-line else so that the control flow stands out, but always hang the else. (Unless the line is so long you have to break it in multiple places, in which case I think I'd prefer a break after the I'm weakly opposed to 1 (I think the control flow should stand out more than that), and I'm more strongly opposed to 3 and 4. (4 because it's too verbose, 3 because it has no precedent and I think it stands out less than 2.) |
I also like options 1 and 2 but I lean a little more towards option 1. I think we need to be wary of "unfamiliarity bias" - wanting to add more line breaks to make the unfamiliar feature stand out. Eventually let-else will be just another tool in your bag and then you might be more comfortable with "just put it all one one line". The extra lines could get verbose if you have several let-else lines in a row, and I think that will be somewhat common. Compare options 1 and 2 at the bottom of the seafn hole_in_the_bottom_of_the_sea(hole: Hole) {
let Some(log) = hole.find_log() else {
return
}
let Some(branch) = log.find_branch() else {
return
}
let Some(bump) = branch.find_bump() else {
return
}
let Some(frog) = bump.take_frog() else {
return
}
println!(
"There's a frog on the bump on the branch
on the log in the hole in the bottom of the sea."
);
} fn hole_in_the_bottom_of_the_sea(hole: Hole) {
let Some(log) = hole.find_log() else { return };
let Some(branch) = log.find_branch() else { return };
let Some(bump) = branch.find_bump() else { return };
let Some(frog) = bump.take_frog() else { return };
println!(
"There's a frog on the bump on the branch
on the log in the hole in the bottom of the sea."
);
} |
Friendly reminder that let-else is stabilized in 1.65 (#93628), it would be nice to have support for it not too far from stabilization |
Thanks -- the style team is aware of this stabilization, and deciding on formatting syntax for |
Regarding semicolons I wanted to share an observation with you that I made while studying auto suggestions while working on rust-lang/rust-clippy#8437 : Semicolons are not always optional. For simple cases like fn bar() {
let _ = () else { if panic!() {}; };
} FTR, this isn't a new weirdness introduced by let-else though, but already a thing with functions that return fn foo() -> ! {
if panic!() {};
} Similarly this also needs a ; for compilation (and also triggers fn foo() -> ! {
if true {
panic!()
} else {
panic!();
42
}
} There are also more examples like |
I just.... wow. Regarding @camsteffen's summary, I think I'm more in line with cam on this one. I don't feel like the having the
nvm: #165 (comment) |
@yaahc IIUC that syntax is specifically disallowed in order to prevent it from looking too much like the trailing half of an if-else block. It would have to be: let Some(1) = ({
some_particularly_long_initializer_expression_that_forces_it_to_wrap
}) else { return }; |
To adapt an example from the use std::cell::RefCell;
pub fn temporary_outlives_local() -> String {
let () = () else {
let x = RefCell::<String>::default();
return x.borrow().clone(); // without the semi: `x` does not live long enough
};
String::new()
} |
As one data point, I've been formatting my Significantly, I did it this way, in every detail, before reading his draft. This suggests that his draft may be the most consistent and expected way to format this construct in Rust. I'd be happy if Regarding suggestions that would have us format this construct more verbosely, perhaps we should remember that let Some(thing) = x.get_thing_base()
else {
return;
}; takes the same number of lines as: let thing = match x.get_thing_base() {
Some(x) => x,
_ => return,
}; then we may wonder whether there was much point in adding |
Conversely, here’s an example where the type checker forbids a semicolon, that I expect will be pretty common (until fn main() -> Result<(), Box<dyn std::error::Error>> {
let _ = () else {
Err("oops")? // here
};
Ok(())
} With a semicolon at |
Hmm how does that compile? I don't know what is the |
The (Perhaps it’s surprising to see let _ = () else {
let e = Err("oops");
let q = e?;
q
}; you can see |
After much discussion, the Style Team has decided to go with option one (as described in #165 (comment)), though with the inclusion of some additional considerations in order to be consistent with the current Style Guide edition. Specifically, the As for next steps:
Thanks to everyone that participated in the discussion! |
For awareness the PR is up adding the rules to the style guide in rust-lang/rust#107312 The thrust of the text is largely the same as what was proposed and discussed here given the aforementioned decision by the style team, with some adjustments to reuse existing language in the style guide via links and/or inline reuse |
CC rust-lang/rustfmt#4914