# Idea: Experimental attribute based support for partial borrows

**URL:** <https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744>\
**Category:** language design\
**Created:** [April 29, 2024, 4:40pm UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744 "2024-04-29T16:40:11Z")\
**Posts on this page:** 16\
**Page:** 2

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [April 30, 2024, 11:38pm UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/21 "2024-04-30T23:38:32Z")

</div>

Disjoint borrows exist within functions already, and I don’t think this has been a significant source of issues in itself. I haven’t heard recommendations to move unsafe code to separate methods or closures just to block it. Rather the opposite, I recall issues where a too-broad `self` loan was making semantics of unsafe code within the function dubious. For example, recently `Arc::drop` needed changes to language semantics to allow a `&self` reference to be a dangling pointer.

Please don’t forget that the borrowing limitation exists for API stability, not for safety. You can merge all your safe methods into one spaghetti megamethod, they’ll get disjoint loans, but they won’t be any less safe.

I think it’s worth having a hard look which features in Rust merely feel necessary, and where lack of restrictions feels uncomfortable, but doesn’t break things in practice. Partial loans still block mutable aliasing, prevent iterator invalidation, enforce immutability and so on. All of Rust’s safety guarantees hold within functions, so they hold with partial loans.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [May 1, 2024, 1:46am UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/22 "2024-05-01T01:46:37Z")

</div>

> [@kornel](#):
>
> Disjoint borrows exist within functions already

This is why I always end up coming back to some kind of `macro fn` for this. Because it's not just borrow splitting that I foresee people wanting for private helpers, but also move awareness, type inference, etc.

To me the simple way forward is to _have_ that megamethod, just written in a nicer non-spaghetti way.

---

<div class="post-metadata">

**Author:** ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)\
**Post date:** [May 2, 2024, 9:13pm UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/23 "2024-05-02T21:13:59Z")

</div>

