【第九课】Rust中泛型和特质

目录

前言

泛型

单态化

特质

多态

总结


前言

这节课来介绍一下Rust中的泛型和特质,泛型在大部分语言中都有,和Java中的泛型差不多,特质在Java并没有,但是在Scala中有,特质其实类似于Java中的接口Interface。

泛型

(1)容器中的泛型

Rust中我们使用的Vector、HashMap其实都是有泛型的,和其他编程语言中的泛型一样

(2)结构体中的泛型

下面代码展示了结构体中的泛型,包括结构体的方法如何定义泛型,包括方法和关联函数。值的注意的是在调用关联函数的时候,我们指定了具体的类型,可以试一下如果不指定会怎么样?不指定编译会报错,为什么会报错呢?这里涉及Rust中泛型的实现方式。

rust 复制代码
use std::fmt::Display;

fn main() {
    let r1 = RecTangle {
        width: 1,
        height: 2.0,
    };

    println!("r1.width={}", r1.width);
    println!("r1.height={}", r1.height);
    r1.custom_print1("hello");
    RecTangle::<i32, i32>::custom_print2("hello");
}


struct RecTangle<T, U> {
    width: T,
    height: U,
}

impl<T, U> RecTangle<T, U> {
    fn get_width(&self) -> &T {
        &self.width
    }

    fn get_height(&self) -> &U {
        &self.height
    }

    fn custom_print1<E: Display>(&self, other: E) {
        println!("other={}", other);
    }

    fn custom_print2<E: Display>(text: E) {
        println!("text = {}", text);
    }
}

单态化

我们来聊一聊Rust中泛型的实现方式:单态化。

有了解过Java中的泛型实现可以知道,Java中的泛型是伪泛型,存在泛型擦除,不管什么泛型最终都是Object。但是Rust中的泛型实现不一样,他是依赖编译期将存在的类型都固定死。什么意思呢?假设我们定义了一个泛型函数,在整个程序中,该泛型可以是i32,f64.那么在编译期间,Rust编译器会根据类型推断生成具体类型的代码,比如这个函数会生成i32和f64对应的函数,在调用函数的地方也会替换为i32和f64的函数,这种行为就叫做单态化,单态化的好处是类型确定且性能优化。我们提供一段代码解释一下,下面的代码中我们定义了泛型函数,在main方法中,调用了&str和i32和f64类型的函数,那么在编译期间,会生成对应类型的my_print函数,并且在main方法中替换函数。

rust 复制代码
use std::fmt::Display;

fn main() {
    my_print("hello");
    my_print(12);
    my_print(3.14);

}


fn my_print<T: Display>(input: T) {
    println!("input is {}", input);
}

特质

Rust中的特质trait可以类比于Java中的接口interface理解。

在下面的代码中,我们定义了一个Trait:Say;并且定义了抽象方法say_hi,又定义了三个不同的结构体,并且为这些结构体实现了Say特质。

rust 复制代码
use std::fmt::Display;

fn main() {
    let p1 = Person {
        name: String::from("p"),
    };
    let d1 = Dog {
        name: String::from("d1"),
    };
    let c1 = Cat {
        name: String::from("c1"),
    };

    p1.say_hi();
    d1.say_hi();
    c1.say_hi();

}

trait Say {
    fn name_f() {
        println!("我是接口Say")
    }

    fn say_hi(&self);
}

struct Person {
    name: String,
}

impl Say for Person {
    fn say_hi(&self) {
        println!("{} say hi", self.name);
    }
}

struct Dog {
    name: String,
}

impl Say for Dog {
    fn say_hi(&self) {
        println!("{} say wang", self.name);
    }
}

struct Cat {
    name: String,
}

impl Say for Cat {
    fn say_hi(&self) {
        println!("{} say miao", self.name);
    }
}

多态

由于Rust中没有继承的概念,不像Java等语言,有父类和子类的概念,在java代码中经常看到使用父类接收子类对象的代码,在Rust中都是依赖Trait来实现这样的功能的。Trait可以当作参数也可以当作返回值。

rust 复制代码
use std::fmt::Display;

