这系列RUST教程一共三篇。这是第一篇,介绍RUST语言的入门概念,主要有enum\trait\impl\match等语言层面的东西。

安装好你的rust开发环境,用cargo创建一个空项目,咱们直接上代码。懵逼的同僚可以参考我8年前的rust文章:https://www.iteye.com/blog/somefuture-2275494 ,虽然8年了,然并不过时。

背景

我们要写一个小程序寻找集合中的最小元素。如果你对这句描述有疑惑,请不要疑惑,它就是这么迷惑,继续看就好了。

对于一个有三五个元素的整型集合,元素都很小的话我们肉眼看一下就知道最小元素。所以这个程序(或者说是一个函数)返回的类型应该是一个整型的。

java程序员可能会说为啥说函数不是方法。它们的区别是函数是不依附于某个对象的,或者说方法对应Java中的实例方法,而函数是静态方法。rust没有static关键字,所以定义出来的的fn是方法还是函数看他的第一个参数,如果是self就是方法。

但再想想,如果集合是空的,返回哪个整数都不合适吧。总不能返回0吧,那一个集合真的有了个元素是0咋办?万一有的集合里面有负数呢,更难区别。

所以我们这个函数返回的是这么个类型:一个整数,或者啥也没有。

那不就是整型包装类就行吗?

在rust中我们使用枚举enum来来定义这个类型。跟其他语言不太一样,rust中的枚举非常常用,使用上有点像C++中的结构体union

enum

直接在你的main.rs文件中main函数上面写

enum IntOrNothing {
Int(i32),
Nothing
}

你看这个枚举里有两个成员,一个是可以携带一个整数的叫 Int,另一个是不携带数据的叫Nothing。当然你可以给他们继续增加参数来携带更多数据,完全没关系。

接下来定义这个求最小值的函数。

fn

方法和函数的定义都用关键字fn,如果要公开就在前面加pub,否则是私有的。函数签名如下:

fn vec_min(vec: Vec<i32>) -> IntOrNothing {}

函数名后面写括号,里面放参数。前面说过,如果第一个参数是self就是实例方法。参数类型用冒号指定,函数返回值类型用->指定。

这里的参数vec是一个Vec类型的集合。Vec<>你可以称他是向量或者集合或者数组集合,它是一个可变大小的集合类型,实现原理有点像Java的ArrayList<>

返回类型就是我们上面定义的枚举类。我们要在传入的参数中寻找最小的元素返回,如果集合是空的就返回枚举中的第二个成员Nothing。所以我们的函数体大致类似:

fn vec_min(vec: Vec<i32>) -> IntOrNothing {
let mut min = IntOrNothing::Nothing;
for el in vec {
// 寻找最小值赋给min
}
return min;
}

首先定义一个变量min,定义变量必须使用关键字mut,不然就会是常量。它的初始值赋值成Nothing,并在最后return。为啥我要说return? 因为rust中不需要在最后使用return。rust是表达式语言,和Java是语句声明语言不一样。表达式语言的意思是任意一个大括号保住的块都是一个表达式,最后返回的是表达式块中的最后一个对象(或者说经过计算的整个块)。


举个例子,定义一个比较两个数大小的函数,大概是这样的:

fn max(i: i32, j: i32) -> i32 { if i >= j { i } else { j } }

这里实际是省略了if前面的return。但是如果用Java写,需要把return写到每个if的分支里面才行。


函数也可以定义在其他函数内部,这样外面的函数就不能访问到他了。

如果集合有元素,我们就来遍历它。

for

和其他语言一样,rust也是用for循环。for循环的语法没啥说的,记着就是for-in就好了。

然后对于循环变量el我们来判断,如果它比现在的min小,就更新它。但是min有可能没存储整数,第一轮循环必然是直接赋值而非更新。你可以使用if进行判断,这里我们使用模式匹配。

match

模式匹配在rust中使用也非常广泛,和erlang有点像(比不上erlang那种程度)。在for循环体中写入如下代码:

        match min {
Int(n) => {min = Int(if el < n {el} else { n })}
Nothing() => {min = Int(el)}
}

