Olá, sei que o código poderia ser totalmente escrito sem nenhum código inseguro, mas estou pesquisando e aprendendo como as coisas funcionam "nos bastidores".
Voltando ao assunto , escrevi um código Rust inseguro que, na minha opinião, deve funcionar sem problemas.
Esta é a definição:
pub struct Container {
inner: Pin<Box<String>>,
half_a: *const str,
half_b: *const str,
}
impl Container {
const SEPARATOR: char = '-';
pub fn new(input: impl AsRef<str>) -> Option<Self> {
let input = input.as_ref();
if input.is_empty() {
return None
}
// Making sure the value is never moved in the memory
let inner = Box::pin(input.to_string());
let separator_index = inner.find(Container::SEPARATOR)?;
let inner_ref = &**inner;
let half_a = &inner_ref[0..separator_index];
let half_b = &inner_ref[separator_index+1..];
// Check if the structure definition is met (populated values + only one separator)
if half_a.is_empty() || half_b.is_empty() || half_b.contains(Container::SEPARATOR) {
return None;
}
Some(Self {
half_a: half_a as *const str,
half_b: half_b as *const str,
inner,
})
}
pub fn get_half_a(&self) -> &str {
unsafe {
&*self.half_a
}
}
pub fn get_half_b(&self) -> &str {
unsafe {
&*self.half_b
}
}
}
Em resumo, ele aceita qualquer entrada que possa ser representada como uma referência str, cria um clone fixado da entrada no heap, obtém endereços que apontam para ambas as metades desse valor e retorna isso como uma estrutura.
Agora, quando faço testes:
let valid = Container::new("first-second").unwrap();
assert_eq!(valid.get_half_a(), "first");
assert_eq!(valid.get_half_b(), "second");
Deve funcionar sem pânico e é isso que acontece no Windows. Ele compila e roda sem problemas várias vezes, mas quando é executado no Ubuntu recebo um erro mostrando que os endereços não apontam mais para um local válido na memória:
thread 'tests::types::container' panicked at 'assertion failed: `(left == right)`
left: `"�K\u{13}϶"`,
right: `"first"`', research/src/tests/types.rs:77:5
Qual poderia ser o problema aqui? Perdi algo?
Estou executando este código como ação do GitHub com o seguinte sinalizador runs-on: ubuntu-latest
.
Aqui está um URL para o playground mostrando que este código é executado sem problemas: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d36b19de4d0fa05340191f5107029d75
Não esperava problemas ao executar este código em um sistema operacional diferente.
Mudar
Box<String>
paraBox<str>
, o que não deve afetar a solidez, aciona o MIRI.Isso vem de
Box
, que não pode ter alias. Embora normalmente seja bom derivar ponteiros deBox
, quando você move oBox
(retornandoContainer
), Rust não sabe mais que oBox
teve ponteiros derivados dele e assume que os acessos através dos ponteiros são inválidos devido ao alias.É por isso que o MIRI é acionado. No entanto, não tenho certeza do que torna esse comportamento indefinido. Os resultados do seu teste sugerem que sim, mas não podem dizer por quê. Meu palpite é que Rust decide que
inner
pode ser descartado assim quenew
retornar, já que é garantido que seja único. Pode até otimizar a alocação para nunca gravar nenhum dado (o ponteiro, o comprimento e a capacidade daString
sua versão), uma vez que esses dados nunca são lidos, o que explicaria o seu erro de tempo de execução.Você pode corrigir isso armazenando apenas ponteiros e implementando
Drop
. (Parque infantil)Eu não acho que
Pin
faça nada pela solidez aqui.Pin
é mais usado para lidar com interfaces públicas. Contanto que você não distribua nenhuma&mut
referência ainner
, não há nada contra o que se proteger. Embora você possa querer isso para garantias internas, suas garantias reais são mais fortes do quePin
porque você não pode usar o valor de forma alguma.