#print #dbg #logging #log #web

no-std custom-print

Define custom println and dbg macros in wasm and other targets

2 releases (1 stable)

1.0.0 Jun 14, 2023
0.1.0 Jun 12, 2021

#126 in WebAssembly

Download history 2/week @ 2024-06-15 4/week @ 2024-07-06 6/week @ 2024-07-13 3/week @ 2024-07-20 14/week @ 2024-07-27 1/week @ 2024-08-03 3/week @ 2024-08-10 1/week @ 2024-08-17 5/week @ 2024-09-21 47/week @ 2024-09-28

52 downloads per month
Used in pico-sdk-sys

MIT/Apache

120KB
1.5K SLoC

custom-print

CI Latest Version Documentation GitHub license Rust Version

The custom-print crate helps you to define print, println and dbg macros in wasm and customize them for other targets without any dependencies.

About

This crate helps you to define print-like macros, dbg and panic_hook on wasm32-unknown-unknown target without wasm-bindgen dependency. Also, it can be used on another targets to override default std write-like macros, add try_ macros variants, or to specify panic hook function. It works on stable Rust, supports no-alloc and no-std environments and has no dependencies.

In most cases it is suggested to use macros define_macros, define_macro or define_init_panic_hook. These macros define macros or functions with the specified names that use FmtWriter, FmtTryWriter, ConcatWriter, ConcatTryWriter, IoWriter or IoTryWriter with the specified closure, unsafe function or extern function.

Usage

First, add the following to your Cargo.toml:

[dependencies]
custom-print = "1.0.0"

This crate depends on the standard library by default. To use this crate in a #![no_std] context but with heap-allocations enabled, use default-features = false in your Cargo.toml as shown below:

[dependencies.custom-print]
version = "1.0.0"
default-features = false
features = ["alloc"]

Examples

An example with an extern functions that takes a UTF-8 chars pointer and byte length with no std prelude:

#![no_std]
extern crate std;

custom_print::define_macros!({ print, println },
    concat, extern "C" fn console_log(_: *const u8, _: usize));
custom_print::define_macros!({ eprint, eprintln, dbg },
    concat, extern "C" fn console_warn(_: *const u8, _: usize));
custom_print::define_init_panic_hook!(
    concat, extern "C" fn console_error(_: *const u8, _: usize));

fn main() {
    init_panic_hook();
    println!("println");
    print!("print");
    eprintln!("eprintln");
    eprint!("eprint");
    dbg!("dbg");
}

An example with a closure that takes an str reference in no_std and no_alloc context:

#![no_std]
custom_print::define_macros!({ print, println }, fmt, |_value: &str| { /* ... */ });

fn main() {
    println!("println");
    print!("print");
}

An example with a function that takes a c_char pointer and overriding std::print and std::println functions:

fn write(_value: *const std::os::raw::c_char) { /* ... */ }

custom_print::define_macros!({ cprint, cprintln }, concat, crate::write);
macro_rules! print { ($($args:tt)*) => { cprint!($($args)*); } }
macro_rules! println { ($($args:tt)*) => { cprintln!($($args)*); } }

fn main() {
    println!("println");
    print!("print");
}

Macro generation macros support specifying custom attributes, so you can re-export the generated macros and use them in other crates:

// foo crate:

custom_print::define_macros!(
    #[macro_export] { print, println, cprint, cprintln },
    fmt, |_value: &str| { /* ... */ }
);

fn func() {
    cprintln!("cprintln");
    cprint!("cprint");
}

// bar crate:

fn main() {
    foo::println!("println");
    foo::print!("print");
}

You can find more usage examples in the tests and examples repository folders.

Macro expansion

The example with define_macros and define_init_panic_hook with extern functions:

#![no_std]
extern crate std;

custom_print::define_macros!({ print, println, try_println },
    concat, extern "C" fn console_log(_: *const u8, _: usize));
custom_print::define_macros!({ eprint, eprintln, dbg },
    concat, extern "C" fn console_warn(_: *const u8, _: usize));
