Crate kathy

Source
Expand description

§kathy

Const-evaluated, zero-cost, very simple keypath functionality for rust.

Requires nightly and env RUSTFLAGS="-Znext-solver=globally" to work and use correctly :)

§0 runtime cost

Let’s take this simply example of a struct that implements kathy::KeyPathIndexable (the trait that makes this all work), and modify it via IndexMut (which the Keyable macro implements for you):

use kathy::Keyable;
use std::ops::IndexMut;

#[derive(Keyable)]
struct Person {
	age: u16
};

fn main() {
	std::hint::black_box(modify(
		Person { age: 40 },
		Person::age,
		500
	));
}

#[inline(never)]
fn modify<KP, F>(person: &mut Person, path: KP, field: F)
where
	Person: IndexMut<KP, Output = F>
{
	person[path] = field;
}

Now, to see what this actually generates, let’s run it through cargo-show-asm to see what this generates:

$ RUSTFLAGS="-Znext-solver=globally" cargo +nightly asm demo::modify
.section .text.demo::modify,"ax",@progbits
	.p2align	2
	.type	demo::modify,@function
demo::modify:
	.cfi_startproc
	mov w8, #5
	strh w8, [x0, #32]
	ret

With no optimizations enabled, doing only the bare minimum to ensure nothing is inlined too aggressively, (and assuming this is the only monomorphization of the requested fn) we get an extremely simple, basically transparent, implementation of the function.

There is no runtime checking or processing - everything is completely transparent and exists only as types at runtime.

And this works at arbitrary nested depths as well, even including working with usize-based keypaths/indices. For example:

// create a keypath to the `people` field of the `Family` struct
let height_kp = Family::people
	// extend that keypath into the first item inside `people`
	.idx::<0>()
	// extend that kp into the `dimensions` field of the first person
	.kp::<"dimensions">()
	// and finally finish the keypath off by telling it to retrieve the height.
	.kp::<"height">();

§usage

The main building block of this crate is the Keyable derive macro - this implements std::ops::Index and std::ops::IndexMut traits for all types which it is used on (along with a few other things).

The specific types which Keyable-derived structs can be Indexed by, however, are unimportant. They’re increasingly annoying to name the more nested they get, and are the most likely part of this library to change from version to version.

The way to create these Index types, however, is by using the named helpers that are provided by the Keyable macro. For example, in the first example up above, a Person::age constant was generated by the macro, which was then used to index into a Person.

These keypaths can then be extended by two methods:

  1. fn kp<const FIELD: &'static str>() -> _, which takes no arguments and uses the single generic argument, a const &'static str, to create another, nested, keypath.
  2. fn idx<const I: usize>() -> _, which also takes no arguments ans uses the single generic argument, a const usize, to create the nested keypath.

Due to the way this API works, all information about which field or index is being accessed is encoded at the type level, and every KeyPath-adjacent type in kathy is 0-sized.

Structs§

Aggregator
A convenience struct that is used to store nested keypaths, normally as increasingly nested tuples. For example, a triply-nested keypath would be represented as an Aggregator<(T1, (T2, T3))>, where T3 is evaluated on the top-most item, then T2, then `T1.
KeyPath
A struct which stores a single field-based keypath. When nesting keypaths, many of these are stored in an Aggregator, in the order and structure defined by that struct’s docs.
UsizeKeyPath
The same as KeyPath, but for index-based keypaths as opposed to field-based.

Traits§

KeyPathIndexable
The trait that powers indexing via key-path. There are not separate traits for move-by-index, mut-ref-by-index, and ref-by-index. Instead, KeyPathIndexable is implemented for T, &T, and &mut T separately.
MapKeyPath
A convenience trait for Iterators
TypeEquals
This was stolen from the type_equals crate and then extended as described in my article on const generic matrices. It is used in the type signature for Index and IndexMut as generated by the Keyable macro.

Derive Macros§

Keyable
Please see the main page of kathy’s docs to learn how to use this.