arsd.core

note: the api and behavior of this module is not externally stable at this time. See the documentation on specific functions for details.

Shared core functionality including exception helpers, library loader, event loop, and possibly more. Maybe command line processor and uda helper and some basic shared annotation types.

I'll probably move the url, websocket, and ssl stuff in here too as they are often shared. Maybe a small internationalization helper type (a hook for external implementation) and COM helpers too. I might move the process helpers out to their own module - even things in here are not considered stable to library users at this time!

If you use this directly outside the arsd library despite its current instability caveats, you might consider using static import since names in here are likely to clash with Phobos if you use them together. static import will let you easily disambiguate and avoid name conflict errors if I add more here. Some names even clash deliberately to remind me to avoid some antipatterns inside the arsd modules!

Contributor notes

arsd.core should be focused on things that enable interoperability primarily and secondarily increased code quality between other, otherwise independent arsd modules. As a foundational library, it is not permitted to import anything outside the druntime core namespace, except in templates and examples not normally compiled in. This keeps it independent and avoids transitive dependency spillover to end users while also keeping compile speeds fast. To help keep builds snappy, also avoid significant use of ctfe inside this module.

On my linux computer, dmd -unittest -main core.d takes about a quarter second to run. We do not want this to grow.

@safe compatibility is ok when it isn't too big of a hassle. @nogc is a non-goal. I might accept it on some of the trivial functions but if it means changing the logic in any way to support, you will need a compelling argument to justify it. The arsd libs are supposed to be reliable and easy to use. That said, of course, don't be unnecessarily wasteful - if you can easily provide a reliable and easy to use way to let advanced users do their thing without hurting the other cases, let's discuss it.

If functionality is not needed by multiple existing arsd modules, consider adding a new module instead of adding it to the core.

Unittests should generally be hidden behind a special version guard so they don't interfere with end user tests.

History

Added March 2023 (dub v11.0). Several functions were migrated in here at that time, noted individually. Members without a note were added with the module.
union floathack

Types 70

aliastypeCast = castTo
aliasNonOverflowingInt = NonOverflowingIntBase!(int.min, int.max)

Does math as a 64 bit number, but saturates at int.min and int.max when converting back to a 32 bit int.

History

Added January 1, 2025

ditto

structNonOverflowingIntBase(int min, int max)

ditto

Fields
private long value
Constructors
this(long v)
structstringz

A wrapper around a const(char)* to indicate that it is a zero-terminated C string.

Fields
private const(char) * raw
Methods
const(char) * ptr() constReturns the original raw pointer back out.
const(char)[] borrow() const @systemBorrows a slice of the pointer up to (but not including) the zero terminator.
Constructors
this(const(char) * raw)Wraps the given pointer in the struct. Note that it retains a copy of the pointer.

A packed date/time/datetime representation added for use with LimitedVariant.

You should probably not use this much directly, it is mostly an internal storage representation.

Fields
private ulong packedData
ubyte[12] daysInMonthTable
Methods
string toString() const
PackedDateTime withDate(int year, int month, int day) staticConstruction helpers
PackedDateTime withTime(int hours, int minutes, int seconds, int fractionalSeconds = 0) staticditto
PackedDateTime withDateAndTime(int year, int month, int day = 1, int hours = 0, int minutes = 0, int seconds = 0, int fractionalSeconds = 0) staticditto
PackedDateTime lastDayOfMonth(int year, int month) staticditto
bool isLeapYear(int year) static
int daysInMonth(int year, int month) static
int daysInYear(int year) static
void setTime(int hours, int minutes, int seconds, int fractionalSeconds = 0)Sets the whole date and time portions in one function call.
void setDate(int year, int month, int day)ditto
void clearTime()ditto
void clearDate()ditto
int fractionalSeconds() const
void fractionalSeconds(int a)ditto
int seconds() constditto
void seconds(int a)ditto
int minutes() constditto
void minutes(int a)ditto
int hours() constditto
void hours(int a)ditto
int day() constditto
void day(int a)ditto
int month() constditto
void month(int a)ditto
int year() constditto
void year(int a)ditto
bool hasTime() constditto
void hasTime(bool a)ditto
bool hasDate() constditto
void hasDate(bool a)ditto
private void setWithMask(int a, int bitOffset, int bitCount)
private int getFromMask(int bitOffset, int bitCount) const
int dayOfWeek() constReturns the day of week for the date portion.
long opCmp(PackedDateTime rhs) const

A PackedInterval can be thought of as the difference between [PackedDateTime]s, similarly to how a [Duration] is a difference between [MonoTime]s or [SimplifiedUtcTimestamp]s.

The key speciality is in how it treats months and days separately. Months are not a consistent length, and neither are days when you consider daylight saving time. This thing assumes that if you add those, the month/day number will always increase, just the exact details since then might be truncated. (E.g., January 31st + 1 month = February 28/29 depending on leap year). If you multiply, the parts are done individually, so January 31st + 1 month * 2 = March 31st, despite + 1 month truncating to the shorter day in February.

Internally, this stores months and days as 16 bit signed shorts each, then the milliseconds is stored as a 32 bit signed int. It applies by first adding months, truncating days as needed, then adding days, then adding milliseconds.

If you iterate over intervals, be careful not to allow month truncation to change the result. (Jan 31st + 1 month) + 1 month will not actually give the same result as Jan 31st + 2 months. You want to add to the interval, then apply to the original date again, not to some accumulated date.

History

Added December 13, 2025
Fields
private ulong packedData
Methods
short months() constGetters and setters for the components
short days() constditto
int milliseconds() constditto
void months(int v)ditto
void days(int v)ditto
void milliseconds(int v)ditto
PackedInterval opBinary(string op : "*")(int iterations) const
Constructors
this(int months, int days = 0, int milliseconds = 0)

