Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Lender type for easily iterating over List builders #25

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion recapn/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{Error, Family, IntoFamily, Result};

use core::convert::TryFrom;
use core::marker::PhantomData;
use core::ops::Range;
use core::ops::{Bound, Range, RangeBounds};

pub use crate::ptr::{ElementCount, ElementSize};

Expand Down Expand Up @@ -302,6 +302,75 @@ impl<'a, V, T: Table> Builder<'a, V, T> {
{
V::get(self, index)
}

/// Create a lending iterator for the whole list.
#[inline]
pub fn into_lender(self) -> Lender<Self> {
self.into_lender_range(..)
}

/// Create a lending iterator for a specified range in the list.
#[inline]
pub fn into_lender_range<R: RangeBounds<u32>>(self, range: R) -> Lender<Self> {
let len = self.len().get();
let start = match range.start_bound() {
Bound::Unbounded => 0,
Bound::Included(i) => *i,
Bound::Excluded(i) => (*i)
.min(ElementCount::MAX_VALUE)
.saturating_add(1),
}.min(len);
let end = match range.end_bound() {
Bound::Unbounded => len,
Bound::Included(i) => (*i)
.min(ElementCount::MAX_VALUE)
.saturating_add(1),
Bound::Excluded(i) => *i,
}.min(len);
Lender { range: start..end, list: self }
}
}

/// A lending iterator for list builders.
pub struct Lender<L> {
range: Range<u32>,
list: L,
}

impl<L> Lender<L> {
/// Zip this lender with a normal `Iterator`
pub fn zip<I: IntoIterator>(self, iter: I) -> ZippedLender<L, I::IntoIter> {
ZippedLender { lender: self, iter: iter.into_iter() }
}
}

impl<'a, V, T: Table> Lender<Builder<'a, V, T>> {
pub fn next<'b>(&'b mut self) -> Option<ElementBuilder<'a, 'b, V, T>>
where
V: ListAccessable<&'b mut Builder<'a, V, T>>
{
let idx = self.range.next()?;
let list_item = self.list.at(idx);
Some(list_item)
}
}

/// A lender with an iterator. Useful for when you need to copy from a Rust type to a
/// Cap'n Proto list.
pub struct ZippedLender<L, I> {
lender: Lender<L>,
iter: I,
}

impl<'a, V, T: Table, I: Iterator> ZippedLender<Builder<'a, V, T>, I> {
pub fn next<'b>(&'b mut self) -> Option<(ElementBuilder<'a, 'b, V, T>, I::Item)>
where
V: ListAccessable<&'b mut Builder<'a, V, T>>
{
let list_item = self.lender.next()?;
let iter_item = self.iter.next()?;
Some((list_item, iter_item))
}
}

/// An element in a list reader
Expand Down
2 changes: 2 additions & 0 deletions tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub mod build_gen {
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
}

pub mod list;

use gen::capnp_test_capnp::{TestAllTypes, TestEnum};
use recapn::message::Message;
use recapn::text;
Expand Down
58 changes: 58 additions & 0 deletions tests/src/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::gen::capnp_test_capnp::{TestAllTypes, TestEnum};
use recapn::message::Message;
use recapn::ty::AsReader;

#[test]
fn test_lender() {
// Lender primarily exists for copying from native types to Cap'n Proto types. Lists are
// especially painful to deal with because it requires using enumerator() and that sucks.
// Lender gives a nicer API that, while it doesn't support `for` syntax, does support
// `while let` which is the next best thing.

struct Data {
value: i8,
big_value: u64,
enum_value: TestEnum,
}

let some_data = vec![
Data { value: 5, big_value: 0, enum_value: TestEnum::Qux },
Data { value: -1, big_value: 0xFFFFFFFFFFFFF, enum_value: TestEnum::Bar },
];

let mut message = Message::global();
let mut builder = message.builder().init_struct_root::<TestAllTypes>();
let mut list = builder.struct_list()
.init(some_data.len() as u32)
.into_lender()
.zip(&some_data);
while let Some((builder, data)) = list.next() {
let mut e = builder.get();
e.int8_field().set(data.value);
e.u_int64_field().set(data.big_value);
e.enum_field().set(data.enum_value);
}

let list = builder.as_reader().struct_list().get();
assert_eq!(list.len(), some_data.len() as u32);

for (e, data) in list.into_iter().zip(&some_data) {
assert_eq!(e.int8_field(), data.value);
assert_eq!(e.u_int64_field(), data.big_value);
assert_eq!(e.enum_field().unwrap(), data.enum_value);
}

// Check to make sure other types of ranges work.
let mut list = builder.bool_list().init(6).into_lender_range(2..=3);
while let Some(mut b) = list.next() {
b.set(true);
}

let list = builder.as_reader().bool_list().get();
assert_eq!(list.len(), 6);
assert!(list.into_iter().eq([false, false, true, true, false, false].into_iter()));

// Going into ranges out of bounds yields no elements.
let mut list = builder.u_int8_list().init(3).into_lender_range(4..);
assert!(list.next().is_none());
}