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

Provide a more explicit way to "clone" a standard reference counted pointer. #1954

Closed
wants to merge 9 commits into from

Conversation

nical
Copy link

@nical nical commented Mar 18, 2017

This is my first RFC.

This RFC proposes a very small addition to the standard reference counted pointer types, that aims solving a recurrent paper-cut-type of annoyance related the lack of clarity of code calling clone().

Rendered.

@alexcrichton alexcrichton added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Mar 18, 2017
@SimonSapin
Copy link
Contributor

I’ve sometimes used Arc::clone(&some_arc) instead of some_arc.clone() for this reason. Type::trait_method works as another shorthand for <Type as Trait>::trait_method when Trait is in scope.

@burdges
Copy link

burdges commented Mar 18, 2017

Isn't there a case for putting new_ref into a trait too, perhaps along with Weak, so as to be generic-ish over Send/Sync? I once considered using something like this to ensure my Arcs did not really need Send/Sync in my own code :

trait RcClone<T> : Deref<Target=T> + Borrow<T> + AsRef<T> + Clone {
    type Weak;
    fn downgrade(&self) -> Weak;
}

impl<T> RcClone<T> for rc::Rc<T> { 
    type Weak = rc::Weak<T>;
    fn downgrade(&self) -> rc::Weak<T> { rc::Rc::downgrade(self) }
}
impl<T> RcClone<T> for sync::Arc<T> { 
    type Weak = sync::Weak<T>;
    fn downgrade(&self) -> sync::Weak<T> { rc::Arc::downgrade(self) }
}

trait RcWeak<T> : Clone {
    type Strong;
    fn upgrade(&self) -> Option<Strong>
}

impl<T> RcWeak<T> for rc::Weak<T> {
    type Strong = rc::Rc<T>;
    fn upgrade(&self) -> Option<rc::Rc<T>> { rc::Weak::upgrade(self) }
}
impl<T> RcWeak<T> for snyc::Weak<T> {
    type Strong = sync::Arc<T>;
    fn upgrade(&self) -> Option<sync::Arc<T>> { sync::Weak::upgrade(self) }
}


This RFC does not involve any compiler change (only minor additions to alloc/rc.rs and alloc/arc.rs).

The following steps apply to ```Rc<T>```, ```Arc<T>```, ```rc::Weal<T>```, ```arc::Weak<T>```.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weal ↦ Weak.


The following steps apply to ```Rc<T>```, ```Arc<T>```, ```rc::Weal<T>```, ```arc::Weak<T>```.

A method ```fn new_ref(&self) -> Self``` is added to the pointer type, into which the code of the ```Clone::clone``` implementation is moved.
Copy link
Member

@kennytm kennytm Mar 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better define it as

fn new_ref(this: &Self) -> Self;

to let user call it as

let p1 = Rc::new(vec![0u8; 1000]);
let p2 = Rc::new_ref(&p1);   // <-- instead of p1.new_ref()

as explained in the doc of Rc:

The inherent methods of Rc are all associated functions, which means that you have to call them as e.g. Rc::get_mut(&value) instead of value.get_mut(). This avoids conflicts with methods of the inner type T.

(The .clone() method on Rc is precisely causing such a conflict.)

@nical
Copy link
Author

nical commented Mar 19, 2017

Thanks a lot for the feedback so far. I had not though about it before, but the concern about new_ref conflicting with the pointee type potentially having a new_ref method of its own is pretty serious and quite obvious in retrospect.

I added it to the RFC under Drawbacks and Alternative, although depending on how the discussion goes it might become a reformulation of the main proposal. It would be great, though, if we could find a solution that is at least almost as convenient as clone, otherwise I fear that what will/may come out of this RFC won't be used in practice, which would defeat part of the purpose.

@hadronized
Copy link
Contributor

I’m balanced. I think the distinction is important. I saw the example in some gfx codebase lately in which
Buffer<_> – defined as a wrapper type over Arc<RawBuffer<_>> – implements Clone.

impl<T: _> Clone for Buffer<T> { _ }

What do we expect a buf.clone() to do? I’d say it actually creates a new buffer holding the exact same data as the source, but living in a different place in memory. Well, that’s not the case. It just clones the inner arc. And that semantic is plain wrong.

