1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
/*! `remote-trait-object` is a general, powerful, and simple [remote method invocation](https://en.wikipedia.org/wiki/Distributed_object_communication) library based on trait objects. It is... 1. Based on _services_ that can be exported and imported **as trait objects** - You register a service object, which is a trait object, and export it. On the other side, you import it into a proxy object, which is also a trait object. 1. Based on a point-to-point connection - All operations are conducted upon a single connection, which has **two ends**. 1. Easy to export and import services - During a remote method call in some service, you **can export and import another service as an argument or a return value** of the method. 1. Independent from the transport model - The transport model is abstracted and **users must provide a concrete implementation of it**. 1. Concurrent - you can both **call and handle remote calls concurrently**. Note that it is commonly abbreviated as **RTO**. ## Introduction **_Connection_** is a concept of a solid point-to-point pair where all operations of `remote-trait-object` are conducted. **_Context_** is one end of a connection, and one connection will have two instances of this. Each side will access the _connection_ via the _context_. This corresponds to [`Context`]. **_Service_** is a set of well-defined, coherent operations that can be provided for use by other code. All communication between two parties takes place only through _services_. **_Service object_** is the subject who provides a _service_. It is a trait object that is wrapped in a _skeleton_. The wrapping _skeleton_ will invoke its method to handle remote calls from the other side. You can use any trait object as a _service object_, as long as the trait is a service trait. You can even export your proxy object as a service object. **_Skeleton_** is a wrapper of a service object, which is registered on a _context_ and will be invoked with remote calls from its _proxy object_ from the _client_ side. This corresponds to [`ServiceToExport`] or [`Skeleton`]. **_Proxy object_** is the provided _service_. It is a trait object that is a proxy to the remote _service object_ on the _server_ side. You can call its methods just like a local object. A _proxy object_ corresponds to exactly one _skeleton_, and vice versa. If a proxy object is dropped, it will request its deletion on the server side. This is called _delete request_. With this, the server side's context will remove the skeleton if the client doesn't own its proxy anymore. **_Service trait_** is a trait that represents a `service`. It is for two trait objects (_service object_ and _proxy object_). Both can share an identical _service trait_, but might have different but compatible _service traits_ as well. **_Server_** side refers to one side (or one _context_) in which the _skeleton_ resides. When talking about service exchange, it is sometimes called the _exporter_ side. **_Client_** side refers to one side (or one _context_) in which the _proxy object_ resides. When talking about service exchange, it is sometimes called the _importer_ side. Note that the concept of _server_ and _client_ are for one _skeleton_-_proxy_ pair, not the whole _context_-_context_ pair itself. No context is either _server_ nor _client_ by itself, but can be referred as one when we say a particular _skeleton_-_proxy_ pair. **_Handle_** is an index-like identifier that corresponds to a particular _skeleton_ in the _server_ side's context. Each _proxy object_ is carrying one. This corresponds to [`ServiceToImport`] or [`HandleToExchange`]. **_Exporting_** a service object means wrapping it in a _skeleton_, registering that on the _server_ side's _context_, producing a _handle_ to it, and passing the _handle_ to the client side. Note that you won't go through all these processes unless you're using [`raw_exchange`] module. **_Importing_** a handle into a proxy object means creating an object that fulfills its method calls by remotely invoking the skeleton on the server side. It carries the handle to fill it in the packet to send, which is for identifying the skeleton that this proxy corresponds to. It is sometimes called an _exchange_ when referring to both _export_ and _import_. ## How It Works ![diagram](https://github.com/CodeChain-io/remote-trait-object/raw/master/remote-trait-object/flow.png) 1. User calls a method of _proxy object_ which is a trait object wrapped in a smart pointer. 2. The call will be delivered to the _context_ from which the _proxy object_ is imported, after serialized into a byte packet. Note that the actual transportation of data happens only at the _context_, which functions as a connection end. 3. The packet will be sent to the other end, (or context) by the _transport_. 4. After the other side's _context_ receives the packet, it forwards the packet to the target _skeleton_ in its registry. 5. The skeleton will dispatch the packet into an actual method call to the _service object_, which is a trait object wrapped in a smart pointer. The result will go back to the user, again via the contexts and transport. ## Smart Pointers Both service object and proxy object are trait objects like `dyn MyService`. To own and pass a trait object, it should be holded by some smart pointer. Currently `remote-trait-object` supports three types of smart pointers for service objects and proxy objects. 1. `Box<dyn MyService>` 2. `std::sync::Arc<dyn MyService>` 3. `std::sync::Arc<parking_lot::RwLock<dyn MyService>>` When you export a service object, you can **export from whichever type** among them. On the other hand when you import a proxy object, you can **import into whichever type** among them. Choosing smart pointer types is completely independent for exporting & importing sides. Both can decide which to use, depending on their own requirements. **Exporter (server)** - Use `Box<>` when you have nothing to do with the object after you export it. It will be registered in the [`Context`], and will be alive until the corresponding proxy object is dropped. You can never access the object directly, since it will be _moved_ to the registry. - Use `Arc<>` when you have something to do with the object after you export it, by `Arc::clone()` and holding somewhere. In this case, both its proxy object and some `Arc` copy on the exporter side can access the object, though the latter can only access it immutably. With this a single _service object_ can be shared among multiple _skeletons_ while a _skeleton_ always matches to exactly one _service object_. - Use `Arc<RwLock<>>` when you have to access the object **mutably**, in the similar situation with `Arc` case. **Importer (client)** - This is not different from the plain Rust programming. Choose whichever type you want depending on your use. Note that `Arc<>` will not be supported if the trait has a method that takes `&mut self`. You must use either `Box<>` or `Arc<RwLock<>>` in such casse. ## Service Trait Service trait is the core idea of the `remote-trait-object`. Once you define a trait that you want to use it as a interface between two ends, you can put `#[remote_trait_object::service]` to make it as a service trait. It will generate all required code to construct a proxy and skeleton to it. ### Trait Requirements There are some rules to use a trait as a service trait. 1. Of course, the trait must be **object-safe** because it will be used as a trait object. 1. It can't have any type item. 1. No generic parameter (including lifetime) is allowed, in both trait definition and methods. 1. All types appeared in method parameter or return value must implement [`serde`]'s [`Serialize`] and [`Deserialize`]. This library performs de/serialization of data using [`serde`], though the data format can be chosen. Depending on your choice of macro arguments, this condition may differ slightly. See this [section](https://github.com/CodeChain-io/remote-trait-object) 1. You can't return a reference as a return type. This holds for a composite type too. For example, you can't return `&i32` nor `(i32, i32, &i32)`. 1. You can pass only first-order reference as a parameter. For example, you can pass `&T` only if the `T` doesn't a contain reference at all. Note that T must be `Sized`. There are two exceptions that accept `?Sized` `T`s: `str` and `[U]` where `U` doesn't contain reference at all. ### Example ``` use remote_trait_object as rto; #[remote_trait_object_macro::service] pub trait PizzaStore : rto::Service { fn order_pizza(&mut self, menu: &str, money: u64); fn ask_pizza_price(&self, menu: &str) -> u64; } ``` ### Service Compatibility Although it is common to use the same trait for both proxy object and service object, it is possible to import a service into another trait. TODO: We have not strictly designed the compatibility model but will be provided in the next version. Roughly, in current version, trait `P` is considered to be compatible to be proxy of trait `S`, only if 1. `P` has exactly the same methods as `S` declared in the same order, that differ only in types of parameter and return value. 2. Such different types must be compatible. 3. Types are considered to be compatible if both are serialized and deserialized with exactly the same value. `remote-trait-object` always guarantees 3. between [`ServiceToExport`], [`ServiceToImport`] and [`ServiceRef`]. ## Export & Import services One of the core features of `remote-trait-object` is its simple and straightforward but extensive export & import of services. Of course this library doesn't make you manually register a service object, passing handle and so on, but provides you a much simpler and abstracted way. There are three ways of exporting and importing a service. ### During Initialization When you create new `remote-trait-object` contexts, you can export and import one as initial services. See details [here](./struct.Context.html#method.with_initial_service) ### As a Parameter or a Return Value This is the most common way of exporting / importing services. See [`ServiceToExport`], [`ServiceToImport`] and [`ServiceRef`] for more. ### Raw Exchange You will be **rarely** needed to perform a service exchange using a raw _skeleton_ and _handle_. If you use this method, you will do basically the same thing as what the above methods would do internally, but have some extra controls over it. Raw exchange is not that frequently required. In most cases using only method 1. and 2. will be sufficient. See the [module-level documentation](./raw_exchange/index.html) for more. ## Example ```ignore use remote_trait_object::*; #[service] pub trait CreditCard: Service { fn pay(&mut self, ammount: u64) -> Result<(), ()>; } struct SomeCreditCard { money: u64 } impl CreditCard for SomeCreditCard { fn pay(&mut self, ammount: u64) -> Result<(), ()> { if ammount <= self.money { self.money -= ammount; Ok(()) } else { Err(()) } } } #[service] pub trait PizzaStore: Service { fn order_pizza(&self, credit_card: ServiceRef<dyn CreditCard>) -> Result<String, ()>; } struct SomePizzaStore; impl PizzaStore for SomePizzaStore { fn order_pizza(&self, credit_card: ServiceRef<dyn CreditCard>) -> Result<String, ()> { let mut credit_card_proxy: Box<dyn CreditCard> = credit_card.unwrap_import().into_proxy(); credit_card_proxy.pay(10)?; Ok("Tasty Pizza".to_owned()) } } // PROGRAM 1 let (send, recv) = unimplemented!("Implement your own transport medium and provide here!") let _context_pizza_town = Context::with_initial_service_export( Config::default_setup(), send, recv, ServiceToExport::new(Box::new(SomePizzaStore) as Box<dyn PizzaStore>), ); // PROGRAM 2 let (send, recv) = unimplemented!("Implement your own transport medium and provide here!") let (_context_customer, pizza_store): (_, ServiceToImport<dyn PizzaStore>) = Context::with_initial_service_import(Config::default_setup(), send, recv); let pizza_store_proxy: Box<dyn PizzaStore> = pizza_store.into_proxy(); let my_credit_card = Box::new(SomeCreditCard {money: 11}) as Box<dyn CreditCard>; assert_eq!(pizza_store_proxy.order_pizza( ServiceRef::create_export(my_credit_card)).unwrap(), "Tasty Pizza"); let my_credit_card = Box::new(SomeCreditCard {money: 9}) as Box<dyn CreditCard>; assert!(pizza_store_proxy.order_pizza( ServiceRef::create_export(my_credit_card)).is_err()); ``` You can check the working code of this example [here](https://github.com/CodeChain-io/remote-trait-object/blob/master/remote-trait-object-tests/src/simple.rs). See more examples [here](https://github.com/CodeChain-io/remote-trait-object/tree/master/remote-trait-object-tests/src). [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html [`Skeleton`]: ./raw_exchange/struct.Skeleton.html [`HandleToExchange`]: ./raw_exchange/struct.HandleToExchange.html [`Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html [`Deserialize`]: https://docs.serde.rs/serde/trait.Deserialize.html */ #[macro_use] extern crate log; mod context; mod forwarder; mod packet; mod port; mod queue; mod service; #[cfg(test)] mod tests; pub mod transport; pub use context::{Config, Context}; pub use service::id::setup_identifiers; pub use service::serde_support::{ServiceRef, ServiceToExport, ServiceToImport}; pub use service::{SerdeFormat, Service}; pub mod raw_exchange { //! This module is needed only if you want to perform some raw exchange (or export/import) of services. //! //! You may have [`Skeleton`], which is a service to be registered, but **with its trait erased**. //! You can prepare one and hold it for a while, and register it on demand. //! Creating an instance of [`Skeleton`] doesn't involve any context. //! That means you can have a service object that both its trait and its context (to be exported later) remains undecided. //! //! You may also have [`HandleToExchange`], which is a raw handle as a result of exporting a [`Skeleton`]. //! It should be imported as a proxy object on the other side, but you can manage it freely until that moment. //! It is useful when there is a third party besides two contexts of a single connection, who wants to perform service exchange by itself, not directly between contexts. //! //! Raw exchange is not that frequently required. In most cases just using ordinary methods like [`ServiceToExport`], [`ServiceToImport`] or [`ServiceRef`] would be enough. //! Please check again that you surely need this module. //! //! [`ServiceToExport`]: ../struct.ServiceToExport.html //! [`ServiceToImport`]: ../struct.ServiceToImport.html //! [`ServiceRef`]: ../enum.ServiceRef.html pub use crate::service::export_import::{ export_service_into_handle, import_null_proxy, import_service_from_handle, HandleToExchange, ImportProxy, IntoSkeleton, Skeleton, }; } #[doc(hidden)] pub mod macro_env { pub use super::raw_exchange::*; pub use super::service::export_import::{get_dispatch, FromSkeleton}; pub use super::*; pub use port::Port; pub use service::export_import::create_skeleton; pub use service::id::{IdMap, MethodIdAtomic, ID_ORDERING, MID_REG}; pub use service::{Cbor as DefaultSerdeFormat, Dispatch, Handle, MethodId}; pub use SerdeFormat; } // Re-export macro pub use remote_trait_object_macro::*;