Crate bypass

Source
Expand description

§Bypass: Thread-local dynamic key-value store

It is sometimes convenient to pass certain values by using an implicit environment. This crate implements such an environment. We “bypass” the standard way of passing values through arguments and return values, and instead store our values in a thread-local key-value store.

This approach effectively mimics the concept known as “dynamic variables” in other programming languages.

§Examples

// Define a thread-local scope.
bypass::scope!(static MY_SCOPE);

MY_SCOPE.scope(|| {
    MY_SCOPE.insert("some name", 123);
    some_function();
});

fn some_function() {
    let item: i32 = MY_SCOPE.get("some name");
    println!("Fetched 'some name': {}", item);
}

This also allows us to pass data from some_function to the caller by calling insert inside some_function followed by calling get or remove after the function call completes.

§Scoping

A scope constrains the extent of inserts.

The following example shows its usage. If we did not have the inner scope surrounding the function call, then this code would panic on the second iteration of the for loop because it would cause a duplicate key to exist.

bypass::scope!(static MAIN);

// Initial scope.
MAIN.scope(|| {
    for _ in 0..10 {
        // Second scope.
        MAIN.scope(|| function());
    }
});

fn function() {
    // Only lives for the duration of the second scope.
    MAIN.insert("x", 123);

    // Further calls that use "x"
    // ...
}

Inside a scope, performing get, modify, or remove will first search the current scope for a matching key. If not found, search the parent. If not found, search the parent’s parent, and so on.

§Lifting

Lifting a key inside a scope means to defer all its core operations to the parent scope, if any such parent exists. Otherwise the current scope is used.

bypass::scope!(static MAIN);

MAIN.scope(|| {
    MAIN.scope(|| {
        MAIN.lift("x");
        MAIN.insert("x", 123);
    });

    let value: i32 = MAIN.get("x");
    println!("x={}", value);
});

The above would panic on get if the lift were to be removed, since the innermost scope would capture the insert which the outermost scope can’t access.

§Translations

Lifts allow you to translate a key. This can be useful when two pieces of code need to share some data but internally use different keys, so you wrap one piece of code in a scope that lifts that key into another key.

bypass::scope!(static MAIN);

MAIN.scope(|| {
    MAIN.scope(|| {
        // Translates any core operation on "x" into "y".
        MAIN.lift("x").to("y");
        MAIN.insert("x", 123);
    });

    let value: i32 = MAIN.get("y");
    println!("y={}", value);
});

§Intended usage

Bypass is not intended to supplant the normal argument-and-return method of passing data around. Its main purpose is to provide a way to avoid tramp data, i.e. data that is only passed through a significant amount of functions without being used directly.

Passing such data along through every intermediary may be cumbersome and noisy. Bypass serves as an alternative mainly for such cases. Especially during the exploratory phase of programming, we may want to experiment with just making some value available somewhere else. Having to pass said value through many places can consume significant time. Bypass can be used instead to quickly get a prototype up and running.

Similarly, we can use bypass to “return” data from a deeply nested call where we are not interested in returning it through every stack frame manually.

As such, this library explicitly does not provide methods like contains, and panics as soon as something is not as expected.

§Logging

Keys can be either &'static str or a String. When strings are constructed (e.g. via format!), it may be hard to figure out where a key is inserted in a codebase. To debug this library you can set a logger via debug.

If no logger is set then the default logger will be used that prints all operations to stderr. The default logger prints the code location of each get and remove, and at which location that data was inserted at.

The output per operation looks like the following.

bypass: get "a" | tests/test.rs:206:28 <--- tests/test.rs:193:11
|         |  |      |                       |
| Library |  | Key  | Called from           | Inserted at
          |
          | Operation type

§Dropping

Upon exiting a scope, values that are part of that scope will be dropped in lexicographic order of their associated keys.

§Core Operations

Core operations are insert, get, modify, and remove.

Macros§

scope
Creates a scope.

Structs§

Lift
Allows setting a translation on a lift.
Log
Contains information used in logging.
Scope
Named scope created using bypass::scope.

Enums§

Operation
Describes the operation that is being performed.