macro_rules! cast {
($value:expr, $T:ty) => { ... };
($value:expr) => { ... };
}
Expand description
Attempt to cast the result of an expression into a given concrete type.
If the expression is in fact of the given type, an Ok
is returned
containing the result of the expression as that type. If the types do not
match, the value is returned in an Err
unchanged.
This macro is designed to work inside a generic context, and allows you to
downcast generic types to their concrete types or to another generic type at
compile time. If you are looking for the ability to downcast values at
runtime, you should use Any
instead.
This macro does not perform any sort of type conversion (such as
re-interpreting i32
as u32
and so on), it only resolves generic types to
concrete types if the instantiated generic type is exactly the same as the
type you specify. If you are looking to reinterpret the bits of a value as a
type other than the one it actually is, then you should look for a different
library.
Invoking this macro is zero-cost, meaning after normal compiler optimization steps there will be no code generated in the final binary for performing a cast. In debug builds some glue code may be present with a small runtime cost.
§Restrictions
Attempting to perform an illegal or unsupported cast that can never be successful, such as casting to a value with a longer lifetime than the expression, will produce a compile-time error.
Due to language limitations with lifetime bounds, this macro is more restrictive than what is theoretically possible and rejects some legal casts. This is to ensure safety and correctness around lifetime handling. Examples include the following:
- Casting an expression by value with a non-
'static
lifetime is not allowed. For example, you cannot attempt to cast aT: 'a
toFoo<'a>
. - Casting to a reference with a non-
'static
lifetime is not allowed if the expression type is not required to be a reference. For example, you can attempt to cast a&T
to&String
, but you can’t attempt to cast aT
to&String
becauseT
may or may not be a reference. You can, however, attempt to cast aT: 'static
to&'static String
. - You cannot cast references whose target itself may contain non-
'static
references. For example, you can attempt to cast a&'a T: 'static
to&'a Foo<'static>
, but you can’t attempt to cast a&'a T: 'b
to&'a Foo<'b>
. - You can cast generic slices as long as the item type is
'static
andSized
, but you cannot cast a generic reference to a slice or vice versa.
Some exceptions are made to the above restrictions for certain types which
are known to be lifetime-free. You can cast a generic type to any
lifetime-free type by value or by reference, even if the generic type is not
'static
.
A type is considered lifetime-free if it contains no generic lifetime
bounds, ensuring that all possible instantiations of the type are always
'static
. To mark a type as being lifetime-free and enable it to be casted
to in this manner by this macro it must implement the LifetimeFree
trait. This is implemented automatically for all primitive types and for
several core
types. If you enable the std
crate feature, then it will
also be implemented for several [std
] types as well. If you enable the
alloc
crate feature, then it will be implemented for several alloc
types without linking to the standard library as the std
feature would.
§Examples
The above restrictions are admittedly complex and can be tricky to reason about, so it is recommended to read the following examples to get a feel for what is, and what is not, supported.
Performing trivial casts:
use castaway::cast;
let value: u8 = 0;
assert_eq!(cast!(value, u8), Ok(0));
let slice: &[u8] = &[value];
assert_eq!(cast!(slice, &[u8]), Ok(slice));
Performing a cast in a generic context:
use castaway::cast;
fn is_this_a_u8<T: 'static>(value: T) -> bool {
cast!(value, u8).is_ok()
}
assert!(is_this_a_u8(0u8));
assert!(!is_this_a_u8(0u16));
// Note that we can also implement this without the `'static` type bound
// because the only type(s) we care about casting to all implement
// `LifetimeFree`:
fn is_this_a_u8_non_static<T>(value: T) -> bool {
cast!(value, u8).is_ok()
}
assert!(is_this_a_u8_non_static(0u8));
assert!(!is_this_a_u8_non_static(0u16));
Specialization in a blanket trait implementation:
use std::fmt::Display;
use castaway::cast;
/// Like `std::string::ToString`, but with an optimization when `Self` is
/// already a `String`.
///
/// Since the standard library is allowed to use unstable features,
/// `ToString` already has this optimization using the `specialization`
/// feature, but this isn't something normal crates can do.
pub trait FastToString {
fn fast_to_string(&self) -> String;
}
impl<T: Display> FastToString for T {
fn fast_to_string<'local>(&'local self) -> String {
// If `T` is already a string, then take a different code path.
// After monomorphization, this check will be completely optimized
// away.
//
// Note we can cast a `&'local self` to a `&'local String` as `String`
// implements `LifetimeFree`.
if let Ok(string) = cast!(self, &String) {
// Don't invoke the std::fmt machinery, just clone the string.
string.to_owned()
} else {
// Make use of `Display` for any other `T`.
format!("{}", self)
}
}
}
println!("specialized: {}", String::from("hello").fast_to_string());
println!("default: {}", "hello".fast_to_string());