Rust 学习笔记 - 基础2

Struct

结构体,自定义的数据类型,为相关联的值命名,打包成有意义的组合。

定义及实例化

struct User {
	username: String,
    email: String,
    sign_in_count: u64,
    active: bool, // 最后的逗号不能省略
}

fn main() {
    let user1 = User { // 字段顺序不必与定义相同,但是字段一个都不能少
        email: String::from("[email protected]"),
        username: String::from("abc"),
        active: true,
        sign_in_count: 556,
    };
}
复制代码
// 如果想要修改 user1 里面的值那么实例化的时候需要加 mut 关键字,所有字段都是可变的,不能部分可变
fn main() {
    let mut user1 = User { // 字段顺序不必与定义相同,但是字段一个都不能少
        email: String::from("[email protected]"),
        username: String::from("abc"),
        active: true,
        sign_in_count: 556,
    };
    user.email = String::from("[email protected]");
}
复制代码
fn build_user(email: String, username: String) -> User {
    User {
        email, // 可以简写,类似 JS
        username,
        active: true,
        sign_in_count: 0,
    }
}
复制代码
fn main() {
    let user1 = User {
        email: String::from("[email protected]"),
        username: String::from("abc"),
        active: true,
        sign_in_count: 556,
    };
    let user2 = User {
        email: String::from("[email protected]"),
        username: String::from("aaa"),
        ..user1 // 使用 .. 把 user1 中相同字段赋值到 user2,与 JS 的扩展运算符类似
    };
}
复制代码

Tuple struct

定义类似 tuple 的 struct,叫做 tuple struct。Tuple struct 整体有个名,但里面的元素没有名,适用于想给整个 tuple 起名,并让它不同于其它 tuple,而且又不需要给每个元素起名。

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

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// black 和 origin 是不同类型,也不能相等
复制代码

Unit-Like Struct(没有任何字段)

可以定义没有任何字段的 struct,因为与 “()”,单元类型类似得名。适用于需要在某个类型上实现某个 trait,但是在里面又没有想要存储的数据。

实例

#[derive(Debug)] // 让自定义类型可以派生于 Debug
struct Rectangle {
    width: u32,
    length: u32,
}

fn main() {
    let rect = Rectangle {
        width: 30,
        length: 50,
    };

    println!("{}", area(&rect)); // 1500

    println!("{:?}", rect); // Rectangle { width: 30, length: 50 }

    println!("{:#?}", rect);
    /*
    Rectangle {
 		width: 30,
    	length: 50,
	}
	*/
}

fn area(rect: &Rectangle) -> u32 {
    rect.width * rect.length
}
复制代码

struct 的方法

struct Rectangle {
    width: u32,
    length: u32,
}

// impl 可以有多个
impl Rectangle {
    // 方法,第一个参数必须是 &self 或者 self,直接用”.“调用
    fn area(&self) -> u32 {
        self.width * self.length
    }

    // 关联函数,参数不能是 self,用"::"调用
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            length: size,
        }
    }
}

fn main() {
    let rect = Rectangle {
        width: 30,
        length: 50,
    };

    let s = Rectangle::square(12);

    println!("{}", rect.area());

    println!("{}", s.area());
}
复制代码

枚举

定义和使用

enum IpAddrKind {
	v4,
	v6,
}

struct IpAddr {
    kind: IpAddrKind,
    address: String,
}

fn main() {
    let four = IpAddrKind::v4;
    let six = IpAddrKind::v6;
    
    let home = IpAddr {
        kind: IpAddrKind::v4,
        address: String::from("127.0.0.1"),
    };
    
    let loopback = IpAddr {
        kind: IpAddrKind::v6,
        address: String::from("::1"),
    };
}
复制代码

将数据附加到枚举的变体中:

enum IpAddr {
    V4(String),
    V6(String),
}
复制代码

优点:不需要额外使用 struct,每个变体可以拥有不同类型以及关联的数据量。

enum IpAddr {
    V4(u8, u8, u8, u8),
    v6(String),
}

fn main() {
    let home = IpAddrKind::V4(127.0.0.1);
}
复制代码

枚举的变体中可以使用任意类型的数据,枚举还可以使用 impl 来定义方法:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {}
}

fn main() {
    let q = Message::Quit;
    let m = Message::Move { x: 20, y: 30 };
    let q = Message::Write(String::from("Hello"));
    let q = Message::ChangeColor(0, 255, 255);
    
    m.call();
}
复制代码

Option 枚举

定义于标准库中,在 Prelude (预导入模块)中,描述了某个值可能存在(某种类型)或不存在的情况。

Rust 中没有 null,但是有类似概念的枚举,用 Option<T> 表示。

// 它包含在 Prelude 中,可以直接使用
enum Option<T> {
	Some(T),
	None,
}
复制代码
fn main() {
    let some_number = Some(5);
    let some_string = Some("A String");
    
    let absent_number: Option<i32> = None;
    // Option<i32> 不能与 i32 直接进行运算,必须先转换才可以,可以能避免空值泛滥的情况
}
复制代码

match

允许一个值与一系列模式进行匹配,并执行匹配的模式对应的代码。模式可以是字面值、变量名、通配符...

#[derive(Debug)]
enum USState {
    Alabama,
    Alaska,
}
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(USState),
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => { // 多行需要花括号
            println!("Penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {:?}!", state);
            25
        },
    }
}
fn main() {
    let c = Coin::Quarter(USState::Alaska);
    println!("{}", value_in_cents(c));
}
复制代码

匹配 Option<T>

fn main() {
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}
复制代码

match 匹配必须穷举所有可能

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        // None => None, // 删除此行会报错,因为未覆盖所有情况
        Some(i) => Some(i + 1),
    }
}
复制代码

使用通配符 _ 来替代剩下的所有可能,必须放在最后。

fn main() {
    let v = 0u8; // 256 种可能

    match v {
        1 => println!("one"),
        2 => println!("two"),
        _ => (), // 指代剩下的所有可能
    }
}
复制代码

if let

处理只关心一种匹配而忽略其它匹配的情况。

// match 写法
fn main() {
    let v = 0u8; // 256 种可能

    match v {
        1 => println!("one"),
        _ => (),
    }
}
复制代码
// if let 写法
fn main() {
    let v = 0u8; // 256 种可能

    if let Some(1) = v {
        println!("one");
    }
}
复制代码

两种写法等价。

另外,也可以使用 else 语法。

fn main() {
    let v = 0u8; // 256 种可能

    if let Some(1) = v {
        println!("one");
    } else {
        println!("others");
    }
}
复制代码

模块系统

Package(包)

Cargo 的特性,让你构建、测试、共享 crate。

一个 Package 包含 1 个 Cargo.toml,它描述了如何构建这些 Crates,只能包含 0 - 1 个 library crate,可以包含任意数量的 binary crate(文件放在 src/bin 下,每个文件是单独的 binary crate),但必须至少包含一个 crate(library 或 binary 都行)。

Crate(单元包)

一个模块树,它可以产生一个 library 或可执行文件。

类型分为:binary 和 library。

Crate Root 是源代码文件,Rust 编译器从这里开始,组成你的 Crate 的根 Module。

两个惯例:

  1. src/main.rs 其实是一个 binary crate 的 crate root,当前 crate 的名称与 package 相同。
  2. src/lib.rs 其实是一个 library crate 的 crate root,package 包含一个library crate,当前 crate 的名称与 package 相同。

Cargo 把 crate root 文件交个 rustc 来构建 library 或 binary。

crate 的作用是将相关功能组合到一个作用域内,便于在项目间进行共享,防止冲突。

Module(模块)

让你控制代码的组织、作用域、私有路径。

在一个 crate 内,将代码进行分组,增加可读性,易于复用,控制项目(item)的私有性。

建立 module 使用 mod 关键字创建,支持嵌套。

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
        fn seat_at_table() {}
    }
    
    mod serving {
        fn take_order() {}
        fn serve_order() {}
        fn take_payment() {}
    }
}
复制代码

Path(路径)

为 struct、function 或 module 等项命名的方式。

绝对路径:从 crate root 开始,使用 crate名或字面值 crate

相对路径:从当前模块开始,使用 self、super 或当前模块的标识符

路径至少有一个标识符组成,标识符之间使用 ::

// src/lib.rs
mod front_of_house {
    pub mod hosting { // 默认是私有的
        pub fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() { // front_of_house 与 eat_at_restaurant 同级可以互相调用
    crate::front_of_house::hosting::add_to_waitlist(); // 绝对路径
    front_of_house::hosting::add_to_waitlist(); // 相对路径
}
复制代码
fn serve_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::serve_order(); // super 代表进入上一级路径
    }
}
复制代码

公共枚举里面的变体都是公共的,而公共结构体下面的字段默认都是私有的。

use

使用 use 来引入 module。

use std::collections::HashMap;
use std::collections::HashMap as hp; // 使用 as 可以起别名
复制代码

