-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Function body blocks #3629
base: master
Are you sure you want to change the base?
Function body blocks #3629
Conversation
Co-authored-by: Eric Holk <[email protected]>
is this supposed to be read together with #3628? |
Both have value independently, and one could be approved without the other, but yes, the two proposals both make each other better. |
A downside of this proposal that I don't see mentioned is that it would make error recovery more difficult in IDEs like rust-analyzer or RustRover. Delimiters like parentheses/curly brackets make it trivial for IDEs to assume boundaries between snippets of incomplete code, but this RFC would throw a wrench into such approaches. I don't think the aesthetic wins outweigh the tooling downsides, especially for new Rust programmers. |
It will be ok for full "block" expressions:
|
i think this will cause some longer-than-expected sequences before disambiguation can be resolved: fn g<T>() where T: for the fn g<T>() where T: for _ in [0] {}
fn g<T>() where T: for<'a> Fn(&'a u8) {} (with rust-lang/rust#86935 you have to consume the I suspect that with #3628 that |
@davidbarsky wrote:
This (for both humans and tools) is exactly why I'm proposing to only allow this for constructs that accept a single block. Allowing an arbitrary expression would have the problem you're describing. Allowing e.g. |
@kennytm wrote:
In this case, the parser should know that it's expecting a type rather than a function body, right? And even if not, as you said, after a couple of tokens you know what you have.
AFAICT you would know one symbol after |
No, fn g<T>() where T: {
for _ in [0] {}
} Similar issue with empty fn g<T>() where for<'a> fn(&'a T): Sized {}
fn g<T>() where { for _ in [0] {} }
Well #3629 (comment) explained it quite clearly. |
yeah I know the inner attribute is bad style but will the following apply the attribute to the function item itself? fn X()
unsafe {
#![allow(non_snake_case)]
} and if we want to add an attribute to the block is it still like this? fn X()
#[allow(unsafe_code)]
unsafe {
} |
cc @rust-lang/style |
What Kenny said in #3629 (comment). Malformed/incomplete code—even along those lines—is really common. It's not necessarily more difficult to change a parser, but the code as-it-exists (or rather, as it is written by people) would mean that they'd have a small, but perceptible degradation in their IDE. If you're to consider a syntactic change like this, I think something like the following: fn foo(x: i32) -> i32 = gen {
todo!()
}; ...would sidestep most of the incremental parsing issues. |
this would be #3369 |
I really don't like this solution for, most of the reasons folks have mentioned. It feels too close to the braceless C statements that Rust tried so hard to avoid, and it just looks weird to me. I get all the motivation for this, but I don't think that the solution is satisfactory. I would much prefer just indenting my functions one more level and dealing with the consequences of that than adding in new syntax, unless we do find that the equals-expression syntax doesn't have the issues that were brought up in previous discussions. I'm basically assuming that the indenting is the main issue here, since typing braces is relatively trivial. The language is a balancing act of tradeoffs and I think that this one is extremely minor and not worth introducing entirely new syntax for. |
I personally think it particularly doesn't read very well when the function header wraps onto multiple lines: fn example(
x: AVeryLongTypeNameThatPushesTheSignatureOntoMultipleLines,
y: u32
) -> u32
match x {
_ => y
} |
Another prior art is scala , where the I don't have a strong opinion on it if there is a delimeter such as |
could I argue that this is ultimately a rustfmt issue, that it should prefer styles that squeeze short block wrappers on the same line or indent level? in other words: why do we need to introduce a separate syntax rather than simply changing the way code gets formatted? saving a pair of curly braces has no practical benefit other than instructing the formatter not to increase the indent level. |
Putting the body block as well as the closing '}' on the same line avoids the indentation, while only beeing a style change: fn example(
x: AVeryLongTypeNameThatPushesTheSignatureOntoMultipleLines,
y: u32
) -> impl Iterator<Item = u32> { gen {
yield x.0;
yield y;
}} You could even put the gen block declaration on the next line: fn example(
x: AVeryLongTypeNameThatPushesTheSignatureOntoMultipleLines,
y: u32
) -> impl Iterator<Item = u32> {
gen {
yield x.0;
yield y;
}} Also in this motivating example: fn foo() -> NamedFutureType
async {
...
} how would you convert the async block to the user-specified |
that's how TAIT works... #![feature(type_alias_impl_trait)]
use std::future::Future;
type NamedFutureType = impl Future<Output = ()>;
fn foo() -> NamedFutureType {
async {
}
} |
I think at least the alternative with a |
How does |
The combined |
This is from the RFC text:
I don't understand how these two statements fit together. Maybe it should say "if and only if there is an else"? |
Even if it's possible to have an expression follow the signature unambiguously I would still prefer some kind of separator. It seems more future-proof, and it would definitely make it easier to read simpler single-line functions.
This seems to be the only problem with using a separator - Rust style prefers line breaks before operators and with extra indentation, which would render the purpose of this moot. fn countup(limit: usize) -> impl Iterator<Item = usize>
= gen {
for i in 0..limit {
yield i;
}
};
// vs
fn countup(limit: usize) -> impl Iterator<Item = usize> {
gen {
for i in 0..limit {
yield i;
}
}
} I think we can all agree that eliding a layer of indentation inside a block is never gonna be an accepted style change, since it breaks the intuitive reading that the beginning and ending of any scope will have corresponding indentation. Now you'd have to match brackets yourself, or maybe your IDE will highlight them for you: fn countup(limit: usize) -> impl Iterator<Item = usize> {
gen {
for i in 0..limit {
yield i;
}
}} Maybe it could be styled like match arms, where the line break occurs after the arrow? fn countup(limit: usize) -> impl Iterator<Item = usize> =>
gen {
for i in 0..limit {
yield i;
}
} On the other hand, obviously putting the keyword before |
I would prefer the fn f() = EXPR; alternative, if we are doing this at all. It can work for both block-like bodies fn f() = match {
// arms
}; and for very short bodies fn f(x: u8) = x + 1; I'm pretty sure there was an old closed RFC about this (by Centril?), but I cannot find it now. |
@davidbarsky made a compelling case that parsing would be substantially easier with the |
@joshtriplett: Given the amount of feedback this RFC this has received, I would prefer if that were opened as a separate RFC PR. It's a totally separate proposal, so it would be nice to give people a separate, clean slate to react to the new proposal. |
please address the arguments from #3369 in that new RFC |
would also be worth considering if anyone prefers a rustfmt-based approach, which involves no compiler change and only has the disadvantage of double closing brace (which is not worse than nested blocks in function calls fn short_fn() { async {
// ...
}}
fn a_long_line(of: Arguments) -> And<Return, Types> {
async {
// ...
}
} |
Co-authored-by: Eric Holk
Rendered