summary refs log blame commit diff stats
path: root/src/lib.rs
blob: 42eb51b7064b277d094575e5947a2edfdef4ece1 (plain) (tree)
1
2
3
4
5
6
7
8







                                                                               







                                                                                                  




































































































                                                                               
                                                               





                                                                    
                                                                   





































                                                                             
#![cfg_attr(not(feature="std"),no_std)]

//! This crate provides "checked" raw pointers.
//!
//! We found ourselves to be experiencing a lot of cognitive overhead when
//! doing FFI interactions. A non-insignificant portion of them came from raw
//! pointers, `as_ptr`, `as_mut_ptr`, and the like. This removes that cognitive
//! overhead.
//!
//! And as [Amos once said], "Until we demand better of our tools, we are
//! doomed to be woken up in the middle of the night, over and over again,
//! because some nil value slipped in where it never should have.". That is, we
//! are doomed to repeat the same mistakes over and over again. So we must
//! demand better of our tools!
//!
//! [Amos once said]: https://fasterthanli.me/articles/lies-we-tell-ourselves-to-keep-using-golang

use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::ops::Range;

/// The actual impls.
mod impls;

/// `*const T` with a lifetime.
///
/// # Examples
///
/// Rust doesn't catch incorrect `as_ptr` usage (tho there is a lint for it):
///
/// ```no_run
/// use std::ffi::{CString, CStr};
/// use std::os::raw::c_char;
///
/// extern "C" {
///     fn my_ffi_function(data: *const c_char);
/// }
/// let str = CString::new("world!").unwrap().as_ptr();
/// //                    oops! CString dropped here! ^
/// unsafe { my_ffi_function(str) } // crashes at runtime
/// ```
///
/// but we can use a `ConstLtPtr` to make it do so:
///
/// ```compile_fail
/// use std::ffi::{CString, CStr};
/// use std::os::raw::c_char;
/// use ltptr::*;
///
/// extern "C" {
///     fn my_ffi_function(data: ConstLtPtr<'_, c_char>);
/// }
/// let str = CString::new("world!").unwrap().as_lt_ptr();
/// unsafe { my_ffi_function(str) }
/// // error[E0716]: temporary value dropped while borrowed
/// ```
///
/// which we can then fix:
///
/// ```no_run
/// use std::ffi::{CString, CStr};
/// use std::os::raw::c_char;
/// use ltptr::*;
///
/// extern "C" {
///     fn my_ffi_function(data: ConstLtPtr<'_, c_char>);
/// }
/// let str = CString::new("world!").unwrap();
/// unsafe { my_ffi_function(str.as_lt_ptr()) }
/// ```
///
///
/// and if we call the wrong thing, it fails to compile:
///
/// ```compile_fail
/// use std::ffi::{CString, CStr};
/// use std::os::raw::c_char;
/// use ltptr::*;
///
/// extern "C" {
///     fn my_ffi_function(data: ConstLtPtr<'_, c_char>);
/// }
/// let str = CString::new("world!").unwrap();
/// unsafe { my_ffi_function(str.as_ptr()) } // should be as_lt_ptr!
/// ```
#[repr(transparent)]
pub struct ConstLtPtr<'a, T: ?Sized> {
  raw: *const T,
  _lt: PhantomData<&'a T>,
}

/// `*mut T` with a lifetime.
#[repr(transparent)]
pub struct MutLtPtr<'a, T: ?Sized> {
  raw: *mut T,
  _lt: PhantomData<&'a UnsafeCell<T>>,
}

/// Trait for conversion into a [`ConstLtPtr`].
pub trait AsLtPtr {
    type Target: ?Sized;
    /// Returns a pointer as if by `as_ptr` on a type that implements this, but
    /// with a bound lifetime.
    fn as_lt_ptr<'a>(&'a self) -> ConstLtPtr<'a, Self::Target>;
}

/// Trait for conversion into a [`MutLtPtr`].
pub trait AsMutLtPtr: AsLtPtr {
    /// Returns a pointer as if by `as_ptr` on a type that implements this, but
    /// with a bound lifetime.
    fn as_mut_lt_ptr<'a>(&'a mut self) -> MutLtPtr<'a, Self::Target>;
}

/// Additional slice methods.
pub trait SliceExt<T>: impls::slice::Sealed {
    /// Returns the two raw pointers spanning the slice.
    ///
    /// See [`slice::as_ptr_range`] for extended documentation.
    ///
    /// The pointers returned by this function have bound lifetimes.
    fn as_lt_ptr_range<'a>(&'a self) -> Range<ConstLtPtr<'a, T>>;

    /// Returns the two unsafe mutable pointers spanning the slice.
    ///
    /// See [`slice::as_mut_ptr_range`] for extended documentation.
    ///
    /// The pointers returned by this function have bound lifetimes.
    fn as_mut_lt_ptr_range<'a>(&'a mut self) -> Range<MutLtPtr<'a, T>>;
}

impl<'a, T: ?Sized> ConstLtPtr<'a, T> {
    /// Creates a new `ConstLtPtr` from the given raw pointer.
    ///
    /// # Safety
    ///
    /// This function is unsafe as an advisory: the returned `ConstLtPtr` has
    /// an arbitrary lifetime `'a`, so using this function directly doesn't
    /// help reduce cognitive overhead.
    #[inline]
    pub const unsafe fn from_raw(raw: *const T) -> Self {
        Self {
            raw: raw,
            _lt: PhantomData,
        }
    }
}

impl<'a, T: ?Sized> MutLtPtr<'a, T> {
    /// Creates a new `MutLtPtr` from the given raw pointer.
    ///
    /// # Safety
    ///
    /// This function is unsafe as an advisory: the returned `MutLtPtr` has
    /// an arbitrary lifetime `'a`, so using this function directly doesn't
    /// help reduce cognitive overhead.
    #[inline]
    pub const unsafe fn from_raw(raw: *mut T) -> Self {
        Self {
            raw: raw,
            _lt: PhantomData,
        }
    }
}