27. 高级特性(下)

目录

  • [一、为了类型安全和抽象而使用 newtype 模式](#一、为了类型安全和抽象而使用 newtype 模式)
  • 二、使用类型别名创建类型同义词
    • [2.1 使用type关键赋予现有类型一个别名](#2.1 使用type关键赋予现有类型一个别名)
    • [2.2 减少重复](#2.2 减少重复)
    • [2.3 与Result<T, E>结合使用](#2.3 与Result<T, E>结合使用)
    • [2.4 从不返回的 never type](#2.4 从不返回的 never type)
  • 三、高级函数和闭包
    • [3.1 函数指针](#3.1 函数指针)
    • [3.2 返回闭包](#3.2 返回闭包)
  • 四、宏
    • [4.1 宏和函数的区别](#4.1 宏和函数的区别)
    • [4.2 macro_rules! 的声明宏](#4.2 macro_rules! 的声明宏)
    • [4.3 基于属性生成代码的过程宏](#4.3 基于属性生成代码的过程宏)
    • [4.4 编写自定义 derive 宏](#4.4 编写自定义 derive 宏)

一、为了类型安全和抽象而使用 newtype 模式

  • newtype模式的应用可以用于确保静态值不被混淆以及表示一个值的单元;
  • newtype模式的应用可以抽象掉一些类型的实现细节;
    • 例如封装类型可以暴露出与直接使用其内部私有类型时所不同的公有 API,以便限制其功能;
  • newtype模式也可以隐藏其内部的泛型类型;

二、使用类型别名创建类型同义词

2.1 使用type关键赋予现有类型一个别名

rust 复制代码
fn main() {
    type Kilometers = i32;

    let x: i32 = 5;
    let y: Kilometers = 5;

    println!("x + y = {}", x + y); 
}
  • 代码输出:x + y = 10

2.2 减少重复

  • 类型别名的主要用途是减少重复,例如类型Box<dyn Fn() + Send + 'static>;
rust 复制代码
let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));

fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {}

fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {}
  • 通过type关键字引入类型别名,则可以修改为
rust 复制代码
type Thunk = Box<dyn Fn() + Send + 'static>;

let f: Thunk = Box::new(|| println!("hi"));

fn takes_long_type(f: Thunk) {}

fn returns_long_type() -> Thunk {}

2.3 与Result<T, E>结合使用

  • 标准库中的std::io模块;
  • I/O 操作通常会返回一个Result<T, E>
  • 标准库中的std::io::Error结构体代表了所有可能的 I/O 错误;
  • std::io中大部分函数会返回Result<T, E>
rust 复制代码
use std::io::Error;
use std::fmt;

pub trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
    fn flush(&mut self) -> Result<(), Error>;

    fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
}
  • 上述代码出现了很多Result<..., Error>,因此,std::io有别名声明;
rust 复制代码
type Result<T> = std::result::Result<T, std::io::Error>;
  • 由于位于std::io,可用的完全限定的别名是std::io::Result<T>,即Result<T, E> 中 E 放入了 std::io::Error
  • 最后的效果如下
rust 复制代码
pub trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    fn flush(&mut self) -> Result<()>;

    fn write_all(&mut self, buf: &[u8]) -> Result<()>;
    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}

2.4 从不返回的 never type

  • Rust有一个! 的特殊类型,它被称为empty type,更倾向于称之为never type
  • 主要用于在函数从不返回的时候充当返回值;
  • 从不返回的函数被称为发散函数
rust 复制代码
fn bar() -> ! {}

用途

  • 有如下代码
rust 复制代码
let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};
  • 我们知道match分支必须返回相同的类型;
  • 如下代码必无法通过编译
rust 复制代码
let guess = match guess.trim().parse() {
    Ok(_) => 5,
    Err(_) => "hello",
}
  • 上述代码里的guess必须既是整型 也是字符串,而 Rust 要求guess 只能是一个类型;
  • 所以continue 返回的值是!

never type 的另一个用途是 panic!

三、高级函数和闭包

3.1 函数指针

  • 可以向函数传递闭包,也可以向函数传递常规函数;
  • 函数的类型是fn,它被称为函数指针
rust 复制代码
fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {}", answer);  //The answer is: 12
}
  • do_twice函数中的f被指定为一个接受一个i32 参数并返回 i32 的函数指针;
  • 就可以在do_twice函数体中调用该函数;
  • fn是一个类型而不是一个trait;
    • 直接指定 fn 作为参数;
    • 声明一个带有 Fn 作为 trait bound 的泛型参数;
  • 函数指针实现了所有三个闭包 trait:Fn、FnMut 和 FnOnce;
  • 总是可以在调用期望闭包的函数时传递函数指针作为参数;
  • 当与不存在闭包的外部代码交互时,可以只期望接受 fn 而不接受闭包;
rust 复制代码
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
    .iter()
    .map(|i| i.to_string())
    .collect();
  • 上述代码使用map函数将一个数字vector 转换为一个字符串 vector;
  • 也可以将函数作为 map 的参数来代替闭包;
rust 复制代码
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
    .iter()
    .map(ToString::to_string)
    .collect();
  • 另一个实用的模式暴露了元组结构体和元组结构体枚举成员的实现细节;
    • 这些项使用 () 作为初始化语法(看起来就像函数调用);
    • 同时它们确实被实现为返回由参数构造的实例的函数;
    • 它们也被称为实现了闭包 trait 的函数指针,并可以采用类似如下的方式调用;
rust 复制代码
enum Status {
    Value(u32),
    Stop,
}

let list_of_statuses: Vec<Status> =
    (0u32..20)
    .map(Status::Value)
    .collect();
  • 创建了Status::Value实例,它通过map用范围的每一个u32 值调用 Status::Value 的初始化函数;

3.2 返回闭包

  • 如下的代码不能通过编译
rust 复制代码
fn returns_closure() -> Fn(i32) -> i32 {
    |x| x + 1
}
  • 使用 trait 对象解决
rust 复制代码
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

四、宏

  • 宏(Macro)指的是 Rust 中一组相关特性的集合:
    • 使用macro_rules! 声明的(Declarative)宏,和三种过程(Procedural)宏:
      1. 自定义#[derive] 宏,用于结构体和枚举上指定通过derive属性添加的代码;
      2. 类似属性宏,可用于任意项的自定义属性;
      3. 类函数宏,看起来像函数调用,作用于作为参数传递的 token;

4.1 宏和函数的区别

  • 宏是一种为写其他代码而编写的代码,即所谓的元编程(metaprogramming)
  • 一个函数标签必须声明函数参数个数和类型,宏能够接受不同数量的参数;
  • 在一个文件里调用宏之前必须定义,或将其引入作用域,函数则可以在任何地方定义和调用;

4.2 macro_rules! 的声明宏

  • 最常用的宏形式是 声明宏(declarative macros),它允许我们编写一些类似 Rust match 表达式的代码 ;
  • 使用macro_rules!定义宏;
  • vec![1, 2, 3];调用下面的宏 (简化);
rust 复制代码
#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}
  • #[macro_export]标注说明,只要将定义了宏的crate引入作用域,宏就应当是可用的;
  • 没有该标注的宏不能被引入作用域;
  • 使用macro_rules!和宏名称开始宏定义,且所定义的宏并不带感叹号,名字后跟大括号表示宏定义体;
  • $x:expr指匹配任何的Rust表达式并命名为 $x ,后面的逗号表示传入的逗号分隔符,后面的*表示能匹配0个或多个;
  • 全部宏语法,参参阅:https://rustwiki.org/zh-CN/reference/macros.html

4.3 基于属性生成代码的过程宏

  • 过程宏(procedural macros),更像函数(一种过程类型);
  • 过程宏接收Rust代码作为输入,然后产生另一些代码作为输出;
  • 还有一种类型的过程宏:
    • 自定义派生;
    • 属性宏;
    • 函数宏;
  • 创建过程宏时
    • 宏定义必须单独放在它们自己的包中,并使用特殊的包类型;
rust 复制代码
use proc_macro;

#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
}
  • some_attribute是过程宏的占位符;
  • 定义过程宏的函数以一个TokenStream作为输入并产生一个TokenStream作为输出;
  • TokenStream类型由包含在 Rust 中的proc_macro crate定义,并表示令牌序列;

4.4 编写自定义 derive 宏

  • 创建hello_macro crate,定义一个拥有关联函数HelloMacro的 trait 和关联函数hello_macro
  • 提供一个能自动实现trait的过程宏;
  • 使用户在它们的类型上标注#[derive(HelloMacro)],进而得到hello_macro的默认实现;

实现

  1. 在一个全新目录下(称为工作空间)创建Cargo.toml文件,写上[workspace]就行了;
  2. 再相同的目录下输入下面两条指令,创建两个crate;
rust 复制代码
cargo new hello_macro --lib
cargo new hello_macro_derive --lib
cargo new pancakes
  1. hello_macro_derive\Cargo.toml文件的内容如下
rust 复制代码
[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
syn = "2.0.68"
quote = "1.0"
  1. Cargo.toml文件内容如下
rust 复制代码
[workspace]

members = ["hello_macro", "hello_macro_derive", "pancakes"]
  1. pancakes/Cargo.toml文件内容如下
rust 复制代码
[package]
name = "pancakes"
version = "0.1.0"
edition = "2021"

[dependencies]
hello_macro = {path = "../hello_macro"}
hello_macro_derive = {path = "../hello_macro_derive"}
  1. 将过程宏放到hello_macro_derive里,其lib.rs内容如下
rust 复制代码
extern crate proc_macro;

use crate::proc_macro::TokenStream;
use quote::quote;
use syn;

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // 将 Rust 代码解析为语法树以便进行操作
    let ast = syn::parse(input).unwrap();

    // 构建 trait 实现
    impl_hello_macro(&ast)
}

fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let gen = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello, Macro! My name is {}", stringify!(#name));
            }
        }
    };
    gen.into()
}
  • proc_macro包提供了编译器接口,从而可以读取可操作的Rust代码;
  • syn是把Rust代码从字符串转换为可供我们进一步操作的数据结构;
  • quote包能够将syn产生的数据结构重新转换为Rust代码;
  • 函数hello_macro_derive负责解析TokenStream ,函数内部的impl_hello_macro负责转换语法库;
  • 效果是:用户标注#[derive(HelloMacro)]后,hello_macro_derive 会被自动调用;
  • 详细的看相关的文档;
  1. hello_macro/src/lib.rs中的代码为
