Rust指南之泛型与特性详解

目录
  • 前言
  • 1、泛型
    • 1.1、在函数中定义泛型
    • 1.2、结构体中的泛型
    • 1.3、枚举类中的泛型
    • 1.4、方法中的泛型
  • 2、特性
    • 2.1、默认特性
    • 2.2、特性做参数
    • 2.3、特性做返回值

前言

在上篇Rust 文章中涉及到了泛型的知识,那么今天就来详细介绍一下Rust 中的泛型与特性。泛型是一个编程语言不可或缺的机制,例如在C++ 语言中用模板来实现泛型。泛型机制是编程语言用于表达类型抽象的机制,一般用于功能确定、数据类型待定的类,如链表、映射表等。

1、泛型

泛型是具体类型或其他属性的抽象代替:

  • 所编写的泛型代码并非最终程序运行的代码,而是一种模板,含有一些"占位符"
  • 编译器在编译的时候将"占位符" 替换为具体的数据类型

优点:

提高代码复用能力

  • 减少代码重复

1.1、在函数中定义泛型

例如,定义一个对整型数字选择排序的函数:

fn max(array: &[i32]) -> i32 {
    let mut max_index = 0;
    let mut i = 1;
    while i < array.len() {
        if array[i] > array[max_index] {
            max_index = i;
        }
        i += 1;
    }
    array[max_index]
}

fn main() {
    let a = [3, 4, 6, 8, 1];
    println!("max = {}", max(&a));
}
//运行结果:max = 8

这是一个简单的取最大值程序,可以用于处理 i32 数字类型的数据,但无法用于 f64 类型的数据。

通过使用泛型我们可以使这个函数可以利用到各个类型中去:

fn max<T>(array: &[T]) -> T {
    let mut max_index = 0;
    let mut i = 1;
    while i < array.len() {
        if array[i] > array[max_index] {
            max_index = i;
        }
        i += 1;
    }
    array[max_index]
}

实际上,并不是所有的数据类型都可以比大小。当T被自定义的结构体或者枚举等类型替代时,这段代码肯定就会报错。所以这段代码并不是用来运行的,而是用来描述一下函数泛型的语法格式。

1.2、结构体中的泛型

结构体泛型举例:点坐标结构体,T 表示描述点坐标的数据类型:

struct Point<T> {
    x: T,
    y: T
}

fn main() {
    let p1 = Point {x: 1, y: 2};
	let p2 = Point {x: 1.0, y: 2.0};
}

使用时并没有声明类型,这里使用的是自动类型机制,但不允许出现类型不匹配的情况如下:

let p = Point {x: 1, y: 2.0};

x 与 1 绑定时就已经将 T 设定为 i32,所以不允许再出现 f64 的类型。如果我们想让 x 与 y 用不同的数据类型表示,可以使用两个泛型标识符

struct Point<T1, T2> {
    x: T1,
    y: T2
}

1.3、枚举类中的泛型

在枚举类中表示泛型的方法诸如 OptionResult

enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

枚举类的具体使用可参考本专栏的文章,有较为详细的讲解。

1.4、方法中的泛型

结构体与枚举类都可以定义方法,那么方法也应该实现泛型的机制,否则泛型的类将无法被有效的方法操作。

struct Point<T> {
    x: T,
    y: T
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 2, y: 4 };
    println!("p.x = {}", p.x());
}
//运行结果:p.x = 1

注意,impl 关键字的后方必须有 <T>,因为它后面的 T 是以之为榜样的。

我们也可以为其中的一种泛型添加方法:

impl Point<i64> {
    fn x(&self) -> i64 {
        self.x
    }
}

impl 块本身的泛型并没有阻碍其内部方法具有泛型的能力

例如:

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

方法 mixup 将一个 Point<T, U> 点的 x 与 Point<V, W> 点的 y 融合成一个类型为 Point<T, W> 的新点。

2、特性

特性(trait)概念接近于 Java 中的接口(Interface),但两者不完全相同。特性与接口相同的地方在于它们都是一种行为规范,可以用于标识哪些类有哪些方法。

特性在 Rust 中用 trait 表示:

trait Descript {
    fn describe(&self) -> String;
}

