Rust 入门教程

2024-02-26 1033阅读

温馨提示:这篇文章已超过441天没有更新,请注意相关的内容是否还可用!

文章目录

  • 前言
  • 1. 使用cargo创建项目
  • 2. 编程语言类型
    • 1. 整数
    • 2. 浮点数
    • 3. 字符
    • 3. 常量与不可变变量的区别
    • 4. 重影(Shadowing)
    • 5. 两个重要的泛型类型
    • 6. 常见的内存管理方式
    • 7. 如何理解生命周期?
    • 8. 条件语句
      • 1. if实例
      • 2. while循环实例
      • 3. for循环实例
      • 4. loop循环实例
      • 9. 变量与数据交互的方式
      • 10. 引用的一些规制
        • 1. 引用实例(实质上"引用"是变量的间接访问方式)
        • 2. "垂悬引用"实例
        • 11. Slice(切片)类型实例
        • 12. 非字符串切片实例
        • 13. 结构体实例
        • 14. 元组结构体实例
        • 15. 输出结构体实例
        • 16. 结构体方法实例
        • 17. 结构体关联函数实例
        • 18. 单元结构体
        • 19. 枚举类
          • 1. match语法实例
          • 2. Option 枚举类
          • 20. if let 语法实例
          • 21. Rust 组织管理
            • 1. 箱(Crate)
            • 2. 包(Package)
            • 3. 模块(Module)
            • 22. 访问权限
              • use 关键字
              • 2. 引用标准库
              • 23. 格式化输出
              • 24. 特性
                • 1. 默认特性
                • 2. 特性做参数
                • 3. 特性做返回值
                • 4. 有条件实现方法
                • 25. 优雅地错误处理
                • 26. 丑陋的 Option/Result 处理
                • 27. Vectors
                • 28. 解引用
                • 29. 生命周期
                  • 1. 显式生命周期
                  • 2. 多个生命周期
                  • 3. 静态生命周期
                  • 30. 数据类型中的生命周期
                  • 31. 原始字符串常量
                  • 32. 文件中的字符串常量
                  • 33. 重温字符串片段(String Slice)
                  • 34. Char
                  • 35. 字符串(String)
                  • 36. 将文本作为函数的参数
                  • 39. 字符串构建
                  • 38. 重温字符串格式化
                    • 1. 宏 format!
                    • 2. 填充/对齐
                    • 3. 星号:.*
                    • 39. 字符串转换
                    • 40. 动态调度和静态调度
                    • 41. 泛型
                    • 42. Box
                    • 43. 重温泛型结构体
                    • 44. 指针
                    • 45. 解引用
                      • 1. 运算符 *
                      • 2. 运算符 .
                      • 46. 智能指针
                      • 47. 智能不安全代码
                      • 48. 重温error的使用
                      • 49. 引用计数
                      • 50. 共享访问
                      • 51. 线程间共享
                      • 52. 组合智能指针
                      • 53. 项目的结构和管理
                        • 1. 模块
                        • 2. 编写程序
                        • 3. 编写库
                        • 4. 引用其他模块和 crate
                        • 5. 引用多个项目
                        • 6. 创建模块
                        • 7. 模块层次结构
                        • 8. 内联模块
                        • 9. 模块内部引用
                        • 10. 导出
                        • 11. 结构体可见性
                        • 54. Prelude
                        • 55. 闭包
                        • 56. Rust中的所有权
                        • 57. 引用和借用
                        • 总结

                          1. 使用cargo创建项目

                          cargo 是Rust 的包管理器和构建系统

                          创建项目的命令如下:

                          cargo new 项目名	
                          

                          编译:

                          cargo build
                          

                          运行:

                          cargo run
                          

                          2. 编程语言类型

                          静态类型?动态类型?强类型?弱类型?

                          • JavaScript是什么类型的语言? 动态,弱类型
                          • C是什么类型的语言? 静态,弱类型
                          • Rust是什么类型的语言? 静态,强类型

                            1. 整数

                            • 无符号整数:u8,u16,u32(推荐),u64,usize
                            • 有符号整数:i8,i16,i32(推荐),i64,isize

                              2. 浮点数

                              • 32位浮点数:f32
                              • 64位浮点数:f64(推荐)

                                3. 字符

                                Char 是Unicode码,并且总是4bytes大小

                                3. 常量与不可变变量的区别

                                既然不可变变量是不可变的,那不就是常量吗?为什么叫变量?

                                变量和常量还是有区别的。在Rust中,以下程序是合法的:

                                let a = 123;   // 可以编译,但可能有警告,因为该变量没有被使用
                                let a = 456;
                                

                                4. 重影(Shadowing)

                                重影的概念与其他面向对象语言里的"重写"(Override)或"重载"(Overload)是不一样的。重影就是刚才讲述的所谓"重新绑定",之所以加引号就是为了在没有介绍这个概念的时候代替一下概念。

                                重影就是指变量的名称可以被重新使用的机制:

                                实例

                                fn main() {
                                    let x = 5;
                                    let x = x + 1;
                                    let x = x * 2;
                                    println!("The value of x is: {}", x);
                                }
                                

                                这段程序的运行结果:

                                The value of x is: 12

                                重影与可变变量的赋值不是一个概念,重影是指用同一个名字重新代表另一个变量实体,其类型、可变属性和值都可以变化。但可变变量赋值仅能发生值的变化。

                                let mut s = "123";
                                s = s.len();
                                

                                这段程序会出错:不能给字符串变量赋整型值。

                                5. 两个重要的泛型类型

                                • Option,代表有或无
                                • Result,代表成功或失败

                                  实例

                                  // 两个重要的泛型类型
                                  enum Option {
                                      Some(T),
                                      None,
                                  }
                                  enum Result {
                                      Ok(T),
                                      Err(E),
                                  }
                                  fn main() {
                                      // 查看当前用户目录—>返回Option
                                      match std::env::home_dir() {
                                          Some(data) => println!("option i some, data = {:?}", data),
                                          // 如果为None执行
                                          None => println!("option is none"),
                                      }
                                      // 查看当前系统的环境变量—>返回Result
                                      match std::env::var("MYSQL_HOME") {
                                          Ok(data) => println!("ok! {:?}", data),
                                          Err(err) => println!("err {}", err),
                                      }
                                  }
                                  

                                  运行结果:

                                  option i some, data = “C:\Users\1”

                                  ok! “D:\Program Files\MySQL\MySQL Server 8.0”

                                  6. 常见的内存管理方式

                                  • C语言的malloc和free(手动管理,Bug制造机)
                                  • GC:Golang,Java等语言(自动管理)
                                  • 基于生命周期的半自动管理:Rust

                                    7. 如何理解生命周期?

                                    在C语言中需要直接手动调用free去释放内存

                                    Rust在编译间计算变量的使用范围

                                    当变量不再被使用时编译自动在源码中插入free代码

                                    8. 条件语句

                                    1. if实例

                                    fn main() {
                                        let a = 3;
                                        let number = if a > 0 { 1 } else { -1 };
                                        println!("number 为 {}", number);
                                    }
                                    

                                    2. while循环实例

                                    常用来循环外部条件,条件不成立结束循环

                                    let mut i = 0;
                                    while i  
                                    

                                    3. for循环实例

                                    常用来遍历一个线性数据据结构(比如数组)

                                    fn main() {
                                        let a = [10, 20, 30, 40, 50];
                                        for i in a.iter() {
                                            println!("值为 : {}", i);
                                        }
                                    }
                                    

                                    4. loop循环实例

                                    常用来循环内部条件,使用 break 结束循环

                                    fn main() {
                                        let s = ['R', 'U', 'N', 'O', 'O', 'B'];
                                        let mut i = 0;
                                        loop {
                                            let ch = s[i];
                                            if ch == 'O' {
                                                break;
                                            }
                                            println!("\'{}\'", ch);
                                            i += 1;
                                        }
                                    }
                                    

                                    福利时刻,玩个游戏放松一下吧!————猜谜游戏

                                    extern crate rand;
                                    use rand::Rng;
                                    use std::cmp::Ordering;
                                    use std::io;
                                    fn main() {
                                        println!("Guess the number!");
                                        let secret_number = rand::thread_rng().gen_range(1..101);
                                        loop {
                                            println!("Please input your guess.");
                                            let mut guess = String::new();
                                            io::stdin()
                                                .read_line(&mut guess)
                                                .expect("Failed to read line");
                                            let guess: u32 = match guess.trim().parse() {
                                                Ok(num) => num,
                                                Err(_) => continue,
                                            };
                                            println!("You guessed: {}", guess);
                                            match guess.cmp(&secret_number) {
                                                Ordering::Less => println!("Too small!"),
                                                Ordering::Greater => println!("Too big!"),
                                                Ordering::Equal => {
                                                    println!("You win!");
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    

                                    运行结果:

                                    Guess the number!

                                    Please input your guess.

                                    60

                                    You guessed: 60

                                    Too big!

                                    Please input your guess.

                                    30

                                    You guessed: 30

                                    Too small!

                                    Please input your guess.

                                    45

                                    You guessed: 45

                                    Too small!

                                    Please input your guess.

                                    55

                                    You guessed: 55

                                    Too small!

                                    Please input your guess.

                                    58

                                    You guessed: 58

                                    Too small!

                                    Please input your guess.

                                    59

                                    You guessed: 59

                                    You win!

                                    9. 变量与数据交互的方式

                                    变量与数据交互方式主要有移动(Move)和克隆(Clone)两种

                                    10. 引用的一些规制

                                    不会获取所有权(所有权稍后会详细介绍),默认情况下是不可变的,同一时间最多只能存在一个可变引用

                                    1. 引用实例(实质上"引用"是变量的间接访问方式)

                                    fn main() {
                                        let s1 = String::from("hello");
                                        let s2 = &s1;
                                        println!("s1 is {}, s2 is {}", s1, s2);
                                    }
                                    

                                    可变引用与不可变引用相比除了权限不同以外,可变引用不允许多重引用,但不可变引用可以

                                    2. "垂悬引用"实例

                                    在 Rust 语言里不允许出现,如果有,编译器会发现它

                                    fn main() {
                                        let reference_to_nothing = dangle();
                                    }
                                    fn dangle() -> &String {
                                        let s = String::from("hello");
                                        &s
                                    }
                                    

                                    很显然,伴随着 dangle 函数的结束,其局部变量的值本身没有被当作返回值,被释放了。但它的引用却被返回,这个引用所指向的值已经不能确定的存在,故不允许其出现。

                                    11. Slice(切片)类型实例

                                    fn main() {
                                        let s = String::from("broadcast");
                                        let part1 = &s[0..5];
                                        let part2 = &s[5..9];
                                        println!("{}={}+{}", s, part1, part2);
                                    }
                                    

                                    运行结果:

                                    broadcast=broad+cast

                                    • …y 等价于 0…y
                                    • x… 等价于位置 x 到数据结束
                                    • … 等价于位置 0 到结束

                                      注意:到目前为止,尽量不要在字符串中使用非英文字符,因为编码的问题。

                                      实际上,到目前为止你一定疑惑为什么每一次使用字符串都要这样写String::from(“runoob”) ,直接写 “runoob” 不行吗?

                                      事已至此我们必须分辨这两者概念的区别了。在 Rust 中有两种常用的字符串类型:str 和 String。str 是 Rust 核心语言类型,字符串切片(String Slice),常常以引用的形式出现(&str)。

                                      凡是用双引号包括的字符串常量整体的类型性质都是 &str:

                                      let s = "hello";
                                      

                                      这里的 s 就是一个 &str 类型的变量。

                                      String 类型是 Rust 标准公共库提供的一种数据类型,它的功能更完善——它支持字符串的追加、清空等实用的操作。String 和 str 除了同样拥有一个字符开始位置属性和一个字符串长度属性以外还有一个容量(capacity)属性。

                                      String 和 str 都支持切片,切片的结果是 &str 类型的数据。

                                      注意:切片结果必须是引用类型,但开发者必须自己明示这一点:

                                      let slice = &s[0..3];
                                      

                                      有一个快速的办法可以将 String 转换成 &str:

                                      let s1 = String::from("hello");
                                      let s2 = &s1[..];
                                      

                                      12. 非字符串切片实例

                                      除了字符串以外,其他一些线性数据结构也支持切片操作,例如数组

                                      fn main() {
                                          let arr = [1, 3, 5, 7, 9];
                                          let part = &arr[0..3];
                                          for i in part.iter() {
                                              println!("{}", i);
                                          }
                                      }
                                      

                                      运行结果:

                                      1

                                      3

                                      5

                                      13. 结构体实例

                                      let domain = String::from("www.runoob.com");
                                      let name = String::from("RUNOOB");
                                      let runoob = Site {
                                          domain,  // 等同于 domain : domain,
                                          name,    // 等同于 name : name,
                                          nation: String::from("China"),
                                          traffic: 2013
                                      };
                                      

                                      有这样一种情况:你想要新建一个结构体的实例,其中大部分属性需要被设置成与现存的一个结构体属性一样,仅需更改其中的一两个字段的值,可以使用结构体更新语法:

                                      let site = Site {
                                          domain: String::from("www.runoob.com"),
                                          name: String::from("RUNOOB"),
                                          ..runoob
                                      };
                                      

                                      注意:…runoob 后面不可以有逗号。这种语法不允许一成不变的复制另一个结构体实例,意思就是说至少重新设定一个字段的值才能引用其他实例的值。

                                      14. 元组结构体实例

                                      "颜色"和"点坐标"是常用的两种数据类型,但如果实例化时写个大括号再写上两个名字就为了可读性牺牲了便捷性,Rust 不会遗留这个问题。元组结构体对象的使用方式和元组一样,通过 . 和下标来进行访问:

                                      fn main() {
                                          struct Color(u8, u8, u8);
                                          struct Point(f64, f64);
                                          let black = Color(0, 0, 0);
                                          let origin = Point(0.0, 0.0);
                                          println!("black = ({}, {}, {})", black.0, black.1, black.2);
                                          println!("origin = ({}, {})", origin.0, origin.1);
                                      }
                                      

                                      运行结果:

                                      black = (0, 0, 0)

                                      origin = (0, 0)

                                      15. 输出结构体实例

                                      调试中,完整地显示出一个结构体实例是非常有用的。但如果我们手动的书写一个格式会非常的不方便。所以 Rust 提供了一个方便地输出一整个结构体的方法:

                                      #[derive(Debug)]
                                      struct Rectangle {
                                          width: u32,
                                          height: u32,
                                      }
                                      fn main() {
                                          let rect1 = Rectangle { width: 30, height: 50 };
                                          println!("rect1 is {:?}", rect1);
                                      }
                                      

                                      如第一行所示:一定要导入调试库 #[derive(Debug)] ,之后在 println 和 print 宏中就可以用 {:?} 占位符输出一整个结构体:

                                      rect1 is Rectangle { width: 30, height: 50 }
                                      

                                      如果属性较多的话可以使用另一个占位符 {:#?} 。

                                      输出结果:

                                      rect1 is Rectangle {

                                      width: 30,

                                      height: 50

                                      }

                                      16. 结构体方法实例

                                      结构体方法的第一个参数必须是 &self ,不需声明类型,因为 self 不是一种风格而是关键字。

                                      struct Rectangle {
                                          width: u32,
                                          height: u32,
                                      }
                                      impl Rectangle {
                                          fn area(&self) -> u32 {
                                              self.width * self.height
                                          }
                                          fn wider(&self, rect: &Rectangle) -> bool {
                                              self.width > rect.width
                                          }
                                      }
                                      fn main() {
                                          let rect1 = Rectangle { width: 30, height: 50 };
                                          let rect2 = Rectangle { width: 40, height: 20 };
                                          println!("{}", rect1.wider(&rect2));
                                      }
                                      

                                      运行结果:

                                      false

                                      17. 结构体关联函数实例

                                      之所以"结构体方法"不叫"结构体函数"是因为"函数"这个名字留给了这种函数:它在 impl 块中却没有 &self 参数。

                                      这种函数不依赖实例,但是使用它需要声明是在哪个 impl 块中的。

                                      一直使用的 String::from 函数就是一个"关联函数"。

                                      #[derive(Debug)]
                                      struct Rectangle {
                                          width: u32,
                                          height: u32,
                                      }
                                      impl Rectangle {
                                          fn create(width: u32, height: u32) -> Rectangle {
                                              Rectangle { width, height }
                                          }
                                      }
                                      fn main() {
                                          let rect = Rectangle::create(30, 50);
                                          println!("{:?}", rect);
                                      }
                                      

                                      运行结果:

                                      Rectangle { width: 30, height: 50 }

                                      贴士:结构体 impl 块可以写几次,效果相当于它们内容的拼接!

                                      18. 单元结构体

                                      结构体可以只作为一种象征而无需任何成员:

                                      struct UnitStruct;

                                      我们称这种没有身体的结构体为单元结构体(Unit Struct)。

                                      19. 枚举类

                                      1. match语法实例

                                      fn main() {
                                          enum Book {
                                              Papery {index: u32},
                                              Electronic {url: String},
                                          }
                                          let book = Book::Papery{index: 1001};
                                          let ebook = Book::Electronic{url: String::from("url...")};
                                          match book {
                                              Book::Papery { index } => {
                                                  println!("Papery book {}", index);
                                              },
                                              Book::Electronic { url } => {
                                                  println!("E-book {}", url);
                                              }
                                          }
                                      }
                                      

                                      运行结果:

                                      Papery book 1001

                                      match 块也可以当作函数表达式来对待,它也是可以有返回值的:

                                      match 枚举类实例 {
                                          分类1 => 返回值表达式,
                                          分类2 => 返回值表达式,
                                          ...
                                      }
                                      

                                      match 除了能够对枚举类进行分支选择以外,还可以对整数、浮点数、字符和字符串切片引用(&str)类型的数据进行分支选择。其中,浮点数类型被分支选择虽然合法,但不推荐这样使用,因为精度问题可能会导致分支错误。

                                      对非枚举类进行分支选择时必须注意处理例外情况,即使在例外情况下没有任何要做的事,例外情况用下划线 _ 表示:

                                      实例

                                      fn main() {
                                          let t = "abc";
                                          match t {
                                              "abc" => println!("Yes"),
                                              _ => {},
                                          }
                                      }
                                      

                                      2. Option 枚举类

                                      Option 是 Rust 标准库中的枚举类,这个类用于填补 Rust 不支持 null 引用的空白。

                                      Rust 在语言层面彻底不允许空值 null 的存在,但无奈null 可以高效地解决少量的问题,所以 Rust 引入了 Option 枚举类:

                                      enum Option {
                                          Some(T),
                                          None,
                                      }
                                      

                                      如果你想定义一个可以为空值的类,你可以这样:

                                      let opt = Option::Some("Hello");
                                      

                                      如果你想针对 opt 执行某些操作,你必须先判断它是否是 Option::None

                                      实例

                                      fn main() {
                                          let opt = Option::Some("Hello");
                                          match opt {
                                              Option::Some(something) => {
                                                  println!("{}", something);
                                              },
                                              Option::None => {
                                                  println!("opt is nothing");
                                              }
                                          }
                                      }
                                      

                                      运行结果:

                                      Hello

                                      如果你的变量刚开始是空值,你体谅一下编译器,它怎么知道值不为空的时候变量是什么类型的呢?

                                      所以初始值为空的 Option 必须明确类型:

                                      实例

                                      fn main() {
                                          let opt: Option = Option::None;
                                          match opt {
                                              Option::Some(something) => {
                                                  println!("{}", something);
                                              },
                                              Option::None => {
                                                  println!("opt is nothing");
                                              }
                                          }
                                      }
                                      

                                      运行结果:

                                      opt is nothing

                                      这种设计会让空值编程变得不容易,但这正是构建一个稳定高效的系统所需要的。由于 Option 是 Rust 编译器默认引入的,在使用时可以省略 Option:: 直接写 None 或者 Some() 。

                                      Option 是一种特殊的枚举类,它可以含值分支选择:

                                      实例

                                      fn main() {
                                          let t = Some(64);
                                          match t {
                                              Some(64) => println!("Yes"),
                                              _ => println!("No"),
                                          }
                                      }
                                      

                                      20. if let 语法实例

                                      let i = 0;
                                      match i {
                                          0 => println!("zero"),
                                          _ => {},
                                      }
                                      

                                      放入主函数运行结果:

                                      zero

                                      这段程序的目的是判断 i 是否是数字 0,如果是就打印 zero。

                                      现在用 if let 语法缩短这段代码:

                                      let i = 0;
                                      if let 0 = i {
                                          println!("zero");
                                      }
                                      

                                      if let 语法格式如下:

                                      if let 匹配值 = 源变量 {
                                          语句块
                                      }
                                      

                                      可以在之后添加一个 else 块来处理例外情况。

                                      if let 语法可以认为是只区分两种情况的 match 语句的"语法糖"(语法糖指的是某种语法的原理相同的便捷替代品)。

                                      对于枚举类依然适用:

                                      实例

                                      fn main() {
                                          enum Book {
                                              Papery(u32),
                                              Electronic(String)
                                          }
                                          let book = Book::Electronic(String::from("url"));
                                          if let Book::Papery(index) = book {
                                              println!("Papery {}", index);
                                          } else {
                                              println!("Not papery book");
                                          }
                                      }
                                      

                                      21. Rust 组织管理

                                      Rust 中有三个重要的组织概念:箱 、包 、模块 。

                                      1. 箱(Crate)

                                      "箱"是二进制程序文件或者库文件,存在于"包"中。

                                      "箱"是树状结构的,它的树根是编译器开始运行时编译的源文件所编译的程序。

                                      注意:“二进制程序文件"不一定是"二进制可执行文件”,只能确定是是包含目标机器语言的文件,文件格式随编译环境的不同而不同。

                                      2. 包(Package)

                                      当我们使用 Cargo 执行 new 命令创建 Rust 工程时,工程目录下会建立一个 Cargo.toml 文件。工程的实质就是一个包,包必须由一个 Cargo.toml 文件来管理,该文件描述了包的基本信息以及依赖项。

                                      一个包最多包含一个库"箱",可以包含任意数量的二进制"箱",但是至少包含一个"箱"(不管是库还是二进制"箱")。

                                      当使用 cargo new 命令创建完包之后,src 目录下会生成一个 main.rs 源文件,Cargo 默认这个文件为二进制箱的根,编译之后的二进制箱将与包名相同。

                                      3. 模块(Module)

                                      对于一个软件工程来说,我们往往按照所使用的编程语言的组织规范来进行组织,组织模块的主要结构往往是树。Java 组织功能模块的主要单位是类,而 JavaScript 组织模块的主要方式是 function。

                                      这些先进的语言的组织单位可以层层包含,就像文件系统的目录结构一样。Rust 中的组织单位是模块(Module)。

                                      22. 访问权限

                                      Rust 中有两种简单的访问权:公共(public)和私有(private)。

                                      默认情况下,如果不加修饰符,模块中的成员访问权将是私有的。

                                      如果想使用公共权限,需要使用 pub 关键字。

                                      对于私有的模块,只有在与其平级的位置或下级的位置才能访问,不能从其外部访问。

                                      use 关键字

                                      use 关键字能够将模块标识符引入当前作用域:

                                      实例

                                      mod nation {
                                          pub mod government {
                                              pub fn govern() {}
                                          }
                                      }
                                      use crate::nation::government::govern;
                                      fn main() {
                                          govern();
                                      }
                                      

                                      这段程序能够通过编译。

                                      因为 use 关键字把 govern 标识符导入到了当前的模块下,可以直接使用。

                                      这样就解决了局部模块路径过长的问题。

                                      当然,有些情况下存在两个相同的名称,且同样需要导入,我们可以使用 as 关键字为标识符添加别名:

                                      实例

                                      mod nation {
                                          pub mod government {
                                              pub fn govern() {}
                                          }
                                          pub fn govern() {}
                                      } 
                                      use crate::nation::government::govern;
                                      use crate::nation::govern as nation_govern;
                                      fn main() {
                                          nation_govern();
                                          govern();
                                      }
                                      

                                      这里有两个 govern 函数,一个是 nation 下的,一个是 government 下的,我们用 as 将 nation 下的取别名 nation_govern。两个名称可以同时使用。

                                      use 关键字可以与 pub 关键字配合使用:

                                      实例

                                      mod nation {
                                          pub mod government {
                                              pub fn govern() {}
                                          }
                                          pub use government::govern;
                                      }
                                      fn main() {
                                          nation::govern();
                                      }
                                      

                                      2. 引用标准库

                                      Rust 官方标准库字典:https://doc.rust-lang.org/stable/std/all.html

                                      在学习了组织管理概念之后,我们可以轻松的导入系统库来方便的开发程序了:

                                      实例

                                      use std::f64::consts::PI;
                                      fn main() {
                                          println!("{}", (PI / 2.0).sin());
                                      }
                                      

                                      运行结果:

                                      1

                                      所有的系统库模块都是被默认导入的,所以在使用的时候只需要使用 use 关键字简化路径就可以方便的使用了。

                                      23. 格式化输出

                                      打印操作由 std::fmt 里面所定义的一系列宏来处理,包括:

                                      • format!:将格式化文本写到字符串。
                                      • print!:与 format! 类似,但将文本输出到控制台(io::stdout)。
                                      • println!: 与 print! 类似,但输出结果追加一个换行符。
                                      • eprint!:与 print! 类似,但将文本输出到标准错误(io::stderr)。
                                      • eprintln!:与 eprint! 类似,但输出结果追加一个换行符。

                                        24. 特性

                                        特性(trait)概念接近于 Java 中的接口(Interface),但两者不完全相同。特性与接口相同的地方在于它们都是一种行为规范,可以用于标识哪些类有哪些方法。

                                        特性在 Rust 中用 trait 表示:

                                        trait Descriptive {
                                            fn describe(&self) -> String;
                                        }
                                        

                                        Descriptive 规定了实现者必需有 describe(&self) -> String 方法。

                                        我们用它实现一个结构体:

                                        实例

                                        struct Person {
                                            name: String,
                                            age: u8
                                        }
                                        impl Descriptive for Person {
                                            fn describe(&self) -> String {
                                                format!("{} {}", self.name, self.age)
                                            }
                                        }
                                        

                                        格式是:

                                        impl for

                                        Rust 同一个类可以实现多个特性,每个 impl 块只能实现一个。

                                        1. 默认特性

                                        这是特性与接口的不同点:接口只能规范方法而不能定义方法,但特性可以定义方法作为默认方法,因为是"默认",所以对象既可以重新定义方法,也可以不重新定义方法使用默认的方法:

                                        实例

                                        trait Descriptive {
                                            fn describe(&self) -> String {
                                                String::from("[Object]")
                                            }
                                        }
                                        struct Person {
                                            name: String,
                                            age: u8
                                        }
                                        impl Descriptive for Person {
                                            fn describe(&self) -> String {
                                                format!("{} {}", self.name, self.age)
                                            }
                                        }
                                        fn main() {
                                            let cali = Person {
                                                name: String::from("Cali"),
                                                age: 24
                                            };
                                            println!("{}", cali.describe());
                                        }
                                        

                                        运行结果:

                                        Cali 24

                                        如果我们将 impl Descriptive for Person 块中的内容去掉,那么运行结果就是:

                                        [Object]

                                        2. 特性做参数

                                        很多情况下我们需要传递一个函数做参数,例如回调函数、设置按钮事件等。在 Java 中函数必须以接口实现的类实例来传递,在 Rust 中可以通过传递特性参数来实现:

                                        fn output(object: impl Descriptive) {
                                            println!("{}", object.describe());
                                        }
                                        

                                        任何实现了 Descriptive 特性的对象都可以作为这个函数的参数,这个函数没必要了解传入对象有没有其他属性或方法,只需要了解它一定有 Descriptive 特性规范的方法就可以了。当然,此函数内也无法使用其他的属性与方法。

                                        特性参数还可以用这种等效语法实现:

                                        fn output(object: T) {
                                            println!("{}", object.describe());
                                        }
                                        

                                        这是一种风格类似泛型的语法糖,这种语法糖在有多个参数类型均是特性的情况下十分实用:

                                        fn output_two(arg1: T, arg2: T) {
                                            println!("{}", arg1.describe());
                                            println!("{}", arg2.describe());
                                        }
                                        

                                        特性作类型表示时如果涉及多个特性,可以用 + 符号表示,例如:

                                        fn notify(item: impl Summary + Display)
                                        fn notify(item: T)
                                        

                                        注意:仅用于表示类型的时候,并不意味着可以在 impl 块中使用。

                                        复杂的实现关系可以使用 where `关键字简化,例如:

                                        fn some_function(t: T, u: U)
                                        

                                        可以简化成:

                                        fn some_function(t: T, u: U) -> i32
                                            where T: Display + Clone,
                                                  U: Clone + Debug
                                        

                                        在了解这个语法之后,泛型章节中的"取最大值"案例就可以真正实现了:

                                        实例

                                        trait Comparable {
                                            fn compare(&self, object: &Self) -> i8;
                                        }
                                        fn max(array: &[T]) -> &T {
                                            let mut max_index = 0;
                                            let mut i = 1;
                                            while i  0 {
                                                    max_index = i;
                                                }
                                                i += 1;
                                            }
                                            &array[max_index]
                                        }
                                        impl Comparable for f64 {
                                            fn compare(&self, object: &f64) -> i8 {
                                                if &self > &object { 1 }
                                                else if &self == &object { 0 }
                                                else { -1 }
                                            }
                                        }
                                        fn main() {
                                            let arr = [1.0, 3.0, 5.0, 4.0, 2.0];
                                            println!("maximum of arr is {}", max(&arr));
                                        }
                                        

                                        运行结果:

                                        maximum of arr is 5

                                        Tip: 由于需要声明 compare 函数的第二参数必须与实现该特性的类型相同,所以 Self (注意大小写)关键字就代表了当前类型(不是实例)本身。

                                        3. 特性做返回值

                                        特性做返回值格式如下:

                                        实例

                                        fn person() -> impl Descriptive {
                                            Person {
                                                name: String::from("Cali"),
                                                age: 24
                                            }
                                        }
                                        

                                        但是有一点,特性做返回值只接受实现了该特性的对象做返回值且在同一个函数中所有可能的返回值类型必须完全一样。比如结构体 A 与结构体 B 都实现了特性 Trait,下面这个函数就是错误的:

                                        实例

                                        fn some_function(bool bl) -> impl Descriptive {
                                            if bl {
                                                return A {};
                                            } else {
                                                return B {};
                                            }
                                        }
                                        

                                        4. 有条件实现方法

                                        impl 功能十分强大,我们可以用它实现类的方法。但对于泛型类来说,有时我们需要区分一下它所属的泛型已经实现的方法来决定它接下来该实现的方法:

                                        struct A {}
                                        impl A {
                                            fn d(&self) {}
                                        }
                                        

                                        这段代码声明了 A 类型必须在 T 已经实现 B 和 C 特性的前提下才能有效实现此 impl 块。

                                        25. 优雅地错误处理

                                        Result 如此常见以至于 Rust 有个强大的操作符 ? 来与之配合。 以下两个表达式是等价的:

                                        do_something_that_might_fail()?
                                        match do_something_that_might_fail() {
                                            Ok(v) => v,
                                            Err(e) => return Err(e),
                                        }
                                        

                                        26. 丑陋的 Option/Result 处理

                                        当你只是试图快速地写一些代码时,Option/Result 对付起来可能比较无聊。 Option 和 Result 都有一个名为 unwrap 的函数:这个函数可以简单粗暴地获取其中的值。

                                        unwrap 会:获取 Option/Result 内部的值

                                        如果枚举的类型是 None/Err, 则会 panic!

                                        这两段代码是等价的:

                                        my_option.unwrap()
                                        match my_option {
                                            Some(v) => v,
                                            None => panic!("some error message generated by Rust!"),
                                        }
                                        

                                        类似的:

                                        my_result.unwrap()
                                        match my_result {
                                            Ok(v) => v,
                                            Err(e) => panic!("some error message generated by Rust!"),
                                        }
                                        

                                        不过啊,做个好 Rustacean,正确地使用 match!

                                        27. Vectors

                                        一些经常使用的泛型是集合类型。一个 vector 是可变长度的元素集合,以 Vec 结构表示。

                                        比起手动构建,宏 vec! 让我们可以轻松地创建 vector。

                                        Vec 有一个形如 iter() 的方法可以为一个 vector 创建迭代器,这允许我们可以轻松地将 vector 用到 for 循环中去。

                                        内存细节:

                                        Vec 是一个结构体,但是内部其实保存了在堆上固定长度数据的引用。

                                        一个 vector 开始有默认大小容量,当更多的元素被添加进来后,它会重新在堆上分配一个新的并具有更大容量的定长列表。(类似 C++ 的 vector)

                                        实例

                                        fn main() {
                                            // 我们可以显式确定类型
                                            let mut i32_vec = Vec::::new(); // turbofish 
                                                println!("{}", word);
                                            }
                                        }
                                        
                                            let mut foo = 42;
                                            let f = &mut foo;
                                            let bar = *f; // 取得所有者值的拷贝
                                            *f = 13;      // 设置引用所有者的值
                                            println!("{}", bar);
                                            println!("{}", foo);
                                        }
                                        
                                            // 静态变量的范围也可以被限制在一个函数内
                                            static mut SECRET: &'static str = "swordfish";
                                            // 字符串字面值拥有 'static 生命周期
                                            let msg: &'static str = "Hello World!";
                                            let p: &'static f64 = Π
                                            println!("{} {}", msg, p);
                                            // 你可以打破一些规则,但是必须是显式地
                                            unsafe {
                                                // 我们可以修改 SECRET 到一个字符串字面值因为其同样是 'static 的
                                                SECRET = "abracadabra";
                                                println!("{}", SECRET);
                                            }
                                        }
                                        
VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]