| # Rust Language Bindings for Thrift |
| |
| ## Getting Started |
| |
| 1. Get the [Thrift compiler](https://thrift.apache.org). |
| |
| 2. Add the thrift crate to your `Cargo.toml`. |
| |
| ```toml |
| thrift = "x.y.z" # x.y.z is the version of the thrift compiler |
| ``` |
| |
| 3. Generate Rust sources for your IDL (for example, `Tutorial.thrift`). |
| |
| ```shell |
| thrift -out my_rust_program/src --gen rs -r Tutorial.thrift |
| ``` |
| |
| 4. Use the generated source in your code. |
| |
| ```rust |
| // generated Rust module from Thrift IDL |
| mod 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 = TTcpChannel::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 mut 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 throughout 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, UserId> MapType |
| ``` |
| ```rust |
| pub type UserId = i64; |
| |
| 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 |