<p>Some languages accept untagged union types (C++, C, etc):</p>
<pre class="lang-cpp prettyprint-override"><code>union Something
{
    char ch;
    int  i;
};
</code></pre>
<p>Some languages accept tagged union types (like Haskell).</p>
<p>But some languages do not have union types at all (like Go).</p>
<p><strong>So, what are pros and cons of these three ways?</strong></p>

<types><union-types>
What are pros and cons of tagged vs untagged union-types?
CC BY-SA 4.0
2023-07-12T09:24:03.410Z
Tagged or untagged unions?
mousetail 'he-him'
CC BY-SA 4.0
2023-07-12T09:24:36.070Z
@mousetail what are they?
чистов_n
CC BY-SA 4.0
2023-07-12T09:25:48.317Z
A tagged union stores what type it contains. Many modern languages have tagged unions (under different names) but untagged unions are very unsafe so not really used anymore
mousetail 'he-him'
CC BY-SA 4.0
2023-07-12T09:27:46.517Z
@mousetail I don't know about types of union, but if "are very unsafe so not really used anymore", I think my question about modern unions (tagged).
чистов_n
CC BY-SA 4.0
2023-07-12T09:34:36.663Z
Again downvote...
чистов_n
CC BY-SA 4.0
2023-07-12T10:20:03.390Z
I don't think your question is that clear. Do you mean tagged unions vs untagged unions or having union/sum types at all?
Your example in C is an untagged union.
Go initially aimed to be simple and omitted many things including generics and unions. It now has generics and unions are [under discussion](https://github.com/golang/go/issues/57644)
Bruce Adams
CC BY-SA 4.0
2023-07-12T15:55:15.937Z
Where is the "tag" in your `union`?
dan04
CC BY-SA 4.0
2023-07-13T04:44:37.563Z
A key distinction.  With untagged unions, `union{ int a; int b;}` does not distinguish in any way between storing an a and storing a b.  With a tagged union, you remember which of those is stored.  This has very important properties if your language is built on category-theory proofs.
Cort Ammon
CC BY-SA 4.0
2023-07-13T09:15:31.037Z
@BruceAdams I think I resolve originally close reason.
чистов_n
CC BY-SA 4.0
2023-07-13T16:14:05.130Z
You also list Go as a third option (not having unions at all), but then ask about "these two ways". Is not having unions at all another thing you want the pros and cons of? Also, don't you mean "tagged union" for Haskell?
rydwolf
CC BY-SA 4.0
2023-07-14T07:07:48.080Z
@RydwolfPrograms I edited my post.
чистов_n
CC BY-SA 4.0
2023-07-14T14:10:23.873Z
@nchistov You didn't fix the Haskell part, and now the title conflicts with the body.
rydwolf
CC BY-SA 4.0
2023-07-17T18:10:50.910Z
Besides name and some parts of the realization, the two concepts are not really related. Tagged unions are sort of a mixture between flat OOP and enums. Unions are usually an operation on the type latice. The syntax and naming may however suggest otherwise. I'd suggest splitting the question into two. One for tagged and one for plain unions.
feldentm
CC BY-SA 4.0
2024-01-26T16:28:11.583Z
I find untagged more useful. I can use them to quickly access certain words or bytes of a larger integer type without any shifts/masks.
CPlus
<h3>Using untagged unions needs a lot of boilerplate to use safely and it's easy to mess up and cause undefined behavior</h3>
<p>A pure untagged union does no type checking so you can interpret it's contents as type A when it is type B. In most languages this is undefined behavior.</p>
<p>To fix this issue, developers need to tag the union manually, usually you'd use something like this:</p>
<pre class="lang-c prettyprint-override"><code>union X {
   int a,
   float b
}

enum XType {
    Float = 1,
    Int = 2,
}

struct TaggedX {
    XType type,
    X value
}
</code></pre>
<p>Then every time you want to access it you need:</p>
<pre class="lang-c prettyprint-override"><code>switch x.type {
    case XType.Float:
        float value = x.value.b;
        // do something with float
    case XType.Int:
        int value = x.value.a;
        // do something with int
}
</code></pre>
<p>You can get a bit creative, maybe you use the type variable for something else too and multiple enum values have the same type. However, mostly you need this boilerplate every time you want to safely use the union. The more you depart the more likely you make a mistake and cause undefined behavior.</p>
<p>Just offering a tagged union (like Rust's <code>enum</code>) means all this work is done for you. You need to write less code and get more safety in return. You may need slightly more memory in some specialized situations but usually the memory benefits are considered worth it.</p>
<p>There is no real disadvantage of offering a untagged union for FFI if you cover it with enough warnings about it's unsafety and offer good alternatives for normal use. Even rust supports them. However, discourage their use outside of specialized situations.</p>

CC BY-SA 4.0
2023-07-12T09:45:32.857Z
2023-07-12T09:45:32.857Z
mousetail 'he-him'
<p>The pro is that you can save some memory (cache) when using them. If there are 2 fields that are never used together then you can put them into a union and only pay the size cost for the larger of the two.</p>
<p>For tagged unions (where the language adds a enum to signal which alternative is active) the downside is that you are locked into the layout of whatever the language defined. So you cannot use them as an aid for parsing a file format or use them for in a foreign function interface for example.</p>
<p>For untagged unions the major downside is type safety is very difficult to guarantee. There is nothing inherent to the union to signal which alternative is active which means a integer could be interpreted as a pointer.</p>

CC BY-SA 4.0
2023-07-12T09:30:20.703Z
2023-07-12T09:30:20.703Z
ratchet freak
CC BY-SA 4.0
2024-04-17T21:00:14.593Z
In my opinion the clarity of what data is present in what states in a tagged union is even more important than the space savings. Say I have a string in state A and an int in state B. If all I have in my language is structs, then I'll have the int and the string fields all the time. It's some wasted space, yes, but more importantly my code might accidentally read those values in the states where they're not set. This is even trickier if instead of a string or an int I want to conditionally manage something like a file or a socket, with no convenient default/missing value.
Jack O'Connor
CC BY-SA 4.0
2024-04-18T09:33:40.797Z
if you don't care about space savings why use a union at all, just make a uberstruct that has all fields, if you have sensible default init you don't even need to check whether the field is active either (depending on the usecase)
ratchet freak
CC BY-SA 4.0
2024-04-19T16:58:13.267Z
Having compiler errors or other tooling that can tell you "this field isn't present in this state" is very nice.
Jack O'Connor
<p>Some tasks require &quot;polymorphic records&quot;. For instance, some simulation involving transport vehicles may need additional fields that differ for a light car, a truck, a motorbike, a light tank and a taxiing aircraft. You may still need to keep those records say as members of the same array, or have some functions that take or return any of them.</p>
<p>It would be possible to have a single structure containing all fields that could ever occur, and leave irrelevant fields unpopulated. One may argue that it would be more clear to have each case of such a structure as a member of the union, where each variant only defines relevant fields. The union must obviously have a way to tell which variant is in use. The compiler will allocate enough memory per union so that any variant would fit.</p>
<p>Polymorphism can also be implemented by storing abstract pointers and allocating different structures dynamically, but very extreme usage of the heap may not scale for very many threads.</p>

CC BY-SA 4.0
2023-07-12T10:36:40.320Z
2023-07-12T10:36:40.320Z
Audrius Meškauskas
<p>It's a trade-off between safety and flexibility.</p>
<p>By attaching a tag to every union, a language can prevent access as the wrong type.  However, that prevents use for type-punning as is commonly necessary  in low-level languages such as C.</p>
<p>Also, a tag creates overhead that may be undesirable - both in the extra instructions to verify access and in the space that's used even if the program logic is able to ensure correct access in other ways such as the flow of control.</p>
<p>There's a fourth alternative, as in C++, which is to provide <em>both</em> low-level untagged unions (<code>union</code>) and one or more high-level tagged types (<code>std::variant</code>, <code>std::any</code>, arguably <code>std::optional</code>) for easy safe use.</p>

CC BY-SA 4.0
2023-07-20T19:32:29.470Z
2023-07-20T19:32:29.470Z
Toby Speight
# What are pros and cons of tagged vs untagged union-types?
Posted by: username_0 on 2023-07-12
Some languages accept untagged union types (C++, C, etc):
union Something
{
    char ch;
    int  i;
};

Some languages accept tagged union types (like Haskell).
But some languages do not have union types at all (like Go).
So, what are pros and cons of these three ways?
**Tags:** types union-types
> **Comments:**
> **username_1**: Tagged or untagged unions?  
> **username_0**: @mousetail what are they?  
> **username_1**: A tagged union stores what type it contains. Many modern languages have tagged unions (under different names) but untagged unions are very unsafe so not really used anymore  
> **username_0**: @mousetail I don't know about types of union, but if "are very unsafe so not really used anymore", I think my question about modern unions (tagged).  
> **username_0**: Again downvote...  
> **username_2**: I don't think your question is that clear. Do you mean tagged unions vs untagged unions or having union/sum types at all?
Your example in C is an untagged union.
Go initially aimed to be simple and omitted many things including generics and unions. It now has generics and unions are [under discussion](https://github.com/golang/go/issues/57644)  
> **username_3**: Where is the "tag" in your `union`?  
> **username_4**: A key distinction.  With untagged unions, `union{ int a; int b;}` does not distinguish in any way between storing an a and storing a b.  With a tagged union, you remember which of those is stored.  This has very important properties if your language is built on category-theory proofs.  
> **username_0**: @BruceAdams I think I resolve originally close reason.  
> **username_5**: You also list Go as a third option (not having unions at all), but then ask about "these two ways". Is not having unions at all another thing you want the pros and cons of? Also, don't you mean "tagged union" for Haskell?  
> **username_0**: @RydwolfPrograms I edited my post.  
> **username_5**: @nchistov You didn't fix the Haskell part, and now the title conflicts with the body.  
> **username_6**: Besides name and some parts of the realization, the two concepts are not really related. Tagged unions are sort of a mixture between flat OOP and enums. Unions are usually an operation on the type latice. The syntax and naming may however suggest otherwise. I'd suggest splitting the question into two. One for tagged and one for plain unions.  
> **username_7**: I find untagged more useful. I can use them to quickly access certain words or bytes of a larger integer type without any shifts/masks.  
---
## 4 answers
### Answer by username_1 on 2023-07-12. Score: 6
Using untagged unions needs a lot of boilerplate to use safely and it's easy to mess up and cause undefined behavior
A pure untagged union does no type checking so you can interpret it's contents as type A when it is type B. In most languages this is undefined behavior.
To fix this issue, developers need to tag the union manually, usually you'd use something like this:
union X {
   int a,
   float b
}

enum XType {
    Float = 1,
    Int = 2,
}

struct TaggedX {
    XType type,
    X value
}

Then every time you want to access it you need:
switch x.type {
    case XType.Float:
        float value = x.value.b;
        // do something with float
    case XType.Int:
        int value = x.value.a;
        // do something with int
}

You can get a bit creative, maybe you use the type variable for something else too and multiple enum values have the same type. However, mostly you need this boilerplate every time you want to safely use the union. The more you depart the more likely you make a mistake and cause undefined behavior.
Just offering a tagged union (like Rust's enum) means all this work is done for you. You need to write less code and get more safety in return. You may need slightly more memory in some specialized situations but usually the memory benefits are considered worth it.
There is no real disadvantage of offering a untagged union for FFI if you cover it with enough warnings about it's unsafety and offer good alternatives for normal use. Even rust supports them. However, discourage their use outside of specialized situations.
---
### Answer by username_8 on 2023-07-12. Score: 5
The pro is that you can save some memory (cache) when using them. If there are 2 fields that are never used together then you can put them into a union and only pay the size cost for the larger of the two.

For tagged unions (where the language adds a enum to signal which alternative is active) the downside is that you are locked into the layout of whatever the language defined. So you cannot use them as an aid for parsing a file format or use them for in a foreign function interface for example.

For untagged unions the major downside is type safety is very difficult to guarantee. There is nothing inherent to the union to signal which alternative is active which means a integer could be interpreted as a pointer.
> **Comments:**
> **username_9** (2024-04-17): In my opinion the clarity of what data is present in what states in a tagged union is even more important than the space savings. Say I have a string in state A and an int in state B. If all I have in my language is structs, then I'll have the int and the string fields all the time. It's some wasted space, yes, but more importantly my code might accidentally read those values in the states where they're not set. This is even trickier if instead of a string or an int I want to conditionally manage something like a file or a socket, with no convenient default/missing value.  
> **username_8** (2024-04-18): if you don't care about space savings why use a union at all, just make a uberstruct that has all fields, if you have sensible default init you don't even need to check whether the field is active either (depending on the usecase)  
> **username_9** (2024-04-19): Having compiler errors or other tooling that can tell you "this field isn't present in this state" is very nice.  
---
### Answer by username_11 on 2023-07-20. Score: 3
It's a trade-off between safety and flexibility.

By attaching a tag to every union, a language can prevent access as the wrong type. However, that prevents use for type-punning as is commonly necessary in low-level languages such as C.

Also, a tag creates overhead that may be undesirable - both in the extra instructions to verify access and in the space that's used even if the program logic is able to ensure correct access in other ways such as the flow of control.

There's a fourth alternative, as in C++, which is to provide *both* low-level untagged unions (`union`) and one or more high-level tagged types (`std::variant`, `std::any`, arguably `std::optional`) for easy safe use.
---
### Answer by username_10 on 2023-07-12. Score: 2
Some tasks require "polymorphic records". For instance, some simulation involving transport vehicles may need additional fields that differ for a light car, a truck, a motorbike, a light tank and a taxiing aircraft. You may still need to keep those records say as members of the same array, or have some functions that take or return any of them.

It would be possible to have a single structure containing all fields that could ever occur, and leave irrelevant fields unpopulated. One may argue that it would be more clear to have each case of such a structure as a member of the union, where each variant only defines relevant fields. The union must obviously have a way to tell which variant is in use. The compiler will allocate enough memory per union so that any variant would fit.

Polymorphism can also be implemented by storing abstract pointers and allocating different structures dynamically, but very extreme usage of the heap may not scale for very many threads.
---
