Using a struct instance in an if statement?

I could not figure out how to google for this answer.

Is there any way to create a type that can be used directly in an if statement? Something like:

struct FeatureFlag : IfTestable {
    public var name: String
    public var value: Bool
}

let coolFeatureEnabled = FeatureFlag(name: "coolFeatureEnabled", value: false)

if coolFeatureEnabled {
    // do something
}

Closest you can get is callAsFunction() -> Bool I think. You could then do if coolFeatureEnabled(). However, I might just approach it as a naming problem and name the feature and check separately.

let coolFeature = FeatureFlag(...)

if coolFeature.isEnabled {
  // ...
}

A bit more explicit, if a bit more verbose.

Yeah, I knew I could do this, it's just a little more verbose. These things are basically booleans, it's just their value comes from somewhere else that needs some metadata associated with it.

Update: See 1-877-547-7272's reply below for a more historically accurate account. :slight_smile:

FWIW, there used to be a protocol in Swift 1.0 to do what you want. BooleanType (maybe it was LogicalValue before that?). It caused lots of problems when mixed with optional types and it ultimately was removed from the language.

2 Likes

Perhaps it's time to pitch callAsProperty().

Swift used to have the Boolean (née BooleanType) protocol for this functionality, but it was removed in Swift 3 due to confusion and because it was rarely used.

From the acceptance rationale:

4 Likes

I wonder if this is a compelling use case. It's compelling to me, but not hard to manage with existing options.

The comparison is a tiny bit unjust because you've chosen to omit "is" from your original variable. Swift conventions would tend to prefer isCoolFeatureEnabled over coolFeatureEnabled. If you prefer to do without "is" in one case, then the alternative could be coolFeature.enabled.

I make this nitpicky point to demonstrate that the second form is scarcely more verbose. It has exactly the same words. :slight_smile:

However, as to the original question, it seems like there'd be a solution via a property wrapper, where the wrapped value was Bool and the projected value (if it even needs to be exposed) was FeatureFlag.

You could probably even generalize it a bit by making the projected value type generic, and create a custom IfTestable protocol to derive the wrapped value automagically.

3 Likes

Yeah, I know the convention is to prefix with is, but I often find it more natural-sounding to not use that (as in your example, coolFeature.enabled). The reason i work it into the name of the feature symbol is it might not indicate enablement. What if it's shouldLog. Yes, you could write that as isLoggingEnabled. And even if I named it, since in practice I write these as extensions on the struct type (similar to e.g. Notification.Name), it ends up being FeatureFlags.isCoolFeatureEnabled. I agree that's not much better than .isEnabled, but when the value could indicate something else, it gets to be more like FeatureFlags.shouldLog.value or some such.

You could use callAsFunction to leave out the if entirely.

protocol FeatureFlagProtocol {
    var name: String { get }
    var value: Bool { get }
}

extension FeatureFlagProtocol {
    func callAsFunction(action: () -> Void) {
        if value {
            action()
        }
    }
}

struct FeatureFlag : FeatureFlagProtocol {
    public var name: String
    public var value: Bool
}

let coolFeatureEnabled = FeatureFlag(name: "coolFeatureEnabled", value: true)

coolFeatureEnabled {
    print("Enabled")
}

We need to have an else clause sometimes. But cool suggestion!

Here you go :smile:

extension FeatureFlagProtocol {
    func callAsFunction(action: () -> Void, else other: () -> Void = {}) {
        if value {
            action()
        } else {
            other()
        }
    }
}

coolFeatureEnabled {
    print("enabled")
} else: {
    print("not enabled")
}
1 Like

Haha I wondered if you could do that. Figured you probably could. But it's a bit more opaque at the call site without the if.