io: add lines example for StreamReader (#5145)

This commit is contained in:
Alice Ryhl
2022-10-31 20:40:52 +01:00
committed by GitHub
parent c2210dfe37
commit a9d5eb2fc7
+82 -8
View File
@@ -1,23 +1,24 @@
use bytes::Buf; use bytes::Buf;
use futures_core::stream::Stream; use futures_core::stream::Stream;
use pin_project_lite::pin_project;
use std::io; use std::io;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use tokio::io::{AsyncBufRead, AsyncRead, ReadBuf}; use tokio::io::{AsyncBufRead, AsyncRead, ReadBuf};
pin_project! {
/// Convert a [`Stream`] of byte chunks into an [`AsyncRead`]. /// Convert a [`Stream`] of byte chunks into an [`AsyncRead`].
/// ///
/// This type performs the inverse operation of [`ReaderStream`]. /// This type performs the inverse operation of [`ReaderStream`].
/// ///
/// This type also implements the [`AsyncBufRead`] trait, so you can use it
/// to read a `Stream` of byte chunks line-by-line. See the examples below.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use bytes::Bytes; /// use bytes::Bytes;
/// use tokio::io::{AsyncReadExt, Result}; /// use tokio::io::{AsyncReadExt, Result};
/// use tokio_util::io::StreamReader; /// use tokio_util::io::StreamReader;
/// # #[tokio::main] /// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> { /// # async fn main() -> std::io::Result<()> {
/// ///
/// // Create a stream from an iterator. /// // Create a stream from an iterator.
@@ -50,7 +51,7 @@ pin_project! {
/// # } /// # }
/// ``` /// ```
/// ///
/// If the stream produces errors which are not [std::io::Error], /// If the stream produces errors which are not [`std::io::Error`],
/// the errors can be converted using [`StreamExt`] to map each /// the errors can be converted using [`StreamExt`] to map each
/// element. /// element.
/// ///
@@ -59,7 +60,7 @@ pin_project! {
/// use tokio::io::AsyncReadExt; /// use tokio::io::AsyncReadExt;
/// use tokio_util::io::StreamReader; /// use tokio_util::io::StreamReader;
/// use tokio_stream::StreamExt; /// use tokio_stream::StreamExt;
/// # #[tokio::main] /// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> { /// # async fn main() -> std::io::Result<()> {
/// ///
/// // Create a stream from an iterator, including an error. /// // Create a stream from an iterator, including an error.
@@ -98,17 +99,65 @@ pin_project! {
/// # } /// # }
/// ``` /// ```
/// ///
/// Using the [`AsyncBufRead`] impl, you can read a `Stream` of byte chunks
/// line-by-line. Note that you will usually also need to convert the error
/// type when doing this. See the second example for an explanation of how
/// to do this.
///
/// ```
/// use tokio::io::{Result, AsyncBufReadExt};
/// use tokio_util::io::StreamReader;
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> std::io::Result<()> {
///
/// // Create a stream of byte chunks.
/// let stream = tokio_stream::iter(vec![
/// Result::Ok(b"The first line.\n".as_slice()),
/// Result::Ok(b"The second line.".as_slice()),
/// Result::Ok(b"\nThe third".as_slice()),
/// Result::Ok(b" line.\nThe fourth line.\nThe fifth line.\n".as_slice()),
/// ]);
///
/// // Convert it to an AsyncRead.
/// let mut read = StreamReader::new(stream);
///
/// // Loop through the lines from the `StreamReader`.
/// let mut line = String::new();
/// let mut lines = Vec::new();
/// loop {
/// line.clear();
/// let len = read.read_line(&mut line).await?;
/// if len == 0 { break; }
/// lines.push(line.clone());
/// }
///
/// // Verify that we got the lines we expected.
/// assert_eq!(
/// lines,
/// vec![
/// "The first line.\n",
/// "The second line.\n",
/// "The third line.\n",
/// "The fourth line.\n",
/// "The fifth line.\n",
/// ]
/// );
/// # Ok(())
/// # }
/// ```
///
/// [`AsyncRead`]: tokio::io::AsyncRead /// [`AsyncRead`]: tokio::io::AsyncRead
/// [`AsyncBufRead`]: tokio::io::AsyncBufRead
/// [`Stream`]: futures_core::Stream /// [`Stream`]: futures_core::Stream
/// [`ReaderStream`]: crate::io::ReaderStream /// [`ReaderStream`]: crate::io::ReaderStream
/// [`StreamExt`]: tokio_stream::StreamExt /// [`StreamExt`]: https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html
#[derive(Debug)] #[derive(Debug)]
pub struct StreamReader<S, B> { pub struct StreamReader<S, B> {
#[pin] // This field is pinned.
inner: S, inner: S,
// This field is not pinned.
chunk: Option<B>, chunk: Option<B>,
} }
}
impl<S, B, E> StreamReader<S, B> impl<S, B, E> StreamReader<S, B>
where where
@@ -250,3 +299,28 @@ where
} }
} }
} }
// The code below is a manual expansion of the code that pin-project-lite would
// generate. This is done because pin-project-lite fails by hitting the recusion
// limit on this struct. (Every line of documentation is handled recursively by
// the macro.)
impl<S: Unpin, B> Unpin for StreamReader<S, B> {}
struct StreamReaderProject<'a, S, B> {
inner: Pin<&'a mut S>,
chunk: &'a mut Option<B>,
}
impl<S, B> StreamReader<S, B> {
#[inline]
fn project(self: Pin<&mut Self>) -> StreamReaderProject<'_, S, B> {
// SAFETY: We define that only `inner` should be pinned when `Self` is
// and have an appropriate `impl Unpin` for this.
let me = unsafe { Pin::into_inner_unchecked(self) };
StreamReaderProject {
inner: unsafe { Pin::new_unchecked(&mut me.inner) },
chunk: &mut me.chunk,
}
}
}