# Enums, typedef and const

> Source: https://learn-c.net/enums-typedef-const/
> Part of Learn C, free to read.

## enum

An enum names a set of related integer constants.

```c
typedef enum {
    STATUS_PENDING,      /* 0 */
    STATUS_SHIPPED,      /* 1 */
    STATUS_DELIVERED,    /* 2 */
    STATUS_CANCELLED,    /* 3 */
} order_status_t;

order_status_t s = STATUS_SHIPPED;
```

Values start at 0 and increment unless you set them:

```c
typedef enum {
    ERR_NONE       = 0,
    ERR_NOT_FOUND  = 404,
    ERR_CONFLICT   = 409,
    ERR_INTERNAL   = 500,
} http_error_t;
```

Why this beats a set of `#define`s: the constants have a type, they appear by name in a debugger, and the compiler can warn when a `switch` misses one.

```c
switch (status) {
case STATUS_PENDING:   return "pending";
case STATUS_SHIPPED:   return "shipped";
case STATUS_DELIVERED: return "delivered";
case STATUS_CANCELLED: return "cancelled";
}
```

Compile with `-Wswitch` (included in `-Wall`) and adding a new enum member turns every `switch` that does not handle it into a warning. That is the closest C gets to an exhaustiveness check, and it is worth building on: **omit the `default` case** in switches over an enum, or the compiler can no longer tell you anything.

Enums are also the idiomatic way to declare an integer constant:

```c
enum { MAX_CONNECTIONS = 256 };
char buffer[MAX_CONNECTIONS];       /* usable as an array size */
```

:::warn An enum is just an int
```c
order_status_t s = 42;              /* compiles. no error. */
s = STATUS_PENDING + 1;             /* also fine */
```
C enums carry no runtime guarantee that the value is one of the named ones. Validate at boundaries — when a value arrives from a file, a socket or a command line, range-check it before you `switch` on it.
:::

## typedef

`typedef` gives a type another name.

```c
typedef unsigned char byte_t;
typedef struct node node_t;

typedef struct {
    double x, y;
} point_t;

point_t p = { .x = 1.0, .y = 2.0 };     /* rather than: struct point p; */
```

Two conventions worth following:

- **Suffix with `_t`** so a reader knows the identifier is a type. (Strictly, POSIX reserves `_t` for itself; in practice most codebases use it anyway. `_type` is the cautious alternative.)
- **Do not typedef away a pointer.**

```c
typedef struct node *node_ptr_t;    /* hides the pointer — avoid */
node_ptr_t a, b;                    /* both pointers, but it does not read that way */
```

Hiding the `*` means readers cannot tell what needs freeing, what can be `NULL`, and what is passed by reference. The standard library's `FILE *` is the model: the struct is typedef'd, the pointer is written out.

### Self-referential structs

```c
typedef struct node {           /* the tag `node` is needed inside the struct */
    int value;
    struct node *next;
} node_t;
```

A struct cannot use its own typedef name before the typedef finishes, so self-referential types need the tag as well.

## const

C's only tool for expressing "this must not change", and the most under-used feature in the language.

```c
const int max = 100;
max = 200;                  /* error */
```

Where it earns its place is function parameters:

```c
size_t count_words(const char *text);       /* promises not to modify text */
void normalise(char *text);                 /* will modify it */
```

The signature now documents intent and the compiler enforces it. A caller can see at a glance which functions mutate their arguments — information that otherwise requires reading the body.

### Reading const with pointers

The rule: **`const` applies to what is on its left, unless there is nothing there, in which case it applies to the right.**

```c
const char *p;          /* pointer to const char — data is fixed, p can move */
char const *p;          /* identical to the above */
char *const p;          /* const pointer to char — p is fixed, data can change */
const char *const p;    /* both fixed */
```

```c
const char *p = "hello";
p[0] = 'H';             /* error — data is const */
p = "world";            /* fine — the pointer is not */

char *const q = buffer;
q[0] = 'H';             /* fine */
q = other;              /* error */
```

`const char *` is the one you write most: a function that reads a string should take it.

### const correctness pays off

```c
/* before: can this modify my buffer? no way to tell without reading it */
int parse_header(char *input, size_t len, header_t *out);

/* after: reads input, writes out. obvious from the signature. */
int parse_header(const char *input, size_t len, header_t *out);
```

Adding `const` to a large codebase is contagious — one `const` parameter forces the functions it calls to be `const` too — which is why it is far easier to start with it than to retrofit.

:::tip String literals are already const in spirit
```c
char *s = "hello";      /* legal C, and writing through s is undefined behaviour */
s[0] = 'H';             /* may segfault, may silently corrupt */

const char *s = "hello";  /* correct — the compiler now stops you */
```
Generated C produces the first form regularly. `-Wwrite-strings` turns it into a warning.
:::

## Exercise

```c
#include <stdio.h>

int main(void) {
    /* 1. Define a `log_level_t` enum: DEBUG, INFO, WARN, ERROR.
       2. Write `const char *level_name(log_level_t)` using a switch with NO
          default, so -Wswitch catches a missing case later.
       3. Write `int count_char(const char *s, char c)` — note the const.
       4. Define a `point_t` struct via typedef and a `translate` function
          taking a `point_t *` (mutable) and a `const point_t *` (offset).  */
    printf("start\n");
    return 0;
}
```

## Common questions

### `enum` or `#define` for constants?

`enum` for integers. The constants get a type, show up by name in a debugger, respect scope, and let `-Wswitch` find unhandled cases. A `#define` is untyped text substitution with none of those properties.

### Should I typedef every struct?

It is a style choice and both conventions are common. Typedefs read more cleanly; writing `struct foo` everywhere makes it obvious that the type is a struct. The one thing to avoid either way is typedefing a pointer, which hides information readers need.

### Why bother with `const` if it is only compile-time?

Because it is a checked contract. It documents which parameters a function mutates, catches accidental writes, and enables some optimisations. Its real value is on function signatures, where it tells a reader what happens without them reading the body.