Descript 规定了实现者必需有 describe(&self) -> String 方法。

例如:

struct Person {
    name: String,
    age: u16
}

impl Descript for Person {
    fn describe(&self) -> String {
        format!("{} {}", self.name, self.age)
    }
}

格式:

  • impl <特性名> for <所实现的类型名>

Rust 同一个类可以实现多个特性,每个 impl 块只能实现一个

2.1、默认特性

这是特性与接口的不同点:

  • 接口只能规范方法而不能定义方法
  • 特性可以定义方法作为默认方法
    • 因为是"默认",所以对象对于是否重新定义方法是自由的

举个例子:

trait Descript {
    fn describe(&self) -> String {
        String::from("[Object]")
    }
}

struct Person {
    name: String,
    age: u8
}

impl Descript for Person {
    fn describe(&self) -> String {
        format!("{} {}", self.name, self.age)
    }
}

fn main() {
    let zhangsan = Person {
        name: String::from("kuangtu"),
        age: 28
    };
    println!("{}", zhangsan.describe());
}
//运行结果:kuangtu 28

如果将 impl Descript for Person 块中的内容去掉,那么运行结果就是 [Object]

2.2、特性做参数

很多情况下我们需要传递一个函数做参数,例如回调函数、设置按钮事件等。在 Java 中函数必须以接口实现的类实例来传递,在 Rust 中可以通过传递特性参数来实现:

fn output(object: impl Descript) {
    println!("{}", object.describe());
}

任何实现了 Descript 特性的对象都可以作为这个函数的参数,这个函数没必要知道传入对象有没有其他属性或方法,只需要了解它一定有 Descript 特性规范的方法就可以了。当然,此函数内也无法使用其他的属性与方法。

特性参数还可以用这种等效语法实现:

fn output<T: Descriptive>(object: T) {
    println!("{}", object.describe());
}

这是一种风格类似泛型的语法糖,这种语法糖在有多个参数类型均是特性的情况下十分实用:

fn output_two<T: Descriptive>(arg1: T, arg2: T) {
    println!("{}", arg1.describe());
    println!("{}", arg2.describe());
}

特性作类型表示时如果涉及多个特性,可以用 + 符号表示,例如:

fn notify(item: impl Summary + Display)
fn notify<T: Summary + Display>(item: T)

注意:仅用于表示类型的时候,并不可以在 impl 块中使用。

复杂的实现关系可以使用 where 关键字简化,例如:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U)

可以简化为:

fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug

泛型通过与特性的结合可以实现上面任意类型值比较的案例:

trait Comparable {
    fn compare(&self, object: &Self) -> i8;
}

fn max<T: Comparable>(array: &[T]) -> &T {
    let mut max_index = 0;
    let mut i = 1;
    while i < array.len() {
        if array[i].compare(&array[max_index]) > 0 {
            max_index = i;
        }
        i += 1;
    }
    &array[max_index]
}

impl Comparable for f64 {
    fn compare(&self, object: &f64) -> i8 {
        if &self > &object { 1 }
        else if &self == &object { 0 }
        else { -1 }
    }
}

fn main() {
    let arr = [1.0, 3.0, 7.0, 4.0, 2.0];
    println!("maximum of arr is {}", max(&arr));
}
//运行结果:maximum of arr is 7

Tip: 由于需要声明 compare 函数的第二参数必须与实现该特性的类型相同,所以 Self (注意大小写)关键字就代表了当前类型(不是实例)本身。

2.3、特性做返回值

格式如下:

fn person() -> impl Descript {
    Person {
        name: String::from("Cali"),
        age: 24
    }
}

注意:特性做返回值只接受实现了该特性的对象做返回值且在同一个函数中所有可能的返回值类型必须完全一样。

比如结构体 A 与结构体 B 都实现了特性 Trait,下面这个函数就是错误的:

fn some_function(bool bl) -> impl Descriptive {
    if bl {
        return A {};
    } else {
        return B {};
    }
}