和其他语言的switch-case有点像。如果min是Nothing(下面的分支),就给他赋值成Int并携带整数el;如果已经是Int了,用变量n捕获其中的整数,并经过和el对比大小生成新的变量赋值给min。可以看到Int的参数可以传入一整个表达式,和传了一个值或者函数效果是一样的。

接下来我们执行一下这个程序看看。

main

和其他多数语言一样,rust也使用main函数作为程序入口。cargo在创建main.rs文件的时候已经创建了main函数,我们修改一下他:

fn main() {
let v = vec![18,5,7,1,9,27];
let min = vec_min(v);
match min {
Nothing => println!("The number is: <nothing>"),
Int(n) => println!("The number is: {}", n),
}
}

我们先用vec!这个宏生成了一个集合,然后用上面的函数计算它里面的最小值,最后打印一句话出来。打印的时候需要用到match来判断,因为rust中println!这个宏只能打印Display这个trait(目前就这样理解就好了),而我们定义的IntOrNothing并没有实现它。但是i32是实现了的,可以打印。

宏是rust中定义生成代码的逻辑规则,我们也可以自定义宏。

你可以多试几个case看一下输出效果。

休息一下,我们再回头来看我们的代码。如果你头脑休息好了,可能会提出这个问题:格式化输出应该属于对象自身,这样我们拿到最小值枚举,直接调用它的输出方法就可以了。

我们来给上面这个枚举增加方法。

impl

impl关键字可以给任何对象增加方法和函数,不论这个对象是自定义的还是rust中已经存在的。

在枚举定义的代码下面增加(当然增加到其他文件里去也可以,只要你能修复遇到的问题)

impl IntOrNothing {
fn print(self) {
match self {
Nothing => println!("The number is: <nothing>"),
Int(n) => println!("The number is: {}", n),
};
}
}

终于用到self了。这是一个方法,不接受任何参数。当自身是Nothing的时候打印第一句,当是整数的时候打印第二句。

现在可以直接调用这个方法了:

fn main() {
let v = vec![18,5,7,1,9,27];
let min = vec_min(v);
min.print();
}

然后作为程序员,开发完整数版本的求最值,那应该想要其他版本的。

前面写的枚举,当有值的时候会携带一个整数,如果想要携带各种类型,我们当然可以开发对于的版本。但是你大概率是从其他语言过来的,肯定想我之前用过泛型,rust可以用吗?

泛型(generic type)

rust也支持泛型,或者说是多态。我们来修改一下前面的枚举,让他支持泛型:

pub enum SomethingOrNothing<T>  {
Something(T),
Nothing,
}

很好理解,有数据就放到Something里面,没有就使用Nothing表示。

如何用它支持前面的整数场景呢?

type

不用说也知道,可以用SomethingOrNothing<i32>

此外,rust提供了type关键字将其绑定为新类型:

type IntOrNothing = SomethingOrNothing<i32>;

接下来就可以跟使用IntOrNothing了,跟之前完全一样。

类似的,我们可以定义SomethingOrNothing<bool>或者SomethingOrNothing<SomethingOrNothing<i32>>甚至更复杂的形式。

实际上rust已经提供这个枚举了,叫Option<T>:

可以看到里面也是两部分,一个空和一个内容。我们给我们上面自定义的枚举增加两个方法,让他和Option能互相转化。添加方法或函数还记得吗,使用impl

impl<T> SomethingOrNothing<T> {
fn new(o: Option<T>) -> Self {
match o { None => Nothing, Some(t) => Something(t) }
} fn to_option(self) -> Option<T> {
match self { Nothing => None, Something(t) => Some(t) }
}
}

不看代码自己上手可能会有点惶恐,无从下手;看了代码你会发现非常简单。

这里定义了一个函数和一个方法,函数new接受一个Option对象,转成SomethingOrNothing:这里使用的Self类型,也就是self的类型。

new在rust中不是保留字,创建对象实例用不到它,不像Java。不过人们习惯用new创建对象,所以生成函数一般命名成这样。

还定义了一个方法to_option,出入参没啥说的。

rust提供了完整详细的官方文档,见 https://doc.rust-lang.org/stable/std/option/index.html

既然生成函数是静态的,那就可以不适用impl来分派,我们可以定义其他函数来生成对象:

