Use all the things?

I often need to use lots of external things, in several different files. This causes two problematic scenarios:

  1. Copy/pasting blocks of use statements and getting hit with a million warnings until I figure out exactly what I need. There's overlap between the files but the dependencies aren't identical everywhere
  2. Being precise and taking the time to only use what I need, which is annoying

To make life simpler, I'm thinking of just keeping a top-level prelude.rs and pub use everything there and then use crate::prelude::* everywhere.

Is that a terrible idea? One concern I have is if it will it affect compile time or bloat in debug builds... (I'd imagine release builds strip unused code).

Personally I think it's better to explicitly sate what you want to use. That is to say your option 2).

That may well be a bit annoying but I feel it's only a minor annoyance in exchange for making ones code clearer and less annoying for anyone to read later. Including oneself.

What typically happens to me is that as I develop code and juggle things around into separate files and modules those use statements do indeed get copied in bulk around the place. Resulting in lots warnings. No worries, my editor highlights all the unused used statements, every now and then when things have stabilised a bit I go in and remove them all. Fairly painless.

If removing annoyance when crafting code were the main priority one would want to get rid of having to code around all that type and memory alias checking Rust does. Go far enough and one may as well use Javascript.

2 Likes

No. use statements do not add to code size at all, and compile time difference is really negligible (it affects parsing and name resolution, but will not add even 1ms probably).

Personally, I prefer exact use statements and solve the problems with tooling (auto-imports etc.). However, I'm not aware of any official guidance, so this is depending on your opinion.

4 Likes

Seems like a bit of a jump to me. The tradeoffs here are very different from eachother...

Sorry, yes it is a bit of jump.

I have a habit to push thing to, often absurd, extremes when trying to make a point.

As far as I know use statements add noting to compile time. At least not noticeably. They don't actually do anything like pulling in lots of extra code to parse and compile like #include does in C.

Rather, all they do is provide shorter names to use in the rest of ones code.

As I said, I prefer to explicitly spell out what is use'ed in a module and prune these use statements down to only what is required.

In the same way one does not want leave a lot of unused variable declarations lying around the place. One declares what is needed rather than having Rust create those declarations.

2 Likes

use is not like C headers. It only enables use of abbreviated names, instead of adding more code. It brings names of items that the compiler already knows about, which is very cheap compared to C header parsing.

4 Likes

At least to me, not having to bother with manually writing use statements is one of the absolutely most important reasons for using an editor/IDE/plugin with code assist features. The same goes for automatically removing ones that are not needed anymore. Having to do a context switch by jumping to the top of the file is a major breaker of flow. Yes, Rust supports local use statements, which is great, but mostly for those things that you really only need once or twice in the whole file.

Specifically I only use local use statements for enum matching in short functions.

2 Likes

I think cargo fix might be the solution you're looking for. I personally don't use it because my editor handles it for me, but I'm pretty sure this would do what you want.

1 Like

One issue you might have, which I have when reading other people's code, is that it becomes quite difficult to know what each type is.

use foo::*;
use bar::*;

Context::new()
    .with("Stuff")
    .execute(|x| x.do_it());

You can't determine where Context comes from, foo or bar. And with a big glob import from a crate-wide prelude, you wouldn't be much better off:

use crate::prelude::*;

Context::new()
    .with("Stuff")
    .execute(|x| x.do_it());

This is even worse now, because you now have to have two files open, one for the imports, and one for the actual code you're working on.

Here's an alternative. I paste this at the top of every file I'm working on:

// TODO: This module is currently under development.
#![allow(unused)]
#![allow(missing_docs)]

I take it out when it's in a valid working state and I'm ready to do clean up.

Usually I replace it with this
    #![warn(anonymous_parameters)]
    #![warn(bad_style)]
    #![warn(bare_trait_objects)]
    #![warn(const_err)]
    #![warn(dead_code)]
    #![warn(elided_lifetimes_in_paths)]
    #![warn(improper_ctypes)]
    #![warn(missing_copy_implementations)]
    #![warn(missing_debug_implementations)]
    #![warn(missing_doc_code_examples)]
    #![warn(missing_docs)]
    #![warn(no_mangle_generic_items)]
    #![warn(non_shorthand_field_patterns)]
    #![warn(nonstandard_style)]
    #![warn(overflowing_literals)]
    #![warn(path_statements)]
    #![warn(patterns_in_fns_without_body)]
    #![warn(private_in_public)]
    #![warn(rust_2018_idioms)]
    #![warn(trivial_casts)]
    #![warn(trivial_numeric_casts)]
    #![warn(unconditional_recursion)]
    #![warn(unused)]
    #![warn(unused_allocation)]
    #![warn(unused_comparisons)]
    #![warn(unused_parens)]
    #![warn(unused_qualifications)]
    #![warn(unused_results)]
    #![warn(variant_size_differences)]
    #![warn(while_true)]
3 Likes

For multiple module in the same crate I tend not to use use that much, I often prefer to explicitly prefix my structures (my_other_module::DataStructure) so I know where they come from.
Same thing if I define a structure with then same name as an std one (in my current code I have a File struct, so I leave prefixes to know which is which).

Yeah, auto-import with Rust Analyzer is a huge help for this. I agree that managing the use staements manually is rather annoying.

If you want to do the glob import I don't think there's anything super wrong with that. A lot of people have good points about it making it hard to tell where things come from, which is valid, but if it's really helps you a lot, just use the glob import and maybe switch it out later.

Rust Analyzer actually has this really cool code action "deglob-import" so that it will actually expand your * import to only the imports you need. You could use glob imports while doing rapid dev and then just deglob the import when you are ready to tidy it up, maybe.

Yet another option: maybe create a private, crate-level prelude and then glob-import that in all your files.

So you would do this in all your different modules:

use crate::local_prelude::*;

That way you could put all your common imports in that local_prelude module and use it throughout the file. If people want to see where things came from, it shouldn't be a huge deal to check out the local_prelude.rs file.

1 Like

I prefer to now use use statements at all if at all possible. You can checkout rust code guidelines of my company, FifthTry.

The primary reason is in git diff, GitHub PR diff view etc, its not easy to see what a term is referring to. So if you have used std::fs::read_to_string() in code somewhere, I know precisely what it is, but if you have used use std::fs::read_to_string at the top of the file, but in the diff all I can see is read_to_string(), I have to now wonder where is this function coming from, when I am reviewing your code.

Code is written once, and read a lot of times, so you should optimise code for easier review, easier skimming. The more the reader has to pause and think, the worse the code is, in my opinion.

1 Like

Aren't most of them already on warn level?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.