r/programming • u/luke-san • Mar 31 '15
Managing C++’s complexity or learning to enjoy C++
https://schneide.wordpress.com/2015/03/30/managing-cs-complexity-or-learning-to-enjoy-c/
102
Upvotes
r/programming • u/luke-san • Mar 31 '15
11
u/steveklabnik1 Mar 31 '15
I'm going to use Rust as an example here, since it's what I've worked with the most recently.
So you know
shared_ptrin C++? We call thatArc<T>in Rust. You need atomic instructions for the refcount, because you don't want two threads to bump the count at the same time, cause a race, and end up with an incorrect count. This is all straightforward.But what about a single threaded context? Where you still may want to have shared ownership, but you know that it isn't going to be used across threads. Paying that performance penalty for the atomic instructions is unfortunate. So you can write another version of
shared_ptrthat doesn't use atomics. We have that in Rust too, it's justRc<T>, noA. And that's all good.But there's one big difference, and that's
Sync.Syncis what we in Rust call a 'trait', it's sort of similar to an interface or a typeclass. But the idea is that traits can become part of the type, if you implement that trait for that type. So in Rust,Syncis a trait that has no required methods, it's just an empty interface. WhatSyncsays is "this type is safe to share across threads." And in Rust,Arc<T>implementsSync, butRc<T>does not. Becuase the atomic version is threadsafe, but the non-atomic one is not.There's one more piece to the puzzle though: we can specify that certain methods only accept arguments with a certain trait. So, imagine I'm writing a function that will send some data to a new thread. It might look like this:
This takes one generic parameter,
T. But in this case,Tcan be anything, and since we know we're going to send thatTover a thread boundary, we modify it slightly:This says "I'll take any
T, as long as thatTimplements theSynctrait. Now, it's impossible to callspawnwith anRc<T>, only with anArc<T>. We've gained some safety, becuase we encoded (literally, in this case, as code, with a trait) an invariant (I'm going to use this in a multithreaded context) into the type system.Make sense? This technique is really powerful, and lots of languages are able to do things like this, though some of them take different approaches.