summaryrefslogtreecommitdiff
path: root/tutorial/rs
diff options
context:
space:
mode:
authorAllen George <allen.george@gmail.com>2016-11-02 08:01:08 -0400
committerJens Geyer <jensg@apache.org>2017-01-26 01:34:16 +0100
commit8b96bfbf1eb058d3c378535e90c1e90280224bb4 (patch)
tree9b7719c1bb205f7132f4b1870f974c6ceb7f0bac /tutorial/rs
parent6fe7fa1b7ed72215a59e93c680a209a99530fada (diff)
downloadthrift-8b96bfbf1eb058d3c378535e90c1e90280224bb4.tar.gz
THRIFT-2945 Add Rust support
Client: Rust Patch: Allen George <allen.george@gmail.com> This closes #1147
Diffstat (limited to 'tutorial/rs')
-rw-r--r--tutorial/rs/Cargo.toml16
-rw-r--r--tutorial/rs/Makefile.am52
-rw-r--r--tutorial/rs/README.md330
-rw-r--r--tutorial/rs/src/bin/tutorial_client.rs136
-rw-r--r--tutorial/rs/src/bin/tutorial_server.rs168
-rw-r--r--tutorial/rs/src/lib.rs23
6 files changed, 725 insertions, 0 deletions
diff --git a/tutorial/rs/Cargo.toml b/tutorial/rs/Cargo.toml
new file mode 100644
index 000000000..9075db7a6
--- /dev/null
+++ b/tutorial/rs/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "thrift-tutorial"
+version = "0.1.0"
+license = "Apache-2.0"
+authors = ["Apache Thrift Developers <dev@thrift.apache.org>"]
+exclude = ["Makefile*", "shared.rs", "tutorial.rs"]
+publish = false
+
+[dependencies]
+clap = "2.18.0"
+ordered-float = "0.3.0"
+try_from = "0.2.0"
+
+[dependencies.thrift]
+path = "../../lib/rs"
+
diff --git a/tutorial/rs/Makefile.am b/tutorial/rs/Makefile.am
new file mode 100644
index 000000000..666331e4a
--- /dev/null
+++ b/tutorial/rs/Makefile.am
@@ -0,0 +1,52 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+THRIFT = $(top_builddir)/compiler/cpp/thrift
+
+gen-rs/tutorial.rs gen-rs/shared.rs: $(top_srcdir)/tutorial/tutorial.thrift
+ $(THRIFT) -out src --gen rs -r $<
+
+all-local: gen-rs/tutorial.rs
+ $(CARGO) build
+ [ -d bin ] || mkdir bin
+ cp target/debug/tutorial_server bin/tutorial_server
+ cp target/debug/tutorial_client bin/tutorial_client
+
+check: all
+
+tutorialserver: all
+ bin/tutorial_server
+
+tutorialclient: all
+ bin/tutorial_client
+
+clean-local:
+ $(CARGO) clean
+ -$(RM) Cargo.lock
+ -$(RM) src/shared.rs
+ -$(RM) src/tutorial.rs
+ -$(RM) -r bin
+
+EXTRA_DIST = \
+ Cargo.toml \
+ src/lib.rs \
+ src/bin/tutorial_server.rs \
+ src/bin/tutorial_client.rs \
+ README.md
+
diff --git a/tutorial/rs/README.md b/tutorial/rs/README.md
new file mode 100644
index 000000000..4d0d7c8af
--- /dev/null
+++ b/tutorial/rs/README.md
@@ -0,0 +1,330 @@
+# Rust Language Bindings for Thrift
+
+## Getting Started
+
+1. Get the [Thrift compiler](https://thrift.apache.org).
+
+2. Add the following crates to your `Cargo.toml`.
+
+```toml
+thrift = "x.y.z" # x.y.z is the version of the thrift compiler
+ordered_float = "0.3.0"
+try_from = "0.2.0"
+```
+
+3. Add the same crates to your `lib.rs` or `main.rs`.
+
+```rust
+extern crate ordered_float;
+extern crate thrift;
+extern crate try_from;
+```
+
+4. Generate Rust sources for your IDL (for example, `Tutorial.thrift`).
+
+```shell
+thrift -out my_rust_program/src --gen rs -r Tutorial.thrift
+```
+
+5. Use the generated source in your code.
+
+```rust
+// add extern crates here, or in your lib.rs
+extern crate ordered_float;
+extern crate thrift;
+extern crate try_from;
+
+// generated Rust module
+mod tutorial;
+
+use std::cell::RefCell;
+use std::rc::Rc;
+use thrift::protocol::{TInputProtocol, TOutputProtocol};
+use thrift::protocol::{TCompactInputProtocol, TCompactOutputProtocol};
+use thrift::transport::{TFramedTransport, TTcpTransport, TTransport};
+use tutorial::{CalculatorSyncClient, TCalculatorSyncClient};
+use tutorial::{Operation, Work};
+
+fn main() {
+ match run() {
+ Ok(()) => println!("client ran successfully"),
+ Err(e) => {
+ println!("client failed with {:?}", e);
+ std::process::exit(1);
+ }
+ }
+}
+
+fn run() -> thrift::Result<()> {
+ //
+ // build client
+ //
+
+ println!("connect to server on 127.0.0.1:9090");
+ let mut t = TTcpTransport::new();
+ let t = match t.open("127.0.0.1:9090") {
+ Ok(()) => t,
+ Err(e) => {
+ return Err(
+ format!("failed to connect with {:?}", e).into()
+ );
+ }
+ };
+
+ let t = Rc::new(RefCell::new(
+ Box::new(t) as Box<TTransport>
+ ));
+ let t = Rc::new(RefCell::new(
+ Box::new(TFramedTransport::new(t)) as Box<TTransport>
+ ));
+
+ let i_prot: Box<TInputProtocol> = Box::new(
+ TCompactInputProtocol::new(t.clone())
+ );
+ let o_prot: Box<TOutputProtocol> = Box::new(
+ TCompactOutputProtocol::new(t.clone())
+ );
+
+ let client = CalculatorSyncClient::new(i_prot, o_prot);
+
+ //
+ // alright! - let's make some calls
+ //
+
+ // two-way, void return
+ client.ping()?;
+
+ // two-way with some return
+ let res = client.calculate(
+ 72,
+ Work::new(7, 8, Operation::MULTIPLY, None)
+ )?;
+ println!("multiplied 7 and 8, got {}", res);
+
+ // two-way and returns a Thrift-defined exception
+ let res = client.calculate(
+ 77,
+ Work::new(2, 0, Operation::DIVIDE, None)
+ );
+ match res {
+ Ok(v) => panic!("shouldn't have succeeded with result {}", v),
+ Err(e) => println!("divide by zero failed with {:?}", e),
+ }
+
+ // one-way
+ client.zip()?;
+
+ // done!
+ Ok(())
+}
+```
+
+## Code Generation
+
+### Thrift Files and Generated Modules
+
+The Thrift code generator takes each Thrift file and generates a Rust module
+with the same name snake-cased. For example, running the compiler on
+`ThriftTest.thrift` creates `thrift_test.rs`. To use these generated files add
+`mod ...` and `use ...` declarations to your `lib.rs` or `main.rs` - one for
+each generated file.
+
+### Results and Errors
+
+The Thrift runtime library defines a `thrift::Result` and a `thrift::Error` type,
+both of which are used throught the runtime library and in all generated code.
+Conversions are defined from `std::io::Error`, `str` and `String` into
+`thrift::Error`.
+
+### Thrift Type and their Rust Equivalents
+
+Thrift defines a number of types, each of which is translated into its Rust
+equivalent by the code generator.
+
+* Primitives (bool, i8, i16, i32, i64, double, string, binary)
+* Typedefs
+* Enums
+* Containers
+* Structs
+* Unions
+* Exceptions
+* Services
+* Constants (primitives, containers, structs)
+
+In addition, unless otherwise noted, thrift includes are translated into
+`use ...` statements in the generated code, and all declarations, parameters,
+traits and types in the generated code are namespaced appropriately.
+
+The following subsections cover each type and their generated Rust equivalent.
+
+### Primitives
+
+Thrift primitives have straightforward Rust equivalents.
+
+* bool: `bool`
+* i8: `i8`
+* i16: `i16`
+* i32: `i32`
+* i64: `i64`
+* double: `OrderedFloat<f64>`
+* string: `String`
+* binary: `Vec<u8>`
+
+### Typedefs
+
+A typedef is translated to a `pub type` declaration.
+
+```thrift
+typedef i64 UserId
+
+typedef map<string, Bonk> MapType
+```
+```rust
+pub type UserId = 164;
+
+pub type MapType = BTreeMap<String, Bonk>;
+```
+
+### Enums
+
+A Thrift enum is represented as a Rust enum, and each variant is transcribed 1:1.
+
+```thrift
+enum Numberz
+{
+ ONE = 1,
+ TWO,
+ THREE,
+ FIVE = 5,
+ SIX,
+ EIGHT = 8
+}
+```
+
+```rust
+#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub enum Numberz {
+ ONE = 1,
+ TWO = 2,
+ THREE = 3,
+ FIVE = 5,
+ SIX = 6,
+ EIGHT = 8,
+}
+
+impl TryFrom<i32> for Numberz {
+ // ...
+}
+
+```
+
+### Containers
+
+Thrift has three container types: list, set and map. They are translated into
+Rust `Vec`, `BTreeSet` and `BTreeMap` respectively. Any Thrift type (this
+includes structs, enums and typedefs) can be a list/set element or a map
+key/value.
+
+#### List
+
+```thrift
+list <i32> numbers
+```
+
+```rust
+numbers: Vec<i32>
+```
+
+#### Set
+
+```thrift
+set <i32> numbers
+```
+
+```rust
+numbers: BTreeSet<i32>
+```
+
+#### Map
+
+```thrift
+map <string, i32> numbers
+```
+
+```rust
+numbers: BTreeMap<String, i32>
+```
+
+### Structs
+
+A Thrift struct is represented as a Rust struct, and each field transcribed 1:1.
+
+```thrift
+struct CrazyNesting {
+ 1: string string_field,
+ 2: optional set<Insanity> set_field,
+ 3: required list<
+ map<set<i32>, map<i32,set<list<map<Insanity,string>>>>>
+ >
+ 4: binary binary_field
+}
+```
+```rust
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub struct CrazyNesting {
+ pub string_field: Option<String>,
+ pub set_field: Option<BTreeSet<Insanity>>,
+ pub list_field: Vec<
+ BTreeMap<
+ BTreeSet<i32>,
+ BTreeMap<i32, BTreeSet<Vec<BTreeMap<Insanity, String>>>>
+ >
+ >,
+ pub binary_field: Option<Vec<u8>>,
+}
+
+impl CrazyNesting {
+ pub fn read_from_in_protocol(i_prot: &mut TInputProtocol)
+ ->
+ thrift::Result<CrazyNesting> {
+ // ...
+ }
+ pub fn write_to_out_protocol(&self, o_prot: &mut TOutputProtocol)
+ ->
+ thrift::Result<()> {
+ // ...
+ }
+}
+
+```
+##### Optionality
+
+Thrift has 3 "optionality" types:
+
+1. Required
+2. Optional
+3. Default
+
+The Rust code generator encodes *Required* fields as the bare type itself, while
+*Optional* and *Default* fields are encoded as `Option<TypeName>`.
+
+```thrift
+struct Foo {
+ 1: required string bar // 1. required
+ 2: optional string baz // 2. optional
+ 3: string qux // 3. default
+}
+```
+
+```rust
+pub struct Foo {
+ bar: String, // 1. required
+ baz: Option<String>, // 2. optional
+ qux: Option<String>, // 3. default
+}
+```
+
+## Known Issues
+
+* Struct constants are not supported
+* Map, list and set constants require a const holder struct \ No newline at end of file
diff --git a/tutorial/rs/src/bin/tutorial_client.rs b/tutorial/rs/src/bin/tutorial_client.rs
new file mode 100644
index 000000000..2b0d4f908
--- /dev/null
+++ b/tutorial/rs/src/bin/tutorial_client.rs
@@ -0,0 +1,136 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#[macro_use]
+extern crate clap;
+
+extern crate thrift;
+extern crate thrift_tutorial;
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use thrift::protocol::{TInputProtocol, TOutputProtocol};
+use thrift::protocol::{TCompactInputProtocol, TCompactOutputProtocol};
+use thrift::transport::{TFramedTransport, TTcpTransport, TTransport};
+
+use thrift_tutorial::shared::TSharedServiceSyncClient;
+use thrift_tutorial::tutorial::{CalculatorSyncClient, TCalculatorSyncClient, Operation, Work};
+
+fn main() {
+ match run() {
+ Ok(()) => println!("tutorial client ran successfully"),
+ Err(e) => {
+ println!("tutorial client failed with error {:?}", e);
+ std::process::exit(1);
+ }
+ }
+}
+
+fn run() -> thrift::Result<()> {
+ let options = clap_app!(rust_tutorial_client =>
+ (version: "0.1.0")
+ (author: "Apache Thrift Developers <dev@thrift.apache.org>")
+ (about: "Thrift Rust tutorial client")
+ (@arg host: --host +takes_value "host on which the tutorial server listens")
+ (@arg port: --port +takes_value "port on which the tutorial server listens")
+ );
+ let matches = options.get_matches();
+
+ // get any passed-in args or the defaults
+ let host = matches.value_of("host").unwrap_or("127.0.0.1");
+ let port = value_t!(matches, "port", u16).unwrap_or(9090);
+
+ // build our client and connect to the host:port
+ let mut client = new_client(host, port)?;
+
+ // alright!
+ // let's start making some calls
+
+ // let's start with a ping; the server should respond
+ println!("ping!");
+ client.ping()?;
+
+ // simple add
+ println!("add");
+ let res = client.add(1, 2)?;
+ println!("added 1, 2 and got {}", res);
+
+ let logid = 32;
+
+ // let's do...a multiply!
+ let res = client.calculate(logid, Work::new(7, 8, Operation::MULTIPLY, None))?;
+ println!("multiplied 7 and 8 and got {}", res);
+
+ // let's get the log for it
+ let res = client.get_struct(32)?;
+ println!("got log {:?} for operation {}", res, logid);
+
+ // ok - let's be bad :(
+ // do a divide by 0
+ // logid doesn't matter; won't be recorded
+ let res = client.calculate(77, Work::new(2, 0, Operation::DIVIDE, "we bad".to_owned()));
+
+ // we should have gotten an exception back
+ match res {
+ Ok(v) => panic!("should not have succeeded with result {}", v),
+ Err(e) => println!("divide by zero failed with error {:?}", e),
+ }
+
+ // let's do a one-way call
+ println!("zip");
+ client.zip()?;
+
+ // and then close out with a final ping
+ println!("ping!");
+ client.ping()?;
+
+ Ok(())
+}
+
+fn new_client(host: &str, port: u16) -> thrift::Result<CalculatorSyncClient> {
+ let mut t = TTcpTransport::new();
+
+ // open the underlying TCP stream
+ println!("connecting to tutorial server on {}:{}", host, port);
+ let t = match t.open(&format!("{}:{}", host, port)) {
+ Ok(()) => t,
+ Err(e) => {
+ return Err(format!("failed to open tcp stream to {}:{} error:{:?}",
+ host,
+ port,
+ e)
+ .into());
+ }
+ };
+
+ // refcounted because it's shared by both input and output transports
+ let t = Rc::new(RefCell::new(Box::new(t) as Box<TTransport>));
+
+ // wrap a raw socket (slow) with a buffered transport of some kind
+ let t = Box::new(TFramedTransport::new(t)) as Box<TTransport>;
+
+ // refcounted again because it's shared by both input and output protocols
+ let t = Rc::new(RefCell::new(t));
+
+ // now create the protocol implementations
+ let i_prot = Box::new(TCompactInputProtocol::new(t.clone())) as Box<TInputProtocol>;
+ let o_prot = Box::new(TCompactOutputProtocol::new(t.clone())) as Box<TOutputProtocol>;
+
+ // we're done!
+ Ok(CalculatorSyncClient::new(i_prot, o_prot))
+}
diff --git a/tutorial/rs/src/bin/tutorial_server.rs b/tutorial/rs/src/bin/tutorial_server.rs
new file mode 100644
index 000000000..9cc186649
--- /dev/null
+++ b/tutorial/rs/src/bin/tutorial_server.rs
@@ -0,0 +1,168 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#[macro_use]
+extern crate clap;
+
+extern crate thrift;
+extern crate thrift_tutorial;
+
+use std::collections::HashMap;
+use std::convert::{From, Into};
+use std::default::Default;
+
+use thrift::protocol::{TInputProtocolFactory, TOutputProtocolFactory};
+use thrift::protocol::{TCompactInputProtocolFactory, TCompactOutputProtocolFactory};
+use thrift::server::TSimpleServer;
+
+use thrift::transport::{TFramedTransportFactory, TTransportFactory};
+use thrift_tutorial::shared::{SharedServiceSyncHandler, SharedStruct};
+use thrift_tutorial::tutorial::{CalculatorSyncHandler, CalculatorSyncProcessor};
+use thrift_tutorial::tutorial::{InvalidOperation, Operation, Work};
+
+fn main() {
+ match run() {
+ Ok(()) => println!("tutorial server ran successfully"),
+ Err(e) => {
+ println!("tutorial server failed with error {:?}", e);
+ std::process::exit(1);
+ }
+ }
+}
+
+fn run() -> thrift::Result<()> {
+ let options = clap_app!(rust_tutorial_server =>
+ (version: "0.1.0")
+ (author: "Apache Thrift Developers <dev@thrift.apache.org>")
+ (about: "Thrift Rust tutorial server")
+ (@arg port: --port +takes_value "port on which the tutorial server listens")
+ );
+ let matches = options.get_matches();
+
+ let port = value_t!(matches, "port", u16).unwrap_or(9090);
+ let listen_address = format!("127.0.0.1:{}", port);
+
+ println!("binding to {}", listen_address);
+
+ let i_tran_fact: Box<TTransportFactory> = Box::new(TFramedTransportFactory::new());
+ let i_prot_fact: Box<TInputProtocolFactory> = Box::new(TCompactInputProtocolFactory::new());
+
+ let o_tran_fact: Box<TTransportFactory> = Box::new(TFramedTransportFactory::new());
+ let o_prot_fact: Box<TOutputProtocolFactory> = Box::new(TCompactOutputProtocolFactory::new());
+
+ // demux incoming messages
+ let processor = CalculatorSyncProcessor::new(CalculatorServer { ..Default::default() });
+
+ // create the server and start listening
+ let mut server = TSimpleServer::new(i_tran_fact,
+ i_prot_fact,
+ o_tran_fact,
+ o_prot_fact,
+ processor);
+
+ server.listen(&listen_address)
+}
+
+/// Handles incoming Calculator service calls.
+struct CalculatorServer {
+ log: HashMap<i32, SharedStruct>,
+}
+
+impl Default for CalculatorServer {
+ fn default() -> CalculatorServer {
+ CalculatorServer { log: HashMap::new() }
+ }
+}
+
+// since Calculator extends SharedService we have to implement the
+// handler for both traits.
+//
+
+// SharedService handler
+impl SharedServiceSyncHandler for CalculatorServer {
+ fn handle_get_struct(&mut self, key: i32) -> thrift::Result<SharedStruct> {
+ self.log
+ .get(&key)
+ .cloned()
+ .ok_or_else(|| format!("could not find log for key {}", key).into())
+ }
+}
+
+// Calculator handler
+impl CalculatorSyncHandler for CalculatorServer {
+ fn handle_ping(&mut self) -> thrift::Result<()> {
+ println!("pong!");
+ Ok(())
+ }
+
+ fn handle_add(&mut self, num1: i32, num2: i32) -> thrift::Result<i32> {
+ println!("handling add: n1:{} n2:{}", num1, num2);
+ Ok(num1 + num2)
+ }
+
+ fn handle_calculate(&mut self, logid: i32, w: Work) -> thrift::Result<i32> {
+ println!("handling calculate: l:{}, w:{:?}", logid, w);
+
+ let res = if let Some(ref op) = w.op {
+ if w.num1.is_none() || w.num2.is_none() {
+ Err(InvalidOperation {
+ what_op: Some(*op as i32),
+ why: Some("no operands specified".to_owned()),
+ })
+ } else {
+ // so that I don't have to call unwrap() multiple times below
+ let num1 = w.num1.as_ref().expect("operands checked");
+ let num2 = w.num2.as_ref().expect("operands checked");
+
+ match *op {
+ Operation::ADD => Ok(num1 + num2),
+ Operation::SUBTRACT => Ok(num1 - num2),
+ Operation::MULTIPLY => Ok(num1 * num2),
+ Operation::DIVIDE => {
+ if *num2 == 0 {
+ Err(InvalidOperation {
+ what_op: Some(*op as i32),
+ why: Some("divide by 0".to_owned()),
+ })
+ } else {
+ Ok(num1 / num2)
+ }
+ }
+ }
+ }
+ } else {
+ Err(InvalidOperation::new(None, "no operation specified".to_owned()))
+ };
+
+ // if the operation was successful log it
+ if let Ok(ref v) = res {
+ self.log.insert(logid, SharedStruct::new(logid, format!("{}", v)));
+ }
+
+ // the try! macro automatically maps errors
+ // but, since we aren't using that here we have to map errors manually
+ //
+ // exception structs defined in the IDL have an auto-generated
+ // impl of From::from
+ res.map_err(From::from)
+ }
+
+ fn handle_zip(&mut self) -> thrift::Result<()> {
+ println!("handling zip");
+ Ok(())
+ }
+}
diff --git a/tutorial/rs/src/lib.rs b/tutorial/rs/src/lib.rs
new file mode 100644
index 000000000..40007e5d3
--- /dev/null
+++ b/tutorial/rs/src/lib.rs
@@ -0,0 +1,23 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+extern crate ordered_float;
+extern crate thrift;
+extern crate try_from;
+
+pub mod shared;
+pub mod tutorial;