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

Add a RFC for TryClone trait #2189

Closed
wants to merge 1 commit into from
Closed

Conversation

achanda
Copy link
Contributor

@achanda achanda commented Oct 25, 2017

fail. In case we do not need this, implementing the RFC will produce extra maintenance
overhead.

[1] https://github.com/search?l=Rust&q=TryClone&type=Code&utf8=%E2%9C%93
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: Prefer inline links instead of footnotes.

```
pub trait TryClone<T> {
type Error,
fn try_clone() -> Result<Self, Self::Error>
Copy link
Contributor

Choose a reason for hiding this comment

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

Should:

fn try_clone_from(&mut self, source: &Self) -> Result<(), Self::Error> { ... }

be added to mirror Clone?

# Motivation
A number of crates have defined `TryClone` as a trait and implemented it on standard
types [1]. Including this in the standard library will reduce some clutter in the code
of those crates.
Copy link
Member

Choose a reason for hiding this comment

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

What code? Can you give an example of real-world code that would be improved here?

@scottmcm scottmcm added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Oct 26, 2017
@ExpHP
Copy link

ExpHP commented Oct 30, 2017

To me, it does not look like try_clone is the standard library's name for a clone function that can fail. I mean, the try_clone methods in the standard library are an odd bunch; notice that all of their descriptions are of the form with Creates a new independently owned handle to the underlying _, despite the general sounding name.

If anything, their naming says more to be about the ancestry of these types than anything else.

A number of crates have defined TryClone as a trait and implemented it on standard types. https://github.com/search?l=Rust&q=TryClone&type=Code&utf8=%E2%9C%93

What I see in that link are:

  • A trait that entirely ignores the standard library's try_clone functions.
  • Three cases (1 2 3) of what appear to me to be premature abstractions. They each wrap precisely one of the standard library's try_clone methods, and it's clear that none of them were ever intended to support any of the other types. AFAICT all of these traits merely exist to allow the structs in these crates to wrap types written by wrappers around the standard library type in client code (who can implement the trait for compatibility).
  • ...and that's it.

@Evrey
Copy link

Evrey commented Nov 1, 2017

This trait should be added in std [...]

You meant core::clone there. I'm not that excited for a second HashMap, i.e. an item that could be in core (or rather alloc in this case) but isn't.

It is not clear under what conditions can cloning a type fail.

Note that those are also candidates for an impl TryClone and that there are probably many more, especially when fallible allocations are a thing.

All in all, I do like the proposal. I probably won't need it any time soon, but I like having it around, just in case.

One more thing, though:

Besides quite some typos, you forgot about Clone::clone_from, so you might want to expand TryClone like this:

pub trait TryClone: Sized {
    type Error;

    fn try_clone(&self) -> Result<Self, Self::Error>;

    #[inline]
    fn try_clone_from(&mut self, src: &Self) -> Result<(), Self::Error> {
        *self = src.try_clone()?
    }
}

This trait should be added in `std` and should be called `TryClone`. It should look similar
to `TryFrom`

```
Copy link
Member

Choose a reason for hiding this comment

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

```rust

For syntax highlighting

Copy link
Member

Choose a reason for hiding this comment

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

Oops, someone has already requestes this.

@achanda
Copy link
Contributor Author

achanda commented Nov 2, 2017

Thanks, everyone. Pushed a new version with all comments addresses.


One example is [Clone](https://doc.rust-lang.org/src/alloc/arc.rs.html#710) for `std::alloc::arc`. This is better represented
by returning a `Result<Arc>`. Doing that will also make the unsafe block unnecessary. Also, this will enable
the caller to handle these errors idiomatically instead of aborting the process.
Copy link
Member

@sfackler sfackler Nov 2, 2017

Choose a reason for hiding this comment

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

This error is only encountered if your process leaks at a minimum 2 billion copies of the Arc, after which you can never clone it again. How is that situation supposed to be "idiomatically handled"?

Copy link

Choose a reason for hiding this comment

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

Talking about 2 billion copies... An Arc takes 4 bytes on 32-bit architectures plus the data behind that 4 bytes pointer. If, in theory, you could use every single byte of your program to allocate a copy of an Arc, then you would have a maximum reference count of 0x40000000_u32, which is < u32::max_value(). And now reduce that number further, because you also have to fit the actual data, your whole program, and the OS kernel into your 4GiB address space.

As a result, it is absolutely impossible to overflow the reference counters of Arc and Rc, ever, on any CPU architecture. So, in a way, you could optimise alloc instead by not doing overflow checks.

Copy link
Member

Choose a reason for hiding this comment

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

let foo = Arc::new(0);
loop {
    mem::forget(foo.clone());
}

Copy link

Choose a reason for hiding this comment

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

Now, that is just evil. ;)

@kornelski
Copy link
Contributor

I see it as useful for wrapping C libraries. Most have functions for copying/duplicating their objects, but they're allowed to fail (NULL is always an option), so a non-panicking Clone sounds like a good fit for these.

@Centril
Copy link
Contributor

Centril commented Nov 8, 2017

Do we need a try_ version for std::borrow::ToOwned?

@burdges
Copy link

burdges commented Nov 8, 2017

Just fyi, Cow::into_owned needs to take self by value, so if you push this enough you'd need to return the Cow along with your error, which breaks ?.

TryCow::try_into_owned(self) -> Result<<B as TryToOwned>::Owned,(Self,<B as TryToOwned>::Error)>

@dtolnay
Copy link
Member

dtolnay commented Jan 29, 2018

One important thing was not clear to me from the RFC and the discussion so far -- the text seems to frame TryClone as an improvement over ad hoc try_clone() methods on individual types, but why is this an improvement? What are some concrete uses cases that this enables that are inconvenient or not possible today?

@dtolnay
Copy link
Member

dtolnay commented Apr 15, 2018

Thanks for pursuing this design and for the insightful discussion! I am going to close because I think we will be all right continuing to use individual try_clone() methods on concrete types. There hasn't surfaced a use case that would benefit from having these abstracted into a TryClone trait.

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.

10 participants