news 2026/8/20 12:57:52

std1.97.1——fmt模块总览

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
std1.97.1——fmt模块总览

目录

  • 0. 准备
    • 0.1 查看当前toolchain的std文档
    • 0.2 语法和词法结构
  • 1. std::fmt
  • 2. 格式化字符串
    • 2.1 位置参数
    • 2.2 命名参数
    • 2.3 格式化参数
      • 2.3.1 宽度
      • 2.3.2 填充/对齐
      • 2.3.3 标志
      • 2.3.4 精度
      • 2.3.5 本地化
      • 2.3.6 转义
  • 3. 语法
  • 4. Trait
    • 4.1 实现Display trait
    • 4.2 Display与Debug的区别
  • 5. 宏
    • 5.1 write!宏
    • 5.2 print!宏
    • 5.3 eprint!
    • 5.4 format_args!
  • 参考

0. 准备

0.1 查看当前toolchain的std文档

命令:rustup doc --std

0.2 语法和词法结构

1. std::fmt

地位:用于格式化与打印字符串的工具集。
本模块包含了对format!语法扩展的运行时支持。该宏由编译器实现,会生成对本模块的调用,以便在运行时将参数格式化为字符串。

2. 格式化字符串

格式化字符串语法,适用于这些宏:format!、write!、writeln!、print!、println!、eprint!、eprintln!、format_args!。
format!宏旨在让熟悉C语言的printf和fprintf函数或Python的str.format函数的开发者感到易于上手。
format!宏返回一个String,下面仅是为了将format!构建的String输出到控制台才调用println!宏:

fnmain(){println!("{}",format!("Hello"));println!("{}",format!("Hello, {}!","world"));println!("{}",format!("The number is {}",1));println!("{}",format!("{:?}",(3,4)));println!("{}",format!("{value}",value=4));letpeople="Rustaceans";println!("{}",format!("Hello {people}!"));println!("{}",format!("{} {}",1,2));println!("{}",format!("{:04}",42));println!("{}",format!("{:#?}",(100,200)));}


format!宏的第一个参数是格式化字符串,它是字符串字面量,不能是传入的变量(以便编译器进行有效性检查)。随后编译器会解析格式字符串,并判断所提供的参数列表是否符合传递给该格式字符串。
若要将单个值转换为字符串,请使用to_string方法。要使用to_string,该值必须实现当前模块的Display trait。

2.1 位置参数

每个格式化占位符都可以指明它引用的是参数列表中的第几个参数,如果省略,则认为它引用参数列表中的下一个参数。例如,格式化字符串{} {} {}会使用参数列表中的三个参数,并按照给出的顺序格式化它们。而格式化字符串{2} {1} {0}则会按相反顺序格式化它们。
一旦开始混用显式编号和隐式顺序这两种位置指定方式,事情就完全不一样了:可以下一个值这种指定方式看作一个在值列表上移动的迭代器。每次遇到一个{},迭代器在值列表上向后移动一步(索引增1)。具体例子如下:

fnmain(){println!("{}",format!("{1} {} {0} {}",1,2));}


对上面这个例子的处理:
1. 遇到{1}时,直接取参数列表索引为1处的值2。
2. 遇到{}时,直接获取内部隐式迭代器当前索引位置0处的值1,向后移动一步,内部隐式迭代器索引变为1。
3. 遇到{0}时,直接取参数列表索引为0处的值1。
4. 遇到{}时,直接获取内部隐式迭代器当前索引位置1处的值2,向后移动一步,内部隐式器此时已指向参数列表末尾,之后无法再获取值(若无更多格式占位符则无问题)。
格式化字符串必须使用参数列表中的所有参数,否则编译会报错。可以在格式化字符串中多次引用参数列表中的同一个参数。

2.2 命名参数

Rust本身并不像Python一样,函数中拥有关键字参数。format!宏作为语法扩展,可以利用命名参数。参数列表末尾可以给出命名参数对应的值,以identifier = expression的形式。
以下是format!宏使用命名参数的例子:

fnmain(){println!("{}",format!("{argument}",argument="test"));println!("{}",format!("{name} {}",1,name=2));println!("{}",format!("{a} {c} {b}",a="a",b='b',c=3));}


如果命名参数没有出现在参数列表中,format!会引用当前作用域中同名的变量:

