Rust 格式化输出
Rust 并没有采用 C 风格的格式化占位符的语法,可能是 C 的这种风格表现力不够,刚开始学习的 Rust 的我,格式化的时候只会使用 {}
,不过如果去了解其中的语法,发现并不是很难。
指定格式化参数
最简单的格式化:
println!("hello {}","world");
参数部分默认是从零开始的序列,但是占位符可以手动指定使用哪个参数。
println!("hello {0}","teemo");
或者使用命名参数,这个是格式化宏特有的语法:
println!("hello {name}", name = "teemo");
命名参数甚至可以不用显示传递,而是在当前作用域查询:
let argument = 2 + 2;
format!("{argument}"); // => "4"
fn make_string(a: u32, b: &str) -> String {
format!("{b} {a}")
}
格式化方式
定位占位符和占位符格式化方式采用:
分割,:
左边为参数的索引或是变量标识,右边是格式化的一些细节。总的语法可以这里查看,:
右边的可以放置的内容如下:
对齐方式还有填充内容
数字符号
+
/-
,对于数字类型可用打印的类型,打印对象的描述信息或是十六进制展示对象之类的,也就是使用哪个
trait
打印出来字符串的宽度
打印浮点数的精度
对齐方式、宽度
:
右边添加数字,表示宽度,指定宽度之后可以指定对齐方式,还有填充内容。对齐方式有:
<
左边对齐^
剧中对齐>
右边对齐
可以在这三个符号前面添加字符用来在长度不够的时候填充内容。简单的例子:
{url}
打印名称为url
的参数{:10}
打印宽度为 10{url:->50}
打印名称为url
的参数,宽度 50 ,右边对齐,使用-
补全。
sign / 0 / #
添加 +
会对数字类型添加正负符号。
println!("{:+}",-0.1);
指定宽度的时候,前面添加了0
,表示使用0
填充到指定宽度
println!("{:010}",6);
#
用来美化输出
#?
对Debug
特征自动添加缩减#x
/#X
/#b
/#o
自动在对应的数据前面添加0x
/0X
/0b
/0o
println!("{:+#010x}",6); // +0x0000006
浮点数进度
.
后面添加一个数字,表示打印多少位的精度,也可以添加一个 $
符号,表示动态设定打印浮点数的精度。
{price:.2}
打印price
参数,并且小数部分只展示两位{price:.2$}
打印price
参数,并且小数部分根据传入的第三个参数决定,这个参数必须是 usize 类型。
格式化方式
Rust 需要格式化数据的时候,默认情况下,对象需要实现 fmt::Display
。这是因为 Rust 格式化的时候默认是外部实现的,不同于 C 语言 printf
直接展示数据本身,对于自定义的数据,需要先实现对应的 trait
,然后才能实现格式化。
符号 | 接口 | 说明 |
---|---|---|
默认 | Display | 如果不指定类型,就会默认使用这个接口展示对象 |
? | Debug | 和Display 区别是可以通过#[derive(Debug)] 注解一个类型,实现这个接口。 |
b | Binary | 二进制格式 |
o | Octal | 八进制格式 |
x | LowerHex | 小写十六进制 |
X | UpperHex | 大写十六进制 |
p | Pointer | 打印指针,引用也是一种指针,这里可以直接打印。 |
e | LowerExp | 使用科学计数法打印数字,小写的 e |
E | UpperExp | 使用科学计数法打印数字,大写的 E |
相关操作宏
print!
/println!
/eprint!
/eprintln!
打印到标准输出、错误输出,ln
表示在结尾处添加一个\n
。format!
格式化字符串write!
/writeln!
格式化写入format_args!
实例
对于自定义结构体,无法直接打印出来:
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 0 };
println!("coords: {}", p);
}
上面的代码不能通过编译,会出现下面的错误,由于结构体 Point
没有实现 std::fmt::Display
接口
error[E0277]: `Point` doesn't implement `std::fmt::Display`
--> src\main.rs:8:28
|
8 | println!("coords: {}", p);
| ^ `Point` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `Point`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)
简单的方法就是就是实现缺失的实现:
struct Point {
x: i32,
y: i32,
}
impl Display for Point {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("x=> {} y=> {}", self.x, self.y))
}
}
或者直接添加 Debug
:
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 0 };
println!("coords: {:?}", p);
}
大部分时候,添加 Debug
已经足够详细的打印出需要的内容,上面会输出:
coords: Point { x: 0, y: 0 }
format_args!
其他几个打印宏比较简单,format_args!
这个宏就会让人比较迷糊。std::fmt::Arguments
是一个结构体,可以调用 as_str
或是 to_string
转换成字符串,其本身也已经实现Debug
/Display
接口,可以作为一个参数打印出来:
fn main() {
weather(format_args!("温度: {}° ~ {}°,降水概率: {}%,风速: {} 公里/时", 24, 29, 70, 11))
}
fn weather(args: Arguments) {
println!("今天的天气:{}", args.to_string())
// 今天的天气:温度: 24° ~ 29°,降水概率: 70%,风速: 11 公里/时
}
创建Arguments
实例的唯一方法是使用 format_args!
宏。格式化的时候,格式是固定的,参数也是固定的,参数的内容不固定,因此能在编译期间就解析好格式,还有参数的格式风格等信息,储存这些信息就是Arguments
,在运行时就不需要再去解析这些内容,减少不必要的开销。而且这个宏不能单独一行使用,如果赋值到临时变量,无法通过编译:
let args = format_args!("温度: {}°", 24);
say(args)
上面的代码会报错:
|
12 | let args = format_args!("温度: {}°", 24);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
13 | say(args)
| ---- borrow later used here
|
这个错误是因为返回内容引用了一个临时变量,然后这个临时变量生命周期没有超过返回引用,导致报错E0716
:
let arr = String::from("abc").as_str();
// ^^^^^^^^^^^^^^^^^^^ 这里创建了一个临时对象 String,返回 &str 指向 String,这一行结束之后 String 将会回收。
println!("{}", arr)
但是如果作为参数传递,则能避免上面的问题:
fn main() {
print(String::from("hello").as_str());
// ^^^^^^^^^^^^^^^^^^^^^ 这里的 String 对象会等到 print 结束之后才会进行回收
}
fn print(s: &str) {
println!("{}", s)
}