Generics in Rust

Our example demonstrates generics in Rust, which are a powerful feature for writing flexible and reusable code.

use std::fmt::Debug;

// SlicesIndex is a generic function that takes a slice of any type T
// that implements PartialEq and an element of that type. It returns
// the index of the first occurrence of v in s, or -1 if not present.
fn slices_index<T: PartialEq>(s: &[T], v: &T) -> i32 {
    for (i, item) in s.iter().enumerate() {
        if item == v {
            return i as i32;
        }
    }
    -1
}

// List is a generic type representing a singly-linked list
// with values of any type T.
#[derive(Debug)]
struct List<T> {
    head: Option<Box<Node<T>>>,
    tail: Option<*mut Node<T>>,
}

#[derive(Debug)]
struct Node<T> {
    val: T,
    next: Option<Box<Node<T>>>,
}

impl<T> List<T> {
    fn new() -> Self {
        List {
            head: None,
            tail: None,
        }
    }

    fn push(&mut self, v: T) {
        let new_node = Box::new(Node {
            val: v,
            next: None,
        });
        let raw_node = Box::into_raw(new_node);
        
        match self.tail {
            None => {
                self.head = Some(unsafe { Box::from_raw(raw_node) });
                self.tail = Some(raw_node);
            }
            Some(tail) => unsafe {
                (*tail).next = Some(Box::from_raw(raw_node));
                self.tail = Some(raw_node);
            },
        }
    }

    fn all_elements(&self) -> Vec<&T> {
        let mut elements = Vec::new();
        let mut current = &self.head;
        while let Some(node) = current {
            elements.push(&node.val);
            current = &node.next;
        }
        elements
    }
}

fn main() {
    let s = vec!["foo", "bar", "zoo"];

    // When calling generic functions, type inference often works
    println!("index of zoo: {}", slices_index(&s, &"zoo"));

    // We could also specify the types explicitly
    let _ = slices_index::<&str>(&s, &"zoo");

    let mut lst = List::<i32>::new();
    lst.push(10);
    lst.push(13);
    lst.push(23);
    println!("list: {:?}", lst.all_elements());
}

To run the program, save it as generics.rs and use rustc to compile it:

$ rustc generics.rs
$ ./generics
index of zoo: 2
list: [10, 13, 23]

This Rust code demonstrates several key concepts:

  1. Generic functions: slices_index is a generic function that works with any type T that implements PartialEq.

  2. Generic structs: List<T> and Node<T> are generic structs that can hold any type T.

  3. Methods on generic types: We define methods like push and all_elements on the generic List<T> type.

  4. Type inference: Rust can often infer the types for generic functions, as shown in the slices_index(&s, &"zoo") call.

  5. Explicit type annotation: We can also specify types explicitly when needed.

  6. Ownership and borrowing: The List implementation uses Box for heap allocation and raw pointers for the tail to demonstrate more advanced Rust concepts.

Note that Rust’s approach to generics is somewhat different from Go’s. Rust uses compile-time monomorphization, which means separate code is generated for each concrete type used with a generic function or struct. This approach provides excellent runtime performance but can lead to larger binary sizes.