1.5. WHY? 13
components RoutingLayerC and PacketLayerC are completely decoupled, and are only bound together when wired.
1.5 Why?
Having a global namespace requires C, C++, and Java to reply on dynamic composition. Referencing a function
requires referencing a unique name in the global namespace, so code uses a level of indirection — function pointers
or factories — in order to decouple one implementation from another.
nesC takes a different approach. First, code is broken up into components, discrete units of functionality. A com-
ponent can only reference variables from its own, local namespace. A component can’t name a variable in another
component. However, a component can declare that it uses a function defined by another component. It can also
declare that it provides a function that another component can call. Building a nesC program involves writing com-
ponents and wiring users to providers. Because this composition occurs at compile-time, it doesn’t require runtime
allocation or storing function pointers in RAM. Additionally, since a program does not have these levels of indirection,
the nesC compiler knows the complete call graph. Before wiring, a component could be calling any other component,
and so is completely decoupled from whom it calls. However, in an application, the cal l is wired to a specific endpoint,
and so the nesC compiler can optimize across the call boundary. In practice, nesC often inlines a call that crosses five
or six components into a flat instruction stream with no function calls.
TinyOS and nesC can take this approach because, unlike end-user computers, which need to be able to dynamically
load new programs in response to user input, sensor networks are composed of embedded computers, which have well-
defined and tightly specified uses. While these might evolve over time, this evolution is very slow in comparison to
how often a PC loads new programs. There’s an additional side to embedment which motivates using static, rather
than dynamic approaches. Because traditional computers usually have some sort of user interaction, faults are more
easily detected. If the server is acting funny, you notice, and reboot it. If your word processor is slow, you close it
and reopen it. The same is not true for embedded systems, which operate, for the most part, unattended. For example,
consider traditional computers that have roles resembling embedded systems, such as a dedicated mail server. If a
logging failure causes the mail server to start losing some mail messages, it might — and often does — go unnoticed
for a long time.
At the level of programming actual behavior or algorithms, nesC is usually quite accessible if you know one
of the above languages. It is, after all, a C dialect. A component can only reference functions in its specification
and only reference variables declared within it, but this usually isn’t too hard to get a handle on. Understandably,
the part that most people new to the language find most challenging is wiring, as there’s no clear analogue in other
system languages. Composing nesC components into working, flexible, and reusable systems requires learning a new
language. While the language itself is very simple (it only really has two operators), the considerations are foreign to
most programmers who are used to global namespaces. Therefore, while this book does go over implementations, it
is mostly dedicated to wiring and composition, as these are what most nesC programmers find challenging.