Leak a read guard of `Arc<RwLock<T>>` with least unsafe code?

I am considering ways to implement borrowed type / type with lifetime for GitHub - fzyzcjy/flutter_rust_bridge: Flutter/Dart <-> Rust binding generator, feature-rich, but seamless and simple. (Rust <-> Dart/Flutter). Roughly speaking, the scenario is that:

Suppose we have a Rust function

fn f(a: &MyStruct) -> &MySubStruct {
  &a.field
}

The current generated code looks like:

let a: Arc<RwLock<MyStruct>> = it_is_gotten_from_dart();
let a_read_guard = a.read();
let output = f(a_read_guard);
give_it_to_dart(output);

If there is no lifetime or reference type, then everything is good. However, since output is of type &MySubStruct, if we want to give it to Dart naively, Rust compiler will (correctly) realize it needs to live shorter than the guard, and thus cause troubles.

One simple way is to do something like:

let pack: (ReadGuard, &MySubStruct) = (a_read_guard, output);
give_it_to_dart(Box::leak(Box::new(pack)));

i.e. if we leak the guard together with the reference, and ensure the reference is dropped before the guard is dropped, then we are safe.

However, in safe Rust, it seems we cannot do something like above (cannot move the guard when it is referred). It seems possible to do with some unsafe code (e.g. maybe an Arc<ReadGuard>, or maybe a self referential type?). But I wonder, is there a best practice / prior art when dealing with things like this?

Thanks for any suggestions!

If you actually leak the read guard, it won't actually drop, and nothing will be able to get the lock again. So you must mean something else.

Your tuple of the read guard and a reference to the data is, indeed, a self-referencial struct. In Rust, that's anything but simple; I don't know of any library which has attempted this which accomplished something useful without multiple soundness holes, for example. There are a couple that are sound or mostly sound, allegedly.

But I don't know that it will solve your problem; there's typically still a lifetime involved with those libraries. You haven't shared the signature of give_it_to_dart or any errors demonstrating the exact problem. Is it simply that everything given to Dart must be 'static? Do you have to wrap everything up in Arc<RwLock<_>> specifically along the way?

Maybe something like a mappable Arc would help, but it again depends on the requirements in play.

if you just want to leak the read guard as your title says, the idiomatic way to do that is via std::mem::forget, but note that this will effectively freeze your datastructure, as it will no longer be possible to get a mutable reference.

to give more helpful advice i would need to know the signature and behavior of give_it_to_dart. mainly, does it ever return, and does it store the reference it is given?

Hi, @quinedot @binarycat thank you for the quick replies! Here are more details, as well we my naive attempts:

Firstly, I made a minimal demo here: 1_original.rs ยท GitHub. The demo mimics what happens when user write Dart code like:

var one = One();
var two = one.f();
two.g();

The 1_original.rs describes current flutter_rust_bridge's approach (which only works for types without lifetime, surely).

The 2_naive_arc.rs shows my naive attempts, which avoids the object be deallocated too early by using arc clone. I wonder whether there is a better way? If no, is my way sound and acceptable?

As for this:

If you actually leak the read guard, it won't actually drop, and nothing will be able to get the lock again. So you must mean something else.
if you just want to leak the read guard as your title says, the idiomatic way to do that is via std::mem::forget , but note that this will effectively freeze your datastructure, as it will no longer be possible to get a mutable reference.

I guess the new demo explains it. For example, in the example above, the two object will be disposed (dropped) when user wants. Only after that, the user can again obtain write guard on the one object.

EDIT: Alternatively speaking, is there something like a "runtime lifetime checker", like how Mutex does runtime instead of compile-time checks? Or, is there more elegant way to ensure Two lives shorter than the ReadGuard?

This is already how lock guards in Rust work, when used normally (without leaking). You don't need to perform any tricks to achieve this behavior.

The guarding mechanism of RwLock is exactly the same as that of Mutex, except that it allows multiple readers or a single writer, whereas Mutex only ever gives you exclusive write access. RwLock isn't any more (or less) "compile-time" (or "run-time") guarded than Mutex; they provide the same correctness and soundness guarantees, using the exact same language features.

RefCell exists, with the same "one mutable or many immutable" rules, except they're checked at runtime.

But that's exactly the same as RwLock but single threaded, no?

@paramagnetic @binarycat Thank you!

Totally agree!

Well, I mean, we know that Rust usually does compile-time check of "no two mutable borrow"; but if one wish, he/she can use a Mutex, then Rust does not do compile-time check anymore and instead check at runtime.

I am wondering that, we know Rust compile-time check the lifetime things. Is there something that can let Rust do not check at compile-time, but instead check at runtime?

Later I found things like GitHub - Voultapher/self_cell: Safe-to-use proc-macro-free self-referential structs in stable Rust. and GitHub - someguynamedjosh/ouroboros: Easy self-referential struct generation for Rust., but they have some restrictions. I guess that may be the answer...

If the answer to that question is not RefCell, Mutex, or RwLock, then what are you looking for? I genuinely don't understand what you are asking for. Lifetimes and scopes are very much the mechanism by which these lock types work, and they provide dynamic checks for shared mutability. At some point, you have to have some compile-time mechanism, because that is how the language can encode constraints.

If you are looking for shared ownership, then the answer is Rc or Arc (depending on whether you need thread safety). Those are the types for dynamically owning a value, without any one lexical scope necessarily determining its lifetime.

@paramagnetic Thank you for the reply. I am asking about extending the lifetimes. But anyway, I finally choose to do Arc::clone with unsafe extending lifetimes, i.e. roughly the way ouroborous generates code (but slightly different to support more functionalities in the future).