fnmake_string(a:u32,b:&str)->String{format!("{b} {a}")}fnmain(){letargument=2+2;println!("{}",format!("{argument}"));println!("{}",make_string(927,"label"));}


提供的命名参数若未被格式化字符串使用,会导致编译错误。

2.3 格式化参数

每个被格式化的参数可以通过若干格式化参数进行转换(对应format_spec格式说明)。这些格式化参数会影响被格式化内容最终呈现的字符串形式。
格式语法中的冒号:用于分隔输入数据的标识符与格式化参数,冒号本身不改变任何内容,仅用于引入后续的格式化参数。

fnmain(){leta=5;letb=&a;println!("{a:e} {b:p}");}

2.3.1 宽度

fnmain(){println!("Hello {:5}!","x");println!("Hello {:1$}!","x",5);println!("Hello {1:0$}!",5,"x");println!("Hello {:width$}!","x",width=5);letwidth=5;println!("Hello {:width$}!","x");}


这是一个用于指定格式化内容所占最小宽度的参数。如果值的字符串长度不足以填满这么多字符,则会使用下一节填充/对齐指定的填充字符来补足所需空间。
宽度也可以通过引用参数列表中的另一个参数来动态提供,只需在参数名后面加$后缀。使用{:N$}可以引用参数列表中第N个位置的参数,N为从0开始的整数,或使用{:name$}来引用参数列表中的命名参数。参数列表被宽度引用的参数类型必须是usize。
使用带$的语法引用参数列表中的参数不会影响下一个参数计数器,因此通常建议按位置引用参数列表中的参数,或者直接使用命名参数。

2.3.2 填充/对齐

fnmain(){println!("Hello {:<5}!","x");println!("Hello {:-<5}!","x");println!("Hello {:^5}!","x");println!("Hello {:>5}!","x");}


可选的填充字符和对齐方式通常与宽度参数配合使用。它们必须定义在宽度之前,紧跟在冒号:之后。这表示当被格式化的值宽度小于指定宽度时,会在其周围额外填充一些字符。
填充支持以下不同的对齐方式
[fill]<:参数在宽度列中左对齐。
[fill]^:参数在宽度列中居中对齐。
[fill]>:参数在宽度列中右对齐。
对于居中对齐来说,宽度N NN为奇数,左右填充字符的数量均为N − 1 2 \frac{N-1}{2}2N1;宽度N NN为偶数,左边填充字符数量为N 2 − 1 \frac{N}{2}-12N1,右边填充字符数量为N 2 \frac{N}{2}2N

fnmain(){println!("Hello {:-^5}!","x");println!("Hello {:-^6}!","x");}


非数值类型的默认填充字符为空格,默认对齐方式为左对齐。数值类型的默认填充字符同样为空格,默认对齐方式为右对齐。如果为数值类型指定了0标志,则隐式填充字符变为0。

fnmain(){println!("Hello {:5}!","x");println!("Hello {:5}!",1);}


某些类型可能并未实现对齐功能,尤其是Debug trait通常不支持对清。确保正确应用填充的一个好方法是:先格式化输入内容,然后对得到的字符串进行填充,以获得最终输出:

fnmain(){println!("Hello {:^15}!",format!("{:?}",Some("hi")));}

2.3.3 标志

fnmain(){println!("Hello {:+}!",5);println!("{:#x}!",27);println!("Hello {:05}!",5);println!("Hello {:05}!",-5);println!("{:#010x}!",27);}


这些标志用于改变格式化器的行为。
+:用于数值类型,表示始终打印正负号。有符号数值的正号和符号都会被打印,无符号数值打印正号。
#:此标志应使用“替代”形式的打印。替代形式包括:
#?:美化Debug的格式化打印(添加换行和缩进)。
#x:在参数前加0x。
#X:在参数前加0x。
#b:在参数前加0b。
#o:在参数前加0o。

fnmain(){println!("{:#?}",(100,200));println!("{:#x}",255);println!("{:#X}",255);println!("{:#b}",10);println!("{:#o}",64);}


0:用于指示数值格式化时,应使用0字符填充至width,且填充需要感知符号。格式{:08}对于整数1会产生00000001,对于整数-1会产生-0000001。填充零始终放在符号(如果有)之后、数字(宽度的数字)之前。与#标志一起使用的时:填充0在前缀之后、数字之前。前缀包含在总宽度中。此标志会覆盖前面的填充字符和对齐,使其不产生效果。