Basically a Phobos SysTime but standing alone as a simple 64 bit integer (but wrapped) for compatibility with LimitedVariant.

Fields
long timestamp
Methods
string toString() const
SimplifiedUtcTimestamp now() staticHistory: Added November 22, 2025
time_t toUnixTime() const
long stdTime() const
SimplifiedUtcTimestamp opBinary(string op : "+")(Duration d) const
Constructors
this(long hnsecTimestamp)
structAdHocBuiltStruct(string tag, string[] names = [], T...)

A little builder pattern helper that is meant for use by other library code.

History

Added October 31, 2025
Fields
T values
Methods
auto opDispatch(string name, Arg)(Arg value)
structiraw

Represents a generic raw element to be embedded in an interpolated sequence.

Use with caution, its exact meaning is dependent on the specific function being called, but it generally is meant to disable encoding protections the function normally provides.

History

Added October 31, 2025
Fields
string s
Constructors
this(string s)

A limited variant to hold just a few types. It is made for the use of packing a small amount of extra data into error messages and some transit across virtual function boundaries.

Note that empty strings and null values are indistinguishable unless you explicitly slice the end of some other existing string!

Fields
private const(void) * length
private const(ubyte) * ptr
Methods
Contains contains() constEach datum stored in the LimitedVariant has a tag associated with it.
bool containsNull() constditto
bool containsDecimal() constditto
bool containsInt() constditto
bool containsMonoTime() constditto
bool containsUtcTimestamp() constditto
bool containsDuration() constditto
bool containsDateTime() constditto
bool containsString() constditto
bool containsDouble() constditto
bool containsBytes() constditto
private void Throw() const
private bool isHighBitSet() const
const(char)[] getString() const returngetString gets a reference to the string stored internally, which may be a temporary. See [toString] to get a normal string representation or whatever is inside.
long getInt() constditto
double getDouble() constditto
const(ubyte)[] getBytes() constditto
Object getObject() constditto
MonoTime getMonoTime() constditto
Duration getDuration() constditto
string toString() const
Constructors
this(string s)Note for integral types that are not `int` and `long` (for example, `short` or `ubyte`), you might want to explicitly convert them to `int`.
this(const(char) * stringz)ditto
this(const(ubyte)[] b)ditto
this(long l, int base = 10)ditto
this(int i, int base = 10)ditto
this(bool i)ditto
this(double d)ditto
this(Object o)ditto
this(MonoTime a)ditto
this(Duration a)ditto
Nested Templates
Contains
private unionfloathack
Fields
void * e

A DynamicDecimal is a fixed-point object whose precision is dynamically typed.

It packs everything into a 64 bit value. It uses one bit for sign, three bits for precision, then the rest of them for the value. This means the precision (that is, the number of digits after the decimal) can be from 0 to 7, and there can be a total of 18 digits.

Numbers can be added and subtracted only if they have matching precision. They can be multiplied and divided only by whole numbers.

History

Added December 12, 2025.
Fields
private ulong storage
Methods
private bool isNegative()
int precision()
long value()
typeof(this) opBinary(string op)(typeof(this) rhs) if(op == "+" || op == "-")Some basic arithmetic operators are defined on this: +, -, *, and /, but only between numbers of the same precision. Note that division always returns the quotient and remainder together in one ret...
typeof(this) opBinary(string op)(int rhs) if(op == "*")ditto
DivisionResult opBinary(string op)(int rhs) if(op == "/")ditto
typeof(this) opUnary(string op : "-")()ditto
long opCmp(typeof(this) rhs)ditto
double toFloatingPoint()Converts to a floating point type. There's potentially a loss of precision here.
string toString(int minimumNumberOfDigitsLeftOfDecimal = 1) @system
Constructors
this(ulong storage)
this(long value, int precision)
Nested Templates
DivisionResultditto
aliasComparator = int function(scope const void *, scope const void *) @system

This is a dummy type to indicate the end of normal arguments and the beginning of the file/line inferred args. It is meant to ensure you don't accidentally send a string that is interpreted as a filename when it was meant to be a normal argument to the function and trigger the wrong overload.

structOwnedClass(Class)

Basically a scope class you can return from a function or embed in another aggregate.

Fields
ubyte[__traits(classInstanceSize, Class)] rawData
Methods
private void initializeRawData() @trusted
bool opCast(T : bool)() @trusted
Class rawInstance() return @trusted
Constructors
this(T t)
Destructors

Alternative for toStringz

History

Added March 18, 2023 (dub v11.0)

See Also

[OsCharzBuffer] is an alias to this on non-Windows platforms (added April 10, 2026)
Fields
private char[] buffer
private char[128] staticBuffer
Methods
size_t length()Length of the string, excluding the zero terminator.
char * ptr()
char[] slice()Returns the slice of the internal buffer, excluding the zero terminator (though there is one present right off the end of the slice). You must assume its lifetime is less than that of the CharzBuffer.
void copyInto(R)(ref R r)Copies it into a static array of chars
Constructors
this(in char[] data)Copies `data` into the CharzBuffer, allocating a new one if needed, and zero-terminates it.

ditto

Fields
private ubyte padTo
private char padWith
private ubyte radix
private char ten
private ubyte groupSize
private char separator
Methods
IntToStringArgs withPadding(int padTo, char padWith = '0')
IntToStringArgs withRadix(int radix, char ten = 'a')
IntToStringArgs withGroupSeparator(int groupSize, char separator = '_')
Fields
private ubyte padTo
private char padWith
private ubyte groupSize
private char separator
private ubyte minimumPrecision
private ubyte maximumPrecision
private bool useScientificNotation
Methods
FloatToStringArgs withPadding(int padTo, char padWith = '0')
FloatToStringArgs withGroupSeparator(int groupSize, char separator = '_')
FloatToStringArgs withPrecision(int minDigits, int maxDigits = 0)
structPresenceOf(alias a)

