[Rust笔记] format! 宏使用心得-汇总

Rust编程:format! 宏详解与使用
本文是作者在使用Rust开发命令行工具时,对`format!`宏的深入学习和总结。文章详细介绍了`format!`宏的依赖关系、调用格式、格式化选项,包括顺序访问、索引、具名引用,以及宽度定制、对齐方式等,并通过例程展示了各种用法。此外,还讨论了自定义数据类型如何响应格式化指令,以及`Display`与`Debug`的区别。

假期,我尝试使用rust做一款命令行工具,来磨砺自己的rust技术熟练度。起初,面对各式各样的字符串格式化功能点,我傻乎乎地尝试自己造轮子。但,实在是遇到了太多技术难点(可难死我了)。后来,通过在论坛发贴请教,我才了解到【标准库 - format!语法扩展】已经90%地满足了我的需求。至于,剩余10%的功能,可通过实现不同的format trait来深度定制-达成。

于是,我修改了假期目标为:

  • 重新复习format!宏相关知识点。相比于之前(真是学过N遍了),我要提高对这块知识点的重视程度。在复习过程,做些归纳总结和条理清晰的笔记。

  • 基于【标准库 - format!语法扩展】与【实现format trait深度定制】的手段,来做一款rust命令行工具。而不是,自己重新造轮子。

下面即是我对第一个目标的达成成果(除了丰富的教条总结归纳,还有30个例程) --- 掌握rust,先做“教条侠”。第二个目标还在进行中...

依赖关系宏观架构图

318c1d218cdb13dd297d9048d0de3782.png

宏调用格式

format!("以{parameter}为占位符的·格式化字符串·字面量", arguments...)

  • {parameter}名曰Formatting Argument

  • argument名曰Value Argument

Formatting Argument格式

下文中的[...]结构表示

  • 一对[]之间的内容是可有可无的。

  • 而且[...]结构是可多层嵌套的。

{[argument][:format-spec]}

  • argument:【引用指令】表示如何找到Value Argument

  • format-spec:【格式化指令】表示如何格式化Value Argument为字符串。

Formatting argument -> argument以如下三种形式引用Value Argument

  • {} 名曰:Next (Value) Argument

    • 【顺序访问】不会受【随机访问】·跳跃式寻找Value Arguments的影响,因为前者维护了独立的【游标变量】来跟踪迭代位置。[例程1]

    • 顺序访问(Value) Argument,逐一遍历arguments列表中的每一项。但是,当format-spec内包含.*时,则会一次迭代遍历两项(下文有详细描述)。

    • Value Argument:无要求

  • {positional parameter} 名曰:【索引】(Value) Argument

    • positional parameterinteger值。

    • 以始于0的索引值,随机访问(Value) Argument

    • Value Argument:无要求

  • {naming parameter} 名曰:【具名】(Value) Argument [例程2]

    • 要么,出现于Value Arguments列表的末端;格式:<parameter name>=<value>

    • 要么,不出现在Value Arguments列表中。相反,编译器会

    • 在当前作用域内,

    • 寻找同名绑定变量,

    • 使用该绑定变量的值。(馁馁地逆天了)

    • naming parameteridentifier字符串。

    • 经由【参数名】,随机访问(Value) Argument

    • Value Argument:

Formatting argument -> format-spec以如下五种形式进一步格式化Value Argument

下文中的[...]结构表示

  • 一对[]之间的内容是可有可无的。

  • 而且[...]结构是可多层嵌套的。

  • 字符串 - 宽度定制

    mini-widthmax-length同时指定,并且mini-width大于max-length,那么 [例程8]

    std::fmt::Display::to_string()成员方法将Value Argument序列化为字符串。

  • b87f9d6aae0249321fb2a081545fa3e5.png

    • padding-char名曰:“填充”

    • align名曰:“对齐”

      若对齐未生效(比如,对Debug trait实例),那就

    • mini-width名曰:“最小宽度”

    • max-length名曰:最多显示字符数(从左向右截取)[例程6]

    • 缺省。即,【空格】填充。

    • < 左 (默认)

    • ^ 中

    • > 右

    • 某个Value argument

    • 或,当前作用域内,某个绑定变量的值

    • 要么,缺省。即,没有限制。

    • 要么,数字字面量;

    • 要么,$后缀【索引值】引用某个Value argument值 [例程4]

    • 要么,$后缀【具名变量】引用 [例程5]

    • 被格式化的值是后一个Value argument

    • 而被格式化值前面(或左侧)的Value argument代表最多显示字符数

    • 某个Value argument

    • 或,当前作用域内,某个绑定变量的值

    • 要么,缺省。即,显示全部字符。

    • 要么,数字字面量;

    • 要么,$后缀【索引值】引用某个Value argument

    • 要么,$后缀【具名变量】引用

    • 要么,.*表示当前Formatting argument同时关联了两个Value argument [例程7]

    • {[<integer | identifier>][:[[[<padding-char>]<align>]<mini-width>][.<max-length>]]} [例程3]

  1. 先使用max-length截断字符串

  2. 再使用mini-width对截断后的字符串有填充与对齐处理

  1. 先·普通格式化Value argument

  2. 再·对结果字符串做·对齐·格式化处理。

