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