ditto

Fields
bool there
structFlexibleDelegate(DelegateType)

Declares a delegate property with several setters to allow for handlers that don't care about the arguments.

Throughout the arsd library, you will often see types of these to indicate that you can set listeners with or without arguments. If you care about the details of the callback event, you can set a delegate that declares them. And if you don't, you can set one that doesn't even declare them and it will be ignored.

classArsdExceptionBase : object.Exception

Base class representing my exceptions. You should almost never work with this directly, but you might catch it as a generic thing. Catch it before generic object.Exception or object.Throwable in any catch chains.

General guidelines for exceptions

The purpose of an exception is to cancel a task that has proven to be impossible and give the programmer enough information to use at a higher level to decide what to do about it.

Cancelling a task is accomplished with the throw keyword. The transmission of information to a higher level is done by the language runtime. The decision point is marked by the catch keyword. The part missing - the job of the Exception class you construct and throw - is to gather the information that will be useful at a later decision point.

It is thus important that you gather as much useful information as possible and keep it in a way that the code catching the exception can still interpret it when constructing an exception. Other concerns are secondary to this to this primary goal.

With this in mind, here's some guidelines for exception handling in arsd code.

Allocations and lifetimes

Don't get clever with exception allocations. You don't know what the catcher is going to do with an exception and you don't want the error handling scheme to introduce its own tricky bugs. Remember, an exception object's first job is to deliver useful information up the call chain in a way this code can use it. You don't know what this code is or what it is going to do.

Keep your memory management schemes simple and let the garbage collector do its job.

  • All thrown exceptions should be allocated with the new keyword.
  • Members inside the exception should be value types or have infinite lifetime (that is, be GC managed).
  • While this document is concerned with throwing, you might want to add additional information to an in-flight exception, and this is done by catching, so you need to know how that works too, and there is a global compiler switch that can change things, so even inside arsd we can't completely avoid its implications.

DIP1008's presence complicates things a bit on the catch side - if you catch an exception and return it from a function, remember to ex.refcount = ex.refcount + 1; so you don't introduce more use-after-free woes for those unfortunate souls.

Error strings

Strings can deliver useful information to people reading the message, but are often suboptimal for delivering useful information to other chunks of code. Remember, an exception's first job is to be caught by another block of code. Printing to users is a last resort; even if you want a user-readable error message, an exception is not the ideal way to deliver one since it is constructed in the guts of a failed task, without the higher level context of what the user was actually trying to do. User error messages ought to be made from information in the exception, combined with higher level knowledge. This is best done in a catch block, not a throw statement.

As such, I recommend that you:

  • Don't concatenate error strings at the throw site. Instead, pass the data you would have used to build the string as actual data to the constructor. This lets catchers see the original data without having to try to extract it from a string. For unique data, you will likely need a unique exception type. More on this in the next section.
  • Don't construct error strings in a constructor either, for the same reason. Pass the useful data up the call chain, as exception members, to the maximum extent possible. Exception: if you are passed some data with a temporary lifetime that is important enough to pass up the chain. You may .idup or to!string to preserve as much data as you can before it is lost, but still store it in a separate member of the Exception subclass object.
  • Do construct strings out of public members in [getAdditionalPrintableInformation]. When this is called, the user has requested as much relevant information as reasonable in string format. Still, avoid concatenation - it lets you pass as many key/value pairs as you like to the caller. They can concatenate as needed. However, note the words "public members" - everything you do in getAdditionalPrintableInformation ought to also be possible for code that caught your exception via your public methods and properties.

Subclasses

Any exception with unique data types should be a unique class. Whenever practical, this should be one you write and document at the top-level of a module. But I know we get lazy - me too - and this is why in standard D we'd often fall back to throw new Exception("some string " ~ some info). To help resist these urges, I offer some helper functions to use instead that better achieve the key goal of exceptions - passing structured data up a call chain - while still being convenient to write.

See: [ArsdException], [Win32Enforce]

