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

Ordering of types of groups within a module #71

Closed
johnthagen opened this issue Mar 7, 2017 · 44 comments
Closed

Ordering of types of groups within a module #71

johnthagen opened this issue Mar 7, 2017 · 44 comments
Labels

Comments

@johnthagen
Copy link
Contributor

johnthagen commented Mar 7, 2017

Is there any guidance about the general order of how things should be defined in a module? For example.

extern crate ...

use ...

// Declaring modules.
mod ...

// Rest of code: consts, structs, impl, functions, trait, etc

I'm unclear where mod declarations should go (before use or after use?). I can't find if this has been discussed before. I'm not sure it's something rustfmt would try to fix, but having a guideline could still be helpful. Relates to #24.

Edit: Sometimes you need to declare a mod before you can import it:

@steveklabnik
Copy link
Member

Also related to (almost a dup of?) #24

@johnthagen
Copy link
Contributor Author

johnthagen commented Mar 7, 2017

Yeah, I almost commented on #24 but wasn't sure if it was quite on topic or not.

@solson
Copy link
Member

solson commented Mar 7, 2017

I think this deserves its own issue. It's more general than #24 because it talks about every kind of item and the entire module.

I was chatting about this just the other day, and the order I typically follow is like this (let me know if I am forgetting something):

  1. extern crate
  2. use
  3. mod <name>
  4. mixed const/static
  5. mixed struct/enum/union/type
  6. trait
  7. impl
  8. fn
  9. mod test { ... }

That said, I'm not sure rustfmt should strictly control this ordering. Some might find that too invasive or obstructive to their desired code organization. We could provide such an ordering as a guideline in the style guide.

But I would be okay with rustfmt forcing extern crate/use/mod <name> to be at the top and in the order decided by issues like #24.

Sometimes you need to declare a mod before you can import it:

@johnthagen I'm fairly sure this is wrong (e.g. see here) and if the order ever mattered I would consider it a bug. Can you make a reproducible test case?

@solson
Copy link
Member

solson commented Mar 7, 2017

Additionally, within the mixed struct/enum/union/type and impl sections, I try to follow the same relative ordering of types, like:

struct A { ... }
enum B { ... }
impl A { ... }
impl B { ... }
impl Foo for B { ... }

I picked this style so all my data definitions are in one place, as opposed to the following mixed types/impls style:

struct A { ... }
impl A { ... }
enum B { ... }
impl B { ... }
impl Foo for B { ... }

@jplatte
Copy link

jplatte commented Mar 7, 2017

There are two things that are usually above everything you've listed so far: file-scoped //! doc comments (module / crate documentation) and file-scoped #![...] attributes. I think I'd put them in that order - doc comments first, then attributes.

@joshtriplett
Copy link
Member

joshtriplett commented Mar 7, 2017

@solson I agree that rustfmt shouldn't reorder those, except perhaps for extern crate, use, and mod.

I don't agree that const and static always belong at the top; sometimes they belong next to a block of related functions that use them.

Regarding struct, enum, and impl, I personally prefer the mixed style: impl A belongs immediately after struct A. And impl Foo for B belongs either right after impl B or right after trait Foo, whichever makes more sense to associate it with. (Depends on if you think of it as "defining a trait and impls for various types" or "defining a type and impls for various traits", both of which can make sense. And, of course, if either the trait or the type appears in some other crate, you only have one of those options available.) But that can vary a bit if you have a set of highly interlinked structs; in that case, it might make sense to declare the interlinked structs together, then write the impls. So in general, I don't think we should specify the order between struct/union, enum, impl, and impl-for.

