What are the differences between the multiple ways to create zero-sized structs?

There are only two functional differences between these four definitions (and a fifth possibility I'll mention in a minute):

  1. Syntax (the most obvious). mcarton's answer goes into more detail.
  2. When the struct is marked pub, whether its constructor (also called struct literal syntax) is usable outside the module it's defined in.

The only one of your examples that is not directly constructible from outside the current module is C. If you try to do this, you will get an error:

mod stuff {
    pub struct C(());
}
let _c = stuff::C(());  // error[E0603]: tuple struct `C` is private

This happens because the field is not marked pub; if you declare C as pub struct C(pub ()), the error goes away.

There's another possibility you didn't mention that gives a marginally more descriptive error message: a normal struct, with a zero-sized non-pub member.

mod stuff {
    pub struct E {
        _dummy: (),
    }
}
let _e = stuff::E { _dummy: () };  // error[E0451]: field `_dummy` of struct `main::stuff::E` is private

(Again, you can make the _dummy field available outside of the module by declaring it with pub.)

Since E's constructor is only usable inside the stuff module, stuff has exclusive control over when and how values of E are created. Many structs in the standard library take advantage of this, like Box (to take an obvious example). Zero-sized types work in exactly the same way; in fact, from outside the module it's defined in, the only way you would know that an opaque type is zero-sized is by calling mem::size_of.

See also

  • What is an idiomatic way to create a zero-sized struct that can't be instantiated outside its crate?
  • Why define a struct with single private field of unit type?

struct D; // unit struct

This is the usual way for people to write a zero-sized struct.

struct A{} // empty struct / empty braced struct
struct B(); // empty tuple struct

These are just special cases of basic struct and tuple struct which happen to have no parameters. RFC 1506 explains the rational to allow those (they didn't used to):

Permit tuple structs and tuple variants with 0 fields. This restriction is artificial and can be lifted trivially. Macro writers dealing with tuple structs/variants will be happy to get rid of this one special case.

As such, they could easily be generated by macros, but people will rarely write those on their own.

struct C(()); // unit-valued tuple struct

This is another special case of tuple struct. In Rust, () is a type just like any other type, so struct C(()); isn't much different from struct E(u32);. While the type itself isn't very useful, forbidding it would make yet another special case that would need to be handled in macros or generics (struct F<T>(T) can of course be instantiated as F<()>).

Note that there are many other ways to have empty types in Rust. Eg. it is possible to have a function return Result<(), !> to indicate that it doesn't produce a value, and cannot fail. While you might think that returning () in that case would be better, you might have to do that if you implement a trait that dictates you to return Result<T, E> but lets you choose T = () and E = !.