Skip to content

Commit

Permalink
Merge branch 'master' into duration-timestamp-overflow
Browse files Browse the repository at this point in the history
  • Loading branch information
argv-minus-one committed Jun 5, 2021
2 parents 431a4b6 + 28e4522 commit 75c20b8
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 110 deletions.
115 changes: 99 additions & 16 deletions README.md
Expand Up @@ -107,10 +107,91 @@ Scalar value types are converted as follows:
#### Enumerations

All `.proto` enumeration types convert to the Rust `i32` type. Additionally,
each enumeration type gets a corresponding Rust `enum` type, with helper methods
to convert `i32` values to the enum type. The `enum` type isn't used directly as
a field, because the Protobuf spec mandates that enumerations values are 'open',
and decoding unrecognized enumeration values must be possible.
each enumeration type gets a corresponding Rust `enum` type. For example, this
`proto` enum:

```proto
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
```

gets this corresponding Rust enum [1]:

```rust
pub enum PhoneType {
Mobile = 0,
Home = 1,
Work = 2,
}
```

You can convert a `PhoneType` value to an `i32` by doing:

```rust
PhoneType::Mobile as i32
```

The `#[derive(::prost::Enumeration)]` annotation added to the generated
`PhoneType` adds these associated functions to the type:

```rust
impl PhoneType {
pub fn is_valid(value: i32) -> bool { ... }
pub fn from_i32(value: i32) -> Option<PhoneType> { ... }
}
```

so you can convert an `i32` to its corresponding `PhoneType` value by doing,
for example:

```rust
let phone_type = 2i32;

match PhoneType::from_i32(phone_type) {
Some(PhoneType::Mobile) => ...,
Some(PhoneType::Home) => ...,
Some(PhoneType::Work) => ...,
None => ...,
}
```

Additionally, wherever a `proto` enum is used as a field in a `Message`, the
message will have 'accessor' methods to get/set the value of the field as the
Rust enum type. For instance, this proto `PhoneNumber` message that has a field
named `type` of type `PhoneType`:

```proto
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
```

will become the following Rust type [1] with methods `type` and `set_type`:

```rust
pub struct PhoneNumber {
pub number: String,
pub r#type: i32, // the `r#` is needed because `type` is a Rust keyword
}

impl PhoneNumber {
pub fn r#type(&self) -> PhoneType { ... }
pub fn set_type(&mut self, value: PhoneType) { ... }
}
```

Note that the getter methods will return the Rust enum's default value if the
field has an invalid `i32` value.

The `enum` type isn't used directly as a field, because the Protobuf spec
mandates that enumerations values are 'open', and decoding unrecognized
enumeration values must be possible.

[1] Annotations have been elided for clarity. See below for a full example.

#### Field Modifiers

Expand Down Expand Up @@ -215,38 +296,40 @@ message AddressBook {
and the generated Rust code (`tutorial.rs`):

```rust
#[derive(Clone, Debug, PartialEq, Message)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Person {
#[prost(string, tag="1")]
pub name: String,
pub name: ::prost::alloc::string::String,
/// Unique ID number for this person.
#[prost(int32, tag="2")]
pub id: i32,
#[prost(string, tag="3")]
pub email: String,
pub email: ::prost::alloc::string::String,
#[prost(message, repeated, tag="4")]
pub phones: Vec<person::PhoneNumber>,
pub phones: ::prost::alloc::vec::Vec<person::PhoneNumber>,
}
/// Nested message and enum types in `Person`.
pub mod person {
#[derive(Clone, Debug, PartialEq, Message)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PhoneNumber {
#[prost(string, tag="1")]
pub number: String,
pub number: ::prost::alloc::string::String,
#[prost(enumeration="PhoneType", tag="2")]
pub type_: i32,
pub r#type: i32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Enumeration)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum PhoneType {
Mobile = 0,
Home = 1,
Work = 2,
}
}
/// Our address book file is just one of these.
#[derive(Clone, Debug, PartialEq, Message)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AddressBook {
#[prost(message, repeated, tag="1")]
pub people: Vec<Person>,
pub people: ::prost::alloc::vec::Vec<Person>,
}
```

Expand All @@ -269,7 +352,7 @@ prost = { version = "0.6", default-features = false, features = ["prost-derive"]
prost-types = { version = "0.6", default-features = false }
```

Additionally, configure `prost-buid` to output `BTreeMap`s instead of `HashMap`s
Additionally, configure `prost-build` to output `BTreeMap`s instead of `HashMap`s
for all Protobuf `map` fields in your `build.rs`:

