The generic

Functions can execute the same code using unknown concrete values in arguments. Functions can also use generic arguments instead of concrete types like i32 or String

Generic function

Generics should be declared after the function name, and then used at the function parameter type and return value type, for example, to find the maximum value in an array:

fn largest<T>(list: &[T]) -> T {
  let mut largest = list[0];
  for &item in list.iter() {
    // There is an error because T can be of any type,
    // Not all types can be compared
    if item > largest {
      largest = item
    }
  }
  largest
}
println!("{}", largest(&['a'.'b'.'c'.'d']));
println!("{}", largest(&[1.2.3.3]));
Copy the code

Generic enumeration

Enumeration values can also contain generics, which are declared after the enumeration name, such as the following built-in Option and Result enumerations:

enum Option<T> {
  Some(T),
  None
}
enum Result<T, E> {
  Ok(T),
  Err(E)
}
Copy the code

Generic structure

The structure also supports generics, and the generic parameters follow the structure name:

#[derive(Debug)]
struct Point<T, U> {
  x: T,
  y: U,
};

// Pass in type parameters
let i32_p = Point::<i32.i32> {
  x: 100,
  y: 100};// Point
      ,>
let f32_p = Point {
  x: 100.1,
  y: 100.1
};
Copy the code

Methods in generic structures

When used in a method, a single instance of Point<i32, i32> can be implemented instead of all generic instances of Point<T, U>, indicating that structures with type Point<i32, i32> are allowed to call the x2 method:

impl Point<i32.i32> {
  fn x(&self) - > &i32{&self.x
  }
}
println!("{}", i32_p.x()); / / 100
println!("{}", f32_p.x()); // Point
      
        structure does not have x method
      ,>
Copy the code

If you want to use this method for all types in the structure, you can implement the generic method:

// Implement generic methods:
impl<T, U> Point<T, U> {
  fn x2(&self) -> &T {
    &self.x
  }
}

// Note that the declaration 
      
        must be followed by the impl keyword so that the type Point
       
         can be specified when the method is implemented.
       ,>
      ,>
// By declaring T and U generic after the IMPL, Rust can recognize that the types in Point Angle brackets are generic and not concrete.
let str_p = Point {
  x: "100px",
  y: "100px"};println!("{}", str_p.x2()); // 100px
Copy the code

The generic parameters in the structure definition are not necessarily the same as the generic type parameters used in the method signature:

// The proximity principle for declaring generics: generics in structures are declared in the IMPL, while generics in methods are declared after methods
impl<T, U> Point<T, U> {
  // The mixup method uses generic parameters different from those defined by the structure
  fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
    return Point {
      x: self.x,
      y: other.y
    }
  }
}

let p1 = Point {
  x: 1,
  y: 2};let p2 = Point {
  x: "hello",
  y: 'c'};println!("{:? }", p1.mixup(p2));
// Point { x: 1, y: 'c' }
Copy the code

When to use generics

When you realize that your code has multiple struct or enumeration definitions, and only the value types are different, you can avoid duplicate code by using generics.

Performance issues with generic code

Rust performs monomorphization of generic code at compile time. Singularization is the process of converting generic code to specific code at compile time, which results in typed code by filling in all used concrete types with generic parameters:

let i = Option: :Some(1);
let f = Option: :Some(1.2);

// Compile to:
enum Option_i32 {
  Some(i32),
  None
}
enum Option_f32 {
  Some(f32),
  None
}
let i = Option_i32::Some(1);
let f = Option_f32::Some(1.2);
Copy the code

Because Rust compiles generic code into a specific type of code in each instance, there is no run-time cost to using generics. When running generic code, it runs just as if we had manually repeated each definition. Singomorphism makes Rust’s generic code extremely efficient at run time.