rust入坑之旅-泛型

Scroll Down

泛型

泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的类型参数是使用尖括号和大驼峰命名的名称:<A, B, ...> 来指定的,泛型类型参数一般用 来表示。在 Rust 中,“泛型的” 除了表示 类型,还表示可以接受一个或多个泛型类型参数 的任何内容。任何用泛型类型参数 表示的类型都是泛型,其他的类型都是具体(非泛型)类型。在Java中泛型 可以代表类,不能代表个别对象,也可以表示泛型函数。由于Java泛型的类型参数之实际类型在编译时会被消除,所以无法在运行时得知其类型参数的类型,而且无法直接使用基本值类型作为泛型类型参数。Java编译程序在编译泛型时会自动加入类型转换的编码。由于运行时会消除泛型的对象实例类型信息等缺陷经常被人诟病,Java通过在生成字节码时添加类型推导辅助信息,从而可以通过反射接口获得部分泛型信息;((ParameterizedType)obj.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

泛型类/结构体

  • Rust泛型结构体
// 具体类型 User。
struct User{
	val:i32
};

// `Single` 是个同样也是具体类型,User 取上面的定义。
struct Single(User);
//            ^ 这里是 `Single` 对类型 User 的第一次使用。

// 此处 `<T>` 在第一次使用 `T` 前出现,所以 `SingleGen` 是一个泛型类型。
// 因为 `T` 是泛型的,所以它可以是任何类型,包括在上面定义的具体类型 User。
struct SingleGen<T>(T);
fn main() {
    // `Single` 是具体类型,并且显式地使用类型 `A`。
    let _s = Single(A);
    
    // 创建一个 `SingleGen<char>` 类型的变量 `_char`,并令其值为 `SingleGen('a')`
    // 这里的 `SingleGen` 的类型参数是显式指定的。
    let _char: SingleGen<char> = SingleGen('a');

    // `SingleGen` 的类型参数也可以隐式地指定。
    let _t    = SingleGen(A); // 使用在上面定义的 `A`。
    let _i32  = SingleGen(6); // 使用 `i32` 类型。
    let _char = SingleGen('a'); // 使用 `char`。
}
  • Java泛型类
class User{}

class Single{
	User	user;
}
// 此处 `<T>` 在第一次使用 `T` 前出现,所以 `SingleGen` 是一个泛型类型。
// 因为 `T` 是泛型的,所以它可以是任何类型,包括在上面定义的具体类型 User。
class SingleClass<T> {}
public static void main(String[] args) {
	User user=new User();
	Single single=new Single();
	single.user=user;
	SingleClass<User> singleClass=new SingleClass<User>();
	List list=new Arraylist<Integer>();
}

泛型函数/实现

  • Rust泛型函数/实现
// 普通函数
fn reg_fn(_s: User) {}
// SingleGen<>` 显式地接受了类型参数 `User,且在 `gen_spec_t` 中,`User` 没有被用作泛型类型参数,所以函数不是泛型的。
fn gen_spec_t(_s: SingleGen<User>) {}.
// <T> 代表该函数是关于 `T` 的泛型函数。
fn generic<T>(_s: SGen<T>) {}
fn main(){
	 //隐式调用泛型方法 T=>User
	  generic(SingleGen(User));  
	 // 泛型方法显示调用
	  generic::<char>(SingleGen('a'));
}
struct GenUser<T>{
    gen_User: T
}
// User 的 `impl` 在这里可以定义User结构体的关联方法
impl User {
    fn value(&self) -> &f64 { &self.val }
}

// SingleGen的 `impl`,指定 `T` 是泛型类型
impl <T> SingleGen<T> {
    fn value(&self) -> &T { &self.gen_val }
}
fn main() {
    let x = User { val: 3 };
    let y = GenVal { gen_val: 3i32 };
    
    println!("{}, {}", x.value(), y.value());
}

  • Java泛型函数
public class Executer<T>{
	// <T> 代表泛型方法
	public <T> Map<String,T> execute(T t){
		...
		return new HashMap<String,T>(){{put("success",t)}};
	}
}

泛型trait

struct User;
struct Role;
// 授权 trait
trait Authorization<T> {
    // 定义一个调用者的方法,接受一个额外的参数 `T`,但不对它做任何事。
    fn authorization(self, _: T);
}
impl<T, U> Authorization<T> for U {
    // 此方法获得两个传入参数的所有权,执行授权。
    fn authorization(self, _: T) {
       println!("{}","执行授权");
    }
}
fn main() {
    let user = User;
    let role  = Role;
    user.authorization(role);
}

泛型约束

Rust

在Rust使用泛型时,类型参数常常必须使用 trait 作为约束来规定结构体应实现哪些功能。如上面那样约束用户的授权

// 定义一个函数 `printer`,接受一个类型为泛型 `T` 的参数,
// 其中 `T` 必须实现 `Display` trait。
struct S<T: Display>(T);
// 报错!`Vec<T>` 未实现 `Display`。此次泛型具体化失败。
let s = S(vec![1]);

还有就是用trait来限制一个实例的可以调用的实现的trait的方法trait约束 矩形实现了求面积的trait,所以在area方法执行是参数T只能是实现了求面积的trait的类型结构体,不然在编译器就会报错

// 这个 trait 用来实现打印标记:`{:?}`。
use std::fmt::Debug;

trait HasArea {
    fn area(&self) -> f64;
}

impl HasArea for Rectangle {
    fn area(&self) -> f64 { self.length * self.height }
}

#[derive(Debug)]
struct Rectangle { length: f64, height: f64 }
#[allow(dead_code)]
struct Triangle  { length: f64, height: f64 }

// 泛型 `T` 必须实现 `Debug` 。只要满足这点,无论什么类型
// 都可以让下面函数正常工作。
fn print_debug<T: Debug>(t: &T) {
    println!("{:?}", t);
}

// `T` 必须实现 `HasArea`。任意符合该约束的泛型的实例
// 都可访问 `HasArea` 的 `area` 函数
fn area<T: HasArea>(t: &T) -> f64 { t.area() }

fn main() {
    let rectangle = Rectangle { length: 3.0, height: 4.0 };
    let _triangle = Triangle  { length: 3.0, height: 4.0 };

    print_debug(&rectangle);
    println!("Area: {}", area(&rectangle));

    //print_debug(&_triangle);
    //println!("Area: {}", area(&_triangle));
    // | 报错:未实现 `Debug` 或 `HasArea`。
}

此外Rust还有多个trait来约束,如下,compare_prints方法的参数T需要同时实现DebugDisplay这俩个trait;针对trait较少的情况可以使用+来实现。如果较多的情况来说会使用where从句

use std::fmt::{Debug, Display};

fn compare_prints<T: Debug + Display>(t: &T) {
    println!("Debug: `{:?}`", t);
    println!("Display: `{}`", t);
}

fn compare_types<T: Debug, U: Debug>(t: &T, u: &U) {
    println!("t: `{:?}", t);
    println!("u: `{:?}", u);
}

fn main() {
    let string = "words";
    let array = [1, 2, 3];
    let vec = vec![1, 2, 3];

    compare_prints(&string);
    //compare_prints(&array);
    // 试一试 ^ 将此行注释去掉。

    compare_types(&array, &vec);
}

// where从句
impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}

