惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
Lohrmann on Cybersecurity
Martin Fowler
Martin Fowler
Engineering at Meta
Engineering at Meta
腾讯CDC
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Azure Blog
Microsoft Azure Blog
G
Google Developers Blog
TaoSecurity Blog
TaoSecurity Blog
博客园_首页
Vercel News
Vercel News
Hugging Face - Blog
Hugging Face - Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Last Week in AI
Last Week in AI
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
The Exploit Database - CXSecurity.com
量子位
Project Zero
Project Zero
A
Arctic Wolf
小众软件
小众软件
NISL@THU
NISL@THU
C
CERT Recently Published Vulnerability Notes
有赞技术团队
有赞技术团队
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
News and Events Feed by Topic
宝玉的分享
宝玉的分享
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Troy Hunt's Blog
P
Privacy & Cybersecurity Law Blog
Security Latest
Security Latest
B
Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
D
DataBreaches.Net
Schneier on Security
Schneier on Security
The Hacker News
The Hacker News
K
Kaspersky official blog
C
Check Point Blog
Hacker News: Ask HN
Hacker News: Ask HN
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Webroot Blog
Webroot Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
人人都是产品经理
人人都是产品经理
AI
AI
Cisco Talos Blog
Cisco Talos Blog
MyScale Blog
MyScale Blog
Cloudbric
Cloudbric
B
Blog RSS Feed
S
Schneier on Security
P
Palo Alto Networks Blog

文章列表 on polarisxu

