Jaka jest różnica między kopiowaniem a klonem?

129

Ten problem wydaje się sugerować, że to tylko szczegół implementacji (w memcpyporównaniu z ???), ale nie mogę znaleźć żadnego wyraźnego opisu różnic.

user12341234
źródło
Kod źródłowy Rusta ma odpowiednie wyjaśnienie
duan

Odpowiedzi:

116

Clonejest przeznaczony do dowolnych duplikatów: Cloneimplementacja typu Tmoże wykonywać dowolnie skomplikowane operacje wymagane do utworzenia nowego T. Jest to normalna cecha (inna niż bycie w preludium) i dlatego wymaga używania jak normalnej cechy, z wywołaniami metod itp.

CopyCecha reprezentuje wartości, które mogą być bezpiecznie powielane poprzez memcpy: rzeczy jak nadpisywaniem i przechodzącej argumentu przez wartość do funkcji są zawsze memcpys, a więc dla Copytypów, kompilator wie, że nie musi brać pod uwagę ruch .

huon
źródło
5
Czy mogę zrozumieć, że Clonejest to głęboka kopia i czy Copyjest to kopia w tle?
Djvu
11
Cloneotwiera możliwość, że typ może wykonać głęboką lub płytką kopię: „dowolnie skomplikowane”.
poolie
85

Główna różnica polega na tym, że klonowanie jest jawne. Notacja niejawna oznacza ruch dla typu innego niż Copytyp.

// u8 implements Copy
let x: u8 = 123;
let y = x;
// x can still be used
println!("x={}, y={}", x, y);

// Vec<u8> implements Clone, but not Copy
let v: Vec<u8> = vec![1, 2, 3];
let w = v.clone();
//let w = v // This would *move* the value, rendering v unusable.

Nawiasem mówiąc, każdy Copytyp też musi być Clone. Jednak nie muszą robić tego samego! W przypadku własnych typów .clone()może to być dowolna metoda, którą wybierzesz, podczas gdy niejawne kopiowanie zawsze wyzwoli a memcpy, a nie clone(&self)implementację.

mdup
źródło
1
Chłodny! To wyjaśnia drugie pytanie, które miałem, dotyczące tego, czy cecha Clone zapewnia ukryte kopiowanie. Okazuje się, że to pytanie i to były bardziej powiązane niż myślałem. Dzięki!
user12341234
W swoim pierwszym przykładzie załóżmy, że chcesz yuzyskać przeniesienie x, a nie jego kopię, jak w przypadku ostatniego zakomentowanego przykładu w = v. Jak byś to określił?
johnbakers
2
Nie możesz i nie możesz, ponieważ Copyma być zaimplementowany dla „tanich” typów, jak u8w przykładzie. Jeśli piszesz dość ciężki typ, dla którego uważasz, że ruch jest bardziej efektywny niż kopia, nie sugeruj się tym Copy. Zauważ, że w przypadku u8 nie można być bardziej wydajnym z ruchem, ponieważ pod maską prawdopodobnie wiązałoby się to przynajmniej z kopią wskaźnika - która jest już tak droga jak kopia u8, więc po co zawracać sobie głowę.
mdup
Czy to oznacza, że ​​obecność Copycechy ma wpływ na ukryte zakresy zmiennych w czasie życia? Jeśli tak, myślę, że jest to godne uwagi.
Brian Cain
7

Jak już opisano w innych odpowiedziach:

  • Copy jest niejawny, niedrogi i nie może być ponownie zaimplementowany (memcpy).
  • Clone jest jawna, może być kosztowna i może zostać dowolnie ponownie wdrożona.

To, czego czasami brakuje w dyskusji o Copyvs, Cloneto to, że wpływa on również na to, jak kompilator używa ruchów w porównaniu z automatycznymi kopiami. Na przykład:

#[derive(Debug, Clone, Copy)]
pub struct PointCloneAndCopy {
    pub x: f64,
}

#[derive(Debug, Clone)]
pub struct PointCloneOnly {
    pub x: f64,
}

fn test_copy_and_clone() {
    let p1 = PointCloneAndCopy { x: 0. };
    let p2 = p1; // because type has `Copy`, it gets copied automatically.
    println!("{:?} {:?}", p1, p2);
}

fn test_clone_only() {
    let p1 = PointCloneOnly { x: 0. };
    let p2 = p1; // because type has no `Copy`, this is a move instead.
    println!("{:?} {:?}", p1, p2);
}

Pierwszy przykład ( PointCloneAndCopy) działa tutaj dobrze ze względu na niejawną kopię, ale drugi przykład ( PointCloneOnly) spowodowałby błąd przy użyciu po przeniesieniu:

error[E0382]: borrow of moved value: `p1`
  --> src/lib.rs:20:27
   |
18 |     let p1 = PointCloneOnly { x: 0. };
   |         -- move occurs because `p1` has type `PointCloneOnly`, which does not implement the `Copy` trait
19 |     let p2 = p1;
   |              -- value moved here
20 |     println!("{:?} {:?}", p1, p2);
   |                           ^^ value borrowed here after move

Aby uniknąć niejawnego ruchu, możemy jawnie wywołać let p2 = p1.clone();.

Może to rodzić pytanie, jak wymusić ruch typu, który implementuje cechę Copy? . Krótka odpowiedź: nie możesz / nie ma sensu.

bluenote10
źródło
@Shepmaster Usunąłem go, chociaż uważam, że jest znacznie bardziej czytelny, ponieważ zawiera ładne kodowanie kolorami kompilatora Rusta i specjalnie upewniłem się, że wszystkie odpowiednie słowa wyszukiwania są również zawarte w tekście.
bluenote10