设立网站,百度文库登录入口,太原网站优化方案,最牛论坛网站Rust 官网#xff1a;https://www.rust-lang.org/zh-CN/
模块 库#xff1a;https://crates.io/ 1、Rust 简介 Rust 语言的主要目标之一是解决传统 系统级编程语言#xff08;如 C 和 C#xff09;中常见的安全性问题#xff0c;例如空指针引用、数据竞争等。为了实现这个…
Rust 官网https://www.rust-lang.org/zh-CN/
模块 库https://crates.io/ 1、Rust 简介 Rust 语言的主要目标之一是解决传统 系统级编程语言如 C 和 C中常见的安全性问题例如空指针引用、数据竞争等。为了实现这个目标Rust 引入了一种称为 所有权 的概念通过静态检查来确保内存安全和线程安全。此外Rust 还具有其他一些特性如模式匹配、代数数据类型、函数式编程风格的特性如闭包和高阶函数等。它还提供了丰富的标准库和包管理器 Cargo使得开发者可以轻松构建和管理他们的项目。
Rust 是一门注重安全safety、速度speed和并发concurrency的现代系统编程语言。Rust 通过内存安全来实现以上目标但不使用垃圾回收机制garbage collection, GC。
Rust 是 静态类型statically typed语言也就是说在编译时就必须知道所有变量的类型。 Rust 特点
高性能Rust 速度惊人且内存利用率极高。由于没有运行时和垃圾回收它能够胜任对性能要求特别高的服务可以在嵌入式设备上运行还能轻松和其他语言集成。可靠性Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全让您在编译期就能够消除各种各样的错误。生产力Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息 还集成了一流的工具——包管理器和构建工具 智能地自动补全和类型检验的多编辑器支持 以及自动格式化代码等等。 Rust 相关概念
channelRust 会发布3个不同版本stable、beta、nightly。 stableRust 的稳定版本每 6 周发布一次。 betaRust 的公开测试版本将是下一个 stable 版本。 nightly每天更新包含以一些实验性的新特性。toolchain一套 Rust 组件包括编译器及其相关工具并且包含 channel版本及支持的平台信息。target指编译的目标平台即编译后的程序在哪种操作系统上运行。component (组件)toolchain 是由 component 组成的。查看所有可用和已经安装的组件命令如下rustup component list。rustup 默认安装的组件 rustcRust 编译器。 rust-stdRust 标准库。 cargo包管理和构建工具。 rust-docsRust 文档。 rustfmt用来格式化 Rust 源代码。 clippyRust 的代码检查工具。profile为了方便对 component 进行管理使用 profile 定义一组 component。不同的 profile 包含不同的组件安装 rustup 时有三种 profile 可选修改 profile 命令如下rustup set profile minimalRustup 是什么Rustup 是 Rust安装器和版本管理工具。安装 Rust 的主要方式是通过 Rustup 这一工具它既是一个 Rust 安装器又是一个版本管理工具。Rust 的升级非常频繁。运行 rustup update 获取最新版本的 Rust。文档https://rust-lang.github.io/rustup/Cargo 是什么Cargo 是 Rust 的 构建工具 和 包管理器。安装 Rustup 时会自动安装。Cargo 可以做很多事情 cargo build 可以构建项目 cargo run 可以运行项目 cargo test 可以测试项目 cargo doc 可以为项目构建文档 cargo publish 可以将库发布到 crates.io。 检查是否安装了 Rust 和 Cargo可以在终端中运行cargo --version 下载、安装
下载https://www.rust-lang.org/tools/install 安装https://www.rust-lang.org/zh-CN/learn/get-started
默认情况Rust 依赖 C build tools没有安装也关系。安装过程需要保证网络正常。
在 Rust 开发环境中所有工具都安装在 ~/.cargo/bin 目录中可以在这里找到包括 rustc、cargo 和 rustup 在内的 Rust 工具链。在安装过程中rustup 会尝试配置 PATH如果 rustup 对 PATH 的修改不生效可以手动添加路径到 PATH ~/.cargo/bin ~/.rustup/bin 以下是一些常用的命令
rustup 相关 rustup -h # 查看帮助 rustup show # 显示当前安装的工具链信息 rustup update # 检查安装更新 rustup self uninstall # 卸载 rustup default stable-x86_64-pc-windows-gnu # 设置当前默认工具链 rustup toolchain list # 查看工具链 rustup toolchain install stable-x86_64-pc-windows-gnu # 安装工具链 rustup toolchain uninstall stable-x86_64-pc-windows-gnu # 卸载工具链 rustup toolchain link toolchain-name toolchain-path # 设置自定义工具链 rustup override list # 查看已设置的默认工具链 rustup override set toolchain --path path # 设置该目录以及其子目录的默认工具链 rustup override unset --path path # 取消目录以及其子目录的默认工具链 rustup target list # 查看目标列表 rustup target add target # 安装目标 rustup target remove target # 卸载目标 rustup target add --toolchain toolchain target # 为特定工具链安装目标 rustup component list # 查看可用组件 rustup component add component # 安装组件 rustup component remove component # 卸载组件 rustc 相关 rustc --version # 查看rustc版本 cargo 相关 cargo --version # 查看cargo版本 cargo new project_name # 新建项目 cargo build # 构建项目 cargo run # 运行项目 cargo check # 检查项目 cargo -h # 查看帮助 配置工具链安装位置 在系统环境变量中添加如下变量 CARGO_HOME 指定 cargo 的安装目录 RUSTUP_HOME 指定 rustup 的安装目录 默认分别安装到用户目录下的.cargo 和.rustup 目录 配置国内镜像
配置 rustup 国内镜像。在系统环境变量中添加如下变量选一个就可以可以组合 # 清华大学 RUSTUP_DIST_SERVERhttps://mirrors.tuna.tsinghua.edu.cn/rustup RUSTUP_UPDATE_ROOThttps://mirrors.tuna.tsinghua.edu.cn/rustup/rustup # 中国科学技术大学 RUSTUP_DIST_SERVERhttps://mirrors.ustc.edu.cn/rust-static RUSTUP_UPDATE_ROOThttps://mirrors.ustc.edu.cn/rust-static/rustup 配置 cargo 国内镜像。在 cargo 安装目录下新建 config 文件注意 config 没有任何后缀文件内容如下 [source.crates-io] registry https://github.com/rust-lang/crates.io-index replace-with tuna # 清华大学 [source.tuna] registry https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git # 中国科学技术大学 [source.ustc] registry git://mirrors.ustc.edu.cn/crates.io-index # 设置代理 [http] proxy 127.0.0.1:8889 [https] proxy 127.0.0.1:8889 Windows 交叉编译 Linux 程序。目标服务器是 Linux(CentOS 7) 64bit, 所以我们添加的 target 应该是x86_64-unknown-linux-gnu(动态依赖) 或者x86_64-unknown-linux-musl静态依赖
动态依赖目标服务器需要包含动态依赖的相关库用户共享库静态依赖目标服务器不需要包含相应的库但是打包文件会更大些
1). 添加需要的 target rustup target add x86_64-unknown-linux-musl 2). 在 cargo 安装目录下新建 config 文件注意 config 没有任何后缀添加的文件内容如下 [target.x86_64-unknown-linux-musl] linker rust-lld 3). 构建 cargo build --target x86_64-unknown-linux-musl 示例
创建新项目
用 Cargo 创建一个新项目。在终端中执行cargo new hello-rust会生成一个名为 hello-rust 的新目录其中包含以下文件 Cargo.toml 为 Rust 的清单文件。其中包含了项目的元数据和依赖库。src/main.rs 为编写应用代码的地方。
进入新创建的目录中执行命令运行此程序cargo run 添加 依赖
现在来为程序添加依赖。可以在 crates.io即 Rust 包的仓库中找到所有类别的库。在 Rust 中通常把 包 称作 crates。在本项目中使用了名为 ferris-says 的库。
在 Cargo.toml 文件中添加以下信息从 crate 页面上获取 [dependencies] ferris-says 0.3.1 下载 依赖 运行cargo build Cargo 就会安装该依赖。运行 build 会创建一个新文件 Cargo.lock该文件记录了本地所用依赖库的精确版本。 使用 依赖 使用该依赖库可以打开 main.rs删除其中所有的内容它不过是个示例而已然后在其中添加下面这行代码use ferris_says::say; 这样就可以使用 ferris-says crate 中导出的 say 函数了。 完整 Rust 示例
现在用上面的依赖库编写一个小应用。在 main.rs 中添加以下代码
use ferris_says::say; // from the previous step
use std::io::{stdout, BufWriter};fn main() {let stdout stdout();let message String::from(Hello fellow Rustaceans!);let width message.chars().count();let mut writer BufWriter::new(stdout.lock());say(message, width, mut writer).unwrap();
}
保存完毕后执行命令运行程序cargo run 成功执行后会打印一个字符形式的螃蟹图案。 Ferris ( 费理斯 ) 是 Rust 社区的非官方吉祥物。 2、Rust 相关文档 https://www.rust-lang.org/zh-CN/learn 核心文档
以下所有文档都可以用 rustup doc 命令在本地阅读它会在浏览器中离线打开这些资源 标准库 详尽的 Rust 标准库 API 手册。https://doc.rust-lang.org/std/index.html 版本指南 Rust 版本指南。https://doc.rust-lang.org/edition-guide/index.html CARGO 手册 Rust 的包管理器和构建系统。https://doc.rust-lang.org/cargo/index.html RUSTDOC 手册 学习如何为 crate 编写完美的文档。https://doc.rust-lang.org/rustdoc/index.html RUSTC 手册 熟悉 Rust 编译器中可用的选项。https://doc.rust-lang.org/rustc/index.html 编译错误索引表 深入解释遇到的编译错误。https://doc.rust-lang.org/error_codes/error-index.html Rust 程序
命令行 程序 用 Rust 构建高效的命令行应用。https://rust-cli.github.io/book/index.html WEBASSEMBLY 手册 通过 WebAssembly 用 Rust 构建浏览器原生的库。https://rustwasm.github.io/docs/book/ 嵌入式手册 Rust 编写嵌入式程序。https://doc.rust-lang.org/stable/embedded-book/ Learn X in Y
// 这是注释单行注释...
/* ...这是多行注释 *////
// 1. 基础 //
///// 函数 (Functions)
// i32 是有符号 32 位整数类型(32-bit signed integers)
fn add2(x: i32, y: i32) - i32 {// 隐式返回 (不要分号)x y
}// 主函数(Main function)
fn main() {// 数字 (Numbers) //// 不可变绑定let x: i32 1;// 整形/浮点型数 后缀let y: i32 13i32;let f: f64 1.3f64;// 类型推导// 大部分时间Rust 编译器会推导变量类型所以不必把类型显式写出来。// 这个教程里面很多地方都显式写了类型但是只是为了示范。// 绝大部分时间可以交给类型推导。let implicit_x 1;let implicit_f 1.3;// 算术运算let sum x y 13;// 可变变量let mut mutable 1;mutable 4;mutable 2;// 字符串 (Strings) //// 字符串字面量let x: str hello world!;// 输出println!({} {}, f, x); // 1.3 hello world// 一个 String – 在堆上分配空间的字符串let s: String hello world.to_string();// 字符串分片(slice) - 另一个字符串的不可变视图// 基本上就是指向一个字符串的不可变指针它不包含字符串里任何内容只是一个指向某个东西的指针// 比如这里就是 slet s_slice: str s;println!({} {}, s, s_slice); // hello world hello world// 数组 (Vectors/arrays) //// 长度固定的数组 (array)let four_ints: [i32; 4] [1, 2, 3, 4];// 变长数组 (vector)let mut vector: Veci32 vec![1, 2, 3, 4];vector.push(5);// 分片 - 某个数组(vector/array)的不可变视图// 和字符串分片基本一样只不过是针对数组的let slice: [i32] vector;// 使用 {:?} 按调试样式输出println!({:?} {:?}, vector, slice); // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]// 元组 (Tuples) //// 元组是固定大小的一组值可以是不同类型let x: (i32, str, f64) (1, hello, 3.4);// 解构 letlet (a, b, c) x;println!({} {} {}, a, b, c); // 1 hello 3.4// 索引println!({}, x.1); // hello//// 2. 类型 (Type) ////// 结构体Sturct)struct Point {x: i32,y: i32,}let origin: Point Point { x: 0, y: 0 };// 匿名成员结构体又叫“元组结构体”‘tuple struct’struct Point2(i32, i32);let origin2 Point2(0, 0);// 基础的 C 风格枚举类型enumenum Direction {Left,Right,Up,Down,}let up Direction::Up;// 有成员的枚举类型enum OptionalI32 {AnI32(i32),Nothing,}let two: OptionalI32 OptionalI32::AnI32(2);let nothing OptionalI32::Nothing;// 泛型 (Generics) //struct FooT { bar: T }// 这个在标准库里面有实现叫 Optionenum OptionalT {SomeVal(T),NoVal,}// 方法 (Methods) //implT FooT {// 方法需要一个显式的 self 参数fn get_bar(self) - T {self.bar}}let a_foo Foo { bar: 1 };println!({}, a_foo.get_bar()); // 1// 接口Traits 其他语言里叫 interfaces 或 typeclasses //trait FrobnicateT {fn frobnicate(self) - OptionT;}implT FrobnicateT for FooT {fn frobnicate(self) - OptionT {Some(self.bar)}}let another_foo Foo { bar: 1 };println!({:?}, another_foo.frobnicate()); // Some(1)///// 3. 模式匹配 (Pattern matching) /////let foo OptionalI32::AnI32(1);match foo {OptionalI32::AnI32(n) println!(it’s an i32: {}, n),OptionalI32::Nothing println!(it’s nothing!),}// 高级模式匹配struct FooBar { x: i32, y: OptionalI32 }let bar FooBar { x: 15, y: OptionalI32::AnI32(32) };match bar {FooBar { x: 0, y: OptionalI32::AnI32(0) } println!(The numbers are zero!),FooBar { x: n, y: OptionalI32::AnI32(m) } if n m println!(The numbers are the same),FooBar { x: n, y: OptionalI32::AnI32(m) } println!(Different numbers: {} {}, n, m),FooBar { x: _, y: OptionalI32::Nothing } println!(The second number is Nothing!),}///// 4. 流程控制 (Control flow) /////// for 循环let array [1, 2, 3];for i in array {println!({}, i);}// 区间 (Ranges)for i in 0u32..10 {print!({} , i);}println!();// 输出 0 1 2 3 4 5 6 7 8 9 // ifif 1 1 {println!(Maths is working!);} else {println!(Oh no...);}// if 可以当表达式let value if true {good} else {bad};// while 循环while 1 1 {println!(The universe is operating normally.);}// 无限循环loop {println!(Hello!);}// 5. 内存安全和指针 (Memory safety pointers) //// 独占指针 (Owned pointer) - 同一时刻只能有一个对象能“拥有”这个指针// 意味着 Box 离开他的作用域后会被安全地释放let mut mine: Boxi32 Box::new(3);*mine 5; // 解引用// now_its_mine 获取了 mine 的所有权。换句话说mine 移动 (move) 了let mut now_its_mine mine;*now_its_mine 2;println!({}, now_its_mine); // 7// println!({}, mine); // 编译报错因为现在 now_its_mine 独占那个指针// 引用 (Reference) – 引用其他数据的不可变指针// 当引用指向某个值我们称为“借用”这个值因为是被不可变的借用所以不能被修改也不能移动// 借用一直持续到生命周期结束即离开作用域let mut var 4;var 3;let ref_var: i32 var;println!({}, var); //不像 mine, var 还可以继续使用println!({}, *ref_var);// var 5; // 编译报错因为 var 被借用了// *ref_var 6; // 编译报错因为 ref_var 是不可变引用// 可变引用 (Mutable reference)// 当一个变量被可变地借用时也不可使用let mut var2 4;let ref_var2: mut i32 mut var2;*ref_var2 2;println!({}, *ref_var2); // 6// var2 2; // 编译报错因为 var2 被借用了
}
rust 打印占位符
在 Rust 中打印的占位符由格式化宏提供最常用的是 println! 和 format!。下面是一些常见的占位符及其用法 {}默认占位符根据值的类型自动选择合适的显示方式。 {:?}调试占位符用于打印调试信息。通常用于 Debug trait 的实现。 {:#?}类似于 {:?}但打印出更具可读性的格式化调试信息可以嵌套显示结构体和枚举的字段。 {x}将变量 x 的值插入到占位符的位置。 {x:format}将变量 x 按照指定的格式进行格式化输出。例如{x:?}, {x:b}, {x:e} 等。
这只是一小部分常见的占位符用法你还可以根据需要使用其他格式化选项。Rust 的格式化宏提供了非常灵活和强大的格式化功能可以满足大多数打印需求。
fn main() {let name Alice;let age 25;let height 1.65;println!(Name: {}, name);println!(Age: {}, age);println!(Height: {:.2}, height); // 格式化为小数点后两位let point (3, 5);println!(Point: {:?}, point);
}打印 枚举、结构体
#[derive(Debug)]
enum MyEnum {Variant1,Variant2(u32),Variant3 { name: String, age: u32 },
}#[derive(Debug)]
struct MyStruct{field_1: String,field_2: usize,
}impl MyStruct {fn init_field(self){let name self.field_1;let age self.field_2;println!({name} --- {age})}
}fn main() {let my_enum MyEnum::Variant2(42);println!({:?}, my_enum);let my_struct MyStruct{field_1: String::from(king),field_2: 100};my_struct.init_field();println!({:?}, my_struct);
}3、Rust 程序设计语言 英文文档https://doc.rust-lang.org/book/
中文文档https://kaisery.github.io/trpl-zh-cn/
《Rust 程序设计语言》被亲切地称为“圣经”。给出了 Rust 语言的概览。在阅读的过程中构建几个项目读完后就能扎实地掌握 Rust 语言。
1. 入门指南 1.1. 安装1.2. Hello, World!1.3. Hello, Cargo!2. 写个猜数字游戏3. 常见编程概念 3.1. 变量与可变性3.2. 数据类型3.3. 函数3.4. 注释3.5. 控制流4. 认识所有权 4.1. 什么是所有权4.2. 引用与借用4.3. Slice 类型5. 使用结构体组织相关联的数据 5.1. 结构体的定义和实例化5.2. 结构体示例程序5.3. 方法语法6. 枚举和模式匹配 6.1. 枚举的定义6.2. match 控制流结构6.3. if let 简洁控制流7. 使用包、Crate 和模块管理不断增长的项目 7.1. 包和 Crate7.2. 定义模块来控制作用域与私有性7.3. 引用模块项目的路径7.4. 使用 use 关键字将路径引入作用域7.5. 将模块拆分成多个文件8. 常见集合 8.1. 使用 Vector 储存列表8.2. 使用字符串储存 UTF-8 编码的文本8.3. 使用 Hash Map 储存键值对9. 错误处理 9.1. 用 panic! 处理不可恢复的错误9.2. 用 Result 处理可恢复的错误9.3. 要不要 panic!10. 泛型、Trait 和生命周期 10.1. 泛型数据类型10.2. Trait定义共同行为10.3. 生命周期确保引用有效11. 编写自动化测试 11.1. 如何编写测试11.2. 控制测试如何运行11.3. 测试的组织结构12. 一个 I/O 项目构建命令行程序 12.1. 接受命令行参数12.2. 读取文件12.3. 重构以改进模块化与错误处理12.4. 采用测试驱动开发完善库的功能12.5. 处理环境变量12.6. 将错误信息输出到标准错误而不是标准输出13. Rust 中的函数式语言功能迭代器与闭包 13.1. 闭包可以捕获其环境的匿名函数13.2. 使用迭代器处理元素序列13.3. 改进之前的 I/O 项目13.4. 性能比较循环对迭代器14. 更多关于 Cargo 和 Crates.io 的内容 14.1. 采用发布配置自定义构建14.2. 将 crate 发布到 Crates.io14.3. Cargo 工作空间14.4. 使用 cargo install 安装二进制文件14.5. Cargo 自定义扩展命令15. 智能指针 15.1. 使用BoxT 指向堆上数据15.2. 使用Deref Trait 将智能指针当作常规引用处理15.3. 使用Drop Trait 运行清理代码15.4. RcT 引用计数智能指针15.5. RefCellT 与内部可变性模式15.6. 引用循环会导致内存泄漏16. 无畏并发 16.1. 使用线程同时地运行代码16.2. 使用消息传递在线程间通信16.3. 共享状态并发16.4. 使用Sync 与 Send Traits 的可扩展并发17. Rust 的面向对象编程特性 17.1. 面向对象语言的特点17.2. 为使用不同类型的值而设计的 trait 对象17.3. 面向对象设计模式的实现18. 模式与模式匹配 18.1. 所有可能会用到模式的位置18.2. Refutability可反驳性: 模式是否会匹配失效18.3. 模式语法19. 高级特征 19.1. 不安全的 Rust19.2. 高级 trait19.3. 高级类型19.4. 高级函数与闭包19.5. 宏20. 最后的项目构建多线程 web server 20.1. 建立单线程 web server20.2. 将单线程 server 变为多线程 server20.3. 优雅停机与清理21. 附录 21.1. A - 关键字21.2. B - 运算符与符号21.3. C - 可派生的 trait21.4. D - 实用开发工具21.5. E - 版本21.6. F - 本书译本21.7. G - Rust 是如何开发的与 “Nightly Rust” Rust 数据类型
Rust 是 静态类型statically typed语言也就是说在编译时就必须知道所有变量的类型。
标量、复合
Rust 2大类数据类型
标量scalar代表一个单独的值。4种基本的标量整型、浮点型、布尔类型、字符。字符串String类型由 Rust 标准库提供而不是编入核心语言。在 Rust 中字符串字面值 使用双引号括起来例如Hello, World!。这是一种字符串类型的常量表示方法。而普通的字符串类型则是指动态可变的字符串即 String 类型。字符串字面值是静态不可变的不能修改其中的内容。你可以直接使用字符串字面值进行一些简单的操作如拼接、切割等但无法修改它们的值。字符串字面值就是 String 的 slice复合compoundRust 有两个原生的复合类型元组tuple、数组array。元组 是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定一旦声明其长度不会增大或缩小。 另一个包含多个值的方式是 数组array。与元组不同数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同Rust 中的数组长度是固定的。
Rust 标准库中包含一系列被称为 集合collections的非常有用的数据结构。大部分其他数据类型都代表一个特定的值不过集合可以包含多个值。不同于内建的数组和元组类型这些集合指向的数据是储存在堆上的这意味着数据的数量不必在编译时就已知并且还可以随着程序的运行增长或缩小。
三个在 Rust 程序中被广泛使用的集合
vector 一个挨着一个地储存一系列数量可变的值。为了创建一个新的空 vector可以调用 Vec::new 函数会用初始值来创建一个 VecT 而 Rust 会推断出储存值的类型所以很少会需要这些类型注解。为了方便 Rust 提供了 vec! 宏这个宏会根据我们提供的值来创建一个新的 vector。字符串string是字符的集合。我们之前见过 String 类型不过在本章我们将深入了解。哈希 maphash map允许我们将值与一个特定的键key相关联。这是一个叫做 map 的更通用的数据结构的特定实现。
对于标准库提供的其他类型的集合请查看文档。
vec 示例
fn main() {let mut v Vec::new();v.push(5);v.push(6);v.push(7);v.push(8);println!({:?}, v);let v vec![1, 2, 3, 4, 5];let third: i32 v[2];println!(The third element is {third});let third: Optioni32 v.get(2);match third {Some(third) println!(The third element is {third}),None println!(There is no third element.),}
}
fn main() {let mut v vec![100, 32, 57];for i in mut v {// 为了修改可变引用所指向的值在使用 运算符之前// 必须使用解引用运算符*获取 i 中的值。*i 100;}for i in v{println!({i})}
}示例
fn main() {let mut v Vec::new();v.push(5);v.push(6);v.push(7);v.push(8);println!({:?}, v);let v vec![1, 2, 3, 4, 5];let third: i32 v[2];println!(The third element is {third});let third: Optioni32 v.get(2);match third {Some(third) println!(The third element is {third}),None println!(There is no third element.),}let mut v vec![100, 32, 57];for i in mut v {*i 100;}for i in v{println!({i})}#[derive(Debug)]enum SpreadsheetCell {Int(i32),Float(f64),Text(String),}// 使用枚举来存储多个类型类比 Python 的 listlet row vec![SpreadsheetCell::Int(3),SpreadsheetCell::Text(String::from(blue)),SpreadsheetCell::Float(10.12),];for cell in row {match cell {SpreadsheetCell::Int(value) println!(整数值: {}, value),SpreadsheetCell::Text(value) println!(文本值: {}, value),SpreadsheetCell::Float(value) println!(浮点数值: {}, value),}}
}
Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加 match 意味着 Rust 能在编译时就保证总是会处理所有可能的情况
如果在编写程序时不能确切无遗地知道运行时会储存进 vector 的所有类型枚举技术就行不通了。相反你可以使用 trait 对象
字符串
Rust 的核心语言中只有一种字符串类型字符串 slice str它通常以被借用的形式出现str。第四章讲到了 字符串 slices它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说由于字符串字面值被储存在程序的二进制输出中因此字符串字面值也是字符串 slices。
fn main() {let s1 String::from(Hello, );let s2 String::from(world!);let s3 s1 s2; // 注意 s1 被移动了不能继续使用println!(s2 --- {s2});println!(s3 --- {s3});let s1 String::from(tic);let s2 String::from(tac);let s3 String::from(toe);// 宏 format! 生成的代码使用引用所以不会获取任何参数的所有权。let s format!({s1}-{s2}-{s3}); 索引字符串println!(s --- {s});println!(s1 --- {s1});println!(s2 --- {s2});println!(s3 --- {s3});
}哈希 mapHashMapK, V
HashMapK, V 类型储存了一个键类型 K 对应一个值类型 V 的映射。它通过一个 哈希函数hashing function来实现映射决定如何将键和值放入内存中。很多编程语言支持这种数据结构不过通常有不同的名字哈希、map、对象、哈希表、关联数组、Python的字典(Dict) 等。
可以使用 new 创建一个空的 HashMap并使用 insert 增加元素。
fn main() {use std::collections::HashMap;let mut scores HashMap::new();scores.insert(String::from(Blue), 10);scores.insert(String::from(Yellow), 50);
}必须首先 use 标准库中集合部分的 HashMap。在这三个常用集合中HashMap 是最不常用的所以并没有被 prelude 自动引用。标准库中对 HashMap 的支持也相对较少例如并没有内建的构建宏。类似于 vector哈希 map 是同质的所有的键必须是相同类型值也必须都是相同类型。 Rust 的 所有权
https://kaisery.github.io/trpl-zh-cn/ch04-01-what-is-ownership.html
所有权系统是 Rust 最为与众不同的特性对语言的其他部分有着深刻含义。它让 Rust 无需垃圾回收garbage collector即可保障内存安全因此理解 Rust 中所有权如何工作是十分重要的。 所有权的规则 Rust 中的每一个值都有一个 所有者owner。值在任一时刻有且只有一个所有者。当所有者变量离开作用域这个值将被 丢弃(释放内存空间)。 变量、作用域
变量是否有效与作用域的关系跟其他编程语言是类似的。 fn main() { { // s 在这里无效它尚未声明 let s hello; // 从此处起s 是有效的 // 使用 s } // 此作用域已结束s 不再有效清理并drop(释放掉)内存空间 } 这里有两个重要的时间点 当 s 进入作用域 时它就是有效的。这一直持续到它 离开作用域 为止。 变量与数据交互移动 (浅拷贝)、克隆 (深拷贝)
浅拷贝shallow copy和 深拷贝deep copy浅拷贝拷贝指针、长度和容量而不拷贝指针所指向内存空间的数据。深拷贝拷贝指针、长度和容量同时也拷贝指针所指向内存空间的数据。 fn main() { let s1 String::from(hello); let s2 s1; // 在 C 中这里会发生浅拷贝不过在 Rust 中会使第一个变量无效这个操作被称为 移动move而不是叫做浅拷贝。为了确保内存安全在 let s2 s1; 之后Rust 认为 s1 不再有效因此 Rust 不需要在 s1 离开作用域后清理任何东西。 println!({}, world!, s1); // 看看在 s2 被创建之后尝试使用 s1 会发生什么这里会报错 // 因为只有 s2 是有效的当其离开作用域它就释放自己的内存 } 这里隐含了 Rust 的一个设计选择Rust 永远也不会自动创建数据的 “深拷贝”。 变量的 移动(转移)
只要进行 赋值()、函数传参 都会有 移动 移动 ( 转移 )变量的所有权总是遵循相同的模式将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时其值将通过 drop 被清理掉除非数据被移动为另一个变量所有。 变量的 克隆 确实 需要深度复制 String 中堆上的数据而不仅仅是栈上的数据可以使用一个叫做 clone 的通用函数。 fn main() { let s1 String::from(hello); let s2 s1.clone(); println!(s1 {}, s2 {}, s1, s2); } 只在栈上的数据拷贝 fn main() { let x 5; let y x; println!(x {}, y {}, x, y); } 一个通用的规则任何一组简单标量值的组合都可以实现 Copy任何不需要分配内存或某种形式资源的类型都可以实现 Copy 。如下是一些 Copy 的类型 所有整数类型比如 u32。布尔类型bool它的值是 true 和 false。所有浮点数类型比如 f64。字符类型char。元组当且仅当其包含的类型也都实现 Copy 的时候。比如(i32, i32) 实现了 Copy但 (i32, String) 就没有。 所有权与函数 将值传递给函数与给变量赋值的原理相似。向函数传递值可能会移动或者复制就像赋值语句一样。 fn main() { let s String::from(hello); // s 进入作用域 takes_ownership(s); // s 的值移动到函数里 ... // ... 所以到这里不再有效 let x 5; // x 进入作用域 makes_copy(x); // x 应该移动函数里 // 但 i32 是 Copy 的 // 所以在后面可继续使用 x } // 这里x 先移出了作用域然后是 s。但因为 s 的值已被移走 // 没有特殊之处 fn takes_ownership(some_string: String) { // some_string 进入作用域 println!({}, some_string); } // 这里some_string 移出作用域并调用 drop 方法。 // 占用的内存被释放 fn makes_copy(some_integer: i32) { // some_integer 进入作用域 println!({}, some_integer); } // 这里some_integer 移出作用域。没有特殊之处 返回值与作用域 返回值也可以转移所有权。 fn main() { let s1 gives_ownership(); // gives_ownership 将返回值 // 转移给 s1 let s2 String::from(hello); // s2 进入作用域 let s3 takes_and_gives_back(s2); // s2 被移动到 // takes_and_gives_back 中 // 它也将返回值移给 s3 } // 这里s3 移出作用域并被丢弃。s2 也移出作用域但已被移走 // 所以什么也不会发生。s1 离开作用域并被丢弃 fn gives_ownership() - String { // gives_ownership 会将 // 返回值移动给 // 调用它的函数 let some_string String::from(yours); // some_string 进入作用域。 some_string // 返回 some_string // 并移出给调用的函数 // } // takes_and_gives_back 将传入字符串并返回该值 fn takes_and_gives_back(a_string: String) - String { // a_string 进入作用域 // a_string // 返回 a_string 并移出给调用的函数 } 引用 ( 借用 )
在任意给定时间要么 只能有一个可变引用要么 只能有多个不可变引用。同时出现时 可变引用、不可变引用 作用域 不能重叠引用不传递所有权。引用必须总是有效的。 引用 ( 借用 ) 符号就是 引用它们允许你使用值但不获取其所有权因为没有所有权所以在离开作用域时不会进行清理释放内存。引用reference像一个指针因为它是一个地址我们可以由此访问储存于该地址的属于其他变量的数据。 与指针不同引用确保指向某个特定类型的有效值。 fn main() { let s1 String::from(hello); let len calculate_length(s1); println!(The length of {} is {}., s1, len); } fn calculate_length(s: String) - usize { // s 是 String 的引用 s.len() } // 这里s 离开了作用域。但因为它并不拥有引用值的所有权 // 所以什么也不会发生 变量 s 有效的作用域与函数参数的作用域一样不过当 s 停止使用时并不丢弃引用指向的数据因为 s 并没有所有权。当函数使用引用而不是实际值作为参数无需返回值来交还所有权因为就不曾拥有所有权。 总结将创建一个引用的行为称为 借用borrowing。正如现实生活中如果一个人拥有某样东西你可以从他那里借来。当你使用完毕必须还回去。因为并不拥有它。 正如变量默认是不可变的引用也一样。默认不允许修改引用的值。 如果想要修改引用的值就需要用到 可变引用mutable reference。可变引用有一个很大的限制如果创建了一个变量的可变引用就不能再创建对该变量的引用。不可变引用的值本身就不希望被改变一个变量可以有多个不可变引用。 fn main() { let mut s String::from(hello); let r1 mut s; let r2 mut s; println!({}, {}, r1, r2); } 这个报错说这段代码是无效的因为我们不能在同一时间多次将 s 作为可变变量借用。第一个可变的借入在 r1 中并且必须持续到在 println 中使用它但是在那个可变引用的创建和它的使用之间我们又尝试在 r2 中创建另一个可变引用该引用借用与 r1 相同的数据。 这一限制以一种非常小心谨慎的方式允许可变性防止同一时间对同一数据存在多个可变引用。新 Rustacean 们经常难以适应这一点因为大部分语言中变量任何时候都是可变的。这个限制的好处是 Rust 可以在编译时就避免数据竞争。数据竞争data race类似于竞态条件它可由这三个行为造成 两个或更多指针同时访问同一数据。至少有一个指针被用来写入数据。没有同步数据访问的机制。 数据竞争会导致未定义行为难以在运行时追踪并且难以诊断和修复Rust 避免了这种情况的发生因为它甚至不会编译存在数据竞争的代码 可以使用大括号来创建一个新的作用域以允许拥有多个可变引用只是不能 同时 拥有 fn main() { let mut s String::from(hello); { let r1 mut s; } // r1 在这里离开了作用域所以我们完全可以创建一个新的引用 let r2 mut s; } Rust 在同时使用可变与不可变引用时也采用的类似的规则。这些代码会导致一个错误 fn main() { let mut s String::from(hello); let r1 s; // 没问题 let r2 s; // 没问题 let r3 mut s; // 大问题 println!({}, {}, and {}, r1, r2, r3); } // r1、r2、r3 作用域都是到这里结束但是上面打印时r1、r2 生效时 r3 也生效所以报错。因为 rust 会自动判断 变量引用的作用域是否重叠所以可以调整 println 的顺序即可。 fn main() { let mut s String::from(hello); let r1 s; // 没问题 let r2 s; // 没问题 println!({}, {}, r1, r2); let r3 mut s; // 大问题 println!({}, r3); } 不可变引用 r1 和 r2 的作用域在 println! 最后一次使用之后结束这也是创建可变引用 r3 的地方。它们的作用域没有重叠所以代码是可以编译的。编译器可以在作用域结束之前判断不再使用的引用。记住这是 Rust 编译器在提前指出一个潜在的 bug 的规定。 悬垂引用Dangling References 在具有指针的语言中很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针dangling pointer所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下在 Rust 中编译器确保引用永远也不会变成悬垂状态当你拥有一些数据的引用编译器确保数据不会在其引用之前离开作用域。 fn main() { let reference_to_nothing dangle(); } fn dangle() - String { // dangle 返回一个字符串的引用 let s String::from(hello); // s 是一个新字符串 s // 返回字符串 s 的引用但是引用不转移所有权所以函数结束时s 被销毁释放内存 } // 这里 s 离开作用域并被丢弃。其内存被释放。 // 危险 正确的做法不返回引用。 fn main() { let string no_dangle(); } fn no_dangle() - String { let s String::from(hello); s // 这样就没有任何错误了。所有权被移动出去所以没有值被释放。 } Slice 类型slice 允许你引用集合中一段连续的元素序列而不用引用整个集合。slice 是一类引用所以它同样没有所有权。 枚举、结构体
枚举enumerations也被称作 enums。枚举允许你通过列举可能的 成员variants来定义一个类型。struct或者 structure是一个自定义数据类型允许你包装和命名多个相关的值从而形成一个有意义的组合。结构体可以定义方法。结构体作用就是将字段和数据聚合在一块形成新的数据类型。
fn main() {enum IpAddr {V4(String),V6(String),}let home IpAddr::V4(String::from(127.0.0.1));let loopback IpAddr::V6(String::from(::1));match home {IpAddr::V4(ip) println!(Home IPv4 地址是: {}, ip),IpAddr::V6(ip) println!(Home IPv6 地址是: {}, ip),}match loopback {IpAddr::V4(ip) println!(Loopback IPv4 地址是: {}, ip),IpAddr::V6(ip) println!(Loopback IPv6 地址是: {}, ip),}
} Rust 的 Result
在 Rust 中Result 是一个枚举类型它代表了可能产生错误的操作的结果。Result 枚举有两个变体Ok 和 Err。
Ok 变体表示操作成功并包含操作返回的值。Err 变体表示操作失败并包含一个错误值用于描述错误的原因。
通常Result 类型被用于表示可能会发生错误的函数的返回类型。这样调用者可以通过检查 Result 来处理操作的成功或失败。简单的示例演示如何使用 Result
fn divide(a: i32, b: i32) - Resulti32, String {if b 0 {return Err(String::from(除数不能为零));}Ok(a / b)
}fn main() {let result divide(10, 2);match result {Ok(value) println!(结果是: {}, value),Err(error) println!(出现错误: {}, error),}
}示例 2
use std::io;
use std::io::stdin;fn main() {let mut input_str String::from();stdin().read_line(mut input_str).expect(获取输入失败);let input_int:usize match input_str.trim().parse() {Ok(n) n,Err(_) {println!(无效的输入);return;}};let result input_int * 100;println!({result})
}
match 控制流结构、匹配 OptionT
Rust 的 match 是极为强大的控制流运算符它允许将一个值与一系列的模式相比较并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成。 OptionT 时是为了从 Some 中取出其内部的 T 值 如果其中含有一个值则执行有值得流程。 如果其中没有值则执行没有值得流程。
fn main() {fn plus_one(x: Optioni32) - Optioni32 {match x {None None,Some(i) Some(i 1),}}let five Some(5);let six plus_one(five);let none plus_one(None);// 使用模式匹配match six {Some(value) println!(Some 值是: {}, value),None println!(None),}// 使用 unwrap() 方法if let Some(value) five {println!(Some 值是: {}, value);} else {println!(None);}if let Some(value) six {println!(Some 值是: {}, value);} else {println!(None);}
} 使用包、Crate、模块管理
Rust 有许多功能可以让你管理代码的组织包括哪些内容可以被公开哪些内容作为私有部分以及程序每个作用域中的名字。这些功能有时被统称为 “模块系统the module system”包括
crate 是 Rust 在编译时最小的代码单位。如果用 rustc 而不是 cargo 来编译一个文件时编译器会将那个文件认作一个 crate。crate 可以包含模块模块可以定义在其他文件然后和 crate 一起编译。crate 有两种形式二进制的可执行程序、lib库。Crates 一个模块的树形结构它形成了库或二进制项目。包PackagesCargo 的一个功能它允许你构建、测试和分享 crate。 包package是提供一系列功能的一个或者多个 crate。一个包会包含一个 Cargo.toml 文件阐述如何去构建这些 crate。Cargo 就是一个包含构建你代码的二进制项的包。Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。包中可以包含至多一个库 crate(library crate)。包中可以包含任意多个二进制 crate(binary crate)但是必须至少包含一个 crate无论是库的还是二进制的。 从 crate 根节点开始当编译一个 crate编译器首先在 crate 根文件通常对于一个库 crate 而言是 src/lib.rs对于一个二进制 crate 而言是 src/main.rs中寻找需要被编译的代码。声明模块: 在 crate 根文件中你可以声明一个新模块比如你用mod garden声明了一个叫做garden的模块。编译器会在下列路径中寻找模块代码 内联在大括号中当mod garden后方不是一个分号而是一个大括号在文件 src/garden.rs在文件 src/garden/mod.rs声明子模块: 在除了 crate 根节点以外的其他文件中你可以定义子模块。比如你可能在src/garden.rs中定义了mod vegetables;。编译器会在以父模块命名的目录中寻找子模块代码 内联在大括号中当mod vegetables后方不是一个分号而是一个大括号在文件 src/garden/vegetables.rs在文件 src/garden/vegetables/mod.rs模块中的代码路径: 一旦一个模块是你 crate 的一部分你可以在隐私规则允许的前提下从同一个 crate 内的任意地方通过代码路径引用该模块的代码。举例而言一个 garden vegetables 模块下的Asparagus类型可以在crate::garden::vegetables::Asparagus被找到。私有 vs 公用: 一个模块里的代码默认对其父模块私有。为了使一个模块公用应当在声明时使用pub mod替代mod。为了使一个公用模块内部的成员公用应当在声明前使用pub。use 关键字: 在一个作用域内use关键字创建了一个成员的快捷方式用来减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus的作用域你可以通过 use crate::garden::vegetables::Asparagus;创建一个快捷方式然后你就可以在作用域中只写Asparagus来使用该类型。
示例创建一个名为backyard的二进制 crate 来说明这些规则。该 crate 的路径同样命名为backyard该路径包含了这些文件和目录 这个例子中的 crate 根文件是src/main.rs该文件包括了
文件名src/main.rs use crate::garden::vegetables::Asparagus; pub mod garden; fn main() { let plant Asparagus {}; println!(Im growing {:?}!, plant); } pub mod garden;行告诉编译器应该包含在 src/garden.rs 文件中发现的代码
文件名src/garden.rs pub mod vegetables; 在此处 pub mod vegetables;意味着在src/garden/vegetables.rs中的代码也应该被包括。这些代码是 #[derive(Debug)] pub struct Asparagus {} 模块的路径有两种形式
绝对路径absolute path是以 crate 根root开头的全路径对于外部 crate 的代码是以 crate 名开头的绝对路径对于当前 crate 的代码则以字面值 crate 开头。相对路径relative path从当前模块开始以 self、super 或当前模块的标识符开头。
绝对路径和相对路径都后跟一个或多个由双冒号::分割的标识符。 4、通过例子学 Rust 英文文档https://doc.rust-lang.org/rust-by-example/
中文文档https://rustwiki.org/zh-CN/rust-by-example/
查看更多 Rust 官方文档中英文双语教程包括双语版《Rust 程序设计语言》出版书名为《Rust 权威指南》 Rust 标准库中文版。
《通过例子学 Rust》(Rust By Example 中文版)翻译自 Rust By Example中文版最后更新时间2022-1-26。查看此书的 Github 翻译项目和源码。 开始学习吧 Hello World - 从经典的 “Hello World” 程序开始学习。 原生类型 - 学习有符号整型无符号整型和其他原生类型。 自定义类型 - 结构体 struct 和 枚举 enum。 变量绑定 - 变量绑定作用域变量遮蔽。 类型系统 - 学习改变和定义类型。 类型转换 表达式 流程控制 - if/elsefor以及其他流程控制有关内容。 函数 - 学习方法、闭包和高阶函数。 模块 - 使用模块来组织代码。 Crate - crate 是 Rust 中的编译单元。学习创建一个库。 Cargo - 学习官方的 Rust 包管理工具的一些基本功能。 属性 - 属性是应用于某些模块、crate 或项的元数据metadata。 泛型 - 学习编写能够适用于多种类型参数的函数或数据类型。 作用域规则 - 作用域在所有权ownership、借用borrowing和生命周期lifetime中起着重要作用。 特性 trait - trait 是对未知类型Self定义的方法集。 宏 错误处理 - 学习 Rust 语言处理失败的方式。 标准库类型 - 学习 std 标准库提供的一些自定义类型。 标准库更多介绍 - 更多关于文件处理、线程的自定义类型。 测试 - Rust 语言的各种测试手段。 不安全操作 兼容性 补充 - 文档和基准测试