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}