Crate daml_derive
source · [−]Expand description
Procedural macros for generating Rust types and conversions from Daml types and Archives.
Overview
Two mechanisms are provided for representing Daml types in Rust:
- Custom attributes which can be applied to Rust structures which generate Daml type converters.
- A procedural macro code generator which takes a Daml
darfile as input and generates Rust types annotated with the custom attributes.
Custom Attributes
This section explains how to use the provided custom attributes to annotate Rust types to generate the Daml ledger API data conversion code required to be able to use them with a Daml ledger.
Mapping Daml Structures to Rust
Daml structures are modelled using various Rust language constructs in conjunction with custom attributes procedural macros as shown in the following table:
| Daml Concept | Rust Construct | Custom Attribute |
|---|---|---|
| Daml Template | struct | DamlTemplate |
| Daml Template Choices | impl block | DamlChoices |
| Daml Data (Record) | struct | DamlData |
| Daml Data (Variant) | enum | DamlVariant |
| Daml Enum | enum | DamlEnum |
Mapping Daml Data Types to Rust
The following table lists the mappings between Daml build-in primitive types and Rust type aliases:
| Daml Type | Rust Type Alias | Concrete Rust Type | Notes |
|---|---|---|---|
Int | DamlInt64 | i64 | |
Numeric | DamlNumeric | bigdecimal::BigDecimal | Note: BigDecimal crate to be replaced |
Text | DamlText | String | |
Bool | DamlBool | bool | |
Party | DamlParty | String | |
Date | DamlDate | chrono::Date | |
Time | DamlTime | chrono::DateTime | |
() | DamlUnit | () | |
ContractId a | DamlContractId | String | Note: this mapping is likely to change |
List a or [a] | DamlList<T> | Vec<T> | type T must be another Rust type alias |
TextMap a | DamlTextMap<T> | HashMap<String, T> | type T must be another Rust type alias |
Optional a | DamlOptional<T> | Option<T> | type T must be another Rust type alias |
Note that the concrete Rust types are shown here as a convenience only, in all cases the Rust type alias must be used when representing Daml constructs so that the Daml types can be determined.
Parameterized Types
The parameterized types (List<T>, TextMap<T> and Optional<T>) may be freely nested to an arbitrary depth and
may be used in all context a type is expected such as templates & data fields as well as choice parameters.
For example these are examples of valid types:
let int: DamlInt64;
let party: DamlParty;
let opt_numeric: DamlOptional<DamlNumeric10>;
let list_of_int: DamlList<DamlInt64>;
let list_of_opt_int: DamlList<DamlOptional<DamlInt64>>;
let list_of_opt_map_party: DamlList<DamlOptional<DamlTextMap<DamlParty>>>;
let opt_list_data: DamlOptional<DamlList<MyData>>;Recursive Data Types
Daml Data (both Records and Variants) may be recursive. For example:
data Foo = Foo
with
bar : Optional Text
foo : FooBoth DamlData and DamlVariant types may therefore be defined recursively. However modelling
such structures in Rust requires that any recurisvely defined items be held via an indirection, typically
via a heap allocation smart pointer such as Box<T>, to ensure a non-infinite size for the struct or enum
(see here for details).
The above example can therefore be represented as follows:
#[DamlData]
pub struct Foo {
bar: DamlOptional<DamlText>,
foo: Box<Foo>,
}Note that Box<T> is the only form of indirection currently supported and it may be used anywhere T is used.
Prelude
All of the above Rust type aliases are defined in the prelude module of
the daml crate and can included by using daml::prelude::*.
Modules
Rust struct and enum types annotated with the custom attributes provided by this crate are
not required to be nested in Rust modules that mirror the Daml module heirarchy. All of the standard Rust
name resolution and visiblity rules apply and therefore it is recommedned to mirror the Daml heirarchy where
possible to avoid namespace collisions.
For example, the MyData data type defined in the Fuji.MyModule.MySubModule Daml module would likely be declared
as follows:
mod fuji {
mod my_module {
mod my_sub_module {
use daml::prelude::*;
#[DamlData]
pub struct MyData {}
}
}
}Example
Given the following Daml template declared in the Fuji.PingPong module of a given package:
template Ping
with
sender: Party
receiver: Party
count: Int
where
signatory sender
observer receiver
controller receiver can
ResetCount : ()
with
new_count: Int
do
create Pong with sender; receiver; count = new_count
return ()This can be represented in Rust by using the DamlTemplate and DamlChoices custom attributes:
use daml::prelude::*;
#[DamlTemplate(package_id = r"...package id hash omitted...", module_name = "Fuji.PingPong")]
pub struct Ping {
pub sender: DamlParty,
pub receiver: DamlParty,
pub count: DamlInt64,
}
#[DamlChoices]
impl Ping {
#[ResetCount]
fn reset_count(&self, new_count: DamlInt64) {}
}A new Ping can then be created as follows:
let ping = Ping::new("Alice", "Bob", 0);To create an instance of the Ping template on a Daml ledger a DamlCreateCommand specfic to our ping data
needs to be constructed. This can be done as follows:
let create_ping_command = ping.create_command();The generated DamlCreateCommand can then be submitted to the Daml ledger via the DamlCommandService or
DamlCommandSubmissionService as usual.
Once the contract instance has been created on the Daml ledger and the corresponding DamlCreatedEvent has been
received then it can be converted into a Rust type as follows:
let ping_contract: PingContract = created_event.try_into()?;
}Note that the DamlCreatedEvent returned by the Daml ledger is converted into a PingContract rather than a
plain Ping. The PingContract type is a struct and provides methods data() -> Ping and
id() -> &PingContractId to access the Ping data and contract id respectively:
assert_eq!("Alice", ping_contract.data().sender);
assert_eq!("Bob", ping_contract.data().receiver);
assert_eq!(0, ping_contract.data().count);
assert_eq!("#0:0", ping_contract.id().contract_id);NOTE: The contract id may be refactored to use a separate type in future.
The PingContract types provides a method for each choice defined by the Daml template along with any
parameters that choice may have. To exercise a choice on a Daml ledger a DamlExerciseCommand specific to our
contract is needed. The can be constructed as follows:
let exercise_command = ping_contract.id().reset_count_command(5);
}The generated DamlExerciseCommand can then be submitted to the Daml ledger via the DamlCommandService or
DamlCommandSubmissionService as usual.
Note that the name of the choice method must match the name of the Daml choice (in snake_case) with a _command
suffix and the choice parameters must match between the Daml and Rust representations.
See the documentation for DamlTemplate, DamlChoices & DamlData for full details and
examples.
Errors
Returns the underlying DamlError (runtime-only) if the try_into() conversion from a DamlValue to an
annotated type fails.
Panics
Panics (compile-time only) if errors are detected in the annotated struct, enum or impl blocks.
Macros
Function-like procedural macro to generate Rust code for a Daml Daml dar (“Daml Archive”) files.
Attribute Macros
Custom attribute for modelling Daml choices.
Custom attribute for modelling Daml data structures.
Custom attribute for modelling Daml enums.
Custom attribute for modelling Daml templates.
Custom attribute for modelling Daml variants.