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