Add borrows_(begin/end) for guard-less concurrency

I have an idea and I'd like to see what people think.

The idea is to add a Drop like trait that provides hooks that allow you to inject code whenever a struct starts being borrowed and stops being borrowed (only the first/last of overlapping borrows would trigger the call).

trait Borrows {
    fn borrows_begin(&mut self) {}
    fn borrows_end(&mut self) {}
}

The borrow checker should already have this information and I think it would be nice to be able to take advantage of it. My primary use case is for signaling to an underlying concurrent data structure that it's safe to modify its data without the need for the consumers to deal with an explicit guard.

Here's an "always" up-to-date lossy mpsc channel as an example:

See here.

In this example the borrows_begin and borrows_end in main would be inserted by the compiler. This allows the consumer to never have to worry about asking for updates (though they would have to be watchful for extended borrows). The example itself is a bit silly and comes with no guarantees of correctness but hopefully it serves for illustrative purposes. Making the lock implicit could be considered an anti-pattern, but it is convenient and in a non-blocking case where there are lots of short borrows, I
think it could provide a significant ergonomic win. This could be particularly relevant for garbage collection or caching schemes.

There are some other obvious downsides. It's easy to trigger calls inadvertently or more frequently than necessary, especially you dereference to something that is Copy. It also promotes a spooky action at distance that could be hard to understand and it does so in a much more prominent way than Drop does. Finally, as it's currently described, the &mut arguments require instances that implement it to always be declared mut, or be implicitly mut. I think &mut is the most flexible choice for the trait, but it does make it more annoying or more magic.

I'm still debating whether it would be a worthwhile addition and I'm sure there are many more tradeoffs (and hopefully other benefitting use cases!), but I haven't seen this idea explored before and given how uniquely rusty it is, I thought I would at least put it out there to see what other people think.

Adding hooks where user code runs can break existing code, especially unsafe code.

Knowing where arbitrary code might run is important for maintaining exception safety (or “panic safety”) in generic code. For example, currently, the following code can never panic, allocate, spawn a thread, etc, even if y has a generic type provided by external code:

let x = &y;

Changing this would break existing libraries that rely on this assumption.

2 Likes

I'd like to point out, that Rust has 2 forums. This is the user forum, for topics related to using Rust. There is also an internals forum, that is about discussing internals and ideas on how to change Rust.

Here's the link to the internals forum:

P.S.: Accounts aren't shared between the 2 forums, so you'll have to register an account there, too.

2 Likes

Good point. There's definitely an assumption that &y won't do anything besides give you a reference. A panic from that code would not be something any library would anticipate. If this trait existed, the bar for documentation about what could and couldn't be done would be extremely high. I would actually find that documentation very interesting and useful, regardless of whether this trait existed or not. And if it did exist it would also lay a nice foundation for allowing the potential specialization of impl Borrow for T. Food for thought. Thanks for your input.

I was debating whether to post there or here. The idea was that this would be solicitation of whether people thought this functionality would be useful, and if so I would pose it as a technical question to Rust internals. Though given how technical it is and how far reaching the implications could be for the language, maybe internals have been a better choice. I just didn't want to waste the Rust team's time unless I had some validation that people actually thought this could be a useful addition.

Contrary to what the naming may suggest, the Rust team is not obligated to respond to internal threads. It's still a community forum. The Rust team is busy with RFCs, fixing bugs, team meetings and other stuff — they do respond sometimes, though. The internals forum is mostly visited by community members who are either contributing to Rust, plan to do so or are simply interested in the internals.

Ah, ok. That makes sense. That is the more appropriate forum. I'll shift my query over there then. Thanks for your help!

1 Like