Rust实战系列-基本语法

本文详细介绍了Rust编程语言的基本语法,包括编译单文件、数字类型、迭代器、控制流、函数定义、项目实战——渲染Mandelbrot集等核心概念。文中通过实例代码详细讲解了Rust的变量、类型转换、函数、控制结构、泛型函数、字符串处理等方面,旨在帮助读者深入理解Rust的编程思想和用法。
摘要由CSDN通过智能技术生成

🚀 优质资源分享 🚀

学习路线指引(点击解锁) 知识定位 人群定位
🧡 Python实战微信订餐小程序 🧡 进阶级 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
💛Python量化交易实战💛 入门级 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

本文是《Rust in action》学习总结系列的第二部分,更多内容请看已发布文章:

一、Rust实战系列-Rust介绍

主要介绍 Rust 的语法、基本类型和数据结构,通过实现一个简单版 grep 命令行工具,来理解 Rust 独有的特性。

1. 编译单文件

编译器负责将源代码编译成机器码,使其成为可运行的程序,Rust 的编译器是 rustc,下面是一个最简单的 Rust 源代码:

fn main() {
 println!("ok")
}



如果想通过 rustc 直接编译单个文件,需要满足以下要求:

  • 文件必须包括一个 main() 函数
  • 在命令行执行 rustc 文件名 对单文件进行编译

对于大型 Rust 项目文件,使用 cargo 进行管理,如果想观察 rustc 的编译过程,只需要添加 -v 参数。

接下来通过简单的示例理解函数和变量的使用:

fn main() {
 let a = 10; //<1>
 let b: i32 = 20; //<2>
 let c = 30i32; //<3>
 let d = 30\_i32; //<4>
 let e = add(add(a, b), add(c, d));

 println!("( a + b ) + ( c + d ) = {}", e);
}

fn add(i: i32, j: i32) -> i32 { //<5>
 i + j //<6>
}



  1. 类型可以由编译器推断
  2. 也可以在声明变量的时候指定类型
  3. 可以直接在数值后面指定类型注解 30i32
  4. 数值后面可以添加下划线,增加可读性,对功能没影响
  5. 定义函数的时候需要指定参数和返回值的类型
  6. 函数默认返回最后一个表达式的值,所以不用写 return 语句

⚠️ 注意:如果在 add 函数的 i + j 之后添加 ; 将会改变语义,使得函数返回空值**()**而不是 i32 类型。

第一行, fn 关键字表示函数定义的开始,Rust 程序的入口是 main 函数,该函数不接受参数,也没有返回值,随后的代码块用花括号进行标识。

第二行,使用 let 关键字声明变量绑定,默认情况下,变量是不可修改的,这是和其他编程语言不同的地方,每条语句通过分号 ; 标识结束。

第三行,通过变量后的 : i32 指定变量类型,当不希望使用编译器推导的数据类型时非常有用。

第四行,Rust 中的数值可以包含类型注解,同时允许在数字后面使用下划线。

第六行,调用了函数,和其他语言的类似。

第八行,println!() 是一个宏,有点像函数,只是返回代码(code)而不是值,每种数据类型都有对应的转为字符串的方法,println!() 负责调用对应的方法。在 Rust 中,单引号和双引号的含义是不同的,双引号表示字符串,单引号表示字符。此外,Rust 使用 {} 表示占位符,而不是 C 语言中的 %s 等。

第十一行,定义函数,和其他使用显式类型声明的编程语言类似,都好分割参数,变量名后面是数据类型,-> 后面是返回值的类型。

2. 数字类型

  • 整数和小数(浮点数)

Rust 使用相对传统的方式定义整数和小数,操作数字使用算数符号。为了实现不同类型的运算,Rust 支持运算符重载。和其他语言不同的方面主要表现在:

  1. Rust 的数字类型非常多,通常以字节为单位来声明变量能存储值的范围以及能否表示负数。
  2. 不同类型互转需要明确指定类型,Rust 不会自动将 16 位整数转换为 32 位。
  3. Rust 的数字可以有方法,例如,求 24.5 四舍五入后的值,使用 24.5_f32.round(),而不是 round(24.5_f32),这种调用方式必须要指定类型后缀。

以下是一个示例:

fn main() {
 let twenty = 20;
 let twenty\_one: i32 = 21;
 let twenty\_two = 22i32;

 let addition = twenty + twenty\_one + twenty\_two;
 println!("{} + {} + {} = {}", twenty, twenty\_one, twenty\_two, addition);

 let one\_million: i64 = 1\_000\_000; //<1>
 println!("{}", one\_million.pow(2)); //<2>

 let forty\_twos = [ //<3>
 42.0, //<4>
 42f32, //<5>
 42.0\_f32, //<6>
 ];

 println!("{:02}", forty\_twos[0]); //<7>
}



  1. 下划线能增加可读性,编译器会忽略
  2. 数字类型有可调用函数
  3. 创建数字数组,这些数组有相同的类型,用方括号包围
  4. 没有明确注解类型的浮点数可能是 32 位或 64 位,取决于上下文
  5. 浮点数也有类型后缀
  6. 也可以添加下划线
  7. 数组元素可以通过下标索引,从 0 开始
  • 数字的二进制、八进制、十六进制转换

Rust 内置了对数字的支持,可以分别定义二进制、八进制、十六进制的值,在格式化宏 println! 中也是可用的。以下是一个示例:

fn main() {
 let three = 0b11; //<1>
 let thirty = 0o36; //<2>
 let three\_hundred = 0x12C; //<3>

 println!("base 10: {} {} {}", three, thirty, three\_hundred);
 println!("base 2: {:b} {:b} {:b}", three, thirty, three\_hundred);
 println!("base 8: {:o} {:o} {:o}", three, thirty, three\_hundred);
 println!("base 16: {:x} {:x} {:x}", three, thirty, three\_hundred);
}



  1. “0b” 前缀表示二进制数字
  2. “0o” 前缀表示八进制数字
  3. “0x” 前缀表示十六进制数字

