27. 高级特性(下)

目录

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

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

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

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

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

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>
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关键字引入类型别名,则可以修改为
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>
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有别名声明;
type Result<T> = std::result::Result<T, std::io::Error>;
  • 由于位于std::io,可用的完全限定的别名是std::io::Result<T>,即Result<T, E> 中 E 放入了 std::io::Error
  • 最后的效果如下
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
  • 主要用于在函数从不返回的时候充当返回值;
  • 从不返回的函数被称为发散函数
fn bar() -> ! {}

用途

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

never type 的另一个用途是 panic!

三、高级函数和闭包

3.1 函数指针

  • 可以向函数传递闭包,也可以向函数传递常规函数;
  • 函数的类型是fn,它被称为函数指针
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 而不接受闭包;
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 的参数来代替闭包;
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
    .iter()
    .map(ToString::to_string)
    .collect();
  • 另一个实用的模式暴露了元组结构体和元组结构体枚举成员的实现细节;
    • 这些项使用 () 作为初始化语法(看起来就像函数调用);
    • 同时它们确实被实现为返回由参数构造的实例的函数;
    • 它们也被称为实现了闭包 trait 的函数指针,并可以采用类似如下的方式调用;
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 返回闭包

  • 如下的代码不能通过编译
fn returns_closure() -> Fn(i32) -> i32 {
    |x| x + 1
}
  • 使用 trait 对象解决
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];调用下面的宏 (简化);
#[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代码作为输入,然后产生另一些代码作为输出;
  • 还有一种类型的过程宏:
    • 自定义派生;
    • 属性宏;
    • 函数宏;
  • 创建过程宏时
    • 宏定义必须单独放在它们自己的包中,并使用特殊的包类型;
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;
cargo new hello_macro --lib
cargo new hello_macro_derive --lib
cargo new pancakes
  1. hello_macro_derive\Cargo.toml文件的内容如下
