{"id":18768629,"url":"https://github.com/slimenull/rusttutorial","last_synced_at":"2026-03-19T06:17:48.487Z","repository":{"id":209137467,"uuid":"723326077","full_name":"SlimeNull/RustTutorial","owner":"SlimeNull","description":"Rust tutorial document","archived":false,"fork":false,"pushed_at":"2023-12-03T12:17:51.000Z","size":25,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-04T10:52:57.164Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SlimeNull.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-11-25T10:06:30.000Z","updated_at":"2024-02-09T01:46:22.000Z","dependencies_parsed_at":"2023-11-25T11:22:26.287Z","dependency_job_id":"1c49c52d-39fe-49dd-9c1b-6a3814c0b45b","html_url":"https://github.com/SlimeNull/RustTutorial","commit_stats":null,"previous_names":["slimenull/rusttutorial"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/SlimeNull/RustTutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SlimeNull%2FRustTutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SlimeNull%2FRustTutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SlimeNull%2FRustTutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SlimeNull%2FRustTutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SlimeNull","download_url":"https://codeload.github.com/SlimeNull/RustTutorial/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SlimeNull%2FRustTutorial/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28848378,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-28T15:15:36.453Z","status":"ssl_error","status_checked_at":"2026-01-28T15:15:13.020Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T19:13:21.691Z","updated_at":"2026-01-28T18:05:37.433Z","avatar_url":"https://github.com/SlimeNull.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# [Rust] 快速基础入门教程\n\nRust 是一个无运行时的强类型语言, 包含很多高级特性, 例如泛型, lambda 等. 又因为其独有的所有权机制, 所以 Rust 的内存安全要比 C++ 完善许多.\n\n\n\n\u003cbr/\u003e\n\n## 风格\n\nRust 与 C 族语言不一样, C 族语言在定义方法, 变量时, 都是 `类型 关键字` 这样的格式, 也就是类型前置. Rust 采用的是类型后置的风格, 即 `关键字: 类型`\n\n\n\n\u003cbr/\u003e\n\n## 基本结构\n\nRust 的结构与 C++ 是差不多的, 一个文件的顶部写要引入的内容, 下面是结构, 函数, 特征的声明.\n\n```rust\nuse std::io::stdout;\nuse std::io::Write;\n\nfn main() {\n    let hello_str = \"Hello world\";\n    let bytes = hello_str.as_bytes();\n\n\tstdout().write_all(bytes).unwrap();\n}\n```\n\n在上面代码中, `std::io::stdout` 与 `std::io::Write` 都是在 `std::io` 下, 它们可以通过花括号合并为以下语句:\n\n```rust\nuse std::io::{Write, stdout};\n```\n\n\n\n\u003cbr/\u003e\n\n## 基本类型\n\n数字类型\n\n| 长度 | 有符号 | 无符号 |\n| --- | --- | --- |\n| 8 位 | i8 | u8 |\n| 16 位 | i16 | u16 |\n| 32 位 | i32 | u32 |\n|64 位 | i64 | u64 |\n|128 位 | i128 | u128 |\n| 平台大小 | isize | usize |\n\n以及三十二位浮点数 `f32` 与六十四位浮点数 `f64`\n\n布尔(逻辑)值 `bool`, 字符(Unicode)值 `char`.\n\n\u003e Rust 中, 一个字符占四个字节, 可以表达任何 Unicode 字符, 包含 Emoji 表情.\n\n\n\n\u003cbr/\u003e\n\n## 函数声明\n\n使用 `fn` 关键字来声明一个函数.\n\n```rust\nfn test1() {\n\tprintln!(\"hello\");\n}\n```\n\n如果要带参数, 直接按照类型后置风格在括号内写明.\n\n```rust\nfn test2(number: i32) {\n\tprintln!(\"number: {}\", number);\n}\n```\n\n如果要带返回值, 直接使用箭头 `-\u003e` 来指定.\n\n```rust\nfn test3(num1: i32, num2: i32) -\u003e i32 {\n\treturn num1 + num2;\n}\n```\n\n当返回值是最后一行的时候, 你可以省略 return 和结尾的分号, 直接将表达式作为返回值返回.\n\n```rust\nfn test3(num1: i32, num2: i32) -\u003e i32 {\n\tnum1 + num2\n}\n```\n\n如果参数或者返回值是函数类型, 使用 `fn` 关键字即可, 下面的例子中, resolver 是一个函数, 这个函数有两个 `i32` 参数, 返回值是 `i32`\n\n```rust\nfn test4(num1: i32, num2: i32, resolver: fn(i32, i32) -\u003e i32) {\n\tprintln!(\"{}\", resolver(num1, num2));\n}\n```\n\n\n\n\u003cbr/\u003e\n\n## 变量声明\n\n使用 `let` 声明一个变量.\n\n```rust\nlet num: i32 = 114514;\n```\n\n大部分情况, 你都可以省略掉类型标记, Rust 会自动推导它的类型.\n\n```rust\nlet num = 114514;\n```\n\n上面声明的变量, 是不可变的. 如果你希望声明可变的变量, 需要使用 `mut` 关键字.\n\n```rust\nlet mut num = 114514;\n\n// 更改其值\nnum = 666;\n```\n\n\n\n\u003cbr/\u003e\n\n## 流程控制\n\nRust 中的 `if` 不使用括号, 直接跟表达式以及语句即可.\n\n```rust\nlet num = 114514;\nif num == 114514 {\n\tprintln!(\"value is 114514\");\n}\n```\n\n同样, Rust 中也有 `else`, `else if` 可用, 和 C 族语言类似, 只不过是少了括号.\n\n```rust\nif num == 114514 {\n\tprintln!(\"value is 114514\");\n} else if num == 1919810 {\n\tprintln!(\"value is 1919810\");\n} else {\n\tprintln!(\"invalid value\");\n}\n```\n\nRust 中的 `if` 也可以实现根据条件返回特定值的需求.\n\n```rust\nlet num = 114514;\nlet tip = if num == 114514 {\n\t\"哼哼哼\"\n} else {\n\t\"啊啊啊\"\n};\n```\n\n在上面的例子中, 对 `num` 进行判断, 如果值为 114514, tip 的值会是 \"哼哼哼\", 否则为 \"啊啊啊\". 需要注意的是, 当你希望 `if` 语句将语句作为结果返回时, 不要在语句末尾添加分号.\n\nRust 中的  `for` 用来对一个实例进行迭代. 使用 `起始值..结束值` 这样的语法可以创建简单的数值范围. 搭配 `for` 即可实现简单的数值循环.\n\n```rust\nfor i in 0..10 {\n\tprintln!(\"current value: {}\", i);\n}\n```\n\n同样的, Rust 中, `continue` 和 `break` 也可用. \n\n```rust\nfor i in 0..10 {\n    println!(\"current value: {}\", i);\n\n    if i == 3 {\n    \tcontinue;\n    }\n\n    if i == 7 {\n    \tbreak;\n    }\n}\n```\n\n值得一提的是, Rust 在循环时, 是允许对集合元素进行修改的. 只需要将迭代变量使用 `mut` 修饰.\n\n```rust\nlet arr = [1, 2, 3, 4];\nfor mut ele in arr {\n\tele = ele * 2;\n}\n```\n\n如果你需要一个 '死循环', 可以直接使用 `loop` 语句.\n\n```rust\nlet mut i = 0;\nloop {\n    println!(\"current value: {}\", i);\n\n    if i == 3 {\n    \tcontinue;\n    }\n\n    if i == 10 {\n    \tbreak;\n    }\n}\n```\n\nRust 的 `loop` 还支持给循环语句加上标签, 然后在内部循环中直接中断指定标签的循环.\n\n```rust\n'loop_out: loop {\n    loop {\n        // 在内部循环直接中断最外部循环\n        break 'loop_out;\n    }\n}\n```\n\nRust 的 `loop` 还可以作为一个带返回值的表达式使用. 只需要在 break 的时候提供返回值即可.\n\n```rust\nlet mut value = 0;\nlet result = loop {\n    value += 1;\n\n    if value == 10 {\n    \tbreak value * 2;\n    }\n};\n```\n\nRust 的 `match` 语句可以近似理解为 `if` 的高级语法. 传入一个值, 以及匹配条件和语句, 可以执行对应语句.\n\n```rust\nlet num = 114514;\nmatch num {\n    114514 =\u003e println!(\"hello\"),\n    1919810 =\u003e println!(\"world\"),\n    _ =\u003e { }\n}\n```\n\n在上面的例子中, 会对 num 进行匹配, 并且在值为 114514 和 1919810 时执行不同的语句, 如果所有条件都没有匹配到, 则会使用 `_ =\u003e { }` 表示的默认情况, 在这里是空语句, 也就是什么也不执行.\n\n同时, Rust 的 `match` 语句也可以作为表达式返回一个值, 只需要 `match` 内的语句是有返回值的表达式即可.\n\n```rust\nlet result = match num {\n    114514 =\u003e \"hello\",\n    1919810 =\u003e \"world\",\n    _ =\u003e \"\"\n};\n```\n\n在上面的示例中, `match` 对 num 进行匹配, 并且在值为 114514 和 1919810 的时候返回不同的字符串, 最终赋值给 result. 如果没有匹配到指定条件, 则是使用默认语句 `_ =\u003e \"\"` 返回一个空的字符串.\n\n\u003e 需要注意的是, 在 `match` 语句中, 使用的是 `=\u003e` 而不是 `-\u003e`.\n\n\n\n\u003cbr/\u003e\n\n## 字符串 / Strings\n\n在 Rust 中, 字符串分两种, 一种是 `str`, 它表示字符串本身, 不可变.\n由于 `str` 作为字符串本身, 其大小是不确定的, 所以它无法作为本地变量存储. 我们在使用时, 使用的都是 `\u0026str`, 也就是 `str` 的引用. \n\n```rust\nlet hello1 : \u0026str = \"你好世界\";\n```\n\n另一种是 `String`, 本质是数组的包装, 它是可变的. 你可以对其进行更改. 你可以将它理解为其他语言中常见的 `StringBuilder`\n\n```rust\nlet hello2 : String = String::new();\n\nhello2.add(\"向字符串中添加一些内容\");\nhello2.add(\", 你好吗?\")\n```\n\n如果你需要将字符串编码为字节数组, 可以直接使用 `as_bytes` 函数\n\n由于 Rust 中字符串使用 UTF-8 存储, 所以该函数的结果即为字符串使用 UTF-8 编码后的结果.\n\n```rust\nlet tip = \"hello world\";\nlet bytes = tip.as_bytes();\n```\n\n如果希望从 UTF-8 转为 Rust 字符串, 可以使用 `std::str::from_utf8` 函数进行转换.\n\n```rust\n// bytes 为需要解码的数据\nlet bytes : \u0026[u8];\nlet some_str = std::str::from_utf8(bytes);\n```\n\n\n\n\u003cbr/\u003e\n\n## 数组 / Arrays\n\n在 Rust 中, 数组时长度不可变的容器, 并且其大小必须在编译时确定.  其类型表达为: `[类型; 长度]`.\n\n```rust\nlet arr : [i32; 4];\n```\n\n在使用这个数组之前, 我们还需要对其进行初始化, 可以使用中括号指定其每一个元素的值.\n\n```rust\nlet arr : [i32; 4] = [1, 2, 3, 4];\n```\n\n当然, 这里的数组类型也可以被省略掉.\n\n```rust\nlet arr = [1, 2, 3, 4];\n```\n\n如果你希望直接初始化一个指定长度的数组, 可以使用中括号以及分号. 就像数组的类型表示.\n\n```rust\nlet arr : [i32; 4] = [0; 4];\nlet arr = [0; 4];\n```\n\n\n\n\u003cbr/\u003e\n\n## 容器 / Collections\n\n如果你需要可变的容器, 可以使用 `Vec\u003cT\u003e`, 当然, 只有在声明时使用 `mut` 关键字, 它才可变.\n\n```rust\nlet mut v : Vec\u003ci32\u003e = Vec::\u003ci32\u003e::new();\n```\n\n你可以将它简写为这样:\n\n```rust\n// 指定变量类型, Vec 的泛型参数会自动推导\nlet mut v : Vec\u003ci32\u003e = Vec::new();\n\n// 指定泛型参数, 变量的类型会自动推导.\nlet mut v = Vec::\u003ci32\u003e::new();\n```\n\n使用 `len` 函数获取其长度:\n\n```rust\nlet len = v.len();\n```\n\n用 `push` 和 `pop` 方法可以在 `Vec\u003cT\u003e` 的结尾增删元素.\n\n```rust\nv.push(114514);\nv.pop();\n```\n\n使用 `insert` 和 `remove` 可以在指定位置增删元素.\n\n```rust\nv.insert(0, 114514);\nv.remove(0);\n```\n\n哈希映射(HashMap)用于存储基于哈希值的键值映射, 像是其他语言中的 \"Dictionary\" 或者 \"Hashtable\",\n\n```rust\nlet mut hm : HashMap\u003c\u0026str, \u0026str\u003e = HashMap::\u003c\u0026str, \u0026str\u003e::new();\n```\n\n简写:\n\n```rust\nlet mut hm : HashMap\u003c\u0026str, \u0026str\u003e = HashMap::new();\nlet mut hm = HashMap::\u003c\u0026str, \u0026str\u003e::new();\n```\n\n使用 `len` 函数获取其长度:\n\n```rust\nlet len = hm.len();\n```\n\ninsert() 方法用于插入或更新一个键值对到哈希映射中, 如果键已经存在, 则更新为新的键值对, 并则返回旧的值. 如果键不存在则执行插入操作并返回 None.\n\n```rust\nhm.insert(\"qwq\", \"awa\");\n```\n\n从哈希映射中获取和删除值.\n\n```rust\nlet valueOption = hm.get(\u0026\"qwq\");\nlet valueOption = hm.remove(\u0026\"qwq\");\n```\n\n也可以使用 `for` 对哈希映射进行循环:\n\n```rust\nfor (k, v) in hm { \n\tprintln!(\"Key: {}, Value: {}\", k, v);\n}\n```\n\n哈希集合是基于哈希值的元素不重复容器, 常用于去重或快速查找元素是否存在.\n\n```rust\nlet mut hs: HashSet\u003ci32\u003e = HashSet::new();\n```\n\n获取长度, 插入数据, 删除数据, 判断数据是否已经存在:\n\n```rust\nlet len = hs.len();\nlet result = hs.insert(123);\nlet result = hs.remove(\u0026123);\nlet result = hs.contains(\u0026123);\n```\n\n\n\n\u003cbr/\u003e\n\n## 结构 / Structures\n\n使用 `struct` 关键字可以创建一个结构体.\n\n```rust\nstruct Point {\n    x: i32,\n    y: i32,\n}\n```\n\n在使用结构体类型的变量时, 该变量必须被初始化.\n\n```rust\nlet p : Point = Point { x: 1, y: 3 };\nlet p = Point { x: 1, y: 3 };\n```\n\n你可以对它的成员进行赋值, 取值.\n\n```rust\np.x = 114514;\nlet x = p.x;\n```\n\n如果需要为该类型添加一些方法, 使用 `impl` 关键字. \n\n```rust\nimpl Point {\n    fn new(x: i32, y: i32) -\u003e Point {\n    \tPoint { x: x, y: y }\n    }\n}\n```\n\n现在, 你可以使用 `Point::new` 来创建一个 `Point` 了.\n\n```rust\nlet p = Point::new(123, 456);\n```\n\n如果你要为该类型的实例创建一些函数, 只需要在编写函数时, 将第一个参数声明为 `self` 即可.\n\n```rust\nimpl Point {\n    fn output(self: \u0026Self) {\n    \tprintln!(\"Point, x: {}, y: {}\", self.x, self.y);\n    }\n}\n```\n\n现在你可以通过一个 `Point` 实例来调用 `output` 函数进行输出了.\n\n```rust\np.output();\n```\n\n在上述代码中, `self` 关键字表示当前实例, `Self` 关键字表示当前类型, 当然, 你也可以将它写成具体的类型. 下面的代码都是有效的实例函数定义:\n\n```rust\nimpl Point {\n    // 不使用 Self 关键字, 而是使用具体的 Point 类型\n    fn output1(self: \u0026Point) { }\n\n    // 不使用 Self 关键字, 而是让其自动推导类型\n    fn output2(\u0026self) { }\n}\n```\n\n\n\n\u003cbr/\u003e\n\n## 特征 / Traits\n\n在 Rust 中, `trait` 表示某种特征. 例如 \"可迭代\", \"可显示\", \"可调试\". 它类似于其他编程语言的接口. 使用 `trait` 关键字创建一个特征.\n\n```rust\ntrait TestTrait {\n\tfn some_func();\n}\n```\n\n在上面的例子中, 我们创建了一个名为 `TestTrait` 的 `trait`, 它规定, 需要有一个名为 `some_func` 的无参无返回值函数.\n\n要使某个结构实现一个 `trait`, 使用 `impl ... for ...` 语法.\n\n```rust\nimpl TestTrait for Point {\n    fn some_func() {\n    \tprintln!(\"hello world from struct Point\");\n    }\n}\n```\n\n`trait` 主要是与泛型搭配使用. 同时和其他编程语言不一样的是, `trait` 无法直接作为一个函数的参数类型. 你需要使用泛型, 然后指定泛型需要实现某 `trait`, 然后将该泛型作为函数的参数类型使用.\n\n`trait` 可以用内置的实现, 在这方面, 它又像其他语言的抽象类. 例如, 某个 `trait` 需要类型实现函数 A, 而函数 B 由该 `trait` 自己实现, 内部逻辑依赖于函数 A. 这时, 只要某个类型实现了这个 trait 并编写函数 A 的实现, 他就可以直接使用 `trait` 内的函数 B.\n\n```rust\ntrait TestTrait {\n    fn get_string(\u0026self) -\u003e String;\n    fn print_string(\u0026self) {\n    \tprintln!(\"{}\", self.get_string());\n    }\n}\n\nimpl TestTrait for Point {\n    fn get_string(\u0026self) -\u003e String {\n    \treturn format!(\"Point, x: {}, y: {}\", self.x, self.y);\n    }\n}\n```\n\n```rust\nlet p = Point::new(1, 2);\np.print_string();\n```\n\n\n\n\u003cbr/\u003e\n\n## 特性 / Attributes\n\nRust 中的特性(Attribute)是一种标记. 类似于 C# 的 `Attribute` 或者 Python 中的 `Decorators`, 在做上标记后, 即可拥有某种行为.\n\n下面使用 `derive` 特性演示特性的使用.\n\n```rust\n#[derive(PartialEq, Eq)]\nstruct Point {\n    x: i32,\n    y: i32\n}\n```\n\n在以上代码中, 我们为 Point 结构添加了 derive 特性, 这个特性用于自动实现指定的 `trait`, 在这里, 我们指定了 `PartialEq` 和 `Eq`.\n\n在实现了 `PartialEq` 和 `Eq` 后, 我们的 Point 结构现在可以使用 `==` 和 `!=` 运算符了.\n\n```rust\nlet p1 = Point { x: 123, y: 456 };\nlet p2 = Point { x: 345, y: 829 };\n\nif p1 == p2 {\n\tprintln!(\"两点相等\")\n}\n```\n\n\n\n\u003cbr/\u003e\n\n## 枚举 / Enumerations\n\nRust 中的枚举和其他语言中的枚举有很大不同. 它枚举可理解为 \"情况\", 既可以作为类似于 C# 中的纯值类型使用, 也可以像 Java 的枚举一样在枚举中存储数据. \n\nRust 对于枚举的优化是很好的, 不像 Java 一般是基于堆中存储的.\n\n```rust\nenum ColorChannel {\n\tRed, Green, Blue\n}\n```\n\n在以上的例子中, 我们声明了一个最简单的枚举, 这个枚举仅包含三种情况, 即 '红', '绿', '蓝'. 在这种情况下, 你可以理解为我们定义了三个数字值常量, 通过 `ColorChannel` 可以访问它们.\n\n```rust\nlet color_channel1 = ColorChannel::Red;\nlet color_channel2 = ColorChannel::Green;\nlet color_channel3 = ColorChannel::Blue;\n```\n\n然后使用模式匹配进行判断.\n\n```rust\nmatch color_channel1 {\n    ColorChannel::Red =\u003e println!(\"is red\"),\n    _ =\u003e {}\n}\n```\n\n在这里我们不能使用 `if` 语句进行判断, 因为我们定义的枚举没有实现名为 `PartialEq` 的 `trait`, 这是内置于 `rust` 的 `trait`, 用于重载 `==` 和 `!=` 运算符.\n\n下面我们将以一个不同情况的颜色讲述 Rust 中枚举存储值的用法.\n\n```rust\nenum Color {\n    Rgb(u8, u8, u8),\n    Channel(ColorChannel)\n}\n```\n\n在上面的示例中, `Color` 分成了两种情况, 一种是 `Rgb`, 一种是 `Channel`. 当是 `Rgb` 的时候, 它存储三个无符号八位整数, 当是 `Channel` 的时候, 它存储一个 `ColorChannel` 枚举.\n\n我们可以这样使用它:\n\n```rust\nlet color = Color::Rgb(89, 43, 233);\n\nmatch color {\n    Color::Rgb(r, g, b) =\u003e {\n    \tprintln!(\"颜色是 RGB 值. R: {}, G: {}, B: {}\", r, g, b);\n    },\n\n    Color::Channel(channel) =\u003e {\n    \tprintln!(\"颜色是通道, {}\", channel);\n    }\n}\n```\n\n\u003e 注意, 因为这里需要将 `ColorChannel` 打印输出, 所以 `ColorChannel` 需要实现名为 `Display` 的 `trait`.\n\n在这种有存储值的情况下, 我们也可以使用 `if let` 的语句对其进行判断:\n\n```rust\nif let Color::Rgb(r, g, b) = color {\n\tprintln!(\"颜色是 RGB 值. R: {}, G: {}, B: {}\", r, g, b);\n}\n```\n\n我们还可以为枚举中的值命名, 这样就可以:\n\n```rust\nenum Color {\n    Rgb { r: u8, g: u8, b:u8 },\n    Channel { channel: ColorChannel }\n}\n```\n\n不过这样的话, 使用方式也需要做些改动:\n\n```rust\nlet color = Color::Rgb { r: 23, g: 12, b: 129 };\n\nmatch color {\n    Color::Rgb { r, g, b } =\u003e {\n    \tprintln!(\"颜色是 RGB 值. R: {}, G: {}, B: {}\", r, g, b);\n    },\n\n    Color::Channel { channel }=\u003e {\n    \tprintln!(\"颜色是通道, {}\", channel);\n    }\n}\n\nif let Color::Rgb { r, g, b } = color {\n\tprintln!(\"颜色是 RGB 值. R: {}, G: {}, B: {}\", r, g, b);\n}\n```\n\n\n\n\u003cbr/\u003e\n\n## 泛型 / Generic\n\n泛型是编程语言中极重要的一个概念. 通过使用泛型, 可以实现一些逻辑的复用. 例如, 当我们自定义的结构实现 Rust 内置的某些 `trait` 时, 我们也可以使用 Rust 内的某些函数.\n\n下面我们将自己定义一个简单的 `trait` 和一个简单的泛型函数.\n\n```rust\ntrait I32Printer {\n\tfn print(\u0026self, value: i32);\n}\n\nfn print_i32\u003cPrinter: I32Printer\u003e(value: i32, printer: Printer) {\n\tprinter.print(value);\n}\n```\n\n在上面的逻辑中, 我们在 `print_i32` 后添加了尖括号, 尖括号中, 冒号的前半部分表示需要使用的泛型类型, 冒号后面是对泛型类型的约束, 表示该泛型类型必须实现 `I32Printer` 这个 `trait`.\n\n在参数列表中, 我们定义了 `printer` 参数, 指定其类型为我们定义的泛型类型. 这样, 它就可以接受任何实现了 `I32Printer` 的类型.\n\n接下来我们定义两个结构, 实现 `I32Printer`, 编写不同的打印逻辑.\n\n```rust\nstruct SimpleI32Printer;\nstruct AnotherI32Printer\u003c'a\u003e {\n\tprompt: \u0026'a str,\n}\n\nimpl I32Printer for SimpleI32Printer {\n    fn print(\u0026self, value: i32) {\n    \tprintln!(\"{}\", value);\n    }\n}\n\nimpl I32Printer for AnotherI32Printer\u003c'_\u003e {\n    fn print(\u0026self, value: i32) {\n    \tprintln!(\"{}: {}\", self.prompt, value);\n    }\n}\n```\n\n在上面的例子中, 我们定义了 `SimpleI32Printer` 和 `AnotherI32Printer` 两个结构, 并且都为它们实现了 `I32Printer` 的 `trait`.\n\n现在, 可以调用方法, 传入不同的 printer, 然后查看运行结果了.\n\n```rust\nlet printer1 = SimpleI32Printer;\nlet printer2 = AnotherI32Printer { prompt: \"Number:\" };\n\nprint_i32::\u003cSimpleI32Printer\u003e(114514, printer1);\nprint_i32::\u003cAnotherI32Printer\u003e(1919810, printer2);\n```\n\n可以看到, 它根据我们传入的不同类型实例, 有了不同的行为.\n\n\u003e 在其他语言, 例如 C# 和 Java 中, 你可以直接将接口作为参数类型指定. 但是在 Rust 中, 你必须创建一个泛型参数来做这样的逻辑.\n\u003e Rust 中, 一切参数, 变量的大小都应该是固定的. 倘若我们允许 `trait` 作为参数类型, 那么类型的大小将不再确定. 而泛型则类似于 C++ 的模板, 在编译时, Rust 编译器会对其做处理, 生成能使用多个类型进行调用的函数.\n\n接下来就是泛型类型了, 在定义类型的时候, 我们也可以使用泛型.\n\n```rust\nstruct TwoValues\u003cT1, T2\u003e {\n    value1: T1,\n    value2: T2\n}\n```\n\n使用起来也很简单:\n\n```rust\nlet two_values = TwoValues::\u003ci32, u8\u003e {\n    value1: 123,\n    value2: 123\n};\n\n// 或者\nlet two_values : TwoValues\u003ci32, u8\u003e = TwoValues {\n    value1: 123,\n    value2: 123\n};\n\n// 也可以自动推导类型, 这里将会被推导为 TwoValues\u003ci32, i32\u003e\nlet two_values = TwoValues {\n    value1: 123,\n    value2: 123\n};\n```\n\n为泛型类型实现方法, 需要这样写:\n\n```rust\nimpl\u003cT1, T2\u003e TwoValues\u003cT1, T2\u003e {\n    fn common_fn(\u0026self) {\n    \tprintln!(\"common func\");\n    }\n}\n```\n\n在上面的例子中, 由于我们不知道泛型类型具体类型, 所以在 `impl` 语句后还是需要声明两个泛型类型, 然后传入到类型.\n\n但如果你希望为带有指定泛型参数的泛型类型定义一些函数, 可以这样写:\n\n```rust\nimpl TwoValues\u003c\u0026str, i32\u003e {\n    fn test_output(\u0026self) {\n    \tprintln!(\"{}: {}\", self.value1, self.value2);\n    }\n}\n```\n\n在上面的例子中, 因为我们只想为泛型类型参数为 `\u0026str` 和 `i32` 的 `TwoValues` 定义函数, 泛型类型已知, 所以不必再定义泛型类型.\n\n下面还有个例子可供参考, 第一个泛型类型参数我们指定为 `\u0026str`, 第二个指定为实现了 `Display` 的泛型类型.\n\n```rust\nimpl\u003cT: Display\u003e TwoValues\u003c\u0026str, T\u003e {\n    fn test_output2(\u0026self) {\n    \tprintln!(\"{}: {}\", self.value1, self.value2)\n    }\n}\n```\n\n需要注意的是, 与其他语言不一样, Rust 在构造类型实例或者调用泛型函数的时候, 需要使用两个冒号以及尖括号来指定泛型类型参数.\n\n```rust\n// 正确使用\nlet two_values = TwoValues::\u003c\u0026str, i32\u003e {\n    value1: \"Tip\",\n    value2: 10\n}\n\nprint_i32::\u003cSimpleI32Printer\u003e(114514, printer1);\n\n// 错误使用\nlet two_values = TwoValues\u003c\u0026str, i32\u003e {\n    value1: \"Tip\",\n    value2: 10\n}\n\nprint_i32\u003cSimpleI32Printer\u003e(114514, printer1);\n```\n\n之所以强调这点, 是因为其他语言, 诸如 C#, Java, Kotlin, 它们在构造类型实例和调用泛型方法的时候, 都是直接使用尖括号来指定泛型类型参数的. Rust 需要多加两个冒号, 初学者可能会忘记这点.\n\n\n\n\u003cbr/\u003e\n\n## 所有权 / Ownership\n\n为了保证内存安全, Rust 引入了 '所有权' 的概念. 其大概思想为:\n\n1. 一个类型实例有唯一的作用域, 当离开其作用域时, 该实例会被销毁\n    这个作用域称为它的 '所有者', 该作用域持有该实例的 '所有权'\n2. 所有权可以转交给另一个作用域, 转交后, 当前作用域将无法继续使用该实例\n3. 所有权可以借用, 并且指定一定的访问权限, 当前作用域仍持有该实例的 '所有权'\n\n大多数编程语言都有作用域的概念, 离开作用域后, 值将作废:\n\n```rust\nif true {\n    let some_integer = 114514;\n}\n\n// 这里将报错, 因为已经脱离了 some_integer 的作用域\nprintln!(\"value: {}\", some_integer);\n```\n\n当一个值直接传入到另外一个函数中, 那么这个值的所有权也将转交到另外一个函数中:\n\n```rust\nstruct MyValue {\n    value: i32\n}\n\nfn print_value(value: MyValue) {\n    println!(\"value: {}\", value.value);\n}\n\nfn main() {\n    let my_value = MyValue { value: 114514 };\n    print_value(my_value);\n\n    // 这里将报错, 因为在执行 print_value 的时候, 所有权已经被转让\n    // 当前作用域不再持有 my_value, 也就无法再使用它\n    println!(\"value: {}\", my_value.value);\n}\n```\n\n如果希望函数不转让传入参数的所有权, 可以将参数类型定义为 '引用'. 你可以将其理解为其他语言中的 '指针'. 只需要在类型前加 `\u0026` 符号即可.\n\n```rust\nstruct MyValue {\n\tvalue: i32\n}\n\nfn print_value(value: \u0026MyValue) {\n\tprintln!(\"value: {}\", value.value);\n}\n\nfn main() {\n    let my_value = MyValue { value: 114514 };\n    print_value(\u0026my_value);\n\n    // 这时, 你仍然可以使用 my_value\n    // 因为当前作用域持有 my_value 的所有权\n    println!(\"value: {}\", my_value.value);\n}\n```\n\n虽然我们将值借给了 `print_value`, 但在 `print_value` 内部, 它只能读取参数的值, 而不能对参数进行修改. 如果你希望它能够修改该实例的值, 需要在类型前添加 `mut` 关键字.\n\n```rust\nfn change_value(value: \u0026mut MyValue) {\n  value.value = 123123;\n}\n```\n\n在调用时也应该使用 `\u0026mut xxx` 来获取该实例的可修改引用.\n\n```rust\nfn main() {\n    let my_value = MyValue { value: 114514 };\n\n    change_value(\u0026mut my_value);\n}\n```\n\n如果你需要一个能够对值本身进行更改, 那么在赋值时, 需要在变量名前添加 `*` 符号.\n\n```rust\nfn change_int_value(value: \u0026mut i32) {\n    *value = 114514;\n}\n```\n\n\n\n\u003cbr/\u003e\n\n## 错误处理\n\n在 Rust 中, 错误分为两种: 可恢复的错误以及不可恢复的错误. 例如, 在将字符串解析为数字时, 如果数字格式不正确, 所引发的错误是程序逻辑上可以处理的. 而类似于内存访问冲突, 栈溢出这种, 就是无法恢复的错误.\n\n不可恢复的错误会直接导致程序崩溃. 你可以使用 `panic` 宏手动引发错误.\n\n```rust\npanic!(\"oops\");\nprintln!(\"test\");   // 这里代码不会被执行, 因为程序已经崩了\n```\n\n而对于可恢复的错误, Rust 中的函数都会返回一个 `Result\u003cT, E\u003e` 来表示可能包含错误值的返回值. 它是一个枚举, 包含两种取值: `Ok(T)` 与 `Err(E)`, 我们可以通过 `match` 语句对其两种情况分别进行处理.\n\n```rust\nlet origin_str = \"123\";\nlet parse_result = origin_str.parse::\u003ci32\u003e();\n\nmatch parse_result {\n    Ok(value) =\u003e println!(\"Value is: {}\", value),\n    Err(err) =\u003e println!(\"Error: {}\", err)\n}\n```\n\n如果你确定该方法的执行不会出现错误, 也可以使用 `unwrap` 函数直接取得正确的值.\n\n```rust\nlet origin_str = \"123\";\nlet parsed_value : i32 = origin_str.parse().unwrap();\n```\n\n但是如果尝试对一个错误值使用 `unwrap`, 就会引发 `panic` 了.\n\n```rust\nlet origin_str = \"不是数字\";\nlet parsed_value: i32 = origin_str.parse().unwrap();   // 这里会直接崩溃, 因为解析是失败的, 无法取得结果值\n```\n\nRust 还提供了一个 `?` 操作符用于简化异常处理. 下面的代码是不使用 `?` 的.\n\n```rust\nfn mul_input_with_10() -\u003e Result\u003ci32, ParseIntError\u003e {\n    let mut input = String::new();\n    std::io::stdin().read_line(\u0026mut input).unwrap();\n\n    let valueResult = input.parse::\u003ci32\u003e();\n\n    match valueResult {\n        Ok(value) =\u003e Ok(value * 10),\n        Err(err) =\u003e Err(err),\n    }\n}\n```\n\n如果使用 `?` 的话, 则是这样. 当结果为 `Err(E)` 的时候, 会直接将结果作为当前函数的返回值返回, 表达式的结果则是正确的值.\n\n```rust\nfn mul_input_with_10() -\u003e Result\u003ci32, ParseIntError\u003e {\n    let mut input = String::new();\n    std::io::stdin().read_line(\u0026mut input).unwrap();\n\n    Ok(input.parse::\u003ci32\u003e()? * 10)\n}\n```\n\n\n\n\u003cbr/\u003e\n\n## 模块\n\n模块是 Rust 中组织源代码的方式. 在 Rust 中, 一个文件或者文件夹都可以叫做一个 \"模块\".\n\n例如, 当我有一个 `main.rs`, 我希望在里面使用 `test.rs` 的成员时:\n\n```rust\n// 这里是 test.rs 的内容\n\n// 公开一个函数\npub fn test_fn() {\n    println!(\"test fn\");\n}\n```\n\n下面是 `main.rs`, 使用 `mod` 语句引入模块, 然后使用 `use` 语句使用模块中的成员:\n\n```rust\n// 引入 test 模块\nmod test;\n\n// 使用模块中的成员\nuse test::test_fn;\n\nfn main() {\n    test_fn();\n}\n```\n\n如果希望将一个文件夹暴露为一个模块的话, 你需要先创建一个文件夹, 然后在文件夹下创建 `mod.rs`, 然后编写内容. 在该文件下向外暴露的成员, 即为该模块的成员.\n\n```rust\n// 这里是 test2/mod.rs 的内容\n\n// 公开一个函数\npub fn test_fn() {\n    println!(\"test fn\");\n}\n```\n\n引用的时候和之前的代码一样, 只需要使用 `mod test2` 即可引入 `test2` 模块.\n\n如果你希望在 `test2`  文件夹下编写更多的文件, 并向外暴露:\n\n```txt\n|- test2\n|  |- another.rs\n|  -- mod.rs\n|\n-- main.rs\n```\n\n那么任何你想要向外暴露的内容, 都应该在 `test2/mod.rs` 下声明好.\n\n```rust\n// 这里是 test2/another.rs 的内容\n\n// 公开一个结构\npub struct AnotherStruct {\n    \n}\n```\n\n在 `test2/mod.rs` 中, 你需要导入并公开 `another` 这个模块.\n\n```rust\n// 这里是 test2/mod.rs 的内容\n\n// 导入并公开 another 模块\npub mod another;\n\n// 公开属于 test2 的成员\npub fn test_fn() {\n    println!(\"test fn\");\n}\n```\n\n于是, 你就可以在 `main.rs` 中, 使用 `AnotherStruct` 这个类型了.\n\n```rust\n// 导入 test2 模块\nmod test2;\n\n// 使用 test2/another 中的 AnotherStruct\nuse test2::another::AnotherStruct;\n\nfn main() {\n    let value = AnotherStruct {};\n}\n```\n\n但是, 如果你希望在使用 `AnotherStruct` 时, 直接通过 `test2::AnotherStruct` 导入, 也可以在 `mod.rs` 这样向外公开:\n\n```rust\n// 这里是 test/mod.rs 的内容\n\n// 导入 another 模块, 但是不公开\nmod another;\n\n// 使用并公开 another 下的结构\npub use another::AnotherStruct;\n```\n\n这样 `AnotherStruct` 可以通过 `use` 语句直接向外暴露, 使用时就可以直接 `use test2::AnotherStruct` 了\n\n```rust\n// 导入 test2 模块\nmod test2;\n\n// 使用 test2 直接暴露的 AnotherStruct\nuse test2::AnotherStruct;\n\nfn main() {\n    let value = AnotherStruct {};\n}\n```\n\n方便起见, 你也可以直接用 `*` 在 `mod.rs` 直接向外暴露某个模块的所以成员:\n\n```rust\nmod another;\n\n// 向外暴露 another 中的所有成员\npub use another::*;\n```\n\n如果你在使用多个模块时, 它们的类型名称相同, 你可以在 `use` 的使用, 使用 `as` 为其取别名:\n\n```rust\nmod test2;\n\nuse test2::AnotherStruct as qwq;\n\nfn main() {\n    let value = qwq {};\n}\n```\n\n\n\n\n\n\n\n\n\n\u003cbr/\u003e \u003cbr/\u003e\n\n---\n\n作者: SlimeNull\n\n日期: 2023-11-25\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslimenull%2Frusttutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fslimenull%2Frusttutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslimenull%2Frusttutorial/lists"}