-
Notifications
You must be signed in to change notification settings - Fork 18
Move std::io::Error
out of std
.
#11
Comments
@indolering are you looking to also avoid having it bring in a dependency on |
I am super unqualified to answer that question, but the aforementioned ticket and many of the tickets linking to it address that question. I just know that this is on a lot of wishlists and didn't see it covered in the Zulip chat or other tickets. |
I'd love to see this but there are 2 problems I currently see:
solving the first one will probably require a backwards incompatible breakage, and solving the second one might not be possible |
It also has a Box internally... |
only in the impl of these two though |
The type |
Wondering if this could maybee be worked around the way panic does with |
Cross referencing:
|
I am trying to move std::io into alloc:io, Error is the most fundenmental peace of code, any prototype design of core::Error are appeared? |
I think |
I've raised a few questions if something like |
This is the right place. And to answer your question, I don't know if |
That's unfortunate. For all of the revisions one might wish for in a hypothetical Rust 2.0 ... consistent error handling would be high up on the list. |
std::io::Error
to core.std::io::Error
out of std
.
Updated name to include migration to Cross referencing Yaahallo's post post on Reddit:
|
I encountered this situation and plan to roughly copy/paste the library implementation, removing the bits about heap allocation. That lead me to an idea that has probably already been considered, but just wanted to check in... Currently pub struct Error {
repr: Repr,
}
enum Repr {
Os(i32),
Simple(ErrorKind),
// &str is a fat pointer, but &&str is a thin pointer.
SimpleMessage(ErrorKind, &'static &'static str),
Custom(Box<Custom>),
}
struct Custom {
kind: ErrorKind,
error: Box<dyn error::Error + Send + Sync>,
} Would it be feasible to make a
pub struct Error {
repr: Repr,
}
enum Repr {
Os(i32),
Simple(ErrorKind),
// &str is a fat pointer, but &&str is a thin pointer.
SimpleMessage(ErrorKind, &'static &'static str),
}
pub struct Error {
repr: Repr,
}
enum Repr {
Core(core::io::Error),
Custom(Box<Custom>),
} This may be naïve on my part, but I think UX would be fairly seamless if there is a conversion from impl From<core::io::Error> for Error {
#[inline]
fn from(err: core::io::Error) -> Error {
Error { repr: Repr::Core(err) }
}
} |
I don't think the wrapping you described would work because of the way that the cc @thomcc |
In #20 I proposed As
All this runs into rust-lang/rust#46139 (comment) though |
Yeah, I see how |
ISTM like there's two issues here:
Personally, I feel like the first of these is more useful. The approach I've wanted for a long time is something like: // Inside mod core::io
pub trait Read {
pub type Error;
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;
// ... And so on
} And then
A downside is that would mean
This last bit brings up a point of open design work (there's certainly more), since some way of using a For moving All that said, I'm not set in stone here, and could be convinced by a sufficiently well-reasoned RFC, but... it should have an overall design for both the traits and errors, etc. EDIT: I just realized that this is the project-error-handling repo. I suppose discusion of the IO traits may be considered off-topic. IMO it's worth considering though, because many of the possible designs for the |
Just an FYI perhaps the |
Your read trait is not object safe @thomcc which maybe acceptable using I think Nico's |
It's object safe for concrete error types. (e.g.
This is indeed the big downside of the design, but it makes the trait usable in far more situations. Making it object-safe requires picking a concrete error type which IMO would significantly limit the usefulness of general-purpose system-independent read/write traits. I'm unsure what you mean about the error propagation. It should be fine. |
Yes, you could concertize the trait in some dependency via features. Any truly generic dependencies would then avoid It's actually not so bad I think. Ideally folks would implement both this and |
Also, I think the concrete error type is actually a feature -- I've had many cases where something implements Read/Write so that it can be used generically, but std::io::Error will always represent a library error type, rather than a system error type. For example, FWIW, I also have had a number of cases where I've wanted to have io traits which could not accept a liballoc dependency, and there's nothing fundamental about the traits that should require allocation, aside from holding custom user error types. I pretty strongly feel that attempting to address io::Traits in libcore by just copying them (including io::Error) wholesale is... honestly, a bad the way to go about this. While I suppose there are reasons to want a subset of io::Error's functionality available to no_std code, most of the time I've heard folks suggest moving io::Error it's been for the traits, and I think that really needs a more considered design.
I mocked up what I mean by this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f1ae589b585384c260bd44fe9be2a0a9. You could imagine doing this with Footnotes
|
Could you sketch a bit of how you'd write code that was generic over fn parse<E>(reader: &mut impl CoreIoRead<Error = E>) -> Result<ParsedData, ???> {
let mut data = [0; 1];
match reader.read(&mut data[..]) {
Err(e) => return Err(e),
Some(0) => return Err(MyError::UnexpectedEndOfInput);
_ => { ... }
}
} |
My intuition is that in cases like this you'd want to further bound the error type to allow conversions fn parse<E>(reader: &mut impl CoreIoRead<Error = E>) -> Result<ParsedData, ???>
where
E: From<MyError>,
{
let mut data = [0; 1];
match reader.read(&mut data[..]) {
Err(e) => return Err(e),
Some(0) => Err(MyError::UnexpectedEndOfInput)?;
_ => { ... }
}
} |
Looks like @burdges , going off Niko's example of @thomcc I am very much interested in having a To give everyone my concrete use case, I want to make some |
The more I think about this, the more uncertain I feel about this issue. Assuming that we can diverge heavily from |
Yes,
|
I have tried the The first approach I attempted in a fork of pub trait CoreIoError: Sized {
/// An operation could not be completed because a call to `poll_write`
/// returned `Ok(Async::Ready(0))`.
fn write_zero(msg: &'static str) -> Self;
/// An operation could not be completed because an "end of file" was
/// reached prematurely.
fn unexpected_eof(msg: &'static str) -> Self;
}
pub trait CoreAsyncRead {
type Error: CoreIoError;
} That makes the builtin utilities like The second approach I attempted in #[derive(Debug)]
pub enum Error<T> {
UnexpectedEof,
Other(T),
}
pub fn read_exact<R: Read>(reader: R, buf: &mut [u8]) -> Result<(), Error<R::Error>> { ... } That is consistent with how other utilities would have to be defined, but has composition issues. If you have a function calling both |
can we just make it depend on alloc? will making it depend on alloc make any other problems than need of adding alloc to dependency? later we can make it unable to wrap Box with io::Error if alloc isn't present (if that's the best we can do) also, while there's many crates that implements similar module as std::io, it's unusable because it doesn't provides compatibility with other crates that use std::io |
Arkworks' wraps core/alloc/std inside its own This actually works, unlike associated Error types, ala https://github.com/QuiltOS/core-io/ It does however create a walled garden, in which your ecosystem's code compiles cleanly without std, but you need shims for other ecosystem's code. The solution is probably for people who want |
Are there people interested into me sending a PR to move this outside of |
I don't know what other non-alloc scenarios we can expect, but embedded development is where it becomes a major issue for me. Even, when I do have an allocator, unless I am dying without it, I am not going to use it. In embedded world, what I have felt is that standard errors defined by POSIX are lacking due all kinds of possible failure reasons. I am not sure what issues @Nemo157 faced, but I am in favor of Again I am not sure if there are any other non-alloc use cases, but these are my two cents |
A big non-alloc case has already been made for being able to use @RaitoBezarius, what was your plan for the PR? If I knew your solutions to the two outstaning problems, I could try to make a PR myself. |
My plan was to move it in |
What was your plan to remove reliance on |
I imagine that it would be reasonable to have associated types or perform some form of Boxing. I would say the usecase I am interested in are usually pre-OS environments, e.g. bootloaders/UEFI/etc. Those have clearly reasonable system errors semantics. Obviously, some folks may be interested into more customized mechanisms while having "zero cost" (e.g. as few as possible allocations), I don't think those folks can avoid for the time being rolling their own solutions, especially given that we are in some sort of paralysis analysis and I assume that catering to the group who can benefit from POSIX-style IO errors semantics can already move the needle to look into how to cater to everyone? |
"associated types" seems to imply something trait-related. So far, traits are not present in |
Note that we avoid. It's worth keeping in mind that some workloads have a lot of IO errors (consider a C compiler which tries to open a bunch of files along the include path), so pessimizing the error case is pretty undesirable. |
Exactly, so Box/dyn seems unnecessary. On the other hand, generics could work, or we could honestly just use |
I don't like the idea of using I still think this feels more flexible and elegant /// core crate
pub mod io {
pub trait Write {
type Error: core::error::Error;
...
}
pub trait Read {
type Error: core::error::Error;
...
}
pub trait Cursor {
type Error: core::error::Error;
...
}
} /// alloc crate
pub mod io {
struct Error { // Current Error implementation
}
pub type Write = core::io::Write<Error = Error>;
pub type Read = core::io::Read<Error = Error>;
pub type Cursor = core::io::Cursor<Error = Error>;
} This allows us to use existing |
@adityashah1212 in your design, what would the default implementation of fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
while !buf.is_empty() {
match this.read(buf) {
Ok(0) => break,
Ok(n) => {
let tmp = buf;
buf = &mut tmp[n..];
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
if !buf.is_empty() {
Err(error::const_io_error!(ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
} else {
Ok(())
}
} |
How about something like this? Again, I am really not an expert on this, so just trying pitch ideas /// core crate
pub mod io {
#[non_exhaustive] // To ensure we can add more errors as the trait implementation changes... Not sure of this one though
enum ErrorKind {
// Separate or limited version of current ErrorKind, since a lot of those errors don't make much sense in case of no_std
}
pub trait Write {
type Error: core::error::Error + From<ErrorKind>;
...
}
pub trait Read {
type Error: core::error::Error + From<ErrorKind>;
...
}
pub trait Seek {
type Error: core::error::Error + From<ErrorKind>;
...
}
} /// alloc crate
pub mod io {
struct Error { // Current Error implementation
}
impl From<core::io::ErrorKind> for Error {
...
}
pub type Write = core::io::Write<Error = Error>;
pub type Read = core::io::Read<Error = Error>;
pub type Seek = core::io::Seek<Error = Error>;
} Edit: I am an idiot for confusing |
You'd need to be able to pass an error message when creating the Error and have something like (Note: I have no authority in the Rust project. This is just my own speculation.) |
I nerd-sniped myself into writing a mostly-working implementation of moving I am unlikely to push it through merging as I don't actually have that much time, so whoever wants to can take my code and base their own PR off it. |
@fintelia, Looking at the current code, I think all we need is what you corrected on top of what I suggested. Using |
Let me correct what I said earlier. /// core crate
pub mod io {
enum ErrorKind {
// Current Error Kind
}
pub trait AsErrorKind {
fn kind(&self) -> ErrorKind;
}
pub trait Write {
type Error: AsErrorKind;
...
}
pub trait Read {
type Error: AsErrorKind;
...
}
pub trait Seek {
type Error: AsErrorKind;
...
}
} /// alloc crate
pub mod io {
struct Error { // Current Error implementation
}
impl core::io::AsErrorKind for Error {
fn kind(&self) -> ErrorKind {
Error::kind(&self)
}
}
pub type Write = core::io::Write<Error = Error>;
pub type Read = core::io::Read<Error = Error>;
pub type Seek = core::io::Seek<Error = Error>;
} |
i don't think there's reason to limit error type to trait Read {
type Error;
fn read(...) -> Result<usize, Self::Error>;
}
//slice for example
impl Read for &[u8] {
type Error: EofError;
...
}
struct SomeRandomOsIoStream;
impl Read for SomeRandomOsIoStream {
type Error = OsError;
...
}
//some wrapping reader example
enum ValidationError<T = Infallible> {
Invalid,
InnerError(T)
}
struct SomeValidationReader<T: Read, Error=ValidationError> where Error: From<ValidationError> + From<T::Error> (T);
impl Read for SomeValidationReader<T: Read, Error=ValidationErrorM<T::Error> where Error: From<ValidationError::<Infallible>> + From<T::Error> {
type Error = Error;
fn read(...) -> Result<usize, Self::Error> {
if(invalid) {
Err(ValidationError::Invalid)?;
}
Ok(self.0.read(...)?)
}
} by using default types we can avoid breaking changes as much as possible, |
It feels to me that the right answer here would be to do something similar to what Something along the lines of: #[os_error_provider]
fn custom_os_error_func() -> i32 {
//...
} If you don't use it then trying to use io::Error should throw a compiler error just like with alloc |
If that's all that's stopping |
Having
std::io::Error
permeates much ofstd::io
, blocking many crates from being used inno_std
environments or re-implementing functionality.I believe Using std::io::{Read, Write, Cursor} in a nostd environment is the canonical tracking issue.
I'm a Rust newbie 🐣, so my apologies if this ticket is not helpful.
The text was updated successfully, but these errors were encountered: