Lab 4: Types and Traits in Rust

This lab explores type-based abstractions and traits in Rust.

Generic Types and Trait Bounds

// Generic struct with type parameter
struct Point<T> {
    x: T,
    y: T,
}
 
// Trait definition
trait Area {
    fn area(&self) -> u32;
}
 
// Implementing a trait
impl Area for Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}
 
// Function with trait bound
fn notify<T: Summary>(item: T) {
    println!("Breaking news! {}", item.summarize());
}

POP3 State Machine Design

For the POP3 protocol state machine exercise, we would design the following types:

// States of the POP3 protocol
struct NotConnected;
struct Authorization {
    connection: TcpStream,
}
struct Transaction {
    connection: TcpStream,
    username: String,
}
struct Update {
    connection: TcpStream,
    username: String,
    changes: Vec<Change>,
}
 
// Methods for state transitions
impl NotConnected {
    fn connect(self, server: &str, port: u16) -> Result<Authorization, Error> {
        let connection = TcpStream::connect((server, port))?;
        Ok(Authorization { connection })
    }
}
 
impl Authorization {
    fn login(self, username: String, password: String) -> Result<Transaction, (Self, Error)> {
        // Try to authenticate...
        // If successful:
        Ok(Transaction { 
            connection: self.connection,
            username,
        })
        // If failed:
        // Err((self, Error::AuthenticationFailed))
    }
    
    fn disconnect(self) -> NotConnected {
        // Close connection
        NotConnected
    }
}
 
impl Transaction {
    fn list(&self) -> Result<Vec<MessageInfo>, Error> {
        // Implementation...
    }
    
    fn retrieve(&self, msg_id: u32) -> Result<Message, Error> {
        // Implementation...
    }
    
    fn delete(&mut self, msg_id: u32) -> Result<(), Error> {
        // Implementation...
    }
    
    fn quit(self) -> Result<Update, Error> {
        // Implementation...
        Ok(Update {
            connection: self.connection,
            username: self.username,
            changes: vec![],
        })
    }
}

The key insight in this design is how Rust’s ownership system enforces the state transitions. For example, when calling login() on an Authorization object, the method takes ownership of self and either returns a new Transaction object (on success) or returns the original Authorization object (on failure). This makes it impossible to use the wrong state for an operation.