Static failable casting


I think I could handle all of my specialisation requirements, without the need for clarification or stablisation of specialisation rules, and do some other cool things, if I had an operator like as?. The as? cast operator would return an optional reference to a trait or concrete type, mutable or immutable, for example &mut Vec<String> or &dyn SomeTrait<u32>, depending on whether it is that type.

Here's a toy example using familiar traits:

fn is_iter_empty(iter: &impl (Iterator + Clone)) {
   // Note the `as?` operator
   if let Some(exact_iter) = iter as? &dyn ExactSizeIterator  {
      return exact_iter.is_empty()
   return iter.clone().next().is_none()

Generally it would return Some(T) as long as it can statically determine the value. It wouldn't be able to go from &dyn SomeTrait to &dyn OtherTrait unless SomeTrait: OtherTrait.

I assume the compiler should be able to resolve this statically, even in the case of generics, due to monomorphisation. I assume this should be able to be done with a lot of the same compiler code that already resolves types and traits.

There also shouldn't be a need to specify the trait on the interface, as it's not a strict requirement on the caller. Although perhaps something will need to be added internally to ensure trait implementations are provided as-needed.

From my layman's perspective this seems to be much more straightforward than specialisation, but is not a replacement for it (it complements it). It can cover many similar use cases for library builders with a much more straightforward fallback path, and it also allows other patterns that specialisation may not cover.

There is precedent for a similar operator in Swift.

What do you think?

This would run into the same fundamental issue as full specialization: it would make monomorphization depend on lifetime information. Consider:

fn evil(r: &u32) {
    if let Some(static_ref) = r as? &'static u32  {
        println!("`r` is `'static`");
    } else {
        println!("`r` is not `'static`");

Any feature that lets you write a function like the one above has to be disallowed in Rust. Lifetimes can only influence whether code compiles, not how it runs.

1 Like

(Also, if you can accept restricting your types to be 'static, you can use std::any to emulate this)

1 Like

Ah thanks for the clarification, that makes sense and might be an issue.

Although I think that's fine with my prerequisite that it fails if it tries to cast to something that's not known statically. So in your example it would always return None. I agree that this is an ergonomics issue at minimum though, perhaps the compiler could warn you that it will always fail.

It's also possible I'm not understanding some edge case.

I imagine it would get the same checks as this code:

fn less_evil(r: &u32) {
    let static_ref: Option<&'static u32> = Some(r);

Except instead of a compile-time error using as?, you'd get None.

Thanks for the suggestion. Although I also don't believe it can work with unsized types (dyn). For me casting with dyn is a primary use case, so I'd need an implementation that can support it.

See my playground implementation, and this discussion.

I believe the current recommended way to do trait casting is something like this:

trait AsX {
    fn as_x(&self) -> &dyn X;
impl AsX for SomeType {
    fn as_x(&self) -> &dyn X> { self }

I'm not aware of any way to do this without having to manually implement a trait like this. I believe though there are some crates that do trait casting, but with a lot of hacks and restrictions. I don't think they would work in my case.