| Allen George | 8b96bfb | 2016-11-02 08:01:08 -0400 | [diff] [blame^] | 1 | # Rust Language Bindings for Thrift |
| 2 | |
| 3 | ## Getting Started |
| 4 | |
| 5 | 1. Get the [Thrift compiler](https://thrift.apache.org). |
| 6 | |
| 7 | 2. Add the following crates to your `Cargo.toml`. |
| 8 | |
| 9 | ```toml |
| 10 | thrift = "x.y.z" # x.y.z is the version of the thrift compiler |
| 11 | ordered_float = "0.3.0" |
| 12 | try_from = "0.2.0" |
| 13 | ``` |
| 14 | |
| 15 | 3. Add the same crates to your `lib.rs` or `main.rs`. |
| 16 | |
| 17 | ```rust |
| 18 | extern crate ordered_float; |
| 19 | extern crate thrift; |
| 20 | extern crate try_from; |
| 21 | ``` |
| 22 | |
| 23 | 4. Generate Rust sources for your IDL (for example, `Tutorial.thrift`). |
| 24 | |
| 25 | ```shell |
| 26 | thrift -out my_rust_program/src --gen rs -r Tutorial.thrift |
| 27 | ``` |
| 28 | |
| 29 | 5. Use the generated source in your code. |
| 30 | |
| 31 | ```rust |
| 32 | // add extern crates here, or in your lib.rs |
| 33 | extern crate ordered_float; |
| 34 | extern crate thrift; |
| 35 | extern crate try_from; |
| 36 | |
| 37 | // generated Rust module |
| 38 | mod tutorial; |
| 39 | |
| 40 | use std::cell::RefCell; |
| 41 | use std::rc::Rc; |
| 42 | use thrift::protocol::{TInputProtocol, TOutputProtocol}; |
| 43 | use thrift::protocol::{TCompactInputProtocol, TCompactOutputProtocol}; |
| 44 | use thrift::transport::{TFramedTransport, TTcpTransport, TTransport}; |
| 45 | use tutorial::{CalculatorSyncClient, TCalculatorSyncClient}; |
| 46 | use tutorial::{Operation, Work}; |
| 47 | |
| 48 | fn main() { |
| 49 | match run() { |
| 50 | Ok(()) => println!("client ran successfully"), |
| 51 | Err(e) => { |
| 52 | println!("client failed with {:?}", e); |
| 53 | std::process::exit(1); |
| 54 | } |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | fn run() -> thrift::Result<()> { |
| 59 | // |
| 60 | // build client |
| 61 | // |
| 62 | |
| 63 | println!("connect to server on 127.0.0.1:9090"); |
| 64 | let mut t = TTcpTransport::new(); |
| 65 | let t = match t.open("127.0.0.1:9090") { |
| 66 | Ok(()) => t, |
| 67 | Err(e) => { |
| 68 | return Err( |
| 69 | format!("failed to connect with {:?}", e).into() |
| 70 | ); |
| 71 | } |
| 72 | }; |
| 73 | |
| 74 | let t = Rc::new(RefCell::new( |
| 75 | Box::new(t) as Box<TTransport> |
| 76 | )); |
| 77 | let t = Rc::new(RefCell::new( |
| 78 | Box::new(TFramedTransport::new(t)) as Box<TTransport> |
| 79 | )); |
| 80 | |
| 81 | let i_prot: Box<TInputProtocol> = Box::new( |
| 82 | TCompactInputProtocol::new(t.clone()) |
| 83 | ); |
| 84 | let o_prot: Box<TOutputProtocol> = Box::new( |
| 85 | TCompactOutputProtocol::new(t.clone()) |
| 86 | ); |
| 87 | |
| 88 | let client = CalculatorSyncClient::new(i_prot, o_prot); |
| 89 | |
| 90 | // |
| 91 | // alright! - let's make some calls |
| 92 | // |
| 93 | |
| 94 | // two-way, void return |
| 95 | client.ping()?; |
| 96 | |
| 97 | // two-way with some return |
| 98 | let res = client.calculate( |
| 99 | 72, |
| 100 | Work::new(7, 8, Operation::MULTIPLY, None) |
| 101 | )?; |
| 102 | println!("multiplied 7 and 8, got {}", res); |
| 103 | |
| 104 | // two-way and returns a Thrift-defined exception |
| 105 | let res = client.calculate( |
| 106 | 77, |
| 107 | Work::new(2, 0, Operation::DIVIDE, None) |
| 108 | ); |
| 109 | match res { |
| 110 | Ok(v) => panic!("shouldn't have succeeded with result {}", v), |
| 111 | Err(e) => println!("divide by zero failed with {:?}", e), |
| 112 | } |
| 113 | |
| 114 | // one-way |
| 115 | client.zip()?; |
| 116 | |
| 117 | // done! |
| 118 | Ok(()) |
| 119 | } |
| 120 | ``` |
| 121 | |
| 122 | ## Code Generation |
| 123 | |
| 124 | ### Thrift Files and Generated Modules |
| 125 | |
| 126 | The Thrift code generator takes each Thrift file and generates a Rust module |
| 127 | with the same name snake-cased. For example, running the compiler on |
| 128 | `ThriftTest.thrift` creates `thrift_test.rs`. To use these generated files add |
| 129 | `mod ...` and `use ...` declarations to your `lib.rs` or `main.rs` - one for |
| 130 | each generated file. |
| 131 | |
| 132 | ### Results and Errors |
| 133 | |
| 134 | The Thrift runtime library defines a `thrift::Result` and a `thrift::Error` type, |
| 135 | both of which are used throught the runtime library and in all generated code. |
| 136 | Conversions are defined from `std::io::Error`, `str` and `String` into |
| 137 | `thrift::Error`. |
| 138 | |
| 139 | ### Thrift Type and their Rust Equivalents |
| 140 | |
| 141 | Thrift defines a number of types, each of which is translated into its Rust |
| 142 | equivalent by the code generator. |
| 143 | |
| 144 | * Primitives (bool, i8, i16, i32, i64, double, string, binary) |
| 145 | * Typedefs |
| 146 | * Enums |
| 147 | * Containers |
| 148 | * Structs |
| 149 | * Unions |
| 150 | * Exceptions |
| 151 | * Services |
| 152 | * Constants (primitives, containers, structs) |
| 153 | |
| 154 | In addition, unless otherwise noted, thrift includes are translated into |
| 155 | `use ...` statements in the generated code, and all declarations, parameters, |
| 156 | traits and types in the generated code are namespaced appropriately. |
| 157 | |
| 158 | The following subsections cover each type and their generated Rust equivalent. |
| 159 | |
| 160 | ### Primitives |
| 161 | |
| 162 | Thrift primitives have straightforward Rust equivalents. |
| 163 | |
| 164 | * bool: `bool` |
| 165 | * i8: `i8` |
| 166 | * i16: `i16` |
| 167 | * i32: `i32` |
| 168 | * i64: `i64` |
| 169 | * double: `OrderedFloat<f64>` |
| 170 | * string: `String` |
| 171 | * binary: `Vec<u8>` |
| 172 | |
| 173 | ### Typedefs |
| 174 | |
| 175 | A typedef is translated to a `pub type` declaration. |
| 176 | |
| 177 | ```thrift |
| 178 | typedef i64 UserId |
| 179 | |
| 180 | typedef map<string, Bonk> MapType |
| 181 | ``` |
| 182 | ```rust |
| 183 | pub type UserId = 164; |
| 184 | |
| 185 | pub type MapType = BTreeMap<String, Bonk>; |
| 186 | ``` |
| 187 | |
| 188 | ### Enums |
| 189 | |
| 190 | A Thrift enum is represented as a Rust enum, and each variant is transcribed 1:1. |
| 191 | |
| 192 | ```thrift |
| 193 | enum Numberz |
| 194 | { |
| 195 | ONE = 1, |
| 196 | TWO, |
| 197 | THREE, |
| 198 | FIVE = 5, |
| 199 | SIX, |
| 200 | EIGHT = 8 |
| 201 | } |
| 202 | ``` |
| 203 | |
| 204 | ```rust |
| 205 | #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] |
| 206 | pub enum Numberz { |
| 207 | ONE = 1, |
| 208 | TWO = 2, |
| 209 | THREE = 3, |
| 210 | FIVE = 5, |
| 211 | SIX = 6, |
| 212 | EIGHT = 8, |
| 213 | } |
| 214 | |
| 215 | impl TryFrom<i32> for Numberz { |
| 216 | // ... |
| 217 | } |
| 218 | |
| 219 | ``` |
| 220 | |
| 221 | ### Containers |
| 222 | |
| 223 | Thrift has three container types: list, set and map. They are translated into |
| 224 | Rust `Vec`, `BTreeSet` and `BTreeMap` respectively. Any Thrift type (this |
| 225 | includes structs, enums and typedefs) can be a list/set element or a map |
| 226 | key/value. |
| 227 | |
| 228 | #### List |
| 229 | |
| 230 | ```thrift |
| 231 | list <i32> numbers |
| 232 | ``` |
| 233 | |
| 234 | ```rust |
| 235 | numbers: Vec<i32> |
| 236 | ``` |
| 237 | |
| 238 | #### Set |
| 239 | |
| 240 | ```thrift |
| 241 | set <i32> numbers |
| 242 | ``` |
| 243 | |
| 244 | ```rust |
| 245 | numbers: BTreeSet<i32> |
| 246 | ``` |
| 247 | |
| 248 | #### Map |
| 249 | |
| 250 | ```thrift |
| 251 | map <string, i32> numbers |
| 252 | ``` |
| 253 | |
| 254 | ```rust |
| 255 | numbers: BTreeMap<String, i32> |
| 256 | ``` |
| 257 | |
| 258 | ### Structs |
| 259 | |
| 260 | A Thrift struct is represented as a Rust struct, and each field transcribed 1:1. |
| 261 | |
| 262 | ```thrift |
| 263 | struct CrazyNesting { |
| 264 | 1: string string_field, |
| 265 | 2: optional set<Insanity> set_field, |
| 266 | 3: required list< |
| 267 | map<set<i32>, map<i32,set<list<map<Insanity,string>>>>> |
| 268 | > |
| 269 | 4: binary binary_field |
| 270 | } |
| 271 | ``` |
| 272 | ```rust |
| 273 | #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] |
| 274 | pub struct CrazyNesting { |
| 275 | pub string_field: Option<String>, |
| 276 | pub set_field: Option<BTreeSet<Insanity>>, |
| 277 | pub list_field: Vec< |
| 278 | BTreeMap< |
| 279 | BTreeSet<i32>, |
| 280 | BTreeMap<i32, BTreeSet<Vec<BTreeMap<Insanity, String>>>> |
| 281 | > |
| 282 | >, |
| 283 | pub binary_field: Option<Vec<u8>>, |
| 284 | } |
| 285 | |
| 286 | impl CrazyNesting { |
| 287 | pub fn read_from_in_protocol(i_prot: &mut TInputProtocol) |
| 288 | -> |
| 289 | thrift::Result<CrazyNesting> { |
| 290 | // ... |
| 291 | } |
| 292 | pub fn write_to_out_protocol(&self, o_prot: &mut TOutputProtocol) |
| 293 | -> |
| 294 | thrift::Result<()> { |
| 295 | // ... |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | ``` |
| 300 | ##### Optionality |
| 301 | |
| 302 | Thrift has 3 "optionality" types: |
| 303 | |
| 304 | 1. Required |
| 305 | 2. Optional |
| 306 | 3. Default |
| 307 | |
| 308 | The Rust code generator encodes *Required* fields as the bare type itself, while |
| 309 | *Optional* and *Default* fields are encoded as `Option<TypeName>`. |
| 310 | |
| 311 | ```thrift |
| 312 | struct Foo { |
| 313 | 1: required string bar // 1. required |
| 314 | 2: optional string baz // 2. optional |
| 315 | 3: string qux // 3. default |
| 316 | } |
| 317 | ``` |
| 318 | |
| 319 | ```rust |
| 320 | pub struct Foo { |
| 321 | bar: String, // 1. required |
| 322 | baz: Option<String>, // 2. optional |
| 323 | qux: Option<String>, // 3. default |
| 324 | } |
| 325 | ``` |
| 326 | |
| 327 | ## Known Issues |
| 328 | |
| 329 | * Struct constants are not supported |
| 330 | * Map, list and set constants require a const holder struct |