Here is a naive idea inspired by the optics (lens and prisms) in bidirectional transformation. Given the view types like lens that provides partial view and update for product types, and pattern types like prisms that provides partial view and update for sum types, the idea is to combine them together: the optics type that provides partial view and update for both product types and sum types.
Here is the syntax sketch.
struct Foo {
x: i32,
y: Bar,
}
enum Bar {
A,
B(i32),
C {
p: i32,
q: i32,
}
}
fn foo(
// lens that views and updates x of Foo
a: &mut optics_type!(Foo is .{ x, .. }),
// prisms that views and updates Bar::B(_) of lens y of Foo
b: &mut optics_type!(Foo is .{ y: Bar::B(_), .. }),
) {
*a.x += 1;
// `b.y` is guaranteed to be `Bar::B` so here it is exhaustive.
let Bar::B(ref mut b) = b.y;
*b += 1;
}
fn main() {
let mut foo = Foo { x: 0, y: Bar::B(0) };
foo(&mut foo, &mut foo);
}
With path-sensitive type checks (like that where we check if a field of a given type is moved), it is even possible to introduce prism types that can change the discriminant of an enum (but only allowed to use in function parameters).
// After calling this function, the discriminant of `c` is guaranteed
// to be changed from `B` to `C`.
fn bar(c: &mut optics_type!(Bar is B(_) => C { .. })) {
let Bar::B(b) = c;
*c = Bar::C { p: b, q: b };
}