3 ms·
I was thinking about the Typestate Pattern because it would also take care of the order of the intermediate steps. Is it possible to mark the last type with #[m
by weinzierl 3y ago
I was thinking about the Typestate Pattern because it would also take care of the order of the intermediate steps. Is it possible to mark the last type with #[must_use] and get a compile time error if I do not go through all steps?
EDIT: I found this:
"Basically, the typestate pattern in Rust today provides a way to ensure that state machines are used correctly, but does not fully defend against them being unused, or discarded before completion." [1]
So I guess #[must_use] is not enough and that's what you were saying in your comment
already.
EDIT 2: I found another good write-up of this topic:
"The Pain Of Real Linear Types in Rust" [2]
[1] https://users.rust-lang.org/t/write-up-on-using-typestates-in-rust/28997/28?u=weinzierl https://users.rust-lang.org/t/write-up-on-using-typestates-i...
[2] https://faultlore.com/blah/linear-rust https://faultlore.com/blah/linear-rust
- chrismorgan 3y agoYou don’t need anything along the lines of typestate, though it can fit in fine as well: having all your functions consuming self and returning a new Self (except the consuming “destructor”) is sufficient. It’s just an ergonomics pain since you need to assign it to something every time: #[must_use] struct Transaction; impl Transaction { fn new() -> Self { … } fn action(self, …) -> Self { … } fn commit(self) -> Result<(), …> { … } } let mut tx = Transaction::new(); tx = tx.action(…); // ← `tx =` is the difference. // (Any panic at this point will roll back the transaction via Drop, // but any errors in the process will be swallowed or abort or who knows what, there’s no standard.) tx.commit()?; // unused-must-use warning on the last assignment of tx (`tx = tx.action(…);`) if this line is absent A hypothetical #[must_consume] would let it be like this: #[must_consume] struct Transaction; impl Transaction { fn new() -> Self { … } fn action(&mut self, …) { … } fn commit(self) -> Result<(), …> { …; drop(self); Ok(()) } } let mut tx = Transaction::new(); tx.action(…); // (Same panic behaviour.) tx.commit()?; // unused-must-consume warning on the last assignment of tx (`let mut tx = Transaction::new();`) if this line is absent With putting the state into the type, you’d be limited to annotating your entire generic type with #[must_use], not individual states. (#[must_use] won’t propagate through generic parameters, nor should it.)
- weinzierl 3y agoThat makes it clearer. Thanks!