There is a range of possibilities, covering the spectrum from lenient to strict.
You did not explicitly state whether the value can be computed lazily when a client needs it, or is computed in a later pass over the data (your example sounds a bit like that). Depending on this, the approaches could be a bit different.
One is to simply use the optional type, and rely on documentation to ensure that developers use the attribute as intended. This would work in both scenarios.
An alternative would be to use a type that can be computed and set once and cannot be changed later. I'm not sure whether the Once<>
type would fit the bill, though.
One possibility that comes to mind with strict typing and functional design would be to have separate types for the AST nodes, one without and one with position information. If it is decidable which parts of your code work with the AST before or after the position-determining pass, you can use these types appropriately, so the type checker would catch any attempts to access the attribute before it is available. The position-determining pass would then not be an operation that fills in position information in an existing AST, but a function transforming a positionless AST into one with positions.
OnceCell
Future<T>
,Promise<T>
,Lazy<T>
and plenty of other "wrappers" exist. You could roll your own, it just needs to be "poll-able" or "listen-able" when the actual value is ready. Heck, aclosure
can be passed around as "compute-able when needed" kind of value.