1. rust
  2. /structs enums
  3. /methods

Methods & Associated Functions

Methods and associated functions allow you to define behavior for your types. While methods operate on instances of a type, associated functions are called on the type itself. Together, they provide a clean way to organize functionality and create intuitive APIs.

Implementation Blocks

Use impl blocks to define methods and associated functions for types:

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // Associated function (constructor)
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }
    
    // Associated function (alternative constructor)
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
    
    // Method (takes &self)
    fn area(&self) -> u32 {
        self.width * self.height
    }
    
    // Method (takes &mut self)
    fn double_size(&mut self) {
        self.width *= 2;
        self.height *= 2;
    }
    
    // Method (takes self - consumes the instance)
    fn into_area(self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    // Using associated functions
    let rect = Rectangle::new(30, 50);
    let square = Rectangle::square(20);
    
    // Using methods
    println!("Area: {}", rect.area());
    
    let mut mutable_rect = Rectangle::new(10, 15);
    mutable_rect.double_size();
    println!("Doubled size area: {}", mutable_rect.area());
    
    // Consuming method
    let final_area = square.into_area();
    println!("Final area: {}", final_area);
    // square is no longer available here
}

Method Types and Self Parameters

Rust has three types of self parameters that determine how methods interact with instances:

Immutable Reference (&self)

Most common for methods that read data without modifying it:

struct Person {
    name: String,
    age: u32,
}

impl Person {
    fn name(&self) -> &str {
        &self.name
    }
    
    fn age(&self) -> u32 {
        self.age
    }
    
    fn is_adult(&self) -> bool {
        self.age >= 18
    }
    
    fn greet(&self) {
        println!("Hello, my name is {}", self.name);
    }
    
    fn full_info(&self) -> String {
        format!("{} is {} years old", self.name, self.age)
    }
}

fn main() {
    let person = Person {
        name: "Alice".to_string(),
        age: 25,
    };
    
    // All these methods can be called multiple times
    println!("Name: {}", person.name());
    println!("Age: {}", person.age());
    println!("Is adult: {}", person.is_adult());
    person.greet();
    println!("Info: {}", person.full_info());
}

Mutable Reference (&mut self)

For methods that need to modify the instance:

struct Counter {
    value: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { value: 0 }
    }
    
    fn increment(&mut self) {
        self.value += 1;
    }
    
    fn increment_by(&mut self, amount: u32) {
        self.value += amount;
    }
    
    fn reset(&mut self) {
        self.value = 0;
    }
    
    fn set(&mut self, value: u32) {
        self.value = value;
    }
    
    fn get(&self) -> u32 {
        self.value
    }
}

fn main() {
    let mut counter = Counter::new();
    
    counter.increment();
    counter.increment_by(5);
    println!("Counter value: {}", counter.get());
    
    counter.reset();
    println!("After reset: {}", counter.get());
    
    counter.set(42);
    println!("After set: {}", counter.get());
}

Owned Self (self)

For methods that consume the instance:

struct Message {
    content: String,
    sender: String,
}

impl Message {
    fn new(content: String, sender: String) -> Message {
        Message { content, sender }
    }
    
    // Consuming method - takes ownership
    fn send(self) -> Result<(), String> {
        println!("Sending message '{}' from {}", self.content, self.sender);
        // Simulate sending...
        Ok(())
    }
    
    // Transform into another type
    fn into_archived(self) -> ArchivedMessage {
        ArchivedMessage {
            content: self.content,
            sender: self.sender,
            archived_at: std::time::SystemTime::now(),
        }
    }
    
    // Extract a field, consuming the struct
    fn into_content(self) -> String {
        self.content
    }
}

struct ArchivedMessage {
    content: String,
    sender: String,
    archived_at: std::time::SystemTime,
}

fn main() {
    let message = Message::new(
        "Hello, world!".to_string(),
        "Alice".to_string(),
    );
    
    // This consumes the message
    let result = message.send();
    // message is no longer available here
    
    let another_message = Message::new(
        "Goodbye!".to_string(),
        "Bob".to_string(),
    );
    
    // Transform into archived message
    let archived = another_message.into_archived();
}

Associated Functions (Static Methods)

Associated functions don't take self and are called on the type itself:

struct User {
    username: String,
    email: String,
    active: bool,
}

impl User {
    // Constructor
    fn new(username: String, email: String) -> User {
        User {
            username,
            email,
            active: true,
        }
    }
    
    // Alternative constructors
    fn inactive(username: String, email: String) -> User {
        User {
            username,
            email,
            active: false,
        }
    }
    
    fn from_email(email: String) -> User {
        let username = email.split('@').next().unwrap_or("user").to_string();
        User::new(username, email)
    }
    
    // Factory methods
    fn admin() -> User {
        User {
            username: "admin".to_string(),
            email: "[email protected]".to_string(),
            active: true,
        }
    }
    
    fn guest() -> User {
        User {
            username: "guest".to_string(),
            email: "[email protected]".to_string(),
            active: false,
        }
    }
    
    // Utility functions
    fn validate_email(email: &str) -> bool {
        email.contains('@') && email.contains('.')
    }
    
    fn max_username_length() -> usize {
        50
    }
}

fn main() {
    // Using different constructors
    let user1 = User::new("alice".to_string(), "[email protected]".to_string());
    let user2 = User::from_email("[email protected]".to_string());
    let admin = User::admin();
    let guest = User::guest();
    
    // Using utility functions
    if User::validate_email("[email protected]") {
        println!("Valid email");
    }
    
    println!("Max username length: {}", User::max_username_length());
}

Method Chaining

Design methods to enable fluent interfaces:

struct QueryBuilder {
    table: String,
    conditions: Vec<String>,
    order_by: Option<String>,
    limit: Option<u32>,
}

impl QueryBuilder {
    fn new(table: &str) -> Self {
        QueryBuilder {
            table: table.to_string(),
            conditions: Vec::new(),
            order_by: None,
            limit: None,
        }
    }
    
    fn where_clause(mut self, condition: &str) -> Self {
        self.conditions.push(condition.to_string());
        self
    }
    
    fn order_by(mut self, column: &str) -> Self {
        self.order_by = Some(column.to_string());
        self
    }
    
    fn limit(mut self, count: u32) -> Self {
        self.limit = Some(count);
        self
    }
    
    fn build(self) -> String {
        let mut query = format!("SELECT * FROM {}", self.table);
        
        if !self.conditions.is_empty() {
            query.push_str(" WHERE ");
            query.push_str(&self.conditions.join(" AND "));
        }
        
        if let Some(order) = self.order_by {
            query.push_str(&format!(" ORDER BY {}", order));
        }
        
        if let Some(limit) = self.limit {
            query.push_str(&format!(" LIMIT {}", limit));
        }
        
        query
    }
}

fn main() {
    let query = QueryBuilder::new("users")
        .where_clause("age > 18")
        .where_clause("active = true")
        .order_by("created_at")
        .limit(10)
        .build();
    
    println!("Query: {}", query);
}

Multiple Implementation Blocks

You can have multiple impl blocks for the same type:

struct Calculator {
    result: f64,
}

// Basic operations
impl Calculator {
    fn new() -> Calculator {
        Calculator { result: 0.0 }
    }
    
    fn add(&mut self, value: f64) -> &mut Self {
        self.result += value;
        self
    }
    
    fn subtract(&mut self, value: f64) -> &mut Self {
        self.result -= value;
        self
    }
    
    fn get_result(&self) -> f64 {
        self.result
    }
}

// Advanced operations
impl Calculator {
    fn multiply(&mut self, value: f64) -> &mut Self {
        self.result *= value;
        self
    }
    
    fn divide(&mut self, value: f64) -> &mut Self {
        if value != 0.0 {
            self.result /= value;
        }
        self
    }
    
    fn power(&mut self, exponent: f64) -> &mut Self {
        self.result = self.result.powf(exponent);
        self
    }
    
    fn clear(&mut self) -> &mut Self {
        self.result = 0.0;
        self
    }
}

fn main() {
    let mut calc = Calculator::new();
    
    let result = calc
        .add(10.0)
        .multiply(2.0)
        .subtract(5.0)
        .divide(3.0)
        .get_result();
    
    println!("Result: {}", result);
}

Generic Implementation Blocks

Implement methods for generic types:

struct Container<T> {
    items: Vec<T>,
}

impl<T> Container<T> {
    fn new() -> Self {
        Container { items: Vec::new() }
    }
    
    fn add(&mut self, item: T) {
        self.items.push(item);
    }
    
    fn len(&self) -> usize {
        self.items.len()
    }
    
    fn is_empty(&self) -> bool {
        self.items.is_empty()
    }
    
    fn get(&self, index: usize) -> Option<&T> {
        self.items.get(index)
    }
}

// Implementation for specific types
impl Container<String> {
    fn add_strings(&mut self, strings: Vec<String>) {
        self.items.extend(strings);
    }
    
    fn concatenate(&self) -> String {
        self.items.join(" ")
    }
}

impl<T: Clone> Container<T> {
    fn duplicate_last(&mut self) {
        if let Some(last) = self.items.last() {
            self.items.push(last.clone());
        }
    }
}

impl<T: std::fmt::Display> Container<T> {
    fn print_all(&self) {
        for item in &self.items {
            println!("{}", item);
        }
    }
}

fn main() {
    let mut string_container = Container::new();
    string_container.add("Hello".to_string());
    string_container.add("World".to_string());
    string_container.add_strings(vec!["Rust".to_string(), "Programming".to_string()]);
    
    println!("Concatenated: {}", string_container.concatenate());
    string_container.print_all();
    
    let mut number_container = Container::new();
    number_container.add(1);
    number_container.add(2);
    number_container.duplicate_last();
    number_container.print_all();
}

Implementation for Enums

Enums can also have methods and associated functions:

#[derive(Debug)]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Triangle { base: f64, height: f64 },
}

impl Shape {
    // Associated functions for construction
    fn circle(radius: f64) -> Shape {
        Shape::Circle { radius }
    }
    
    fn rectangle(width: f64, height: f64) -> Shape {
        Shape::Rectangle { width, height }
    }
    
    fn square(side: f64) -> Shape {
        Shape::Rectangle { width: side, height: side }
    }
    
    fn triangle(base: f64, height: f64) -> Shape {
        Shape::Triangle { base, height }
    }
    
    // Methods
    fn area(&self) -> f64 {
        match self {
            Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
            Shape::Rectangle { width, height } => width * height,
            Shape::Triangle { base, height } => 0.5 * base * height,
        }
    }
    
    fn perimeter(&self) -> f64 {
        match self {
            Shape::Circle { radius } => 2.0 * std::f64::consts::PI * radius,
            Shape::Rectangle { width, height } => 2.0 * (width + height),
            Shape::Triangle { base, height } => {
                // Simplified calculation for isosceles triangle
                let side = (height.powi(2) + (base / 2.0).powi(2)).sqrt();
                base + 2.0 * side
            }
        }
    }
    
    fn scale(&mut self, factor: f64) {
        match self {
            Shape::Circle { radius } => *radius *= factor,
            Shape::Rectangle { width, height } => {
                *width *= factor;
                *height *= factor;
            }
            Shape::Triangle { base, height } => {
                *base *= factor;
                *height *= factor;
            }
        }
    }
    
    fn shape_type(&self) -> &'static str {
        match self {
            Shape::Circle { .. } => "Circle",
            Shape::Rectangle { .. } => "Rectangle",
            Shape::Triangle { .. } => "Triangle",
        }
    }
}

fn main() {
    let mut shapes = vec![
        Shape::circle(5.0),
        Shape::rectangle(4.0, 6.0),
        Shape::square(3.0),
        Shape::triangle(4.0, 3.0),
    ];
    
    for shape in &mut shapes {
        println!("{}: Area = {:.2}, Perimeter = {:.2}", 
                 shape.shape_type(), shape.area(), shape.perimeter());
        
        shape.scale(1.5);
        println!("After scaling: Area = {:.2}", shape.area());
    }
}

Advanced Patterns

Builder Pattern

#[derive(Debug)]
struct HttpRequest {
    method: String,
    url: String,
    headers: Vec<(String, String)>,
    body: Option<String>,
}

struct HttpRequestBuilder {
    method: Option<String>,
    url: Option<String>,
    headers: Vec<(String, String)>,
    body: Option<String>,
}

impl HttpRequestBuilder {
    fn new() -> Self {
        HttpRequestBuilder {
            method: None,
            url: None,
            headers: Vec::new(),
            body: None,
        }
    }
    
    fn method(mut self, method: &str) -> Self {
        self.method = Some(method.to_string());
        self
    }
    
    fn url(mut self, url: &str) -> Self {
        self.url = Some(url.to_string());
        self
    }
    
    fn header(mut self, key: &str, value: &str) -> Self {
        self.headers.push((key.to_string(), value.to_string()));
        self
    }
    
    fn body(mut self, body: &str) -> Self {
        self.body = Some(body.to_string());
        self
    }
    
