Rust由非盈利组织Mozilla基金会开发,像著名的Mozilla Firefox浏览器和MDN Web Docs都出自该基金会

安装

官方推荐使用Rustup来安装(Rustup是rust的安装器和版本管理工具)

通过rustup-init来安装Rust

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

windows直接安装pustup-init.exe来安装(需联网),默认通过vc安装,建议选择2自定义安装

Linux或者macOS则直接执行以下命令

curl –proto ‘=https’ –tlsv1.2 -sSf https://sh.rustup.rs | sh

注意:Rust的一些包,可能还依赖C编译器,因此最好安装一下GCC

检查是否安装完成

rustc –version

如果喜欢用Visual Studio Code开发,可搭配rust-analyzer插件来使用

更新rust

rustup update

卸载rust和rustup

rustup self uninstall

在安装rustup的同时也会安装Cargo

Cargo是rust的项目构建工具和包管理器

检查是否安装成功

cargo –version

创建第一个rust项目

cargo new halloword

其中Cargo.toml文件是项目的依赖库文件

通过编辑Cargo.toml文件来添加依赖

rust依赖可通过https://crates.io/查找

[package]
name = "hallo_word"
version = "0.0.1"
edition = "2021"
[dependencies]
hyper = "0.14.20" # 来自https://crates.io/
# hyper = { git = "https://github.com/hyperium/hyper" } # 来自第三方社区
# hyper = { path = "../hyper" } # 来自本地文件

构建依赖到项目中

cargo build halloword

当构建完成后,会在项目的根目录中创建一个名叫Cargo.lock的文件,该文件记录项目依赖的版本

src/main.rs是该项目的程序编写文件

在src/main.rs添加以下内容

fn main() {
    println!("hallo, world");
};

进入该项目根目录运行该项目

cargo run

或者直接编译main.rs

rustc main.rs ./main

注意:在windows上,需要加.exe,.\main.exe

在hallo word例子中看到,fn是创建函数的关键字,main是函数名,该函数调用了println!(),其实这是一个macro(宏),“hallo, world"作为字符串参数传递到了该宏中

另外cargo提供了一个命令,来不用生成二进制文件的前提下构建项目,来检查项目的错误

cargo check

build和run默认是debug版本的,如果需要编译release版本执行cargo build –release


在rust中函数是一等公民,其中main函数是程序的入口函数(和golang一样)

注释可以让rust编译器忽略,例如:// hallo word

多行注释可使用块注释,例如:

/*
    hallo
    word
*/

文档注释,行注释例如:

/// 1
/// 2
/// 3

文档块注释,例如:

/**
    hallo
    word
*/

注意:文档注释必须位于lib类型的包中,文档注释看使用md语法

可使用cargo doc命令,来对文档注释生成html文件,放在target/doc目录下,也可以使用 cargo doc –open 生成文档后自动打开html网页

变量通过let关键字来创建

fn main() {
    let mut abc = "hallo word";
    abc = "hhh";
    println!(" abc: {}",abc);
};

注意:rust变量默认是不能变得,需要通过mut关键字来让变量可变

变量存在遮蔽性,如果使用let关键重复声明同一个变量,会遮蔽之前声明的变量的值和类型

变量的遮蔽性和mut关键字的区别是,mut关键字无法修改变量的类型,mut关键字修改的只能是相同类型的值

常量通过const关键字来创建

fn main() {
    const ABC = "hallo word";
    println!(" ABC: {}",ABC);
};

常量不可变,也不能使用mut,而且必须声明类型(字面量也算是一种类型声明)


数据类型

rust是静态型编译语言,因此需要在编译之前就知道全部变量的类型

rust有4大基础类型,这个基础类型又叫标量类型,分别是整型,浮点型,布尔型,字符型

整型就是没有小数的整数(分符号类型和无符号类型,区别是是否为负数,可能为负数使用有符号类型,不可能为负数使用无符号类型)

整型使用u表示无符号类型,用i表示有符号类型,再声明使用多少位的空间,例如i32,就是占32位空间的有符号类型

还有2个特殊的类型,isize和usize,这个类型取决程序执行环境的系统架构,使用64位架构就是占64位空间

整型支持十进制,八进制,二进制,十六进制和字节,rust默认使用i32类型

注意:使用有符号类型会将以二进制补码的方式来存储

浮点类型,就是带小数的数字,rust的浮点型有2个类型,分别是f32和f64,默认是f64,f64是双精度浮点型,f32类型是单精度,并且全部类型都是具备符号的(可为负数)

布尔类型,布尔类型只能是这2个值得其中之一,true和false,布尔类型使用bool关键字来声明,例如 let a: bool = true;,当然也是可以使用字面量来声明

字符类型,字符类型使用单引号包括,并且只能表示一个Unicode字符(因为字符类型只使用4个字节)

字符串类型使用双引号表示,可表示多个字符,使用String表示,例如:

let a:String = "hallo word";
prinln!("{}",a);

除了标量类型外还有复合类型,复合类型有6种,分别是元组和数组,枚举,结构体,字符串,切片

元组类型就是无法扩展和收缩的数组(元组可使用不同的类型),长度是固定的,例如:

let a: (i64, f64) = (100,3.14);

获取元组的元素可通过解构的方式来完成,例如:

let a: (i64, f64) = (100,3.14);
let (b,c) = a;

也可以通过索引值来获取,例如:

let a: (i64, f64) = (100,3.14);
let b = a.1;

元组的索引是从0开始的

数组类型和元组不同,数组的类型必须是相同类型的,数组长度也是固定的

let a = [1,233,666];

rust也提供了动态数组(vector),动态数组将被分配到堆内存空间,而普通数组会被分配到栈内存空间

动态数组使用Vec的方式来声明,动态数组也不能使用不同的类型,但是动态数组可以扩展空间和缩小,而且全部元素在内存是相邻排列的,可使用Vec::new()来创建一个空的动态数组,例如:

let a: Vev<u64> = Vec::new();

当然也可以提供初始值来创建

let b = vec![2,4,68];

rust可以根据字面量来自动推断出类型

动态数组增加元素使用push方法,例如:

let mut a: Vev = Vec::new(); a.push(1); a.push(2); a.push(3); a.push(4); a.push(5);

注意:动态数组离开当前作用域会被丢弃销毁

获取动态数组的元素,可通过get方法或者索引来完成,例如:

let a = vec![2,4,68];
let b = a.get(2);
let c = &a[2];

get方法和索引的区别是,get方法访问超过数组不存在的元素是不会报错的,而是返回none,而索引访问是会导致报错,程序崩溃的

遍历动态数组

let a = vec![2,4,68];
fot i in &a{
    println!("{}",i);
};

动态数组支持枚举类型,这可以让动态数组存储不同类型的值,例如;

enum Hallo{
    Int(i64),
    Text(String),
};
let a = vec![
    Hallo::Int(666),
    Hallo::Text(Sting::from("hallo word")),
];

函数,是rust的第一个公民,main函数是rust程序入口文件,而且函数定义可在main函数之前或者之后,使用fn关键字来声明,例如:

fn hallo_word(){
    println!("hallo word");
};
fn main(){
    hallo_word();
};

函数参数

fn main(){
    a(666);
};
fn a(x:i64){
    println!("{}",x);
};

函数返回值

fn main(){
    a(666);
};
fn a(x:i64) ->{
    x+123
};

注意:返回值使用->返回,而且不能使用分号,因为语句无法作为返回值返回


if判断

let a = 1;
if a <10{
    println!("true >10");
}else if(a>0){
    println!("true >10");
}else{
    println!('none')
};

注意:if判断必须提供一个布尔类型的值,否则会抛出错误

表达式是可以赋值给变量的,例如:let boola = true; let a = if boola{1} else{2}

rust的循环有3种,分别是for,while,以及loop

for遍历循环(常用于遍历集合元素),例如:

let a = [1,2,3,45,6];
for b in a {
    println!("{}",b);
};

while循环,条件为真执行,为假停止循环。例如:

let mut a = 0
while a <= 10{
    println!("hallo wrod");
    a +=1;
};

loop关键字可以一直执行语句,直到被跳出(可使用break关键字),例如:

let mut count = 0;
loop{
    println!("hallo word");
    if (count == 5){
        break;
    };
    count +=1;
};

loop循环可以搞个标签,来指定终止哪次循环(嵌套循环下),例如:‘a’: loop{ break ‘a’}

continue操作符可跳出当前循环,去执行下次循环,一般搭配if分支语句来使用


所有权

所有权是rust的特性之一,无需gc垃圾回收机制就可以保证内存安全

作用域是一个语句块在程序中有效的范围,在该范围内的变量是有效的,离开该范围后将自动释放内存空间

释放内存空间是使用rust提供的一个叫drop函数,会在结尾}自动调用该函数

当一个变量的值是指针时,赋值给另一个变量,当这2个变量离开作用域后,会导致二次释放,因为这2个变量指向的是同一个内存空间地址,二次释放会导致内存污染,这个指针实质上是在堆上的

rust为了解决二次释放导致的内存安全问题,当一个存在指针值的变量赋值给另一个变量后,会销毁第一个变量,而无需再等到离开作用域,第一个变量将变成是无效的,这个赋值将不是复制,而是剪切板一样,移动数据

如果需要复制堆的数据,可使用clone方法,例如:

let a = String::from("hallo word");
let b = a.clone();
println!("{},{}"a,b);

注意:移动数据只针对栈上的指针之类的有效,如果是赋值的是整型之类存在在栈上的,是不需要移动,因为复制指针这些"引用类型"需要耗费性能,rust通过copy trait标注来决定是否赋值给其他变量后,这个变量是否还有效,只要类型实现了copy trait就可以

支持copy trait的类型:全部整数类型,布尔类型,浮点类型,字符类型,元组(部分类型支持)

为了避免需要引用变量而导致原变量失效,rust提供了引用功能,例如:

let a = String::from("hallo word");
let b = &a;

引用不会具备该值的所有权,但是可以指向这个值

引用实质上是借用的,是不能修改借用的值,如果想修改可提前对具备所有权的变量声明mut可变,例如:

let mut a = String::from("hallo");
let b = &mut a;

注意:同一个作用域中只能有一个对这个数据进行可变引用,使用多个可变引用会报错,这是为了避免竞争

切片类型也是没有所有权的,对切片类型进行操作,是不会影响到原来的值,例如:

let a = String::from("hallo world");
let hello = &a[0..5];
let world = &a[6..11];
a.clear();

结构体与元组一样,元素可以是多个类型,但是和元组不同的是,结构图具备元素别名,并且使用struct来定义结构体,例如:

struct Name{
    id: i64,
    username: String,
    email: String,
    pass: String,
};

上面例子就是一个结构体,这个结构体的名称叫Name,具备4个字段,每个字段有自己对于的字段名称和类型

有结构体当然也有结构体实例,结构体是结构体实例的抽象

let name1 = Name{
    id: 1,
    username: String::from("root"),
    email: String::form("a@zhizheng123.test.com"),
    pass: String::form("123456789"),
};
println!("hallo {}", name1.username);
name1.email = String::from("b@zhizheng123.test.com");

注意:结构体实例必须按照结构体每个字段的类型要求进行初始化,不需要按照结构体声明的字段顺序一样

利用函数返回值来完成结构体的实例化,例如:

fn NameTest(username: String, email: String, pass: String) ->{
    Name{
        username,
        email,
        pass,
    };
};
fn main(){
    let name01 = NameTest("user01","test@zhizheng123.test.com","12356987");
    println!("hallo {}", name01.username);
};

基于已有的实例,创建新的实例,例如:

let name2 = Name{
    username: String::from("uesr02"),
    ..name01
};

注意:其中..name01是表示其他字段将从name01实例中获取

元组结构体(结构体字段没名称的就叫元组结构体)

struct Abc(i64,bool,f64);
let abc1 = Abc(100,true,3.14);

单元结构体(这玩意和go的空结构体一样,只定义一个结构体,但是没有字段和属性)

struct Xyz;
let xyz1 = Xyz;

结构体的所有权:当使用已有的实例,创建新的实例时,改变的字段的所有权将会被转移到新的实例,原有的实例的那个字段将失效,其他字段正常,因此使用结构体应该避免使用引用类型


枚举

enum HomeTest{
    Hallo,
    Xyz,
    Abc,
    Hahaha(i64)
};
struct Demo{
    home: HomeTest,
    id: i64,
}
fn main(){
    let test1 = HomeTest::Hallo;
    let test2 = HomeTest::Abc;
    println!(test1);
    println!(test2);
    let a = Demo{
        home: HomeTest::Abc,
        id: 1,
    }
    let b = Demo{
        home: HomeTest::Hallo,
        id: 2,
    }
}

上面例子中,通过enum关键字声明并且定义一个枚举类型,这个枚举类型有3个枚举成员,通过::操作符来访问枚举的成员,枚举是一种类型,因此可用于函数参数类型,结构体类型等等

枚举的成员可以是任何类型的数据,也可以限制成员的数据类型,结构体使用{},单个使用(),多个可使用逗号分割

rust的空值使用Option枚举类型来表示,而不是null

Option,Some,None都包含在prelude标准库中,不需要在源码中声明,或者引用,直接就可以使用

Option枚举类型的实现

enum Option<T> {
    Some(T),
    None,
};

表示一个空值

let null1: Option<i64> = None;

如果使用None,则需要提前提供是什么类型,因为无法通过None来推断出类型

Option比null好的原因是,Option(T)和任何类型都不相同,无法将Option和i64类型的值进行处理

如果有值则:

let num1:Option <i64> = Some(233);

如果使用Some,表示有个值存于Some中

使用Option枚举类型:

fn Test(a:Option<i64>) -> Option<i64> {
    match a {
        Some(i) => Some(i),
        None => None,
    }
}
let abc = Some(666);
let xyz = Test(abc);

上面例子中,通过Test函数传入一个Option类型的参数,并且返回一个Option类型的值,使用match模式匹配,如果传入None则返回None,传入Some(i64),则返回其本身(也就是Some(i64))


模式匹配

match语句

enum Demo{
    Hallo,
    Abc,
    Xyz,
};
let test1 = Demo::Xyz;
let test2 = match test1 {
    Demo::Hallo => "hallo word",
    Demo::Abc | Demo::Xyz => "hallo abcxyz",
    },
    _ =>"0",
};

上面例子中进行匹配Demo对应的枚举类型,match会穷尽列出全部已知的可能,如果存在不知道的可能,使用下划线_来表示,match语句与switch语句很像,match的分支必须指向一个表达式,而且每个分支的表达式结果必须是相同类型的

假如只想对单个模式来进行处理,可使用if let语句,例如:

let test1 = Demo::Xyz;
if let Test2(Demo::Xyz) = test1{
    println!("hallo wrod");
}

上面例子中,如果匹配到Demo::Xyz,输出hallo wrod,如果不匹配则忽略

注意:match和if let会在模式匹配时进行覆盖老值,绑定新的值

matches!宏匹配,可以将一个表达式和模式来进行匹配,返回结果是布尔值,例如:

let a = 1;
assert!(matches!(1..100 | -1..-100));

方法

rust使用impl关键字来定义方法

