Skip to content

add match_ty based on cast_fns#37

Draft
clouds56 wants to merge 10 commits intosagebind:masterfrom
clouds56-contrib:cast_fns
Draft

add match_ty based on cast_fns#37
clouds56 wants to merge 10 commits intosagebind:masterfrom
clouds56-contrib:cast_fns

Conversation

@clouds56
Copy link

@clouds56 clouds56 commented Sep 30, 2025

Fixes #35.

This PR is based on #36, I'd like to perform rebase when #36 landed.

  • add try_cast_from, get_cast_fns for TryCast*
  • every TryCast trait should implemented CAST_METHOD (just for test, tell which trait is actually calling on), can_cast, unchecked_from and unchecked_to, other methods would derived from these methods
  • add can_cast!, get_cast_fns! and match_ty! macros
type From = u8;
type To = u8;

assert!(can_cast!(From, To));

if let Some((from, to)) = get_cast_fns!(From, To) {
  assert_eq!(from(1), 1);
  assert_eq!(to(1), 1);
}

let result: From = match_ty!(To, {
  u8 => from(1u8),
  u16 => from(2u16), // this would never called
  _ => unreachable!()
});

It would be better naming

  • current match_type to match_cast
  • and match_ty in this PR to match_type

But for non-breaking change, we naming it match_ty

@clouds56 clouds56 marked this pull request as draft September 30, 2025 07:52
@clouds56
Copy link
Author

This would somehow also address #24 by implementing cast_from!

@clouds56
Copy link
Author

Perform a compile-time style match over a single source type against one or
more candidate destination types, executing the first matching branch.

This macro is the type-level analogue to [match_type!], but instead of
matching on the runtime value of an expression it examines only the static
type
From (the first argument). Each arm lists one or more concrete
destination types separated by |. If any of those destination types can
be (symmetrically) cast to/from From (i.e. they are identical under this
crate's rules), the corresponding branch expression is evaluated and its
value becomes the macro's result.

Internally this uses [can_cast!] (fast path) and/or [get_cast_fns!]
(when you request the function pointers) to test feasibility. Untaken
branches compile away completely; there is no runtime overhead after
monomorphization. No data transformation occurs: only identity casts for
concretely equal types (respecting lifetime / lifetime‑free constraints)
are considered matches.

Syntax

Basic form (boolean style matching on type only):

match_ty!(From, {
    Type1 => expr1,
    Type2 | Type3 => expr2,
    _ => default_expr,
})

A leading | before the first type in an arm (| Type1 | Type2 => ...) is
also accepted for stylistic consistency with normal Rust pattern groups.

Function‑capturing form (obtain zero‑cost cast function pointers):

match_ty!(From, (from_fn, to_fn), {
    TargetType => expr_using_from_fn_and_to_fn,
    _ => fallback,
})

In a matching arm, from_fn has type fn(TargetType) -> From and to_fn
has type fn(From) -> TargetType. In non‑matching arms those names are not
bound (the code for that arm is never executed anyway). You may ignore one
of them with _.

An entirely empty arm set is permitted: match_ty!(T, {}) expands to ().
A final default arm (_ => ...) is otherwise required because you cannot
enumerate all possible types.

Differences from [match_type!]

  • Operates only on types; no value is evaluated or bound.
  • Branch expressions cannot pattern‑match on a value of the matched type.
  • Directly returns the branch expression (no intermediate Result).

Captured function pointer ordering

The tuple captured internally (and exposed via your (from_fn, to_fn)
pattern) is (fn(Target) -> From, fn(From) -> Target). Naming them from
and to is a common convention used in the examples below.

Examples

Basic categorization:

use castaway::match_ty;
fn classify<T: 'static>() -> &'static str {
    match_ty!(T, {
        u8 | i8 => "byte",
        u16 => "short",
        _ => "other",
    })
}
assert_eq!(classify::<u8>(), "byte");
assert_eq!(classify::<i8>(), "byte");
assert_eq!(classify::<u16>(), "short");
assert_eq!(classify::<u32>(), "other");

Specializing while capturing cast functions (note _ ignores the second):

use castaway::match_ty;
fn default_value<T: 'static + Default>() -> T {
    match_ty!(T, (from, _), {
        u8 => from(1u8),
        i32 => from(2i32),
        _ => T::default(),
    })
}
assert_eq!(default_value::<u8>(), 1);
assert_eq!(default_value::<i32>(), 2);
assert_eq!(default_value::<u16>(), 0); // via Default

Accessing both conversion directions directly:

use castaway::match_ty;
fn maybe_round_trip<T: 'static + Copy>() -> Option<(fn(u32)->T, fn(T)->u32)> {
    match_ty!(T, (from, to), {
        u32 => Some((from, to)),
        _ => None,
    })
}
assert!(maybe_round_trip::<u32>().is_some());
assert!(maybe_round_trip::<u16>().is_none());

Empty usage (rare):

use castaway::match_ty;
const _: () = match_ty!(u8, {}); // Expands to ()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: is it possible to delay running expression until type matches?

1 participant