In Rust, if I understand the article, you can create a “trait” that marks a type as conforming to an invariant, so in the article they marked thread-safe structures as Send and the thread functions as requiring types that implement Send.
Send isn’t an API to implement or type definition, it’s a sentinel saying “I declare that this type conforms to the documented expectations” even though the expectations can’t be checked by a compiler.
As a very general example, you can repeat basically any statement in C twice and it'll still compile. If you get lucky, the compiler might tell you you've ended up with a double-free or something, but that's a very limited set of cases, and won't help if the second copy is invoked down a couple function calls.
There may be some ways to still get additional true guarantees in C, but they'll be rather more restrictive than ones you can write in Rust, and you'll likely end up with overhead, which tempts skimping out on doing things properly in the name of performance.
Looking at the Rust code of this project though, I trust my C code a lot more though... ;-)
Is that not the cause of like all memory safety vulnerabilities, which are like 30%-or-whatever of linux ones? I've certainly written my fair share of mistakes around invariants in C code. Of course, if you're a perfect developer, indeed the choice of language won't end up mattering.
Interesting. So perhaps the next step is to sprinkle asserts in randomly at runtime to help with catching bugs.
For some examples, imagine an HTTP server that answers requests from clients. You might imagine having a Response object that lets you set headers and the body, with a send method that sends the response back to the client that made the request. It would be an obvious sort of error to send the thing twice, so in C you would assert that send was only called once. In Rust, on the other hand, the send method can _consume_ the Response object. This takes it away from the caller, so the compiler will ensure that they can’t even write code with two calls to the send method. You can’t enforce this at compile time in C because in C all methods take a simple pointer to the object to act on.
Another invariant that you might want to enforce is that only one body gets attached to the Response, that the body is attached before the Response is sent, and that the user cannot forget to attach a body. You would start with a Response object that has methods for adding headers. It would also have a method that attaches a body. This body method would _consume_ the Response and return a ResponseWithBody object. The ResponseWithBody object doesn’t have any methods for adding headers, or for adding a body, so several of our requirements are now checked by the compiler. It does have the method for sending the response though, and the Response object does not. This satisfies the rest of the requirements. If you try to send a Response, it’ll fail to compile. If you try to add headers after the body, it’ll fail to compile. You literally just make a state machine out of types, with methods that consume one type and return another, and the compiler enforces that only valid state transitions are possible. This is usually called “typestate programming” if you want to search for more examples.
https://cs.opensource.google/fuchsia/fuchsia/+/main:src/conn...
I doubt an ownership system will ever arrive.
AT&T work on Cyclone ended up being picked by Rust instead of anyone at WG14 getting some inspiration for papers.
Having read about their journey, I can see they use 77 mutexes and a hierarchy chart for locking to prevent deadlocks. How quaint. I keep on harping about the Actor programming model to deaf ears, but I guess the apprentices need more stumbling around before achieving true enlightenment.
Version #4, perhaps?
Any guru want to share what path to take after Actors? I’m ready…
- They need async. Otherwise you need to implement yielding in house, have one actor per thread, etc. OS code is usually sync / callback based.
- They need owned and usually send for all input. Since you have to send input / messages over a channel it makes it a requirement to have owned values and send if crossing thread boundaries. Very annoying requirement in rust.
Messages are guaranteed to be processed ordered sequentially.
Neither the language maintainers nor architects
Absolute hogwash. Plenty of people care. Plenty of people don't care, but there's no shortage of people that do.
Finding all possible data races in arbitrarily large and complex programs (even across 3rd party dependencies and dynamic callbacks) seems like a challenging task requiring specialized static and dynamic analyzers. But it turns out it can be reduced to automatically marking structs as safe to move to a thread, and type annotations on mutexes and thread-spawning functions.
Whenever you explicitly declare a lock ordering "B must be locked after A", it creates an (explicit) trait implementation "impl LockAfter<A> for B". It also creates a blanket (generic) trait implementation "impl LockAfter<X> for B" for any X where A implements LockAfter<X>; this basically fills in all the transitive edges of the graph.
Rust prohibits multiple implementations for the same trait and type. If there's a cycle in this graph involving A, then eventually the transitive walk will generate an "impl LockAfter<A> for A", after which it will generate a blanket "impl LockAfter<A> for B" which conflicts with the explicit impl and thus results in a compiler error.