5 releases
0.2.2 | Mar 8, 2022 |
---|---|
0.2.1 | Mar 7, 2022 |
0.2.0 | Mar 4, 2022 |
0.1.1 | Mar 3, 2022 |
0.1.0 | Mar 1, 2022 |
#10 in #daml
Used in daml
3MB
11K
SLoC
Daml Derive
This crate provides procedural macros for generating Rust types from daml
.
This crate should not be used directly, instead you should depend on the daml
crate and enable the derive
feature:
[dependencies]
daml = { version = "0.2.2", features = [ "derive" ] }
License
daml-derive
is distributed under the terms of the Apache License (Version 2.0).
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in time by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
See LICENSE for details.
Copyright 2022
lib.rs
:
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
dar
file 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 |
[macro@DamlTemplate ] |
Daml Template Choices | impl block |
[macro@DamlChoices ] |
Daml Data (Record) | struct |
[macro@DamlData ] |
Daml Data (Variant) | enum |
[macro@DamlVariant ] |
Daml Enum | enum |
[macro@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 : Foo
Both [macro@DamlData
] and [macro@DamlVariant
] types may therefore be defined recursively. However modelling
such structures in Rust requires that any recursively 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
hierarchy. All of the standard Rust
name resolution and visibility rules apply and therefore it is recommend to mirror the Daml hierarchy 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 [macro@DamlTemplate
] and [macro@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
specific 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 [macro@DamlTemplate
], [macro@DamlChoices
] & [macro@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.
Dependencies
~7–10MB
~181K SLoC