ohos_hilog/
lib.rs

1// Copyright 2024 The ohos_hilog Developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! A logger which writes to ohos output.
9//!
10//! ## Example
11//!
12//! ```
13//! #[macro_use] extern crate log;
14//! extern crate ohos_hilog;
15//!
16//! use log::LevelFilter;
17//! use ohos_hilog::Config;
18//!
19//! /// Ohos code may not have obvious "main", this is just an example.
20//! fn main() {
21//!     ohos_hilog::init_once(
22//!         Config::default().with_max_level(LevelFilter::Trace),
23//!     );
24//!
25//!     debug!("this is a debug {}", "message");
26//!     error!("this is printed by default");
27//! }
28//! ```
29//!
30//! ## Example with module path filter
31//!
32//! It is possible to limit log messages to output from a specific crate,
33//! and override the logcat tag name (by default, the crate name is used):
34//!
35//! ```
36//! #[macro_use] extern crate log;
37//! extern crate ohos_hilog;
38//!
39//! use log::LevelFilter;
40//! use ohos_hilog::{Config,FilterBuilder};
41//!
42//! fn main() {
43//!     ohos_hilog::init_once(
44//!         Config::default()
45//!             .with_max_level(LevelFilter::Trace)
46//!             .with_tag("mytag")
47//!             .with_filter(FilterBuilder::new().parse("debug,hello::crate=trace").build()),
48//!     );
49//!
50//!     // ..
51//! }
52//! ```
53//!
54//! ## Example with a custom log formatter
55//!
56//! ```
57//! use ohos_hilog::Config;
58//!
59//! ohos_hilog::init_once(
60//!     Config::default()
61//!         .with_max_level(log::LevelFilter::Trace)
62//!         .format(|f, record| write!(f, "my_app: {}", record.args()))
63//! )
64//! ```
65
66#[cfg(all(target_os = "linux", target_env = "ohos"))]
67extern crate ohos_hilog_sys as log_ffi;
68extern crate once_cell;
69use once_cell::sync::OnceCell;
70#[macro_use]
71extern crate log;
72
73extern crate env_logger;
74
75use log::{Level, LevelFilter, Log, Metadata, Record};
76#[cfg(all(target_os = "linux", target_env = "ohos"))]
77use log_ffi::LogType;
78#[cfg(all(target_os = "linux", target_env = "ohos"))]
79use log_ffi::LogLevel;
80use std::ffi::{CStr, CString};
81use std::fmt;
82use std::mem::{self, MaybeUninit};
83use std::ptr;
84
85pub use env_logger::filter::{Builder as FilterBuilder, Filter};
86pub use env_logger::fmt::Formatter;
87
88pub(crate) type FormatFn = Box<dyn Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send>;
89
90/// Outputs log to Ohos system.
91#[cfg(all(target_os = "linux", target_env = "ohos"))]
92fn ohos_log(
93    buf_id: Option<LogType>,
94    level: LogLevel,
95    tag: &CStr,
96    msg: &CStr,
97) {
98    if let Some(buf_id) = buf_id {
99        unsafe {
100            log_ffi::OH_LOG_Print(
101                buf_id as LogType,
102                level,
103                0 as log_ffi::c_uint,
104                tag.as_ptr() as *const log_ffi::c_char,
105                msg.as_ptr() as *const log_ffi::c_char,
106            );
107        };
108    } else {
109        unsafe {
110            log_ffi::OH_LOG_Print(
111                log_ffi::LogType::LOG_APP,
112                level,
113                0 as log_ffi::c_uint,
114                tag.as_ptr() as *const log_ffi::c_char,
115                msg.as_ptr() as *const log_ffi::c_char,
116            );
117        };
118    }
119}
120
121/// Dummy output placeholder for tests.
122#[cfg(not(all(target_os = "linux", target_env = "ohos")))]
123fn ohos_log(_buf_id: Option<u32>, _level: Level, _tag: &CStr, _msg: &CStr) {}
124
125/// Underlying ohos logger backend
126pub struct OhosLogger {
127    config: OnceCell<Config>,
128}
129
130impl OhosLogger {
131    /// Create new logger instance from config
132    pub fn new(config: Config) -> OhosLogger {
133        OhosLogger {
134            config: OnceCell::from(config),
135        }
136    }
137
138    fn config(&self) -> &Config {
139        self.config.get_or_init(Config::default)
140    }
141}
142
143static OHOS_LOGGER: OnceCell<OhosLogger> = OnceCell::new();
144
145const LOGGING_TAG_MAX_LEN: usize = 23;
146const LOGGING_MSG_MAX_LEN: usize = 4000;
147
148impl Default for OhosLogger {
149    /// Create a new logger with default config
150    fn default() -> OhosLogger {
151        OhosLogger {
152            config: OnceCell::from(Config::default()),
153        }
154    }
155}
156
157impl Log for OhosLogger {
158    fn enabled(&self, metadata: &Metadata) -> bool {
159        let config = self.config();
160        // todo: consider OH_LOG_IsLoggable.
161        metadata.level() <= config.log_level.unwrap_or_else(log::max_level)
162    }
163
164    fn log(&self, record: &Record) {
165        let config = self.config();
166
167        if !self.enabled(record.metadata()) {
168            return;
169        }
170
171        // this also checks the level, but only if a filter was
172        // installed.
173        if !config.filter_matches(record) {
174            return;
175        }
176
177        // tag must not exceed LOGGING_TAG_MAX_LEN
178        let mut tag_bytes: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
179
180        let module_path = record.module_path().unwrap_or_default().to_owned();
181
182        // If no tag was specified, use module name
183        let custom_tag = &config.tag;
184        let tag = custom_tag
185            .as_ref()
186            .map(|s| s.as_bytes())
187            .unwrap_or_else(|| module_path.as_bytes());
188
189        // truncate the tag here to fit into LOGGING_TAG_MAX_LEN
190        self.fill_tag_bytes(&mut tag_bytes, tag);
191        // use stack array as C string
192        let tag: &CStr = unsafe { CStr::from_ptr(mem::transmute(tag_bytes.as_ptr())) };
193
194        // message must not exceed LOGGING_MSG_MAX_LEN
195        // therefore split log message into multiple log calls
196        let mut writer = PlatformLogWriter::new(config.buf_id, record.level(), tag);
197
198        // If a custom tag is used, add the module path to the message.
199        // Use PlatformLogWriter to output chunks if they exceed max size.
200        let _ = match (custom_tag, &config.custom_format) {
201            (_, Some(format)) => format(&mut writer, record),
202            (Some(_), _) => fmt::write(
203                &mut writer,
204                format_args!("{}: {}", module_path, *record.args()),
205            ),
206            _ => fmt::write(&mut writer, *record.args()),
207        };
208
209        // output the remaining message (this would usually be the most common case)
210        writer.flush();
211    }
212
213    fn flush(&self) {}
214}
215
216impl OhosLogger {
217    fn fill_tag_bytes(&self, array: &mut [MaybeUninit<u8>], tag: &[u8]) {
218        if tag.len() > LOGGING_TAG_MAX_LEN {
219            for (input, output) in tag
220                .iter()
221                .take(LOGGING_TAG_MAX_LEN - 2)
222                .chain(b"..\0".iter())
223                .zip(array.iter_mut())
224            {
225                output.write(*input);
226            }
227        } else {
228            for (input, output) in tag.iter().chain(b"\0".iter()).zip(array.iter_mut()) {
229                output.write(*input);
230            }
231        }
232    }
233}
234
235/// Filter for ohos logger.
236#[derive(Default)]
237pub struct Config {
238    log_level: Option<LevelFilter>,
239    #[cfg(all(target_os = "linux", target_env = "ohos"))]
240    buf_id: Option<LogType>,
241    #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
242    buf_id: Option<u32>,
243    filter: Option<env_logger::filter::Filter>,
244    tag: Option<CString>,
245    custom_format: Option<FormatFn>,
246}
247
248impl Config {
249    /// Changes the maximum log level.
250    ///
251    /// Note, that `Trace` is the maximum level, because it provides the
252    /// maximum amount of detail in the emitted logs.
253    ///
254    /// If `Off` level is provided, then nothing is logged at all.
255    ///
256    /// [`log::max_level()`] is considered as the default level.
257    pub fn with_max_level(mut self, level: LevelFilter) -> Self {
258        self.log_level = Some(level);
259        self
260    }
261
262    /// Changes the Ohos logging system buffer to be used.
263    ///
264    /// By default, logs are sent to the [`Main`] log. Other logging buffers may
265    /// only be accessible to certain processes.
266    ///
267    /// [`Main`]: LogType::LOG_APP
268    #[cfg(all(target_os = "linux", target_env = "ohos"))]
269    pub fn with_log_buffer(mut self, buf_id: LogType) -> Self {
270        self.buf_id = Some(buf_id);
271        self
272    }
273    #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
274    pub fn with_log_buffer(mut self, buf_id: u32) -> Self {
275        self.buf_id = Some(buf_id);
276        self
277    }
278
279    fn filter_matches(&self, record: &Record) -> bool {
280        if let Some(ref filter) = self.filter {
281            filter.matches(record)
282        } else {
283            true
284        }
285    }
286
287    pub fn with_filter(mut self, filter: env_logger::filter::Filter) -> Self {
288        self.filter = Some(filter);
289        self
290    }
291
292    pub fn with_tag<S: Into<Vec<u8>>>(mut self, tag: S) -> Self {
293        self.tag = Some(CString::new(tag).expect("Can't convert tag to CString"));
294        self
295    }
296
297    /// Sets the format function for formatting the log output.
298    /// ```
299    /// # use ohos_hilog::Config;
300    /// ohos_hilog::init_once(
301    ///     Config::default()
302    ///         .with_max_level(log::LevelFilter::Trace)
303    ///         .format(|f, record| write!(f, "my_app: {}", record.args()))
304    /// )
305    /// ```
306    pub fn format<F>(mut self, format: F) -> Self
307    where
308        F: Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send + 'static,
309    {
310        self.custom_format = Some(Box::new(format));
311        self
312    }
313}
314
315pub struct PlatformLogWriter<'a> {
316    #[cfg(all(target_os = "linux", target_env = "ohos"))]
317    level: LogLevel,
318    #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
319    level: Level,
320    #[cfg(all(target_os = "linux", target_env = "ohos"))]
321    buf_id: Option<LogType>,
322    #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
323    buf_id: Option<u32>,
324    len: usize,
325    last_newline_index: usize,
326    tag: &'a CStr,
327    buffer: [MaybeUninit<u8>; LOGGING_MSG_MAX_LEN + 1],
328}
329
330impl<'a> PlatformLogWriter<'a> {
331    #[cfg(all(target_os = "linux", target_env = "ohos"))]
332    pub fn new_with_level(
333        buf_id: Option<LogType>,
334        level: LogLevel,
335        tag: &CStr,
336    ) -> PlatformLogWriter<'_> {
337        #[allow(deprecated)] // created an issue #35 for this
338        PlatformLogWriter {
339            level,
340            buf_id: buf_id,
341            len: 0,
342            last_newline_index: 0,
343            tag,
344            buffer: uninit_array(),
345        }
346    }
347
348    #[cfg(all(target_os = "linux", target_env = "ohos"))]
349    pub fn new(buf_id: Option<LogType>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
350        PlatformLogWriter::new_with_level(
351            buf_id,
352            match level {
353                Level::Warn => LogLevel::WARN,
354                Level::Info => LogLevel::INFO,
355                Level::Debug => LogLevel::DEBUG,
356                Level::Error => LogLevel::ERROR,
357                Level::Trace => LogLevel::DEBUG,
358            },
359            tag,
360        )
361    }
362
363    #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
364    pub fn new(buf_id: Option<u32>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
365        #[allow(deprecated)] // created an issue #35 for this
366        PlatformLogWriter {
367            level: level,
368            buf_id,
369            len: 0,
370            last_newline_index: 0,
371            tag,
372            buffer: uninit_array(),
373        }
374    }
375
376    /// Flush some bytes to ohos logger.
377    ///
378    /// If there is a newline, flush up to it.
379    /// If ther was no newline, flush all.
380    ///
381    /// Not guaranteed to flush everything.
382    fn temporal_flush(&mut self) {
383        let total_len = self.len;
384
385        if total_len == 0 {
386            return;
387        }
388
389        if self.last_newline_index > 0 {
390            let copy_from_index = self.last_newline_index;
391            let remaining_chunk_len = total_len - copy_from_index;
392
393            self.output_specified_len(copy_from_index);
394            self.copy_bytes_to_start(copy_from_index, remaining_chunk_len);
395            self.len = remaining_chunk_len;
396        } else {
397            self.output_specified_len(total_len);
398            self.len = 0;
399        }
400        self.last_newline_index = 0;
401    }
402
403    /// Flush everything remaining to ohos logger.
404    pub fn flush(&mut self) {
405        let total_len = self.len;
406
407        if total_len == 0 {
408            return;
409        }
410
411        self.output_specified_len(total_len);
412        self.len = 0;
413        self.last_newline_index = 0;
414    }
415
416    /// Output buffer up until the \0 which will be placed at `len` position.
417    fn output_specified_len(&mut self, len: usize) {
418        let mut last_byte = MaybeUninit::new(b'\0');
419
420        mem::swap(&mut last_byte, unsafe {
421            self.buffer.get_unchecked_mut(len)
422        });
423
424        let msg: &CStr = unsafe { CStr::from_ptr(self.buffer.as_ptr().cast()) };
425        ohos_log(self.buf_id, self.level, self.tag, msg);
426
427        unsafe { *self.buffer.get_unchecked_mut(len) = last_byte };
428    }
429
430    /// Copy `len` bytes from `index` position to starting position.
431    fn copy_bytes_to_start(&mut self, index: usize, len: usize) {
432        let dst = self.buffer.as_mut_ptr();
433        let src = unsafe { self.buffer.as_ptr().add(index) };
434        unsafe { ptr::copy(src, dst, len) };
435    }
436}
437
438impl<'a> fmt::Write for PlatformLogWriter<'a> {
439    fn write_str(&mut self, s: &str) -> fmt::Result {
440        let mut incomming_bytes = s.as_bytes();
441
442        while !incomming_bytes.is_empty() {
443            let len = self.len;
444
445            // write everything possible to buffer and mark last \n
446            let new_len = len + incomming_bytes.len();
447            let last_newline = self.buffer[len..LOGGING_MSG_MAX_LEN]
448                .iter_mut()
449                .zip(incomming_bytes)
450                .enumerate()
451                .fold(None, |acc, (i, (output, input))| {
452                    output.write(*input);
453                    if *input == b'\n' {
454                        Some(i)
455                    } else {
456                        acc
457                    }
458                });
459
460            // update last \n index
461            if let Some(newline) = last_newline {
462                self.last_newline_index = len + newline;
463            }
464
465            // calculate how many bytes were written
466            let written_len = if new_len <= LOGGING_MSG_MAX_LEN {
467                // if the len was not exceeded
468                self.len = new_len;
469                new_len - len // written len
470            } else {
471                // if new length was exceeded
472                self.len = LOGGING_MSG_MAX_LEN;
473                self.temporal_flush();
474
475                LOGGING_MSG_MAX_LEN - len // written len
476            };
477
478            incomming_bytes = &incomming_bytes[written_len..];
479        }
480
481        Ok(())
482    }
483}
484
485/// Send a log record to Ohos logging backend.
486///
487/// This action does not require initialization. However, without initialization it
488/// will use the default filter, which allows all logs.
489pub fn log(record: &Record) {
490    OHOS_LOGGER
491        .get_or_init(OhosLogger::default)
492        .log(record)
493}
494
495/// Initializes the global logger with an ohos logger.
496///
497/// This can be called many times, but will only initialize logging once,
498/// and will not replace any other previously initialized logger.
499///
500/// It is ok to call this at the activity creation, and it will be
501/// repeatedly called on every lifecycle restart (i.e. screen rotation).
502pub fn init_once(config: Config) {
503    let log_level = config.log_level;
504    let logger = OHOS_LOGGER.get_or_init(|| OhosLogger::new(config));
505
506    if let Err(err) = log::set_logger(logger) {
507        debug!("ohos_hilog: log::set_logger failed: {}", err);
508    } else if let Some(level) = log_level {
509        log::set_max_level(level);
510    }
511}
512
513// FIXME: When `maybe_uninit_uninit_array ` is stabilized, use it instead of this helper
514fn uninit_array<const N: usize, T>() -> [MaybeUninit<T>; N] {
515    // SAFETY: Array contains MaybeUninit, which is fine to be uninit
516    unsafe { MaybeUninit::uninit().assume_init() }
517}
518
519#[cfg(test)]
520mod tests {
521    use super::*;
522    use std::fmt::Write;
523    use std::sync::atomic::{AtomicBool, Ordering};
524
525    #[test]
526    fn check_config_values() {
527        // Filter is checked in config_filter_match below.
528        let config = Config::default()
529            .with_max_level(LevelFilter::Trace)
530            .with_log_buffer(0)
531            .with_tag("my_app");
532
533        assert_eq!(config.log_level, Some(LevelFilter::Trace));
534        assert_eq!(config.buf_id, Some(0));
535        assert_eq!(config.tag, Some(CString::new("my_app").unwrap()));
536    }
537
538    #[test]
539    fn log_calls_formatter() {
540        static FORMAT_FN_WAS_CALLED: AtomicBool = AtomicBool::new(false);
541        let config = Config::default()
542            .with_max_level(LevelFilter::Info)
543            .format(|_, _| {
544                FORMAT_FN_WAS_CALLED.store(true, Ordering::SeqCst);
545                Ok(())
546            });
547        let logger = OhosLogger::new(config);
548
549        logger.log(&Record::builder().level(Level::Info).build());
550
551        assert!(FORMAT_FN_WAS_CALLED.load(Ordering::SeqCst));
552    }
553
554    #[test]
555    fn logger_enabled_threshold() {
556        let logger = OhosLogger::new(Config::default().with_max_level(LevelFilter::Info));
557
558        assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Warn).build()));
559        assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Info).build()));
560        assert!(!logger.enabled(&log::MetadataBuilder::new().level(Level::Debug).build()));
561    }
562
563    // Test whether the filter gets called correctly. Not meant to be exhaustive for all filter
564    // options, as these are handled directly by the filter itself.
565    #[test]
566    fn config_filter_match() {
567        let info_record = Record::builder().level(Level::Info).build();
568        let debug_record = Record::builder().level(Level::Debug).build();
569
570        let info_all_filter = env_logger::filter::Builder::new().parse("info").build();
571        let info_all_config = Config::default().with_filter(info_all_filter);
572
573        assert!(info_all_config.filter_matches(&info_record));
574        assert!(!info_all_config.filter_matches(&debug_record));
575    }
576
577    #[test]
578    fn fill_tag_bytes_truncates_long_tag() {
579        let logger = OhosLogger::new(Config::default());
580        let too_long_tag: [u8; LOGGING_TAG_MAX_LEN + 20] = [b'a'; LOGGING_TAG_MAX_LEN + 20];
581
582        let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
583        logger.fill_tag_bytes(&mut result, &too_long_tag);
584
585        let mut expected_result = [b'a'; LOGGING_TAG_MAX_LEN - 2].to_vec();
586        expected_result.extend("..\0".as_bytes());
587        assert_eq!(unsafe { assume_init_slice(&result) }, expected_result);
588    }
589
590    #[test]
591    fn fill_tag_bytes_keeps_short_tag() {
592        let logger = OhosLogger::new(Config::default());
593        let short_tag: [u8; 3] = [b'a'; 3];
594
595        let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
596        logger.fill_tag_bytes(&mut result, &short_tag);
597
598        let mut expected_result = short_tag.to_vec();
599        expected_result.push(0);
600        assert_eq!(unsafe { assume_init_slice(&result[..4]) }, expected_result);
601    }
602
603    #[test]
604    fn platform_log_writer_init_values() {
605        let tag = CStr::from_bytes_with_nul(b"tag\0").unwrap();
606
607        let writer = PlatformLogWriter::new(None, Level::Warn, tag);
608
609        assert_eq!(writer.tag, tag);
610        // Ohos uses LogLevel instead, which doesn't implement equality checks
611        #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
612        assert_eq!(writer.level, Level::Warn);
613    }
614
615    #[test]
616    fn temporal_flush() {
617        let mut writer = get_tag_writer();
618
619        writer
620            .write_str("12\n\n567\n90")
621            .expect("Unable to write to PlatformLogWriter");
622
623        assert_eq!(writer.len, 10);
624        writer.temporal_flush();
625        // Should have flushed up until the last newline.
626        assert_eq!(writer.len, 3);
627        assert_eq!(writer.last_newline_index, 0);
628        assert_eq!(
629            unsafe { assume_init_slice(&writer.buffer[..writer.len]) },
630            "\n90".as_bytes()
631        );
632
633        writer.temporal_flush();
634        // Should have flushed all remaining bytes.
635        assert_eq!(writer.len, 0);
636        assert_eq!(writer.last_newline_index, 0);
637    }
638
639    #[test]
640    fn flush() {
641        let mut writer = get_tag_writer();
642        writer
643            .write_str("abcdefghij\n\nklm\nnopqr\nstuvwxyz")
644            .expect("Unable to write to PlatformLogWriter");
645
646        writer.flush();
647
648        assert_eq!(writer.last_newline_index, 0);
649        assert_eq!(writer.len, 0);
650    }
651
652    #[test]
653    fn last_newline_index() {
654        let mut writer = get_tag_writer();
655
656        writer
657            .write_str("12\n\n567\n90")
658            .expect("Unable to write to PlatformLogWriter");
659
660        assert_eq!(writer.last_newline_index, 7);
661    }
662
663    #[test]
664    fn output_specified_len_leaves_buffer_unchanged() {
665        let mut writer = get_tag_writer();
666        let log_string = "abcdefghij\n\nklm\nnopqr\nstuvwxyz";
667        writer
668            .write_str(log_string)
669            .expect("Unable to write to PlatformLogWriter");
670
671        writer.output_specified_len(5);
672
673        assert_eq!(
674            unsafe { assume_init_slice(&writer.buffer[..log_string.len()]) },
675            log_string.as_bytes()
676        );
677    }
678
679    #[test]
680    fn copy_bytes_to_start() {
681        let mut writer = get_tag_writer();
682        writer
683            .write_str("0123456789")
684            .expect("Unable to write to PlatformLogWriter");
685
686        writer.copy_bytes_to_start(3, 2);
687
688        assert_eq!(
689            unsafe { assume_init_slice(&writer.buffer[..10]) },
690            "3423456789".as_bytes()
691        );
692    }
693
694    #[test]
695    fn copy_bytes_to_start_nop() {
696        let test_string = "Test_string_with\n\n\n\nnewlines\n";
697        let mut writer = get_tag_writer();
698        writer
699            .write_str(test_string)
700            .expect("Unable to write to PlatformLogWriter");
701
702        writer.copy_bytes_to_start(0, 20);
703        writer.copy_bytes_to_start(10, 0);
704
705        assert_eq!(
706            unsafe { assume_init_slice(&writer.buffer[..test_string.len()]) },
707            test_string.as_bytes()
708        );
709    }
710
711    fn get_tag_writer() -> PlatformLogWriter<'static> {
712        PlatformLogWriter::new(
713            None,
714            Level::Warn,
715            CStr::from_bytes_with_nul(b"tag\0").unwrap(),
716        )
717    }
718
719    unsafe fn assume_init_slice<T>(slice: &[MaybeUninit<T>]) -> &[T] {
720        &*(slice as *const [MaybeUninit<T>] as *const [T])
721    }
722}