How could I implement the following example without using std?
let text = format!("example {:.1} test {:x} words {}", num1, num2, num3);
text
has type &str
and num1
, num2
and num3
have any numeric type.
I've tried using numtoa
and itoa/dtoa
for displaying numbers but numtoa
does not support floats and itoa
does not support no_std
. I feel like displaying a number in a string is fairly common and that I'm probably missing something obvious.
In general, you don't. format!
allocates a String
, and a no_std
environment doesn't have an allocator.
If you do have an allocator, you can use the alloc crate. This crate contains the format!
macro.
#![crate_type = "dylib"]
#![no_std]
#[macro_use]
extern crate alloc;
fn thing() {
let text = format!("example {:.1} test {:x} words {}", 1, 2, 3);
}
See also:
Write a formatter!
use core::fmt::{self, Write};
use core::str;
fn main() {
// For LCD 160 / 8 = 20 chars
let mut buf = [0u8; 20];
let mut buf = ByteMutWriter::new(&mut buf[..]);
buf.clear();
write!(&mut buf, "Hello {}!", "Rust").unwrap();
// buf.as_str()
}
pub struct ByteMutWriter<'a> {
buf: &'a mut [u8],
cursor: usize,
}
impl<'a> ByteMutWriter<'a> {
pub fn new(buf: &'a mut [u8]) -> Self {
ByteMutWriter { buf, cursor: 0 }
}
pub fn as_str(&self) -> &str {
str::from_utf8(&self.buf[0..self.cursor]).unwrap()
}
#[inline]
pub fn capacity(&self) -> usize {
self.buf.len()
}
pub fn clear(&mut self) {
self.cursor = 0;
}
pub fn len(&self) -> usize {
self.cursor
}
pub fn empty(&self) -> bool {
self.cursor == 0
}
pub fn full(&self) -> bool {
self.capacity() == self.cursor
}
}
impl fmt::Write for ByteMutWriter<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let cap = self.capacity();
for (i, &b) in self.buf[self.cursor..cap]
.iter_mut()
.zip(s.as_bytes().iter())
{
*i = b;
}
self.cursor = usize::min(cap, self.cursor + s.as_bytes().len());
Ok(())
}
}
In addition to Shepmaster's answer you can also format strings without an allocator.
In core::fmt::Write
you only need to implement write_str
and then you get write_fmt
for free.
With format_args!(...)
(same syntax as format!
) you can prepare a core::fmt::Arguments
value, which can be passed to core::fmt::write
.
See Playground:
#![crate_type = "dylib"]
#![no_std]
pub mod write_to {
use core::cmp::min;
use core::fmt;
pub struct WriteTo<'a> {
buffer: &'a mut [u8],
// on write error (i.e. not enough space in buffer) this grows beyond
// `buffer.len()`.
used: usize,
}
impl<'a> WriteTo<'a> {
pub fn new(buffer: &'a mut [u8]) -> Self {
WriteTo { buffer, used: 0 }
}
pub fn as_str(self) -> Option<&'a str> {
if self.used <= self.buffer.len() {
// only successful concats of str - must be a valid str.
use core::str::from_utf8_unchecked;
Some(unsafe { from_utf8_unchecked(&self.buffer[..self.used]) })
} else {
None
}
}
}
impl<'a> fmt::Write for WriteTo<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
if self.used > self.buffer.len() {
return Err(fmt::Error);
}
let remaining_buf = &mut self.buffer[self.used..];
let raw_s = s.as_bytes();
let write_num = min(raw_s.len(), remaining_buf.len());
remaining_buf[..write_num].copy_from_slice(&raw_s[..write_num]);
self.used += raw_s.len();
if write_num < raw_s.len() {
Err(fmt::Error)
} else {
Ok(())
}
}
}
pub fn show<'a>(buffer: &'a mut [u8], args: fmt::Arguments) -> Result<&'a str, fmt::Error> {
let mut w = WriteTo::new(buffer);
fmt::write(&mut w, args)?;
w.as_str().ok_or(fmt::Error)
}
}
pub fn test() {
let mut buf = [0u8; 64];
let _s: &str = write_to::show(
&mut buf,
format_args!("write some stuff {:?}: {}", "foo", 42),
).unwrap();
}
You can also combine the usage of numtoa
and arrayvec
crates. Example:
#![no_std]
use numtoa::NumToA;
use arrayvec::ArrayString;
fn main() -> ! {
let mut num_buffer = [0u8; 20];
let mut text = ArrayString::<[_; 100]>::new();
let num1 = 123;
let num2 = 456;
let num3 = 789;
// text.clear(); (on subsequent usages)
text.push_str("example ");
text.push_str(num1.numtoa_str(10, &mut num_buffer));
text.push_str(" test ");
text.push_str(num2.numtoa_str(10, &mut num_buffer));
text.push_str(" words ");
text.push_str(num3.numtoa_str(10, &mut num_buffer));
}
Note that push_str can panic. Check out the api for try_
-methods
And Cargo.toml
[dependencies]
arrayvec = { version = "0.5", default-features = false }
numtoa = "0.2"
I build a small crate based on Shepmaster's post. There is also a macro included that allows easy use. All this works without a heap allocator and is compatible with no_std.
use arrform::{arrform, ArrForm};
let af = arrform!(64, "write some stuff {}: {:.2}", "foo", 42.3456);
assert_eq!("write some stuff foo: 42.35", af.as_str());
This macro first reserves a buffer on the stack. Then it uses the struct ArrForm
to format text and numbers. It returns an instance of ArrForm
that allows easy access to the contained text. The macro panics if the buffer is chosen too small.
see https://github.com/Simsys/arrform or https://crates.io/crates/arrform.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With