On the other side, I think that Arc should have an implementation of Clone creating a new T instead. But you suggest is to default the implementation to new_ref. Which wouln’t solve the buffer problem I mentionned above.

It’s all about confusion. In a perfect world, I’d rather prefer not having an impl Clone for ref counted pointers, and have a dedicated method for that.

@oli-obk
Copy link
Contributor

oli-obk commented Mar 20, 2017

I'm 100% behind the motivation for this RFC, but as @SimonSapin stated, we already have an explicit version (Arc::clone(&some_arc)). I'd prefer if the compiler simply told me that some_arc.clone() is easy to misinterpret and Arc::clone(&some_arc) should be used instead. Writing a lint that does this is pretty easy.

If we don't add such a lint, the new_ref method doesn't add any benefit to existing code and to new code written without knowledge of that method.

The "how do we teach this" section is insufficient imo, since the change of adding new_ref goes by pretty silently except for documentation changes. If we consider some_arc.clone() to be unidiomatic, we should deprecate or lint it.

@nical
Copy link
Author

nical commented Mar 21, 2017

Very good point about the lint. Is this something that should go into the compiler, or is clippy the place for this type of thing?
I would be surprised that we have the luxury to completely deprecate Rc::clone and friends at this point (as in, move towards making it not compile), considering that most likely all of the code bases that use standard reference counted pointers in rust clone them at some point (otherwise why use reference counting).

@oli-obk
Copy link
Contributor

oli-obk commented Mar 21, 2017

Well... in clippy we can insert this without an RFC. For the compiler, this RFC would need to continue.

Deprecation is not breaking the code. Deprecation means you start getting warnings when you use it. So basically the same as adding a lint.

@sfackler
Copy link
Member

Would this also deprecate deriving Clone for any type containing a reference counted pointer?

@oli-obk
Copy link
Contributor

oli-obk commented Mar 21, 2017

Would this also deprecate deriving Clone for any type containing a reference counted pointer?

We're not deprecating the Clone impl of the reference counted pointers. Only the method call syntax for clone on these pointers.

@sfackler
Copy link
Member

Is the derived impl going to suppress that deprecation warning then?

@eddyb
Copy link
Member

eddyb commented Mar 21, 2017

@sfackler Those use $crate::clone::Clone::clone to disambiguate, not method call syntax.

@Kixunil
Copy link

Kixunil commented Mar 22, 2017

This sounds like a good idea. Maybe add clone_inner() for T: Clone too? It might be more readable than (*rc).clone().

@jfager
Copy link

jfager commented Mar 22, 2017

Without weighing in on the specific solution, I just want to voice support for this being a problem that needs solving. I've always hated calling clone on pointer types, though seeing this RFC is the first articulation of the reason for that.

Minor noncommittal bike shed: share?

@plumenator
Copy link

Typo: repsective -> respective

@eddyb eddyb closed this Mar 23, 2017
@eddyb eddyb reopened this Mar 23, 2017
@eddyb
Copy link
Member

eddyb commented Mar 23, 2017

Ugh, sorry, mobile UI has no confirmation on acidental press.

@scottmcm
Copy link
Member

I think "use Arc::clone" (from @SimonSapin) deserves to be explicitly noted as an alternative in the RFC text. All the clarity and linting points could be done immediately with that, no need for a stabilization period. And at least the two data.clone() calls in the webrender example file linked from the RFC don't call methods after the .clone(), so don't need method call syntax for ergonomics.

I think whether new_ref calls clone or clone calls new_ref is irrelevant, as it's invisible "when reading or reviewing code".

Bikeshed: I'm not a fan of "new" in the name, since that has a somewhat different connotation in its usual use. What about just clone_ref? I also like @jfager's share.

Also, if the name is going to include ref, it should probably work for &. Of course that's somewhat silly non-generically, so implies a trait.

Is this always going to be on something where clone is shallow anyway? Clone being shallow for Arc is certainly more fundamental, since a deep clone is Arc::new(p.deref().clone()). And "deep clone" on a & can't return a &, so is called to_owned. Weak is even weirder, since returning a Weak is possible but fundamentally useless, so it's good that nothing "deep clone"-like is provided there.

