Fieldnames for an instance of a structure

Hi, been a while since I have asked a dumb question in New to Julia so here we go.

Is there a way to define the function fieldnamesor similar to give me the field names for an instance of a structure? I would like to see the fields of mine (or other people’s) custom structures.

AFAIK, fieldnames works on the type, and it throws an error if you use it on an instance of the structure. But for that you need to know the custom type name, that you might have forgotten (not that that would ever happen to me).

However, a hack, that I have been using is misspelling a field on purpose, then the error shows all the available field names. Is there a better way? I have seen some topics on the question, such as this one, but that solution seems even worse than my hack.

Here is what I mean
Code snippet

struct Foo{T}
    a::T
    b::T
end                 # define a struct and forget its fields 3 secs later because I have a memory span of a goldfish
Fooinst = Foo(4,2)  # create an instance
fieldnames(Foo)     # this works, prints the fields
fieldnames(Fooinst) # this does not work because it is not on the type, but I need to know the type name "Foo"
Fooinst.c           # misspell on purpose, this works, because the error throws the field names

Output:

julia> struct Foo{T}
           a::T
           b::T
       end                 # define a struct and forget its fields 3 secs later because I have a memory span of a goldfish

julia> Fooinst = Foo(4,2)  # create an instance
Foo{Int64}(4, 2)

julia> fieldnames(Foo)     # this works, prints the fields
(:a, :b)

julia> fieldnames(Fooinst) # this does not work because it is not on the type, but I need to know the type name "Foo"
ERROR: MethodError: no method matching fieldnames(::Foo{Int64})
The function `fieldnames` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  fieldnames(::Core.TypeofBottom)
   @ Base runtime_internals.jl:339
  fieldnames(::Type{<:Tuple})
   @ Base runtime_internals.jl:341
  fieldnames(::UnionAll)
   @ Base runtime_internals.jl:338
  ...

Stacktrace:
 [1] top-level scope
   @ c:\Users\bblagov\Dropbox\JuliaNew\BEAVARs\dev\devCPZ.jl:27

julia> Fooinst.c       # misspell on purpose, this works, because the error throws the field names
ERROR: FieldError: type Foo has no field `c`, available fields: `a`, `b`
Stacktrace:
 [1] getproperty(x::Foo{Int64}, f::Symbol)
   @ Base .\Base_compiler.jl:54
 [2] top-level scope
   @ c:\Users\bblagov\Dropbox\JuliaNew\BEAVARs\dev\devCPZ.jl:28

The last line gives me the info that I need:

ERROR: FieldError: type Foo has no field c, available fields: a, b

What about fieldnames(typeof(Fooinst))?

There is also propertynames which might give different answers, but depending on your usecase that might not matter. Also usually it does give the same answer.

Seems like this is the issue:

Julia 0.7, which is 1.0 with deprecation warnings, behaves as follows:

julia> fieldnames(Foo)     # this works, prints the fields
(:a, :b)

julia> fieldnames(Fooinst) # this does not work because it is not on the type, but I need to know the type name "Foo"
┌ Warning: `fieldnames(v)` is deprecated, use `fieldnames(typeof(v))` instead.
│   caller = top-level scope at none:0
└ @ Core none:0
(:a, :b)

julia> VERSION
v"0.7.0"

I use these two in GMT.jl

fields(arg) = fieldnames(typeof(arg))
fields(arg::Array) = fieldnames(typeof(arg[1]))

I don’t even know why that issue is still open. Fields are defined along with types, while properties can be defined in associated methods and vary with instances. The different inputs make sense for fieldnames and propertynames, and the latter reasonably falls back to the former: propertynames(x) = fieldnames(typeof(x)).

I’ll also point out that these deliberately avoided supporting both instances and types as inputs because types themselves are instances of types. The fallback __names(instance) = __names(typeof(instance)) would recurse endlessly, so it would have to stop somewhere like __names(t::Type) = do_something(t). In that case, we would be forced to handle types of types in manual branches: [x isa Type ? __names(typeof(x)) : __names(x) for x in [1im, Complex]]. That sort of typeof-forwarding only works when types of types don’t have the characteristic of interest, like eltype.

@matthias314 This works, I haven’t thought about it, thanks! My only argument against it is that it is just a bit long (requires two tab completions).

But using propertynames does exactly what I expected, just didn’t know this existed. Thanks @BeastyBlacksmith!