Solution for exercise 3: Iterators
#![allow(unused)] fn main() { // ... previous code ... struct IteratorHandle { ptr: NonNull<leveldb_iterator_t>, } impl IteratorHandle { fn new(database: &Database, read_options: ReadOptions) -> IteratorHandle { unsafe { let iterator_ptr = leveldb_create_iterator(database.handle.ptr.as_ptr(), read_options.ptr.as_ptr()); leveldb_iter_seek_to_first(iterator_ptr); IteratorHandle { ptr: NonNull::new_unchecked(iterator_ptr), } } } fn next(&self) { unsafe { leveldb_iter_next(self.ptr.as_ptr()) }; } fn valid(&self) -> bool { unsafe { leveldb_iter_valid(self.ptr.as_ptr()) != 0 } } fn value(&self) -> (*const i8, usize) { unsafe { let mut len = 0; let data = leveldb_iter_value(self.ptr.as_ptr(), &mut len); (data, len) } } } impl Drop for IteratorHandle { fn drop(&mut self) { unsafe { leveldb_iter_destroy(self.ptr.as_ptr()) } } } impl Database { // ... previous code ... pub fn iter(&self) -> Iterator<'_> { let read_options = ReadOptions::new(); let handle = IteratorHandle::new(self, read_options); Iterator { handle: handle, start: true, database: self, } } } pub struct Iterator<'iterator> { handle: IteratorHandle, start: bool, #[allow(unused)] database: &'iterator Database, } impl<'iterator> Iterator<'iterator> { fn read_current(&self) -> Option<Box<[u8]>> { unsafe { if !self.handle.valid() { return None; }; let data = self.handle.value(); let slice = std::slice::from_raw_parts(data.0 as *mut u8, data.1); Some(Box::from(slice)) } } } impl<'iterator> std::iter::Iterator for Iterator<'iterator> { type Item = Box<[u8]>; fn next(&mut self) -> Option<Self::Item> { if self.start { self.start = false; self.read_current() } else { self.handle.next(); self.read_current() } } } #[cfg(test)] mod test { // ... previous code ... #[test] fn test_iter() { let tmp = TempDir::new("test_iter").unwrap(); let mut options = Options::new(); options.create_if_missing(true); let database = Database::open(tmp.path().join("database"), options).unwrap(); let key1: &[u8] = b"test1"; let key2: &[u8] = b"test2"; let key3: &[u8] = b"test3"; let value1: &[u8] = b"value1"; let value2: &[u8] = b"value2"; let value3: &[u8] = b"value3"; database.put(key1, value1).unwrap(); database.put(key2, value2).unwrap(); database.put(key3, value3).unwrap(); let mut iter = database.iter(); assert_eq!(iter.next(), Some(Box::from(value1))); assert_eq!(iter.next(), Some(Box::from(value2))); assert_eq!(iter.next(), Some(Box::from(value3))); assert_eq!(iter.next(), None); } } }
Bonus task solution
Lifetime relationships can be expressed without occupying space in application memory by using PhantomData<T>
:
#![allow(unused)] fn main() { use std::marker::PhantomData; pub struct Iterator<'iterator> { handle: IteratorHandle, start: bool, phantom: PhantomData<&'iterator Database>, } // usage: Iterator { handle: handle, start: true, phantom: PhantomData, } }