4 ms·
Oh right. Enlightening! It's interesting you mention STM. That's one of the reasons I only dabble in Rust instead of switching over to it completely. In do_
by mrkeen 2y ago
Oh right. Enlightening!
It's interesting you mention STM. That's one of the reasons I only dabble in Rust instead of switching over to it completely.
In do_invalid_stuff(),
block(bad_account)
transfer(bad_account, good_account) // <- this will fail
This can only be determined syntactically if you and the compiler agree to stay on a single thread, right? I would expect STM to be the thing which safely bridges across threads - since it will realistically a customer will call transfer() but a bank manager will call block().
- vlovich123 2y ago> This can only be determined syntactically if you and the compiler agree to stay on a single thread, right? No. This will fail because in block the ownership of bad_account is lost. You’d need block(&bad_account) or block(&mut bad_account) to pass a reference while letting the transfer succeed. The only way the transfer would succeed silently like that is if Account implemented Copy (the compiler would automatically inject block(bad_account.clone())). This has nothing to do with threading (which Rust would still have protection mechanisms for). It would work identically if the block and transfer were member methods (i.e. bad_account.block(); good_account.transfer_from(bad_account)) since even member methods can be declared to be requiring ownership.
- mrkeen 2y ago> This has nothing to do with threading Right, but all the promises of the type system have to lead to a first-class multi-threading experience at some point. The safest concurrent program isn't a single-threaded program.
- vlovich123 2y agoIt doesn’t automatically solve every possible problem for you just like something like Lean doesn’t prevent you from writing buggy proofs. I’d argue it does lead to a first-class multi-threading experience within the systems programming domain it’s targeting. Even outside systems programming it can be quite surprisingly good and most incorrect code fails to compile to begin with. Is there a language that in your opinion offers a better multi-threading experience?
- mrkeen 2y agoI think 70's style ACID-ish transactions are the way to go. Single-threaded reasoning safely transfers across to multi-threaded situations. Parent mentioned them above, argued that that ownership supersedes them, >>> Clojure has software transactional memory. With ownership it wouldn't need half of the machinery (only that for rollback) but then only showed a single-threaded demo of ownership. I don't need STM either if I'm only on a single thread. STM's a good model. You just write code as if it were single-threaded, slap an 'atomically' around the lines which shouldn't be logically divisible, and you're basically done.
- j-pb 2y agoI'm not sure how to make the multithreaded case more explicit. The point is that with ownership the multi-threaded case does look exactly like the single threaded case, just with some machinery to "aquire" all the things you want to own snd do the transaction over. But "aquiring" them just looks like having them all in local scope, so there is no explicit `lock`. In the transactional case you mentioned there can still be multiple transactions having access to the same values, so you need the explicit transaction semantics. Ownership never has such cases, unless you use locks to manage ownership between threads, but it could also be done via channels, or CSP, or whatever.
- j-pb 2y agoMy sibling comment is explaining this correctly but maybe using a bit too much Rust jargon for the uninitiated. Put simply: How would two thread get access to the same account at the same time? There would be a point in time where the object would have to be split to end up in both of them, which would be invalid. This is why you see things like this in rust let x = 5; let thread = thread::spawn(move || { // <- important bit println!("{}", x); }); println!("{}", x); // <- this would fail The thread uses a 'move closure', which takes ownership of all values from the outside scope that are used inside its body. Note that a value needs to be marked as `Send` so that it can pass across threads, but most types are. That's what makes it so powerful. You know that any value you have in any variable, is only in that variable and nowhere else in the "world". Sure that value can be `Copy` and automatically copied, but that's still a new value. Your value can be `Sync` which means that a reference of it is `Send`, but you're not splitting up the value, you're creating and tracking a new reference value that is then send across the thread boundary.
- mrkeen 2y agoRight, I was thrown by the real world terms. The ownership system (and syntax) isn't doing anything about a blocked account, it's just preventing a use-after-move, which has nothing to do with this-thread-blocks, that-thread-transfers. (This is also why I walk past Erlang. Such emphasis on share-nothing prevents an account from being shared by a customer and a bank manager.) So, what is the actual solution for a thread asking if any other thread has blocked(acct1), so that transfer() can proceed or be rolled back?
- vlovich123 2y agoPinning objects to single threads and message passing and/or transactions systems. Transactions typically aren’t seen at the language level and are more common within database systems. Typically a thread-per-core design with objects pinned to cores randomly would scale the best even for transactions; you can develop hotspots but in practice you win a lot more by not having to synchronize unless you’re doing cross-object transactions. I’m not aware of any language that provides these kinds of guarantees statically or what that would even look like. You’re asking for a lot vs where the cutting state of language design is.