Rust实战系列-Rust介绍
作者:互联网
“
学习资料:rust in action[1]
1. Rust 安装
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source"$HOME/.cargo/env"
2. hello world
- 创建 hello 项目
mkdir rust_tmp && cd rust_tmp
cargo new hello
cd hello
cargo run
看到这样的输出,就表示已经成功运行了 Rust 项目,尽管还没写任何代码。接下来看看发生了什么。
- Cargo
Cargo 是一个同时提供项目构建和软件包管理功能的工具。也就是说,Cargo 执行 rustc(Rust 编译器)将 Rust 代码转换为可执行的二进制文件或共享库。cargo new
会创建一个遵循标准模板的项目,目录结构如下:
Cargo.toml:描述项目的元数据信息(项目名,版本,依赖)
[package]
name = "hello"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
src:源码目录,Rust 的源码文件扩展名为 .rs
创建好项目后,运行 cargon run 命令启动项目,这个过程完成了很多工作。
当敲下 cargon run 命令准备 run 项目时,实际上并没有可以 run 的内容,因此,cargon 会以 debug 模式编译项目,编译生成的可执行文件位于:target/debug/hello,然后执行这个文件,输出我们看到的内容:“Hello, world!”。
编译之后,项目的目录结构发生了变化,在根目录多了 Cargo.lock 文件和 target 目录,它们都是通过 cargon 进行管理的,不需要人工修改。
“
Cargo.lock 指定了所有依赖项的确切版本号,这样,在 Cargo.toml 被修改之前,项目编译过程都会以相同的方式进行。
3. 第一个 Rust 项目
目标:输出不同语言的 hello world,理解 Rust 的两个特性:易于迭代和原生支持 Unicode。
直接修改 hello 项目中 src/main.rs 的内容:
fn main() {
println!("Hello, world!");
let southern_germany = "Grüß Gott!";
let japan = "ハロー・ワールド";
let china = "你好,世界!";
let regions = [southern_germany, japan, china];
for region in regions.iter() {
println!("{}", ®ion);
}
}
-
感叹号表示引用了一个宏
-
Rust 中的变量赋值,更恰当的称呼是变量绑定,使用 let 关键字
-
原生支持 Unicode,不需要考虑乱码问题
-
使用方括号表示数组
-
很多数据类型可以通过 iter()返回迭代器
-
&表示取出地址的值
修改后,项目的执行结果:
4. 文本处理
接下来,通过实例了解 Rust 的文本处理能力。主要包括以下特性:
-
常见的控制流机制:包括 for 循环和 continue 关键字
-
函数语法:虽然 Rust 不是面向对象的,因为它不支持继承,但它继承了面向对象语言的这个特点
-
高级编程:函数可以同时接受和返回函数。例如,实例第 19 行包括一个闭包,也被称为匿名函数或 λ(lambda)函数
-
类型注解:虽然用得不多,但偶尔也需要这些注解提示编译器(第 28 行)
-
有条件地编译:编译项目时,不会编译第 22-24 行的内容
-
隐式返回:Rust 提供了一个 return 关键字,但它通常被省略了
fn main() { // <1>
let penguin_data = "\
common name,length (cm)
Little penguin,33
Yellow-eyed penguin,65
Fiordland penguin,60
Invalid,data
";
let records = penguin_data.lines(); // <2>
for (i, record) in records.enumerate() { // <3>
if i == 0 || record.trim().len() == 0 { // <4>
continue;
}
let fields: Vec<_> = record // <5>
.split(',') // <6>
.map(|field| field.trim()) // <7>
.collect(); // <8>
ifcfg!(debug_assertions) { // <9>
eprintln!("debug: {:?} -> {:?}",
record, fields); // <10>
}
let name = fields[0]; // <11>
ifletOk(length) = fields[1].parse::<f32>() { // <12>
println!("{}, {}cm", name, length); // <13>
}
}
}
-
可执行项目必须有一个 main 函数
-
将字符串按行拆分成切片
-
遍历每行字符串,i 是下标,record 是 item
-
跳过表头和空行
-
Vec 类型是向量的简称,向量是一种数组,在需要时可以动态扩展。下划线要求编译器推断出向量的元素类型。即变量名 fields,类型为 Vec,Vec 中元素类型 Rust 推导。
-
将 record 拆分成子字符串数组(以 逗号 为分隔符)
-
对于循环,可以使用高级函数,这里去掉空白字符。map()对 split 出来的每个子字符串应用函数 term(),field 临时变量表示每个子字符串(个人理解,不一定对)
-
Collects 迭代的结果并保存到向量 fields 中
-
这个代码块是为了调试,感叹号 ! 表示宏调用,宏类似于函数,返回代码而不是值,通常用于简化常见的模式
-
打印到标准错误输出, {:?} 语法请求这两种数据类型的默认调试格式作为输出
-
Rust 支持用整数下标对集合进行索引
-
将字符串解析为 f32(单精度浮点数)类型,parse 可以将字符串解析为任何实现了 FromStr trait 的类型(在 Rust 中,为了安全起见,不允许隐式的数据类型转换),使用 Ok()函数是为了在 if 的条件中创建 length 变量并进行赋值操作
-
打印到 stdout,{} 语法表示 Rust 应该使用用户自定义的方法来输出字符串的值,而不是用 {:?} 来显示调试结果
运行项目的输出结果:
可以看到有输出以 debug 开头的行,通过 --release 参数去掉这部分调试内容。
可以通过 -q(quiet) 来进一步减少输出信息:
“
严格来说,rustc 才是 Rust 编译器,但我们并没有使用它来编译项目,cargon 代替我们调用 rustc ,简化编译过程。如果希望查看 rustc 编译过程的详细信息,使用 --verbose 或 -v 参数。(需要保证目标文件未被编译,如果已经编译则没有对应输出)
rustc:管理 Rust 源代码的编译
rustup:管理 Rust 的安装
5. Rust 的目标:安全
“
we need a safer systems programming language[2]
Rust 不受以下情况的影响:
-
空悬指针(dangling pointers):对程序运行过程中变得无效的数据进行实时引用(指针被释放后,仍然引用原来的内存)
-
数据竞争(data race):由于外部环境的变化,无法确定程序在运行过程中的行为(非线程安全的情况下,多线程对同一个地址空间进行写操作)
-
缓存溢出(Buffer overflow):试图访问一个只有 6 个元素数组的第 12 个元素
-
迭代器失效(Iterator invalidation):已经迭代的内容被中途修改后导致的问题(python 中遇到过这种问题)
当程序在调试模式下被编译时,Rust 也会对整数溢出进行保护。
“
什么是整数溢出:整数只能代表有限的一组数字;这些数字在内存中占用固定的长度。整数溢出是指当整数达到其极限时发生的情况。
- 空悬指针的示例代码
#[derive(Debug)]// <1>
enum Cereal { // <2>
Barley, Millet, Rice,
Rye, Spelt, Wheat,
}
fn main() {
letmut grains: Vec<Cereal> = vec![]; // <3>
grains.push(Cereal::Rye); // <4>
drop(grains); // <5>
println!("{:?}", grains); // <6>
}
-
允许 println! 打印 Cereal 枚举
-
枚举是一种有固定数量有效值的类型
-
初始化空的向量(数组)grains
-
向 grains 添加元素
-
删除向量 grains 和其中的内容
-
尝试访问被删除的值
代码中,Vec是用一个指向底层数组的内部指针实现的,尝试编译项目会出错:
- 数据竞争的示例代码
use std::thread; // <1>
fn main() {
letmut data = 100;
thread::spawn(|| { data = 500; }); // <2>
thread::spawn(|| { data = 1000; }); // <2>
println!("{}", data);
}
-
导入 thread 标准库
-
thread::spawn() 产生一个新的线程,thread::spawn()的闭合由竖条和大括号表示(例如,|| {...})
使用 cargo 编译不会通过。
- 缓存溢出的示例代码
fn main() {
let fruit = vec!['标签:实战,cargo,系列,编译,编译器,let,内存,Rust
来源: https://www.cnblogs.com/sctb/p/16606864.html