I recently looked into rust at work and got curious about the generative programming capabilities of the language. In general I've been fairly positively surprised with the language and ecosystem.
I've not yet completed the money transfer example or any more complex. So far I've only implemented an Account context where a ledger and an int plays the only roles but I'm confident in claiming that Rust can support DCI based on a macro.
This is what the Account looks like:
#[five::context]
pub mod account {
pub trait LedgerContract : {
fn push(&mut self, entry: LedgerEntry);
fn as_vec(&self) -> Vec<LedgerEntry>;
}
trait LedgerRole : LedgerContract{
fn add(&mut self, entry: LedgerEntry){
self.push(entry.clone());
self.log(entry.message());
}
fn log(&self, msg: String) {
println!("{}",msg);
}
}
struct Context {
ledger : LedgerRole,
account_no: i64
}
impl Context {
fn deposit(&mut self, message : String, amount: i32){
self.ledger.add(LedgerEntry::Deposit(message,amount))
}
fn withdraw(&mut self, message : String, amount: i32){
self.ledger.add(LedgerEntry::Withdrawal(message,amount))
}
fn balance(&self) -> i32 {
self.ledger.as_vec().iter().map(|entry| match entry {
LedgerEntry::Deposit(_, amount) => *amount,
LedgerEntry::Withdrawal(_, amount) => -*amount,
}).sum()
}
}
}
The idea of the macro is that any trait that ends in Role is a role and any trait that ends in Contract is a Role contract for the corresponding role. The context struct and impl is where the publicly available methods of the context are defined and those methods have access to the role methods as well as those of the contract.
Using this would look something like this:
use account::Account;
let mut account = account::bind(ledger, 67676555);
account.deposit(String::from("Deposit 1"), 100);
account.withdraw(String::from("Withdrawal 1"), 50);
account.deposit(String::from("Deposit 2"), 200);
account.withdraw(String::from("Withdrawal 2"), 100);
println!("Balance: {}", account.balance());
The second line being the one where the players are bound to their roles. The macro uses the same rewrite "trick" as always, so if I've implemented the macro "magic" correctly there's no identity issues.
That being said identity is actually at the forefront of rust memory management, and it's very clear in the code whether you clone/copy an object (not preserving identity) or you borrow the object (preserving identity)
The result of the above would be:
Deposit 1
Withdrawal 1
Deposit 2
Withdrawal 2
Balance: 150
Design choices:
Aside from the Contracts nothing in the module is public. The rewriting of the module creates a bind function and a trait that's publicly available. The trait being named after the module and describing the methods for the Context struct. Anything you see in the account module above is rewritten or removed completely.