Rust语言文档精简版(上)——cargo、输出、基础语法、数据类型、所有权、结构体、枚举和模式匹配

Rust文档精简版

没学过本语言的建议看官方文档,作者不建议看本文档进行学习,本文档适用于复习,本文为官方文档的精简版并加以改进。

rust安装: https://www.rust-lang.org/zh-CN/tools/install

文章目录

  • Rust文档精简版
    • 一、初识cargo
    • 二、Rust输出
      • 使用 `rustc`编译rs文件
      • 执行main
      • println!(宏)
      • 案例1:输出
      • 案例2:随机数
      • 案例3:比较大小
    • 三、基础语法
      • 1、变量
      • 2、常量const
      • 3、shadowing隐藏
      • 4、注释
      • 5、函数
      • 6、控制流
        • 1、`if` 表达式
        • 2、 `else if` 处理多重条件
        • 3、循环
          • 1、loop循环
          • 2、while循环
          • 3、for循环
    • 四、数据类型
      • 1、标量
        • 1、整数类型
        • 2、浮点类型
        • 3、数字运算
        • 4、布尔类型
        • 5、字符类型
      • 2、复合类型
        • 1、元组类型
        • 2、数组类型
    • 五、所有权
      • 1、stack与heap栈与堆
      • 2、克隆
      • 3、所有权规则
      • 4、引用和借用
        • 数据竞争
        • 引用规则
      • 5、切片
    • 六、Struct结构体
        • 1、普通结构体
        • 2、元组结构体
        • 3、struct方法
    • 七、枚举和模式匹配
      • 1、定义枚举
        • 枚举与结构体结合例子:
      • 2、 option枚举
      • 3、match控制流运算符
      • 4、if let简单控制流语句

一、初识cargo

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

创建Rust 工程目录

cargo new xxx

Cargo 除了创建工程以外还具备构建(build)工程、运行(run)工程等一系列功能,构建和运行分别对应以下命令:

cargo build 
cargo run 

Cargo 还具有获取包、打包、高级构建等功能。

  • cargo clippy: 类似eslint,lint工具检查代码可以优化的地方
  • cargo check:检查代码。
  • cargo fmt: 类似go fmt,代码格式化
  • cargo tree: 查看第三方库的版本和依赖关系
  • cargo bench: 运行benchmark(基准测试,性能测试)
  • cargo udeps(第三方): 检查项目中未使用的依赖
  • cargo build --release:编译时会进行优化,代码会运行的更快,但编译时间更长(正式发布)

二、Rust输出

Rust 输出文字的方式主要有两种:println!()print!()。前者会在输出的最后附加输出一个换行符。当用这两个"函数"输出信息的时候,第一个参数是格式字符串,后面是一串可变参数,对应着格式字符串中的"占位符"

fn main() {
    let a = 12;
    println!("a is {}", a);
}
//输出a is 12

使用 rustc编译rs文件

rustc main.rs

执行main

./main

println!(宏)

{} 之间可以放一个数字,它将把之后的可变参数当作一个数组来访问,下标从 0 开始。

println!("a is {0}, a again is {0}", a); 

格式字符串

fn main() { 
    println!("{{内容}}"); 
} 
//输出{内容}

案例1:输出

use std::io; //prelude

fn main() {
    println!("猜数");
    println!("猜一个数");
	//定义mut可变
    let mut guess = String::new();
	//最好加expect来判断
    io::stdin().read_line(&mut guess).expect("无法读取行");
    println!("你猜的数是{}",guess)
}

案例2:随机数

use rand::Rng; //trait

fn main() {
    let secret_number = rand::thread_rng().gen_range(1..101);
    println!("神秘数字是{}",secret_number);

}

案例3:比较大小

use std::io; //prelude
use rand::Rng; //trait
use std::cmp::Ordering;

