Skip to main content

Crate unbug

Crate unbug 

Source
Expand description

Unbug logo

Unbug

Debug breakpoint assertions for Rust.

These macros are designed to help developers catch errors during debugging sessions that would otherwise be a panic (which may not be desirable in certain contexts) or simply a log message (which may go unnoticed).

Shims are provided so breakpoints will not be compiled in release builds. This means that the macros in this crate can be used freely throughout your code without having to conditionally compile them out yourself.

§Examples

§VSCode debugging example

§Programmatic breakpoint assertions

// trigger the debugger
unbug::breakpoint!();

for i in 0..5 {
    // ensure! will only trigger the debugger once
    // when the expression argument is false
    unbug::ensure!(false);
    unbug::ensure!(false, "Ensure can take an optional log message");
    // ensure! messages will not be compiled into release builds
    unbug::ensure!(false, "{}", i);

    // ensure_always! will trigger the debugger every time
    // when the expression argument is false
    unbug::ensure_always!(i % 2 == 0);

    // fail! pauses and logs an error message
    // will also only trigger once
    unbug::fail!("fail! will continue to log in non-debug builds");

    if i < 3 {
        // fail! and fail_always! can be formatted just like error!
        // from the Tracing crate
        unbug::fail!("{}", i);
    }

    let Some(_out_var) = some_option else {
        // fail! and fail_always! will continue to log a message in release builds
        unbug::fail_always!("fail_always! will trigger every time");
    };
}

§debug_fail macro

// activate the macro for the following function
#[debug_fail]
fn my_system() -> Result {
    // all ? operators in this function will have breakpoints attached
    let some_data = get_option()?;

    // In addition to attaching a breakpoint
    // expressions annotated with fail_msg will log an error message
    // on failure (even in a release build)
    #[fail_msg = "A message to log when the value is Err"]
    let ok_data = get_result()?;

    // All ? operators in the following expression will have
    // the same error message
    #[fail_msg = "A message to log when the value is Err"]
    let other_ok_data = get_other_result()?.get_another()?;

    // use fail_ignore to skip attaching breakpoints to
    // invocations of the ? operator for the following expression
    #[fail_ignore]
    let ignore_data = get_result()?;

    Ok(())
}

§Usage

Prepare your environment for debugging Rust.

[!IMPORTANT]

If you are using VSCode you will need the Rust Analyzer and Code LLDB (Linux/Mac) or the C/C++ (Windows) extensions. See Microsoft’s Documentation on Rust Debugging in VSCode.

1. Add Unbug to your project’s dependencies

Cargo.toml:

[dependencies]
unbug = "0.5"

2. Set up a debug launch configuration

Sample VSCode .vscode/launch.json with LLDB (Linux/Mac):

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "LLDB Debug",
            "cargo": {
                "args": [
                    "build",
                    "--bin=my_project",
                    "--package=my_project"
                ],
                "filter": {
                    "name": "my_project",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}",
            "env": {
                "CARGO_MANIFEST_DIR": "${workspaceFolder}"
            }
        }
    ]
}

Sample VSCode .vscode/launch.json with msvc (Windows):

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Windows debug",
            "type": "cppvsdbg",
            "request": "launch",
            "program": "${workspaceRoot}/target/debug/unbug_basic_example.exe",
            "stopAtEntry": false,
            "cwd": "${workspaceRoot}",
            "preLaunchTask": "win_build_debug"
        }
    ]
}

and complimentary .vscode/tasks.json

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "cargo",
            "command": "build",
            "args": [
                "--bin=my_project",
                "--package=my_project"
            ],
            "problemMatcher": [
                "$rustc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "label": "win_build_debug"
        }
    ]
}

3. Select the debug launch configuration for your platform and start debugging

In VSCode, open the “Run and Debug” panel from the left sidebar.

launch configurations can be now found in the dropdown menu next to the green “Start Debugging” button.

When debugging is active, controls for resume execution, step-over, and step-out are at the top of the window under the search field.

§Features

§Tracing integration

Cargo feature: tracing (enabled by default)

Example: examples/tracing

Provides compatibility with Tracing

Use the tracing_subscriber crate in your project to format log messages.

tracing_subscriber::fmt::init();

§Bevy integration

Cargo feature: bevy (NOT enabled by default)

Example: examples/bevy

Add to your Cargo.toml:

[dependencies]
unbug = "0.5"
features = ["bevy"]

Provides compatibility with the Bevy game engine for BevyError support with the macro feature (see below)

§Supported bevy version
UnbugBevy
0.5.00.18

§debug_fail macro

Cargo feature: macro (enabled by default)

Example: examples/macro

An easy way to trigger debug assertions via try-operator (?) usage.

Add the debug_fail annotation to functions. This macro will attach breakpoints automatically to all invocations the try-operator (?). These breakpoints are triggered when the value of a Result is an Err or when the value of an Option is None.

fail_msg can be added above expressions to add log messages during those breakpoints and these logs will be present in release builds.

You can use fail_ignore to completely skip attaching a breakpoint.

[!NOTE] The Bevy game engine integration (with the bevy cargo feature) provides the required Option and Result traits using the BevyError type.

Option types processed by the macro will be converted to Results with a BevyError Err type

To use the macro without Bevy, you will have to implement traits with both the functions .on_fail and .try_to_result implemented for both Options and Results with your error type. See the macro example for a sample implementation

§Debugger presence detection

Cargo feature: check_debugger (enabled by default)

Unbug can detect if it is in a debugging session

§Debugger presence cache

Cargo feature: cache_debugger (enabled by default)

Unbug will remember whether or not it is in a debug session and avoid repeated checks

§Late attach debugging support

By default this crate assumes that the debugger is attached to the process as soon as execution begins. This means that the debugger detection cache is populated when the first breakpoint occurs. If a debugger is not attached before then, Unbug will not fire breakpoints for the rest of that execution session. If you plan on attaching to a process late you can disable default features and omit the cache_debugger feature to check for the presence of a debugger every time a breakpoint is called. This will incur a runtime cost which may significantly impact performance on some platforms.

§(Experimental) testing support

Cargo feature: testing (only used in tests - SEE EXAMPLE)

Example: examples/testing

Assertions can trigger test failures. There is extra scaffolding required in your project’s Cargo.toml, See the example.

§Other platforms

[!IMPORTANT]

Stable Rust is only supported on x86, x86_64, and ARM64.

Other targets require nightly Rust with the breakpoint feature enabled in your crate (#![feature(breakpoint)]).

Including, but not limited to WASM, RISCV, PowerPC, and ARM32

You’ll need nightly Rust with the breakpoint feature enabled.

To Enable Nightly Rust:

You can set a workspace toolchain override by adding a rust-toolchain.toml file at the root of your project with the following contents:

[toolchain]
channel = "nightly"

OR you can set cargo to default to nightly globally:

rustup install nightly
rustup default nightly

enable the breakpoint feature in the root of your crate (src/main.rs or src/lib.rs):

src/main.rs:

// this configuration will conditionally activate the breakpoint feature only in a dev build
#![cfg_attr(debug_assertions, feature(breakpoint))]

Additonally, debugging may not land on the macro statements themselves. This can have the consequence that the debgger may pause on an internal module. To avoid this, return or continue immediately following a macro invocation. Alternatively, use your debugger’s “step-out” feature until you reenter the scope of your code.

§License

Unbug is free and open source. All code in this repository is dual-licensed under either:

at your option.

Modules§

prelude