Methods
void toString(scope void delegate(in char[]) sink) constThe toString method will print out several components:
string toString()ditto
void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) constUsers might like to see additional information with the exception. API consumers should pull this out of properties on your child class, but the parent class might not be able to deal with the arbi...
string printableExceptionName() constThis is the name of the exception class, suitable for printing. This should be static data (e.g. a string literal). Override it in subclasses.
void msg() @disabledeliberately hiding `Throwable.msg`. Use [message] and [toString] instead.
const(char)[] message() const
Constructors
this(string operation, string file = __FILE__, size_t line = __LINE__, Throwable next = null)Don't call this except from other exceptions; this is essentially an abstract class.
Fields
InvalidArgument[] invalidArguments
Methods
void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const
Constructors
this(InvalidArgument[] invalidArguments, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
this(string argumentName, string argumentDescription, LimitedVariant givenArgumentValue = LimitedVariant.init, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
this(string argumentName, string argumentDescription, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
Nested Templates
InvalidArgument

Base class for when you've requested a feature that is not available. It may not be available because it is possible, but not yet implemented, or it might be because it is impossible on your operating system.

Constructors
this(string featureName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null)

This means the feature could be done, but I haven't gotten around to implementing it yet. If you email me, I might be able to add it somewhat quickly and get back to you.

Constructors
this(string featureName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null)

This means the feature is not supported by your current operating system. You might be able to get it in an update, but you might just have to find an alternate way of doing things.

Constructors
this(string featureName, string file = __FILE__, size_t line = __LINE__, Throwable next = null)

A tagged union that holds an error code from system apis, meaning one from Windows GetLastError() or C's errno.

You construct it with SystemErrorCode(thing) and the overloaded constructor tags and stores it.

Fields
Type type
int code
Methods
bool wasSuccessful() constReturns if the code indicated success.
string toString() constConstructs a string containing both the code and the explanation string.
string codeAsString() constThe numeric code itself as a string.
string errorString() const @trustedA text explanation of the code. See [codeAsString] for a string representation of the numeric representation.
Constructors
this(int errno)C/unix error are typed as signed ints... Windows' errors are typed DWORD, aka unsigned...
this(uint win32)ditto
Nested Templates
Type
Fields
string name
Fields
SystemErrorCode errorCode
Methods
void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const
Constructors
this(string msg, int originalErrorNo, scope SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
this(string msg, SystemErrorCode code, SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null)

The low level use of this would look like throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()) but it is meant to be used from higher level things like [Win32Enforce].

History

Moved from simpledisplay.d to core.d in March 2023 (dub v11.0).

History

Moved from simpledisplay.d to core.d in March 2023 (dub v11.0).

You can also pass a handle to a specific thread, if you have one.

CurrentThreadThe callback should be only run by the same thread that set it.
UiThreadThe UI thread is a special one - it is the supervisor of the workers and the controller of gui and console handles. It is the first thread to call [arsd_core_init] actively running an event loop un...
AnyAvailableTaskRunnerThreadThe callback can be called from any available worker thread. It will be added to a global queue and the first thread to see it will run it.
AnyAvailableBlockingWorkerThreadThese are expected to run longer blocking, but independent operations. Think an individual function with no context.
BroadcastToAllThreadsThe callback will be duplicated across all threads known to the arsd.core event loop.
Fields
int function() getTimeout
private bool exitApplicationRequested
Methods
void run(scope bool delegate() until)Runs the event loop for this thread until the `until` delegate returns `true`.
void exitApplication() static
RunOnceResult runOnce(Duration timeout = Duration.max)Runs a single iteration of the event loop for this thread. It will return when the first thing happens, but that thing might be totally uninteresting to anyone, or it might trigger significant work...
UnregisterToken addDelegateOnLoopIteration(void delegate() dg, uint timingFlags)Adds a delegate to be called on each loop iteration, called based on the `timingFlags`.
UnregisterToken addDelegateOnLoopIteration(void function() dg, uint timingFlags)
Nested Templates
RunOnceResultReturns details from a call to [runOnce]. Use the named methods here for details, or it can be used in a `while` loop directly thanks to its `opCast` automatic conversion to `bool`.

The internal types that will be exposed through other api things.

AdHocThe event loop is being run temporarily and the thread doesn't promise to keep running it.
ExplicitThe event loop struct has been instantiated at top level. Its destructor will run when the function exits, which is only at the end of the entire block of work it is responsible for.
UiA specialization of `Explicit`, so all the same rules apply there, but this is specifically the event loop coming from simpledisplay or minigui. It will run for the duration of the UI's existence.
TaskRunnerA special event loop specifically for threads that listen to the task runner queue and handle I/O events from running tasks. Typically, a task runner runs cooperatively multitasked coroutines (so t...
HelperWorkerA special event loop specifically for threads that listen to the helper function request queue. Helper functions are expected to run independently for a somewhat long time (them blocking the thread...
Fields
void delegate() callback
void *[3] argsStore
private shared(int) refcount
private uint flags
Methods
void call()
void addref()
void release()
Constructors
this(void function() callback)
this(void delegate() callback, bool addRoot = true)
guess
ifVersionWindows
yes
no
structFilePath

This represents a file. Technically, file paths aren't actually strings (for example, on Linux, they need not be valid utf-8, while a D string is supposed to be), even though we almost always use them like that.

This type is meant to represent a filename / path. I might not keep it around.

Fields
private string path
Methods
bool isNull() const
bool opCast(T: bool)() const
string toString() const
FilePath makeAbsolute(FilePath base, TreatAsWindowsPath treatAsWindowsPath = TreatAsWindowsPath.guess) const
void foreachPathComponent(scope bool delegate(size_t index, in char[] component) dg) const
private FilePath removeExtraParts() const
bool isUNC() const
string driveName() const
string filename()Returns the path with the directory cut off.
string directoryName()Returns the path with the filename cut off.
string extension()Returns the file extension, if present, including the last dot.
string contentTypeFromFileExtension()Guesses the media (aka mime) content type from the file extension for this path.
Constructors
this(string path)

ditto

error
dir
other
structUriString

Specialization of string to indicate it is a URI. You should generally use arsd.uri.Uri instead of this in user code.

History

Added November 2, 2025
Fields
string uri
Methods
string toString()

Shared base code for web socket client in arsd.http2 and server in arsd.cgi.

History

Moved to arsd.core on November 2, 2025
Fields
int readyState_
ubyte[] receiveBuffer
size_t receiveBufferUsedLength
Config config
CONNECTINGSocket has been created. The connection is not yet open.
OPENThe connection is open and ready to communicate.
CLOSINGThe connection is in the process of closing.
CLOSEDThe connection is closed or couldn't be opened.
bool closeCalled
ubyte continuingType
ubyte[] continuingData
arsd.core.FlexibleDelegate!(void delegate(CloseEvent event)) oncloseThe `CloseEvent` you get references a temporary buffer that may be overwritten after your handler returns. If you want to keep it or the `event.reason` member, remember to `.idup` it.
void delegate() onerror
void delegate(in char[]) ontextmessage
void delegate(in ubyte[]) onbinarymessage
void delegate() onopen
bool isClient
Methods
int readyState()Returns one of [CONNECTING], [OPEN], [CLOSING], or [CLOSED].
void close()Closes the connection, sending a graceful teardown message to the other side. If you provide no arguments, it sends code 1000, normal closure. If you provide a code, you should also provide a short...
void close(int code, string reason)ditto
void close(int code)
void ping(in ubyte[] data = null)Sends a ping message to the server. This is done automatically by the library if you set a non-zero [Config.pingFrequency], but you can also send extra pings explicitly as well with this function. ...
void pong(in ubyte[] data = null)Sends a pong message to the server. This is normally done automatically in response to pings. Group: foundational
void send(in char[] textData)Sends a text message through the websocket. Group: foundational
void send(in ubyte[] binaryData)Sends a binary message through the websocket. Group: foundational
WebSocketFrame waitForNextMessage()Waits for and returns the next complete message on the socket.
bool waitForNextMessageWouldBlock()Tells if [waitForNextMessage] would block. Group: blocking_api
bool isMessageBuffered()Is there a message in the buffer already? If `true`, [waitForNextMessage] is guaranteed to return immediately. If `false`, check [isDataPending] as the next step. Group: blocking_api
void onmessage(void delegate(in char[]) dg)Group: browser_api
void onmessage(void delegate(in ubyte[]) dg)ditto
void llsend(ubyte[] d)
ubyte getRandomByte() @trusted
void llclose()
void llshutdown()
bool isDataPending(Duration timeout = 0.seconds)
Nested Templates
ConfigGroup: foundational
CloseEventArguments for the close event. The `code` and `reason` are provided from the close message on the websocket, if they are present. The spec says code 1000 indicates a normal, default reason close, b...
enumWebSocketOpcode : ubyte
continuation = 0
text = 1
binary = 2
close = 8
ping = 9
pong = 10
Fields
bool populated
bool fin
bool rsv1
bool rsv2
bool rsv3
bool masked
ubyte lengthIndicator
ulong realLength
ubyte[4] maskingKey
ubyte[] data
Methods
WebSocketFrame simpleMessage(WebSocketOpcode opcode, in void[] data) static
private void send(scope void delegate(ubyte[]) llsend, scope ubyte delegate() getRandomByte)
WebSocketFrame read(ref ubyte[] d) static
char[] textData()

Represents a generic async, waitable request.

Methods
void start()Actually issues the request, starting the operation.
void cancel()Cancels the request. This will cause `isComplete` to return true once the cancellation has been processed, but [AsyncOperationResponse.wasSuccessful] will return `false` (unless it completed before...
bool isComplete()Returns `true` if the operation has been completed. It may be completed successfully, cancelled, or have errored out - to check this, call [waitForCompletion] and check the members on the response ...
AsyncOperationResponse waitForCompletion()Waits until the request has completed - successfully or otherwise - and returns the response object. It will run an ad-hoc event loop that may call other callbacks while waiting.
Methods
bool wasSuccessful()Returns true if the request completed successfully, finishing what it was supposed to.

ditto

Fields
Constructors

Some functions that return arrays allow you to provide your own buffer. These are indicated in the type system as UserProvidedBuffer!Type, and you get to decide what you want to happen if the buffer is too small via the [OnOutOfSpace] parameter.

These are usually optional, since an empty user provided buffer with the default policy of reallocate will also work fine for whatever needs to be returned, thanks to the garbage collector taking care of it for you.

The API inside UserProvidedBuffer is all private to the arsd library implementation; your job is just to provide the buffer to it with provideBuffer or a constructor call and decide on your on-out-of-space policy.

properly size a buffer, I suggest looking at what covers about 80% of cases. Trying to cover everything often leads to wasted buffer space, and if you use a reallocate policy it can cover the rest. You might be surprised how far just two elements can go!

History

Added August 4, 2023 (dub v11.0)
Fields
private T[] buffer
private int actualLength
private OnOutOfSpace policy
Methods
bool append(T item)
T[] slice()
Constructors
this(scope T[] buffer, OnOutOfSpace policy = OnOutOfSpace.reallocate)

Possible policies for [UserProvidedBuffer]s that run out of space.

reallocatereallocate the buffer with the GC to make room
discarddiscard all contents that do not fit in your provided buffer
exceptionthrow an exception if there is data that would not fit in your provided buffer
Fields
private ubyte tail
private ubyte head
private bool overflowed
private void *[256] buffer
Methods
void queue(alias func, T...)(T args)
void process()
Nested Templates
Job

ditto

success
fileNotFound

PARTIALLY IMPLEMENTED / NOT STABLE

Fields
private FilePath path
private string globPattern
private bool recursive
private void delegate(FilePath filename, ChangeOperation op) changeHandler
Methods
private void triggered()
void dispose()
Constructors
this(FilePath directoryToWatch, string globPattern, bool recursive, void delegate(FilePath pathModified, ChangeOperation op) dg)Watches a directory and its contents. If the `globPattern` is `null`, it will not attempt to add child items but also will not filter it, meaning you will be left with platform-specific behavior.
Nested Templates
ChangeOperation

This starts up a local pipe. If it is already claimed, it just communicates with the existing one through the interface.

Fields
ubyte[] bufferWritten
SystemErrorCode errorCode
Methods
Constructors
this(SystemErrorCode errorCode, const(ubyte)[] bufferWritten)
Fields
ubyte[] bufferRead
SystemErrorCode errorCode
Methods
Constructors
this(SystemErrorCode errorCode, const(ubyte)[] bufferRead)
classTaskCancelledException : object.Exception
Constructors
Fields
private LoopIterationDelegate[] loopIterationDelegates
Methods
private void runLoopIterationDelegates(bool isAfter)
UnregisterToken addDelegateOnLoopIteration(void delegate() dg, uint timingFlags)
void unregisterDg(void delegate() dg)
void setTimeout()
void addFileOrDirectoryChangeListener(FilePath name, uint flags, bool recursive = false)
Nested Templates
LoopIterationDelegate
structSynchronizedCircularBuffer(T, size_t maxSize = 128)

This is primarily a helper for the event queues. It is public in the hope it might be useful, but subject to change without notice; I will treat breaking it the same as if it is private. (That said, it is a simple little utility that does its job, so it is unlikely to change much. The biggest change would probably be letting it grow and changing from inline to dynamic array.)

It is a fixed-size ring buffer that synchronizes on a given object you give it in the constructor.

After enqueuing something, you should probably set an event to notify the other threads. This is left as an exercise to you (or another wrapper).

Fields
private T[maxSize] ring
private int front
private int back
private SynchronizableObject synchronizedOn
Methods
bool isEmpty()Note the potential race condition between calling this and actually dequeuing something. You might want to acquire the lock on the object before calling this (nested synchronized things are allowed...
bool isFull()Note the potential race condition between calling this and actually queuing something.
private bool isFullUnsynchronized() nothrow const
bool enqueue(T what)If this returns true, you should signal listening threads (with an event or a semaphore, depending on how you dequeue it). If it returns false, the queue was full and your thing was NOT added. You ...
private T dequeueUnsynchronized() nothrow
T dequeue()If you are using a semaphore to signal, you can call this once for each count of it and you can do that separately from this call (though they should be paired).
T[] dequeueSeveral(return T[] buffer, scope void delegate() runInsideLockIfEmpty = null)Note that if you use a semaphore to signal waiting threads, you should probably not call this.
Constructors
this(SynchronizableObject synchronizedOn)The Object's monitor is used to synchronize the methods in here.
irrelevant
littleEndian
bigEndian

A class to help write a stream of binary data to some target.

NOT YET FUNCTIONAL

Fields
private ubyte[] buffer
private int bufferPosition
private bool isClosed_
Methods
void put(T)(T value, ByteOrder byteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__)
void put(T : E[], E)(T value, ByteOrder elementByteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__)ditto
void close()Performs a final flush() call, then marks the stream as closed, meaning no further data will be written to it.
void flush()Writes what is currently in the buffer to the target and waits for the target to accept it. Please note: if you are subclassing this to go to a different target
bool isClosed()Returns true if either you closed it or if the receiving end closed their side, indicating they don't want any more data.
private final writeOneByte(ubyte value)
Constructors
this(size_t bufferSize)
this(ubyte[] buffer)ditto

UNSTABLE, NOT FULLY IMPLEMENTED. DO NOT USE YET.

You might use this like:

auto proc = new ExternalProcess();
auto stdoutStream = new ReadableStream();

// to use a stream you can make one and have a task consume it
runTask({
	while(!stdoutStream.isClosed) {
		auto line = stdoutStream.get!string(e => e == '\n');
	}
});

// then make the process feed into the stream
proc.onStdoutAvailable = (got) {
	stdoutStream.feedData(got); // send it to the stream for processing
	stdout.rawWrite(got); // forward it through to our own thing
	// could also append it to a buffer to return it on complete
};
proc.start();

Please note that this does not currently and I have no plans as of this writing to add support for any kind of direct file descriptor passing. It always pipes them back to the parent for processing. If you don't want this, call the lower level functions yourself; the reason this class is here is to aid integration in the arsd.core event loop. Of course, I might change my mind on this.

Fields
private AsyncFile _stdin
private AsyncFile _stdout
private AsyncFile _stderr
private bool coredumped
private bool stopped
private bool completed
private int _status
void delegate(ExternalProcess) oncomplete
Methods
void start()
private void markComplete(int status)
private void markTerminatedBySignal(int signal, bool coredumped)
private void markStoppedBySignal(int signal)
private void markContinued()
void waitForChange()History: Added November 23, 2025
bool isComplete()
bool isStopped()History: Added November 23, 2025
bool leftCoreDump()History: Added November 23, 2025
int status()
classLoggerOf(T, size_t bufferSize = 16) : SynchronizableObject

DO NOT USE THIS YET IT IS NOT FUNCTIONAL NOR STABLE

The arsd.core logger works differently than many in that it works as a ring buffer of objects that are consumed (or missed; buffer overruns are possible) by a different thread instead of strings written to some file.

A library (or an application) defines a log source. They write to this source.

Applications then define log listeners, zero or more, which reads from various sources and does something with them.

Log calls, in this sense, are quite similar to asynchronous events that can be subscribed to by event handlers. The difference is events are generally not dropped - they might coalesce but are usually not just plain dropped in a buffer overrun - whereas logs can be. If the log consumer can't keep up, the details are just lost. The log producer will not wait for the consumer to catch up.

An application can also set a default subscriber which applies to all log objects throughout.

All log message objects must be capable of being converted to strings and to json.

Ad-hoc messages can be done with interpolated sequences.

Messages automatically get a timestamp. They can also have file/line and maybe even a call stack.

Examples

auto logger = new shared LoggerOf!GenericEmbeddableInterpolatedSequence;

mylogger.info(i"$this heartbeat");

History

Added May 27, 2024

Not actually implemented until February 6, 2025, when it changed from mixin template to class.

Fields
private LoggedMessage!T[bufferSize] ring
private ulong writeBufferPosition
private Mutex mutex
private Condition condition
private bool active
private int listenerCount
Methods
void close() sharedCloses the log channel and waits for all listeners to finish pending work before returning.
LogListenerController addListener(void delegate(LoggedMessage!T message, int missedMessages) dg) sharedExamples:
void log(LoggedMessage!T message) shared
void log(LogLevel level, T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) sharedditto
void info(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) sharedditto
void trace(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) sharedditto
void warn(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) sharedditto
void error(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) sharedditto
Constructors

ditto

Methods
void disconnect()Disconnects from the log producer as soon as possible, possibly leaving messages behind in the log buffer. Once disconnected, the log listener will terminate asynchronously and cannot be reused. Us...
void waitForCompletion()Waits for the listener to finish its pending work and terminate. You should call [disconnect] first to make it start to exit.

ditto

Fields
string moduleName
size_t line
structLoggedMessage(T)

ditto

Fields
LogLevel level
SourceLocation sourceLocation
Thread originatingThread
T message
Methods
string toString()

ditto

trace
info
warn
error
clasststring

Represents a translatable string.

This depends on interpolated expression sequences to be ergonomic to use and in most cases, a function that uses this should take it as tstring name...; a typesafe variadic (this is also why it is a class rather than a struct - D only supports this particular feature on classes).

You can use null as a tstring. You can also construct it with UFCS: i"foo".tstring.

The actual translation engine should be set on the application level.

It is possible to get all translatable string templates compiled into the application at runtime.

History

Added June 23, 2024
Fields
private shared(TranslationEngine) translationEngine_
TranslatableElement * first
private templateStringFor
Methods
string toString()
tstring opCall(Args...)(InterpolationHeader hdr, Args args, InterpolationFooter ftr) static
void translationEngine(shared TranslationEngine e) static
Constructors
this(InterpolationHeader hdr, Args args, InterpolationFooter ftr)For a case where there is no plural specialization.
Nested Templates
TranslatableElement
tstringTemplateProcessor

ditto

Methods
void handleTemplate(tstring.TranslatableElement t) sharedIf the translation engine has been set early in the module construction process (which it should be!)
Fields
Element[8] seq
Methods
string toString()
Nested Templates
Element
aliaswritelnStdOut = writeln

ditto

structValueWithFormattingArgs(T : double)
Fields
double value
Methods
ValueWithFormattingArgs opBinary(string op)(double value)
ValueWithFormattingArgs opOpAssign(string op)(double value)
structValueWithFormattingArgs(T : long)
Fields
long value

Functions 68

fnT castTo(T, S)(auto ref S v) refCasts value `v` to type `T`.
fnT reinterpretCast(T, V)(return ref V value) ref @systemTreats the memory of one variable as if it is the type of another variable.
fnbool isSliceOf(T1, T2)(scope const(T1)[] needle, scope const(T2)[] haystack) @trusted pure nothrow @nogcDetermines whether `needle` is a slice of `haystack`.
fnint countOfBitsSet(ulong v)Counts the number of bits set to `1` in a value, using intrinsics when available.
fnvoid nonPhobosSort(T)(T[] obj, Comparator comparator) @trusted
fnvoid filenameStringSort(string[] names)Sorts the list of names according to how I like to sort filenames.
fnT[] mallocSlice(T)(size_t n)A trivial wrapper around C's malloc that creates a D slice. It multiples n by T.sizeof and returns the slice of the pointer from 0 to n.
fnchar[] mallocedStringz(in char[] original)Uses C's malloc to allocate a copy of `original` with an attached zero terminator. It may return a slice with a `null` pointer (but non-zero length!) if `malloc` fails and you are responsible for f...
fnint findIndexOfZero(in wchar[] str)
fnint findIndexOfZero(in char[] str)
fnchar[] intToString(long value, char[] buffer, IntToStringArgs args = IntToStringArgs.init)An int printing function that doesn't need to import Phobos. Can do some of the things std.conv.to and std.format.format do.
fnchar[] floatToString(double value, char[] buffer, FloatToStringArgs args = FloatToStringArgs.init)
fninout(char)[] stripInternal(return inout(char)[] s) nothrow @safe @nogc pureHistory: Moved from color.d to core.d in March 2023 (dub v11.0).
fninout(char)[] stripRightInternal(return inout(char)[] s) nothrow @safe @nogc pureditto
fnstring toStringInternal(T)(T t)Shortcut for converting some types to string without invoking Phobos (but it may as a last resort).
fnstring flagsToString(Flags)(ulong value)
fnsize_t encodeUtf8(out char[4] buf, dchar c) @safe pure
fnchar[] transcodeUtf(scope const wchar[] input, char[] buffer)If it fits in the provided buffer, it will use it, otherwise, it will reallocate as-needed with the append operator.
fnchar[] transcodeUtf(scope const dchar[] input, char[] buffer)ditto
fninout(char)[] transcodeUtf(inout(char)[] input)
private fnbool isValidDchar(dchar c) pure nothrow @safe @nogc
fnstring encodeUriComponent(string s)
fnstring decodeUriComponent(string s, bool translatePlusToSpace = false)
fnauto toDelegate(T)(T t)
fnStruct populateFromArgs(Struct, Args...)(Args args)ditto
fnsize_t firstDefaultParam(alias fn)()ditto
fnvoid arsd_core_init(int numberOfWorkers = 0)Initializes the arsd core event loop and creates its worker threads. You don't actually have to call this, since the first use of an arsd.core function that requires it will call it implicitly, but...
fnICoreEventLoop getThisThreadEventLoop(EventLoopType type = EventLoopType.AdHoc)Get the event loop associated with this thread
fninout(char)[] trimSlashesRight(inout(char)[] txt)
fnAsyncOperationRequest waitForFirstToComplete(AsyncOperationRequest[] requests...)It returns the request so you can identify it more easily. `request.waitForCompletion()` is guaranteed to return the response without any actual wait, since it is already complete when this functio...
fnAsTheyCompleteRange asTheyComplete(AsyncOperationRequest[] requests...)Waits for all the `requests` to complete, giving each one through the range interface as it completes.
fnvoid writeFile(string filename, const(void)[] contents)Reads or writes a file in one call. It might internally yield, but is generally blocking if it returns values. The callback ones depend on the implementation.
fnconst(ubyte[]) readBinaryFile(string filename)ditto
fnstring readTextFile(string filename, string fileEncoding = null)ditto
fnUserProvidedBuffer!T provideBuffer(T)(scope T[] buffer, OnOutOfSpace policy = OnOutOfSpace.reallocate)ditto
fnbool matchesFilePattern(scope const(char)[] name, scope const(char)[] pattern, char star = '*')This is currently a simplified glob where only the wildcard in the first or last position gets special treatment or a single in the middle.
fnint indexOf(scope const(char)[] haystack, scope const(char)[] needle)
fnint indexOf(scope const(ubyte)[] haystack, scope const(char)[] needle)
fnbool endsWith(scope const(char)[] haystack, scope const(char)[] needle)
fnbool startsWith(scope const(char)[] haystack, scope const(char)[] needle)
private fnint numberOfCpus()
fnbool postMessage(ThreadToRunIn destination, void delegate() code)
fnbool postMessage(ThreadToRunIn destination, Object message)
private fnchar[] asciiToUpper(scope const(char)[] s) pure
fnvoid logSwallowedException(Exception e)Makes note of an exception you catch and otherwise ignore.
private fnvoid appendToBuffer(ref char[] buffer, ref int pos, scope const(char)[] what)
private fnvoid appendToBuffer(ref char[] buffer, ref int pos, long what)
private fnvoid appendToBuffer(ref char[] buffer, ref int pos, long what, IntToStringArgs args)
private fnvoid appendToBuffer(ref char[] buffer, ref int pos, double what)
private fnvoid appendToBuffer(ref char[] buffer, ref int pos, double what, FloatToStringArgs args)
fnvoid dumpParamsImpl(T...)(string func, T args)Don't call this directly, use `mixin(dumpParams);` instead
fnvoid writeln(T...)(T t)A `writeln` (and friends) that actually works, at least for some basic types.
fnvoid writelnStderr(T...)(T t)ditto
fnvoid writeStdout(T...)(T t)ditto
fnvoid writeStderr(T...)(T t)ditto
fnValueWithFormattingArgs!double formatArgs(double value, int precision)
fnstring enumNameForValue(T)(T t)
private fnconst(char)[] writeGuts(T...)(scope char[] buffer, string prefix, string suffix, string argSeparator, bool printInterpolatedCode, bool quoteStrings, string function(scope char[] result) writer, T t)
private fnvoid writeIndividualArg(T)(ref char[] buffer, ref int pos, bool quoteStrings, T arg)
private fnstring makeString(scope char[] buffer) @safe
private fnstring actuallyWriteToStdout(scope char[] buffer) @safe
private fnstring actuallyWriteToStderr(scope char[] buffer) @safe
private fnstring actuallyWriteToStdHandle(int whichOne, scope char[] buffer) @trusted
fnstring getEnvironmentVariable(scope const(char)[] name)As the C function it calls, this is not thread safe.
fnvoid setEnvironmentVariable(scope const(char)[] name, scope const(char)[] value)As the C function it calls, this is not thread safe. Always overwrites existing values.

Variables 9

enumvarchar_invalid = '\xFF'
enumvarwchar_invalid = '\uFFFF'
enumvardchar_invalid = '\U0000FFFF'
private enumvarreplacementDchar = '\uFFFD'
varCleanupQueue cleanupQueue
private varint eventLoopRound = - 1
varvoid delegate(Throwable t) taskUncaughtException
private varshared(LoggerOf!GenericEmbeddableInterpolatedSequence) _commonLogger
enumvardumpParams = q{ { import arsd.core; arsd.core.dumpParamsImpl(__FUNCTION__, __traits(parameters)); } }

You can use mixin(dumpParams); to put out a debug print of your current function call w/ params.

Templates 5

tmplpopulateFromUdas(Struct, UDAs...)

This populates a struct from a list of values (or other expressions, but it only looks at the values) based on types of the members, with one exception: bool members.. maybe.

It is intended for collecting a record of relevant UDAs off a symbol in a single call like this:

struct Name {
	string n;
}

struct Validator {
	string regex;
}

struct FormInfo {
	Name name;
	Validator validator;
}

@Name("foo") @Validator(".*")
void foo() {}

auto info = populateFromUdas!(FormInfo, __traits(getAttributes, foo));
assert(info.name == Name("foo"));
assert(info.validator == Validator(".*"));

Note that instead of UDAs, you can also pass a variadic argument list and get the same result, but the function is populateFromArgs and you pass them as the runtime list to bypass "args cannot be evaluated at compile time" errors:

void foo(T...)(T t) {
	auto info = populateFromArgs!(FormInfo)(t);
	// assuming the call below
	assert(info.name == Name("foo"));
	assert(info.validator == Validator(".*"));
}

foo(Name("foo"), Validator(".*"));

The benefit of this over constructing the struct directly is that the arguments can be reordered or missing. Its value is diminished with named arguments in the language.

tmpldefaultParam(alias fn, size_t idx)

Given a function and a parameter index, returns a function that only takes that parameter and returns it. If called with zero arguments, it returns the default value, if it exists. If not, you'll get a compiler error.

Note that it is a function because function parameters in D can have function calls, including side effects, as a default argument.

firstDefaultParam returns the index of the first defaultParam for the given function fn that can be called without an argument.

On older compilers, firstDefaultParam may always be parameters.length, since defaultParam requires a relatively new language feature (__traits(parameters), introduced in upstream D 2.099.0, released March 6, 2022).

History

Added May 26, 2026
tmplArsdException(alias Type, DataTuple...)

This is a generic exception with attached arguments. It is used when I had to throw something but didn't want to write a new class.

You can catch an ArsdException to get its passed arguments out.

You can pass either a base class or a string as Type.

See the examples for how to use it.

Types
tmplErrnoEnforce(alias fn, alias errorValue = void)

Calls the C API function fn. If it returns an error value, it throws an [ErrnoApiException] (or subclass) after getting errno.

tmplWillFitInGeis(Args...)
Functions
int lengthRequired()