Rust 是一门系统编程语言(Systems Programming Language),兼顾安全(Safety)、性能(Speed)和并发(Concurrency)。Rust作为一门底层的系统编程语言,理论上,使用 C/C++ 的领域都可以使用Rust实现,例如对硬件需要精细控制的嵌入式编程、对性能要求极高的应用软件(数据库引擎、浏览器引擎,3D渲染引擎等)。相对于 C/C++ 的系统性缺陷(内存管理不当造成的安全漏洞),Rust通过所有权(Ownership)机制在编译期间确保内存安全,无需垃圾回收(Garbage Collection, GC),也不需要手动释放内存。
1. Hello World
1.1 安装 Rust
- 在线安装
- Windows:下载 rustup-init.exe,自动引导安装。
- Linux:curl –proto ‘=https’ –tlsv1.2 -sSf https://sh.rustup.rs | sh
- 离线安装
- 下载独立安装包
- Windows: 下载 .msi 文件,双击安装即可。
- Linux:下载 .tar.gz 文件,tar -xvf xxx.tar.gz 解压后,执行 install.sh 即可。
- 查看版本
$ rustc --versionrustc 1.39.0 (4560ea788 2019-11-04)$ cargo --versioncargo 1.39.0 (1c6ec66d5 2019-09-30)
1.2 第一个Rust程序
fn main() { println!("Hello, world!");}
使用 fn 声明函数。与大部分编程语言一致,main() 是 Rust 程序的入口。println! 表示打印文本到控制台,! 表示这是一个宏(macro),而非一个函数(function)。
- 保存为 hello_world.rs,rs 为 Rust 语言的后缀。
- 编译:rustc hello_world.rs。
- 执行:./hello_world(Linux),hello_world.exe(Windows)
尝试下 println! 更多的用法。
fn main() { println!("{}, {}!", "Hello", "world"); // Hello, world! println!("{0}, {1}!", "Hello", "world"); // Hello, world! println!("{greeting}, {name}!", greeting="Hello", name="world"); // Hello, world! let y = String::from("Hello, ") + "world!"; println!("{}", y); // Hello, world!}
以上代码将输出
Hello, world!Hello, world!Hello, world!Hello, world!
1.3 使用 Cargo
为了方便之后的调试和学习,先介绍 Rust 内置的包管理和构建系统 Cargo,crates.io 是 Rust 的社区仓库。
- 创建新项目:cargo new
- 编译:cargo build
- 运行:cargo run
- 更新项目依赖:cargo update
- 执行测试:cargo test
- 生成文档:cargo doc
- 静态检查:cargo check
- 新建二进制(Binary/Executable)项目
$ cargo new tutu --bin$ cd tutu && tree├── Cargo.toml└── src └── main.rs
在 main.rs 中写入
fn main() { println!("Hello, Cargo!");}
在项目目录下执行 cargo run
$ cargo run tutu git:(master) ✗ cargo run Compiling tutu v0.1.0 (/xxx/demo/tutu) Finished dev [unoptimized + debuginfo] target(s) in 0.49s Running `target/debug/tutu`Hello, Cargo!
- 新建 Library 项目
$ cargo new tutu --lib$ cd tutu && tree├── Cargo.toml└── src └── lib.rs
- Cargo.toml 是工程的描述文件,包含 Cargo 所需的所有元信息。
- src 放置源代码。
- main.rs / lib.rs 是入口文件。
运行 cargo run 或 cargo build,可执行文件将生成在 target/debug/ 目录,运行 cargo build –release,可执行文件将生成在 target/release/ 。
2 基本概念
2.1 注释
/// 外部注释mod test { // 行注释 /* 块注释 */}mod test { //! 包/模块级别的注释 // ...}
///用于 mod 块外部,//!用于书写包/模块级别的注释注释支持 markdown 语法,使用 cargo doc 生成 HTML 文档。
2.2 变量
- 局部变量
Rust 中变量默认是不可变的(immutable),称为变量绑定(Variable bindings),使用 mut 标志为可变(mutable)。
let 声明的变量是局部变量,声明时可以不初始化,使用前初始化即可。Rust是静态类型语言,编译时会检查类型,使用let声明变量时可以省略类型,编译时会推断一个合适的类型。
// 不可变let c;let a = true;let b: bool = true;let (x, y) = (1, 2);c = 12345;// 可变let mut z = 5;z = 6;
- 全局变量
rust 中可用 static 声明全局变量。用 static 声明的变量的生命周期是整个程序,从启动到退出,它占用的内存空间是固定的,不会在执行过程中回收。另外,static 声明语句,必须显式标明类型,不支持类型自动推导。全局变量在声明时必须初始化,且须是简单赋值,不能包括复杂的表达式、语句和函数调用。
// 静态变量(不可变)static N: i32 = 5;// 静态变量(可变)static mut N: i32 = 5;
- 常量
const 的生命周期也是整个程序,const 与 static 的最大区别在于,编译器并不一定会给 const 常量分配内存空间,在编译过程中,它很可能会被内联优化,类似于C语言的宏定义。
const N: i32 = 5;
2.3 函数
使用 fn 声明函数。
fn main() { println!("Hello, world!");}
参数需要指定类型
fn print_sum(a: i8, b: i8) { println!("sum is: {}", a + b);}
默认返回值为空(),如果有返回值,需要使用->指定返回类型。
fn plus_one(a: i32) -> i32 { a + 1 // 等价于 return a + 1,可省略为 a + 1}
可以利用元组(tuple)返回多个值
fn plus_one(a: i32) -> (i32, i32) { (a, &a + 1)}fn main() { let (add_num, result) = plus_one(10); println!("{} + 1 = {}", add_num, result); // 10 + 1 = 11}
函数指针也可以作为变量使用
let b = plus_one;let c = b(5); //6
2.4 基本数据类型
- 布尔值(bool)
- 字符(char)
- 有符号整型(i8, i16, i32, i64, i128)
- 无符号整型(u8, u16, u32, u64, u128)
- 指针大小的有符号/无符号整型(isize/usize,取决于计算机架构,32bit 的系统上,isize 等价于i32)
- 浮点数(f32, f64)
- 数组(arrays),由相同类型元素构成,长度固定。
let a = [1, 2, 3]; // a[0] = 1, a[1] = 2, a[2] = 3let mut b = [1, 2, 3];let c: [int; 3] = [1, 2, 3]; // [类型; 数组长度]let d: ["my value"; 3]; //["my value", "my value", "my value"];let e: [i32; 0] = []; // 空数组println!("{:?}", a); //[1, 2, 3]
数组(arrays)的长度是可不变的,动态/可变长数组可以使用 Vec (非基本数据类型)。
- 元组(tuples),由相同/不同类型元素构成,长度固定。
let a = (1, 1.5, true, 'a', "Hello, world!");// a.0 = 1, a.1 = 1.5, a.2 = true, a.3 = 'a', a.4 = "Hello, world!"let b: (i32, f64) = (1, 1.5);let (c, d) = b; // c = 1, d = 1.5let (e, _, _, _, f) = a; //e = 1, f = "Hello, world!", _ 作为占位符使用,表示忽略该位置的变量let g = (0,); // 只包含一个元素的元组let h = (b, (2, 4), 5); //((1, 1.5), (2, 4), 5)println!("{:?}", a); //(1, 1.5, true, 'a', "Hello, world!")
元组的长度也是不可变的,更新元组内元素的值时,需要与之前的值的类型相同。
- 切片(slice),指向一段内存的指针。
切片并没有拷贝原有的数组,只是指向原有数组的一个连续部分,行为同数组。访问切片指向的数组/数据结构,可以使用&操作符。
let a: [i32; 4] = [1, 2, 3, 4];let b: &[i32] = &a; // 全部let c = &a[0..4]; // [0, 4)let d = &a[..]; // 全部let e =