Rust语言入门

  1. 资料: Rust 教程 | 菜鸟教程 (runoob.com)

  2. cargo: 是rust的包管理器和构建系统

    cargo build # 将当前目录下的项目进行编译
    cargo run # 运行当前目录下的项目
    cargo clippy # 检查代码可优化地方
    cargo fmt # 代码格式化
    cargo tree # 查看依赖关系
    cargo bench # 进行性能基准测试
    cargo udeps # 检查项目中未使用的依赖
    
  3. rust输出到命令行

    println!("hello"); // 该函数后面的感叹号表示其是一个宏, 而不是函数
    print!("hello"); // rust以分号结尾
    println!("hello {}", 123); // 其format形式和log4j一致
    println!("a is {0}, a again is {0}", a); // 可以指定输入参数ID进行重复使用
    println!("{{}}"); // 对于大括号的转义, 输出为 {}
    
    fn main() {} // 其需要定义主函数, 其主函数默认是一个无参的void函数 
    
  4. 基础语法

    // rust是强类型语言, 其是进行类型自动推断. 这是进行变量创建和赋值
    let a = 123; // *不可变变量*声明
    let b : i32 = 123; // 声明不可变变量的同时声明类型
    let c : i64 = 123; // 注意, 如果不声明, 会被当做i32, 可能有溢出风险
    a = 123; // ERROR: cannot assign twice to immutable variable. 直接使用let声明的都是变量
    
    let mut a = 1; // 声明变量
    a = 2;
    
    const a : i32 = 1; // 常量声明 -- 必须申明类型
    a = 2;
    
    // 重影机制 -- 不可变变量可被重新绑定
    let a : i32 = 1; // 新旧a其实不同, 其类型可能不同, 但是名字相同
    let a : f32 = 1000f32;
    
    let mut s = "123"; // 字符串和字符串长度
    print!(s.len());
    s = 100; // ERRROR 可变变量的赋值需要校验类型
    let mut s = 100; // 可变变量的重新绑定不需要校验类型
    
  5. 数据类型

    i8, u8 // 对应字节,可以用ASCII字符进行输入: 例如 let a:u8 = b'A';
    i16, u16
    i32, u32
    i64, u64
    i128, u128
    f32, f64 // 直接只用 let a = 1.0; 绑定时类型为f64
    // rust 不支持 ++ 和 --
    bool // 对应的值为小写的 true 和 false
    char // 表示字符, 但是不是ASCII字符, 而是Unicode字符, 大小为四个字节.
    // 源文件中的中文字符需要以UTF-8编码
    
    let tup: (i32, f64, u8) = (500, 6.4, 1); // 可以声明元组数据
    let (x, y, z) = tup; // 可以进行解包
    
    let a = [1, 2, 3, 4, 5]; // 数组 -- 要求是同类型数据
    let c: [i32; 5] = [1, 2, 3, 4, 5]; // 注意声明数组类型的方法
    let first = a[0]; // 数组访问
    let mut a = [1, 2, 3]; // 数组必须可变才能改变其中参数
    a[0] = 4; // 数组更新
    
  6. 函数

    // 函数的定义没有前后依赖
    fn functName(){
        //....
    }
    // 函数参数的定义方式
    fn another_function(x: i32, y: i32) {
    }
    // 注意返回类型定义, 可能有关于引用的内容
    fn another_function(x: i32, y: i32) -> &'static str {
        return "hello";
    }
    fn fun1(){
        // 可以在函数内部定义函数
        fn fun2() -> i32 {
            return 5; // 该return可以省略
        }
    }
    /// 以三个斜杠开头的内容是会被生成到Doc中的注释. 其可以使用md格式进行编写`
    // 在函数中定义子域 -- 等效于定义一个无入参的匿名函数, 将返回值赋值给y
    fn main() {
        let y = {
            let x = 3;
            x + 1
        };
      	// 函数内部能够定义函数
      	fn five() -> i32 {
            5
        }
        println!("five() 的值为: {}", five());
        // 条件语句
        if number < 5 {
            println!("条件为 true");
        } else {
            println!("条件为 false");
        }
      	// 三目操作符可以使用在一行的if进行替代
        let number = if a > 0 { 1 } else { -1 };
        // 循环 -- 没有do-while循环
        while number != 4 {
            number += 1;
        }
        // for循环需要通过迭代器进行循环
        let a = [10, 20, 30, 40, 50];
        for i in a.iter() {
            println!("值为 : {}", i);
        }
        // 简单的range for循环使用range命令执行 -- 从零开始到5,每次加一
      	for i in 0..5 {
            println!("a[{}] = {}", i, a[i]);
        }
        // 原生无限循环操作
        loop {
            if ch == 'O' {
                break;
        }
        // 在break的同时将结果传出
        let location = loop {
            let i = 10;
            if ch == 'O' {
                break i;
            }
        };
    }
    
  7. 所有权:

    • 所有权概念的目标是在编译阶段使得编译器能够分析内存资源的有用性而实现内存管理

    • 所有权的三条规则;

      1. Rust 中的每个值都有一个变量,称为其所有者。
      2. 一次只能有一个所有者。
      3. 当所有者不在程序运行范围时,该值将被删除。
    • 对于所有权的理解:

      每个值都和某个变量绑定, 如果该值通过赋值函数赋值给另外一个变量实际进行的是内存的深拷贝. 所以这个使用该值的有效生命周期和变量的生命周期是一致的 – 可以编译中进行变量名的回收的时候同时回收该变量的值

      编译器会在每个变量生命周期结束时调用这个变量的析构函数 — 解决了内存泄漏的问题

      由此引发的代价: 其对于变量的表现会有额外影响

      // 基础类型在栈中, 其赋值是复制操作, 其他所有类型是在堆中, 其赋值是所有权转移
      let s1 = String::from("hello"); // hello的数据是在堆中, 其默认进行所有权的转移
      let s2 = s1; // 该赋值操作将hello字符串的所有权移交给了s2
      println!("{}, world!", s1); // 错误!s1 已经失效
      
      let s1 = String::from("hello");
      let s2 = s1.clone(); // 堆中的数据只有通过显式复制才会进行对象的深拷贝
      println!("s1 = {}, s2 = {}", s1, s2);
      
      // 函数入参的所有权和变量的类似, 入参相当于是将堆中的所有权传给了子域中的一个变量
      fn take_heap_obj(input:String){}
      let s = String::from("aaa");
      take_heap_obj(s);
      print!(s); // error: s的所有权已经转移
      
      fn take_stack_obj(input:i32){}
      let s:i32 = 1;
      take_stack_obj(s);
      print!(s); // correct: 栈对象通过复制进行传递
      
      // 函数返回值的所有权机制 -- 函数返回值的所有权会被移出函数
      let s2 = String::from("hello");
      let s3 = takes_and_gives_back(s2); // s2的所有权会被移入函数在移出给s3
      fn takes_and_gives_back(a_string: String) -> String{a_string}
      
      // 引用机制 -- rust中引用时所有权的引用,而不是对象的引用
      let s1 = String::from("hello");
      let s2 = &s1;
      // 其引用关系为:
      //  stack[s2 --> s1] -> heap[ "hello" ]
      // 当s1的所有权丧失之后, s2的数据会无法访问 -- 其是对于所有权的租借
      fn main() {
          let s1 = String::from("hello");
          let mut s2 = &s1;
          s2.push_str("aaa") // error: 引用对象如果不经声明不能进行数据更改
          let s3 = s1;
          s2 = &s3; // 重新从 s3 租借所有权 -- 因为涉及到s2的重新赋值所以s2是可变变量
          println!("{}", s2);
      }
      
      // 可变引用 -- 通过申明使得引用变量有权限更改原始对象的值
      let mut s1 = String::from("run");
      let s2 = &mut s1; // s2有权修改s1内容
      s2.push_str("oob");
      // 不允许存在多个可变引用访问同个对象
      let mut s1 = String::from("run");
      let r1 = &mut s1;
      let r2 = &mut s1;
      print!("{},{}",r1,r2); // ERROR -- 不是不允许存在多个可变引用, 而是同一时间只能使用一个
      /*
      error[E0499]: cannot borrow `s1` as mutable more than once at a time
       --> .\run.rs:4:14
        |
      3 |     let r1 = &mut s1;
        |              ------- first mutable borrow occurs here
      4 |     let r2 = &mut s1; // error
        |              ^^^^^^^ second mutable borrow occurs here
      5 |     print!("{},{}",r1,r2)
        |                    -- first borrow later used here
      
      error: aborting due to previous error
      */
      
      // 悬垂引用 -- 指的是某个引用的无法访问实际数据 -- rust会在编译时报错
      fn main() {
          let reference_to_nothing = dangle();
      }
      
      fn dangle() -> &String {
          let s = String::from("hello");
          &s // 局部变量, 会在函数结束后释放, 导致输出成为悬垂引用
      }
      
  8. 切片类型

    // 字符串切片 -- 可以进行字符串取子串
        let s = String::from("broadcast"); // 其类型为 String -- 支持字符串的修改和增加
        let part1 = &s[0..5]; // 其类型为 &str
        let part2 = &s[5..]; // 从第五字符开始到结束
    	let whole = &s[..]; // 表示从头开始到结束
    // 该切片是通过引用指针引用方法在已有的字符串中创建的, 不是独立的堆变量
    // 被切片引用的字符串不允许更改值
    
    // 其他线性对象也可以使用切片
    let arr = [1, 3, 5, 7, 9];
    let part = &arr[0..3];
    
  9. 结构体

    struct Site {
        domain: String, // 注意这里是逗号不是分号
        name: String,
        nation: String,
        found: u32
    }
    // 进行结构体实例化的时候使用一个类似json的方式进行赋值
    let runoob = Site {
        domain: String::from("www.runoob.com"),
        name: String::from("RUNOOB"),
        nation: String::from("China"),
        found: 2013
    };
    // 如果变量名和字段同名可以省略
    let domain = String::from("www.runoob.com");
    let name = String::from("RUNOOB");
    let runoob1 = Site {
        domain,  // 等同于 domain : domain,
        name,    // 等同于 name : name,
        nation: String::from("China"),
        traffic: 2013
    };
    // 如果结构体需要基于另外的结构体进行赋值
    let runoob = Site {
        domain : String::from("hello"),
        ..runoob1
    }
    
    // 有序但是无名的结构体 -- 元组结构体
    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);
    
    // 结构体的所有权 -- 结构体必须掌握其内所有字段的所有权, 因为需要在结构体析构时释放所有字段
    // 结构体的打印
    #[derive(Debug)] // 注解操作, 需要写在对应struct上
    let runoob = Site {
        domain: String::from("www.runoob.com"),
        name: String::from("RUNOOB"),
        nation: String::from("China"),
        found: 2013
    };
    println!("rect1 is {:?}", runoob); // {:?}会以json形式打印结构体 -- 注意无法打印元组结构体
    println!("rect1 is {:#?}", runoob); // {:#?}在打印字段较多的结构体时效果较好 -- 会自动beautify
    
  10. 结构体方法

    // Rust 语言不是面向对象的,从它所有权机制的创新可以看出这一点
    struct Rectangle {
        width: u32,
        height: u32,
    }
       
    impl Rectangle { // 两者是分开定义的
        fn area(&self, factorio:u32) -> u32 { // 第一个参数一定是 &self, 和python类似,是关键字
            self.width * self.height * factorio
        }
        fn create(width: u32, height: u32) -> Rectangle { // 相当于静态方法 -- 结构体关联方法
            Rectangle { width, height }
        }
    }
    
    impl Rectangle { // 可以写多次, 在编译阶段会进行拼接
        fn create1(width: u32, height: u32) -> Rectangle { 
            Rectangle { width, height }
        }
    }
    
    fn main() {
        let rect1 = Rectangle { width: 30, height: 50 };
        println!("rect1's area is {}", rect1.area(10));
        let rect = Rectangle::create(30, 50);
    }
    
  11. 单元结构体

    struct UnitStruct; // 指的是没有变量的结构体, 可以用来写Utils类
    
  12. 枚举类

    #[derive(Debug)]
    enum Book {
        Papery, Electronic
    }
    enum Book { // 为枚举变量添加元组结构体
        Papery(u32),
        Electronic(String),
    }
    enum Book { // 为枚举对象添加结构体 -- 每个枚举对象的结构体可以不同
        Papery { index: u32 },
        Electronic { url: String },
    }
    let book = Book::Papery{index: 1001};
    
  13. 对于枚举值的相等校验 -- 类似于switch

        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);
            }
        }
    
        enum Book {
            Papery(u32),
            Electronic {url: String},
        }
        match book {
            Book::Papery(i) => { // 对于元组结构体需要为其命名
                println!("{}", i);
            },
            Book::Electronic { url } => {
                println!("{}", url);
            }
            _ => {} // 对于例外情况使用下划线表示
        }
    
  14. Option枚举 -- 用于替代null引用

        enum Option<T> {
            Some(T),
            None,
        }
    	let opt = Option::Some("Hello"); // 和java的Optional相似
    	match opt {
            Option::Some(something) => {
                println!("{}", something);
            },
            Option::None => {
                println!("opt is nothing");
            }
        }
    
    	// 对于空值的初始化需要指定类型
    	let opt: Option<&str> = Option::None;
    	// Option类是编译器默认引入的, 可以省略Option::
    	let t = Some(64);
        match t {
                Some(64) => println!("Yes"),
                _ => println!("No"),
        }
    
  15. if let语法 -- 对于二状态match的语法糖, 可以支持枚举和基础类型

    let i = 0;
    if let 0 = i {
        println!("zero");
    }
    
    let t = Some(64);
    if let Some(12) = t { // Some初始化一定需要内容
        // ... 
    } else {
        // ... 
    }
    
    // 对于结果非空的校验
    let t = Some(64);
    if let None = t {
        // ... 
    } else {
        // ... 
    }
    
  16. 代码结构

    • 三个层次: 箱, 包, 模块

    • 箱: 编译后的二进制文件或者动态链接库, 其是一个树结构

    • 包: 一个Cargo.toml文件组织下的src就是一个包, 其中的main.rs文件时该包编译后箱的根

    • 模块: 类似c++中的命名空间, 可以多层嵌套

      mod nation {
          pub mod government { // 用于暴露子模块
              pub fn govern() {} // 只有显式声明才是公开的, 否则都是private(模块内可访问)
              pub struct PublicStruct{
                  pub name:String; // 可以用于暴露字段
              }
              impl PublicStruct {
                  pub fn funct(&self){ // public struct函数
                      // ... 
                  }
              }
          }
          mod congress {
              fn legislate() {}
          }
          mod court {
              fn judicial() {}
          }
      }
      fn main(){
          nation::government.govern()
      }
      
      // 枚举内的内含字段都是公开的
      mod SomeModule {
          pub enum Person {
              King {
                  name: String
              },
              Queen
          }
      }
      
      fn main() {
          let person = SomeModule::Person::King{
              name: String::from("Blue")
          };
          match person {
              SomeModule::Person::King {name} => {
                  println!("{}", name);
              }
          }
      }
      
    • 进行模块的引入, 类似import

      // main.rs
      mod testmode; // 引入同一文件目录下的文件名为second_module的源文件
      fn main(){
          print!("main");
          testmode::main();
          
          use testmode::nation::government::govern;
          use testmode::nation::government::govern1 as newGov;
          
          pub use testmode::nation::government::govern;
          govern();
          newGov();
      }
      
      // testmode.rs
      pub fn main(){
          print!("test.main");
      }
      pub mod nation {
          pub mod government {
              pub fn govern() {}
              pub fn govern1() {}
          }
      }
      
    • 标准库使用

      标准库是默认引用的, 只需要在使用的时候使用use进行路径简化即可

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

    • 分为可恢复错误和不可恢复错误

      • 可恢复错误使用Result<T,E>进行处理

        enum Result<T, E> {
            Ok(T),
            Err(E),
        }
        // 手动在函数返回时传出error
        let f : Result<File, std::io::Error> = File::open("hello.txt");
        if let Ok(a) = f {
            println!("{}",a); // 这里a是枚举类的字段, 相当于作为参数传入了回调函数 
        } 
        let f1 = File::open("hello.txt").unwrap(); // 试图提取结果, 如果有异常则报错, 直接panic
        let f2 = File::open("hello.txt").expect("Failed to open."); // 如果有异常则报出指定错误信息, 直接panic
        
        // error的多层传递 -- 使用match进行结果的转化
        fn g(i: i32) -> Result<i32, bool> {
            let t = f(i);
            return match t {
                Ok(i) => Ok(i),
                Err(b) => Err(b)
            };
        }
        // 通过?标记符将同类异常直接传出
        fn g(i: i32) -> Result<i32, bool> {
            let t = f(i)?; // 如果f()报错则直接在这抛出异常 -- 要求f()的异常类型和g()的异常类型一致
            Ok(t) // 因为确定 t 不是 Err, t 在这里已经是 i32 类型
        }
        
        // 对于error类型的分类处理
        fn read_text_from_file(path: &str) -> Result<String, io::Error> {
            let mut f = File::open(path)?;
            let mut s = String::new();
            f.read_to_string(&mut s)?;
            Ok(s)
        }
        
        fn main() {
            let str_file = read_text_from_file("hello.txt");
            match str_file {
                Ok(s) => println!("{}", s),
                Err(e) => { // 先提取error数据
                    match e.kind() { // 获取error的类型
                        io::ErrorKind::NotFound => {
                            println!("No such file");
                        },
                        _ => {
                            println!("Cannot read the file");
                        }
                    }
                }
            }
        }
        
      • 不可恢复错误使用panic!进行处理

        默认异常是不显示堆栈的, 可以通过环境变量进行激活

        $env:RUST_BACKTRACE=1 ; cargo run

  18. 泛型

    fn max<T>(array: &[T]) -> T { // 函数名称后接泛型列表
        let mut max_index = 0;
        let mut i = 1;
        while i < array.len() {
            if array[i] > array[max_index] {
                max_index = i;
            }
            i += 1;
        }
        array[max_index]
    }
    
    struct Point<T> { // struct名称后接泛型列表
        x: T,
        y: T
    }
    impl<T> Point<T> { // impl后需要列出目标struct的泛型列表 -- 需要写两次泛型列表
        fn mixup<V>(self, other: Point<V>) -> Point<T> { // 泛型struct内可以使用函数泛型
            Point {
                x: self.x,
                y: self.y,
            }
        }
    }
    
    enum Result<T, E> { // 枚举名称后接泛型列表
        Ok(T),
        Err(E),
    }
    
  19. 特性: 类似java中的接口概念

    struct Person {
        name: String,
        age: u8
    }
    // 指定对于某个struct实现某个特性, 即可在该对象中使用该特性提供的函数
    impl Descriptive for Person {
        fn describe(&self) -> String {
            format!("{} {}", self.name, self.age)
        }
    }
    
posted @ 2023-07-28 08:08  NoobSir  阅读(63)  评论(0编辑  收藏  举报