使用强制转换安全的可变访问器吗?

Lii*_*Lii 5 unsafe rust

我试图了解getter 类型函数&和重复代码的问题&mut。我试图了解在unsafe块内使用强制转换来解决此问题的特定解决方案是否安全。

以下是该问题的一个示例。它取自非常好的教程Learning Rust With Entirely Too Many Linked Lists

type Link<T> = Option<Box<Node<T>>>;

pub struct List<T> {
    head: Link<T>,
}

struct Node<T> {
    elem: T,
    next: Link<T>,
}

impl<T> List<T> {
    // Other methods left out...

    // The implementation of peek is simple, but still long enough
    // that you'd like to avoid duplicating it if that is possible.
    // Some other getter-type functions could be much more complex
    // so that you'd want to avoid duplication even more.
    pub fn peek(&self) -> Option<&T> {
        self.head.as_ref().map(|node| {
            &node.elem
        })
    }

    // Exact duplicate of `peek`, except for the types
    pub fn peek_mut(&mut self) -> Option<&mut T> {
        self.head.as_mut().map(|node| {
            &mut node.elem
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

解决方案

在我看来,您可以在unsafe块中使用强制转换来解决此问题。该解决方案似乎具有以下属性:

  • 它可以以安全的方式完成。
  • 它为不安全的实现提供了一个安全的接口。
  • 实现很简单。
  • 它消除了代码重复。

以下是解决方案:

// Implemention of peek_mut by casting return value of `peek()`
pub fn peek_mut(&mut self) -> Option<&mut T> {
    unsafe {
        std::mem::transmute(self.peek())
    }
}
Run Code Online (Sandbox Code Playgroud)

这些是我认为它看起来安全的理由:

  1. The return value of peek() is from a known source with a known alias situation.
  2. Since the parameter type is &mut self there are no refs to its elements.
  3. Hence the return value of peek() in unaliased.
  4. The return value of peek() does not escape this function body.
  5. Casting an unaliased & to &mut doesn't seem to violate the pointer aliasing rules.
  6. The lifetimes of the target and source of the cast match each other.

Misc notes

  • The same problem is discussed the following question: How to avoid writing duplicate accessor functions for mutable and immutable references in Rust?

    This question is different because it asks about details of one specific solution to the problem.

  • There are other kinds of solutions to the problem, such as this one: Abstracting over mutability in Rust

    But all other solutions seem to introduce quite a bit of extra complexity to the code.

  • The Nomicon asserts strongly that it is always undefined behaviour to cast & to &mut, which implies that this solution is not safe. But it makes no attempt whatsoever to explain why.

  • The Rust Reference states that "Breaking the pointer aliasing rules" is UB. To me it seems like this solution doesn't do that, for reasons given above.


Questions

I have the following questions to people with deeper Rust knowledge than myself:

  • Is the provided implementation of peek_mut safe?
  • Are there other necessary arguments that are needed to establish that it is safe, that I have missed?
  • If it is indeed not safe, why is that? Can you give a detailed explanation?
  • Is there in that case a similar solution to the problem that is safe?

Sve*_*ach 6

我相信这段代码会调用未定义的行为。引用Nomicon

  • 将 & 转换为 &mut 是 UB
    • 将 & 转换为 &mut 始终是 UB
    • 不,你不能这样做
    • 不,你并不特别

更重要的是,Rust 编译器会将peek()LLVM 中间表示中的返回值标记为不可变,并且 LLVM 可以根据此断言自由进行优化。在这种特定情况下目前可能不会发生,但我仍然认为这是未定义的行为。如果你想不惜一切代价避免重复,你可以使用宏。

  • @Lii 话虽如此,这是未定义的行为“因为 Rust 是这么说的”。它不是 UB,因为编译器必然会做一些神奇的事情,但它“允许”编译器基于所有格式良好的程序都没有 UB 的假设进行优化。UB 的分类非常广泛,因为它限制了格式良好的程序的数量,并使编译器更容易对其进行优化,但这并不一定意味着每种未定义行为的情况背后都有一些编译器优化。这只是意味着编译器可以自由地这样做。 (3认同)