Rust 的数字类型:

Rust 的数字类型可以分成以下几类:

  1. 有符号的整数(i)代表负整数和正整数
  2. 无符号整数(u)只表示正整数,最大能表示比有符号整数大一倍的数字
  3. 浮点整数(f)表示实数,具有正无穷、负无穷和 “非数字” 三个特殊值

整数宽度是指该类型在 RAM 和 CPU 中使用的 bits 数,占用更多空间的类型,例如,和 i8 相比,u32 能表示更大的数字,但也会浪费额外的存储空间。

数字类型支持大量比较操作,和其他编程语言类似:

在 Rust 中,不支持直接对不同类型的数字进行比较,需要进行类型转换。以下是一个例子:b as i32

fn main() {
 let a: i32 = 10;
 let b: u16 = 100;

 if a < (b as i32) {
 println!("Ten is less than one hundred.");
 }
}



最安全的做法是将占用内存空间较小的类型转换为较大的类型(例如:将 16 位类型转换为 32 位类型),也可以将 u32 类型转换为 u16 类型,但这种转换存在风险。

⚠️ 注意:类型转换使用错误会导致程序结果不正确,例如,300_i32 作为 i8 类型时值是 44。

使用 as 关键词进行类型转换存在很多限制,也可以使用 Rust 函数进行转换:

use std::convert::TryInto; // <1>

fn main() {
 let a: i32 = 10;
 let b: u16 = 100;

 if a < b.try\_into().unwrap() { // <2>
 println!("Ten is less than one hundred.");
 }
}



  1. 将 try_into() 函数添加在 u16 类型
  2. b.try_into() 返回一个 i32 类型的值,try_into()会在转换出错的时候返回错误信息。(细节在下一章)
  • 浮点危害

对浮点数类型(f32 和 f64)进行比较是一个特别的情况,有两点原因:

  1. 浮点数通常近似于它们所代表的数字,因为浮点类型是以基数 2 来实现的,但我们经常以基数 10 来进行计算,产生了不匹配。
  2. 浮点数能表示非直观语义的值,与整数不同,浮点类型的很多值不能很好地和其他类型一起运算,形式上,只有部分等价关系。

⚠️ 注意:无法用二进制表示浮点数。

在计算机中,浮点数的是通过二进制数学运算实现的,但是通常表示的是十进制的值,这就导致了问题,比如 0.1 并不能直接用二进制表示。Rust 的目标是可用性,以下代码编译和运行不会出错:

fn main() {
 assert!(0.1 + 0.2 == 0.3);
}



⚠️ 注意:当表达式的值不是 true 时,assert! 宏将会使进程退出。

Rust 有容忍机制,允许浮点数之间进行比较,这些机制定义在 f32::EPSILON 和 f64::EPSILON 中。更准确地说,可以更使浮点数比较更接近 Rust 内部工作方式。Rust 编译器将浮点数的比较工作委托给 CPU,浮点运算实际上是在硬件中实现的。

fn main() {
 let result: f32 = 0.1 + 0.1;
 let desired: f32 = 0.2;
 let absolute\_difference = (desired - result).abs();
 assert!(absolute\_difference <= f32::EPSILON);
}



数学上未定义的比较结果:

浮点类型包括 "非数字"值(通常表示为 NaN,在 Rust 中表示为 NAN),这些值表示未定义的数字运算的结果,例如对一个负数进行平方根运算。根据定义,两个 NAN 值绝不相等。以下是示例:

fn main() {
 assert\_eq!(f32::NAN == f32::NAN, false);
}



  • 有理数、复数和其他数字类型

Rust 标准库相对来说精简,没有其他语言中经常用到的类型,例如:

  1. 处理有理数和复数的数学对象
  2. 任意大小的整数和浮点数,用于表示大数和小数
  3. 处理货币的定点小数

要访问特定的数字类型,需要格式化数字 create,Creates 是扩展标准库的可安装包。以下是示例代码,解释了(1)两个复杂数字如何相加,(2.1 + −1.2i) + (11.1 + 22.22i),输出结果为 13.2 + 21i,(2)新的命令,介绍了两种初始化非原始数据类型的方法,一是 Rust 语言提供的语法,二是 new() 静态函数,为了使用方便,很多数字类型都实现了这个方法。静态方法是某个类型的可用方法,但不是类型的实例。(3)如何访问第三方 create(num),调用类型的 new() 方法,使用 . 操作符访问字段。

use num::complex::Complex; //<1>

fn main() {
 let a = Complex { re: 2.1, im: -1.2 }; //<2>
 let b = Complex::new(11.1, 22.2); //<3>
 let result = a + b;

 println!("{} + {}i", result.re, result.im) //<4>
}



  1. use 关键字将 create 导入到当前文件范围,命名空间操作符(::)限制了包含的内容,只需要类型:Complex
  2. 类型不需要构造函数,使用类型名称(Complex)并在大括号 { } 内给它们的字段(re, im)赋值(2.1, -1.2)即可初始化类型
  3. 为了简化,许多语言的类型实现了 new()方法,Rust 语言没有这个约定
  4. num::complex::Complex 类型有两个字段:re 代表实部,im 代表虚部,都可以通过点操作符 . 访问

用 cargo 向项目添加第三方依赖关系的快捷方式:

# 添加cargo-edit命令
cargo install cargo-edit
# 通过命令添加依赖
cargo add regex



会显示第三方依赖拥有的 features:

3. 迭代器

<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值