为什么 `format_args!()` 会忽略截断?那么如何在没有分配的情况下截断呢?

rsa*_*mei 0 rust

为什么这样做?

fn main() {
    println!("{:.3}", "this is just a test");
}
Run Code Online (Sandbox Code Playgroud)

打印 => thi

虽然这没有?

fn main() {
    println!("{:.3}", format_args!("this is just a test"));
}
Run Code Online (Sandbox Code Playgroud)

打印 => this is just a test

这里是游乐场

对于更多的上下文,我对其背后的推理感兴趣,以及一种无需任何分配就可以做到的方法。

我正在用 Rust开发一个终端游戏,其中有一个write!显示有关渲染和游戏循环的一些统计信息,并且该文本可能很长。现在我读取了终端大小并相应地调整了它的输出,我需要截断该输出,但没有任何分配。当我重构这个时,我认为我非常聪明:

write!(
  stdout,
  "{} ({} {} {}) {}",
  ...
)
Run Code Online (Sandbox Code Playgroud)

进入这个:

write!(
  stdout,
  "{:.10}", // simulate only 10 cols in terminal.
  format_args!(
    "{} ({} {} {}) {}",
    ...
  )
)
Run Code Online (Sandbox Code Playgroud)

多么不幸,它不起作用……如何在不分配字符串的情况下做到这一点?

kmd*_*eko 5

一方面,并​​非每种类型都遵守所有格式参数:

println!("{:.3}", 1024);
Run Code Online (Sandbox Code Playgroud)
println!("{:.3}", 1024);
Run Code Online (Sandbox Code Playgroud)

其次,format_args!作为所有std::fmt公用事业的支柱。从文档开始format_args

此宏通过采用包含{}每个附加参数的格式化字符串文字来运行。format_args!准备附加参数以确保输出可以解释为字符串并将参数规范化为单一类型。任何实现Displaytrait 的值都可以传递给format_args!,任何Debug实现都可以传递给{:?}格式化字符串中的 a 。

这个宏产生一个类型的值fmt::Arguments。该值可以传递给内部的宏std::fmt以执行有用的重定向。所有其他格式宏(format!write!println!等)都通过此宏进行代理。format_args!,与其派生的宏不同,避免了堆分配。

您可以使用在调试和显示上下文fmt::Argumentsformat_args!返回的值,如下所示。该示例还显示了DebugDisplay格式相同的内容:在format_args!.

let debug = format!("{:?}", format_args!("{} foo {:?}", 1, 2));
let display = format!("{}", format_args!("{} foo {:?}", 1, 2));
assert_eq!("1 foo 2", display);
assert_eq!(display, debug);
Run Code Online (Sandbox Code Playgroud)

查看 的来源impl Display for Arguments,它只是忽略任何格式参数。我在任何地方都找不到明确的记录,但我可以想到几个原因:

  • 参数已经被认为是格式化的。如果您真的想格式化格式化的字符串,请format!改用。
  • 由于它在内部用于多种目的,因此最好保持这部分简单;它已经在做格式繁重的工作。试图让负责格式化参数的事情本身接受格式化参数听起来不必要地复杂。

我真的很想在不分配任何字符串的情况下截断一些输出,你知道怎么做吗?

您可以写入固定大小的缓冲区:

use std::io::{Write, ErrorKind, Result};
use std::fmt::Arguments;

fn print_limited(args: Arguments<'_>) -> Result<()> {
    const BUF_SIZE: usize = 3;
    let mut buf = [0u8; BUF_SIZE];
    let mut buf_writer = &mut buf[..];

    let written = match buf_writer.write_fmt(args) {
        // successfully wrote into the buffer, determine amount written
        Ok(_) => BUF_SIZE - buf_writer.len(),
        
        // a "failed to write whole buffer" error occurred meaning there was 
        // more to write than there was space for, return entire size.
        Err(error) if error.kind() == ErrorKind::WriteZero => BUF_SIZE,
        
        // something else went wrong
        Err(error) => return Err(error),
    };
    
    // Pick a way to print `&buf[..written]`
    println!("{}", std::str::from_utf8(&buf[..written]).unwrap());
    
    Ok(())
}

fn main() {
    print_limited(format_args!("this is just a test")).unwrap();
    print_limited(format_args!("{}", 123)).unwrap();
    print_limited(format_args!("{}", 'a')).unwrap();
}
Run Code Online (Sandbox Code Playgroud)
1024
Run Code Online (Sandbox Code Playgroud)

这实际上比我原先想象的要复杂得多。可能有一种更清洁的方法来做到这一点。