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

following needless_borrows_for_generic_args leads to unnecessary moves and makes code less refactoring-friendly #12454

Closed
RalfJung opened this issue Mar 10, 2024 · 0 comments · Fixed by #12706

Comments

@RalfJung
Copy link
Member

RalfJung commented Mar 10, 2024

Description

Consider this code:

use std::path::*;

fn item(_x: impl AsRef<Path>) {}

fn work(x: PathBuf) {
    item(&x);
    //item(&x);
}

Clippy shows a warning here:

warning: the borrowed expression implements the required traits
 --> src/lib.rs:6:10
  |
6 |     item(&x);
  |          ^^ help: change this to: `x`
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrows_for_generic_args
  = note: `#[warn(clippy::needless_borrows_for_generic_args)]` on by default

I do not think this is good advice. For functions that take AsRef, I generally think one should give them borrowed arguments, for the following reasons:

  • They will end up borrowing anyway, so there's no possible performance benefit from moving.
  • If the code gets refactored later to use x more than once, I will get an error saying x has been moved already. Now I have to go up and change the use of x to &x. If I don't realize that I can change x to &x, then I might resort to x.clone(), which would be even worse. I generally prefer to write my code in a future-proof way that doesn't require further changes at the beginning of the function just because I want to change what I do at the end of the function.

You can simulate point 2 by uncommenting the second call to item. In the original code, that just works. If the code gets changed the way clippy wants, now this requires changing the first call.

In fact I think clippy should do the opposite of what it does today, and lint against this code:

use std::path::*;

fn item(_x: impl AsRef<Path>) {}

fn work(x: PathBuf) {
    item(x); // unnecesary move into `AsRef` function
}

That would steer people towards making it visible that we have borrowing here. But as a first step it'd be great if Clippy could stop linting against the pattern that avoids unnecessary moves and keeps code more refactoring-friendly.

Of course, the following should still lint, here the & is truly unnecessary since x is already Copy so passing it by-ref has no advantage (assuming that it is not prohibitively big and hence expensive to copy):

use std::path::*;

fn item(_x: impl AsRef<Path>) {}

pub fn work(x: &Path) {
    item(&x);
}

