Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose the type_name intrinsic #1428

Closed
sfackler opened this issue Dec 27, 2015 · 167 comments · Fixed by rust-lang/rust#60066
Closed

Expose the type_name intrinsic #1428

sfackler opened this issue Dec 27, 2015 · 167 comments · Fixed by rust-lang/rust#60066
Labels
disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this RFC. T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC.

Comments

@sfackler
Copy link
Member

sfackler commented Dec 27, 2015

We have an intrinsic which returns a &'static str naming a type but it's not exposed in a safe/stable manner anywhere: http://doc.rust-lang.org/std/intrinsics/fn.type_name.html

Something like this should suffice:

pub fn type_name<T: Reflect + ?Sized>() -> &'static str {
     unsafe { std::intrinsics::type_name::<T>() }
}
@sfackler sfackler added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Dec 27, 2015
@nagisa
Copy link
Member

nagisa commented Dec 29, 2015

What is the usecase for this?

Probably would be good for diagnostics/panic messages in libraries. E.g. .unwrap() would be so much better if it said “called unwrap() on a None variant of Option<MonoType>” IMO. Exposing this would allow making similar messages possible in libraries.

@sfackler why the Reflect bound?

@sfackler
Copy link
Member Author

We use bounds like Any and Reflect to make sure that people are opting into paramicity violations. Getting a name of a type might be harmless enough that we could drop the bound, though.

Thoughts, @rust-lang/libs?

@durka
Copy link
Contributor

durka commented Dec 30, 2015

I think you would have to drop the bound for that example to be workable, no? Option::<T>::unwrap would need to add a T: Reflect bound and that's a breaking change AFAIK. Edit: guess not.

@Gankra
Copy link
Contributor

Gankra commented Dec 30, 2015

I would ditch the reflect bound, but I'm not actually that familiar with our current reflection strategy. If this makes it incoherent, that's bad.

I am slightly terrified that people will use this to impl adhoc name-based specialization.

@sfackler
Copy link
Member Author

You can already do that in a less messed up way by comparing TypeIds.

@jonas-schievink
Copy link
Contributor

@ticki macros don't have type information and don't work in generic contexts

@aturon
Copy link
Member

aturon commented Dec 31, 2015

@sfackler It needs the Reflect bound, or else Reflect really isn't doing much for us :)

That said, Reflect is likely going away anyway, if specialization lands.

@jonas-schievink
Copy link
Contributor

OTOH mem::size_of already violates parametricity

@aturon
Copy link
Member

aturon commented Dec 31, 2015

@jonas-schievink Indeed, but in a much "lossier" way. (FWIW, I'm not terribly fond of the whole Reflect business in any case, but until we deprecate it, we should at least not introduce new leaks.)

@brson
Copy link
Contributor

brson commented Jan 6, 2016

I don't even see anywhere in-tree where this is used except test cases. It was added in rust-lang/rust@e256b7f but even then didn't appear to be used.

Please lets come up with a concrete use case and use it, not just add the feature speculatively.

@sfackler
Copy link
Member Author

sfackler commented Jan 6, 2016

@brson I opened this issue because it was a thing I wanted to use for a concrete use case.

Specifically, rust-postgres reports this error when the user attempts to convert a Postgres value to an incompatible Rust value or vice versa, for example a Postgres VARCHAR to a Rust i32, but right now all it can say is that a value of type VARCHAR could not be converted to... something. If type_name was stable, I could stick a &'static str in that error type and be set.

@ranma42
Copy link
Contributor

ranma42 commented Jan 6, 2016

I would expect more something like

 // no need for Reflection, resolved at compile time, possibly make it even const
pub fn type_name<T>() -> &'static str
 // resolved dynamically, hence needs reflection, no obvious need for size bound
pub fn type_name_of_val<T: Any>(val: &T) -> &'static str

The former would be convenient when generating error messages as pointed out by @nagisa.
I expect the latter to be used in conjunction with Any, in order to provide a better error message when a downcast_{ref,mut} fails (in a way, this would provide a "string" version of TypeId):

pub fn downcast<'a, T: Any>(x: &'a Any) -> Result<&'a T, String> {
    match x.downcast_ref() {
        Some(y) => Ok(y),
        None => Err(format!("got {}, expected {}", type_name_of_val(x), type_name<T>())),
    }
}

@glaebhoerl
Copy link
Contributor

@ranma42 You can implement type_name_of_val using type_name without requiring an Any constraint, so I don't think this is the right distinction to make.

One distinction I think we should make is between things which exist for debugging/development purposes and things which are used for implementing "actual/intended program behavior". I think it's totally fine to pierce whatever abstraction boundaries you want in the former case, and not at all in the latter. Of course, how to prevent things intended for the former from being abused for the latter is an interesting question, but even Haskell has Debug.Trace which does IO inside pure code, but "only for debugging purposes".

So that then raises the question of what type_name is for. If it's mainly a debugging aid (I think you could plausibly argue that emitting better error messages falls in this category, even if that's something which is observable runtime behavior), then having it be constraint-free seems fine. On the other hand, if actual program logic is going to rely on it in some way, then I strongly believe it should require a Reflect/Any constraint (this kind of thing is what they exist for).

@ranma42
Copy link
Contributor

ranma42 commented Jan 7, 2016

@glaebhoerl I might be missing something (in particular, the signature I used on the function is possibly wrong), but in my mind the purpose for type_name_of_val would be to obtain the type of a value wrapped in a fat pointer. This type might depend on the actual execution (for example a &Any reference might point to an Option::<u32> or to a u32 indifferently). How could this be implemented using type_name?

@glaebhoerl
Copy link
Contributor

Ah. In that case you probably wanted to write pub fn type_name_of_val(val: &Any) -> &'static str.

@rphmeier
Copy link

I also have a use-case for this (entity component system: asserting that each entity with a certain component has all of that component's dependencies). I can currently print a message along the lines of "Entity {} failed to fulfill all its dependencies", which isn't the most user-friendly.

@durka
Copy link
Contributor

durka commented Feb 4, 2016

One way to make the "only for debugging purposes" @glaebhoerl wants is to simply enforce it, by exposing a new fn like this:

#[cfg(debug_assertions)]
pub fn debug_typename<T>() -> &'static str {
    unsafe {
        intrinsics::type_name::<T>()
    }
}

#[cfg(not(debug_assertions))]
pub fn debug_typename<T>() -> &'static str {
    "[no type information]"
}

It doesn't need to be a macro, but it could be -- for some reason it feels better to have a macro that changes behavior at runtime (like debug_assert!) than a function. Also, if this function/macro is in core then intrinsics::type_name doesn't have to be stabilized.

@Gankra
Copy link
Contributor

Gankra commented Feb 4, 2016

@durka sadly this doesn't work quite right because cfgs are statically evaluated during compilation of the library. In this case, the standard library. So they would have to be using a custom "debug std" to see these names. Although perhaps this is what you're going for?

@durka
Copy link
Contributor

durka commented Feb 4, 2016

@gankro meh, you're right. So that's the real reason it would be a macro :) -- that way it could expand into a #[cfg(debug_assertions)] in the user crate.

macro_rules! debug_typename {
    ($t:ty) => {
        (
            #[cfg(not(debug_assertions))] "[no type information]",
            #[cfg(debug_assertions)] unsafe { ::std::intrinsics::type_name::<$t>() }
        ).0
    }
}

@brson brson closed this as completed Mar 1, 2016
@brson brson reopened this Mar 1, 2016
@brson
Copy link
Contributor

brson commented Mar 1, 2016

I wanted to use this recently to add some instrumentation to std. Though that doesn't require stabilization I admit this is useful :)

@jdm
Copy link

jdm commented May 10, 2016

I found this useful for adding meaningful debug logging to a function that accepts a generic type, where I was only concerned about figuring out if it was being called for certain types.

@sfackler
Copy link
Member Author

I'd like to add a stable wrapper on top of this, but I'm not totally sure where it should go. std::any seems like the least-wrong place?

@nagisa
Copy link
Member

nagisa commented Jun 29, 2016

We wanted to use this intrinsic before for pass names in MIR pipeline, but it seemed to return fully qualified pathes for foreign types, where all we hoped for was just the name of type.

It should either:

  • have an argument specifying whether the type_name returns fully qualified path; or
  • be renamed; or
  • be fixed to only ever return the type name only.

@sfackler
Copy link
Member Author

It may be overkill, but it could return a struct with the path, name, and generic arguments all split out.

@withoutboats
Copy link
Contributor

withoutboats commented Apr 19, 2019

There is already a user (@mitsuhiko) commenting in this thread who wants to use type_name in an important ecosystem library (failure) but needs to conform to an interface that expects &'static str. We do not need to find use cases, they are already present here.

The return type of this function does not have lang implications; this is a libs team question.

@sfackler
Copy link
Member Author

@oli-obk dtolnay's example still behaves the same on nightly today it looks like.

@sfackler
Copy link
Member Author

if and when we consider extending type_name to const fn, then it means that two calls of type_name::<MyType>() may yield different results.

But we aren't considering that here. If and when we want to make type_name a const fn, we can deal with that issue.

If the fix here is straightforward, then we can fold it into the stabilization, but it does not seem to me to be a blocker. It's probably desirable to have the same names in all contexts, but every use case I'm aware of wouldn't see that as a hard blocker.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 20, 2019

The return type of this function does not have lang implications; this is a libs team question.

Can someone with github permissions please remove the lang label in that case?

We do not need to find use cases, they are already present here.

I misunderstood that use case. I thought it was just expensive to use a String, but in fact the use case was that the public API already expected a &'static str.

If and when we want to make type_name a const fn, we can deal with that issue.

Yes, that's a problem for the future, and since we are explicitly stating that the exact output is unspecified, we can resolve that when the time comes.

every use case I'm aware of wouldn't see that as a hard blocker.

I think having this incoherence in-language is however worse because a polymorphic function instantiated with the same type arguments yield different results based on where it is called.

Note that such a polymorphic function can already yield different results based on its call site by using the address of a local (debug printing a local's address is fine). Additionally I feel like having differences in debug printing inside a no_std crate and outside a no_std crate is ok. I don't think we want to end up printing paths that point into liballoc even though the user printed a libstd type from their perspective. Imo, the current best effort method gives the most expected representation from a user perspective.

@Centril
Copy link
Contributor

Centril commented Apr 20, 2019

We do not need to find use cases, they are already present here.

I understand that not every use case will be satisfied by exposing only impl Debug. This restriction exists however for good reasons. fn fail can continue to return None as the default.

The return type of this function does not have lang implications; this is a libs team question.

It does have lang implications and I disagree that it is solely a libs team question. As Felix has shown, returning &'static str means that you can always materialize a type name at compile time, and moreover it has implications wrt. how much we encourage and discourage non-parametric reasoning.

Yes, that's a problem for the future, and since we are explicitly stating that the exact output is unspecified, we can resolve that when the time comes.

In my view it's not just about const fn; it's also about implementation quality. I think the current one has bugs and I'm not going to agree to stabilizing something with known bugs.

Note that such a polymorphic function can already yield different results based on its call site by using the address of a local (debug printing a local's address is fine).

A const fn?

Additionally I feel like having differences in debug printing inside a no_std crate and outside a no_std crate is ok. I don't think we want to end up printing paths that point into liballoc even though the user printed a libstd type from their perspective. Imo, the current best effort method gives the most expected representation from a user perspective.

It would be one thing if std:: was uniformly used in a single binary (including statics and consts). I would be fine with that. But this is not the case here. Moreover, in my view, such differences are definitely not fine with a const fn.

@sfackler
Copy link
Member Author

As Felix has shown, returning &'static str means that you can always materialize a type name at compile time,

The compiler already does this, both for diagnostics directly emitted by the compiler and inserted in debuginfo. Zero people are asking for anything more precise than that output.

I think the current one has bugs and I'm not going to agree to stabilizing something with known bugs.

I do not agree that this is a bug - the different outputs are all valid names for the type. You noted above that "the reason for doing it now is that otherwise I think it will never get done." If no one cares about this enough to invest time in solving it, then why is it so important to fix that you want to prevent people from using this functionality entirely?

Moreover, in my view, such differences are definitely not fine with a const fn.

Good thing this is not a const fn then.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 21, 2019

There are a few ways forward here, I'm going to list them and list the problems mentioned with them (opinion post will follow, so please call me out if I add an opinion to thist post).

  1. use &'static str
  2. use impl Debug as the return type
  3. 2 but maybe move to &'static str in the future
  4. 1 but const fn
  5. 2 but const fn
  6. 3 but const fn
  7. 5 but impl Debug implies an impl const Debug

Problems mentioned with 1.

  • varying output is considered a bug (@Centril can you elaborate on this in the not const fn case?)

Problems mentioned with 2.

  • useless for existing use cases
  • all problems from 1.

Problems mentioned with 3.

  • all problems from 2.

Problems mentioned with 4.

  • const fn called with same args must produce same output

Problems mentioned with 5.

  • all problems from 4.
  • useless for existing use cases

Problems mentioned with 6.

  • all problems from 5.

Problems with 7.

  • all problems from 4.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 21, 2019

So... I believe that if we are talking about const fn in the future at all, we can just go with the &'static str return type. My reasoning is as follows:

if we choose 5 (const fn + impl Debug) I see no reason not to go with 7 (also have impl const Debug on the return type). If we go with 7, the user can implement their own fn foo<T>() -> &'static str on top of type_name at some point.

const fn foo<T>() -> &'static str {
    &format!("{}", type_name::<T>())
}

So if we choose to keep the door open for const fn type_name in the future, we're basically just blocking the &'static str return type on sufficiently advanced const eval. Also, if we want a more structured return type in the future, we can just implement type_name on top of that at that point.

So, if we are talking const fn in the future, then I see absolutely no reason not to go with &'static str right now. Additionally we can and must block the const fn change on using absolute paths in order to make the output with the same input deterministic.

It would be one thing if std:: was uniformly used in a single binary (including statics and consts). I would be fine with that. But this is not the case here.

It is used uniformly. What you see above is not two calls to type_name within a single crate, but one call in a no_std crate and one in an std crate. Even if these are linked together at some point, that seems like an independent fact. Why does linking some code together make the way they are compiled individually special?

Additionally, calling a random library crate may give you arbitrary output. That this output depends on the type_name intrinsic is an implementation detail. Two calls to type_name within the same crate will always yield the same output.

That said, we can easily "fix" the output to use absolute paths, but imo the output will be less helpful to users than if we keep the current output. This is meant for debugging, and debugging is something humans do. Let's make it easier for humans to work with.

@Centril
Copy link
Contributor

Centril commented Apr 21, 2019

The compiler already does this, both for diagnostics directly emitted by the compiler and inserted in debuginfo.

The context here was that it was claimed that the return type is a T-Libs question. That the compiler does this for diagnostics and for debuginfo is irrelevant. These are not part of the language's specification but with type_name() -> &'static str that changes.

  • varying output is considered a bug (@Centril can you elaborate on this in the not const fn case?)

For const fn I think it's more than a bug; I consider it a soundness issue as well.

That a function as simple as type_name have different results, based on what crate it is in, is in my view hugely surprising. Even if it isn't a const fn, it should act as one.

So... I believe that if we are talking about const fn in the future at all, we can just go with the &'static str return type.

This discounts the other reasons for impl Debug:

  • piggybacking on Debug to more clearly note the for-debugging-only nature
  • discouraging matching on the &'static str

If we go with 7, the user can implement their own fn foo<T>() -> &'static str on top of type_name at some point.

Emphasis on at some point. The format! and format_args! machinery use both trait objects and function pointers baked inside structs. It seems to me that this requires an effect system that is more deeply baked into the compiler than #2632 comes close to. Thus, I think that the point you are referring to is far off and may not even happen ever (I hope it will). And even if we believe it is likely to happen at some point, this still requires type_name to be const fn. I also don't see why "at some point" is a good justification for relaxing things now. It is a good justification for at that point. I don't agree with similar arguments re. specialization that have been made here saying that "stable specialization will happen at some point".

Why does linking some code together make the way they are compiled individually special?

The inverse question should be answered in my view -- why is the compilation model of linking together independent crates relevant?

That said, we can easily "fix" the output to use absolute paths, but imo the output will be less helpful to users than if we keep the current output. This is meant for debugging, and debugging is something humans do. Let's make it easier for humans to work with.

I disagree that it makes things easier for humans or that it is more useful for debugging. If you are e.g. logging errors then it seems unfortunate that some errors should mention core:: and some std:: and that you should have to implement special logic to de-duplicate things when that difference occurs. Moreover, if you want to act upon the output of type_name, I think it's more useful to get the actual location something is defined in rather than getting re-exports.

@mitsuhiko
Copy link
Contributor

The context here was that it was claimed that the return type is a T-Libs question. That the compiler does this for diagnostics and for debuginfo is irrelevant. These are not part of the language's specification but with type_name() -> &'static str that changes.

I don't see how this is true. The intention is this to give access to the debug information available to the compiler, not for this to be specified as part of the language with the exact format.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 21, 2019

This discounts the other reasons for impl Debug:

  • piggybacking on Debug to more clearly note the for-debugging-only nature
  • discouraging matching on the &'static str

What I'm saying is that users will end up being able to do these two things even with impl Debug. The docs are discouraging this already. Putting stumbling stones in the way of usability will only enourage hacky workarounds.

For const fn I think it's more than a bug; I consider it a soundness issue as well.

I fully agree, my question was about without type_name being const fn. I explicitly created separate discussion points for with const fn and without.

That a function as simple as type_name have different results, based on what crate it is in, is in my view hugely surprising. Even if it isn't a const fn, it should act as one.

Any sort of debugging code will have surprising results. Usage of the file! macro will give you different results depending on which computer you are compiling your crate on. One may argue that file! is a macro while type_name isn't, but we could implement file! as a function/intrinsic just fine.

The format! and format_args! machinery use both trait objects and function pointers baked inside structs. It seems to me that this requires an effect system that is more deeply baked into the compiler than #2632 comes close to. Thus, I think that the point you are referring to is far off and may not even happen ever (I hope it will).

How long it takes for something to become stable is not relevant to my argument. We could use impl ToString as the return type and the argument would work just as well.

why is the compilation model of linking together independent crates relevant?

If you compile a crate on one computer as a cdylib and have it export a type name, and use it from a different crate compiled on another computer with a possibly different rustc, you don't have any guarantees for equality of the result of printing the same type. But through the cdylib (or e.g. by exchanging info via network) we may end up in a situation where both stringified versions of a type end up in a single runtime program.

If you are e.g. logging errors then it seems unfortunate that some errors should mention core:: and some std:: and that you should have to implement special logic to de-duplicate things when that difference occurs.

Deduplication is only useful if you are automatically processing things.

Moreover, if you want to act upon the output of type_name, I think it's more useful to get the actual location something is defined in rather than getting re-exports.

I thought the argument for deterministic type_name output was that nothing should act upon the result of type_name. Or are you talking about user action, and not codified action? Even so, is it really helpful for the user to get some_crate::Foo if they are using other_crate::bar::Bar which is a reexport of some_crate::Foo, but their dependency list doesn't have some_crate in it.

I'm ok with changing the output to absolute paths, especially since we're not guaranteeing output stability in any way. I just find it surprising to have a different output than what diagnostic messages are giving us. With diagnostics we are trying to give output that is close to what the user expects at the site where they used the type. type_name just gives this ability to users.

@eddyb
Copy link
Member

eddyb commented Apr 24, 2019

Has anyone brought up the possibility of Any using this to provide some debuggability?
Unless we don't want to encumber Any users with one string per type.

@Centril
Copy link
Contributor

Centril commented Jun 7, 2019

I still feel strongly that -> impl Debug would give the language more freedom and discourage, in a good way, against matching on the type_name::<T>() as a substitute for specialization. That said, now that the PR for deterministic output has landed, in the spirit of compromise:

@rfcbot resolve action-plan

(I would still like to see a report in brief on the stabilization PR however before it is merged)

@rfcbot rfcbot added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. labels Jun 7, 2019
@rfcbot
Copy link
Collaborator

rfcbot commented Jun 7, 2019

🔔 This is now entering its final comment period, as per the review above. 🔔

@gnzlbg
Copy link
Contributor

gnzlbg commented Jun 13, 2019

From a brief discussion on Discord with @oli-obk , it should actually be trivial to make type_name a const fn under a separate unstable feature(const_type_name).

Some comments mention potential issues with making type_name a const fn, and I think it might be useful to be able to try those examples out on nightly at least.

@rfcbot
Copy link
Collaborator

rfcbot commented Jun 17, 2019

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

The RFC will be merged soon.

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this RFC. and removed final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. labels Jun 17, 2019
@Gankra
Copy link
Contributor

Gankra commented Jul 24, 2019

s-soon?

@Centril
Copy link
Contributor

Centril commented Jul 24, 2019

Well there's nothing to merge in this repo; it's not a PR...

@Lokathor
Copy link
Contributor

It looks like people largely agreed to make type_name() -> &'static str a non const function for now.

We just have to write up the actual RFC for it. With an actual RFC, that discussion can be pointed at this issue as a "pre-discussion" and should be able to go through quicker than a normal RFC.

@sfackler
Copy link
Member Author

This does not require an RFC. I just need to get around to finishing up rust-lang/rust#60066

sfackler added a commit to sfackler/rust that referenced this issue Jul 25, 2019
Centril added a commit to Centril/rust that referenced this issue Jul 25, 2019
Stabilize the type_name intrinsic in core::any

Stabilize `type_name` in `core::any`.

Closes rust-lang/rfcs#1428

FCP completed over there.

`RELEASES.md`: Prefer T-libs for categorization.
@oli-obk oli-obk closed this as completed Jul 26, 2019
@oli-obk
Copy link
Contributor

oli-obk commented Jul 26, 2019

This has now been stabilized as core::any::type_name with &'static str return type

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this RFC. T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging a pull request may close this issue.