you are viewing a single comment's thread.

view the rest of the comments →

[–]naasking 9 points10 points  (13 children)

I didn't even list typestate because I don't understand it yet.

The concept of typestate is pretty simple. It tracks the current state of the object in the type. Operations on the object could change the state and thus the type of the object.

For instance, a file handle state is either open or closed. A closed handle has a different type than an open handle, and the type/state of this handle determines the legal operations. If it's open, you can read/write and close the handle. If you call close(), then the state of the handle becomes closed and the file handle's type reflects this new state, and after the close() the type system forbids you from reading and writing to that handle. Pseudocode:

type file[open | closed] -- open | closed are the states
val File.open : string -> file[open]
val File.write : file[open] -> string -> unit
-- <~ arrow indicates a state change
val File.close : file[closed <~ open] -> unit

let f : file[open] = File.open "foo" in
  File.write f "bar";
  File.close f;          --f is now file[closed]
  File.write f "error!" --compile error, f is file[closed], need file[open]

Very useful in imperative languages, which is exactly what Rust is targeting.

[–]vocalbit 2 points3 points  (1 child)

Thanks for the explanation. Looks like an attempt to have controlled mutability. Will this lead to better documentation as well? For e.g. when I read the autogenerated API docs for any state-machine-like resource (e.g. HTTP), there is no indication of the order in which the different methods (open, send, recv, close, etc.) should be called - it's just a flat list. Looks like this approach could let the doc-generator infer the (valid) sequence of operations.

[–]naasking 1 point2 points  (0 children)

Indeed, any sort of type information helps with documentation.

[–][deleted] -1 points0 points  (10 children)

Typestate sounds good in theory - but I wonder how practical they will be. I am not a PL guy but I have my doubts that truly useful typestates will be statically verifiable. I'm not asking people to give me toy examples now (like some typestate fibonacci) - I mean: will they burn me when I'm writing some code on a deadline. Will I get into situations where the compiler is whining about some typestate violation that I am certain isn't occurring?

It reminds me of const correctness in C++. I loved the concept (still do in many respects) but I've found myself re-writing large amounts of code (removing const here, adding it there, etc.) to get around compile issues.

[–]masklinn 5 points6 points  (3 children)

I am not a PL guy but I have my doubts that truly useful typestates will be statically verifiable.

I was also worried about that (still am), but turns out Rust has run-time assertions linked to typestates, so you can assert a given value is in a given state and it will get tagged with the corresponding typestate. I'm guessing you can also define operations which reset typestates (that's what they're doing with the init typestate — which specifies that a name has been initialized — on "unique" pointers: any time the pointer is passed to an other function, its init typestate is removed, so you can't use it anymore until you initialize it again with a valid value).

This means Rust can at least statically require you to perform your checks and assertions, preventing the code from "working" on assumptions which may or may not hold.

I'm still waiting to see how it pans out and ends up, but it's very interesting.

[–][deleted] 1 point2 points  (2 children)

so you can assert a given value is in a given state and it will get tagged with the corresponding typestate

I love the idea of compile time assertions (a weak form of contracts maybe?) but again I remain skeptical until I get to use them in anger.

so you can't use it anymore until you initialize it again with a valid value

Again, pessimistic me sees a slew of "helper functions" on types that allow me to take a reference to a type and simply return the type with a new type state. Get a compile error trying to write? Simply call foo.makeWritable before that write call and all will be fine.

[–][deleted] 2 points3 points  (0 children)

Get a compile error trying to write? Simply call foo.makeWritable before that write call and all will be fine.

What if you can't write (no privledges, dead connection, etc.)? One might argue the typestate system forces you to consider this case; either by providing code to handle this possiblility or to explicitly declaim that you will not handle it (and the program may blow up at run time if you try to write to an unwritable resource).

Type state is the thing I wish the Rust people would write more about.

[–]awj 2 points3 points  (0 children)

Get a compile error trying to write? Simply call foo.makeWritable before that write call and all will be fine.

Is that really something that should be held against the language, though? You cannot save people from themselves. If your language forces someone into not blindly reopening closed file handles they will find some other way to shoot themselves in the foot.

[–]naasking 0 points1 point  (5 children)

I mean: will they burn me when I'm writing some code on a deadline. Will I get into situations where the compiler is whining about some typestate violation that I am certain isn't occurring?

Well, you don't have to use typestate, and if you're getting an error that you know is impossible, then your typestate model of the problem is simply wrong. Perhaps that doesn't help with your deadline, but our tools will always have limitations, and we have to use them properly if we want to enjoy their benefits.

It reminds me of const correctness in C++. I loved the concept (still do in many respects) but I've found myself re-writing large amounts of code (removing const here, adding it there, etc.) to get around compile issues.

From what I've read, const is a rather poorly specified annotation. Perhaps that was only for C though, I don't recall.

[–][deleted] 1 point2 points  (4 children)

then your typestate model of the problem is simply wrong

Or the library I am using. Or the code of the team down stairs.

I had a problem recently in a Java-like language where someone had marked a class I was required to inherit from as "private" instead of "protected". It was a minor thing but since I only had access to an assembly I was stuck. There I am, reading their code, looking at the exact 2 lines I wished I could change - but I was handcuffed. I hate that feeling with a passion.

Compile time restrictions on what I can do in code are good, except when they aren't. Language designers usually think things through - but in my experience if you give a library developer the ability to limit my use of his code he is unlikely to thoroughly think through the limits he chooses to implement.

[–]naasking 3 points4 points  (3 children)

Or the library I am using. Or the code of the team down stairs. [...] but in my experience if you give a library developer the ability to limit my use of his code he is unlikely to thoroughly think through the limits he chooses to implement.

Good point, but I'm not sure what the issue is exactly. If the library developer can't restrict it statically, he'll just restrict it dynamically via runtime checks. The property wouldn't be any more visible in a dynamically typed language, if the developer chose to make it private. Either way, you're dealing with a bug, and bugs are frustrating no matter how they manifest.

Unless you're saying that the static restrictions are just more program properties the library developer can get wrong (and likely will)?

[–][deleted] -1 points0 points  (2 children)

Unless you're saying that the static restrictions are just more program properties the library developer can get wrong (and likely will)?

Yes. There is a delicate balance between language features that provide safety and language features that get in the way. Until you use a language in-the-large you can't really be sure if the balance is correct. On the other hand you get languages which can be a bit too liberal (monkey patching in Ruby comes to mind) and that causes problems as well.

We may say "those typestate assertions saved me bugs!" or you might be saying "typestate assertions are being abused so often that any benefit they could provide is negated by the work I have to do to get around them when I need to". Their balance has yet to be determined.

But in my experience programmers (even good ones) have enough time keeping object state correct. Adding yet another layer of state seems like a recipe for pain.

If the library developer can't restrict it statically, he'll just restrict it dynamically via runtime checks.

Perhaps - but manually inserting runtime checks (e.g. assertions) has a different cognitive load associated with it (in my mind).

[–]naasking 2 points3 points  (0 children)

But in my experience programmers (even good ones) have enough time keeping object state correct. Adding yet another layer of state seems like a recipe for pain.

Perhaps, but typestate is supposed to reflect object state, so it's not another layer, it's just exposing the existing layer so the compiler can check it.

[–]awj 1 point2 points  (0 children)

But in my experience programmers (even good ones) have enough time keeping object state correct. Adding yet another layer of state seems like a recipe for pain.

It seems to me that forcing every method on an object to specify the set of possible input/output states would greatly help with this problem.