Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using std::io::{Read, Write, Cursor} in a nostd environment #48331

Open
roblabla opened this issue Feb 18, 2018 · 18 comments
Open

Using std::io::{Read, Write, Cursor} in a nostd environment #48331

roblabla opened this issue Feb 18, 2018 · 18 comments
Labels
A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` C-feature-request Category: A feature request, i.e: not implemented / a PR. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@roblabla
Copy link
Contributor

I'm surprised there's no ticket for this, so here we go.

It isn't possible in nostd to use std::io::Cursor. That seems to be mostly because it requires std::io::Read, Write, and Seek. However, I'd argue that those traits should also be part of libcore, and only the specific implementations be in libstd. After all, the Read/Write/Seek traits and the Cursor struct don't seem to rely on anything that's part of the stdlib: they don't need allocation, etc...

My use-case is, I have to write data into an array, and treating that array in a file would lead to much more idiomatic code. Furthermore it'd allow using crates like byteorder in nostd environment.

@retep998
Copy link
Member

The problem with this, which has been covered in previous issues, is that Read and Write have std::io::Error hardcoded in their method signatures which requires quite a bit of std machinery including the ability to allocate.

@roblabla
Copy link
Contributor Author

Do you have a link to previous issues ? I tried looking for it, but nothing came up :(.

@retep998
Copy link
Member

I... can't find the previous issues. Maybe it was just scattered comments over the place and IRC discussions...

@pietroalbini pietroalbini added C-enhancement Category: An issue proposing an enhancement or a PR with one. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. C-feature-request Category: A feature request, i.e: not implemented / a PR. and removed C-enhancement Category: An issue proposing an enhancement or a PR with one. labels Feb 20, 2018
@jD91mZM2
Copy link
Contributor

These traits being absent is the reason rust-core_io exists. Relibc would thank you if core::io became a thing, and I could then continue on my newly started quest to make more crates support no_std.

@burdges
Copy link

burdges commented Jan 3, 2020

Anything here requires the std::error::Error trait, which requires std::backtrace::Backtrace, but maybe all that only requires core+alloc. Assuming so..

We should explore replacing std::io::Error with some more flexible error type, vaguely inspired by the SmallError type suggested in rust-lang/rfcs#2820 (comment) that encodes the io::error::Repr enum with some vtable pointer.

In essence, it should act like Box<dyn Error+Send+Sync+'static> except small error types get passed by value.

We could maybe preserve mem::size_of::<SmallError>() being only twice mem::size_of::<usize>() too.

Also, I'm unsure if std::error::Error could feature gate the std::backtrace::Backtrace methods on alloc, but maybe. We must encode Drop inside the errorr::Error trait though, which I suspect either requires magic similar to Box, or else makes Drop a supertrait for errorr::Error. We'd wind up with an Error trait like

pub trait Error: Drop + Debug + Display {
    fn description(&self) -> &str { "description() is deprecated; use Display" }
    fn cause(&self) -> Option<&dyn Error> { self.source() }
    fn source(&self) -> Option<&(dyn Error + 'static)> { None }
    fn type_id(&self, _: private::Internal) -> TypeId where Self: 'static { TypeId::of::<Self>() }
    #[cfg(feature = "alloc")]
    fn backtrace(&self) -> Option<&Backtrace> { None }
}

We must then use wrapper types that handle drop correctly.

#[derive(Clone)]
pub struct BoxError {
    inner: Box<dyn Error+Send+Sync+'static>,
}
// delegate Deref+DerefMut+Drop to inner

impl Drop for BoxError {
    fn drop(&mut self) {
        self.deref_mut().drop()
    }
}

use core::mem;
use core::raw::TraitObject;

const SMALL_ERROR_SIZE : usize = mem::size_of::<usize>();

#[derive(Clone)]
pub struct SmallError {
    vtable: *mut (),
    data: [u8; SMALL_ERROR_SIZE],
}

impl Drop for SmallError {
    fn drop(&mut self) {
        self.deref_mut().drop()
    }
}
impl Send for SmallError {}
impl Sync for SmallError {}

impl SmallError {
    pub fn new<E: Error+Clone+Send+Sync>(err: E) -> SmallError 
    // where mem::size_of::<E>() <= SMALL_ERROR_SIZE  // sigh
    {
        assert!(mem::size_of::<E>() <= SMALL_ERROR_SIZE);
        let data = [0u8; SMALL_ERROR_SIZE],
        core::intrinsics::copy(&err as *const E, &mut data as *mut [u8; SMALL_ERROR_SIZE] as *mut E , 1);
        let vtable = unsafe { mem::transmute::<&mut dyn Error,TraitObject>(&mut dyn err).vtable };
        // mem::forget(err);
        SmallError { vtable, data }
    }
}

impl Deref for SmallError {
    type Target = dyn Error+Send+Sync;  // Should we add +Clone when mutli-trait objects exist
    fn deref(&self) -> &Self::Target {
        let SmallError { vtable, ref data } = self;
        mem::transmute::<TraitObject,&mut dyn Error>(TraitObject { vtable, data })
    }
}

impl DerefMut for SmallError {
    fn deref(&mut self) -> &mut Self::Target {
        let SmallError { vtable, ref data } = self;
        mem::transmute::<TraitObject,&mut dyn Error>(TraitObject { vtable, data })
    }
}

We then need some outer type that calls Drop for BoxError and SmallError, but forwards Error methods, like

pub struct DerefError(&dyn Deref<Target = dyn Error+Send+Sync+'static>+DerefMut+Drop)
impl Drop for DerefError {
    fn drop(&mut self) { self.drop() }
}
impl Error for DerefError {
    // delegate all methods to self.deref()
}

@roblabla
Copy link
Contributor Author

roblabla commented Jan 3, 2020

Backtrace cannot really exist outside std. It is highly dependent on OS features. For instance, to get function names (symbolication step of printing a backtrace), it requires opening the executable as a file and parsing it to access the debug information table, e.g. it requires doing open(argv[0]). Or it might require dlresolve or other kind of libc stuff.

In an ideal world, binaries compiled in no_std could just have an "empty" implementation of Backtraces. I don't think that's possible however, since libstd simply reexports everything in libcore verbatim. It's not possible for libstd to use a different implementation of things than libcore AFAICT.

Unfortunately, moving backtraces to be an exposed interface of std::error::Error seems to permanently kill any possibility of std::error and std::io being available in libcore.

@burdges
Copy link

burdges commented Jan 3, 2020

Could we replace &Backtrace with some opaque pub struct BacktraceRef(usize); type that exists libcore? We'd then provide inherent methods for that type only in std, assuming that flies with whatever magic.

@roblabla
Copy link
Contributor Author

roblabla commented Jan 3, 2020

Std cannot (currently) add inherent impls for structs it does not itself define. It plays by the same rule as the rest of the ecosystem when it comes to coherence, there's no magic going on here (modulo some escape hatches for fundamental types that don't apply here). Libstd can implement libstd traits on libcore structs, or implement libcore traits for libstd structs. But it can't add its own inherent impls for (libcore) structs, only the crate defining a type can do that.

The best I can figure out is changing the backtrace function to return a &'a dyn BacktraceTrait and do away entirely with the Backtrace structs (they'd be an implementation detail). This would prevent adding new functionality to backtraces (since they're now a trait that anyone can impl) but it means libcore's Error could return an empty backtrace impl.

Except that's also undesirable because now all the errors living in libcore won't get to use backtraces - only errors in libstd would use the "right" backtrace implementation.

Maybe that'd be fixable using the same mechanism global allocators use? Have some kind of backtrace factory, have libstd give out a default implementation, and ask libcore users to provide their own (probably stubbed) implementation.

@burdges
Copy link

burdges commented Jan 4, 2020

I suppose std cannot use features for dependencies like many crates do if we want std to ever actually be the standard library on some future operating system. In other words, if features are additive then std should always have all its features enabled.

I think &dyn BacktraceTrait sounds fine because std::backtrace::Backtrace indicates it'd resemble

trait BacktraceTrait : Debug + Display {
    fn status(&self) -> BacktraceStatus;
}

I still think BacktraceRef could be defined in core, but you define BacktraceTrait in std. Also we've several proposals for extensions like (a) doing inherent methods across distinguished crate boundaries, or (b) allowing similar opaque extern types like you gets from C header files, but your &dyn BacktraceTrait works well enough here I think.

As for implementation, there are several options here:

  • We could treat Error special with the trait object tricks I wrote above.
  • We could implement those same tricks via some proc macro, and some SmallBoxDyn type, so that other trait objects could benefit form a small Copy types being passed, and do not require even alloc, while larger types get boxed, requiring alloc.
  • We could do some SmallBox type that handled the forwarding via compiler magic for the forwarding vtables, but also worked without always passing through trait objects.

@burdges
Copy link

burdges commented Jan 21, 2020

I think anyhow::Error already provides no_std wrapper for std::io::Error. At present, it requires alloc but it looks like a reasonable starting point if we want to make this dependency optional.

We'd need to remove the Box in https://docs.rs/anyhow/1.0.26/src/anyhow/lib.rs.html#322-324 which increases the size from one to two usizes.

It appears anyhow::Error already manually implements its trait object internally, which I considered overkill but if they needed it then maybe my suggestions here also do.

We should reimplement the std::error::Error trait so that its new method does not depend upon std.

I think anyhow::Error addresses the Backtrace machinery by depending upon the backtrace crate, so maybe that already works no_std but requires alloc. We need a BacktraceTrait that avoids even needing alloc, as discussed above.

We should also remove the backtrace that anyhow::Error attaches https://docs.rs/anyhow/1.0.25/src/anyhow/error.rs.html#672 because it requires alloc. We could however permit layering one internally when using alloc via another internal wrapper type.

After all this we could then reimplement all traits from std::io and their async cousins. Appears doable although not sure until you try.

csnover added a commit to csnover/binread that referenced this issue Mar 9, 2021
This allows most implementers of `BinRead` to use std features
on the reader instead of restricting them to the subset of
features exposed by the no_std reimplementation. This also
appears to have been the original intent of the `binread::io`
module, per the module-level comment.

This is a breaking change because `iter_bytes` is called `bytes`
in std and consumes the reader, so this has been changed in the
binread implementation to conform to that API.

This commit also fixes the earlier binread implementations not
handling errors as std does; this is now fixed (by copying
from std).

(Hopefully rust-lang/rust#48331 will be addressed someday and then
this code can disappear entirely.)
csnover added a commit to csnover/binread that referenced this issue Mar 9, 2021
This allows most implementers of `BinRead` to use std features
on the reader instead of restricting them to the subset of
features exposed by the no_std reimplementation. This also
appears to have been the original intent of the `binread::io`
module, per the module-level comment.

This is a breaking change because `iter_bytes` is called `bytes`
in std and consumes the reader, so this has been changed in the
binread implementation to conform to that API.

This commit also fixes the earlier binread implementations not
handling errors as std does; this is now fixed (by copying
from std).

(Hopefully rust-lang/rust#48331 will be addressed someday and then
this code can disappear entirely.)
csnover added a commit to csnover/binread that referenced this issue Mar 9, 2021
This allows most implementers of `BinRead` to use std features
on the reader instead of restricting them to the subset of
features exposed by the no_std reimplementation. This also
appears to have been the original intent of the `binread::io`
module, per the module-level comment.

This is a breaking change because `iter_bytes` is called `bytes`
in std and consumes the reader, so this has been changed in the
binread implementation to conform to that API.

This commit also fixes the earlier binread implementations not
handling errors as std does; this is now fixed (by copying
from std).

(Hopefully rust-lang/rust#48331 will be addressed someday and then
this code can disappear entirely.)
csnover added a commit to csnover/binread that referenced this issue Mar 10, 2021
This allows most implementers of `BinRead` to use std features
on the reader instead of restricting them to the subset of
features exposed by the no_std reimplementation. This also
appears to have been the original intent of the `binread::io`
module, per the module-level comment.

This is a breaking change because `iter_bytes` is called `bytes`
in std and consumes the reader, so this has been changed in the
binread implementation to conform to that API.

This commit also fixes the earlier binread implementations not
handling errors as std does; this is now fixed (by copying
from std).

(Hopefully rust-lang/rust#48331 will be addressed someday and then
this code can disappear entirely.)
jam1garner pushed a commit to jam1garner/binread that referenced this issue Mar 10, 2021
This allows most implementers of `BinRead` to use std features
on the reader instead of restricting them to the subset of
features exposed by the no_std reimplementation. This also
appears to have been the original intent of the `binread::io`
module, per the module-level comment.

This is a breaking change because `iter_bytes` is called `bytes`
in std and consumes the reader, so this has been changed in the
binread implementation to conform to that API.

This commit also fixes the earlier binread implementations not
handling errors as std does; this is now fixed (by copying
from std).

(Hopefully rust-lang/rust#48331 will be addressed someday and then
this code can disappear entirely.)
@bstrie
Copy link
Contributor

bstrie commented Mar 19, 2021

I believe that the error-handling working group is making it a priority to move std::error::Error into libcore, so with luck this has a path forward.

Std cannot (currently) add inherent impls for structs it does not itself define.

This isn't quite true; with lang items, anything is possible. This is how, for example, both std and core can define inherent impls on floats:

#[lang = "f32_runtime"]

@NitinSaxenait
Copy link

@roblabla Hey, I am working in no_std env and want to send sensors reading as a post request. For that I need readings, is that anyway I can write data to a file in no_std env. Using the discovery book I am able to get reading in itm.txt using itmdump

@thomcc
Copy link
Member

thomcc commented Aug 12, 2021

For that I need readings, is that anyway I can write data to a file in no_std env

no_std is portable to systems without files, so no. However, there's probably something on crates.io that you can use. For example, the libc and winapi crates expose access to common OS apis, and there are crates built on top of them.

(EDIT: to be clear, I'm in favor of finding a way for core to access these traits (and any types like Cursor which can be implemented there...) but that won't help this user write data to a file)

@dobromyslov
Copy link

Found nice fresh slice of the std::io to use as a workaround in no_std environments: https://github.com/dataphract/acid_io

@c410-f3r
Copy link
Contributor

#99917 has been merged so is anyone going to create a PR?

@thomcc
Copy link
Member

thomcc commented Aug 24, 2022

I think it needs a lot more discussion. It returns std::io::Error, which cannot trivially be moved into core, even now that std::error::Error can.

See rust-lang/project-error-handling#11 on some of the challenges there, and discussion if doing so is even desirable. My personal stance is that it should be closer to something like core::io::Write<Error = ...>, as I mentioned in rust-lang/project-error-handling#11 (comment). That would allow keeping useful information in std::io::Error, without regressing performance, etc.

@c410-f3r
Copy link
Contributor

Thank you for the explanation @thomcc.

Here goes a copy-and-paste of library/std/src/io/error.rs with the new error_in_core feature -> https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=e30835b743ae49d77feb0c1f7c89d934

As seen above, the only thing preventing the inclusion of std::io structures and traits into the alloc crate (not core but that would still enable #[no_std]) is the ErrorData::Os(i32) variant and its std::sys::* calls.

Not sure if the the new provider API can help here but if not, then core::io::Write<Error = ...> seems like the only alternative.

@ChrisDenton ChrisDenton added the A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` label Jan 26, 2023
@RaitoBezarius
Copy link

