bank_account/
bank_account.rs

1use actor12::prelude::*;
2use actor12::{Multi, MpscChannel, Call, spawn, Link};
3use std::time::Duration;
4use std::future::Future;
5use tokio::time::sleep;
6
7// Bank account actor that handles concurrent transactions safely
8pub struct BankAccount {
9    balance: f64,
10    account_number: String,
11}
12
13// Messages for bank operations
14#[derive(Debug)]
15pub struct Deposit {
16    pub amount: f64,
17}
18
19#[derive(Debug)]
20pub struct Withdraw {
21    pub amount: f64,
22}
23
24#[derive(Debug)]
25pub struct GetBalance;
26
27#[derive(Debug)]
28pub struct Transfer {
29    pub to_account: Link<BankAccount>,
30    pub amount: f64,
31}
32
33// Custom error for insufficient funds
34#[derive(Debug, thiserror::Error)]
35pub enum BankError {
36    #[error("Insufficient funds: balance {balance}, requested {requested}")]
37    InsufficientFunds { balance: f64, requested: f64 },
38    #[error("Invalid amount: {amount}")]
39    InvalidAmount { amount: f64 },
40}
41
42impl Actor for BankAccount {
43    type Spec = (String, f64); // (account_number, initial_balance)
44    type Message = Multi<Self>;
45    type Channel = MpscChannel<Self::Message>;
46    type Cancel = ();
47    type State = ();
48
49    fn state(_spec: &Self::Spec) -> Self::State {}
50
51    fn init(ctx: Init<'_, Self>) -> impl Future<Output = Result<Self, Self::Cancel>> + Send + 'static {
52        let (account_number, initial_balance) = ctx.spec;
53        async move {
54            println!("Bank account {} created with initial balance: ${:.2}", account_number, initial_balance);
55            Ok(BankAccount {
56                balance: initial_balance,
57                account_number,
58            })
59        }
60    }
61}
62
63impl Handler<Deposit> for BankAccount {
64    type Reply = Result<f64, anyhow::Error>;
65
66    async fn handle(&mut self, _ctx: Call<'_, Self, Self::Reply>, msg: Deposit) -> Self::Reply {
67        if msg.amount <= 0.0 {
68            return Err(BankError::InvalidAmount { amount: msg.amount }.into());
69        }
70
71        self.balance += msg.amount;
72        println!("Account {}: Deposited ${:.2}, new balance: ${:.2}", 
73                 self.account_number, msg.amount, self.balance);
74        Ok(self.balance)
75    }
76}
77
78impl Handler<Withdraw> for BankAccount {
79    type Reply = Result<f64, anyhow::Error>;
80
81    async fn handle(&mut self, _ctx: Call<'_, Self, Self::Reply>, msg: Withdraw) -> Self::Reply {
82        if msg.amount <= 0.0 {
83            return Err(BankError::InvalidAmount { amount: msg.amount }.into());
84        }
85
86        if self.balance < msg.amount {
87            return Err(BankError::InsufficientFunds { 
88                balance: self.balance, 
89                requested: msg.amount 
90            }.into());
91        }
92
93        self.balance -= msg.amount;
94        println!("Account {}: Withdrew ${:.2}, new balance: ${:.2}", 
95                 self.account_number, msg.amount, self.balance);
96        Ok(self.balance)
97    }
98}
99
100impl Handler<GetBalance> for BankAccount {
101    type Reply = Result<f64, anyhow::Error>;
102
103    async fn handle(&mut self, _ctx: Call<'_, Self, Self::Reply>, _msg: GetBalance) -> Self::Reply {
104        println!("Account {}: Balance inquiry: ${:.2}", self.account_number, self.balance);
105        Ok(self.balance)
106    }
107}
108
109impl Handler<Transfer> for BankAccount {
110    type Reply = Result<(), anyhow::Error>;
111
112    async fn handle(&mut self, _ctx: Call<'_, Self, Self::Reply>, msg: Transfer) -> Self::Reply {
113        if msg.amount <= 0.0 {
114            return Err(BankError::InvalidAmount { amount: msg.amount }.into());
115        }
116
117        if self.balance < msg.amount {
118            return Err(BankError::InsufficientFunds { 
119                balance: self.balance, 
120                requested: msg.amount 
121            }.into());
122        }
123
124        // Withdraw from this account
125        self.balance -= msg.amount;
126        println!("Account {}: Transfer out ${:.2}, new balance: ${:.2}", 
127                 self.account_number, msg.amount, self.balance);
128
129        // Deposit to target account
130        let _ = msg.to_account.ask_dyn(Deposit { amount: msg.amount }).await?;
131        println!("Transfer of ${:.2} completed from {} to target account", 
132                 msg.amount, self.account_number);
133
134        Ok(())
135    }
136}
137
138#[tokio::main]
139async fn main() -> anyhow::Result<()> {
140    // Create two bank accounts
141    let alice_account = spawn::<BankAccount>(("ALICE-001".to_string(), 1000.0));
142    let bob_account = spawn::<BankAccount>(("BOB-002".to_string(), 500.0));
143
144    // Perform some operations
145    println!("\n=== Initial Operations ===");
146    
147    // Check initial balances
148    let alice_balance = alice_account.ask_dyn(GetBalance).await?;
149    let bob_balance = bob_account.ask_dyn(GetBalance).await?;
150    println!("Alice: ${:.2}, Bob: ${:.2}", alice_balance, bob_balance);
151
152    // Alice deposits money
153    let _ = alice_account.ask_dyn(Deposit { amount: 200.0 }).await?;
154
155    // Bob withdraws money
156    let _ = bob_account.ask_dyn(Withdraw { amount: 100.0 }).await?;
157
158    println!("\n=== Transfer Operation ===");
159    
160    // Alice transfers money to Bob
161    alice_account.ask_dyn(Transfer { 
162        to_account: bob_account.clone(), 
163        amount: 300.0 
164    }).await?;
165
166    println!("\n=== Final Balances ===");
167    let alice_final = alice_account.ask_dyn(GetBalance).await?;
168    let bob_final = bob_account.ask_dyn(GetBalance).await?;
169    println!("Alice: ${:.2}, Bob: ${:.2}", alice_final, bob_final);
170
171    println!("\n=== Error Handling ===");
172    
173    // Try to withdraw more than available (should fail)
174    match alice_account.ask_dyn(Withdraw { amount: 2000.0 }).await {
175        Ok(_) => println!("Withdrawal succeeded unexpectedly!"),
176        Err(e) => println!("Withdrawal failed as expected: {}", e),
177    }
178
179    // Wait a moment before shutting down
180    sleep(Duration::from_millis(100)).await;
181
182    Ok(())
183}