struct Demo {
    id: i64,
    user: String,
}
impl Demo {
    fn new(id: i64, user: String) -> Demo {
        Demo{
            id: id,
            user: user,
        }
    }
    fn printa(&self){
        println!(self.user)
    }
}
fn mian(){
    let a = Demo{
        id: 1,
        user: "root",
    }
    a.printa()
}

&self为借用Demo结构体,new函数是Demo方法的关联函数,用于初始化结构体实例

&self是self: &Self的简写,self有所有权,而&self是表示不可变借用,&mut self就是可变借用

impl Demo是struct Demo的实现,另外rust允许方法名与结构体的字段名相同,调用方法加(),否则就是在访问字段

rust自动引用和解引用:当一个对象调用方法时,会自动为该对象添加&,&mut,*来确保该对象能与方法匹配

关联函数:当一个存在于impl中,但是没有使用self的函数就被叫关联函数,通常存在impl中的new函数是结构体的构造实例器(rust没有使用new作为关键字,并不强制使用new)

关联函数因为不是方法,需要使用::来调用,例如Demo::new(2,“admin”)

rust允许将结构体定义成多个impl块,而不需要全部都写到一块,更灵活扩展

枚举类型也可被实现


泛型与特征

泛型,当需要对可能存在不同类型的数据处理,但是处理逻辑是一样时,可使用泛型来避免重复,例如:

fn test<T: std::ops::Add<Output = T>>(a:T, b:T) -> T {
    a + b
}
fn mian(){
    println!("{}",test(1,2));
    println!("{}",test(3.14,1.23));
}

注意:T是泛型参数,泛型参数是可自定义的,不过一般都是用T(表示Type),T可以表示任何类型,但是不是所有类型可以进行比较运算处理的,需要通过特征(这里是<T: std::ops::Add<Output = T>)来限制T的类型

结构体泛型

struct Demo<T>{
    a: T,
    b: T,
}
fn main(){
    let x = Demo{a: 1,b: 21};
    let y = Demo{a: 1.23, b: 3.14};
    let z = Demo{a: "hallo word", b: "hahaha"}
}

注意:结构体中使用T泛型的,必须是相同类型,因为第一个字段被赋值,会推断T类型为该赋值的实质类型,因此会报错的

如果想使用类型不同,又想使用泛型,可使用多个泛型参数来分别表示不同的类型

枚举泛型,Option枚举类型就是使用了泛型的,这里就不写了

方法泛型,例如:

struct Demo<T, U> {
    id: T,
    user: U,
}
impl<T,U> Demo<T,U> {
    fn new(id: T, user: U) -> Demo<T,U> {
        Demo{
            id: id,
            user: user,
        }
    }
    fn printa(&self){
        println!(self.user)
    }
}

注意:使用泛型参数必须在提前声明

const泛型(Rust 1.51 版本引入)

const泛型是针对值的泛型,而不是针对类型,例如:

fn Demo<T: std::fmt::Debug,const U:i64>(a:[T; U]){
    println!("{:?}" a)
}
fn main(){
    let a: [i64:5] = [0,123,666,888,987];
    Demo(a)
    let a: [i64:2] = [0,123];
    Demo(a)
}

rust在编译阶段会对泛型的多个类型生成独立的代码(单态),会牺牲一些编译完成文件的大小和编译速度

特征

声明一个特征使用trait关键字,例如:

pub trait Demo {
    fn DemoTest(&self);
}

实现一个特征

pub struct Home {
    pub user: String,
    pub pass: String,
}
impl Demo for Home {
    fn DemoTest(&self) -> String {
        format!("用户是{},密码是{}",self.user,self.pass);
    }
}
pub struct Abc {
    pub admin: String,
    pub rank: String,
}
impl Demo for Abc {
    fn DemoTest(&self) -> String {
        format!("管理者是{},管理等级是{}",self.admin,self.rank);
    }
}

