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}