blob: a4f98adf628deb33f192390e4b666ad9fa5997ab [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#![no_std]
/// Like [`From`], but supporting target-specific casts.
///
/// [`From`] does not support casts which are not valid on all targets (such as
/// `u32` to `usize`, which is not valid on 16-bit platforms). `CastFrom`, by
/// contrast, supports all conversions which are valid on a given target. As a
/// consequence, uses of `CastFrom` may not be portable to all targets.
///
/// Like [`From`], `CastFrom` only supports lossless casts.
pub trait CastFrom<T> {
fn cast_from(t: T) -> Self;
}
/// Like [`Into`], but supporting target-specific casts.
///
/// See [`CastFrom`]'s docs for more information.
///
/// Like [`Into`], `CastInto` only supports lossless casts.
pub trait CastInto<T> {
fn cast_into(self) -> T;
}
impl<T, U: CastFrom<T>> CastInto<U> for T {
fn cast_into(self) -> U {
U::cast_from(self)
}
}
/// Performs a lossless cast.
///
/// `cast!` performs the same cast as [`CastFrom::cast_from`] or
/// [`CastInto::cast_into`], but unlike those methods, it works in a `const`
/// context.
#[macro_export]
macro_rules! cast {
($e:expr) => { $crate::cast!($e => _) };
($e:expr => $t:ty) => {{
#[allow(clippy::as_underscore)]
if false {
// SAFETY: This branch is never taken.
#[allow(clippy::macro_metavars_in_unsafe)]
unsafe { $crate::__cast($e) }
} else {
// The preceding call to `__cast` ensures that the source type (the
// type of `$e`) can be casted into the destination type (`$t`) via
// `CastFrom`, which ensures that this is a lossless cast.
$e as $t
}
}};
}
/// Attempts to perform a lossless cast.
///
/// This macro evaluates to an expression of type `Option<$t>`. It is similar to
/// [`TryInto`], but it works in a `const` context.
#[macro_export]
macro_rules! try_cast {
($e:expr => $t:ty) => {{
let min = $crate::cast!(<$t>::MIN);
let max = $crate::cast!(<$t>::MAX);
// Triggered when `$e` is a literal which trivially satisfies one of
// these comparisons.
#[allow(unused_comparisons)]
// Triggered when invoked with `x + 1` or `y - 1`.
#[allow(clippy::int_plus_one)]
if $e >= min && $e <= max {
#[allow(clippy::cast_possible_truncation)]
Some($e as $t)
} else {
None
}
}};
}
// A test to ensure that `cast!` and `try_cast!` work in a const context, and
// work properly.
const _: () = {
let x: usize = cast!(0u32);
assert!(x == 0usize);
assert!(cast!(0u32 => usize) == 0usize);
assert!(try_cast!(0u32 => u16).unwrap() == 0u16);
assert!(try_cast!(256u16 => u8).is_none());
#[allow(clippy::identity_op)]
{
// Test more complex `$e:expr` fragments.
let x: usize = cast!(0u32 + 1);
assert!(x == 1usize);
assert!(cast!(0u32 + 1 => usize) == 1usize);
assert!(try_cast!(0u32 + 1 => u16).unwrap() == 1u16);
assert!(try_cast!(255u16 + 1 => u8).is_none());
}
};
/// # Safety
///
/// It is UB to call this function.
#[doc(hidden)]
pub const unsafe fn __cast<T, U: CastFrom<T>>(_t: T) -> U {
unsafe { core::hint::unreachable_unchecked() }
}
macro_rules! impl_cast_from {
($($src:ty => $dst:ty),* $(,)?) => {
$(
impl CastFrom<$src> for $dst {
#[allow(clippy::cast_lossless, clippy::cast_possible_truncation)]
fn cast_from(t: $src) -> $dst {
const _: () = assert!(size_of::<$src>() <= size_of::<$dst>());
// So long as `$src` and `$dst` are numeric primitives, the
// preceding assert ensures that this is a lossless cast.
t as $dst
}
}
)*
};
}
#[cfg(target_pointer_width = "16")]
impl_cast_from!(
u8 => usize,
u8 => u16,
bool => usize,
u16 => usize,
usize => u16,
usize => u32,
usize => u64,
);
#[cfg(target_pointer_width = "32")]
impl_cast_from!(
u8 => usize,
u8 => u16,
bool => usize,
u16 => usize,
u16 => u32,
u32 => usize,
u32 => u64,
usize => u32,
usize => u64,
);
#[cfg(target_pointer_width = "64")]
impl_cast_from!(
u8 => usize,
u8 => u16,
bool => usize,
u16 => usize,
u16 => u32,
u32 => usize,
u32 => u64,
u64 => usize,
usize => u64,
);