fn main() {
    println!("猜数");
    let secret_number = rand::thread_rng().gen_range(1..101);
    println!("神秘数字是{}",secret_number);
    loop {
        println!("猜一个数");

        let mut guess = String::new();
    
        io::stdin().read_line(&mut guess).expect("无法读取行");
    
        let guess:u32 = match guess.trim().parse(){
            Ok(num) => num,
            Err(_) => continue,
        };
    
        println!("你猜的数是{}",guess);
    
        match  guess.cmp(&secret_number){
            Ordering::Less => println!("猜的数比神秘数小"),
            Ordering::Greater => println!("猜的数比神秘数大"),
            Ordering::Equal =>{
                println!("你赢了");
                break;
            },
        }
    }
}

三、基础语法

1、变量

声明了 a 为无符号 64 位整型变量

如果没有声明类型,a 将自动被判断为有符号 32 位整型变量

let a: u64 = 123;

声明a为可变变量。

let mut a = 123;

2、常量const

不可使用mut,

const MAX_POINTS:u32 = 100_00

3、shadowing隐藏

可以使用相同名字声明新变量,新变量会shadow之前声明的同名变量。

let spaces = "    "
let spaces = space.len();

4、注释

// 单行
/*
多行
*/

5、函数

Rust 中的函数定义以 fn 开始,后跟着函数名和一对圆括号。大括号告诉编译器函数体在哪里开始和结束。

风格:下划线命名法

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

参数的使用

fn main() {
    another_function(5);
}
fn another_function(x: i32) {
    println!("The value of x is: {}", x);
}

6、控制流

1、if 表达式

注意:代码中if后面的条件必须bool 值。如果条件不是 bool 值,会报错。

fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

2、 else if 处理多重条件

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

3、循环

Rust 有三种循环:loopwhilefor

1、loop循环

使用 loop 重复执行代码,相当于python等语言中的while true

fn main() {
    loop {
        //重复执行此大括号内的代码
        //可适当添加if控制结束
        println!("again!");
    }
}
2、while循环
fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number -= 1;
    }

    println!("LIFTOFF!!!");
}
3、for循环

for 循环的安全性和简洁性使得它成为 Rust 中使用最多的循环结构。

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {}", element);
    }
}

四、数据类型

1、标量

1、整数类型

长度 有符号类型 无符号类型
8 位 i8 u8
16 位 i16 u16
32 位 i32 u32
64 位 i64 u64
128 位 i128 u128
arch isize usize
数字字面量 示例
十进制 98_222
十六进制 0xff
八进制 0o77
二进制 0b1111_0000
字节 (仅限于 u8) b'A'

比方说有一个 u8 ,它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值,比如 256,则会发生整型溢出

要显式处理溢出的可能性,可以使用标准库针对原始数字类型提供的以下一系列方法:

  • 使用 wrapping_* 方法在所有模式下进行包裹,例如 wrapping_add
  • 如果使用 checked_* 方法时发生溢出,则返回 None
  • 使用 overflowing_* 方法返回该值和一个指示是否存在溢出的布尔值
  • 使用 saturating_* 方法使值达到最小值或最大值

2、浮点类型

f32 类型是单精度浮点型,f64 为双精度浮点型。

3、数字运算

基本数学运算:加法、减法、乘法、除法和取模运算

 // addition
    let sum = 5 + 10;

    // subtraction
    let difference = 95.5 - 4.3;

    // multiplication
    let product = 4 * 30;

    // division
    let quotient = 56.7 / 32.2;
    let floored = 2 / 3; // Results in 0

    // remainder
    let remainder = 43 % 5;

4、布尔类型

true和false

let t = true;
let f: bool = false;

5、字符类型

char(字符)类型是该语言最基本的字母类型,大小为 4 个字节

一些合法值:标音字母,中文/日文/韩文的文字,emoji,还有零宽空格(zero width space)

2、复合类型

1、元组类型

元组中的每个位置都有一个类型,并且元组中不同值的类型不要求是相同的。

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}

想从元组中获取个别值,我们可以使用模式匹配来解构destructuring)元组的一个值

fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {}", y);
}

使用一个句点(.)连上要访问的值的索引来直接访问元组元素。

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}

没有任何值的元组 () 是一种特殊的类型,该类型被称为单元类型unit type),只有一个值,该值被称为单元值unit value

2、数组类型

数组的每个元素必须具有相同的类型。Rust 中的数组具有固定长度。

fn main() {
    let a = [1, 2, 3, 4, 5];
}

明确元素数量不需要改变时,数组会特别有用。但它们不像 vector (Rust 中动态数组)类型那么灵活。vector 类型类似于标准库中提供的集合类型,其大小允许增长或缩小。

let a: [i32; 5] = [1, 2, 3, 4, 5];

这里,i32 是每个元素的类型。分号之后,数字 5 表明该数组包含 5 个元素。

let a = [3; 5];

变量名为 a 的数组将包含 5 个元素,这些元素的值初始化为 3。这种写法与 let a = [3, 3, 3, 3, 3]; 效果相同。

访问数组元素

fn main() {
    let a = [1, 2, 3, 4, 5];

    let first = a[0];
    let second = a[1];
}

五、所有权

1、stack与heap栈与堆

栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 后进先出last in, first out

所有权解决问题:

  • 跟踪代码的哪些部分正在使用heap的哪些数据
  • 最小化Heap上的重复数据量
  • 清理heap上未使用的数据以避免空间不足

2、克隆

需要复制String中堆上的数据需要使用clone方法

    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {}, s2 = {}", s1, s2);

而在栈上的数据不需要使用clone,

Rust 有一个叫做 Copy trait 的特殊标注,可以用在类似整型这样的存储在栈上的类型上。

如果一个类型实现了 Copy trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。

    let x = 5;
    let y = x;

    println!("x = {}, y = {}", x, y);

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

  • 所有整数类型,比如 u32
  • 布尔类型,bool,它的值是 truefalse
  • 所有浮点数类型,比如 f64
  • 字符类型,char
  • 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,(i32, i32) 实现了 Copy,但 (i32, String) 就没有。

3、所有权规则

首先,让我们看一下所有权的规则。当我们通过举例说明时,请谨记这些规则:

  • Rust 中的每一个值都有一个被称为其 所有者owner)的变量。
  • 值在任一时刻有且只有一个所有者。
  • 当所有者(变量)离开作用域,这个值将被丢弃。

返回值与作用域

函数在返回值的过程中也会发生所有权的转移

当一个包含heap数据的变量离开作用域,它的值就会被drop函数清理,除非数据所有权移动到另一个变量上。

4、引用和借用

fn main(){
	let s1 = String::from("hello");
	let len = calculate_length(&s1);
	println!("The length of '{}' is {}.",s1,len);
}

fn calculate_length(s:&String) -> usize {
	s.len()
}

数据竞争

  • 两个或更多指针同时访问同一数据。
  • 至少有一个指针被用来写入数据。
  • 没有同步数据访问的机制。

引用规则

  • 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
  • 引用必须总是有效的。

5、切片

    let s = String::from("hello world");

    let hello = &s[0..5];
    let world = &s[6..11];

参数s类型需改为字符串

    let my_string = String::from("hello world");

    // `first_word` 接受 `String` 的切片,无论是部分还是全部
    let word = first_word(&my_string[0..6]);
    let word = first_word(&my_string[..]);
    // `first_word` 也接受 `String` 的引用,
    // 这等同于 `String` 的全部切片
    let word = first_word(&my_string);

    let my_string_literal = "hello world";

    // `first_word` 接受字符串字面量的切片,无论是部分还是全部
    let word = first_word(&my_string_literal[0..6]);
    let word = first_word(&my_string_literal[..]);

    // 因为字符串字面值**就是**字符串 slice,
    // 这样写也可以,即不使用 slice 语法!
    let word = first_word(my_string_literal);

六、Struct结构体

1、普通结构体

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

使用user1中的一个值创建一个新的User实例

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    // --snip--

    let user1 = User {
        email: String::from("[email protected]"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    let user2 = User {
        email: String::from("[email protected]"),
        ..user1
    };
}

2、元组结构体

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

3、struct方法

// fn main() {
//     let w = 50;
//     let h = 50;
//     println!("{}",area(w, h));
// }

// fn area(width:u32,length:u32) -> u32 {
//     width*length
// }

//改进
// fn main() {
//     let rect = (30,50);
//     println!("{}",area(rect));
// }

// fn area(dim:(u32,u32))->u32 {
//     dim.0*dim.1
// }

//最终改进
#[derive(Debug)]
struct Rectangle {
    width:u32,
    length:u32,
}
fn main(){
    let rect = Rectangle{
        width:30,
        length:50,
    };
    println!("{}",area(&rect));

    println!("{:#?}",rect)
}
//计算长方形面积,借用Rectangle的实例
fn area(rect: &Rectangle) -> u32 {
    rect.width * rect.length
}

七、枚举和模式匹配

枚举是一个很多语言都有的功能,不过不同语言中其功能各不相同。Rust 的枚举与 F#、OCaml 和 Haskell 这样的函数式编程语言中的 代数数据类型algebraic data types)最为相似。

1、定义枚举

#![allow(unused)]
fn main() {
enum IpAddrKind {
    V4,
    V6,
}
}



//可以像这样创建 IpAddrKind 两个不同成员的实例:
#![allow(unused)]
fn main() {
enum IpAddrKind {
    V4,
    V6,
}

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
}

枚举与结构体结合例子:

#![allow(unused)]
fn main() {
enum IpAddr {
    V4(String),
    V6(String),
}

let home = IpAddr::V4(String::from("127.0.0.1"));

let loopback = IpAddr::V6(String::from("::1"));
}

2、 option枚举

#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),
    None,
}
}

Option 枚举是如此有用以至于它甚至被包含在了 prelude 之中,你不需要将其显式引入作用域。另外,它的成员也是如此,可以不需要 Option:: 前缀来直接使用 SomeNone。即便如此 Option 也仍是常规的枚举,Some(T)None 仍是 Option 的成员。

3、match控制流运算符

接下来是 match 的分支。一个分支有两个部分:一个模式和一些代码。

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        //Coin::Penny是模式,1是代码,=>将其分开
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

编写一个函数,如果其中含有一个值,将其加一。如果其中没有值,函数应该返回 None 值,而不尝试执行任何操作

fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

match 与枚举相结合在很多场景中都是有用的。

Rust 代码中很多这样的模式:match 一个枚举,绑定其中的值到一个变量,接着根据其值执行代码。

Rust 中的匹配是穷举式的exhaustive):必须穷举到最后的可能性来使代码有效。

案例:

fn main() {
    let dice_roll = 3;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        other => move_player(other),
    }

    fn add_fancy_hat() {
        println!("你戴上了帽子,并没有走动")
    }
    fn remove_fancy_hat() {
        println!("你摘下了帽子,并没有走动")
    }
    fn move_player(num_spaces: u8) {
        println!("你走了{}步",num_spaces);
    }
}

改变游戏规则:当你掷出的值不是 3 或 7 的时候,你必须再次掷出。

使用 _ 来替代变量 other

fn main() {
    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => reroll(),
    }

    fn add_fancy_hat() {
        println!("你戴上了帽子,并没有走动")
    }
    fn remove_fancy_hat() {
        println!("你摘下了帽子,并没有走动")
    }
    fn reroll() {
        println!("重新")
    }
}

改变游戏规则:如果你掷出 3 或 7 以外的值,你的回合将无事发生

fn main() {
    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => (),
    }

    fn add_fancy_hat() {
        println!("你戴上了帽子,并没有走动")
    }
    fn remove_fancy_hat() {
        println!("你摘下了帽子,并没有走动")
    }
}

4、if let简单控制流语句

处理只匹配一个模式的值而忽略其他模式的情况使用if let

if let 获取通过等号分隔的一个模式和一个表达式。它的工作方式与 match 相同,这里的表达式对应 match 而模式则对应第一个分支。

#![allow(unused)]
fn main() {
let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
    println!("three");
}
}

可以在 if let 中包含一个 elseelse 块中的代码与 match 表达式中的 _ 分支块中的代码相同

let mut count = 0;
match coin {
    Coin::Quarter(state) => println!("State quarter from {:?}!", state),
    _ => count += 1,
}
//等同于
let mut count = 0;
if let Coin::Quarter(state) = coin {
    println!("State quarter from {:?}!", state);
} else {
    count += 1;
}

你可能感兴趣的:(Rust,rust,开发语言,后端)