Is there a Swift equivalent of C#'s 'nameof()' function to get a variable or member's name at compile time?
Asked Answered
N

2

13

Ok, there's an existing question here on S/O with the following title:

Swift: Get Variable Actual Name as String

By it's name, it seems that's exactly what I want. However, looking at the accepted answer (and the other non-accepted ones), they are referring to key path manipulation, which isn't what I'm after. (i.e. This is not a duplicate!)

In my case, I want the name of one variable to be stored in a second variable of type string.

In C#, this is trivial using nameof, like so...

int someVar = 3

string varName = nameof(someVar)
// 'varName' now holds the string value "someVar"

Note: nameof() executes at compile-time, not run-time so no reflection or anything else is needed. The compiler simply subs in the name of the variable as if someone manually typed its name as a string constant.

It's pretty handy when you, for instance, want to define a query object where your member names match the query parameters passed in a URL.

Here's a pseudo-code example (i.e. this clearly won't compile, but shows what I'm after.)

Also, please don't focus on the URL aspects of this. This is definitely *bad code*. In real code I'd use URLComponents, not string-append a URL like I'm doing here. Again, this is *only* illustrating my question, not building URLs.

struct queryObject{

    let userName  : String
    let highScore : Int

    var getUrl:String{
        return "www.ScoreTracker.com/postScore?\(nameof(userName))=\(userName)&\(nameof(highScore))=\(highScore)"
    }
}

Here's how you'd use it and what it would return:

let queryObject = QueryObject(userName:"Maverick", highScore:123456)

let urlString = queryObject.getUrl

The return value would be:

www.ScoreTracker.com/postScore?userName=Maverick&highScore=123456

The advantages of having access to a string representation of a variable, instead of using string constants are many:

  1. No hard-coded strings.
  2. You can use refactoring/renaming of symbols as usual and the generated strings track
  3. Allows you to keep your API and implementation in sync which aids in readability.

For instance, say the API changed the query param from userName to userId, all one would have to do is refactor the variable name and the output would update accordingly without having to manually touch any strings.

So, can this be done in Swift? Can you get the actual variable name and store it in a second variable?

