Simplifying declaration of a static variable initialized once

Is there a way to have a static variable, which throws, that we initialize on first run and cache for future runs? Right now, it’s written as below, which works, but is a little convoluted.

public static var shared: Object {
    get throws {
        if let singleton = Self.singleton {
            return singleton
        }
        Self.singleton = try Object()
        return Self.singleton!
    }
}

private static var singleton: Object? = nil

Ideally, I want this to be something like:

public static let singleton: Object {
    get throws {
        if (singleton is uninitialized) { return try Object() }
        else { return (previously initialized object) }
    }
}

This is a throwing variant of Lazy. But it can't work, as

Property wrappers currently cannot define an 'async' or 'throws' accessor

:crying_cat_face:

So there is no way to implement this more simply at the moment?

…Are you willing to always call it with ()? That's the least boilerplate I can think of.

struct Object {
  public private(set) static var shared = Object.init

  init() throws {
    Self.shared = { [self] in self }
  }
}

For someone interested in doing the work, I think it would be reasonable to allow global or static let properties to be throws and/or async when their initializer throws. lazy var instance properties could as well.

1 Like

I'd like to highlight a subtlety in semantics here.

The original code from Winston attempts to create the object at every call of the getter until one call produces an object instead of an error.

If instead you're willing to go the route of doing a single attempt, remembering the error if one was thrown, this would be much simpler:

static private let _shared = Result { try Object() }
static public var shared: Object {
	get throws { try _shared.get() }
}

I think if this was supported as a language feature I would expect the behavior I wrote above where you get only one try and it remembers the result including the error. There's certainly cases where you want the retry-at-every-call-until-it-works approach, but it'd be very surprising to me if a global or static variable initializer got called more than once.

7 Likes