mem::forget(mem::uninitialized())是否是定义良好的行为?

13
mutagen 中,我正在对代码进行各种变异。其中一个我想要变异的模式是 if let Ok(x) = y { .. }。然而,这带来了相当大的挑战,因为我无法知道 y 的类型 - 用户可能已经构建了自己的枚举,其中包含一个一元的 Ok 变体。对于实际上具有实现 Default 的错误类型的 Result 的情况,我仍然可以机会主义地进行变异,使用以下简化的 trait:
#![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。我看不到任何可能导致崩溃的方式,因此我们应该假设该值确实被遗忘了。

这是定义良好的行为还是应该预期出现问题?


5
脑海里浮现的一些想法:Rust可以自由地操纵枚举类型的表示方式,这取决于其变体的有效载荷的表示方式(例如Option<Box<T>>Box<T>的大小相同的技巧)。我不知道有没有影响Result的任何布局优化,但我也看不到为什么不能在未来加入。如果是这种情况,mem::uninitialized可能会使E值处于无效状态,从而改变对Result的解释。 - DK.
是的,我也想到了。因此,在将无效状态放入枚举中时,不存在定义行为的保证。 - llogiq
目前还不确定,但 Result<T, !> 可以(并且将来可能)与 T 具有相同的布局。 - Stefan
1个回答

10

不,这并非是定义良好的行为,至少对于所有类型都不是。(我无法确定您的代码是否作为变异的一部分被调用,因此我不知道您在这里是否有控制类型的权限,但通用的impl看起来您没有。)以下代码证明了这一点:

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

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

如果你在playground上运行此程序,你会看到程序因为使用了一个无法被占用的类型!的值而立即死亡,并且ASM输出显示LLVM优化了整个程序,导致程序出现SIGILL错误。
playground::main:
    ud2

一般来说,在通用代码中正确使用mem::uninitialized几乎是不可能的,例如请参阅rc::Weak这个问题。因此,该函数正在被弃用和替换。但这对你并没有帮助;对于Result<T, !>来说,你想做的事情是绝对非法的。请注意保留HTML标签。

4
LLVM 刚刚优化了整个程序,导致立即出现 SIGILL 错误 —— 这才是高效的代码生成。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接