Nicolina answered 19/12, 2017 at 16:34 Comment(24)
You can't, Swift variable names are abstracted away at compile time. They're mere placeholders for register and memory addresses determined by the compiler. The information might make its way into debug executables, but that's not publicly available API.Billhook
One a side note, you should really avoid using String to encode URLs. You'll have a much easier time with URL. Also, take a look at URLComponents and URLQueryItem for a better way of generating urls with parameters.Billhook
Like I mentioned above, I believe C# does this at compile-time, and Swift too would have access to them at compile-time as part of the source tokenization step. I was hoping Swift had something similar. And as for your string comment, this is example code, only to illustrate a point simply. We don't use URLs stored in strings, but writing out a full example showing how we build up URL objects is outside of the scope of this question. It's just a KISS example.Nicolina
I'm not saying it's not possible, I'm just saying it's not (yet?) being done.Billhook
Any ideas in how we could achieve this (outside of writing our own pre-processor that is?)Nicolina
I'm curious myself, i'm looking into it right nowBillhook
You could use key paths as a way to centralize these hard coded strings, so they only need to be maintained in a single place: https://mcmap.net/q/329204/-getting-string-from-swift-4-new-key-path-syntaxBillhook
Yeah, but as shown there, that still requires hard-coding strings, defeating the entire purpose of what we're trying to do here.Nicolina
Let us continue this discussion in chat.Billhook
They block that here at work, unfortunately. Makes no sense, but we get 'access denied'. If you come up with something neat/clever/interesting, throw it in an answer. (On a side-note, I wonder who voted this down.)Nicolina
I didn't vote this down, it seems like a decently written question. Here's what I wrote in chat: Yes i'm aware, I literally said that. Just because something isn't an ideal solution doesn't mean it's "defeating the purpose" of the question. You asked "can this be done", the answer is "no... but here's a way to get as close as possible"Billhook
"The names of your members are used as the query parameters keeping the struct's usage clean and simple" This can actually be double edged. Innocent seeming rename refactorings can accidentally lead to broken API query strings.Billhook
That can be true of any refactor, and is even worse if you're forced to deal with strings since finding and replacing string values can't be scoped. But even so, again, that's just an example of using the feature that I'm asking about which is the variable names.Nicolina
@MarqueIV: I am fairly sure that no direct equivalent exists in Swift. #keyPath perhaps comes close to it (but requires @objc properties and classes), and Mirror(reflecting:) can do something at runtime. The Swift 4 KeyPath has no string representation (https://mcmap.net/q/329204/-getting-string-from-swift-4-new-key-path-syntax/1187415). – On the other hand, if the actual purpose is to encode/serialize a custom structure then the Swift 4 Codable protocol might be utilized, have a look at some JSONEncoder examples.Straddle
Is it possible to leverage Codable in your (actual) use case? Here's a quick example of how you could build a URL query encoder: gist.github.com/hamishknight/d1cabdf19cce90ca8458da9294562542Smilacaceous
@Hamish: That is exactly what I had in mind (but not coded). What do you think about posting a Q&A "How do I build my custom encoder/decoder" (with the URLQueryEncoder as a concrete example)? – There seems to be not much documentation about that topic yet (apart from the proposals).Straddle
@MartinR That's a great idea! I'll certainly try give it a shot at some point (though it may take a while) :).Smilacaceous
Hey... I actually have a few topics which would qualify as a Q&A-type thing. I've made the mistake in the past of posting a question that really wasn't a question, but showing how to do something cool, only to have that commented is not being the proper way to post such info. Does SO have something specific for when we've solved a particular problem, but want to share our process, exactly like you're talking here, or do you just post the question, then answer it yourself (in which case you have to wait a day.)Nicolina
@MarqueIV: Self-answered questions are welcome (stackoverflow.com/help/self-answer, stackoverflow.blog/2011/07/01/…). Just note that the same "quality standards" apply as for any other question and answers, i.e. don't ask only "How do I ..." followed by your answer.Straddle
@MarqueIV It's nice to be able to operate under the assumption that the names of properties and methods are just for the reader, that can be refactored at will, and that they don't silently participate as some part of API call. I would be reluctant to use nameof in the scenario you pictured, because I wouldn't want an innocent renaming to break my query string.Billhook
@MarqueIV Your desire to couple the query string label to the name of the property is giving you a false sense of safety. What you should really be doing is coupling the query string label to the backend that parses it, and the string that it expect to find, which isn't possible unless you're also running a Swift backend.Billhook
While I appreciate your opinion, I personally disagree with thinking it's a good thing to allow the member names to be arbitrary and not tied to the data for the same reasons we keep our JSON-based objects in sync member-name-wise with the JSON they are encoded/decoded with. These are data-defined classes used solely for the purpose of representing query params specifically so we don't have to deal with string constants. 'Innocent renaming' should cause tests to fail, just as arbitrarily changing those string constants would. We're just going to have to agree to disagree here.Nicolina
Just for fun, I had a crack at implementing a #name(...) literal that can be applied to an expression referring to a declaration (such as a property): github.com/hamishknight/swift/commit/… :)Smilacaceous
Hey @Hamish, I just revisited this old post and saw your link to trying to implement this. Looked it over, but not quite following it. Did you ever get it working? Just curious.Nicolina
O
1

Yes you can. I've used this as a solution for the next scenario: When I print an object/struct in the console, I like to see the vars names along with associated values:

extension CustomStringConvertible {
    var description: String {
        var description = "\n          *****  \(type(of: self)) *****\n"
        let mirrorS = Mirror(reflecting: self)
        for child in mirrorS.children {
            if let propertyName = child.label {
                description += "   \(getSpaces(forWord: propertyName))\(propertyName):  \(child.value)\n"
            }
        }

        return description
    }
}

And to use this, let's have as an example the following struct:

