TheBevyFlock / bevy_cli

A Bevy CLI tool.
Apache License 2.0
30 stars 5 forks source link

Add lint: `&mut Commands` function argument #127

Open BD103 opened 2 weeks ago

BD103 commented 2 weeks ago

Quoting @ItsDoot in https://github.com/bevyengine/bevy/issues/15657:

How can Bevy's documentation be improved?

We should more clearly and directly recommend that developers write their helper functions to take Commands (and friends) directly, instead of passing as references (&Commands). And by doing so, these helpers can look just like systems. As an example:

fn my_system(commands: Commands, foo: Query<&Foo>) {
    // we should recommend this:
    good_helper(commands.reborrow(), foo.reborrow());
    // instead of this:
    bad_helper(&mut commands, &foo);
}

// This is kind of annoying:
fn bad_helper(commands: &mut Commands, foo: &Query<&Foo>) {
    // ...
}

// This is easier to deal with:
fn good_helper(commands: Commands, foo: Query<&Foo>) {
    // ...
}

We should recommend reborrowing in the top level docs for Commands and Query.

We can write a lint that would warn against taking &mut Commands in a function! It would probably be in the pedantic category, though.

janhohenheim commented 2 weeks ago

TIL about reborrow 👀

MrGVSV commented 17 hours ago

Working on this now!

MrGVSV commented 17 hours ago

I think if we're going to standardize on this via a lint, it would make sense to extend it to all re-borrwable types, including (in 0.15):

Additionally, as raised by @tim-blackbird here, there are some cases where this doesn't make sense. Specifically, immutable references. In some cases, it makes more sense for a helper to take &Query rather than a re-borrowed Query, such as when already immutably iterating through the Query.

Therefore, we should probably only apply this lint where we are mutably borrowing the type.

MrGVSV commented 17 hours ago

Looks like we'd also need to decide how to handle the EntityCommands problem. Obviously, users could simply opt-out of the lint in such cases, but does that add more friction than desired for a lint like this?

BD103 commented 16 hours ago

Specifically, immutable references.

Do all reborrow() implementations require a mutable reference? If so, then we shouldn't lint on immutable references at all.

To my understanding, Commands internally holds a &mut Buffer to the actual data. We're linting against &mut Commands because it ends up being &mut &mut Buffer, which is slower and hurts caching. &&mut Buffer, on the other hand, is distinct because it is immutable. There shouldn't be a lint in this case because you cannot simplify the structure further.

As an aside, you should be able to determine if a reference is mutable or not using MutTy, which is accessible from rustc_hir::Ty::kind's Ref variant.

MrGVSV commented 15 hours ago

Specifically, immutable references.

Do all reborrow() implementations require a mutable reference? If so, then we shouldn't lint on immutable references at all.

Yeah I just wanted to explicitly call it out