diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Cargo.toml | 15 | ||||
-rw-r--r-- | src/impls.rs | 10 | ||||
-rw-r--r-- | src/impls/cstr.rs | 18 | ||||
-rw-r--r-- | src/impls/ptr_traits.rs | 174 | ||||
-rw-r--r-- | src/impls/slice.rs | 40 | ||||
-rw-r--r-- | src/lib.rs | 155 |
7 files changed, 414 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1cf8088 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ltptr" +version = "0.1.0" +edition = "2021" +license = "CC0" +description = "Checked raw pointers." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +# our expectation is that the ppl using this want the CStr methods by default. +default = ["std"] +std = [] + +[dependencies] diff --git a/src/impls.rs b/src/impls.rs new file mode 100644 index 0000000..4690dc9 --- /dev/null +++ b/src/impls.rs @@ -0,0 +1,10 @@ + +/// impls for pointer traits: {Copy,Clone,etc} for {Const,Mut}LtPtr. +pub mod ptr_traits; + +/// impls for CStr. +#[cfg(feature="std")] +pub mod cstr; + +/// impls for slices. +pub mod slice; diff --git a/src/impls/cstr.rs b/src/impls/cstr.rs new file mode 100644 index 0000000..5467963 --- /dev/null +++ b/src/impls/cstr.rs @@ -0,0 +1,18 @@ + +use std::ffi::CStr; +use std::os::raw::c_char; + +use crate::*; + +impl AsLtPtr for CStr { + type Target = c_char; + + /// Returns the inner pointer to this C string. + /// + /// The returned pointer points to a contiguous region of memory terminated + /// with a 0 byte to represent the end of the string. + #[inline] + fn as_lt_ptr(&self) -> ConstLtPtr<'_, c_char> { + unsafe { ConstLtPtr::from_raw(self.as_ptr()) } + } +} diff --git a/src/impls/ptr_traits.rs b/src/impls/ptr_traits.rs new file mode 100644 index 0000000..768d364 --- /dev/null +++ b/src/impls/ptr_traits.rs @@ -0,0 +1,174 @@ + +//use core::ops::{Deref, DerefMut}; +use core::panic::{RefUnwindSafe, UnwindSafe}; + +use crate::*; + +// maybe we shouldn't have Deref? we want to make it hard to fuck things up. + +//impl<'a, T: ?Sized> Deref for ConstLtPtr<'a, T> { +// type Target = *const T; +// fn deref(&self) -> &*const T { +// &self.raw +// } +//} +// +//impl<'a, T: ?Sized> DerefMut for ConstLtPtr<'a, T> { +// fn deref_mut(&mut self) -> &mut *const T { +// &mut self.raw +// } +//} +// +//impl<'a, T: ?Sized> Deref for MutLtPtr<'a, T> { +// type Target = *mut T; +// fn deref(&self) -> &*mut T { +// &self.raw +// } +//} +// +//impl<'a, T: ?Sized> DerefMut for MutLtPtr<'a, T> { +// fn deref_mut(&mut self) -> &mut *mut T { +// &mut self.raw +// } +//} + +// *mut T are UnwindSafe, so ours should be too. +impl<'a, T: RefUnwindSafe + ?Sized> UnwindSafe for MutLtPtr<'a, T> {} + +impl<'a, T: ?Sized> Copy for ConstLtPtr<'a, T> {} +impl<'a, T: ?Sized> Clone for ConstLtPtr<'a, T> { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T: ?Sized> core::fmt::Debug for ConstLtPtr<'a, T> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.raw, f) + } +} + +impl<'a, T: ?Sized> core::fmt::Pointer for ConstLtPtr<'a, T> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Pointer::fmt(&self.raw, f) + } +} + +impl<'a, T: ?Sized> core::hash::Hash for ConstLtPtr<'a, T> { + #[inline] + fn hash<H: core::hash::Hasher>(&self, state: &mut H) { + core::hash::Hash::hash(&self.raw, state) + } +} + +impl<'a, T: ?Sized> core::cmp::Ord for ConstLtPtr<'a, T> { + #[inline] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + core::cmp::Ord::cmp(&self.raw, &other.raw) + } +} + +impl<'a, T: ?Sized> core::cmp::Eq for ConstLtPtr<'a, T> {} +impl<'a, T: ?Sized> core::cmp::PartialEq for ConstLtPtr<'a, T> { + #[inline] + fn eq(&self, other: &Self) -> bool { + core::cmp::PartialEq::eq(&self.raw, &other.raw) + } +} + +impl<'a, T: ?Sized> core::cmp::PartialOrd for ConstLtPtr<'a, T> { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { + core::cmp::PartialOrd::partial_cmp(&self.raw, &other.raw) + } + + #[inline] + fn lt(&self, other: &Self) -> bool { + core::cmp::PartialOrd::lt(&self.raw, &other.raw) + } + + #[inline] + fn le(&self, other: &Self) -> bool { + core::cmp::PartialOrd::le(&self.raw, &other.raw) + } + + #[inline] + fn gt(&self, other: &Self) -> bool { + core::cmp::PartialOrd::gt(&self.raw, &other.raw) + } + + #[inline] + fn ge(&self, other: &Self) -> bool { + core::cmp::PartialOrd::ge(&self.raw, &other.raw) + } +} + +impl<'a, T: ?Sized> Copy for MutLtPtr<'a, T> {} +impl<'a, T: ?Sized> Clone for MutLtPtr<'a, T> { + #[inline] + fn clone(&self) -> Self { + *self + } +} + +impl<'a, T: ?Sized> core::fmt::Debug for MutLtPtr<'a, T> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.raw, f) + } +} + +impl<'a, T: ?Sized> core::fmt::Pointer for MutLtPtr<'a, T> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Pointer::fmt(&self.raw, f) + } +} + +impl<'a, T: ?Sized> core::hash::Hash for MutLtPtr<'a, T> { + #[inline] + fn hash<H: core::hash::Hasher>(&self, state: &mut H) { + core::hash::Hash::hash(&self.raw, state) + } +} + +impl<'a, T: ?Sized> core::cmp::Ord for MutLtPtr<'a, T> { + #[inline] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + core::cmp::Ord::cmp(&self.raw, &other.raw) + } +} + +impl<'a, T: ?Sized> core::cmp::Eq for MutLtPtr<'a, T> {} +impl<'a, T: ?Sized> core::cmp::PartialEq for MutLtPtr<'a, T> { + #[inline] + fn eq(&self, other: &Self) -> bool { + core::cmp::PartialEq::eq(&self.raw, &other.raw) + } +} + +impl<'a, T: ?Sized> core::cmp::PartialOrd for MutLtPtr<'a, T> { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { + core::cmp::PartialOrd::partial_cmp(&self.raw, &other.raw) + } + + #[inline] + fn lt(&self, other: &Self) -> bool { + core::cmp::PartialOrd::lt(&self.raw, &other.raw) + } + + #[inline] + fn le(&self, other: &Self) -> bool { + core::cmp::PartialOrd::le(&self.raw, &other.raw) + } + + #[inline] + fn gt(&self, other: &Self) -> bool { + core::cmp::PartialOrd::gt(&self.raw, &other.raw) + } + + #[inline] + fn ge(&self, other: &Self) -> bool { + core::cmp::PartialOrd::ge(&self.raw, &other.raw) + } +} diff --git a/src/impls/slice.rs b/src/impls/slice.rs new file mode 100644 index 0000000..ff9b9de --- /dev/null +++ b/src/impls/slice.rs @@ -0,0 +1,40 @@ + +use crate::*; + +pub trait Sealed {} +impl<T> Sealed for [T] {} + +impl<T> AsLtPtr for [T] { + type Target = T; + fn as_lt_ptr(&self) -> ConstLtPtr<'_, T> { + unsafe { ConstLtPtr::from_raw(self.as_ptr()) } + } +} + +impl<T> AsMutLtPtr for [T] { + fn as_mut_lt_ptr(&mut self) -> MutLtPtr<'_, T> { + unsafe { MutLtPtr::from_raw(self.as_mut_ptr()) } + } +} + +impl<T> SliceExt<T> for [T] { + fn as_lt_ptr_range(&self) -> Range<ConstLtPtr<'_, T>> { + let Range { start, end } = self.as_ptr_range(); + unsafe { + Range { + start: ConstLtPtr::from_raw(start), + end: ConstLtPtr::from_raw(end), + } + } + } + + fn as_mut_lt_ptr_range(&mut self) -> Range<MutLtPtr<'_, T>> { + let Range { start, end } = self.as_mut_ptr_range(); + unsafe { + Range { + start: MutLtPtr::from_raw(start), + end: MutLtPtr::from_raw(end), + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0b1837d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,155 @@ +#![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. + +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 [`[T]::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 [`[T]::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, + } + } +} |