`type_name` of opaque type

Hello,

While exploring opaque types, I came up with the following toy program

use std::fmt::Display;

fn type_name_of_val<T: ?Sized>(_val: &T) -> &'static str {
    std::any::type_name::<T>()
}

fn somefunc(arg: impl Display) -> impl Display {
    format!("Hay! I am not the same as \"{}\"", arg)
}

fn main() {
    let x = 123;
    let result = somefunc(x);
    dbg!(type_name_of_val(&result));
    println!("{}", result);
}

Is it possible to add a type declaration to the line let result = somefunc(x);?

According to type_name the type of result is alloc::string::String, but this is only the invisible concrete type. We are not allowed to declare result as a String here.

It is possible to write let result: &dyn Display = &somefunc(x);, so in a way the return type of somefunc is dyn Display - only that this is not a valid type for a variable.

If no type can be written down for variable result, isn't this a (slight) conceptual problem in the language?

"Annotation".

Not currently (without some sort of type erasure).[1] impl Trait in local bindings is part of the RFC, but not available yet. Technically it would be a fresh opaque type though.

The opaque return type is not dyn Display, but it can be coerced to dyn Display (behind some sort of pointer type). Trait objects are distinct types. And non-Sized types can't be returned so far.

It's only "not a valid type for a variable" because it's not Sized. Rust may get unsized locals or such some day.

Eventually there will be ways to name it. And we might also get "return type notation" (RTN)[2] which would let you do something like

// The exact syntax probably won't look like this but hopefully
// you get the idea
let result: <fn#somefunc>::Output = somefunc(x);

but for now it can be problematic sometimes, yes.


Incidentally, there are other unnameable types in Rust, opaque types (-> impl Trait) just made it more common. Namely closures, function items, and compiler-generated futures (async fn outputs and async blocks).


  1. Or let result: _ = ... :wink: ↩︎

  2. you can sorta emulate it today, sometimes ↩︎

3 Likes

I guess I'll add, if you meant, "can I ever annotate it as String?", the answer is no -- part of the point of opaque return types[1] is that it gives the function author flexibility to change their implementation without breaking downstream. So long as the abilities of the type remain the same,[2] they can return an entirely new type without it being a breaking change.[3]


  1. and eventually associated types, type aliases, etc ↩︎

  2. including the leaky bits like auto traits ↩︎

  3. And some leaky things like the size of the type aren't considered breaking changes. Which is also true when you can name it. ↩︎

Many thanks for your very helpful explanation!

No, it was clear to me that the concrete type was lost. That's why I was surprised to see type_name showing the original type. I was expecting it to return "impl Display" or something similar, like with trait objects.

Right, so despite every opaque type being distinct from a consumer perspective (i.e. mostly/notionally/in-spirit), they are really just "thin aliases" around existing types. But trait objects are actually distinct types, with their own implementations and so on.

For example.

This also means you can sometimes get around the opaqueness,[1] but if your code breaks in the future because you did so, reasonable people would likely not blame the opaque type returner.


  1. for 'static types and if there's not an actual unique/unnameable type involved, like a closure ↩︎

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.