na_print/lib.rs
1//! Print to the terminal *without* allocating!
2
3/// Unstable (ish) internals
4///
5/// this contains the actual write fn that is used, so it can be used for other things in users libs
6pub mod internal {
7 use std::io;
8 use libc::{write, c_int, c_void};
9
10 // The maximum read limit on most POSIX-like systems is `SSIZE_MAX`,
11 // with the man page quoting that if the count of bytes to read is
12 // greater than `SSIZE_MAX` the result is "unspecified".
13 //
14 // On macOS, however, apparently the 64-bit libc is either buggy or
15 // intentionally showing odd behavior by rejecting any read with a size
16 // larger than or equal to INT_MAX. To handle both of these the read
17 // size is capped on both platforms.
18 #[cfg(target_os = "macos")]
19 const READ_LIMIT: usize = libc::c_int::MAX as usize - 1;
20 #[cfg(not(target_os = "macos"))]
21 const READ_LIMIT: usize = libc::ssize_t::MAX as usize;
22
23
24 /// writes all of `buf` to the given fd. can be used with [`libc::STDOUT_FILENO`]
25 /// and [`libc::STDERR_FILENO`] to print without allocating
26 ///
27 /// # Saftey
28 /// it seemed similar enough to what happenes in [`std::sys::unix::fd::FileDesc::write`], so it should be fine?
29 /// not synced or locked in any way so expect some strange behavior
30 ///
31 #[cfg(unix)]
32 pub unsafe fn write_all(fd: c_int, buf: &[u8]) -> io::Result<()> {
33 assert!(buf.len() <= READ_LIMIT, "writing more than {} bytes is unspecified behavior!", READ_LIMIT);
34 let mut remaining = buf.len();
35 loop {
36 let res = write(fd, buf[buf.len() - remaining..].as_ptr() as *const c_void, remaining);
37 if res < 0 {
38 return Err(io::Error::last_os_error());
39 } else if res == 0 {
40 break Ok(());
41 } else {
42 remaining -= res as usize;
43 }
44 }
45 }
46}
47
48#[cfg(unix)]
49fn print_to(fd: libc::c_int, txt: &str) {
50 unsafe { internal::write_all(fd, txt.as_bytes()) }.unwrap();
51}
52
53/// Prints `txt` to stdout. performs no allocations, and panics on error.
54#[cfg(unix)]
55#[inline(always)]
56pub fn print(txt: &str) {
57 print_to(libc::STDOUT_FILENO, txt);
58}
59
60/// [`print`], but prints a trailing newline
61#[cfg(unix)]
62#[inline(always)]
63pub fn println(txt: &str) {
64 print(txt);
65 print("\n");
66}
67
68/// Prints `txt` to stderr. for mor info, see [`print`]
69#[cfg(unix)]
70#[inline(always)]
71pub fn eprint(txt: &str) {
72 print_to(libc::STDERR_FILENO, txt);
73}
74
75/// [`eprint`], but prints a trailing newline
76#[cfg(unix)]
77#[inline(always)]
78pub fn eprintln(txt: &str) {
79 print(txt);
80 print("\n");
81}
82
83#[cfg(all(test, unix))]
84#[test]
85fn it_works() {
86 print("Hello, World!");
87 eprint("oh no");
88}