Many Rust beginners with a background in systems programming tend to use
u8 — an 8-bit unsigned integer type) to represent "state".
For example, how about a
bool to indicate whether a user is active or not?
Initially, this might seem fine, but as your codebase grows,
you'll find that "active" is not a binary state. There are many
different states that a user can be in. For example, a user
might be suspended or deleted. However, extending the user struct
can get problematic, because other parts of the code might
rely on the fact that
active is a
Another problem is that
bool is not self-documenting. What does
active = false mean? Is the user inactive? Or is the user
deleted? Or is the user suspended? We don't know!
Alternatively, you could use an unsigned integer to represent state:
This is slightly better, because we can now use different values to represent more states:
const ACTIVE: u8 = 0; const INACTIVE: u8 = 1; const SUSPENDED: u8 = 2; const DELETED: u8 = 3; let user = User ;
A common use-case for
u8 is when you interface with C code.
In that case, using
u8 might seemingly be the only option.
However, we could still wrap that
u8 in a
; const ACTIVE: UserStatus = UserStatus; const INACTIVE: UserStatus = UserStatus; const SUSPENDED: UserStatus = UserStatus; const DELETED: UserStatus = UserStatus; let user = User ;
This way, we can still use
u8 to represent state, but we can
now also put the type system to work (a common pattern in idiomatic Rust). For
example, we can define methods on
And we can even define a constructor that validates the input:
It's still not ideal, however! Not even if you interface with C code, as we will see in a bit. But first, let's look at the recommended way to represent state in Rust.
Use Enums Instead!
Enums are a great way to model state inside your domain. They allow you to express your intent in a very concise way.
We can plug this enum into our
But that's not all; in Rust, enums are much more powerful than in many other languages. For example, we can add data to our enum variants:
We can even represent state transitions:
Look how much ground we've covered with just a few lines of code! We can extend the application with confidence, knowing that we can't accidentally delete a user twice or re-activate a deleted user. Illegal state transitions are now impossible!
Using Enums to Interact with C Code
Earlier, I promised that you can still use enums, even if you have to interact with C code.
Suppose you have a C library with a user status type (I've omitted the other fields for brevity).
typedef struct User; User *;
You can write a Rust enum to represent the status:
#[repr(u8)] attribute? It tells the compiler to represent this
enum as an unsigned 8-bit integer. This is critical for compatibility with the C
Now, let's wrap the C function in a safe Rust wrapper:
The Rust code now communicates with the C code using a rich enum type, allowing for more expressive and type-safe code.
If you want, you can play around with the code on the Rust playground.
Enums in Rust are more powerful than in most other languages. They can be used to elegantly represent state transitions — even across language boundaries.
You should consider using enums whenever you need to represent a set of possible values, like when representing the state of an object.