no_stdout/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3use core::{
4    fmt::{self},
5    hint,
6    sync::atomic::{AtomicUsize, Ordering},
7};
8
9static STATE: AtomicUsize = AtomicUsize::new(UNINITIALIZED);
10static mut STDOUT: &dyn StdOut = &NopOut;
11
12/// A trait describes common operations with the stdout.
13pub trait StdOut: Send + 'static {
14    /// Writes a bytes slice into the stdout, returning whether write succeeded.
15    fn write_bytes(&self, bytes: &[u8]) -> fmt::Result;
16    /// Writes a string slice.
17    fn write_str(&self, s: &str) -> fmt::Result;
18    /// Writes a formatted string.
19    fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result;
20    /// Ensures that none of the previously written bytes are still buffered.
21    fn flush(&self) -> fmt::Result;
22}
23
24// Three different states can occur during the program lifecycle:
25//
26// The stdout is uninitialized yet.
27const UNINITIALIZED: usize = 0;
28// The stdout is initializing right now.
29const INITIALIZING: usize = 1;
30// The stdout has been initialized and currently is active.
31const INITIALIZED: usize = 2;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct SetStdoutError(());
35
36impl SetStdoutError {
37    fn new() -> Self {
38        Self(())
39    }
40}
41
42struct NopOut;
43
44impl StdOut for NopOut {
45    fn write_str(&self, _: &str) -> fmt::Result {
46        Ok(())
47    }
48
49    fn write_bytes(&self, _bytes: &[u8]) -> fmt::Result {
50        Ok(())
51    }
52
53    fn write_fmt(&self, _args: fmt::Arguments) -> fmt::Result {
54        Ok(())
55    }
56
57    fn flush(&self) -> fmt::Result {
58        Ok(())
59    }
60}
61
62fn set_stdout_inner<F>(stdout: F) -> Result<(), SetStdoutError>
63where
64    F: FnOnce() -> &'static dyn StdOut,
65{
66    let old_state = match STATE.compare_exchange(
67        UNINITIALIZED,
68        INITIALIZING,
69        Ordering::SeqCst,
70        Ordering::SeqCst,
71    ) {
72        Ok(s) | Err(s) => s,
73    };
74
75    match old_state {
76        // The state was UNINITIALIZED and then changed to INITIALIZING.
77        UNINITIALIZED => {
78            unsafe {
79                STDOUT = stdout();
80            }
81            STATE.store(INITIALIZED, Ordering::SeqCst);
82
83            Ok(())
84        }
85
86        // The state is already INITIALIZING.
87        INITIALIZING => {
88            // Make sure the state became INITIALIZING finally.
89            while STATE.load(Ordering::SeqCst) == INITIALIZING {
90                hint::spin_loop();
91            }
92
93            Err(SetStdoutError::new())
94        }
95
96        _ => Err(SetStdoutError::new()),
97    }
98}
99
100/// Initialized the global stdout with the a specified `&'static dyn StdOut`.
101///
102/// This function may only be called once during the program lifecycle.
103pub fn init(stdout: &'static dyn StdOut) -> Result<(), SetStdoutError> {
104    set_stdout_inner(move || stdout)
105}
106
107/// Returns a reference to the stdout.
108///
109/// If a stdout has not been set, returns a no-op implementation.
110pub fn stdout() -> &'static dyn StdOut {
111    if STATE.load(Ordering::SeqCst) != INITIALIZED {
112        static NOP: NopOut = NopOut;
113        &NOP
114    } else {
115        unsafe { STDOUT }
116    }
117}
118
119/// Macro for printing to the configured stdout, without a newline.
120#[macro_export]
121macro_rules! uprint {
122    ($s:expr) => {{
123        $crate::stdout()
124            .write_str($s)
125            .ok();
126    }};
127    ($s:expr, $($tt:tt)*) => {{
128        $crate::stdout()
129            .write_fmt(format_args!($s, $($tt)*))
130            .ok();
131    }};
132}
133
134/// Macro for printing to the configured stdout, with a newline.
135#[macro_export]
136macro_rules! uprintln {
137    () => {{
138        $crate::stdout()
139            .write_str(uprintln!(@newline))
140            .ok();
141    }};
142    ($s:expr) => {{
143        $crate::stdout()
144            .write_str(concat!($s, uprintln!(@newline)))
145            .ok();
146    }};
147    ($s:expr, $($tt:tt)*) => {{
148        $crate::stdout()
149            .write_fmt(format_args!(concat!($s, uprintln!(@newline)), $($tt)*))
150            .ok();
151    }};
152
153    (@newline) => { "\r\n" };
154}
155
156/// Macro for printing to the configured stdout, without a newline.
157///
158/// This method prints only if the `dprint` feature enabled, which is useful
159/// for debugging purposes.
160#[cfg(any(feature = "dprint", doc))]
161#[macro_export]
162macro_rules! dprint {
163    ($s:expr) => {{
164        $crate::stdout()
165            .write_str($s)
166            .ok();
167    }};
168    ($s:expr, $($tt:tt)*) => {{
169        $crate::stdout()
170            .write_fmt(format_args!($s, $($tt)*))
171            .ok();
172    }};
173}
174#[cfg(not(any(feature = "dprint", doc)))]
175#[macro_export]
176macro_rules! dprint {
177    ($s:expr) => {};
178    ($s:expr, $($tt:tt)*) => {};
179}
180
181/// Macro for printing to the configured stdout, with a newline.
182///
183/// This method prints only if the `dprint` feature enabled, which is useful
184/// for debugging purposes.
185#[macro_export]
186#[cfg(any(feature = "dprint", doc))]
187macro_rules! dprintln {
188    () => {{
189        $crate::stdout()
190            .write_str(dprintln!(@newline))
191            .ok();
192    }};
193    ($s:expr) => {{
194        $crate::stdout()
195            .write_str(concat!($s, dprintln!(@newline)))
196            .ok();
197    }};
198    ($s:expr, $($tt:tt)*) => {{
199        $crate::stdout()
200            .write_fmt(format_args!(concat!($s, dprintln!(@newline)), $($tt)*))
201            .ok();
202    }};
203
204    (@newline) => { "\r\n" };
205}
206#[cfg(not(any(feature = "dprint", doc)))]
207#[macro_export]
208macro_rules! dprintln {
209    () => {};
210    ($s:expr) => {};
211    ($s:expr, $($tt:tt)*) => {};
212}