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.
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.
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.
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);
}
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.