fn main() {
    let home1 = Home{user: "xiaochen",padd: "abc123456789"};
    let xyz = Abc{admin: "main",rank: "max"};
println!("{}",home1.DemoTest());
println!("{}",xyz.DemoTest());
}

从上面的例子中可以看出,特征是定义了一种行为,实现该特征就可使用该行为,特征和结构体,枚举类型是很像的,但是特征可被共享到每个实现上(因为rust没有继承概念,但是行为/方法是相同的)

注意:特征存在一种规则,想实现一个特征必须至少一个是在当前作用域定义的,这个规则是为了避免不会破坏特征和实现的定义

默认实现(无需实现该方法,默认使用该实现,也可对默认实现进行重载)

pub trait Demo {
    fn DemoTest(&self) -> String {
        String::form("hallo wrod");
    }
}

只要在实现中没有重载该方法,就会使用默认实现

特征作为函数参数

pub fn Demo1(data: &impl Demo){
    println!("{}",data.DemoTest());
}

在上面例子中,data函数参数实现了Demo特征,当然也可以使用其他实现了Demo特征的类型来作为参数

特征约束

pub fn Demo2<T: Demo> (data: &T){}

上面例子就是特征约束,特征约束可限制函数参数必须是实现了Demo特征的类型

多重约束

pub fn Demo3<T: Demo + AbcTest> (data: &T){}

或者

pub fn Demo3(data: &(impl Demo + AbcTest)){}

上面例子就是多重约束,可限制参数必须使用Demo特征,还必须实现AbcTest特征

Where 约束(简写多重约束)

pub fn Demo4<T>(data: &T) -> String
    where T: Demo + AbcTest,
    {}

特征约束高级应用

impl <T:Dome1 + Dome2> Demo<T> {
    fn Test1(&self){
        println!("hallo word");
    }
}

上面例子中,只有实现了Dome1特征和Dome2特征的Demo才拥有Test1方法

impl Demo也可以声明返回了一个类型,这个类型实现了Demo特征,例如:

fn Demo5() -> impl Demo {
    Home{
        user: String::from("xiaochen"),
        padd: String::from("abc123456789"),
    };
}

集合类型

集合类型,在rust中是特殊的类型,因为集合类型可以表示多个值,而其他数据类型大多只能表示一个值,集合类型的值被分配到堆内存上,集合类型分3种,分别是vector动态数组(每个元素分配的空间都是一致的,大小,宽度,高度),HashMap KV存储(每个元素都是成对,一个k对应着另一个v),以及String类型

创建vector动态数组

let mut a: Vec(i64) = Vec::new();

注意:rust编译器可通过a.push()自行推导出数据类型,因此不显式声明类型也是没问题的

还可以通过vec!宏来创建,例如:

let mut a = vec![123,666,888,1000];

更新动态数组,通过push方法来完成,例如:

let mut a = Vec::new();
a.push(123);

读取元素,有2种方式,分别是下标索引,get方法,例如:

let mut a = vec![123,666,888,1000];
let b: &i32 = &a[3];
    match a.get(3){
            Some(c) => println!("{c}"),
            None => println!("没有这个元素")
    }

集合类型的下标索引从0开始,&a[3]是借用a动态数组的第4个元素,也就是1000,最后获取导该元素的引用

get方法的返回值是Option<&T>,因此需要match来匹配解构出目标值

下标索引和get方法的区别,很简单,就是下标索引会出现数组越界问题(报错),而get方法通过Option<&T>。如果不存在该值会返回None(安全,不报错)

动态数组和其他类型一样,超出作用域外,会被自动销毁

注意:动态数组的大小是可变的,因此当原数组大小不够时,会分配一个更大的内存空间,再将原数组拷贝到这个更大的内存空间的新数组上,因此在push之前不要进行任何引用避免分配到新内存空间后,之前的引用指到一块无效的内存,rust编译器会自动检查,如果在push之前进行了引用(包括不可变借用和可变借用),如果在push之后没有使用,就正常通过,如果使用了,会报错

