Rust格式化输出

 本文基于 Rust 1.44,未来 Rust 行为可能会发生改变,请以最新版 Rust 的结果为准。

 在 Rust 中,模块std::fmt用来格式化输出字符串,你可以使用宏format!对字符串进行格式化。print!println!format!的功能非常接近,唯一的不同是print!会将字符串打印到控制台,println!会在打印的字符串之后加上一个换行符。为了方便演示,本文的例子也会使用print!println!

引子

 下面是一些格式化输出的例子:

1
2
3
4
5
6
7
print!("Hello");                 // => "Hello"
print!("Hello, {}!", "world");   // => "Hello, world!"
print!("The number is {}", 1);   // => "The number is 1"
print!("{:?}", (3, 4));          // => "(3, 4)"
print!("{value}", value=4);      // => "4"
print!("{} {}", 1, 2);           // => "1 2"
print!("{:04}", 42);             // => "0042" with leading zeros

 可以看到print!接受的是可变参数,第一个参数是一个字符串常量,它表示最终输出字符串的格式,注意这里必须是常量,不能是变量,因为编译器需要做一些校验。在第一个参数中可以看到{}这样的符号,这是位置参数。它相当于占位符,表示在最终输出的结果里面会按照指定的规则进行替换。你可以在{}指定哪个参数,也可以不指定。看下面的例子:

1
2
print!("{}{}",1,2); // =>"12"
print!("{1}{0}",1,2); //=>"21"

 如果不指定,我们称为"下一个参数"指示符,它会自动的进行遍历,看下面这个例子:

1
print!("{1}{}{0}{}",1,2); //=>2112

 可以看到{}并不受它前面{1}的影响,而是从第一个参数开始遍历。所以第一个{}是1,第二个{}是2。

带名称变量

 除了指定参数的位置,print!也支持指定参数的名字,例如:

1
2
3
print!("{argument}", argument = "test");   // => "test"
print!("{name} {}", 1, name = 2);          // => "2 1"
print!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"

 这里需要注意,带名字的参数必须放在不带名字参数的后面,也就是说下面这种无法通过编译:

1
print!("{abc} {1}", abc = "def", 2);

格式化参数

 除了以上一些基本的功能,print!还提供了格式化参数来完成一些特殊功能。

宽度

 先看几个例子:

1
2
3
4
5
// All of these print "Hello x    !"
println!("Hello {:5}!", "x");
println!("Hello {:1$}!", "x", 5);
println!("Hello {1:0$}!", 5, "x");
println!("Hello {:width$}!", "x", width = 5);

 例子中{}里面多了一个:,冒号后面的参数叫做”最小宽度“,如果宽度不够需要进行填充和对齐,这部分下面会着重介绍。”最小宽度“这个参数可以指定,指定的时候会在最后跟一个$,比如例子里面的1$,0$,width$等。冒号前面可以指定被格式化的参数的位置,比如上面例子里面的第三个,当然如果你不指定就会从第一个参数开始进行遍历,下面这个例子就能很好的说明:

1
println!("Hello {:1$}!{}", "x", 5); //=>Hello x    !5

填充和对齐

 在上面的例子中,‘x’的宽度为1,但是我们指定的最小宽度是5,可以看到输出的结果里面是左对齐,并且使用空格进行填充。实际上,如果我们不指定填充和对齐的方式, Rust 会用默认的方式进行填充和对齐。对于非数字采用的是空格填充左对齐的方式,对于数字是空格填充右对齐。下面给一些例子,来看看如何指定填充和对齐方式。

1
2
3
4
assert_eq!(format!("Hello {:<5}!", "x"),  "Hello x    !");
assert_eq!(format!("Hello {:-<5}!", "x"), "Hello x----!");
assert_eq!(format!("Hello {:^5}!", "x"),  "Hello   x  !");
assert_eq!(format!("Hello {:>5}!", "x"),  "Hello     x!");
  • fill<: 表示左对齐,用fill进行填充,比如上面例子的第1个和第2个。
  • fill^: 居中对齐。
  • fill>: 右对齐。