到此这篇关于Rust指南泛型与特性的文章就介绍到这了,更多相关Rust泛型与特性内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 如何使用rust实现简单的单链表

    目录 前言 1.链表节点的定义 2.链表的定义 3.实现从链表头部插入节点的prepend方法 4.为链表实现Display trait定制链表的打印显示 5.为链表实现翻转链表功能的reverse方法 注意事项 总结 前言 作为初学者,在掌握了rust的基本语法和所有权机制,尝试写一下常见数据结构和算法,目标是为了更好的理解rust的所有权机制. 受限于个人目前对rust仍处于入门阶段,因此本文代码实现不一定是最合适的,甚至可能存在问题. 今天的目标是用rust实现一个简单的单链表Linked

  • 深入讲解下Rust模块使用方式

    目录 前言 模块声明&使用 方法一:直接在根文件下声明add.rs 方法二:声明add文件夹,文件夹下包含mod.rs 方法三:add.rs和add文件夹同时存在 同模块相邻文件引用 不同模块引用 小结 前言 本文适用于刚开始学习rust的同学,用于帮助理解rust模块间是如何相互引用的.本文尽量用极少的代码来演示,即便之前没有了解过rust也可以混个眼熟.用的时候可以有个印象. 如果你之前没有了解过rust,只需要知道:Cargo-依赖管理工具,类似于npm,Cargo 使用文件放置约定,即文

  • Rust 能够取代 C 语言吗

    Rust 是 Mozilla 基金会的一个雄心勃勃的项目,号称是 C 语言和 C++ 的继任者.一直以来,C/C++ 中的一些基本问题都没能得到解决,比如分段错误.手动内存管理.内存泄漏风险和不可预测的编译器行为.Rust 的诞生就是为了解决这些问题,并提高安全性和性能. Evrone(一家软件公司)在很多项目中使用了 Rust,我们的工程师们这方面在积累了丰富的经验.在这篇文章中,我们将分享 Rust 的一些主要特性. 主要特性 强静态类型:无垃圾回收以及通过指针手动控制数据存储位置的能力:强

  • 教你使用RustDesk 搭建一个自己的远程桌面中继服务器

    目录 应用背景 干货下载链接 搭建自己的rustdesk中转服务器 服务端安装 客户端配置 愉快使用 应用背景 现在很多商业远程软件要么收费,要么有或多或少的问题.因此急需一个可以自定义且方便快捷的远程桌面软件代替他们,rustdesk就是这样一个开源项目 干货下载链接 github地址:https://github.com/rustdesk/rustdesk 可以自行下载编译,或者按照自己的需求修改. release 可直接下载发布二进制 server端下载:https://rustdesk.

  • Rust指南枚举类与模式匹配详解

    目录 前言 1.Rust基本枚举类语法 1.1.定义枚举 1.2.将数据附加到枚举的变体中 1.3.变体的多种嵌套方式 1.4.定义枚举方法 2.Option枚举 2.1.引入Option枚举解决控制问题 2.2.枚举类的具体使用 3.match控制流运算符 4.if let 语法 前言 书接上文,本篇博客分享的是Rust 枚举类与模式匹配 的知识.作为安全性强的语言,Rust 的枚举类并不像其他编程语言中的概念那样简单,但依然可以十分简单的使用. 1.Rust基本枚举类语法 枚举允许我们列举所

  • Rust指南之泛型与特性详解

    目录 前言 1.泛型 1.1.在函数中定义泛型 1.2.结构体中的泛型 1.3.枚举类中的泛型 1.4.方法中的泛型 2.特性 2.1.默认特性 2.2.特性做参数 2.3.特性做返回值 前言 在上篇Rust 文章中涉及到了泛型的知识,那么今天就来详细介绍一下Rust 中的泛型与特性.泛型是一个编程语言不可或缺的机制,例如在C++ 语言中用模板来实现泛型.泛型机制是编程语言用于表达类型抽象的机制,一般用于功能确定.数据类型待定的类,如链表.映射表等. 1.泛型 泛型是具体类型或其他属性的抽象代替

  • Rust指南之生命周期机制详解

    目录 前言 1.所有权中的垂悬引用解析 2.结构体中使用String 而不用&str 的原因 3.生命周期注释 4.结构体中使用字符串切片引用 5.静态生命周期 6.泛型.特性与生命周期综合使用 前言   Rust 生命周期机制是与所有权机制同等重要的资源管理机制,之所以引入这个概念主要是应对复杂类型系统中资源管理的问题.引用是对待复杂类型时必不可少的机制,毕竟在Rust 中复杂类型的数据不能被处理器轻易地复制和计算.但是为什么还有引入生命周期的概念呢,这是因为引用常常会导致非常复杂的资源管理问

  • Go1.18新特性工作区模糊测试及泛型的使用详解

    目录 前言 Go工作区模式(Go Workspace Mode) 现实的情况 多仓库同时开发 多个新仓库开始开发 工作区模式是什么 推荐的使用方法 使用时的注意点 Go模糊测试(Go Fuzzing Test) 为什么Golang要支持模糊测试 模糊测试是什么 Golang的模糊测试如何使用 最简单的实践例子 提供自定义语料 使用时的注意点 Go的泛型 类型参数(Type Parameters) 类型集合(Type Sets) 类型推导(Type Inference) 类型统一化(Type Un

  • Rust 编程语言中的所有权ownership详解

    目录 I. 前言 II. Rust Ownership概述 III. Ownership的代码实践 IV. Ownership与内存管理 首先和c++相比 和我们熟悉的jvm垃圾回收相比 V. 总结 I. 前言 Rust,不少程序员的白月光,这里我们简单罗列一些大牛的评价. Linus Torvalds:Linux内核的创始人,对Rust的评价是:“Rust的主要优点是代码的安全性和速度,很难在C++中实现这种安全性,而且Rust编译器会捕获很多C++难以发现的错误”. Brian Kernig

  • Linux正则表达式特性详解及BRE与ERE的异同点

    Linux正则表达式(Regular Expression)主要遵从POSIX BRE或者POSIX ERE标准.什么是POSIX呢,POSIX Portable Operating System Interface 可移植操作系统接口ERE是BRE的扩展版本,具体更强的处理能力,并增加了一些元字符(metacharactor). BRE主要的能力集有: 1) 普通字符(Literal text),如a,b,c等 2)非打印字符,包括TAB,回车,换行,回车换行(WINDOWS) 3)任意字符.

  • InnoDb 体系架构和特性详解 (Innodb存储引擎读书笔记总结)

    后台线程 •Master Thread 核心后台线程,主要负责将缓冲池的数据异步刷新到磁盘.例如脏页的刷新,插入缓冲的合并,undo 页的回收等. 每秒一次的操作: 1.日志缓冲刷新到磁盘,即使该事务还没有提交.该操作总是会发生,这个就是为了再大的事务,提交时间都很短. 2.当IO压力很小时(1s内发生的IO次数小于5% innodb_io_capacity)合并5% innodb_io_capacity 的插入缓冲. 3.当脏页比例大于 innodb_max_dirty_pages_cnt,

  • Android5.0新特性详解之全新的动画

    在Material Design设计中,为用户与app交互反馈他们的动作行为和提供了视觉上的连贯性.Material主题为控件和Activity的过渡提供了一些默认的动画,在android L上,允许自定义这些动画: Touch feedback 触摸反馈 Circular Reveal 圆形展示 Curved motion 曲线运动 View state changes 视图状态变化 Vector Drawables 矢量图动画 Activity transitions 活动转场 触摸反馈 触

  • SQL Server 2012 FileTable 新特性详解

    FileTable是基于FILESTREAM的一个特性.有以下一些功能: •一行表示一个文件或者目录. •每行包含以下信息: • •file_Stream流数据,stream_id标示符(GUID). •用户表示和维护文件及目录层次关系的path_locator和parent_path_locator •有10个文件属性 •支持对文件和文档的全文搜索和语义搜索的类型列. •filetable强制执行某些系统定义的约束和触发器来维护命名空间的语义 •针对非事务访问时,SQL Server配置FIL

  • JavaScript_ECMA5数组新特性详解

    var arr = [ 1, 2, 3, 4, 5, 4, 3, 2, 1 ]; 新加位置的方法: indexOf lastIndexOf 1.1个参数的时候表示传值 返回索引位置(index从0开始) var index = arr.indexOf(4); alert(index); //3 2. 2个参数的时候 第一个参数表示起始位置 第二个参数还是值 var index = arr.indexOf(4,4); alert(index); //5 3.他们查找数组比较的时候 '===' la

随机推荐