remote_trait_object/
lib.rs

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