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 a T: 'a to Foo<'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 a T to &String because T may or may not be a reference. You can, however, attempt to cast a T: '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 and Sized, 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.

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());