summaryrefslogtreecommitdiffstats
path: root/buffered-reader
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@pep.foundation>2018-10-04 11:37:42 +0200
committerNeal H. Walfield <neal@pep.foundation>2018-10-04 12:08:35 +0200
commite121bc00a9987df81ae6306ad4c94f2df37fe448 (patch)
treefde7993d1d0769a9f6a980604455a5a6bdd2d645 /buffered-reader
parent6788e434dc1278261d1b16825540b612cdd6a59a (diff)
buffered-reader: Rework documentation.
Diffstat (limited to 'buffered-reader')
-rw-r--r--buffered-reader/src/decompress_bzip2.rs14
-rw-r--r--buffered-reader/src/decompress_deflate.rs28
-rw-r--r--buffered-reader/src/dup.rs19
-rw-r--r--buffered-reader/src/eof.rs4
-rw-r--r--buffered-reader/src/file_generic.rs2
-rw-r--r--buffered-reader/src/file_unix.rs2
-rw-r--r--buffered-reader/src/generic.rs8
-rw-r--r--buffered-reader/src/lib.rs547
-rw-r--r--buffered-reader/src/limitor.rs18
-rw-r--r--buffered-reader/src/memory.rs20
-rw-r--r--buffered-reader/src/reserve.rs14
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
- /// consumed. It is an error to call this function without having
- /// first successfully called `data` (or a related function) to