Nice API to temporarily detach a field?

A relatively common pattern that I employ is that I have a &mut self and I need to temporarily have both &mut self and &mut self.somefield. Usually the way I do it, is that I make the field a Option<T> and just self.somefield.take() and then self.somefield.put(v) it back.

However, that's kind of a lot of churn. I wish there was something nicer to accomplish it. I looked around and there's already detach - Rust which addresses exactly the thing I'm talking about but this macro API is far from ideal, IMO.

What I would ideally want is something like:

struct Foo
  bar: Detach<String>,
  ... // stuff
}


impl Foo {
  fn use_bar(&mut self) {
     self.bar.detach().with(|s| { *s = format!("{} more with {}", s, self.some_mutable_stuff());});
  }
}

or something. The detach() would be sort of a guard on the field. I thought about it, and I can't figure out how to make this API safe. The piece that I'm missing is somehow making the result of detach() be bound to exactly same lifetime as &mut self without actually mutably borrowing self. Runtime check would make sure a field can't be detached twice, and lifetime would make sure that self does not dissapear while s is used. Is there any technique to accomplish this?

Alternatively - does anyone have any idea how to make an API better then a macro?

The detach crate is just a macro to generate your Option approach with some utility to panic when you expected it to not be detached. Perhaps you know this, but the rest of your post made me think you're looking for something that doesn't alter the data structure?

If so, your example isn't workable as such because you would have two mutable reference paths to the same data at the same time. E.g. if you're not using Option or similar, how can you be sure some_mutable_stuff doesn't look at or clobber the detached field?

(The example is also simple enough to work around I'm not convinced I understand your use case.)

Same way as RefCell does - use a marker and check it before allowing any access.

My experience is that you can usually make a simple change to your code that eliminates the need for having both &mut self and &mut self.somefield.

1 Like

Oftentimes. But this generally requires twisting code and/or splitting structs into sub-structs etc. for the sole purpose of satisfying borrow checker. In a lot of cases take + put is much simpler and generally is more flexible, at the cost of Option and runtime checks it brings. What I'm looking for is just making take + put more ergonomic.

1 Like

One issue with this API that I can anticipate is that a &mut self allows one to replace the entire Detach<T> field using safe code, mutating the T without obtaining a &mut T through the Detach API, bypassing its runtime check.

Right, so, you're okay altering the data structure.

The only fully general solution I can think of is making detachable fields Arc<RefCell> (or Rc<RefCell>) so you can leverage shared ownership to maintain a path to the field which is independent of &mut self (by detaching cloning the Arc). Then it won't matter if some other &mut self method clobbers your field; it won't be dropped because you're maintaining ownership elsewhere.

(Won't matter in terms of Rust's safety guarantees; probably still a bug.)

Related.

I'm think the mere existence of a &mut self for the struct to which you also have a shared reference of one of its fields would be UB due to aliasing rules, no?

If the field borrow is derived from the self borrow in a way the language recognizes it's fine (and using the self borrow kills the field borrow), but otherwise it's UB.

1 Like

Indeed. I think that's OK. The only other approach I can think of is using some form of static magic-counter, incremented on every instance construction and checked before attempting the drop.

You can have the API you suggested. (Though at that point I'd probably just write an extention trait for Arc instead of a new type, personally.)

1 Like

I guess this works (though we either make Arc<Mutex<T>> or Rc<RefCell<T>>, though if someone figured out anything like:

I would love to avoid the heap allocation. :slight_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.