public struct FavoriteApp: Codable, CustomStringConvertible, Hashable {
    let appId: String
    let appName: String
    let bundleId: String
    let image: NSImage
    let isMacApp: Bool
    let rating: String
    let screenshotUrls: [String]
    let sellerName: String
    let sellerUrl: String
    let releaseDate: String
    let genres: String
    let latestVersion: String

}

let anApp:FavoriteApp = getTheAppInfoFromServer()
print(anApp) 

// This will print into console: 
    //        *****  FavoriteApp *****
    //        appId:          1438547269
    //        appName:        Dynamic wallpaper
    //        bundleId:        com.minglebit.DynamicWallpaper
    //        image:           -not visible- (but is there, trust me :))
    //        isMacApp         true
    //        rating:          0
    //
    //        screenshotUrls:  https://is1-ssl.mzstatic.com/image/thumb/Purple128/v4/5c/08/b8/5c08b824-12eb-e0b6-22fb-0e234f396b9e/pr_source.jpg/800x500bb.jpg
    //                        https://is1-ssl.mzstatic.com/image/thumb/Purple128/v4/f3/34/0e/f3340e21-714c-0a17-4437-73ba1828cbba/pr_source.jpg/800x500bb.jpg
    //        sellerName:      MingleBit SRL
    //        sellerUrl:       https://minglebit.com/products/dynamicwallpaper.php
    //        releaseDate:     2018-10-18T14:59:47Z
    //        genres:          Entertainment, Lifestyle
    //        latestVersion:   2.3

So, to concentrate the answer, use Mirror(reflecting: object) to get what you want.

Onlybegotten answered 30/3, 2021 at 18:5 Comment(1)
Hey! While Mirror is really cool, this isn't exactly what I'm asking. In this case you're reflecting to get the properties on something, then examining their names. That also expects an object with properties in the first place. I'm asking about a simple variable called x if you can then get the string x from it. Plus, as mentioned, nameof() does this at compile time. It's really handy for refactoring and such since you don't have hard-coded strings, instead inferring them from the variable's names.Nicolina
B
1

If you are only dealing with URLs, it is recommended to use URLComponents, which is better.

model:

struct QueryObject{

    var params = [String: String]()

    var getUrl: String{
        let result = "www.ScoreTracker.com/postScore"
        guard var url = URL(string: result) else { return result}
        url.appendQueryParameters(params)
        return url.absoluteString
    }

    init(params: [String: String]) {
        self.params = params
    }

}

then:

    let params = ["userName": "Maverick", "highScore": "123456"]
    let queryObject = QueryObject(params: params)
    let urlString = queryObject.getUrl
    DDLog(urlString) //www.ScoreTracker.com/postScore?userName=Maverick&highScore=123456

Github link

Billhook answered 28/6, 2021 at 3:26 Comment(4)
Hi, and Welcome, Alex! :) Your answer doesn't quite address what I'm asking. You are correct you should be using URLComponents in that case but that was more to show a simple example of what I'm after. Specifically, in your 'then' block, you're still hard-coding strings to represent the parameters userName and highScore. I'm asking about how to get those strings from variables with the same names.Nicolina
I think your question may not have an answer. Because all data types in c# inherit from Object, and variables are Object types; in Swift, all basic types are value types and are implemented in the form of structures at the bottom. I have also looked at the source code of struct String {}, but did not find the method you want. @MarkA.DonohoeBillhook
I’m not sure it has anything to do with reference types versus value types. Again in C# it’s a compiler feature that has nothing to do with value versus reference. It’s dealing with the identifier of a variable, not anything to do with what the variable points to. Same thing with a property or a function passed in. It’s talking about their identifiers, but not what they represent. Swift has identifiers so it theoretically should be easy for the compiler to implement.Nicolina
What I mean is that the official method does not provide this method, and it is difficult for us to implement it on the basis of source code. I can only pray that other people have a good way. @MarkA.DonohoeBillhook

© 2022 - 2024 — McMap. All rights reserved.