15 ms·
Type-Safe Unions in C++ and Rust
- Animats 10y agoSomeone just re-invented discriminated variants from Pascal.
- pjmlp 10y agoThis seems to have become quite prevalent. Apparently the majority of CS degrees don't teach history of programming languages. On my language design lectures in the mid-90's we had to learn all major ones, all the way back to Fortran.
- Manishearth 10y agoThis really doesn't seem like a reinventing to me (from either the Rust or C++ side). "Reinventing" implies ignoring history in the way you mention. Tagged unions are prevalent in so many languages that the designers of both C++17 and Rust are bound to know about them. This is "borrowing" (or "stealing" :p). The original Rust was ML-like and had an OCaml compiler, so Rust's enums definitely descended from those. I can't talk for C++ for sure, but like I said it's common in so many languages that they're bound to have derived inspiration from them.
- pjmlp 10y agoI guess I was ranting a bit out of place....
- nickpsecurity 10y agoI agree with Manishearth. It looks like they're learning more than forgetting. The Rust history page indicated it cherry-picked good things from a number of languages. Looks like this is just another one they've evolved into a type-safe default. A Good Thing.
- jnbiche 10y agoAs someone pointed out elsewhere, Pascal's discriminated variants ("variant records") aren't typesafe, since the tag isn't checked before the variant is accessed. From Essential Pascal, 4th edition: "The use of a variant record type is not type-safe." The whole point of the article's discriminated unions/records in Rust and C++17 is type safety. Maybe this has been built into one of the newer Pascal descendants, but it hasn't been around for years and years. And embedded C programmers have been using these kinds of "unsafe" discriminated unions for years. That said, I do think Ada has had these kinds of actually typesafe variant records for quite a while. But again, Rust isn't making any claims to innovation. Even the language name ("Rust"), is a reference to the language being based on rusty old best practices.
- pcwalton 10y agoNone of us would claim to have invented them. Sheesh, we wrote the bootstrap compiler in OCaml :)
- flyx86 10y ago> but it’s the first language I’ve experimented with that has made them a first-class feature It is a feature of ALGOL68, Pascal, Ada and quite some newer languages: https://en.wikipedia.org/wiki/Tagged_union https://en.wikipedia.org/wiki/Tagged_union
- Manishearth 10y agoOne interesting thing about Rust is that none of the language features are really new. Even the borrow checker is from research papers and languages from quite a while ago. The only thing I can think of that might be truly unique to Rust is the concurrency safety model (Send+Sync), though that might be old too. Rust has just managed to take all these features and put them together well, and strive to be more than a research language by working on things that would make others actually use the language. (Of course, this particular feature is common in many, many languages)
- vvanders 10y agoYup, a lot of people like to say X has Y, however execution of Y is just as important(if not moreso) than the initial idea of Y. Technical ideas don't thrive in isolation, they need to be nurtured and grown. Just look at the dominance of Javascript in the programming landscape, you're seeing decades of high quality executions of the language(I include community in the "execution" definition) despite it being far from the best technical language.
- junke 10y agoAda and Pascal are not really research languages, though. But still, there are improvements taken from research, see Ada 2012 contracts for example.
- Manishearth 10y agoRight, like I said, this particular feature exists in a million languages. I'm talking about the whole set of features that Rust has; some are from research languages and may have never been seen in the industry before (e.g. regions/borrow checking), but they're not really new. This particular feature in Rust descended from the ML family (since Rust used to be ML-like), which in turn probably got it from Ada or w/e.
- petters 10y agoHopefully there can be a new function returning optional at some point in the future.
- hellofunk 10y agoI'm not sure I understand; in C++17, why can't you write a function that returns std::optional?
- logicchains 10y agoNote C++2017's std::variant is based upon boost::variant, which has been around since at least 2004 (http://www.boost.org/doc/libs/1_31_0/doc/html/variant.html http://www.boost.org/doc/libs/1_31_0/doc/html/variant.html, http://www.boost.org/users/history/ http://www.boost.org/users/history/). boost::variant however lacks a nice visit method that takes lambdas, instead requiring the user to create visitor classes. This verbosity may be part of the reason it wasn't adopted in mass, in spite of its advantages in terms of type safety.
- hellofunk 10y agoThey are implemented completely differently. The only real similarity is in the name and API. Boost variant suffers from significant performance penalties that the new std::variant does not have.
- gpderetta 10y agoBoost.variant suffers for being correct by default (i.e. strongly exception safe) plus opt-in for speed, while the new std::variant can get in an invalid state if an exception is thrown at a bad time. Let's say that the trade-offs will be hotly debated until the standard actually ships.
- hellofunk 10y agoThe problem with Boost.variant is that all data is doubly allocated, including at least 1 heap allocation as a copy. This is not obvious and is certainly not what a lot of users would want. But you are right, it does this to avoid exceptional situations.
- Sharlin 10y agoThe tradeoffs have been hotly debated for years :) The current spec is finally something the committee could agree on, after countless proposals, counter-proposals, endless email discussions and long evenings at committee meetings.
- deleted 10y ago[deleted]
- marvel_boy 10y agoNewbie here. What are the differences with Swift Enumerations?
- Manishearth 10y agoSwift enums are basically the same as Rust ones. The differences between C++ variants and Rust enums listed in the blog post apply between C++ and Swift too.
- nathankleyn 10y agoCompared to Rust enumerations, they are one and the same. In fact, looking at the documentation for Swift Enumerations yields this little nugget: You can define Swift enumerations to store associated values of any given type, and the value types can be different for each case of the enumeration if needed. Enumerations similar to these are known as discriminated unions, tagged unions, or variants in other programming languages. [1] Haskell and other ML family languages also have similar constructs, although these are usually modelled using "Algebraic Data Types", or "sum types". [2] [3] [1]: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html https://developer.apple.com/library/content/documentation/Sw... [2]: https://wiki.haskell.org/Algebraic_data_type https://wiki.haskell.org/Algebraic_data_type [3]: https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/sum-types https://www.schoolofhaskell.com/school/to-infinity-and-beyon...
- masklinn 10y agoDetails (some features they provide or can provide), Swift's enumerations and Rust's enum are both implementation of the older concept of Algebraic Data Types (ADT), which can be found in pretty much any language inspired by ML (directly or indirectly).
- verroq 10y agoIsn't this just an algebraic data structure?
- gpderetta 10y agowell, yes. The nice thing is that in C++ can be implemented purely as a library. Sometimes C++ feels like the high level languages assembler.
- stcredzero 10y agoC++ is what it is. It is "itself." I mean that in the Irish euphemism for moonshine kind of way. C++ is kind of like its own const keyword. It is what it is. const isn't immutability. It's something else. It isn't "pure" -- but it's still pretty darn useful.
- nightcracker 10y agoI have no clue what you're trying to say.
- stcredzero 10y agoMy guess is that you're missing a cultural referent, and you're unused to programming language discussion comments that aren't contentiously positive or negative, but are rather whimsical.
- masklinn 10y ago> The nice thing is that in C++ can be implemented purely as a library. The bad thing is that they suck.
- gpderetta 10y agoThey are certainly nowhere as nice as a builtin language feature, but they are not too bad. They are in fact quite usable. In the next 10 years C++ might even grow another leg and incorporate them in the basic language (together with proper pattern matching).
- killercup 10y agoCame here to say that for the specific problem at hand (`ConnectionState`) you should probably use session types in Rust -- but someone on /r/rust beat me to it _and_ the author already added it to the post while I was reading! :)
- Benjamin_Dobell 10y agoBelieve it or not, even a weakly typed language like JavaScript can take advantage of type-safe unions, thanks to Flow - https://flowtype.org/docs/union-intersection-types.html https://flowtype.org/docs/union-intersection-types.html
- sidlls 10y agoThis is neat. I'm writing a very badly constructed toy application to get used to Rust, and some of the differences have been interesting to note. In this case, when I first encountered Rust's enums the first thing that came to mind is the fact that C and C++ both offer the ability to support tagged-unions, but certainly not as a first-class entity and definitely with a lot more cruft, with or without safety checks. For me the jury is still out on where the best "fit" for Rust is. I really appreciate the enforced safety of Rust for higher-level systems applications programming. I'm not convinced yet it won't just get in the way pointlessly for much lower level programming (especially embedded). Perhaps that's just my relative novice understanding of Rust, though.
- bluejekyll 10y agoIt depends, do you want a piece of code that will be more reliable and require less maintenance after development? Even after more than a year of learning the language I do find that some things take me longer to build, but the end product is far better.
- sidlls 10y agoI can write reliable code requiring less maintenance in C or C++. What you're engaging in is language zealotry. Rust isn't a panacea.
- bluejekyll 10y agoI think you're reading into the comment too much. Rust by definition, will create a safer variant of whatever similar code you write in C or C++. This isn't really debatable, things like bounds checking on arrays, strongly typed error results, thread safe memory sharing semantics. These will absolutely guarantee that in general you will have safer code in Rust. What I said is that it might take you longer to write it in Rust, than something similar in C/C++, but you won't have some of the guarantees you get from the Rust semantics. So the tradeoff is up to you; write code faster, or write code safer.
- radarsat1 10y agoThe first example could have been implemented in C or C++ using a union. Wouldn't necessarily be type-safer, but the reminder of writing myConnection.connected.m_id instead of just myConnection.m_id is also a pretty good way to ensuring that you remember to check if (myConnection.m_connectionState == CONNECTED) before ever accessing myConnection.connected. That said, having compiler errors is much better. I'd love to be able to achieve this in pure C. One way might be to use opaque types with accessor functions to return a pointer to the correct part of the union according to the requested connection state, a bit like std::variant::get_if.
- jjnoakes 10y agoIs there any demand for a syntactic sugar layer on top of C++? Something to make these new features more ergonomic? Something one could opt-in to for newer code, or code that doesn't need to be backward compatible to 1990? Something that outputs valid C++ and so works with any tool chain?
- lfowles 10y agoWell we did get `for (auto x : range)` not long ago!
- hellofunk 10y agoLot of syntactic sugar recently added to C++ and a lot more in C++17. The structured bindings in particular are really excellent ways for unpacking tuples and structs with type inference into new variables automatically.
- jjnoakes 10y agoYeah but if you read the article and look at the syntax for C++ match-like statements, it leaves a lot to be desired.
- hellofunk 10y agoFor pattern matching, that's true. And my response is, stay tuned. C++ has a lot more to offer in this area in the future. std::variant is opening a big door for all kinds of new features in the language, and for those who cannot wait, there are pattern matching libraries out there to peak at.
- bluejekyll 10y agoI feel like this point is often made about C++: "in the future we will have that feature too!" If you don't want to wait, want to experiment with something else, learn a new language, etc., Rust is awesome. Obviously the counter argument to this is, "but there's 30+ years of C++ in production", but be honest, who actually wants to work on a 30 year old codebase?
- ensiferum 10y agoI think the example is either naive or then contrived. There's probably some behavioral changes that go with the state of the connection in which case you'd be much better served by having a State interface and then implementations for different actual states. For example: class State { ... }; class Connected : public State { ... }; std::unique_ptr<State> state;
- mike_hock 10y agoIt's clearly a contrived example to show how std::variant can be used for a state machine.
- dbaupp 10y agoThat has downsides like, for one, requiring allocations for every state transition, and the runtime infrastructure (plus loss of static assurances) required to do downcasts when one needs functionality that only exists on a specific state. For closed state spaces like this example, a discriminated union is far more controlled and has many advantages, whereas subclassing is often better suited to open (or large) sets of states.