Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rust study - 03 (枚举 & 包/crate/pub/use/mod) #59

Open
zhuzhh opened this issue Jan 25, 2023 · 0 comments
Open

rust study - 03 (枚举 & 包/crate/pub/use/mod) #59

zhuzhh opened this issue Jan 25, 2023 · 0 comments

Comments

@zhuzhh
Copy link
Owner

zhuzhh commented Jan 25, 2023

枚举

eg:

enum IpAddrKind {
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

    route(IpAddrKind::V4);
    route(IpAddrKind::V6);
}

fn route(ip_kind: IpAddrKind) {}

和struct一起使用

fn main() {
    enum IpAddrKind {
        V4,
        V6,
    }

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

    let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };

    let loopback = IpAddr {
        kind: IpAddrKind::V6,
        address: String::from("::1"),
    };
}

更简洁的方式

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"));
}
fn main() {
    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }

    let home = IpAddr::V4(127, 0, 0, 1);

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

真实场景

#![allow(unused)]
fn main() {
  struct Ipv4Addr {
    // --snip--
  }

  struct Ipv6Addr {
    // --snip--
  }

  enum IpAddr {
      V4(Ipv4Addr),
      V6(Ipv6Addr),
  }
}

枚举成员类型可以是多种类型

fn main() {
    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }
    // 结构体和枚举还有另一个相似点:就像可以使用 impl 来为结构体定义方法那样,
    // 也可以在枚举上定义方法。这是一个定义于我们 Message 枚举上的叫做 call 的方法
    impl Message {
        fn call(&self) {
            // 在这里定义方法体
        }
    }

    let m = Message::Write(String::from("hello"));
    m.call();
}

Option

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

Option<T>枚举,不需要显示引入作用域;使用成员也是,不需要Option::前缀,直接使用SomeNone

当有一个Some值时,我们知道存在一个值,而这个值保存在Some中。当有个None值时,在某种意义上,它跟空值具有相同的意义:并没有一个有效的值
那么,Option 为什么就比空值要好呢?
因为 Option 和 T(这里 T 可以是任何类型)是不同的类型,编译器不允许像一个肯定有效的值那样使用 Option。例如,这段代码不能编译,因为它尝试将 Option 与 i8 相加:

    let x: i8 = 5;
    let y: Option<i8> = Some(5);

    let sum = x + y; // 编译报错

Rust不知道如何将 Option 与 i8 相加,因为他们类型不同。
i8类型,编译器能确保它总是有一个有效值。
Option类型,需要担心可能没有值。

为了拥有一个可能为空的值,必须显式的将其放入对应类型的Option<T>中;当使用这个值时,必须明确的处理为空的情况。
只要一个值不是Option<T>类型,就可以安全的认定它的值不为空
这是Rust经过深思熟虑的设计决策,来限制空值的泛滥以增加Rust代码的安全性

match表达式就是一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码。

Match控制流结构
match极为强大的控制流运算符。可以将一个值与一系列的模式相比较
模式可以由字面值、变量、通配符和其他内容构成。

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

相对于if使用的条件表达式,必须返回一个布尔值,而这里它可以是任何类型。

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,
    }
}
#[derive(Debug)]
enum UsState {
    a,
    b,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {:?}!", state);
            25
        }
    }
}

fn main() {
    value_in_cents(Coin::Quarter(UsState::a));
}

匹配 Option

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);
}

匹配必须是可穷尽的

fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x { // 编译报错 pattern `None` not covered
            Some(i) => Some(i + 1),
        }
    }
}

没有明确处理 None 的情况

通配模式和 _ 占位符

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

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
}
    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _a => aaa(_a), // 传参
    }

    fn add_fancy_hat() { }
    fn remove_fancy_hat() {}
    fn aaa(num_spaces: u8) {
        println!("{}", num_spaces);
    }

if let 简洁控制流

fn main() {
    let config_max = Some(3u8);
    match config_max {
        Some(max) => println!("The maximum is configured to be {}", max),
        _ => (),
    }
}

简化:

    let config_max = Some(3u8);
    if let Some(max) = config_max {
        println!("The maximum is configured to be {}", max);
    }

if let 会失去 match 强制要求的可穷尽检查。
if let 是 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;
    }

包和 Create
Crate 是 Rust在编译时最小的代码单元。
rustc编译单个文件(rustc main.rs),编译器会将那个文件认作一个 crate。
crate可以包含模块,模块可以定义在其他文件,然后和crate一起编译。

crate有两种形式: 二进制项和库
二进制项可以编译为可执行程序,它们必须有一个main函数来定义当程序被执行的时候所需做的事情。目前我们创建的crate都是二进制项。
并没有main函数,它们也不会被编译为可执行程序,它们提供一些函数之类的东西,其他项目也可以使用。
比如 rand crate就提供生成随机数
大多数crate指的是库。

crate root 是一个源文件,Rust编译器以它为起点,构建crate的根模块。

包 提供一系列功能的一个或多个crate。
一个 包 会包含一个 Cargo.toml 文件,阐述如何去构建这些crate。
Cargo 就是一个包含构建你代码的二进制项的包。
Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。

从 crate 根节点开始: 当编译一个 crate, 编译器首先在 crate 根文件(通常,对于一个库 crate 而言是src/lib.rs,对于一个二进制 crate 而言是src/main.rs)中寻找需要被编译的代码。

声明模块: 在 crate 根文件中,你可以声明一个新模块;比如,你用mod garden声明了一个叫做garden的模块。编译器会在下列路径中寻找模块代码:

  • 内联,在大括号中,当mod garden后方不是一个分号而是一个大括号
  • 在文件 src/garden.rs
  • 在文件 src/garden/mod.rs

