-
Notifications
You must be signed in to change notification settings - Fork 45
Add no_std
Support
#200
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
base: master
Are you sure you want to change the base?
Add no_std
Support
#200
Conversation
Not sure why the test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we split this up over two or three PRs?
There's probably a way to change the architecture to make this work right, more sans-io, but this looks on the surface like adding some confusion in trying to support two different underlying models of streams at the same time. I find it very hard to judge the correctness of what's being implemented through the wrappers and traits that are intermingled with pulling things out of the internals.
The best way to make this reviewable would be:
- first PR the changes to trait bounds (e.g.
Read
toBufRead
) and API surface ofstd
, so that their justification can be done independently from no-std. This also makes it easier to judge ramifications of them in isolation. - then PR some cleanup of the error types as necessary.
- then PR the traits for no-std which now should appear as their separate API surface so we don't have to worry about changes to std behavior at this stage.
I've indicated some of the critical parts of the code in comments, they apply to one of these three bullet points respectively.
|
||
#[cfg(feature = "color_quant")] | ||
use std::collections::{HashMap, HashSet}; | ||
use alloc::collections::{BTreeMap, BTreeSet}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's one of those little things that I meant with: "will there be a tradeoff between no-std and performance". Those are not equivalent, though of course it may not matter much. The use, building a palette, is one of the noticeably slowest paths of naively encoding.
Maybe this is actually good because performance sensitive users may already be falling back to supplying a palette and thus not executing any of the set operations. But maybe they do. It's very speculative.
/// Returned if the encoder could not allocate enough memory to proceed. | ||
OutOfMemory, | ||
/// Returned when an IO error has occurred. | ||
Io(Box<dyn error::Error + Send + Sync + 'static>), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I get an IO error I want to inspect at least the error code, i.e. if it is WouldBlock
or ConnectionReset
, StorageFull
etc. Exactly this is being erase which the majority of std
users do not benefit from. Another tradeoff between no-std and proper modelling.
vec.try_reserve( | ||
usize::from(self.current_frame.width) | ||
* usize::from(self.current_frame.height) | ||
/ 4, | ||
) | ||
.map_err(|_| DecodingError::InsufficientBuffer)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part I actually like, io::ErrorKind
is arguably incorrect. If you want to rework the interface bit-by-bit into something amenable to no-std, it would be a good start to ensure all the internal errors are no longer dependent on io::Error/io::ErrorKind
in any way. This could also clean up some stringly typed errors.
impl<R: io::Read> Decoder<IoBufReader<io::BufReader<R>>> { | ||
/// Create a new decoder with default options. | ||
#[inline] | ||
pub fn new(reader: R) -> Result<Self, DecodingError> { | ||
DecodeOptions::new().read_info(reader) | ||
} | ||
} | ||
|
||
impl<R: BufRead> Decoder<R> { | ||
/// Create a new decoder with default options and the provided [`BufRead`]. | ||
#[inline] | ||
pub fn new_with_buffered_reader(reader: R) -> Result<Self, DecodingError> { | ||
DecodeOptions::new().read_info_with_buffered_reader(reader) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good change regardless. We may have been double-buffering all the time and the interface for constructing the reader isn't actually that different. The only change change for existing downstream users would be that they have to denote the type as Decoder<BufReader<R>>
instead of Decoder<R>
if they actually annotated the type exactly.
If you intend to publish this as a crate in some capacity, compare to |
Marking as draft while I split out smaller PRs based on this one. Once the splitting is complete I'll close this PR. |
Objective
Before committing to
no_std
support inimage
, there are open questions around how to sever dependence onstd::io
without compromising current performance or compatibility. Sinceimage-gif
current does not haveno_std
support, and is comparatively small, solving that problem here could indicate how the problem could be handled more broadly.Solution Overview
Similar to
ciborium-io
, I have re-defined a subset of thestd::io
traits for use withinimage-gif
. However, unlikeciborium-io
, I do not blanket-implement the trait for all types implementing thestd::io
originals. Instead, each redefined trait gets a transparent wrapper type which forwards theimage-gif
methods to the underlyingstd::io
ones.Since the wrapper is only available with the feature that adds the implementation, features remain additive. For
image-gif
, I have not provided any baked-in implementations for types likeVec<u8>
, opting instead to leave it up to end users to write their own wrappers aroundcore
/alloc
types if they need to. This reduces the maintenance burden onimage-gif
itself, since it only has 1 implementation to test, the one that simply forwards tostd::io
which it was already testing.Solution
core::error::Error
. This simplifies feature gates considerably.color_quant
feature to useBTreeMap
andBTreeSet
instead ofHashMap
andHashSet
. This is purely an internal change and should not have any observable side effects.BufRead
andWrite
traits totraits.rs
which contain the bare-minimum functionality required for the rest of the crate. These are public so crate consumers may define their own readers/writers.WriteBytesExt
to be a super-trait ofWrite
. I also changed the implementations to useu16::to_le_bytes
(likewise foru32
andu64
). This solves what I believe is a possible bug in the implementation where one of those values could be partially written before an error is returned, which is inconsistent with the otherwise total use ofwrite_all
.std
feature] AddedIoBufReader
andIoWrite
wrapper types and implementedBufRead
andWrite
for them.EncodingFormatError::BufferImproperlySized
variant. This reduces reliance onstd::io::Error
and is not a breaking change, asEncodingFormatError
is alreadynon_exhaustive
EncodingError::MissingWriter
andEncodingError::OutOfMemory
for the same reasons above. This is a breaking change as it was notnon_exhaustive
. I have now marked itnon_exhaustive
to prevent this issue in the future.EncodingError::Io
to wrap aBox<dyn Error>
instead of anio::Error
. This just allows any error type to be used for IO errors, but is a breaking change.Encoder::new_with_writer
which constructs anEncoder<W>
whereW: Write
. This is contrast to the existingEncoder::new
, which now returns anEncoder<IoWriter<W>>
whereW: std::io::Write
.DecodingError::InsufficientBuffer
,DecodingError::UnexpectedEof
,DecodingError::MissingDecoder
variants for the same reasons as above.DecodingError::Io
to wrap aBox<dyn Error>
for the same reasons as above.OutputBuffer::Vec
code-path forLzwReader::decode_bytes
. This wasn't required, but it appeared simple enough.DecodeOptions::read_info_with_buffered_reader
which constructs aDecoder<R>
whereR: BufRead
. This is contrast to the existingDecodeOptions::read_info
, which now returns anDecoder<IoBufReader<std::io::BufReader<R>>>
whereR: std::io::Read
.Decoder::new_with_buffered_reader
which constructs aDecoder<R>
whereR: BufRead
. This is contrast to the existingDecoder::new
, which now returns anDecoder<IoBufReader<std::io::BufReader<R>>>
whereR: std::io::Read
.cargo fmt
(this is the reason the diff is so large, the actualno_std
changes are closer to +400/-200). Please refer to the individual commits to see the effect of formatting on its own.