THRIFT-2945 Add Rust support
Client: Rust
Patch: Allen George <allen.george@gmail.com>

This closes #1147
diff --git a/tutorial/Makefile.am b/tutorial/Makefile.am
index efa314a..d8ad09c 100755
--- a/tutorial/Makefile.am
+++ b/tutorial/Makefile.am
@@ -74,6 +74,10 @@
 SUBDIRS += dart
 endif
 
+if WITH_RS
+SUBDIRS += rs
+endif
+
 #
 # generate html for ThriftTest.thrift
 #
diff --git a/tutorial/rs/Cargo.toml b/tutorial/rs/Cargo.toml
new file mode 100644
index 0000000..9075db7
--- /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 0000000..666331e
--- /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 0000000..4d0d7c8
--- /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 0000000..2b0d4f9
--- /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 0000000..9cc1866
--- /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 0000000..40007e5
--- /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;