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

Add no_std support using alloc collections #148

Merged
merged 24 commits into from
May 17, 2020
Merged

Conversation

xoloki
Copy link
Contributor

@xoloki xoloki commented Sep 13, 2019

This PR builds on the work of @mullr in #145 but uses the alloc crate for vector and map types when in no_std mode.

pb-rs now takes a new flag (--nostd). When this flag is passed, the generated Rust code will use alloc::vec::Vec and alloc::collections::BTreeMap. BTreeMap<K,V> will be typedef'd to HashMap<K,V>, so client code will not need to do anything different in std vs no_std modes.

mullr and others added 18 commits September 9, 2019 15:07
This replaces all public uses of the Writer trait with the new WriterBackend,
which defines only the interaction points which are actually used. It also
introduces the BytesWriter struct as a new implementation of WriterBackend that
will work in no_std, along with the serialize_into_slice convenience fn.

This is technically a breaking change, as it will require stubs to be
regenerated.
- Add the `std` feature to the quick-protobuf runtime crate
- Put uses of Vec and std::io::Write behind the `std` feature
- Change other uses of `std` to `core`
- Add the `quick-protobuf/no-std-example` compile test
@xoloki
Copy link
Contributor Author

xoloki commented Sep 13, 2019

All tests now passing @tafia @nerdrew.
Not sure why github thinks there are merge commits. I'll try rebasing on upstream master.

@xoloki
Copy link
Contributor Author

xoloki commented Sep 13, 2019

Weird, after reloading the page github is no longer complaining about merge commits.

Let me know if you need anything else.

pb-rs/src/main.rs Show resolved Hide resolved
@@ -0,0 +1,868 @@
// Automatically generated rust module for 'test_basic_pb.proto' file
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this file get removed from the PR? We are going to keep only a couple generated files and the no-std-example above shows what the --nostd codegen looks like. Looks like the file should get added to the gitignore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -1,4 +1,4 @@
// This file is generated by rust-protobuf 2.8.0. Do not edit
// This file is generated by rust-protobuf 2.8.1. Do not edit
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this file checked in? I couldn't tell when I removed a bunch of other generate files if this one could go.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to check this one I think

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

run_test.sh Outdated
@@ -8,4 +8,9 @@ cargo run -p quick-protobuf --example pb_rs_example_v3_owned
cargo run -p quick-protobuf --example pb_rs_example
cargo run -p quick-protobuf --example pb_rs_example_v3

# test that no_std can build
pushd quick-protobuf/no-std-example
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we condense the example into a single file the rest of the examples? I assume the feature toggling makes it a little trickier... If it isn't too much work, it'd be nice to have it be consistent with the other examples.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -99,6 +99,23 @@ message TestTypesRepeated {
repeated TestEnumDescriptor enum_field = 16 [packed=false];
}

