Overview
In Rust, generics are a powerful tool that allows us to write reusable and flexible code. Generics allow us to create data structures and functions that work for multiple types without having to repeatedly write the same logic for each type. In Rust, generics are implemented by specifying type parameters that are replaced by specific types at compile time.
Generic functions
Generic functions allow us to define functions that can operate on multiple types without having to write functions individually for each type. In function signatures, we can use the type parameter to specify which types can be accepted.
use std::fmt::Display; // Generic function, T is a type parameterfn print_value<T: Display>(value: T) { println!("value is: {}", value); } fn main() { // Print with integer type print_value(666); // Print using string type print_value("CSDN"); }
In the above example code, T is a type parameter that implements the Display feature. When the print_value function is called, the compiler will infer the specific type of T based on the actual parameter type passed in.
Generic structure
In addition to generic functions, Rust also allows us to define generic structures. In a generic structure, we can use type parameters to declare fields.
struct Block<T> { value: T } fn main() { // Block containing integer type values let block1 = Block { value: 66 }; println!("{}", ); // Block containing string type value let block2 = Block { value: "CSDN".to_string() }; println!("{}", ); }
In the above example code, we first define a generic structure called Block, which has a field value of type T. We can then create a Block instance containing different types of values. Finally, we print out the value field of the Block instance.
In Rust, we can also define generic functions or structures with multiple type parameters.
struct CustomPair<T, U> { first: T, second: U, } impl<T, U> CustomPair<T, U> { fn new(first: T, second: U) -> Self { CustomPair { first, second } } } fn main() { // Create a CustomPair, the first one is the integer type and the second one is the string type let pair1 = CustomPair::new(66, "CSDN".to_string()); // Output: 66 CSDN println!("{} {}", , ); // Create a CustomPair, the first is the string type, and the second is the floating point number type let pair2: CustomPair<String, f64> = CustomPair::new("sin".to_string(), 3.14); // Output: sin 3.14 println!("{} {}", , ); }
In the above example code, T and U are independent type parameters. When instantiating such a generic, the compiler will infer the specific types of these type parameters based on the actual type passed in. When creating an instance of CustomPair type pair1, the first one is the integer type and the second one is the string type. When creating an instance of CustomPair type pair2, the first one is the string type and the second one is the floating point number type.
Generic Methods
A generic method refers to a method defined on a generic structure or generic enum, which can use type parameters in a structure or enum.
struct Container<T> { value: T, } impl<T> Container<T> { // Accept any type T as input and return a reference to that type fn get_value(&self) -> &T { & } // Accept another Container as input fn replace_with<U>(&mut self, other: Container<U>) where T: Copy, U: Into<T>, { let new_value = (); = new_value; } } fn main() { let mut container1 = Container { value: 66 }; // Output: 66 println!("{}", container1.get_value()); let container2 = Container { value: "CSDN".to_string() }; container1.replace_with(Container { value: 100 }); // Output: 100 println!("{}", container1.get_value()); }
In the example code above, the Container structure has a type parameter T. The get_value method is a generic method, so it works for all types of Container instances. The replace_with method is also a generic method, but it introduces more type constraints through the where clause.
1. T: Copy constraints indicate that the type T of the method operation must implement Copy features.
2. U: The Into<T> constraint means that the type parameter U of the second Container can be converted to T.
With such a generic approach, we can write more general and reusable code that exhibits consistent behavior across different data types.
In the content of this article, we have actually come into contact with many concepts of features, such as Display features, Copy features, etc. In the next article, we will introduce the characteristics of Rust, namely Trait.
This is the end of this article about learning notes on generics in Rust. For more related Rust generic content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!