(That's also why just allowing needless_borrows_for_generic_args is not a great solution, the lint groups together two very different situations: borrows of already borrowed things that are just entirely redundant, and borrows of owned things that could be avoided in principle but it's less clear-cut whether that's a win.)

Version

Current stable (on playground)

Additional Labels

No response

pacak added a commit to pacak/rust that referenced this issue Apr 23, 2024
Current implementation looks for significant drops, that can change the
behavior, but that's not enough - value might not have a Drop itself but
one of its children might have it.

A good example is passing a reference to `PathBuf` to `std::fs::File::open`.
There's no benefits to pass `PathBuf` by value, but since clippy can't
see `Drop` on `Vec` several layers down it complains forcing pass by
value and making it impossible to use the same name later.

New implementation only looks at variables and only those that are copy,
so existing variable will never be moved but things that take a string
reference created and value is created inplace `&"".to_owned()` will
make it to suggest to use `"".to_owned()` still.

Fixes rust-lang/rust-clippy#12454
pacak added a commit to pacak/rust that referenced this issue Apr 23, 2024
Current implementation looks for significant drops, that can change the
behavior, but that's not enough - value might not have a Drop itself but
one of its children might have it.

A good example is passing a reference to `PathBuf` to `std::fs::File::open`.
There's no benefits to pass `PathBuf` by value, but since clippy can't
see `Drop` on `Vec` several layers down it complains forcing pass by
value and making it impossible to use the same name later.

New implementation only looks at variables and only those that are copy,
so existing variable will never be moved but things that take a string
reference created and value is created inplace `&"".to_owned()` will
make it to suggest to use `"".to_owned()` still.

Fixes rust-lang/rust-clippy#12454
pacak added a commit to pacak/rust-clippy that referenced this issue Apr 23, 2024
Current implementation looks for significant drops, that can change the
behavior, but that's not enough - value might not have a Drop itself but
one of its children might have it.

A good example is passing a reference to `PathBuf` to `std::fs::File::open`.
There's no benefits to pass `PathBuf` by value, but since clippy can't
see `Drop` on `Vec` several layers down it complains forcing pass by
value and making it impossible to use the same name later.

New implementation only looks at variables and only those that are copy,
so existing variable will never be moved but things that take a string
reference created and value is created inplace `&"".to_owned()` will
make it to suggest to use `"".to_owned()` still.

Fixes rust-lang#12454
pacak added a commit to pacak/rust-clippy that referenced this issue Apr 23, 2024
Current implementation looks for significant drops, that can change the
behavior, but that's not enough - value might not have a Drop itself but
one of its children might have it.

A good example is passing a reference to `PathBuf` to `std::fs::File::open`.
There's no benefits to pass `PathBuf` by value, but since clippy can't
see `Drop` on `Vec` several layers down it complains forcing pass by
value and making it impossible to use the same name later.

New implementation only looks at variables and only those that are copy,
so existing variable will never be moved but things that take a string
reference created and value is created inplace `&"".to_owned()` will
make it to suggest to use `"".to_owned()` still.

Fixes rust-lang#12454
pacak added a commit to pacak/rust-clippy that referenced this issue Apr 23, 2024
Current implementation looks for significant drops, that can change the
behavior, but that's not enough - value might not have a Drop itself but
one of its children might have it.

A good example is passing a reference to `PathBuf` to `std::fs::File::open`.
There's no benefits to pass `PathBuf` by value, but since clippy can't
see `Drop` on `Vec` several layers down it complains forcing pass by
value and making it impossible to use the same name later.

New implementation only looks at copy values or values created inplace
so existing variable will never be moved but things that take a string
reference created and value is created inplace `&"".to_owned()` will
make it to suggest to use `"".to_owned()` still.

Fixes rust-lang#12454
bors added a commit that referenced this issue May 15, 2024
less aggressive needless_borrows_for_generic_args

Current implementation looks for significant drops, that can change the behavior, but that's not enough - value might not have a `Drop` itself but one of its children might have it.

A good example is passing a reference to `PathBuf` to `std::fs::File::open`. There's no benefits to pass `PathBuf` by value, but since `clippy` can't see `Drop` on `Vec` several layers down it complains forcing pass by value and making it impossible to use the same name later.

New implementation only looks at copy values or values created in place     so existing variable will never be moved but things that take a string reference created and value is created inplace `&"".to_owned()` will make it to suggest to use `"".to_owned()` still.

Fixes #12454

changelog: [`needless_borrows_for_generic_args`]: avoid moving variables
@bors bors closed this as completed in 79a14de May 15, 2024
meithecatte added a commit to meithecatte/rust-clippy that referenced this issue Jun 5, 2024
This commit fixes a bug introduced in rust-lang#12706, where the behavior of the
lint has been changed, to avoid suggestions that introduce a move. The
motivation in the commit message is quite poor (if the detection for
significant drops is not sufficient because it's not transitive, the
proper fix would be to make it transitive). However, rust-lang#12454, the linked
issue, provides a good reason for the change — if the value being
borrowed is bound to a variable, then moving it will only introduce
friction into future refactorings.

Thus rust-lang#12706 changes the logic so that the lint triggers if the value
being borrowed is Copy, or is the result of a function call, simplifying
the logic to the point where analysing "is this the only use of this
value" isn't necessary.

However, said PR also introduces an undocumented carveout, where
referents that themselves are mutable references are treated as Copy,
to catch some cases that we do want to lint against. However, that is
not sound — it's possible to consume a mutable reference by moving it.

To avoid emitting false suggestions, this PR reintroduces the
referent_used_exactly_once logic and runs that check for referents that
are themselves mutable references.

Thinking about the code shape of &mut x, where x: &mut T, raises the
point that while removing the &mut outright won't work, the extra
indirection is still undesirable, and perhaps instead we should suggest
reborrowing: &mut *x. That, however, is left as possible future work.

Fixes rust-lang#12856
meithecatte added a commit to meithecatte/rust-clippy that referenced this issue Jun 5, 2024
This commit fixes a bug introduced in rust-lang#12706, where the behavior of the
lint has been changed, to avoid suggestions that introduce a move. The
motivation in the commit message is quite poor (if the detection for
significant drops is not sufficient because it's not transitive, the
proper fix would be to make it transitive). However, rust-lang#12454, the linked
issue, provides a good reason for the change — if the value being
borrowed is bound to a variable, then moving it will only introduce
friction into future refactorings.

Thus rust-lang#12706 changes the logic so that the lint triggers if the value
being borrowed is Copy, or is the result of a function call, simplifying
the logic to the point where analysing "is this the only use of this
value" isn't necessary.

However, said PR also introduces an undocumented carveout, where
referents that themselves are mutable references are treated as Copy,
to catch some cases that we do want to lint against. However, that is
not sound — it's possible to consume a mutable reference by moving it.

To avoid emitting false suggestions, this PR reintroduces the
referent_used_exactly_once logic and runs that check for referents that
are themselves mutable references.

Thinking about the code shape of &mut x, where x: &mut T, raises the
point that while removing the &mut outright won't work, the extra
indirection is still undesirable, and perhaps instead we should suggest
reborrowing: &mut *x. That, however, is left as possible future work.

Fixes rust-lang#12856
meithecatte added a commit to meithecatte/rust-clippy that referenced this issue Jun 6, 2024
This commit fixes a bug introduced in rust-lang#12706, where the behavior of the
lint has been changed, to avoid suggestions that introduce a move. The
motivation in the commit message is quite poor (if the detection for
significant drops is not sufficient because it's not transitive, the
proper fix would be to make it transitive). However, rust-lang#12454, the linked
issue, provides a good reason for the change — if the value being
borrowed is bound to a variable, then moving it will only introduce
friction into future refactorings.

Thus rust-lang#12706 changes the logic so that the lint triggers if the value
being borrowed is Copy, or is the result of a function call, simplifying
the logic to the point where analysing "is this the only use of this
value" isn't necessary.

However, said PR also introduces an undocumented carveout, where
referents that themselves are mutable references are treated as Copy,
to catch some cases that we do want to lint against. However, that is
not sound — it's possible to consume a mutable reference by moving it.

To avoid emitting false suggestions, this PR reintroduces the
referent_used_exactly_once logic and runs that check for referents that
are themselves mutable references.

Thinking about the code shape of &mut x, where x: &mut T, raises the
point that while removing the &mut outright won't work, the extra
indirection is still undesirable, and perhaps instead we should suggest
reborrowing: &mut *x. That, however, is left as possible future work.

Fixes rust-lang#12856
meithecatte added a commit to meithecatte/rust-clippy that referenced this issue Jun 6, 2024
This commit fixes a bug introduced in rust-lang#12706, where the behavior of the
lint has been changed, to avoid suggestions that introduce a move. The
motivation in the commit message is quite poor (if the detection for
significant drops is not sufficient because it's not transitive, the
proper fix would be to make it transitive). However, rust-lang#12454, the linked
issue, provides a good reason for the change — if the value being
borrowed is bound to a variable, then moving it will only introduce
friction into future refactorings.

Thus rust-lang#12706 changes the logic so that the lint triggers if the value
being borrowed is Copy, or is the result of a function call, simplifying
the logic to the point where analysing "is this the only use of this
value" isn't necessary.

However, said PR also introduces an undocumented carveout, where
referents that themselves are mutable references are treated as Copy,
to catch some cases that we do want to lint against. However, that is
not sound — it's possible to consume a mutable reference by moving it.

To avoid emitting false suggestions, this PR reintroduces the
referent_used_exactly_once logic and runs that check for referents that
are themselves mutable references.

Thinking about the code shape of &mut x, where x: &mut T, raises the
point that while removing the &mut outright won't work, the extra
indirection is still undesirable, and perhaps instead we should suggest
reborrowing: &mut *x. That, however, is left as possible future work.

Fixes rust-lang#12856
DJMcNab added a commit to DJMcNab/vello that referenced this issue Jun 11, 2024
DJMcNab added a commit to DJMcNab/vello that referenced this issue Jun 11, 2024
DJMcNab added a commit to DJMcNab/vello that referenced this issue Jun 28, 2024
xFrednet pushed a commit to xFrednet/rust-clippy that referenced this issue Jul 23, 2024
This commit fixes a bug introduced in rust-lang#12706, where the behavior of the
lint has been changed, to avoid suggestions that introduce a move. The
motivation in the commit message is quite poor (if the detection for
significant drops is not sufficient because it's not transitive, the
proper fix would be to make it transitive). However, rust-lang#12454, the linked
issue, provides a good reason for the change — if the value being
borrowed is bound to a variable, then moving it will only introduce
friction into future refactorings.

Thus rust-lang#12706 changes the logic so that the lint triggers if the value
being borrowed is Copy, or is the result of a function call, simplifying
the logic to the point where analysing "is this the only use of this
value" isn't necessary.

However, said PR also introduces an undocumented carveout, where
referents that themselves are mutable references are treated as Copy,
to catch some cases that we do want to lint against. However, that is
not sound — it's possible to consume a mutable reference by moving it.

To avoid emitting false suggestions, this PR reintroduces the
referent_used_exactly_once logic and runs that check for referents that
are themselves mutable references.

Thinking about the code shape of &mut x, where x: &mut T, raises the
point that while removing the &mut outright won't work, the extra
indirection is still undesirable, and perhaps instead we should suggest
reborrowing: &mut *x. That, however, is left as possible future work.

Fixes rust-lang#12856
xFrednet pushed a commit to xFrednet/rust-clippy that referenced this issue Jul 23, 2024
This commit fixes a bug introduced in rust-lang#12706, where the behavior of the
lint has been changed, to avoid suggestions that introduce a move. The
motivation in the commit message is quite poor (if the detection for
significant drops is not sufficient because it's not transitive, the
proper fix would be to make it transitive). However, rust-lang#12454, the linked
issue, provides a good reason for the change — if the value being
borrowed is bound to a variable, then moving it will only introduce
friction into future refactorings.

Thus rust-lang#12706 changes the logic so that the lint triggers if the value
being borrowed is Copy, or is the result of a function call, simplifying
the logic to the point where analysing "is this the only use of this
value" isn't necessary.

However, said PR also introduces an undocumented carveout, where
referents that themselves are mutable references are treated as Copy,
to catch some cases that we do want to lint against. However, that is
not sound — it's possible to consume a mutable reference by moving it.

To avoid emitting false suggestions, this PR reintroduces the
referent_used_exactly_once logic and runs that check for referents that
are themselves mutable references.

Thinking about the code shape of &mut x, where x: &mut T, raises the
point that while removing the &mut outright won't work, the extra
indirection is still undesirable, and perhaps instead we should suggest
reborrowing: &mut *x. That, however, is left as possible future work.

Fixes rust-lang#12856
bors added a commit that referenced this issue Jul 25, 2024
needless_borrows_for_generic_args: Fix for &mut

This commit fixes a bug introduced in #12706, where the behavior of the lint has been changed, to avoid suggestions that introduce a move. The motivation in the commit message is quite poor (if the detection for significant drops is not sufficient because it's not transitive, the proper fix would be to make it transitive). However, #12454, the linked issue, provides a good reason for the change — if the value being borrowed is bound to a variable, then moving it will only introduce friction into future refactorings.

Thus #12706 changes the logic so that the lint triggers if the value being borrowed is Copy, or is the result of a function call, simplifying the logic to the point where analysing "is this the only use of this value" isn't necessary.

However, said PR also introduces an undocumented carveout, where referents that themselves are mutable references are treated as Copy, to catch some cases that we do want to lint against. However, that is not sound — it's possible to consume a mutable reference by moving it.

To avoid emitting false suggestions, this PR reintroduces the referent_used_exactly_once logic and runs that check for referents that are themselves mutable references.

Thinking about the code shape of &mut x, where x: &mut T, raises the point that while removing the &mut outright won't work, the extra indirection is still undesirable, and perhaps instead we should suggest reborrowing: &mut *x. That, however, is left as possible future work.

Fixes #12856

changelog: none
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant