Rust lang 中是否使用中间变量有什么区别?

问题描述 投票:0回答:1

我正在尝试用 rust lang 编写一个双向链表。主要两种类型定义为:

struct IntList {
    head: *mut Node,
    tail: *mut Node,
    size: usize,
}

#[derive(Copy)]
#[derive(Clone)]
struct Node {
    data: i32,
    prev: *mut Node,
    next: *mut Node,
}

我使用了 2 个 util 函数:

pub unsafe fn raw_into_box<T>(raw: *mut T) -> Box<T> {
    mem::transmute(raw)
}

pub fn box_into_raw<T>(b: Box<T>) -> *mut T {
    unsafe { mem::transmute(b) }
}

而且,我想为列表类型创建一个插入方法:

impl IntList {
    fn new() -> Self {
        IntList {
            head: ptr::null_mut(),
            tail: ptr::null_mut(),
            size: 0,
        }
    }

    unsafe fn add_last(&mut self, v: i32) {
        let node = Node { data: v, prev: self.tail, next: ptr::null_mut() };
        let raw = box_into_raw(Box::new(node));
        let node = *raw;
        match self.size {
            0 => {
                self.head = raw;
                self.tail = raw;
            }
            _ => unsafe {
                // let mut last = *self.tail; // Notice here
                // last.next = raw;
                // not work
                self.tail = raw;
            }
        }
        self.size += 1;
        println!("added:{}", node);
    }
// ...

看哪,注释的行没有按预期工作。列表中的元素只有一种大小。然而,我改变了它们,就像这样

(*self.tail).next = raw;

结果将是正确的。


所以我的问题是,二分法有什么区别:

(*self.tail).next = raw;

let mut last = *self.tail;
last.next = raw;

前者有效,后者无效。为什么我不能使用中间对象?

rust
1个回答
0
投票

*self.tail
是一个位置表达式(它代表self.tail指向的
内存位置
);但是,表达式可以在不同的上下文中求值:

  • (*self.tail).next = raw;
    中,
    *self.tail
    是字段表达式的左操作数,因此位于位置表达式上下文中;然而

  • let mut last = *self.tail;
    中,
    *self.tail
    let
    语句的初始化器,因此位于值表达式上下文中。

作为 Rust 参考文档移动和复制类型

当在值表达式上下文中计算位置表达式时,或者由模式中的值绑定时,它表示该内存位置中保存的值。如果该值的类型实现了 Copy

,那么该值将被复制。 [...]

在你的例子中,值的类型是
Node

,它确实实现了

Copy
,所以
let mut last = *self.tail;
Node*self.tail
复制到堆栈变量
last
。然后,您对该副本进行变异,当 
last
 超出范围时,该副本将被删除; 
*self.tail
中的内存保持不变。
您可以通过创建对地点表达式的引用来
借用

地点表达式,这将实现您的预期:

let last = &mut *self.tail;

如果您考虑每种情况下
last
的类型,差异会更加明显。在您最初的尝试中,

last

Node
类型,而在上面它是
&mut Node
类型。
总而言之,实际上根本没有必要使用原始指针和 

unsafe
代码(我会

强烈
鼓励您不要这样做)。例如,您可以按照以下方式执行操作: use std::{cell::Cell, rc::Rc}; struct IntList { head: Option<Rc<Node>>, tail: Option<Rc<Node>>, size: usize, } #[derive(Clone)] struct Node { data: Cell<i32>, prev: Option<Rc<Node>>, next: Option<Rc<Node>>, } impl IntList { fn new() -> Self { IntList { head: None, tail: None, size: 0, } } fn add_last(&mut self, v: i32) { let prev = self.tail.take(); let node = self.tail.insert(Rc::new(Node { data: Cell::new(v), prev, next: None })); // if self.size == 0 { // self.head = Some(node.clone()); // } self.size += 1; println!("added:{node}"); } }

但是,当标准库为您提供
std::collections::LinkedList

(“具有自有节点的双向链表”)时,为什么要重新发明轮子。但请注意:


注意:使用 

Vec

VecDeque 几乎总是更好,因为基于数组的容器通常速度更快,内存效率更高,并且可以更好地利用 CPU 缓存。

    

© www.soinside.com 2019 - 2024. All rights reserved.