Help. AI has out smarted me!

I know questions about AI generated code are frowned upon here but this question is not really about the AI.

I was busy building a doubly linked list that adhered to the rules of Crust Introducing Crust. Like C/C++ but C/Rust. Well OK, I my new AI friend to do it for me... All was going well, the code looked good, like a linked list in C. The tests all passed.

The I thought it would be nice to iterate over the list with for..in... Well, I thought that might be impossible because the Iterator trait uses references which breaks the rules of Crust. Or at least too complicate be reasonable. But once again my AI friend managed it, it does not look so complex, and it works!

BUT.. The AI has introduced a _marker: PhantomData<&'a T>, field into the Iter struct and sprinkled a bunch of `'around the place.

pub struct Iter<'a, T> {
    current: *mut Node<T>,
    _marker: PhantomData<&'a T>,
}

All of a sudden I don't understand what the AI has generated!

As far as vaguely understand what it is doing is taking care of the fact that the current field is a raw pointer which has no lifetime information associated with it. So a marker filed is introduced which is a reference with a lifetime. And that lifetime can be checked in the Iterator. Quite how all this changes together is a bit murky for me.

When I ask my AI friend how it works it goes on about "Variance" and "Drop Check" which is not helping.

Anyone have simple explanation of this PhantomData thing?

The PhantomData an lifetime parameter is only used to restrict the scope you can use the Iter in, which is obviously unnecessary for Crust. You can just remove it.

Your Iter can simply return the raw pointers by value the same way the Range iterator returns integers by value.

PhantomData specifically is used to tell the compiler about the variance of your struct aka. If you have a Vec<Iter<'a>> it allows (should) you to place an item with a longer lifetime in it. If you had used a PhantomData<&mut 'a> that is not allowed.

Nomicon

1 Like

did you read the documentation on PhantomData? it explains the important concept and usage quite well, so I recommend you read through it first. one of the example is a slice type, which is very similar to the list iterator of your example.

2 Likes

Ah ha. Quite so. I don't know why I thought implementing Iterator required using references. It does not. So I removed Phantom data and the the pesky lifetimes like so:

impl<T> Iterator for Iter<T> {
    type Item = *mut T;

    fn next(&mut self) -> Option<Self::Item> {
        unsafe {
            if self.current.is_null() {
                None
            } else {
                // Get the current element value
                let element = &mut (*self.current).element;

                // Move to the next node
                self.current = (*self.current).next;

                // Return the current element
                let ptr = &mut (*element) as *mut T;
                Some(ptr)
            }
        }
    }
}

Which works when tested like so:

            assert_eq!(*iter.next().unwrap(), 0);
            assert_eq!(*iter.next().unwrap(), 1);
            assert_eq!(*iter.next().unwrap(), 2);
            assert_eq!(iter.next(), None);

            for (index, element) in list_ptr.iter().enumerate() {
                *element = *element + 1;
            }

            for (index, element) in list_ptr.iter().enumerate() {
                assert_eq!(*element, (index + 1) as i32);
            }

Thanks. That is much more nice and Crusty.

I took a good look. And it kind of made sense to me.

Problem is there is a big gap between that and knowing how to use it properly. Like the gap between knowing what addition is and knowing how to use addition to arrive at integral calculus.

You also do not need the &mut for field access , we now have &raw mut to directly create a raw pointer

2 Likes

You mean I can use this:

    let list_ptr = &raw mut list;

instead of this:

    let list_ptr = &mut list as *mut LinkedList<i32>;

Neat. Thanks.