f317d87ee6 io: Enable "alloc" from "std" (Tobin C. Harding)
1d00d47b32 io: Add Changelog (Tobin C. Harding)
83397c465c io: Add documentation to all public types and functions (Tobin C. Harding)
2810b08b0d io: Add code comment to feature gate (Tobin C. Harding)
4cf2bf4b40 io: Make Take::read_to_end public (Tobin C. Harding)

Pull request description:

  Do some cleanups to the new `io` crate.

  - Make `Take::read_to_end` public
  - Add CI script
  - Add documentation
  - Add changelog

ACKs for top commit:
  apoelstra:
    ACK f317d87ee6
  Kixunil:
    ACK f317d87ee6

Tree-SHA512: 6c7bc0d629a8995d985f8d8a245579ecdac6d0c10fa885c9a3550cc313a933c05ba087340fa3a638a9b631998157f86d439d17e56208f2457ee9ada9741f203d
This commit is contained in:
Andrew Poelstra 2024-02-07 20:01:28 +00:00
commit 814b72779f
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
3 changed files with 70 additions and 9 deletions

15
io/CHANGELOG.md Normal file
View File

@ -0,0 +1,15 @@
# 0.1 - Initial Release - 2023-01-18
Create the `io` crate, add basic I/O traits, types, and implementations.
Traits:
- `Read`
- `BufRead`
- `Write`
Types:
- `Take`
- `Cursor`
- `Sink`

View File

