Является ли mem :: forget (mem :: uninitialized()) определенным поведением?

В мутагене, я инъекционный различные мутации в коде. Одна вещь, которую я хотел бы мутировать, - это шаблон, if let Ok(x) = y {.. }. Однако это представляет собой сложную задачу, так как я не могу знать тип y - пользователь мог бы создать собственное перечисление с унарным вариантом Ok. Я все еще могу оппортунистически мутировать его для случаев, когда у нас действительно есть Result, тип ошибки которого реализует значение по Default используя свойство, которое выглядит следующим упрощенным:

#![feature(specialization)]

pub trait Errorer {
    fn err(self, mutate: bool) -> Self;
}

impl<X> Errorer for X {
    default fn err(self, _mutate: bool) -> Self {
        self
    }
}

impl<T, E> Errorer for Result<T, E>
where
    E: Default,
{
    fn err(self, mutate: bool) -> Self {
        if mutate {
            Err(Default::default())
        } else {
            self
        }
    }
}

Увы, не так много ошибок, которые реализуют Default, поэтому это не слишком полезно. Даже реализация для Result<T, Box<Error>> даст нам больше шансов для доллара (и будет вполне возможно). Однако, учитывая, что мне все равно, что код действительно проверяет ошибку, мне интересно, могу ли я сделать общую реализацию, расширив мутацию вышеуказанного кода до

match Errorer::err(y, mutation) {
    Ok(x) => { .. }
    Err(x) => { mem::forget(x); }
}

и иметь err возвращать Err(mem::uninitialized()) при мутировании - так ли это поведение безопасно? Примечание. Я возвращаю Err(mem::uninitialized()) из метода, только для mem::forget его позже. Я не вижу, чтобы это могло паниковать, поэтому мы должны предположить, что ценность будет действительно забыта.

Является ли это определенным поведением или я должен ожидать носовых демонов?

Ответ 1

Нет, это не определено поведение, по крайней мере, не для всех типов. (Я не могу сказать, как ваш код будет вызываться как часть мутации, поэтому я не знаю, есть ли у вас контроль над impl типами здесь, но общий impl делает его похожим на то, что вы этого не делаете.) Это продемонстрировано следующий фрагмент кода:

#![feature(never_type)]
use std::mem;

fn main() {
    unsafe { mem::forget(mem::uninitialized::<!>()) }
}

Если вы запустите это на игровой площадке, вы увидите, что программа умирает с помощью SIGILL. Выход ASM показывает, что LLVM просто оптимизировал всю программу до немедленного SIGILL из-за того, как он использует значение нежилого типа ! :

playground::main:
    ud2

Вообще говоря, почти невозможно правильно использовать mem::uninitialized в общем коде, см., Например, эту проблему rc::Weak. По этой причине эта функция находится в процессе устаревания и замены. Но это вам не поможет; то, что вы хотите сделать, просто незаконно для Result<T, !>.