diff options
-rw-r--r-- | buffered-reader/src/decompress_bzip2.rs | 14 | ||||
-rw-r--r-- | buffered-reader/src/decompress_deflate.rs | 28 | ||||
-rw-r--r-- | buffered-reader/src/dup.rs | 19 | ||||
-rw-r--r-- | buffered-reader/src/eof.rs | 4 | ||||
-rw-r--r-- | buffered-reader/src/file_generic.rs | 2 | ||||
-rw-r--r-- | buffered-reader/src/file_unix.rs | 2 | ||||
-rw-r--r-- | buffered-reader/src/generic.rs | 8 | ||||
-rw-r--r-- | buffered-reader/src/lib.rs | 547 | ||||
-rw-r--r-- | buffered-reader/src/limitor.rs | 18 | ||||
-rw-r--r-- | buffered-reader/src/memory.rs | 20 | ||||
-rw-r--r-- | buffered-reader/src/reserve.rs | 14 |
11 files changed, 576 insertions, 100 deletions
diff --git a/buffered-reader/src/decompress_bzip2.rs b/buffered-reader/src/decompress_bzip2.rs index 41fba37a..f118fa6a 100644 --- a/buffered-reader/src/decompress_bzip2.rs +++ b/buffered-reader/src/decompress_bzip2.rs @@ -6,22 +6,26 @@ use bzip2::read::BzDecoder; use super::*; +/// Decompresses the underlying `BufferedReader` using the bzip2 +/// algorithm. pub struct BufferedReaderBzip<R: BufferedReader<C>, C> { reader: BufferedReaderGeneric<BzDecoder<R>, C>, } impl <R: BufferedReader<()>> BufferedReaderBzip<R, ()> { - /// Instantiate a new bzip decompression reader. `reader` is - /// the source to wrap. + /// Instantiates a new bzip decompression reader. + /// + /// `reader` is the source to wrap. pub fn new(reader: R) -> Self { Self::with_cookie(reader, ()) } } impl <R: BufferedReader<C>, C> BufferedReaderBzip<R, C> { - /// Like `new()`, but sets a cookie, which can be retrieved using - /// the `cookie_ref` and `cookie_mut` methods, and set using - /// the `cookie_set` method. + /// Like `new()`, but uses a cookie. + /// + /// The cookie can be retrieved using the `cookie_ref` and + /// `cookie_mut` methods, and set using the `cookie_set` method. pub fn with_cookie(reader: R, cookie: C) -> Self { BufferedReaderBzip { reader: BufferedReaderGeneric::with_cookie( diff --git a/buffered-reader/src/decompress_deflate.rs b/buffered-reader/src/decompress_deflate.rs index c46b9eea..3f3da76c 100644 --- a/buffered-reader/src/decompress_deflate.rs +++ b/buffered-reader/src/decompress_deflate.rs @@ -6,22 +6,26 @@ use flate2::read::ZlibDecoder; use super::*; +/// Decompresses the underlying `BufferedReader` using the deflate +/// algorithm. pub struct BufferedReaderDeflate<R: BufferedReader<C>, C> { reader: BufferedReaderGeneric<DeflateDecoder<R>, C>, } impl <R: BufferedReader<()>> BufferedReaderDeflate<R, ()> { - /// Instantiate a new deflate decompression reader. `reader` is - /// the source to wrap. + /// Instantiates a new deflate decompression reader. + /// + /// `reader` is the source to wrap. pub fn new(reader: R) -> Self { Self::with_cookie(reader, ()) } } impl <R: BufferedReader<C>, C> BufferedReaderDeflate<R, C> { - /// Like `new()`, but sets a cookie, which can be retrieved using - /// the `cookie_ref` and `cookie_mut` methods, and set using - /// the `cookie_set` method. + /// Like `new()`, but uses a cookie. + /// + /// The cookie can be retrieved using the `cookie_ref` and + /// `cookie_mut` methods, and set using the `cookie_set` method. pub fn with_cookie(reader: R, cookie: C) -> Self { BufferedReaderDeflate { reader: BufferedReaderGeneric::with_cookie( @@ -118,22 +122,26 @@ impl<R: BufferedReader<C>, C> BufferedReader<C> } } +/// Decompresses the underlying `BufferedReader` using the zlib +/// algorithm. pub struct BufferedReaderZlib<R: BufferedReader<C>, C> { reader: BufferedReaderGeneric<ZlibDecoder<R>, C>, } impl <R: BufferedReader<()>> BufferedReaderZlib<R, ()> { - /// Instantiate a new zlib decompression reader. `reader` is - /// the source to wrap. + /// Instantiates a new zlib decompression reader. + /// + /// `reader` is the source to wrap. pub fn new(reader: R) -> Self { Self::with_cookie(reader, ()) } } impl <R: BufferedReader<C>, C> BufferedReaderZlib<R, C> { - /// Like `new()`, but sets a cookie, which can be retrieved using - /// the `cookie_ref` and `cookie_mut` methods, and set using - /// the `cookie_set` method. + /// Like `new()`, but uses a cookie. + /// + /// The cookie can be retrieved using the `cookie_ref` and + /// `cookie_mut` methods, and set using the `cookie_set` method. pub fn with_cookie(reader: R, cookie: C) -> Self { BufferedReaderZlib { reader: BufferedReaderGeneric::with_cookie( diff --git a/buffered-reader/src/dup.rs b/buffered-reader/src/dup.rs index 770a091f..4dd8c347 100644 --- a/buffered-reader/src/dup.rs +++ b/buffered-reader/src/dup.rs @@ -4,8 +4,8 @@ use std::cmp; use super::*; -/// A `BufferedReader` that duplicates the underlying `BufferedReader` -/// without consuming any of the data. +/// Duplicates the underlying `BufferedReader` without consuming any +/// of the data. /// /// Note: this will likely cause the underlying stream to buffer as /// much data as you read. Thus, it should only be used for peeking @@ -30,17 +30,19 @@ impl<'a, C> fmt::Debug for BufferedReaderDup<'a, C> { } impl<'a> BufferedReaderDup<'a, ()> { - /// Instantiate a new memory-based reader. `buffer` contains the - /// reader's contents. + /// Instantiates a new `BufferedReaderDup` buffered reader. + /// + /// `reader` is the `BufferedReader` to duplicate. pub fn new(reader: Box<'a + BufferedReader<()>>) -> Self { Self::with_cookie(reader, ()) } } impl<'a, C> BufferedReaderDup<'a, C> { - /// Like `new()`, but sets a cookie, which can be retrieved using - /// the `cookie_ref` and `cookie_mut` methods, and set using - /// the `cookie_set` method. + /// Like `new()`, but uses a cookie. + /// + /// The cookie can be retrieved using the `cookie_ref` and + /// `cookie_mut` methods, and set using the `cookie_set` method. pub fn with_cookie(reader: Box<'a + BufferedReader<C>>, cookie: C) -> Self { BufferedReaderDup { reader: reader, @@ -49,8 +51,7 @@ impl<'a, C> BufferedReaderDup<'a, C> { } } - /// Returns the number of bytes that have been consumed by this - /// reader. + /// Returns the number of bytes that this reader has consumed. pub fn total_out(&self) -> usize { return self.cursor; } diff --git a/buffered-reader/src/eof.rs b/buffered-reader/src/eof.rs index 1e7e5a1c..cadc035a 100644 --- a/buffered-reader/src/eof.rs +++ b/buffered-reader/src/eof.rs @@ -4,7 +4,7 @@ use std::fmt; use BufferedReader; -/// A `BufferedReaderEOF` always returns EOF. +/// Always returns EOF. pub struct BufferedReaderEOF<C> { cookie: C, } @@ -17,6 +17,7 @@ impl<C> fmt::Debug for BufferedReaderEOF<C> { } impl BufferedReaderEOF<()> { + /// Instantiates a new `BufferedReaderEOF`. pub fn new() -> Self { BufferedReaderEOF { cookie: (), @@ -25,6 +26,7 @@ impl BufferedReaderEOF<()> { } impl<C> BufferedReaderEOF<C> { + /// Instantiates a new `BufferedReaderEOF` with a cookie. pub fn with_cookie(cookie: C) -> Self { BufferedReaderEOF { cookie: cookie, diff --git a/buffered-reader/src/file_generic.rs b/buffered-reader/src/file_generic.rs index deb8da95..45844a78 100644 --- a/buffered-reader/src/file_generic.rs +++ b/buffered-reader/src/file_generic.rs @@ -5,7 +5,7 @@ use std::path::Path; use super::*; -/// A `BufferedReader` implementation for files. +/// Wraps files. /// /// This is a generic implementation that may be replaced by /// platform-specific versions. diff --git a/buffered-reader/src/file_unix.rs b/buffered-reader/src/file_unix.rs index 77c27d77..9dc80ce1 100644 --- a/buffered-reader/src/file_unix.rs +++ b/buffered-reader/src/file_unix.rs @@ -20,7 +20,7 @@ use super::*; // (Justus) system, mmaping is faster than sequentially reading. const MMAP_THRESHOLD: u64 = 16 * 4096; -/// A `BufferedReader` implementation for files. +/// Wraps files using `mmap`(). /// /// This implementation tries to mmap the file, falling back to /// just using a generic reader. diff --git a/buffered-reader/src/generic.rs b/buffered-reader/src/generic.rs index ed187eff..61dd8da6 100644 --- a/buffered-reader/src/generic.rs +++ b/buffered-reader/src/generic.rs @@ -5,10 +5,10 @@ use std::io::{Error, ErrorKind}; use super::*; -/// A generic `BufferedReader` implementation that only requires a -/// source that implements the `Read` trait. This is sufficient when -/// reading from a file, and it even works with a `&[u8]` (but -/// `BufferedReaderMemory` is more efficient). +/// Wraps a `Read`er. +/// +/// This is useful when reading from a file, and it even works with a +/// `&[u8]` (but `BufferedReaderMemory` is more efficient). pub struct BufferedReaderGeneric<T: io::Read, C> { buffer: Option<Box<[u8]>>, // The next byte to read in the buffer. diff --git a/buffered-reader/src/lib.rs b/buffered-reader/src/lib.rs index 7d3e475e..be34f74a 100644 --- a/buffered-reader/src/lib.rs +++ b/buffered-reader/src/lib.rs @@ -1,4 +1,227 @@ -//! An improved `BufRead` interface. +//! A `BufferedReader` is a super-powered `Read`er. +//! +//! Like the [`BufRead`] trait, the `BufferedReader` trait has an +//! internal buffer that is directly exposed to the user. This design +//! enables two performance optimizations. First, the use of an +//! internal buffer amortizes system calls. Second, exposing the +//! internal buffer allows the user to work with data in place, which +//! avoids another copy. +//! +//! The [`BufRead`] trait, however, has a significant limitation for +//! parsers: the user of a [`BufRead`] object can't control the amount +//! of buffering. This is essential for being able to conveniently +//! work with data in place, and being able to lookahead without +//! consuming data. The result is that either the sizing has to be +//! handled by the instantiator of the [`BufRead`] object---assuming +//! the [`BufRead`] object provides such a mechanism---which is a +//! layering violation, or the parser has to fallback to buffering if +//! the internal buffer is too small, which eliminates most of the +//! advantages of the [`BufRead`] abstraction. The `BufferedReader` +//! trait addresses this shortcoming by allowing the user to control +//! the size of the internal buffer. +//! +//! The `BufferedReader` trait also has some functionality, +//! specifically, a generic interface to work with a stack of +//! `BufferedReader` objects, that simplifies using multiple parsers +//! simultaneously. This is helpful when one parser deals with +//! framing (e.g., something like [HTTP's chunk transfer encoding]), +//! and another decodes the actual objects. It is also useful when +//! objects are nested. +//! +//! # Details +//! +//! Because the [`BufRead`] trait doesn't provide a mechanism for the +//! user to size the interal buffer, a parser can't generally be sure +//! that the internal buffer will be large enough to allow it to work +//! with all data in place. +//! +//! Using the standard [`BufRead`] implementation, [`BufReader`], the +//! instantiator can set the size of the internal buffer at creation +//! time. Unfortunately, this mechanism is ugly, and not always +//! adequate. First, the parser is typically not the instantiator. +//! Thus, the instantiator needs to know about the implementation +//! details of all of the parsers, which turns an implementation +//! detail into a cross-cutting concern. Second, when working with +//! dynamically sized data, the maximum amount of the data that needs +//! to be worked with in place may not be known apriori, or the +//! maximum amount may be significantly larger than the typical +//! amount. This leads to poorly sized buffers. +//! +//! Alternatively, the code that uses, but does not instantiate a +//! [`BufRead`] object, can be changed to stream the data, or to +//! fallback to reading the data into a local buffer if the internal +//! buffer is too small. Both of these approaches increase code +//! complexity, and the latter approach is contrary to the +//! [`BufRead`]'s goal of reducing unnecessary copying. +//! +//! The `BufferedReader` trait solves this problem by allowing the +//! user to dynamically (i.e., at read time, not open time) ensure +//! that the internal buffer has a certain amount of data. +//! +//! The ability to control the size of the internal buffer is also +//! essential to straightforward support for speculative lookahead. +//! The reason that speculative lookahead with a [`BufRead`] object is +//! difficult is that speculative lookahead is /speculative/, i.e., if +//! the parser backtracks, the data that was read must not be +//! consumed. Using a [`BufRead`] object, this is not possible if the +//! amount of lookahead is larger than the internal buffer. That is, +//! if the amount of lookahead data is larger than the [`BufRead`]'s +//! internal buffer, the parser first has to `BufRead::consume`() some +//! data to be able to examine more data. But, if the parser then +//! decides to backtrack, it has no way to return the unused data to +//! the [`BufRead`] object. This forces the parser to manage a buffer +//! of read, but unconsumed data, which significantly complicates the +//! code. +//! +//! The `BufferedReader` trait also simplifies working with a stack of +//! `BufferedReader`s in two ways. First, the `BufferedReader` trait +//! provides *generic* methods to access the underlying +//! `BufferedReader`. Thus, even when dealing with a trait object, it +//! is still possible to recover the underlying `BufferedReader`. +//! Second, the `BufferedReader` provides a mechanism to associate +//! generic state with each `BufferedReader` via a cookie. Although +//! it is possible to realize this functionality using a custom trait +//! that extends the `BufferedReader` trait and wraps existing +//! `BufferedReader` implementations, this approach eliminates a lot +//! of error-prone, boilerplate code. +//! +//! # Examples +//! +//! The following examples show not only how to use a +//! `BufferedReader`, but also better illustrate the aforementioned +//! limitations of a [`BufRead`]er. +//! +//! Consider a file consisting of a sequence of objects, which are +//! laid out as follows. Each object has a two byte header that +//! indicates the object's size in bytes. The object immediately +//! follows the header. Thus, if we had two objects: "foobar" and +//! "xyzzy", in that order, the file would look like this: +//! +//! ```text +//! 0 6 f o o b a r 0 5 x y z z y +//! ``` +//! +//! Here's how we might parse this type of file using a +//! `BufferedReader`: +//! +//! ``` +//! use buffered_reader::*; +//! use buffered_reader::BufferedReaderFile; +//! +//! fn parse_object(content: &[u8]) { +//! // Parse the object. +//! # let _ = content; +//! } +//! +//! # f(); fn f() -> Result<(), std::io::Error> { +//! # const FILENAME : &str = "/dev/null"; +//! let mut br = BufferedReaderFile::open(FILENAME)?; +//! +//! // While we haven't reached EOF (i.e., we can read at +//! // least one byte). +//! while br.data(1)?.len() > 0 { +//! // Get the object's length. +//! let len = br.read_be_u16()? as usize; +//! // Get the object's content. +//! let content = br.data_consume_hard(len)?; +//! +//! // Parse the actual object using a real parser. Recall: +//! // `data_hard`() may return more than the requested amount (but +//! // it will never return less). +//! parse_object(&content[..len]); +//! } +//! # Ok(()) } +//! ``` +//! +//! Note that `content` is actually a pointer to the +//! `BufferedReader`'s internal buffer. Thus, getting some data +//! doesn't require copying the data into a local buffer, which is +//! often discarded immediately after the data is parsed. +//! +//! Further, `data`() (and the other related functions) are guaranteed +//! to return at least the requested amount of data. There are two +//! exceptions: if an error occurs, or the end of the file is reached. +//! Thus, only the cases that actually need to be handled by the user +//! are actually exposed; there is no need to call something like +//! `read`() in a loop to ensure the whole object is available. +//! +//! Because reading is separate from consuming data, it is possible to +//! get a chunk of data, inspect it, and then consume only what is +//! needed. As mentioned above, this is only possible with a +//! [`BufRead`] object if the internal buffer happens to be large +//! enough. Using a `BufferedReader`, this is always possible, +//! assuming the data fits in memory. +//! +//! In our example, we actually have two parsers: one that deals with +//! the framing, and one for the actual objects. The above code +//! buffers the objects in their entirety, and then passes a slice +//! containing the object to the object parser. If the object parser +//! also worked with a `BufferedReader` object, then less buffering +//! will usually be needed, and the two parsers could run +//! simultaneously. This is particularly useful when the framing is +//! more complicated like [HTTP's chunk transfer encoding]. Then, +//! when the object parser reads data, the frame parser is invoked +//! lazily. This is done by implementing the `BufferedReader` trait +//! for the framing parser, and stacking the `BufferedReader`s. +//! +//! For our next example, we rewrite the previous code asssuming that +//! the object parser reads from a `BufferedReader` object. Since the +//! framing parser is really just a limit on the object's size, we +//! don't need to implement a special `BufferedReader`, but can use a +//! `BufferedReaderLimitor` to impose an upper limit on the amount +//! that it can read. After the object parser has finished, we drain +//! the object reader. This pattern is particularly helpful when +//! individual objects that contain errors should be skipped. +//! +//! ``` +//! use buffered_reader::*; +//! use buffered_reader::BufferedReaderFile; +//! +//! fn parse_object<R: BufferedReader<()>>(br: &mut R) { +//! // Parse the object. +//! # let _ = br; +//! } +//! +//! # f(); fn f() -> Result<(), std::io::Error> { +//! # const FILENAME : &str = "/dev/null"; +//! let mut br : Box<BufferedReader<()>> +//! = Box::new(BufferedReaderFile::open(FILENAME)?); +//! +//! // While we haven't reached EOF (i.e., we can read at +//! // least one byte). +//! while br.data(1)?.len() > 0 { +//! // Get the object's length. +//! let len = br.read_be_u16()? as u64; +//! +//! // Set up a limit. +//! br = Box::new(BufferedReaderLimitor::new(br, len)); +//! +//! // Parse the actual object using a real parser. +//! parse_object(&mut br); +//! +//! // If the parser didn't consume the whole object, e.g., due to +//! // a parse error, drop the rest. +//! br.drop_eof(); +//! +//! // Recover the framing parser's `BufferedReader`. +//! br = br.into_inner().unwrap(); +//! } +//! # Ok(()) } +//! ``` +//! +//! Of particular note is the generic functionality for dealing with +//! stacked `BufferedReader`s: the `into_inner`() method is not bound +//! to the implementation, which is often not be available due to type +//! erasure, but is provided by the trait. +//! +//! In addition to utility `BufferedReader`s like the +//! `BufferedReaderLimitor`, this crate also includes a few +//! general-purpose parsers, like the `BufferedReaderZip` +//! decompressor. +//! +//! [`BufRead`]: https://doc.rust-lang.org/stable/std/io/trait.BufRead.html +//! [`BufReader`]: https://doc.rust-lang.org/stable/std/io/struct.BufReader.html +//! [HTTP's chunk transfer encoding]: https://en.wikipedia.org/wiki/Chunked_transfer_encoding #[cfg(feature = "compression-deflate")] extern crate flate2; @@ -51,52 +274,138 @@ pub use self::file_unix::BufferedReaderFile; // The default buffer size. const DEFAULT_BUF_SIZE: usize = 8 * 1024; -/// A `BufferedReader` is a type of `Read`er that has an internal -/// buffer, and allows working directly from that buffer. Like a -/// `BufRead`er, the internal buffer amortizes system calls. And, -/// like a `BufRead`, a `BufferedReader` exposes the internal buffer -/// so that a user can work with the data in place rather than having -/// to first copy it to a local buffer. However, unlike `BufRead`, -/// `BufferedReader` allows the caller to ensure that the internal -/// buffer has a certain amount of data. +/// The generic `BufferReader` interface. pub trait BufferedReader<C> : io::Read + fmt::Debug { /// Returns a reference to the internal buffer. /// - /// Note: this will return the same data as self.data(0), but it - /// does so without mutable borrowing self. + /// Note: this returns the same data as `self.data(0)`, but it + /// does so without mutably borrowing self: + /// + /// ``` + /// # f(); fn f() -> Result<(), std::io::Error> { + /// use buffered_reader::*; + /// use buffered_reader::BufferedReaderMemory; + /// + /// let mut br = BufferedReaderMemory::new(&b"0123456789"[..]); + /// + /// let first = br.data(10)?.len(); + /// let second = br.buffer().len(); + /// // `buffer` must return exactly what `data` returned. + /// assert_eq!(first, second); + /// # Ok(()) } + /// ``` fn buffer(&self) -> &[u8]; - /// Return the data in the internal buffer. Normally, the - /// returned buffer will contain *at least* `amount` bytes worth - /// of data. Less data may be returned if (and only if) the end - /// of the file is reached or an error occurs. In these cases, - /// any remaining data is returned. Note: the error is not - /// discarded, but will be returned when data is called and the + /// Ensures that the internal buffer has at least `amount` bytes + /// of data, and returns it. + /// + /// If the internal buffer contains less than `amount` bytes of + /// data, the internal buffer is first filled. + /// + /// The returned slice will have *at least* `amount` bytes unless + /// EOF has been reached or an error occurs, in which case the + /// returned slice will contain the rest of the file. + /// + /// If an error occurs, it is not discarded, but saved. It is + /// returned when `data` (or a related function) is called and the /// internal buffer is empty. /// - /// This function does not advance the cursor. Thus, multiple - /// calls will return the same data. To advance the cursor, use - /// `consume`. + /// This function does not advance the cursor. To advance the + /// cursor, use `consume()`. + /// + /// Note: If the internal buffer already contains at least + /// `amount` bytes of data, then `BufferedReader` implementations + /// are guaranteed to simply return the internal buffer. As such, + /// multiple calls to `data` for the same `amount` will return the + /// same slice. + /// + /// Further, `BufferedReader` implementations are guaranteed to + /// not shrink the internal buffer. Thus, once some data has been + /// returned, it will always be returned until it is consumed. + /// As such, the following must hold: + /// + /// ``` + /// # f(); fn f() -> Result<(), std::io::Error> { + /// use buffered_reader::*; + /// use buffered_reader::BufferedReaderMemory; + /// + /// let mut br = BufferedReaderMemory::new(&b"0123456789"[..]); + /// + /// let first = br.data(10)?.len(); + /// let second = br.data(5)?.len(); + /// // Even though less data is requested, the second call must + /// // return the same slice as the first call. + /// assert_eq!(first, second); + /// # Ok(()) } + /// ``` fn data(&mut self, amount: usize) -> Result<&[u8], io::Error>; - /// Like `data`, but returns an error if there is not at least + /// Like `data()`, but returns an error if there is not at least /// `amount` bytes available. + /// + /// `data_hard()` is a variant of `data()` that returns at least + /// `amount` bytes of data or an error. Thus, unlike `data()`, + /// which will return less than `amount` bytes of data if EOF is + /// encountered, `data_hard()` returns an error, specifically, + /// `io::ErrorKind::UnexpectedEof`. + /// + /// # Examples + /// + /// ``` + /// # f(); fn f() -> Result<(), std::io::Error> { + /// use buffered_reader::*; + /// use buffered_reader::BufferedReaderMemory; + /// + /// let mut br = BufferedReaderMemory::new(&b"0123456789"[..]); + /// + /// // Trying to read more than there is available results in an error. + /// assert!(br.data_hard(20).is_err()); + /// // Whereas with data(), everything through EOF is returned. + /// assert_eq!(br.data(20)?.len(), 10); + /// # Ok(()) } + /// ``` fn data_hard(&mut self, amount: usize) -> Result<&[u8], io::Error> { let result = self.data(amount); if let Ok(buffer) = result { if buffer.len() < amount { - return Err(Error::new(ErrorKind::UnexpectedEof, "unexpected EOF")); + return Err(Error::new(ErrorKind::UnexpectedEof, + "unexpected EOF")); } } return result; } - /// Return all of the data until EOF. Like `data`, this does not + /// Returns all of the data until EOF. Like `data()`, this does not /// actually consume the data that is read. /// /// In general, you shouldn't use this function as it can cause an /// enormous amount of buffering. But, if you know that the /// amount of data is limited, this is acceptable. + /// + /// # Examples + /// + /// ``` + /// # f(); fn f() -> Result<(), std::io::Error> { + /// use buffered_reader::*; + /// use buffered_reader::BufferedReaderGeneric; + /// + /// const AMOUNT : usize = 100 * 1024 * 1024; + /// let buffer = vec![0u8; AMOUNT]; + /// let mut br = BufferedReaderGeneric::new(&buffer[..], None); + /// + /// // Normally, only a small amount will be buffered. + /// assert!(br.data(10)?.len() <= AMOUNT); + /// + /// // `data_eof` buffers everything. + /// assert_eq!(br.data_eof()?.len(), AMOUNT); + /// + /// // Now that everything is buffered, buffer(), data(), and + /// // data_hard() will also return everything. + /// assert_eq!(br.buffer().len(), AMOUNT); + /// assert_eq!(br.data(10)?.len(), AMOUNT); + /// assert_eq!(br.data_hard(10)?.len(), AMOUNT); + /// # Ok(()) } + /// ``` fn data_eof(&mut self) -> Result<&[u8], io::Error> { // Don't just read std::usize::MAX bytes at once. The // implementation might try to actually allocate a buffer that @@ -132,25 +441,101 @@ pub trait BufferedReader<C> : io::Read + fmt::Debug { return self.data(s); } - /// Mark the first `amount` bytes of the internal buffer as - /// |