    fn build(self) -> Result<HttpRequest, String> {
        let method = self.method.ok_or("Method is required")?;
        let url = self.url.ok_or("URL is required")?;
        
        Ok(HttpRequest {
            method,
            url,
            headers: self.headers,
            body: self.body,
        })
    }
}

impl HttpRequest {
    fn builder() -> HttpRequestBuilder {
        HttpRequestBuilder::new()
    }
}

fn main() {
    let request = HttpRequest::builder()
        .method("POST")
        .url("https://api.example.com/users")
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer token123")
        .body(r#"{"name": "John", "email": "[email protected]"}"#)
        .build()
        .unwrap();
    
    println!("Request: {:#?}", request);
}

Delegation Pattern

struct Database {
    connection_string: String,
}

impl Database {
    fn new(connection_string: String) -> Self {
        Database { connection_string }
    }
    
    fn query(&self, sql: &str) -> Vec<String> {
        println!("Executing query: {}", sql);
        vec!["result1".to_string(), "result2".to_string()]
    }
    
    fn execute(&self, sql: &str) -> bool {
        println!("Executing command: {}", sql);
        true
    }
}

struct UserRepository {
    db: Database,
}

impl UserRepository {
    fn new(db: Database) -> Self {
        UserRepository { db }
    }
    
    fn find_by_id(&self, id: u32) -> Option<String> {
        let sql = format!("SELECT * FROM users WHERE id = {}", id);
        let results = self.db.query(&sql);
        results.first().cloned()
    }
    
    fn create_user(&self, name: &str, email: &str) -> bool {
        let sql = format!("INSERT INTO users (name, email) VALUES ('{}', '{}')", name, email);
        self.db.execute(&sql)
    }
    
    fn delete_user(&self, id: u32) -> bool {
        let sql = format!("DELETE FROM users WHERE id = {}", id);
        self.db.execute(&sql)
    }
}

fn main() {
    let db = Database::new("postgresql://localhost/mydb".to_string());
    let user_repo = UserRepository::new(db);
    
    user_repo.create_user("Alice", "[email protected]");
    if let Some(user) = user_repo.find_by_id(1) {
        println!("Found user: {}", user);
    }
}

Best Practices

1. Use Descriptive Method Names

// Good: Clear intent
impl User {
    fn is_active(&self) -> bool { self.active }
    fn deactivate(&mut self) { self.active = false; }
    fn get_email(&self) -> &str { &self.email }
    fn update_email(&mut self, email: String) { self.email = email; }
}

// Avoid: Unclear purpose
impl User {
    fn check(&self) -> bool { self.active }
    fn toggle(&mut self) { self.active = false; }
    fn email(&self) -> &str { &self.email }
    fn set(&mut self, email: String) { self.email = email; }
}
impl Rectangle {
    // Constructors
    fn new(width: u32, height: u32) -> Self { /* ... */ }
    fn square(size: u32) -> Self { /* ... */ }
    
    // Measurements
    fn area(&self) -> u32 { /* ... */ }
    fn perimeter(&self) -> u32 { /* ... */ }
    
    // Transformations
    fn scale(&mut self, factor: f64) { /* ... */ }
    fn rotate(&mut self) { /* ... */ }
    
    // Predicates
    fn is_square(&self) -> bool { /* ... */ }
    fn can_contain(&self, other: &Rectangle) -> bool { /* ... */ }
}

3. Choose Appropriate Self Types

impl Calculator {
    // Use &self for queries that don't modify state
    fn get_result(&self) -> f64 { self.result }
    
    // Use &mut self for operations that modify state
    fn add(&mut self, value: f64) { self.result += value; }
    
    // Use self for transformations that consume the instance
    fn into_result(self) -> f64 { self.result }
}

4. Provide Convenient Constructors

impl User {
    // Basic constructor
    fn new(name: String, email: String) -> Self { /* ... */ }
    
    // Convenience constructors
    fn from_email(email: String) -> Self { /* ... */ }
    fn guest() -> Self { /* ... */ }
    fn admin() -> Self { /* ... */ }
    
    // Builder pattern for complex construction
    fn builder() -> UserBuilder { /* ... */ }
}

Methods and associated functions are essential for creating clean, intuitive APIs in Rust. They allow you to encapsulate behavior with data and provide multiple ways to construct and manipulate your types. Use them to create expressive and ergonomic interfaces that make your code easy to use and understand.