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

Add std::function_name macro for getting the name of the current function #1719

Closed
wants to merge 6 commits into from

Conversation

sdroege
Copy link

@sdroege sdroege commented Aug 19, 2016

This was previously discussed in rust-lang/rust#35651

Rendered

@sdroege
Copy link
Author

sdroege commented Aug 19, 2016

I would also open to implementing this myself btw, from a short look it seems like all information required might already be available in libsyntax/ext/source_util.rs. The CodeMap mentions callees and callsites, but without looking closer I'm not sure if this only applies to macros calling macros.

`std::file`. It would return the fully qualified function name, which would be
the same as seen with `RUST_BACKTRACE=1`, e.g.

- `hello::main::h14dbb225e6ef2d49` for function `main` crate `hello`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems... less than good? Why are we trying to give this information. I'd much rather know just the fully qualified function name:

  • hello::main
  • hello::bar::foo
  • hello::bar::Foo::new
  • hello::bar::Foo<T>::new

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be without the hash, and otherwise only make a difference for cases with generic parameters? I'll add this to the unresolved questions, thanks for the feedback!

Intuitively I also prefer this, but there was probably a reason for not doing this for backtraces and consistency also seems useful.

Copy link

@strega-nil strega-nil Aug 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backtraces are implementation defined; this is not :)

Backtraces are based on the name you'd call this function in C or assembly, with mangling.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know, that convinces me that this is a better scheme indeed. I'll update the RFC accordingly

@strega-nil
Copy link

I want it to use the "real" qualified function name, but otherwise I really really want this.

@strega-nil
Copy link

So, question:

Do you see Foo<T>::new as being

struct Foo<T>;
impl Foo<T> {
    fn new() { }
}

fn main() {
    struct T;
    Foo::<T>::new() <- here
    Foo::<u32>::new() <- or here as well
}

@sdroege
Copy link
Author

sdroege commented Aug 19, 2016

That would actually depend on the details. But IMHO it should return exactly what you see in the code where the std::function! was inserted, i.e. in your case Foo<T>::new.
If there was a impl Foo<i32> (as compared to impl<T> Foo<T>), and the std::function! was inserted in there, it would be Foo<i32>::new.

Does that make sense? I should clarify that, if it does

@strega-nil
Copy link

@sdroege Definitely clarify. Also, remember, Foo<T> and Foo<i32> are two different types, so knowing that you're calling Foo<i32>::new rather than Foo<u32>::new might be a good idea.

@sdroege
Copy link
Author

sdroege commented Aug 19, 2016

Thanks noticing that, I'll update it on Monday. Also I noticed that trait implementations need to be handled too. Any preference for those?

@strega-nil
Copy link

strega-nil commented Aug 19, 2016

@sdroege Personally, how you'd call them, so <i32 as std::fmt::Display>::display, for example.

I don't know, tho :P, whatever you think is right :)

@eddyb
Copy link
Member

eddyb commented Aug 19, 2016

Getting the fully qualified name of the current function would actually be easier to implement, even if more invasive, than doing it during macro expansion.
However, some usecases (e.g. logging filters) may prefer an unadorned path.
It's possible intrinsics would work better here, fwiw.

@sdroege
Copy link
Author

sdroege commented Aug 19, 2016

@ubsan that makes sense, yes. Thanks.

Same question goes for closures, I'll check when I'm back home.

@eddyb I'll take a look at what using intrinsics would involve

@nrc nrc added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Aug 20, 2016
@ahicks92
Copy link

One advantage of an intrinsic is that you're not polluting the global namespace. If it's possible for there to be such thing as a stable intrinsic, I would personally prefer that approach.

If this needs to be a macro, it should probably be called function_name or something. function is something that a domain-specific language might want to use, and we don't have the RFC that allows for namespacing macros as of yet. Macro names are valuable if put in std: a macro foo in stdtakes foo away from the rest of the ecosystem.

@eddyb
Copy link
Member

eddyb commented Aug 20, 2016

I'd recommend function_path!, to go with module_path! (if it's just ::-delimited identifiers).

- Call the macro `function_name` to make conflicts with other macros less
  likely, make the name an "Unresolved Question" and mention in "Drawbacks" that
  adding a new macro takes it away from the global namespace

- Don't include the `module_path` anymore, it seems easy enough to just use
  the corresponding macro for that in addition if it is needed

- Clarify how the function names would look like in the context of
  generic functions, various types of `impl` blocks and closures

- Add naming of closures to "Unresolved Questions"

- Add suggestion of using an intrinsic instead of a macro to "Unresolved
  Questions"
@sdroege sdroege force-pushed the standard-function-macro branch from bda3a0e to 6c14afe Compare August 22, 2016 08:51
@sdroege
Copy link
Author

sdroege commented Aug 22, 2016

Thanks for all the comments. I've updated the text now to include all your suggestions, please take a look again and let me know what you think. A small changelog is in the commit message

@Centril
Copy link
Contributor

Centril commented Aug 25, 2016

Regarding closures, here are a few alternatives for naming closures:

  1. $owning_fn::<closure> (the current proposal) - This is probably good enough given that most closures (I think) are used in one-liner map/reduce/filter contexts.
  2. $owning_fn::<closure/$definition_line_number> - This helps for longer functions with many closures as you can at least see which the "offending" line is. Replace "/" with the preferred separator.
  3. $owning_fn::<closure/$nth_closure> - The first closure is 1, the second defined is 2, and so on...
  4. $owning_fn::<closure/$inferred_type> - The inferred type is probably too much info and probably technically difficult to achieve for little usability gain.

My favorite is alternative 2, and it wouldn't be too hard to implement. It could be combined with 3. for multiple closures on the same line, but that seems like a tiny share of all use cases.

@comex
Copy link

comex commented Aug 25, 2016

@Centril I think closure names should be implementation defined - mainly because I'd like it if rustc someday applied heuristics to generate 'smart' closure names like some_fn::<closure passed to 'map'>, some_fn::foo (if the closure was assigned to a local immutable variable foo), etc.

Actually, is there really much disadvantage to having names be implementation defined in general? There are other simplifications that might be nice to apply to long generic names, but would be hard to specify precisely. For example:

  • Since generic parameters don't have to be imported at the point where the generic item is defined, in some cases they should be written using package names. i.e. if module b imports module a's generic function foo and specializes it with its own type T, the name foo<T> would misleadingly suggest that T exists in module a, so it should be foo<b::T> instead. But probably the only way to make this syntax unambiguous is to prefix all parameters with package names, which would be very verbose in common cases.
  • When a generic parameter is specified using a type alias, it would be good to have the generated name use that alias rather than the fully expanded type - but this doesn't work if a single monomorphization is named from different places in different ways. Also, since type parameters can be determined very indirectly via type inference, it would be hard to define how far alias names should be carried.
  • Type parameters which have their default value could be omitted.
  • Would be nice to condense names that involve repeated references to the same type using some substitution scheme, if the type's name is long enough.
  • Not a simplification, but what if a generic parameter refers to the type itself? Actually, rustc seems to ban such types with the error cyclic type of infinite size, but in many cases the type would not really be of infinite size and ought to be allowed. (I'm going to file a bug report.)

@sdroege
Copy link
Author

sdroege commented Aug 25, 2016

@Centril 2) is actually a very good idea, thanks. I'll update that accordingly soon.

@comex I would consider all these strings as not-set-in-stone. They are for guiding the user where to find the corresponding code (or more exact to get some idea what that code is doing without looking it up), they are not meant for automatic processing.
That's what you mean with making them "implementation defined", that is, the macro gives some meaningful information to locate the definition of the function but the exact scheme how it does that can change from implementation to implementation.

As such, I could probably say in the RFC that the actual strings are implementation defined, clarify a bit more what they're good for and what not, and give some examples (i.e. how the first implementation will look like). Does that make sense to you?

@comex About your other comments, they make sense but seem a bit tricky to implement. I would leave that for later times. Especially the first one is an interesting point.

…mples

Also let the closure example include the line number of definition as
suggested in the PR.
@sdroege
Copy link
Author

sdroege commented Aug 29, 2016

Done so now. Any other feedback? How should this move forward?

@TimNN
Copy link
Contributor

TimNN commented Sep 7, 2016

I think I would like this RFC to at least mention nested functions, ie something like in this (very contrived) example:

fn main() {
    let foo = || {
        if 1 == 1 {
            fn bar() {
                println!("{}", function!())
            }

            bar();
        } else { // EDIT: else case added
            fn bar() {
                println!("{}", function!());
            }

            bar();
        }
    };

    foo();
}

Edit: (Added the else case) Also, are the two bar functions disambiguated somehow?

@sdroege
Copy link
Author

sdroege commented Sep 7, 2016

Thanks, I'll add something along those lines. Nested functions should probably be treated like closures in that regard, but including their actual name

@sdroege sdroege changed the title Add std::function macro for getting the name of the current function Add std::function_name macro for getting the name of the current function Sep 8, 2016
@sdroege
Copy link
Author

sdroege commented Sep 8, 2016

I've mentioned nested functions too now, thanks again

}
````

- Similar for generic `impl` blocks, e.g. `SomeStruct<T>::some_function` for
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this outputs generic version, rather than monomorphisation?

@nagisa
Copy link
Member

nagisa commented Sep 9, 2016

Thanks for all the feedback. That basically brings us back to square one though 😄 Do you have any suggestions / preferred way forward for this, anything that still keeps the macro useful?

I feel that looking at prior art is an important factor in making decision here. I’ve already pointed out the behaviour of C/C++ macros here. I since have remembered using a similar debug format in my python project: funcName formatter also outputs only the function name (as opposed to a full path).

What are other languages which provide a way to log function name for debugging purposes? Do they have a way to log the full path to the function? If yes, how often is it used by the users of the language (as opposed to the method to log just the function name, if there’s also a way to do that)? Do any of these languages include type parameters? If yes, why?


Regardless of prior art, I strongly suggest to abandon any attempts to uniquely identify any function by its name (or simply use UFCS).

@burdges
Copy link

burdges commented Oct 26, 2016

A type alias that represented the return type of the current function might be useful too.

@nrc
Copy link
Member

nrc commented Dec 21, 2016

ping @rust-lang/libs (on behalf of @ubsan), anyone got opinions on this one? Want to move it forward?

@sdroege
Copy link
Author

sdroege commented Dec 21, 2016

Sorry for not having any progress here in the last weeks, it's still on my list but other things got into the way. If someone else wants to move it forward, please go ahead.

This also is relevant for slog, e.g. https://github.com/slog-rs/slog/blob/9ec2bc5286c273fe7c0df7dd29c4f23f1ef9c705/src/lib.rs#L307 just uses "" as the function name currently.

@aturon
Copy link
Member

aturon commented Mar 28, 2017

The libs team discussed this RFC during triage. The motivation generally seems compelling, and we'd love to see something like this land. It seems to've stalled out a bit, though. I'm going to go ahead and close the RFC, for the sake of clearing the queue, but @sdroege (or anyone else) should feel free to reopen with revisions accounting for the discussion on thread.

@aturon aturon closed this Mar 28, 2017
@ckaran
Copy link

ckaran commented Oct 17, 2017

Is there any progress on this?

@sdroege
Copy link
Author

sdroege commented Oct 18, 2017

Is there any progress on this?

Not really, for myself I've postponed this for now as there are more urgent things to work on. Having this solved would nonetheless be nice, just that coming up with a meaningful and short scheme for "function name" is far from trivial.

@ckaran
Copy link

ckaran commented Oct 25, 2017

Why try to find a silver bullet? Create a macro that accepts arguments, and let the user figure out what they want. This is a stab at the idea (please forgive any errors, I started learning rust ~4 weeks ago):

enum FuncInfoOptions {
    Name,
    Path,
    GenericArgumentList, // Generic arguments before being specialized to concrete types
    ConcreteArgumentList,
    GenericReturnType,
    ConcreteReturnType,
    ... // Whatever other options people think might be useful.
}

macro_rules! funcInfo() {
    // Here there be magic incomprehensible to Mortal Man, 
    // or at least Man with <= 4 weeks of rust experience.
}

fn myFunc() -> () {
    println!("This function is named: {}", funcInfo!(Name));
    println!("This is the generic signature of this function: {} {}::{} {}", funcInfo!(GenericReturnType), funcInfo!(Path), funcInfo!(Name), funcInfo!(GenericArgumentList));
    // Other exciting examples that make everyone think this is a good idea...
}

This method has the following advantages:

  1. You don't need to find a silver bullet. When you consider ordinary functions, closures, generics, and I don't know what else, figuring out what the user will truly want is going to be really difficult. Make your life easy, and give the user the tools to specify what they want.
  2. You can adapt to change. Rust is still evolving, and I expect it will continue to evolve for a long time to come. You can keep on adding new options to the enum without breaking the old options. If you do have to break the old options, this will likely be because the language has changed so drastically that you were forced to make the change.
  3. Users have significantly greater control. Having very fine-grained control over the output means that the user can create a log file format that makes sense for their own purposes; this means that instead of having to parse the output of the 'silver bullet' solution and create their ideal format, they can generate the ideal format directly. If the user has built in automatic bug reporting, this could be very, very useful.

I'm sure that there are other good reasons to implement a macro this way, I'm not familiar enough with rust to make the case for them.

- `function` would be nicer but easily conflicts with existing code
- `fn` would be consistent with how functions are declared
- `function_path`, makes more sense if the `module_path` would be
prepended (see below)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can func also be considered as a name?
I'm guessing probably not because there's column instead of col (presumably there was a col! before (but was renamed to column!) since I see it mentioned here )

But I don't think fn!() would be any good; wouldn't it conflict with fn ?

On the other hand, nothing seems to be using function.

@ghost
Copy link

ghost commented Jan 29, 2018

What about a compromise: function_name!() would always return:

  • ONLY the function name (that is without :: or <T, U> qualifiers) ie. some_func
  • PLUS the line number of that function's definition. This way, the function can be uniquely identified no matter what. ie. /76

However, the function path (ie. those ::, eg. main::inner2::inner_b) could also be useful to see, to be honest, now that I think about it.

So, maybe it would be desirable to have a way to specify some arg(s) to help select which of those you want to have in the resulting output.

  • function_name!(Function_Name::Path | Function_Name::Line | Function_Name::Generic_Arguments | Function_Name::Struct_Name | Function_Name::Struct_Generic_Arguments | Function_Name::Trait_Name) - ok, maybe not this crazy, some may just be part of ::Path
  • function_name!(Function_Name::None)(as in no extra args) which would be equivalent to function_name!(Function_Name::Name) (or maybe this should be always implied - ie. you can't NOT show function name)

Eh, just a thought. Feel free to ignore :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.