I think it needs a lot more discussion. It returns std::io::Error, which cannot trivially be moved into core, even now that std::error::Error can.

See rust-lang/project-error-handling#11 on some of the challenges there, and discussion if doing so is even desirable. My personal stance is that it should be closer to something like core::io::Write<Error = ...>, as I mentioned in rust-lang/project-error-handling#11 (comment). That would allow keeping useful information in std::io::Error, without regressing performance, etc.

I feel like, there could be already an alloc feature, then we can go further and have the core::io::Write<_> thing?

dequbed added a commit to dequbed/rsasl that referenced this issue Aug 10, 2024
Hello 👋 

[This advisory](GHSA-x4mq-m75f-mx8m) has
been bothering me since a while.
The origin of it is
[`acid_io`](https://github.com/dataphract/acid_io/blob/v0.1.0/Cargo.toml#L28),
where the [issue has been
fixed](dataphract/acid_io#21), but no release
has been publish 😞 (since 3 years!)

So, after reading this
[discussion/issue](rust-lang/rust#48331), I
decided to replace `acid_io` for
[`core2`](https://crates.io/crates/core2) (which seems quite used, even
if the last release was 2 years ago).

The changes was trivial as `acid_io` and `core2::io` have the same API
as `std::io`. But, while trying to test those changes, the CI failed
(which was also the case [`last
week`](https://github.com/dequbed/rsasl/actions/runs/8870496836)). So I
started to fix the CI, and made changes to make the CI pass.

I also added a `dependabot.yaml`, PR will be created monthly to update
the dependencies in the `Cargo.toml` and `ci.yml`.

I formatted the `README.md` and `Cargo.toml`.

I had to bump MSRV to 1.65.

I bumped the dependencies to their latest versions, and pushed the
`Cargo.lock`, see
[`why`](https://doc.rust-lang.org/cargo/faq.html#why-have-cargolock-in-version-control).

And I ran `cargo clippy --fix` and `cargo fmt`, with not much
modification from myself.



I just finished to write the description of this PR and realize that
someone was working on #36, oops
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` C-feature-request Category: A feature request, i.e: not implemented / a PR. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests