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:
Generic functions:
slices_index
is a generic function that works with any typeT
that implementsPartialEq
.Generic structs:
List<T>
andNode<T>
are generic structs that can hold any typeT
.Methods on generic types: We define methods like
push
andall_elements
on the genericList<T>
type.Type inference: Rust can often infer the types for generic functions, as shown in the
slices_index(&s, &"zoo")
call.Explicit type annotation: We can also specify types explicitly when needed.
Ownership and borrowing: The
List
implementation usesBox
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.