custom_print::define_init_panic_hook!(
    concat, extern "C" fn console_error(_: *const u8, _: usize));

fn main() {
    init_panic_hook();
    println!("Greetings from println");
    let _ = try_println!("Greetings from try_println");
    eprintln!("Greetings from eprintln");
    let _ = dbg!("Greetings from dbg");
}

partially expands to:

fn init_panic_hook() {
    fn panic_hook(info: &::std::panic::PanicInfo<'_>) {
        ::core::writeln!(
            ::custom_print::ConcatWriter::from_closure({
                extern "C" { fn console_error(_: *const u8, _: usize); }
                |arg1: *const u8, arg2: usize| unsafe { console_error(arg1, arg2) }
            }),
            "{}",
            info
        ).expect("failed writing panic info");
    }
    ::std::panic::set_hook(::std::boxed::Box::new(panic_hook))
}

fn main() {
    init_panic_hook();

    ::core::writeln!(
        ::custom_print::ConcatWriter::from_closure({
            extern "C" { fn console_log(_: *const u8, _: usize); }
            |arg1: *const u8, arg2: usize| unsafe { console_log(arg1, arg2) }
        }),
        "Greetings from println"
    ).expect("failed writing");

    let _ = ::core::writeln!(
        ::custom_print::ConcatTryWriter::from_closure({
            extern "C" { fn console_log(_: *const u8, _: usize); }
            |arg1: *const u8, arg2: usize| unsafe { console_log(arg1, arg2) }
        }),
        "Greetings from try_println"
    );

    ::core::writeln!(
        ::custom_print::ConcatWriter::from_closure({
            extern "C" { fn console_warn(_: *const u8, _: usize); }
            |arg1: *const u8, arg2: usize| unsafe { console_warn(arg1, arg2) }
        }),
        "Greetings from eprintln"
    ).expect("failed writing");

    let _ = ::custom_print::dbgwrite!(
        ::core::writeln,
        ::custom_print::ConcatWriter::from_closure({
            extern "C" { fn console_error(_: *const u8, _: usize); }
            |arg1: *const u8, arg2: usize| unsafe { console_error(arg1, arg2) }
        }),
        expect,
        ":?",
        "Greetings from dbg"
    );
}

Documentation

API Documentation

Feature Flags

Similar crates

  • web-log provides print, println, eprint, eprintln, requires wasm-bindgen.
  • wasm-rs-dbg provides dbg, requires web-sys.
  • console_log provides logging with trace, debug, warn, error etc., requires log and web-sys.
  • console_error_panic_hook provides panic_hook and panic hook set functions, requires wasm-bindgen.

Troubleshooting

Macro name is ambiguous

Errors like `println` is ambiguous (macro-expanded name vs less macro-expanded name from outer scope during import/macro resolution) occur because of the inability to overwrite standard rust macros in textual scope with macro-expanded macros.

Use can use proxy macros to replace std macros in textual scope:

custom_print::define_macro!(cprintln, once: write_fn);
macro_rules! println { ($($args:tt)*) => { cprintln!($($args)*); } }

Alternatively, use can override macro in the path-based scope:

custom_print::define_macro!(cprintln, once: write_fn);
use cprintln as println;

See define_macro for more details.

Println, dbg and others do nothing in submodules

It looks like you have overridden print-like macros in the path-based scope, but you have not overridden them in submodules. Use proxy macros as it shown above or do not forget to override it in submodules.

custom_print::define_macro!(cprintln, once: write_fn);
use cprintln as println;
mod submodule {
    use cprintln as println;
}

You can always use cargo expand to find out where the problem is.

The trait bound [closure]: IntoWriteFn<_> is not satisfied

Errors like: the trait bound `...: IntoWriteFn<_>` is not satisfied or the trait bound `...: IntoTryWriteFn<_>` is not satisfied, with note: required by ``...Writer::<F1>::from_closure `` errors occur because of the inability to determine the appropriate type of wrapper for the closure.

Specify closure arguments if you haven't already, or use helper closure that takes acceptable arguments (&str, &[u8], etc.) and convert them to the arguments your function requires.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

No runtime deps