< 返回版块

elderhammer 发表于 2023-09-12 10:49

Tags:Unsafe, Box, raw pointer, NonNull

在《Learning Rust With Entirely Too Many Linked Lists》一文中,作者定义了如下链表结构:

pub struct List<T> {
    head: Link<T>,
    tail: *mut Node<T>,
}

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

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

作者认为这个结构不好,混用 Box 和 raw pointer 容易导致 UB。作者认为 Box 是安全指针,引入了一些 raw pointer 根本不会遵守的约束。作者思路是全部用 raw pointer。

我对作者思路的理解是: 1.通过解引用 Box 来修改变量是 safe 的,但是通过解引用 raw pointer 是 unsafe 的。所以当一个变量有两个指针 raw pointer 和 Box 的时候,容易通过解引用 Box (这是 safe 的)直接把变量修改了,而忘记了还有 raw pointer 这个 unsafe 的声明了; 2.如果全部用 raw pointer 或者用 NonNull 来替换 Box,那么不管何时修改变量都要限制在 unsafe 中,开发者就会注意变量的读写逻辑(例如遵守 reborrow 规则),避免 UB;

请教下大家的理解,如果能给出例子最好了,谢谢。:-)


Ext Link: https://rust-unofficial.github.io/too-many-lists/fifth-stacked-borrows.html

评论区

写评论
viruscamp 2023-09-19 23:30

基本思路应该是对的。
作者的实践方案是:

  1. 写比较完善的测试
  2. 让 cargo miri test 能通过

我在写完 unsafe_queue 后,改写 Link<T>Box<Node<T>>NonNull<Node<T>>
两种数据结构设定,有五个操作不同,但可以抽象出一个 trait 来屏蔽差异

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

type Link<T> = Option<OwnerLink<T>>;

type WeakLink<T> = Option<NonNull<Node<T>>>;

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

trait OwnerLinkExt<T> where Self: Sized {
    fn create(node: Node<T>) -> Self;
    fn into_node(self) -> Node<T>;
    fn ref_node(&self) -> &Node<T>;
    fn mut_node(&mut self) -> &mut Node<T>;
    fn as_weak_link(&self) -> WeakLink<T>;
}

#[cfg(feature = "box-link")]
type OwnerLink<T> = Box<Node<T>>;
#[cfg(feature = "box-link")]
impl<T> OwnerLinkExt<T> for Box<Node<T>> {
    #[inline(always)]
    fn create(node: Node<T>) -> Self {
        Box::new(node)
    }

    #[inline(always)]
    fn into_node(self) -> Node<T> {
        *self
    }

    #[inline(always)]
    fn ref_node(&self) -> &Node<T> {
        self.as_ref()
    }

    #[inline(always)]
    fn mut_node(&mut self) -> &mut Node<T> {
        self.as_mut()
    }

    #[inline(always)]
    fn as_weak_link(&self) -> WeakLink<T> {
        Some(unsafe {
            NonNull::new_unchecked(self.as_ref() as *const _ as *mut _)
        })
    }
}

#[cfg(not(feature = "box-link"))]
type OwnerLink<T> = NonNull<Node<T>>;
#[cfg(not(feature = "box-link"))]
impl<T> OwnerLinkExt<T> for NonNull<Node<T>> {
    #[inline(always)]
    fn create(node: Node<T>) -> Self {
        let b = Box::new(node);
        unsafe { NonNull::new_unchecked(Box::into_raw(b)) }
    }

    #[inline(always)]
    fn into_node(self) -> Node<T> {
        let b = unsafe { Box::from_raw(self.as_ptr()) };
        *b
    }

    #[inline(always)]
    fn ref_node(&self) -> &Node<T> {
        unsafe { self.as_ref() }
    }

    #[inline(always)]
    fn mut_node(&mut self) -> &mut Node<T> {
        unsafe { self.as_mut() }
    }
    
    #[inline(always)]
    fn as_weak_link(&self) -> WeakLink<T> {
        Some(*self)
    }
}
作者 elderhammer 2023-09-15 16:38

谢谢大佬指路,之前没怎么读过 reference,里面内容还挺多的。:-)

--
👇
Bai-Jinlin: 指的应该是编译器noalias这方面东西 https://doc.rust-lang.org/reference/behavior-considered-undefined.html

Box本身是noalias,不允许其他引用访问或者修改Box指向的内存,你给的代码假如head和tail引用了一个Node就违反了这个保证,在rust里就是ub。

Bai-Jinlin 2023-09-13 10:52

指的应该是编译器noalias这方面东西 https://doc.rust-lang.org/reference/behavior-considered-undefined.html

Box本身是noalias,不允许其他引用访问或者修改Box指向的内存,你给的代码假如head和tail引用了一个Node就违反了这个保证,在rust里就是ub。

1 共 3 条评论, 1 页