```rust
Expand Down Expand Up @@ -349,7 +432,7 @@ pub enum Gender {
There are two complications with trying to serialize Protobuf messages with
Serde:

- Protobuf fields require a numbered tag, and curently there appears to be no
- Protobuf fields require a numbered tag, and currently there appears to be no
mechanism suitable for this in `serde`.
- The mapping of Protobuf type to Rust type is not 1-to-1. As a result,
trait-based approaches to dispatching don't work very well. Example: six
Expand Down
84 changes: 45 additions & 39 deletions benches/varint.rs
@@ -1,13 +1,14 @@
use std::mem;

use bytes::Buf;
use criterion::{Benchmark, Criterion, Throughput};
use criterion::{Criterion, Throughput};
use prost::encoding::{decode_varint, encode_varint, encoded_len_varint};
use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng};

fn benchmark_varint(criterion: &mut Criterion, name: &str, mut values: Vec<u64>) {
// Shuffle the values in a stable order.
values.shuffle(&mut StdRng::seed_from_u64(0));
let name = format!("varint/{}", name);

let encoded_len = values
.iter()
Expand All @@ -16,53 +17,58 @@ fn benchmark_varint(criterion: &mut Criterion, name: &str, mut values: Vec<u64>)
.sum::<usize>() as u64;
let decoded_len = (values.len() * mem::size_of::<u64>()) as u64;

let encode_values = values.clone();
let encode = Benchmark::new("encode", move |b| {
let mut buf = Vec::<u8>::with_capacity(encode_values.len() * 10);
b.iter(|| {
buf.clear();
for &value in &encode_values {
encode_varint(value, &mut buf);
criterion
.benchmark_group(&name)
.bench_function("encode", {
let encode_values = values.clone();
move |b| {
let mut buf = Vec::<u8>::with_capacity(encode_values.len() * 10);
b.iter(|| {
buf.clear();
for &value in &encode_values {
encode_varint(value, &mut buf);
}
criterion::black_box(&buf);
})
}
criterion::black_box(&buf);
})
})
.throughput(Throughput::Bytes(encoded_len));
.throughput(Throughput::Bytes(encoded_len));

let decode_values = values.clone();
let decode = Benchmark::new("decode", move |b| {
let mut buf = Vec::with_capacity(decode_values.len() * 10);
for &value in &decode_values {
encode_varint(value, &mut buf);
}
criterion
.benchmark_group(&name)
.bench_function("decode", {
let decode_values = values.clone();

b.iter(|| {
let mut buf = &mut buf.as_slice();
while buf.has_remaining() {
let result = decode_varint(&mut buf);
debug_assert!(result.is_ok());
criterion::black_box(&result);
}
})
})
.throughput(Throughput::Bytes(decoded_len));
move |b| {
let mut buf = Vec::with_capacity(decode_values.len() * 10);
for &value in &decode_values {
encode_varint(value, &mut buf);
}

let encoded_len = Benchmark::new("encoded_len", move |b| {
b.iter(|| {
let mut sum = 0;
for &value in &values {
sum += encoded_len_varint(value);
b.iter(|| {
let mut buf = &mut buf.as_slice();
while buf.has_remaining() {
let result = decode_varint(&mut buf);
debug_assert!(result.is_ok());
criterion::black_box(&result);
}
})
}
criterion::black_box(sum);
})
})
.throughput(Throughput::Bytes(decoded_len));
.throughput(Throughput::Bytes(decoded_len));

let name = format!("varint/{}", name);
criterion
.bench(&name, encode)
.bench(&name, decode)
.bench(&name, encoded_len);
.benchmark_group(&name)
.bench_function("encoded_len", move |b| {
b.iter(|| {
let mut sum = 0;
for &value in &values {
sum += encoded_len_varint(value);
}
criterion::black_box(sum);
})
})
.throughput(Throughput::Bytes(decoded_len));
}

fn main() {
Expand Down
2 changes: 1 addition & 1 deletion prost-build/Cargo.toml
Expand Up @@ -12,7 +12,7 @@ edition = "2018"
[dependencies]
bytes = { version = "1", default-features = false }
heck = "0.3"
itertools = "0.9"
itertools = "0.10"
log = "0.4"
multimap = { version = "0.8", default-features = false }
petgraph = { version = "0.5", default-features = false }
Expand Down
12 changes: 6 additions & 6 deletions prost-build/src/code_generator.rs
Expand Up @@ -237,12 +237,12 @@ impl<'a> CodeGenerator<'a> {

for (idx, oneof) in message.oneof_decl.into_iter().enumerate() {
let idx = idx as i32;
self.append_oneof(
&fq_message_name,
oneof,
idx,
oneof_fields.remove(&idx).unwrap(),
);
// optional fields create a synthetic oneof that we want to skip
let fields = match oneof_fields.remove(&idx) {
Some(fields) => fields,
None => continue,
};
self.append_oneof(&fq_message_name, oneof, idx, fields);
}

self.pop_mod();
Expand Down
6 changes: 3 additions & 3 deletions prost-build/src/lib.rs
Expand Up @@ -51,10 +51,10 @@
//! `build.rs` build-script:
//!
//! ```rust,no_run
//! # use std::io::Result;
//! use std::io::Result;
//! fn main() -> Result<()> {
//! prost_build::compile_protos(&["src/items.proto"], &["src/"])?;
//! Ok(())
//! prost_build::compile_protos(&["src/items.proto"], &["src/"])?;
//! Ok(())
//! }
//! ```
//!
Expand Down
2 changes: 1 addition & 1 deletion prost-derive/Cargo.toml
Expand Up @@ -14,7 +14,7 @@ proc_macro = true