通过一个例子让你彻底掌握 Go 工作区模式 这道题正确率竟然只有 22% Go 开源项目推荐:一个简单的 Go 练手项目 go test 如何禁用缓存? Go1.18 快讯:constraints 包被移除标准库 跟着 Go 作者学泛型 「2022 版」轻松搞定 Go 开发环境 鹅厂小哥用行动反对加班:程序员加班能被终结吗? Go 为什么选择 Gopher 作为吉祥物? 本地如何配置多个 GitHub/Gitee 账号? 为 Java 开发者准备的 Go 教程 02:Java 有而 Go 无 GitHub 发现了 studygolang 项目依赖的漏洞 Typora 宣布收费后,这款开源 Markdown 编辑器火了 为 Java 开发者准备的 Go 教程 01:漫游了 我的 2021 年终总结 泛型版 singleflight:Go 中如何防止缓存击穿? 2021 年你写了多少代码?这个 Go 工具帮你统计 Go1.18 新特性:TryLock Go 泛型入门教程 程序员瑞士军刀:各种结构的转换工具 终于有 Go 版的 Elasticsearch 了 Go泛型系列:maps 包讲解 Go 1.18 中的 any 是什么? Go1.18 快讯:新的 IP 包 Go编程模式:详解函数式选项模式 Go泛型系列:slices 包讲解 Nginx 竟然也有 playground — Go 语言构建的 Go泛型系列:Go1.18 类型约束那些事 Go1.18 快讯:Module 工作区模式 Go1.18 快讯:新增的 Cut 函数太方便了 Go1.18 快讯:废弃了这个 API Go1.18 快讯:新增字符串 Clone API Go:如何获得项目根目录? Go 如何获取和设置环境变量 重磅变动:更快找到微信公众号里的Go文章 Go 这样设置版本号:我们的项目也可以 假期结束,推荐 2 本 manning 出的 Go 图书 Go Fiber 框架系列教程 04:测试应用 Go Fiber 框架系列教程 03:中间件 Go泛型系列:提前掌握Go泛型的基本使用 推荐一个 Go GUI 实战项目 Go Fiber 框架系列教程 02:详解相关 API 的使用 Go Fiber 框架系列教程 01: 和 Express 对比学习 Go Module:私有不合规库怎么解决引用问题 Go1.17 新特性:go get 变了 Go1.17 新特性:testing 包的相关变化 StackOverflow 上关于 Go select 死锁的问题 新书推荐:用 Gin 框架构建分布式应用 Go 官网要变天。。。 Gin 这是要成为 Go 官方框架? 扬眉吐气:刚刚,Go 已经默认支持泛型了 Go 1.17 新特性:Module 有哪些变化? Go 第三方库推荐:类型转换如此简单 担心密码提交到 GitHub?建议使用这个 Go 开源工具 Go 的时间格式化为什么是 2006-01-02 15:04:05? GitHub 为 Go 社区带来安全支持 Go开源项目推荐:500行代码确认请求时间花在哪 一道关于 len 函数的诡异 Go 面试题解析 厉害了我的 Go:推荐系统都有开源实现 Go1.17 新特性:新版构建约束 这个工具真好:看看你的Go项目依赖有无漏洞 网友很强大,发现了Go并发下载的Bug Rust 劝退系列 09:函数 Go项目实战:一步步构建一个并发文件下载器 这本 Go 新书挺期待的:100 个常见错误 Go 启用新的官方问答社区 这是要干嘛?!微软招 Go 编译器全职开发人员 Go1.17 快报之标准库越来越注重易用性 Go1.17 新特性之切片变数组 回顾 Go 官网的演变史 站长8年前的Go代码竟然进入大厂的项目里了 Go图书翻译:一个好消息,一个坏消息 不怕烂尾!决定组织翻译这本 Go 图书 Rust 劝退系列 08:模式匹配 一本花了2.5年写成的Go免费在线图书 被黑惨了:一句话,说明自己会 Go,咋整? Go1.16 中的新函数 signal.NotifyContext 怎么用? Go 1.17 新特性提前学之测试执行随机化 Rust 劝退系列 07:流程控制 官方的 Go 多版本管理:使用和原理 Rust新书:给你一个劝退的理由 周刊题解:常量表达式这个规则应该了解下 盘点那些使用 Go 语言的国外公司 编写了50万行Go代码是一种什么体验 Rust 劝退系列 06:常量 Uber 使用 Go 的规模这么大?!都自己定制的 Go 编译器了 Go Team Leader — rsc 大神新开源了一个库,增强模板功能 再一次看到了 Go 的节制:int128 类型要不要支持? 「卷」有理论依据:海勒姆定律—Go又是怎么卷的 Go 真的也可以进行 GUI 开发:还有这样的图书呢 Rust 劝退系列 04:基本数据类型 我的 Go 语言书单 Rust 劝退系列 03:变量 我又来推荐免费 Go 新书了:一本用 Go 讲架构的书 注释竟然还有特殊用途?一文解惑 //go:linkname 指令 这个功能,公众号、微信群会不会被玩坏? 一道 Go 闭包题,面试官说原来自己答错了:面别人也涨知识 Rust 劝退系列 02:第一个 Rust 程序 Rust 劝退系列 01:打造开发环境 答应我,这次一定彻底搞懂 Go 中的类型别名
Rust 劝退系列 05:复合数据类型
2021-05-06 · via 文章列表 on polarisxu

大家好,我是站长 polarisxu。

这是 Rust 劝退系列的第 5 个教程,探讨 Rust 中的复合数据类型(Compound types)。Rust 中有两种原生的复合类型:元组(tuple)和数组(array),顺带介绍切片。

01 元组类型

Go 语言没有元组类型,但多返回值有点类似元组(但还是有区别的哦)。Python 中有元组类型,因此如果你熟悉 Python,对元组应该很熟悉。

什么是元组类型?

元组是一个可以包含各种类型的值的组合。元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度无法增大或缩小。 元组的类型由各组成元素类型的序列定义。

元组通过小括号定义,里面的元素通过逗号分隔,例如:

这个字面值元组的类型是:(f64, i32, char),即对应每个元素的默认类型。因此,我们可以通过 let 将这个元组绑定到变量上,Rust 会进行类型推断:

let tup = (23.2, 27, 'a');

在 VSCode 中可以看到 tup 的类型就是:(f64, i32, char)。同样地,我们也可以为 tup 使用类型注解:

