rust - que - restringir edicion en word
¿Es posible hacer que un tipo solo se mueva y no se pueda copiar? (2)
La forma más fácil es insertar algo en su tipo que no se puede copiar.
La biblioteca estándar proporciona un "tipo de marcador" para exactamente este caso de uso: NoCopy . Por ejemplo:
struct Triplet {
one: i32,
two: i32,
three: i32,
nocopy: NoCopy,
}
Nota del editor : esta pregunta se formuló antes de que Rust 1.0 y algunas de las afirmaciones en la pregunta no sean necesariamente ciertas en Rust 1.0. Algunas respuestas se han actualizado para abordar ambas versiones.
Tengo esta estructura
struct Triplet {
one: i32,
two: i32,
three: i32,
}
Si paso esto a una función, se copia implícitamente. Ahora, a veces leo que algunos valores no se pueden copiar y, por lo tanto, tienen que moverse.
¿Sería posible hacer que esta estructura Triplet
no se pueda copiar? Por ejemplo, ¿sería posible implementar un rasgo que haría que Triplet
no se pueda copiar y, por lo tanto, sea "movible"?
Leí en alguna parte que uno tiene que implementar el rasgo Clone
para copiar cosas que no se pueden copiar implícitamente, pero nunca lo he leído al revés, es decir, tener algo que se puede copiar implícitamente y hacerlo no copiable para que se mueva en su lugar.
¿Eso tiene sentido?
Prefacio : esta respuesta se escribió antes de que se implementaran los rasgos integrados opcionales ( específicamente los aspectos de Copy
. He usado comillas de bloque para indicar las secciones que solo se aplicaron al esquema anterior (el que se aplicó cuando se formuló la pregunta).
Antiguo : para responder a la pregunta básica, puede agregar un campo marcador que almacene un valor
NoCopy
. P.ej
struct Triplet { one: int, two: int, three: int, _marker: NoCopy }
También puedes hacerlo teniendo un destructor (mediante la implementación del rasgo
Drop
), pero se prefiere usar los tipos de marcador si el destructor no está haciendo nada.
Los tipos ahora se mueven de manera predeterminada, es decir, cuando define un nuevo tipo, no implementa Copy
menos que lo implemente explícitamente para su tipo:
struct Triplet {
one: i32,
two: i32,
three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move
La implementación solo puede existir si cada tipo contenido en la nueva struct
o enum
es en sí mismo Copy
. Si no, el compilador imprimirá un mensaje de error. También solo puede existir si el tipo no tiene una implementación Drop
.
Para responder a la pregunta que no hizo ... "¿qué pasa con los movimientos y copia?":
Primero definiré dos "copias" diferentes:
- una copia de bytes , que simplemente copia poco a poco un objeto byte a byte, sin seguir los punteros, por ejemplo, si tiene
(&usize, u64)
, es 16 bytes en una computadora de 64 bits, y una copia superficial tomaría esos 16 bytes y replicando su valor en algún otro trozo de 16 bytes de memoria, sin tocar elusize
en el otro extremo de&
. Es decir, es equivalente a llamar amemcpy
. - una copia semántica , duplicando un valor para crear una nueva instancia (algo) independiente que se puede usar de forma segura por separado a la anterior. Por ejemplo, una copia semántica de un
Rc<T>
implica simplemente aumentar el recuento de referencias, y una copia semántica de unVec<T>
implica crear una nueva asignación, y luego copiar semánticamente cada elemento almacenado de lo viejo a lo nuevo. Estas pueden ser copias profundas (por ejemplo,Vec<T>
) o superficiales (por ejemplo,Rc<T>
no tocan laT
almacenada),Clone
se define libremente como la menor cantidad de trabajo requerido para copiar semánticamente un valor de tipoT
desde adentro a&T
aT
Rust es como C, cada uso de valor por valor de un valor es una copia de byte:
let x: T = ...;
let y: T = x; // byte copy
fn foo(z: T) -> T {
return z // byte copy
}
foo(y) // byte copy
Son copias de bytes, independientemente de que T
mueva o se "copie implícitamente". (Para ser claros, no son necesariamente copias literalmente byte a byte en tiempo de ejecución: el compilador puede optimizar las copias si se preserva el comportamiento del código).
Sin embargo, hay un problema fundamental con las copias de bytes: terminas con valores duplicados en la memoria, lo que puede ser muy malo si tienen destructores, por ejemplo
{
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
} // destructors run here
Si w
fuera solo una copia de byte simple de v
entonces habría dos vectores apuntando a la misma asignación, ambos con destructores que lo liberan ... causando una doble libre , que es un problema. NÓTESE BIEN. Esto estaría perfectamente bien, si Vec<u8>
una copia semántica de v
en w
, ya que entonces w
sería su propio Vec<u8>
y los destructores no se estarían pisoteando.
Hay algunas soluciones posibles aquí:
- Deje que el programador lo maneje, como C. (no hay destructores en C, por lo que no es tan malo ... simplemente se queda con fugas de memoria en su lugar.: P)
- Realice una copia semántica implícitamente, de modo que
w
tenga su propia asignación, como C ++ con sus constructores de copia. - Considere los usos de valor por valor como una transferencia de propiedad, por lo que
v
ya no se puede usar y no se ejecuta su destructor.
Lo último es lo que hace Rust: un movimiento es solo un uso de valor por defecto cuando la fuente está invalidada estáticamente, por lo que el compilador evita el uso posterior de la memoria no válida.
let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value
Los tipos que tienen destructores deben moverse cuando se usan por valor (también conocido como byte copiado), ya que tienen administración / propiedad de algún recurso (por ejemplo, asignación de memoria o manejador de archivo) y es muy poco probable que una copia de bytes duplique correctamente este propiedad.
"Bueno ... ¿qué es una copia implícita?"
Piensa en un tipo primitivo como u8
: una copia de bytes es simple, solo copia el byte simple, y una copia semántica es igual de simple, copia el byte simple. En particular, una copia de byte es una copia semántica ... Rust incluso tiene una Copy
rasgo incorporada que captura qué tipos tienen copias semánticas y de bytes idénticas.
Por lo tanto, para estos tipos de Copy
, los usos de valor por valor también son automáticamente copias semánticas, por lo que es perfectamente seguro continuar usando la fuente.
let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine
Antiguo : el marcador
NoCopy
anula el comportamiento automático del compilador al suponer que los tipos que pueden serCopy
(es decir, que solo contienen agregados de primitivas y&
) sonCopy
. Sin embargo, esto cambiará cuando se implementen los rasgos integrados opcionales .
Como se mencionó anteriormente, se implementan los rasgos integrados opcionales, por lo que el compilador ya no tiene un comportamiento automático. Sin embargo, la regla utilizada para el comportamiento automático en el pasado son las mismas reglas para verificar si es legal implementar Copy
.