<p>While scrolling through a Rust syntax <a href="https://cheats.rs/#data-structures" rel="noreferrer">cheat sheet</a> today, I noticed an odd item:</p>
<pre><code>struct S; // Define zero sized unit struct. Occupies no space, optimized away.
</code></pre>
<p>This seems like a strange thing to allow; Rust already has a zero-size unit type, <code>()</code> (which does have <a href="https://doc.rust-lang.org/nightly/nomicon/exotic-sizes.html#zero-sized-types-zsts" rel="noreferrer">uses</a>), and I can't think of many situations where a zero-size struct would provide an advantage over that. Perhaps in a language with a type system that had non-tagged unions, you could do something like <code>let dog_breed: Schnauser | Corgie = Schnauser {};</code> to implement a sort of enum pattern. This would still seem pretty useless though, especially in a language which already has enums/tagged unions.</p>
<p>What justification do existing languages with this feature provide, and/or what purposes could it serve?</p>

<data-types><struct><unit-type>
Why would a language allow zero-size structs?
CC BY-SA 4.0
2023-07-01T06:04:22.780Z
Follow up question: How would a zero-size struct be different from `void` in practice?
CPlus
CC BY-SA 4.0
2023-07-02T00:25:39.867Z
@user16217248-OnStrike `void` in C is not inhabited in the same sense: an expression or function has type `void` but you cannot construct and pass around a value of type `void`. In Rust, a ZST mostly behaves like other types: you _can_ have a value of type `()` or any other ZST. A `!Copy` ZST has the same ownership semantics of other types.
chbaker0
CC BY-SA 4.0
2023-07-02T14:19:50.590Z
Are you asking about why it is allowed for a struct to have no members (like in C++), or are you asking about why it has zero size?
user3840170
CC BY-SA 4.0
2023-07-02T16:01:39.163Z
@user3840170 For this question, both for the most part. I'm used to making the assumption zero members == zero size, so I kind of worded the question around that, but answers talking about non-zero-size ones would still probably be useful tho.
rydwolf
CC BY-SA 4.0
2023-07-02T22:47:17.780Z
Why would a language _not_ allow zero-size structs?
leftaroundabout
CC BY-SA 4.0
2023-07-03T20:46:54.623Z
Re Rust, have a look at [What are the differences between the multiple ways to create zero-sized structs?](https://stackoverflow.com/questions/50162597/what-are-the-differences-between-the-multiple-ways-to-create-zero-sized-structs)
Bergi
<p>Consider <em>Option</em> types.</p>
<p><em>Some</em> a struct with a backed field.
<em>None</em> is a zero size struct.</p>
<p>The usefulness of <em>None</em> is that it can participate in type casts.</p>
<p>A language can recognize that zero-size structs can be singleton, so you could distinguish <em>Some</em> from <em>None</em> via a pointer check.</p>
<p>Some runtimes instead store out-of-band type information when a struct is up-cast to an abstract type.  Go for example does this so that it can check that down-casts from interface types are safe.</p>

CC BY-SA 4.0
2023-07-05T00:32:44.790Z
2023-07-05T00:32:44.790Z
Mike Samuel
CC BY-SA 4.0
2023-07-05T14:42:22.647Z
This is only sort of an answer, since this same functionality can be achieved with a zero-size tuple/unit type, and I'm more asking about why a language would specifically allow something along the lines of `struct X;`
rydwolf
CC BY-SA 4.0
2023-07-05T15:12:13.907Z
@RydwolfPrograms Sorry, I misunderstood.  I interpreted "struct type" as *product type*.  But unless your type system allows great flexibility with union types, you still need some kind of common super-type for *Some* and *None*?
Mike Samuel
CC BY-SA 4.0
2023-07-05T15:14:30.423Z
Isn't that normally done with a tagged union? Like `None` would be `00` with some padding, and `Some` would be `01` followed by the data? (With things like null pointer optimization if the data is a pointer)
rydwolf
CC BY-SA 4.0
2023-07-05T15:32:13.673Z
It often is, and often tagged unions are syntactic sugar for a group of struct type definitions that have with some tag bits at a well known offset.  Doing it that way means that a tagged union like `type ThreeValueLogic = Unknown | Known(Bool)` can benefit from struct layout optimizations to pack the single tag bit with the single bit for the bool into one byte so the sizeof the union as a whole is 1 instead of treating tag bits as some special out-of-band thing.  (Though in this case you can also enumerate the possible values)
Mike Samuel
<h2>For trait implementations</h2>
<p>They can be useful for getting a concrete implementation of a trait. For example, see this <a href="https://rust-unofficial.github.io/patterns/patterns/behavioural/visitor.html" rel="nofollow noreferrer">example</a> in the Rust design patterns book:</p>
<pre class="lang-rust prettyprint-override"><code>// The data we will visit
mod ast {
    pub enum Stmt {
        Expr(Expr),
        Let(Name, Expr),
    }

    pub struct Name {
        value: String,
    }

    pub enum Expr {
        IntLit(i64),
        Add(Box&lt;Expr&gt;, Box&lt;Expr&gt;),
        Sub(Box&lt;Expr&gt;, Box&lt;Expr&gt;),
    }
}

// The abstract visitor
mod visit {
    use ast::*;

    pub trait Visitor&lt;T&gt; {
        fn visit_name(&amp;mut self, n: &amp;Name) -&gt; T;
        fn visit_stmt(&amp;mut self, s: &amp;Stmt) -&gt; T;
        fn visit_expr(&amp;mut self, e: &amp;Expr) -&gt; T;
    }
}

use visit::*;
use ast::*;

// An example concrete implementation - walks the AST interpreting it as code.
struct Interpreter;
impl Visitor&lt;i64&gt; for Interpreter {
    fn visit_name(&amp;mut self, n: &amp;Name) -&gt; i64 { panic!() }
    fn visit_stmt(&amp;mut self, s: &amp;Stmt) -&gt; i64 {
        match *s {
            Stmt::Expr(ref e) =&gt; self.visit_expr(e),
            Stmt::Let(..) =&gt; unimplemented!(),
        }
    }

    fn visit_expr(&amp;mut self, e: &amp;Expr) -&gt; i64 {
        match *e {
            Expr::IntLit(n) =&gt; n,
            Expr::Add(ref lhs, ref rhs) =&gt; self.visit_expr(lhs) + self.visit_expr(rhs),
            Expr::Sub(ref lhs, ref rhs) =&gt; self.visit_expr(lhs) - self.visit_expr(rhs),
        }
    }
}
</code></pre>
<p>Note the zero-sized <code>Interpreter</code> struct in the code. It is solely used for the implementation and instantiation of the <code>Visitor</code> trait.</p>

CC BY-SA 4.0
2023-07-06T18:01:05.293Z
2023-07-06T18:01:05.293Z
Seggan
<p>Allowing zero-sized structs makes things easier for automatically-generated code, because the code generator doesn't need to treat this as a special case that it must avoid. Similar considerations apply to zero-length arrays, functions with no variables, etc.</p>
<p>Where many people notice this kind of problem is when they generate SQL, and they're generating <code>WHERE column IN (&lt;list of values&gt;)</code> dynamically. SQL doesn't allow the degenerate case of an empty list; if their source data has an empty list, they have to take special measures to address this (one approach I've used is to add an &quot;impossible&quot; value to the source list before filling it into the template, another is to conditionalize adding this condition to the <code>WHERE</code> clause).</p>

CC BY-SA 4.0
2023-07-01T04:28:07.097Z
2023-07-01T19:15:51.497Z
2023-07-01T19:15:51.497Z
Barmar
CC BY-SA 4.0
2023-07-01T04:30:28.667Z
On the contrary, zero-sized data adds a *ton* of special-cases to your average compiler. The Rustonomicon has an [entire page](https://doc.rust-lang.org/nomicon/vec/vec-zsts.html) dedicated to the special-casing that `Vec` has to have in order to work correctly with ZSTs.
Silvio Mayolo
CC BY-SA 4.0
2023-07-01T04:31:43.417Z
I'm not talking about compilers, I'm talking about programs that generate structure definitions automatically.
Barmar
CC BY-SA 4.0
2023-07-01T04:41:53.507Z
Ah, that's a fair point. The C/C++ approach of "allow empty types, but make them one byte" is a compromise position, but your argument is still completely valid.
Silvio Mayolo
CC BY-SA 4.0
2023-07-01T16:16:51.607Z
This is particularly important for Rust since it has strong support for macros which can generate code, including struct definitions.
kaya3
CC BY-SA 4.0
2023-07-02T08:08:14.727Z
I've seen and written code that has conditions of the form 'where 1=1' or 'where 1=0' just to have a generic way to build the structure. It is definitely useful if the language allows these use cases without such work arounds.
quarague
CC BY-SA 4.0
2023-07-03T14:06:57.973Z
I used to use a C++ compiler that allowed 0-byte classes, but only as base classes.
gnasher729
<h2>For use in generics</h2>
<p>Consider code like this:</p>
<pre class="lang-rs prettyprint-override"><code>trait Message {
    fn print(&amp;self) -&gt; String
}

fn print_message&lt;T: Message&gt;(message: T) {
     print!(&quot;{}&quot;, T.print());
}

struct EmptyMessage;

impl Message for EmptyMessage {
    fn print(&amp;self) -&gt; String { &quot;&quot;.to_string() }
}
</code></pre>
<p>In this case, EmptyMessage isn't actually really a struct, since it has no size. It's just used to indicate a certain behavior.</p>
<p>At runtime the entire struct will be completely optimized away, yet it's useful so we don't need to define separate functions depending on if the type actually needs any data to do it's work or no.</p>
<p>For a look at more advanced cases check out the Bevy game engine, which extensively uses unit structs as parameters to generics, even if the struct doesn't even implement any custom methods.</p>

CC BY-SA 4.0
2023-07-01T11:00:08.323Z
2023-07-01T11:00:08.323Z
mousetail 'he-him'
<h1>If it has methods or computed properties</h1>
<p>Consider this most basic of SwiftUI views:</p>
<pre class="lang-swift prettyprint-override"><code>struct ExampleView: View {
  var body: some View {
    Text(&quot;Hello, World!&quot;)
  }
}
</code></pre>
<p>It has &quot;zero&quot; size, but its vtable is important.</p>

CC BY-SA 4.0
2023-07-01T04:09:20.723Z
2023-07-01T04:09:20.723Z
Bbrk24
CC BY-SA 4.0
2023-07-01T22:55:14.987Z
Then the struct can't really have zero size, since it needs to store a pointer to the vtable or some runtime type information.
dan04
CC BY-SA 4.0
2023-07-01T22:59:24.773Z
@dan04 There’s a reason I put “zero” in quotes. `MemoryLayout<ExampleView>.size` is zero but the second you put it in an `any View` it takes 40 bytes.
Bbrk24
CC BY-SA 4.0
2023-07-02T16:50:28.423Z
This applies to true zero-size types too, in languages that allow the right sorts of polymorphism. In C++, a captureless lambda would be a zero-sized type, if the language didn't have a 1 byte minimum. The type is useful since it has a `operator()` method attached. Lambdas are used with templates, so the method doesn't need to be `virtual` and no vtable is needed. In Haskell, you can make a data type with no fields an instance of a class. The class methods are attached to the type, but not the values, since Haskell visibly separates "vtables" from their data.
HTNW
CC BY-SA 4.0
2023-07-02T22:27:44.260Z
@dan04 A pointer to type information is only necessary if it's used in a place that handles it dynamically/polymorphically. When used in a context that has the exact concrete type (e.g. if you have a function that returns `ExampleView` and not `any View`), then the compiler knows where to lookup methods without needing it stored in the instances.
Alexander
CC BY-SA 4.0
2023-07-03T07:26:47.127Z
@HTNW: Note that C++ kinda support zero-sized types from C++20 on with `[[no_unique_address]]`, comparators, hashers, allocators, etc... are now often zero-sized data-members, even if measured separately their size is reported as 1.
Matthieu M.
<p>Here's one that doesn't actually apply to Rust but which nearly applies to Rust.</p>
<p><strong>As a compile-time token</strong>, to help the programmer manage lifetimes or satisfy other ordering constraints. Here's some pseudocode:</p>
<pre><code>type Token = private {}

fun obtainToken() : Token = 
    // some side-effectful stuff here, then…
    new Token {}

fun doGuardedThing(_token: Token) =
    // some stuff that is guaranteed only to happen
    // if you've called `obtainToken` already
</code></pre>
<p>Sure, it's not exactly <em>common</em> to have literally no data to pass around in the token, but I've certainly wanted to do this before. If you've only got one zero-sized type (the unit type), then you can't guarantee that someone obtained your token through your desired API: any caller is free to magic up a value of type <code>()</code> anyway.</p>
<p>You can instead solve this problem with a single-case enum, but that takes space at runtime unless you're holding a zero-sized type anyway, which just punts the problem down the line: enum cases have the same visibility as their containing type, in Rust.</p>
<p>(The reason this doesn't apply to Rust in real life is because you can actually construct a <code>Foo {}</code> even if <code>pub struct Foo = {}</code>. The workaround, I guess, is <code>pub struct Foo = { garbage: () }</code>, or a <code>PhantomData</code>. I'm really considering a hypothetical language which had something more like F#'s visibility modifiers.)</p>

CC BY-SA 4.0
2023-07-02T21:25:58.143Z
2023-07-03T08:12:56.067Z
2023-07-03T08:12:56.067Z
Patrick Stevens
CC BY-SA 4.0
2024-08-15T16:39:34.273Z
The workaround of `pub struct Foo = { garbage: () }` does work in Rust - and is a zero-sized token that thus only exists at compile time and not at runtime.
Simon Farnsworth
<p>C99 allows for a <a href="https://en.wikipedia.org/wiki/Flexible_array_member" rel="noreferrer">Flexible array member</a> to allow a variable length array to be allocated at the last member of a structure. This can simplify dynamic allocation of a structure which ends with a variable length array since the compiler will adding to ensure the flexible array type to have  the correct starting alignment.</p>
<p>GCC allowed <a href="https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html" rel="noreferrer">Arrays of Length Zero</a> as extension prior to the C99 Flexible array member.</p>

CC BY-SA 4.0
2023-07-01T18:00:18.600Z
2023-07-01T18:00:18.600Z
Chester Gillon
<p>Having named zero-sized types is useful to create handles to resources that are fixed at compile-time. This pattern comes up a lot in embedded systems / bare-metal programming, for example. The system has a fixed amount of resources and peripherals, often at fixed addresses or specific CPU instructions; a library that provides bindings to the peripheral can create a handle to represent that resource.</p>
<p>For example:</p>
<pre class="lang-rust prettyprint-override"><code>pub struct Rng {
    _private: (),
}
</code></pre>
<p>The <code>_private</code> field makes it impossible for library users to construct it, so they must obtain the handle through a constructor function in the peripheral library. The peripheral library can use this to track how many handles exist. For example, it's often used to guarantee unique &quot;ownership&quot; of the resource, by only providing one handle to the library user:</p>
<pre class="lang-rust prettyprint-override"><code>impl Rng {
    pub fn take() -&gt; Option&lt;Self&gt; {
        // This doesn't quite work; simplified for demonstration purposes
        static mut TAKEN = false;
        if TAKEN {
            None
        } else {
            TAKEN = true;
            Some(Self { _private: () })
        }
    }
}
</code></pre>
<p>The handle to the peripheral doesn't need to store any runtime references, since the peripheral address is known at compile-time. Most of the time, it also doesn't need to store any state - it just provides functions to conveniently, directly access the peripheral's I/O:</p>
<pre class="lang-rust prettyprint-override"><code>const RNG_ADDR: *const u32 = 0x48021800 as *const u32;

impl Rng {
    pub fn read(&amp;mut self) -&gt; u32 {
        unsafe { RNG_ADDR.read_volatile() }
    }
}
</code></pre>
<p>Therefore, it only makes sense that it takes up no memory. It's just a marker used at compile-time to check who has control over that resource at that point in the program.</p>
<p>For a practical example, see the <a href="https://docs.rs/stm32f0/0.15.1/stm32f0/index.html" rel="noreferrer"><code>stm32f0</code></a> crate. Most peripheral-access crates (PACs) look like this. The entrypoint is usually <a href="https://docs.rs/stm32f0/0.15.1/src/stm32f0/stm32f0x0/mod.rs.html#1283-1291" rel="noreferrer"><code>Peripherals::take()</code></a>, which provides the <a href="https://docs.rs/stm32f0/0.15.1/stm32f0/stm32f0x0/struct.Peripherals.html" rel="noreferrer"><code>Peripherals</code></a> collection exactly once to the first caller.</p>

CC BY-SA 4.0
2024-07-26T00:06:25.807Z
2024-07-26T00:16:47.060Z
2024-07-26T00:16:47.060Z
Adam Gausmann
<h1>For when you <em>need</em> a value, but don't care about the <em>actual</em> value</h1>
<p>Go allows empty structs (<code>struct{}</code>). Its main use is with the built-in <code>map</code> type for imitating sets, where the value of the map isn't needed:</p>
<pre class="lang-go prettyprint-override"><code>eles := []string{ ... }
set := make(map[string]struct{})
for _,e := range eles {
    if _,ok := set[e]; !ok {
        set[e] = struct{} // add the element to the set
    }
}
for k,_ := range set {
   // do stuff with k, which is a unique element of eles
}
</code></pre>
<p>and with channels, as a signal with a small size:</p>
<pre class="lang-go prettyprint-override"><code>import (&quot;time&quot;;&quot;fmt&quot;)
signal := make(chan struct{}) // read-write channel
go func() {
    time.Sleep(2*time.Second)
    signal &lt;- struct{} // tell the outside world we're done
}
for {
    select {
    case &lt;-signal:
        fmt.Println(&quot;Waited 2 seconds&quot;)
        break // exit loop
    default:
        fmt.Println(&quot;Waiting...&quot;)
        time.Sleep(time.Second / 2)
    }
}
</code></pre>

CC BY-SA 4.0
2023-07-01T04:21:54.043Z
2023-07-02T22:29:03.390Z
2023-07-02T22:29:03.390Z
bigyihsuan
CC BY-SA 4.0
2023-07-01T04:35:46.920Z
The idiom for that, in languages like Rust, is to use an empty tuple rather than an empty struct.
Bbrk24
CC BY-SA 4.0
2023-07-02T18:45:02.797Z
@Bbrk24 sure, but Go doesn't have a tuple type, and in any case tuples are just a degenerate case of structs where the elements can't have meaningful names. If there are no elements, that distinction vanishes, so an empty tuple and an empty struct are really the same thing :)
hobbs
CC BY-SA 4.0
2023-07-03T01:17:45.427Z
In Swift, [tuples and structs are actually quite different](https://langdev.stackexchange.com/a/1645/15) (and actually, I do mention empty types in that post).
Bbrk24
<p>Zero-sized structs could be used to force changes to the alignment of members of a containing struct. This is more likely when the object is a primitive and not an object.</p>
<pre><code>struct {
  type_a a[2];
  // Potential padding here
  type_b b[0];
  // Potential padding here
  type_c c[10];
} d;
</code></pre>

CC BY-SA 4.0
2023-07-01T15:11:44.690Z
2023-07-02T18:49:18.177Z
2023-07-02T18:49:18.177Z
chux
CC BY-SA 4.0
2023-07-02T13:05:14.267Z
In the example, I don't see where a zero-sized struct might come in.
Paŭlo Ebermann
CC BY-SA 4.0
2023-07-02T15:18:42.017Z
@PaŭloEbermann This is more of a zero sized array than a zero sized type.  The alignment requirements of `type_b` still affect `d`, even though `b[]` has zero size.
chux
CC BY-SA 4.0
2023-07-02T16:03:01.723Z
@chux-ReinstateMonica But in what ways does it do that? I'm not familiar enough with C's struct layout rules to see how this would be any different without `b`
rydwolf
CC BY-SA 4.0
2023-07-02T18:51:31.980Z
@RydwolfPrograms C does not allow 0 sized arrays.  This example surmises what would happen if it did.   Suppose alignment requirements were 1, 4, TBD for types: `type_a, type_b, type_c` and size of `.a` is 1*2.  With `type_b b[0];` the member `.c` cannot start on a +2 address as `.b` needs a  multiple of 4, even though it is size 0 forcing 2 padding bytes between `.a` and `.b`.  Now the padding requirement for `.c` to  `.b` is determined.  If `.c` alignment is 1, then with no `.b`, there would be no padding at all from `.a` to `.c`.
chux
CC BY-SA 4.0
2023-07-02T19:47:59.127Z
@RydwolfPrograms: For example, take `int32_t a[1]; int64_t b[0]; int16_t c[2];`. Without `b`, we could store `c` immediately after `a`, because 32-bit alignment is adequate for 16-bit alignment. But with `b`, we must leave 32 bits of padding to reach a 64-bit boundary first, so that `b` is aligned; and we must also pad after `c` so that the end of the structure is 64-bit–aligned.
Jon Purdy
<p>Out of symmetry, if it has templates that allow you to pass void as an argument. In that case, you'd end up with means to create zero-sized structs anyway. Also, you can use them as markers for variable length content provided by subtypes, if the language has such types.</p>

CC BY-SA 4.0
2023-07-02T09:18:19.720Z
2023-07-02T09:18:19.720Z
feldentm
CC BY-SA 4.0
2023-07-03T02:39:20.490Z
Very much this. Don't discount the value of "we would have to add a rule to explicitly disallow it, and that would require justification".
Pseudonym
<p><strong>Regularity</strong></p>
<p>A number of specific arguments have been provided, but I believe that a more generic argument applies here, and in many other situations: regularity is good.</p>
<p>For example, if we take a (relatively) immature language such as Rust, you'll notice that part of the difficulties for newcomers are that some things are available in some contexts, but not others.</p>
<p>Commas are easy:</p>
<pre class="lang-rust prettyprint-override"><code>//  A trailing comma is allowed after the last argument of a function.
fn foo(a: A, b: B,);

//  A trailing comma is allowed after the last argument of a call.
foo(a, b,);

//  A trailing comma is allowed after the last field of a struct.
struct Foo {
    a: A,
    b: B,
}
</code></pre>
<p>Generics are getting better:</p>
<pre class="lang-rust prettyprint-override"><code>//  A type alias can be generic.
type InlineVec&lt;T, const N: usize&gt; = Vec&lt;T, InlineSingleStore&lt;T, N&gt;&gt;;

trait X {
    //  An associated type can only be generic from 1.65 forward (yeah!).
    type Inline/*&lt;T, const N: usize&gt;*/;
}
</code></pre>
<p>Compile-time callable (<code>const</code>) are still iffy:</p>
<pre class="lang-rust prettyprint-override"><code>//  A free-function can be marked `const`, and evaluated at compile-time.
const fn foo() -&gt; i32;

impl S {
   //  An associated function of a struct can be marked `const`.
    const fn bar(&amp;self) -&gt; i32;
}

trait X {
    //  An associated function of a trait cannot be marked `const`.
    /*const*/ fn baz(&amp;self) -&gt; i32;
}

//  Instead, a trait _implementation_ can be marked `const`,
//  but only if the trait declaration was tagged `#[const_trait]`.
impl const X for S {
    fn baz(&amp;self) -&gt; i32 { 4 }
}
</code></pre>
<p>There are good reasons for this -- design and implementation complexity, notably -- however from a user point of view it's jarring. It's a myriad rules to remember.</p>
<p>Regularity, or Orthogonality, as in the ability to apply a concept everywhere it is sensible to, makes for a simpler language <em>to use</em>.</p>
<p>Thus, the question should be: <em>Why would a language not allow X?</em></p>
<p><em>(ie, what advantage is there is NOT providing X?)</em></p>

CC BY-SA 4.0
2023-07-03T12:06:52.910Z
2023-07-03T13:28:38.480Z
2023-07-03T13:28:38.480Z
Matthieu M.
CC BY-SA 4.0
2023-07-03T13:12:24.260Z
Welcome! I'd suggest avoiding `~~` and other meta-syntax that isn't part of the language you're demonstrating in code blocks, since this is likely to confuse people who aren't familiar with the language.
kaya3
CC BY-SA 4.0
2023-07-03T13:29:10.520Z
@kaya3-supportthestrike: Replaced with inline comment syntax. Gets better highlighting too so it's a win :)
Matthieu M.
# Why would a language allow zero-size structs?
Posted by: username_0 on 2023-07-01
While scrolling through a Rust syntax cheat sheet today, I noticed an odd item:
struct S; // Define zero sized unit struct. Occupies no space, optimized away.

This seems like a strange thing to allow; Rust already has a zero-size unit type, () (which does have uses), and I can't think of many situations where a zero-size struct would provide an advantage over that. Perhaps in a language with a type system that had non-tagged unions, you could do something like let dog_breed: Schnauser | Corgie = Schnauser {}; to implement a sort of enum pattern. This would still seem pretty useless though, especially in a language which already has enums/tagged unions.
What justification do existing languages with this feature provide, and/or what purposes could it serve?
**Tags:** data-types struct unit-type
> **Comments:**
> **username_1**: Follow up question: How would a zero-size struct be different from `void` in practice?  
> **username_2**: @user16217248-OnStrike `void` in C is not inhabited in the same sense: an expression or function has type `void` but you cannot construct and pass around a value of type `void`. In Rust, a ZST mostly behaves like other types: you _can_ have a value of type `()` or any other ZST. A `!Copy` ZST has the same ownership semantics of other types.  
> **username_3**: Are you asking about why it is allowed for a struct to have no members (like in C++), or are you asking about why it has zero size?  
> **username_0**: @user3840170 For this question, both for the most part. I'm used to making the assumption zero members == zero size, so I kind of worded the question around that, but answers talking about non-zero-size ones would still probably be useful tho.  
> **username_4**: Why would a language _not_ allow zero-size structs?  
> **username_5**: Re Rust, have a look at [What are the differences between the multiple ways to create zero-sized structs?](https://stackoverflow.com/questions/50162597/what-are-the-differences-between-the-multiple-ways-to-create-zero-sized-structs)  
---
## 12 answers
### Answer by username_8 on 2023-07-01. Score: 36
Allowing zero-sized structs makes things easier for automatically-generated code, because the code generator doesn't need to treat this as a special case that it must avoid. Similar considerations apply to zero-length arrays, functions with no variables, etc.

Where many people notice this kind of problem is when they generate SQL, and they're generating `WHERE column IN (

)` dynamically. SQL doesn't allow the degenerate case of an empty list; if their source data has an empty list, they have to take special measures to address this (one approach I've used is to add an "impossible" value to the source list before filling it into the template, another is to conditionalize adding this condition to the `WHERE` clause).
> **Comments:**
> **username_9** (2023-07-01): On the contrary, zero-sized data adds a *ton* of special-cases to your average compiler. The Rustonomicon has an [entire page](https://doc.rust-lang.org/nomicon/vec/vec-zsts.html) dedicated to the special-casing that `Vec` has to have in order to work correctly with ZSTs.  
> **username_8** (2023-07-01): I'm not talking about compilers, I'm talking about programs that generate structure definitions automatically.  
> **username_9** (2023-07-01): Ah, that's a fair point. The C/C++ approach of "allow empty types, but make them one byte" is a compromise position, but your argument is still completely valid.  
> **username_10** (2023-07-01): This is particularly important for Rust since it has strong support for macros which can generate code, including struct definitions.  
> **username_11** (2023-07-02): I've seen and written code that has conditions of the form 'where 1=1' or 'where 1=0' just to have a generic way to build the structure. It is definitely useful if the language allows these use cases without such work arounds.  
> **username_12** (2023-07-03): I used to use a C++ compiler that allowed 0-byte classes, but only as base classes.  
---
### Answer by username_13 on 2023-07-01. Score: 21
For use in generics
Consider code like this:
trait Message {
    fn print(&self) -> String
}

fn print_message(message: T) {
     print!("{}", T.print());
}

struct EmptyMessage;

impl Message for EmptyMessage {
    fn print(&self) -> String { "".to_string() }
}

In this case, EmptyMessage isn't actually really a struct, since it has no size. It's just used to indicate a certain behavior.
At runtime the entire struct will be completely optimized away, yet it's useful so we don't need to define separate functions depending on if the type actually needs any data to do it's work or no.
For a look at more advanced cases check out the Bevy game engine, which extensively uses unit structs as parameters to generics, even if the struct doesn't even implement any custom methods.
---
### Answer by username_14 on 2023-07-01. Score: 18
If it has methods or computed properties
Consider this most basic of SwiftUI views:
struct ExampleView: View {
  var body: some View {
    Text("Hello, World!")
  }
}

It has "zero" size, but its vtable is important.
> **Comments:**
> **username_15** (2023-07-01): Then the struct can't really have zero size, since it needs to store a pointer to the vtable or some runtime type information.  
> **username_14** (2023-07-01): @dan04 There’s a reason I put “zero” in quotes. `MemoryLayout<ExampleView>.size` is zero but the second you put it in an `any View` it takes 40 bytes.  
> **username_16** (2023-07-02): This applies to true zero-size types too, in languages that allow the right sorts of polymorphism. In C++, a captureless lambda would be a zero-sized type, if the language didn't have a 1 byte minimum. The type is useful since it has a `operator()` method attached. Lambdas are used with templates, so the method doesn't need to be `virtual` and no vtable is needed. In Haskell, you can make a data type with no fields an instance of a class. The class methods are attached to the type, but not the values, since Haskell visibly separates "vtables" from their data.  
> **username_17** (2023-07-02): @dan04 A pointer to type information is only necessary if it's used in a place that handles it dynamically/polymorphically. When used in a context that has the exact concrete type (e.g. if you have a function that returns `ExampleView` and not `any View`), then the compiler knows where to lookup methods without needing it stored in the instances.  
> **username_18** (2023-07-03): @HTNW: Note that C++ kinda support zero-sized types from C++20 on with `[[no_unique_address]]`, comparators, hashers, allocators, etc... are now often zero-sized data-members, even if measured separately their size is reported as 1.  
---
### Answer by username_28 on 2023-07-02. Score: 8
Out of symmetry, if it has templates that allow you to pass void as an argument. In that case, you'd end up with means to create zero-sized structs anyway. Also, you can use them as markers for variable length content provided by subtypes, if the language has such types.
> **Comments:**
> **username_29** (2023-07-03): Very much this. Don't discount the value of "we would have to add a rule to explicitly disallow it, and that would require justification".  
---
### Answer by username_19 on 2023-07-02. Score: 7
Here's one that doesn't actually apply to Rust but which nearly applies to Rust.
As a compile-time token, to help the programmer manage lifetimes or satisfy other ordering constraints. Here's some pseudocode:
type Token = private {}

fun obtainToken() : Token = 
    // some side-effectful stuff here, then…
    new Token {}

fun doGuardedThing(_token: Token) =
    // some stuff that is guaranteed only to happen
    // if you've called `obtainToken` already

Sure, it's not exactly common to have literally no data to pass around in the token, but I've certainly wanted to do this before. If you've only got one zero-sized type (the unit type), then you can't guarantee that someone obtained your token through your desired API: any caller is free to magic up a value of type () anyway.
You can instead solve this problem with a single-case enum, but that takes space at runtime unless you're holding a zero-sized type anyway, which just punts the problem down the line: enum cases have the same visibility as their containing type, in Rust.
(The reason this doesn't apply to Rust in real life is because you can actually construct a Foo {} even if pub struct Foo = {}. The workaround, I guess, is pub struct Foo = { garbage: () }, or a PhantomData. I'm really considering a hypothetical language which had something more like F#'s visibility modifiers.)
> **Comments:**
> **username_20** (2024-08-15): The workaround of `pub struct Foo = { garbage: () }` does work in Rust - and is a zero-sized token that thus only exists at compile time and not at runtime.  
---
### Answer by username_21 on 2023-07-01. Score: 7
C99 allows for a [Flexible array member](https://en.wikipedia.org/wiki/Flexible_array_member) to allow a variable length array to be allocated at the last member of a structure. This can simplify dynamic allocation of a structure which ends with a variable length array since the compiler will adding to ensure the flexible array type to have the correct starting alignment.

GCC allowed [Arrays of Length Zero](https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html) as extension prior to the C99 Flexible array member.
---
### Answer by username_18 on 2023-07-03. Score: 7
Regularity
A number of specific arguments have been provided, but I believe that a more generic argument applies here, and in many other situations: regularity is good.
For example, if we take a (relatively) immature language such as Rust, you'll notice that part of the difficulties for newcomers are that some things are available in some contexts, but not others.
Commas are easy:
//  A trailing comma is allowed after the last argument of a function.
fn foo(a: A, b: B,);

//  A trailing comma is allowed after the last argument of a call.
foo(a, b,);

//  A trailing comma is allowed after the last field of a struct.
struct Foo {
    a: A,
    b: B,
}

Generics are getting better:
//  A type alias can be generic.
type InlineVec = Vec>;

trait X {
    //  An associated type can only be generic from 1.65 forward (yeah!).
    type Inline/**/;
}

Compile-time callable (const) are still iffy:
//  A free-function can be marked `const`, and evaluated at compile-time.
const fn foo() -> i32;

impl S {
   //  An associated function of a struct can be marked `const`.
    const fn bar(&self) -> i32;
}

trait X {
    //  An associated function of a trait cannot be marked `const`.
    /*const*/ fn baz(&self) -> i32;
}

//  Instead, a trait _implementation_ can be marked `const`,
//  but only if the trait declaration was tagged `#[const_trait]`.
impl const X for S {
    fn baz(&self) -> i32 { 4 }
}

There are good reasons for this -- design and implementation complexity, notably -- however from a user point of view it's jarring. It's a myriad rules to remember.
Regularity, or Orthogonality, as in the ability to apply a concept everywhere it is sensible to, makes for a simpler language to use.
Thus, the question should be: Why would a language not allow X?
(ie, what advantage is there is NOT providing X?)
> **Comments:**
> **username_10** (2023-07-03): Welcome! I'd suggest avoiding `~~` and other meta-syntax that isn't part of the language you're demonstrating in code blocks, since this is likely to confuse people who aren't familiar with the language.  
> **username_18** (2023-07-03): @kaya3-supportthestrike: Replaced with inline comment syntax. Gets better highlighting too so it's a win :)  
---
### Answer by username_22 on 2024-07-26. Score: 5
Having named zero-sized types is useful to create handles to resources that are fixed at compile-time. This pattern comes up a lot in embedded systems / bare-metal programming, for example. The system has a fixed amount of resources and peripherals, often at fixed addresses or specific CPU instructions; a library that provides bindings to the peripheral can create a handle to represent that resource.
For example:
pub struct Rng {
    _private: (),
}

The _private field makes it impossible for library users to construct it, so they must obtain the handle through a constructor function in the peripheral library. The peripheral library can use this to track how many handles exist. For example, it's often used to guarantee unique "ownership" of the resource, by only providing one handle to the library user:
impl Rng {
    pub fn take() -> Option {
        // This doesn't quite work; simplified for demonstration purposes
        static mut TAKEN = false;
        if TAKEN {
            None
        } else {
            TAKEN = true;
            Some(Self { _private: () })
        }
    }
}

The handle to the peripheral doesn't need to store any runtime references, since the peripheral address is known at compile-time. Most of the time, it also doesn't need to store any state - it just provides functions to conveniently, directly access the peripheral's I/O:
const RNG_ADDR: *const u32 = 0x48021800 as *const u32;

impl Rng {
    pub fn read(&mut self) -> u32 {
        unsafe { RNG_ADDR.read_volatile() }
    }
}

Therefore, it only makes sense that it takes up no memory. It's just a marker used at compile-time to check who has control over that resource at that point in the program.
For a practical example, see the stm32f0 crate. Most peripheral-access crates (PACs) look like this. The entrypoint is usually Peripherals::take(), which provides the Peripherals collection exactly once to the first caller.
---
### Answer by username_23 on 2023-07-01. Score: 4
For when you need a value, but don't care about the actual value
Go allows empty structs (struct{}). Its main use is with the built-in map type for imitating sets, where the value of the map isn't needed:
eles := []string{ ... }
set := make(map[string]struct{})
for _,e := range eles {
    if _,ok := set[e]; !ok {
        set[e] = struct{} // add the element to the set
    }
}
for k,_ := range set {
   // do stuff with k, which is a unique element of eles
}

and with channels, as a signal with a small size:
import ("time";"fmt")
signal := make(chan struct{}) // read-write channel
go func() {
    time.Sleep(2*time.Second)
    signal <- struct{} // tell the outside world we're done
}
for {
    select {
    case <-signal:
        fmt.Println("Waited 2 seconds")
        break // exit loop
    default:
        fmt.Println("Waiting...")
        time.Sleep(time.Second / 2)
    }
}
> **Comments:**
> **username_14** (2023-07-01): The idiom for that, in languages like Rust, is to use an empty tuple rather than an empty struct.  
> **username_24** (2023-07-02): @Bbrk24 sure, but Go doesn't have a tuple type, and in any case tuples are just a degenerate case of structs where the elements can't have meaningful names. If there are no elements, that distinction vanishes, so an empty tuple and an empty struct are really the same thing :)  
> **username_14** (2023-07-03): In Swift, [tuples and structs are actually quite different](https://langdev.stackexchange.com/a/1645/15) (and actually, I do mention empty types in that post).  
---
### Answer by username_25 on 2023-07-01. Score: 4
Zero-sized structs could be used to force changes to the alignment of members of a containing struct. This is more likely when the object is a primitive and not an object.
struct {
  type_a a[2];
  // Potential padding here
  type_b b[0];
  // Potential padding here
  type_c c[10];
} d;
> **Comments:**
> **username_26** (2023-07-02): In the example, I don't see where a zero-sized struct might come in.  
> **username_25** (2023-07-02): @PaŭloEbermann This is more of a zero sized array than a zero sized type.  The alignment requirements of `type_b` still affect `d`, even though `b[]` has zero size.  
> **username_0** (2023-07-02): @chux-ReinstateMonica But in what ways does it do that? I'm not familiar enough with C's struct layout rules to see how this would be any different without `b`  
> **username_25** (2023-07-02): @RydwolfPrograms C does not allow 0 sized arrays.  This example surmises what would happen if it did.   Suppose alignment requirements were 1, 4, TBD for types: `type_a, type_b, type_c` and size of `.a` is 1*2.  With `type_b b[0];` the member `.c` cannot start on a +2 address as `.b` needs a  multiple of 4, even though it is size 0 forcing 2 padding bytes between `.a` and `.b`.  Now the padding requirement for `.c` to  `.b` is determined.  If `.c` alignment is 1, then with no `.b`, there would be no padding at all from `.a` to `.c`.  
> **username_27** (2023-07-02): @RydwolfPrograms: For example, take `int32_t a[1]; int64_t b[0]; int16_t c[2];`. Without `b`, we could store `c` immediately after `a`, because 32-bit alignment is adequate for 16-bit alignment. But with `b`, we must leave 32 bits of padding to reach a 64-bit boundary first, so that `b` is aligned; and we must also pad after `c` so that the end of the structure is 64-bit–aligned.  
---
### Answer by username_7 on 2023-07-06. Score: 3
For trait implementations
They can be useful for getting a concrete implementation of a trait. For example, see this example in the Rust design patterns book:
// The data we will visit
mod ast {
    pub enum Stmt {
        Expr(Expr),
        Let(Name, Expr),
    }

    pub struct Name {
        value: String,
    }

    pub enum Expr {
        IntLit(i64),
        Add(Box, Box),
        Sub(Box, Box),
    }
}

// The abstract visitor
mod visit {
    use ast::*;

    pub trait Visitor {
        fn visit_name(&mut self, n: &Name) -> T;
        fn visit_stmt(&mut self, s: &Stmt) -> T;
        fn visit_expr(&mut self, e: &Expr) -> T;
    }
}

use visit::*;
use ast::*;

// An example concrete implementation - walks the AST interpreting it as code.
struct Interpreter;
impl Visitor for Interpreter {
    fn visit_name(&mut self, n: &Name) -> i64 { panic!() }
    fn visit_stmt(&mut self, s: &Stmt) -> i64 {
        match *s {
            Stmt::Expr(ref e) => self.visit_expr(e),
            Stmt::Let(..) => unimplemented!(),
        }
    }

    fn visit_expr(&mut self, e: &Expr) -> i64 {
        match *e {
            Expr::IntLit(n) => n,
            Expr::Add(ref lhs, ref rhs) => self.visit_expr(lhs) + self.visit_expr(rhs),
            Expr::Sub(ref lhs, ref rhs) => self.visit_expr(lhs) - self.visit_expr(rhs),
        }
    }
}

Note the zero-sized Interpreter struct in the code. It is solely used for the implementation and instantiation of the Visitor trait.
---
### Answer by username_6 on 2023-07-05. Score: 1
Consider *Option* types.

*Some* a struct with a backed field.
*None* is a zero size struct.

The usefulness of *None* is that it can participate in type casts.

A language can recognize that zero-size structs can be singleton, so you could distinguish *Some* from *None* via a pointer check.

Some runtimes instead store out-of-band type information when a struct is up-cast to an abstract type. Go for example does this so that it can check that down-casts from interface types are safe.
> **Comments:**
> **username_0** (2023-07-05): This is only sort of an answer, since this same functionality can be achieved with a zero-size tuple/unit type, and I'm more asking about why a language would specifically allow something along the lines of `struct X;`  
> **username_6** (2023-07-05): @RydwolfPrograms Sorry, I misunderstood.  I interpreted "struct type" as *product type*.  But unless your type system allows great flexibility with union types, you still need some kind of common super-type for *Some* and *None*?  
> **username_0** (2023-07-05): Isn't that normally done with a tagged union? Like `None` would be `00` with some padding, and `Some` would be `01` followed by the data? (With things like null pointer optimization if the data is a pointer)  
> **username_6** (2023-07-05): It often is, and often tagged unions are syntactic sugar for a group of struct type definitions that have with some tag bits at a well known offset.  Doing it that way means that a tagged union like `type ThreeValueLogic = Unknown | Known(Bool)` can benefit from struct layout optimizations to pack the single tag bit with the single bit for the bool into one byte so the sizeof the union as a whole is 1 instead of treating tag bits as some special out-of-band thing.  (Though in this case you can also enumerate the possible values)  
---