@ -2,6 +2,7 @@
use alloc::boxed::Box; use alloc::boxed::Box;
use core::fmt::{Debug, Display, Formatter}; use core::fmt::{Debug, Display, Formatter};
/// The `io` crate error type.
#[derive(Debug)] #[derive(Debug)]
pub struct Error { pub struct Error {
kind: ErrorKind, kind: ErrorKind,
@ -13,6 +14,7 @@ pub struct Error {
} }
impl Error { impl Error {
/// Creates a new I/O error.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn new<E>(kind: ErrorKind, error: E) -> Error pub fn new<E>(kind: ErrorKind, error: E) -> Error
where where
@ -21,18 +23,22 @@ impl Error {
Self { kind, error: Some(error.into()) } Self { kind, error: Some(error.into()) }
} }
/// Creates a new I/O error.
#[cfg(all(feature = "alloc", not(feature = "std")))] #[cfg(all(feature = "alloc", not(feature = "std")))]
pub fn new<E: sealed::IntoBoxDynDebug>(kind: ErrorKind, error: E) -> Error { pub fn new<E: sealed::IntoBoxDynDebug>(kind: ErrorKind, error: E) -> Error {
Self { kind, error: Some(error.into()) } Self { kind, error: Some(error.into()) }
} }
/// Returns the error kind for this error.
pub fn kind(&self) -> ErrorKind { self.kind } pub fn kind(&self) -> ErrorKind { self.kind }
/// Returns a reference to this error.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn get_ref(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> { pub fn get_ref(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
self.error.as_deref() self.error.as_deref()
} }
/// Returns a reference to this error.
#[cfg(all(feature = "alloc", not(feature = "std")))] #[cfg(all(feature = "alloc", not(feature = "std")))]
pub fn get_ref(&self) -> Option<&(dyn Debug + Send + Sync + 'static)> { self.error.as_deref() } pub fn get_ref(&self) -> Option<&(dyn Debug + Send + Sync + 'static)> { self.error.as_deref() }
} }
@ -97,15 +103,17 @@ impl From<Error> for std::io::Error {
} }
macro_rules! define_errorkind { macro_rules! define_errorkind {
($($kind: ident),*) => { ($($(#[$($attr:tt)*])* $kind:ident),*) => {
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
/// A minimal subset of [`std::io::ErrorKind`] which is used for [`Error`]. Note that, as with /// A minimal subset of [`std::io::ErrorKind`] which is used for [`Error`]. Note that, as with
/// [`std::io`], only [`Self::Interrupted`] has defined semantics in this crate, all other /// [`std::io`], only [`Self::Interrupted`] has defined semantics in this crate, all other
/// variants are provided here only to provide higher-fidelity conversions to and from /// variants are provided here only to provide higher-fidelity conversions to and from
/// [`std::io::Error`]. /// [`std::io::Error`].
pub enum ErrorKind { pub enum ErrorKind {
$($kind),* $(
$(#[$($attr)*])*
$kind
),*
} }
impl ErrorKind { impl ErrorKind {
@ -134,24 +142,42 @@ macro_rules! define_errorkind {
} }
define_errorkind!( define_errorkind!(
/// An entity was not found, often a file.
NotFound, NotFound,
/// The operation lacked the necessary privileges to complete.
PermissionDenied, PermissionDenied,
/// The connection was refused by the remote server.
ConnectionRefused, ConnectionRefused,
/// The connection was reset by the remote server.
ConnectionReset, ConnectionReset,
/// The connection was aborted (terminated) by the remote server.
ConnectionAborted, ConnectionAborted,
/// The network operation failed because it was not connected yet.
NotConnected, NotConnected,
/// A socket address could not be bound because the address is already in use elsewhere.
AddrInUse, AddrInUse,
/// A nonexistent interface was requested or the requested address was not local.
AddrNotAvailable, AddrNotAvailable,
/// The operation failed because a pipe was closed.
BrokenPipe, BrokenPipe,
/// An entity already exists, often a file.
AlreadyExists, AlreadyExists,
/// The operation needs to block to complete, but the blocking operation was requested to not occur.
WouldBlock, WouldBlock,
/// A parameter was incorrect.
InvalidInput, InvalidInput,
/// Data not valid for the operation were encountered.
InvalidData, InvalidData,
/// The I/O operations timeout expired, causing it to be canceled.
TimedOut, TimedOut,
/// An error returned when an operation could not be completed because a call to `write` returned `Ok(0)`.
WriteZero, WriteZero,
/// This operation was interrupted.
Interrupted, Interrupted,
/// An error returned when an operation could not be completed because an “end of file” was reached prematurely.
UnexpectedEof, UnexpectedEof,
// Note: Any time we bump the MSRV any new error kinds should be added here! // Note: Any time we bump the MSRV any new error kinds should be added here!
/// A custom error that does not fall under any other I/O error kind
Other Other
); );

View File

@ -14,7 +14,7 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
// Coding conventions. // Coding conventions.
// #![warn(missing_docs)] #![warn(missing_docs)]
// Exclude lints we don't think are valuable. // Exclude lints we don't think are valuable.
#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
@ -34,12 +34,15 @@ use core::convert::TryInto;
#[rustfmt::skip] // Keep public re-exports separate. #[rustfmt::skip] // Keep public re-exports separate.
pub use self::error::{Error, ErrorKind}; pub use self::error::{Error, ErrorKind};
/// Result type returned by functions in this crate.
pub type Result<T> = core::result::Result<T, Error>; pub type Result<T> = core::result::Result<T, Error>;
/// A generic trait describing an input stream. See [`std::io::Read`] for more info. /// A generic trait describing an input stream. See [`std::io::Read`] for more info.
pub trait Read { pub trait Read {
/// Reads bytes from source into `buf`.
fn read(&mut self, buf: &mut [u8]) -> Result<usize>; fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
/// Reads bytes from source until `buf` is full.
#[inline] #[inline]
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> { fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> {
while !buf.is_empty() { while !buf.is_empty() {
@ -53,6 +56,7 @@ pub trait Read {
Ok(()) Ok(())
} }
/// Creates an adapter which will read at most `limit` bytes.
#[inline] #[inline]
fn take(&mut self, limit: u64) -> Take<Self> { Take { reader: self, remaining: limit } } fn take(&mut self, limit: u64) -> Take<Self> { Take { reader: self, remaining: limit } }
@ -63,7 +67,7 @@ pub trait Read {
/// ///
/// Similar to `std::io::Read::read_to_end` but with the DOS protection. /// Similar to `std::io::Read::read_to_end` but with the DOS protection.
#[doc(alias = "read_to_end")] #[doc(alias = "read_to_end")]
#[cfg(any(feature = "alloc", feature = "std"))] #[cfg(feature = "alloc")]
#[inline] #[inline]
fn read_to_limit(&mut self, buf: &mut Vec<u8>, limit: u64) -> Result<usize> { fn read_to_limit(&mut self, buf: &mut Vec<u8>, limit: u64) -> Result<usize> {
self.take(limit).read_to_end(buf) self.take(limit).read_to_end(buf)
@ -83,15 +87,19 @@ pub trait BufRead: Read {
fn consume(&mut self, amount: usize); fn consume(&mut self, amount: usize);
} }
/// Reader adapter which limits the bytes read from an underlying reader.
///
/// Created by calling `[Read::take]`.
pub struct Take<'a, R: Read + ?Sized> { pub struct Take<'a, R: Read + ?Sized> {
reader: &'a mut R, reader: &'a mut R,
remaining: u64, remaining: u64,
} }
impl<'a, R: Read + ?Sized> Take<'a, R> { impl<'a, R: Read + ?Sized> Take<'a, R> {
#[cfg(any(feature = "alloc", feature = "std"))] /// Reads all bytes until EOF from the underlying reader into `buf`.
#[cfg(feature = "alloc")]
#[inline] #[inline]
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> { pub fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
let mut read: usize = 0; let mut read: usize = 0;
let mut chunk = [0u8; 64]; let mut chunk = [0u8; 64];
loop { loop {
@ -160,7 +168,7 @@ impl<R: std::io::BufRead + Read + ?Sized> BufRead for R {
fn consume(&mut self, amount: usize) { std::io::BufRead::consume(self, amount) } fn consume(&mut self, amount: usize) { std::io::BufRead::consume(self, amount) }
} }
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))] // Conflicts with blanket impl when "std" is enabled.
impl Read for &[u8] { impl Read for &[u8] {
#[inline] #[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize> { fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
@ -171,7 +179,7 @@ impl Read for &[u8] {
} }
} }
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))] // Conflicts with blanket impl when "std" is enabled.
impl BufRead for &[u8] { impl BufRead for &[u8] {
#[inline] #[inline]
fn fill_buf(&mut self) -> Result<&[u8]> { Ok(self) } fn fill_buf(&mut self) -> Result<&[u8]> { Ok(self) }
@ -181,18 +189,24 @@ impl BufRead for &[u8] {
fn consume(&mut self, amount: usize) { *self = &self[amount..] } fn consume(&mut self, amount: usize) { *self = &self[amount..] }
} }
/// Wraps an in memory reader providing the `position` function.
pub struct Cursor<T> { pub struct Cursor<T> {
inner: T, inner: T,
pos: u64, pos: u64,
} }
impl<T: AsRef<[u8]>> Cursor<T> { impl<T: AsRef<[u8]>> Cursor<T> {
/// Creates a `Cursor` by wrapping `inner`.
#[inline] #[inline]
pub fn new(inner: T) -> Self { Cursor { inner, pos: 0 } } pub fn new(inner: T) -> Self { Cursor { inner, pos: 0 } }
/// Returns the position read up to thus far.
#[inline] #[inline]
pub fn position(&self) -> u64 { self.pos } pub fn position(&self) -> u64 { self.pos }
/// Returns the inner buffer.
///
/// This is the whole wrapped buffer, including the bytes already read.
#[inline] #[inline]
pub fn into_inner(self) -> T { self.inner } pub fn into_inner(self) -> T { self.inner }
} }
@ -226,10 +240,14 @@ impl<T: AsRef<[u8]>> BufRead for Cursor<T> {
/// A generic trait describing an output stream. See [`std::io::Write`] for more info. /// A generic trait describing an output stream. See [`std::io::Write`] for more info.
pub trait Write { pub trait Write {
/// Writes `buf` into this writer, returning how many bytes were written.
fn write(&mut self, buf: &[u8]) -> Result<usize>; fn write(&mut self, buf: &[u8]) -> Result<usize>;
/// Flushes this output stream, ensuring that all intermediately buffered contents
/// reach their destination.
fn flush(&mut self) -> Result<()>; fn flush(&mut self) -> Result<()>;
/// Attempts to write an entire buffer into this writer.
#[inline] #[inline]
fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { fn write_all(&mut self, mut buf: &[u8]) -> Result<()> {
while !buf.is_empty() { while !buf.is_empty() {
@ -282,6 +300,8 @@ impl<'a> Write for &'a mut [u8] {
} }
/// A sink to which all writes succeed. See [`std::io::Sink`] for more info. /// A sink to which all writes succeed. See [`std::io::Sink`] for more info.
///
/// Created using `io::sink()`.
pub struct Sink; pub struct Sink;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]