Re: [PATCH v3 06/10] rust: list: add List

Alice Ryhl
2024-08-01T09:41:11Z

On Thu, Aug 1, 2024 at 11:11 AM Benno Lossin <benno.lossin@proton.me> wrote:
>
> On 23.07.24 10:22, Alice Ryhl wrote:
> > + /// Add the provided item to the back of the list.
> > + pub fn push_back(&mut self, item: ListArc<T, ID>) {
> > + let raw_item = ListArc::into_raw(item);
> > + // SAFETY:
> > + // * We just got `raw_item` from a `ListArc`, so it's in an `Arc`.
> > + // * If this requirement is violated, then the previous caller of `prepare_to_insert`
> > + // violated the safety requirement that they can't give up ownership of the `ListArc`
> > + // until they call `post_remove`.
>
> I don't like this negative phrasing, what about "Since we have ownership
> of the `ListArc`, `post_remove` must have been called after each
> previous call to `prepare_to_insert`."?

I think we just need to argue about the most recent call to
prepare_to_insert but ok.

> > + // * We own the `ListArc`.
> > + // * Removing items from this list is always done using `remove_internal_inner`, which
> > + // calls `post_remove` before giving up ownership.
> > + let list_links = unsafe { T::prepare_to_insert(raw_item) };
> > + // SAFETY: We have not yet called `post_remove`, so `list_links` is still valid.
> > + let item = unsafe { ListLinks::fields(list_links) };
> > +
> > + if self.first.is_null() {
> > + self.first = item;
> > + // SAFETY: The caller just gave us ownership of these fields.
> > + // INVARIANT: A linked list with one item should be cyclic.
> > + unsafe {
> > + (*item).next = item;
> > + (*item).prev = item;
> > + }
> > + } else {
> > + let next = self.first;
> > + // SAFETY: By the type invariant, this pointer is valid or null. We just checked that
> > + // it's not null, so it must be valid.
> > + let prev = unsafe { (*next).prev };
> > + // SAFETY: Pointers in a linked list are never dangling, and the caller just gave us
> > + // ownership of the fields on `item`.
> > + // INVARIANT: This correctly inserts `item` between `prev` and `next`.
> > + unsafe {
> > + (*item).next = next;
> > + (*item).prev = prev;
> > + (*prev).next = item;
> > + (*next).prev = item;
> > + }
>
> You have this pattern several times, maybe make a function for this?

It's just two times. I think it's fine.

> > + if !next.is_null() {
> > + // This is really a no-op, but this ensures that `item` is a raw pointer that was
> > + // obtained without going through a pointer->reference->pointer conversion rountrip.
> > + // This ensures that the list is valid under the more restrictive strict provenance
> > + // ruleset.
> > + //
> > + // SAFETY: We just checked that `next` is not null, and it's not dangling by the
> > + // list invariants.
> > + unsafe {
> > + debug_assert_eq!(item, (*next).prev);
> > + item = (*next).prev;
> > + }
>
> How bad do you reckon is this for performance?

I don't think it's a problem at all.

Alice