fn call_constructor(x: i32) -> SomethingOrNothing<i32> {
SomethingOrNothing::new(Some(x))
}

为了演示,这里的生成流程很长。先使用了Option中的Some,然后传给了new函数,整个作为call_constructor的函数体。

泛型枚举定义好了。现在你可以闭目养神一会,回来后我们继续完成目标:计算任意类型的集合最小值。


要想求任意类型的集合最值,就要求集合元素支持比较大小。前面我们使用的整数,当然是支持的。其他类型如果天然不知道怎么定义比较方法呢?聪明的你一定想到了:使用接口。

trait

rust中的接口称为trait,也就是特征。这个单词我觉得比Java中的interface或C++的template更形象。Java编程中有一个原则叫“基于接口编程”,实际上就是基于行为特征编程。

我们先来定义一个trait,里面有一个方法compare_get_min

pub trait Minimum  {
fn compare_get_min(self, s: Self) -> Self;
}

这个方法接收一个跟自身相同类型的参数,比较厚返回小的(留意一下参数定义)。

接下来就修改vec_min函数。之前的定义是fn vec_min(vec: Vec<i32>) -> IntOrNothing,只接受整数,也最多返回整数。现在改成这样:

pub fn vec_min<T: Minimum>(v: Vec<T>) -> SomethingOrNothing<T> {}

在函数名称后面写<>,里面定义泛型参数T,这样参数列表中和返回类型中就可以使用T了。同时通过冒号:限制泛型边界,必须实现了traitMinimum。函数体修改如下:

    let mut min = Nothing;
for e in v {
min = Something(match min {
Nothing => e,
Something(n) => {
e.compare_get_min(n)
}
});
}
min

注意看我们用到的trait方法compare_get_min

要让我们之前的代码运行,还有最后一步:让i32实现Minimum。因为只有实现这个trait的元素才能在集合中被拿到最值。

impl Minimum for i32 {
fn compare_get_min(self, b: Self) -> Self {
if self < b { self } else { b }
}
}

再一次,注意表达式语言的应用。

写在最后

这篇文章的最后剧透一个枚举用法。因为枚举中的数据是保存在枚举的某一个分支中的,比如SomethingOrNothing中的SomethingOption中的Someresult::Result中的两个分支OKErr。要拿到其中的数据,除了使用模式匹配,一般会定义一个方法unwrap。这个方法的返回就是枚举中存储的数据:当确有数据时,就拿到数据;没有数据时,会报出异常。所以当我们确定(或要求)其中必须有值的时候可以调用这个方法,否则还是使用模式匹配分别处理。

