├── .gitignore ├── LICENSE ├── README.adoc └── grpc-example ├── go-grpc ├── README.md ├── build │ └── gen │ │ └── notes.pb.go ├── client.go └── server.go ├── proto └── notes.proto └── rust-grpc ├── Cargo.toml ├── README.md ├── build.rs └── src ├── client.rs └── server.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | **/target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Comparing Distributed Programming in Rust and Go 2 | 3 | == Overview 4 | 5 | The purpose of this project is to compare the programming languages Rust and Go regarding their capabilities and characteristics for distributed programming. 6 | 7 | First, we will take a look at the general characteristic of Rust and Go. 8 | If you are interested in more technical details head over to <> where we will implement a gRPC Server and Client in Rust and Go. 9 | 10 | == General Aspects of Rust and Go 11 | 12 | Rust and Go are in many ways quite similar but they do have some striking differences. 13 | Regarding syntax, both languages are inspired by C and C++ and simple code snippets can sometimes look almost identical: 14 | 15 | [source,rust] 16 | ---- 17 | fn main(){ 18 | println!("This says Hello World in Rust!"); 19 | } 20 | ---- 21 | 22 | [source,go] 23 | ---- 24 | func main() { 25 | fmt.Println("This says Hello World in Go!") 26 | } 27 | ---- 28 | 29 | Both languages have a strong, static type system that supports type inference. 30 | When it comes to programming paradigms, both languages follow a mixed paradigm approach that is mostly imperative. 31 | Rust and Go are both compiled languages with a compiler that supports cross compilation. 32 | The most important technical difference between the two is that Go has a garbage collector whereas Rust does not. 33 | This of course means that Rust offers better memory efficiency overall but it also adds additional complexity when coding in Rust as you have deal with Rust's https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html[ownership system]. 34 | 35 | |=== 36 | |Language |Rust |Go 37 | 38 | | Garbage collector 39 | | No GC 40 | | Has GC 41 | 42 | | Type system 43 | | static 44 | | static 45 | 46 | | Cross compilation 47 | | supported 48 | | supported 49 | 50 | |=== 51 | 52 | == Distributed Programming in Rust and Go 53 | 54 | One thing that is pretty much inevitable when it comes to distributed systems programming is to have some form of messaging solution so that individual computing nodes can exchange serialized data. 55 | 56 | A widely used solution for this is Google's https://grpc.io/[gRPC] so let's take a look at gRPC implementations for Go and Rust. For demonstration purposes, i built a small noticeboard service with a server that hosts notes which the client can query by title or author. You can find the code in the `/grpc-example` directory. 57 | The `/grpc-example/proto` directory contains the protobuf definition file for the gRPC service. 58 | Since both implementations of the service use the same protobuf definition, the respective client and server should be able to communicate with each other interchangeably. 59 | That means you can run the Rust client with the Go server and vice versa. 60 | 61 | Go of course has an https://github.com/grpc/grpc-go[official implementation of gRPC] so that's what i used. 62 | For Rust i had to look around a bit to find a suitable implementation. 63 | Luckily, Rust has a very convenient package registry with https://www.crates.io[crates.io]. 64 | 65 | On crates.io you can find multiple crates (Rust's version of packages/libraries) that offer gRPC implementations. The most promising one at the time seems to be https://crates.io/crates/tonic[tonic] because it has the highest number of all-time and recent downloads. This is the one i decided to use. 66 | 67 | === Setup - Protobuf Code Generation 68 | 69 | What's nice about working with Protobuf is that the compiler `protoc` can generate code for different languages based on your service definition that helps implement the service in the respective language. 70 | This way you don't have to worry about things like serializing your data structures as Protobuf messages. 71 | 72 | ==== Code Generation in Rust 73 | 74 | Tonic offers the additional package `tonic-build` which can handle the code generation automatically. 75 | This way you don't need to invoke `protoc` seperately and can just use Rust's own build tool Cargo. 76 | To generate Rust code for implementing the service all we need to do is add `tonic-build` as a build dependency in the `Cargo.toml` configuration file: 77 | 78 | [source,rust] 79 | ---- 80 | [build-dependencies] 81 | tonic-build = "0.3" 82 | ---- 83 | 84 | and an additional file called `build.rs` in the ``grpc-example/rust-grpc`` directory with the following content: 85 | [source,rust] 86 | ---- 87 | fn main() { 88 | tonic_build::compile_protos("../proto/notes.proto") // make sure to provide the correct path 89 | .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); 90 | } 91 | ---- 92 | 93 | Now we can run `cargo build` in the `/grpc-example/rust-grpc` directory to compile our code. 94 | The generated code will appear inside the `/grpc-example/rust-grpc/target/debug/build/` directory, look for a folder called `rust-grpc-...`. 95 | 96 | ==== Code Generation in Go 97 | 98 | The process of generating the code for Go is actually a bit more tedious since you have to use `protoc` directly. 99 | That's why i included the generated code in the repository under `grpc-example/go-grpc/build/gen`. 100 | 101 | Provided you have installed `protoc` and set everything up correctly (https://developers.google.cn/protocol-buffers/docs/reference/go-generated[find out how to do that here]), you should be able to generate the code with this command: 102 | `protoc --proto_path=../proto --go_out=plugins=grpc:build/gen ../proto/notes.proto` 103 | 104 | === Service Implementation 105 | 106 | Now that everything is setup let's have look at the actual service implementations. 107 | The noticeboard service provides two RPC methods: `GetNoteByTitle` and `ListNotesByAuthor`. 108 | `GetNoteByTitle` is a simple RPC method that takes a message of type `Title` (which consists of only a single string) and returns one message of type `Note`. 109 | 110 | This is the server side implementation for `GetNoteByTitle` in Rust: 111 | 112 | [source,rust] 113 | ---- 114 | async fn get_note_by_title(&self, request: Request) -> Result<Response<Note>, Status> { 115 | for note in &self.notes[..] { 116 | if note.title == request.get_ref().title { 117 | return Ok(Response::new(note.clone())); 118 | } 119 | } 120 | Err(Status::new(Code::NotFound, "Note not found")) 121 | } 122 | ---- 123 | 124 | The implementation in Go looks like this: 125 | 126 | [source,go] 127 | ---- 128 | func (s *NoticeboardService) GetNoteByTitle(ctx context.Context, title *noticeboard.Title) (*noticeboard.Note, error) { 129 | for _, note := range s.noticeboard { 130 | if (note.Title == title.Title) { 131 | return ¬e, nil 132 | } 133 | } 134 | return nil, errors.New("note not found") 135 | } 136 | ---- 137 | 138 | Overall, these implementations are almost identical. What's worth noting though is that in the Rust implementation the notes are stored in a vector in an https://doc.rust-lang.org/std/sync/struct.Arc.html[Arc]: `notes: Arc<Vec<Note>>`. An `Arc` is a reference counting pointer providing shared ownership of a value and is thread-safe. 139 | 140 | The RPC method `ListNotesByAuthor` is a bit more complicated since it uses _server side streaming_. 141 | That means that instead of returning a single message as the response, the server to the client's request with a stream of multiple messages. 142 | 143 | This is the server side implementation of `ListNotesByAuthor` in Rust: 144 | 145 | [source,rust] 146 | ---- 147 | async fn list_notes_by_author( 148 | &self, 149 | request: Request<Author>, 150 | ) -> Result<Response<Self::ListNotesByAuthorStream>, Status> { 151 | let (mut sender, receiver) = mpsc::channel(100); 152 | let notes = self.notes.clone(); 153 | tokio::spawn(async move { 154 | for note in ¬es[..] { 155 | match ¬e.author { 156 | Some(a) => { 157 | if a.mail == request.get_ref().mail { 158 | sender.send(Ok(note.clone())).await.unwrap(); 159 | } 160 | } 161 | _ => (), 162 | } 163 | } 164 | }); 165 | Ok(Response::new(receiver)) 166 | } 167 | ---- 168 | 169 | And this is the server side implementation of `ListNotesByAuthor` in Go: 170 | [source,go] 171 | ---- 172 | func (s *NoticeboardService) ListNotesByAuthor(author *noticeboard.Author, srv noticeboard.Noticeboard_ListNotesByAuthorServer) error { 173 | for _, note := range s.noticeboard { 174 | if (note.Author.Mail == author.Mail) { 175 | srv.Send(¬e) 176 | } 177 | } 178 | return nil 179 | } 180 | ---- 181 | 182 | As you can see, the code is a bit simpler in Go. That's because the `Noticeboard_ListNotesByAuthorServer` uses a https://pkg.go.dev/google.golang.org/grpc/#ServerStream[`ServerStream`]. Using the `Send` method, it is possible to send the notes to the stream one after another. In the Rust implementation it is necessary to spawn a second thread using the `tokio` package. This thread sends the notes to the main thread using a https://docs.rs/tokio/0.1.16/tokio/sync/mpsc/index.html[`tokio::sync::mpsc`] queue. An `mpsc` queue offers similar functionality to channels in Go. 183 | 184 | This approach for exchanging data between seperate threads is comparable to using a goroutine with a channel in Go. 185 | Consider this example from the Go client implementation: 186 | 187 | [source,go] 188 | ---- 189 | ch := make(chan bool) 190 | go func() { 191 | for { 192 | resp, err := stream.Recv() 193 | if err == io.EOF { 194 | ch <- true 195 | return 196 | } 197 | ... 198 | } 199 | }() 200 | <-ch //wait until end of the stream is reached 201 | ---- 202 | 203 | As we have seen, Rust offers similar capabilities for asynchronous programming although you have to rely on a third party package. In this regard, Go offers better functionality out of the box and using channels and goroutines feels a bit more convenient because the syntax feels very natural. 204 | 205 | == Available Frameworks 206 | 207 | When it comes to building large distributed systems that are suited for production use in an enterprise environment, most of the time you probably don't want to build everything from scratch. So it makes sense to choose a framework or platform to build upon. Ideally, this should be one that is already being used by many other people and has turned out to be tried and true. 208 | 209 | Below is a small overview of available frameworks, platforms and libraries that can be used to build (large) distributed systems in Rust and Go respectively. 210 | 211 | One thing that stood out while researching available frameworks is that the overall amount of frameworks is a lot higher for Go than for Rust. Also, most of these frameworks seem to be generally more mature and better supported (as indicated by their number of contributors, releases as well as the age of the project). 212 | While i did find some very promising projects for Rust, only Actix and Tokio seem to be in a state where it would be advisable to use them in an enterprise production system. 213 | 214 | The conclusion to be drawn here is that the "community ecosystem" regarding distributed programming is a lot better with Go than with Rust. This is not very surprising considering the fact that Go was made by Google with these kinds of systems in mind from the start. Also the usage of Go in high profile projects like Kubernetes and Docker contributed to the languages popularity in the world of cloud native computing and distributed systems. 215 | 216 | === Frameworks for Rust 217 | 218 | [cols="1,9a,1"] 219 | |=== 220 | |Name |Description |Stars on GitHubfootnoteref:[1,as of 2021-01-09] 221 | 222 | |https://github.com/actix/actix[Actix] 223 | | 224 | - framework for using an https://en.wikipedia.org/wiki/Actor_model[actor model] in Rust 225 | - offer asynchronous message handling via futures 226 | |5.9k 227 | 228 | |https://github.com/tokio-rs/tokio[Tokio] 229 | | 230 | - asynchronous runtime for rust 231 | - promises very high performance and scalability 232 | |10.7k 233 | 234 | |https://github.com/ballista-compute/ballista[Ballista] 235 | | 236 | - also offers support for Python and Java as well as connectors for JDBC and Spark 237 | - promises high memory efficiency, superior to Spark's 238 | - based on the memory model of https://arrow.apache.org/[Apache Arrow] 239 | |971 240 | 241 | |https://github.com/constellation-rs/amadeus[Amadeus] 242 | | 243 | - distributed data processing using streams 244 | - supports various data formats and database systems for importing data (e.g. CSV, JSON, S3, Postgres) 245 | - supposed to offer a particularly easy approach to distributed data processing 246 | |213 247 | 248 | |https://github.com/fede1024/rust-rdkafka[rust-rdkafka] 249 | | 250 | - asynchronous client for https://kafka.apache.org/[Apache Kafka] 251 | - wrapper for https://github.com/edenhill/librdkafka[librdkafka] (Kafka client for C/C++) 252 | |595 253 | 254 | |=== 255 | 256 | === Frameworks for Go 257 | 258 | [cols="1,9a,1"] 259 | |=== 260 | |Name |Description |Stars on GitHubfootnoteref:[1,as of 2021-01-09] 261 | 262 | |https://github.com/asim/go-micro[go-micro] 263 | | 264 | - broadly diversified framework for distributed systems development offering many different features 265 | - supports RPC based communication 266 | - promises to provide sane defaults to enable quick productivity 267 | |15.2k 268 | 269 | |https://github.com/emitter-io/emitter[Emitter] 270 | | 271 | - distributed publish/subscribe platform using MQTT 272 | - Fulfils the high-availability and partition tolerance criteria of the CAP theorem 273 | |2.7k 274 | 275 | |https://github.com/lni/dragonboat[Dragonboat] 276 | | 277 | - high performance multi-group Raft consensus library 278 | - claims to be easy to use and handle all technical difficulties of the Raft protocol 279 | |3.4k 280 | 281 | |https://github.com/chrislusf/glow[glow] 282 | | 283 | - library for scalable parallel and distributed data processing 284 | - functional aproach using map reduce 285 | |2.9k 286 | 287 | |https://github.com/chrislusf/gleam[gleam] 288 | | 289 | - high performance and efficient distributed execution system 290 | - also using map reduce funcionality 291 | |2.7k 292 | 293 | |=== 294 | 295 | == Additional Thoughts 296 | 297 | One central aspect of Rust's philosophy is its focus on performance. 298 | Although better low-level performance is in general a good thing, this particular advantage of Rust might not be as useful when it comes to building distributed systems. Since the performance of a distributed system as a whole tends to be constrained more by network latency than by the execution time of individual tasks. 299 | 300 | On the other hand, the fact that Rust offers very good memory efficiency means that it could be suited very well for distributed systems that keep a lot of data in memory at a time. For example, the authors of the Ballista framework claim that: 301 | 302 | __"The combination of Rust and Arrow provides excellent memory efficiency and memory usage can be 5x - 10x lower than Apache Spark in some cases"__footnote:[https://github.com/ballista-compute/ballista#how-does-this-compare-to-apache-spark] 303 | 304 | == Summary 305 | 306 | As we have seen, Rust and Go offer very similar functionality when it comes to building distributed systems using gRPC. What makes Go stand out is its excellent support for asynchronous programming out of the box. Using goroutines and channels is very convenient. But you can achieve similar results in Rust if you use the third party package tokio. 307 | 308 | Regarding gRPC support, tonic is an excellent implementation that is easy to use and can stand up to Google's official gRPC implementation for go. What's especially good about using Rust and tonic is that you can handle protobuf code generation directly with Rust's own build tool Cargo with minimal configuration effort. 309 | 310 | The one aspect that seperates Go from Rust for distributed programming is what i call its "community ecosystem". 311 | With a plethora of libraries, frameworks and platforms available, Go can hardly be beaten as the go to language for building distributed systems on a solid foundation. From this point of view, Rust is more suited for building low level systems from the ground up because there are just not as many projects to build upon. 312 | 313 | In my personal opinion, the bottom line here is that Go is generally a better choice for distributed programming because it offers everything you need and there are a lot of resources out there to make your life easier. 314 | But Rust offers exceptional performance and memory efficiency so if you care more about high performance, choose Rust. 315 | In the end you can't really go wrong with either language and you should choose the one that suits your personal preferences. -------------------------------------------------------------------------------- /grpc-example/go-grpc/README.md: -------------------------------------------------------------------------------- 1 | # Go gRPC Server Client Example 2 | ## Requirements 3 | - [Go](https://golang.org/doc/install) 4 | - [protoc](https://developers.google.com/protocol-buffers/docs/gotutorial#compiling-your-protocol-buffers) (only necessary if you want to generate the source code already included in /build/gen) 5 | 6 | ## Run Commands 7 | - Server: `go run server.go` 8 | - Client: `go run client.go` 9 | - To generate the source code in /build/gen: `protoc --proto_path=../proto --go_out=plugins=grpc:build/gen ../proto/notes.proto` 10 | -------------------------------------------------------------------------------- /grpc-example/go-grpc/build/gen/notes.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.25.0-devel 4 | // protoc v3.14.0 5 | // source: notes.proto 6 | 7 | package notes 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 15 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 16 | reflect "reflect" 17 | sync "sync" 18 | ) 19 | 20 | const ( 21 | // Verify that this generated code is sufficiently up-to-date. 22 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 23 | // Verify that runtime/protoimpl is sufficiently up-to-date. 24 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 25 | ) 26 | 27 | type Note struct { 28 | state protoimpl.MessageState 29 | sizeCache protoimpl.SizeCache 30 | unknownFields protoimpl.UnknownFields 31 | 32 | Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` 33 | Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` 34 | Author *Author `protobuf:"bytes,3,opt,name=author,proto3" json:"author,omitempty"` 35 | } 36 | 37 | func (x *Note) Reset() { 38 | *x = Note{} 39 | if protoimpl.UnsafeEnabled { 40 | mi := &file_notes_proto_msgTypes[0] 41 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 42 | ms.StoreMessageInfo(mi) 43 | } 44 | } 45 | 46 | func (x *Note) String() string { 47 | return protoimpl.X.MessageStringOf(x) 48 | } 49 | 50 | func (*Note) ProtoMessage() {} 51 | 52 | func (x *Note) ProtoReflect() protoreflect.Message { 53 | mi := &file_notes_proto_msgTypes[0] 54 | if protoimpl.UnsafeEnabled && x != nil { 55 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 56 | if ms.LoadMessageInfo() == nil { 57 | ms.StoreMessageInfo(mi) 58 | } 59 | return ms 60 | } 61 | return mi.MessageOf(x) 62 | } 63 | 64 | // Deprecated: Use Note.ProtoReflect.Descriptor instead. 65 | func (*Note) Descriptor() ([]byte, []int) { 66 | return file_notes_proto_rawDescGZIP(), []int{0} 67 | } 68 | 69 | func (x *Note) GetTitle() string { 70 | if x != nil { 71 | return x.Title 72 | } 73 | return "" 74 | } 75 | 76 | func (x *Note) GetContent() string { 77 | if x != nil { 78 | return x.Content 79 | } 80 | return "" 81 | } 82 | 83 | func (x *Note) GetAuthor() *Author { 84 | if x != nil { 85 | return x.Author 86 | } 87 | return nil 88 | } 89 | 90 | type Title struct { 91 | state protoimpl.MessageState 92 | sizeCache protoimpl.SizeCache 93 | unknownFields protoimpl.UnknownFields 94 | 95 | Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` 96 | } 97 | 98 | func (x *Title) Reset() { 99 | *x = Title{} 100 | if protoimpl.UnsafeEnabled { 101 | mi := &file_notes_proto_msgTypes[1] 102 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 103 | ms.StoreMessageInfo(mi) 104 | } 105 | } 106 | 107 | func (x *Title) String() string { 108 | return protoimpl.X.MessageStringOf(x) 109 | } 110 | 111 | func (*Title) ProtoMessage() {} 112 | 113 | func (x *Title) ProtoReflect() protoreflect.Message { 114 | mi := &file_notes_proto_msgTypes[1] 115 | if protoimpl.UnsafeEnabled && x != nil { 116 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 117 | if ms.LoadMessageInfo() == nil { 118 | ms.StoreMessageInfo(mi) 119 | } 120 | return ms 121 | } 122 | return mi.MessageOf(x) 123 | } 124 | 125 | // Deprecated: Use Title.ProtoReflect.Descriptor instead. 126 | func (*Title) Descriptor() ([]byte, []int) { 127 | return file_notes_proto_rawDescGZIP(), []int{1} 128 | } 129 | 130 | func (x *Title) GetTitle() string { 131 | if x != nil { 132 | return x.Title 133 | } 134 | return "" 135 | } 136 | 137 | type Author struct { 138 | state protoimpl.MessageState 139 | sizeCache protoimpl.SizeCache 140 | unknownFields protoimpl.UnknownFields 141 | 142 | Nickname string `protobuf:"bytes,1,opt,name=nickname,proto3" json:"nickname,omitempty"` 143 | Mail string `protobuf:"bytes,2,opt,name=mail,proto3" json:"mail,omitempty"` 144 | } 145 | 146 | func (x *Author) Reset() { 147 | *x = Author{} 148 | if protoimpl.UnsafeEnabled { 149 | mi := &file_notes_proto_msgTypes[2] 150 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 151 | ms.StoreMessageInfo(mi) 152 | } 153 | } 154 | 155 | func (x *Author) String() string { 156 | return protoimpl.X.MessageStringOf(x) 157 | } 158 | 159 | func (*Author) ProtoMessage() {} 160 | 161 | func (x *Author) ProtoReflect() protoreflect.Message { 162 | mi := &file_notes_proto_msgTypes[2] 163 | if protoimpl.UnsafeEnabled && x != nil { 164 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 165 | if ms.LoadMessageInfo() == nil { 166 | ms.StoreMessageInfo(mi) 167 | } 168 | return ms 169 | } 170 | return mi.MessageOf(x) 171 | } 172 | 173 | // Deprecated: Use Author.ProtoReflect.Descriptor instead. 174 | func (*Author) Descriptor() ([]byte, []int) { 175 | return file_notes_proto_rawDescGZIP(), []int{2} 176 | } 177 | 178 | func (x *Author) GetNickname() string { 179 | if x != nil { 180 | return x.Nickname 181 | } 182 | return "" 183 | } 184 | 185 | func (x *Author) GetMail() string { 186 | if x != nil { 187 | return x.Mail 188 | } 189 | return "" 190 | } 191 | 192 | var File_notes_proto protoreflect.FileDescriptor 193 | 194 | var file_notes_proto_rawDesc = []byte{ 195 | 0x0a, 0x0b, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x6e, 196 | 0x6f, 0x74, 0x65, 0x73, 0x22, 0x5d, 0x0a, 0x04, 0x4e, 0x6f, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 197 | 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 198 | 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 199 | 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x06, 200 | 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6e, 201 | 0x6f, 0x74, 0x65, 0x73, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x52, 0x06, 0x61, 0x75, 0x74, 202 | 0x68, 0x6f, 0x72, 0x22, 0x1d, 0x0a, 0x05, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 203 | 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 204 | 0x6c, 0x65, 0x22, 0x38, 0x0a, 0x06, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 205 | 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 206 | 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x69, 0x6c, 207 | 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x61, 0x69, 0x6c, 0x32, 0x71, 0x0a, 0x0b, 208 | 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x2d, 0x0a, 0x0e, 0x47, 209 | 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x42, 0x79, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x0c, 0x2e, 210 | 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x2e, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x1a, 0x0b, 0x2e, 0x6e, 0x6f, 211 | 0x74, 0x65, 0x73, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x11, 0x4c, 0x69, 212 | 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x42, 0x79, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 213 | 0x0d, 0x2e, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x1a, 0x0b, 214 | 0x2e, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x22, 0x00, 0x30, 0x01, 0x62, 215 | 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 216 | } 217 | 218 | var ( 219 | file_notes_proto_rawDescOnce sync.Once 220 | file_notes_proto_rawDescData = file_notes_proto_rawDesc 221 | ) 222 | 223 | func file_notes_proto_rawDescGZIP() []byte { 224 | file_notes_proto_rawDescOnce.Do(func() { 225 | file_notes_proto_rawDescData = protoimpl.X.CompressGZIP(file_notes_proto_rawDescData) 226 | }) 227 | return file_notes_proto_rawDescData 228 | } 229 | 230 | var file_notes_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 231 | var file_notes_proto_goTypes = []interface{}{ 232 | (*Note)(nil), // 0: notes.Note 233 | (*Title)(nil), // 1: notes.Title 234 | (*Author)(nil), // 2: notes.Author 235 | } 236 | var file_notes_proto_depIdxs = []int32{ 237 | 2, // 0: notes.Note.author:type_name -> notes.Author 238 | 1, // 1: notes.Noticeboard.GetNoteByTitle:input_type -> notes.Title 239 | 2, // 2: notes.Noticeboard.ListNotesByAuthor:input_type -> notes.Author 240 | 0, // 3: notes.Noticeboard.GetNoteByTitle:output_type -> notes.Note 241 | 0, // 4: notes.Noticeboard.ListNotesByAuthor:output_type -> notes.Note 242 | 3, // [3:5] is the sub-list for method output_type 243 | 1, // [1:3] is the sub-list for method input_type 244 | 1, // [1:1] is the sub-list for extension type_name 245 | 1, // [1:1] is the sub-list for extension extendee 246 | 0, // [0:1] is the sub-list for field type_name 247 | } 248 | 249 | func init() { file_notes_proto_init() } 250 | func file_notes_proto_init() { 251 | if File_notes_proto != nil { 252 | return 253 | } 254 | if !protoimpl.UnsafeEnabled { 255 | file_notes_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 256 | switch v := v.(*Note); i { 257 | case 0: 258 | return &v.state 259 | case 1: 260 | return &v.sizeCache 261 | case 2: 262 | return &v.unknownFields 263 | default: 264 | return nil 265 | } 266 | } 267 | file_notes_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 268 | switch v := v.(*Title); i { 269 | case 0: 270 | return &v.state 271 | case 1: 272 | return &v.sizeCache 273 | case 2: 274 | return &v.unknownFields 275 | default: 276 | return nil 277 | } 278 | } 279 | file_notes_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 280 | switch v := v.(*Author); i { 281 | case 0: 282 | return &v.state 283 | case 1: 284 | return &v.sizeCache 285 | case 2: 286 | return &v.unknownFields 287 | default: 288 | return nil 289 | } 290 | } 291 | } 292 | type x struct{} 293 | out := protoimpl.TypeBuilder{ 294 | File: protoimpl.DescBuilder{ 295 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 296 | RawDescriptor: file_notes_proto_rawDesc, 297 | NumEnums: 0, 298 | NumMessages: 3, 299 | NumExtensions: 0, 300 | NumServices: 1, 301 | }, 302 | GoTypes: file_notes_proto_goTypes, 303 | DependencyIndexes: file_notes_proto_depIdxs, 304 | MessageInfos: file_notes_proto_msgTypes, 305 | }.Build() 306 | File_notes_proto = out.File 307 | file_notes_proto_rawDesc = nil 308 | file_notes_proto_goTypes = nil 309 | file_notes_proto_depIdxs = nil 310 | } 311 | 312 | // Reference imports to suppress errors if they are not otherwise used. 313 | var _ context.Context 314 | var _ grpc.ClientConnInterface 315 | 316 | // This is a compile-time assertion to ensure that this generated file 317 | // is compatible with the grpc package it is being compiled against. 318 | const _ = grpc.SupportPackageIsVersion6 319 | 320 | // NoticeboardClient is the client API for Noticeboard service. 321 | // 322 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 323 | type NoticeboardClient interface { 324 | GetNoteByTitle(ctx context.Context, in *Title, opts ...grpc.CallOption) (*Note, error) 325 | ListNotesByAuthor(ctx context.Context, in *Author, opts ...grpc.CallOption) (Noticeboard_ListNotesByAuthorClient, error) 326 | } 327 | 328 | type noticeboardClient struct { 329 | cc grpc.ClientConnInterface 330 | } 331 | 332 | func NewNoticeboardClient(cc grpc.ClientConnInterface) NoticeboardClient { 333 | return ¬iceboardClient{cc} 334 | } 335 | 336 | func (c *noticeboardClient) GetNoteByTitle(ctx context.Context, in *Title, opts ...grpc.CallOption) (*Note, error) { 337 | out := new(Note) 338 | err := c.cc.Invoke(ctx, "/notes.Noticeboard/GetNoteByTitle", in, out, opts...) 339 | if err != nil { 340 | return nil, err 341 | } 342 | return out, nil 343 | } 344 | 345 | func (c *noticeboardClient) ListNotesByAuthor(ctx context.Context, in *Author, opts ...grpc.CallOption) (Noticeboard_ListNotesByAuthorClient, error) { 346 | stream, err := c.cc.NewStream(ctx, &_Noticeboard_serviceDesc.Streams[0], "/notes.Noticeboard/ListNotesByAuthor", opts...) 347 | if err != nil { 348 | return nil, err 349 | } 350 | x := ¬iceboardListNotesByAuthorClient{stream} 351 | if err := x.ClientStream.SendMsg(in); err != nil { 352 | return nil, err 353 | } 354 | if err := x.ClientStream.CloseSend(); err != nil { 355 | return nil, err 356 | } 357 | return x, nil 358 | } 359 | 360 | type Noticeboard_ListNotesByAuthorClient interface { 361 | Recv() (*Note, error) 362 | grpc.ClientStream 363 | } 364 | 365 | type noticeboardListNotesByAuthorClient struct { 366 | grpc.ClientStream 367 | } 368 | 369 | func (x *noticeboardListNotesByAuthorClient) Recv() (*Note, error) { 370 | m := new(Note) 371 | if err := x.ClientStream.RecvMsg(m); err != nil { 372 | return nil, err 373 | } 374 | return m, nil 375 | } 376 | 377 | // NoticeboardServer is the server API for Noticeboard service. 378 | type NoticeboardServer interface { 379 | GetNoteByTitle(context.Context, *Title) (*Note, error) 380 | ListNotesByAuthor(*Author, Noticeboard_ListNotesByAuthorServer) error 381 | } 382 | 383 | // UnimplementedNoticeboardServer can be embedded to have forward compatible implementations. 384 | type UnimplementedNoticeboardServer struct { 385 | } 386 | 387 | func (*UnimplementedNoticeboardServer) GetNoteByTitle(context.Context, *Title) (*Note, error) { 388 | return nil, status.Errorf(codes.Unimplemented, "method GetNoteByTitle not implemented") 389 | } 390 | func (*UnimplementedNoticeboardServer) ListNotesByAuthor(*Author, Noticeboard_ListNotesByAuthorServer) error { 391 | return status.Errorf(codes.Unimplemented, "method ListNotesByAuthor not implemented") 392 | } 393 | 394 | func RegisterNoticeboardServer(s *grpc.Server, srv NoticeboardServer) { 395 | s.RegisterService(&_Noticeboard_serviceDesc, srv) 396 | } 397 | 398 | func _Noticeboard_GetNoteByTitle_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 399 | in := new(Title) 400 | if err := dec(in); err != nil { 401 | return nil, err 402 | } 403 | if interceptor == nil { 404 | return srv.(NoticeboardServer).GetNoteByTitle(ctx, in) 405 | } 406 | info := &grpc.UnaryServerInfo{ 407 | Server: srv, 408 | FullMethod: "/notes.Noticeboard/GetNoteByTitle", 409 | } 410 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 411 | return srv.(NoticeboardServer).GetNoteByTitle(ctx, req.(*Title)) 412 | } 413 | return interceptor(ctx, in, info, handler) 414 | } 415 | 416 | func _Noticeboard_ListNotesByAuthor_Handler(srv interface{}, stream grpc.ServerStream) error { 417 | m := new(Author) 418 | if err := stream.RecvMsg(m); err != nil { 419 | return err 420 | } 421 | return srv.(NoticeboardServer).ListNotesByAuthor(m, ¬iceboardListNotesByAuthorServer{stream}) 422 | } 423 | 424 | type Noticeboard_ListNotesByAuthorServer interface { 425 | Send(*Note) error 426 | grpc.ServerStream 427 | } 428 | 429 | type noticeboardListNotesByAuthorServer struct { 430 | grpc.ServerStream 431 | } 432 | 433 | func (x *noticeboardListNotesByAuthorServer) Send(m *Note) error { 434 | return x.ServerStream.SendMsg(m) 435 | } 436 | 437 | var _Noticeboard_serviceDesc = grpc.ServiceDesc{ 438 | ServiceName: "notes.Noticeboard", 439 | HandlerType: (*NoticeboardServer)(nil), 440 | Methods: []grpc.MethodDesc{ 441 | { 442 | MethodName: "GetNoteByTitle", 443 | Handler: _Noticeboard_GetNoteByTitle_Handler, 444 | }, 445 | }, 446 | Streams: []grpc.StreamDesc{ 447 | { 448 | StreamName: "ListNotesByAuthor", 449 | Handler: _Noticeboard_ListNotesByAuthor_Handler, 450 | ServerStreams: true, 451 | }, 452 | }, 453 | Metadata: "notes.proto", 454 | } 455 | -------------------------------------------------------------------------------- /grpc-example/go-grpc/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log" 7 | "fmt" 8 | 9 | noticeboard "./build/gen" 10 | 11 | "google.golang.org/grpc" 12 | ) 13 | 14 | const port = 9000 15 | 16 | func main() { 17 | // connect to the server 18 | ctx := context.Background() 19 | srv, err := grpc.DialContext(ctx, fmt.Sprintf("localhost:%d", port), grpc.WithInsecure()) 20 | if err != nil { 21 | log.Fatalf("can not connect with server %v", err) 22 | } 23 | 24 | client := noticeboard.NewNoticeboardClient(srv) 25 | 26 | var title = noticeboard.Title{Title: "What up"} 27 | note, err := client.GetNoteByTitle(ctx, &title) 28 | if err != nil { 29 | log.Fatalf("Error receiving note: %v", err) 30 | } 31 | log.Printf("Note received: %s", note.Content) 32 | 33 | log.Printf("Streaming notes from the server") 34 | author := ¬iceboard.Author{Nickname: "", Mail: "hans@gmail.com"} 35 | // open the stream 36 | stream, err := client.ListNotesByAuthor(context.Background(), author) 37 | if err != nil { 38 | log.Fatalf("open stream error %v", err) 39 | } 40 | 41 | // this channel is used to tell the main thread when we are done 42 | ch := make(chan bool) 43 | 44 | go func() { 45 | for { 46 | resp, err := stream.Recv() 47 | if err == io.EOF { 48 | ch <- true 49 | return 50 | } 51 | if err != nil { 52 | log.Fatalf("cannot receive %v", err) 53 | } 54 | log.Printf("Note received: %s", resp.Content) 55 | } 56 | }() 57 | 58 | <-ch //wait until 59 | log.Printf("finished") 60 | } -------------------------------------------------------------------------------- /grpc-example/go-grpc/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "errors" 8 | "context" 9 | //"time" 10 | 11 | noticeboard "./build/gen" 12 | 13 | "google.golang.org/grpc" 14 | ) 15 | 16 | type NoticeboardService struct { 17 | noticeboard []noticeboard.Note 18 | } 19 | 20 | const port = 9000 21 | 22 | func (s *NoticeboardService) GetNoteByTitle(ctx context.Context, title *noticeboard.Title) (*noticeboard.Note, error) { 23 | 24 | for _, note := range s.noticeboard { 25 | if (note.Title == title.Title) { 26 | return ¬e, nil 27 | } 28 | } 29 | 30 | return nil, errors.New("note not found") 31 | } 32 | 33 | func (s *NoticeboardService) ListNotesByAuthor(author *noticeboard.Author, srv noticeboard.Noticeboard_ListNotesByAuthorServer) error { 34 | 35 | for _, note := range s.noticeboard { 36 | if (note.Author.Mail == author.Mail) { 37 | srv.Send(¬e) 38 | } 39 | // uncomment this to see that streaming the noticeboard actually works 40 | // time.Sleep(2 * time.Second) 41 | } 42 | 43 | return nil 44 | 45 | } 46 | 47 | func main() { 48 | 49 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) 50 | if err != nil { 51 | log.Fatalf("failed to listen: %v", err) 52 | } 53 | 54 | s := NoticeboardService{ 55 | noticeboard: []noticeboard.Note{ 56 | noticeboard.Note { 57 | Title: "Hello", 58 | Content: "This note says hello.", 59 | Author: ¬iceboard.Author { 60 | Nickname: "Hans", 61 | Mail: "hans@gmail.com", 62 | }, 63 | }, 64 | noticeboard.Note { 65 | Title: "Goodbye", 66 | Content: "This note says goodbye.", 67 | Author: ¬iceboard.Author { 68 | Nickname: "Hans", 69 | Mail: "hans@gmail.com", 70 | }, 71 | }, 72 | noticeboard.Note { 73 | Title: "What up", 74 | Content: "This note says what up.", 75 | Author: ¬iceboard.Author { 76 | Nickname: "Lisa", 77 | Mail: "lisa@gmail.com", 78 | }, 79 | }, 80 | }, 81 | } 82 | 83 | grpcServer := grpc.NewServer() 84 | 85 | noticeboard.RegisterNoticeboardServer(grpcServer, &s) 86 | 87 | if err := grpcServer.Serve(lis); err != nil { 88 | log.Fatalf("failed to serve: %s", err) 89 | } 90 | } -------------------------------------------------------------------------------- /grpc-example/proto/notes.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package notes; 3 | 4 | service Noticeboard { 5 | rpc GetNoteByTitle(Title) returns (Note) {} 6 | 7 | rpc ListNotesByAuthor(Author) returns (stream Note) {} 8 | } 9 | 10 | message Note { 11 | string title = 1; 12 | string content = 2; 13 | Author author = 3; 14 | } 15 | 16 | message Title { 17 | string title = 1; 18 | } 19 | 20 | message Author { 21 | string nickname = 1; 22 | string mail = 2; 23 | } 24 | -------------------------------------------------------------------------------- /grpc-example/rust-grpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-grpc" 3 | version = "0.1.0" 4 | authors = ["joham <mail@joham.dev>"] 5 | edition = "2018" 6 | build = "build.rs" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | tonic = "0.3" 12 | prost = "0.6" 13 | futures-core = "0.3" 14 | futures-util = "0.3" 15 | tokio = { version = "0.2", features = ["macros", "sync", "stream", "time"] } 16 | 17 | async-stream = "0.2" 18 | serde = { version = "1.0", features = ["derive"] } 19 | serde_json = "1.0" 20 | rand = "0.7" 21 | 22 | [build-dependencies] 23 | tonic-build = "0.3" 24 | 25 | [[bin]] 26 | name = "noticeboard-server" 27 | path = "src/server.rs" 28 | 29 | [[bin]] 30 | name = "noticeboard-client" 31 | path = "src/client.rs" -------------------------------------------------------------------------------- /grpc-example/rust-grpc/README.md: -------------------------------------------------------------------------------- 1 | # Rust gRPC Server Client Example 2 | ## Requirements 3 | - [Rust](https://www.rust-lang.org/tools/install) 4 | 5 | ## Run Commands 6 | - Server: `cargo run --bin noticeboard-server` 7 | - Client: `cargo run --bin noticeboard-client` 8 | -------------------------------------------------------------------------------- /grpc-example/rust-grpc/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tonic_build::compile_protos("../proto/notes.proto") 3 | .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); 4 | } -------------------------------------------------------------------------------- /grpc-example/rust-grpc/src/client.rs: -------------------------------------------------------------------------------- 1 | pub mod notes { 2 | tonic::include_proto!("notes"); 3 | } 4 | 5 | use std::error::Error; 6 | 7 | use tonic::transport::{Endpoint, Channel}; 8 | use tonic::Request; 9 | 10 | use notes::noticeboard_client::NoticeboardClient; 11 | use notes::{Author, Title}; 12 | 13 | async fn get_notes_by_author( 14 | client: &mut NoticeboardClient<Channel>, 15 | author_mail: &str 16 | ) -> Result<(), Box<dyn Error>> { 17 | let author = Author { 18 | nickname: "".to_string(), 19 | mail: author_mail.to_string(), 20 | }; 21 | 22 | let mut stream = client 23 | .list_notes_by_author(Request::new(author)) 24 | .await? 25 | .into_inner(); 26 | 27 | while let Some(note) = stream.message().await? { 28 | println!("Note received: {:?}", note); 29 | } 30 | println!("finished"); 31 | 32 | Ok(()) 33 | } 34 | 35 | #[tokio::main] 36 | async fn main() -> Result<(), Box<dyn std::error::Error>> { 37 | let mut client = NoticeboardClient::connect(Endpoint::from_static("tcp://127.0.0.1:9000")).await?; 38 | 39 | let response = client 40 | .get_note_by_title(Request::new(Title { 41 | title: "Hello".to_string(), 42 | })) 43 | .await?; 44 | 45 | println!("Note received: {:?}", response.get_ref()); 46 | 47 | println!("\n Streaming notes from the server:"); 48 | get_notes_by_author(&mut client, "hans@gmail.com").await?; 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /grpc-example/rust-grpc/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 3 | 4 | use tokio::sync::mpsc; 5 | use tonic::transport::Server; 6 | use tonic::{Request, Response, Status, Code}; 7 | 8 | use notes::noticeboard_server::{Noticeboard, NoticeboardServer}; 9 | use notes::{Author, Note, Title}; 10 | 11 | pub mod notes { 12 | // You need to pass the name of the package declared in the .proto file here. In this case: notes. 13 | tonic::include_proto!("notes"); 14 | } 15 | 16 | // #[...] is an attribute. derive(Debug) causes the compiler to auto-generate an implementation for this trait 17 | #[derive(Debug)] 18 | pub struct NoticeboardService { 19 | notes: Arc<Vec<Note>>, 20 | } 21 | 22 | #[tonic::async_trait] 23 | impl Noticeboard for NoticeboardService { 24 | 25 | async fn get_note_by_title(&self, request: Request<Title>) -> Result<Response<Note>, Status> { 26 | for note in &self.notes[..] { 27 | if note.title == request.get_ref().title { 28 | return Ok(Response::new(note.clone())); 29 | } 30 | } 31 | Err(Status::new(Code::NotFound, "Note not found")) 32 | } 33 | 34 | type ListNotesByAuthorStream = mpsc::Receiver<Result<Note, Status>>; 35 | 36 | async fn list_notes_by_author( 37 | &self, 38 | request: Request<Author>, 39 | ) -> Result<Response<Self::ListNotesByAuthorStream>, Status> { 40 | let (mut sender, receiver) = mpsc::channel(100); 41 | let notes = self.notes.clone(); 42 | 43 | tokio::spawn(async move { 44 | for note in ¬es[..] { 45 | match ¬e.author { 46 | Some(a) => { 47 | if a.mail == request.get_ref().mail { 48 | sender.send(Ok(note.clone())).await.unwrap(); 49 | } 50 | } 51 | _ => (), 52 | } 53 | } 54 | }); 55 | 56 | Ok(Response::new(receiver)) 57 | } 58 | } 59 | 60 | #[tokio::main] 61 | async fn main() -> Result<(), Box<dyn std::error::Error>> { 62 | let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 9000); 63 | 64 | let noticeboard = NoticeboardService { 65 | notes: Arc::new(vec![ 66 | Note { 67 | title: "Hello".to_string(), 68 | content: "This note says hello.".to_string(), 69 | author: Some(Author { 70 | nickname: "Hans".to_string(), 71 | mail: "hans@gmail.com".to_string(), 72 | }), 73 | }, 74 | Note { 75 | title: "Goodbye".to_string(), 76 | content: "This note says goodbye.".to_string(), 77 | author: Some(Author { 78 | nickname: "Hans".to_string(), 79 | mail: "hans@gmail.com".to_string(), 80 | }), 81 | }, 82 | Note { 83 | title: "What up".to_string(), 84 | content: "This note says what up.".to_string(), 85 | author: Some(Author { 86 | nickname: "Lisa".to_string(), 87 | mail: "lisa@gmail.com".to_string(), 88 | }), 89 | }, 90 | ]), 91 | }; 92 | 93 | let svc = NoticeboardServer::new(noticeboard); 94 | 95 | Server::builder().tcp_nodelay(false).add_service(svc).serve(addr).await?; 96 | 97 | Ok(()) 98 | } 99 | --------------------------------------------------------------------------------