You can do the linear typing thing in Rust. Have a hidden internal handle and API wrapper objects on top of it that get consumed on method calls and can return different wrappers holding the same handle. I took a shot at doing a toy implementation for the TCP case:
type internal_tcp_handle = usize; // Hidden internal implementation
/// Initial closed state
#[derive(Debug)]
pub struct Tcp(internal_tcp_handle);
impl Tcp {
pub fn connect_unauthenticated(self) -> Result<AuthTcp, Tcp> {
// Consume current API wrapper,
// return next state API wrapper with same handle.
Ok(AuthTcp(self.0))
}
pub fn connect_password(self, _user: &str, pass: &str) -> Result<AuthTcp, Tcp> {
// Can fail back to current state if password is empty.
if pass.is_empty() { Err(self) } else { Ok(AuthTcp(self.0)) }
}
}
/// Authenticated state.
#[derive(Debug)]
pub struct AuthTcp(internal_tcp_handle);
impl AuthTcp {
pub fn connect_tcp(self, addr: &str) -> Result<TcpConnection, AuthTcp> {
if addr.is_empty() { Err(self) } else { Ok(TcpConnection(self.0)) }
}
pub fn connect_udp(self, addr: &str) -> Result<UdpConnection, AuthTcp> {
if addr.is_empty() { Err(self) } else { Ok(UdpConnection(self.0)) }
}
}
#[derive(Debug)]
pub struct TcpConnection(internal_tcp_handle);
#[derive(Debug)]
pub struct UdpConnection(internal_tcp_handle);
fn main() {
// Create unauthenticated TCP object.
let tcp = Tcp(123);
println!("Connection state: {:?}", tcp);
// This would be a compiler error:
// let tcp = tcp.connect_tcp("8.8.8.8").unwrap();
// 'tcp' is bound to an API that doesn't support connect operations yet.
// Rebind the stupid way, unwrap just runtime errors unless return is Ok.
let tcp = tcp.connect_unauthenticated().unwrap();
// Now 'tcp' is bound to the authenticated API, we can open connections.
println!("Connection state: {:?}", tcp);
// The runtime errory way is ugly, let's handle failure properly...
if let Ok(tcp) = tcp.connect_tcp("8.8.8.8") {
println!("Connection state: {:?}", tcp);
} else {
println!("Failed to connect to address!");
}
// TODO Now that we can use connected TCP methods on 'tcp',
// implement those and write some actual network code...
}
You can do the linear typing thing in Rust. Have a hidden internal handle and API wrapper objects on top of it that get consumed on method calls and can return different wrappers holding the same handle. I took a shot at doing a toy implementation for the TCP case: