Qual é a diferença entre Copiar e Clonar?

127

Esse problema parece sugerir que é apenas um detalhe da implementação ( memcpyvs ???), mas não consigo encontrar nenhuma descrição explícita das diferenças.

user12341234
fonte
O código fonte do Rust tem explicação relevante
duan 5/03

Respostas:

114

Clonefoi projetado para duplicações arbitrárias: uma Cloneimplementação para um tipo Tpode executar operações arbitrariamente complicadas, necessárias para criar um novoT . É uma característica normal (além de estar no prelúdio) e, portanto, exige ser usada como uma característica normal, com chamadas de método etc.

A Copycaracterística representa valores que podem ser duplicados com segurança via memcpy: coisas como reatribuições e passar um argumento por valor para uma função são sempre memcpys, e, para os Copytipos, o compilador entende que não precisa considerar essas mudanças .

huon
fonte
5
Posso entender como Cloneé uma cópia em profundidade e é uma cópia em Copysombra?
Djvu
11
Cloneabre a possibilidade de que o tipo possa fazer uma cópia profunda ou superficial: "arbitrariamente complicado".
poolie 9/09/16
85

A principal diferença é que a clonagem é explícita. Notação implícita significa mover para um não- Copytipo.

// 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.

A propósito, todo Copytipo também é necessário Clone. No entanto, eles não são obrigados a fazer a mesma coisa! Para seus próprios tipos, .clone()pode ser um método arbitrário de sua escolha, enquanto a cópia implícita sempre acionará uma memcpy, não a clone(&self)implementação.

mdup
fonte
1
Legal! Isso esclarece uma pergunta secundária que eu tive sobre se a característica Clone fornece cópia implícita. Acontece que essa pergunta e essa eram mais relacionadas do que eu pensava. Obrigado!
user12341234
No seu primeiro exemplo, suponha que você queira yser movido x, e não uma cópia, como no seu último exemplo comentado w = v. Como você especificaria isso?
Johnbakers
2
Você não pode, e não o faz, porque Copydeve ser implementado para tipos "baratos", como u8no exemplo. Se você escreve um tipo bastante pesado, para o qual considera que uma movimentação é mais eficiente que uma cópia, não implemente Copy. Observe que, no caso do u8, você não pode ser mais eficiente com um movimento, pois, por baixo do capô, isso provavelmente implicaria pelo menos uma cópia indicadora - que já é tão cara quanto uma cópia do u8, então, por que se preocupar?
Mdup # 9/18
Isso significa que a presença da Copycaracterística afeta os escopos implícitos da vida útil das variáveis? Se sim, acho isso digno de nota.
Brian Cain
6

Como já coberto por outras respostas:

  • Copy é implícito, barato e não pode ser reimplementado (memcpy).
  • Clone é explícito, pode ser caro e pode ser reimplementado arbitrariamente.

O que às vezes falta na discussão de Copyvs Cloneé que ele também afeta como o compilador usa movimentos versus cópias automáticas. Por exemplo:

#[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);
}

O primeiro exemplo ( PointCloneAndCopy) funciona bem aqui por causa da cópia implícita, mas o segundo exemplo ( PointCloneOnly) com erro após o uso após a movimentação:

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

Para evitar a mudança implícita, poderíamos ligar explicitamente let p2 = p1.clone();.

Isso pode levantar a questão de como forçar uma movimentação de um tipo que implementa a característica de cópia? . Resposta curta: você não pode / não faz sentido.

bluenote10
fonte
@ Shepmaster Eu o removi, embora o ache muito mais legível, pois contém o bom código de cores do compilador Rust e, especificamente, certifiquei-me de que todas as palavras relevantes da pesquisa também estivessem contidas no texto.
bluenote10 6/01