Closed JackKelly closed 6 months ago
A challenge is that typestate
uses different types to encode different states of the same system (duh)... but we need to keep a Vec
of these states! And we don't want to use Vec<Box<dyn TypeStateTrait>>
because we want to minimise heap allocations.
Once Rust's const generics are stabilised then we may be able to use enum variants for the generic parameter of a struct. See this reddit thread for a code example. But maybe we still won't be able to store those in a Vec
because those struct Foo<MyEnum::A>
will still be considered a different type to struct Foo<MyEnum::B>
?? Also, it's not clear when const generics will be stabilised.
So I think the answer is to use enums
to represent state, and ensure we can only progress forwards. See this blog and the typestate_enum
crate and slide 18 in the PDF slides.
So I think we want something like this, if it's allowed:
enum UringOperation<M> {
GetRange {
byte_range: UringOptimisedByteRanges<M>,
fixed_file_descriptor: Option<usize>, // TODO: Use types::Fixed
state: Enum {
GetFilesizeAndOpen,
Read,
Close,
},
},
}
Or, maybe better:
enum UringOperation<M> {
GetRangeGetFilesizeAndOpen,
GetRangeRead,
GetRangeClose,
}
As expected, this doesn't work:
struct Operation<S: IoState> {
marker: std::marker::PhantomData<S>,
}
trait IoState {}
enum GetRangesRead {}
enum GetRangesClose {}
impl IoState for GetRangesRead {}
impl IoState for GetRangesClose {}
impl Operation<GetRangesRead> {
fn foo() {}
}
impl Operation<GetRangesClose> {
fn boo() {}
}
fn main() {
let read_op: Operation<GetRangesRead> = Operation {
marker: std::marker::PhantomData,
};
let close_op: Operation<GetRangesClose> = Operation {
marker: std::marker::PhantomData,
};
let v = vec![read_op, close_op]; // Error: mismatched types
}
This doesn't work either:
let v: Vec<Box<Operation<dyn IoState>>> = vec![Box::new(read_op), Box::new(close_op)];
Also see: https://github.com/JackKelly/rust-playground/tree/main/typestate
I'm pretty happy with my current planned approach. See the lower half of the code here: https://github.com/JackKelly/light-speed-io/blob/new-design-March-2024/src/new_design_march_2024.rs
Idea from the talk "Abusing the Type System for Fun and for Profit" at Rust Nation UK 2024. In particular, see slide 18 in the PDF slides.
This is called the "typestate pattern".
e.g.
Rocket<Ground>
vsRocket<Air>
. You convert fromRocket<Ground>
toRocket<Air>
bylaunch
ingRocket<Ground>
(and you can'tlaunch
Rocker<Air>
).Perhaps better, see this tutorial: https://cliffle.com/blog/rust-typestate/