27. 高级特性(下)

news2024/10/6 10:31:46

目录

  • 一、为了类型安全和抽象而使用 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.coloradmin.cn/o/1866532.html

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

相关文章

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

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

【八股系列】探索响应式布局的奥秘:关键技术与实战代码示例

&#x1f389; 博客主页&#xff1a;【剑九 六千里-CSDN博客】 &#x1f3a8; 上一篇文章&#xff1a;【为什么组件中的 data 必须是一个函数&#xff0c;然后 return 一个对象&#xff0c;而 new Vue 实例里&#xff0c;data 可以直接是一个对象&#xff1f;】 &#x1f3a0; …

【python】eval函数

1.eval函数的语法及用法 &#xff08;1&#xff09;语法&#xff1a;eval(expression) 参数说明&#xff1a; expression&#xff1a;必须为字符串表达式&#xff0c;可为算法&#xff0c;也可为input函数等。 说明&#xff1a;表达式必需是字符串&#xff0c;否则会报错&a…

(2024,稀疏高秩适配器(SHiRA),适配器快速切换和多适配器融合,稀疏掩码,稀疏高秩且正交的适配器,移动场景部署)

Sparse High Rank Adapters 公和众与号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. 介绍 2. 背景&#xff1a;LoRA 的边缘部署挑战 3. 提议的方法 3.1 稀疏高秩适配器&#xff0…

共享资源,共谋协同发展新机遇

树莓集团在资源共享与协同发展方面取得了显著成效。通过优化资源配置、构建产业服务平台、校企合作育人、推动产业转型升级以及主导产业园运营与建设等举措&#xff0c;树莓集团不仅促进了数字产业的快速发展&#xff0c;也为各数字产业园区和合作企业提供了更多的发展机会和合…

Compiled blocks

目录 一&#xff0c;编译块 Compiled Block 二&#xff0c;编译循环 三&#xff0c;调用编译块 Invoke 四&#xff0c;Tips and notes 一&#xff0c;编译块 Compiled Block 在几何体网络内&#xff0c;可将网络的一部分放入编译块&#xff08;compiled block&#xff09;内…

天润融通:AI赋能客户体验,推动企业收入和业绩增长

“客户体验已经成为全球企业差异化的关键。人工智能与数据分析等创新技术正在加速推动企业在客户体验计划中取得成功&#xff0c;以保持领先地位”。Customer Insights & Analysis 研究经理Craig Simpson说道。 客户体验 (CX&#xff0c;Customer Experience) 是客户在与企…

基于PHP+MySql的留言管理系统的设计与实现

功能概述 网页留言板管理系统&#xff0c;用户层面分为普通用户和管理员&#xff0c;并设权限&#xff08;即后台留言管理系统普通用户不能访问&#xff0c;别人的留言自己不可以修改删除&#xff0c;未登录不能使用留言功能&#xff09;&#xff0c;功能包括用户登录注册、留…

鸿蒙开发HarmonyOS NEXT(一)

最近总听见大家讨论鸿蒙&#xff0c;前端转型的好方向&#xff1f;先入门学习下 目前官方版本和文档持续更新中 一、开发环境 提示&#xff1a;要占用的空间比较多&#xff0c;建议安装在剩余空间多的盘 1、下载&#xff1a;官网最新工具 - 下载中心 - 华为开发者联盟 (huaw…

【Mac】王国保卫战:起源 for mac(塔防策略游戏)游戏介绍和安装教程

游戏介绍 《王国保卫战&#xff1a;起源》&#xff08;Kingdom: Origins&#xff09;是一款策略塔防游戏&#xff0c;其核心玩法融合了塔防、策略管理和资源管理元素。游戏的主要目标是在一个开放的像素化世界中建立和管理自己的王国&#xff0c;并抵御夜晚来袭的怪物入侵。 …

c语言学习记录(十)———函数

文章目录 前言一、函数的基本用法二、函数的参数传递1.基本方式2 数组在函数中的传参 前言 一个学习C语言的小白~ 有问题评论区或私信指出~ 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、函数的基本用法 函数是一个完成特定功能的代码模块&…

