Rust 笔记

概念

  • 变量(关键字:let)

    • 变量默认是不可改变的(immutable),如下的代码运行则会报错。
    fn main() {
        let x = 5;
        println!("x的值为:{x}")
        x = 6;
        println!("x的值为:{x}")
    }
    
    • 尽管变量默认是不可变的,你仍然可以在变量名前添加 mut 来使其可变,mut 也向读者表明了其他代码将会改变这个变量值的意图。将上面的代码修改如下,则可以正常运行。
    fn main() {
        let mut x = 5;
        println!("x的值为:{x}")
        x = 6;
        println!("x的值为:{x}")
    }
    
  • 常量(关键字:const)

    • 类似于不可变变量,常量 (constants) 是绑定到一个名称的不允许改变的值。
    • 不允许对常量使用 mut。常量不光默认不可变,它总是不可变。
    • 常量可以在任何作用域中声明,包括全局作用域。
    • 常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值。
    const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
    
  • 隐藏

    • 我们可以定义一个与之前变量同名的新变量,称之为第一个变量被第二个 隐藏(Shadowing) 了。
    • 当我们使用变量的名称时,编译器将看到第二个变量,任何使用该变量名的行为中都会视为是在使用第二个变量,直到第二个变量自己也被隐藏或第二个变量的作用域结束。
    • 可以用相同变量名称来隐藏一个变量,以及重复使用 let 关键字来多次隐藏,如下所示:
    fn main() {
        let x = 5;
        let x = x + 1;
        {
            let x = x * 2;
            println!("The value of x in the inner scope is: {x}"); // 12 
        }
        println!("The value of x is: {x}"); // 6
    }
    
  • mut 与 隐藏(Shadowing)的区别

    • 当不小心尝试对变量重新赋值时,如果没有使用 let 关键字,就会导致编译时错误。通过使用 let,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不可变的。
    • 当再次使用 let 时,实际上创建了一个新变量,我们可以改变值的类型,并且复用这个名字。
    • 而使用 mut 在修改值的时候,我们不能改变变量的类型。

