Attribute Macro pre::forward[][src]

#[forward]
Expand description

Forward the call to a different function that has the preconditions for the original function.

Currently this attribute does not work by itself. It needs to be used inside of a context that is annotated by a pre attribute.

The purpose of this attribute is to apply the preconditions defined on items in a extern_crate module to the use of the original item.

It is useful if you want to check preconditions for an existing file without making modifications to the file besides adding attributes.

For most use cases, it is better to just use the item inside of the extern_crate module directly. The only case where this attribute is currently absolutely needed, is if you want to check preconditions for functions and methods defined inside of an impl block in an extern_crate attribute.

Examples

Basic usage:

use pre::pre;

#[pre::extern_crate(std)]
mod new_std {
    mod ptr {
        #[pre(valid_ptr(dst, w))]
        unsafe fn write_unaligned<T>(dst: *mut T, src: T);
    }
}

#[pre]
fn main() {
    // Replacing this with `new_std::ptr::write_unaligned` would make the first `forward`
    // attribute below unnecessary.
    use std::ptr::write_unaligned;

    let mut x = 0;

    // No preconditions are checked here.
    unsafe { write_unaligned(&mut x, 1) };

    // Here the actual function being called is `new_std::ptr::write_unaligned`, which checks
    // the preconditions and is otherwise functionally equivalent to
    // `std::ptr::write_unaligned`.
    #[forward(new_std::ptr)]
    #[assure(
        valid_ptr(dst, w),
        reason = "`dst` is created from a reference"
    )]
    unsafe { write_unaligned(&mut x, 2) };

    // Here the `std` segment of the path is replaced with `new_std`, so again
    // `new_std::ptr::write_unaligned` is called.
    // The same effect could be achieved without a `forward` attribute, by replacing the `std`
    // in the path of the call with `new_std`.
    #[forward(std -> new_std)]
    #[assure(
        valid_ptr(dst, w),
        reason = "`dst` is created from a reference"
    )]
    unsafe { std::ptr::write_unaligned(&mut x, 3) };
}

For functions and methods inside of impl blocks, using the forward attribute with the impl keyword inside is necessary to check the preconditions:

use pre::pre;

#[pre::extern_crate(std)]
mod new_std {
    mod ptr {
        impl<T> NonNull<T> {
            #[pre(!ptr.is_null())]
            const unsafe fn new_unchecked(ptr: *mut T) -> NonNull<T>;
        }
    }
}

#[pre]
fn main() {
    let mut val = 0;

    // Even though this uses the `NonNull` type through the `extern_crate` module, this will
    // unfortunately not check the preconditions.
    let non_null = unsafe { new_std::ptr::NonNull::new_unchecked(&mut val) };

    // This call actually checks the preconditions. Note the `impl` keyword before the path
    // below. This is required, so that the preconditions can be properly checked for a
    // function in an `impl` block inside of a `extern_crate` module.
    #[forward(impl new_std::ptr::NonNull)]
    #[assure(
        !ptr.is_null(),
        reason = "a reference is never null"
    )]
    let non_null = unsafe { new_std::ptr::NonNull::new_unchecked(&mut val) };

    // The same thing also works when using the `NonNull` through the `std::ptr` path.
    #[forward(impl new_std::ptr::NonNull)]
    #[assure(
        !ptr.is_null(),
        reason = "a reference is never null"
    )]
    let non_null = unsafe { std::ptr::NonNull::new_unchecked(&mut val) };
}

Syntax

This attribute has three different forms:

Direct call

#[forward(<path>)]

How it works

<path> is prepended to the path of the annotated call.

Example

#[forward(abc::def)]
ghi::jkl();

becomes

abc::def::ghi::jkl();

Path replacement

#[forward(<old_path> -> <new_path>)]

How it works

<old_path> is replaced with <new_path> in the path of the annotated call.

Example

#[forward(abc -> def)]
abc::ghi::jkl();

becomes

def::ghi::jkl();

Impl call

#[forward(impl <path>)]

How it works

Instead of checking the preconditions on the original method call, the preconditions are checked for a function or method with the same name located at an impl block at <path>. For this to work, the impl block at <path> must be inside of an extern_crate-annotated module.

Example

let v: SomeType<bool> = SomeType::new();

#[forward(impl some_impl::SomeType)]
#[assure(
    <some_condition>,
    reason = <some_reason>
)]
v.some_method(some_arg);

works similar to

let v: SomeType<bool> = SomeType::new();

#[assure(
    <some_condition>,
    reason = <some_reason>
)]
some_impl::SomeType::some_method(); // Does not actually do anything, just checks the
                                    // preconditions.
v.some_method(some_arg);

The exact inner workings of this are different to make it work in more contexts, but this is a good mental model to think about it.