pub trait Nullable {
const NULL: Self;
fn is_null(&self) -> bool;
}
macro_rules! impl_nullable_integer {
($first:ty, $( $rest:ty ),* $(,)?) => {
impl Nullable for $first {
const NULL: Self = 0;
#[inline]
fn is_null(&self) -> bool { *self == Self::NULL }
}
impl_nullable_integer!($( $rest, )*);
};
() => { };
}
impl<T> Nullable for *const T {
const NULL: Self = std::ptr::null();
#[inline]
fn is_null(&self) -> bool { *self == Self::NULL }
}
impl<T> Nullable for *mut T {
const NULL: Self = std::ptr::null_mut();
#[inline]
fn is_null(&self) -> bool { *self == Self::NULL }
}
impl_nullable_integer!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize);
impl<T> Nullable for Option<T> {
const NULL: Self = None;
#[inline]
fn is_null(&self) -> bool { self.is_none() }
}
impl Nullable for () {
const NULL: Self = ();
#[inline]
fn is_null(&self) -> bool { true }
}
#[macro_export]
macro_rules! null_pointer_check {
($ptr:expr) => {
$crate::null_pointer_check!($ptr, Nullable::NULL)
};
($ptr:expr, $null:expr) => {{
#[allow(unused_imports)]
use $crate::Nullable;
if <_ as $crate::Nullable>::is_null(&$ptr) {
$crate::error_handling::update_last_error($crate::NullPointer);
return $null;
}
}};
}
#[derive(Debug, Copy, Clone, PartialEq, Fail)]
#[fail(display = "A null pointer was passed in where it wasn't expected")]
pub struct NullPointer;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn null_pointer_check_on_garbage_address_doesnt_segfault() {
let random_address = 12345 as *const u8;
assert!(!random_address.is_null());
}
#[test]
fn can_detect_null_pointers() {
let null = 0 as *const u8;
assert!(<_ as Nullable>::is_null(&null));
}
#[test]
fn can_detect_non_null_pointers() {
let thing = 123;
let not_null = &thing as *const i32;
assert!(!<_ as Nullable>::is_null(¬_null));
}
}