The robust keyword

Robust is the dual of unsafe with respect to variance. It is thus natural to imagine a notation for it. The main reason this doesn't exist is because it is not recommended to actively document a type as robust. This should only be done when the robustness of the type is needed for a proof, thus justifying the burden of making sure the implementation is actually robust.

Let's look at a world where robust is the dual keyword of unsafe. We'll make the update type explicit for clarity, but it may be omitted.

/// Prints a message to the standard output.
///
/// # Robustness
///
/// The message doesn't need to be valid UTF-8.
pub robust fn supersafe_print(msg: &Update<str>);

/// Consumes a box returning its raw pointer.
///
/// # Robustness
///
/// The pointer will be properly aligned and non-null.
pub robust fn into_raw(b: Box<T>) -> Update<*mut T>

Note that a function may be both robust and unsafe.

/// Returns the n-th valid `char` of a string.
///
/// # Robustness
///
/// The string doesn't need to be valid UTF-8.
///
/// # Safety
///
/// There must be at least `n` valid `char`s in `s`. A `char` is valid in the
/// string if it is the first sequence of bytes that form a UTF-8 `char` since
/// the previous valid `char` in the string (or the beginning of the string).
pub robust unsafe fn chars_nth(s: &Update<str>, n: Update<usize>) -> char;

The robust keyword would also bring clarity to unsafe traits. When a trait is unsafe only because some of its non-inherited methods or associated constants are robust, then it actually doesn't need to be unsafe.

/// Dynamic management of some large owned allocation.
trait Allocator {
    /// Allocates a small allocation from the large allocation.
    ///
    /// # Errors
    ///
    /// Returns an error in the following conditions:
    /// - The layout is not supported by the allocator.
    /// - There is not enough available space in the large allocation.
    ///
    /// # Robustness
    ///
    /// The result points to a small allocation from the large allocation with
    /// the following properties:
    /// - It does not alias with any previously allocated (and not yet
    ///   deallocated) small allocations.
    /// - It is not null (a consequence of coming from the large allocation).
    /// - It satisfies the layout (correct size and alignment).
    ///
    /// Note that the small allocation is uninitialized and thus not valid for
    /// read.
    robust fn allocate(&self, layout: Layout) -> Result<Update<*mut u8>, AllocError>;

    /// Deallocates a small allocation back to the large allocation.
    ///
    /// # Safety
    ///
    /// The following properties must hold:
    /// - The pointer comes from a previous call to `allocate()` with the same
    ///   layout.
    /// - The pointer has not yet been deallocated yet (a consequence of the
    ///   next point).
    /// - The pointer will not be used anymore. The ownership of the small
    ///   allocation it represents is given back to the allocator.
    unsafe fn deallocate(&self, ptr: Update<*mut u8>, layout: Layout);
}

Notice how implementing the trait is safe. Implementing allocate() will require some proof of robustness and thus be "unsafe" to implement. Implementing deallocate() on the other hand is "safe". One can simply leak the pointer. However in practice, one will probably modify the allocator internal state to be able to reuse that small allocation. This will require some proofs that some allocator invariant is preserved, which is "unsafe". The next chapter discusses exactly this topic of invariants.