Overall, that could turn the proposal to something like this:

trait CloneRef : Clone {
    fn clone_ref(&self) -> Self { self.clone() }
}

impl<'a, T: ?Sized> CloneRef for &'a T {}
impl<T: ?Sized> CloneRef for std::rc::Rc<T> {}
impl<T: ?Sized> CloneRef for std::rc::Weak<T> {}
impl<T: ?Sized> CloneRef for std::sync::Arc<T> {}
impl<T: ?Sized> CloneRef for std::sync::Weak<T> {}

Another alternative: Put these methods (and possibly trait) in a crate, since there are no coherence issues. See how broadly the crate is used before putting into std.

On the lint: warning on Arc::clone feels quite aggressive for rustc. I think it should live in clippy.

@aturon aturon self-assigned this Mar 28, 2017
@nical
Copy link
Author

nical commented Mar 28, 2017

I added the suggestion made by @SimonSapin to the alternatives section as well as something equivalent to the CloneRef trait suggested by @scottmcm.

@aturon
Copy link
Member

aturon commented Mar 29, 2017

The libs team discussed this RFC a bit in triage yesterday, and the general consensus was that the Arc::clone approach would be preferable to us, as a more minimal shift. There was also agreement with @scottmcm's point that linting here should probably go through clippy, as it is indeed quite aggressive.

@nical
Copy link
Author

nical commented Mar 30, 2017

I take it that the decision is made to go for Arc::clone(&foo).

So, taking this approach, what is the way forward from here? Is there a separate RFC process for clippy? Should I re-write this RFC to formalize that Arc::clone is the idiomatic way to clone references (is it actually part of the consensus)? What about updating the documentation? (sorry, lots of questions, I am new to these things).

I'm about to go offline for two weeks but I'll be happy to help with these when I come back.

I don't think that this approach will solve the problem (as in, the vast majority of code will still be written foo.clone() and most people won't know about the possibility to do otherwise. But I understand the decision to minimize change and it's fine because I suppose we can easily come back in a year and revisit the question if it turns out the problem still exists.

@kennytm
Copy link
Member

kennytm commented Mar 30, 2017

@nical You just need to file an issue to https://github.com/Manishearth/rust-clippy/issues. Typical reports seem to be pretty short (e.g. https://github.com/Manishearth/rust-clippy/issues/1594).

@nical
Copy link
Author

nical commented Mar 30, 2017

Thanks, I filed a clippy issue. It would be good to clarify what the official position is with respect to the idiomatic way to clone a reference counted pointer before we close this pull request.

@Kixunil
Copy link

Kixunil commented Mar 30, 2017

What is considered as idiomatic way of cloning inner value then? Is it just (*my_rc).clone() or something else?

@oli-obk
Copy link
Contributor

oli-obk commented Mar 30, 2017

What is considered as idiomatic way of cloning inner value then? Is it just (*my_rc).clone() or something else?

Technically you could do T::clone(&*my_rc) to be very sure about what's going on.

@SimonSapin
Copy link
Contributor

you could do T::clone(&*my_rc)

Or T::clone(&my_rc), with implicit autoderef.

@sfackler
Copy link
Member

I have always used the T::foo syntax when I want to be explicit about what type I want the method called on.

bors-servo pushed a commit to servo/webrender that referenced this pull request Apr 3, 2017
Favor the function over the method syntax when cloning a reference counted pointer.

```data.clone()``` reads like performance hazard, due to the vague definition of Clone, which can be either very expensive if data is an uniquely owned large resource or very cheap if it is a reference counted pointer to such a resource. This has already confused people reading the code and even code reviews several times in webrender, so I would like to work around this unfortunate ambiguity.

