Git Product home page Git Product logo

genio's Introduction

Generic IO

A type safe, low level replacement for std::io.

Supports no_std for embedded development, just disable cargo feature std.

Important

The development of this crate stalled for a while but there's an effort to revive and redesign it. Do not expect the API to stay like this, the hange will be big. Especially regarding uninitialized buffers which are unsound in the current version. I'll be happy to receive feedback on the redesign!

Motivation

The IO routines you can find in standard library are very useful. However, because they use io::Error as the only error type they suffer these problems:

  • It's impossible to express infallible operations (e.g. in-memory operations) in types.
  • If you know the operation can't fail, you still have to call unwrap(). If you did mistake you get runtime error.
  • Compiler has to insert check (compare and branch), which slows down the code. Of course, it can be eliminated by inlining and optimization but it's not sure thing.
  • io::Error may allocate, which makes not just slower but prevents usage on bare metal.
  • io::Error is very broad and in some contexts it can have nonsense values (e.g. full disk when doing read()). Perfect program should not ignore these values but it's difficult to decide what to do about them.
  • You can reasonably decide what to do with error only using ErrorKind. That way some information may be lost.
  • The io::Error propagates to other interfaces and contaminates them. For example protobuf parser "could" return it even in case it's reading memory. Tokio-core uses io::Error everywhere.
  • Sometimes it is hard to tell from signature what exactly can fail and why. E.g. do you know why tokio_core::reactor::Core::new() may fail and what to do with the error?

The aim of genio is to enable better exploitation of Rust's type system to solve these problems. It steals many ideas from std::io with one important difference: Read and Write traits can define their own errors. There's also bunch of tools for handling errors as well as glue for std::io.

Since everything that impls genio::{Read, Write} can trivially impl std::io::{Read, Write}, it's better to write algorithms just for genio and let users wrap them in glue.

Contributing

This crate is certainly not finished project and it needs more work to perfectly work with std::io. It'd also be beneficial to implement additional algorithms, wrappers and tools. Finally, other crates should start using genio. I invite everyone to help with this crate, to be as good as it can be.

I'm open to every PR you can imagine.

Status

This crate is considered unstable, although mandatory types and methods in Read and Write traits are unlikely to change. Currently, there are only traits for synchronous streams. Please, if your type is not synchronous stream, don't implement these traits, but help designing other traits.

Planned:

  • Synchronous streams
  • Asynchronous streams
  • Synchronous message streams with known message size (usually in the header of message)
  • Synchronous message streams with unknown size (usually using some delimiter)
  • Asynchronous message streams with known message size (usually in the header of message)
  • Asynchronous message streams with unknown size (usually using some delimiter)
  • Sound impls for primitive types
  • Sound impls for std::io types
  • Integrate with partial-io

And of course, appropriate combinators for all of them.

Differences between genio and std::io

There are other differences than just associated error types. Most importantly, genio aims to implement thinner wrappers and layers. For example, it doesn't handle EINTR automatically, but requires you to handle it yourself - for example, using Restarting wrapper. This gives you better control of what's going on in the code and also simplifies the implementation of the wrappers.

Some operations use slightly different types. read_exact may return UnexpectedEnd in addition to lower-level error type. Chain combines two error types. read_to_end enables to use any type which can be extended from reader. Flushing can return different error than write(). If there's nothing to flush, it can return Void type.

In addition, read() may hint that there are not enough bytes (usable to skip reading in error cases) and Write may be hinted how many bytes will be written. (This is unstable yet.)

Blanket impls

There are intentionally no blanket impls of genio traits for std::io or vice versa. This is to enable people to implement both traits. If you use std::io traits in your crates, you can add support for genio without fear. Please, do not impl genio traits with associated types being io::Error. If you don't have time to write full-featured impls, stick to glue wrappers. Don't waste opportunity to implement genio the best way!

Disclaimer

I highly appreciate all the hard work Rust developers did to create std::io. The genio crate isn't meant to bash them, judge them, etc. They likely didn't have enough time to implement something like this or feared it'd be too complicated. The aim of this crate is to improve the world by providing tool for people who want more generic IO.

genio's People

Contributors

adelarsq avatar gguoss avatar kaidokert avatar kixunil avatar rozbb avatar xfoxfu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

genio's Issues

Async IO combinators

Async IO combinators would allow us to turn async reads/writes into std::Future.

#[derive(Debug)] for Error types?

Is there any reason genio error types shouldn't derive the Debug trait?

