Skip to content

Add no_std support using alloc collections#148

Merged
tafia merged 24 commits into
tafia:masterfrom
xoloki:no_std
May 17, 2020
Merged

Add no_std support using alloc collections#148
tafia merged 24 commits into
tafia:masterfrom
xoloki:no_std

Conversation

@xoloki

@xoloki xoloki commented Sep 13, 2019

Copy link
Copy Markdown
Contributor

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

xoloki commented Sep 13, 2019

Copy link
Copy Markdown
Contributor Author

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

@xoloki

xoloki commented Sep 13, 2019

Copy link
Copy Markdown
Contributor Author

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

Let me know if you need anything else.

Comment thread pb-rs/src/main.rs
@@ -0,0 +1,868 @@
// Automatically generated rust module for 'test_basic_pb.proto' file

Copy link
Copy Markdown
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
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment thread perftest/src/perftest_data.rs Outdated
@@ -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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment thread run_test.sh Outdated
cargo run -p quick-protobuf --example pb_rs_example_v3

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

Copy link
Copy Markdown
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
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

repeated TestEnumDescriptor enum_field = 16 [packed=false];
}

message TestTypesRepeatedArrayVec {

Copy link
Copy Markdown
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
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nope, removed.

@tafia tafia left a comment

Copy link
Copy Markdown
Owner

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.

Comment thread pb-rs/src/types.rs
{
writeln!(w, "use std::borrow::Cow;")?;
if config.nostd {
writeln!(w, "extern crate alloc;")?;

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

Comment thread pb-rs/src/types.rs
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown

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
Copy Markdown
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
Copy Markdown
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....

@TheVova TheVova Nov 24, 2019

Copy link
Copy Markdown

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
Copy Markdown
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.

Comment thread pb-rs/src/types.rs
writeln!(w, "use std::borrow::Cow;")?;

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

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown

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.

Comment thread perftest/src/perftest_data.rs Outdated
@@ -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
Copy Markdown
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

tafia commented Oct 11, 2019

Copy link
Copy Markdown
Owner

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

TheVova commented Nov 16, 2019

Copy link
Copy Markdown

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

lexxvir commented Nov 17, 2019

Copy link
Copy Markdown
Contributor

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

tafia commented Nov 30, 2019

Copy link
Copy Markdown
Owner

@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