Artichoke is a Cargo workspace and uses separate crates for implementing the data structures that back Ruby Core APIs.
Splitting core data structures into their own crates helps me ensure they are high quality. For example, crates fail CI if they have missing documentation. Using a separate crate for each data structure also makes it easier to have multiple implementations of the API.
Having multiple implementations of core data structures allows Artichoke to be built with different use cases in mind. For example, Artichoke distributes a
ruby CLI frontend that should allow users to interact with the host system; but Artichoke also aims to support embedding use cases which may wish to limit the ways the interpreter may interact with the host system.
VISION.md expounds on Artichoke's design and goals.
spinoso-array is the crate that implements the contiguous buffer type that backs Ruby
Array. Using conditional compilation with cargo features,
spinoso-array by default exposes two concrete buffer types:
Array, which is backed by a Rust
SmallArray, which is backed by a
spinoso-array does not have a trait that unifies these two types to ensure they can be used interchangeably. Instead, these structs implement the exact same API. Flipping between implementations is as simple as changing an import:
use spinoso_array::Array as SpinosoArray; // or use spinoso_array::SmallArray as SpinosoArray;
Another data structure that implements the multiple backend pattern is Artichoke's access to environment variables.
spinoso-env implements multiple types that expose an identical API for accessing the environment:
Systemis a wrapper around
std::envand gives Ruby code access to the platform environment variables via native APIs.
Memoryis a wrapper around a
HashMap, which allows Ruby code to have a functional
ENVin environments where mutable access to the host system's environment is undesirable (if embedding Artichoke in another application) or unavailable (if building for e.g. the
The Artichoke VM is configurable at compile-time to select the
[features] # Enable resolving environment variables with the `ENV` core object. core-env = ["spinoso-env"] # Enable resolving environment variables with the `ENV` core object using native # OS APIs. This feature replaces the in-memory backend with `std::env`. core-env-system = ["core-env", "spinoso-env/system-env"]
The code that wires up the environment backend indirects the concrete type with a conditionally-compiled type alias and requires no other code changes:
type Backend = spinoso_env::Memory; type Backend = spinoso_env::System;
Source-compatible data structure implementations have the following nice properties: