2 releases

0.1.1 Feb 15, 2022
0.1.0 Oct 29, 2021

#1707 in Rust patterns

MIT license

8KB

haz

A thin abstraction over polymorphic environments.

github crates.io docs.rs build status

Motivation

Consider a scenario where we want to pass some data to a couple of functions.

Perhaps we've got a type Config representing our app's configuration which wraps a bunch of data (e.g. Host, Port, Verbosity, Restriction, and maybe a bunch of extra fields):

struct Config {
  host: Host,
  port: Port,
  verbosity: Verbosity,
  restriction: Restriction,
  // ...
}

We might want to pass this data around to a couple of functions which would then use the relevant fields to take some action:

fn do_something_with_host_port_verbosity(...) {
  // ...
}

// ...

We could pass a reference to the whole Config to each function:

fn do_something_with_host_port_verbosity(cfg: &Config) {
  //...
}

// ...

Perhaps not every function needs to know about every single field and we might want to avoid such an unnecessary coupling.

One way to go about it is to explicitly pass each field to each function that requires them:

fn do_something_with_host_port_verbosity(host: &Host, port: &Port, restriction: &Restriction) {
  // ...
}

// ...

However at usage site it might get tedious since we need to pass each field individually:

let cfg = read_config();
do_something_with_host_port_verbosity(&cfg.host, &cfg.port, &cfg.verbosity);

I can haz data?

The idea behind haz is to help in achieving both:

  • Don't unnecessarily pass data to functions that don't require access to it
  • Don't require passing each field individually

Everything floats around the thin trait Has<Component>:

trait Has<Component> {
    fn access(&self) -> &Component;
}

By implementing Has<Component> for some type Container, we're stating that Container can yield read-only access to Component.

Equipped with this trait and assuming we have implemented Has<Host>, Has<Port>, Has<Verbosity>, etc (maybe with impl_has_for_named_component) for Config, we may leverage it as:

fn do_something_with_host_port_verbosity<C>(cfg: &C)
where
  C: Has<Host> + Has<Port> + Has<Verbosity> {
  //...
}

// ...

We've managed to explicitly state exactly what we as do_something_with_host_port_verbosity require and hence will get access to that but nothing else.

At usage site, it would look like:

let cfg = read_config();
do_something_with_host_port_verbosity(&cfg);

We're simply passing a reference to Config and not each field individually.

In summary, we've managed to achieve both of ours goals:

  • Don't unnecessarily pass data to functions that don't require access to it
  • Don't require passing each field individually

No runtime deps