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_from in the standard library #28481

Closed
Aatch opened this issue Sep 18, 2015 · 13 comments · Fixed by #70052 or #80400
Closed

clone_from in the standard library #28481

Aatch opened this issue Sep 18, 2015 · 13 comments · Fixed by #70052 or #80400
Labels
C-enhancement Category: An issue proposing an enhancement or a PR with one. E-help-wanted Call for participation: Help is requested to fix this issue. I-slow Issue: Problems and improvements with respect to performance of generated code. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@Aatch
Copy link
Contributor

Aatch commented Sep 18, 2015

The Clone trait has a sadly-neglected clone_from method that clones a value into an existing one. There is a default implementation, but it's fairly naive and therefore not optimal for all cases, notably collections.

It would be good if the major collections had better implementations for clone_from where it can be implemented more efficiently.

/cc @rust-lang/libs

@huonw huonw added A-libs I-slow Issue: Problems and improvements with respect to performance of generated code. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Sep 18, 2015
@sfackler
Copy link
Member

Seems pretty reasonable to me. We might also want to consider updating deriving logic to implement clone_from explicitly

@petrochenkov
Copy link
Contributor

cc #27939

@alexcrichton
Copy link
Member

My thoughts here are that one-off specialized implementations are fine, but I'm not a huge fan of altering derive(Clone)] (e.g. see my comments on #27939). In that sense I'm fine just updating Clone implementations in-tree for collections to "do the right thing"

@apasel422
Copy link
Contributor

apasel422 commented Sep 26, 2015

  • BinaryHeap
  • BTreeMap
  • BTreeSet
  • HashMap
  • HashSet
  • LinkedList
  • String
  • Vec
  • VecDeque
  • Option
  • Result

@bluss
Copy link
Member

bluss commented Jan 31, 2017

Edited the list to add Option and Result

@Mark-Simulacrum Mark-Simulacrum added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label Jul 24, 2017
@steveklabnik
Copy link
Member

Triage: the above list is still accurate today.

@jonas-schievink jonas-schievink added the E-help-wanted Call for participation: Help is requested to fix this issue. label Mar 16, 2019
@jonas-schievink
Copy link
Contributor

Seems like a good issue for people who want to work on libstd

bors added a commit that referenced this issue Jun 10, 2019
Implement Clone::clone_from for Option and Result

See #28481
Centril added a commit to Centril/rust that referenced this issue Jun 12, 2019
…odrAus

Implement Clone::clone_from for Option and Result

See rust-lang#28481
@m-ou-se
Copy link
Member

m-ou-se commented Jul 22, 2019

@apasel422 You can update your comment to check Option and Result. :)

Manishearth added a commit to Manishearth/rust that referenced this issue Oct 2, 2019
Implement Clone::clone_from for LinkedList

See rust-lang#28481. This represents a substantial speedup when the list sizes are comparable, and shouldn't ever be significantly worse. Technically split_off is doing an unnecessary search, but the code is hopefully cleaner as a result. I'm happy to rework anything that needs to be changed as well!
Centril added a commit to Centril/rust that referenced this issue Oct 2, 2019
Implement Clone::clone_from for LinkedList

See rust-lang#28481. This represents a substantial speedup when the list sizes are comparable, and shouldn't ever be significantly worse. Technically split_off is doing an unnecessary search, but the code is hopefully cleaner as a result. I'm happy to rework anything that needs to be changed as well!
Centril added a commit to Centril/rust that referenced this issue Oct 3, 2019
Implement Clone::clone_from for LinkedList

See rust-lang#28481. This represents a substantial speedup when the list sizes are comparable, and shouldn't ever be significantly worse. Technically split_off is doing an unnecessary search, but the code is hopefully cleaner as a result. I'm happy to rework anything that needs to be changed as well!
@AnthonyMikh
Copy link
Contributor

Do we currently have any specific requirements to field drop order for this method? The docs are too vague. This can actuallly cause problems. For example, the following code:

#[derive(Clone)]
struct A;

impl Drop for A {
    fn drop(&mut self) {
        println!("A dropped");
    }
}

#[derive(Clone)]
struct B;

impl Drop for B {
    fn drop(&mut self) {
        println!("B dropped");
    }
}

