如何使用 no_std 且没有分配器将输出格式化为字节数组?
How to format output to a byte array with no_std and no allocator?
我想做类似的事情:
let x = 123;
let mut buf = [0 as u8; 20];
format_to!(x --> buf);
assert_eq!(&buf[..3], &b"123"[..]);
使用 #![no_std]
且不使用任何内存分配器。
据我了解,u64
有一个 core::fmt::Display
的实现,如果可能我想使用它。
换句话说,我想做类似 format!(...)
的事情,但没有内存分配器。我该怎么做?
让我们从标准版开始:
use std::io::Write;
fn main() {
let x = 123;
let mut buf = [0 as u8; 20];
write!(&mut buf[..], "{}", x).expect("Can't write");
assert_eq!(&buf[0..3], b"123");
}
如果我们再删除标准库:
#![feature(lang_items)]
#![no_std]
use core::panic::PanicInfo;
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
loop {}
}
fn main() {
let x = 123;
let mut buf = [0 as u8; 20];
write!(&mut buf[..], "{}", x).expect("Can't write");
assert_eq!(&buf[0..3], b"123");
}
我们收到错误
error[E0599]: no method named `write_fmt` found for type `&mut [u8]` in the current scope
--> src/main.rs:17:5
|
17 | write!(&mut buf[..], "{}", x).expect("Can't write");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
write_fmt
由core::fmt::Write
在核心库中实现。如果我们自己实现它,我们能够传递该错误:
#![feature(lang_items)]
#![feature(start)]
#![no_std]
use core::panic::PanicInfo;
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
loop {}
}
use core::fmt::{self, Write};
struct Wrapper<'a> {
buf: &'a mut [u8],
offset: usize,
}
impl<'a> Wrapper<'a> {
fn new(buf: &'a mut [u8]) -> Self {
Wrapper {
buf: buf,
offset: 0,
}
}
}
impl<'a> fmt::Write for Wrapper<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let bytes = s.as_bytes();
// Skip over already-copied data
let remainder = &mut self.buf[self.offset..];
// Check if there is space remaining (return error instead of panicking)
if remainder.len() < bytes.len() { return Err(core::fmt::Error); }
// Make the two slices the same length
let remainder = &mut remainder[..bytes.len()];
// Copy
remainder.copy_from_slice(bytes);
// Update offset to avoid overwriting
self.offset += bytes.len();
Ok(())
}
}
#[start]
fn start(_argc: isize, _argv: *const *const u8) -> isize {
let x = 123;
let mut buf = [0 as u8; 20];
write!(Wrapper::new(&mut buf), "{}", x).expect("Can't write");
assert_eq!(&buf[0..3], b"123");
0
}
请注意,我们正在将 io::Cursor
的行为复制到此包装器中。通常,多次写入 &mut [u8]
会相互覆盖。这有利于重用分配,但当您连续写入相同数据时就没用了。
那你想写个宏就好了
您应该也可以使用像 arrayvec 这样的 crate,它已经为您编写了这段代码。这是未经测试的:
#![feature(lang_items)]
#![feature(start)]
#![no_std]
use core::panic::PanicInfo;
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
use arrayvec::ArrayString; // 0.4.10
use core::fmt::Write;
#[start]
fn start(_argc: isize, _argv: *const *const u8) -> isize {
let x = 123;
let mut buf = ArrayString::<[u8; 20]>::new();
write!(&mut buf, "{}", x).expect("Can't write");
assert_eq!(&buf, "123");
0
}
与bare_io:
use bare_io::{Cursor, Write};
let mut buf = [0 as u8; 256];
let mut cur = Cursor::new(&mut buf[..]);
write!(&mut cur, "hello world, stack buf, {}\n[=10=]", 234).expect("!write");
unsafe { puts(buf.as_ptr()) };
与 bare_io
、smallvec 和 alloc
:
use smallvec::{Array, SmallVec};
struct WriteSmallVec<A: Array<Item = u8>>(SmallVec<A>);
impl<A: Array<Item = u8>> Write for WriteSmallVec<A> {
fn write(&mut self, buf: &[u8]) -> bare_io::Result<usize> {
self.0.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> bare_io::Result<()> {
Ok(())
}
}
let mut sv = WriteSmallVec(SmallVec::<[u8; 256]>::new());
write!(&mut sv, "hello world, SmallVec, prev len: {}\n[=11=]", len).expect("!write");
unsafe { puts(sv.0.as_ptr()) };
与 bare_io
、patched inlinable_string 和 alloc
:
use core::fmt::Write;
use inlinable_string::{InlinableString, StringExt};
let mut is = InlinableString::new();
write!(&mut is, "hello world, InlinableString, {}\n[=12=]", 345).expect("!write");
unsafe { puts(is.as_ptr()) };
在 Linux 内核中测试,
cargo build --release -Z build-std=core,alloc --target=x86_64-linux-kernel
也做了一些基准测试,将一个简单的数组与 SmallVec 和 InlinableString 进行了比较:https://gitlab.com/artemciy/lin-socks/-/blob/95d2bb96/bench/stack-string.rs
p.s。 bare-io
has been yanked 不过
我想做类似的事情:
let x = 123;
let mut buf = [0 as u8; 20];
format_to!(x --> buf);
assert_eq!(&buf[..3], &b"123"[..]);
使用 #![no_std]
且不使用任何内存分配器。
据我了解,u64
有一个 core::fmt::Display
的实现,如果可能我想使用它。
换句话说,我想做类似 format!(...)
的事情,但没有内存分配器。我该怎么做?
让我们从标准版开始:
use std::io::Write;
fn main() {
let x = 123;
let mut buf = [0 as u8; 20];
write!(&mut buf[..], "{}", x).expect("Can't write");
assert_eq!(&buf[0..3], b"123");
}
如果我们再删除标准库:
#![feature(lang_items)]
#![no_std]
use core::panic::PanicInfo;
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
loop {}
}
fn main() {
let x = 123;
let mut buf = [0 as u8; 20];
write!(&mut buf[..], "{}", x).expect("Can't write");
assert_eq!(&buf[0..3], b"123");
}
我们收到错误
error[E0599]: no method named `write_fmt` found for type `&mut [u8]` in the current scope
--> src/main.rs:17:5
|
17 | write!(&mut buf[..], "{}", x).expect("Can't write");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
write_fmt
由core::fmt::Write
在核心库中实现。如果我们自己实现它,我们能够传递该错误:
#![feature(lang_items)]
#![feature(start)]
#![no_std]
use core::panic::PanicInfo;
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
loop {}
}
use core::fmt::{self, Write};
struct Wrapper<'a> {
buf: &'a mut [u8],
offset: usize,
}
impl<'a> Wrapper<'a> {
fn new(buf: &'a mut [u8]) -> Self {
Wrapper {
buf: buf,
offset: 0,
}
}
}
impl<'a> fmt::Write for Wrapper<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let bytes = s.as_bytes();
// Skip over already-copied data
let remainder = &mut self.buf[self.offset..];
// Check if there is space remaining (return error instead of panicking)
if remainder.len() < bytes.len() { return Err(core::fmt::Error); }
// Make the two slices the same length
let remainder = &mut remainder[..bytes.len()];
// Copy
remainder.copy_from_slice(bytes);
// Update offset to avoid overwriting
self.offset += bytes.len();
Ok(())
}
}
#[start]
fn start(_argc: isize, _argv: *const *const u8) -> isize {
let x = 123;
let mut buf = [0 as u8; 20];
write!(Wrapper::new(&mut buf), "{}", x).expect("Can't write");
assert_eq!(&buf[0..3], b"123");
0
}
请注意,我们正在将 io::Cursor
的行为复制到此包装器中。通常,多次写入 &mut [u8]
会相互覆盖。这有利于重用分配,但当您连续写入相同数据时就没用了。
那你想写个宏就好了
您应该也可以使用像 arrayvec 这样的 crate,它已经为您编写了这段代码。这是未经测试的:
#![feature(lang_items)]
#![feature(start)]
#![no_std]
use core::panic::PanicInfo;
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
use arrayvec::ArrayString; // 0.4.10
use core::fmt::Write;
#[start]
fn start(_argc: isize, _argv: *const *const u8) -> isize {
let x = 123;
let mut buf = ArrayString::<[u8; 20]>::new();
write!(&mut buf, "{}", x).expect("Can't write");
assert_eq!(&buf, "123");
0
}
与bare_io:
use bare_io::{Cursor, Write};
let mut buf = [0 as u8; 256];
let mut cur = Cursor::new(&mut buf[..]);
write!(&mut cur, "hello world, stack buf, {}\n[=10=]", 234).expect("!write");
unsafe { puts(buf.as_ptr()) };
与 bare_io
、smallvec 和 alloc
:
use smallvec::{Array, SmallVec};
struct WriteSmallVec<A: Array<Item = u8>>(SmallVec<A>);
impl<A: Array<Item = u8>> Write for WriteSmallVec<A> {
fn write(&mut self, buf: &[u8]) -> bare_io::Result<usize> {
self.0.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> bare_io::Result<()> {
Ok(())
}
}
let mut sv = WriteSmallVec(SmallVec::<[u8; 256]>::new());
write!(&mut sv, "hello world, SmallVec, prev len: {}\n[=11=]", len).expect("!write");
unsafe { puts(sv.0.as_ptr()) };
与 bare_io
、patched inlinable_string 和 alloc
:
use core::fmt::Write;
use inlinable_string::{InlinableString, StringExt};
let mut is = InlinableString::new();
write!(&mut is, "hello world, InlinableString, {}\n[=12=]", 345).expect("!write");
unsafe { puts(is.as_ptr()) };
在 Linux 内核中测试,
cargo build --release -Z build-std=core,alloc --target=x86_64-linux-kernel
也做了一些基准测试,将一个简单的数组与 SmallVec 和 InlinableString 进行了比较:https://gitlab.com/artemciy/lin-socks/-/blob/95d2bb96/bench/stack-string.rs
p.s。 bare-io
has been yanked 不过