> [@crumblingstatue](#):
>
> Can you expand on what it means to "detect" the attribute?

I meant that would the borrow checker detect the shared-mutable reference conflict.

---

<div class="post-metadata">

**Author:** ![Ddystopia](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ddystopia/32/10915_2.png) [@Ddystopia](https://internals.rust-lang.org/u/Ddystopia)\
**Post date:** [May 3, 2024, 12:51pm UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/24 "2024-05-03T12:51:05Z")

</div>

I'm not sure why partial borrows must require any new syntax? Can't compiler store and use that information itself? Because partial borrows are quite useful and might be used a lot, but it would be extremely painful to write all of that of every time, especially when you need to change stuff frequently because you are only prototyping/exploring/trying out

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [May 3, 2024, 3:27pm UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/25 "2024-05-03T15:27:20Z")

</div>

The main argument against that (if allowed for public functions) would be that it would be semver breaking to add more things you use. But for crate or less public, that would be OK probably. Explicit vs implicit is the the question.

---

<div class="post-metadata">

**Author:** ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)\
**Post date:** [May 3, 2024, 3:41pm UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/26 "2024-05-03T15:41:27Z")

</div>

> [@Ddystopia](#):
>
> I'm not sure why partial borrows must require any new syntax? Can't compiler store and use that information itself?

I think at least saying "this is a partial borrow API" when it is accessible from outside the crate is important. Having it be implicit everywhere is feels like having semver-breaking [caltrops](https://en.wikipedia.org/wiki/Caltrop) sprinkled around codebases.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [May 3, 2024, 11:49pm UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/27 "2024-05-03T23:49:10Z")

</div>

> [@Ddystopia](#):
>
> Can't compiler store and use that information itself?

One trap you want to avoid is having a function body be `todo!()`, writing a bunch of other stuff, then going back to fill it in and finding out that everything else only worked because the compiler saw that you never used the borrow in the body at all, and let you do whatever you wanted.

I'm hopeful that this will be like return types. They're not inferred, but if you write `-> _` you get a nice structured suggestion so you hit a button and the actual type gets put there.

The same can reasonably happen for partial borrows; you can start using a new part of the struct and the compiler says "well that's not in your borrow, but I can add it to your partial borrow for you" with a structured suggestion if that's what you wanted.

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [May 4, 2024, 6:21am UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/28 "2024-05-04T06:21:52Z")

</div>

> [@scottmcm](#):
>
> The same can reasonably happen for partial borrows; you can start using a new part of the struct and the compiler says "well that's not in your borrow, but I can add it to your partial borrow for you" with a structured suggestion if that's what you wanted.

That seems reasonable. The main concern here seems like API stability. Accessing a new field would be a breaking change.

---

<div class="post-metadata">

**Author:** ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)\
**Post date:** [May 4, 2024, 10:27am UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/29 "2024-05-04T10:27:46Z")

</div>

Ah, I think I have an interesting use case here: declaring a partial borrow _without actually using the member_. See [this MR](https://gitlab.kitware.com/utils/ghostflow-director/-/merge_requests/357) (though in particular [this commit](https://gitlab.kitware.com/utils/ghostflow-director/-/merge_requests/357/diffs?commit_id=f6eb477ca2e8f0f6a34f97dfdfadf71ee7a67011)). Here, the highlighted commit, instead of making a new type, may have been able to utilize partial borrowing to declare that using the `writer` is a conceptual `&mut` borrow of `data` even though there is no method that uses both syntactically. The point is that even though `data` is not mentioned, it _must be considered borrowed in order to avoid a deadlock_ because the _receiving_ end of `writer` needs a write lock on the `RwLock<data::Data>` to actually consume items from the channel.

To me, this means that there must be a way to explicitly list members that are considered borrowed even if there is support for implicit computation because there may be safety considerations in how members are grouped into types (not that this example is one of Rust safety/soundness guarantees given that the deadlock behavior is Rust-safe, but someone can probably craft such an example).

---

<div class="post-metadata">

**Author:** ![zirconium-n](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zirconium-n/32/9078_2.png) [@zirconium-n](https://internals.rust-lang.org/u/zirconium-n)\
**Post date:** [May 5, 2024, 1:06am UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/30 "2024-05-05T01:06:52Z")

</div>

I'm really fond of this `macro fn` feature. Since I'm sure there would be some details somewhere, I somehow feel that the best way to promote or refine it is implementing a rough version first. What's the chance that we start an implementation without a rigid RFC? Let's say I implemented this feature on a fork of rustc, how can I get people try it out or merge this in to the main branch (under a nightly feature flag of course)?

---

<div class="post-metadata">

**Author:** ![ekuber](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ekuber/32/1646_2.png) [@ekuber](https://internals.rust-lang.org/u/ekuber)\
**Post date:** [May 5, 2024, 5:50am UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/31 "2024-05-05T05:50:10Z")

</div>

To be honest, there are lots of subtle ways to make breaking changes in your APIs, to the point that I feel that relying on cargo-semver to help you catch those is a better avenue than trying to avoid adding features that our users can misuse to cause a break accidentally. In general we _should_ make doing the right thing easier, but here it is a tension between making two separate things easier (one that users want to do, another that users should want to avoid), but for one we have no real answer today, and for the other we have a tool that will help.

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [May 5, 2024, 6:56am UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/32 "2024-05-05T06:56:17Z")

</div>

> [@zirconium-n](#):
>
> Since I'm sure there would be some details somewhere [about `macro fn`]

[My old thread on `macro fn`](https://internals.rust-lang.org/t/rfc-macro-functions/17886) is the most complete writeup of what they'd entail, I believe. There are various levels of transparency that can be argued for — my original proposal basically wanted to permit `_` in the function signature with semantics similar to C++20 abbreviated function templates with `auto`, meaning field-precise borrowing still wouldn't happen — but I still hold that they're a strong kernel of an idea that I'd like to see developed.

A key bit of functionality of `macro fn` to me is that unlike `macro_rules!`, it should instantiate an actual function once per signature; that in every way other than signature transparency it acts like any other function item. I'm looking at it both from the lens of "less constrained function" and "more constrained macro," but imo it should be more like `fn` than `macro` since bang macros still exist and work well for what they do. `macro fn` should be more than just what a carefully written functionlike macro can do, not just the same but easier.

I think the first implementation step would be one of:

- `$:value` and/or `$:place` fragment matchers for macros, which accept the same syntax as `$:expr` but evaluate the expression before any of the macro's expansion (with any temporary lifetimes treating the macro as a single unit), and the fragment expansion naming the result of that expression evaluation as a value or as a place, respectively.
- The ability to express and check field-precise borrows across function boundaries in MIR, without any (or with placeholder) surface language syntax.

The former isn't _directly_ useful for `macro fn`, but it is directly related in that it makes well behaved bang macros more straightforward to write, helping to address a decent portion of the need which is satisfied with each invocation being separately inlined and reanalyzed at each call site. `$:value` for imitating functions, and `$:place` to permit partial place usage, including field-precise borrowing. The latter is necessary in order to express what a `macro fn` is after type inference information from the caller is provided to fill in any type holes, if `macro fn` is to permit field-precise borrowing.

Although I must say I'm not the _most_ fond of either `f(&place)` or `f(&mut place)` at the call site potentially being field-precise borrows. It's a lot easier to accept for method call syntax, extending the existing place autoref to include field-precise partial borrowing of the receiver. But I'm a lot _less_ fond of `f(place)` potentially receiving `place` by reference instead of moving (copying) from it. Leave that behavior to syntactically noted bang macros.

> [@ekuber](#):
>
> Personally, I would make it something along the lines of the following, which is perfectly _invalid_ today:
> 
> ```rust
> impl App {
> // Borrow the field `meta_state` immutably, and `hex_ui` mutably
> pub(crate) fn switch_layout(self { &meta_state, &mut hex_hi, .. }, k: LayoutKey) {
> 
> ```

This reminds me of a proposal from a good while back for generalizing type ascription in function signatures, allowing you to replace the somewhat repetitive `fn f(Newtype(x): Newtype<i32>)` with instead just `fn f(Newtype(x: i32))`, or even leaving type ascription out entirely if the patterns uniquely constrain the argument type.

Tbh I don't know how well it would interact with default binding modes, since `Newtype(x: &i32)` could be a pattern for type `Newtype<&i32>` or `&Newtype<i32>`. Or how the unique `fn(&self)` sugar that isn't a pattern like other function arguments but some special third thing. And yet another difficulty of course being that in a named struct pattern `Widget { a: b }`, `a` is the field name and `b` is the pattern.

All this to say I'm not _against_ `fn f(Widget { &id, .. })` as a syntax for field-precise argument capture in function signatures by analogy to the `&self` sugar, but I am at least somewhat wary of it. `fn f(Widget { &id, .. }: Widget)` is thankfully invalid, needing to be written as `fn f(Widget { id: &id, .. }: Widget)` instead, but `fn f(Widget { ref id, .. }: Widget)` _is_ valid. (Which honestly doesn't quite vibe with my lazy mental model for binding modes that wants `&` to switch from the by-ref binding mode to by-move in the same way `ref` switches from by-move to by-ref, even though I know it doesn't actually work like that\[1\]\[2\].)

* * *

1. "Fun" fact! Given `struct S { a: &'static i32 }` and a pattern scrutinee of type `&mut S`, a pattern of `S { a: x }` binds `x: &mut &i32`, `S { a: ref x }` binds `x: &&i32`, `S { x: mut a }` binds `x: &i32`, and `S { a: &x }` binds `x: i32`. If you peel back the curtain, they actually bind `ref mut x: &i32`, `ref x: &i32`, `mut x: &i32`, and `&x: &i32`, respectively, so there is straightforward predictable logic driving this, but match ergonomics is certainly relying heavily on deref coercion and never mixing in explicit binding modes for it's "just works" target. 

2. That a `&_` pattern switches the default binding mode back to by-move (but still only is allowed when "destructuring" a reference) whereas named type patterns project the binding mode almost feels like a bug, and my mental model would be happier if `&_` universally switched out of a by-ref binding mode if present before attempting to destructure a reference scrutinee. I.e. that default binding modes worked more as if the subpattern scrutinized a temporary value of type `&mut Field`, instead of still scrutinizing a place of type `Field` and automatically applying the `ref mut` mode, but only if no explicit binding mode is used. I realize actually creating a temporary field reference to scrutinize would impact otherwise unscrutinized fields, which shouldn't actually happen, and it shouldn't be possible to bind to the "temporary" by-`ref` either, but the synthetic field-precise reference scrutinee illustrates how my mental model wants default binding modes to behave.

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [May 5, 2024, 7:27am UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/33 "2024-05-05T07:27:05Z")

</div>

I feel like this is missing a nuance, if I read what you are proposing correctly. That is, you want implicit partial borrows, and since we are discussing semver at, you want this in public functions.

If that were the case, yes something like caego-semver-checks could catch it (if the info is included in the rust doc json file that it parses).

However, it also makes it much harder to make changes that are semver-preserving in general. Consider today, if you add a new private field to a struct (as long as it already has private fields or is non-exhaustive) and start using it from two existing functions, that is fine. This will no longer be the case if those two functions were partially borrowing different parts of the structure.

There are many other currently semver safe changes that would no longer be so, along similar lines. This reduces the design space for library authors. For this reason we need to make partial borrowing optional.

The question then is if it should be opt-in as suggested in this thread, or opt-out (along the lines of `non_exhaustive`). I would prefer opt-in, for less foot guns (`non_exhaustive` is easy to forget today, I would prefer it to be the default, but that can't be changed at this point).

I believe that we are _all_ missing the point proposed in OP though. Let's make an intentionally anti-bikeshed experiment (using rustc attributes) so we can leave this sort of discussion to later once we know that the functionality works, and have some experience with the feature itself.

---

<div class="post-metadata">

**Author:** ![ekuber](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ekuber/32/1646_2.png) [@ekuber](https://internals.rust-lang.org/u/ekuber)\
**Post date:** [May 6, 2024, 4:03pm UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/34 "2024-05-06T16:03:26Z")

</div>

> [@Vorpal](#):
>
> I feel like this is missing a nuance, if I read what you are proposing correctly. That is, you want implicit partial borrows, and since we are discussing semver at, you want this in public functions.

No, I'm stating that changing _explicit_ borrows _is_ a semver break, but that _that_ shouldn't entirely preclude us from providing them. I very much lean towards explicitness over implicitness for Rust's design, at the cost of ergonomics, and to claw back some of it back through tooling.

> [@CAD97](#):
>
> This reminds me of a proposal from a good while back for generalizing type ascription in function signatures, allowing you to replace the somewhat repetitive `fn f(Newtype(x): Newtype<i32>)` with instead just `fn f(Newtype(x: i32))`, or even leaving type ascription out entirely if the patterns uniquely constrain the argument type.

I was one of the people on board with that, but if you have

```rust
struct Foo<'a> {
    a: &'a (),
    b: &'a (),
}

```

and want to write a method that _only_ captures `a` and not `b`, then you are still on the same boat as today: their lifetimes are the same and the type syntax won't allow you to differentiate them, so you need a way to be explicit about the lifetime name on the pattern side. For simple cases you don't _need_ that (because you'd write `fn foo<'a>(Foo { a, .. }: Foo<'a>) -> &'a () { a }`), but for more complex cases you would:

```rust
fn foo<'a>(Foo { &'a a, &b }: Foo<'_>) -> &'a () {
    do_something(b); // We capture foo.b only for the current function, but release it after completion
    a // field foo.a is captured beyond the current function, but foo.b can still be accessed after calling this function
}

```

---

<div class="post-metadata">

**Author:** ![binarycat](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/binarycat/32/12210_2.png) [@binarycat](https://internals.rust-lang.org/u/binarycat)\
**Post date:** [May 28, 2024, 5:12am UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/35 "2024-05-28T05:12:03Z")

</div>

I'm somewhat confused why we are discussing API stability when the proposal explicitly is not allowed in public APIs, to prevent having this exact discussion.

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [August 26, 2024, 5:12am UTC](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744/36 "2024-08-26T05:12:41Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.

[Previous page](https://internals.rust-lang.org/t/idea-experimental-attribute-based-support-for-partial-borrows/20744.md?page=1)