let tup: (f32, i8, char) = (23.2, 27, 'a');

因为元组是多个类型的集合,对元组中的类型没有限制。因此,可以嵌套。比如:

不过建议别嵌套太多,否则可读性太差。

如何访问元组元素呢?

上面说,Go 语言中函数多返回值类似元组,在接收多返回值时,通过多个变量接收,比如:

// Go 语言
f, err := os.Open("abc.txt")

在 Rust 中,可以解构元组(这也叫模式匹配解构):

let tup = (23.2, 27, 'a');
let (x, y, z) = tup;	// 注意:需要小括号

和 Go 语言一样,如果某个元素我们不关心,可以放入垃圾桶(_):

let tup = (23.2, 27, 'a');
let (x, _, z) = tup;	// 注意:需要小括号

Rust 中变量定义未使用,不会像 Go 一样报错,但会警告!

除了模式匹配解构,还可以使用类似访问数组元素的方式访问元组元素,只不过不是用[],而是用 . 加索引的方式(索引也是从 0 开始):

let tup = (23.2, 27, 'a');
println!("{}", tup.1);	// 输出:27

特殊的元组

当元组中只有一个元素时(即元组长度是 1),唯一的元素后面必须加上逗号:

let tup = (2,);	// 逗号不能少,否则会提示你,单个值应该去掉小括号。这是避免将小括号当做计算的优先级

自然,模式匹配解构元组时,也必须有逗号。

如果元组没有元素呢?即空元组。看下面的代码:

fn main() {
    let result = test_tuple();
    println!("{:?}", result);
}

fn test_tuple() {
    println!("test empty tuple");
}

你猜打印 result 是啥?

擦,竟然是 (),即空元组。而且 Rust 给它专门取了一个名字:单元类型(unit type),也就是说,() 叫单元类型,它有一个唯一值:空元组 ()。而且,因为没有任何元素,Rust 将其归为变量类型。

还嫌 Rust 不够复杂吗?就叫空元组不行吗?非得搞一个单元类型,这么奇怪的类型。。。

为了避免复杂性,我觉得大家将其理解为空元组即可。至于为什么这里会返回空元组,在函数部分会讲解。

注意:() 是不占空间的,这和 Go 中的空结构体类似。

02 数组

Rust 中的数组和 Go 中的类似,是不可变的,由元素类型和长度确定,且长度必须是编译期常量。Rust 中,数组类型标记为 [T; size]。数组字面值使用 [] 表示:

同样会进行类型推断(包括长度)(这里推断出 a 的类型是 [i32; 4]),也可以显示进行类型注解:

let a: [i8; 4] = [1, 2, 3, 4];

相比较而言,Rust 创建数组比 Go 简单,它和 PHP 这样的动态语言类似。在 Go 中一般这样创建数组:

// Go 语言
a := [...]int{1, 2, 3, 4}

也就是说,Go 中创建数组是,类型信息不能少,没法跟 Rust 一样进行类型推断。

除了上面的初始化方法,Rust 中还可以这样简单的初始化:

let a = [-1; 4];	// 4 个元素都是 -1

Rust 变量必须初始化后才能使用,而 Go 语言中,变量会有默认值。所以,Go 中可以简单的定义一个数组,然后使用默认的初始值。如:

// Go 语言
var a [4]int		// a 的值是:[0 0 0 0]

此外,Rust 中数组总是分配在栈中的,因此可以认为数组是「值类型」,和 Go 一样,我们不应该直接传递数组,而应该和 Go 一样,使用 slice。

03 切片(slice)

Rust 中的切片和 Go 中的切片意思一样,表示对数组部分元素的引用。但和 Go 不同的是,Rust 的切片没有容量的概念,只有一个指向数据的指针和切片的长度。Rust 中切片的类型标记为 &[T],即对数组进行引用(&)就是切片。

