有没有办法使不可变的引用可变?

时间:2019-01-17 14:03:23

标签: data-structures rust mutability interior-mutability

我想用Rust(Remove Nth Node From End of List)解决leetcode问题。我的解决方案使用两个指针来找到要删除的Node

#[derive(PartialEq, Eq, Debug)]
pub struct ListNode {
    pub val: i32,
    pub next: Option<Box<ListNode>>,
}

impl ListNode {
    #[inline]
    fn new(val: i32) -> Self {
        ListNode { next: None, val }
    }
}

// two-pointer sliding window
impl Solution {
    pub fn remove_nth_from_end(head: Option<Box<ListNode>>, n: i32) -> Option<Box<ListNode>> {
        let mut dummy_head = Some(Box::new(ListNode { val: 0, next: head }));
        let mut start = dummy_head.as_ref();
        let mut end = dummy_head.as_ref();
        for _ in 0..n {
            end = end.unwrap().next.as_ref();
        }
        while end.as_ref().unwrap().next.is_some() {
            end = end.unwrap().next.as_ref();
            start = start.unwrap().next.as_ref();
        }
        // TODO: fix the borrow problem
        // ERROR!
        // start.unwrap().next = start.unwrap().next.unwrap().next.take();
        dummy_head.unwrap().next
    }
}

我借用了链表的两个不可变的引用。找到要删除的目标节点后,我要删除其中一个并使另一个可变。以下每个代码示例均会导致编译器错误:

// ERROR
drop(end); 
let next = start.as_mut().unwrap.next.take();

// ERROR
let mut node = *start.unwrap()

我不知道这种解决方案是否可以用Rust编写。如果我可以使不可变的引用变得可变,该怎么办?如果不是,在使借阅检查器感到满意的同时还能实现相同的逻辑吗?

2 个答案:

答案 0 :(得分:6)

  

有没有办法使不可变的引用可变?

否。

您可以编写不安全的Rust代码来强制类型对齐,但​​是该代码实际上是不安全的,并导致未定义的行为。你不要这个。


有关您的特定问题,请参阅:

答案 1 :(得分:-1)

正确的答案是您不应该这样做。这是未定义的行为,并且打破了编译器在编译程序时所做的许多假设。

但是,可以执行此操作。其他人也提到了为什么这不是一个好主意,但实际上他们并未显示执行此操作所需的代码。即使您不应该不这样做,它也是这样的:

unsafe fn very_bad_function<T>(reference: &T) -> &mut T {
    let const_ptr = reference as *const T;
    let mut_ptr = const_ptr as *mut T;
    &mut *mut_ptr
}

本质上,您将常量指针转换为可变指针,然后将可变指针转换为引用。

这是一个非常不安全且不可预测的例子:

fn main() {
    static THIS_IS_IMMUTABLE: i32 = 0;
    unsafe {
        let mut bad_reference = very_bad_function(&THIS_IS_IMMUTABLE);
        *bad_reference = 5;
    }
}

如果运行此命令,则会出现段错误。发生了什么?本质上,您通过尝试写入已标记为不可变的内存区域来使内存规则无效。本质上,当您使用这样的函数时,会破坏编译器对您的信任,以免弄乱常量内存。

这就是为什么您永远不要使用它的原因,尤其是在公共API中,因为如果有人传递了对您的函数的无辜不可变引用,并且您的函数对其进行了突变,并且引用是针对某个区域的,内存不适合写入,则会出现段错误。

简而言之:不要试图欺骗借阅检查器。在那里是有原因的。

编辑:除了我刚才提到的这是未定义行为的原因外,另一个原因是违反了引用别名规则。也就是说,由于您可以同时拥有对变量的可变引用和不可变引用,因此,当您将变量分别传递给同一函数时,就会带来很多问题,其中假定不可变引用和可变引用是唯一的。有关详细信息,请阅读Rust文档中的this page