[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文件内容如下
[workspace]

members = ["hello_macro", "hello_macro_derive", "pancakes"]
  1. pancakes/Cargo.toml文件内容如下
[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内容如下
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中的代码为
pub trait HelloMacro{
    fn hello_macro();
}
  1. pancakes/src/main.rs的代码为
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;

#[derive(HelloMacro)]
struct Pancakes;

fn main() {
    Pancakes::hello_macro();
}
  • 运行的结果如下

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/747611.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024 最新推广服务 API 推荐,助力业务腾飞

在数字化营销的浪潮中&#xff0c;API 服务正以其强大的功能和高效的特性&#xff0c;成为企业和开发者们实现精准推广、优化营销效果的得力助手。2024 年的今天&#xff0c;各种创新的 API 服务层出不穷&#xff0c;为广告投放、数据洞察等领域带来了前所未有的机遇。在接下来…

echarts隔行背景色

看了下使用说明&#xff0c;试了半天终于搞对了 参考文档&#xff1a;Documentation - Apache ECharts option {xAxis: {type: category,data: [Mon, Tue, Wed, Thu, Fri, Sat, Sun]},yAxis: {type: value},series: [{data: [120, 200, 150, 80, 70, 110, 130],type: bar,mar…

视频共享融合赋能平台LntonCVS视频监控业务平台建设安全煤矿矿井应用方案

随着我国经济的飞速增长&#xff0c;煤炭作为主要的能源之一&#xff0c;在我国的能源结构中扮演着至关重要的角色。然而&#xff0c;煤矿事故的频繁发生&#xff0c;不仅造成了巨大的人员伤亡和财产损失&#xff0c;也对社会产生了深远的负面影响。因此&#xff0c;实现煤矿的…

多家国产大模型提供OpenAI API服务替代方案,谷歌将推出明星网红AI聊天机器人

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 1、OpenAI终止对中国提供服务 6月25日凌晨&#xff0c;多个用户收到OpenAI的推送邮件&#xff0c;信中称&#xff0c;自今年7月9日起&#xff0c;将开始阻止来自非支持国家和地区的API&#xff08;应…

华为od-C卷200分题目3 - 两个字符串间的最短路径问题

华为od-C卷200分题目3 - 两个字符串间的最短路径问题 题目描述 给定两个字符串&#xff0c;分别为字符串A与字符串B。 例如A字符串为ABCABBA&#xff0c;B字符串为CBABAC可以得到下图m*n的二维数组&#xff0c;定义原点为(0, 0)&#xff0c;终点为(m, n)&#xff0c;水平与垂…

python自动化系列:自动将工作簿下的所有工作表合并到新工作表

作品介绍 作品名称&#xff1a;自动将工作簿下的所有工作表合并到新工作表 开发环境&#xff1a;PyCharm 2023.3.4 python3.7 用到的库&#xff1a;os、xlwings 作品简介&#xff1a;该实例使用xlwings库来操作Excel文件&#xff0c;其主要功能是将一个工作簿中所有工作表…

玩机进阶教程----MTK芯片使用Maui META修复基带 改写参数详细教程步骤解析

目前mtk芯片与高通芯片在主流机型 上使用比较普遍。但有时候版本更新或者误檫除分区等等原因会导致手机基带和串码丢失的故障。mtk芯片区别与高通。在早期mtk芯片中可以使用工具SN_Writer_Tool读写参数。但一些新版本机型兼容性不太好。今天使用另外一款工具来演示mtk芯片改写参…

打破数据分析壁垒:SPSS复习必备(十)

Means过程 统计学上的定义和计算公式 定义&#xff1a;Means过程是SPSS计算各种基本描述统计量的过程&#xff0c;其实就是按照用户指定条件&#xff0c;对样本进行分组计算均数和标准差&#xff0c;如按性别计算各组的均数和标准差。 用户可以指定一个或多个变量作为分组变…

上古世纪台服注册账号+下载客户端全方位图文教程

又一款新的MMRPG游戏即将上线啦&#xff0c;游戏名称叫做《上古世纪》游戏采用传统MMO类型游戏的玩法&#xff0c;但是开发商采用了先进的游戏引擎&#xff0c;让玩家们可以享受到极致的视觉体验。同时游戏的背景是建立在大陆分崩离析的基础上。各个部落因为领地的原因纷纷开战…

【大数据】—量化交易实战案例双均线策略(移动平均线)

声明&#xff1a;股市有风险&#xff0c;投资需谨慎&#xff01;本人没有系统学过金融知识&#xff0c;对股票有敬畏之心没有踏入其大门&#xff0c;今天用另外一种方法模拟炒股&#xff0c;后面的模拟的实战全部用同样的数据&#xff0c;最后比较哪种方法赚的钱多。 量化交易…

解决问题:浏览器中使用必应时提示“cn.bing.com将您的重定向的次数过多“

目录 一、问题分析二、关闭代理三、更新配置文件 一、问题分析 专业问题分析见其它博主的博文&#xff1a;重定向次数过多。 看了其它博文有一定启发&#xff0c;我自己尝试后发现两种解决办法。 二、关闭代理 我自己用的梯子是Clash&#xff0c;参考其他博主的分析&#x…

亚马逊运营专词(二)

1. A页面&#xff1a;亚马逊A页面即图文版商品详情页面&#xff0c;可以通过A页面使用不同的方式来描述商品特征&#xff0c;例如在页面中添加品牌故事、产品图片、产品文字介绍等&#xff0c;进一步完善页面。但目前A页面只对在亚马逊上注册了品牌的商家开放。 2. 跟卖&#x…

【PWN · TcachebinAttack | UAF】[2024CISCN · 华中赛区] note

一道简单的tcache劫持 一、题目 二、思路 存在UAF&#xff0c;libc版本2.31&#xff0c;经典菜单题 1.通过unsorted-bin-attack来leak-libc 2.通过uaf打tcache-bin-attack劫持__free_hook实现getshell 三、EXP from pwn import * context(archamd64,log_leveldebug)ioproce…

阿里提出MS-Diffusion:一键合成你喜爱的所有图像元素,个性化生成新思路!

文本到图像生成模型的最新进展极大地增强了从文本提示生成照片级逼真图像的能力&#xff0c;从而增加了人们对个性化文本到图像应用的兴趣&#xff0c;尤其是在多主题场景中。然而&#xff0c;这些进步受到两个主要挑战的阻碍&#xff1a; 需要根据文本描述准确维护每个参考主题…

ElasticSearch8.X查询DSL语法案例进阶实战

什么是Query DSL Query DSL主要由两部分组成&#xff1a;查询和过滤。 查询部分&#xff1a;用于指定搜索条件和匹配规则。例如&#xff0c;可以使用match查询进行全文检索&#xff0c;term查询进行精确匹配&#xff0c;range查询进行范围匹配等。过滤部分&#xff1a;用于对查…

怎么使用python进行整除取余求幂

怎么使用python进行整除取余求幂&#xff1f; 整除法是//&#xff0c;称为地板除&#xff0c;两个整数的除法仍然是整数。 10//33 3 求模运算是%&#xff0c;相当于mod&#xff0c;也就是计算除法的余数。 5%2 1 求幂运算使用两个连续的*&#xff0c;幂运算符比取反的优先级高…

一码多址与同义词解决方案

随着地址库中的数据不断的丰富&#xff0c;地址库中一码多址和同义词的数据也会越来越多&#xff0c;一码多址和同义词在统一地址管理平台中的概念并不相同。 一码多址指的是多个地址编码相同&#xff0c;例如通过民政地址找到编码&#xff0c;再通过编码找到房产地址描述。 本…

meizu M10 魅蓝 10 mblu10 root 解锁 安装LSPosed框架 紫光展锐改串 AT命令 一键新机 改机软件 硬改 改参数

meizu M10 魅蓝 10 mblu10 root 解锁 安装LSPosed框架 紫光展锐改串 AT命令 一键新机 改机软件 硬改 改参数 ro.system.build.version.release11 ro.system.build.version.release_or_codename11 ro.system.build.version.sdk30 ro.system.custom.versionAndroid_M01 ro.prod…

苹果Mac安装adobe软件报错“installer file may be damaged”解决方案

最近Mac电脑系统的有小伙伴在安装PS、AI、AE、PR等软件&#xff0c;出现了一个错误&#xff0c;让人头疼不已&#xff0c;苦苦找寻&#xff0c;也找不到完美的解决方法。让我们来一起看看吧&#xff01; 很多小伙伴都喜欢苹果电脑&#xff0c;但是在安装外来软件时&#xff0c;…

AI Agent实战:智能检索在Kingbase数据库管理中的优势应用

前言 在信息技术飞速发展的今天&#xff0c;数据库管理已成为IT专业人员日常工作中不可或缺的一部分。然而&#xff0c;面对复杂的SQL问题&#xff0c;传统的web搜索往往难以提供精准的答案&#xff0c;尤其是在针对特定数据库系统&#xff0c;如金仓数据库时&#xff0c;这种…