Explict Type Safety¶
Integer Types Are Ambiguous¶
Using integers as parameters and return types obfuscates code
Conversions happen automatically, without any notice by the compiler
Worse: their semantics is not always clear
size_t
helps to a certain extent
Even more worse:
Mixing integers with different semantics
Changing semantics ⟶ no help by compiler
Example: error handling …
Example: Ambiguous Error Schemes (1)¶
Returns a “signed size type”: negative on error, size written otherwise (Unix tradition: waste half of the domain for an occasional -1):
ssize_t send_frame(
struct protocol_engine *eng,
const struct frame *f);
Always returns a valid sum:
int sum(const int *begin, const int *end);
Example: Ambiguous Error Schemes (2)¶
Automatic Conversion Massacre
unsigned int send_sum(
struct protocol_engine *engine,
const int *begin, const int *end)
{
struct frame f;
int retval = sum(begin, end);
f.type = INT32;
f.v_int32 = retval;
retval = send_frame(engine, &f);
return retval;
}
Example: Ambiguous Error Schemes (3)¶
What are we trying to accomplish?
int sum()
: ok; sum of integers is an integerShould think of overflow (gosh)
ssize_t send_frame()
: ok, but uses weird Unix style error reporting.unsigned int send_sum()
: combines these in a spectacular way, and returns an application defined error number (0 for ok).
Imagine for a moment that there is one programmer who is able to code such crap …
Compiler happily converts between all these different integer types
⟶ Hell will break loose sooner or later
Artificial Integer Type Safety¶
Passing struct
By-Value
struct point
{
int x, y;
};
struct point addpoints(struct point lhs, struct point rhs);
struct
assignment only possible on equally typed values
Mixing impossible
Why not wrap our integer error codes in
struct
s of adequate type?
Error Schemes, Revisited¶
What was our problem?
Unix system calls have that weird “-1 on failure, examine global
errno
variable if so” schemeValid
errno
errors are always > 0
Application-defined
unsigned int
errors otherwiseMixing is prevented only by coding very carefully
Proposed solution: two dedicated error types …
struct unix_error
, encapsulating a Unix errorstruct app_error
, encapsulating the application’s own error values
Error Schemes: Encapsulating Unix Details¶
Sketch: Definition of unix_error
struct unix_error
{
int errno;
};
static inline struct unix_error unix_error_create(int errno)
{
struct unix_error e;
e.errno = errno;
return e;
}
static inline int unix_error_ok(struct unix_error e)
{
return e.errno == 0;
}
Error Schemes: Using Encapsulated Stuff¶
Sketch: Usage of Type Safe Errors
struct app_error send_sum(
struct protocol_engine *engine,
const int *begin, const int *end)
{
struct unix_error uerr;
...
uerr = send_frame(engine, &f);
if (!unix_error_ok(uerr))
return app_error_create(APP_OS_ERROR);
...
}
Wrap-Up: Artificial Type Safety¶
Good news:
It is possible to write entirely type safe code in C
Using the right measures (inlining, small structs), no performance impact
Greatly enhances maintainability
Bad news:
A lot of explicit typing
C++ can do the same with overloading and much less typing