符号/#/0

 先看一些例子:

1
2
3
4
5
assert_eq!(format!("Hello {:+}!", 5), "Hello +5!");
assert_eq!(format!("{:#x}!", 27), "0x1b!");
assert_eq!(format!("Hello {:05}!", 5),  "Hello 00005!");
assert_eq!(format!("Hello {:05}!", -5), "Hello -0005!");
assert_eq!(format!("{:#010x}!", 27), "0x0000001b!");
  • +: 表示永远打印数字的符号,默认情况下不会打印正数的符号。
  • #: 表示采用其他形式来输出结果:  #b: 表示用二进制输出,比如下面的例子,我们可以看到最小宽度是10,并且10包含了0b这两个字符。
1
println!("Hello {:#010b}!", 10); //=> Hello 0b00001010!

#o: 表示用8进制输出  #x : 表示用16进制小写输出(即:a,b,c,d,e,f)  #X : 表示用16进制大写输出(即:A,B,C,D,E,F)

  • 0:表示整数使用0进行填充。对于正数1,{:08}的结果是00000001,8表示是宽度;对于-1,{:08}的结果是-0000001,注意-占了一位。

精度

 精度的语法和前面宽度的语法有点类似,先介绍一些语法,然后再举例说明实际的用法。

  1. .N:其中N表示精度值。
  2. .N$:和宽度一样,可以指定某个参数来表示精度值。
  3. .*:这个在宽度一节里面没有出现,它的意思是{}会接收两个输入,第一个输入表示的是精度,第二个输入将要被格式化的值。如果采用以下的形式:{<arg>:<spec>.*},那么<arg>是要被格式化的值,<arg>之前的一个参数表示精度。

 对于非数字类型的数据,精度相当于最大长度,如果格式化结果的长度比精度大,会进行截断,下面是一个例子:

1
2
3
4
5
6
7
8
//结果都是:  Hello abcdefg!
println!("Hello {:.1$}!", "abcdefg", 10);
println!("Hello {:.10}!", "abcdefg");
println!("Hello {0:.10}!", "abcdefg");
println!("Hello {:.*}!", 10, "abcdefg");
println!("Hello {1:.*}!", 10, "abcdefg");
//下面这个结合宽度和精度
println!("Hello {0:+>7.4}!", "abcdefg"); //=> Hello +++abcd!

 对于整数,精度会被忽略,对于浮点数,精度表示小数点之后需要打印多少位。下面是一些例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Hello {arg 0 ("x")} is {arg 1 (0.01) with precision specified inline (5)}
println!("Hello {0} is {1:.5}", "x", 0.01);

// Hello {arg 1 ("x")} is {arg 2 (0.01) with precision specified in arg 0 (5)}
println!("Hello {1} is {2:.0$}", 5, "x", 0.01);

// Hello {arg 0 ("x")} is {arg 2 (0.01) with precision specified in arg 1 (5)}
println!("Hello {0} is {2:.1$}", "x", 5, 0.01);

// Hello {next arg ("x")} is {second of next two args (0.01) with precision
//                          specified in first of next two args (5)}
println!("Hello {} is {:.*}",    "x", 5, 0.01);

// Hello {next arg ("x")} is {arg 2 (0.01) with precision
//                          specified in its predecessor (5)}
println!("Hello {} is {2:.*}",   "x", 5, 0.01);

// Hello {next arg ("x")} is {arg "number" (0.01) with precision specified
//                          in arg "prec" (5)}
println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);

转义

 有时需要输出{},但这两个字符是特殊字符,需要进行转义,转义的方法非常简单,{{进行转义,}}。例如:

1
2
assert_eq!(format!("Hello {{}}"), "Hello {}");
assert_eq!(format!("{{ Hello"), "{ Hello");

 如果你想了解更多关于格式化输出的内容,可以访问 Rust 官方文档

Licensed under CC BY-NC-ND 4.0
使用 Hugo 构建
主题 StackJimmy 设计