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

clone_on_ref_ptr lint is not always trivial to resolve #2048

Closed
chriscoomber opened this issue Sep 13, 2017 · 12 comments
Closed

clone_on_ref_ptr lint is not always trivial to resolve #2048

chriscoomber opened this issue Sep 13, 2017 · 12 comments
Labels
C-bug Category: Clippy is not doing the correct thing E-medium Call for participation: Medium difficulty level problem and requires some initial experience. good-first-issue These issues are a good way to get started with Clippy S-needs-discussion Status: Needs further discussion before merging or work can be started

Comments

@chriscoomber
Copy link

chriscoomber commented Sep 13, 2017

We've just spotted the clone_on_ref_ptr lint throw up clippy warnings in our code. In an effort to go through and fix them, we realized that is isn't actually always very trivial to do so.

I've had a go at condensing our code down into a minimal repro. It has something to with type inference, and trait objects (e.g. interpreting an Arc of a struct as an Arc of a trait).

Concrete example: playground

let _z = Bar(y.clone()); fails the clippy lint:

warning: using '.clone()' on a ref-counted pointer
  --> src/main.rs:13:18
   |
13 |     let _z = Bar(y.clone());
   |                  ^^^^^^^^^ help: try this: `Arc::clone(&y)`
   |
   = note: #[warn(clone_on_ref_ptr)] on by default
   = help: for further information visit https://rust-lang-nursery.github.io/rust-clippy/v0.0.160/index.html#clone_on_ref_ptr

The suggested fix, however, does not compile.

error[E0308]: mismatched types
  --> src/main.rs:13:29
   |
13 |     let _z = Bar(Arc::clone(&y));
   |                             ^^ expected trait Foo, found struct `Bar`
   |
   = note: expected type `&std::sync::Arc<Foo>`
              found type `&std::sync::Arc<Bar>`

One way to actually get this to work, is to provide explicit types (Arc::<Bar>::clone). However, in real life the struct Bar might actually have a bunch of generics, which are incredibly tedious to write out (even with underscores) - for example Arc::Bar<_, _, _, _>::clone.

Why I think this is an issue

In this particular case, the clippy warning is trying to get us to write the clone in a slightly different way, to make it clear that we're cloning an Arc (cheap). However, that slightly different way doesn't actually compile. This forces us to disable the warning, or jump through several hoops to get some quite inelegant code which satisfies the compiler and clippy.

@oli-obk oli-obk added the S-needs-discussion Status: Needs further discussion before merging or work can be started label Sep 13, 2017
@chriscoomber
Copy link
Author

@oli-obk oli-obk added E-medium Call for participation: Medium difficulty level problem and requires some initial experience. C-bug Category: Clippy is not doing the correct thing labels Sep 29, 2017
@seanmonstar
Copy link

I'd personally consider the lint wrong in all cases, and would rather see it removed. It's more annoying to write, and type inference will do The Right Thing anyways.

@oli-obk
Copy link
Contributor

oli-obk commented Oct 6, 2017

Yea we can move it to the restriction lints.

@Aaron1011
Copy link
Member

I'd personally consider the lint wrong in all cases, and would rather see it removed. It's more annoying to write, and type inference will do The Right Thing anyways.

The reason for writing Rc::clone and Arc::clone isn't to help with type inference - it's to make it clear that only the pointer is being cloned, as opposed to the underlying data. The former is always fast, while the latter can be very expensive depending on what is being cloned.

It's also the recommended by the standard library docs: nical/rust@1b414f5

@seanmonstar
Copy link

The reason for writing Rc::clone and Arc::clone isn't to help with type inference - it's to make it clear that only the pointer is being cloned, as opposed to the underlying data.

I realize that. What I meant with type inference is that you almost never have to worry that you're actually cloning the internal piece, because if you pass the clone to a function taking Rc<Foo>, you are not at risk of actually cloning Foo. The compiler knows better.

It's also the recommended by the standard library docs.

I assume this is a newer recommendation, I don't recall seeing it before. But again, I'd personally disagree with it. foo.clone() is subjectively better than Rc::clone(&foo) when I already know the argument is an Rc<Foo>.

@chriscoomber
Copy link
Author

chriscoomber commented Oct 9, 2017

One thing I have against the lint is that I can create my own structs which implement clone in different ways, shallow (e.g. cloning references such as Arcs) or deep - or sometimes a hybrid of the two.

Suppose Foo implements Clone in a shallow way, e.g. #[derive(Clone)] struct Foo { x: Arc<String>, y: Arc<String> }.

  • Should I write Foo::clone(&foo)? Does that make it any more obvious that I'm cloning references only? I don't think so - someone would need to look at the documentation of Foo to know that clone is a shallow clone for Foo. If they're doing that, they will be happy with foo.clone().
  • Should clippy complain if I wrote foo.clone()? That seems impossible to implement - how can clippy know if it's a shallow clone or not?

To me, unless the answers to the above questions are both "yes" for a custom struct, I don't see any value in making them "yes" for a standard library struct.

Edit 1: The argument for Arc::clone(&foo) being clearer only works if you know what an Arc is, and that cloning it will clone the reference only. The actual syntax doesn't make anything clearer, as String::clone(&foo) looks identical and is not just cloning references. I think the argument is that the syntax is different, and there is a general understanding that this implies something about the deepness.

Edit 2: If x is an Arc<Vec<_>> you can do both Arc::clone(&x) and Vec::clone(&x) (due to auto-deref) and they (obviously) do different things. This is already mentioned by others in this discussion, but that link might be useful. This may be an argument for the Arc::clone notation. x.clone() and Arc::clone(&x) are equivalent to the compiler in this case.

@Aaron1011
Copy link
Member

Aaron1011 commented Oct 9, 2017

I realize that. What I meant with type inference is that you almost never have to worry that you're actually cloning the internal piece, because if you pass the clone to a function taking Rc, you are not at risk of actually cloning Foo. The compiler knows better.

My bad - when I said "it's to make it clear that only the pointer is being cloned, as opposed to the underlying data.", I was referring only to people reading the code, not the compiler itself. The issue isn't that the compiler will do the wrong thing - it's that people reading the code will get an incorrect impression of what it's doing.

The argument for Arc::clone(&foo) being clearer only works if you know what an Arc is, and that cloning it will clone the reference only.

I believe the idea behind the recommendation in the docs is that people can be expected to be familiar with the common container/pointer types in the stdlib (Arc, Rc, Vec, Box, etc) since they're used so pervasively in Rust. That's why this lint is called clone_on_ref_ptr and not something like shallow_clone_invocation - it's intended to make certain specific, well-defined usages of clone less ambigious.

@seanmonstar
Copy link

people can be expected to be familiar with the common container/pointer types in the stdlib

So likewise, when dealing with an Rc or Arc, users should be familiar with foo.clone() being a shallow clone.

@Aaron1011
Copy link
Member

So likewise, when dealing with an Rc or Arc, users should be familiar with foo.clone() being a shallow clone.

The idea of this line is that it's not always obvious what the type of foo is. foo could be a field in a struct declared in a different crate, or a local variable with an inferred type. Explicitly writing out Rc::clone or Arc::clone makes it clear that the cloning operation is happening on the pointer itself, and is independent of whatever type is being wrapped.

@oli-obk oli-obk added the good-first-issue These issues are a good way to get started with Clippy label Oct 25, 2017
@oli-obk
Copy link
Contributor

oli-obk commented Oct 25, 2017

Since I do not see a way that we can detect this case in clippy (inference makes this ridiculously hard), I marked this as an easy issue, since all that needs to be done is move it to the restriction lints and maybe ensure that the suggestion also prints the full types in case traits are involved (which would also print the full thing when no inference happened)

@alusch
Copy link
Contributor

alusch commented Jan 11, 2018

Hi there, I'm new here and interested in picking this up. Do you have an example for the output you had in mind for printing the full type? Thanks!

@oli-obk
Copy link
Contributor

oli-obk commented Jan 11, 2018

@alusch awesome!

I was thinking that we'd get Arc::<Foo>::clone(&y) instead of Arc::clone(&y).

oli-obk added a commit that referenced this issue Jan 16, 2018
Fix #2048: Move `clone_on_ref_ptr` to the restriction lints
mlodato517 added a commit to mlodato517/rust_bst that referenced this issue Nov 24, 2021
**This Commit**
Denies the use of `.clone()` on reference counted pointers as advised by
[this `clippy` lint][0] and [The Rust Book][1]. It also addresses
instances where the lint was violated.

**Why?**
It's best practice to make clear the clone is cheap. There is [some
debate][2] on the issue and the [standard library no longer explicitly
recommends it][3] but it is still noted as more idiomatic.

In any case, for us there are no issues making the change and if there
is a possibility for being more expressive, we should take it.

See #6 (comment)
for more details.

[0]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
[1]: https://doc.rust-lang.org/book/ch15-04-rc.html#using-rct-to-share-data
[2]: rust-lang/rust-clippy#2048
[3]: rust-lang/rust#76138
mlodato517 added a commit to mlodato517/rust_bst that referenced this issue Nov 24, 2021
**This Commit**
Adds history to the functional BST and no longer requires ownership to
`insert`/`delete`.

**Why?**
Mainly to make benchmarking easier but this has the benefit of more
closely matching a functional data structure in that, when new trees are
created from "mutating" operations, the history of trees/operations is
still accessible.

Use struct update syntax for conciseness

**Why?**
I didn't think I could do this but the issue was that I had `derive`d
`Clone` on `Node` when I should've implemented it directly because it's
generic and leverages reference counting.

See #6 (comment)

Explicitly clone needed fields of nodes

**This Commit**
Removes instances of instantiating new `Node`s using the functional
update syntax of a clone of an existing `Node`. That is it replaces:

```rust
Node {
  some_field: some_value,
  ..existing_node.clone()
}
```

with

```rust
Node {
  some_field: some_value,
  other_field: existing_node.other_field.clone(),
  // etc.
}
```

**Why?**
For clarity - when using `..node.clone()` what's
happening is we are first cloning the node in its entirety (which
results in bumping the reference count for all its fields), moving the
fields we care about to the new `Node`, and then dropping the remaining
fields (and decrementing their reference count). This is a surprise to
the reader and is needless and not what we want to express. It also may
have a performance impact but that isn't measured.

For more details see [this comment thread][0].

[0]: #6 (comment)

Deny `clippy::clone_on_ref_ptr`

**This Commit**
Denies the use of `.clone()` on reference counted pointers as advised by
[this `clippy` lint][0] and [The Rust Book][1]. It also addresses
instances where the lint was violated.

**Why?**
It's best practice to make clear the clone is cheap. There is [some
debate][2] on the issue and the [standard library no longer explicitly
recommends it][3] but it is still noted as more idiomatic.

In any case, for us there are no issues making the change and if there
is a possibility for being more expressive, we should take it.

See #6 (comment)
for more details.

[0]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
[1]: https://doc.rust-lang.org/book/ch15-04-rc.html#using-rct-to-share-data
[2]: rust-lang/rust-clippy#2048
[3]: rust-lang/rust#76138
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: Clippy is not doing the correct thing E-medium Call for participation: Medium difficulty level problem and requires some initial experience. good-first-issue These issues are a good way to get started with Clippy S-needs-discussion Status: Needs further discussion before merging or work can be started
Projects
None yet
Development

No branches or pull requests

5 participants