我想用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编写。如果我可以使不可变的引用变得可变,该怎么办?如果不是,在使借阅检查器感到满意的同时还能实现相同的逻辑吗?
答案 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。