0.9.0-alpha.1
Relaxed method return type requirements.
Prior to this release, only Result
type is accepted as the return type for
exported methods. Now, for methods that do not really need to be a Result
(eg. a simple addition), the return type
no long needs to be wrapped inside a Result
. A more detailed example is provided below and can be found in
this example.
Service definition
Please note that the codes that import the crates are intentionally omitted in the example below. Please refer to the example on github for more information.
Service definition with service struct impl
#![allow(unused)] fn main() { pub struct Echo { } #[export_impl] impl Echo { /// This shows an exported method that returns a non-result type #[export_method] pub async fn echo_i32(&self, req: i32) -> i32 { req } /// This shows an exported method that returns a Result type #[export_method] pub async fn echo_if_equal_to_one(&self, req: i32) -> Result<i32, i32> { match req { 1 => Ok(req), _ => Err(req), // This will present on the client call as `Error::ExecutionError` } } } }
Service definition with a trait
Please note that if impl_for_client
is enabled, all the methods in the service trait must return
a Result
type.
#![allow(unused)] fn main() { /// Here we define a trait `Arith` that comes with default implementations /// for all of its methods #[async_trait] #[export_trait] pub trait Arith { /// Addition #[export_method] async fn add(&self, args: (i32, i32)) -> i32 { args.0 + args.1 } /// Subtraction #[export_method] async fn subtract(&self, args: (i32, i32)) -> i32 { args.0 - args.1 } /// Multiplication #[export_method] async fn multiply(&self, args: (i32, i32)) -> i32 { args.0 * args.1 } /// Division. We cannot divide by zero #[export_method] async fn divide(&self, args: (i32, i32)) -> Result<i32, String> { let (numerator, denominator) = args; match denominator { 0 => return Err("Divide by zero!".to_string()), _ => Ok( numerator / denominator ) } } } /// Here we are going to define another trait (service) tha is almost /// identical to `Arith` shown above, but we are going to generate the trait /// impl for the client using `impl_for_client` argument in our `#[export_trait]` /// attribute. Plus, we will not going to supply a default implementation either. #[async_trait] /// All methods must be exported if client trait impl generation is enabled. /// If `impl_for_client` is enabled, all methods in the trait must return /// a Result #[export_trait(impl_for_client)] pub trait Arith2 { #[export_method] async fn add(&self, args: (i32, i32)) -> anyhow::Result<i32>; #[export_method] async fn subtract(&self, args: (i32, i32)) -> anyhow::Result<i32>; #[export_method] async fn multiply(&self, args: (i32, i32)) -> anyhow::Result<i32>; #[export_method] async fn divide(&self, args: (i32, i32)) -> anyhow::Result<i32>; } }
Server implementation
Nothing really changed in terms of usage for the server side. The server side example code is attached below for completeness.
struct Abacus { } /// We will simply use the default implementation provided in the trait /// definition for all except for add #[async_trait] #[export_trait_impl] impl Arith for Abacus { /// We are overriding the default implementation just for /// the sake of demo async fn add(&self, args: (i32, i32)) -> i32 { args.0 + args.1 } } /// For now, you need a separate type for a new service struct Abacus2 { } #[async_trait] #[export_trait_impl] impl Arith2 for Abacus2 { async fn add(&self, args: (i32, i32)) -> anyhow::Result<i32> { Ok(args.0 + args.1) } async fn subtract(&self, args: (i32, i32)) -> anyhow::Result<i32> { Ok(args.0 - args.1) } async fn multiply(&self, args: (i32, i32)) -> anyhow::Result<i32> { Ok(args.0 * args.1) } async fn divide(&self, args: (i32, i32)) -> anyhow::Result<i32> { let (numerator, denominator) = args; match denominator { 0 => return Err(anyhow::anyhow!("Divide by zero!")), _ => Ok( numerator / denominator ) } } } #[tokio::main] async fn main() { env_logger::init(); let addr = "127.0.0.1:23333"; let echo_service = Arc::new( Echo { } ); let arith = Arc::new(Abacus { }); let arith2 = Arc::new(Abacus2 { }); let server = Server::builder() .register(echo_service) .register(arith) .register(arith2) .build(); let listener = TcpListener::bind(addr).await.unwrap(); log::info!("Starting server at {}", &addr); let handle = task::spawn(async move { server.accept(listener).await.unwrap(); }); handle.await.expect("Error"); }
Client side implementation
Like the server side implementation, there isn't much change to the client side usage. The only thing
worth mentioning is that even if the method doesn't return a Result
in the service definition, the client
will still get a Result
because there could be errors with connection or serialization/deserialization.
#[tokio::main] async fn main() { let _ = run().await; } async fn run() -> anyhow::Result<()> { env_logger::init(); // Establish connection let addr = "127.0.0.1:23333"; let client = Client::dial(addr).await.unwrap(); // Perform RPC using `call()` method let call: Call<i32> = client.call("Echo.echo_i32", 13i32); let reply = call.await?; println!("{:?}", reply); let reply: i32 = client.call("Echo.echo_i32", 1313i32).await?; println!("{:?}", reply); let ok_result = client .echo() // refering to `Echo` service .echo_if_equal_to_one(1) // refering to `echo_if_equal_to_one` method .await; let err_result = client .echo() .echo_if_equal_to_one(2) .await; println!("Ok result: {:?}", ok_result); println!("Err result: {:?}", err_result); // Demo usage with the `Arith` trait let addition = client .arith() // generated for `Arith` service .add((1, 3)).await; // call `add` method println!("{:?}", addition); // Although the return type of `divide` is a `Result<T, E>`, // the execution result will be mapped to `Result<T, toy_rpc::Error>` // where `E` is mapped to `toy_rpc::Error::ExecutionError` so that // (1) the Error type doesn't need to implement `Serialize` and // (2) the users don't need to unwrap twice let division = client .arith() .divide((3, 1)).await; println!("{:?}", division); // let's try to get an execution error let divide_by_zero = client .arith() .divide((3, 0)).await; println!("{:?}", divide_by_zero); // Now let's take a look at using the generated trait impl for the client. let addition = Arith2::add(&client, (7, 8)).await; println!("{:?}", addition); let division = Arith2::divide(&client, (7, 2)).await; println!("{:?}", division); let divide_by_zero = Arith2::divide(&client, (7, 0)).await; println!("{:?}", divide_by_zero); client.close().await; Ok(()) }