Go 语言中有直接创建切片的语法(比如 make),但 Rust 中没有,它必须依赖数组或 Vec(以后讲解),通过引用来创建。

let xs = [1, 2, 3, 4, 5];
let slice = &xs;

既然切片是数组元素的片段引用,那如何引用部分片段呢?

在 Go 中是这么做的:

var arr = [...]int{1, 2, 3, 4}
var slice1 = arr[:]			// 结果是 [1 2 3 4],全部元素
var slice2 = arr[1:3]		// 结果是 [2 3]
var slice3 = arr[:3]		// 结果是 [1 2 3]
var slice4 = arr[1:]		// 结果是 [2 3 4]

而在 Rust 中是这么做的:(结果和上面一样)

let arr = [1, 2, 3, 4];
let slice1 = &arr[..];
let slice2 = &arr[1..3];
let slice3 = &arr[..3];
let slice4 = &arr[1..];

看到不同了吗?

  • Rust 中生成切片,需要引用(&);
  • Go 中使用 : 来引用片段;而 Rust 使用 ..

相同的点是,都可以省略起始或终止位置,或都省略。

关于 .. 以后还会讲到

切片类型的方法(也适用于数组)

在 Rust 中,一切类型都有实现一些 trait,包括上一节的标量类型(用面向对象来讲,一切皆对象)。现在先不探讨 trait,着重看看 len 方法。具体参考标准库文档:https://doc.rust-lang.org/std/primitive.slice.html

1)len:计算长度

数组或切片有一个 len() 方法可以计算长度。

pub const fn len(&self) -> usize

// 具体使用
let arr = [1, 2, 3];
assert_eq!(arr.len(), 3);		// assert_eq 和 println 一样,是一个宏,用来断言

而 Go 语言中,使用 len(arr) 的形式,len 是内置函数。

不过,关于 len 还有一些细小的点。看下面的 Go 代码,你觉得有问题吗?

var arr = [...]int{1, 2, 3, 4}
var slice = arr[:]

var arr2 [len(arr)]int
var arr3 [len(slice)]int

在 Go 中,要求数组长度要求是编译期常量。len(arr) 是编译期常量,而 len(slice) 却不是,因为 slice 的长度是可变的。所以,以上代码 arr2 正确,arr3 编译错误。

那 Rust 中是怎么样的呢?

let arr = [1, 2, 3, 4];
let slice = &arr[..];

let arr2 = [0;arr.len()];
let arr3 = [0;slice.len()];

arr2 和 arr3 都编译错误。arr3 错误可以理解,为什么 arr2 也不行呢?

根据编译器提示,怎么修改 arr2 就可以了:

const ARR:[i32; 4] = [1, 2, 3, 4];
let arr2 = [0; ARR.len()];

也就是说必须是数组常量。。。但数组本身不就是不可变的吗?非得定义成常量,多此一举?据说,Rust 有可能将数组改成可变的。。。有了切片,为啥还要把数组搞这么复杂?!

2)其他方法

  • is_empty:判断数组或切片是否为空
  • first:获取第一个元素
  • last:获取最后一个元素
  • 。。。

first 和 last 有什么用?为啥不直接通过下标获取?

  • last 的存在,使得我们不需要先调用 len 获取长度来间接获取最后一个元素。
  • 而 first 的存在,使得我们不需要先判断是否为空。

不过,因为存在数组或切片为空的情况,因此 first 和 last 返回的都是 Opiton 类型。关于该类型后续再讲。

04 小结

我们用两篇讲解了 Rust 中的数据类型,同时和 Go 的数据类型进行了对比。但 Rust 中的数据类型不止这些,还有其他类型,我们以后再讲,包括通过标准库定义的数据类型。

再强调一次,本系列教程的目标是让大家学习尽可能不被劝退,因此有些特别复杂但我认为可以不用的,就不会介绍。关于 Rust 中的 primitive type 可以在标准库文档找到,以及每个类型的方法。https://doc.rust-lang.org/std/index.html#primitives