Is it possible to mark a variable such that it will only be used once?

I assume you have a reason for creating the variable; otherwise, Bart Hofland is on-point.

I suspect you can't (but then, just about every time I think that about TypeScript, I learn I'm wrong).

But there's a way (perhaps a hack) you can do it with object properties:

const v = {
    a_value: initialValue(),
    get a() {
        if (this.a_used) {
            throw new Error("a has already been used");
        }
        this.a_used = true;
        return this.a_value;
    }
};

// ...
const b = f(v.a); // Works
// ...
const d = h(v.a); // Fails: "a has already been used"

If you're going to do that more than once (which seems likely), you can use a helper function:

function oneTime(obj: object, name: string, value: any) {
    let used = false;
    Object.defineProperty(obj, name, {
        get() {
            if (used) {
                throw new Error(`${name} has already been used`);
            }
            used = true;
            return value;
        }
    });
    return obj;
}

// ...

const v = {};
oneTime(v, "a", initialValue());
const b = f(v.a); // Works
// ...
const d = h(v.a); // Fails: "a has already been used"

You can make it even more convenient to use if you build it in a proxy:

function oneTimer() {
    const used = new Set<string | Symbol>();
    return new Proxy({}, {
        get(target: object, name: string | Symbol) {
            if (used.has(name)) {
                throw new Error(`${String(name)} has already been used`);
            }
            used.add(name);
            return target[name];
        }
    });
}


const v = oneTimer();
v.a = initialValue();
v.b = f(v.a);
v.c = g(v.b);
v.d = h(v.a); // Error: a has already been used

Live Example:

function oneTimer() {
    const used = new Set/*<string | Symbol>*/();
    return new Proxy({}, {
        get(target/*: object*/, name/*: string | Symbol*/) {
            if (used.has(name)) {
                throw new Error(`${String(name)} has already been used`);
            }
            used.add(name);
            return target[name];
        }
    });
}


const v = oneTimer();
v.a = initialValue();
v.b = f(v.a);
v.c = g(v.b);
console.log(v.c);
v.d = h(v.a); // Error: a has already been used


















function initialValue() {
    return 42;
}
function f(n) {
    return n * 2;
}
function g(n) {
    return n / 2;
}
function h(n) {
    return n * 3;
}


If you don't want to reuse a variable, simply don't create it. Instead, use the expression that you use to initialize the variable where you use(d) the variable itself.

const b = f(initialValue())
const c = g(b)

A variable is traditionally used for storing values for reuse. If you don't want that, don't use them.


I would recommend some sort of pipe to compose functions in a readable manner:

increment(increment(multiply(2)(toLength("foo")))) // 8

becomes

flow(
  toLength,
  multiply(2),
  increment,
  increment
)("foo") // 8

This will prevent temporary variables, as @Bart Hofland said, while keeping code clean.

Playground sample
(this is the flow implementation from fp-ts library)


If you really (really) need something like a compile-time check for a "one-time" variable :
type UsedFlag = { __used__: symbol } // create a branded/nominal type 
type AssertNotUsed<T> = T extends UsedFlag ? "can be only referenced once" : T

const a = 42 // a has type 42
const b = f(a)
markAsUsed(a)
a // now type (42 & UsedFlag)
const d = h(a) // error: '42 & UsedFlag' is not assignable '"can be only referenced once"'.

function markAsUsed<T>(t: T): asserts t is T & UsedFlag {
    // ... nothing to do here (-:
}

function f(a: number) { }
function h<T>(a: AssertNotUsed<T>) { }

Playground sample

Tags:

Typescript