HAL2000's answer is good, still I will elaborate on it. As discussed in the comments, assert()
is only the correct way if the setter is expected to be always called with a number between 0
and 9
and failure to do so is considered a programming error.
If you want validation instead (only set the value when it's in the correct range, otherwise report an error to the calling code), you'd change the setter like this:
static inline int
digit_set(struct digit *d, unsigned int val)
{
if (val > 9) return -1;
d->value = val;
return 0;
}
You can pick the returned values freely, -1
for error and 0
for success is very widespread in C, so I picked this. You could also prefer 1
for success and 0
for error, which would make your return value directly usable in a boolean expression.
This whole idea has a little weakness: Nothing will ever prevent you from directly setting the value
member, bypassing your setter. Now, there's a solution to this as well, but it comes with costs. You can hide the definition of the struct
in a separate translation unit, therefore calling code doesn't ever see its internals. It could look like this:
digit.h:
#ifndef DIGIT_H
#define DIGIT_H
typedef struct Digit Digit;
Digit *Digit_create(int val);
int Digit_setVal(Digit *self, int val);
int Digit_val(const Digit *self);
void Digit_destroy(Digit *self);
#endif
dgit.c:
#include <stdlib.h>
#include "digit.h"
struct Digit {
int val;
};
Digit *Digit_create(int val)
{
if (val < 0 || val > 9) return 0;
Digit *self = malloc(sizeof *self);
if (!self) return 0;
self->val = val;
return self;
}
int Digit_setVal(Digit *self, int val)
{
if (val < 0 || val > 9) return -1;
self->val = val;
return 0;
}
int Digit_val(const Digit *self)
{
return self->val;
}
void Digit_destroy(Digit *self)
{
free(self);
}
The cost:
This way of information hiding unfortunately requires a separate translation unit and dynamic allocation of the objects, which is expensive. I personally wouldn't recommend it for such a simple thing. But if you're e.g. writing a library and want to enforce it can't be used in a wrong way, it's worth a thought.
This basically is OOP realized in C, and for more complex structures, it really gives some benefit (also in structuring your program).
In the example above, the "constructor" Digit_create()
makes sure to only ever construct valid objects. This is generally a good idea for robust code in more complex scenarios.
If you decide to implement something like this, make sure the deallocation function (here Digit_destroy()
) works the same way as free()
: It should silently ignore a null pointer. This is trivial here, because there's nothing else to do than calling free()
.