struct Pair {
    a: A,
    b: B,
}

impl Clone for Pair {
    fn clone(&self) -> Self {
        Pair {
            a: self.a.clone(),
            b: self.b.clone(),
        }
    }
    
    // fn clone_from(&mut self, other: &Self) {
    //     self.b = other.b.clone(); // <-- note: assigned in other order
    //     self.a = other.a.clone();
    // }
}

fn main() {
    let mut first = Pair { a: A, b: B };
    let second = Pair { a: A, b: B };
    first.clone_from(&second);
}

prints:

A dropped
B dropped
A dropped
B dropped
A dropped
B dropped

, but if we uncomment custom implementation of clone_from, then it prints:

B dropped
A dropped
A dropped
B dropped
A dropped
B dropped

This can break the code which relies on specific drop order, so implementing clone_from for the sake of optimisation is actually a breaking change.

Centril added a commit to Centril/rust that referenced this issue Oct 13, 2019
Implement Clone::clone_from for VecDeque

See rust-lang#28481. For simple data types with the target much longer than the source, this implementation can be significantly slower than the default (probably due to the use of truncate). However, it should be substantially faster when cloning from nested data structures with similar shapes or when cloning from VecDeques with similar lengths, hopefully more common use cases for clone_from.
bors added a commit that referenced this issue Nov 22, 2019
Implement clone_from for BTreeMap and BTreeSet

See #28481. This results in up to 90% speedups on simple data types when `self` and `other` are the same size, and is generally comparable or faster. Some concerns:

1. This implementation requires an `Ord` bound on the `Clone` implementation for `BTreeMap` and `BTreeSet`. Since these structs can only be created externally for keys with `Ord` implemented, this should be fine? If not, there's certainly a less safe way to do this.
2. Changing `next_unchecked` on `RangeMut` to return mutable key references allows for replacing the entire overlapping portion of both maps without changing the external interface in any way. However, if `clone_from` fails it can leave the `BTreeMap` in an invalid state, which might be unacceptable.

This probably needs an FCP since it changes a trait bound, but (as far as I know?) that change cannot break any external code.
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this issue Jan 30, 2020
Implement clone_from for BTreeMap and BTreeSet

See rust-lang#28481. This results in up to 90% speedups on simple data types when `self` and `other` are the same size, and is generally comparable or faster. Some concerns:

1. This implementation requires an `Ord` bound on the `Clone` implementation for `BTreeMap` and `BTreeSet`. Since these structs can only be created externally for keys with `Ord` implemented, this should be fine? If not, there's certainly a less safe way to do this.
2. Changing `next_unchecked` on `RangeMut` to return mutable key references allows for replacing the entire overlapping portion of both maps without changing the external interface in any way. However, if `clone_from` fails it can leave the `BTreeMap` in an invalid state, which might be unacceptable.

~This probably needs an FCP since it changes a trait bound, but (as far as I know?) that change cannot break any external code.~
@crgl
Copy link
Contributor

crgl commented Jan 30, 2020

@apasel422 you can check BTreeMap, BTreeSet, LinkedList, and VecDeque. I would also argue that since HashMap and HashSet derive from hashbrown (and the benefits of clone_from seem dubious there anyway), this issue could be closed?

@bors bors closed this as completed in 8b26609 Aug 7, 2020
@adlerd
Copy link

adlerd commented Dec 27, 2020

This was marked fixed by @Amanieu in #70052; however, currently std::collections::{HashMap, HashSet}::clone_from use the trait default impl rather than hashbrown::*::clone_from because they #[derive(Clone)]. Is this intentional? If not, perhaps this issue should be reopened for now?

@Amanieu
Copy link
Member

Amanieu commented Dec 27, 2020

Nice catch! It should be pretty simple to resolve though, would you like to create a PR?

@adlerd
Copy link

adlerd commented Dec 27, 2020

Opened #80400.

@bors bors closed this as completed in 0f42d47 Dec 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-enhancement Category: An issue proposing an enhancement or a PR with one. E-help-wanted Call for participation: Help is requested to fix this issue. I-slow Issue: Problems and improvements with respect to performance of generated code. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet