> [!warning] > This is a work-in-progress document. I'll publish an official draft to the [IRLO thread](https://internals.rust-lang.org/t/pre-pre-rfc-working-prototype-borrow-aware-automated-context-passing/21279) as soon as it's done. - Feature Name: `auto_context` - Start Date: ==(fill me in with today's date, YYYY-MM-DD)== - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary [summary]: #summary This RFC introduces a language feature which automatically forwards context parameters through a chain of function calls. Unlike user-land context-injection alternatives, context parameters are each individually borrow-checked, allowing users to borrow one context parameter while calling into functions borrowing a different context parameter. This RFC includes a novel mechanism called "realms" to help users communicate which set of contextual parameters a function is actually allowed to borrow, helping to tame the implicit nature of this context passing mechanism. # Motivation [motivation]: #motivation In today's Rust, it is currently impossible to devise a scheme for forwarding context parameters such that it exhibits the following three properties: 1. **Granular Borrow Checking:** A context-passing mechanism allows granular borrow checking if it enables each contextual parameter to be individually borrow-checked. That is, one should be allowed to borrow a context parameter `Foo` in one function while calling into another function borrowing a distinct context parameter `Bar`. 2. **Bounded Refactors:** A context-passing mechanism allows bounded refactors if introducing a dependency upon a given contextual parameter can be done *without* updating the chain of ancestor function calls leading up to the origin of that value. That is, if `foo` calls `bar` and `bar` calls `baz`, adding a dependency on a value `MyType` originating from the caller of `foo` should not require the user to update `foo`, `bar`, and `baz`'s signatures or bodies. 3. **Checked:** A context-passing mechanism is checked if dereferences of any contextual parameter is infallible at runtime. That is, the user should get neither a "context item missing" error, nor a "context item is already borrowed elsewhere" error at runtime if the program successfully compiles. Although it is quite feasible to achieve two of these properties simultaneously, achieving all three at the same time is not due to a fundamental limitation of the Rust borrow checker. If you pass each contextual parameter as its own function parameter, one achieves *granular borrow checking* which is properly *checked* but fails to achieve *bounded refactors* since gaining access to a given contextual parameter requires the signatures of all transitively calling functions to be updated. ```rust // === Before refactor === // fn foo(cx_1: &mut Cx1, cx_2: &Cx2) { foo(cx_1, cx_2); } fn bar(cx_1: &mut Cx1, cx_2: &Cx2) { bar(cx_1, cx_2); } fn baz(cx_1: &mut Cx1, cx_2: &Cx2) { cx_1.do_something(); cx_2.do_something_else(); } // === After refactor === // // All of these functions need to be updated... fn foo(cx_1: &mut Cx1, cx_2: &Cx2, cx_3: &mut Cx3) { foo(cx_1, cx_2, cx_3); } fn bar(cx_1: &mut Cx1, cx_2: &Cx2, cx_3: &mut Cx3) { bar(cx_1, cx_2, cx_3); } fn baz(cx_1: &mut Cx1, cx_2: &Cx2, cx_3: &mut Cx3) { cx_1.do_something(); cx_2.do_something_else(); // ...so that we can run this: cx_3.do_something_new(); } ``` If, instead, you pass the entire context as a single "context bundle" parameter, you gain *bounded refactors* but lose either *granular borrow checking* or *checked borrow-checking* since passing the bundle around mutably makes the mechanism non-granular and passing the bundle around immutably necessitates runtime borrow-check for the interior mutability that solution requires. This bundle implementation, for example, is checked but not granular: ```rust trait FooBundle: BarBundle { fn cx_1(&mut self) -> &mut Cx1; } trait BarBundle: BazBundle { fn cx_2(&mut self) -> &mut Cx2; } trait BazBundle { fn cx_3(&mut self) -> &mut Cx3; } fn foo(cx: &mut impl FooBundle) { for _val in cx.cx_1().iter_mut() { // ERROR: `cx` is already borrowed by the `for`-loop. bar(cx); } } fn bar(cx: &mut impl BarBundle) { for _val in cx.cx_2().iter_mut() { // ERROR: `cx` is already borrowed by the `for`-loop. baz(cx); } } fn baz(cx: &mut impl BazBundle) { cx.cx_3().do_something(); } ``` This bundle implementation, meanwhile, is granular but not checked: ```rust trait FooBundle: BarBundle { fn cx_1(&self) -> RefMut<'_, Cx1>; } trait BarBundle: BazBundle { fn cx_2(&self) -> RefMut<'_, Cx2>; } trait BazBundle { fn cx_3(&self) -> RefMut<'_, Cx3>; } fn foo(cx: &impl FooBundle) { // This borrow results in a runtime error much later in the body of `baz`. let _clueless = cx.cx_3(); for _val in cx.cx_1().iter_mut() { bar(cx); } } fn bar(cx: &impl BarBundle) { for _val in cx.cx_2().iter_mut() { baz(cx); } } fn baz(cx: &impl BazBundle) { cx.cx_3().do_something(); } ``` These properties are all quite valuable: - *Granular borrow checking* allows us to write code that otherwise wouldn't compile. This is especially true of code that relies upon iterators over contextual parameters to drive calls into deeper functions. - *Bounded refactors* allow us to experiment with and ultimately refactor our program without having to work through uninteresting churn. Additionally, bounded refactors remove the tradeoff between refactor ergonomics and the number of behaviors overloaded into each contextual element. This tradeoff exists since the churn required by unbounded refactors is proportional to the number of distinct contextual elements and this can encourage users to break the single responsibility principle for the sake of ergonomics. - *Checked* programs allow us to borrow context elements with confidence that the new borrow operation will not unexpectedly conflict with existing borrow operations potentially very far from the origin and perhaps only occasionally exercised at runtime. Additionally, relying on compile-time borrow checks rather than runtime borrow-checks allows the compiler to automatically bound the duration for which the borrow takes place rather than forcing the user to manually drop and reacquire their context guard for each function call which could potentially conflict with it. The lack of a context-injection mechanism which achieves all three of these properties simultaneously is unfortunate since plenty of patterns require granular exterior mutability to function and that makes those patterns impossible to effectively dependency inject. For example, users could implement copyable smart pointers to objects which can be safely mutably dereferenced and destroyed. This could be implemented, for example, through the [generational arena](https://docs.rs/generational-arena/latest/generational_arena/) pattern, which implements what is essentially a really efficient `HashMap` from numeric handle objects to object values. Since handles are just numbers, they can be freely copied and since they can index into their arena of origin using regular `HashMap` indexing rules, deletion and accesses are entirely compile-time checked. In essence, implementing this feature makes the borrow checker considerably more powerful without really changing any of its core analyses, allowing users to replace patterns which previously required interior mutability with a more natural exterior mutable implementation. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation Let's try out the feature! We begin by defining a **realm** for our module. A realm is just a set of **context parameters** and multiple realms can contain the same context parameter. Realms are used to signal to the user which context parameters a given function could potentially be interested in accessing and, therefore, it is customary to define a realm for each subsystem of your application. ```rust /// A subsystem containing common debugging tools such as the profiler. realm Debugging; ``` Realms can inherit the context parameters of one or more realms. Since all our subsystems do some sort of profiling, they'll all inherit the `Debugging` realm. Additionally, since a game application transitively works with context parameters from both the "world state" and "rendering" subsystems, it inherits both the `WorldState` and `Renderer` realms. The fact that the `Debugging` realm finds itself included transitively more than once is fine—the realm graph just has to be acyclic. ```rust /// The world state subsystem comprises all the objects describing what /// our hypothetical game world should look like. realm WorldState: Debugging; /// The renderer subsystem comprises all the objects necessary for drawing /// those objects to the screen. realm Renderer: Debugging; /// The application realm is the root-most realm for the entire application /// and all its subsystems. realm Application: WorldState, Renderer; ``` We can then define context parameters to populate these realms. Each context parameter belongs to exactly one base realm but can ultimately exist in multiple realms thanks to the aforementioned realm inheritance feature. ```rust ctx PROFILER in Debugging = Profiler; ctx ZOMBIE_ARENA in WorldState = Arena<Zombie>; ctx PLAYER_STATE in WorldState = PlayerState; ctx TEXTURE_ARENA in Renderer = Arena<Texture>; ctx SHOULD_QUIT_FLAG in Application = bool; ``` Now, let's bind values to these context items to values. These bindings take effect only for the scope of the `bind` expression and can be shadowed freely. ```rust fn tick_app(app: &mut App) { bind( PROFILER = &mut app.profiler, ZOMBIE_ARENA = &mut app.world_state.zombies, PLAYER_STATE = &mut app.world_state.player_state, TEXTURE_ARENA = &mut app.renderer.textures, SHOULD_QUIT_FLAG = &mut app.should_exit, ) { update_app(); render_app(); } if app.should_exit { std::process::exit(0); } } ``` Finally, let's write some functions that actually use this context. ```rust // This function uses context from the application realm and calls functions // from realms it inherits. fn update_app() use Application { update_zombies(); update_player(); if rand() < 0.00001 && cfg!(feature = "fault_injection") { // We can access the `ShouldQuitFlag` context parameter since it's part // of the `Application` realm and bound in the caller of `update_app`. *SHOULD_QUIT_FLAG = true; } } // This function only needs to touch the world state—not the renderer—so let's // chose this more limited realm for this function. fn update_zombies() use WorldState { // The `Debugging` realm was inherited by the `WorldState` realm so we can // access it. let _scope = PROFILER.scope("update zombies"); for zombie in ZOMBIE_ARENA.values_mut() { update_zombie(zombie); } } fn update_zombie(zombie: &mut Zombie) use WorldState { // We'll explain why this borrow of `PLAYER_STATE` works in a bit :) zombie.look_at(PLAYER_STATE.position); zombie.walk_forwards(zombie.speed); } fn update_player() use WorldState { // We can do all sorts of fun things here! // (fun player update code omitted) } ``` One import fact about realms to note from the example is that realms define which context elements a function is *permitted* to borrow—not which elements it *actually* borrows. Hence, this code: ```rust fn update_zombies() use WorldState { ... for zombie in ZOMBIE_ARENA.values_mut() { update_zombie(zombie); } } fn update_zombie(zombie: &mut Zombie) use WorldState { zombie.look_at(PLAYER_STATE.position); ... } ``` ...compiles despite the fact that `update_zombie`'s signature indicates that it *could* access `ZOMBIE_ARENA` using its access of the `WorldState` because it *doesn't actually use that permission*. If the permission were used, we'd see a borrow checker error instead. ```rust fn update_zombies() use WorldState { for zombie in ZOMBIE_ARENA.values_mut() { update_zombie(zombie); } } fn update_zombie(_zombie: &mut Zombie) use WorldState { // This does not compile because `ZOMBIE_ARENA` is borrowed // simultaneously in both the `for` loop in `update_zombies` // and the `update_zombie` method it calls in that loop. ZOMBIE_ARENA.clear(); } ``` In a similar vane, having access to a realm does not necessarily mean that every single contextual parameter of that realm is available for borrowing, nor does it guarantee that every contextual parameter is available mutably. For example, this compiles: ```rust realm MyRealm; ctx CX_1 in MyRealm = u32; ctx CX_2 in MyRealm = u32; fn main() { bind(CX_1 = &1) { foo(); } } fn foo() use MyRealm { println!("The magic number is {}", *CX_1); // This would not compile because, one of its callers, `main`, // only binds an immutable reference to `CX_1`—not a mutable one. // *CX_1 += 1; // This would not compile because, one of its callers, `main`, // does not bind `CX_2` whatsoever. // println!("The scientific number is {}", *CX_2); } ``` The permission rules enforced by `use` statements are watertight. It is impossible to downcast from a "smaller" realm to a "larger" realm not inherited by that weaker realm. ```rust realm SubRealm; realm SiblingRealm; realm ParentRealm: SubRealm, SiblingRealm; fn do_sub_stuff() use SubRealm { // Fails to compile since `SiblingRealm` is not inherited by `SubRealm`. do_sibling_stuff(); // Fails to compile since `ParentRealm` is not inherited by `SubRealm`. do_parent_stuff(); } fn do_sibling_stuff() use SiblingRealm { // (it doesn't even need to do anything with the realm!) } fn do_parent_stuff() use ParentRealm { // (...) } ``` Likewise, you can only use context parameters contained within your current function's realm (or a realm inherited by that realm). ```rust realm SubRealm: SubRealm; realm ParentRealm: SubRealm; ctx MY_PARENT_CX in ParentRealm = u32; ctx MY_SUB_CX in SubRealm = u32; fn do_sub_stuff() use SubRealm { // Denied because `SubRealm` does not contain `MY_PARENT_CAP`. *MY_PARENT_CAP += 1; } fn do_parent_stuff() use ParentRealm { // Allowed because `ParentRealm` inherits `SubRealm` and `SubRealm` // contains `MY_SUB_CX`. *MY_SUB_CX += 1; } ``` If your function is not in any realm, you know that it won't access any of the context from its callee, even if it rebinds the context. ```rust realm MyRealm; ctx MY_CX in MyRealm = u32; fn main() { let mut val = 0; bind(MY_CX = &mut val) { foo(); } assert_eq!(val, 1); } fn foo() use MyRealm { *MY_CX += 1; bar(); } fn bar() { // Denied under regular context access rules. // *MY_CX = 0xBADF00D; bind(MY_CX = &mut 0) { baz(); } } fn baz() use MyRealm { // Works because `MY_CX` is shadowed by the `bind` directive in `bar`. *MY_CX = 0xABADCAFE; } ``` Realm compatibility is determined by name—not by the set of contexts to which they permit access. This exists to ensure that realms which haven't been given their own context parameters yet don't accidentally unify with unrelated realms. ```rust realm MyRealm1; realm MyRealm2; realm CompositeRealm1: MyRealm1, MyRealm2; realm CompositeRealm2: MyRealm1, MyRealm2; fn foo() use CompositeRealm1 { // Does not compile because, although `CompositeRealm1` and `CompositeRealm2` // inherit the same realms, there is always the potential for a downstream crate // to extend either of the realms with additional context the other may not have. bar(); } fn bar() use CompositeRealm2 { // Same applies in this direction as well. foo(); } ``` Users can define realm aliases to provide useful short forms for unions of one or more realms. Context parameters cannot be placed into realm aliases. ```rust realm MyRealm1; realm MyRealm2; realm CompositeRealm1 = MyRealm1, MyRealm2; realm CompositeRealm2 = MyRealm1, MyRealm2; fn foo() use CompositeRealm1 { // Compiles because `CompositeRealm1` and `CompositeRealm2` expand to compatible // sets of realms. bar(); baz(); } fn bar() use CompositeRealm2 { // Same applies in this direction as well. foo(); baz(); } // Realm aliases can be inlined. fn baz() use MyRealm1, MyRealm2 { foo(); bar(); } // This line does not compile because realm aliases cannot be given context items of // their own. ctx MY_CTX in CompositeRealm = u32; ``` Realms cannot be extended with additional context outside of the crate in which they were defined. ```rust // === crate1 === // pub realm MyRealm; // Allowed. ctx MY_CTX in MyRealm = u32; // === crate2 === // use crate1::MyRealm; // Not allowed. ctx MY_FOREIGN_CTX in MyRealm = u32; // Instead, you should define your own realm inheriting the // upstream crate's realm and extend that instead. realm MyRealm2: MyRealm; ctx MY_FOREIGN_CTX in MyRealm2 = u32; ``` Realms serve as a useful mechanism for taming the implicitness of the automated context passing by succinctly indicating the set of contextual parameters the function could access. However, this information is only available in the signature of the function. If a user wants to further emphasize this restriction, they can use a "realm annotation" like so: ```rust realm SubRealm; realm ParentRealm: SubRealm; fn foo() use ParentRealm { // This is the syntax in question. This marker asserts that the // function call operates in the `ParentRealm` realm. If the realms // mismatch, this marker emits an error. bar(use ParentRealm, 1, 2, 3); } fn bar(a: u32, b: u32, c: u32) use ParentRealm { // `_` is shorthand for the realm of the current function. baz(use _); } fn baz() use ParentRealm { maz(use SubRealm); // If we used `ParentRealm` here instead, we'd get an error since we're // expecting exact realm equality—not realm compatibility! // maz(use ParentRealm); } fn maz() use SubRealm { // ... } ``` By default, these are entirely optional. Particularly cautious users can render these markers mandatory by changing the lint level of `missing_realm_annotations`. ```rust #![forbid(missing_realm_annotations)] realm SubRealm; realm ParentRealm: SubRealm; fn foo() use ParentRealm { // The lint is emitted for this line—even though the realms are // compatible—since the annotation is missing. bar(); } fn bar() use SubRealm { // ... } ``` Changing either the realm or the effective borrow set of a public function is a breaking change. While the former is easy to spot, the latter is much harder! This is where the `#[borrows_only]` attribute comes in. This attribute asserts that a function the given set of context parameters and nothing more. ```rust realm MyRealm; ctx MY_CTX_1 in MyRealm = u32; ctx MY_CTX_2 in MyRealm = u32; ctx MY_CTX_3 in MyRealm = u32; #[borrows_only(mut MY_CTX_1, ref MY_CTX_2)] pub fn my_func() use MyRealm { // Okay because we allow mutable access to `MY_CTX_1`. *MY_CTX_1 += 1; // Okay because we allow immutable access to `MY_CTX_2`. eprintln!("The meaning of life is {}", *MY_CTX_2); // Fails to compile because only immutable access to `MY_CTX_2` is // permitted by `#[borrows_only]`, even if all of its callers // could potentially provide it. // *MY_CTX_2 += 1; // Fails to compile—even though `MY_CTX_3` is in `MyRealm`—because we // never listed it in the `#[borrows_only]` attribute. // *MY_CTX_3 += 1; my_nested_func(); } fn my_nested_func() use MyRealm { // Still fails to compile because it violates the expectation of `#[borrows_only]` // that `MY_CTX_3` is unborrowed. // *MY_CTX_3 += 1; } ``` If a contextual parameter is listed in `#[borrows_only]` but not actually borrowed, callers of that function will still pretend as if it were borrowed. ```rust realm MyRealm; ctx MY_CTX in MyRealm = u32; fn user() { // This code produces a borrow conflict between `other_borrow` and the // call to `my_func` since we still assume that `my_func` borrows `MY_CTX` // mutably regardless of its body. let other_borrow = &mut *MY_CTX; my_func(); let _ = other_borrow; } #[borrows_only(mut MY_CTX)] pub fn my_func() use MyRealm { todo!(); } ``` To encourage adoption of this feature by crate authors, a warn-by-default lint named `unspecified_borrows_in_public_api` triggers on all publicly accessible functions with unannotated borrows. This warning can be promoted into an error once the crate is released or disabled entirely if the crate is intended to be entirely private and unstable. ```rust #![forbid(unspecified_borrows_in_public_api)] realm MyRealm; pub fn foo() use MyRealm {} fn bar() use MyRealm {} ``` By default, contextual parameters can only be borrowed for the duration of the function call. Users can ask to borrow a contextual parameter for longer using the `where ctx` syntax: ```rust realm MyRealm; ctx MY_CTX in MyRealm = u32; fn does_not_compile<'a>() -> &'a mut u32 use MyRealm { // Does not compile since borrows of `MY_CTX` are expected to not outlive // the function. &mut *MY_CTX } fn fixed<'a>() -> &'a mut u32 use MyRealm where ctx mut MY_CTX: 'a, { // Compiles thanks to the `where ctx` clause. &mut *MY_CTX } fn broken_again<'a>() -> &'a mut u32 use MyRealm where ctx ref MY_CTX: 'a, { // This fails to compile because the where clause indicates that `'a` is // only tied to an immutable borrow of `MY_CTX` but we actually need a // mutable borrow. &mut *MY_CTX } fn caller() use MyRealm { let a = fixed(); let b = fixed(); // The borrow checker rejects this line because we're borrowing `MY_CTX` // mutably in two places simultaneously. let _ = (a, b); } ``` ### Generic Programming One may wonder how generic programming works with this system. Are we introducing "context generics?" How do trait dispatches on methods taking context work? What about dynamic dispatches? The answer is that, with only the mechanisms introduced so far, generic programming is impossible. The target of a context borrow is always statically known and invariant with respect to generic parameters. This is because this proposal intentionally leaves out a `ctx`-generic feature. This was done to avoid the complex negative reasoning required to support such a feature: ```rust realm MyRealm; fn clueless<ctx T: Vec<u32>, ctx V: Vec<u32>>() { // ^^^______________^^^ // These do NOT EXIST! // ...because you could construct code like this: for v in T.iter_mut() { V.push(*v); } } // ...which implies negative bounds on `T` and `V`—in this case `T != V`. ctx MY_CTX in MyRealm = Vec<u32>; fn uh_oh() use MyRealm { // Uh oh. clueless::<MY_CTX, MY_CTX>(); } ``` Trait methods are not allowed to be placed in realms and therefore not allowed to inherit any context from their callers. Functions in a realm cannot be converted into function pointers for a similar reason. ```rust realm MyRealm; ctx MY_CTX in MyRealm = Vec<u32>; fn denied() { // Creating this closure will result in an error because the closure // uses context from its caller. let impossible = || MY_CTX.push(4); } trait MyTrait { fn do_something(&mut self) use MyRealm; // ^^^^^^^^^^^ // This syntax does NOT EXIST! } impl MyTrait for u32 { fn do_something(&mut self) use MyRealm // ^^^^^^^^^^^ // This syntax does NOT EXIST! { // ...making this context access invalid! MY_CTX.clear(); } } fn borrows_something() use MyRealm { MY_CTX.clear(); } fn demo() { // Converting this `FnDef` into a function pointer is invalid // because it borrows something from a realm. let f = borrows_something as fn(); } ``` To safely support generic programming, we introduce a new `!Sized` object to the `core::context` module of the standard library: `Bundle<T>`. A bundle is a value representing a borrow of a given set of context parameters. The `T` in `Bundle<T>` is a `ContextSet`, which is easiest to define with some examples: ```rust use core::context::{ContextParam, Ref, Mut}; realm MyRealm; ctx MY_CX_1 in MyRealm = u32; ctx MY_CX_2 in MyRealm = u32; // Each `ctx` item also defines an unconstructible marker type of the same name. // These marker types implement the `ContextParam` trait. type MyCx1 = MY_CX_1; // The `ContextParam` trait exposes the `Value` associated item, which represents the // type of the contextual parameter's value. This projection, for example, resolves to `u32`. type MyCx1Value = <MY_CX_1 as ContextParam>::Value; // From `ContextParam`s, we can construct `ContextSet`s. The simplest sets one could // construct are the singleton sets: type Set1 = Mut<MY_CX_1>; type Set2 = Ref<MY_CX_2>; // From there, we can compose larger sets using tuples. type Set3 = (); type Set4 = (Set1, Set2, Set3); // These sets are free to include duplicates. Sets with duplicates // are well-formed and treated identically to their deduplicated counterparts. type Set5 = (Set4, Mut<MY_CX_2>); ``` To avoid potential confusion between `where ctx` and `where Type: bound` clauses, a deny-by-default lint `lifetime_bounds_on_context_types` is emitted if a user attempts to constrain a `ContextParam` type marker to a lifetime: ```rust realm MyRealm; ctx MY_CTX in MyRealm = u32; fn oops<'a>() -> &'a () use MyRealm where // A warning is emitted for this line since we're constraining a marker type living for // `'static` to live for `'a`, which is vacuous. MY_CTX: 'a, { unimplemented!() } fn works<'a>() -> &'a () use MyRealm where // The user probably intended to write this instead. ctx MY_CTX: 'a, { unimplemented!() } ``` A bundle can be constructed from the contextual environment using the `Bundle::acquire()` intrinsic method. If this is done, `T` must not depend on the function's generic parameters. ```rust use core::context::{Bundle, Ref, Mut}; realm MyRealm; ctx MY_CX_1 in MyRealm = u32; ctx MY_CX_2 in MyRealm = u32; fn demo() use MyRealm { let borrows_1 = Bundle::<(Mut<MY_CX_1>, Ref<MY_CX_2>)>::acquire(); let borrows_2 = Bundle::<Mut<MY_CX_2>>::acquire(); // This line forces `borrows_1` and `borrows_2` to live simultaneously. // This causes a context borrow error since `MY_CX_2` is borrowed both // mutably and immutably at the same time. let _ = (borrows_1, borrows_2); } fn uh_oh<T: ContextParam, V: ContextParam>() use MyRealm { // This, meanwhile, is INVALID because the set depends on a generic parameter. let borrows_1 = Bundle::<Mut<T>>::acquire(); let borrows_2 = Bundle::<Mut<V>>::acquire(); // ...and thank goodness it's denied; otherwise, this code would introduce a negative // bound of `T != V`. let _ = (borrows_1, borrows_2); } ``` Bundles can also be constructed at runtime by supplying values directly: ```rust use core::context::{Bundle, Ref, Mut, ContextElem}; fn make_bundle<'a, T, V>(a: &'a mut T::Value, b: &'a mut V::Value) where T: ContextElem, V: ContextElem, { let bundle: &'a mut Bundle<(Mut<T>, Mut<V>)> = Bundle::builder() .with(a) .with(b) .finish::<(Mut<T>, Mut<V>)>(); // We now have a `&mut Bundle<(Mut<T>, Mut<V>)> at our disposal living for `'a'! } ``` The `finish` method has no compile-time safety guards in place. If it receives multiple different assignments to a given contextual elements or lacks the values necessary to construct the desired bundle, it will panic at runtime. Bundles whose `ContextSet`s contain no generic types can then be bound into the regular passing context using `bind`. ```rust use core::context::{Bundle, Ref, Mut}; realm MyRealm; ctx MY_CX_1 in MyRealm = u32; ctx MY_CX_2 in MyRealm = u32; fn demo_1(cx: &mut Bundle<(Mut<MY_CAP_1>, Mut<MY_CAP_2>)>) { bind cx { // This function does not need to be in a realm because all the acceses of // `MY_CX_1` are wrapped within this `bind` block. *MY_CX_1 += *MY_CX_2; } } fn demo_2(cx: &mut Bundle<(Mut<MY_CAP_1>, Mut<MY_CAP_2>)>) { // Since binding bundles for the duration of a function is so common, the following // alternative form of `bind` exists to do exactly that. bind cx; *MY_CX_1 *= *MY_CX_2; } ``` Bundles are the only way to forward sets of context to a trait member. There are two common patterns for doing this: embedding the bundle type as part of the trait and passing the bundle to the method at call time and embedding the bundle instance in the trait implementor. Here's the former pattern in action: ```rust use core::context::{Bundle, ContextSet}; // Trait definition trait MyTrait { type Cx: ContextSet; fn do_something(&mut self, cx: &mut Bundle<Self::Cx>); } // Trait caller fn call_my_trait<T: MyTrait>(cx: &mut Bundle<T::Cx>, callee: &mut T) { callee.do_something(cx); } // Trait implementor realm MyRealm; ctx MY_CX in MyRealm = u32; struct Demo; impl MyTrait for Demo { type Cx = Mut<MY_CX>; fn do_something(&mut self, cx: &mut Bundle<Self::Cx>) { bind cx; *MY_CX += 1; } } // Driver fn driver() use MyRealm { call_my_trait(Bundle::acquire(), &mut Demo); } ``` ...and here's the latter: ```rust use core::context::{Bundle, ContextSet}; // Trait definition trait MyTrait { fn do_something(&mut self); } // Trait caller fn call_my_trait(callee: &mut impl MyTrait) { callee.do_something(cx); } // Trait implementor realm MyRealm; ctx MY_CX in MyRealm = u32; struct Demo<'a> { cx: &'a mut Bundle<Mut<MY_CX>>, } impl MyTrait for Demo { fn do_something(&mut self) { bind self.cx; *MY_CX += 1; } } // Driver fn driver() use MyRealm { call_my_trait(&mut Demo { cx: Bundle::acquire() }); } ``` This pattern is most commonly seen when forwarding context to closures: ```rust use core::context::{Bundle, Mut}; realm MyRealm; ctx MY_CX in MyRealm = u32; fn call_my_trait(f: impl FnOnce()) { f(); } fn driver() use MyRealm { let cx = Bundle::<Mut<MY_CX>>::acquire(); call_my_trait(|| { bind cx; *MY_CX += 1; }); } ``` To avoid breaking the *bounded refactors* property of this system, users are allowed to define `ContextSet` marker types whose definition is inferred by usage using the `infer_set!` macro. The inference information is not made visible to the user and, at type-inference time, the parameter is resolved as just that marker type. ```rust use core::context::{Bundle, infer_set}; realm MyRealm; ctx MY_CX_1 in MyRealm = u32; ctx MY_CX_2 in MyRealm = u32; realm MyOtherRealm; ctx MY_OTHER_CX in MyOtherRealm; fn call_my_trait(f: impl FnOnce()) { f(); } fn driver() use MyRealm { // At borrow-checking time, `MyInferenceSet` is resolved as `(Mut<MY_CX_1>, Mut<MY_CX_2>)`. infer_set!(MyInferenceSet); let cx = Bundle::<MyInferenceSet>::acquire(); // Calling `infer_set!()` without any arguments expands to the same code as above. let _cx = infer_set!(); call_my_trait(|| { bind cx; // This call would fail if `Mut<MY_CX_1>` was not in the set so it gets included. *MY_CX_1 += 1; }); call_my_trait(|| { bind cx; // This call would fail if `Mut<MY_OTHER_CX>` was not in the set so it gets included. // However, since `Bundle::<MyInferenceSet>::acquire()` is being ran in a function // permitted only to access contextual parameters in `MyRealm`, the program fails // to compile. *MY_OTHER_CX += 1; }); } ``` To prevent the ambiguities arising from having more than one inference set in a tuple of `ContextSet`s, `bind`ing on a `ContextSet` with more than one distinct inference set results in an error. This property can be checked directly at the `bind`-site since the type of each `Bundle` upon which users bind is always fully known. ```rust use core::context::{Bundle, infer_set}; realm MyRealm; ctx MY_CX in MyRealm = u32; fn call_my_trait(f: impl FnOnce()) { f(); } fn driver() { infer_set!(InferSet1); infer_set!(InferSet2); let cx = Bundle::<(InferSet1, InferSet2)>::acquire(); call_my_trait(|| { // This would fail to compile since the `ContextSet` of type `(InferSet1, InferSet2)` // contains more than one inference set. bind cx; // This prevents an ambiguity with this line. If this situation were not prevented, // which inference set would hold? *MY_CX += 1; }); } ``` In case more than one `bind` expression acting upon an inference set is in the ancestry of a borrow, the inference set of the nearest ancestor `bind` scope is updated. ```rust use core::context::infer_set; realm MyRealm; ctx MY_CX_A in MyRealm = u32; ctx MY_CX_B in MyRealm = u32; fn call_my_trait(f: impl FnOnce()) { f(); } fn driver() { let cx_1 = infer_set!(); let cx_2 = infer_set!(); call_my_trait(|| { bind cx_1 { bind cx_2 { // The nearest `bind` with an inference set is `cx_2` so its borrow set is // updated to include `Mut<MY_CX_A>`. *MY_CX_A += 1; } // The nearest `bind` with an inference set is `cx_1` so its borrow set is // updated to include `Mut<MY_CX_B>`. *MY_CX_B += 1; } }); } ``` # Reference-level explanation [reference-level-explanation]: #reference-level-explanation This section describes a set of theoretical algorithms one could employ to implement this RFC. This section also describes the diagnostics the compiler should attempt to produce and theoretical algorithms for producing them. ## Syntax There are several new syntactic elements introduced by this proposal. **Realm items** are introduced using the following item syntax: (==FIXME: `realm` is not a reserved keyword so we need to pick something else :sob:==) ``` ConcreteRealmDefinition: `realm` IDENTIFIER (: (SimplePath),* )? ; RealmAliasDefinition: `realm` IDENTIFIER = (SimplePath),+ ; ``` These items are beholden to the following rules: - The expected naming convention for realm names is `PascalCase`. If this style expectation is not met, a new `non_camel_case_realms` lint is emitted. - The `SimplePath` is expected to reference another realm item (i.e. a concrete realm or realm alias). - If realm aliases and realm definitions form a cycle, an error will be emitted at name resolution time. - Referenced realms must be at least as visible at the realm item. - Generics are not supported and this is unlikely to be lifted. Attempts to define them in the natural place (*i.e.* right after the identifier) will produce a parse error notifying the user of this restriction. Realms behave like any other item definition in Rust: - They can be documented and can receive a visibility specifier. - Declarative macros can operate upon them. - Macros by example can match item using the `$i:item` metavariable. - They can be imported and re-exported using `use` statements. - They can be defined within expressions. - They are visible in `rustdoc`. - They are *not* valid in a trait definition, trait `impl` body, or inherent `impl` body. **Context items** are introduced using the following item syntax: (==FIXME: `ctx` is not a reserved keyword so we need to pick something else :sob:==) ``` ContextDefinition: `ctx` IDENTIFIER `in` SimplePath = Type; ``` These items are beholden to the following rules: - The expected naming convention for context names is `SCREAMING_CASE`. If this style expectation is not met, a new `non_upper_case_contexts` lint is emitted. - The `SimplePath` is expected to reference a base realm. Placing contexts in realm aliases is not allowed and will result in an error at name-resolution time. - The `Type` must be non-generic, live for `'static`, be inhabited, and be at least as visible as the `ctx` item. The type can rely on type projections, `impl Trait`-in-type type aliases, and be unsized. If these type expectations are not met, an error will be produced at type-checking time. - The `Type` must be at least as visible as the `ctx` item. - Generics are currently unsupported although this restriction could eventually be lifted. Attempts to define them in the natural place (*i.e.* right after the identifier) will produce a parse error notifying the user of this limitation. Context items behave like any other item definition in Rust. See the list of things that implies under the section defining realm syntax. Context items can also behave as types. These types are unconstructible and implement the otherwise sealed `ContextItem` language-item trait defined in the "core library interface" section. If context items gain the ability to be generic, their generic parameters will likely be inherited by the structure so that `ContextItem::Value` can continue to be a parameterless associated item. Finally, context items behave like `static`s but admit special access semantics detailed in the "borrow checking" and "realm checking" sections. **Binding expressions** are introduced using the following syntax: ``` ScopedBindBundleExpr: `bind` Expr `{` Body `}` ScopedBindListExpr: `bind` `(` ( SimplePath = Expr ),* `)` `{` Body `}` FuncBindBundleStmt: `bind` Expr `;` ``` ==TODO: Write rules (also, double-check definition)== **Context lifetime constraints** ==TODO== **Function definition realm annotations** ==TODO== **Function call realm annotations** ==TODO== **The `#[only_borrows`] Attribute** ==TODO== ## Standard Library Interface All standard library interfaces pertaining to this feature are located in the newly-created `core::context` module, which is mirrored in `std::context`. ==TODO== ## Borrow Checking ==TODO== ## Realm Checking ==TODO== # Drawbacks [drawbacks]: #drawbacks ==TODO== Why should we *not* do this? # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives ==TODO== - Why is this design the best in the space of possible designs? - What other designs have been considered and what is the rationale for not choosing them? - What is the impact of not doing this? - If this is a language proposal, could this be done in a library or macro instead? Does the proposed change make Rust code easier or harder to read, understand, and maintain? # Prior art [prior-art]: #prior-art ==TODO== Discuss prior art, both the good and the bad, in relation to this proposal. A few examples of what this can include are: - For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? - For community proposals: Is this done by some other community and what were their experiences with it? - For other teams: What lessons can we learn from what other communities have done here? - Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. Please also take into consideration that rust sometimes intentionally diverges from common language features. # Unresolved questions [unresolved-questions]: #unresolved-questions ==TODO== - What parts of the design do you expect to resolve through the RFC process before this gets merged? - What parts of the design do you expect to resolve through the implementation of this feature before stabilization? - What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? # Future possibilities [future-possibilities]: #future-possibilities ==TODO== Think about what the natural extension and evolution of your proposal would be and how it would affect the language and project as a whole in a holistic way. Try to use this section as a tool to more fully consider all possible interactions with the project and language in your proposal. Also consider how this all fits into the roadmap for the project and of the relevant sub-team. This is also a good place to "dump ideas", if they are out of scope for the RFC you are writing but otherwise related. If you have tried and cannot think of any future possibilities, you may simply state that you cannot think of anything. Note that having something written down in the future-possibilities section is not a reason to accept the current or a future RFC; such notes should be in the section on motivation or rationale in this or subsequent RFCs. The section merely provides additional information.