写给rust初学者的教程(一):枚举、特征、实现、模式匹配的更多相关文章

  1. 10篇写给Git初学者的最佳教程(转)

    身为网页设计师或者网页开发者的你,可能已经听说过Git这个正快速成长的版本控制系统.它由GitHub维护:GitHub是一个开放性的.存储众人代码的网站.如果你想学习如何使用Git,请参考本文.在文章 ...

  2. Swift中文教程(六)--枚举和结构

    原文:Swift中文教程(六)--枚举和结构 Enumerations 枚举 使用 enum 来创建一个枚举.跟Classes(类)和其他类型的命名方式一样,枚举也可以有Method(方法). enu ...

  3. Rust极简教程

    目录 简介 特性 特征 用途 安装 核心组件 常用命令 基础语法 数据类型 标量类型 复合类型 示例 条件语句 循环 输出&输入 输出 输出花括号 输出非基础类型 输入 所有权 切片 结构体 ...

  4. 专为设计师而写的GitHub快速入门教程

    专为设计师而写的GitHub快速入门教程 来源: 伯乐在线 作者:Kevin Li     原文出处: Kevin Li 在互联网行业工作的想必都多多少少听说过GitHub的大名,除了是最大的开源项目 ...

  5. [转]让你从零开始学会写爬虫的5个教程(Python)

    让你从零开始学会写爬虫的5个教程(Python)   写爬虫总是非常吸引IT学习者,毕竟光听起来就很酷炫极客,我也知道很多人学完基础知识之后,第一个项目开发就是自己写一个爬虫玩玩. 其实懂了之后,写个 ...

  6. Java基础教程:枚举类型

    Java基础教程:枚举类型 枚举类型 枚举是将一具有类似特性的值归纳在一起的方法.比如,我们可以将周一到周日设计为一个枚举类型.彩虹的七种颜色设计为一个枚举类型. 常量实现枚举 我们通过定义常量的方式 ...

  7. 写给 Linux 初学者的一封信

    大家好,我是肖邦. 这篇文章是写给 Linux 初学者的,我会分享一些作为初学者应该知道的一些东西,这些内容都是本人从事 Linux 开发工作多年的心得体会,相信会对初学者有所帮助.如果你是 Linu ...

  8. 【译】Rust宏:教程与示例(二)

    原文标题:Macros in Rust: A tutorial with examples 原文链接:https://blog.logrocket.com/macros-in-rust-a-tutor ...

  9. 让你从零开始学会写爬虫的5个教程(Python)

    写爬虫总是非常吸引IT学习者,毕竟光听起来就很酷炫极客,我也知道很多人学完基础知识之后,第一个项目开发就是自己写一个爬虫玩玩. 其实懂了之后,写个爬虫脚本是很简单的,但是对于新手来说却并不是那么容易. ...

  10. JAVA写接口傻瓜(#)教程(四)

    接上篇 7.sevlert 啊啊啊终于写到最重要的实现部分了.Servlet = Service + Applet,表示小服务程序.Servlet 是在服务器上运行的小程序.这个词是在 Java ap ...

随机推荐

  1. gorm 返回的 *DB 说明

    RecordNotFound 跟在查询的后面(Find/First),bool true:没有查到记录 false:查到记录 Error 跟在修改(create/update)的后面,如果错误就会报错 ...

  2. Python基础篇(流程控制)

    流程控制是程序运行的基础,流程控制决定了程序按照什么样的方式执行. 条件语句 条件语句一般用来判断给定的条件是否成立,根据结果来执行不同的代码,也就是说,有了条件语句,才可以根据不同的情况做不同的事, ...

  3. 腾讯面试:如何提升Kafka吞吐量?

    Kafka 是一个分布式流处理平台和消息系统,用于构建实时数据管道和流应用.它最初由 LinkedIn 开发,后来成为 Apache 软件基金会的顶级项目. Kafka 特点是高吞吐量.分布式架构.支 ...

  4. 多线程池Flask实战应用

    多线程池Flask实战应用 import json import time import flask from concurrent.futures import ThreadPoolExecutor ...

  5. 如何更加优雅的使用 SSH 进行登录

    引言 我们在日常的开发过程中,很多时候需要连接服务器查看日志或者在服务器上调试代码.但是,使用 ssh 命令登录服务器每次都需要输出密码,就比较繁琐.因此我们可以使用 sshpass 通过参数指定密码 ...

  6. 恭喜PaddleOCRSharp开源项目通过PaddleOCR社区常规赛优秀项目首次评选

    PaddleOCR优秀社区项目推荐: PaddleOCR社区常规赛首次评选结果已于日前出炉,本次优秀项目推广为大家带来的是[部署篇]:️ PaddleOCR的.NET调用库:包含文本识别.文本检测.基 ...

  7. Java 中 hashCode 和 equals 方法是什么?它们和 == 各有什么区别?

    在 Java 中,hashCode 和 equals 方法都是 Object 类的方法.它们的作用分别如下: hashCode 方法返回对象的哈希码,用于支持基于哈希表的集合,如 HashMap.Ha ...

  8. Qt-udp通信

    1  简介 参考视频:https://www.bilibili.com/video/BV1XW411x7NU?p=61 说明:UDP是面向无连接的,客户端并不与服务器不建立连接,直接向服务器发送数据, ...

  9. numpy基础--ndarray(一种多维数组对象)

    NumPy基本介绍 NumPy(Numerical Python)是高性能科学计算和数据分析的基础包.其提供了以下基本功能: ndarray:一种具有矢量算术运算和复杂广播能力的快速且节省空间的多维数 ...

  10. 使用python获取房价信息

    从贝壳网获取房价信息. 基本的步骤和我的这篇博文一样:https://www.cnblogs.com/mrlayfolk/p/12319414.html.不熟悉的可参考一下. 下面的代码是获取3000 ...