Crate assume

source ·
Expand description

A macro for stating unsafe assumptions in Rust.

Using this macro, one can supply assumptions to the compiler for use in optimization. These assumptions are checked in debug_assertion configurations, and are unchecked (but still present) otherwise.

This is an inherently unsafe operation. It lives in the space between regular assert! and pure unsafe accesses - it relies heavily on an optimizing compiler’s ability to track unreachable paths to eliminate unnecessary asserts.

Examples:

use assume::assume;

let v = vec![1, 2, 3];

// Some computed index that, per invariants, is always in bounds.
let i = get_index();

assume!(
    unsafe: i < v.len(),
    "index {} is beyond vec length {}",
    i,
    v.len(),
);
let element = v[i];  // Bounds check optimized out per assumption.
use assume::assume;

let items: HashMap<u32, String> = populate_items();

// Some item that, per invariants, always exists.
let item_zero_opt: Option<&String> = items.get(&0);

assume!(
    unsafe: item_zero_opt.is_some(),
    "item zero missing from items map",
);
let item_zero = item_zero_opt.unwrap();  // Panic check optimized out per assumption.
use assume::assume;

enum Choices {
    This,
    That,
    Other,
}

// Some choice that, per invariants, is never Other.
let choice = get_choice();

match choice {
    Choices::This => { /* ... */ },
    Choices::That => { /* ... */ },
    Choices::Other => {
        // This case optimized out entirely, no panic emitted.
        assume!(
            unsafe: @unreachable,
            "choice was other",
        );
    },
}
use assume::assume;

#[inline(always)]
fn compute_value() -> usize {
    let result = compute_value_internal();

    // Can also be used to provide hints to the caller,
    // after the optimizer inlines this assumption.
    assume!(
        unsafe: result < 12,
        "result is invalid: {}",
        result,
    );
    result
}

fn compute_value_internal() -> usize {
    /* ... */
}

fn process_data(data: &[f64; 100]) {
    // Bounds check elided per implementation's assumption.
    let value = data[compute_value()];
}

Gotchas

  • Unlike debug_assert! et. al., the condition of an assume! is always present - it’s the panic that is removed. Complicated assumptions involving function calls and side effects are unlikely to be helpful; the condition ought to be trivial and involve only immediately available facts.

  • As stated, this relies on the optimizer to propagate the assumption. Differences in optimization level or mood of the compiler may cause it to fail to elide assertions in the final output. If you simply must have no checking and do not want to rely on optimizations, then a debug_assert! + unsafe access is the way to go.

  • Avoid using assume!(unsafe: false) to indicate unreachable code. Although this works, the return type is () and not !. This can result in warnings or errors if e.g. other branches evaluate to a type other than (). Use assume!(unsafe: @unreachable) instead.

Macros

  • Assumes that the given condition is true.