[dependencies]
anyhow = "1"
itertools = "0.9"
itertools = "0.10"
proc-macro2 = "1"
quote = "1"
syn = { version = "1", features = [ "extra-traits" ] }
10 changes: 5 additions & 5 deletions prost-derive/src/field/mod.rs
Expand Up @@ -32,7 +32,7 @@ impl Field {
/// If the meta items are invalid, an error will be returned.
/// If the field should be ignored, `None` is returned.
pub fn new(attrs: Vec<Attribute>, inferred_tag: Option<u32>) -> Result<Option<Field>, Error> {
let attrs = prost_attrs(attrs)?;
let attrs = prost_attrs(attrs);

// TODO: check for ignore attribute.

Expand All @@ -58,7 +58,7 @@ impl Field {
/// If the meta items are invalid, an error will be returned.
/// If the field should be ignored, `None` is returned.
pub fn new_oneof(attrs: Vec<Attribute>) -> Result<Option<Field>, Error> {
let attrs = prost_attrs(attrs)?;
let attrs = prost_attrs(attrs);

// TODO: check for ignore attribute.

Expand Down Expand Up @@ -224,8 +224,8 @@ impl fmt::Display for Label {
}

/// Get the items belonging to the 'prost' list attribute, e.g. `#[prost(foo, bar="baz")]`.
pub(super) fn prost_attrs(attrs: Vec<Attribute>) -> Result<Vec<Meta>, Error> {
Ok(attrs
fn prost_attrs(attrs: Vec<Attribute>) -> Vec<Meta> {
attrs
.iter()
.flat_map(Attribute::parse_meta)
.flat_map(|meta| match meta {
Expand All @@ -244,7 +244,7 @@ pub(super) fn prost_attrs(attrs: Vec<Attribute>) -> Result<Vec<Meta>, Error> {
NestedMeta::Lit(lit) => bail!("invalid prost attribute: {:?}", lit),
}
})
.collect())
.collect()
}

pub fn set_option<T>(option: &mut Option<T>, value: T, message: &str) -> Result<(), Error>
Expand Down
6 changes: 0 additions & 6 deletions protobuf/benches/dataset.rs
Expand Up @@ -84,9 +84,6 @@ dataset!(google_message1_proto2, proto2::GoogleMessage1);
dataset!(google_message1_proto3, proto3::GoogleMessage1);
dataset!(google_message2, proto2::GoogleMessage2);
dataset!(google_message3_1, GoogleMessage3);
dataset!(google_message3_2, GoogleMessage3);
dataset!(google_message3_3, GoogleMessage3);
dataset!(google_message3_4, GoogleMessage3);
dataset!(google_message3_5, GoogleMessage3);
dataset!(google_message4, GoogleMessage4);

Expand All @@ -95,9 +92,6 @@ criterion_group!(
google_message1_proto2,
google_message1_proto3,
google_message2,
google_message3_2,
google_message3_3,
google_message3_4,
);

criterion_group! {
Expand Down
5 changes: 1 addition & 4 deletions protobuf/build.rs
Expand Up @@ -270,8 +270,5 @@ fn install_datasets(src_dir: &Path, prefix_dir: &Path) -> Result<()> {
.with_context(|| format!("failed to move {}", dataset.display()))?;
}

download_tarball(
"https://storage.googleapis.com/protobuf_opensource_benchmark_data/datasets.tar.gz",
share_dir,
)
Ok(())
}

0 comments on commit 75c20b8

Please sign in to comment.