rust 复制代码
pub trait HelloMacro{
    fn hello_macro();
}
  1. pancakes/src/main.rs的代码为
rust 复制代码
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;

#[derive(HelloMacro)]
struct Pancakes;

fn main() {
    Pancakes::hello_macro();
}
  • 运行的结果如下
相关推荐
数据小小爬虫35 分钟前
Python爬虫获取AliExpress商品详情
开发语言·爬虫·python
小爬虫程序猿36 分钟前
利用Python爬虫速卖通按关键字搜索AliExpress商品
开发语言·爬虫·python
一朵好运莲43 分钟前
React引入Echart水球图
开发语言·javascript·ecmascript
Eiceblue1 小时前
使用Python获取PDF文本和图片的精确位置
开发语言·python·pdf
xianwu5431 小时前
反向代理模块。开发
linux·开发语言·网络·c++·git
xiaocaibao7771 小时前
Java语言的网络编程
开发语言·后端·golang
木向1 小时前
leetcode22:括号问题
开发语言·c++·leetcode
comli_cn1 小时前
使用清华源安装python包
开发语言·python
筑基.2 小时前
basic_ios及其衍生库(附 GCC libstdc++源代码)
开发语言·c++
雨颜纸伞(hzs)2 小时前
C语言介绍
c语言·开发语言·软件工程