pub use 重新导出

// src/lib.rs
mod front_of_house {
    pub mod hosting { // 默认是私有的
        pub fn add_to_waitlist() {}
    }
}
use crate::front_of_house::hosting; // 这个模块不会暴露给外部
pub use crate::front_of_house::hosting; // 这个模块会暴露给外部
复制代码

使用外部包(package):

  1. cargo.toml 添加依赖包(package),默认是从 crates.io 去下载
  2. use 将特定条目引入作用域
  3. 标准库(std)也被当做外部包,但是不需要修改 cargo.toml 来包含 std,需要使用 use 将 std 中的特定条目引入当前作用域

使用嵌套路径来清理大量的 use 语句:

use std::cmp::Ordering;
use std::io;
// 可以简写为
use std::{cmp::Ordering, io};
复制代码
use std::io;
use std::io::Write;
// 可以简写为
use std::io::{self, Write};
复制代码
// * 为通配符,可以把路径中所有的公共条目引入到作用域
// 谨慎使用
// 一般测试的时候将所有被测试代码引入到 tests 模块会用
// 有时候被用于与导入(prelude)模块
use std::collections::*;
复制代码

常用集合

这些集合都是存储在堆内存上的,大小可变的数据。

Vector

由标准库提供的,可以存储多个值,且值的数据类型都相同,值在内存中连续存放。

fn main() {
	let v: Vec<i32> = Vec::new(); // 创建
}
复制代码
fn main() {
	let v = vec![1, 2, 3]; // 使用宏创建
}
复制代码
fn main() {
	let mut v = Vec::new();
    v.push(1);
    v.push(2); // 添加值
}
复制代码
// 读取 vec 里面的元素
fn main() {
	let v = vec![1, 2, 3, 4, 5];
    let third: &i32 = &v[2]; // 如果 index 范围越界,程序会 panic
    println!("The third element is {}", third);

    match v.get(2) {
        Some(third) => println!("The third element is {}", third),
        None => println!("There is no third element"),
    }
}
复制代码
// vec 的遍历
fn main() {
	let v = vec![1, 2, 3, 4, 5];
    for i in &v {
        println!("{}", i);
    }
}
复制代码
fn main() {
	let mut v = vec![1, 2, 3, 4, 5];
    for i in &mut v {
        *i += 50; // 解引用
    }

    for i in v {
        println!("{}", i);
    }
}
复制代码
// 结合 enum 来实现 vector 存储多种类型的数据
enum E {
    Int(i32),
    Float(f64),
    Text(String),
}

fn main() {
	let v = vec![
        E::Int(3),
        E::Text(String::from("aaa")),
        E::Float(12.43),
    ];
}
复制代码

String

字符串是基于 Byte 的一些集合,并提供了一些方法,能够将 byte 解析为文本。Rust 的核心语音层面只有一个字符串类型:字符串切片(&str)。字符串切片是对存储在其他地方、UTF-8 编码的字符串的引用,字符串字面量存储在二进制中,也是字符串切片。

String 是来自标准库,而不是来自核心语言,可增长、可修改、可拥有,也是 UTF-8 编码。

创建

let mut s = String::new();
复制代码
let data = "abcd";
let s = data.to_string();
复制代码
let s = String::from("abcd");
复制代码

更新

fn main() {
	let mut s = String::from("foo");
    s.push_str("bar");
    println!("{}", s);
}
复制代码
fn main() {
	let mut s = String::from("foo");
    s.push('a'); // 单个字符加到 String
    println!("{}", s);
}
复制代码
fn main() {
	let s1 = String::from("foo");
    let s2 = String::from("bar");

    let s3 = s1 + &s2; // 注意后面是字符串切片,前面 s1 无法再继续使用了
    
    println!("{}", s3);
    // println!("{}", s1); // 会报错
    // println!("{}", s2); 
}
复制代码
fn main() {
	let s1 = String::from("foo");
    let s2 = String::from("bar");
    let s3 = String::from("abc");

    let s = format!("{}-{}-{}", s1, s2, s3);
    println!("{}", s); // foo-bar-abc
}
复制代码

方法

fn main() {
	let len = String::from("Hola").len();

    println!("{}", len); // 4
}
复制代码
fn main() {
	let len = String::from("哈哈").len();

    println!("{}", len); // 6
}
复制代码
fn main() {
	let w = String::from("哈哈");

    for b in w.bytes() {
        println!("{}", b); // 229 147 136 229 147 136
    }
}
复制代码
fn main() {
	let w = String::from("哈哈");

    for c in w.chars() { // unicode 标量值
        println!("{}", c); // 哈 哈
    }
}
复制代码

