Rust Generics - rFronteddu/general_wiki GitHub Wiki
Generic Data Types We use generics to create definitions for items like function signatures or structs, which we can then use with many different concrete data types.
In Function Definitions When defining a function that uses generics, we place the generics in the signature of the function where we would usually specify the data types of the parameters and return value.
To parameterize the types in a new single function, we need to name the type parameter, just as we do for the value parameters to a function. You can use any identifier as a type parameter name. But we’ll use T because, by convention, type parameter names in Rust are short, often just one letter, and Rust’s type-naming convention is UpperCamelCase. Short for type, T is the default choice of most Rust programmers.
When we use a parameter in the body of the function, we have to declare the parameter name in the signature so the compiler knows what that name means. Similarly, when we use a type parameter name in a function signature, we have to declare the type parameter name before we use it. To define the generic largest function, we place type name declarations inside angle brackets, <>, between the name of the function and the parameter list, like this:
fn largest(list: &[T]) -> &T { We read this definition as: the function largest is generic over some type T. This function has one parameter named list, which is a slice of values of type T. The largest function will return a reference to a value of the same type T. This piece of code won't compile as is, Rust requires you to state the expected capabilities of generic types up front. If T needs to be comparable, then largest must say so. Therefore this compiler error says largest will not compile until T is restricted.
In Struct Definitions We can also define structs to use a generic type parameter in one or more fields using the <> syntax.
struct Point { x: T, y: T, }
fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 }; } The syntax for using generics in struct definitions is similar to that used in function definitions. First we declare the name of the type parameter inside angle brackets just after the name of the struct. Then we use the generic type in the struct definition where we would otherwise specify concrete data types.
Note that because we’ve used only one generic type to define Point, this definition says that the Point struct is generic over some type T, and the fields x and y are both that same type, whatever that type may be.
To define a Point struct where x and y are both generics but could have different types, we can use multiple generic type parameters.
In Enum Definitions As we did with structs, we can define enums to hold generic data types in their variants.
enum Option { Some(T), None, } This definition should now make more sense to you. As you can see, the Option enum is generic over type T and has two variants: Some, which holds one value of type T, and a None variant that doesn’t hold any value. By using the Option enum, we can express the abstract concept of an optional value, and because Option is generic, we can use this abstraction no matter what the type of the optional value is.
Enums can use multiple generic types as well.
In Method Definitions We can implement methods on structs and enums and use generic types in their definitions too.
struct Point { x: T, y: T, }
impl Point { fn x(&self) -> &T { &self.x } }
fn main() { let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
} Here, we’ve defined a method named x on Point that returns a reference to the data in the field x. Note that we have to declare T just after impl so we can use T to specify that we’re implementing methods on the type Point. By declaring T as a generic type after impl, Rust can identify that the type in the angle brackets in Point is a generic type rather than a concrete type.
We can also specify constraints on generic types when defining methods on the type. We could, for example, implement methods only on Point instances rather than on Point instances with any generic type.
impl Point { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } } This code means the type Point will have a distance_from_origin method; other instances of Point where T is not of type f32 will not have this method defined. You cannot simultaneously implement specific and generic methods of the same name this way.
ore generally, Rust does not have inheritance-like mechanisms for specializing methods as you might find in an object-oriented language, with one exception (default trait methods).
Using generic types won’t make your program run any slower than it would with concrete types. Rust accomplishes this by performing monomorphization of the code using generics at compile time. Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled. Because Rust compiles generic code into code that specifies the type in each instance, we pay no runtime cost for using generics. When the code runs, it performs just as it would if we had duplicated each definition by hand. The process of monomorphization makes Rust’s generics extremely efficient at runtime.