fnmain(){println!("{:+05}",5);println!("{:+05}",-2);println!("{:+06}",5.1);}

2.3.4 精度

对于非数字类型,这可以被视为最大宽度。如果字符串长于此宽度,则会被截断到这么多字符,并且在设置了填充、对齐和宽度参数时,截断后的值会以正确的填充、对齐和宽度输出。

fnmain(){println!("{:+>10.7}","Hello world from Rust");}


对于整数类型,精度被忽略。
对于浮点类型,精度指示应打印小数点后多少位数字。
有三种方式可以指定所需的精度:
1. 整数.N:N本身为精度。
2. .N/name$:N引用参数列表中对应位置的参数,name引用参数列表中的命名参数。
3. .*:
使用形如{:<spec>.*}的格式字符串,其中<spec>指的是其他参数,会消耗隐式迭代器当前及其后的两个输入:当前位置的输入作为usize精度,下一个输入才是要打印的值。
使用形如{<arg>:<spec>.*}的格式字符串,则<arg>显式指明要打印的值,精度则取自隐式迭代器当前位置的输入。

fnmain(){// 遇到{0},位置参数对应位置0处的"x"// 遇到{1:.5},1在冒号之前是位置参数,对应位置1处的0.01,.5对应精度5println!("Hello {0} is {1:.5}","x",0.01);// 遇到{1},位置参数对应位置1处的"x"// 遇到{2:.0$},2在冒号之前是位置参数,对应位置2处的0.01,.0$表示精度取位置0处的5println!("Hello {1} is {2:.0$}",5,"x",0.01);// 遇到{0},位置参数对应位置0处的"x"// 遇到{2:.1$},2在冒号之前是位置参数,对应位置2处的0.01,.1$表示精度取位置1处的5println!("Hello {0} is {2:.1$}","x",5,0.01);// 遇到{},隐式迭代器当前位置0,对应"x"// 遇到{:.*},符合{:<spec>.*},隐式迭代器当前位置1,精度对应5,// 下一个位置2对应的0.01是打印的值println!("Hello {} is {:.*}","x",5,0.01);// 遇到{1},位置参数对应位置1处的"x"// 遇到{2:.*},符合{<arg>:<spec>.*},2在冒号前指明打印的值为位置2处的0.01// 隐式迭代器当前位置0,精度取位置0处的5println!("Hello {1} is {2:.*}",5,"x",0.01);// 遇到{},隐式迭代器当前位置0,对应"x"// 遇到{2:.*},符合{<arg>:<spec>.*},2在冒号前指明打印的值为位置2处的0.01,// 隐式迭代器当前位置1,精度取位置1处的5println!("Hello {} is {2:.*}","x",5,0.01);// 遇到{},隐式迭代器当前位置0,对应"x"// 遇到{number:.prec$},number在冒号前是命名参数对应0.01,.prec$表示精度取命名参数prec的值5println!("Hello {} is {number:.prec$}","x",prec=5,number=0.01);}

fnmain(){// 遇到{}, 隐式迭代器当前位置0,对应"Hello"// 遇到{name:.*},匹配{<arg>:<spec>.*},name在冒号之前是命名参数对应1234.56// 隐式迭代器当前位置1,精度取位置1处的3println!("{}, `{name:.*}` has 3 fractional digits","Hello",3,name=1234.56);// 遇到{},隐式迭代器当前位置0,对应"Hello"// 遇到{name:.*},匹配{<arg>:<spec>.*},name在冒号之前是命名参数对应"1234.56"// 隐式迭代器当前位置1,精度取位置1处的3// 精度对应打印值的类型是非数值类型,产生截断,截断后打印的值是"123"println!("{}, `{name:.*}` has 3 characters","Hello",3,name="1234.56");// 遇到{},隐式迭代器当前位置0,对应"Hello"// 遇到{name:>8.*},匹配{<arg>:<spec>.*},name在冒号之前是命名参数对应"1234.56"// >8指明右对齐,宽度为8// 隐式迭代器当前位置1,精度取位置1处的3// 精度对应打印值的类型是非数值类型,产生截断,截断后打印的值是"123"// 最终{name:>8.*}为 123println!("{}, `{name:>8.*}` has 3 right-aligned characters","Hello",3,name="1234.56");}


在截断浮点数时,Rust使用四舍六入五成双,这是IEEE 754中的默认舍入模式。
四舍六入五成双:
1. 末位<5:舍去。
2. 末位>5:进一。
3. 末尾=5:看前一位,奇数进一,偶数舍去。

fnmain(){println!("{0:.1$e}",12345,3);println!("{0:.1$e}",12355,3);}

2.3.5 本地化

Rust标准库提供的格式化函数没有任何区域概念,无论用户配置如何,在所有系统上都会产生相同的结果。

2.3.6 转义

字面字符{和}可以通过在前面加上相同的字符来包含在字符串中。

fnmain(){println!("Hello {{}}");println!("{{ Hello");}

3. 语法

格式化语法的文法:

format_string := text [ maybe_format text ] * maybe_format := '{' '{' | '}' '}' | format format := '{' [ argument ] [ ':' format_spec ] [ ws ] * '}' argument := integer | identifier format_spec := [[fill]align][sign]['#']['0'][width]['.'precision][type] fill := character align := '<' | '^' | '>' sign := '+' | '-' width := count precision := count | '*' type := '?' | 'x?' | 'X?' | 'o' | 'x' | 'X' | 'p' | 'b' | 'e' | 'E' count := parameter | integer parameter := argument '$'

text蹦年包含任何{或}。
ws是Rust定义的空白字符。
integer是一个十进制整数,可包含前导零且必须能放入usize类型。
identifier是Rust定义的IDENTIFIER_OR_KEYWORD,不能是单个下划线。

fnmain(){// {{ -> {// {name:-<0$.pre$},name在冒号前是命名参数对应"test",// -是填充字符,<是左对齐,0$是宽度对应位置0处的值10// .pre$是精度,pre是精度引用的命名参数,对应值3// name不是数值类型,会因精度产生截断,截断后的值为tes// }} -> }println!("{{ {name:-<0$.pre$} }}",10,name="test",pre=3);}

4. Trait

格式占位符中的每个type对应一个特定的格式化Trait。type到trait的映射如下:
未指定 => Display
? => Debug
x? => Debug(仅对整数触发,十六进制,小写)
X? => Debug(仅对整数触发,十六进制,大写)
o => Octal
x => LowerHex
X => UpperHex
p => Pointer
b => binary
e => LowerExp
E => UpperExp
这意味着,任何实现了fmt::Binary trait的类型都可以用{:b}格式化。标准库为许多原始类型实现了这些trait。当未指定type时,使用的trait是Display。
为自己的类型实现格式化trait时,你需要实现以下签名的方法:

fnfmt(&self,f:&mutfmt::Formatter<'_>)->fmt::Result(

你的类型将按引用作为self传入,然后函数应向实现了fmt::Write的Formatter f输出内容。每个格式化trait的实现都有责任正确遵守所请求的格式化参数。这些参数的值可以通过Formatter结构体的方法访问。为了帮助完成此工作,Fromatter结构体还提供了一些辅助方法。
此外,此函数的返回值是fmt::Result,它是Result<(),std::fmt::Error>的别名。格式化本身不会失败,类型不匹配、精度写错在编译时就解决了。返回Result的唯一原因是最终要向底层输出流写入,而写入可能失败,fmt::Error就是用来传递这个失败的。所以你的实现只需要做一件事:调用Formatter的方法时它返回Err就原样转发,其余情况返回Ok,不要自行凭空返回Err。

4.1 实现Display trait

usestd::fmt;#[derive(Debug)]structVector2D{x:isize,y:isize,}implfmt::DisplayforVector2D{fnfmt(&self,f:&mutfmt::Formatter<'_>)->fmt::Result{// `f` 实现了 `Write` trait,这正是 write! 宏所期望的。// 请注意,此格式化忽略了格式字符串提供的各种标志。write!(f,"({}, {})",self.x,self.y)}}// 不同的 trait 允许类型的不同输出形式。// 此格式的含义是打印向量的模。implfmt::BinaryforVector2D{fnfmt(&self,f:&mutfmt::Formatter<'_>)->fmt::Result{letmagnitude=(self.x*self.x+self.y*self.y)asf64;letmagnitude=magnitude.sqrt();// 使用 Formatter 对象上的辅助方法 `pad_integral`// 来遵守格式化标志。详见方法文档,// `pad` 函数可用于填充字符串。letdecimals=f.precision().unwrap_or(3);letstring=format!("{magnitude:.decimals$}");f.pad_integral(true,"",&string)}}fnmain(){letmyvector=Vector2D{x:3,y:4};println!("{myvector}");// => "(3, 4)"println!("{myvector:?}");// => "Vector2D {x: 3, y:4}"println!("{myvector:10.3b}");// => " 5.000"}

4.2 Display与Debug的区别

Display实现断言该类型始终可以忠实地表示为UTF-8字符串。并非期望所有类型都实现Display trait。
Debug实现应为所有公开类型实现。输出通常应尽可能忠实地表示内部状态。Debug trait的目的是方便调试Rust代码。在大多数情况下,使用#[derive(Debug)]就足够了,也是推荐的做法。

5. 宏

5.1 write!宏

write!和writeln!是两个用于将格式化字符串输出到指定流的宏。用于避免格式字符串的中间分配,直接写入输出。在底层,此函数实际调用的是std::io::Write和std::fmt::Write trait上定义的write_fmt函数。

usestd::fmt::Write;fnmain(){letmutw=String::new();write!(&mutw,"Hello {}!","world").unwrap();println!("{:?}",w);}

5.2 print!宏

此宏和println!将输出发送到stdout。与write!宏类似,这些宏的目标是在打印输出时避免中间分配。

fnmain(){print!("Hello {}!","world");println!("I have a newline {}","character at the end");}

5.3 eprint!

eprint!和eprintln!宏分别与print!和println!相同,区别在于它们将输出发送到stderr。

5.4 format_args!

format_args!是一个奇特的宏,用于安全地传递一个描述格式字符串的不透明对象。此对象不需要任何堆分配即可创建,且引用栈上的信息。在底层,所有宏都是基于它实现的。

usestd::fmt;usestd::io::{self,Write};fnmy_fmt_fn(args:fmt::Arguments<'_>){write!(&mutio::stdout(),"{args}").unwrap();}fnmain(){letmutsome_writer=io::stdout();write!(&mutsome_writer,"{}",format_args!("print with a {}","macro")).unwrap();my_fmt_fn(format_args!(", or a {} too","function"));}


format_args!宏的结果是fmt::Arguments类型的值。然后可以将此结构体传递给本模块的write和format函数以处理格式字符串。此宏的目标是在处理格式化字符串时进一步避免中间分配。

参考

1、Rust std::fmt

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/20 12:57:30

ARM开发入门指南:从架构认知到实战环境搭建

1. 为什么说“了解完这些再学ARM也不迟”&#xff1f; 每次看到有朋友兴致勃勃地打开一本《ARM体系结构权威指南》&#xff0c;或者准备一头扎进某个嵌入式开发板的教程时&#xff0c;我总想先拉住他聊上几句。不是要泼冷水&#xff0c;而是因为我自己在ARM这条路上踩过的坑&am…

作者头像 李华
网站建设 2026/8/20 12:54:32

python的运筹学工业场景模拟第六十八篇:读取历史故障报修记录,统计故障到达速率,维修耗时,输出排队论M/M/S模型输入参数。

维修“算账师”&#xff1a;用Python从故障记录里算清 M/M/S 排队论输入参数 “某汽车焊装车间有 4 名维修工&#xff0c;每天处理 20~30 起设备报修。班长总觉得‘人不够’&#xff0c;申请再加 2 人&#xff0c;一年人力成本 多 18 万。后来我用 Python 读了 3 个月的历史报修…

作者头像 李华
网站建设 2026/8/20 12:53:14

最新DeepSeek Harness的部署安装

最新DeepSeek Harness的部署安装一、DeepSeek Harness的计算机最低配置要求DeepSeek Harness能正常在电脑中运行&#xff0c;需要计算机具有以下最低配置要求。‌CPU‌&#xff1a;4核及以上x86/ARM架构处理器&#xff0c;支持AVX2指令集&#xff1b;‌内存‌&#xff1a;8GB可…

作者头像 李华