Pre-pre-RFC/Working Prototype: Borrow-Aware Automated Context Passing

Facets are not actually exploring the same problem space as this prototype because, as you mention, facets require interior immutability to work with mutable context elements. As I mentioned in an earlier comment, the context injection solution this proposal implements, meanwhile, has the ability to effectively inject mutable references to context elements where each elements' borrow status is tracked independently.

I have actually previously explored a solution to this problem where parameter passing is done explicitly while allowing each component to be individually borrow checked and the response was fairly cold. In my opinion, since this problem already requires a language feature to solve effectively, we might as well avoid any half-measures and eliminate the need for explicitly passing these context bundles entirely since that task really isn't that interesting to users. Indeed, the predecessor to this proposal seems to have stalled, not because users are uncomfortable with the concept of automated context passing, but because there were too many complex edge cases to solve. As far as I can tell, this solution manages to solve most of them. (perhaps @tmandry has a different perspective on this?)

It seems like you and @Vorpal are still a little bit confused about what this feature is supposed to do. Fundamentally, it is pure sugar for manually passing references through a chain of functions. For example, this program:

use autoken::*;

cap! {
    Cx1 = Vec<Foo>;
    Cx2 = Vec<Bar>;
    Cx3 = Vec<Baz>;
}

fn main() {
    cap! {
        Cx1: &mut my_vec_1,
        Cx2: &mut my_vec_2,
        Cx3: &mut my_vec_3,
    =>
        foo(my, params, here);
    }
}

fn foo(a: Random, b: Parameters, c: Here) {
    bar(other, params, here);
}

fn bar(a: More, b: Random, c: Parameters) {
    let my_value = &mut cap!(mut Cx1)[1];
    let my_other_value = baz(even, more, params);
    do_something_with(my_value);
    do_something_else_with(my_other_value);
}

fn baz<'a>(g: Wow, h: Even, i: More) -> &'a Cx3 {
    tie!('a => mut Cx3);

    cap!(mut Cx2).push(Bar { ... });
    &mut cap!(mut Cx3)[2]
}

Would desugar to:

fn main() {
    foo(&mut my_vec_1, &mut my_vec_2, &mut my_vec_3, my, params, here);
}

fn foo(cx_1: &mut Vec<Foo>, cx_2: &mut Vec<Bar>, cx_3: &mut Vec<Baz>, a: Random, b: Parameters, c: Here) {
    bar(cx_1, cx_2, cx_3, other, params, here);
}

fn bar(cx_1: &mut Vec<Foo>, cx_2: &mut Vec<Bar>, cx_3: &mut Vec<Baz>, a: More, b: Random, c: Parameters) {
    let my_value = &mut cx_1[1];
    let my_other_value = baz(cx_2, cx_3, even, more, params);
    do_something_with(my_value);
    do_something_else_with(my_other_value);
}

fn baz<'a>(cx_2: &mut Vec<Bar>, cx_3: &'a mut Vec<Baz>, g: Wow, h: Even, i: More) -> &'a Cx3 {
    cx_2.push(Bar { ... });
    &mut cx_3[2]
}

Note that at each element of the context gets its own parameter, allowing each element to be borrowed separately.

Although I implemented it with thread locals in my prototype, there is no need to do that in an actual implementation and, indeed, we might find it useful, for the sake of #[no_std] support, to pass this context like any other parameter under the hood.

1 Like