I first tried to address this at the [standard library level](rust-lang/rfcs#1954) but the decision of the rust team appears to be that we should use the function syntax instead of the method syntax to address the problem.

See also the [clippy issue](https://github.com/Manishearth/rust-clippy/issues/1645) about providing a lint for this.

I decided to take a systematic approach and use the function call syntax for all of the outstanding reference counted pointer clones that I came across (I would not be surprised that I missed some of them). Rather than try to separate the obvious from the ambiguous ones. I would rather make this a default policy than have to debate about where the line is in each code review.

The three commits are independent and can be reviewed separately (although they are small and simple enough to look at as one diff if you prefer).

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/webrender/1037)
<!-- Reviewable:end -->
@aturon
Copy link
Member

aturon commented Apr 15, 2017

Yep, T::clone is idiomatic.

@nical, do you want to close?

@nical
Copy link
Author

nical commented Apr 15, 2017

Alright! I'll look into updating the docs as a follow up soon-ish.

@nical nical closed this Apr 15, 2017
nical added a commit to nical/rust that referenced this pull request May 22, 2017
It was decided in the RFC discussion rust-lang/rfcs#1954 to make the function call syntax Rc::clone(&foo) the idiomatic way to clone a reference counted pointer (over the method call syntax foo.clone(). This change updates the documentation of Rc, Arc and their respoective Weak pointers to reflect it and bring more exposure to the existence of the function call syntax.
Mark-Simulacrum added a commit to Mark-Simulacrum/rust that referenced this pull request May 27, 2017
Update to Rc and Arc documentation to favor the Rc::clone(&ptr) syntax.

This is a followup of the discussion in rust-lang/rfcs#1954.

The solution chosen by the core team to address the problem tackled by the [the RFC](rust-lang/rfcs#1954) was to make the function call syntax Rc::clone(&foo) the idiomatic way to clone a reference counted pointer (over the method call syntax foo.clone()).
This change updates the documentation of Rc, Arc and their respective Weak pointers to reflect this decision and bring more exposure to the existence of the function call syntax.
Mark-Simulacrum added a commit to Mark-Simulacrum/rust that referenced this pull request May 27, 2017
Update to Rc and Arc documentation to favor the Rc::clone(&ptr) syntax.

This is a followup of the discussion in rust-lang/rfcs#1954.

The solution chosen by the core team to address the problem tackled by the [the RFC](rust-lang/rfcs#1954) was to make the function call syntax Rc::clone(&foo) the idiomatic way to clone a reference counted pointer (over the method call syntax foo.clone()).
This change updates the documentation of Rc, Arc and their respective Weak pointers to reflect this decision and bring more exposure to the existence of the function call syntax.
bors added a commit to rust-lang/rust that referenced this pull request May 27, 2017
Update to Rc and Arc documentation to favor the Rc::clone(&ptr) syntax.

This is a followup of the discussion in rust-lang/rfcs#1954.

The solution chosen by the core team to address the problem tackled by the [the RFC](rust-lang/rfcs#1954) was to make the function call syntax Rc::clone(&foo) the idiomatic way to clone a reference counted pointer (over the method call syntax foo.clone()).
This change updates the documentation of Rc, Arc and their respective Weak pointers to reflect this decision and bring more exposure to the existence of the function call syntax.
Centril added a commit to Centril/rust that referenced this pull request Aug 19, 2019
Remove recommendation about idiomatic syntax for Arc::clone

I believe we should not make this recommendation. I don't want to argue that `Arc::clone` is less idiomatic than `arc.clone`, but that the choice is not clear cut and that we should not be making this kind of call in the docs.

The `.clone()` form has advantages too: it is more succinct, it is more likely to be understood by beginners, and it is more uniform with other `clone` calls, indeed with most other method calls.

Whichever approach is better, I think that this discussion belongs in a style guide or textbook, rather than the library docs. We don't talk much about idiomatic code in the docs, this place is pretty exceptional.

The recommendation is also not followed in this repo. It is hard to figure out how many calls there are of the `.clone()` form, but there are 1550 uses of `Arc` and only 65 uses of `Arc::clone`. The recommendation has existed for over two years.

The recommendation was added in rust-lang#42137, as a result of rust-lang/rfcs#1954. However, note that that RFC was closed because it was not necessary to change the docs (the original RFC proposed a new function instead). So I don't think an RFC is necessary here (and I'm not trying to re-litigate the discussion on that RFC (which favoured `Arc::clone` as idiomatic) in any case).

cc @nical (who added the docs in the first place; sorry :-) )

r? @alexcrichton (or someone else on @rust-lang/libs )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.