6

我想解决 Rust 中的一个 leetcode 问题(Remove Nth Node From End of List)。我的解决方案使用两个指针来查找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 编写。如果我可以使不可变引用可变,我该怎么做?如果没有,是否有在使借用检查器满意的同时实现相同的逻辑?

4

2 回答 2

9

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

不。

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


对于您的具体问题,请参阅:

于 2019-01-17T14:51:15.780 回答
7

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

但是,可以这样做。其他人也提到了为什么这不是一个好主意,但他们实际上并没有展示做这样的事情的代码是什么样的。即使您不应该这样做,它也会是这样的:

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 文档中的此页面以获取有关此内容的更多信息。

于 2019-01-17T18:23:02.363 回答