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();
}
  • 运行的结果如下

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python基础语法 003-3 数据类型元组

1 元组 1.1 元组含义 1.1.1 元组的表示 #元组的表示方法:() names ("xiaoyun", "xiaoming") print(names)--结果------- (xiaoyun, xiaoming) 1.1.2 空元组 #空元组 names () print(type(names)) print(len(names))----------------结果--------- &l…

安装vue开发者工具

浏览器控制台提示&#xff1a; 打开网址 GitHub - vuejs/devtools: ⚙️ Browser devtools extension for debugging Vue.js applications. 点击添加 上图地址&#xff1a;Installation | Vue Devtools 安装好了

群体优化算法---石墨烯优化算法介绍以及在期权定价上的应用(Black-Scholes模型来计算欧式期权的理论价格)

介绍 石墨烯算法是一种新兴的优化算法&#xff0c;灵感来自于石墨烯的结构和特性。石墨烯是一种由碳原子构成的二维蜂窝状晶格结构&#xff0c;具有优异的机械、电学和热学性能。石墨烯算法通过模拟石墨烯原子之间的相互作用和迁移&#xff0c;来求解复杂的优化问题 基本概念…

K8S -理解StatefulSet - 部署有状态应用

什么是 有状态服务和 无状态服务 有状态服务&#xff08;Stateful Service&#xff09;&#xff1a; 有状态服务是指在处理请求期间维护和跟踪用户状态或会话信息的服务。这意味着服务在多个请求之间保持状态&#xff0c;并且需要在请求之间共享和使用这些状态信息。通常&…

Websocket在Java中的实践——握手拦截器

在《Websocket在Java中的实践——最小可行案例》一文中&#xff0c;我们看到如何用最简单的方式实现Websocket通信。本文中&#xff0c;我们将介绍如何在握手前后进行干涉&#xff0c;以定制一些特殊需求。 在《Websocket在Java中的实践——最小可行案例》的基础上&#xff0c;…

PID原理及控制算法详解

文章目录 1. 概念 1.1 PID框图 1.2 具体示例&#xff1a;无人机高度控制 2. PID原理 3. 常用术语 4. 计算过程 4.1 比例控制&#xff08;Proportional&#xff09; 4.2 积分控制&#xff08;Integral&#xff09; 4.3 微分控制&#xff08;Derivative&#xff09; 5.…

windows@文件高级共享设置@网络发现功能@从资源管理器网络中访问远程桌面

文章目录 高级共享设置常用选项其他选项操作界面说明 网络类型检查和设置(专用网络和公用网络)&#x1f47a;Note 高级共享设置和防火墙&#x1f47a;命令行方式使用图形界面方式配置 网络发现网络发现功能的详细介绍网络发现的作用&#x1f47a;网络发现的工作原理启用和配置网…

【Python实战因果推断】2_因果效应异质性2

目录 CATE with Regression Evaluating CATE Predictions CATE with Regression 我想你可能已经预料到了&#xff1a;与应用因果推理中的大多数情况一样&#xff0c;答案往往从线性回归开始。但在走这条路之前&#xff0c;让我们把事情变得更具体一些。假设你在一家遍布全国的…

[A133]uboot启动流程

[A133]uboot启动流程 hongxi.zhu 2024-6-21 1. 第一阶段 lds描述 从u-boot.lds中能找到程序的汇编入口ENTRY(_start) brandy/brandy-2.0/u-boot-2018/u-boot.lds OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUT…

vant组件 顶部下拉刷新和页面底部下拉获取数据+顶部搜索框

1.html部分&#xff08;顶部tab切换无&#xff0c;只有主体list部分&#xff09; <div class"yd" ><!-- yd端 --><!-- 搜索框 --><van-searchv-model"ydsearchvalue"show-actionplaceholder"请输入搜索关键词"search"…

JavaEE之HTTP协议(1)_HTTP基础知识,HTTP 请求、响应格式,方法,状态码

一、HTTP协议 1.1 基本概念: HTTP全称超文本传输协议&#xff0c;是一种无状态的、应用层的协议&#xff0c;它基于请求/响应模型。客户端&#xff08;通常是Web浏览器&#xff09;通过发送HTTP请求到服务器来获取或发送信息&#xff0c;服务器则返回HTTP响应作为回应。HTTP协…

shell (三)shell脚本

SHELL脚本 编程语言的分类 解释型语言&#xff1a;shell&#xff0c;Python&#xff0c;需要解析器 编译型语言&#xff1a;C语言&#xff0c;C&#xff0c;需要编译器 shell脚本 操作系统的结构 shell&#xff08;贝壳&#xff09; 应用层 app&#xff0c;代码 应用层需要通…

1、线性回归模型

1、主要解决问题类型 1.1 预测分析(Prediction) 线性回归可以用来预测一个变量(通常称为因变量或响应变量)的值,基于一个或多个输入变量(自变量或预测变量)。例如,根据房屋的面积、位置等因素预测房价。 1.2 异常检测(Outlier Detection) 线性回归可以帮助识别数…

鸿蒙开发系统基础能力:【@ohos.systemTime (设置系统时间)】

设置系统时间 本模块用来设置、获取当前系统时间&#xff0c;设置、获取当前系统日期和设置、获取当前系统时区。 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import systemTime …

沙盒在数据防泄密领域意义

在信息化快速发展的今天&#xff0c;数据已成为企业最宝贵的资产之一。然而&#xff0c;数据泄密事件频发&#xff0c;给企业的安全和发展带来了巨大威胁。SDC沙盒防泄密系统&#xff0c;作为一种创新的数据防泄密解决方案&#xff0c;正逐渐在数据防泄密领域发挥着越来越重要的…

安装zabbix时报错Could not resolve host: mirrors.huaweicloud.com;Unknown error解决办法

目录 1、问题原因 2、解决办法 3、知识拓展 DNS的区别 DNS配置文件解析 域名解析过程 4、书籍推荐 当安装Zabbix server&#xff0c;Web前端&#xff0c;agent时出现&#xff1a; [rootsc-zabbix-server ~]# yum install zabbix-server-mysql zabbix-agent安装过程中会出…

Python3极简教程(一小时学完)上

开始 Python 之旅 本教程基于 Python for you and me 教程翻译制作&#xff0c;其中参考了 Python tutorial 和 _The Python Standard Library_&#xff0c;并对原教程的内容进行了改进与补充。 相关链接地址如下&#xff1a; _Python tutorial_&#xff1a;Python 入门指南…

数字孪生流域:定义、组成等

数字孪生流域&#xff1a;定义、组成等 1 数字孪生流域&#xff08;Digital Twin Basin/Watershed&#xff09;总则1.1 定义1.2 适用范围1.3 建设目标1.4 建设原则 2 数字孪生流域框架与组成2.1 数字孪生流域框架2.2 数字孪生流域组成2.2.1 数字孪生平台2.2.2 信息化基础设施 3…

JavaScript学习笔记(二)

12、数字 常规用法和java的用法相似&#xff0c;就不再做详细的记录, JavaScript 数字 以下只记录特殊用法&#xff1a; 12.1 数字字符串运算 在所有数字运算中&#xff0c;JavaScript 会尝试将字符串转换为数字&#xff1a; var x "100"; var y "10"…

第 5 章理解 ScrollView 并构建 Carousel UI

通过上一章的学习,我相信你现在应该明白如何使用堆栈构建复杂的 UI。当然,在你掌握 SwiftUI 之前,你还需要大量的练习。因此,在深入研究 ScrollView 以使视图可滚动之前,让我们先以一个挑战开始本章。你的任务是创建一个类似于图 1 所示的卡片视图。 …