-
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
closures #35
Comments
Some random initial thoughts from me: Always leave off the |x| x + 1 // yes
|x| { x + 1 } // no single-expression closures can stay on one line, multi-statement ones should be spread out like this: |x| {
let mut y = x + 5;
y = y * 2;
y
} That is, following the usual function formatting. ... I thought I had more, but I'm not sure yet. |
@steveklabnik I think you meant "one-line expression" instead of "single expression". || Foo {
a: 5,
b: 10,
} I like to write the code above like this: || {
Foo {
a: 5,
b: 10,
} Of course instead of |
For what it's worth, the style @steveklabnik suggested is exactly the one we use for JavaScript internally, and it's extremely reliable and maintainable. (The difference there is the requirement of |
Question, if the closure has a single expression, but it spans multiple lines, should we use braces? Possible answers are (at least): always, never, if the expression is not block-like (for some definition thereof). E.g.s,
I believe Rustfmt always uses braces if the expression is multi-line. Related is the match arm case. |
For multi-line non-block-like expressions, I would definitely use braces in both closures and match arms. As for block-like expressions... I used to do things like this pretty often, to avoid a level of indentation: match e {
pat => for i in blah {
// ...
},
} But gradually I have found this style more readable in real code, despite the extra indentation: match e {
pat => {
for i in blah {
// ...
}
}
} Hence, I would choose the option to simply always use braces for multi-line expressions. To be honest, I don't run into this exact case much with closures, but this is what I would do if it came up. |
By statements, do you generally mean expressions with the value |
@solson I don't know if "has type I don't think I'd classify a simple function call without a semicolon as a statement, even if it returned Some examples: takes_closure1(|x| x + 1);
takes_closure2(|x| f(x + 1));
takes_closure3(|x| {
f(x + 1);
});
takes_closure4(|x| if cond { x } else { 42 });
takes_closure5(|x| {
if cond {
f(x);
} else {
g(42);
}
}); Note that #21 and #34 have similar discussions regarding expressions versus statements; for instance, in #21 I argued that |
I see. What do you think of single-line |
@solson Not a fan, but in some circumstances you could write that as |
@solson I feel like we should apply roughly the same rules for closures as other blocks (but where a single-line block would not have braces for a closure), and I think we would not permit that as a one-line block. @joshtriplett there is a class of expressions which can be statements without semi-colons (basically anything which ends with a block), I think you want to force braces for these expressions and 'proper' statements. However, you seem to want to allow eliding braces for single line |
Some more points: we should recommend what to do with types, but I think it is pretty obvious how to format them (just like other functions). The consensus here seems to be that we should force braces for multi-line single expressions. This is interesting to me because it is what Rustfmt currently does, but I got the sense from issues filed there and comments that sentiment was moving to allow eliding braces in that case (similarly for match arms). So, I'd like to explicitly ask the question (beyond those who have already comment in the thread), do you prefer:
or
Another question (which I think is orthogonal to the above) is should we have an extra indent for closure bodies?
or
I think I prefer the first. Rustfmt does the second (at least sometimes), but I believe that is a consequence of nesting and visual indent, and with our preference for block indent, it will not be an issue. |
Question 1: I used to prefer the former (no block) but after using both for a while, I ended up preferring the latter (with surrounding block). I think the benefit of the block is apparent when long patterns push the match x {
SuperLongPattern(_, SoMuchPattern(..), PatternPatternPattern(a, b, c)) => Foo {
f: x,
g: "foo",
},
} match x {
SuperLongPattern(_, SoMuchPattern(..), PatternPatternPattern(a, b, c)) => {
Foo {
f: x,
g: "foo",
}
}
} Question 2: I strongly prefer no extra indentation for closure bodies. |
@nrc "multi-line expression" accurately captures the distinction for me, yes: if you could otherwise write the expression on one line if not inside a closure, you can do so in a closure and omit the block. Together with the existing rule about when to write if on one line, I think that works. For question 1: in the examples you posted (and very specifically for a struct constructor), I prefer no extra block. A similar case arises for function parameters, and I think we should make a consistent decision there; do we allow the following, if the line lengths fit? some_function_call(Foo {
f: x,
g: "foo",
}); For question 2: I also strongly prefer no extra indentation level for closure bodies (one level of indentation, not two). |
@joshtriplett as opposed to forcing block indent?
Or something else? |
@nrc Right. I don't know if we should have that special case, but I've seen it in a lot of code, and it seems quite readable to me. Note that by design you can't nest it more than one level deep on the same line, since the next opportunity to insert a constructor would occur inside the struct initializer on a separate line; that pattern can only appear as the last thing on a line. But I think we should apply the same rule to both function calls and closure bodies here: we should allow either both or neither of the following, for consistency: some_call(Foo {
f: x,
g: "foo",
})
some_call(|x| Foo {
f: x,
g: "foo",
}) |
I'm not clear about what you mean here, actually. The examples above are all multi-line expressions (i.e., you couldn't write them on one line if not inside a closure) but don't have blocks. My interpretation is that if the body of the closure is a single expression (with no comments outside) then it can be written without a block in a closure (or match arm), no matter how many lines it spans. Is that correct? @solson the argument about pushing the struct name away from its fields seems to me like a general criticism of block indent and occurs in many places independent of this block issue. E.g.,
I don't think I'm persuaded that it is worth the extra vertical space and syntactic noise of forcing a block here, but not in other cases.
|
Yes, that works, assuming that "single expression" has the obvious intuitive meaning. |
To summarise the answers (that I prefer and seem to be close to consensus in this thread) to questions I asked earlier:
|
yes |
I myself omit the block whenever possible (including multiline expressions) in most cases. That’s what I prefer. That being said, I also use sanity factor, which is kinda hard to encode into a formatter :) |
I haven't been following the style RFCs, but it's worth mentioning that there are some issues raised in the rustfmt repo regarding closure syntax: rust-lang/rustfmt#1791 |
From, #1791, there is some discussion about whether we should be allowing multi-line single expressions which are block-terminated (i.e., mostly control flow) as the body of a closure without a block (which we has agreed on further up this thread). @joshtriplett asserts that "This definitely shouldn't happen with There is an argument that not introducing a block makes it easy to make mistakes, e.g.:
I'm not sure how sympathetic I am to that argument. It seems reasonable at first blush, but I feel there are other places where you need to be careful about matching braces or you get errors too, and this doesn't seem to be the worst. So, I feel like this is a topic we've moved around a lot on and wherever we land we get some unhappy people. So I think we need to acknowledge that we're not going to be perfect here. So I see two options:
The first option seems bad to me because of predictability. The second has some unfortunate effects, e.g.,
I guess we might also have some heuristic based on the number of lines in the block, but I feel these have been more loss than win in the past. |
(FWIW, I don't see the last one as an unfortunate effect, I'd be fine with such formatting) |
My preferred rules would be:
I'm okay with compacting expressions that don't affect control-flow, e.g.: |x| unsafe {
// ...
} Those are block-expressions that are simply marked or labeled in some way. But if the block-expression affects the direction of the program flow ( I also find collapsed control-flow blocks rather annoying to edit, for example, to get from this: match *self {
State::Promise { closed_count } => if closed_count < len {
actor::current_wait_until(deadline);
} else {
actor::current_select(CaseId::abort());
},
// ...
} To this: match *self {
State::Promise { closed_count } => {
if closed_count < len {
actor::current_wait_until(deadline);
} else {
actor::current_select(CaseId::abort());
}
*self = State::Revoke { case_id: actor::current_selected() }; // The new line.
}
// ...
} I do the following steps:
Having to do the 5th step only would make my life a bit easier. :) Honestly, when rustfmt first started putting if-expressions right after |
Count me in the category of "if the closure has multiple lines (regardless of # of expressions), it should use braces" Dislike 🤢 fn main() {
thread::spawn(|| {
thread::spawn(|| if true {
100000000000000000000
} else {
200000000000000000000
})
});
} Like 😋 fn main() {
thread::spawn(|| {
thread::spawn(|| {
if true {
100000000000000000000
} else {
200000000000000000000
}
})
});
} Rationale 🤔 I don't quite know how to explain it, but having the I also think this applies beyond closures. It appears as if my example above does what I want with a Like 😋 fn main() {
match a {
_ => if true { 100000000000 } else { 2000000000000 },
}
} Like 😋 fn main() {
match a {
_ => {
if true {
10000000000000
} else {
200000000000000
}
}
}
} |
Just to throw in yet another example that has been bugging me for a while... I have the following piece of code: *self = Machine::Counting {
len: len + 1,
first_id: if first_id == CaseId::none() {
case_id
} else {
first_id
},
deadline,
seen_blocked,
}; What throws me off here is that But the *self = Machine::Counting {
len: len + 1,
first_id: {
if first_id == CaseId::none() {
case_id
} else {
first_id
}
},
deadline,
seen_blocked,
}; If you're not convinced, here's a more difficult example. Try skimming over the code and figuring out what belongs where. It's kinda tricky. :) Then try comparing with a version with more indentation. |
What should they look like? I'd like to define the un-nested cases first and deal with nesting at the same time as nested blocks once we have the basics for a few other expression classes done.
The text was updated successfully, but these errors were encountered: