about
04/28/2022
RustBites - Abstractions
Code Repository Code Examples Rust Bites Repo

Rust Bite - Abstractions

Software abstractions are simplified models of code

1.0 Introduction

Abstractions are simpified representations of complex entities, things that are composed of many detailed parts and processing. Large software systems may be composed of hundreds of thousands, even millions, of lines of source code. Much more than any developer can understand completely. In order to use and maintain these systems a developer needs to have some way of understanding their properties and expected operations. Most often we factor systems into components that are small enough to be well understood and visualize the complete system as couplings of these smaller components. Often the components themselves may be decomposed into fundamental parts. An abstraction consists of a set of components with specified responsibilities and usually one or more diagrams That illustrate ownership and usage relationships between components.

2. RustComm Abstraction

Figure 1. RustComm Abstractions
Figure 1. RustComm Testing
We will use code from the RustComm repository as an Example for this discussion. RustComm is a message-passing communication system based on Tcp Sockets provided by the Rust std::net library. Figure 1. is a Universal Modeling Language (UML) class diagram that displays the most important types and traits used in the RustComm implementation. The main players are Connector<M,P,L> and Listener<P,L>. Listener<P,L> listens for incoming connection requests, extablishes a connection, and processes received messages, returning a reply for each. P is a generic parameter representing one of a set of processing types that determine what the listener does with incoming messages, and how it replies. L is a generic parameter representing one of a set of loggers used to capture processing events. Connector<M,P,L> attempts to establish connections with a listener at a specified endpoint, ipaddress:port. If a connection is established it then sends messages and receives replies. The generic arguments M, P, and L represent sets of message, processing, and logging types. Each of them are constrained by traits, some of which are shown in Fig. 1. The traits Msg, Sndr<>, Rcvr<>, Process<M> and Logger each define method signatures that types with those traits are obligated to implement. Traits say nothing about the implementation of those methods, so each trait establishes a bound on the types that the library uses.
  • Msg defines methods that messages provide for managing their contents. The Msg trait was designed to support more than one style of message. RustComm uses fixed header size messages, but Msg supports other types as well.
  • Sndr<M> and Rcvr<M> define method signatures for sending and receiving messages. They are parameterized on a Message type M, so they can be tailored to the type of messages a library defines.
  • Process<M> declares methods for processing messages. Both Connector<M,P,L> and Listener<P,L> have process members which are different, but satisfy the same trait bounds.
  • Logger defines method signatures for writing to log streams in the console or files.
  • The generic parameters: M, P, and L are also bound by some of Rust's std traits, e.g., Debug, Clone, Send, Sync, and in some cases Copy, and 'static.
Connector<M,P,L> and Listener<P,L> use the TcpStream and TcpListener types to provide socet-based Tcp messaging infrastructure. Each of the four layers shown in Fig 1. have a specific role to play in RustComm's implementation:
  • The top, traits, layer provides component interfaces that are implementation agnostic. That means that Connector<M,P,L> and Listener<P,L> can easily adopt different messaging and message-passing designs without a lot of breakage, because most of their code uses the trait interfaces.
  • The second, generic parameter layer, supports compile-time substitution of specialized components that tailor RustComm behaviors to meet the needs of a specific design.
  • The third, custom type layer, holds the connector and listener user-defined types. They provide the framework that supports and uses the first two layers. It is this layer that using code works with to build communicating processes.
  • The bottom, std libary component, layer provides all of the low level system processing, greatly simplifying implementation of listener and connector.
You can access all of the code for RustComm in this Repository.

3. Epilogue

The combination of traits and generic types provides a powerful environment for building flexible systems that can adapt to changing requirements and be used to explore new design ideas. Adding selections from the well crafted Rust std libraries can lead to designs that are easy to use, understand and maintain, and modify for new uses. Highly recommended.
  Next Prev Pages Sections About Keys