Fluent Rust - Type State Pattern #2

Migo·2023년 8월 21일
0

Fluent Rust

목록 보기
5/27
post-thumbnail

In the last article(https://velog.io/@migorithm/Fluent-Rust-Type-State-Pattern), I covered one way of handling type state pattern in Rust.

As safe as it is, it has some drawbacks; multiple structs to describe potentially one DB entity, Box smart pointer, enum state manager and so on. Can we pare it down more? And well, Yes you can:

Types

// states
#[derive(Deserialize,Serialize,Debug)]
pub struct InCart;
#[derive(Deserialize,Serialize,Debug)]
pub struct PurchaseRequested;
#[derive(Deserialize,Serialize,Debug)]
pub struct PurchaseMade;

// struct
pub struct Transaction<State=InCart>{
    items: Vec<String>,
    amount: i32,
    state : String,
    #[serde(skip_serializing)]
    _state: PhantomData<State>
}

Surely, you wonder, “what’s with PhantomData? Well, PhantomData is zero-sized type that literally doesn’t take up any memory but just to mark things so the struct can “act like” it owns the type.

Let’s see the magic you can spell out with this:

impl Transaction<InCart>{
    pub fn request_purchase(self) -> Transaction<PurchaseRequested>{
        Transaction{_state : PhantomData::<PurchaseRequested>, amount: self.amount, items:self.items,state:"PurchaseRequested".to_string()}
    }
}

impl Transaction<PurchaseRequested>{
    pub fn make_purchase(self)->Transaction<PurchaseMade>{
       Transaction { items: self.items, amount: self.amount, _state: PhantomData::<PurchaseMade> ,state:"PurchaseMade".to_string()} 
    }
}

impl Transaction{
    fn new(items:Vec<String>,amount:i32)-> Transaction<InCart>{
        Transaction { items, amount, _state: PhantomData::<InCart>,state:"InCart".to_string() }
    }
}

You may have noticed that every time I implement methods, I specify the state in which the following methods can be invoked. That way, you can achieve the same effect as you have by implementing multiple structs that denote each state while cutting down the code to one struct.

Note also too that I manage two states, one(state) is String type state that is mainly for persistence and the other _state is PhantomData that’s for application side operation.

Result

fn main(){
    let transaction = Transaction::new(
        vec!["item1".to_string(),"item2".to_string(),"item3".to_string()],
        30000
    );
    let transaction = transaction.request_purchase();
    let transaction = transaction.make_purchase();

    println!("{:?}",serde_json::json!(&transaction)); // serializable and it contains type information!

}

// Object {"amount": Number(30000), "items": Array [String("item1"), String("item2"), String("item3")], "state": String("PurchaseMade")}
profile
Dude with existential crisis

0개의 댓글

관련 채용 정보