Get the Thrift compiler.
Add the following crates to your Cargo.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"
lib.rs
or main.rs
.extern crate ordered_float; extern crate thrift; extern crate try_from;
Tutorial.thrift
).thrift -out my_rust_program/src --gen rs -r Tutorial.thrift
// add extern crates here, or in your lib.rs extern crate ordered_float; extern crate thrift; extern crate try_from; // generated Rust module use tutorial; use thrift::protocol::{TCompactInputProtocol, TCompactOutputProtocol}; use thrift::protocol::{TInputProtocol, TOutputProtocol}; use thrift::transport::{TFramedReadTransport, TFramedWriteTransport}; use thrift::transport::{TIoChannel, TTcpChannel}; 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 c = TTcpTransport::new(); c.open("127.0.0.1:9090")?; let (i_chan, o_chan) = c.split()?; let i_prot = TCompactInputProtocol::new( TFramedReadTransport::new(i_chan) ); let o_prot = TCompactOutputProtocol::new( TFramedWriteTransport::new(o_chan) ); 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(()) }
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.
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 defines a number of types, each of which is translated into its Rust equivalent by the code generator.
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.
Thrift primitives have straightforward Rust equivalents.
bool
i8
i16
i32
i64
OrderedFloat<f64>
String
Vec<u8>
A typedef is translated to a pub type
declaration.
typedef i64 UserId typedef map<string, UserId> MapType
pub type UserId = i64; pub type MapType = BTreeMap<String, Bonk>;
A Thrift enum is represented as a Rust enum, and each variant is transcribed 1:1.
enum Numberz { ONE = 1, TWO, THREE, FIVE = 5, SIX, EIGHT = 8 }
#[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 { // ... }
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 <i32> numbers
numbers: Vec<i32>
set <i32> numbers
numbers: BTreeSet<i32>
map <string, i32> numbers
numbers: BTreeMap<String, i32>
A Thrift struct is represented as a Rust struct, and each field transcribed 1:1.
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 }
#[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<()> { // ... } }
Thrift has 3 “optionality” types:
The Rust code generator encodes Required fields as the bare type itself, while Optional and Default fields are encoded as Option<TypeName>
.
struct Foo { 1: required string bar // 1. required 2: optional string baz // 2. optional 3: string qux // 3. default }
pub struct Foo { bar: String, // 1. required baz: Option<String>, // 2. optional qux: Option<String>, // 3. default }