message TestTypesRepeatedArrayVec {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this new message?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, removed.

Copy link
Owner

@tafia tafia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

On top of what @nerdrew said, could you please remove the HashMap aliasing and review the extern crate declarations.

{
writeln!(w, "use std::borrow::Cow;")?;
if config.nostd {
writeln!(w, "extern crate alloc;")?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure but the extern crate may be repeated many times when implemented this way. Won't the rust compiler complain?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't appear to happen in the example, as you can see in the file quick-protobuf/examples/pb_rs_nostd/protos/no_std.rs after run_test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added an extern crate alloc; to pb_rs_example_nostd.rs, the compiler doesn't care that the create is extern'd twice.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. Sounds weird to me.

writeln!(w, "use std::collections::HashMap;")?;
if config.nostd {
writeln!(w, "use alloc::collections::BTreeMap;")?;
writeln!(w, "pub type HashMap<K, V> = BTreeMap<K, V>;")?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather use a BTreeMap directly here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems good to have client code not care about which mode the proto is compiled in, in the default case. Why do you prefer to have separate APIs?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like misleading APIs. HashMap conveys a O(1) and a Hash key instead of a O(log(n)) and a Ord key.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kinda like the suggestion of depending on hashbrown directly. Would adding that dependency in a no_std env be acceptable?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i actually dont like this at all. in general in no_std you dont want too many dependencies, because the status of your code being 'no_std' depends on all the dependencies (including transitive ones) being no_std, which can get annoying. i see the utility of having support for all protobuf types, but i still think that you should feature-gate this, and have the dependency as optional for this feature (like features = ["hashmap"], etc.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A feature gate makes sense, though I think we can probably just do a build flag instead since this is just for generated code. pb-rs --hashmap or something like that. Would you want it to error if the proto has a map type but --hashmap isn't enabled?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I assume --hashmap would use hashbrown or something similar if enabled?

Hmmm. We would need a feature-gate + the pb-rs flag I think. Feature-gate for quick-protobuf and flag for pb-rs....

Copy link

@TheVova TheVova Nov 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah exactly, i agree on the flag + feature. as for errors, it would make most sense to error in case of mismatch. the more interesting way to deal with this (IMHO) is actually given in the protobuf api itself. see https://developers.google.com/protocol-buffers/docs/proto#maps, under "Backwards compatibility". especially the requirement that "Any protocol buffers implementation that supports maps must both produce and accept data that can be accepted by the above definition." Which technically means we can still be wire-compatible even without depending on anything. this is arguably more work to implement, and is probably outside the scope of this PR, but something to think about. as a corollary this probably also means that pb-rs doesnt actually comply with the protobuf specification?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. I hadn't thought about the maps-are-really-just-a-repeated-field aspect. I like that as the default. I suppose we'd just create a synthetic nested message for the map field in the generated code.

writeln!(w, "use std::borrow::Cow;")?;

if config.nostd {
writeln!(w, "extern crate alloc;")?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment for the repeated extern crate calls. Usually the files generated by pb-rs will be modules to be inserted into wider project. I believe the extern crate declarations should be in the lib.rs or main.rs file, not here. We could probably trigger some cli message or add a comment in every file?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think agree with this. The usability of requiring an extern crate ... in your code to use no_std is sub-optimal, but I like the explicitness of it. I don't really like changing global properties because you included some generated code.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see the official take on this from when alloc was stabilised:
https://blog.rust-lang.org/2019/07/04/Rust-1.36.0.html#the-alloc-crate-is-stable
so this should be on lib.rs, yes.

@@ -1,4 +1,4 @@
// This file is generated by rust-protobuf 2.8.0. Do not edit
// This file is generated by rust-protobuf 2.8.1. Do not edit
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to check this one I think

@tafia
Copy link
Owner

tafia commented Oct 11, 2019

Thanks for the changes, I didn't see them. Could you fix the fmt issue?

@nerdrew any more comment?
I am still not a fan of HashMap aliasing for BTreeMap.

@TheVova
Copy link

TheVova commented Nov 16, 2019

whats the status with this? i'd have much use of this being no_std... if i can help fix issues let me know. As for HashMap vs B-Tree, i agree with @tafia , changing abstractions like this isnt good. The fact that you put a no_std flag on your library already means there are certain choices you will have to make explicitly. in no_std context the client does and should care about the implementation to a degree. i use Rust pretty exclusively in embedded context (so no_std), and known performance and being sure of whats implemented is important. The correct solution (imho, and as done in most no_std crates) is to feature-gate this type (i.e normally hasmaps are not supported), and if you explicitly opt-in (opt-in to the hashmap feature, not no_std in general), then make it clear the type is changed to BtreeMap.

@lexxvir
Copy link
Contributor

lexxvir commented Nov 17, 2019

Hi all,

I'd like to join to you efforts to make quick-protobuf no_std compatible. Currently I extensively use quick-protobuf in no_std project of my company. I have to maintain my own fork where HashMap is replaced by the BTreeMap (with some other changes).

I suggest to try hashbrown for map types in no_std because std already use it under the hood.

@tafia tafia closed this Nov 22, 2019
@tafia tafia reopened this Nov 22, 2019
@tafia
Copy link
Owner

tafia commented Nov 30, 2019

@xoloki would you be able to make the changes we discussed on the PR or should someone else make another one?

@jpopesculian jpopesculian mentioned this pull request Jan 23, 2020
@tafia tafia merged commit 1c5f753 into tafia:master May 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants