pinocchio_log/
logger.rs

1use core::{
2    cmp::min, mem::MaybeUninit, ops::Deref, ptr::copy_nonoverlapping, slice::from_raw_parts,
3};
4
5#[cfg(all(target_os = "solana", not(target_feature = "static-syscalls")))]
6mod syscalls {
7    // Syscalls provided by the SVM runtime (SBPFv0, SBPFv1 and SBPFv2).
8    extern "C" {
9        pub fn sol_log_(message: *const u8, len: u64);
10
11        pub fn sol_memcpy_(dst: *mut u8, src: *const u8, n: u64);
12
13        pub fn sol_memset_(s: *mut u8, c: u8, n: u64);
14
15        pub fn sol_remaining_compute_units() -> u64;
16    }
17}
18
19#[cfg(all(target_os = "solana", target_feature = "static-syscalls"))]
20mod syscalls {
21    // Syscalls provided by the SVM runtime (SBPFv3 and newer).
22    pub(crate) fn sol_log_(message: *const u8, length: u64) {
23        let syscall: extern "C" fn(*const u8, u64) = unsafe { core::mem::transmute(544561597u64) }; // murmur32 hash of "sol_log_"
24        syscall(message, length)
25    }
26
27    pub(crate) fn sol_memcpy_(dest: *mut u8, src: *const u8, n: u64) {
28        let syscall: extern "C" fn(*mut u8, *const u8, u64) =
29            unsafe { core::mem::transmute(1904002211u64) }; // murmur32 hash of "sol_memcpy_"
30        syscall(dest, src, n)
31    }
32
33    pub(crate) fn sol_memset_(s: *mut u8, c: u8, n: u64) {
34        let syscall: extern "C" fn(*mut u8, u8, u64) =
35            unsafe { core::mem::transmute(930151202u64) }; // murmur32 hash of "sol_memset_"
36        syscall(s, c, n)
37    }
38
39    pub(crate) fn sol_remaining_compute_units() -> u64 {
40        let syscall: extern "C" fn() -> u64 = unsafe { core::mem::transmute(3991886574u64) }; // murmur32 hash of "sol_remaining_compute_units"
41        syscall()
42    }
43}
44
45#[cfg(not(target_os = "solana"))]
46extern crate std;
47
48/// Bytes for a truncated `str` log message.
49const TRUNCATED_SLICE: [u8; 3] = [b'.', b'.', b'.'];
50
51/// Byte representing a truncated log.
52const TRUNCATED: u8 = b'@';
53
54/// An uninitialized byte.
55const UNINIT_BYTE: MaybeUninit<u8> = MaybeUninit::uninit();
56
57/// Logger to efficiently format log messages.
58///
59/// The logger is a fixed size buffer that can be used to format log messages
60/// before sending them to the log output. Any type that implements the `Log`
61/// trait can be appended to the logger.
62pub struct Logger<const BUFFER: usize> {
63    // Byte buffer to store the log message.
64    buffer: [MaybeUninit<u8>; BUFFER],
65
66    // Length of the log message.
67    len: usize,
68}
69
70impl<const BUFFER: usize> Default for Logger<BUFFER> {
71    #[inline]
72    fn default() -> Self {
73        Self {
74            buffer: [UNINIT_BYTE; BUFFER],
75            len: 0,
76        }
77    }
78}
79
80impl<const BUFFER: usize> Deref for Logger<BUFFER> {
81    type Target = [u8];
82
83    fn deref(&self) -> &Self::Target {
84        // SAFETY: the slice is created from the buffer up to the length
85        // of the message.
86        unsafe { from_raw_parts(self.buffer.as_ptr() as *const _, self.len) }
87    }
88}
89
90impl<const BUFFER: usize> Logger<BUFFER> {
91    /// Append a value to the logger.
92    #[inline(always)]
93    pub fn append<T: Log>(&mut self, value: T) -> &mut Self {
94        self.append_with_args(value, &[]);
95        self
96    }
97
98    /// Append a value to the logger with formatting arguments.
99    #[inline]
100    pub fn append_with_args<T: Log>(&mut self, value: T, args: &[Argument]) -> &mut Self {
101        if self.is_full() {
102            if BUFFER > 0 {
103                // SAFETY: the buffer is checked to be full.
104                unsafe {
105                    let last = self.buffer.get_unchecked_mut(BUFFER - 1);
106                    last.write(TRUNCATED);
107                }
108            }
109        } else {
110            self.len += value.write_with_args(&mut self.buffer[self.len..], args);
111
112            if self.len > BUFFER {
113                // Indicates that the buffer is full.
114                self.len = BUFFER;
115                // SAFETY: the buffer length is checked to greater than `BUFFER`.
116                unsafe {
117                    let last = self.buffer.get_unchecked_mut(BUFFER - 1);
118                    last.write(TRUNCATED);
119                }
120            }
121        }
122
123        self
124    }
125
126    /// Log the message in the buffer.
127    #[inline(always)]
128    pub fn log(&self) {
129        log_message(self);
130    }
131
132    /// Clear the message buffer.
133    #[inline(always)]
134    pub fn clear(&mut self) {
135        self.len = 0;
136    }
137
138    /// Check whether the log buffer is at the maximum length or not.
139    #[inline(always)]
140    pub fn is_full(&self) -> bool {
141        self.len == BUFFER
142    }
143
144    /// Get the remaining space in the log buffer.
145    #[inline(always)]
146    pub fn remaining(&self) -> usize {
147        BUFFER - self.len
148    }
149}
150
151/// Log a message.
152#[inline(always)]
153pub fn log_message(message: &[u8]) {
154    #[cfg(target_os = "solana")]
155    // SAFETY: the message is always a valid pointer to a slice of bytes
156    // and `sol_log_` is a syscall.
157    unsafe {
158        syscalls::sol_log_(message.as_ptr(), message.len() as u64);
159    }
160    #[cfg(not(target_os = "solana"))]
161    {
162        let message = core::str::from_utf8(message).unwrap();
163        std::println!("{}", message);
164    }
165}
166
167/// Remaining CUs.
168#[inline(always)]
169pub fn remaining_compute_units() -> u64 {
170    #[cfg(target_os = "solana")]
171    // SAFETY: `sol_remaining_compute_units` is a syscall that returns the remaining compute units.
172    unsafe {
173        syscalls::sol_remaining_compute_units()
174    }
175    #[cfg(not(target_os = "solana"))]
176    core::hint::black_box(0u64)
177}
178
179/// Formatting arguments.
180///
181/// Arguments can be used to specify additional formatting options for the log message.
182/// Note that types might not support all arguments.
183#[non_exhaustive]
184pub enum Argument {
185    /// Number of decimal places to display for numbers.
186    ///
187    /// This is only applicable for numeric types.
188    Precision(u8),
189
190    /// Truncate the output at the end when the specified maximum number of characters
191    /// is exceeded.
192    ///
193    /// This is only applicable for `str` types.
194    TruncateEnd(usize),
195
196    /// Truncate the output at the start when the specified maximum number of characters
197    /// is exceeded.
198    ///
199    /// This is only applicable for `str` types.
200    TruncateStart(usize),
201}
202
203/// Trait to specify the log behavior for a type.
204///
205/// # Safety
206///
207/// The implementation must ensure that the value returned by any of the methods correctly
208/// reflects the actual number of bytes written to the buffer. Returning a value greater
209/// than the number of bytes written to the buffer will result in undefined behavior, since
210/// it will lead to reading uninitialized memory from the buffer.
211pub unsafe trait Log {
212    #[inline(always)]
213    fn debug(&self, buffer: &mut [MaybeUninit<u8>]) -> usize {
214        self.debug_with_args(buffer, &[])
215    }
216
217    #[inline(always)]
218    fn debug_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
219        self.write_with_args(buffer, args)
220    }
221
222    #[inline(always)]
223    fn write(&self, buffer: &mut [MaybeUninit<u8>]) -> usize {
224        self.write_with_args(buffer, &[])
225    }
226
227    fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], parameters: &[Argument]) -> usize;
228}
229
230/// Implement the log trait for unsigned integer types.
231macro_rules! impl_log_for_unsigned_integer {
232    ( $type:tt ) => {
233        unsafe impl Log for $type {
234            #[inline]
235            fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
236                // The maximum number of digits that the type can have.
237                const MAX_DIGITS: usize = $type::MAX.ilog10() as usize + 1;
238
239                if buffer.is_empty() {
240                    return 0;
241                }
242
243                match *self {
244                    // Handle zero as a special case.
245                    0 => {
246                        // SAFETY: the buffer is checked to be non-empty.
247                        unsafe {
248                            buffer.get_unchecked_mut(0).write(b'0');
249                        }
250                        1
251                    }
252                    mut value => {
253                        let mut digits = [UNINIT_BYTE; MAX_DIGITS];
254                        let mut offset = MAX_DIGITS;
255
256                        while value > 0 {
257                            let remainder = value % 10;
258                            value /= 10;
259                            offset -= 1;
260                            // SAFETY: the offset is always within the bounds of the array since
261                            // `offset` is initialized with the maximum number of digits that
262                            // the type can have and decremented on each iteration; `remainder`
263                            // is always less than 10.
264                            unsafe {
265                                digits
266                                    .get_unchecked_mut(offset)
267                                    .write(b'0' + remainder as u8);
268                            }
269                        }
270
271                        let precision = if let Some(Argument::Precision(p)) = args
272                            .iter()
273                            .find(|arg| matches!(arg, Argument::Precision(_)))
274                        {
275                            *p as usize
276                        } else {
277                            0
278                        };
279
280                        let written = MAX_DIGITS - offset;
281                        let length = buffer.len();
282
283                        // Space required with the specified precision. We might need
284                        // to add leading zeros and a decimal point, but this is only
285                        // if the precision is greater than zero.
286                        let required = match precision {
287                            0 => written,
288                            // decimal point
289                            _precision if precision < written => written + 1,
290                            // decimal point + one leading zero
291                            _ => precision + 2,
292                        };
293                        // Determines whether the value will be truncated or not.
294                        let is_truncated = required > length;
295                        // Cap the number of digits to write to the buffer length.
296                        let digits_to_write = min(MAX_DIGITS - offset, length);
297
298                        // SAFETY: the length of both `digits` and `buffer` arrays are guaranteed
299                        // to be within bounds and the `digits_to_write` value is capped to the
300                        // length of the `buffer`.
301                        unsafe {
302                            let source = digits.as_ptr().add(offset);
303                            let ptr = buffer.as_mut_ptr();
304
305                            // Copy the number to the buffer if no precision is specified.
306                            if precision == 0 {
307                                #[cfg(target_os = "solana")]
308                                syscalls::sol_memcpy_(
309                                    ptr as *mut _,
310                                    source as *const _,
311                                    digits_to_write as u64,
312                                );
313                                #[cfg(not(target_os = "solana"))]
314                                copy_nonoverlapping(source, ptr, digits_to_write);
315                            }
316                            // If padding is needed to satisfy the precision, add leading zeros
317                            // and a decimal point.
318                            else if precision >= digits_to_write {
319                                // Prefix.
320                                (ptr as *mut u8).write(b'0');
321
322                                if length > 2 {
323                                    (ptr.add(1) as *mut u8).write(b'.');
324                                    let padding = min(length - 2, precision - digits_to_write);
325
326                                    // Precision padding.
327                                    #[cfg(target_os = "solana")]
328                                    syscalls::sol_memset_(
329                                        ptr.add(2) as *mut _,
330                                        b'0',
331                                        padding as u64,
332                                    );
333                                    #[cfg(not(target_os = "solana"))]
334                                    (ptr.add(2) as *mut u8).write_bytes(b'0', padding);
335
336                                    let current = 2 + padding;
337
338                                    // If there is still space, copy (part of) the number.
339                                    if current < length {
340                                        let remaining = min(digits_to_write, length - current);
341
342                                        // Number part.
343                                        #[cfg(target_os = "solana")]
344                                        syscalls::sol_memcpy_(
345                                            ptr.add(current) as *mut _,
346                                            source as *const _,
347                                            remaining as u64,
348                                        );
349                                        #[cfg(not(target_os = "solana"))]
350                                        copy_nonoverlapping(source, ptr.add(current), remaining);
351                                    }
352                                }
353                            }
354                            // No padding is needed, calculate the integer and fractional
355                            // parts and add a decimal point.
356                            else {
357                                let integer_part = digits_to_write - precision;
358
359                                // Integer part of the number.
360                                #[cfg(target_os = "solana")]
361                                syscalls::sol_memcpy_(
362                                    ptr as *mut _,
363                                    source as *const _,
364                                    integer_part as u64,
365                                );
366                                #[cfg(not(target_os = "solana"))]
367                                copy_nonoverlapping(source, ptr, integer_part);
368
369                                // Decimal point.
370                                (ptr.add(integer_part) as *mut u8).write(b'.');
371                                let current = integer_part + 1;
372
373                                // If there is still space, copy (part of) the remaining.
374                                if current < length {
375                                    let remaining = min(precision, length - current);
376
377                                    // Fractional part of the number.
378                                    #[cfg(target_os = "solana")]
379                                    syscalls::sol_memcpy_(
380                                        ptr.add(current) as *mut _,
381                                        source.add(integer_part) as *const _,
382                                        remaining as u64,
383                                    );
384                                    #[cfg(not(target_os = "solana"))]
385                                    copy_nonoverlapping(
386                                        source.add(integer_part),
387                                        ptr.add(current),
388                                        remaining,
389                                    );
390                                }
391                            }
392                        }
393
394                        let written = min(required, length);
395
396                        // There might not have been space.
397                        if is_truncated {
398                            // SAFETY: `written` is capped to the length of the buffer and
399                            // the required length (`required` is always greater than zero);
400                            // `buffer` is guaranteed  to have a length of at least 1.
401                            unsafe {
402                                buffer.get_unchecked_mut(written - 1).write(TRUNCATED);
403                            }
404                        }
405
406                        written
407                    }
408                }
409            }
410        }
411    };
412}
413
414// Supported unsigned integer types.
415impl_log_for_unsigned_integer!(u8);
416impl_log_for_unsigned_integer!(u16);
417impl_log_for_unsigned_integer!(u32);
418impl_log_for_unsigned_integer!(u64);
419impl_log_for_unsigned_integer!(u128);
420impl_log_for_unsigned_integer!(usize);
421
422/// Implement the log trait for the signed integer types.
423macro_rules! impl_log_for_signed {
424    ( $type:tt ) => {
425        unsafe impl Log for $type {
426            #[inline]
427            fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
428                if buffer.is_empty() {
429                    return 0;
430                }
431
432                match *self {
433                    // Handle zero as a special case.
434                    0 => {
435                        // SAFETY: the buffer is checked to be non-empty.
436                        unsafe {
437                            buffer.get_unchecked_mut(0).write(b'0');
438                        }
439                        1
440                    }
441                    value => {
442                        let mut prefix = 0;
443
444                        if *self < 0 {
445                            if buffer.len() == 1 {
446                                // SAFETY: the buffer is checked to be non-empty.
447                                unsafe {
448                                    buffer.get_unchecked_mut(0).write(TRUNCATED);
449                                }
450                                // There is no space for the number, so just return.
451                                return 1;
452                            }
453
454                            // SAFETY: the buffer is checked to be non-empty.
455                            unsafe {
456                                buffer.get_unchecked_mut(0).write(b'-');
457                            }
458                            prefix += 1;
459                        };
460
461                        prefix
462                            + $type::unsigned_abs(value)
463                                .write_with_args(&mut buffer[prefix..], args)
464                    }
465                }
466            }
467        }
468    };
469}
470
471// Supported signed integer types.
472impl_log_for_signed!(i8);
473impl_log_for_signed!(i16);
474impl_log_for_signed!(i32);
475impl_log_for_signed!(i64);
476impl_log_for_signed!(i128);
477impl_log_for_signed!(isize);
478
479/// Implement the log trait for the `&str` type.
480unsafe impl Log for &str {
481    #[inline]
482    fn debug_with_args(&self, buffer: &mut [MaybeUninit<u8>], _args: &[Argument]) -> usize {
483        if buffer.is_empty() {
484            return 0;
485        }
486        // SAFETY: the buffer is checked to be non-empty.
487        unsafe {
488            buffer.get_unchecked_mut(0).write(b'"');
489        }
490
491        let mut offset = 1;
492        offset += self.write(&mut buffer[offset..]);
493
494        match buffer.len() - offset {
495            0 => {
496                // SAFETY: the buffer is guaranteed to be within `offset` bounds.
497                unsafe {
498                    buffer.get_unchecked_mut(offset - 1).write(TRUNCATED);
499                }
500            }
501            _ => {
502                // SAFETY: the buffer is guaranteed to be within `offset` bounds.
503                unsafe {
504                    buffer.get_unchecked_mut(offset).write(b'"');
505                }
506                offset += 1;
507            }
508        }
509
510        offset
511    }
512
513    #[inline]
514    fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
515        // There are 4 different cases to consider:
516        //
517        // 1. No arguments were provided, so the entire string is copied to the buffer if it fits;
518        //    otherwise, the buffer is filled as many characters as possible and the last character
519        //    is set to `TRUNCATED`.
520        //
521        // Then cases only applicable when precision formatting is used:
522        //
523        // 2. The buffer is large enough to hold the entire string: the string is copied to the
524        //    buffer and the length of the string is returned.
525        //
526        // 3. The buffer is smaller than the string, but large enough to hold the prefix and part
527        //    of the string: the prefix and part of the string are copied to the buffer. The length
528        //    returned is `prefix` + number of characters copied.
529        //
530        // 4. The buffer is smaller than the string and the prefix: the buffer is filled with the
531        //    prefix and the last character is set to `TRUNCATED`. The length returned is the length
532        //    of the buffer.
533        //
534        // The length of the message is determined by whether a precision formatting was used or
535        // not, and the length of the buffer.
536
537        let (size, truncate_end) = match args
538            .iter()
539            .find(|arg| matches!(arg, Argument::TruncateEnd(_) | Argument::TruncateStart(_)))
540        {
541            Some(Argument::TruncateEnd(size)) => (*size, Some(true)),
542            Some(Argument::TruncateStart(size)) => (*size, Some(false)),
543            _ => (buffer.len(), None),
544        };
545
546        // Handles the write of the `str` to the buffer.
547        //
548        // - `destination`: pointer to the buffer where the string will be copied. This is always
549        //   the a pointer to the log buffer, but it could de in a different offset depending on
550        //   whether the truncated slice is copied or not.
551        //
552        // - `source`: pointer to the string that will be copied. This could either be a pointer
553        //   to the `str` itself or `TRUNCATE_SLICE`).
554        //
555        // - `length_to_write`: number of characters from `source` that will be copied.
556        //
557        // - `written_truncated_slice_length`: number of characters copied from `TRUNCATED_SLICE`.
558        //   This is used to determine the total number of characters copied to the buffer.
559        //
560        // - `truncated`: indicates whether the `str` was truncated or not. This is used to set
561        //   the last character of the buffer to `TRUNCATED`.
562        let (destination, source, length_to_write, written_truncated_slice_length, truncated) =
563            // No truncate arguments were provided, so the entire `str` is copied to the buffer
564            // if it fits; otherwise indicates that the `str` was truncated.
565            if truncate_end.is_none() {
566                let length = min(size, self.len());
567                (
568                    buffer.as_mut_ptr(),
569                    self.as_ptr(),
570                    length,
571                    0,
572                    length != self.len(),
573                )
574            } else {
575                let max_length = min(size, buffer.len());
576                let ptr = buffer.as_mut_ptr();
577
578                // The buffer is large enough to hold the entire `str`, so no need to use the
579                // truncate args.
580                if max_length >= self.len() {
581                    (ptr, self.as_ptr(), self.len(), 0, false)
582                }
583                // The buffer is large enough to hold the truncated slice and part of the string.
584                // In this case, the characters from the start or end of the string are copied to
585                // the buffer together with the `TRUNCATED_SLICE`.
586                else if max_length > TRUNCATED_SLICE.len() {
587                    // Number of characters that can be copied to the buffer.
588                    let length = max_length - TRUNCATED_SLICE.len();
589                    // SAFETY: the `ptr` is always within `length` bounds.
590                    unsafe {
591                        let (offset, source, destination) = if truncate_end == Some(true) {
592                            (length, self.as_ptr(), ptr)
593                        } else {
594                            (
595                                0,
596                                self.as_ptr().add(self.len() - length),
597                                ptr.add(TRUNCATED_SLICE.len()),
598                            )
599                        };
600                        // Copy the truncated slice to the buffer.
601                        copy_nonoverlapping(
602                            TRUNCATED_SLICE.as_ptr(),
603                            ptr.add(offset) as *mut _,
604                            TRUNCATED_SLICE.len(),
605                        );
606
607                        (destination, source, length, TRUNCATED_SLICE.len(), false)
608                    }
609                }
610                // The buffer is smaller than the `PREFIX`: the buffer is filled with the `PREFIX`
611                // and the last character is set to `TRUNCATED`.
612                else {
613                    (ptr, TRUNCATED_SLICE.as_ptr(), max_length, 0, true)
614                }
615            };
616
617        if length_to_write > 0 {
618            // SAFETY: the `destination` is always within `length_to_write` bounds.
619            unsafe {
620                #[cfg(target_os = "solana")]
621                syscalls::sol_memcpy_(
622                    destination as *mut _,
623                    source as *const _,
624                    length_to_write as u64,
625                );
626                #[cfg(not(target_os = "solana"))]
627                copy_nonoverlapping(source, destination as *mut _, length_to_write);
628            }
629
630            // There might not have been space for all the value.
631            if truncated {
632                // SAFETY: the `destination` is always within `length_to_write` bounds.
633                unsafe {
634                    let last = buffer.get_unchecked_mut(length_to_write - 1);
635                    last.write(TRUNCATED);
636                }
637            }
638        }
639
640        written_truncated_slice_length + length_to_write
641    }
642}
643
644/// Implement the log trait for the slice type.
645macro_rules! impl_log_for_slice {
646    ( [$type:ident] ) => {
647        unsafe impl<$type> Log for &[$type]
648        where
649            $type: Log
650        {
651            impl_log_for_slice!(@generate_write);
652        }
653    };
654    ( [$type:ident; $size:ident] ) => {
655        unsafe impl<$type, const $size: usize> Log for &[$type; $size]
656        where
657            $type: Log
658        {
659            impl_log_for_slice!(@generate_write);
660        }
661    };
662    ( @generate_write ) => {
663        #[inline]
664        fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], _args: &[Argument]) -> usize {
665            if buffer.is_empty() {
666                return 0;
667            }
668
669            // Size of the buffer.
670            let length = buffer.len();
671            // SAFETY: the buffer is checked to be non-empty.
672            unsafe {
673                buffer.get_unchecked_mut(0).write(b'[');
674            }
675
676            let mut offset = 1;
677
678            for value in self.iter() {
679                if offset >= length {
680                    // SAFETY: the buffer is checked to be non-empty and the `length`
681                    // represents the buffer length.
682                    unsafe {
683                        buffer.get_unchecked_mut(length - 1).write(TRUNCATED);
684                    }
685                    offset = length;
686                    break;
687                }
688
689                if offset > 1 {
690                    if offset + 2 >= length {
691                        // SAFETY: the buffer is checked to be non-empty and the `length`
692                        // represents the buffer length.
693                        unsafe {
694                            buffer.get_unchecked_mut(length - 1).write(TRUNCATED);
695                        }
696                        offset = length;
697                        break;
698                    } else {
699                        // SAFETY: the buffer is checked to be non-empty and the `offset`
700                        // is smaller than the buffer length.
701                        unsafe {
702                            buffer.get_unchecked_mut(offset).write(b',');
703                            buffer.get_unchecked_mut(offset + 1).write(b' ');
704                        }
705                        offset += 2;
706                    }
707                }
708
709                offset += value.debug(&mut buffer[offset..]);
710            }
711
712            if offset < length {
713                // SAFETY: the buffer is checked to be non-empty and the `offset`
714                // is smaller than the buffer length.
715                unsafe {
716                    buffer.get_unchecked_mut(offset).write(b']');
717                }
718                offset += 1;
719            }
720
721            offset
722        }
723    };
724}
725
726// Supported slice types.
727impl_log_for_slice!([T]);
728impl_log_for_slice!([T; N]);
729
730/// Implement the log trait for the `bool` type.
731unsafe impl Log for bool {
732    #[inline]
733    fn debug_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
734        let value = if *self { "true" } else { "false" };
735        value.debug_with_args(buffer, args)
736    }
737
738    #[inline]
739    fn write_with_args(&self, buffer: &mut [MaybeUninit<u8>], args: &[Argument]) -> usize {
740        let value = if *self { "true" } else { "false" };
741        value.write_with_args(buffer, args)
742    }
743}