libpanic_unwind is no_std (because it’s linked into libstd, for a start), so you can’t access OS-level stdio facilities. Not sure if there’s a way to work around it
I agree that failing paths (e.g., no silent truncation or even silently ignoring a bad argument) are the most sensitive thing to do, in the general case.
By the way, ends_with is not flawless either, since it ignores inner null bytes.
s.iter().position(|&x| x == b'\0') == Some(s.len() - 1) would be the way to go
Since the OP wanted a debug function, however, in this case a convenience "do the best with what I get" function seemed to me more appropriate than a panic! within a panic! leading to a harder to debug abort, even at the cost of potential message truncation and the worst-case which silently ignores the call altogether.
But I should have stated so in my post, so thanks for pointing that out.
Here goes another shot at handling bad case scenarios (always within a no_std environment):
/// if `$assertion` is `false`, there is an index out of bounds access at compile time.
macro_rules! static_assert {($assertion:expr) => ({
static _ASSERTION: () = [(); 1][(!$assertion) as usize];
})}
fn puts (s: &'_ [u8])
{
const BUF_CAPACITY: usize
= 256
;
const TRUNCATION_MESSAGE_LEN: usize
= 6
;
static TRUNCATION_MESSAGE: &'static [u8; TRUNCATION_MESSAGE_LEN]
= b"<...>\0"
;
static_assert!(
BUF_CAPACITY >= TRUNCATION_MESSAGE_LEN
);
static_assert!(
TRUNCATION_MESSAGE[TRUNCATION_MESSAGE_LEN - 1] == b'\0'
);
unsafe {
if s.iter().position(|&x| x == b'\0') == Some(s.len() - 1) {
::libc::puts(s.as_ptr() as *const ::libc::c_char);
} else {
let mut buffer = [0_u8; BUF_CAPACITY];
buffer
.iter_mut()
.zip(s.iter().cloned().filter(|&x| x != b'\0'))
.for_each(|(p, x)| *p = x);
if !buffer.ends_with(b"\0") {
buffer[BUF_CAPACITY - TRUNCATION_MESSAGE_LEN ..]
.copy_from_slice(TRUNCATION_MESSAGE)
;
}
::libc::puts(buffer.as_ptr() as *const ::libc::c_char);
}
}
}
You could avoid needing to copy anything, or worrying about truncation of the message, by looping over the buffer calling putchar.
fn puts (s: &[u8])
{
use ::libc::{putchar, c_int};
unsafe {
for c in s {
putchar(*c as c_int);
}
putchar('\n' as c_int);
}
}
Debugging messages ought to go to stderr so they don’t corrupt any primary output of the program, and a function like this ought to lock and unlock the FILE object itself so no other thread’s output can get mixed in, which would look like this …
fn eputs (s: &[u8])
{
use ::libc::{c_int, stderr, fputc_unlocked,
flockfile, funlockfile, fflush};
unsafe {
flockfile(stderr);
for c in s {
fputc_unlocked(*c as c_int, stderr);
}
fputc_unlocked('\n' as c_int, stderr);
fflush(stderr);
funlockfile(stderr);
}
}
… but unfortunately libc doesn’t expose flockfile, funlockfile, fputc_unlocked, or even stderr (!)
An alternative would be to bypass the stdio layer:
fn eputs (s: &[u8])
{
use ::libc::{write, c_void, STDERR_FILENO};
if s.len() > 0 {
unsafe {
write(STDERR_FILENO, s.as_ptr() as *const c_void, s.len());
}
}
}
With this version, callers have to provide a trailing newline themselves. All of these functions ignore errors, and the last one assumes all of the message will be consumed by a single call to write, which is not guaranteed but usually okay for what stderr gets connected to.