切片

fn main() {
	let w1 = String::from("abcde");
    let w2 = String::from("你好啊");

    let s1 = &w1[0..2];
    let s2 = &w2[0..3];
    // let s3 = &w2[0..2]; // 运行会报错 

    println!("{}", s1); // ab
    println!("{}", s2); // 你
}
复制代码

HashMap

HashMap<K, V>,键值对的形式存储数据,一个键对应一个值。Hash 函数决定如何在内存中存放 K 和 V。

适用场景:通过 K(任何类型)来寻找数据,而不是通过索引。

数据存储在 heap 上。

HashMap 用的较少,所以不在 Prelude 中,标准库对其支持较少,没有内置的宏来创建 HashMap。

HashMap 是同构的,一个 HashMap 中,所有的 K 必须是同一种类型,所有的 V 必须是同一种类型。

创建

use std::collections::HashMap;

fn main() {
	let mut scores: HashMap<String, i32> = HashMap::new();
}
复制代码
use std::collections::HashMap;

fn main() {
	let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
}
复制代码
use std::collections::HashMap;

fn main() {
    let teams = vec![String::from("blue"), String::from("Yellow")];
    let intial_scores = vec![10, 50];

    // _, _ 代表根据后面的数据类型进行类型推断
    // zip 有拉链的意思,就是把两个 vec 像拉链一样拉起来,组合到一起
	let scores: HashMap<_, _> = teams.iter().zip(intial_scores.iter()).collect();
}
复制代码

HashMap 和所有权

对于实现了 Copy trait 的类型(例如 i32),值会被复制到 HashMap 中,对于拥有所有权的值(如 String),值会被移动,所有权会转移给 HashMap。

use std::collections::HashMap;

fn main() {
    let s1 = String::from("abc");
    let s2 = String::from("bcd");

    let mut map = HashMap::new();
    map.insert(s1, s2);

    println!("{}, {}", s1, s2); // 报错
}
复制代码
use std::collections::HashMap;

fn main() {
    let s1 = String::from("abc");
    let s2 = String::from("bcd");

    let mut map = HashMap::new();
    map.insert(&s1, &s2); // 传值的引用

    println!("{}, {}", s1, s2); // 不报错
}
复制代码

获取值

use std::collections::HashMap;

fn main() {
    let s1 = String::from("abc");
    let s2 = String::from("bcd");

    let mut map = HashMap::new();
    map.insert(&s1, 10);
    map.insert(&s2, 50);

    let m = String::from("abc");
    let s = map.get(&m);

    match s {
        Some(s) => println!("{}", s),
        None => println!("None"),
    }
}
复制代码

遍历

use std::collections::HashMap;

fn main() {
    let s1 = String::from("abc");
    let s2 = String::from("bcd");

    let mut map = HashMap::new();
    map.insert(&s1, 10);
    map.insert(&s2, 50);

    for (k, v) in &map {
        println!("{}, {}", k, v);
    }
}
复制代码

更新

K 已经存在了,对应一个 V:

  1. 替换现有 V
  2. 保留现有 V,忽略新的 V
  3. 合并现有的 V 和新的 V

K 不存在直接添加新的。

// 情况1
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(String::from("abc"), 10);
    map.insert(String::from("abc"), 50);

    println!("{:?}", map); // {"abc": 50}
}
复制代码
// 情况2
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(String::from("abc"), 10);

    let e = map.entry(String::from("edf")); // entry 检查 K 是否存在
    e.or_insert(50);

    map.entry(String::from("abc")).or_insert(30);

    println!("{:?}", map); // {"abc": 10, "edf": 50}
}
复制代码
// 情况3
use std::collections::HashMap;

fn main() {
    let text = "hello world wonderful world";

    let mut map = HashMap::new();
    
    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0); // 返回可变引用
        *count += 1; // 解引用,并加1
    }

    println!("{:?}", map); // {"hello": 1, "world": 2, "wonderful": 1}
}
复制代码

Hash 函数

默认情况下,HashMap 使用加密功能强大的 Hash 函数,可以抵抗拒绝服务(DoS)攻击,不过他不是最快的 Hash 算法,但是安全性比较强。

我们可以指定不同的 hasher 来切换到另一个函数。hasher 是实现 BuildHasher trait 的类型。

おすすめ

転載: juejin.im/post/7035487427644882952