Skip to main content

rialo_s_msg/
lib.rs

1#![allow(unsafe_code)]
2
3/// Print a message to the log.
4///
5/// Supports simple strings as well as Rust [format strings][fs]. When passed a
6/// single expression it will be passed directly to [`rlo_log`]. The expression
7/// must have type `&str`, and is typically used for logging static strings.
8/// When passed something other than an expression, particularly
9/// a sequence of expressions, the tokens will be passed through the
10/// [`format!`] macro before being logged with `rlo_log`.
11///
12/// [fs]: https://doc.rust-lang.org/std/fmt/
13/// [`format!`]: https://doc.rust-lang.org/std/fmt/fn.format.html
14///
15/// Note that Rust's formatting machinery is relatively CPU-intensive
16/// for constrained environments like the Solana VM.
17///
18/// # Examples
19///
20/// ```
21/// use rialo_s_msg::msg;
22///
23/// // The fast form
24/// msg!("verifying multisig");
25///
26/// // With formatting
27/// let err = "not enough signers";
28/// msg!("multisig failed: {}", err);
29/// ```
30///
31/// The fast form does not run `format!`, so a lone literal cannot interpolate.
32/// Inline captures in it are rejected at compile time rather than logged with
33/// their braces intact:
34///
35/// ```compile_fail
36/// use rialo_s_msg::msg;
37///
38/// let err = "not enough signers";
39/// msg!("multisig failed: {err}"); // error: use msg!("multisig failed: {}", err)
40/// ```
41#[macro_export]
42macro_rules! msg {
43    // Allocation-free fast path for string literals: the literal reaches the
44    // syscall as-is, with no `format!` and no `String`.
45    //
46    // Guarded, because the fast path cannot interpolate: a literal carrying a
47    // format placeholder (`msg!("fee={fee}")`) would otherwise log the braces
48    // verbatim, silently — the defect this arm exists to make impossible. The
49    // guard turns it into a compile error pointing at the formatting form.
50    //
51    // The guard sees the *cooked* string, so escapes whose braces belong to
52    // Rust's lexer rather than to `format!` (`"\u{2014}"`) pass unaffected.
53    ($msg:literal) => {{
54        const _: () = $crate::assert_literal_has_no_format_placeholder($msg);
55        $crate::rlo_log($msg)
56    }};
57    ($msg:expr) => {
58        $crate::rlo_log($msg)
59    };
60    ($($arg:tt)*) => ($crate::rlo_log(&format!($($arg)*)));
61}
62
63/// Compile-time guard behind [`msg!`]'s allocation-free literal fast path.
64///
65/// Aborts compilation if `message` contains `{` or `}`. Called only from a
66/// `const` item inside `msg!`, so a violation is a build failure rather than a
67/// log line with visible braces.
68///
69/// Not intended to be called directly; it is public only because `msg!`
70/// expands at the call site.
71pub const fn assert_literal_has_no_format_placeholder(message: &str) {
72    let bytes = message.as_bytes();
73    let mut index = 0;
74    while index < bytes.len() {
75        if bytes[index] == b'{' || bytes[index] == b'}' {
76            panic!(
77                "this `msg!` literal contains `{{` or `}}`, but the single-literal form is \
78                 allocation-free and does not run `format!`, so the braces would be logged \
79                 verbatim. Pass the values as arguments instead: `msg!(\"fee={{}}\", fee)`. \
80                 For a literal brace, use the argument form as well."
81            );
82        }
83        index += 1;
84    }
85}
86
87#[cfg(target_os = "solana")]
88pub mod syscalls;
89
90/// Print a string to the log.
91#[inline]
92pub fn rlo_log(message: &str) {
93    #[cfg(target_os = "solana")]
94    unsafe {
95        syscalls::rlo_log_(message.as_ptr(), message.len() as u64);
96    }
97
98    #[cfg(not(target_os = "solana"))]
99    println!("{message}");
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    /// `const` context, so these are checked at compile time: were the guard to
107    /// reject any of them, this module would fail to build.
108    #[test]
109    fn guard_accepts_literals_without_format_placeholders() {
110        const _: () = assert_literal_has_no_format_placeholder("");
111        const _: () = assert_literal_has_no_format_placeholder("verifying multisig");
112        // Braces belonging to a lexer escape, not to `format!` — the guard sees
113        // the cooked string, so this is an em dash and passes.
114        const _: () = assert_literal_has_no_format_placeholder("reconnecting \u{2014} retrying");
115        const _: () = assert_literal_has_no_format_placeholder("percent % and backslash \\ pass");
116    }
117
118    /// The rejecting direction is covered by the `compile_fail` doctest on
119    /// [`msg!`]; a runtime call here would abort the test process rather than
120    /// fail an assertion, since the guard panics.
121    #[test]
122    fn fast_path_and_formatting_path_both_log() {
123        let err = "not enough signers";
124        let owned = String::from("owned");
125        msg!("verifying multisig");
126        msg!("multisig failed: {}", err);
127        msg!(&owned);
128        msg!("reconnecting \u{2014} retrying");
129    }
130}