可创建指定大小的Vector,通过Vec::with_capacity(10)来完成

迭代遍历动态数组元素

let mut a = vec![123,666,888,1000];
for i in &mut a {
    *i += 1;
    println!("{i}");
}

通过枚举类型和特征对象来实现动态数组存储不同类型的元素,例如:

枚举类型

enum Abc {
    id(i64),
    user(String),
    padd(String),
}
fn useraddr(abc:Abc){
    println!("{:?}",abc);
}

fn main(){
    let a = vec![
        Abc::id(1); 
        Abc::user("admin".to_string()); 
        Abc::padd(String::from("123456")); 
    ]
    for i in a {
        useraddr(i);
    }
}

特征对象

trait Abc {
    fn useraddr(&self);
}
struct id(i64);
imp Abc for id {
    fn useraddr(&self) {
        println!("id: {:?}",self.0)
    }
}

struct user(String);
imp Abc for user {
    fn useraddr(&self) {
        println!("user: {:?}",self.0)
    }
}

struct padd(String);
imp Abc for padd {
    fn useraddr(&self) {
        println!("padd: {:?}",self.0)
    }
}
fn main() {
    let a: Vec<data<dyn Abc>> = vec![
        data::new(id(1)),
        data::new(user("admin".to_string())),
        data::new(padd("123456".to_string())),
    ];
    for i in a {
        i.useraddr();
    }
}

注意:必须表示数组中存储的是哪个特征的对象,例如上面例子中的Vec<data>,表示是特征Abc的对象

HashMap KV存储和动态数组不同,存储的是映射的KV键值对,可通过一个键查询到值,查询效率非常高,复杂度为O(1)

创建HashMap KV存储

可通过new方法创建,例如:

use std::collections::HashMap;
let mut hashdata = HashMap::new();
hashdata.insert("uesr","admin");
hashdata.insert("padd","admin12345");

注意:HashMap没有包含在Rust的prelude中,需要手动从标准库中use引入

可创建指定大小的HashMap,通过HashMap::with_capacity(10)来完成

可以通过迭代器和collect方法创建,例如:

use std::collections::HashMap;
let DataList = vec![
    ("牛奶".to_string(), 3),
    ("方便面".to_string(), 4),
    ("可乐".to_string(), 3),
];
let MapData: HashMap<_,_> = DataList.into_iter().collect();
println!("{:?}",MapData)

注意:HashMap的所有权取决于kv的类型,如果类型实现了Copy特征,那么改类型被复制到HashMap中,如果没有实现,则所有权转移到HashMap

获取HashMap元素通过get方法,例如:

use std::collections::HashMap;
let mut abc = HashMap::new();
abc.insert(String::from("雪糕"), 4);
let test = String::from("雪糕");
let testSearch: Option<&i32> = abc.get(&test);
let testScore: i32 = testSearch.get(&test).copied().unwrap_or(0);

上面例子使用了借用规则,避免获取元素时,发生所有权的转移,并且使用Option<&i32>类型,如果没有该对象,返回None,安全

更新HashMap的中,例如:

use std::collections::HashMap;
let mut data1 = HashMap::new();
   data1.insert("薯条", 5);
   let a = data1.insert("薯条", 10);
assert_eq!(a, Some(5));
   let b = data1.get("薯条");
assert_eq!(b, Some(&10));
let c = data1.entry("啤酒").or_insert(5);
assert_eq!(*c, 5);

上面例子中包含覆盖值;查询新插入的值;查询目标值,如果不存在则插入新值,3种情况

注意:不是所有类型都可以作为hashmap的key,能否作为取决于类型是否实现了std::cmp::Eq特征(相等比较),目前HashMap使用的哈希函数是SipHash散列函数


类型转换


包和模块


格式化输出


生命周期


错误处理