【Rust中的泛型与特征(一)】


什么是泛型(通识)

泛型的本质是类型的参数化,允许在定义类、接口、方法时使用类型形参,当使用时指定具体类型,所有使用该泛型参数的地方都被统一化,保证类型一致。

C++中的泛型

1) 函数模板

template <typename T>
void printContainer(const T& container) {
    
    
    for (const auto& element : container) {
    
    
        std::cout << element << " ";
    }
    std::cout << std::endl;
}

2) 类模板

template<typename T>
T MyClass<T>::getT() {
    
    
    return t;
}

template<typename T>
void MyClass<T>::setT(T t) {
    
    
    this->t = t;
}

C# 中的泛型


using System;
using System.Collections.Generic;
 
public class MyLinkedList<T> {
    
    
    private Node<T> head;
 
    private class Node<T> {
    
    
        public T Value {
    
     get; set; }
        public Node<T> Next {
    
     get; set; }
 
        public Node(T value) {
    
    
            Value = value;
            Next = null;
        }
    }
 
    public void Add(T value) {
    
    
        if (head == null) {
    
    
            head = new Node<T>(value);
        } else {
    
    
            Node<T> current = head;
            while (current.Next != null) {
    
    
                current = current.Next;
            }
            current.Next = new Node<T>(value);
        }
    }
 
    public IEnumerator<T> GetEnumerator() {
    
    
        Node<T> current = head;
        while (current != null) {
    
    
            yield return current.Value;
            current = current.Next;
        }
    }
}

不论是C++中的函数模板、类模板,还是C#中的泛型编程,简单来说 都是以类型参数为占位符,在实际使用时,或是通过匿名调用,或是通过显示调用,编译器会根据具体的使用情况,将泛型代码转换为实际情况中使用的类型,并生成新的代码,若 一个泛型容器在使用时分别用int32,double,string,初始化,那么编译器会生成以上三种的不同容器以供后续的使用。

泛型编程的特点

  1. 泛型编程在编译期间确定类型,同时定义出以不同类型生成的代码。
  2. 泛型编程保证了类型安全,编译期即可获知使用的是否正确
  3. 泛型编程以空间换时间,在运行期间不同类型执行自己的不同代码,不会运行期间再次确定。
  4. 泛型编程可以简单理解为静态多态。

所谓静态多态,即在编译期间为每一种使用的类型做单态处理。

Rust中的泛型

Rust中的泛型与C++,C#中的泛型概念一致,即静态多态和复用。

Rust中的泛型示例(泛型函数)

fn use_generic<T>(data: T) -> T {
    
    
    data
}

Rust中的const泛型

const泛型,可以简单理解为主要对于泛型容器大小的泛型设计,在rust中,不同大小的vector,也属于不同的类型,C++中的非类型泛型,多数也是为了容器大小的设置,如vector,list等。不得不说,rust真的很注重安全

伪代码:

fn do_container<T:【一些特征约束】, const N: usize>(arr: [T; N]) {
    
    
    dosomething
}

Rust中的特征(trait)

A trait is a collection of methods defined for an unknown type: Self. They can access other methods declared in the same trait,Traits can be implemented for any data type.

Rust与其他语言的共通之处

Rust中的trait类似于C#中的interface,同是为了不同类型的对象可以调用相同的方法,或使用traits内部本身实现的方法,或使用traits在指定的struct or enum 中实现的traits。不同于C++中的基类,Rust中并没有继承的概念,Ps: 但是从编程语言的设计来看,派生与traits之间是存在异曲同工之妙的,设计者与使用者的角度是有所不同的。

traits 示例(官方示例)

struct Sheep {
    
     naked: bool, name: &'static str }

trait Animal {
    
    
    // Associated function signature; `Self` refers to the implementor type.
    fn new(name: &'static str) -> Self;

    // Method signatures; these will return a string.
    fn name(&self) -> &'static str;
    fn noise(&self) -> &'static str;

    // Traits can provide default method definitions.
    fn talk(&self) {
    
    
        println!("{} says {}", self.name(), self.noise());
    }
}

// Implement the `Animal` trait for `Sheep`.
impl Animal for Sheep {
    
    
    // `Self` is the implementor type: `Sheep`.
    fn new(name: &'static str) -> Sheep {
    
    
        Sheep {
    
     name: name, naked: false }
    }

    fn name(&self) -> &'static str {
    
    
        self.name
    }

    fn noise(&self) -> &'static str {
    
    
        if self.is_naked() {
    
    
            "baaaaah?"
        } else {
    
    
            "baaaaah!"
        }
    }
    
    // Default trait methods can be overridden.
    fn talk(&self) {
    
    
        // For example, we can add some quiet contemplation.
        println!("{} pauses briefly... {}", self.name, self.noise());
    }
}

Rust Traits 的特别之处

impl Trait can be used in two locations:
1. as an argument type
2. as a return type

一、 作为函数参数

fn parse_csv_document(src: impl std::io::BufRead) -> std::io::Result<Vec<Vec<String>>> {
    
    
    src.lines()
        .map(|line| {
    
    
            // For each line in the source
            line.map(|line| {
    
    
                // If the line was read successfully, process it, if not, return the error
                line.split(',') // Split the line separated by commas
                    .map(|entry| String::from(entry.trim())) // Remove leading and trailing whitespace
                    .collect() // Collect all strings in a row into a Vec<String>
            })
        })
        .collect() // Collect all lines into a Vec<Vec<String>>
}

二、作为函数返回值

// Returns a function that adds `y` to its input
fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 {
    
    
    let closure = move |x: i32| {
    
     x + y };
    closure
}

fn main() {
    
    
    let plus_one = make_adder_function(1);
    assert_eq!(plus_one(2), 3);
}

src: impl std::io::BufRead —src在调用传入时,其必须是实现了std::io::Bufread的对象类型,这像极了C++中父类指针接收子类
对象的思想(不过这里时静态的,与c++中的派生有很大区别,不过思想通用),不过trait更加的轻量化,简单化,零损耗。
比如闭包无法获知具体类型,不使用trait的情况只能使用堆分配,而使用trait后静态方式也可以做到。

三、特征约束

特征约束即通过对泛型参数类型进行trait实现上的约束,来实现特定的函数调用,或解决在泛型编程中需要明确告知编译器传入参数需要什么样的特征(如:计算类函数传入参数需要有可以计算的能力等)
简单来说,特征约束限制了泛型编程中只有拥有某种或某几种能力的参数才能够使用此方法或函数

特征约束示例


trait Add<T> {
    
    
    fn add(&self, other: &T) -> T;
}

impl Add<f64> for f64 {
    
    
    fn add(&self, other: &f64) -> f64 {
    
    
        *self + *other
    }
}

fn add_numbers<T: Add<T>>(a: T, b: T) -> T {
    
    
    a.add(&b)
}
fn do_somgthing<T: Display + Copy, U: Clone + Debug>(t: &T, u: &U) -> i64 {
    
    }
where语法:
fn add_numbers<T>(a: T, b: T) -> T
where
    T: Add<T>,
{
    
    
    a.add(&b)
}

一言以蔽之,约束就是限制,限制了泛型编程中可以作为入参的类型的范围。

如有勘误,烦请指出。

参考文章:
[1] https://course.rs/basic/trait/intro.html
[2] https://doc.rust-lang.org/rust-by-example/trait.html

猜你喜欢

转载自blog.csdn.net/m0_37719524/article/details/143224642