- Rust 教程
- Rust - 首页
- Rust - 简介
- Rust - 环境搭建
- Rust - HelloWorld 示例
- Rust - 数据类型
- Rust - 变量
- Rust - 常量
- Rust - 字符串
- Rust - 运算符
- Rust - 决策制定
- Rust - 循环
- Rust - 函数
- Rust - 元组
- Rust - 数组
- Rust - 所有权
- Rust - 借用
- Rust - 切片
- Rust - 结构体
- Rust - 枚举
- Rust - 模块
- Rust - 集合
- Rust - 错误处理
- Rust - 泛型
- Rust - 输入输出
- Rust - 文件输入/输出
- Rust - 包管理器
- Rust - 迭代器和闭包
- Rust - 智能指针
- Rust - 并发
- Rust 有用资源
- Rust 快速指南
- Rust - 有用资源
- Rust - 讨论
Rust 快速指南
Rust - 简介
Rust 是一种系统级编程语言,由 Graydon Hoare 开发。Mozilla Labs 后来收购了该项目。
应用编程语言与系统编程语言
像 Java/C# 这样的应用编程语言用于构建软件,这些软件直接为用户提供服务。它们帮助我们构建业务应用程序,如电子表格、文字处理器、Web 应用程序或移动应用程序。
像 C/C++ 这样的系统编程语言用于构建软件和软件平台。它们可以用于构建操作系统、游戏引擎、编译器等。这些编程语言需要高度的硬件交互。
系统和应用编程语言面临两个主要问题:
- 难以编写安全的代码。
- 难以编写多线程代码。
为什么选择 Rust?
Rust 关注三个目标:
- 安全
- 速度
- 并发
该语言旨在以简单的方式开发高度可靠和快速的软件。Rust 可用于编写从高级程序到特定于硬件的程序。
性能
Rust 编程语言在设计上没有垃圾回收器 (GC)。这提高了运行时的性能。
编译时内存安全
使用 Rust 构建的软件可以免受内存问题(如悬空指针、缓冲区溢出和内存泄漏)的影响。
多线程应用程序
Rust 的所有权和内存安全规则提供了无数据竞争的并发。
支持 WebAssembly (WASM)
WebAssembly 有助于在浏览器、嵌入式设备或任何其他地方执行高计算密集型算法。它以原生代码的速度运行。Rust 可以编译成 WebAssembly 以实现快速、可靠的执行。
Rust - 环境搭建
Rust 的安装通过 **rustup** 简化了,rustup 是一个基于控制台的工具,用于管理 Rust 版本和相关工具。
在 Windows 上安装
让我们学习如何在 Windows 上安装 RUST。
在 Windows 上运行 Rust 程序,必须安装 Visual Studio 2013 或更高版本,并包含 C++ 工具。首先,从这里下载 Visual Studio VS 2013 Express
下载并安装适用于 Windows 的 **rustup** 工具。**rustup-init.exe** 可从这里下载:Rust Lang
双击 **rustup-init.exe** 文件。单击后,将出现以下屏幕。
按 Enter 进行默认安装。安装完成后,将出现以下屏幕。
从安装屏幕可以清楚地看到,Rust 相关文件存储在以下文件夹中:
C:\Users\{PC}\.cargo\bin
文件夹的内容为:
cargo-fmt.exe cargo.exe rls.exe rust-gdb.exe rust-lldb.exe rustc.exe // this is the compiler for rust rustdoc.exe rustfmt.exe rustup.exe
**Cargo** 是 Rust 的包管理器。要验证是否已安装 **cargo**,请执行以下命令:
C:\Users\Admin>cargo -V cargo 1.29.0 (524a578d7 2018-08-05)
Rust 的编译器是 **rustc**。要验证编译器版本,请执行以下命令:
C:\Users\Admin>cargo -V cargo 1.29.0 (524a578d7 2018-08-05)
在 Linux/Mac 上安装
要在 Linux 或 macOS 上安装 **rustup**,请打开终端并输入以下命令。
$ curl https://sh.rustup.rs -sSf | sh
该命令下载一个脚本并启动 **rustup** 工具的安装,该工具会安装最新稳定版本的 Rust。系统可能会提示您输入密码。如果安装成功,将显示以下行:
Rust is installed now. Great!
安装脚本会在您下次登录后自动将 Rust 添加到系统 PATH 中。要立即开始使用 Rust 而不是重新启动终端,请在 shell 中运行以下命令以手动将 Rust 添加到系统 PATH 中:
$ source $HOME/.cargo/env
或者,您可以将以下行添加到您的 ~/.bash_profile 中:
$ export PATH="$HOME/.cargo/bin:$PATH"
**注意** - 当您尝试编译 Rust 程序并收到指示链接器无法执行的错误时,表示您的系统上未安装链接器,您需要手动安装一个。
使用 Tutorials Point Coding Ground 进行 RUST 开发
读取-评估-打印循环 (REPL) 是一种易于使用的交互式 shell,用于编译和执行计算机程序。如果您想在浏览器中在线编译和执行 Rust 程序,请使用 Tutorialspoint 的 Coding Ground。
Rust - HelloWorld 示例
本章通过 **HelloWorld** 示例解释了 Rust 语言的基本语法。
创建 **HelloWorld-App** 文件夹,并在终端中导航到该文件夹
C:\Users\Admin>mkdir HelloWorld-App C:\Users\Admin>cd HelloWorld-App C:\Users\Admin\HelloWorld-App>
要创建 Rust 文件,请执行以下命令:
C:\Users\Admin\HelloWorld-App>notepad Hello.rs
Rust 程序文件扩展名为 .rs。以上命令创建一个空文件 **Hello.rs** 并在记事本中打开它。将下面给出的代码添加到此文件中:
fn main(){ println!("Rust says Hello to TutorialsPoint !!"); }
以上程序定义了一个函数 main fn main()。fn 关键字用于定义函数。main() 是一个预定义函数,充当程序的入口点。println! 是 Rust 中的预定义宏。它用于将字符串(此处为 Hello)打印到控制台。宏调用始终用感叹号 - ! 标记。
使用 **rustc** 编译 **Hello.rs** 文件。
C:\Users\Admin\HelloWorld-App>rustc Hello.rs
程序成功编译后,会生成一个可执行文件(file_name.exe)。要验证是否生成了 .exe 文件,请执行以下命令。
C:\Users\Admin\HelloWorld-App>dir //lists the files in folder Hello.exe Hello.pdb Hello.rs
- 执行 Hello.exe 文件并验证输出。
什么是宏?
Rust 提供了一个强大的宏系统,允许元编程。正如您在前面的示例中看到的,宏看起来像函数,只是它们的名称以感叹号(!)结尾,但它们不是生成函数调用,而是扩展成源代码,这些源代码与程序的其余部分一起编译。因此,与函数不同,它们为程序提供了更多运行时功能。宏是函数的扩展版本。
使用 println! 宏 - 语法
println!(); // prints just a newline println!("hello ");//prints hello println!("format {} arguments", "some"); //prints format some arguments
Rust 中的注释
注释是提高程序可读性的方法。注释可用于包含有关程序的其他信息,例如代码作者、有关函数/构造的提示等。编译器会忽略注释。
Rust 支持以下类型的注释:
单行注释 ( // ) - // 和行尾之间的任何文本都被视为注释
多行注释 (/* */) - 这些注释可以跨越多行。
示例
//this is single line comment /* This is a Multi-line comment */
在线执行
Rust 程序可以通过 Tutorialspoint 的 Coding Ground 在线执行。在编辑器选项卡中编写 HelloWorld 程序,然后单击执行以查看结果。
Rust - 数据类型
类型系统表示语言支持的不同类型的值。类型系统会在程序存储或操作提供的值之前检查其有效性。这确保了代码按预期运行。类型系统还有助于更丰富的代码提示和自动文档。
Rust 是一种静态类型语言。Rust 中的每个值都具有一定的数据类型。编译器可以根据分配给变量的值自动推断变量的数据类型。
声明变量
使用 let 关键字声明变量。
fn main() { let company_string = "TutorialsPoint"; // string type let rating_float = 4.5; // float type let is_growing_boolean = true; // boolean type let icon_char = '♥'; //unicode character type println!("company name is:{}",company_string); println!("company rating on 5 is:{}",rating_float); println!("company is growing :{}",is_growing_boolean); println!("company icon is:{}",icon_char); }
在以上示例中,变量的数据类型将从分配给它们的值中推断出来。例如,Rust 会将字符串数据类型分配给变量 company_string,将浮点数数据类型分配给 rating_float 等。
println! 宏接受两个参数:
- 特殊语法 { },它是占位符
- 变量名或常量
占位符将被变量的值替换
以上代码片段的输出将是:
company name is: TutorialsPoint company rating on 5 is:4.5 company is growing: true company icon is: ♥
标量类型
标量类型表示单个值。例如,10、3.14、'c'。Rust 有四种主要的标量类型。
- 整数
- 浮点数
- 布尔值
- 字符
我们将在后续章节中了解每种类型。
整数
整数是没有小数部分的数字。简单地说,整数数据类型用于表示整数。
整数可以进一步分类为有符号和无符号。有符号整数可以存储负值和正值。无符号整数只能存储正值。下面给出了整数类型的详细描述:
序号 | 大小 | 有符号 | 无符号 |
---|---|---|---|
1 | 8 位 | i8 | u8 |
2 | 16 位 | i16 | u16 |
3 | 32 位 | i32 | u32 |
4 | 64 位 | i64 | u64 |
5 | 128 位 | i128 | u128 |
6 | 体系结构 | isize | usize |
整数的大小可以是 体系结构。这意味着数据类型的大小将从机器的 体系结构 派生。大小为 体系结构 的整数在 x86 机器上为 32 位,在 x64 机器上为 64 位。体系结构整数主要用于索引某种集合。
说明
fn main() { let result = 10; // i32 by default let age:u32 = 20; let sum:i32 = 5-15; let mark:isize = 10; let count:usize = 30; println!("result value is {}",result); println!("sum is {} and age is {}",sum,age); println!("mark is {} and count is {}",mark,count); }
输出将如下所示:
result value is 10 sum is -10 and age is 20 mark is 10 and count is 30
如果将 age 的值替换为浮点数,以上代码将返回编译错误。
整数范围
每个有符号变体可以存储从 -(2^(n-1) 到 2^(n-1) -1 的数字,其中 n 是变体使用的位数。例如,i8 可以存储从 -(2^7) 到 2^7 -1 的数字 - 在这里我们将 n 替换为 8。
每个无符号变体可以存储从 0 到 (2^n)-1 的数字。例如,u8 可以存储从 0 到 (2^8)-1 的数字,等于 0 到 255。
整数溢出
当分配给整数变量的值超过 Rust 为数据类型定义的范围时,就会发生整数溢出。让我们用一个例子来理解这一点:
fn main() { let age:u8 = 255; // 0 to 255 only allowed for u8 let weight:u8 = 256; //overflow value is 0 let height:u8 = 257; //overflow value is 1 let score:u8 = 258; //overflow value is 2 println!("age is {} ",age); println!("weight is {}",weight); println!("height is {}",height); println!("score is {}",score); }
无符号 u8 变量的有效范围是 0 到 255。在上面的示例中,变量被赋予了大于 255 的值(Rust 中整数变量的上限)。执行时,上述代码将返回一个警告 - **警告 - 字面量超出 u8 范围**,用于 weight、height 和 score 变量。超过 255 的溢出值将从 0、1、2 等开始。最终没有警告的输出如下所示:
age is 255 weight is 0 height is 1 score is 2
浮点数
Rust 中的浮点数据类型可以分为 **f32** 和 **f64**。f32 类型是单精度浮点数,f64 是双精度浮点数。默认类型是 f64。请考虑以下示例以了解更多关于浮点数据类型的知识。
fn main() { let result = 10.00; //f64 by default let interest:f32 = 8.35; let cost:f64 = 15000.600; //double precision println!("result value is {}",result); println!("interest is {}",interest); println!("cost is {}",cost); }
输出将如下所示:
interest is 8.35 cost is 15000.6
自动类型转换
Rust 中不允许自动类型转换。请考虑以下代码片段。一个整数值被赋予浮点变量 **interest**。
fn main() { let interest:f32 = 8; // integer assigned to float variable println!("interest is {}",interest); }
编译器抛出一个 **类型不匹配错误**,如下所示。
error[E0308]: mismatched types --> main.rs:2:22 | 2 | let interest:f32=8; | ^ expected f32, found integral variable | = note: expected type `f32` found type `{integer}` error: aborting due to previous error(s)
数字分隔符
为了方便阅读大数字,我们可以使用视觉分隔符 _ 下划线来分隔数字。即 50,000 可以写成 50_000。这在下面的示例中显示。
fn main() { let float_with_separator = 11_000.555_001; println!("float value {}",float_with_separator); let int_with_separator = 50_000; println!("int value {}",int_with_separator); }
输出如下所示:
float value 11000.555001 int value 50000
布尔值
布尔类型有两个可能的值 - true 或 false。使用 **bool** 关键字声明布尔变量。
说明
fn main() { let isfun:bool = true; println!("Is Rust Programming Fun ? {}",isfun); }
上述代码的输出将是:
Is Rust Programming Fun ? true
字符
Rust 中的字符数据类型支持数字、字母、Unicode 和特殊字符。使用 **char** 关键字声明字符数据类型的变量。Rust 的 char 类型表示 Unicode 标量值,这意味着它可以表示的不仅仅是 ASCII。Unicode 标量值的范围从 **U+0000** 到 **U+D7FF** 和 **U+E000** 到 **U+10FFFF**(包括)。
让我们考虑一个示例以了解更多关于字符数据类型的知识。
fn main() { let special_character = '@'; //default let alphabet:char = 'A'; let emoji:char = '😁'; println!("special character is {}",special_character); println!("alphabet is {}",alphabet); println!("emoji is {}",emoji); }
上述代码的输出将是:
special character is @ alphabet is A emoji is 😁
Rust - 变量
变量是程序可以操作的命名存储。简单来说,变量帮助程序存储值。Rust 中的变量与特定数据类型相关联。数据类型决定了变量内存的大小和布局、可以在该内存中存储的值的范围以及可以对变量执行的操作集。
变量命名规则
在本节中,我们将学习变量命名的不同规则。
变量名可以由字母、数字和下划线字符组成。
它必须以字母或下划线开头。
大小写字母是不同的,因为 Rust 区分大小写。
语法
在 Rust 中声明变量时,数据类型是可选的。数据类型是从赋予变量的值中推断出来的。
声明变量的语法如下所示。
let variable_name = value; // no type specified let variable_name:dataType = value; //type specified
说明
fn main() { let fees = 25_000; let salary:f64 = 35_000.00; println!("fees is {} and salary is {}",fees,salary); }
上述代码的输出将是 fees is 25000 and salary is 35000。
不可变
默认情况下,变量在 Rust 中是不可变的 - 只读。换句话说,一旦将值绑定到变量名,就不能更改变量的值。
让我们用一个例子来理解这一点。
fn main() { let fees = 25_000; println!("fees is {} ",fees); fees = 35_000; println!("fees changed is {}",fees); }
输出将如下所示:
error[E0384]: re-assignment of immutable variable `fees` --> main.rs:6:3 | 3 | let fees = 25_000; | ---- first assignment to `fees` ... 6 | fees=35_000; | ^^^^^^^^^^^ re-assignment of immutable variable error: aborting due to previous error(s)
错误消息指示了错误的原因 - 您不能将值两次赋予不可变变量 fees。这是 Rust 允许程序员编写代码并利用其安全性与易并发性的众多方式之一。
可变
变量默认情况下是不可变的。在变量名前缀 **mut** 关键字以使其可变。可变变量的值可以更改。
声明可变变量的语法如下所示:
let mut variable_name = value; let mut variable_name:dataType = value; Let us understand this with an example fn main() { let mut fees:i32 = 25_000; println!("fees is {} ",fees); fees = 35_000; println!("fees changed is {}",fees); }
代码片段的输出如下所示:
fees is 25000 fees changed is 35000
Rust - 常量
常量表示不能更改的值。如果您声明了一个常量,则其值绝不会更改。用于使用常量的关键字是 **const**。常量必须显式类型化。以下是声明常量的语法。
const VARIABLE_NAME:dataType = value;
Rust 常量命名约定
常量的命名约定与变量的命名约定类似。常量名称中的所有字符通常都大写。与声明变量不同,**let** 关键字不用于声明常量。
我们在下面的示例中使用了 Rust 中的常量:
fn main() { const USER_LIMIT:i32 = 100; // Declare a integer constant const PI:f32 = 3.14; //Declare a float constant println!("user limit is {}",USER_LIMIT); //Display value of the constant println!("pi value is {}",PI); //Display value of the constant }
常量与变量
在本节中,我们将学习常量和变量之间的区别因素。
常量使用 **const** 关键字声明,而变量使用 **let** 关键字声明。
变量声明可以选择具有数据类型,而常量声明必须指定数据类型。这意味着 const USER_LIMIT=100 将导致错误。
使用 **let** 关键字声明的变量默认情况下是不可变的。但是,您可以选择使用 **mut** 关键字对其进行修改。常量是不可变的。
常量只能设置为常量表达式,而不是设置为函数调用的结果或将在运行时计算的任何其他值。
常量可以在任何作用域中声明,包括全局作用域,这使得它们对于代码的许多部分需要了解的值很有用。
变量和常量的遮蔽
Rust 允许程序员声明同名的变量。在这种情况下,新变量会覆盖之前的变量。
让我们用一个例子来理解这一点。
fn main() { let salary = 100.00; let salary = 1.50 ; // reads first salary println!("The value of salary is :{}",salary); }
上述代码声明了两个名为 salary 的变量。第一个声明被赋予 100.00,而第二个声明被赋予 1.50。第二个变量在显示输出时会遮蔽或隐藏第一个变量。
输出
The value of salary is :1.50
Rust 在遮蔽时支持不同数据类型的变量。
请考虑以下示例。
代码声明了两个名为 uname 的变量。第一个声明被赋予字符串值,而第二个声明被赋予整数。len 函数返回字符串值中的字符总数。
fn main() { let uname = "Mohtashim"; let uname = uname.len(); println!("name changed to integer : {}",uname); }
输出
name changed to integer: 9
与变量不同,常量不能被遮蔽。如果上述程序中的变量被替换为常量,编译器将抛出一个错误。
fn main() { const NAME:&str = "Mohtashim"; const NAME:usize = NAME.len(); //Error : `NAME` already defined println!("name changed to integer : {}",NAME); }
Rust - 字符串
Rust 中的字符串数据类型可以分为以下几种:
字符串字面量(&str)
字符串对象(String)
字符串字面量
字符串字面量 (&str) 用于在编译时已知字符串值的情况。字符串字面量是一组字符,硬编码到变量中。例如,let company="Tutorials Point"。字符串字面量位于模块 std::str 中。字符串字面量也称为字符串切片。
以下示例声明了两个字符串字面量 - company 和 location。
fn main() { let company:&str="TutorialsPoint"; let location:&str = "Hyderabad"; println!("company is : {} location :{}",company,location); }
字符串字面量默认情况下是静态的。这意味着字符串字面量保证在整个程序的持续时间内有效。我们还可以像下面这样显式地将变量指定为静态的:
fn main() { let company:&'static str = "TutorialsPoint"; let location:&'static str = "Hyderabad"; println!("company is : {} location :{}",company,location); }
上述程序将生成以下输出:
company is : TutorialsPoint location :Hyderabad
字符串对象
String 对象类型在标准库中提供。与字符串字面量不同,字符串对象类型不是核心语言的一部分。它在标准库中定义为公共结构 pub struct String。String 是一个可增长的集合。它是可变的且 UTF-8 编码的类型。String 对象类型可用于表示在运行时提供的字符串值。字符串对象分配在堆中。
语法
要创建 String 对象,我们可以使用以下任何语法:
String::new()
上述语法创建一个空字符串
String::from()
这将创建一个带有某些默认值的字符串,作为参数传递给 from() 方法。
以下示例说明了 String 对象的使用。
fn main(){ let empty_string = String::new(); println!("length is {}",empty_string.len()); let content_string = String::from("TutorialsPoint"); println!("length is {}",content_string.len()); }
上述示例创建了两个字符串 - 使用 new 方法创建的空字符串对象和使用 from 方法从字符串字面量创建的字符串对象。
输出如下所示:
length is 0 length is 14
常用方法 - 字符串对象
序号 | 方法 | 签名 | 描述 |
---|---|---|---|
1 | new() | pub const fn new() → String | 创建一个新的空 String。 |
2 | to_string() | fn to_string(&self) → String | 将给定值转换为 String。 |
3 | replace() | pub fn replace<'a, P>(&'a self, from: P, to: &str) → String | 用另一个字符串替换模式的所有匹配项。 |
4 | as_str() | pub fn as_str(&self) → &str | 提取包含整个字符串的字符串切片。 |
5 | push() | pub fn push(&mut self, ch: char) | 将给定的 char 附加到此 String 的末尾。 |
6 | push_str() | pub fn push_str(&mut self, string: &str) | 将给定的字符串切片附加到此 String 的末尾。 |
7 | len() | pub fn len(&self) → usize | 返回此 String 的长度(以字节为单位)。 |
8 | trim() | pub fn trim(&self) → &str | 返回删除了前导和尾随空格的字符串切片。 |
9 | split_whitespace() | pub fn split_whitespace(&self) → SplitWhitespace | 按空格拆分字符串切片并返回迭代器。 |
10 | split() | pub fn split<'a, P>(&'a self, pat: P) → Split<'a, P> , where P is pattern can be &str, char, or a closure that determines the split. | 返回此字符串切片子字符串的迭代器,这些子字符串由模式匹配的字符分隔。 |
11 | chars() | pub fn chars(&self) → Chars | 返回字符串切片字符的迭代器。 |
图示:new()
使用 new() 方法创建一个空字符串对象,并将它的值设置为 hello。
fn main(){ let mut z = String::new(); z.push_str("hello"); println!("{}",z); }
输出
上述程序生成以下输出:
hello
图示:to_string()
要访问 String 对象的所有方法,请使用 to_string() 函数将字符串字面量转换为对象类型。
fn main(){ let name1 = "Hello TutorialsPoint , Hello!".to_string(); println!("{}",name1); }
输出
上述程序生成以下输出:
Hello TutorialsPoint , Hello!
图示:replace()
replace() 函数接受两个参数 - 第一个参数是要搜索的字符串模式,第二个参数是要替换的新值。在上面的示例中,Hello 在 name1 字符串中出现了两次。
replace 函数将字符串 Hello 的所有出现替换为 Howdy。
fn main(){ let name1 = "Hello TutorialsPoint , Hello!".to_string(); //String object let name2 = name1.replace("Hello","Howdy"); //find and replace println!("{}",name2); }
输出
上述程序生成以下输出:
Howdy TutorialsPoint , Howdy!
图示:as_str()
as_str() 函数提取包含整个字符串的字符串切片。
fn main() { let example_string = String::from("example_string"); print_literal(example_string.as_str()); } fn print_literal(data:&str ){ println!("displaying string literal {}",data); }
输出
上述程序生成以下输出:
displaying string literal example_string
图示:push()
push() 函数将给定的 char 附加到此 String 的末尾。
fn main(){ let mut company = "Tutorial".to_string(); company.push('s'); println!("{}",company); }
输出
上述程序生成以下输出:
Tutorials
图示:push_str()
push_str() 函数将给定的字符串切片附加到 String 的末尾。
fn main(){ let mut company = "Tutorials".to_string(); company.push_str(" Point"); println!("{}",company); }
输出
上述程序生成以下输出:
Tutorials Point
图示:len()
len() 函数返回字符串中的字符总数(包括空格)。
fn main() { let fullname = " Tutorials Point"; println!("length is {}",fullname.len()); }
输出
上述程序生成以下输出:
length is 20
图示:trim()
trim() 函数删除字符串中前导和尾随空格。注意,此函数不会删除内联空格。
fn main() { let fullname = " Tutorials Point \r\n"; println!("Before trim "); println!("length is {}",fullname.len()); println!(); println!("After trim "); println!("length is {}",fullname.trim().len()); }
输出
上述程序生成以下输出:
Before trim length is 24 After trim length is 15
图示:split_whitespace()
split_whitespace() 将输入字符串拆分为不同的字符串。它返回一个迭代器,因此我们像下面这样遍历标记:
fn main(){ let msg = "Tutorials Point has good t utorials".to_string(); let mut i = 1; for token in msg.split_whitespace(){ println!("token {} {}",i,token); i+=1; } }
输出
token 1 Tutorials token 2 Point token 3 has token 4 good token 5 tutorials
图示:split() 字符串
split() 字符串方法返回字符串切片子字符串的迭代器,这些子字符串由模式匹配的字符分隔。split() 方法的限制是结果不能存储以供以后使用。collect 方法可用于将 split() 返回的结果存储为向量。
fn main() { let fullname = "Kannan,Sudhakaran,Tutorialspoint"; for token in fullname.split(","){ println!("token is {}",token); } //store in a Vector println!("\n"); let tokens:Vec<&str>= fullname.split(",").collect(); println!("firstName is {}",tokens[0]); println!("lastname is {}",tokens[1]); println!("company is {}",tokens[2]); }
以上示例在遇到逗号(,)时分割字符串fullname。
输出
token is Kannan token is Sudhakaran token is Tutorialspoint firstName is Kannan lastname is Sudhakaran company is Tutorialspoint
示例:chars()
可以使用 chars 方法访问字符串中的单个字符。让我们来看一个例子来理解这一点。
fn main(){ let n1 = "Tutorials".to_string(); for n in n1.chars(){ println!("{}",n); } }
输出
T u t o r i a l s
使用 + 运算符连接字符串
可以将一个字符串值附加到另一个字符串。这称为连接或插值。字符串连接的结果是一个新的字符串对象。+ 运算符在内部使用 add 方法。add 函数的语法有两个参数。第一个参数是 self – 字符串对象本身,第二个参数是第二个字符串对象的引用。如下所示:
//add function add(self,&str)->String { // returns a String object }
示例:字符串连接
fn main(){ let n1 = "Tutorials".to_string(); let n2 = "Point".to_string(); let n3 = n1 + &n2; // n2 reference is passed println!("{}",n3); }
输出将如下所示
TutorialsPoint
示例:类型转换
以下示例演示如何将数字转换为字符串对象:
fn main(){ let number = 2020; let number_as_string = number.to_string(); // convert number to string println!("{}",number_as_string); println!("{}",number_as_string=="2020"); }
输出将如下所示
2020 true
示例:Format! 宏
将两个字符串对象加在一起的另一种方法是使用名为 format 的宏函数。Format! 的用法如下所示。
fn main(){ let n1 = "Tutorials".to_string(); let n2 = "Point".to_string(); let n3 = format!("{} {}",n1,n2); println!("{}",n3); }
输出将如下所示
Tutorials Point
Rust - 运算符
运算符定义将在数据上执行的一些函数。运算符作用于的数据称为操作数。考虑以下表达式:
7 + 5 = 12
这里,值 7、5 和 12 是操作数,而 + 和 = 是运算符。
Rust 中的主要运算符可以分类为:
- 算术运算符
- 位运算符
- 比较运算符
- 逻辑运算符
- 位运算符
- 条件运算符
算术运算符
假设变量 a 和 b 中的值分别为 10 和 5。
序号 | 运算符 | 描述 | 示例 |
---|---|---|---|
1 | +(加法) | 返回操作数的和 | a+b 为 15 |
2 | -(减法) | 返回值的差 | a-b 为 5 |
3 | *(乘法) | 返回值的积 | a*b 为 50 |
4 | /(除法) | 执行除法运算并返回商 | a / b 为 2 |
5 | %(取模) | 执行除法运算并返回余数 | a % b 为 0 |
注意:Rust 中不支持 ++ 和 -- 运算符。
关系运算符
关系运算符测试或定义两个实体之间关系的类型。关系运算符用于比较两个或多个值。关系运算符返回布尔值:true 或 false。
假设 A 的值为 10,B 的值为 20。
序号 | 运算符 | 描述 | 示例 |
---|---|---|---|
1 | > | 大于 | (A > B) 为 False |
2 | < | 小于 | (A < B) 为 True |
3 | >= | 大于或等于 | (A >= B) 为 False |
4 | <= | 小于或等于 | (A <= B) 为 True |
5 | == | 等于 | (A == B) 为 False |
6 | != | 不等于 | (A != B) 为 True |
逻辑运算符
逻辑运算符用于组合两个或多个条件。逻辑运算符也返回布尔值。假设变量 A 的值为 10,B 的值为 20。
序号 | 运算符 | 描述 | 示例 |
---|---|---|---|
1 | && (与) | 只有当所有指定的表达式都返回 true 时,该运算符才返回 true | (A > 10 && B > 10) 为 False |
2 | ||(或) | 如果指定的表达式中至少有一个返回 true,则该运算符返回 true | (A > 10 || B >10) 为 True |
3 | ! (非) | 该运算符返回表达式的结果的反值。例如:!(>5) 返回 false | !(A >10 ) 为 True |
位运算符
假设变量 A = 2,B = 3。
序号 | 运算符 | 描述 | 示例 |
---|---|---|---|
1 | & (按位与) | 它对整数参数的每个位执行布尔 AND 运算。 | (A & B) 为 2 |
2 | | (按位或) | 它对整数参数的每个位执行布尔 OR 运算。 | (A | B) 为 3 |
3 | ^ (按位异或) | 它对整数参数的每个位执行布尔异或运算。异或意味着操作数一为真或操作数二为真,但不能两者都为真。 | (A ^ B) 为 1 |
4 | ! (按位取反) | 它是一个一元运算符,通过反转操作数中的所有位来操作。 | (!B) 为 -4 |
5 | << (左移) | 它将第一个操作数中的所有位向左移动第二个操作数中指定的位数。新位用零填充。将值左移一位相当于将其乘以 2,左移两位相当于将其乘以 4,依此类推。 | (A << 1) 为 4 |
6 | >> (右移) | 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。 | (A >> 1) 为 1 |
7 | >>> (带零的右移) | 此运算符与 >> 运算符类似,只是左移的位始终为零。 | (A >>> 1) 为 1 |
Rust - 决策制定
决策结构要求程序员指定一个或多个要由程序评估或测试的条件,以及如果条件确定为真则要执行的语句或语句,以及可选地,如果条件确定为假则要执行的其他语句。
下面是大多数编程语言中常见的决策结构的通用形式:
序号 | 语句和描述 |
---|---|
1 | if 语句 if 语句由一个布尔表达式后跟一个或多个语句组成。 |
2 | if...else 语句 if 语句后面可以跟一个可选的 else 语句,当布尔表达式为假时执行该语句。 |
3 | else...if 和嵌套 if 语句 可以在另一个 if 或 else if 语句中使用一个 if 或 else if 语句。 |
4 | match 语句 match 语句允许将变量与值列表进行比较。 |
If 语句
if…else 结构在执行代码块之前评估条件。
语法
if boolean_expression { // statement(s) will execute if the boolean expression is true }
如果布尔表达式计算结果为真,则将执行 if 语句内的代码块。如果布尔表达式计算结果为假,则将执行 if 语句结束后的第一组代码(在右花括号之后)。
fn main(){ let num:i32 = 5; if num > 0 { println!("number is positive") ; } }
以上示例将打印number is positive,因为 if 块指定的条件为真。
if else 语句
if 后面可以跟一个可选的else块。如果 if 语句测试的布尔表达式计算结果为假,则将执行 else 块。
语法
if boolean_expression { // statement(s) will execute if the boolean expression is true } else { // statement(s) will execute if the boolean expression is false }
流程图
if 块保护条件表达式。如果布尔表达式计算结果为真,则执行与 if 语句关联的块。
if 块后面可以跟一个可选的 else 语句。如果表达式计算结果为假,则执行与 else 块关联的指令块。
示例 - 简单 if…else
fn main() { let num = 12; if num % 2==0 { println!("Even"); } else { println!("Odd"); } }
以上示例打印变量中的值是偶数还是奇数。if 块检查值是否能被 2 整除以确定这一点。以下是以上代码的输出:
Even
嵌套 If
else…if 梯用于测试多个条件。语法如下所示:
语法
if boolean_expression1 { //statements if the expression1 evaluates to true } else if boolean_expression2 { //statements if the expression2 evaluates to true } else { //statements if both expression1 and expression2 result to false }
使用 if…else…if 和 else 语句时,需要牢记以下几点。
- if 可以有零个或一个 else,并且它必须位于任何 else..if 之后。
- if 可以有零个到多个 else..if,并且它们必须位于 else 之前。
- 一旦 else..if 成功,就不会测试任何剩余的 else..if 或 else。
示例:else…if 梯
fn main() { let num = 2 ; if num > 0 { println!("{} is positive",num); } else if num < 0 { println!("{} is negative",num); } else { println!("{} is neither positive nor negative",num) ; } }
代码段显示该值是正数、负数还是零。
输出
2 is positive
Match 语句
match 语句检查当前值是否与值列表中的任何值匹配,这与 C 语言中的 switch 语句非常相似。首先,请注意 match 关键字后面的表达式不必用括号括起来。
语法如下所示。
let expressionResult = match variable_expression { constant_expr1 => { //statements; }, constant_expr2 => { //statements; }, _ => { //default } };
在以下示例中,state_code 与值列表MH, KL, KA, GA匹配 - 如果找到任何匹配项,则将字符串值返回到变量state。如果未找到匹配项,则默认情况 _ 匹配并返回值Unkown。
fn main(){ let state_code = "MH"; let state = match state_code { "MH" => {println!("Found match for MH"); "Maharashtra"}, "KL" => "Kerala", "KA" => "Karnadaka", "GA" => "Goa", _ => "Unknown" }; println!("State name is {}",state); }
输出
Found match for MH State name is Maharashtra
Rust - 循环
在某些情况下,可能需要重复执行一段代码。通常,编程指令是按顺序执行的:函数中的第一个语句首先执行,然后是第二个语句,依此类推。
编程语言提供了各种控制结构,允许更复杂的执行路径。
循环语句允许我们多次执行语句或语句组。下面是在大多数编程语言中循环语句的通用形式。
Rust 提供不同类型的循环来处理循环需求:
- while
- loop
- for
确定循环
迭代次数确定/固定的循环称为确定循环。for 循环是确定循环的一种实现。
For 循环
for 循环执行代码块指定的次数。它可用于迭代一组固定的值,例如数组。for 循环的语法如下所示
语法
for temp_variable in lower_bound..upper_bound { //statements }
for 循环的示例如下所示
fn main(){ for x in 1..11{ // 11 is not inclusive if x==5 { continue; } println!("x is {}",x); } }
注意:变量 x 只能在 for 块内访问。
输出
x is 1 x is 2 x is 3 x is 4 x is 6 x is 7 x is 8 x is 9 x is 10
不确定循环
当循环的迭代次数不确定或未知时,使用不确定循环。
不确定循环可以使用以下方法实现:
序号 | 名称和描述 |
---|---|
1 | While while 循环在每次指定的条件计算结果为真时执行指令 |
2 | Loop loop 是一个 while(true) 不确定循环 |
示例 - for while
fn main(){ let mut x = 0; while x < 10{ x+=1; println!("inside loop x value is {}",x); } println!("outside loop x value is {}",x); }
输出如下所示:
inside loop x value is 1 inside loop x value is 2 inside loop x value is 3 inside loop x value is 4 inside loop x value is 5 inside loop x value is 6 inside loop x value is 7 inside loop x value is 8 inside loop x value is 9 inside loop x value is 10 outside loop x value is 10
示例 - loop
fn main(){ //while true let mut x = 0; loop { x+=1; println!("x={}",x); if x==15 { break; } } }
break 语句用于将控制权从构造中取出。在循环中使用 break 会导致程序退出循环。
输出
x=1 x=2 x=3 x=4 x=5 x=6 x=7 x=8 x=9 x=10 x=11 x=12 x=13 x=14 x=15
Continue 语句
continue 语句跳过当前迭代中的后续语句,并将控制权返回到循环的开头。与 break 语句不同,continue 不会退出循环。它终止当前迭代并开始后续迭代。
continue 语句的示例如下所示。
fn main() { let mut count = 0; for num in 0..21 { if num % 2==0 { continue; } count+=1; } println! (" The count of odd values between 0 and 20 is: {} ",count); //outputs 10 }
以上示例显示了 0 到 20 之间偶数值的数量。如果数字为偶数,则循环退出当前迭代。这是使用 continue 语句实现的。
0 到 20 之间奇数值的数量为 10
Rust - 函数
函数是可读、可维护和可重用代码的构建块。函数是一组用于执行特定任务的语句。函数将程序组织成逻辑代码块。一旦定义,就可以调用函数来访问代码。这使得代码可重用。此外,函数使程序代码易于阅读和维护。
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。
序号 | 函数和描述 |
---|---|
1 |
定义函数 函数定义指定如何完成特定任务。 |
2 |
调用或调用函数 必须调用函数才能执行它。 |
3 |
返回函数 函数还可以将值以及控制权一起返回给调用方。 |
4 |
参数化函数 参数是将值传递给函数的一种机制。 |
定义函数
函数定义指定如何完成特定任务。在使用函数之前,必须先定义它。函数体包含函数应执行的代码。命名函数的规则类似于变量。函数使用fn关键字定义。定义标准函数的语法如下所示
语法
fn function_name(param1,param2..paramN) { // function body }
函数声明可以包含可选的参数。参数用于将值传递给函数。
示例 - 简单函数定义
//Defining a function fn fn_hello(){ println!("hello from function fn_hello "); }
调用函数
必须调用函数才能执行它。此过程称为**函数调用**。在调用函数时,应传递参数的值。调用另一个函数的函数称为**调用函数**。
语法
function_name(val1,val2,valN)
示例:调用函数
fn main(){ //calling a function fn_hello(); }
这里,main()是调用函数。
说明
以下示例定义了一个函数fn_hello()。该函数将消息打印到控制台。main()函数调用fn_hello()函数。
fn main(){ //calling a function fn_hello(); } //Defining a function fn fn_hello(){ println!("hello from function fn_hello "); }
输出
hello from function fn_hello
从函数返回值
函数还可以将值与控制一起返回给调用方。此类函数称为返回函数。
语法
可以使用以下语法中的任何一种来定义具有返回类型的函数。
使用return语句
// Syntax1 function function_name() -> return_type { //statements return value; }
没有return语句的简写语法
//Syntax2 function function_name() -> return_type { value //no semicolon means this value is returned }
图示
fn main(){ println!("pi value is {}",get_pi()); } fn get_pi()->f64 { 22.0/7.0 }
输出
pi value is 3.142857142857143
带参数的函数
参数是将值传递给函数的一种机制。参数构成函数签名的一部分。在调用函数期间,参数值将传递给函数。除非明确指定,否则传递给函数的值的数量必须与定义的参数数量匹配。
可以使用以下技术之一将参数传递给函数:
按值传递
当调用方法时,将为每个值参数创建一个新的存储位置。实际参数的值将复制到其中。因此,在被调用方法内部对参数进行的更改不会影响参数。
以下示例声明了一个变量no,它最初为5。该变量作为参数(按值)传递给mutate_no_to_zero()函数,该函数将值更改为零。在函数调用后,当控制权返回到main方法时,该值将保持不变。
fn main(){ let no:i32 = 5; mutate_no_to_zero(no); println!("The value of no is:{}",no); } fn mutate_no_to_zero(mut param_no: i32) { param_no = param_no*0; println!("param_no value is :{}",param_no); }
输出
param_no value is :0 The value of no is:5
按引用传递
当您按引用传递参数时,与值参数不同,不会为这些参数创建新的存储位置。引用参数表示与提供给方法的实际参数相同的内存位置。可以通过在变量名前添加&来按引用传递参数值。
在下面给出的示例中,我们有一个变量no,它最初为5。将变量no的引用传递给mutate_no_to_zero()函数。该函数对原始变量进行操作。在函数调用后,当控制权返回到main方法时,原始变量的值将为零。
fn main() { let mut no:i32 = 5; mutate_no_to_zero(&mut no); println!("The value of no is:{}",no); } fn mutate_no_to_zero(param_no:&mut i32){ *param_no = 0; //de reference }
*运算符用于访问变量param_no指向的内存位置中存储的值。这也被称为取消引用。
输出将是:
The value of no is 0.
将字符串传递给函数
main()函数将字符串对象传递给display()函数。
fn main(){ let name:String = String::from("TutorialsPoint"); display(name); //cannot access name after display } fn display(param_name:String){ println!("param_name value is :{}",param_name); }
输出
param_name value is :TutorialsPoint
Rust - 元组
元组是一种复合数据类型。标量类型只能存储一种数据。例如,i32变量只能存储单个整数值。在复合类型中,我们可以一次存储多个值,并且它们可以是不同类型的值。
元组具有固定长度 - 一旦声明,它们的大小就不能增长或缩小。元组索引从0开始。
语法
//Syntax1 let tuple_name:(data_type1,data_type2,data_type3) = (value1,value2,value3); //Syntax2 let tuple_name = (value1,value2,value3);
说明
以下示例显示元组中的值。
fn main() { let tuple:(i32,f64,u8) = (-325,4.9,22); println!("{:?}",tuple); }
println!("{ }",tuple)语法不能用于显示元组中的值。这是因为元组是一种复合类型。使用println!("{:?}", tuple_name)语法打印元组中的值。
输出
(-325, 4.9, 22)
说明
以下示例打印元组中的各个值。
fn main() { let tuple:(i32,f64,u8) = (-325,4.9,22); println!("integer is :{:?}",tuple.0); println!("float is :{:?}",tuple.1); println!("unsigned integer is :{:?}",tuple.2); }
输出
integer is :-325 float is :4.9 unsigned integer is :2
说明
以下示例将元组作为参数传递给函数。元组按值传递给函数。
fn main(){ let b:(i32,bool,f64) = (110,true,10.9); print(b); } //pass the tuple as a parameter fn print(x:(i32,bool,f64)){ println!("Inside print method"); println!("{:?}",x); }
输出
Inside print method (110, true, 10.9)
解构
解构赋值是Rust的一项功能,其中我们解包元组的值。这是通过将元组分配给不同的变量来实现的。
考虑以下示例:
fn main(){ let b:(i32,bool,f64) = (30,true,7.9); print(b); } fn print(x:(i32,bool,f64)){ println!("Inside print method"); let (age,is_male,cgpa) = x; //assigns a tuple to distinct variables println!("Age is {} , isMale? {},cgpa is {}",age,is_male,cgpa); }
变量x是一个元组,它被分配给let语句。每个变量 - age、is_male和cgpa将包含元组中相应的值。
输出
Inside print method Age is 30 , isMale? true,cgpa is 7.9
Rust - 数组
在本章中,我们将学习数组以及与其相关的各种特性。在我们学习数组之前,让我们看看数组与变量有何不同。
变量具有以下限制:
变量本质上是标量的。换句话说,变量声明一次只能包含一个值。这意味着要在一个程序中存储n个值,需要n个变量声明。因此,当需要存储较大的值集合时,变量的使用不可行。
程序中的变量以随机顺序分配内存,从而难以按声明顺序检索/读取值。
数组是值的同构集合。简单来说,数组是相同数据类型的值的集合。
数组的特性
数组的特性如下所示:
数组声明分配连续的内存块。
数组是静态的。这意味着一旦初始化,数组就不能调整大小。
每个内存块代表一个数组元素。
数组元素由一个唯一的整数标识,该整数称为元素的下标/索引。
填充数组元素称为数组初始化。
数组元素的值可以更新或修改,但不能删除。
声明和初始化数组
使用以下语法在Rust中声明和初始化数组。
语法
//Syntax1 let variable_name = [value1,value2,value3]; //Syntax2 let variable_name:[dataType;size] = [value1,value2,value3]; //Syntax3 let variable_name:[dataType;size] = [default_value_for_elements,size];
在第一个语法中,数组的类型是在初始化期间从数组第一个元素的数据类型推断出来的。
图示:简单数组
以下示例明确指定了数组的大小和数据类型。println!()函数的{:?}语法用于打印数组中的所有值。len()函数用于计算数组的大小。
fn main(){ let arr:[i32;4] = [10,20,30,40]; println!("array is {:?}",arr); println!("array size is :{}",arr.len()); }
输出
array is [10, 20, 30, 40] array size is :4
图示:没有数据类型的数组
以下程序声明了一个包含4个元素的数组。在变量声明期间没有明确指定数据类型。在这种情况下,数组将为整数类型。len()函数用于计算数组的大小。
fn main(){ let arr = [10,20,30,40]; println!("array is {:?}",arr); println!("array size is :{}",arr.len()); }
输出
array is [10, 20, 30, 40] array size is :4
图示:默认值
以下示例创建一个数组并将其所有元素初始化为-1的默认值。
fn main() { let arr:[i32;4] = [-1;4]; println!("array is {:?}",arr); println!("array size is :{}",arr.len()); }
输出
array is [-1, -1, -1, -1] array size is :4
图示:使用for循环的数组
以下示例遍历数组并打印索引及其对应值。循环从索引0到4(最后一个数组元素的索引)检索值。
fn main(){ let arr:[i32;4] = [10,20,30,40]; println!("array is {:?}",arr); println!("array size is :{}",arr.len()); for index in 0..4 { println!("index is: {} & value is : {}",index,arr[index]); } }
输出
array is [10, 20, 30, 40] array size is :4 index is: 0 & value is : 10 index is: 1 & value is : 20 index is: 2 & value is : 30 index is: 3 & value is : 40
图示:使用iter()函数
iter()函数获取数组中所有元素的值。
fn main(){ let arr:[i32;4] = [10,20,30,40]; println!("array is {:?}",arr); println!("array size is :{}",arr.len()); for val in arr.iter(){ println!("value is :{}",val); } }
输出
array is [10, 20, 30, 40] array size is :4 value is :10 value is :20 value is :30 value is :40
图示:可变数组
mut关键字可用于声明可变数组。以下示例声明了一个可变数组并修改了第二个数组元素的值。
fn main(){ let mut arr:[i32;4] = [10,20,30,40]; arr[1] = 0; println!("{:?}",arr); }
输出
[10, 0, 30, 40]
将数组作为参数传递给函数
数组可以按值或按引用传递给函数。
图示:按值传递
fn main() { let arr = [10,20,30]; update(arr); print!("Inside main {:?}",arr); } fn update(mut arr:[i32;3]){ for i in 0..3 { arr[i] = 0; } println!("Inside update {:?}",arr); }
输出
Inside update [0, 0, 0] Inside main [10, 20, 30]
图示:按引用传递
fn main() { let mut arr = [10,20,30]; update(&mut arr); print!("Inside main {:?}",arr); } fn update(arr:&mut [i32;3]){ for i in 0..3 { arr[i] = 0; } println!("Inside update {:?}",arr); }
输出
Inside update [0, 0, 0] Inside main [0, 0, 0]
数组声明和常量
让我们考虑以下示例以了解数组声明和常量。
fn main() { let N: usize = 20; let arr = [0; N]; //Error: non-constant used with constant print!("{}",arr[10]) }
编译器将导致异常。这是因为数组的长度必须在编译时知道。这里,变量“N”的值将在运行时确定。换句话说,变量不能用于定义数组的大小。
但是,以下程序是有效的:
fn main() { const N: usize = 20; // pointer sized let arr = [0; N]; print!("{}",arr[10]) }
以const关键字为前缀的标识符的值在编译时定义,并且在运行时不能更改。usize是指针大小的,因此其实际大小取决于您编译程序的体系结构。
Rust - 所有权
程序的内存可以分配在以下位置:
- 栈
- 堆
栈
栈遵循后进先出的顺序。栈存储大小在编译时已知的数据值。例如,固定大小的i32变量是栈分配的候选者。它的大小在编译时已知。所有标量类型都可以存储在栈中,因为大小是固定的。
考虑字符串的示例,该字符串在运行时分配了一个值。此类字符串的确切大小在编译时无法确定。因此,它不是栈分配的候选者,而是堆分配的候选者。
堆
堆内存存储大小在编译时未知的数据值。它用于存储动态数据。简单来说,堆内存分配给在程序的生命周期中可能发生变化的数据值。与栈相比,堆是内存中组织性较差的区域。
什么是所有权?
Rust中的每个值都具有一个称为该值**所有者**的变量。Rust中存储的每个数据都将有一个与其关联的所有者。例如,在语法中:let age = 30,age是值30的所有者。
每个数据一次只能拥有一个所有者。
两个变量不能指向同一内存位置。变量将始终指向不同的内存位置。
转移所有权
可以通过以下方式转移值的所有权:
将一个变量的值赋给另一个变量。
将值传递给函数。
从函数返回值。
将一个变量的值赋给另一个变量
Rust作为一门语言的主要卖点是其内存安全性。内存安全性是通过严格控制谁可以使用什么以及何时限制来实现的。
考虑以下代码段:
fn main(){ let v = vec![1,2,3]; // vector v owns the object in heap //only a single variable owns the heap memory at any given time let v2 = v; // here two variables owns heap value, //two pointers to the same content is not allowed in rust //Rust is very smart in terms of memory access ,so it detects a race condition //as two variables point to same heap println!("{:?}",v); }
以上示例声明了一个向量v。所有权的概念是只有一个变量绑定到资源,要么v绑定到资源,要么v2绑定到资源。以上示例抛出错误:use of moved value: `v`。这是因为资源的所有权已转移到v2。这意味着所有权从v移动到v2(v2=v),并且v在移动后无效。
将值传递给函数
当我们将堆中的对象传递给闭包或函数时,值的所有权也会发生变化。
fn main(){ let v = vec![1,2,3]; // vector v owns the object in heap let v2 = v; // moves ownership to v2 display(v2); // v2 is moved to display and v2 is invalidated println!("In main {:?}",v2); //v2 is No longer usable here } fn display(v:Vec<i32>){ println!("inside display {:?}",v); }
从函数返回值
传递给函数的所有权将在函数执行完成后失效。对此的一种解决方法是让函数将拥有的对象返回给调用方。
fn main(){ let v = vec![1,2,3]; // vector v owns the object in heap let v2 = v; // moves ownership to v2 let v2_return = display(v2); println!("In main {:?}",v2_return); } fn display(v:Vec<i32>)->Vec<i32> { // returning same vector println!("inside display {:?}",v); }
所有权和原始类型
对于原始类型,一个变量的内容被复制到另一个变量。因此,不会发生所有权移动。这是因为原始变量需要的资源少于对象。考虑以下示例:
fn main(){ let u1 = 10; let u2 = u1; // u1 value copied(not moved) to u2 println!("u1 = {}",u1); }
输出将是 - 10。
Rust - 借用
将变量的所有权传递给另一个函数然后返回所有权非常不方便。Rust支持一个概念,即借用,其中值的拥有权暂时转移到一个实体,然后返回给原始拥有者实体。
考虑以下:
fn main(){ // a list of nos let v = vec![10,20,30]; print_vector(v); println!("{}",v[0]); // this line gives error } fn print_vector(x:Vec<i32>){ println!("Inside print_vector function {:?}",x); }
main函数调用一个函数print_vector()。将向量作为参数传递给此函数。向量的所有权也从main()传递给print_vector()函数。当main()函数尝试访问向量v时,以上代码将导致如下所示的错误。
| print_vector(v); | - value moved here | println!("{}",v[0]); | ^ value used here after move
这是因为一旦所有权转移到另一个函数,最初拥有该变量或值的函数将无法再使用它。
什么是借用(Borrowing)?
当一个函数暂时(一段时间)将其对变量/值的控制权转移到另一个函数时,这称为借用。这是通过传递变量的引用(& var_name)而不是将变量/值本身传递给函数来实现的。在传递控制权的函数执行完成后,变量/值的所有权将转移回该变量的原始所有者。
fn main(){ // a list of nos let v = vec![10,20,30]; print_vector(&v); // passing reference println!("Printing the value from main() v[0]={}",v[0]); } fn print_vector(x:&Vec<i32>){ println!("Inside print_vector function {:?}",x); }
输出
Inside print_vector function [10, 20, 30] Printing the value from main() v[0] = 10
可变引用(Mutable References)
函数可以通过使用对该资源的可变引用来修改借用的资源。可变引用以&mut为前缀。可变引用只能对可变变量进行操作。
示例:修改整数引用
fn add_one(e: &mut i32) { *e+= 1; } fn main() { let mut i = 3; add_one(&mut i); println!("{}", i); }
main()函数声明一个可变整数变量i,并将i的可变引用传递给add_one()。add_one()将变量i的值加1。
示例:修改字符串引用
fn main() { let mut name:String = String::from("TutorialsPoint"); display(&mut name); //pass a mutable reference of name println!("The value of name after modification is:{}",name); } fn display(param_name:&mut String){ println!("param_name value is :{}",param_name); param_name.push_str(" Rocks"); //Modify the actual string,name }
main()函数将变量name的可变引用传递给display()函数。display函数将额外的字符串附加到原始name变量。
输出
param_name value is :TutorialsPoint The value of name after modification is:TutorialsPoint Rocks
Rust - 切片
切片是指向内存块的指针。切片可用于访问存储在连续内存块中的数据部分。它可以与数组、向量和字符串等数据结构一起使用。切片使用索引号访问数据部分。切片的大小在运行时确定。
切片是指向实际数据的指针。它们通过引用传递给函数,这也称为借用。
例如,切片可用于获取字符串值的某一部分。切片字符串是指向实际字符串对象的指针。因此,我们需要指定字符串的起始和结束索引。索引从0开始,就像数组一样。
语法
let sliced_value = &data_structure[start_index..end_index]
最小索引值为0,最大索引值为数据结构的大小。注意,最终字符串中不包含end_index。
下图显示了一个示例字符串Tutorials,它包含9个字符。第一个字符的索引为0,最后一个字符的索引为8。
以下代码从字符串中获取5个字符(从索引4开始)。
fn main() { let n1 = "Tutorials".to_string(); println!("length of string is {}",n1.len()); let c1 = &n1[4..9]; // fetches characters at 4,5,6,7, and 8 indexes println!("{}",c1); }
输出
length of string is 9 rials
示例 - 切割整数数组
main()函数声明一个包含5个元素的数组。它调用use_slice()函数,并将三个元素的切片(指向data数组)传递给它。切片通过引用传递。use_slice()函数打印切片的值及其长度。
fn main(){ let data = [10,20,30,40,50]; use_slice(&data[1..4]); //this is effectively borrowing elements for a while } fn use_slice(slice:&[i32]) { // is taking a slice or borrowing a part of an array of i32s println!("length of slice is {:?}",slice.len()); println!("{:?}",slice); }
输出
length of slice is 3 [20, 30, 40]
可变切片(Mutable Slices)
&mut关键字可用于将切片标记为可变的。
fn main(){ let mut data = [10,20,30,40,50]; use_slice(&mut data[1..4]); // passes references of 20, 30 and 40 println!("{:?}",data); } fn use_slice(slice:&mut [i32]) { println!("length of slice is {:?}",slice.len()); println!("{:?}",slice); slice[0] = 1010; // replaces 20 with 1010 }
输出
length of slice is 3 [20, 30, 40] [10, 1010, 30, 40, 50]
以上代码将可变切片传递给use_slice()函数。该函数修改原始数组的第二个元素。
Rust - 结构体
数组用于表示同构值的集合。类似地,结构体是Rust中另一种用户定义的数据类型,它允许我们将不同类型的数据项组合在一起,包括另一个结构体。结构体将数据定义为键值对。
语法 - 声明结构体
struct关键字用于声明结构体。由于结构体是静态类型的,因此结构体中的每个字段都必须与数据类型相关联。结构体的命名规则和约定与变量类似。结构体块必须以分号结尾。
struct Name_of_structure { field1:data_type, field2:data_type, field3:data_type }
语法 - 初始化结构体
声明结构体后,应为每个字段分配一个值。这称为初始化。
let instance_name = Name_of_structure { field1:value1, field2:value2, field3:value3 }; //NOTE the semicolon Syntax: Accessing values in a structure Use the dot notation to access value of a specific field. instance_name.field1 Illustration struct Employee { name:String, company:String, age:u32 } fn main() { let emp1 = Employee { company:String::from("TutorialsPoint"), name:String::from("Mohtashim"), age:50 }; println!("Name is :{} company is {} age is {}",emp1.name,emp1.company,emp1.age); }
以上示例声明了一个名为Employee的结构体,它包含三个字段:name、company和age,分别对应不同的类型。main()初始化了该结构体。它使用println!宏打印结构体中定义的字段的值。
输出
Name is :Mohtashim company is TutorialsPoint age is 50
修改结构体实例
要修改实例,应将实例变量标记为可变的。以下示例声明并初始化了一个名为Employee的结构体,然后将age字段的值从50修改为40。
let mut emp1 = Employee { company:String::from("TutorialsPoint"), name:String::from("Mohtashim"), age:50 }; emp1.age = 40; println!("Name is :{} company is {} age is {}",emp1.name,emp1.company,emp1.age);
输出
Name is :Mohtashim company is TutorialsPoint age is 40
将结构体传递给函数
以下示例演示了如何将结构体实例作为参数传递。display方法将Employee实例作为参数,并打印详细信息。
fn display( emp:Employee) { println!("Name is :{} company is {} age is {}",emp.name,emp.company,emp.age); }
这是完整的程序:
//declare a structure struct Employee { name:String, company:String, age:u32 } fn main() { //initialize a structure let emp1 = Employee { company:String::from("TutorialsPoint"), name:String::from("Mohtashim"), age:50 }; let emp2 = Employee{ company:String::from("TutorialsPoint"), name:String::from("Kannan"), age:32 }; //pass emp1 and emp2 to display() display(emp1); display(emp2); } // fetch values of specific structure fields using the // operator and print it to the console fn display( emp:Employee){ println!("Name is :{} company is {} age is {}",emp.name,emp.company,emp.age); }
输出
Name is :Mohtashim company is TutorialsPoint age is 50 Name is :Kannan company is TutorialsPoint age is 32
从函数返回结构体
让我们考虑一个函数who_is_elder(),它比较两个员工的年龄并返回年龄较大的人。
fn who_is_elder (emp1:Employee,emp2:Employee)->Employee { if emp1.age>emp2.age { return emp1; } else { return emp2; } }
这是完整的程序:
fn main() { //initialize structure let emp1 = Employee{ company:String::from("TutorialsPoint"), name:String::from("Mohtashim"), age:50 }; let emp2 = Employee { company:String::from("TutorialsPoint"), name:String::from("Kannan"), age:32 }; let elder = who_is_elder(emp1,emp2); println!("elder is:"); //prints details of the elder employee display(elder); } //accepts instances of employee structure and compares their age fn who_is_elder (emp1:Employee,emp2:Employee)->Employee { if emp1.age>emp2.age { return emp1; } else { return emp2; } } //display name, comapny and age of the employee fn display( emp:Employee) { println!("Name is :{} company is {} age is {}",emp.name,emp.company,emp.age); } //declare a structure struct Employee { name:String, company:String, age:u32 }
输出
elder is: Name is :Mohtashim company is TutorialsPoint age is 50
结构体中的方法(Method)
方法类似于函数。它们是一组逻辑上的编程指令。方法使用fn关键字声明。方法的作用域在结构体块内。
方法是在结构体块外部声明的。impl关键字用于在结构体的上下文中定义方法。方法的第一个参数始终为self,它表示结构体的调用实例。方法对结构体的数据成员进行操作。
要调用方法,我们需要首先实例化结构体。可以使用结构体的实例调用方法。
语法
struct My_struct {} impl My_struct { //set the method's context fn method_name() { //define a method } }
说明
以下示例定义了一个名为Rectangle的结构体,其字段为:width和height。在结构体的上下文中定义了一个名为area的方法。area方法通过self关键字访问结构体的字段,并计算矩形的面积。
//define dimensions of a rectangle struct Rectangle { width:u32, height:u32 } //logic to calculate area of a rectangle impl Rectangle { fn area(&self)->u32 { //use the . operator to fetch the value of a field via the self keyword self.width * self.height } } fn main() { // instanatiate the structure let small = Rectangle { width:10, height:20 }; //print the rectangle's area println!("width is {} height is {} area of Rectangle is {}",small.width,small.height,small.area()); }
输出
width is 10 height is 20 area of Rectangle is 200
结构体中的静态方法(Static Method)
静态方法可用作实用程序方法。这些方法在结构体实例化之前就存在。静态方法使用结构体的名称调用,并且可以在没有实例的情况下访问。与普通方法不同,静态方法不会使用&self参数。
语法 - 声明静态方法
静态方法与函数和其他方法一样,可以选择包含参数。
impl Structure_Name { //static method that creates objects of the Point structure fn method_name(param1: datatype, param2: datatype) -> return_type { // logic goes here } }
语法 - 调用静态方法
structure_name ::语法用于访问静态方法。
structure_name::method_name(v1,v2)
说明
以下示例使用getInstance方法作为工厂类,它创建并返回Point结构体的实例。
//declare a structure struct Point { x: i32, y: i32, } impl Point { //static method that creates objects of the Point structure fn getInstance(x: i32, y: i32) -> Point { Point { x: x, y: y } } //display values of the structure's field fn display(&self){ println!("x ={} y={}",self.x,self.y ); } } fn main(){ // Invoke the static method let p1 = Point::getInstance(10,20); p1.display(); }
输出
x =10 y=20
Rust - 枚举
在Rust编程中,当我们必须从一组可能的变体中选择一个值时,我们使用枚举数据类型。枚举类型使用enum关键字声明。以下是枚举的语法:
enum enum_name { variant1, variant2, variant3 }
示例:使用枚举
该示例声明了一个枚举:GenderCategory,它具有Male和Female两个变体。print!宏显示枚举的值。编译器将抛出一个错误trait std::fmt::Debug is not implemented for GenderCategory。#[derive(Debug)]属性用于抑制此错误。
// The `derive` attribute automatically creates the implementation // required to make this `enum` printable with `fmt::Debug`. #[derive(Debug)] enum GenderCategory { Male,Female } fn main() { let male = GenderCategory::Male; let female = GenderCategory::Female; println!("{:?}",male); println!("{:?}",female); }
输出
Male Female
结构体和枚举
以下示例定义了一个Person结构体。gender字段的类型为GenderCategory(这是一个枚举),可以将其赋值为Male或Female。
// The `derive` attribute automatically creates the implementation // required to make this `enum` printable with `fmt::Debug`. #[derive(Debug)] enum GenderCategory { Male,Female } // The `derive` attribute automatically creates the implementation // required to make this `struct` printable with `fmt::Debug`. #[derive(Debug)] struct Person { name:String, gender:GenderCategory } fn main() { let p1 = Person { name:String::from("Mohtashim"), gender:GenderCategory::Male }; let p2 = Person { name:String::from("Amy"), gender:GenderCategory::Female }; println!("{:?}",p1); println!("{:?}",p2); }
该示例创建了p1和p2两个Person类型的对象,并为每个对象的属性name和gender进行了初始化。
输出
Person { name: "Mohtashim", gender: Male } Person { name: "Amy", gender: Female }
Option枚举
Option是Rust标准库中预定义的枚举。此枚举有两个值:Some(data)和None。
语法
enum Option<T> { Some(T), //used to return a value None // used to return null, as Rust doesn't support the null keyword }
这里,类型T表示任何类型的值。
Rust不支持null关键字。enumOption中的None值可由函数用于返回空值。如果要返回值,则函数可以返回Some(data)。
让我们用一个例子来理解:
程序定义了一个函数is_even(),其返回类型为Option。该函数验证传递的值是否为偶数。如果输入为偶数,则返回true,否则函数返回None。
fn main() { let result = is_even(3); println!("{:?}",result); println!("{:?}",is_even(30)); } fn is_even(no:i32)->Option<bool> { if no%2 == 0 { Some(true) } else { None } }
输出
None Some(true)
Match语句和枚举
match语句可用于比较存储在枚举中的值。以下示例定义了一个函数print_size,它将CarType枚举作为参数。该函数将参数值与预定义的一组常量进行比较,并显示相应的消息。
enum CarType { Hatch, Sedan, SUV } fn print_size(car:CarType) { match car { CarType::Hatch => { println!("Small sized car"); }, CarType::Sedan => { println!("medium sized car"); }, CarType::SUV =>{ println!("Large sized Sports Utility car"); } } } fn main(){ print_size(CarType::SUV); print_size(CarType::Hatch); print_size(CarType::Sedan); }
输出
Large sized Sports Utility car Small sized car medium sized car
Option的匹配
返回Option类型的is_even函数的示例也可以使用match语句实现,如下所示:
fn main() { match is_even(5) { Some(data) => { if data==true { println!("Even no"); } }, None => { println!("not even"); } } } fn is_even(no:i32)->Option<bool> { if no%2 == 0 { Some(true) } else { None } }
输出
not even
Match和带有数据类型的枚举
可以为枚举的每个变体添加数据类型。在以下示例中,枚举的Name和Usr_ID变体分别为String和整数类型。以下示例演示了使用带有数据类型的枚举的match语句。
// The `derive` attribute automatically creates the implementation // required to make this `enum` printable with `fmt::Debug`. #[derive(Debug)] enum GenderCategory { Name(String),Usr_ID(i32) } fn main() { let p1 = GenderCategory::Name(String::from("Mohtashim")); let p2 = GenderCategory::Usr_ID(100); println!("{:?}",p1); println!("{:?}",p2); match p1 { GenderCategory::Name(val)=> { println!("{}",val); } GenderCategory::Usr_ID(val)=> { println!("{}",val); } } }
输出
Name("Mohtashim") Usr_ID(100) Mohtashim
Rust - 模块
代码的逻辑组称为模块。多个模块编译成一个称为crate的单元。Rust程序可能包含一个二进制crate或一个库crate。二进制crate是一个可执行项目,它具有main()方法。库crate是一组可以在其他项目中重用的组件。与二进制crate不同,库crate没有入口点(main()方法)。Cargo工具用于管理Rust中的crate。例如,network模块包含网络相关的函数,而graphics模块包含绘图相关的函数。模块类似于其他编程语言中的命名空间。可以使用cargo从crates.io下载第三方crate。
序号 | 术语和描述 |
---|---|
1 | crate 是Rust中的编译单元;Crate编译成二进制文件或库。 |
2 | cargo crate的官方Rust包管理工具。 |
3 | module 在crate中逻辑地对代码进行分组。 |
4 |
官方Rust包注册表。 |
语法
//public module pub mod a_public_module { pub fn a_public_function() { //public function } fn a_private_function() { //private function } } //private module mod a_private_module { fn a_private_function() { } }
模块可以是公共的或私有的。私有模块中的组件无法被其他模块访问。Rust中的模块默认是私有的。相反,公共模块中的函数可以被其他模块访问。模块应该以pub关键字为前缀才能使其成为公共的。公共模块中的函数也必须是公共的。
示例:定义模块
该示例定义了一个公共模块:movies。该模块包含一个函数play(),它接受一个参数并打印其值。
pub mod movies { pub fn play(name:String) { println!("Playing movie {}",name); } } fn main(){ movies::play("Herold and Kumar".to_string()); }
输出
Playing movie Herold and Kumar
Use关键字
use关键字有助于导入公共模块。
语法
use public_module_name::function_name;
说明
pub mod movies { pub fn play(name:String) { println!("Playing movie {}",name); } } use movies::play; fn main(){ play("Herold and Kumar ".to_string()); }
输出
Playing movie Herold and Kumar
嵌套模块
模块也可以嵌套。comedy模块嵌套在english模块中,后者进一步嵌套在movies模块中。以下示例在movies/english/comedy模块中定义了一个函数play。
pub mod movies { pub mod english { pub mod comedy { pub fn play(name:String) { println!("Playing comedy movie {}",name); } } } } use movies::english::comedy::play; // importing a public module fn main() { // short path syntax play("Herold and Kumar".to_string()); play("The Hangover".to_string()); //full path syntax movies::english::comedy::play("Airplane!".to_string()); }
输出
Playing comedy movie Herold and Kumar Playing comedy movie The Hangover Playing comedy movie Airplane!
示例 - 创建库crate并在二进制crate中使用
让我们创建一个名为movie_lib的库箱,其中包含一个模块movies。为了构建movie_lib库箱,我们将使用工具cargo。
步骤 1 - 创建项目文件夹
创建一个名为movie-app的文件夹,并在其下创建一个名为movie-lib的子文件夹。创建文件夹和子文件夹后,在此目录中创建一个src文件夹和一个Cargo.toml文件。源代码应放在src文件夹中。在src文件夹中创建lib.rs和movies.rs文件。Cargo.toml文件将包含项目元数据,例如版本号、作者姓名等。
项目目录结构如下所示:
movie-app movie-lib/ -->Cargo.toml -->src/ lib.rs movies.rs
步骤 2 - 编辑Cargo.toml文件以添加项目元数据
[package] name = "movies_lib" version = "0.1.0" authors = ["Mohtashim"]
步骤 3 - 编辑lib.rs文件。
将以下模块定义添加到此文件中。
pub mod movies;
以上代码创建了一个公共模块 - movies。
步骤 4 - 编辑movies.rs文件
此文件将定义movies模块的所有函数。
pub fn play(name:String){ println!("Playing movie {} :movies-app",name); }
以上代码定义了一个名为play()的函数,该函数接受一个参数并将其打印到控制台。
步骤 5 - 构建库箱
使用cargo build命令构建应用程序,以验证库箱的结构是否正确。确保您位于项目的根目录 - movie-app文件夹。如果构建成功,终端将显示以下消息。
D:\Rust\movie-lib> cargo build Compiling movies_lib v0.1.0 (file:///D:/Rust/movie-lib) Finished dev [unoptimized + debuginfo] target(s) in 0.67s
步骤 6 - 创建测试应用程序
在movie-app文件夹中创建另一个名为movie-lib-test的文件夹,并在其下创建一个Cargo.toml文件和src文件夹。此项目应该具有main方法,因为这是一个二进制箱,它将使用之前创建的库箱。在src文件夹中创建一个main.rs文件。文件夹结构如下所示。
movie-app movie-lib // already completed movie-lib-test/ -->Cargo.toml -->src/ main.rs
步骤 7 - 在Cargo.toml文件中添加以下内容
[package] name = "test_for_movie_lib" version = "0.1.0" authors = ["Mohtashim"] [dependencies] movies_lib = { path = "../movie-lib" }
注意 - 库文件夹的路径设置为依赖项。下图显示了这两个项目的目录。
步骤 8 - 将以下内容添加到main.rs文件
extern crate movies_lib; use movies_lib::movies::play; fn main() { println!("inside main of test "); play("Tutorialspoint".to_string()) }
以上代码导入了一个名为movies_lib的外部包。检查当前项目的Cargo.toml以验证箱名。
步骤 9 - 使用cargo build和cargo run
我们将使用cargo build和cargo run来构建二进制项目并执行它,如下所示:
Rust - 集合
Rust的标准集合库提供了最常见的通用编程数据结构的高效实现。本章讨论了常用集合 - 向量、哈希表和哈希集的实现。
向量
向量是一个可调整大小的数组。它将值存储在连续的内存块中。预定义结构Vec可用于创建向量。向量的一些重要特性包括:
向量可以在运行时增长或缩小。
向量是同构集合。
向量按特定顺序存储数据作为元素序列。向量中的每个元素都分配一个唯一的索引号。索引从0开始,一直到n-1,其中n是集合的大小。例如,在一个包含5个元素的集合中,第一个元素的索引为0,最后一个元素的索引为4。
向量只会将值追加到(或接近)末尾。换句话说,向量可用于实现堆栈。
向量的内存分配在堆上。
语法 - 创建向量
let mut instance_name = Vec::new();
Vec结构的静态方法new()用于创建向量实例。
或者,也可以使用vec!宏创建向量。语法如下所示:
let vector_name = vec![val1,val2,val3]
下表列出了Vec结构的一些常用函数。
序号 | 方法 | 签名和描述 |
---|---|---|
1 | new() | pub fn new()->Vect 构造一个新的空Vec。在元素被推入之前,向量不会分配内存。 |
2 | push() | pub fn push(&mut self, value: T) 将元素追加到集合的末尾。 |
3 | remove() | pub fn remove(&mut self, index: usize) -> T 移除并返回向量中索引位置处的元素,并将所有后续元素向左移动。 |
4 | contains() | pub fn contains(&self, x: &T) -> bool 如果切片包含具有给定值的元素,则返回true。 |
5 | len() | pub fn len(&self) -> usize 返回向量中的元素数量,也称为其“长度”。 |
图解:创建向量 - new()
要创建向量,我们使用静态方法new -
fn main() { let mut v = Vec::new(); v.push(20); v.push(30); v.push(40); println!("size of vector is :{}",v.len()); println!("{:?}",v); }
以上示例使用Vec结构中定义的静态方法new()创建向量。push(val)函数将作为参数传递的值追加到集合中。len()函数返回向量的长度。
输出
size of vector is :3 [20, 30, 40]
图解:创建向量 - vec!宏
以下代码使用vec!宏创建向量。向量的类型由分配给它的第一个值推断得出。
fn main() { let v = vec![1,2,3]; println!("{:?}",v); }
输出
[1, 2, 3]
如前所述,向量只能包含相同数据类型的值。以下代码段将引发error[E0308]: mismatched types错误。
fn main() { let v = vec![1,2,3,"hello"]; println!("{:?}",v); }
图示:push()
将元素追加到集合的末尾。
fn main() { let mut v = Vec::new(); v.push(20); v.push(30); v.push(40); println!("{:?}",v); }
输出
[20, 30, 40]
图解:remove()
移除并返回向量中索引位置处的元素,并将所有后续元素向左移动。
fn main() { let mut v = vec![10,20,30]; v.remove(1); println!("{:?}",v); }
输出
[10, 30]
图解 - contains()
如果切片包含具有给定值的元素,则返回true -
fn main() { let v = vec![10,20,30]; if v.contains(&10) { println!("found 10"); } println!("{:?}",v); }
输出
found 10 [10, 20, 30]
图示:len()
返回向量中的元素数量,也称为其“长度”。
fn main() { let v = vec![1,2,3]; println!("size of vector is :{}",v.len()); }
输出
size of vector is :3
从向量中访问值
可以使用相应的索引号访问向量中的各个元素。以下示例创建一个向量并打印第一个元素的值。
fn main() { let mut v = Vec::new(); v.push(20); v.push(30); println!("{:?}",v[0]); } Output: `20`
也可以使用对集合的引用来获取向量中的值。
fn main() { let mut v = Vec::new(); v.push(20); v.push(30); v.push(40); v.push(500); for i in &v { println!("{}",i); } println!("{:?}",v); }
输出
20 30 40 500 [20, 30, 40, 500]
哈希表
映射是键值对(称为条目)的集合。映射中没有两个条目可以具有相同的键。简而言之,映射是一个查找表。哈希表将键和值存储在哈希表中。条目按任意顺序存储。键用于在哈希表中搜索值。HashMap结构在std::collections模块中定义。应显式导入此模块以访问HashMap结构。
语法:创建哈希表
let mut instance_name = HashMap::new();
HashMap结构的静态方法new()用于创建HashMap对象。此方法创建一个空的HashMap。
下面讨论了HashMap的常用函数:
序号 | 方法 | 签名和描述 |
---|---|---|
1 | insert() | pub fn insert(&mut self, k: K, v: V) -> Option 插入键值对,如果不存在键,则返回None。更新后,返回旧值。 |
2 | len() | pub fn len(&self) -> usize 返回映射中的元素数量。 |
3 | get() | pub fn get<Q: ?Sized>(&lself, k: &Q) -> Option<&V> where K:Borrow Q:Hash+ Eq 返回对应于键的值的引用。 |
4 | iter() | pub fn iter(&self) -> Iter<K, V> 一个以任意顺序访问所有键值对的迭代器。迭代器元素类型为(&'a K, &'a V)。 |
5 | contains_key | pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool 如果映射包含指定键的值,则返回true。 |
6 | remove() | pub fn remove_entry<Q: ?Sized>(&mut self, k: &Q) -> Option<(K, V)> 从映射中移除键,如果键先前在映射中,则返回存储的键和值。 |
图解:insert()
将键值对插入HashMap中。
use std::collections::HashMap; fn main(){ let mut stateCodes = HashMap::new(); stateCodes.insert("KL","Kerala"); stateCodes.insert("MH","Maharashtra"); println!("{:?}",stateCodes); }
以上程序创建了一个HashMap并使用2个键值对对其进行初始化。
输出
{"KL": "Kerala", "MH": "Maharashtra"}
图示:len()
返回映射中的元素数量
use std::collections::HashMap; fn main() { let mut stateCodes = HashMap::new(); stateCodes.insert("KL","Kerala"); stateCodes.insert("MH","Maharashtra"); println!("size of map is {}",stateCodes.len()); }
以上示例创建了一个HashMap并打印其中的元素总数。
输出
size of map is 2
图解 - get()
返回对应于键的值的引用。以下示例检索HashMap中键KL的值。
use std::collections::HashMap; fn main() { let mut stateCodes = HashMap::new(); stateCodes.insert("KL","Kerala"); stateCodes.insert("MH","Maharashtra"); println!("size of map is {}",stateCodes.len()); println!("{:?}",stateCodes); match stateCodes.get(&"KL") { Some(value)=> { println!("Value for key KL is {}",value); } None => { println!("nothing found"); } } }
输出
size of map is 2 {"KL": "Kerala", "MH": "Maharashtra"} Value for key KL is Kerala
图解 - iter()
返回一个包含对所有键值对的引用的迭代器,这些键值对按任意顺序排列。
use std::collections::HashMap; fn main() { let mut stateCodes = HashMap::new(); stateCodes.insert("KL","Kerala"); stateCodes.insert("MH","Maharashtra"); for (key, val) in stateCodes.iter() { println!("key: {} val: {}", key, val); } }
输出
key: MH val: Maharashtra key: KL val: Kerala
图解:contains_key()
如果映射包含指定键的值,则返回true。
use std::collections::HashMap; fn main() { let mut stateCodes = HashMap::new(); stateCodes.insert("KL","Kerala"); stateCodes.insert("MH","Maharashtra"); stateCodes.insert("GJ","Gujarat"); if stateCodes.contains_key(&"GJ") { println!("found key"); } }
输出
found key
图解:remove()
从映射中移除键。
use std::collections::HashMap; fn main() { let mut stateCodes = HashMap::new(); stateCodes.insert("KL","Kerala"); stateCodes.insert("MH","Maharashtra"); stateCodes.insert("GJ","Gujarat"); println!("length of the hashmap {}",stateCodes.len()); stateCodes.remove(&"GJ"); println!("length of the hashmap after remove() {}",stateCodes.len()); }
输出
length of the hashmap 3 length of the hashmap after remove() 2
哈希集
哈希集是类型T的唯一值的集合。添加和移除值很快,并且快速询问给定值是否在集合中。HashSet结构在std::collections模块中定义。应显式导入此模块以访问HashSet结构。
语法:创建哈希集
let mut hash_set_name = HashSet::new();
HashSet结构的静态方法new用于创建哈希集。此方法创建一个空的哈希集。
下表列出了HashSet结构的一些常用方法。
序号 | 方法 | 签名和描述 |
---|---|---|
1 | insert() | pub fn insert(&mut self, value: T) -> bool 将值添加到集合中。如果集合中不存在此值,则返回true,否则返回false。 |
2 | len() | pub fn len(&self) -> usize 返回集合中的元素数量。 |
3 | get() | pub fn get<Q:?Sized>(&self, value: &Q) -> Option<&T> where T: Borrow,Q: Hash + Eq, 返回集合中与给定值相等的值的引用(如果有)。 |
4 | iter() | pub fn iter(&self) -> Iter 返回一个以任意顺序访问所有元素的迭代器。迭代器元素类型为&'a T。 |
5 | contains_key | pub fn contains<Q: ?Sized>(&self, value: &Q) -> bool 如果集合包含值,则返回true。 |
6 | remove() | pub fn remove<Q: ?Sized>(&mut self, value: &Q) -> bool 从集合中移除值。如果该值存在于集合中,则返回true。 |
图解 - insert()
将值添加到集合中。哈希集不会将重复值添加到集合中。
use std::collections::HashSet; fn main() { let mut names = HashSet::new(); names.insert("Mohtashim"); names.insert("Kannan"); names.insert("TutorialsPoint"); names.insert("Mohtashim");//duplicates not added println!("{:?}",names); }
输出
{"TutorialsPoint", "Kannan", "Mohtashim"}
图示:len()
返回集合中的元素数量。
use std::collections::HashSet; fn main() { let mut names = HashSet::new(); names.insert("Mohtashim"); names.insert("Kannan"); names.insert("TutorialsPoint"); println!("size of the set is {}",names.len()); }
输出
size of the set is 3
图解 - iter()
返回一个以任意顺序访问所有元素的迭代器。
use std::collections::HashSet; fn main() { let mut names = HashSet::new(); names.insert("Mohtashim"); names.insert("Kannan"); names.insert("TutorialsPoint"); names.insert("Mohtashim"); for name in names.iter() { println!("{}",name); } }
输出
TutorialsPoint Mohtashim Kannan
图解:get()
返回集合中与给定值相等的值的引用(如果有)。
use std::collections::HashSet; fn main() { let mut names = HashSet::new(); names.insert("Mohtashim"); names.insert("Kannan"); names.insert("TutorialsPoint"); names.insert("Mohtashim"); match names.get(&"Mohtashim"){ Some(value)=>{ println!("found {}",value); } None =>{ println!("not found"); } } println!("{:?}",names); }
输出
found Mohtashim {"Kannan", "Mohtashim", "TutorialsPoint"}
图解 - contains()
如果集合包含值,则返回true。
use std::collections::HashSet; fn main() { let mut names = HashSet::new(); names.insert("Mohtashim"); names.insert("Kannan"); names.insert("TutorialsPoint"); if names.contains(&"Kannan") { println!("found name"); } }
输出
found name
图解:remove()
从集合中移除值。
use std::collections::HashSet; fn main() { let mut names = HashSet::new(); names.insert("Mohtashim"); names.insert("Kannan"); names.insert("TutorialsPoint"); println!("length of the Hashset: {}",names.len()); names.remove(&"Kannan"); println!("length of the Hashset after remove() : {}",names.len()); }
输出
length of the Hashset: 3 length of the Hashset after remove() : 2
Rust - 错误处理
在Rust中,错误可以分为两大类,如下表所示。
序号 | 名称和描述 | 用法 |
---|---|---|
1 | 可恢复 可以处理的错误 | Result枚举 |
2 | 不可恢复 无法处理的错误 | panic宏 |
可恢复错误是可以纠正的错误。程序可以在遇到可恢复错误时重试失败的操作或指定备用操作方案。可恢复错误不会导致程序突然失败。可恢复错误的一个示例是文件未找到错误。
不可恢复错误会导致程序突然失败。如果发生不可恢复错误,程序无法恢复到其正常状态。它无法重试失败的操作或撤消错误。不可恢复错误的一个示例是尝试访问数组末尾之外的位置。
与其他编程语言不同,Rust没有异常。它为可恢复错误返回枚举Result<T, E>,而在程序遇到不可恢复错误时调用panic宏。panic宏会导致程序突然退出。
Panic宏和不可恢复错误
panic!宏允许程序立即终止并向程序的调用者提供反馈。当程序达到不可恢复状态时,应使用它。
fn main() { panic!("Hello"); println!("End of main"); //unreachable statement }
在以上示例中,当程序遇到panic!宏时,它将立即终止。
输出
thread 'main' panicked at 'Hello', main.rs:3
图解:panic!宏
fn main() { let a = [10,20,30]; a[10]; //invokes a panic since index 10 cannot be reached }
输出如下所示:
warning: this expression will panic at run-time --> main.rs:4:4 | 4 | a[10]; | ^^^^^ index out of bounds: the len is 3 but the index is 10 $main thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 10', main.rs:4 note: Run with `RUST_BACKTRACE=1` for a backtrace.
如果违反业务规则,程序可以调用panic!宏,如下例所示:
fn main() { let no = 13; //try with odd and even if no%2 == 0 { println!("Thank you , number is even"); } else { panic!("NOT_AN_EVEN"); } println!("End of main"); }
以上示例如果变量分配的值为奇数,则返回错误。
输出
thread 'main' panicked at 'NOT_AN_EVEN', main.rs:9 note: Run with `RUST_BACKTRACE=1` for a backtrace.
Result枚举和可恢复错误
枚举Result – <T,E>可用于处理可恢复错误。它有两个变体 - OK和Err。T和E是泛型类型参数。T表示在成功情况下将在OK变体中返回的值的类型,E表示在失败情况下将在Err变体中返回的错误的类型。
enum Result<T,E> { OK(T), Err(E) }
让我们借助示例来理解这一点:
use std::fs::File; fn main() { let f = File::open("main.jpg"); //this file does not exist println!("{:?}",f); }
如果文件已存在,程序返回OK(File);如果文件未找到,则返回Err(Error)。
Err(Error { repr: Os { code: 2, message: "No such file or directory" } })
现在让我们看看如何处理 Err 变体。
以下示例使用match语句处理打开文件时返回的错误
use std::fs::File; fn main() { let f = File::open("main.jpg"); // main.jpg doesn't exist match f { Ok(f)=> { println!("file found {:?}",f); }, Err(e)=> { println!("file not found \n{:?}",e); //handled error } } println!("end of main"); }
注意 - 尽管未找到文件,但程序仍然打印了main事件的end。这意味着程序已优雅地处理了错误。
输出
file not found Os { code: 2, kind: NotFound, message: "The system cannot find the file specified." } end of main
说明
is_even函数如果数字不是偶数,则返回错误。main() 函数处理此错误。
fn main(){ let result = is_even(13); match result { Ok(d)=>{ println!("no is even {}",d); }, Err(msg)=>{ println!("Error msg is {}",msg); } } println!("end of main"); } fn is_even(no:i32)->Result<bool,String> { if no%2==0 { return Ok(true); } else { return Err("NOT_AN_EVEN".to_string()); } }
注意 - 由于 main 函数优雅地处理了错误,因此打印了main语句的end。
输出
Error msg is NOT_AN_EVEN end of main
unwrap() 和 expect()
标准库包含几个辅助方法,Result<T,E>和Option<T>枚举都实现了这些方法。您可以使用它们来简化您确实不希望出现错误的错误情况。如果方法成功,则使用“unwrap”函数提取实际结果。
序号 | 方法 | 签名和描述 |
---|---|---|
1 | unwrap | unwrap(self): T 期望 self 为 Ok/Some 并返回其中包含的值。如果它改为Err或None,则会引发 panic,并显示错误内容。 |
2 | expect | expect(self, msg: &str): T 行为类似于 unwrap,除了在出现 panic 之前除了错误内容之外还会输出自定义消息。 |
unwrap()
unwrap() 函数在操作成功时返回实际结果。如果操作失败,则返回带有默认错误消息的 panic。此函数是 match 语句的简写。如下面的示例所示 -
fn main(){ let result = is_even(10).unwrap(); println!("result is {}",result); println!("end of main"); } fn is_even(no:i32)->Result<bool,String> { if no%2==0 { return Ok(true); } else { return Err("NOT_AN_EVEN".to_string()); } } result is true end of main
修改上述代码,将奇数传递给is_even()函数。
unwrap()函数将出现 panic 并返回如下所示的默认错误消息
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "NOT_AN_EVEN"', libcore\result.rs:945:5 note: Run with `RUST_BACKTRACE=1` for a backtrace
expect()
程序可以在出现 panic 的情况下返回自定义错误消息。以下示例显示了这一点 -
use std::fs::File; fn main(){ let f = File::open("pqr.txt").expect("File not able to open"); //file does not exist println!("end of main"); }
expect() 函数类似于 unwrap()。唯一的区别是可以使用 expect 显示自定义错误消息。
输出
thread 'main' panicked at 'File not able to open: Error { repr: Os { code: 2, message: "No such file or directory" } }', src/libcore/result.rs:860 note: Run with `RUST_BACKTRACE=1` for a backtrace.
Rust - 泛型
泛型是为具有不同类型的多个上下文编写代码的一种方法。在 Rust 中,泛型指的是数据类型和特征的参数化。泛型允许通过减少代码重复和提供类型安全来编写更简洁和干净的代码。泛型的概念可以应用于方法、函数、结构体、枚举、集合和特征。
<T> 语法称为类型参数,用于声明泛型构造。T表示任何数据类型。
插图:泛型集合
以下示例声明了一个只能存储整数的向量。
fn main(){ let mut vector_integer: Vec<i32> = vec![20,30]; vector_integer.push(40); println!("{:?}",vector_integer); }
输出
[20, 30, 40]
考虑以下代码段:
fn main() { let mut vector_integer: Vec<i32> = vec![20,30]; vector_integer.push(40); vector_integer.push("hello"); //error[E0308]: mismatched types println!("{:?}",vector_integer); }
上面的示例表明,整数类型的向量只能存储整数值。因此,如果我们尝试将字符串值推入集合,编译器将返回错误。泛型使集合更具类型安全性。
插图:泛型结构体
类型参数表示一种类型,编译器稍后将填充该类型。
struct Data<T> { value:T, } fn main() { //generic type of i32 let t:Data<i32> = Data{value:350}; println!("value is :{} ",t.value); //generic type of String let t2:Data<String> = Data{value:"Tom".to_string()}; println!("value is :{} ",t2.value); }
上面的示例声明了一个名为Data的泛型结构体。<T>类型表示某种数据类型。main()函数创建了两个实例 - 结构体的整数实例和字符串实例。
输出
value is :350 value is :Tom
特征
特征可用于在多个结构体中实现一组标准的行为(方法)。特征就像面向对象编程中的接口。特征的语法如下所示 -
声明一个特征
trait some_trait { //abstract or method which is empty fn method1(&self); // this is already implemented , this is free fn method2(&self){ //some contents of method2 } }
特征可以包含具体方法(带主体的方法)或抽象方法(不带主体的方法)。如果方法定义将由实现该特征的所有结构体共享,请使用具体方法。但是,结构体可以选择覆盖特征定义的函数。
如果方法定义对于实现结构体有所不同,请使用抽象方法。
语法 - 实现特征
impl some_trait for structure_name { // implement method1() there.. fn method1(&self ){ } }
以下示例定义了一个带有一个print()方法的Printable特征,该方法由book结构体实现。
fn main(){ //create an instance of the structure let b1 = Book { id:1001, name:"Rust in Action" }; b1.print(); } //declare a structure struct Book { name:&'static str, id:u32 } //declare a trait trait Printable { fn print(&self); } //implement the trait impl Printable for Book { fn print(&self){ println!("Printing book with id:{} and name {}",self.id,self.name) } }
输出
Printing book with id:1001 and name Rust in Action
泛型函数
该示例定义了一个泛型函数,该函数显示传递给它的参数。参数可以是任何类型。参数的类型应实现 Display 特征,以便其值可以由 println! 宏打印。
use std::fmt::Display; fn main(){ print_pro(10 as u8); print_pro(20 as u16); print_pro("Hello TutorialsPoint"); } fn print_pro<T:Display>(t:T){ println!("Inside print_pro generic function:"); println!("{}",t); }
输出
Inside print_pro generic function: 10 Inside print_pro generic function: 20 Inside print_pro generic function: Hello TutorialsPoint
Rust - 输入输出
本章讨论如何从标准输入(键盘)接受值并将值显示到标准输出(控制台)。在本章中,我们还将讨论传递命令行参数。
读取器和写入器类型
Rust 的标准库用于输入和输出的功能围绕两个特征组织 -
- 读取
- 写入
序号 | 特征和描述 | 示例 |
---|---|---|
1 | 读取 实现 Read 的类型具有用于面向字节的输入的方法。它们被称为读取器 | Stdin,File |
2 | 写入 实现 Write 的类型支持面向字节和 UTF-8 文本输出。它们被称为写入器。 | Stdout,File |
读取特征
读取器是程序可以从中读取字节的组件。例如,从键盘、文件等读取输入。此特征的read_line()方法可用于一次读取一行数据,来自文件或标准输入流。
序号 | 特征 | 方法和描述 |
---|---|---|
1 | 读取 | read_line(&mut line)->Result 读取一行文本并将其追加到 line,line 是一个字符串。返回值是 io::Result,读取的字节数。 |
插图 - 从控制台读取 - stdin()
Rust 程序可能需要在运行时接受用户的值。以下示例从标准输入(键盘)读取值并将其打印到控制台。
fn main(){ let mut line = String::new(); println!("Enter your name :"); let b1 = std::io::stdin().read_line(&mut line).unwrap(); println!("Hello , {}", line); println!("no of bytes read , {}", b1); }
stdin()函数返回当前进程的标准输入流的句柄,read_line函数可以应用于该句柄。此函数尝试在遇到换行符时读取输入缓冲区中存在的所有字符。
输出
Enter your name : Mohtashim Hello , Mohtashim no of bytes read , 10
写入特征
写入器是程序可以向其写入字节的组件。例如,将值打印到控制台、写入文件等。此特征的 write() 方法可用于将数据写入文件或标准输出流。
序号 | 特征 | 方法和描述 |
---|---|---|
1 | 写入 | write(&buf)->Result 将切片 buf 中的一些字节写入底层流。它返回 io::Result,写入的字节数。 |
插图 - 写入控制台 - stdout()
print!或println!宏可用于在控制台上显示文本。但是,您也可以使用write()标准库函数将某些文本显示到标准输出。
让我们考虑一个例子来理解这一点。
use std::io::Write; fn main() { let b1 = std::io::stdout().write("Tutorials ".as_bytes()).unwrap(); let b2 = std::io::stdout().write(String::from("Point").as_bytes()).unwrap(); std::io::stdout().write(format!("\nbytes written {}",(b1+b2)).as_bytes()).unwrap(); }
输出
Tutorials Point bytes written 15
stdout()标准库函数返回当前进程的标准输出流的句柄,write函数可以应用于该句柄。write() 方法返回一个枚举 Result。unwrap() 是一个辅助方法,用于从枚举中提取实际结果。如果发生错误,unwrap 方法将发送 panic。
注意 - 文件 IO 在下一章中讨论。
命令行参数
命令行参数在执行程序之前传递给程序。它们就像传递给函数的参数一样。命令行参数可用于将值传递给main()函数。std::env::args()返回命令行参数。
说明
以下示例将值作为命令行参数传递给 main() 函数。程序在一个名为main.rs的文件中创建。
//main.rs fn main(){ let cmd_line = std::env::args(); println!("No of elements in arguments is :{}",cmd_line.len()); //print total number of values passed for arg in cmd_line { println!("[{}]",arg); //print all values passed as commandline arguments } }
编译后,程序将生成一个main.exe文件。多个命令行参数应以空格分隔。从终端执行 main.exe,如main.exe hello tutorialspoint。
注意 - hello和tutorialspoint是命令行参数。
输出
No of elements in arguments is :3 [main.exe] [hello] [tutorialspoint]
输出显示 3 个参数,因为main.exe是第一个参数。
说明
以下程序计算作为命令行参数传递的值的总和。一系列以空格分隔的整数被传递给程序。
fn main(){ let cmd_line = std::env::args(); println!("No of elements in arguments is :{}",cmd_line.len()); // total number of elements passed let mut sum = 0; let mut has_read_first_arg = false; //iterate through all the arguments and calculate their sum for arg in cmd_line { if has_read_first_arg { //skip the first argument since it is the exe file name sum += arg.parse::<i32>().unwrap(); } has_read_first_arg = true; // set the flag to true to calculate sum for the subsequent arguments. } println!("sum is {}",sum); }
执行程序时,如 main.exe 1 2 3 4,输出将为 -
No of elements in arguments is :5 sum is 10
Rust - 文件输入/输出
除了读取和写入控制台外,Rust 还允许读取和写入文件。
File 结构体表示一个文件。它允许程序对文件执行读写操作。File 结构体中的所有方法都返回 io::Result 枚举的一个变体。
File 结构体常用的方法列在下表中 -
序号 | 模块 | 方法 | 签名 | 描述 |
---|---|---|---|---|
1 | std::fs::File | open() | pub fn open<P: AsRef>(path: P) -> Result | open 静态方法可用于以只读模式打开文件。 |
2 | std::fs::File | create() | pub fn create<P: AsRef>(path: P) -> Result | 静态方法以写模式打开文件。如果文件已存在,则旧内容将被销毁。否则,将创建一个新文件。 |
3 | std::fs::remove_file | remove_file() | pub fn remove_file<P: AsRef>(path: P) -> Result<()> | 从文件系统中删除文件。不能保证文件会立即被删除。 |
4 | std::fs::OpenOptions | append() | pub fn append(&mut self, append: bool) -> &mut OpenOptions | 设置文件的追加模式选项。 |
5 | std::io::Writes | write_all() | fn write_all(&mut self, buf: &[u8]) -> Result<()> | 尝试将整个缓冲区写入此写入器。 |
6 | std::io::Read | read_to_string() | fn read_to_string(&mut self, buf: &mut String) -> Result | 读取此源中的所有字节直到 EOF,并将它们追加到 buf。 |
写入文件
让我们看一个示例来了解如何写入文件。
以下程序创建一个文件“data.txt”。create() 方法用于创建文件。如果文件创建成功,则该方法返回文件句柄。最后一行write_all函数将字节写入新创建的文件。如果任何操作失败,expect() 函数将返回错误消息。
use std::io::Write; fn main() { let mut file = std::fs::File::create("data.txt").expect("create failed"); file.write_all("Hello World".as_bytes()).expect("write failed"); file.write_all("\nTutorialsPoint".as_bytes()).expect("write failed"); println!("data written to file" ); }
输出
data written to file
从文件读取
以下程序读取文件 data.txt 中的内容并将其打印到控制台。“open”函数用于打开现有文件。文件绝对或相对路径作为参数传递给 open() 函数。如果文件不存在或由于任何原因无法访问,则 open() 函数会抛出异常。如果成功,则将此类文件的句柄分配给“file”变量。
“file”句柄的“read_to_string”函数用于将该文件的内容读取到字符串变量中。
use std::io::Read; fn main(){ let mut file = std::fs::File::open("data.txt").unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); print!("{}", contents); }
输出
Hello World TutorialsPoint
删除文件
以下示例使用 remove_file() 函数删除文件。如果发生错误,expect() 函数将返回自定义消息。
use std::fs; fn main() { fs::remove_file("data.txt").expect("could not remove file"); println!("file is removed"); }
输出
file is removed
将数据追加到文件
append() 函数将数据写入文件的末尾。这在以下示例中显示 -
use std::fs::OpenOptions; use std::io::Write; fn main() { let mut file = OpenOptions::new().append(true).open("data.txt").expect( "cannot open file"); file.write_all("Hello World".as_bytes()).expect("write failed"); file.write_all("\nTutorialsPoint".as_bytes()).expect("write failed"); println!("file append success"); }
输出
file append success
复制文件
以下示例将文件中的内容复制到新文件。
use std::io::Read; use std::io::Write; fn main() { let mut command_line: std::env::Args = std::env::args(); command_line.next().unwrap(); // skip the executable file name // accept the source file let source = command_line.next().unwrap(); // accept the destination file let destination = command_line.next().unwrap(); let mut file_in = std::fs::File::open(source).unwrap(); let mut file_out = std::fs::File::create(destination).unwrap(); let mut buffer = [0u8; 4096]; loop { let nbytes = file_in.read(&mut buffer).unwrap(); file_out.write(&buffer[..nbytes]).unwrap(); if nbytes < buffer.len() { break; } } }
执行上述程序时,如main.exe data.txt datacopy.txt。执行文件时传递两个命令行参数 -
- 源文件的路径
- 目标文件
Rust - 包管理器
Cargo 是 RUST 的包管理器。它充当工具并管理 Rust 项目。
下表列出了一些常用的 Cargo 命令 -
序号 | 命令和描述 |
---|---|
1 | cargo build 编译当前项目。 |
2 | cargo check 分析当前项目并报告错误,但不构建目标文件。 |
3 | cargo run 构建并执行 src/main.rs。 |
4 | cargo clean 删除 target 目录。 |
5 | cargo update 更新 Cargo.lock 中列出的依赖项。 |
6 | cargo new 创建一个新的 Cargo 项目。 |
Cargo 帮助下载第三方库。因此,它充当包管理器。您还可以构建自己的库。安装 Rust 时,Cargo 会默认安装。
要创建新的 Cargo 项目,我们可以使用以下命令。
创建二进制板条箱
cargo new project_name --bin
创建库板条箱
cargo new project_name --lib
要检查 Cargo 的当前版本,请执行以下命令 -
cargo --version
插图 - 创建二进制 Cargo 项目
游戏生成一个随机数,并提示用户猜测这个数字。
步骤 1 - 创建项目文件夹
打开终端并输入以下命令 cargo new guess-game-app --bin。
这将创建以下文件夹结构。
guess-game-app/ -->Cargo.toml -->src/ main.rs
cargo new 命令用于创建一个板条箱。--bin 标志表示创建的板条箱是一个二进制板条箱。公共板条箱存储在一个名为 crates.io 的中央存储库中 https://crates.io/。
步骤 2 - 包含对外部库的引用
此示例需要生成一个随机数。由于内部标准库不提供随机数生成逻辑,因此我们需要查看外部库或板条箱。让我们使用 rand 板条箱,它在 crates.io 网站上可用 crates.io
rand 是一个用于随机数生成的 Rust 库。Rand 提供了生成随机数、将其转换为有用的类型和分布以及一些与随机性相关的算法的实用程序。
下图显示了 crates.io 网站和 rand 板条箱的搜索结果。
将 rand 板条箱的版本复制到 Cargo.toml 文件中 rand = "0.5.5"。
[package] name = "guess-game-app" version = "0.1.0" authors = ["Mohtashim"] [dependencies] rand = "0.5.5"
步骤 3:编译项目
导航到项目文件夹。在终端窗口上执行命令 cargo build -
Updating registry `https://github.com/rust-lang/crates.io-index` Downloading rand v0.5.5 Downloading rand_core v0.2.2 Downloading winapi v0.3.6 Downloading rand_core v0.3.0 Compiling winapi v0.3.6 Compiling rand_core v0.3.0 Compiling rand_core v0.2.2 Compiling rand v0.5.5 Compiling guess-game-app v0.1.0 (file:///E:/RustWorks/RustRepo/Code_Snippets/cargo-projects/guess-game-app) Finished dev [unoptimized + debuginfo] target(s) in 1m 07s
rand 板条箱和所有传递依赖项(rand 的内部依赖项)将自动下载。
步骤 4 - 理解业务逻辑
现在让我们看看猜数字游戏的业务逻辑是如何工作的 -
游戏最初生成一个随机数。
提示用户输入并猜测数字。
如果数字小于生成的数字,则打印消息“太低”。
如果数字大于生成的数字,则打印消息“太高”。
如果用户输入程序生成的数字,则游戏退出。
步骤 5 - 编辑 main.rs 文件
将业务逻辑添加到 main.rs 文件中。
use std::io; extern crate rand; //importing external crate use rand::random; fn get_guess() -> u8 { loop { println!("Input guess") ; let mut guess = String::new(); io::stdin().read_line(&mut guess) .expect("could not read from stdin"); match guess.trim().parse::<u8>(){ //remember to trim input to avoid enter spaces Ok(v) => return v, Err(e) => println!("could not understand input {}",e) } } } fn handle_guess(guess:u8,correct:u8)-> bool { if guess < correct { println!("Too low"); false } else if guess> correct { println!("Too high"); false } else { println!("You go it .."); true } } fn main() { println!("Welcome to no guessing game"); let correct:u8 = random(); println!("correct value is {}",correct); loop { let guess = get_guess(); if handle_guess(guess,correct){ break; } } }
步骤 6 - 编译并执行项目
在终端上执行命令 cargo run。确保终端指向项目目录。
Welcome to no guessing game correct value is 97 Input guess 20 Too low Input guess 100 Too high Input guess 97 You got it ..
Rust - 迭代器和闭包
在本章中,我们将学习迭代器和闭包在 RUST 中是如何工作的。
迭代器
迭代器有助于迭代一系列值,例如数组、向量、映射等。迭代器实现了在 Rust 标准库中定义的 Iterator 特性。iter() 方法返回集合的迭代器对象。迭代器对象中的值称为项目。迭代器的 next() 方法可用于遍历项目。当 next() 方法到达集合的末尾时,它返回一个 None 值。
以下示例使用迭代器从数组中读取值。
fn main() { //declare an array let a = [10,20,30]; let mut iter = a.iter(); // fetch an iterator object for the array println!("{:?}",iter); //fetch individual values from the iterator object println!("{:?}",iter.next()); println!("{:?}",iter.next()); println!("{:?}",iter.next()); println!("{:?}",iter.next()); }
输出
Iter([10, 20, 30]) Some(10) Some(20) Some(30) None
如果像数组或向量这样的集合实现了 Iterator 特性,则可以使用 for...in 语法遍历它,如下所示 -
fn main() { let a = [10,20,30]; let iter = a.iter(); for data in iter{ print!("{}\t",data); } }
输出
10 20 30
以下 3 种方法从集合中返回一个迭代器对象,其中 T 表示集合中的元素。
序号 | 方法和描述 |
---|---|
1 | iter() iter() |
2 | 给出 &T(对 T 的引用)的迭代器 into_iter() |
3 | 给出 T 的迭代器 iter_mut() |
给出 &mut T 的迭代器
插图:iter()
fn main() { let names = vec!["Kannan", "Mohtashim", "Kiran"]; for name in names.iter() { match name { &"Mohtashim" => println!("There is a rustacean among us!"), _ => println!("Hello {}", name), } } println!("{:?}",names); // reusing the collection after iteration }
输出
Hello Kannan There is a rustacean among us! Hello Kiran ["Kannan", "Mohtashim", "Kiran"]
iter() 函数使用借用概念。它返回对集合中每个元素的引用,使集合保持不变并可以在循环后重复使用。
插图 - into_iter()
fn main(){ let names = vec!["Kannan", "Mohtashim", "Kiran"]; for name in names.into_iter() { match name { "Mohtashim" => println!("There is a rustacean among us!"), _ => println!("Hello {}", name), } } // cannot reuse the collection after iteration //println!("{:?}",names); //Error:Cannot access after ownership move }
输出
Hello Kannan There is a rustacean among us! Hello Kiran
此函数使用所有权的概念。它将集合中的值移动到 iter 对象中,即集合被消耗并且不再可用。
插图 - for 和 iter_mut()
fn main() { let mut names = vec!["Kannan", "Mohtashim", "Kiran"]; for name in names.iter_mut() { match name { &mut "Mohtashim" => println!("There is a rustacean among us!"), _ => println!("Hello {}", name), } } println!("{:?}",names); //// reusing the collection after iteration }
输出
Hello Kannan There is a rustacean among us! Hello Kiran ["Kannan", "Mohtashim", "Kiran"]
此函数类似于 iter() 函数。但是,此函数可以修改集合中的元素。
闭包
闭包指的是一个函数在另一个函数内部。这些是匿名函数——没有名称的函数。闭包可以用于将函数分配给变量。这允许程序将函数作为参数传递给其他函数。闭包也称为内联函数。外层函数中的变量可以被内联函数访问。
语法:定义闭包
let closure_function = |parameter| { //logic }
闭包定义可以选择具有参数。参数括在两个竖线之间。
closure_function(parameter); //invoking
说明
调用闭包的语法实现了 Fn 特性。因此,可以使用 () 语法调用它。
fn main(){ let is_even = |x| { x%2==0 }; let no = 13; println!("{} is even ? {}",no,is_even(no)); }
输出
13 is even ? false
说明
fn main(){ let val = 10; // declared outside let closure2 = |x| { x + val //inner function accessing outer fn variable }; println!("{}",closure2(2)); }
以下示例在函数 main() 中定义了一个闭包 is_even。如果数字为偶数,则闭包返回 true,如果数字为奇数,则返回 false。
输出
12
Rust - 智能指针
main() 函数声明了一个变量 val 和一个闭包。闭包访问在外层函数 main() 中声明的变量。
序号 | Rust 默认情况下在堆栈上分配所有内容。您可以通过将内容包装在智能指针(如 Box)中来将其存储在堆上。像 Vec 和 String 这样的类型隐式地帮助堆分配。智能指针实现了下表中列出的特性。这些智能指针的特性将它们与普通的结构区分开来 - | 特性名称 |
---|---|---|
1 | 包和描述 | Deref std::ops::Deref |
2 | 用于不可变的解引用操作,如 *v。 | Drop std::ops::Drop |
用于在值超出范围时运行一些代码。这有时称为析构函数
在本章中,我们将学习有关 Box 智能指针的知识。我们还将学习如何创建像 Box 这样的自定义智能指针。
Box
Box 智能指针也称为 box,允许您将数据存储在堆上而不是堆栈上。堆栈包含指向堆数据的指针。除了将数据存储在堆上之外,Box 没有性能开销。
fn main() { let var_i32 = 5; //stack let b = Box::new(var_i32); //heap println!("b = {}", b); }
输出
b = 5
让我们看看如何使用 box 将 i32 值存储在堆上。
fn main() { let x = 5; //value type variable let y = Box::new(x); //y points to a new value 5 in the heap println!("{}",5==x); println!("{}",5==*y); //dereferencing y }
为了访问变量指向的值,请使用解引用。* 用作解引用运算符。让我们看看如何在 Box 中使用解引用。
输出
true true
变量 x 是一个值为 5 的值类型。因此,表达式 5==x 将返回 true。变量 y 指向堆。要访问堆中的值,我们需要使用 *y 进行解引用。*y 返回值 5。因此,表达式 5==*y 返回 true。
插图 - Deref 特性
use std::ops::Deref; struct MyBox<T>(T); impl<T> MyBox<T> { // Generic structure with static method new fn new(x:T)-> MyBox<T> { MyBox(x) } } impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 //returns data } } fn main() { let x = 5; let y = MyBox::new(x); // calling static method println!("5==x is {}",5==x); println!("5==*y is {}",5==*y); // dereferencing y println!("x==*y is {}",x==*y); //dereferencing y }
输出
5==x is true 5==*y is true x==*y is true
标准库提供的 Deref 特性要求我们实现一个名为 deref 的方法,该方法借用 self 并返回对内部数据的引用。以下示例创建一个结构 MyBox,它是一个泛型类型。它实现了 Deref 特性。此特性帮助我们使用 *y 访问 y 包装的堆值。
插图 - Drop 特性
use std::ops::Deref; struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x:T)->MyBox<T>{ MyBox(x) } } impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -< &T { &self.0 } } impl<T> Drop for MyBox<T>{ fn drop(&mut self){ println!("dropping MyBox object from memory "); } } fn main() { let x = 50; MyBox::new(x); MyBox::new("Hello"); }
Drop 特性包含 drop() 方法。当实现了此特性的结构超出范围时,将调用此方法。在某些语言中,程序员必须在每次完成使用智能指针实例后都调用代码来释放内存或资源。在 Rust 中,您可以使用 Drop 特性实现自动内存释放。
dropping MyBox object from memory dropping MyBox object from memory
Rust - 并发
在上面的示例中,drop 方法将被调用两次,因为我们在堆中创建了两个对象。
在并发编程中,程序的不同部分独立执行。另一方面,在并行编程中,程序的不同部分同时执行。这两种模型都同等重要,因为越来越多的计算机利用了它们的多处理器功能。
线程
我们可以使用线程同时运行代码。在当前的操作系统中,已执行程序的代码在一个进程中运行,操作系统同时管理多个进程。在您的程序中,您还可以拥有同时运行的独立部分。运行这些独立部分的功能称为线程。
创建线程
//import the necessary modules use std::thread; use std::time::Duration; fn main() { //create a new thread thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); //code executed by the main thread for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } }
输出
hi number 1 from the main thread! hi number 1 from the spawned thread! hi number 2 from the main thread! hi number 2 from the spawned thread! hi number 3 from the main thread! hi number 3 from the spawned thread! hi number 4 from the spawned thread! hi number 4 from the main thread!
thread::spawn 函数用于创建一个新线程。spawn 函数将闭包作为参数。闭包定义了线程应执行的代码。以下示例从主线程打印一些文本,从新线程打印其他文本。
主线程打印从 1 到 4 的值。
注意 - 当主线程结束时,新线程将停止。此程序的输出每次可能略有不同。
thread::sleep 函数强制线程停止执行一小段时间,从而允许其他线程运行。线程可能会轮流运行,但这并非保证——它取决于操作系统如何调度线程。在此运行中,首先打印主线程,即使来自已生成线程的打印语句在代码中首先出现。此外,即使已生成线程被编程为打印直到 9 的值,它在主线程关闭之前只到达了 5。
连接句柄
use std::thread; use std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } handle.join().unwrap(); }
输出
hi number 1 from the main thread! hi number 1 from the spawned thread! hi number 2 from the spawned thread! hi number 2 from the main thread! hi number 3 from the spawned thread! hi number 3 from the main thread! hi number 4 from the main thread! hi number 4 from the spawned thread! hi number 5 from the spawned thread! hi number 6 from the spawned thread! hi number 7 from the spawned thread! hi number 8 from the spawned thread! hi number 9 from the spawned thread!
已生成的线程可能没有机会运行或完全运行。这是因为主线程很快完成。函数 spawn<F, T>(f: F) -> JoinHandlelt;T> 返回一个 JoinHandle。JoinHandle 上的 join() 方法等待关联的线程完成。
主线程和已生成的线程继续切换。