Structure and usage
Of course, we have already used some generic structures, namely Vec
and Box
.
Type inference made it possible that we only needed to declare the type of the variable. The type of the respective new
function, or rather the entire container, was inferred.
Variants of generic elements (types as well as functions) are accessed like a subelement using the so-called turbofish operator ::<>
.
Note that Vec
and Box
actually take two generic parameters, the second is the memory allocator. Rust allows you to set default types for any number of the rightmost parameters, so you only need to assign them when you want a type beside the default.
To my knowledge, this is the only place where the language itself does allow default values at all. Default values of types are handled by the Default
trait.
// Generic type, normal function
let my_box = Box::<usize>::new(42);
// Generic function
let fun = std::cmp::max::<usize>(42, 69);
Generics are very well integrated into the language. To make a type or function generic, just define at least one generic parameter in angel brackets behind the name and that's all, you're already good to go. These parameters are then usable inside the type or function.
struct SomeWrapper<T> {
inner_data: T,
}
fn swap_order<T, U>(input: (T, U)) -> (U, T) {
let (first, second) = input;
(second, first)
}
For implementing types, you should be aware that Rust does not have specialisation of generic types. That means you can't overwrite anything provided by a generic implementation through a specialised one. This falls in line with Rust not providing function overloading in general. I guess this is because firstly, you can't have functions with the same name doing different things and secondly, that could be considered some form of compile time reflection. Rust generally tries to avoid reflection and I agree with this stance. During runtime, reflection relies on information a compiled program shouldn't have since that's a pretty considerable overhead. In source code, it makes code opaque and in the case of function overloading, it's easy to mix up different versions while having them doing different things.
But while you can't overwrite generic functions with specialised ones, you can extend your generic type with specialised impl
blocks.
#![allow(unused)] fn main() { // Consider the previous code block struct SomeWrapper<T> { inner_data: T, } // Generic impl impl<T> SomeWrapper<T> { pub fn reference_to_inner(&self) -> &T { &self.inner_data } } // Specialised impl // This type will have both methods impl SomeWrapper<u64> { pub fn as_bytes(&self) -> [u8; 8] { self.inner_data.to_ne_bytes() } // Compiler error, redefining this function is not allowed! pub fn reference_to_inner(&self) -> &u64 { &self.inner_data } } }