It's likely the best way to achieve what you're after is to use distinct
typing, so you can convert inbetween them:
type
State = enum
sGas
sLiquid
sSolid
ExperimentImpl[T] = object
data: T
Experiment[T; MaterialState: static State] = distinct ExperimentImpl[T]
proc `=copy`[T](dest: var ExperimentImpl[T], source: ExperimentImpl[T])
{.error: "do not copy".}
proc `=dup`[T](x: ExperimentImpl[T]): ExperimentImpl[T] {.error: "do not
duplicate".}
func initExperiment[T; MaterialState: static State](initialData: T):
Experiment[T, MaterialState] =
Experiment[T, MaterialState](ExperimentImpl[T](data: initialData))
func `$`[T; MaterialState: static State](e: Experiment[T, MaterialState]):
string =
$MaterialState & " " & $ExperimentImpl[T](e).data
func condense[T](e: sink Experiment[T, sGas]): Experiment[T, sLiquid] =
Experiment[T, sLiquid](e)
func deposit[T](e: sink Experiment[T, sGas]): Experiment[T, sSolid] =
Experiment[T, sSolid](e)
func evaporate[T](e: sink Experiment[T, sLiquid]): Experiment[T, sGas] =
Experiment[T, sGas](e)
func solidify[T](e: sink Experiment[T, sLiquid]): Experiment[T, sSolid] =
Experiment[T, sSolid](e)
func melt[T](e: sink Experiment[T, sSolid]): Experiment[T, sLiquid] =
Experiment[T, sLiquid](e)
func sublimate[T](e: sink Experiment[T, sSolid]): Experiment[T, sGas] =
Experiment[T, sGas](e)
proc main =
var e = initExperiment[int, sGas](initialData = 42)
echo e
let e2 = e.condense()
echo e2
let e3 = e2.solidify()
echo e3
let e4 = e3.sublimate()
echo e4
let e5 = e4.deposit()
echo e5
if isMainModule:
main()
Run