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
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.