Skip to content

Commit e9f2c51

Browse files
committed
Make prost a no_std compatible library, and prost-build able to generate no_std code
The alternative is to get collections types from `core` and `alloc`. In the `no_std` mode in `prost_build`, we force it to always use BTreeMap since HashMap was not stabilized in `alloc::collections` library. The functionality is identical and the only incompatibilities in the interface are that we cannot use `std::error::Error` or `std::io::Error` in `no_std` mode because these types have not been moved to `alloc` and there is no alternative.
1 parent 9551f28 commit e9f2c51

File tree

9 files changed

+122
-21
lines changed

9 files changed

+122
-21
lines changed

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ members = [
2525
"protobuf",
2626
"tests",
2727
"tests-2015",
28+
"tests-alloc",
2829
]
2930
exclude = [
3031
# The fuzz crate can't be compiled or tested without the 'cargo fuzz' command,
@@ -33,8 +34,11 @@ exclude = [
3334
]
3435

3536
[features]
36-
default = ["prost-derive"]
37+
default = ["prost-derive", "std"]
3738
no-recursion-limit = []
39+
std = [] # When disabled, we attempt to provide no_std support in prost
40+
# Config::use_alloc_collections() should be set when using prost_build in your build.rs
41+
# so that generated files will not have std:: either, and will use alloc crate instead
3842

3943
[dependencies]
4044
bytes = "0.4.7"

prost-build/src/code_generator.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::extern_paths::ExternPaths;
1818
use crate::ident::{match_ident, to_snake, to_upper_camel};
1919
use crate::message_graph::MessageGraph;
2020
use crate::Config;
21+
use crate::CollectionsLib;
2122

2223
#[derive(PartialEq)]
2324
enum Syntax {
@@ -366,12 +367,14 @@ impl<'a> CodeGenerator<'a> {
366367
self.buf.push_str(&to_snake(field.name()));
367368
self.buf.push_str(": ");
368369
if repeated {
369-
self.buf.push_str("::std::vec::Vec<");
370+
self.buf.push_str(self.config.collections_lib.to_str());
371+
self.buf.push_str("::vec::Vec<");
370372
} else if optional {
371-
self.buf.push_str("::std::option::Option<");
373+
self.buf.push_str("::core::option::Option<");
372374
}
373375
if boxed {
374-
self.buf.push_str("::std::boxed::Box<");
376+
self.buf.push_str(self.config.collections_lib.to_str());
377+
self.buf.push_str("::boxed::Box<");
375378
}
376379
self.buf.push_str(&ty);
377380
if boxed {
@@ -403,7 +406,7 @@ impl<'a> CodeGenerator<'a> {
403406
self.append_doc();
404407
self.push_indent();
405408

406-
let btree_map = self
409+
let btree_map = (self.config.collections_lib != CollectionsLib::Std) || self
407410
.config
408411
.btree_map
409412
.iter()
@@ -426,8 +429,9 @@ impl<'a> CodeGenerator<'a> {
426429
self.append_field_attributes(msg_name, field.name());
427430
self.push_indent();
428431
self.buf.push_str(&format!(
429-
"pub {}: ::std::collections::{}<{}, {}>,\n",
432+
"pub {}: {}::collections::{}<{}, {}>,\n",
430433
to_snake(field.name()),
434+
self.config.collections_lib.to_str(),
431435
rust_ty,
432436
key_ty,
433437
value_ty
@@ -459,7 +463,7 @@ impl<'a> CodeGenerator<'a> {
459463
self.append_field_attributes(fq_message_name, oneof.name());
460464
self.push_indent();
461465
self.buf.push_str(&format!(
462-
"pub {}: ::std::option::Option<{}>,\n",
466+
"pub {}: ::core::option::Option<{}>,\n",
463467
to_snake(oneof.name()),
464468
name
465469
));
@@ -713,8 +717,8 @@ impl<'a> CodeGenerator<'a> {
713717
Type::Int32 | Type::Sfixed32 | Type::Sint32 | Type::Enum => String::from("i32"),
714718
Type::Int64 | Type::Sfixed64 | Type::Sint64 => String::from("i64"),
715719
Type::Bool => String::from("bool"),
716-
Type::String => String::from("std::string::String"),
717-
Type::Bytes => String::from("std::vec::Vec<u8>"),
720+
Type::String => [self.config.collections_lib.to_str(), "::string::String"].concat(),
721+
Type::Bytes => [self.config.collections_lib.to_str(), "::vec::Vec<u8>"].concat(),
718722
Type::Group | Type::Message => self.resolve_ident(field.type_name()),
719723
}
720724
}

prost-build/src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,32 @@ pub trait ServiceGenerator {
165165
fn finalize(&mut self, _buf: &mut String) {}
166166
}
167167

168+
/// Configuration enum for whether to use `std` prefixes or `alloc` prefixes in generated code
169+
///
170+
/// This option also forces Btree everywhere, overriding the BtreeMap options,
171+
/// since HashMap is not in alloc::collections, only std (it requires randomness)
172+
///
173+
#[derive(PartialEq)]
174+
pub enum CollectionsLib {
175+
Std,
176+
Alloc,
177+
}
178+
179+
impl CollectionsLib {
180+
pub fn to_str(&self) -> &'static str {
181+
match self {
182+
CollectionsLib::Std => { "::std" },
183+
CollectionsLib::Alloc => { "::alloc" },
184+
}
185+
}
186+
}
187+
168188
/// Configuration options for Protobuf code generation.
169189
///
170190
/// This configuration builder can be used to set non-default code generation options.
171191
pub struct Config {
172192
service_generator: Option<Box<dyn ServiceGenerator>>,
193+
collections_lib: CollectionsLib,
173194
btree_map: Vec<String>,
174195
type_attributes: Vec<(String, String)>,
175196
field_attributes: Vec<(String, String)>,
@@ -460,6 +481,15 @@ impl Config {
460481
self
461482
}
462483

484+
/// Configure the code generator to use the `::alloc` namespace rather than `::std`, and Btree everywhere
485+
/// rather than `std`.
486+
///
487+
/// This allows generated code to be used in a `#![no_std]` crate
488+
pub fn use_alloc_collections_lib(&mut self) -> &mut Self {
489+
self.collections_lib = CollectionsLib::Alloc;
490+
self
491+
}
492+
463493
/// Configures the output directory where generated Rust files will be written.
464494
///
465495
/// If unset, defaults to the `OUT_DIR` environment variable. `OUT_DIR` is set by Cargo when
@@ -581,6 +611,7 @@ impl default::Default for Config {
581611
fn default() -> Config {
582612
Config {
583613
service_generator: None,
614+
collections_lib: CollectionsLib::Std,
584615
btree_map: Vec::new(),
585616
type_attributes: Vec::new(),
586617
field_attributes: Vec::new(),

src/encoding.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
//!
33
//! Meant to be used only from `Message` implementations.
44
5-
use std::cmp::min;
6-
use std::mem;
7-
use std::u32;
8-
use std::usize;
5+
use core::cmp::min;
6+
use core::mem;
7+
use core::u32;
8+
use core::usize;
99

1010
use ::bytes::{Buf, BufMut};
1111

@@ -1040,10 +1040,8 @@ pub mod group {
10401040
/// generic over `HashMap` and `BTreeMap`.
10411041
macro_rules! map {
10421042
($map_ty:ident) => {
1043-
use std::collections::$map_ty;
1044-
use std::hash::Hash;
1045-
10461043
use crate::encoding::*;
1044+
use core::hash::Hash;
10471045

10481046
/// Generic protobuf map encode function.
10491047
pub fn encode<K, V, B, KE, KL, VE, VL>(
@@ -1225,11 +1223,17 @@ macro_rules! map {
12251223
};
12261224
}
12271225

1226+
#[cfg(feature = "std")]
12281227
pub mod hash_map {
1228+
use std::collections::HashMap;
12291229
map!(HashMap);
12301230
}
12311231

12321232
pub mod btree_map {
1233+
#[cfg(feature = "std")]
1234+
use std::collections::BTreeMap;
1235+
#[cfg(not(feature = "std"))]
1236+
use alloc::collections::BTreeMap;
12331237
map!(BTreeMap);
12341238
}
12351239

src/error.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
//! Protobuf encoding and decoding errors.
22
3+
// Note: this can always be alloc::borrow::Cow with rust >= 1.36 because alloc was stabilized
4+
#[cfg(feature = "std")]
35
use std::borrow::Cow;
6+
#[cfg(not(feature = "std"))]
7+
use alloc::borrow::Cow;
8+
9+
#[cfg(feature = "std")]
410
use std::error;
5-
use std::fmt;
11+
12+
use core::fmt;
13+
14+
#[cfg(feature = "std")]
615
use std::io;
716

817
/// A Protobuf message decoding error.
@@ -54,12 +63,14 @@ impl fmt::Display for DecodeError {
5463
}
5564
}
5665

66+
#[cfg(feature = "std")]
5767
impl error::Error for DecodeError {
5868
fn description(&self) -> &str {
5969
&self.description
6070
}
6171
}
6272

73+
#[cfg(feature = "std")]
6374
impl From<DecodeError> for io::Error {
6475
fn from(error: DecodeError) -> io::Error {
6576
io::Error::new(io::ErrorKind::InvalidData, error)
@@ -108,12 +119,14 @@ impl fmt::Display for EncodeError {
108119
}
109120
}
110121

122+
#[cfg(feature = "std")]
111123
impl error::Error for EncodeError {
112124
fn description(&self) -> &str {
113125
"failed to encode Protobuf message: insufficient buffer capacity"
114126
}
115127
}
116128

129+
#[cfg(feature = "std")]
117130
impl From<EncodeError> for io::Error {
118131
fn from(error: EncodeError) -> io::Error {
119132
io::Error::new(io::ErrorKind::InvalidInput, error)

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#![doc(html_root_url = "https://docs.rs/prost/0.5.0")]
22

3+
#![cfg_attr(not(feature = "std"), no_std)]
4+
35
mod error;
46
mod message;
57
mod types;

src/message.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use std::fmt::Debug;
2-
use std::usize;
1+
use core::fmt::Debug;
2+
use core::usize;
33

44
use ::bytes::{Buf, BufMut, IntoBuf};
55

tests-alloc/Cargo.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[package]
2+
name = "tests-alloc"
3+
version = "0.0.0"
4+
authors = ["Dan Burkert <dan@danburkert.com>"]
5+
publish = false
6+
edition = "2018"
7+
8+
build = "../tests/src/build.rs"
9+
10+
[lib]
11+
doctest = false
12+
path = "../tests/src/lib.rs"
13+
14+
[features]
15+
default = ["nostd-collections"]
16+
nostd-collections = []
17+
18+
[dependencies]
19+
bytes = "0.4.7"
20+
cfg-if = "0.1"
21+
prost = { path = ".." }
22+
prost-types = { path = "../prost-types" }
23+
protobuf = { path = "../protobuf" }
24+
25+
[dev-dependencies]
26+
diff = "0.1"
27+
prost-build = { path = "../prost-build" }
28+
tempfile = "3"
29+
30+
[build-dependencies]
31+
cfg-if = "0.1"
32+
env_logger = { version = "0.6", default-features = false }
33+
prost-build = { path = "../prost-build" }
34+
protobuf = { path = "../protobuf" }

tests/src/build.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,21 @@ fn main() {
2020
let src = PathBuf::from("../tests/src");
2121
let includes = &[src.clone()];
2222

23+
let mut config = prost_build::Config::new();
24+
2325
// Generate BTreeMap fields for all messages. This forces encoded output to be consistent, so
2426
// that encode/decode roundtrips can use encoded output for comparison. Otherwise trying to
2527
// compare based on the Rust PartialEq implementations is difficult, due to presence of NaN
2628
// values.
27-
let mut config = prost_build::Config::new();
28-
config.btree_map(&["."]);
29+
//
30+
// Note nostd collections implies Btree everywhere anyways
31+
cfg_if! {
32+
if #[cfg(feature = "nostd-collections")] {
33+
config.use_alloc_collections_lib();
34+
} else {
35+
config.btree_map(&["."]);
36+
}
37+
}
2938
// Tests for custom attributes
3039
config.type_attribute("Foo.Bar_Baz.Foo_barBaz", "#[derive(Eq, PartialOrd, Ord)]");
3140
config.type_attribute(

0 commit comments

Comments
 (0)