Approach to modify a struct using helpers within the struct?

I keep running into a common pattern that causes me trouble: when I need to use lookup functions from within a &mut self function. I'll use my current game bots as an example.

I have a State class that owns the details of the state. These are broken into several types, such as Item or Unit and I have a Vec of these items (like a Vec<Item>). This structure seems valid, as this State logically owns all of this stuff.

I have lookup functions to get at items, such as:

fn find_item<'a>(&'a self, name : &str) -> Option<&'a Item> { ... }

I also have a Turn type which represents a "change" to the system. I've structured it this way so the Turn can be created using an immutable reference to the State. I don't have to worry about any borrowing problems while constructing the turn. However, there comes a point when I must apply the turn, and I have a apply function for that:

pub fn apply_turn(&mut self, hero : usize, turn : &Turn)

This is where my difficulty starts. The moment I attempt to use any lookup function, like find_item, I trip up the borrow-checker, since I end up with multiple borrows of the state.

For example, this function (called from apply_turn):

	fn sell_item(&mut self, hero : usize, name : &str ) {
		let item = self.find_item(name).unwrap();
		self.us.gold += item.cost / 2;

This fails, since find_item borrows self and self.us.gold += requires a borrow as well (I don't know if that's the right word now).

In this simple case I was forced to call clone() on the item so I could keep processing. But it's not a workable solution in most cases. Consider that in many functions I'll need to do something like this:

fn apply_bit(&mut self, hero_ndx : usize, ndx : usize ) {
    let &mut her = self.lookup_mut_hero(hero_ndx);
    let &item = self.lookup_item(ndx);

    her.field += item.field;
    self.field -= her.field;
}

This type of thing seems completely off-limits due to the borrow checker. But I'm at a total loss as to how I should otherwise accomplish this pattern? In one of my attempts I resorted to using usize indexes everywhere and refering to the array as needed, never creating any name aliases: but that code is hard to read and maintain due to it.

How do I approach the problem of needing references to items within a mutable state function?

4 Likes

You should look into the suggestions I gave you in Borrowing issue, trying to avoid redundancy - #2 by vitalyd. Specifically, #2.

Instead of having “one big self” container of data that you borrow from, borrow from the individual fields. You may need to move some fields into their own smaller data types to organize it in a borrow-conducive manner.

1 Like

I'm just not seeing how this disjointness can be done at a practical level. The apply_turn function, regardless of where it resides, needs to have access to a mutable State. Something has to determine which fields are to be modified, and then modify them. The only way that thing can access those fields is if it has access to a mutable state.

Consider the top-level game loop:

loop {
   read_state( &mut state );
   let turn = eval_turn( &state );
   state.apply_turn( turn );
}

apply_turn needs a mutable state object, otherwise it can't work. How can I create disjoint functions without just inlining the apply_turn function?

apply_turn(&mut self, ...) is probably fine itself - it’s the entrypoint for mutation. Once you’re inside apply_turn, you can borrow individual fields of self separately.

For example, you mentioned a sell_item function that’s called from apply_turn:

fn sell_item(&mut self, hero : usize, name : &str ) {
		let item = self.find_item(name).unwrap();
		self.us.gold += item.cost / 2;

This is a good example to zoom in on. The issue, as you’ve discovered, is find_item makes it look to the compiler like you’re holding a reference to all of self. In reality, it’s a reference to an element in the Vec<Item> only. The us field, which owns gold, is disjoint ... but the way the methods are structured, the compiler cannot see this.

A couple of simple tweak options for this case:
(1) Define a private struct called Items, which is just a wrapper around a Vec<Item> and then hang the find_item function on it. The sell_item then looks like this:

fn sell_item(&mut self, hero : usize, name : &str ) {
		let item = self.items.find_item(name).unwrap();
		self.us.gold += item.cost / 2;

(2) Add a helper find_item associated function (rather than method):

fn find_item<'a>(v: &'a Vec<Item>, name: &str) -> Option<&'a Item> { ...} 
fn sell_item(&mut self, hero : usize, name : &str ) {
		let item = Self::find_item(&self.items, name).unwrap();
		self.us.gold += item.cost / 2;

In both cases, you want to make the compiler see that disjoint fields are being borrowed.

2 Likes

Is there anyway to achieve this disjoing tracking on the signature of the find_item I have? Or do I have to have the caller aware of the internal structure and do it directly?

There’s no way - if a method takes &self (or &mut self) and returns something with a lifetime tied to &self, the whole self is kept borrowed until that reference falls out of scope. This isn’t an issue when working with only immutable refs, but you’ll hit issues when a mutable one is in the mix.

Note that the caller of apply_turn doesn’t need to know the structure, but inside that method you’ll need to make the compiler see disjoint borrows if you’re doing them.

This might seem a bit jarring at first but you may start to appreciate it because it makes it easier to reason about control and data flow.

When you have a method with a signature like this:

fn method(&'a self) -> &'a T

The compiler doesn't know the relationship between Self and T. All it knows is that for &T to be valid, there must be a specific Self available from which its value is derived. In your case, it is obvious to us that T is a separable subcomponent of Self, but that is not something the method signature tells us. The T could equally well be something like a wrapper around Self and in such cases there can be no disjoint access to a T provided through this method.

How the signature should look like to tell this?

You cannot convey this in today's Rust. I'm assuming the context to your question is what you were asking in your other post, which is how to tell the compiler that only a subset of fields needs to be borrowed. This is not possible. The best approach, IMO, is to use associated functions with explicit arguments that represent the different borrows, and then borrow individual fields when passing them as args to the function.

You assumption is correct. I thought I understood this subject, but got confused again. When I access a field of a struct variable.field.method() does it borrow whole variable or only field?

Just field there so you can borrow multiple fields from variable concurrently (if you had more). But you cannot borrow all of self while a field is borrowed (if at least one borrow is mutable, that is).

1 Like

Probably worth mentioning that use of associated functions with field-by-field borrows might seem unnatural if you’re coming from Scala (or similar), where you just call methods left and right and access any which field is reachable from there. But, this won’t work well with Rust in many places.

The good news is you may end up liking the clarity of what’s borrowed how, right at the fn signature, rather than having to dig through method bodies to see if anything is mutating things you didn’t expect.

I just want to point that there are some discussions on the internals forum to allow fields in traits, which in turn would allow to use traits as views. See Fields in Traits - #12 by nikomatsakis - language design - Rust Internals. It would simplify this kind of problems. Thus, it is possible that in the future we will not need to borrow fields individually.

Virtual fields in traits will make traits more borrow conducive since you can’t borrow fields of a trait. It won’t really help cases like the one in this thread.

Not sure if I am missing something, but in that thread there are a few opposing arguments about if allowing partial borrows between traits. I suppose that there are some possible implementations of fields in traits that allows this use and other that do not allow it. When I commented on using views, being able to borrow from a view was the main point. Then they pointed that traits with fields should be able to make that work.

Right, I understand (I think). My point is this doesn’t help the case discussed in this thread or @avkonst’s other similar thread where there’re no traits in play - just structs and we already have fields on them to borrow from.

To make methods more borrow friendly, regardless of traits or fields on a struct, you’d need some way to convey to the compiler that a method is only borrowing some subset of self. In other words, the disjointness (or something akin to it) will need to be stated more abstractly via the signature. I’m not even sure what that would look like nor do I necessarily think it’s worthwhile given fields (associated fns) already allow this, albeit not abstractly. But maybe someone will come up with a nice scheme for this.

You have the point that for these particular examples there is not much need of these 'views'. With them we could do something like the following.

struct State
{
	items: Vec<Item>,
	us: Vec<Person>,
	more_fields: MoreFields,
}

trait HasItems
{
	let mut items: Vec<Item>,
	fn find_item(&self,name:&str) -> &Item { ... }
}

impl HasItems for State
{
	let mut items = self.items;
}

fn sell_item(&mut self, hero : usize, name : &str ) {
	let item = self.find_item(name).unwrap();//Only borrows 'items', since it is the field of the HasItems trait
	self.us.gold += item.cost / 2;
}

What advantages could this have over moving items into its own struct? I see two reasons:

  • The calling syntax would have one less access. Not very important.
  • If we need to resolve a similar problem for sets of fields with some overlap, it could be easily done with views.

Additionally, the development on this kind of traits could end up with automatic inference of views for each function. So for this case, if the HasItems trait was automatically inferred as the Self of find_item, then the code would have worked since the beginning.

You don’t need to move items to its own struct - that was one suggestion for demo purposes. The associated fn is more appropriate if there’s no other reason to create a new struct.

Putting a trait here just wraps the concrete data structures into something more abstract but with the same end state - you have disjoint borrows available.

The virtual fields in a trait help when you have generic code working with trait bounds. Today all you have is methods (or fns) there and there’s no way to do disjoint borrows at all and so you’re stuck. But this is in a generic/abstract scenario; when you have concrete types it doesn’t buy you anything.

How should the above example be modified to benefit from more abstract HasItems, like the above, and achieve disjoint borrows?

The link @nakacristo gave has an example using the “view” technique which in turn leverages the virtual fields in a trait. It’s a strawman because this feature doesn’t exist so specifics may differ when/if we get there. That example, IMO, is perhaps not that great because it demonstrates the feature in a case where you can already do disjointness because you have the concrete struct in hand. A better example would be solving issues like the one outlined in the RFC where all you have is a generic trait.

1 Like