I'm trying to write my own implementation of Cursor for (eventually) an embedded project. I would like to be able to call cursor.read_exact(&mut buffer) and then unwrap the result to check for errors. But unwrap requires an implementation of Debug.

When I try to run this code:

  let mut buf = [0; 5];
  let mut cursor = Cursor::new(vec![1u8, 2u8, 3u8]);
  cursor.read_exact(&mut buf).unwrap();

I get this error:

no method named `unwrap` found for type `std::result::Result<(), genio::error::ReadExactError<void::Void>>` in the current scope

note: the method `unwrap` exists but the following trait bounds were not satisfied: genio::error::ReadExactError<void::Void> : std::fmt::Debug

Alternative impls for std::fs and std::net

These should be separate crates, but they are tracked here. Apart from the obvious benefit of providing idiomatic implementations of genio they will make it easier to evaluate the new API.

  • fs
  • net
  • async net

Support vectored IO

Vectored IO (AKA gather/scatter) allows for better optimizations by performing IO on multiple buffers in a single syscall.

This is currently also supported by std but in somewhat deficient manner: it doesn't use MaybeUninit for reading and falls back to default implementation, causing performance drop if one forgets to override the default method. This is mainly caused by backwards compatibility requirement. Thankfully, we can do better in case of genio.

Crate redesign

Rust language made a lot of progress since this crate was created and it'd be great if this crate caught up.

Specifically:

  • MaybeUninit<[u8]> is the right way of dealing with possibly uninitialized buffers
  • Vectored IO is supported by std
  • async is becoming very popular

std design and anything that copies it has several limitations that this crate could attempt to solve (by not trying hard to follow std design):

  • Read is safe and takes &mut [u8]. We already have unsafe version but it seems that having a safe Read with read taking some special, safe type is a better approach. It avoids many issues, including forgetting to specialize the implementation and having more unsafe than necessary.
  • Vectored IO is an afterthought and suffers from similar problems that read does. Even weirdly, some vectored impls (e.g. TLS) make programs slower, not faster. While it might be a better design if the impls were fixed, it could be fundamentally impossible. (Not sure.) The resulting design should somehow take this into account.
  • AsyncRead and AsyncWrite traits don't exist in std and there was confusion where they belong. Previously, the design even didn't use such traits which was quite a footgun. We can do better by encoding Pending in the return type, but this needs to be researched to work correctly with Interrupted.
  • The Read and Write traits don't have a way to self-describe their implementor which makes declaring errors that contain this information complicated.

Rough proposal:

// buffer containing IO slices
// Note that conversion to low-level representation is done by the implementor.
trait IOSliceBuf {
    fn push<'a>(&'a mut self, &'a mut OutSlice<u8>);
}

unsafe trait Buffer {
    fn get_mut(&mut self) -> &mut OutSlice<u8>;
    unsafe fn advance(&mut self, len: usize);
    fn fill_io_slice<'a>(&'a mut self, &'a mut dyn IoSliceBuf);

    // provided impls:
    fn try_push(&mut self, byte: u8) -> bool { /*...*/ }
    fn try_push_slice(&mut self, &[u8]) -> bool { /*...*/ }
    // push as many bytes as possible, return the remaining slice
    fn push_slice_min<'a>(&mut self, &'a [u8]) -> &'a [u8] { /*...*/ }
    fn zeroed(&mut self) -> &mut [u8];
}

// Separated so it can be shared between Read and Write
trait ObjectDescription {
    type Description;

    fn describe(&self) -> Self::Description;
}

unsafe trait Read: ObjectDescription {
    type Error: fmt::Display + fmt::Debug;

    // Implemented by the implementor
    // we don't return the number of bytes because it's reflected in the `Buffer`
    fn read_impl(&mut self, buffer: &mut dyn Buffer) -> Result<(), Self::Error>;

    fn read(&mut self, buffer: &mut Buffer) -> Result<(), Error<Self::Description, Self::Error>> { /* ... */ }

   // other helpers like read_exact, etc
}

// Any reader that returns this error can be converted to `Future`
enum AsyncError<E> {
    Pending,
    Other(E),
}

struct Error<D, E> {
    object: D,
    error: E
}

Of course, dyn is sketchy and ideally we don't want it.

Dynamic traits

Make it possible to use genio in dynamic contexts using helpers.

Async IO

I believe the best path to async IO is to have Error types classifiable just like in the case of EINTR. This should be sufficient - AsyncRead and AsyncWrite like in case of tokio shoudn't be needed.

Add tests

Add tests for most important combinators.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.