¿Esa llamada asumir_inicio desencadenó un comportamiento indefinido?
Si. "Sin inicializar" es solo otro valor que puede tener un byte en Rust Abstract Machine, junto al habitual 0x00 - 0xFF. Escribamos este byte especial como 0xUU. (Consulte esta publicación de blog para obtener un poco más de información sobre este tema ). 0xUU se conserva mediante copias al igual que cualquier otro valor posible que un byte pueda tener se conserva mediante copias.
Pero los detalles son un poco más complicados. Hay dos formas de copiar datos en la memoria en Rust. Desafortunadamente, los detalles para esto tampoco están especificados explícitamente por el equipo de lenguaje Rust, por lo que lo que sigue es mi interpretación personal. Creo que lo que digo no es controvertido a menos que se indique lo contrario, pero, por supuesto, podría ser una impresión equivocada.
Copia sin tipo / byte-wise
En general, cuando se copia un rango de bytes, el rango de origen simplemente sobrescribe el rango de destino, por lo que si el rango de origen era "0x00 0xUU 0xUU 0xUU", entonces, después de la copia, el rango de destino tendrá esa lista exacta de bytes.
Así es como se comporta memcpy
/ memmove
en C (en mi interpretación del estándar, que desafortunadamente no está muy claro aquí). En Rust, ptr::copy{,_nonoverlapping}
probablemente realiza una copia en bytes, pero en realidad no se especifica con precisión en este momento y algunas personas pueden querer decir que también está escrita. Esto se discutió un poco en este tema .
Copia mecanografiada
La alternativa es una "copia escrita", que es lo que sucede en cada asignación normal ( =
) y al pasar valores a / desde una función. Una copia mecanografiada interpreta la memoria de origen en algún tipo T
y luego "vuelve a serializar" ese valor de tipo T
en la memoria de destino.
La diferencia clave para una copia en bytes es que T
se pierde información que no es relevante en el tipo . Básicamente, esta es una forma complicada de decir que una copia escrita "olvida" el relleno y, efectivamente, la restablece como no inicializada. En comparación con una copia sin tipo, una copia con tipo pierde más información. Las copias sin tipo conservan la representación subyacente, las copias con tipo solo conservan el valor representado.
Entonces, incluso cuando transmuta 0usize
a PaddingDemo
, una copia escrita de ese valor puede restablecer esto a "0x00 0xUU 0xUU 0xUU" (o cualquier otro byte posible para el relleno) - suponiendo que se data
encuentre en el desplazamiento 0, lo que no está garantizado (agregue #[repr(C)]
si lo desea esa garantía)
En su caso, ptr::write
toma un argumento de tipo PaddingDemo
, y el argumento se pasa a través de una copia escrita. Entonces, en ese punto, los bytes de relleno pueden cambiar arbitrariamente, en particular pueden convertirse en 0xUU.
Sin inicializar usize
Si su código tiene UB, entonces depende de otro factor, es decir, si tener un byte no inicializado en a usize
es UB. La pregunta es, ¿un rango de memoria (parcialmente) no inicializado representa algún número entero? Actualmente, no lo hace y, por lo tanto, hay UB . Sin embargo, si ese debería ser el caso, se debate mucho y parece probable que eventualmente lo permitamos.
Muchos otros detalles aún no están claros, sin embargo - por ejemplo, la transmutación de "0x00 0xUU 0xUU 0xUU" a un entero bien puede resultar en una completamente número entero no inicializado, es decir, números enteros pueden no ser capaces de preservar "inicialización parcial". Para preservar bytes parcialmente inicializados en números enteros, tendríamos que decir básicamente que un número entero no tiene un "valor" abstracto, es solo una secuencia de bytes (posiblemente no inicializados). Esto no refleja cómo se usan los enteros en operaciones como /
. (Algo de esto también depende de las decisiones de LLVM poison
yfreeze
; LLVM podría decidir que al realizar una carga en el tipo entero, el resultado es completo poison
si algún byte de entrada espoison
.) Entonces, incluso si el código no es UB porque permitimos enteros no inicializados, puede que no se comporte como se esperaba porque se están perdiendo los datos que desea transferir.
Si desea transferir bytes sin procesar, sugiero usar un tipo adecuado para eso, como MaybeUninit
. Si usa un tipo entero, el objetivo debe ser transferir valores enteros, es decir, números.