fn main() {
    let p1 = Person {
        name: String::from("p"),
    };
    let d1 = Dog {
        name: String::from("d1"),
    };
    let c1 = Cat {
        name: String::from("c1"),
    };

    custom_print(&p1);
    p1.say_hi();

}

fn custom_print(input: &impl Say) {
    input.say_hi();
}

trait Say {
    fn name_f() {
        println!("我是接口Say")
    }

    fn say_hi(&self);
}

struct Person {
    name: String,
}

impl Say for Person {
    fn say_hi(&self) {
        println!("{} say hi", self.name);
    }
}

struct Dog {
    name: String,
}

impl Say for Dog {
    fn say_hi(&self) {
        println!("{} say wang", self.name);
    }
}

struct Cat {
    name: String,
}

impl Say for Cat {
    fn say_hi(&self) {
        println!("{} say miao", self.name);
    }
}

上面的代码是用Trait当作参数的例子,定义了custom_print函数,函数的参数使用impl Say表示接收一个实现了Say特质的参数,注意这里我使用了&表示这只是一个引用,我们复习一下所有权转移的知识,如果不加&,所以权会转移给入参,当函数运行结束后,这一块内存会被释放,于是我们还在想函数调用结束后,使用结构体对象的话,就会报错,于是我们使用&借用只读权限,这样不会报错,由此可见,所有权无处不在。

下面的代码展示了如何使用Trait作为函数的返回值

在函数的返回值处使用impl Say表示函数返回的是一个实现了Say特质的对象。

rust 复制代码
use std::fmt::Display;

fn main() {
    let p1 = Person {
        name: String::from("p"),
    };
    let d1 = Dog {
        name: String::from("d1"),
    };
    let c1 = Cat {
        name: String::from("c1"),
    };

    Say::name_f();
    Person::name_f();

    custom_print(&p1);
    p1.say_hi();

    let xx = new_say();
    xx.say_hi();

}

fn new_say() -> impl Say {
    Dog {
       name: String::from("www"),
    }
}

fn custom_print(input: &impl Say) {
    input.say_hi();
}

trait Say {
    fn name_f() {
        println!("我是接口Say")
    }

    fn say_hi(&self);
}

struct Person {
    name: String,
}

impl Say for Person {
    fn say_hi(&self) {
        println!("{} say hi", self.name);
    }
}

struct Dog {
    name: String,
}

impl Say for Dog {
    fn say_hi(&self) {
        println!("{} say wang", self.name);
    }
}

struct Cat {
    name: String,
}

impl Say for Cat {
    fn say_hi(&self) {
        println!("{} say miao", self.name);
    }
}

总结

这节课讲述了Rust中面向对象的部分,对于泛型,Rust中泛型实现方式和其他语言不用,采用了单态化的方式在编译器生成代码,Rust将很多工作都移动到了编译器执行,这也是编译时间长的原因之一,Rust中的特质和Scala中的特质Java中的接口类似。

相关推荐
就爱六点起16 分钟前
C/C++ 中的类型转换方式
c语言·开发语言·c++
我明天再来学Web渗透18 分钟前
【SQL50】day 2
开发语言·数据结构·leetcode·面试
猫猫的小茶馆19 分钟前
【C语言】指针常量和常量指针
linux·c语言·开发语言·嵌入式软件
潜洋27 分钟前
Spring Boot教程之五:在 IntelliJ IDEA 中运行第一个 Spring Boot 应用程序
java·spring boot·后端
DanielYQ1 小时前
LCR 001 两数相除
开发语言·python·算法
yngsqq1 小时前
037集——JoinEntities连接多段线polyline和圆弧arc(CAD—C#二次开发入门)
开发语言·c#·swift
Zԅ(¯ㅂ¯ԅ)1 小时前
C#桌面应用制作计算器进阶版01
开发语言·c#
过期的H2O21 小时前
【H2O2|全栈】JS进阶知识(七)ES6(3)
开发语言·javascript·es6
一路冰雨1 小时前
Qt打开文件对话框选择文件之后弹出两次
开发语言·qt
St_Ludwig1 小时前
C语言 蓝桥杯某例题解决方案(查找完数)
c语言·c++·后端·算法·游戏·蓝桥杯