// 使用 `where` 从句来表达约束
impl <A, D> MyTrait<A, D> for YourType where
    A: TraitB + TraitC,
    D: TraitE + TraitF {}

如果实现了trait 的结构体是泛型的,则须遵守类型规范要求trait的使用者必须指出trait的全部泛型类型。Contains trait 允许使用泛型类型 A 或 B。然后我们为 Container 类型实现了这个 trait,将 A 和 B 指定为 i32,这样就可以对 它们使用 difference() 函数。

struct Container(i32, i32);

// 这个 trait 检查给定的 2 个项是否储存于容器中
// 并且能够获得容器的第一个或最后一个值。
trait Contains<A, B> {
    fn contains(&self, _: &A, _: &B) -> bool; // 显式地要求 `A` 和 `B`
    fn first(&self) -> i32; // 未显式地要求 `A` 或 `B`
    fn last(&self) -> i32;  // 未显式地要求 `A` 或 `B`
}
impl Contains<i32, i32> for Container {
    // 如果存储的数字和给定的相等则为真。
    fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
        (&self.0 == number_1) && (&self.1 == number_2)
    }
    // 得到第一个数字。
    fn first(&self) -> i32 { self.0 }
    // 得到最后一个数字。
    fn last(&self) -> i32 { self.1 }
}
// 容器 `C` 就包含了 `A` 和 `B` 类型。鉴于此,必须指出 `A` 和 `B` 显得很麻烦。
fn difference<A, B, C>(container: &C) -> i32 where
    C: Contains<A, B> {
    container.last() - container.first()
}

这样的处理导致different方法的泛型trait约束必须把所有的泛型类型都列出来。这里可以使用关联类型来提升代码可读性。通过把容器内部的类型放到 trait 中作为输出类型,改写Containstrait 如下所示 把原有impl块中的泛型A,B 通过结构体的类型传入

// 关联
trait Contains {
    type A;
    type B;

    // 这种语法能够泛型地表示这些新类型。
    fn contains(&self, &Self::A, &Self::B) -> bool;
}
// 不使用关联类型
fn difference<A, B, C>(container: &C) -> i32 where
    C: Contains<A, B> { ... }

