Closed nrc closed 6 years ago
My take on glob imports:
Using glob imports can lead to unexpected compiler errors when merely adding or renaming a symbol in one module (example example). This is not a problem when glob importing (crate-)local symbols, but can be a big problem when glob importing symbols from foreign crates.
(additional explanation in [square brackets])
Appendix: as a consequence of rule (2.), changing the set of symbols in a public prelude is considered a breaking change and should lead to a major version bump.
@LukasKalbertodt
Using glob imports can lead to unexpected compiler errors when merely adding or renaming a symbol in one module (example).
The example doesn't break on nightly, RFC 1560 solved the glob conflict problem and was stabilized recently.
@petrochenkov
The example doesn't break on nightly, RFC 1560 solved the glob conflict problem and was stabilized recently.
I forgot to use bar()
... my bad! I updated my original comment. I agree, that the RFC makes the problem a lot less bad, but it's still possible to break builds like that.
I can't see how vertical space on import lines matters one way or the other. Vertical space matters when you need to see them along with what's above and below them, and nothing is above them.
I'm sorry, but I can't agree on "ASCIIbetical" order (I've always heard it called "lexicographic order").
That puts all-caps things like simple integer constants at the front of the list, which makes the imports look top/front heavy and makes them more difficult to scan since the least significant parts (compared to types and functions) come first.
(Sort of a "big endian is easier to read in hex dumps" sort of thing, though not as severe.)
This correction just looks too wrong and I currently keep my imports sorted by hand because of it.
- use self::libc::{access, c_char, c_int, W_OK};
+ use self::libc::{W_OK, access, c_char, c_int};
Given that I'm a stubborn [omitted], if rustfmt tried enforcing it more strongly, I'd write some kind of formatting canary and a CI hook to refuse commits in which it got removed or modified. (Heck, I still might add such a thing to my project templates, since, as I remember, the only thing stopping it right now is a rustfmt default)
Also, in the examples @nrc lists, I use and prefer the "Single import" variants. Visual indent looks nicer, but I'd also accept block indent. (As long as I'm not seeing the repeated foo::
clutter or a lot of wasted screen space from the "one name per line" variants)
The underlying theme being "It's all the same to the compiler, so what's easiest for me to scan when I'm coming back to an old codebase and need to re-familiarize myself with how it works? ...especially when Vim is hard-coded to drag the cursor along for the ride when I have to scroll."
@ssokolow I agree with that. I really don't like ASCIIbetical.
I am in favour of "multiple imports, one per line":
foo::
s to help in scanning, rather than be cluttering@heycam
Sorry, but I just can't agree. When I had to code in Java in uni., it was one of the things which grated on me the most and I perceive it as not only distracting, but primitive in a "It's 2016 and the language is still penalizing non-autocomplete users because the compiler is stupid?" sort of way.
EDIT: ...or, in less "It's 5AM and I'm overdue for bed" terms...
Even with autocomplete, it's still more awkward to expect the user to learn some IDE-specific autocomplete pseudo-macro grammar to avoid having to type EnterFTabbTabbTab (assuming the first completion match is the desired one) in place of ,
if they want to do a bunch of imports from Foo::bar::baz::
.(Or risk driving them to wildcard imports, which also add unnecessary reliance on tooling to understand the origin of symbols in the code.)
Better to optimize the part the human must unavoidably do (communicating what is to be imported) at the expense of work that rustfmt is demonstrably able to take responsibility for (syntax-aware sorting) rather than burdening them down with focusing on their autocomplete (or clipboard) invocations when it's not necessary.
I could live with dropping ASCIIbetical sorting. I personally prefer it because it puts all the constants first, which matches the order of definitions in the module itself; I put constants at the top of modules, so having them at the top of imports makes sense to me.
I put constants at the top of my modules too, but I find that different concerns inform the ordering of definitions and imports.
Back in november, @nrc said
I think this is ready for FCP. Could someone write up a summary please?
It doesn't seem that anyone has done this. I can't right this moment, but maybe we could do that to move this along?
@Screwtapello makes a good point about common names such as Connection
. Encouraging people to - at declaration or consumption - duplicate the module name in the type name runs counter to this style recommendation and the existing behavior of the standard library. It also increases the likelihood of stale aliases when renames happen during refactoring.
Separately; the recommendation against using super
has the unfortunate side-effect of making directory-level refactoring harder by requiring updates in more places. This may be something tooling can address, but at the moment it's tedious to fix.
Bit late to the party here but we should consider other forms of change, should Rustfmt change:
foo::{bar}
to foo::bar
?::foo
to foo
?I say yes to both (we currently do both of these)
@nrc I agree with both, but I know at least one person who prefers use ::foo
everywhere, so there may be a desire for an option.
I can't imagine anyone wanting foo::{bar}
.
@nrc Definitely yes to the first. For the second, do you mean changing use ::foo
to use foo
? Can rustfmt definitively determine the equivalence?
@joshtriplett I don't think there's any determining necessary. They're always equivalent.
Can rustfmt definitively determine the equivalence?
They are always equivalent since paths in imports are always absolute
And another:
use foo::self
-> use foo
I like to leave use foo::{Bar};
as-is or even use foo::{Bar,};
to reduce diff noise as additional items are added/removed: use foo::{Bar, Baz,};
We've changed our process: rather than requiring a full RFC, a PR to close this issue only requires making the changes discussed here to the style guide. You can see more details about the process in the readme for this repo.
If you'd like to help with this work, let me know, I'd be very happy to help you help us.
I'd be interested in working on this. If I understand the readme correctly, is the next step to implement the agreed-upon style in rustfmt (i.e., the 4th bullet point here)? If so, I don't see what the consensus on style was in the above comments.
cc @nrc, as I'm not sure if you get a notification otherwise
@alobb The Rustfmt tracking issue is https://github.com/rust-lang-nursery/rustfmt/issues/1361. I think the next steps are doing the implementation and creating a PR in this repo against the style guide, these are meant to be sequential, but could be done in parallel.
I'm afraid there is no good summary in this thread. It might be a good to start is by writing such a summary. On re-reading this thread it is hard to decide exactly where consensus is, so anything that fits at least some of the comments is probably a good place to start.
I would include the following:
To summarize the discussion in this thread:
use foo::{bar} // becomes `use foo::bar`
use ::foo // becomes `use foo`
use foo::self // becomes `use foo`
super::
to refer to anything outside the current fileuse std::fmt; // and use fmt::Write
use std::io; // and use io::Write
self
is included in a group of paths, it should be the first path listed (If self
is the only path, it will be normalized and removed)self
) will be sorted alphabeticallyuse foo::*;
use foo::bar::baz;
use
statement.use
statement fits within one line, it is not split into multiple lines.use
statement over multiple lines there is also the question of how far the indent should go; it can be 1 or 2 indents; 2 indents has the benefit of avoiding import names lining up with module names.
// Single import, one name per line.
use foo::{
bar,
baz,
qux,
dssdfas,
};
// Multiple imports, maximum names per line. use foo::{bar, baz, qux}; use foo::{dssdfasf};
// Single import, full block indent use foo::{ bar, baz, qux, dssdfasf, dsfsdfs, };
// Single import, visual indent. use foo::{bar, baz, qux, dssdfasf, dsfsdfs};
// Multiple imports, one name per line. use foo::bar; use foo::baz; use foo::qux;
// Single import, block indent. use foo::{bar, baz, qux, dssdfasf, dfsdfs};
If anyone feels that I have misrepresented any of the above (_especially_ preferences of import layout), please comment on this saying so; I've attempted to pull all shared ideas, but may missed something.
@alobb That looks like an accurate summary to me. Three nits:
1) The styles you mentioned only apply if the names take up more room than will fit on a single line. If the entire import statement fits on one line, put it on one line.
2) On any of the examples that have a continuation line (which includes "single import, one name per line" and "single import, block indent"), I'd personally suggest using two levels of block indentation on the names, rather than one, so that they don't line up with the module name; otherwise, the module name and the imported names from that module appear at the same level.
3) The description of "Types and functions should appear ahead of constants within a use statement.", while not directly tied to any particular consensus I could find in this issue, does sound reasonable to me. (While I prefer "constants first" instead of "constants last", as long as constants get grouped together separately from types/functions, that works for me.)
Thanks for the feedback, updated the summary!
you mention that changes to the compiler would allow rustfmt to perform gather this data; what is the state of that work?
The changes I'm thinking of are moving name resolution into the same phase as macro expansion. That work is complete (or almost complete). However, leveraging this in rustfmt seems like hard work at the moment. So, I think for now we should expect to specify but not implement this part.
Thanks for the great summary @alobb
Thoughts:
At a function or block level
I would not even recommend this. Although I would frown on it less than at module level.
Avoid using super:: to refer to anything outside the current file
Not sure how I feel about this. I agree in principle, but there are cases where it is more succinct and just as clear.
I would add to advice - avoid importing individual variants or all variants of an enum; use the name of the enum in paths (except for variants in the prelude).
either types and functions appear first, or constants appear first
I'm having second thoughts about this. I think this is hard to implement - we could only rely on convention in order to enforce it. Alphabetical sorting would get close to this and I think is good enough.
It is unclear if prelude glob imports should go ahead of other crate imports
They should not.
For sorting, I think it would pay to be explicit about sorting of import statements, and of sorting names within a single list import.
In particular, we should probably say that glob imports get sorted before other imports from the same module, e.g.
use foo::*;
use foo::bar::baz;
how far the indent should go; it can be 1 or 2 indents
I prefer one - we don't use two anywhere else and I don't see a strong argument for starting here.
For layout, I nominate 'Multiple imports, maximum names per line.' (I personally prefer 'Single import, block indent.' (or visual indent), but they seem unpopular).
Thanks for the feedback, I'm integrating it now. I'm not sure indentation level has enough consensus (I don't think it was discussed enough) to choose either way in the summary.
Also, it looked like there was quite a bit of support for limiting usage of super::
, so I've left that as-is for now.
To be clear, most of my comment was intended as how we should move forward (IMO) with the tricky bits, rather than claiming consensus now. I should have been more explicit about which was which.
@nrc
I'm having second thoughts about this. I think this is hard to implement - we could only rely on convention in order to enforce it. Alphabetical sorting would get close to this and I think is good enough.
Alphabetical sorting wouldn't do this. ASCIIbetical sorting would come closer, but it'd sort types and constants together rather than separating them, and two comments in this issue disliked ASCIIbetical order. I think "separate constants from types/functions/etc, and sort those two groups separately" seems promising (no matter which order those two groups go in). I don't mind doing that by convention if rustfmt can't do it by type.
Although it might not be possible to get the grouping exactly correct without the compiler, would it be enough to consider all paths that contain ['A'-'Z' | '_'] to be constants (and therefore sorted and grouped together), and everything else to be types and functions? I may be misunderstanding something, but if the users are following the naming conventions, it seems to me that this should work. (This may be what was meant by relying on convention, not sure)
Edit: I am also in favor of clearly separating constants from functions and types, as long it doesn't make the implementation too complex or inefficient.
I don't mind doing that by convention if rustfmt can't do it by type.
The trouble is that if rustfmt does any sorting at all, then it break the convention even if the user is abiding by it, and it has no way to know whether the source code is following the convention which it can't support or just out of order.
I am also in favor of clearly separating constants from functions and types
I do not think this is possible since rustfmt doesn't know the difference between the name of a type and the name of a function
I don't mind doing that by convention
Actually, perhaps I misunderstood. Are you proposing we group the imports based on ALL_CAPS vs CamelCase vs snake_case and order each group? (With or without merging CamelCase/snake_case).
That could work, the question then is should we do that or alphabetical or ASCIIbetical?
@nrc I would suggest grouping those and sorting alphabetically within groups, rather than sorting ASCIIbetically. I could live with any ordering of those groups, though I'd tend to favor constants then types then functions.
It took me a while to realize what the actual ordering problem was, so here's a concrete example for anyone else in my position. This is plain "ASCIIbetical" or lexicographic order:
use foo::{BAZ_QUUX, BazQuuz, FOO_BAR, FooBar, baz_quux, foo_bar};
I didn't realize earlier that this simple approach would interleave ALL_CAPS
and MixedCase
identifiers. I agree with the people suggesting something like this:
use foo::{BAZ_QUUX, FOO_BAR, BazQuux, FooBar, baz_quux, foo_bar};
However, like @joshtriplett, I don't care strongly about the order between the groups. I just want them cleanly separated and independently sorted. I think this would be easy to support.
OK, that (alphabetically sorted groups) sounds fine to me too. I don't feel too strongly about the ordering either, but I think we should define one. If I were to do this myself, I would go functions/modules, types, constants. But I don't really know why.
I order them as constants, types, functions/modules, because this is what sort
does.
I can see arguments for both. I tend to put constants/types/functions in that order, both because it tends to match the order I declare things in a module, and because I'm used to ASCIIbetical order in ls
placing things like Makefile
and README
first. On the other hand, the more I think about it, the more I can understand wanting to put constants last, because seeing them can potentially front-load information without context ("why do you need that constant, oh, you're calling that function"). I could understand an "outer-to-inner-usage" argument for putting constants last. On the other other hand, the kinds of constants you pass to functions would make sense to put inside appropriate enum types, while the kinds of constants you declare at the top level might not actually get passed to functions, so the "outside-in" approach might not make sense.
In the end, though, the argument that makes the most sense to me is "order the use list in the same order as the declarations in the module", which would argue for constants/types/functions.
So, given that we recommend functions not be imported but called via their module, it is probably worth thinking of the groups as modules/types/constants. As well as @joshtriplett's "outer-to-inner-usage" argument, I would argue that the modules/types/constants order puts the most frequently used things earlier (I rarely use constants, frequently use modules (esp to scope functions), and types are in between.
As long as constants are at the end (because they're the least "significant"), I'm flexible.
I've been working on a C++11 project using ClangFormat, and found that it does the alphabetical sort of #include
groups, which is really nice (even though it can and did break my code once).
I didn't realize how much manual time I actually spent keeping those sections all sorted nicely, so I am now greatly looking forward to when rustfmt
does this. So big +1 for that feature.
This is how I currently handle imports that are too long for one line in winapi. I'm fine with this not being the default, as long as it is provided as an option.
use um::d3dcommon::{
D3D_CBUFFER_TYPE, D3D_INCLUDE_TYPE, D3D_NAME, D3D_REGISTER_COMPONENT_TYPE,
D3D_RESOURCE_RETURN_TYPE, D3D_SHADER_CBUFFER_FLAGS, D3D_SHADER_INPUT_FLAGS,
D3D_SHADER_INPUT_TYPE, D3D_SHADER_MACRO, D3D_SHADER_VARIABLE_CLASS, D3D_SHADER_VARIABLE_FLAGS,
D3D_SHADER_VARIABLE_TYPE, ID3DInclude,
};
This is what I'd prefer to be the default for rustfmt.
use um::d3dcommon::{
D3D_CBUFFER_TYPE,
D3D_INCLUDE_TYPE,
D3D_NAME,
D3D_REGISTER_COMPONENT_TYPE,
D3D_RESOURCE_RETURN_TYPE,
D3D_SHADER_CBUFFER_FLAGS,
D3D_SHADER_INPUT_FLAGS,
D3D_SHADER_INPUT_TYPE,
D3D_SHADER_MACRO,
D3D_SHADER_VARIABLE_CLASS,
D3D_SHADER_VARIABLE_FLAGS,
D3D_SHADER_VARIABLE_TYPE,
ID3DInclude,
};
rustfmt issue for linebroken use rust-lang-nursery/rustfmt/issues/788
Thread is summarised in https://github.com/rust-lang-nursery/fmt-rfcs/issues/24#issuecomment-289649096
Within each import, we sort in groups: snake_case, CamelCase, SCREAMING_SNAKE_CASE, within each group, sort alphabetically
In an internal module, should I prefer importing by path (e.g. use foo::Foo
) over importing the re-exported item (e.g. use Foo
) or the other way around?
cc https://github.com/rust-lang-nursery/rustfmt/pull/1562 which implements grouping based on whitespace rather than semantics of names
One thing I haven't seen mentioned at all is spaces inside the braces in a multi-item use
. Everyone seems to take it for granted that there aren't any spaces, but personally I find that very jarring as in all other cases an open brace is always succeeded by whitespace and a close brace is always preceded by whitespace. Specifically instead of use foo::{self, bar, baz};
I write use foo::{ self, bar, baz };
.
At this point from the amount of code I've seen in the wild using the former I guess I'm wrong and I need to retrain my internal parser to not always expect whitespace in braced blocks, but just thought this could be noted in case there was a silent majority it may provoke to rise up.
To be honest, my formatting intuition has been parsing it as a Python set literal.
Even ignoring that, I see the fact that it's a comma-separated list as being more significant than that it uses curly braces... thus, it should use whitespace rules consistent with [arrays], (tuples), and fn(arguments).
For one thing, if you're looking up the origin of a name imported via a glob import, it's immediately necessary to know whether there is more than one glob import. If there are two glob imports and you only notice one of them, you might incorrectly assume it's the source and start barking up the wrong tree.