数字 - 宽度定制

就数字格式化而言,【正负号】与【进制符】都被计入总宽度内,并挤占了【占位符】的“坑位”。[例程16]

std::fmt::Display::to_string()成员方法将Value Argument序列化为字符串。

6d33168530861788b883dd6c3d04d84d.png

  • padding-char名曰:填充

  • align名曰:对齐

    若对齐未生效(比如,对Debug trait实例),那就

  • sign名曰:正负号

  • 0名曰:填充0数字

  • mini-width名曰:最小宽度

  • precision名曰:精度 [例程14]

  • padding-char指定整个数字(含正负号)前后的填充符。

  • padding-char填充符可以是任意字符。

  • padding-char的填充优先级低于0 [例程10]

  • 缺省。即,【空格】填充。

  • sign后的0填充符作用不同,

  • < 左

  • ^ 中

  • > 右 (默认)

  • 要么,缺省。即,按需显示-

  • 要么,+。即,总是显示+/-

  • 0表示在【正负号】与【有效数字】之间以数字0加以填充`。

  • 填充符号仅能是数字0

  • 0填充优先级高于padding-char [例程11]

  • padding-char填充符作用不同,

  • 某个Value argument

  • 或,当前作用域内,某个绑定变量的值

  • 要么,缺省。即,没有限制。

  • 要么,数字字面量。

  • 要么,$后缀【索引值】引用某个Value argument值 [例程12]

  • 要么,$后缀【具名变量】引用 [例程13]

  • 被格式化的值是后一个Value argument

  • 而在被格式值前面(左侧)的Value argument代表精度

  • 某个Value argument

  • 或,当前作用域内,某个绑定变量的值

  • 要么,缺省。即,没有限制。

  • 要么,数字字面量;

  • 要么,$后缀【索引值】引用某个Value argument

  • 要么,$后缀【具名变量】引用

  • 要么,.*表示当前Formatting argument同时关联了两个Value argument [例程15]

  • {[<integer | identifier>][:[[[<padding-char>]<align>][<sign>][0]<mini-width>][.<precision>]]} [例程9]

  1. 先·普通格式化Value argument

  2. 再·对结果字符串做·对齐·格式化处理。

数字 - 进制转换 + 有进制符前缀 + 宽度定制

就数字格式化而言,【正负号】与【进制符】都被计入总宽度内,并挤占了【占位符】的“坑位”。[例程22]

任何实现了Format trait的【自定义-数据类型】的实例都能被format-spec指令序列化与格式化。

标准库已经为基本数据类型提供了Format trait的默认实现。

4ebf64609af05e00fe044d5d19a3bf5b.png

  • padding-char名曰:填充

  • align名曰:对齐

    若对齐未生效(比如,对Debug trait实例),那就

  • sign名曰:正负号

  • #名曰:进制换算指令。

  • 0名曰:填充0数字

  • mini-width名曰:最小宽度

  • numeration名曰:进制符

  • 1763b59c6feccadb09c8abf5b6cb9a25.png

  • padding-char指定整个数字(含·正负号·与·进制符前缀0x0o0b·)前后的填充符。

  • padding-char填充符可以是任意字符。

  • padding-char的填充优先级低于#0 [例程18]

  • 缺省。即,【空格】填充。

  • #0填充符的作用不同,

  • < 左

  • ^ 中

  • > 右 (默认)

  • 要么,缺省。按需显示-

  • 要么,+。即,总是显示正负号。

  • 与末尾处的numeration参数配套出现。

  • #0表示在【进制符前缀0x0o0b】与【有效数字】之间以数字0加以填充`。

  • 填充符号仅能是数字0

  • #0填充优先级高于padding-char [例程19]

  • padding-char填充符作用不同,

  • 某个Value argument

  • 或,当前作用域内,某个绑定变量的值

  • 要么,缺省。即,没有限制。

  • 要么,数字字面量。

  • 要么,$后缀【索引值】引用某个Value argument值 [例程20]

  • 要么,$后缀【具名变量】引用 [例程21]

  • {[<integer | identifier>][:[[<padding-char>]<align>][<sign>]#[[0]<mini-width>]<numeration>]} [例程17]

  1. 先·普通格式化Value argument

  2. 再·对结果字符串做·对齐·格式化处理。

数字 - 进制转换 + 无进制符前缀 + 宽度定制

bfe57fbaa0322dc1cbb5f9397589ca7b.png

就数字格式化而言,【正负号】与【进制符】都被计入总宽度内,并挤占了【占位符】的“坑位”。[例程27]

任何实现了Format trait的【自定义-数据类型】的实例都能被format-spec指令序列化与格式化。

标准库已经为基本数据类型提供了Format trait的默认实现。

  • padding-char名曰:填充

  • align名曰:对齐

    若对齐未生效(比如,对Debug trait实例),那就

  • sign名曰:正负号

  • 0名曰:填充0数字

  • mini-width名曰:最小宽度

  • numeration名曰:进制符

  • c766f6ea803654817ee8449399fa5e94.png

  • padding-char指定整个数字(含正负号)前后的填充符。

  • padding-char填充符可以是任意字符。

  • padding-char的填充优先级低于0 [例程23]

  • 缺省。即,【空格】填充。

  • sign0填充符作用不同,

  • < 左

  • ^ 中

  • > 右 (默认)

  • 要么,缺省。按需显示-

  • 要么,+。即,总是显示正负号

  • 0表示在【正负号】与【有效数字】之间以数字0加以填充。

  • 填充符号仅能是数字0

  • 0填充优先级高于padding-char [例程24]

  • padding-char填充符作用不同,

  • 某个Value argument

  • 或,当前作用域内,某个绑定变量的值

  • 要么,缺省。即,没有限制。

  • 要么,数字字面量。

  • 要么,$后缀【索引值】引用某个Value argument值 [例程25]

  • 要么,$后缀【具名变量】引用 [例程26]

  • {[<integer | identifier>][:[[[<padding-char>]<align>][<sign>][[0]<mini-width>]]<numeration>]}

  1. 先·普通格式化Value argument

  2. 再·对结果字符串做·对齐·格式化处理。

有缩进格式化

  • 适用于复合数据结构。比如,Vec<T>HashMap<K, V>

  • {[<integer | identifier>][:#?]} [例程28]

在·格式化字符串·字面量内,转义录入{}字面量

  • 转义{{{

  • 转义}}}

Value argument两种语法错误形式

  • Value argument未被任何Formatting Argument所引用

  • 【索引】(Value) ArgumentNext (Value) Argument出现于【具名】(Value) Argument之后。[例程29]

使format-spec格式化指令对自定义数据类型(的实例)起作用

技术手段就是给【自定义数据类型】实现各种Format trait,从std::fmt::Displaystd::fmt::Debugstd::fmt::Octal等等一个都别落下。[例程30]

但是,有两个点值得一聊:

  • Format trait默认实现已经帮助开发者完成了

    开发者仅需调用std::fmt::Formatter的成员方法(比如,std::fmt::Formatter::fill(&self))就可获取格式化指令的具体值。

    • 对【格式化字符串·字面量】的解析处理

    • 和,对format-spec指令值的提取工作

  • 虽然“抽象”成员方法fn fmt(...) -> std::fmt::Result的返回值类型是Result,但是fn fmt()不应该将format trait业务实现代码的“本地”错误伪装成std::fmt::Result返回。因为rust设计要求:

    • 字符串格式化自身是一个【无错】操作

    • std::fmt::Result仅被用来反映底层输出流遇到的硬件失败。

std::fmt::Displaystd::fmt::Debug的区别

就功能来说,这两个trait都差不多。它们之间的差别之处都集中在语义上:

  • std::fmt::Display表示一个类型实例能够由UTF-8字符串来描述。因为不是所有类型的实例都是可字符串描述的(只可意会,不可言传),所以不是所有的类型都需要实现该trait

  • std::fmt::Debug用于debugging目的,描述某个类型实例的内部数据状态。所以,理论上,所有的类型都应该实现该trait,以方便随时按需程序调试。

结束语

这次想和大家分享的内容就是这些。

---------------------

另附上苦瓜小仔的一份思维导图:

e9ef5ccaae560161b968cb76837801a4.png

清晰版请参阅:https://www.yuque.com/zhoujiping/programming/pygvaf?inner=sSp4s

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值