As input ranges serve as generators or iterators over data, output ranges serve as sinks for data. Output ranges may print data, perform one-way algorithms (such as message digests or hashes), collect data, or whatever else you can achieve with its required put
method.
Here, we'll write an output range that prints the arrays it receives out in hexadecimal format. We'll write sixteen bytes per line, always ending with a new line.
Let's create an output range by executing the following steps:
put
method that takes the data we want to consume. As our hex dumper takes any kind of array and doesn't store it, we want in void[]
. The put
method will perform the printing we need.put
. To format our lines, we'll need to keep a count of how many items we've outputted so far.writeln
function in the destructor.isOutputRange
for the types it must accept.The code is as follows:
import std.range; import std.stdio; struct HexDumper { int outputted; void put(in void[] data) { foreach(b; cast(const ubyte[]) data) { writef("%02x ", b); outputted++; if(outputted == 16) { writeln(); outputted = 0; } } } ~this() { if(outputted) writeln(); } } static assert(isOutputRange!(HexDumper, ubyte)); static assert(isOutputRange!(HexDumper, char)); void main() { HexDumper output; output.put("Hello, world! In hex."); }
Run it and you'll see the following output:
48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 20 49 6e 20 68 65 78 2c
An output range is much simpler than an input range. It only has one required method: put
. An output range has no required invariant; you are not required to set anything up in the constructor.
The put
method doesn't have to do anything in particular. Its only requirement is to accept a single argument of the type the range can handle. Typically, the put
method does one of the following three things:
std.digest
provides output ranges of this style.std.array.Appender
is an output range of this style.Using an output range at the core consists only of creating the object and calling the put
method to feed the object with data. Some ranges, such as the std.digest
ranges, also require calls to methods such as start
and finish
to inform the object that you have finished the process so that it can perform finalization. In our case, we used the destructor to finalize.
A destructor is declared in a struct with the syntax ~this() { /* code */ }
. The destructor runs immediately when the struct object goes out of scope.
Destructors are used for clean up—freeing memory, releasing resources, or finalizing an operation, with the limitation that the object is no longer available.
The std.digest
cannot use a destructor as its finish
method changes the data and returns the final result. A destructor cannot return a value; by the time the destructor runs, the object is no longer available, so it would be impossible to get the results. Our HexDumper
method can output a new line in the destructor, as that does not need to return data.
The implementation of our put
method loops over the data and writes it in hexadecimal using std.stdio.writef
. The loop includes a cast to const ubyte[]
because the function accepts generic data in the form of in void[]
. Any array will implicitly cast to void[]
, allowing us to pass anything to the put
method, including strings and other arrays. However, a void[]
array is only usable if it is explicitly casted to something else first. We want to print out the individual bytes, so we casted to const ubyte[]
. Then, the foreach
loop will go one byte at a time.
Finally, the writef
function works like writefln
, but does not print an automatic new line at the end. The writef
function is D's version of the classic C printf
function. The first argument is a format string. Our format string, "%02x"
, means pad with zeroes to at least two digits wide, and then put out the argument in hexadecimal, followed by a space.
The std.range.put
function is a generic function that extends the ability of your output range to accept data. You only need to implement the core data type your function needs. Let other transformations such as accepting generic input ranges be handled by the standard library. An example of this can be seen in the Creating a digest recipe in Chapter 2, Phobos – The Standard Library.
writef
function's format string18.226.104.27