数据类型

  • 标量:标量(scalar)类型代表一个单独的值。Rust 有四种基本的标量类型:整型(有符号(i),无符号(u))、浮点型(f32,f64)、布尔类型(true,false)和字符类型(char)。
  • 复合类型:复合类型(Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。
    • 元组:将多个其他类型的值组合进一个复合类型的主要方式,长度固定,一旦声明,其长度不会增大或缩小。可以使用模式匹配(pattern matching)来解构(destructure)元组值。也可以使用点号(.)后跟值的索引来直接访问它们。
      fn main() {
        const tup: (i32, f64, u8) = (500, 6.4, 1);
        // 解构
        let (x, y, z) = tup;
        println!("the value of y is: {y}"); // 6.4
        // 直接访问
        let x1 = tup.0;
        let x2 = tup.1;
        let x3 = tup.2;
        println!("the values of x1 x2 x3 is: {x1} {x2} {x3}"); // 500 6.4 1
      }
      
    • 数组:与元组不同,数组中的每个元素的类型必须相同,数组长度是固定的。vector 类型是标准库提供的一个允许增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,那么很可能应该使用 vector。数组是在栈上分配空间。
      // 定义已知长度的数组
      let months = ["January", "February", "March", "April", "May", "June", "July","August", "September", "October", "November", "December"];
      // 定义类型相同的,已知长度的数组
      let a: [i32; 5] = [1, 2, 3, 4, 5];
      // 定义长度为 5 都是数字 3 的数组
      let a = [3; 5];
      // 在栈 (stack) 上分配的已知固定大小的单个内存块。可以使用索引来访问数组的元素
      let a = [1, 2, 3, 4, 5];
      let first = a[0];
      let second = a[1];
      

函数

  • 在函数签名中,必须 声明每个参数的类型。
  • 函数体由一系列的语句和一个可选的结尾表达式构成。
  • 语句和表达式:
    • 语句:是执行一些操作但不返回值的指令。
    // 整个函数定义也是一个语句
    fn main() {
      let y = 6; // 是一个语句
    }
    
    • 表达式:计算并产生一个值。表达式的结尾没有分号。如果在表达式的结尾加上分号,它就变成了语句,而语句不会返回值。
    fn main() {
      // 这是一个表达式
      let y = {
          let x = 3;
          x + 1
      };
      println!("The value of y is: {y}"); // 值是 4
    
      let x = five();
      println!("The value of x is: {x}"); // 5
    
      et x = plus_one(5);
      println!("The value of x is: {x}");
    }
    // 在 five 函数中没有函数调用、宏、甚至没有 let 语句 —— 只有数字 5。这在 Rust 中是一个完全有效的函数。
    fn five() -> i32 {
      5
    }
    // 错误:期望返回一个i32类型的值,这里加上了分号,变成了语句,没有返回值
    fn plus_one(x: i32) -> i32 {
        x + 1; // 去掉分号才正确
    }
    

控制流

  • if 表达式
    • 根据条件执行不同的代码分支。不会尝试自动地将非布尔值转换为布尔值。
    • 在 let 语句中使用 if,if 是一个表达式,我们可以在 let 语句的右侧使用它。
    fn main() {
        let number = 3;
    
        if number < 5 {
            println!("condition was true");
        } else {
            println!("condition was false");
        }
        // 报错,不会自动类型转换,if 后面必须是布尔值
        if number {
            println!("number was three");
        }
        // 在 let 语句中使用 if
        let condition = true;
        let number = if condition { 5 } else { 6 };
        println!("The value of number is: {number}"); // 5
    
        let number = if condition { 5 } else { "six" }; // 报错,if 代码块中的表达式返回一个整数,而 else 代码块中的表达式返回一个字符串。
    }
    
  • 循环
    • Rust 有三种循环:loop、while 和 for。
      • loop
        • 关键字告诉 Rust 一遍又一遍地执行一段代码直到你明确要求停止。
        fn main() {
          loop {
            println!("again"); // 重复打印 again,ctrl + c 终止程序,才能停止
          }
        }
        
        • 使用 break 关键字终止程序,并返回值
        fn main() {
          let mut counter = 0;
          let result = loop {
            counter += 1;
            if(counter == 10) {
              break counter * 2;
            }
          }
          println!("The result is {result}"); // 20
        }
        
        • 使用循环标签,可以在多个循环之间消除歧义
        fn main() {
          let mut count = 0;
          'counting_up': loop {
            println!("count = {count}");
            let mut remaining = 10;
            loop {
               println!("remaining = {remaining}");
               if remaining == 9 {
                break;
               }
               if count == 2 {
                break 'counting_up'; // count == 2 时,终止外层循环
               }
               remaining -= 1;
            }
            count += 1;
          }
          println!("End count = {count}");
        }
        /** 
         *  count = 0
            remaining = 10
            remaining = 9
            count = 1
            remaining = 10
            remaining = 9
            count = 2
            remaining = 10
            End count = 2
        */
        
      • while 条件循环
        • 当条件为 true,执行循环。当条件不再为 true,调用 break 停止循环。可以通过 loop、if、else 和 break 来实现相同效果。
        • 这种结构消除了很多使用 loop、if、else 和 break 时所必须的嵌套,这样更加清晰。当条件为 true 就执行,否则退出循环。
        fn main() {
          // 倒计时的while实现
          let mut number = 3;
          while number != 0 {
            println!("number = {number}");
            number -= 1;
          }
          // 如果使用 for, rev 用来反转 range
          for number in (1..4).rev() {
             println!("number = {number}");
          }
        
          println!("LIFTOFF!!!");
        }
        
      • for 循环
        • 在循环遍历数组时,使用while循环,可能会出现越界的情况,更为推荐的是使用for循环,增加代码的安全性。
        fn main() {
          let a = [1, 2, 3, 4, 5];
        
          let mut index = 0;
          while index < 5 { // 如果数组长度变 4, 而忘记了更改这里的 5 就会使程序 panic
              println!("the value is: {}", a[index]);
        
              index += 1;
          }
          // for 循环 作为更简洁的替换方案
          for element in a {
            println!("the value is: {element}");
          }
        }
        
  • 所有权

    • 所有权(系统)是 Rust 最为与众不同的特性,对语言的其他部分有着深刻含义。它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全,因此理解 Rust 中所有权如何工作是十分重要的。

    • 规则

        1. 每一个值都有一个 所有者(owner)。
        1. 值在任一时刻有且只有一个所有者。
        1. 当所有者(变量)离开作用域,这个值将被丢弃。
          下面的例子跟普通的大括号作用域类似,它是硬编码进程序里的字符串值,是不可变的。
      {                       // s 在这里无效,它尚未声明
        let s = "hello world"; // 从此处起,s 是有效的
                              // 使用 s
      }                       // 此作用域结束,s 不再有效
      

      如果要处理动态长度,则需要将使用到另一种字符串类型:String。

      • 必须在运行时向内存分配器(memory allocator)请求内存。
      • 需要一个当我们处理完 String 时将内存返回给分配器的方法。
      let mut s = String::from("hello");
      
      s.push_str(", world"); // 在字符串后面追加字面值
      
      println!("s = {s}"); // hello, world
      

      当离开作用域时,rust 会自动调用drop,进行垃圾回收。

      • 拷贝和移动
      // 将 5 绑定到 x;接着生成一个值 x 的拷贝并绑定到 y,这里 5 是存储在栈中,所以是拷贝
      let x = 5;
      let y = x; 
      
      // 这里s2拷贝了s1存储在栈中指针,对应着堆中的同一份数据,同时,s1 将失效,防止s2 和 s1离开作用域时,释放相同的内存,导致二次释放的错误
      // 不是浅拷贝,这个操作叫做 移动
      let s1 = String::from("hello");
      let s2 = s1;
      
      • 克隆
      // 这里深度复制了堆上的数据,类似于深拷贝
      let s1 = String::from("hello");
      let s2 = s1.clone();
      println!("s1 = {}, s2 = {}", s1, s2);
      
    • 所有权与函数

      fn main() {
        let s = String::from("hello"); // s 进入作用域
        take_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 移出作用域。没有特殊之处
      

      作为一个通用的规则,任何一组简单标量值的组合都可以实现 Copy,任何不需要分配内存或某种形式资源的类型都可以实现 Copy 。如下是一些 Copy 的类型:

      • 所有整数类型,比如 u32。
      • 布尔类型,bool,它的值是 true 和 false。
      • 所有浮点数类型,比如 f64。
      • 字符类型,char。
      • 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,(i32, i32) 实现了 Copy,但 (i32, String) 就没有。
    • 返回值与作用域
      返回值也可以转移所有权。

      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 并移出给调用的函数
      }
      

      变量的所有权总是遵循相同的模式:

      • 将值赋给另一个变量时移动它。
      • 当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有。
    • 引用与借用

      // 不使用引用,取值的所有权
      fn main() {
        let s1 = String::from("hello");
      
        let (s2, len) = calc_length(s1);
      
        println!("The length of '{}' is {}.", s2, len);
        
      }
      
      fn calc_length(s: String) -> (String, usize) {
        let length = s.len();
        (s, length)         // 通过返回值来交还所有权
      }
      
      // 使用引用,它以一个对象的引用作为参数而不是获取值的所有权:
      fn main() {
          let s1 = String::from("hello");
      
          let len = calc_length(&s1);
      
          println!("The length of '{}' is {}.", s1, len);
        
      }
      
      fn calc_length(s: &String) -> usize { // s 是 String 的引用
          s.len()
      }                                     // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
                                            // 所以什么也不会发生
      

      (默认)不允许修改引用的值,如果要修改,要使用可变引用

      fn main() {
          let mut s = String:from("hello");
          change($mut s);
      }
      change(some_string: &mut String) {
          some_string.push_str(", world");
      }
      

      不允许同时拥有多个对该变量的可变引用,但可以是不可变引用

      let mut s = String::from("hello");
      
      let r1 = &mut s;
      let r2 = &s; // 没问题
      let r3 = &s; // 没问题
      let r4 = &mut s; // 不可行
      
      {
          let r5 = &mut s;
      } // r5 在这里离开了作用域,所以我们完全可以创建一个新的引用
      
      let r6 = &mut s; // 可行
      
      
      • 垂直引用
      // 错误
      fn main() {
          let reference_to_nothing = dangle();
      }
      
      fn dangle() -> &String {
          let s = String::from("hello");
          &s
      } // 这里 s 离开作用域并被丢弃。其内存被释放。所以上面返回的它的引用也是指向无效地址
      
      // 直接返回 String 将控制权交出去,才是正确的做法,所有权被移动出去,所以没有值被释放。
      fn no_dangle() -> String {
          let s = String::from("hello");
          s
      }
      

      引用的规则总结

      • 在任意给定时间,要么只有一个可变引用,要么只能有多个不可变引用。
      • 引用必须总是有效的。
    • slice 类型
      slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。slice 是一种引用,所以它没有所有权。

      fn main() {
        let s = String::from("hello world");
        let index = first_word(&s); // 需要关注 index 值的有效性,如果 s 被清空,仍然用index去访问,就会出现问题
      
        let hello = &s[0..5]; // 前闭后开 等同于 &s[..5]
        let world = &s[6..11]; // 等同于 &s[6..] &s[6..s.len()]
      }
      
      fn first_word(s: &String) -> usize {
        let bytes = s.as_bytes();
        for(i, &item) in bytes.iter().enumerate() {
          if item == b' ' {
            return i
          }
        }
        s.len()
      }
      
      // 用切片实现上面的功能
      fn use_slice_first_word(s: &String) -> &str {
          let bytes = s.as_bytes();
          for(i, &item) in bytes.iter().enumerate() {
            if item == b' ' {
              return &s[0..i];
            }
          }
          &s[..] // 整个 string
      }
      
  • 结构体

    • struct,或者 structure,是一个自定义数据类型,允许你包装和命名多个相关的值,从而形成一个有意义的组合。
    • 定义
      struct User {
        active: bool,
        username: String,
        email: String,
        sign_in_count: u64,
      }
      
    • 使用
      fn main() {
          let user1 = User {
              active: true,
              username: String::from("someusername123"),
              email: String::from("someone@example.com"),
              sign_in_count: 1,
          };
      }
      
      可以使用点号来获取某个特定的值。如果结构体实例是可变的,还可以进行赋值。
      注意:整个实例必须是可变的,不允许只将某个字段标记为可变。
      fn main() {
        let mut user2 = User {
            active: true,
            username: String::from("someusername123"),
            email: String::from("someone@example.com"),
            sign_in_count: 1,
        }
      }
      user2.email = String::from("anotheremail@example.com");
      
      // 使使用结构体更新语法从其他实例创建实例
      let user3 = User {
          active: user2.active,
          username: user2.username,
          email: String::from("other@example.com"),
          sign_in_count: user2.sign_in_count,
      }
      
      // 使用 user3 中的一个值创建一个新的 User 实例
      let user4 = User {
        email: String::from("another@example.com");
        ..user3, // 必须放在最后,以指定其余的字段应从 user3 的相应字段中获取其值,但我们可以选择以任何顺序为任意字段指定值,而不用考虑结构体定义中字段的顺序。
      }
      
      们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式地返回这个实例。
      fn build_user(email: String, username: String) -> User {
        User {
            active: true,
            username: username, // 可以简写为:username,
            email: email,       // 可以简写为:email,
            sign_in_count: 1,
        }
      }
      
      定义元组结构体
      元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。
      struct Color(i32, i32, i32);
      struct Point(i32, i32, i32);
      
      fn main() {
          let black = Color(0, 0, 0);
          let origin = Point(0, 0, 0);
      }
      // 注意 black 和 origin 值的类型不同,因为它们是不同的元组结构体的实例
      
      unit 单元结构体
      struct Unit;
      
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容