Target - Rust

Quick Start

gugugu-rust \
        --input=dir/containing/gugugu/definitions \
        --output=dir/containing/rust/code \
        --module-prefix=some::prefix \
        --derives=Debug,PartialEq \
        --with-codec \
        --with-server \
        --with-client \
        ;

Compatibility

Gugugu generates Rust 2018 code and utilize Future. The theoritical minimum version of rustc is 1.36. The example compiles with rustc 1.39.

Module

Gugugu module is represented by Rust module, with module name lower-cased.

Types

Primitives

Gugugu Type

Rust Type

Unit

()

Bool

bool

Int32

i32

Double

f64

String

String

Maybe A

Option<A>

List A

Vec<A>

Record Type

Record type are represented with Rust struct with derive attribute specified in command line options.

data Book
  = Book
    { id   :: Int32
    , name :: String
    }

becomes

#[derive(Debug, PartialEq)]
pub struct Book {
  pub id  : i32,
  pub name: String,
}

Enum Type

Enum type are represented by Rust enumeration type with derive attribute specified in command line options.

data Color
  = Red
  | Green
  | Blue

becomes

#[derive(Debug, PartialEq)]
pub enum Color {
  Red,
  Green,
  Blue,
}

Foreign Type

data DateTime
  {-# FOREIGN rust chrono::NaiveDateTime #-}

becomes

pub type DateTime = chrono::NaiveDateTime;

Encoder and Decoder

All types in this section are located in module crate::gugugu::lang::rust::runtime::codec with default configuration.

pub trait Encoding {
  fn encode<C>
    ( s: C::State
    , a: &Self
    , c: &C
    ) -> Result<C::State, C::Error>
  where C: EncoderImpl
  ;
}

pub trait Decoding: Sized {
  fn decode<C>
    ( s: C::State
    , c: &C
    ) -> Result<(C::State, Self), C::Error>
  where C: DecoderImpl
  ;
}

All types generated by gugugu will have implementation for trait Encoding and Decoding.

The c is a value you have to provide to describe how to encode and decode a value. You also have to provide the EncoderImpl or DecoderImpl instance.

EncoderImpl and DecoderImpl

There are two traits named EncoderImpl and DecoderImpl which you should provide implementation to do the encoding/decoding. They both have three associated types Repr, Error and State.

  • The Repr is the serialized type encoding to or decoding from. e.g. String or Vec<u8>.

  • The Error is the type for possible errors occurred.

  • The State is the intermediate state used during encoding or decoding.

You might find examples/lang/rust/src/codec/json.rs useful to write implementation for EncoderImpl/DecoderImpl.

Most trait functions do not use Repr except the following two.

pub trait EncoderImpl: ForeignEncodersImpl {
  type Repr;

  fn encode_value<A>
    ( &self
    , a: &A
    ) -> Result<Self::Repr, Self::Error>
  where A: Encoding
  ;
}

pub trait DecoderImpl: ForeignDecodersImpl {
  type Repr;

  fn decode_value<A>
    ( &self
    , r: &Self::Repr
    ) -> Result<A, Self::Error>
  where A: Decoding
  ;
}

For an encode_value, you usually should

  • Provide an initial state

  • Feed it to A::encode

  • Transform the state returned to serialized type, Repr

For a decode_value, you usually should

  • Transform the Repr into an initial state State

  • Feed it to A::decode

  • Make sure the returned state did not go wrong

  • Return the decoded value returned by A::decode

Encode/Decode Record Type

pub trait EncoderImpl: ForeignEncodersImpl {
  fn encode_record<A>
    ( &self
    , s: Self::State
    , n_fields: usize
    , a: &A
    , k: fn(&Self, Self::State, &A) -> Result<Self::State, Self::Error>
    ) -> Result<Self::State, Self::Error>
  ;
  fn encode_record_field<A>
    ( &self
    , s: Self::State
    , i: usize
    , name: &str
    , a: &A
    ) -> Result<Self::State, Self::Error>
  where A: Encoding
  ;
}

pub trait DecoderImpl: ForeignDecodersImpl {
  fn decode_record<A>
    ( &self
    , s: Self::State
    , n_fields: usize
    , k: fn(&Self, Self::State) -> Result<(Self::State, A), Self::Error>
    ) -> Result<(Self::State, A), Self::Error>
  ;
  fn decode_record_field<A>
    ( &self
    , s: Self::State
    , i: usize
    , name: &str
    ) -> Result<(Self::State, A), Self::Error>
  where A: Decoding
  ;
}

The generated encoder/decoder for record type consists of a call to encode_record/decode_record with the number of fields. And the provided callback will call the encode_record_field/decode_record_field several times with indices and names of the fields.

Encode/Decode Enum Type

pub trait EncoderImpl: ForeignEncodersImpl {
  fn encode_enum<A>
    ( &self
    , s: Self::State
    , a: A
    , as_index: fn(A) -> i32
    , as_name: fn(A) -> &'static str
    ) -> Result<Self::State, Self::Error>
  ;
}

pub trait DecoderImpl: ForeignDecodersImpl {
  fn decode_enum<A>
    ( &self
    , s: Self::State
    , by_index: fn(i32) -> Option<A>
    , by_name: fn(&str) -> Option<A>
    ) -> Result<(Self::State, A), Self::Error>
  ;
}

The generated encoder/decoder for enum type consists of a call to encode_enum/decode_enum. You should encode/decode the value with the name or the index.

Encode/Decode Primitive and Foreign Types

pub trait ForeignEncodersImpl {
  type Error;
  type State;
  // foreign encoder members, e.g.
  // fn encode_date_time
  //   ( self: &Self
  //   , s: Self::State
  //   , v: &chrono::NaiveDateTime
  //   ) -> Result<Self::State, Self::Error>
  // ;
}
pub trait ForeignDecodersImpl {
  type Error;
  type State;
  // foreign decoder members, e.g.
  // fn decode_date_time
  //   ( self: &Self
  //   , s: Self::State
  //   ) -> Result<(Self::State, chrono::NaiveDateTime), Self::Error>
  // ;
}

pub trait EncoderImpl: ForeignEncodersImpl {
  fn encode_maybe<A>
    ( &self
    , s: Self::State
    , v: &Option<A>
    ) -> Result<Self::State, Self::Error>
  where A: Encoding
  ;
  fn encode_list<A>
    ( &self
    , s: Self::State
    , v: &Vec<A>
    ) -> Result<Self::State, Self::Error>
  where A: Encoding
  ;
  fn encode_unit
    ( &self
    , s: Self::State
    , v: &()
    ) -> Result<Self::State, Self::Error>
  ;
  fn encode_bool
    ( &self
    , s: Self::State
    , v: &bool
    ) -> Result<Self::State, Self::Error>
  ;
  fn encode_int32
    ( &self
    , s: Self::State
    , v: &i32
    ) -> Result<Self::State, Self::Error>
  ;
  fn encode_double
    ( &self
    , s: Self::State
    , v: &f64
    ) -> Result<Self::State, Self::Error>
  ;
  fn encode_string
    ( &self
    , s: Self::State
    , v: &String
    ) -> Result<Self::State, Self::Error>
  ;
}

pub trait DecoderImpl: ForeignDecodersImpl {
  fn decode_maybe<A>
    ( &self
    , s: Self::State
    ) -> Result<(Self::State, Option<A>), Self::Error>
  where A: Decoding
  ;
  fn decode_list<A>
    ( &self
    , s: Self::State
    ) -> Result<(Self::State, Vec<A>), Self::Error>
  where A: Decoding
  ;
  fn decode_unit
    ( &self
    , s: Self::State
    ) -> Result<(Self::State, ()), Self::Error>
  ;
  fn decode_bool
    ( &self
    , s: Self::State
    ) -> Result<(Self::State, bool), Self::Error>
  ;
  fn decode_int32
    ( &self
    , s: Self::State
    ) -> Result<(Self::State, i32), Self::Error>
  ;
  fn decode_double
    ( &self
    , s: Self::State
    ) -> Result<(Self::State, f64), Self::Error>
  ;
  fn decode_string
    ( &self
    , s: Self::State
    ) -> Result<(Self::State, String), Self::Error>
  ;
}

The primitive types and foreign types will generate functions like above. And the encoder/decoder simply calls the function you provide.

Client and Server

All types in this section are located in module crate::gugugu::lang::rust::runtime::transport with default configuration.

module Hello where

foo :: FooReq -> IO FooRes
bar :: BarReq -> IO BarRes

becomes

use std::future::Future;
use std::sync::Arc;

pub trait HelloModule<E, I, O>
{
  type FooFuture: Future<Output = Result<(O, FooRes), E>> + Send;
  fn foo
    ( self: &Self
    , a: FooReq
    , i: I
    ) -> Self::FooFuture
  ;
  type BarFuture: Future<Output = Result<(O, BarRes), E>> + Send;
  fn bar
    ( self: &Self
    , a: BarReq
    , i: I
    ) -> Self::BarFuture
  ;
}

pub fn ask_transport<A, CA, CB, CH, E, I, O, EA, EB, RA, RB>
  ( namespace: &[&str]
  , name: &str
  ) -> Option<fn(Arc<A>, &CH, Arc<CA>, Arc<CB>, RA, I) -> CH::OutputFuture>
where A: HelloModule<E, I, O> + Send + Sync + 'static
    , CA: DecoderImpl<Error = EA, Repr = RA> + Send + Sync + 'static
    , CB: EncoderImpl<Error = EB, Repr = RB> + Send + Sync + 'static
    , CH: ServerCodecHandler<E, I, O, RA, RB, EA, EB>
{
  // definition omitted
}

impl<T, CA, CB, E, I, O> HelloModule<E, I, O> for GuguguClient<T, CA, CB>
where T: ClientTransport<E, I, O, CA::Repr, CB::Repr, CA::Error, CB::Error>
    , CA: EncoderImpl + Send + Sync + 'static
    , CB: DecoderImpl + Send + Sync + 'static
{
  // definition omitted
}

A value with HelloModule can be used as the client when used in client code, or as the server implementation in server code.

The CA is the encoder or decoder used by request, The CB is the encoder or decoder used by response.

The I and O are metadata of request and response.

Server Usage

pub trait ServerCodecHandler<E, I, O, RA, RB, EA, EB> {
  type OutputFuture: Future<Output = Result<(O, RB), E>>;

  fn run<A, CA, CB, B, R>
    ( &self
    , ca: Arc<CA>
    , cb: Arc<CB>
    , k: impl FnOnce(A, I) -> R + Send + 'static
    , ra: RA
    , i: I
    ) -> Self::OutputFuture
  where R: Future<Output = Result<(O, B), E>> + Send
      , A: Decoding + Send + 'static
      , B: Encoding + Send + 'static
      , CA: DecoderImpl<Error = EA, Repr = RA> + Send + Sync + 'static
      , CB: EncoderImpl<Error = EB, Repr = RB> + Send + Sync + 'static
  ;

}

pub fn ask_transport<A, CA, CB, CH, E, I, O, EA, EB, RA, RB>
  ( namespace: &[&str]
  , name: &str
  ) -> Option<fn(Arc<A>, &CH, Arc<CA>, Arc<CB>, RA, I) -> CH::OutputFuture>
where A: HelloModule<E, I, O> + Send + Sync + 'static
    , CA: DecoderImpl<Error = EA, Repr = RA> + Send + Sync + 'static
    , CB: EncoderImpl<Error = EB, Repr = RB> + Send + Sync + 'static
    , CH: ServerCodecHandler<E, I, O, RA, RB, EA, EB>
{
  // definition omitted
}

ask_transport returns a function that can handle the request if found.

The implementation for ServerCodecHandler should be straightforward, it should handle a request deserialized from RA with response serialized to RB.

Please consult examples/lang/rust/src/bin/gugugu-example-rust-jsonhttp-server.rs for how to use the it.

Client Usage

pub trait ClientTransport<E, I, O, RA, RB, EA, EB> {
  fn send<A, B, CA, CB>
    ( &self
    , namespace: &[&str]
    , name: &str
    , a: A
    , i: I
    , ca: Arc<CA>
    , cb: Arc<CB>
    ) -> Pin<Box<dyn Future<Output = Result<(O, B), E>> + Send>>
  where A: Encoding + Send + 'static
      , B: Decoding + Send + 'static
      , CA: EncoderImpl<Error = EA, Repr = RA> + Send + Sync + 'static
      , CB: DecoderImpl<Error = EB, Repr = RB> + Send + Sync + 'static
  ;
}

pub struct GuguguClient<T, CA, CB> {
  pub transport: T,
  pub encoder_impl: Arc<CA>,
  pub decoder_impl: Arc<CB>,
}

impl<T, CA, CB, E, I, O> HelloModule<E, I, O> for GuguguClient<T, CA, CB>
where T: ClientTransport<E, I, O, CA::Repr, CB::Repr, CA::Error, CB::Error>
    , CA: EncoderImpl + Send + Sync + 'static
    , CB: DecoderImpl + Send + Sync + 'static
{
  // definition omitted
}

Like ServerCodecHandler, ClientTransport can handle request serialized to RA with response deserialized from RB.

Please consult examples/lang/rust/src/bin/gugugu-example-rust-jsonhttp-client.rs for how to write a ClientTransport.

Command Line Options

Usage: gugugu-rust (-i|--input INPUT) (-o|--output OUTPUT)
                   (-p|--module-prefix MODULE_PREFIX)
                   [-r|--runtime-module RUNTIME_MODULE] [--derives DERIVES]
                   [--with-codec] [--with-server] [--with-client]
                   [--trans-module-code ARG] [--trans-module-value ARG]
                   [--trans-module-type ARG] [--trans-func-code ARG]
                   [--trans-func-value ARG] [--trans-type-code ARG]
                   [--trans-type-func ARG] [--trans-field-code ARG]
                   [--trans-field-value ARG] [--trans-enum-code ARG]
                   [--trans-enum-value ARG] [--version]

Available options:
  -i,--input INPUT         the directory containing the definition files
  -o,--output OUTPUT       the directory to put the generated sources
  -p,--module-prefix MODULE_PREFIX
                           the package prefix, e.g. some::prefix
  -r,--runtime-module RUNTIME_MODULE
                           location of gugugu runtime
                           module (default: "gugugu::lang::rust::runtime")
  --derives DERIVES        derive attribute for data type, use comma to separate
                           multiples, e.g. Debug,PartialEq
  --with-codec             pass this flag to generate codecs, default to false
  --with-server            pass this flag to generate server, default to false,
                           implies with-codec
  --with-client            pass this flag to generate client, default to false,
                           implies with-codec
  --trans-module-code ARG  module name transformer for code (default: lower)
  --trans-module-value ARG module name transformer for value (default: snake)
  --trans-module-type ARG  module name transformer for type of
                           client/server (default: id)
  --trans-func-code ARG    function name transformer for code (default: snake)
  --trans-func-value ARG   function name transformer for value (default: snake)
  --trans-type-code ARG    type name transformer for code (default: id)
  --trans-type-func ARG    type name transformer in function (default: snake)
  --trans-field-code ARG   record field name transformer for
                           code (default: snake)
  --trans-field-value ARG  record field name transformer for
                           value (default: snake)
  --trans-enum-code ARG    enum name transformer for code (default: id)
  --trans-enum-value ARG   enum name transformer for
                           value (default: upper-snake)
  -h,--help                Show this help text
  --help-transformers      list available name transformers
  --version                show version