3个版本
0.1.2 | 2023年9月10日 |
---|---|
0.1.1 | 2023年9月4日 |
0.1.0 | 2023年9月3日 |
#100 in FFI
30KB
351 行
fmtbuf
将格式化的字符串写入固定缓冲区。当您需要将用户提供的缓冲区写入时,这非常有用,尤其是在编写与C的FFI时,字符串需要以null终止符结尾。
用法
use fmtbuf::WriteBuf;
use std::fmt::Write;
fn main() {
let mut buf: [u8; 10] = [0; 10];
let mut writer = WriteBuf::new(&mut buf);
if let Err(e) = write!(&mut writer, "🚀🚀🚀") {
println!("write error: {e:?}");
}
let written_len = match writer.finish_with("\0") {
Ok(len) => len, // <- won't be hit since 🚀🚀🚀 is 12 bytes
Err(len) => {
println!("writing was truncated");
len
}
};
let written = &buf[..written_len];
println!("wrote {written_len} bytes: {written:?}");
println!("result: {:?}", std::str::from_utf8(written));
}
🚀🚀
主要用例是实现像 strerror_r
这样的API,其中用户提供缓冲区。
use std::{ffi, fmt::Write, io::Error};
use fmtbuf::WriteBuf;
#[no_mangle]
pub unsafe extern "C" fn mylib_strerror(
err: *mut Error,
buf: *mut ffi::c_char,
buf_len: usize
) {
let mut buf = unsafe {
// Buffer provided by a users
std::slice::from_raw_parts_mut(buf as *mut u8, buf_len)
};
// Reserve at least 1 byte at the end because we will always
// write '\0'
let mut writer = WriteBuf::with_reserve(buf, 1);
// Use the standard `write!` macro (no error handling for
// brevity) -- note that an error here might only indicate
// write truncation, which is handled gracefully be this
// library's finish___ functions
let _ = write!(writer, "{}", err.as_ref().unwrap());
// null-terminate buffer or add "..." if it was truncated
let _written_len = writer.finish_with_or(b"\0", b"...\0")
// Err value is also number of bytes written
.unwrap_or_else(|e| e);
}
特性
!#[no_std]
通过禁用默认功能并重新启用 "std"
功能来启用对 !#[no_std]
的支持。
fmtbuf = { version = "*", default_features = false }
常见问题解答
为什么不能写入 &mut [u8]
?
Rust标准库中的 std::io::Write
trait为 &mut [u8]
实现,可以用来代替这个库。这种方法的问题是没有UTF-8编码支持(此外,在 #![no_std]
中也不可用)。
use std::io::{Cursor, Write};
fn main() {
let mut buf: [u8; 10] = [0; 10];
let mut writer = Cursor::<&mut [u8]>::new(&mut buf);
if let Err(e) = write!(&mut writer, "rocket: 🚀") {
println!("write error: {e:?}");
}
let written_len = writer.position() as usize;
let written = &buf[..written_len];
println!("wrote {written_len} bytes: {written:?}");
println!("result: {:?}", std::str::from_utf8(written));
}
运行此程序将显示错误
write error: Error { kind: WriteZero, message: "failed to write whole buffer" }
wrote 10 bytes: [114, 111, 99, 107, 101, 116, 58, 32, 240, 159]
result: Err(Utf8Error { valid_up_to: 8, error_len: None })
问题是,""rocket: 🚀"
"被编码成一个12字节的序列——🚀表情符号在UTF-8中以4字节编码为b"\xf0\x9f\x9a\x80"
——但我们的目标缓冲区只有10字节长。对光标的write!
操作天真地切断了🚀的编码过程,使得编码后的字符串无效UTF-8,尽管它已经将光标前进了整个10字节。这是预期的,因为std::io::Write
来自io
,不知道任何关于字符串编码的信息;它在u8
级别上操作。
可以使用std::str::Utf8Error
来正确地截断buf
。唯一的问题是性能。由于std::str::from_utf8
向前扫描整个字符串,这需要O(n)的时间来测试,而fmtbuf
只需在O(1)时间内完成,因为它只查看最后的几个字节。
关于Unicode的“奇怪格式字符”呢?
这个库只能保证目标缓冲区的内容是有效的UTF-8。它不对由于Unicode格式字符(特别是U+200D
、U+200E
和U+200F
)截断而产生的语义做任何保证。
什么?
如果你不知道那些是什么,没关系。只要知道人类语言很复杂,Unicode有一套功能使事情成为可能,但当你没有足够的空间在固定大小的缓冲区中存储时,事情就会出错。如果你想了解更多细节,请参阅下面的小节。
U+200D
:零宽度连接符
某些图形符号,如"🙇♀"(你可能将其视为两个单独的图形符号),由三个码点组成
因此,单个字符图形是10字节的序列 b"\xf0\x9f\x99\x87\xe2\x80\x8d\xe2\x99\x80"
。问题是:如果缓冲区大小只有9字节怎么办?在截断时,此库将丢弃应作为修饰符的码点。此库将截断最后一个Unicode码点,留下 b"\xf0\x9f\x99\x87\xe2\x80\x8d"
-- 一个鞠躬的人和一个零宽连接符与空内容连接,因为女性修饰符无法容纳。
U+200E
和 U+200F
:方向标记
考虑阿拉伯语,它是一种从右到左的语言
希望有一天Rust能取代C++。
根据您的文本编辑器或浏览器对从右到左呈现的兼容性,您可能会以任何一种方式看到文本(如果文本右侧有“آمل”,则表示呈现正常)。但请注意,借用的单词“Rust”和“C++”在从右到左的文本中仍然按从左到右的方式拼写(或者应该是这样)。这是通过编码 U+200E
左到右标记,然后写入借用文本,然后写入 U+200F
右到左标记 来实现的。
如果文本被反转,但缓冲区中没有足够的空间来翻转它,会发生什么?在截断时,此库可能会让您处于文本反转运行的中途。
埃及象形文字和其他类似语言的构造面临类似的问题。应该在何处截断?此库不知道“𓁪𓌍𓃻”和“𓁪𓌍”之间的区别。弄清楚这一点是更高层次结构的责任。