-
Notifications
You must be signed in to change notification settings - Fork 52
new literacy::{Read, Write} traits to handle std/no_std (alternative) #127
Conversation
src/literacy.rs
Outdated
@@ -0,0 +1,123 @@ | |||
#[cfg(all(feature = "std", feature = "use-core2"))] | |||
compile_error!("feature \"std\" and \"use-core2\" cannot be enabled together."); |
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.
I think we should "default" to core2 in this case - if we already are building against the core2 dep, we might as well just assume the user set core2/std and then the core2 impl is the same as doing the std impl.
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.
I agree but there are some implementation issue, in particular
we might as well just assume the user set core2/std
How so? with another feature use-core2-std = ["core2/std"]
? This would cause other issues in feature combinations...
The other option, using std = ["core2/std"]
will cause problem in default features compilation on rust 1.29
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.
like you said in chat....
ugh, I wish there were a way in cargo.toml to say "if core2 && std -> require core2/std"
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.
How so? with another feature use-core2-std = ["core2/std"]? This would cause other issues in feature combinations...
We don't have to specify it ourselves for core2/std to be enabled by some other dependency path. Its probably neater to just impl for std and not core2 than to compile-error, and if users intended to use core2 they can always fix it by, themselves, directly requiring core2/std. This avoids the issue of having some crate that depends on A and B and A enables std and B enables core2, giving the user no way to fix the issue aside from patching either A or B.
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.
Sure, removed the compile_error
and implementing std impl only if there is std but not core2
I think also we should have a not-std&¬-core2 impl block for Write into a Vec and Read from a slice, like @devrandom did in rust-bitcoin at https://github.com/rust-bitcoin/rust-bitcoin/pull/603/files#diff-76866598ce8fd16261a27ac58a84b2825e6e77fc37c163a6afa60f0f4477e569R199-R256 |
cargo clean | ||
CC='clang -fsanitize=memory -fno-omit-frame-pointer' \ | ||
RUSTFLAGS='-Zsanitizer=memory -Zsanitizer-memory-track-origins -Cforce-frame-pointers=yes' \ | ||
cargo test --lib --all --features="$FEATURES" -Zbuild-std --target x86_64-unknown-linux-gnu |
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.
should be --all-features
here and above, waiting to find a solution for defaulting to core2
if enabled (without conflicting with std
)
added in 4291b4b and also removed write_all blanket impl |
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.
I think this is basically almost there. Is it time to un-draft it?
src/lib.rs
Outdated
@@ -25,7 +25,7 @@ | |||
#![deny(non_camel_case_types)] | |||
#![deny(non_snake_case)] | |||
#![deny(unused_mut)] | |||
#![deny(missing_docs)] | |||
//#![deny(missing_docs)] |
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.
I hope you didn't intend to land this :)
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.
removed, and added doc where missing in 8bf2f9e
src/literacy.rs
Outdated
@@ -0,0 +1,123 @@ | |||
#[cfg(all(feature = "std", feature = "use-core2"))] | |||
compile_error!("feature \"std\" and \"use-core2\" cannot be enabled together."); |
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.
How so? with another feature use-core2-std = ["core2/std"]? This would cause other issues in feature combinations...
We don't have to specify it ourselves for core2/std to be enabled by some other dependency path. Its probably neater to just impl for std and not core2 than to compile-error, and if users intended to use core2 they can always fix it by, themselves, directly requiring core2/std. This avoids the issue of having some crate that depends on A and B and A enables std and B enables core2, giving the user no way to fix the issue aside from patching either A or B.
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.
utACK 8bf2f9e for the rust code, not so sure about the tests, needs some clarification at least imo.
contrib/test.sh
Outdated
FEATURES="serde serde-std" | ||
# Combination of features to test, should be every features combination but std and core2 together | ||
# note std has a comma in the end so that following regex avoid matching serde-std | ||
FEATURES=("" "std," "use-core2" "use-core2-std" "std,use-core2" "std,serde-std" "use-core2,serde-std") |
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.
What happened to the serde
feature? I assume serde
without std is expected to work in no-std mode so it should be tested accordingly?
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.
serde added in 7b909c9, I forget about it because it's used but not listed in the Cargo.toml feature section...
Not sure if other combination are needed though
echo "--------------$feature----------------" | ||
cargo build --no-default-features --features="$feature" | ||
if [[ ${feature} =~ "std," ]] ; then | ||
cargo test --no-default-features --features="$feature" |
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.
Why do we only test with std
and just build the rest of the time?
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.
because tests have std enabled
#![cfg_attr(all(not(test), not(feature = "std")), no_std)]
I tried to compile the following example, which uses a pattern seen in extern crate bitcoin_hashes;
use bitcoin_hashes::literacy::Write;
use bitcoin_hashes::{sha256d, Hash, hash_newtype, hex_fmt_impl, index_impl, serde_impl, borrow_slice_impl};
hash_newtype!(Txid, sha256d::Hash, 32, doc="A bitcoin transaction hash/transaction ID.");
fn do_it<W: Write>(mut w: W) {
let _ = w.write_all(&[0u8; 1]);
}
fn main() {
let mut enc = Txid::engine();
do_it(&mut enc);
} But this fails to compile. I tried creating an |
What about |
Yeah, the other way results in the same conflict. I'm playing with taking A further issue is that rust-bitcoin uses And one more question - how exactly do we concretize the pub trait Encodable {
fn consensus_encode<W: io::Write<Error=io::Error>>(&self, writer: &mut W) -> Result<usize, io::Error>;
} (where the |
@afilini and I did some test for accepting both we can't however, Alekos come up with one generic implementation that should support both
which works but the problem in @devrandom's example remains because engines implement directly the literacy trait so they are not getting the BorrowMut thing |
I don't really like the I think implementing |
Not even that, because implementing it that way made it only apply to types that already implemented |
Yea, I think we definitely can't do anything that relies on dyn here, the optimization story of this type of deserialization logic is already pretty rough (and we've had users with significant performance bottlenecks in our deserialization before, it probably still is the bottleneck for electrs), we can't make it worse. |
I think 19b74b7 is the way to go, but there are some issues with lifetimes on 1.29 https://github.com/RCasatta/bitcoin_hashes/runs/2631403404?check_suite_focus=true, @sgeisler do you want to look at it? |
Sure, will do, the commit was merely to illustrate my point, I'll also remove the example again. |
Using a macro makes the code more compact and allows us to easily add an impl for &mut HashEngine which could not be achieved through the type system otherwise.
The embedded test broke somehow 😦 I'm not sure if it has anything to do with my commit. Did it increase the lib size so much that it doesn't fit into the flash anymore? |
7b909c9
to
a37f2be
Compare
added in a37f2be Another option without adding the fn to the trait and the associated type would have been to restore the |
This question remains, how do we concretize One issue is that the hash engines already concretize
|
with the latest commits it should be:
1f500f1 use literacy::Error also for engines (the |
src/literacy.rs
Outdated
/// The error type returned in Result | ||
type Error; | ||
/// The type to implement limited reads | ||
type Take; |
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.
I would add a trait bound to literacy::Read
here, it might help later on when working with generics
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.
Alternatively you could also add another trait specifically for the Take
type that "extends" Read
and forces a specific constructor-like method. With that you can then provide a default implementation for take()
like std does, because you can always construct an object of type Self::Take
using the constructor defined by its trait.
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.
added trait bound in 6b7b28f
After adding the But I would like to ear opinions before doing such changes |
Why? Lets generally avoid dyn indirection unless we have a good reason to prefer it. |
How much do we actually add an error ourselves? I assume we should basically never error unless the underlying |
Do we really want these verbose function signatures (illustrated for the std case): pub trait Decodable: Sized {
/// Decode an object with a well-defined format
fn consensus_decode<D: io::Read<Error=encode::Error, Take=::std::io::Take<D>>>(d: &mut D) -> Result<Self, encode::Error>;
}
impl Decodable for OutPoint {
fn consensus_decode<D: io::Read<Error=encode::Error, Take=::std::io::Take<D>>>(d: &mut D) -> Result<Self, encode::Error> {
Ok(OutPoint {
txid: Decodable::consensus_decode(d)?,
vout: Decodable::consensus_decode(d)?,
})
}
} |
Please don't, the entire point of this PR was to have the error be an associated type instead of a
My vision for the API is as follows: pub trait Encodable {
fn consensus_encode<W: literacy::Write>(&self, writer: W) -> Result<usize, W::Error>;
}
pub trait Decodable: Sized {
fn consensus_decode<D: literacy::Read>(d: D) -> Result<Self, DecodeError<D::Error>>;
}
pub enum DecodeError<E: Display + Debug + …> {
Io(E),
Psbt(psbt::Error),
…
} The caller of one of these functions typically knows if they use std or core2, or if not they will just have to do the same generic trick that we are doing here. So there is no need to use a
I think there are some misconceptions about having to name associated types in that context, you don't have to. You can access them, for every struct-trait pair these are well-defined, so no need to actually specify them anywhere here. |
@TheBlueMatt @sgeisler so it's good like it is? |
src/literacy.rs
Outdated
/// see [std::io::Read::read_exact] | ||
fn read_exact(&mut self, buf: &mut [u8]) -> ::core::result::Result<(), Self::Error>; | ||
/// see [std::io::Read::take] | ||
fn take(self, limit: u64) -> Self::Take; |
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 doesn't seem to quite work with the &mut
pattern:
fn consensus_decode<D: io::Read>(d: &mut D) -> Result<Self, encode::Error> {
let mut d = d.take(MAX_VEC_SIZE as u64);
gives:
error[E0507]: cannot move out of `*d` which is behind a mutable reference
let mut d = d.take(MAX_VEC_SIZE as u64);
^ move occurs because `*d` has type `D`, which does not implement the `Copy` trait
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.
maybe removing take
from the trait and using something like the CappedRead in RCasatta/rust-bitcoin@24015e8 ?
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.
I'll give this a try.
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.
Seems to work fine, let's take out the take
.
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.
take removed in c6d4ca4
I tried to migrate the consensus de/encoding API in rust-bitcoin to this PR and have to say it's not too pleasant 😆 some problems I noticed:
This means we'd have to do a lot of work to make struct IoError {
kind: IoErrorKind,
error: Box<dyn OurErrorTrait>, //
}
trait OurErrorTrait: Debug + Any {} This would allow cheap conversion from both |
Would like to ear from @devrandom, @apoelstra and @TheBlueMatt about @sgeisler proposal before doing changes |
It sounds plausible, but it's difficult to evaluate without trying it. If it can be quickly prototyped, I can try it out in rust-bitcoin. BTW, I had the same experience as @sgeisler when trying to apply the current version to rust-bitcoin, which motivated some of my previous questions... |
Without having dug into it too much, building on @sgeisler's suggestion, what about:
|
…rospection, adds ErrorTrait to expose kind
So I worked on the |
I'm having trouble following all the discussion, especially as the PR is now 14 commits, many of which undo changes that earlier commits do. I agree with others saying that we should not use I'm also unconvinced of the merits of creating our own parallel implementation of the core2 crate. Our types which implement our traits will not be usable with other crates that expect the |
Ugh, yea, after all the work here, it just seems much more complicated than I was anticipating. Maybe if ErrorKind was exposed it could have gone down this path without the crazy amount of work here. Apologies for pushing the wrong way here. |
At least we tried... Somehow relieved to close this... I remain with an open question though, is "deeper introspection when needed by downcasting the underlying error" really needed somewhere? or one could hypothetically go with just ErrorKind (not ideal losing error information but maybe good enough)? |
Alternative to #126
see description there