`NoBug` provides GLARING! assertions and annotations for rust code. These have different semantic than the stdlib assertions. The Idea behind `NoBug` is to have active annotations with semantic meaning. The annotations are not just comments but actively checked at runtime or compile time. By using uppercase macros they are easily visible and searchable in source code. - [Overview](#overview) - [Assertions](#assertions) - [Annotations](#annotations) - [Message logging](#message-logging) - [Message Format](#message-format) - [Tool Macros](#tool-macros) - [Feature Flags](#feature-flags) - [Workflow](#workflow) - [History](#history) - [Roadmap](#roadmap) # Overview Unlike the stdlib assertions which panic on failure, the `NoBug` assertions will abort the program and thus are not catchable. Without the unwinding overhead they wont generate any excess code. Only in debug build tests they will panic because the test harness running tests in threads and catching panics. When aborting at runtime is not desired then the `*_DBG!` variants of the macros can be used. Those will only check the condition in debug builds. This can be enforced by disabling the `abort_on_fail` feature. ## Assertions Aside from a generic (and discouraged) [`ASSERT!`] macro we provide: * [`REQUIRE!`] -- checking preconditions Asserts that input parameters for functions and methods are valid. This is important when a function or method takes inputs that have a wider range of valid ranges than the function or method expects. This can be numeric data or text for example. * [`ENSURE!`] -- checking postconditions Asserts that the output of a code block is valid. To be used when a computation could produce a result that is not within a valid range. * [`CHECK!`] -- generic checks for testing and similar cases. Substitute for `std::assert!` macros in tests. Each of those has a correspondending `ASSERT_DBG!`, `REQUIRE_DBG!` and `ENSURE_DBG!` variants that inserts checks only in debug builds. `REQUIRE!` and `CHECK!` have somme specializations for const asserts and comparisons which give more concise error messages. Since some programs must not abort on assertion failure we provide a `abort_on_failure` feature. When this is not enabled then any `DIE!` and any other macro calling it will cause a compile error in release, non-test builds. One can only use the `*_DBG!` variants of the nobug macros then. Threre is a special [`ASSERT_ONCE!`] macro that will only check the condition once and then never again. ## Annotations `NoBug` defines macros to actively annotate code. Actively means that these macros can do different things in release or debug builds, depending on configuration: * Be a [`compile_error!()`] When things must be resolved before software is released and deployed. * Check that assertions are valid * Abort the program with a message When a code path can't make progress because of unfinished code. * Log a message each time the macro is encountered Just to nag the programmer * Log a message once when the macro is encountered for the first time Less nagging * Do nothing/optimize out In release builds all annotations are either optimized out or compile errors. Thus they have zero cost in release builds. We provide following annotation macros (with the noted defaults): * [`FIXME!`] -- Bug that must be fixed. * [`FIXED!`] -- Annotate fixed bugs. * [`TODO!`] -- Incomplete but required code. * [`DONE!`] -- Completed code. * [`WIP!`] -- Work in progress. * [`IDEA!`] -- Not required improvement of existing code. * [`PLANNED!`] -- Potential future planned feature. `FIXME!`, `FIXED!`, `WIP!` and `DONE!` can be augmented with optional code blocks that are checked at runtime. Notifying about assertions still being valid and detecting regressions. `MOCK!` must have code blocks either a single one or conditional IF-THEN-ELSE blocks. This can be used where `WIP!` is not sufficient. ## Message logging Nobug logs messages to stderr (using the log or tracing crate is planned for the future). Messages can be of different severity levels. * [`CRITICAL!`] -- Print a critical message. This is used when the program is going to abort. * [`NOTICE!`] -- Print a notice to the user. This is used for annotations. * [`TRACE!`] -- Print a trace message to the user. This is used for debugging/progress. * [`NOTICE_ONCE!`] -- Print a notice to the user. This is used for annotations. Each of these has a `*_DBG!` variant that only logs in debug builds. There is a [`TRACE_DBG_IF!`] macro that does conditional logging in debug builds. ### Message Format The messages are entries delimited by a colon followed by a space, they always start with the file and line number of the message. ```text :: : ``` where `` can have multiple free-form parts each delimited by colon space as well ## Tool Macros The above uses some tool macros which may be useful on their own in some cases. * [`DIE!`] -- Abort (or panic). `compile_error!()` when `abort_on_fail` is not enabled. * [`ONCE!`] -- Execute a block only once. * [`MOCK!`] -- Trivial code mocking. # Feature Flags * `abort_on_fail` This is enabled by default. When not enabled then [`DIE!`] and all macros using it will cause a compile error in release/non-test builds. This is useful when you want to make sure that nobug will never abort in release builds. One can only use the `*_DBG!` variants of the nobug macros in release builds then. * `const_expr` This is enabled by default. When not enabled then the `REQUIRE!` and `CHECK!` macros will not use `const {}` expression specializations and fall back to a inferior implementation. Disabling this feature is required when using a compiler before v1.79.0. * `trace_nobug` Makes `nobug` `TRACE!` all calls to its own macros. This is useful for debugging nobug itself or to get some quick insight of the a program's progress. # Workflow The idea is to use the annotations and assertions to actively document the code. Assertions ideally stay in the code and are not removed. They are the contract of the code, only when performance is critical they can be replaced by a the `*_DBG!` variant. Keep in mind that in some cases the compiler can optimize the checks away or do more aggressive optimizations when it knows that certain conditions are always true. Nobug assertions are not a substitution for the `std::assert!` macros. When required they can be mixed. For the cases where panics need to be caught and handled the `std::assert!` macros must be used. The `NoBug` macros are for the cases where the program is in a undefined state due to a programming error that can't be detected at compile time and should be aborted. The annotations can be used to define a stateful workflow, for example: * `FIXME!` -> `FIXED!` * `IDEA!` -> `WIP!` -> `DONE!` * `PLANNED!` -> `TODO!` -> `WIP!` -> `DONE!` This is not rigid, states can be left out and unused. Only keep whatever makes sense. Especially the final `FIXED!` and `DONE!` states can eventually accumulate and clutter the code. It may still be benefitical to keep them in the code for a while since they guard against regressions. Found bugs that are annotated with `FIXME!` may as well transformed into a test case within the unit or integration tests. Incomplete code can be mocked with `MOCK!` and then replaced with the real implementation when it is done. In some cases this might be preferable to or complement `WIP!` since it allows to run some tests with the mocked code. Testsuites may prefer to use the `CHECK!` macro if appropriate. # History In 2006 I started a C Debug library of the same name (). This implementation will carry some of its ideas over to rust. # Roadmap Currently we only implemented most basic functionally. In future more features will be added. * Eventually some of the macros will become proc macros * Support for invariants on objects (needs proc macros) * With proc macros we can have some nicer syntax, for example: ```rust,ignore #[nobug] fn example(i: i32 where i > 0) -> i32 where result < 100 { ... } ``` * Add support for logging and tracing