// 使用关联类型
fn difference<C: Contains>(container: &C) -> i32 { ... }

impl Contains for Container {
    // 指出 `A` 和 `B` 是什么类型。如果 `input`(输入)类型
    // 为 `Container(i32, i32)`,那么 `output`(输出)类型
    // 会被确定为 `i32` 和 `i32`。
    type A = i32;
    type B = i32;

    // `&Self::A` 和 `&Self::B` 在这里也是合法的类型。
    fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
        (&self.0 == number_1) && (&self.1 == number_2)
    }

    // 得到第一个数字。
    fn first(&self) -> i32 { self.0 }

    // 得到最后一个数字。
    fn last(&self) -> i32 { self.1 }
}

协变与逆变

官方解释

逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果𝐴、𝐵表示类型,𝑓(⋅)表示类型转换,≤表示继承关系(比如,𝐴≤𝐵表示𝐴是由𝐵派生出来的子类);
𝑓(⋅)是逆变(contravariant)的,当𝐴≤𝐵时有𝑓(𝐵)≤𝑓(𝐴)成立;
𝑓(⋅)是协变(covariant)的,当𝐴≤𝐵时有𝑓(𝐴)≤𝑓(𝐵)成立;
𝑓(⋅)是不变(invariant)的,当𝐴≤𝐵时上述两个式子均不成立,即𝑓(𝐴)与𝑓(𝐵)相互之间没有继承关系。

协变与逆变主要针对继承关系的类型转换, Rust中没有结构体继承,更多的是trait,组合的形式为主。协变与逆变用java类描述下。其实Java泛型是不变的, Java的数组是协变的,编译允许,尽管编译器允许了这样做,运行时的数组机制知道它处理的是子类数组,因此会在向数组中放置异构类型时抛出异常。java可以通过通配符< ? super Xxx>< ? extend Xxx>来实现协变与逆变.多态就顺道说下里氏替换。

里氏替换原则包含一下四层含义:

  • 子类完全拥有父类的方法,且具体子类必须实现父类的抽象方法;

  • 子类中可以增加自己的方法;

  • 当子类覆盖或者实现父类方法时,方法的形参要比父类方法的更为宽松;

  • 当子类覆盖或者实现父类方法时,方法的返回值要比父类的更严格。

class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}

//协变
List<? extends Fruit> flist = new ArrayList<Apple>();

//逆变
void writeTo(List<? super Apple> apples) {
        apples.add(new Apple());
        apples.add(new Jonathan());
}

producer-extends, consumer-super(PECS)

从数据流来看,extends是限制数据来源的(生产者),而super是限制数据流入的(消费者

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    int srcSize = src.size();
    if (srcSize > dest.size())
        throw new IndexOutOfBoundsException("Source does not fit in dest");

    if (srcSize < COPY_THRESHOLD ||
        (src instanceof RandomAccess && dest instanceof RandomAccess)) {
        for (int i=0; i<srcSize; i++)
            dest.set(i, src.get(i));
    } else {
        ListIterator<? super T> di=dest.listIterator();
        ListIterator<? extends T> si=src.listIterator();
        for (int i=0; i<srcSize; i++) {
            di.next();
            di.set(si.next());
        }
    }
}

Java自限定

class SelfClass<T extends SelfClass<T>> { }

基类用子类代替其参数,泛型基类变成了一种其所有子类的公共方法模版

Java版本的SingleFlight

public class SingleFlight {
    private ReentrantLock reentrantLock;
    private HashMap<String, Call> cache;
    public SingleFlight() {}
    public SingleFlight(ReentrantLock reentrantLock) {
        this.reentrantLock = reentrantLock;
    }
    static class Call<T> {
        private CountDownLatch countDownLatch;
        private T t;
        private int dups;
    }
    public <T> Result<T> Do(String key, Supplier<T> fn) {
        this.reentrantLock.lock();
        if (this.cache == null) {
            this.cache = new HashMap<>();
        }
        Call<T> call = (Call<T>) this.cache.get(key);
        try {
            if (call != null) {
                call.dups++;
                this.reentrantLock.unlock();
                call.countDownLatch.await();
                return new Result<>(call.t, false);
            }
            call = new Call<T>();
            call.countDownLatch = new CountDownLatch(1);
            this.cache.put(key, call);
            call.t = fn.get();
            call.countDownLatch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
            this.reentrantLock.unlock();
            return new Result<>(null, false);
        }
        this.reentrantLock.unlock();
        return new Result<>(call.t, call.dups > 0);
    }
}
class Result<T> {
    public T t;
    public boolean shared;

    public Result(T t, boolean shared) {
        this.shared = shared;
        this.t = t;
    }
}