0

In Rust, a struct can have a method that takes ownership of the struct and moves it, making it illegal to use the struct again later, e.g.

struct Foo {}

impl Foo {
  fn consume(self) -> u32 {
    println!("consumed!");
    42
  }
}

fn main() {
  let x = Foo {};
  let val_1 = x.consume();
  let val_2 = x.consume(); // WON'T COMPILE
}

Is there any way to represent a similar state change using TypeScript's type system?

9
  • Rust and its ownership system / borrow checker are rather unique, Typescript doesn't have anything close enough. The best I can think is to set a variable and throw an error at runtime, but not at compile time, though. Commented May 7, 2024 at 18:42
  • This aspect of Rust isn't unique, though. C++ has been able to express the same semantics for awhile (though not via methods since this is always a pointer). Commented May 7, 2024 at 19:05
  • 1
    This is not possible; it is requested in ms/TS#16148. The closest you could get is with assertion methods but those are not allowed to return anything, so while you could make it that x.consume(); can only be written once, let val_1 = x.consume() wouldn't assign anything useful to val_1. Does this fully address the question? If so I'll write up an answer explaining; if not, what am I missing? Commented May 7, 2024 at 19:12
  • 1
    @cdhowie right, but C++ can not fully provide the same guarantees that the Rust borrow checker provides. Commented May 7, 2024 at 19:34
  • 2
    @cdhowie: What in C++ achieves this? Note that the important part isn’t the concept of an operation that makes an object unusable, but being able to represent that in the type system or otherwise enforce correctness at compile time (also not a new idea in Rust, but not because of C++, as far as I’m aware). Commented May 8, 2024 at 2:57

1 Answer 1

1

TypeScript does not currently support substructural types like linear/affine types, which are needed to represent the sort of ownership model you're trying to use here. There's a longstanding open feature request for it at microsoft/TypeScript#16148 listed as "awaiting more feedback", meaning they need to hear more community demand for it before they'd seriously consider implementing it. And there doesn't seem to be enough of that. It wouldn't hurt for you to add your 👍 and describe your use case if it's compelling, but it probably wouldn't help either.


TypeScript doesn't really model arbitrary state changes in its type system. It only really allows for narrowing, meaning that at best you could do something to x which would make TypeScript treat it as more specific type. Generally speaking more specific types have more capabilities than less specific types, so on first blush this isn't really something you can do easily. You could sort of, kind of, use assertion functions to narrow x.consume all the way to the never type, which will give you an error if you call it. But assertion functions have caveats: they can't return a value (so assigning to val_1 wouldn't do much) and you need an explicit type annotation. So the closest I can get to what you want looks like this:

class Foo {
    consume(
        ret: { value: number; }
    ): asserts this is { consume: never } {
        console.log("consumed!")
        ret.value = 42;
    }
}

const foo: Foo = new Foo();
const ret = { value: 0 };
foo.consume(ret);
let val1 = ret.value;
//  ^?
console.log(val1); // 42
foo.consume(ret);
//  ~~~~~~~
// error! This expression is not callable.

Here a Foo instance has a consume() assertion method which accepts a ret parameter which acts as a container for the desired return value. If you call consume() on an instance of Foo (which has been explicitly annotated as Foo), then it will mutate ret and the narrow the instance so that consume is of type never and it cannot be called again.

Whether or not this is actually better than just giving up and representing the state in some other way (e.g., just have consume() be callable multiple times but return a cached value after the first time, etc.) depends on your use case. I'd doubt assertion methods are the way to go, but that's outside the scope of the question, really.

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.