Note de l'éditeur : cette question a été posée avant Rust 1.0 et certaines des affirmations de la question ne sont pas nécessairement vraies dans Rust 1.0. Certaines réponses ont été mises à jour pour répondre aux deux versions.
J'ai cette structure
struct Triplet {
one: i32,
two: i32,
three: i32,
}
Si je passe ceci à une fonction, il est implicitement copié. Maintenant, je lis parfois que certaines valeurs ne sont pas copiables et doivent donc être déplacées.
Serait-il possible de rendre cette structure Triplet
non copiable? Par exemple, serait-il possible de mettre en œuvre un trait qui rendrait Triplet
non copiable et donc «déplaçable»?
J'ai lu quelque part qu'il Clone
fallait implémenter le trait pour copier des choses qui ne sont pas implicitement copiables, mais je n'ai jamais lu l'inverse, c'est-à-dire avoir quelque chose qui est implicitement copiable et le rendre non copiable afin qu'il se déplace à la place.
Cela a-t-il même un sens?
Réponses:
Préface : Cette réponse a été rédigée avant la mise en œuvre des caractéristiques intégrées opt-in (en particulier les
Copy
aspects) . J'ai utilisé des guillemets pour indiquer les sections qui ne s'appliquaient qu'à l'ancien schéma (celui qui s'appliquait lorsque la question était posée).Les types se déplacent maintenant par défaut, c'est-à-dire que lorsque vous définissez un nouveau type, il n'implémente pas à
Copy
moins que vous ne l'implémentiez explicitement pour votre type:L'implémentation ne peut exister que si chaque type contenu dans le nouveau
struct
ouenum
est lui-mêmeCopy
. Sinon, le compilateur imprimera un message d'erreur. Il ne peut également exister que si le type n'existe pas d'Drop
implémentation.Pour répondre à la question que vous n'avez pas posée ... "Que se passe-t-il avec les mouvements et la copie?":
Tout d'abord, je définirai deux "copies" différentes:
(&usize, u64)
, c'est 16 octets sur un ordinateur 64 bits, et une copie superficielle prendrait ces 16 octets et répliquerait leur valeur dans un autre bloc de mémoire de 16 octets, sans toucherusize
à l'autre extrémité du&
. Autrement dit, cela équivaut à appelermemcpy
.Rc<T>
implique simplement d'augmenter le nombre de références, et une copie sémantique de aVec<T>
implique la création d'une nouvelle allocation, puis la copie sémantique de chaque élément stocké de l'ancien vers le nouveau. Ceux-ci peuvent être des copies profondes (par exempleVec<T>
) ou superficielles (par exemple,Rc<T>
ne touche pas le stockéT
),Clone
est défini de manière approximative comme la plus petite quantité de travail requise pour copier sémantiquement une valeur de typeT
de l'intérieur de a&T
àT
.Rust est comme C, chaque utilisation par valeur d'une valeur est une copie d'octets:
Ce sont des copies d'octets, qu'elles soient ou non
T
déplacées ou "implicitement copiables". (Pour être clair, ils ne sont pas nécessairement des copies octet par octet au moment de l'exécution: le compilateur est libre d'optimiser les copies si le comportement du code est conservé.)Cependant, il y a un problème fondamental avec les copies d'octets: vous vous retrouvez avec des valeurs dupliquées en mémoire, ce qui peut être très mauvais si elles ont des destructeurs, par exemple
Si
w
c'était juste une copie d'octet simple dev
alors il y aurait deux vecteurs pointant vers la même allocation, tous deux avec des destructeurs qui la libèrent ... provoquant un double free , ce qui est un problème. NB. Ce serait parfaitement bien, si nous faisions une copie sémantique dev
intow
, puisque cew
serait alors son propreVec<u8>
et les destructeurs ne se piétineraient pas les uns les autres.Il y a quelques correctifs possibles ici:
w
ait sa propre allocation, comme C ++ avec ses constructeurs de copie.v
cela ne puisse plus être utilisé et que son destructeur ne soit pas exécuté.Le dernier est ce que fait Rust: un déplacement est juste une utilisation par valeur où la source est statiquement invalidée, de sorte que le compilateur empêche toute utilisation ultérieure de la mémoire désormais invalide.
Les types qui ont des destructeurs doivent se déplacer lorsqu'ils sont utilisés par valeur (c'est-à-dire lorsque l'octet est copié), car ils ont la gestion / la propriété de certaines ressources (par exemple une allocation de mémoire ou un descripteur de fichier) et il est très peu probable qu'une copie d'octet duplique correctement cela la possession.
"Eh bien ... qu'est-ce qu'une copie implicite?"
Pensez à un type primitif comme
u8
: une copie d'octet est simple, copiez simplement l'octet unique, et une copie sémantique est tout aussi simple, copiez l'octet unique. En particulier, une copie d'octet est une copie sémantique ... Rust a même un trait intégréCopy
qui capture quels types ont des copies sémantiques et d'octets identiques.Par conséquent, pour ces
Copy
types, les utilisations par valeur sont également automatiquement des copies sémantiques, et il est donc parfaitement sûr de continuer à utiliser la source.Comme mentionné ci-dessus, les caractéristiques intégrées opt-in sont implémentées, de sorte que le compilateur n'a plus de comportement automatique. Cependant, la règle utilisée pour le comportement automatique dans le passé sont les mêmes règles pour vérifier si sa mise en œuvre est légale
Copy
.la source
Le moyen le plus simple est d'incorporer dans votre texte quelque chose qui n'est pas copiable.
La bibliothèque standard fournit un "type de marqueur" pour exactement ce cas d'utilisation: NoCopy . Par exemple:
la source