pub unsafe trait ReprC: Sized {
    type CLayout: CType;

    fn is_valid(it: &Self::CLayout) -> bool;
}
Expand description

The meat of the crate. The trait. This trait describes that a type has a defined / fixed #[repr(C)] layout.

This is expressed at the type level by the unsafe (trait) type association of ReprC::CLayout, which must be a CType.

Because of that property, the type may be used in the API of an #[ffi_export]-ed function, where ABI-wise it will be replaced by its equivalent C layout.

Then, #[ffi_export] will transmute the CType parameters back to the provided ReprC types, using from_raw_unchecked.

Although, from a pure point of view, no checks are performed at this step whatsoever, in practice, when debug_assertions are enabled, some “sanity checks” are performed on the input parameters: ReprC::is_valid is called in that case (as part of the implementation of from_raw).

  • Although that may look innocent, it is actually pretty powerful tool:

    For instance, a non-null pointer coming from C can, this way, be automatically checked and unwrapped, and the same applies for enumerations having a finite number of valid bit-patterns.

Safety

It must be sound to transmute from a ReprC::CLayout instance when the bit pattern represents a safe instance of Self.

Implementing ReprC

It is generally recommended to avoid manually (and unsafe-ly) implementing the ReprC trait. Instead, the recommended and blessed way is to use the #[derive_ReprC] attribute on your #[repr(C)] struct (or your field-less #[repr(<integer>)] enum).

Examples

Simple struct
use ::safer_ffi::prelude::*;

#[derive_ReprC]
#[repr(C)]
struct Instant {
    seconds: u64,
    nanos: u32,
}
  • corresponding to the following C definition:

    typedef struct {
        uint64_t seconds;
        uint32_t nanos;
    } Instant_t;
    
Field-less enum
use ::safer_ffi::prelude::*;

#[derive_ReprC]
#[repr(u8)]
enum Status {
    Ok = 0,
    Busy,
    NotInTheMood,
    OnStrike,
    OhNo,
}
  • corresponding to the following C definition:

    typedef uint8_t Status_t; enum {
        STATUS_OK = 0,
        STATUS_BUSY,
        STATUS_NOT_IN_THE_MOOD,
        STATUS_ON_STRIKE,
        STATUS_OH_NO,
    }
    
Generic struct
use ::safer_ffi::prelude::*;

#[derive_ReprC]
#[repr(C)]
struct Point<Coordinate : ReprC> {
    x: Coordinate,
    y: Coordinate,
}

Each monomorphization leads to its own C definition:

  • Point<i32>

    typedef struct {
        int32_t x;
        int32_t y;
    } Point_int32_t;
    
  • Point<f64>

    typedef struct {
        double x;
        double y;
    } Point_double_t;
    

Required Associated Types

The CType having the same layout as Self.

Required Methods

Sanity checks that can be performed on an instance of the CType layout.

Such checks are performed when calling from_raw, or equivalently (⚠️ only with debug_assertions enabled ⚠️), from_raw_unchecked.

Implementation-wise, this function is only a “sanity check” step:

  • It is valid (although rather pointless) for this function to always return true, even if the input may be unsafe to transmute to Self, or even be an invalid value of type Self.

  • In the other direction, it is not unsound, although it would be a logic error, to always return false.

  • This is because it is impossible to have a function that for any type is able to tell if a given bit pattern is a safe value of that type.

In practice, if this function returns false, then such result must be trusted, i.e., transmuting such instance to the Self type will, at the very least, break a safety invariant, and it will even most probably break a validity invariant.

On the other hand, if the function returns true, then the result is inconclusive; there is no explicit reason to stop going on, but that doesn’t necessarily make it sound.

TL,DR

This function may yield false positives but no false negatives.

Example: Self = &'borrow i32

When Self = &'borrow i32, we know that the backing pointer is necessarily non-null and well-aligned.

This means that bit-patterns such as 0 as *const i32 or 37 as *const i32 are “blatantly unsound” to transmute to a &'borrow i32, and thus <&'borrow i32 as ReprC>::is_valid will return false in such cases.

But if given 4 as *const i32, or if given { let p = &*Box::new(42) as *const i32; p }, then is_valid will return true in both cases, since it doesn’t know better.

Example: bool or #[repr(u8)] enum Foo { A, B }

In the case of bool, or in the case of a #[repr(<integer>)] field-less enum, then the valid bit-patterns and the invalid bit-patterns are all known and finite.

In that case, ReprC::is_valid will return a bool that truly represents the validity of the bit-pattern, in both directions

  • i.e., no false positives (validity-wise);

Still, there may be safety invariants involved with custom types, so even then it is unclear.

Implementations on Foreign Types

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls include up to 9 function parameters.

Simplified for lighter documentation, but the actual impls range from 1 up to 32, plus a bunch of significant lengths up to 1024.

Simplified for lighter documentation, but the actual impls range from 1 up to 32, plus a bunch of significant lengths up to 1024.

Implementors