I do agree that mod tests { belongs at the end. More generally, I'd suggest that any inline-defined module (e.g. mod foo { ... } rather than mod foo;) belongs either at the beginning right after all the mod foo; declarations or at the end before mod tests {. Whether the module belongs at the beginning or at the end can differ on a case-by-case basis; in general, I'd say that a small helper module that exists for intra-crate usage, or a module defined with a cfg(target) and gets consumed or re-exported by a portable exported API belongs at the top, while a module that exists to export APIs consumed outside the crate belongs at the bottom. But while we might provide guidelines on that, I don't think we should mandate it.

@solson
Copy link
Member

solson commented Mar 7, 2017

I don't agree that const and static always belong at the top; sometimes they belong next to a block of related functions that use them.

I wasn't so sure about my placement for these. I guess the place I chose is where I dump them if I have no better idea.

But that can vary a bit if you have a set of highly interlinked structs; in that case, it might make sense to declare the interlinked structs together, then write the impls. So in general, I don't think we should specify the order between struct/union, enum, impl, and impl-for.

Agreed. I was imagining defining a set of structs/enums together which contain each other in some kind of tree structure (which comes up a lot for me), but it's not a universal rule.

@solson
Copy link
Member

solson commented Mar 7, 2017

I just remembered something else: macro_rules and, I suppose, the upcoming macro system.

I tend to write macro_rules all at the top of the file, just after uses, since macro_rules are actually order-dependent. With an order-independent macro I would probably place them closer to top-level fns.

EDIT: On the other hand, even with an order-independent macro I may place it next to type definitions if the macro is used for defining types, for example.

@johnthagen
Copy link
Contributor Author

I'm fairly sure this is wrong (e.g. see here) and if the order ever mattered I would consider it a bug. Can you make a reproducible test case?

You are absolutely correct, I was wrong about this.

@nrc
Copy link
Member

nrc commented Mar 14, 2017

I think we could recommend:

  1. extern crate
  2. use
  3. mod
  4. everything else

Both in modules and functions. Beyond that I feel like there are too many special cases, or trade-offs to make a strong recommendation.

I also think that by default Rustfmt should not make this change, but it would be nice if we could opt in to it.

@solson
Copy link
Member

solson commented Mar 14, 2017

I'm in favor of rustfmt enforcing the order @nrc mentioned by default (leaving everything in 4 in its original order).

Hiding a module-scope extern crate, use, or mod <name>; among the items later in the file seems like a pretty misleading style given the strong convention of placing them at the top. I've never seen anyone do it intentionally.

@nrc
Copy link
Member

nrc commented Mar 14, 2017

@solson mod declarations sometimes have to come after macro declarations in order for the macro to be visible.

Macro uses in general might appear anywhere within the 3 groups, I think.

I've occasionally put use near an impl which is the client, though I'm not sure that is really good style.

@solson
Copy link
Member

solson commented Mar 14, 2017

Ah, yeah, macro_rules's order-sensitivity ruins a lot of things. :)

Maybe we can automatically move extern crate and use to the top, but only give a strong recommendation for mod.

I've occasionally put use near an impl which is the client, though I'm not sure that is really good style.

I do consider this bad style. I like when the answer to the question "where does this name come from" is answered in a single predictable place for all names.

@joshtriplett
Copy link
Member

There's a strong convention of putting mod tests { at the end of a module; I don't want to break that.

@solson
Copy link
Member

solson commented Mar 14, 2017

Although its name varies (mod test {, mod tests {, possibly other things). Can we even make a reasonable rule for rustfmt here?

We should definitely mention putting mod tests { at the end as a guideline for humans, at least.

@nrc
Copy link
Member

nrc commented Mar 14, 2017

I think actually the name is not too important - inline mods belong with everything else, only placeholder mods should be at the top (I'd put mod test; at the top, rather than bottom).

@SergioBenitez
Copy link

SergioBenitez commented Mar 22, 2017

I strongly prefer for the ordering of imports to be:

  1. extern
  2. mod
  3. use

This ordering follows the module hierarchy, from "most" global to local. An extern is importing code from outside of the crate, a mod is importing code from outside of the current module, and a use is bringing code already inside the current module into the local namespace.

@solson
Copy link
Member

solson commented Mar 22, 2017

In my mind, the global → local ordering is that which people suggested earlier:

  1. extern crate - the widest scope, pulling crate-external code into the crate
  2. use - the second widest scope, pulling possibly module-external items into the module
  3. mod - definitions of submodules nested within the current module

@SergioBenitez
Copy link

@solson A non-inline mod is always pulling code from outside of the current module, while a use brings identifiers reachable from the current namespace into the current namespace. Another way to look at this is that mod does not depend on the local namespace (instead only on the file hierarchy) while use does. As a result, use is confined to more local reasoning.

I suspect that some of the preference for the extern -> use -> mod ordering is due to the fact that older versions of rustc required this ordering due to limitations. I recall that many users found this confusing, often asking "how can I be useing something that I haven't imported via mod?" The problem was so prevalent that the Rust book mentioned this peculiarity exactly, referring to it as a limitation of the compiler.

@solson
Copy link
Member

solson commented Mar 23, 2017

A non-inline mod is always pulling code from outside of the current module

I disagree with this phrasing. It's defining a submodule inside the current module and that code is nested inside the current module even though it's in a different file. I suspect our disagreement stems from the fact that I view mod items as definitions but your wording suggests you view them as imports.

I suspect that some of the preference for the extern -> use -> mod ordering is due to the fact that older versions of rustc required this ordering due to limitations

I prefer this ordering because to me it's the most natural expression of "external imports first, local definitions after".

@SergioBenitez
Copy link

SergioBenitez commented Mar 23, 2017

I suspect our disagreement stems from the fact that I view mod items as definitions but your wording suggests you view them as imports.

Perhaps this is part of it. But if we consider why users had an issue with this ordering, I believe it's because it feels like, and indeed is, a "use before definition". I strive to have definitions appear before their use in my source code. This is why fn main() is always at the bottom of the file, and why I try to define functions before their use. This ordering feels intuitive and normal.

I believe that the style imposed by rustfmt should feel as normal as possible to as many users as possible. There is empirical evidence that the ordering extern -> use -> mod strikes a dissonant chord with users, but I have never observed the same issue with the ordering I propose. I've conjectured as to why, and my feeling is that this is indeed the reason. In fact, it's likely why I prefer it myself.

@solson
Copy link
Member

solson commented Mar 23, 2017

I believe it's because it feels like, and indeed is, a "use before definition". I strive to have definitions appear before their use in my source code. This is why fn main() is always at the bottom of the file, and why I try to define functions before their use. This ordering feels intuitive and normal.

Ah... I don't think in this way. I was quite happy that Rust did not enforce definition-use ordering. I like to put the most "high-level" things first, like struct Foo(A, B) and put the smaller things like A and B later. Likewise I would put the most high-level function first and the subroutines it calls to handle the details below. The high-level things act like a "table of contents" and give context for the details to come. I may have an easier time understanding a function if I already know where and why it's called.

There is empirical evidence that the ordering extern -> use -> mod strikes a dissonant chord with users, but I have never observed the same issue with the ordering I propose.

Well, I am myself an example of seeing externmoduse as importsdefinitionsimports, which seems like the same issue.


All that said, I feel like I'm mostly disagreeing with the supporting points you've brought up, and not your actual proposal. :)

I wouldn't terribly mind either order, but I'd like to see more feedback from the community at large. I feel like there are not many distinct voices on this thread so far.

@johnthagen
Copy link
Contributor Author

I'm +1 towards use first because in all language I've ever used I always pull in code (import) at the first. I agree with @solson's view of mod as a declaration. Yes it's actually defined in another file, but the mod statement declares whether it's pub or not and thus is at least defining something about it.

@ebkalderon
Copy link

Expanding on @johnthagen's example, below is my preferred list including pub:

  1. pub extern crate (only in lib.rs)
  2. extern crate (only in lib.rs or main.rs)
  3. pub use
  4. use
  5. pub mod
  6. mod

@steveklabnik
Copy link
Member

I always mod before use for the same reasons as @SergioBenitez

@nrc
Copy link
Member

nrc commented Apr 3, 2017

So, the question from @ebkalderon is (I think) whether pub items should be separated out from non-pub ones? Also note that pub is no longer a monolithic thing and we have pub(restricted) now too.

I'm against recommending that extern crate should only be used in lib.rs/main.rs - I think it is useful sometimes to import a crate more locally.

@nrc
Copy link
Member

nrc commented Apr 3, 2017

We discussed this issue at the style team meeting. The two outstanding questions are:

  • the ordering of use vs mod. We decided that we would like to know what the consensus is currently. The best way to do this would be a scan of crates.io. @joshtriplett may do so if he can leverage Crater locally to do this. If anyone else is interested in helping/doing this, please give us a shout.
  • whether pub items should be separated - we decided no because there is no consensus on the ordering, and it makes sense in some cases but not others. Although the motivation for making pub stuff easier to find is good, this is undermined by having pub items of different kinds in different places and is better addressed by tooling.

@joshtriplett
Copy link
Member

OK, I just finished analyzing all 8487 crates from crates.io.

A total of 2096 crates use both use and mod. Of those:

768 crates use mod before use and never the reverse.
814 crates use use before mod and never the reverse.
514 crates do both.

That seems to indicate a complete lack of any standard convention. Between that and the specific cases I quoted above, I'd consider this conclusive evidence that we should not attempt to reorder use and mod statements.

@joshtriplett
Copy link
Member

joshtriplett commented Apr 4, 2017

If you'd like to do some further analysis, results.txt contains a summary of every .rs file on crates.io that contains ^mod .*;, ^pub use , or ^use . The file contains filenames (which may include spaces) followed by a column containing a series of m, p, and u characters, corresponding to each mod, pub use, and use, respectively.

(Note that while that file contains one entry per Rust source file, I would recommend doing the analysis by crate rather than by file, to avoid overcounting the conventions of crates with more source files.)

@nrc
Copy link
Member

nrc commented Apr 4, 2017

Awesome, thanks @joshtriplett ! Did you notice any instances where extern crate was deliberately interleaved with uses or mods?

@nrc
Copy link
Member

nrc commented Apr 4, 2017

Given this data and our discussion I'd like to propose the following:

  • we recommend and enforce that extern crates come at the start of a file
  • we recommend and enforce that mods and uses come before other items
  • we recommend, but do not enforce, uses come before mods and that mods and uses are kept separate unless there is semantic reason to do so.

Rationale for the recommendation is that this is what the (slim) majority of crates and the Rust repo does.

@johnthagen
Copy link
Contributor Author

we recommend, but do not enforce, uses come before mods and that mods and uses are kept separate unless there is semantic reason to do so.

I like this. Part of the reasons for a lack of standard convention could very well be because one has never been offered (the whole point of this project), so this seems like a very sensible compromise. Hopefully over time we then see a gradual conformity for a majority of projects.

@joshtriplett
Copy link
Member

joshtriplett commented Apr 4, 2017

@nrc I just did a check for extern crate.

7450 crates contained a file with both an extern crate line and one of the other types of items. Of those, 492 contained an extern crate line after one of the other types of items, and 6958 did not.

Of those, I see two common patterns:

  • Putting use lines for std before extern crate, and all other use lines after.
  • Pairing extern crate x with the corresponding use x::....

Personally, I would propose the following:

  • We recommend and enforce that extern crate, mod (out-of-line only), and use (including pub use) come before all other items.
  • We recommend but do not enforce putting extern crate first, then use, then mod.
  • We support @withoutboats' proposal to make extern crate x; redundant in favor of use x;, and once that becomes widely available in stable Rust, we recommend and enforce using use instead of extern crate, and we recommend and enforce combining adjacent use statements for the same module.

@nrc
Copy link
Member

nrc commented Apr 4, 2017

Thanks for collecting the data!

(out-of-line only)

Yes, this is a good clarification

We recommend but do not enforce putting extern crate first, then use, then mod.

I think the difference here is you think rustfmt should not move out extern crates to the top of the file. ISTM that the majority of crates do this and it is reasonable to do. The small number of exceptions seem like something that can be handled by rustfmt_skip or by turning off an option (and to clarify I think all this re-ordering should be behind options, I'm just discussing defaults here) if the authors feel strongly about it.

We support @withoutboats' proposal

I don't think we should have an opinion on a language issue.

@joshtriplett
Copy link
Member

@nrc Are you OK with pulling all the extern crate lines to the top of the file when the author interleaved them right before the corresponding use line for that crate's items? I don't object to doing so, but I wonder.

And the only reason I commented on the language proposal is that eliminating extern crate in favor of use rather nicely avoids the problem, and I do think we should have an opinion on the style of the resulting language after such a change.

@nrc
Copy link
Member

nrc commented Apr 4, 2017

Are you OK with pulling all the extern crate lines to the top of the file when the author interleaved them right before the corresponding use line for that crate's items? I don't object to doing so, but I wonder.

Yes. This doesn't feel any worse than (e.g.) using block formatting where the author used visual formatting. And if they strongly object they can turn off the option.

And the only reason I commented on the language proposal is that eliminating extern crate in favor of use rather nicely avoids the problem, and I do think we should have an opinion on the style of the resulting language after such a change.

Although you are right that it would avoid the problem, it seems like a rather large change for a rather minor problem. If that happens for other reasons, then of course we should have an opinion on the style in the resulting language, but I don't think our motivation is strong enough to try and influence the decision there.

@ebkalderon
Copy link

ebkalderon commented Apr 15, 2017

@nrc As part of your proposal, did you arrive at an ultimate consensus for how to handle the ordering of pub use and pub mod?

@nrc
Copy link
Member

nrc commented May 1, 2017

@ebkalderon I don't think we did exactly. I've been away for a few weeks and I'll try and get to this soon.

@nrc
Copy link
Member

nrc commented May 9, 2017

Summary of recommendations (assume mods = out-of-line mods for all of this):

  • extern crates, mods, and uses come before other items (enforced, optional)
  • extern crates come before mods and uses (enforced, optional)
  • uses come before mods (optionally enforced, off by default)
  • uses, mods, and extern crates are alphabetically ordered (enforced only if the category is separated, and optional)
  • we make no recommendation about ordering of pub vs non-pub items

@mathstuf
Copy link

mathstuf commented Feb 5, 2018

@nrc I think that instead of extern crate statements must be first in a file., it should be extern crate statements must be first in a scope..

@matklad
Copy link
Member

matklad commented Jul 3, 2018

Curious: are all the people sure that out-of-line mod declarations should be sorted alphabetically?

I think I have a pretty strong argument for preserving the order of mods that the programmer specified.

A good logical ordering (for example. from low-level to high-level) of mods is a huge help for reading the code for the first time: you can see that foo, bar and baz are small helpers and spam and eggs are complicated beasts where the interesting stuff happens.

To me, mod declarations seems much closer to function definitions than to imports, because their ordering has the potential to expose the high-level structure of the code.

This probably doesn't matter if you working with a particular code-base a lot, because you are most likely using navigation by type or file names.

However, if you are just diving in into the project, figuring out which 20% of code is actually interesting can be very challenging, and here I think manually ordered mod declarations can help a lot?

@topecongiro
Copy link

FWIW you can just put the important modules in a separate group to not get reordered with others.

For example, given the following code,

mod foo;
mod bar;
mod baz;

mod spams;
mod eggs;

it should be formatted like:

mod bar;
mod baz;
mod foo;

mod eggs;
mod spams;

@nrc
Copy link
Member

nrc commented Jul 6, 2018

A good logical ordering (for example. from low-level to high-level) of mods is a huge help for reading the code for the first time

Our consensus about this sort of thing was that ordering is a weak and implicit way to supply information and we shouldn't encourage it (for one thing, the reader has to expect you to be ordering/grouping modules to recognise that there is actually information there). We thought that letting the user group modules using whitespace but not maintain order otherwise was a good compromise.

donovanglover added a commit to donovanglover/hyprdim that referenced this issue Aug 2, 2023
6 years ago, there was a discussion on whether mod should come before or
after use declarations. A code analysis[^1] of crates.io showed that the
majority of crates prefer to use mod either before or after use
declarations, but not both.

Now in 2023, rust-analyzer[^2] uses mod before use declarations and
rustfmt[^3] uses mod after use declarations. This shouldn't matter too
much, although it does seem rather lax compared to the nature of
correctness in rust.

For this project specifically, mod after use enables the important use
declarations to be read first before the less important module includes
(which should rarely change if ever).

[^1]: rust-lang/style-team#71 (comment)
[^2]: https://github.com/rust-lang/rust/blob/master/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
[^3]: https://github.com/rust-lang/rustfmt/blob/master/src/config/mod.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests