Closed beoran closed 4 years ago
// usage with constants type AstKind string const AstExpression = AstKind(nameof(AstExpression)) fmt.Println(AstExpression) // output: AstKind
What are the rules for that? I would expect it to output AstExpression. At least, C# works that way.
@creker: You are right. I fixed this in the proposal.
We started down this path with a suggestion for a way to get the names of the fields of a struct type as a []string
. Just for my own understanding, am I correct in thinking that this proposal does not support that?
@ianlancetaylor, indeed, as we discussed in the other issue, that use case is not necessary and makes nameof() confusingly different from what is in C#.
By staying close to the C# nameof we can stick to something "tried and tested", that will be familiar to some people.
However, as you can see from the examples above, nameof() could still be used to get the names of struct fields one by one, with a compile time error if the field does not exist. That is also useful for ORM, etc.
Also, to show the utility of a nameof() construct in a programming language, consider that in the C/C++/Objective-C langauges we have the C preprocessor with the #
stringify operation, which is used to good effect for many similar use cases as nameof() would be. Furthermore, a nameof() built -in function is much more clean and easy to understand than #
and the C preprocessor.
Can't this be already done via reflect?
@qtplatypus, No, the C# like nameof() in this proposal produces compile time constants, based on the names of the variables, which is not possible in the general case with reflect. Only the struct field case is covered, but this nameof proposal is much wider, as it allows to get names of anything that has a name and is in scope. Furthermore with reflect, you would get a you won't get a compile time error in case the field you are looking up does not exist.
@beoran I'm not even sure it's possible to get a name of the field with reflect. I'm not a reflect expert but poking around I didn't find any obvious way that would do this
type foo struct {
fld int
}
fooVal := foo{}
//some reflect magic to get "fld" just from fooVal.fld at runtime
Indeed, if it is a non exported field, reflect does not work. In this case, nameof() would work fine.
@beoran it doesn't even matter whether it's exported or not. I just don't see a way how can I go from struct field to its name at runtime. Only thing I can get from it with reflect is Type and Value. Both do not provide any link to the parent struct nor contain any information that it's actually a struct field.
We can see the utility of this, it would be simple to implement, and it is backward compatible. But it's not quite clear how useful this would be. It would help to point out some real existing code that would benefit from this. Are there places where the standard library would use this functionality?
In the logrus example above, the "after" code seems worse than the "before" code.
In general nameof
seems only useful for names that are visible to some external operation, such as SQL or JSON. In general this seems like it might be most useful for struct field names, but not anything else (like the earlier issue).
The logrus example seems worse after, but it is in fact better because it makes it easier to refactor the variable name with tools, and to keep the string and the name of the variable matched if it changes.
Speaking for myself, the code in https://gitlab.com/beoran/muesli, an embeddable scripting language interpreter written in Go, for example in https://gitlab.com/beoran/muesli/-/blob/master/ast.go would be helped by this.
For example:
func (astkind AstMetaKindSet) String() string { return "AstSet" }
would be better as func (astkind AstMetaKindSet) String() string { return nameof(AstSet) }
, because I am still developing the interpreter and changing the AST kinds, so I sometimes forget to change the strings as well. With nameof
I would get a compile time error in stead.
As for the Go standard library and runtime. I had a quick look and I found a few points where I think nameof could be useful:
I would like other people who gave thumbs up to this issue to contribute examples of how they would like to use nameof
as well.
While it may be true that using nameof
makes the logrus example easier to refactor, that isn't normally the most important characteristic of a piece of code. Readability is normally more important than ease of refactoring. So the example still doesn't seem entirely convincing.
Keeping the name of a function and a string description in a test seems fairly minor.
We can't really use nameof
for things like runtime/select.go where the runtime and the compiler have to be in synch. For that we should use code generation, instead. We already do that partially, in cmd/compile/internal/gc/runtime, and we could do more of it if we wanted to.
Still looking for clearer examples of where this is really useful.
It's also worth noting that the ease of refactoring due to nameof
can in some cases introduce a problem. If an exported name is based on a nameof
, then renaming an internal name may surprisingly change the external API in an invisible way. This may not be too likely but it's worth considering.
I understand you would like more examples but l have more or less reached the point where I can't think of more ways of how I'd like to use nameof(), without having it to play around with. So as I said before, I would like others interested in this feature also provide examples.
I don't feel confident in my ability to implement a prototype for this feature myself, otherwise I would play around a bit to see how I would use nameof.
I also don't have anything significant to add. Provided examples already cover pretty much everything I expect nameof to be useful for. In C# it was also solving significant pain point around implementing INotifyPropertyChanged interface. That doesn't apply to Go so we're left with everything else - logging, error augmentation, query building. All in the name of allowing safe refactoring.
On the point of refactoring, I do think that code that is easier to refactor but slightly harder to read is better than code that looks easier to read but is harder to refactor.
In general, I feel many Go programmers value ease of refactoring greatly. This is why many people program Go from an IDE, which facilitates refactoring. The nameof built in function would be a feature that helps refactoring in IDE more easily in some cases.
In the absence of compelling examples, this is a likely decline. Leaving open for four weeks for final comments.
Well, the examples we gave are perhaps enough to show that nameof() would be convenient, but I guess that to the Go developers, they do not demonstrate enough of an improvement to the Go language to warrant implementation.
I would like to ask the others who gave this issue thumbs up, or anyone else who reads this and likes the idea, again to provide some more examples as well. We will need them to convince the Go language developers that nameof() is worth while. I have proposed this issue for the benefit of the Go community, but if there is not enough interest, the Go team is well justified in pursuing more pressing issues.
No change in consensus. Closing.
@beoran I know I'm mad late on this issue, and I'm not an experienced Go developer by any means (exp'd outside of Go), but if you decide to propose this in the future: testing.
Currently using testify to do some simple interface mocks, and there's a need to bind to method signatures on a type: testObj.On("DoSomething", 123).Return(true, nil)
constantly. A nameof
, or similar, compile-time check on the name of a signature would be extremely useful here.
I'll also just throw my 2c in for doing anything to get rid of magic-strings and sentinel value paradigms in any statically typed language-- they are faux-simpler, horrible to use at scale, and demonstrate a very weak static type system. I feel nameof
is just as readable as a string for much less magic overall in this case.
For API concerns: it would probably have to function with package-level visibility, which is in line with Go in general. nameof
on exported signatures when used on sigs of another package, any signature within your own package.
Also, thank you to those who proposed this for making the effort. Sorry I came so late-- just hit the frustration point enough to even Google this. :)
Well, the issue was already closed, so I'm sorry but I think it's too late now, at least for the time being. But thanks for your examples, they really show the value of a built-in nameof. If more people come in to support this idea, I might consider resubmitting this issue with improvements.
@beoran I'm late too, but I'd like to show you my use case.
I'm using GORM to manage my database.
GORM has some filter functions that, in some cases, must receive a map[string]interface{}
(column name to value mapping).
My goal is to get the column name to use in these functions.
I have a NamingStrategy
(that implements schema.Namer interface) used to change column names according to the table name.
Using the NamingStrategy
object, I can get the table name by passing the struct name to the TableName
function. By passing the result and the struct field name to the ColumnName
function, I can get the column name.
I might get the struct name with reflect
, but I don't have a easy way to get a specific struct field name with it.
With nameof
, I could simply do something like this:
dbColumnName := db.NamingStrategy.ColumnName(db.NamingStrategy.TableName(nameof(MyStruct)), nameof(MyStruct.MyField))
Just like that, my code is now safe from typing errors in the column names and we might change the naming strategy without needing to change many places in code.
Heylow, too late as well, but frankly I could not have been sooner since I am learning Go since this week only ;-)
Being so new, I cannot give Go examples, but coming from C# I can give a little example on how it's been useful to me:
When I want to change the behaviour (not even the name) of a field (e.g. changing from "NOT NULL" to "NULL"able), I temporarily change the field name (e.g. from 'SomeField' to 'SomeFieldX') and launch the compilation.
Then I start changing the names everywhere the compiler complains there's an unknown field. That forces me to manually check the code everywhere that field is used until all compilation errors (ie. including all nameof() references) are resolved.
Like @beoran mentionned above, I too value safe refactoring much more than ease of reading.
Before C#6 (ie. before 'nameof' was available), I used to put things like const string SomeFieldName = "SomeField"
right next to each fields that I referenced by name. But because of the burden that is, I was clearly the only one and in practice, more than once problems due to refactoring field names flew under the radar until crashes happened at the customers...
Now, if I ever make something serious with Go, I am personally going to put those SomeFieldName
constants everywhere, and that won't ease readability either...
Also, in an IDE, being able to "Find all references" of something is so much useful.
For the latecomers, Inlike many projects, the Go project does not use GitHub Issues for general discussion or asking questions. Please direct your discussion to other forums. Thank you.
Stack Overflow with questions tagged "go"
The Go Forum, a web-based forum
Gophers Slack, use the invite app for access. The #general
channel is a good starting point.
Go Community on Hashnode with questions and posts tagged with "go"
IRC channel #go-nuts on Freenode
Proposal
I propose to add C# like nameof built-in compile time function. This proposal is based on the C# standard here: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/nameof. The main use of this is to make error values and logging easier to refactor, especially when using tools such as go pls, and also for use in ORM and intepreters where the name of an identifier, constant or stuct member, might often have to be mapped to an identical string value.
nameof built-in function
The nameof built-in function obtains the name of a variable, type, or member as a string constant:
As the preceding example shows, in the case of a type and a package, the produced name is not fully qualified. The same goes for struct members, only the name of the last field in the chain of field accesses is produced.
The nameof built-in function is evaluated at compile time and has no effect at run time.
You can use the nameof built-in function or ORM, for constants that evaluate to themselves as a string value, and for debugging, or to make error values or log messages more maintainable. For example:
Proposal template
Example: Logging and error values:
Before:
After:
While the before looks simpler, it has a problem: when you rename the
name
variable, especially using tools, "name" in the string will not be changed by the tool as well. The nameof function can be supported by such tools, and then refactor the name correctly so the error ans log message will also be correct.Example: Orm-ish use:
Before:
After:
In this case, the after code is also somewhat more complex, but it is definitely more correct. If the struct layout changes, then in stead of getting a run time error, we will get a compile time error which is far preferable in such a situation.
What is the cost of this proposal? (Every language change has a cost). The function has to be implemented, documented and tested.
Can you describe a possible implementation? See above for the proposal based on C# documentation, extended with some more situations.
How would the language spec change? The Built-in_functions section would need to get a sub chapter on "Obtaining Names" or such.
Orthogonality: how does this change interact or overlap with existing features? It does not overlap with reflect, except maybe for names of fields, but nameof() can also fetch names of non-expressions, which is not possible using reflect. It would also allow to check on compile time whether a variable or constant exists: eg:
Is the goal of this change a performance improvement? No.
Does this affect error handling? Only in that it improves the quality of error values in when renaming variables.
Is this about generics? No.