diff --git a/Cargo.lock b/Cargo.lock index 8432ed4..722e23d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,7 +41,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.53", + "syn", "which", ] @@ -125,35 +125,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "ext-trait" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" -dependencies = [ - "ext-trait-proc_macros", -] - -[[package]] -name = "ext-trait-proc_macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "extension-traits" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" -dependencies = [ - "ext-trait", -] - [[package]] name = "glob" version = "0.3.1" @@ -205,31 +176,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "lending-iterator" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc07588c853b50689205fb5c00498aa681d89828e0ce8cbd965ebc7a5d8ae260" -dependencies = [ - "extension-traits", - "lending-iterator-proc_macros", - "macro_rules_attribute", - "never-say-never", - "nougat", - "polonius-the-crab", -] - -[[package]] -name = "lending-iterator-proc_macros" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5445dd1c0deb1e97b8a16561d17fc686ca83e8411128fb036e9668a72d51b1d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "libc" version = "0.2.153" @@ -258,22 +204,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "macro_rules_attribute" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf0c9b980bf4f3a37fd7b1c066941dd1b1d0152ce6ee6e8fe8c49b9f6810d862" -dependencies = [ - "macro_rules_attribute-proc_macro", - "paste", -] - -[[package]] -name = "macro_rules_attribute-proc_macro" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d" - [[package]] name = "matchers" version = "0.1.0" @@ -295,12 +225,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "never-say-never" -version = "6.6.666" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5a574dadd7941adeaa71823ecba5e28331b8313fb2e1c6a5c7e5981ea53ad6" - [[package]] name = "nom" version = "7.1.3" @@ -311,27 +235,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nougat" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b57b9ced431322f054fc673f1d3c7fa52d80efd9df74ad2fc759f044742510" -dependencies = [ - "macro_rules_attribute", - "nougat-proc_macros", -] - -[[package]] -name = "nougat-proc_macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84f77a45e99a2f9b492695d99e1c23844619caa5f3e57647cffacad773ca257" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -348,12 +251,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - [[package]] name = "pin-project-lite" version = "0.2.13" @@ -366,12 +263,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "polonius-the-crab" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a69ee997a6282f8462abf1e0d8c38c965e968799e912b3bed8c9e8a28c2f9f" - [[package]] name = "prettyplease" version = "0.2.16" @@ -379,7 +270,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.53", + "syn", ] [[package]] @@ -396,7 +287,6 @@ name = "psrdada" version = "0.4.0" dependencies = [ "env_logger 0.9.3", - "lending-iterator", "nom", "page_size", "psrdada-sys", @@ -501,17 +391,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.53" @@ -551,7 +430,7 @@ checksum = "c8f546451eaa38373f549093fe9fd05e7d2bade739e2ddf834b9968621d60107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn", ] [[package]] @@ -583,7 +462,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 98d3f08..dd3da9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ categories = ["encoding", "parser-implementations"] psrdada-sys = { path = "./psrdada-sys", version = "0.4.0" } page_size = "0.6" tracing = "0.1" -lending-iterator = "0.1" nom = "7" [dev-dependencies] diff --git a/README.md b/README.md index 73aaa70..33b6656 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ [![Codecov](https://img.shields.io/codecov/c/github/kiranshila/psrdada-rs?style=flat-square)](https://app.codecov.io/gh/kiranshila/psrdada-rs) This is a rust library around the [psrdada](http://psrdada.sourceforge.net/) library commonly used in radio astronomy. -Unfortunately, the C library is for the most part undocumented, so the behavior presented by this rust library is what -the authors have been able to ascertain by reading the original example code. -As such, this might not be a 1-to-1 implementation of the original use case. +Unfortunately, the C library is for the most part undocumented, so the behavior presented by this rust library is what the authors have been able to ascertain by reading the original example code. +As such, this might not be a 1-to-1 implementation of the original use case and implements only a subset +of the features available in the C library. ## Usecase @@ -17,26 +17,9 @@ Use this library if you want a safe abstraction around working with psrdada. As in, use this library if you need to interface with applications that are expecting psrdada buffers. Do not use if you don't have to, as it (psrdada itself) isn't as performant or featureful as other IPC libraries. -### Alternatives - -The rust library [shmem-ipc](https://github.com/diwic/shmem-ipc) has excellent performance over shmem, useful for large -data transfers (like windows of spectral data). It creates shared ringbuffers, much like psrdada. -Interfacing with D-Bus is fine for signaling and headers. - -If you _need_ CUDA support, [NVSHMEM](https://developer.nvidia.com/nvshmem) -is a thing that exists, and you should use it. Also, linux has [mkfifo](https://linux.die.net/man/3/mkfifo) which works fine with CUDA -as discussed [here](https://forums.developer.nvidia.com/t/gpu-inter-process-communications-ipc-question/35936/12). - -Lastly, there is [ipc-channel](https://github.com/servo/ipc-channel), which uses the Rust channel API over OS-native IPC abstractions. -It's a really nice library. - -In short, if you are constructing a pipeline from scratch, don't use psrdada. -There are more mature, documented, more performant alternatives. - ## Installation -We are building and linking the psrdada library as part of the build of this crate, which requires you have a working C compiler. -See the [cc](https://docs.rs/cc/latest/cc/) crate for more details. +You need to build and install PSRDADA manually, following the installation guide found [here](https://psrdada.sourceforge.net/download.shtml). Alternatively, you can use the [nix](https://nixos.org/) flake [here](https://github.com/kiranshila/psrdada.nix/blob/main/flake.nix) to declaratively create environments (shells/docker containers/operating systems) with PSRDADA baked in (deterministically). ## Example @@ -107,22 +90,9 @@ read_block.read_exact(&mut buf).unwrap(); without that `write_block.commit()` line, this code would not compile as there still exist a write in progress. Additionally, you can only ever `split` once, so you'll only ever have a single reader and writer for each type. -## What we learned about psrdada - -- Don't use `ipcio_t` or `dada_hdu`. - -They are wrappers around `ipcbuf_t` and have all sorts of undefined behavior. -Specifically, `ipcio_t` reimplemented stdlib `read` and `write` behavior, but in unsafe ways. -Our abstraction presented here reimplements the behavior, but with Rust's compile-time guarantees. -`dada_hdu` combines two `ipcbuf_t`s, the header and data buffers. -However, doing so breaks CUDA support (for some reason) and messes up the signaling of successful reads. - -- "End of data" is more or less a meaningless flag. +### Thanks -End of data doesn't prevent us from reading more data or writing more data. It is just a signal we can observe. -The iterator interface we provide will produce `None` if we run out of data, trying to be consistent with what that -might mean. Additionally, there is a very specific order in which eod is set and read. It _must_ be set after `mark_filled` -and before `unlock_write`. It _must_ be read after `mark_cleared` and before `unlock_read`. Any other ordering doesn't work. +Much of the implementation is inspired by other "modern" wrappings of PSRDADA, especially [PSRDADA_CPP](https://gitlab.mpcdf.mpg.de/mpifr-bdg/psrdada_cpp). ### License diff --git a/flake.nix b/flake.nix index 8cc0a49..83efeb5 100644 --- a/flake.nix +++ b/flake.nix @@ -29,7 +29,9 @@ pkgs = import nixpkgs { inherit system overlays; }; nativeBuildInputs = with pkgs; [ rustPlatform.bindgenHook pkg-config ]; buildInputs = with pkgs; [ - rust-bin.stable.latest.default + (rust-bin.stable.latest.default.override { + extensions = ["rust-src" "rust-analyzer"]; + }) psrdada.packages.${system}.default ]; in with pkgs; { diff --git a/src/builder.rs b/src/builder.rs index ff08a31..aae33e5 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -73,7 +73,8 @@ impl DadaClientBuilder { #[tracing::instrument] /// Builder for DadaClient - /// Buffer size will default to 4x of 128*Page Size + /// + /// Buffer size will default to 4x of 128*Page Size. /// Header size will default to 8x of Page Size pub fn build(self) -> PsrdadaResult { // Unpack the things we need, defaulting as necessary diff --git a/src/client.rs b/src/client.rs index 9891631..5205cc7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -64,10 +64,16 @@ impl DadaClient { Ok(s) } - #[tracing::instrument] + #[deprecated(note = "This wasn't an obvious name, use `DataClient::connect` instead")] /// Construct a new DadaClient and connect to existing ring buffers pub fn new(key: i32) -> PsrdadaResult { - let (data_buf, header_buf) = Self::connect(key)?; + Self::connect(key) + } + + #[tracing::instrument] + /// Construct a new DadaClient by connecting to existing ring buffers + pub fn connect(key: i32) -> PsrdadaResult { + let (data_buf, header_buf) = Self::connect_both(key)?; let s = Self { data_buf, header_buf, @@ -78,7 +84,7 @@ impl DadaClient { #[tracing::instrument] /// Internal method to actually build and connect - fn connect(key: i32) -> PsrdadaResult<(*const ipcbuf_t, *const ipcbuf_t)> { + fn connect_both(key: i32) -> PsrdadaResult<(*const ipcbuf_t, *const ipcbuf_t)> { debug!(key, "Connecting to dada buffer"); unsafe { let data_buf = Box::into_raw(Box::default()); @@ -203,7 +209,7 @@ mod tests { fn test_connect() { let key = next_key(); let _client = DadaClientBuilder::new(key).build().unwrap(); - let _connected = DadaClient::new(key).unwrap(); + let _connected = DadaClient::connect(key).unwrap(); } #[test] diff --git a/src/dada_iter.rs b/src/dada_iter.rs new file mode 100644 index 0000000..dddcbd4 --- /dev/null +++ b/src/dada_iter.rs @@ -0,0 +1,7 @@ +//! A trait that we will use that leverages [generic associated types](https://blog.rust-lang.org/2022/10/28/gats-stabilization.html) +//! to create a dada iterator that garuntees that references to a given buffer only exist when it is safe to do so. + +pub trait DadaIterator { + type Item<'a>; + fn next<'a>(&mut self) -> Option>; +} diff --git a/src/headers.rs b/src/headers.rs index f2124a7..c5a5432 100644 --- a/src/headers.rs +++ b/src/headers.rs @@ -199,7 +199,7 @@ mod tests { let hdr = b"FOO\tBAR # A comment\nBAZ \tquuz123#morecomment__\n\nbEanS __RICE__"; let (remaining, pairs) = header(hdr).unwrap(); - let p1 = pairs.get(0).unwrap(); + let p1 = pairs.first().unwrap(); assert_eq!(b"FOO", p1.0); assert_eq!(b"BAR", p1.1); @@ -219,7 +219,7 @@ mod tests { let hdr = b"foo bar\nbaz buzz#foob\n\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; let (_, pairs) = header(hdr).unwrap(); - let p1 = pairs.get(0).unwrap(); + let p1 = pairs.first().unwrap(); assert_eq!(b"foo", p1.0); assert_eq!(b"bar", p1.1); diff --git a/src/highlevel.rs b/src/highlevel.rs index 5b0b6fd..0140016 100644 --- a/src/highlevel.rs +++ b/src/highlevel.rs @@ -2,10 +2,9 @@ use std::io::Write; -use lending_iterator::LendingIterator; - use crate::{ client::DadaClient, + dada_iter::DadaIterator, errors::{PsrdadaError, PsrdadaResult}, io::{ReadHalf, WriteHalf}, }; @@ -51,9 +50,7 @@ impl DadaClient { #[cfg(test)] mod tests { - use lending_iterator::LendingIterator; - - use crate::{builder::DadaClientBuilder, tests::next_key}; + use crate::{builder::DadaClientBuilder, dada_iter::DadaIterator, tests::next_key}; #[test] fn test_push() { diff --git a/src/io.rs b/src/io.rs index aec3a03..15e01a4 100644 --- a/src/io.rs +++ b/src/io.rs @@ -2,12 +2,13 @@ use std::marker::PhantomData; -use lending_iterator::{gat, prelude::*, LendingIterator}; +use crate::{ + client::{DataClient, HeaderClient}, + dada_iter::DadaIterator, +}; use psrdada_sys::*; use tracing::{debug, error}; -use crate::client::{DataClient, HeaderClient}; - /// The writer associated with a ringbuffer pub struct WriteHalf<'a> { buf: *const ipcbuf_t, @@ -107,11 +108,10 @@ impl Drop for WriteBlock<'_> { } } -#[gat] -impl LendingIterator for WriteHalf<'_> { +impl DadaIterator for WriteHalf<'_> { type Item<'next> = WriteBlock<'next>; - fn next(&'_ mut self) -> Option> { + fn next<'next>(&mut self) -> Option> { // Get a lock debug!("Locking ringbuffer"); unsafe { @@ -167,7 +167,7 @@ impl std::io::Write for WriteBlock<'_> { Ok(buf.len()) } - // Not relevant here + // Not relevant here because the memory is unbuffered fn flush(&mut self) -> std::io::Result<()> { Ok(()) } @@ -219,11 +219,10 @@ impl ReadBlock<'_> { } } -#[gat] -impl LendingIterator for ReadHalf<'_> { +impl DadaIterator for ReadHalf<'_> { type Item<'next> = ReadBlock<'next>; - fn next(&'_ mut self) -> Option> { + fn next<'next>(&mut self) -> Option> { if self.done { return None; } @@ -266,13 +265,12 @@ impl LendingIterator for ReadHalf<'_> { #[cfg(test)] mod tests { + use crate::{ + builder::DadaClientBuilder, client::DadaClient, dada_iter::DadaIterator, tests::next_key, + }; use std::io::Write; - - use lending_iterator::LendingIterator; use test_log::test; - use crate::{builder::DadaClientBuilder, client::DadaClient, tests::next_key}; - #[test] fn test_write() { let key = next_key(); @@ -322,7 +320,7 @@ mod tests { // Spawn a reader thread, which will block until the data shows up let handle = std::thread::spawn(move || { - let mut client = DadaClient::new(key).unwrap(); + let mut client = DadaClient::connect(key).unwrap(); let (_, mut dc) = client.split(); let mut reader = dc.reader(); assert_eq!([0u8, 1u8, 2u8, 3u8], reader.next().unwrap().read_block()); diff --git a/src/lib.rs b/src/lib.rs index d1a8f39..960d96e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,126 +1,13 @@ -//! # psrdada-rs -//! -//! This is a rust library around the [psrdada](http://psrdada.sourceforge.net/) library commonly used in radio astronomy. -//! Unfortunately, the C library is for the most part undocumented, so the behavior presented by this rust library is what -//! the authors have been able to ascertain by reading the original example code. -//! As such, this might not be a 1-to-1 implementation of the original use case. -//! -//! ## Usecase -//! -//! Use this library if you want a safe abstraction around working with psrdada. -//! As in, use this library if you need to interface with applications that are expecting psrdada buffers. -//! Do not use if you don't have to, as it (psrdada itself) isn't as performant or featureful as other IPC libraries. -//! -//! ### Alternatives -//! -//! The rust library [shmem-ipc](https://github.com/diwic/shmem-ipc) has excellent performance over shmem, useful for large -//! data transfers (like windows of spectral data). It creates shared ringbuffers, much like psrdada. -//! Interfacing with D-Bus is fine for signalling and headers. -//! -//! If you *need* CUDA support, [NVSHMEM](https://developer.nvidia.com/nvshmem) -//! is a thing that exists, and you should use it. Also, linux has [mkfifo](https://linux.die.net/man/3/mkfifo) which works fine with CUDA -//! as discussed [here](https://forums.developer.nvidia.com/t/gpu-inter-process-communications-ipc-question/35936/12). -//! -//! Lastly, there is [ipc-channel](https://github.com/servo/ipc-channel), which uses the Rust channel API over OS-native IPC abstractions. -//! It's a really nice library. -//! -//! In short, if you are constructing a pipeline from scratch, don't use psrdada. -//! There are more mature, documented, more performant alternatives. -//! -//! ## Installation -//! -//! We are building and linking the psrdada library as part of the build of this crate, which requires you have a working C compiler. -//! See the [cc](https://docs.rs/cc/latest/cc/) crate for more details. -//! -//! ## Example -//! -//! The most simple way to use this library is to use the top-level `push` and `pop` methods. -//! ```rust -//! use std::collections::HashMap; -//! use psrdada::builder::DadaClientBuilder; -//! -//! let key = 0xb0ba; -//! let mut client = DadaClientBuilder::new(key).build().unwrap(); - -//! let data = [0u8, 5u8, 10u8]; -//! let header = HashMap::from([ -//! ("foo".to_owned(), "bar".to_owned()), -//! ("baz".to_owned(), "buzz".to_owned()), -//! ]); - -//! client.push_data(&data).unwrap(); -//! // Unsafe as we're not checking if the keys and values are valid -//! unsafe { client.push_header(&header).unwrap() }; -//! ``` -//! -//! Beyond this, you can `split` the `DadaClient` into separate clients for headers and data, which can then be read and written to/from. -//! -//! ## Safety -//! -//! The original library is intrinsically unsafe as it is written in C. This library tries to ensure at compile time some of the things the -//! C library checks at runtime. For example, If you try to write to buffer while something else is trying to read (from the same `DadaClient`), this -//! would usually fail a lock. Instead, in this library, we use Rust's borrowing system to ensure you can't build both at the same time. -//! -//! Take the following code as an example -//! ```rust -//! use std::io::{Read, Write}; -//! -//! use lending_iterator::LendingIterator; -//! use psrdada::builder::DadaClientBuilder; -//! -//! // Build the paired client -//! let key = 0xb0ba; -//! let mut client = DadaClientBuilder::new(key).build().unwrap(); -//! -//! // Split into individual clients -//! let (_, mut data_client) = client.split(); -//! -//! // Construct the writer (mutable borrow) -//! let mut writer = data_client.writer(); -//! -//! // Grab the next block in the ring (assuming we can) -//! let mut write_block = writer.next().unwrap(); -//! -//! // Write using std::io::Write so you can write chunks at a time -//! write_block.write_all(&[0u8; 10]).unwrap(); -//! write_block.commit(); -//! -//! // Construct the reader (mutable borrow) -//! let mut reader = data_client.reader(); -//! -//! // Grab the next read block in the ring -//! let mut read_block = reader.next().unwrap(); -//! -//! // Read using std::io::Read -//! let mut buf = [0u8; 10]; -//! read_block.read_exact(&mut buf).unwrap(); -//! ``` -//! -//! without that `write_block.commit()` line, this code would not compile as there still exist a write in progress. -//! Additionally, you can only ever `split` once, so you'll only ever have a single reader and writer for each type. -//! -//! ## What we learned about psrdada -//! -//! - Don't use `ipcio_t` or `dada_hdu`. -//! -//! They are wrappers around `ipcbuf_t` and have all sorts of undefined behavior. -//! Specifically, `ipcio_t` reimplemented stdlib `read` and `write` behavior, but in unsafe ways. -//! Our abstraction presented here reimplements the behavior, but with Rust's compile-time guarantees. -//! `dada_hdu` combines two `ipcbuf_t`s, the header and data buffers. -//! However, doing so breaks CUDA support (for some reason) and messes up the signaling of successful reads. -//! -//! - "End of data" is more or less a meaningless flag. -//! -//! End of data doesn't prevent us from reading more data or writing more data. It is just a signal we can observe. -//! The iterator interface we provide will produce `None` if we run out of data, trying to be consistent with what that -//! might mean. Additionally, there is a very specific order in which eod is set and read. It *must* be set after `mark_filled` -//! and before `unlock_write`. It *must* be read after `mark_cleared` and before `unlock_read`. Any other ordering doesn't work. +//! TODO - Fill with new readme pub mod builder; pub mod client; +pub mod dada_iter; pub mod errors; pub mod headers; -pub mod highlevel; +// Doesn't expose new symbols, so it doesn't need to be public +// Otherwise we get confused in the docs +mod highlevel; pub mod io; pub mod prelude; #[cfg(test)] diff --git a/src/prelude.rs b/src/prelude.rs index bc064a6..186af34 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,3 +1,5 @@ //! Reexports to save us some time -pub use lending_iterator::{prelude::*, LendingIterator}; +// pub use lending_iterator::{prelude::*, LendingIterator}; + +// TODO reexport the trait we write that becomes iterable