使用Dropout大幅优化PyTorch模型,实现图像识别

大家好&#xff0c;在机器学习模型中&#xff0c;如果模型的参数太多&#xff0c;而训练样本又太少&#xff0c;训练出来的模型很容易产生过拟合的现象。在训练神经网络时&#xff0c;过拟合具体表现在模型训练数据损失函数较小&#xff0c;预测准确率较高&#xff0c;但是在测…

gc.log中 CMS-concurrent-abortable-preclean

问题 在gc日志中看到 2024-06-26T16:16:07.5040800: 64690272.666: [CMS-concurrent-abortable-preclean-start]CMS: abort preclean due to time 2024-06-26T16:16:12.5530800: 64690277.716: [CMS-concurrent-abortable-preclean: 1.052/5.049 secs] [Times: user1.33 sys0…

点云处理实战 PCL求解点云表面曲率

目录 一、什么是曲率 二、曲率计算过程 三、pcl 求解点云局部曲率 四、思考?为何曲率计算会使用协方差矩阵? 五、推荐阅读 一、什么是曲率 曲率是几何学中用来描述曲线或曲面形状变化的一个量。它反映了曲线或曲面的弯曲程度。在不同的上下文中,曲率的定义和计算方式有…

C语言 指针——向函数传递字符串

目录 向函数传递字符串 计算实际字符个数 用字符数组编程实现字符串复制 用字符指针编程实现字符串复制 关于程序的效率的几点建议 向函数传递字符串 向函数传递字符串时  既可用 字符数组 作函数参数  也可用 字符指针 作函数参数 Simulating Call by reference &…

第二期书生·浦语大模型实战营优秀项目一览

书生浦语社区于 2023 年年底正式推出了书生浦语大模型实战营系列活动&#xff0c;至今已有两期五批次同学参加大模型学习、实战&#xff0c;线上课程累计学习超过 10 万人次。 实战营特设项目实践环节&#xff0c;提供 A100 算力支持&#xff0c;鼓励学员动手开发。第 2 期实战…

51单片机STC89C52RC——9.1 DS1302涓流充电计时芯片

目录 目的/效果 一&#xff0c;STC单片机模块 二&#xff0c;DS1302计时器 2.1 特性/板子位置 2.1.1 特性 2.1.2 板子上的位置 2.2 针脚定义 2.3 数据传输 2.3.1 读数据 2.3.2 写数据 2.4 BCD码 2.5 可编程涓流充电器 2.6 时钟动态设置 三&#xff0c;创建Keil项目…

算法导论 总结索引 | 第四部分 第十六章:贪心算法

1、求解最优化问题的算法 通常需要经过一系列的步骤&#xff0c;在每个步骤都面临多种选择。对于许多最优化问题&#xff0c;使用动态规划算法求最优解有些杀鸡用牛刀了&#xff0c;可以使用更简单、更高效的算法 贪心算法&#xff08;greedy algorithm&#xff09;就是这样的算…

云服务器部署LNMP Web环境教程合集(多版linux系统安装方法)

LNMP环境包括Linux、Nginx、MySQL和PHP&#xff0c;Nginx是一款小巧而高效的Web服务器软件&#xff0c;使用阿里云服务器搭建LNMP Web网站环境很简单&#xff0c;支持多种LNMP环境部署教程&#xff0c;可使用ROS模板部署、LNMP镜像以及基于不同Linux操作系统手动部署LNMP全流程…

华为仓颉编程语言正式发布!相比Java、Go、Swift优势在哪?附入门教程~

在2024年6月21日举行的华为开发者大会上&#xff0c;华为公司不仅对外宣布了其新一代鸿蒙操作系统HarmonyOS NEXT&#xff0c;还正式发布了他们自主研发的编程语言&#xff0c;名为仓颉。 我们一起看看仓颉编程语言的定位及其优势所在&#xff0c;以及它的入门教程&#x1f4aa…