NDLR: 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 struct
struct Triplet {
one: i32,
two: i32,
three: i32,
}
Si je passe cela à une fonction, elle est implicitement copiée. Maintenant, parfois je lis 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 d'implémenter un trait qui rendrait Triplet
non copiable et donc "mobile"?
J'ai lu quelque part que l'on doit implémenter le trait Clone
pour copier des choses qui ne sont pas implicitement copiables, mais je n'ai jamais lu l'inverse, c'est d'avoir quelque chose qui est implicitement copiable et de le rendre non copiable afin qu'il bouge à la place.
Cela a-t-il même un sens?
Préface: Cette réponse a été écrite avant traits intégrés opt-in - spécifiquement les aspects Copy
- ont été implémentés . 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 a été posée).
Ancien : Pour répondre à la question de base, vous pouvez ajouter un champ marqueur stockant une valeur
NoCopy
. Par exemple.struct Triplet { one: int, two: int, three: int, _marker: NoCopy }
Vous pouvez également le faire en ayant un destructeur (via l'implémentation du trait
Drop
), mais l'utilisation des types de marqueurs est préférable si le destructeur ne fait rien.
Les types se déplacent désormais par défaut, c'est-à-dire que lorsque vous définissez un nouveau type, il n'implémente pas Copy
sauf si vous l'implémentez explicitement pour votre type:
struct Triplet {
one: i32,
two: i32,
three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move
L'implémentation ne peut exister que si chaque type contenu dans le nouveau struct
ou enum
est lui-même Copy
. Sinon, le compilateur affichera un message d'erreur. Il ne peut également exister que si le type n'existe pas a une implémentation Drop
.
Pour répondre à la question que vous n'avez pas posée ... "Qu'en est-il des mouvements et de la copie?":
Je définirai d'abord deux "copies" différentes:
(&usize, u64)
, il s'agit de 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 toucher le usize
à l'autre bout du &
. Autrement dit, cela équivaut à appeler memcpy
.Rc<T>
implique simplement d'augmenter le nombre de références, et une copie sémantique d'un Vec<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. Il peut s'agir de copies complètes (par exemple Vec<T>
) ou peu profond (par exemple Rc<T>
ne touche pas le stocké T
), Clone
est défini de manière lâche comme la plus petite quantité de travail requise pour copier sémantiquement une valeur de type T
depuis l'intérieur d'un &T
à T
.La rouille est comme C, chaque par valeur, l'utilisation d'une valeur est une copie d'octets:
let x: T = ...;
let y: T = x; // byte copy
fn foo(z: T) -> T {
return z // byte copy
}
foo(y) // byte copy
Ce sont des copies d'octets, que T
se déplace ou soit "implicitement copiable". (Pour être clair, ils ne sont pas nécessairement des copies littéralement octet par octet au moment de l'exécution: le compilateur est libre d'optimiser les copies si le comportement du code est préservé.)
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 s'ils ont des destructeurs, par ex.
{
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
} // destructors run here
Si w
n'était qu'une copie en octets simples de v
, alors il y aurait deux vecteurs pointant vers la même allocation, tous deux avec des destructeurs qui la libèrent ... provoquant ne double liberté , ce qui est un problème. NB. Ce serait parfaitement bien, si nous faisions une copie sémantique de v
dans w
, puisque w
serait alors son propre Vec<u8>
et les destructeurs ne se piétineraient pas.
Il y a quelques correctifs possibles ici:
w
ait sa propre allocation, comme C++ avec ses constructeurs de copie.v
ne peut plus être utilisé et que son destructeur ne soit pas exécuté.Le dernier est ce que Rust fait: a déplacer est juste une utilisation par valeur où la source est statiquement invalidée, donc le compilateur empêche l'utilisation ultérieure du now -mémoire non valide.
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value
Les types qui ont des destructeurs doivent se déplacent lorsqu'ils sont utilisés par valeur (aka 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) il est peu probable qu'une copie d'octets reproduise correctement cette propriété.
Pensez à un type primitif comme u8
: une copie d'octet est simple, copiez simplement le seul octet, et une copie sémantique est tout aussi simple, copiez le seul octet. En particulier, une copie d'octets 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 types de Copy
, les utilisations par valeur sont également automatiquement des copies sémantiques, et il est donc parfaitement sûr de continuer à utiliser la source.
let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine
Ancien : le marqueur
NoCopy
remplace le comportement automatique du compilateur en supposant que les types qui peuvent êtreCopy
(c'est-à-dire contenant uniquement agrégats de primitives et&
) sontCopy
. Cependant, cela changera lorsque traits intégrés opt-in est implémenté.
Comme mentionné ci-dessus, les traits intégrés opt-in sont implémentés, donc le compilateur n'a plus de comportement automatique. Cependant, les règles utilisées pour le comportement automatique dans le passé sont les mêmes règles pour vérifier s'il est légal d'implémenter Copy
.
Le moyen le plus simple consiste à intégrer quelque chose dans votre type qui n'est pas copiable.
La bibliothèque standard fournit un "type de marqueur" pour exactement ce cas d'utilisation: NoCopy . Par exemple:
struct Triplet {
one: i32,
two: i32,
three: i32,
nocopy: NoCopy,
}