Hi,
I am currently wrapping an external C library using FFI (generated by bindgen).
This library has two struct
s that I will name c_table
, and c_line
.
c_table
is a linked list with c_line
items.
These types have their respective constructors struct c_table* table_new()
,
and struct c_line* line_new()
. I use wrapper types to store the pointers
returned by the constructors:
#[repr(transparent)]
pub struct Table {
inner: *mut c_table,
}
#[repr(transparent)]
pub struct Line {
inner: *mut c_line,
}
The library provides a function to iterate over the c_line
s in the c_table
:
int table_next(struct* table, struct* gen_iter, struct ** c_line)
which takes a c_table
, a gen_iter
(manages the state between iterations), writes a pointer to the next c_line
and outputs a return code (0
on success, 1
if we are at the end of the list, and a negative number in case of error).
To have a nice rust API, I created a structure TableIter
that implements the Iterator
trait.
pub struct TableIter<'a> {
table: &'a Table,
state: *mut gen_iter,
}
impl<'a> TableIter<'a> {
pub fn new(table: &'a Table) -> TableIter<'a> {
let state = new_gen_iter();
Self { table, state }
}
}
impl<'a> Iterator for TableIter<'a> {
type Item = &'a Line;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let mut line = MaybeUninit<*mut c_line>::uninit();
match table_next(self.table.inner, self.state, line.as_mut_ptr()) {
// next line
0 => {
// We take the address of the `*mut c_line` received from
// `table_next` (`*const *mut c_line`), cast it to a `*const Line`
// (allowed since Line is defined as #[repr(transparent)]), then at
// last turn it into an Option<&'a Line>
line.as_ptr().cast::<Line>().as_ref();
}
// reached end of table
1 => None,
// error
_ => None,
}
}
}
}
So far, so good! However, this problem crops up when I use TableIter
:
// Assuming `table` is initialized with at least two lines
let iter = TableIter::new(table);
let first: &Line = iter.next().unwrap();
// first = 0x7f54abbaca70 first.inner = 0x7f54a4001100
let second: &Line = iter.next().unwrap();
// second = 0x7f54abbaca70 second.inner = 0x7f54a40012b0
You might have noticed the references first
and second
are pointing to the same instance of Line
, however the values of their inner
fields are different.
From what I understand, the uninitialized *mut c_line
constructed by
let mut line = MaybeUninit<*mut c_line>::uninit();
resides at the same memory location between calls to TableIter::next
. So, each call overwrites the value of the previous item returned by TableIter::next
.
I would like first
and second
to point to different instances of Line
.
Therefore, my questions are:
- what/where is the flaw in my code?
- how do I prevent
MaybeUninit
from reusing the same memory address between
calls toTableIter::next
?
Thanks for taking the time to read this (very long?) description, and for your help!