声明子模块: 在除了 crate 根节点以外的其他文件中,你可以定义子模块。比如,你可能在src/garden.rs中定义了mod vegetables;。编译器会在以父模块命名的目录中寻找子模块代码:

  • 内联,在大括号中,当mod vegetables后方不是一个分号而是一个大括号
  • 在文件 src/garden/vegetables.rs
  • 在文件 src/garden/vegetables/mod.rs

模块中的代码路径: 一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个 garden vegetables 模块下的Asparagus类型可以在crate::garden::vegetables::Asparagus被找到。

私有 vs 公用: 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用pub mod替代mod。为了使一个公用模块内部的成员公用,应当在声明前使用pub。

use 关键字: 在一个作用域内,use关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus的作用域,你可以通过 use crate::garden::vegetables::Asparagus;创建一个快捷方式,然后你就可以在作用域中只写Asparagus来使用该类型。

mod aa {
    pub mod hosting {
        pub fn waitlist() {}
    }
}

pub fn bb() {
    // 绝对路径
    crate::aa::hosting::waitlist();

    // 相对路径
    aa::hosting::waitlist();
}

使用super起始的相对路径

fn deliver_order() {}

mod aa {
    fn bb() {
        cook_order();
        super::deliver_order();
    }

    fn cook_order() {}
}

bb 函数在 aa 模块中,所以我们使用 super 进入 aa 父模块,在这里可以找到 deliver_order

// src/lib.rs
mod aa {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}

pub fn bb() {
    // 在夏天订购一个黑麦土司作为早餐
    let mut meal = aa::Breakfast::summer("Rye");
    // 改变主意更换想要面包的类型
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);

    // 如果取消下一行的注释代码不能编译;
    // 不允许查看或修改早餐附带的季节水果
    // meal.seasonal_fruit = String::from("blueberries");
}

因为 aa:Breakfast 结构体的 toast 字段是公共的,所以可以在 bb 中使用点号来随意读写 toast 字段。注意,不能再 bb 中使用 seasonal_fruit 字段,因为 seasonal_fruit 字段是私有的,所以该字段是不能查看和修改的。
因为 aa:Breakfast 具有私有字段,所以这个结构体需要提供一个公共的关联函数来构造 Breadkfast 的实例(这里我们命名为 summer )。如果没有这个关联函数,我们将无法在 bb 中创建 Breakfast 实例,因为不能在 bb 中设置私有字段 seasonal_fruit 的值。

如果枚举(enum)设为公有,则他的所有成员都将变为公有。

mod back_of_house {
    pub enum Appetizer {
        Soup,
        Salad,
    }
}

fn eat_at_restaurant() {
    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
}

如果枚举成员不是公有的,那么枚举会显得用处不大;给枚举的所有成员挨个添加 pub 是很令人恼火的,因此枚举成员默认就是公有的。

结构体通常使用时,不必将它们的字段公有化,因此结构体遵循常规,内容全部是私有的,除非使用 pub 关键字。

use关键字将路径引入作用域

mod aaa {
    pub mod hosting {
        pub fn waitlist() {}
    }
}

use crate::aaa::hosting;

pub fn eat_at_restaurant() {
    hosting::waitlist();
}

use 只能创建 use 所在的特定作用域内的短路径。

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

mod customer {
    pub fn eat_at_restaurant() {
        // eat_at_restaurant 和 use 不在同一个作用域
        hosting::add_to_waitlist(); // error   use of undeclared crate or module `hosting`
    }
}

相同类型——具有相同的Result

use std::fmt;
use std::io;

fn function1() -> fmt::Result {
    // --snip--
    Ok(())
}

fn function2() -> io::Result<()> {
    // --snip--
    Ok(())
}

使用 as

use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
    Ok(())
}

fn function2() -> IoResult<()> {
    // --snip--
    Ok(())
}

使用 pub use 重导出名称
不仅将一个名称导入了当前作用域,还允许别人把它导入他们自己的作用域

// restaurant
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

如果 use 前没加 pub ,外部代码想要使用 add_to_waitlist 方法,则需要 restaurant::front_of_house::hosting::add_to_waitlist() 来调用。现在使用 pub use 外部代码可以使用路径 restaurant::hosting::add_to_waitlist即可

嵌套路径来消除大量的 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};

通过 glob 运算符将所有的公有定义引入作用域
如果希望将一个路径下 所有 公有项引入作用域,可以指定路径后跟 *,glob 运算符:

use std::collections::*;

glob 运算符经常用于测试模块 tests 中,这时会将所有内容引入作用域;

模块拆分-多个文件

// src/lib.rs
mod aa;
pub use crate::aa::hosting;
pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

// src/aa.rs
pub mod hosting {
    pub fn add_to_waitlist() {}
}

mod aa; 告诉编译器,去找 aa 文件(加载这个文件),

继续拆分
文件名:src/aa.rs

pub mod hosting;

文件名:src/aa/hosting.rs

pub fn add_to_waitlist() {}

如果将 hosting.rs 放在 src 目录,编译器会认为 hosting 模块中的 hosting.rs 的代码声明于 crate 根,而不是声明为 front_of_house 的子模块。

mod 关键字声明了模块,Rust 会在与模块同名的文件中查找模块的代码。

@zhuzhh zhuzhh changed the title rust study - 03 rust study - 03(枚举 & 包/crate/pub/use/mod) Jan 29, 2023
@zhuzhh zhuzhh changed the title rust study - 03(枚举 & 包/crate/pub/use/mod) rust study - 03 (枚举 & 包/crate/pub/use/mod) Jan 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant