Hi,
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.