Essa chamada assume_init acionou um comportamento indefinido?
Sim. "Não inicializado" é apenas outro valor que um byte na Rust Abstract Machine pode ter, próximo ao usual 0x00 - 0xFF. Vamos escrever este byte especial como 0xUU. (Consulte esta postagem do blog para obter mais informações sobre esse assunto .) 0xUU é preservado por cópias, assim como qualquer outro valor possível que um byte possa ter é preservado por cópias.
Mas os detalhes são um pouco mais complicados. Existem duas maneiras de copiar dados na memória no Rust. Infelizmente, os detalhes para isso também não são explicitamente especificados pela equipe de idiomas da Rust; portanto, a seguir, é minha interpretação pessoal. Eu acho que o que estou dizendo é incontroverso, a menos que seja indicado de outra forma, mas é claro que isso poderia ser uma impressão errada.
Cópia sem tipo / byte
Em geral, quando um intervalo de bytes está sendo copiado, o intervalo de origem apenas substitui o intervalo de destino - portanto, se o intervalo de origem era "0x00 0xUU 0xUU 0xUU", depois da cópia, o intervalo de destino terá a lista exata de bytes.
É assim que se comporta memcpy
/ memmove
em C (na minha interpretação do padrão, que não está muito claro aqui, infelizmente). No Rust, ptr::copy{,_nonoverlapping}
provavelmente executa uma cópia em bytes, mas na verdade não está especificada com precisão no momento e algumas pessoas podem querer dizer que foi digitada também. Isso foi discutido um pouco nesta edição .
Cópia digitada
A alternativa é uma "cópia digitada", que é o que acontece em todas as atribuições normais ( =
) e ao passar valores para / de uma função. Uma cópia digitada interpreta a memória de origem em algum tipo T
e, em seguida, "re-serializa" esse valor do tipo T
na memória de destino.
A principal diferença para uma cópia em bytes é que as informações que não são relevantes para o tipo T
são perdidas. Essa é basicamente uma maneira complicada de dizer que uma cópia digitada "esquece" o preenchimento e a redefine efetivamente para não inicializada. Comparado a uma cópia não digitada, uma cópia digitada perde mais informações. Cópias sem tipo preservam a representação subjacente, cópias digitadas apenas preservam o valor representado.
Portanto, mesmo quando você transmuta 0usize
para PaddingDemo
, uma cópia digitada desse valor pode redefinir para "0x00 0xUU 0xUU 0xUU" (ou quaisquer outros bytes possíveis para o preenchimento) - supondo- data
se que esteja no deslocamento 0, que não é garantido (adicione #[repr(C)]
se desejar essa garantia).
No seu caso, ptr::write
aceita um argumento do tipo PaddingDemo
e o argumento é passado por uma cópia digitada. Portanto, já nesse ponto, os bytes de preenchimento podem mudar arbitrariamente, em particular eles podem se tornar 0xUU.
Não inicializado usize
Se o seu código possui UB depende de outro fator, a saber, se um byte não inicializado em a usize
é UB. A questão é: um intervalo de memória (parcialmente) não inicializado representa algum número inteiro? Atualmente, não existe e, portanto, existe UB . No entanto, se esse deve ser o caso é muito debatido e parece provável que eventualmente o permitiremos.
Muitos outros detalhes ainda não são claras, embora - por exemplo, transmutando "0x00 0xUU 0xUU 0xUU" para um inteiro pode resultar em um totalmente inteiro não inicializado, ou seja, inteiros pode não ser capaz de preservar a "inicialização parcial". Para preservar bytes parcialmente inicializados em números inteiros, teríamos que dizer basicamente que um número inteiro não tem um "valor" abstrato, é apenas uma sequência de bytes (possivelmente não inicializados). Isso não reflete como os inteiros são usados em operações como /
. (Parte disso também depende das decisões do LLVM poison
efreeze
; o LLVM pode decidir que, ao fazer uma carga no tipo inteiro, o resultado será totalmente poison
se houver algum byte de entrada.poison
.) Portanto, mesmo que o código não seja UB porque permitimos números inteiros não inicializados, ele pode não se comportar conforme o esperado, porque os dados que você deseja transferir estão sendo perdidos.
Se você deseja transferir bytes não processados, sugiro usar um tipo adequado para isso, como MaybeUninit
. Se você usar um tipo inteiro, o objetivo deve ser transferir valores inteiros - ou seja, números.