Cows and structs fields interaction

Let's say I define a structure

struct Vehicle {
  motor: Motor,
  brand: Brand
}

and I'd like to manipulate Cow<Vehicle>s. If I have a v: Cow<Vehicle>, I want that v.motor be of type Cow<Motor> so that I can pass it around as a Cow<...>.

Is it possible to do so? As it current stands, it seems like v.motor is of type &Motor only, even if v is originally a Cow::Owned

Passing around v.motor as a value of type Cow<Motor> means in particular, that it might be owned. Thinking just about the owned case, if you have a v of type Vehicle and want to “pass around” v.motor as a value of type Motor, how to get the ownership? You could deconstruct the vehicle by taking out the motor, then pass that around. Is that the intention? Otherwise, if the intention is to keep the vehicle intact, you won’t ever be able to get a (useful[1]) Cow<Motor> out of it, and you only get &Motor anyways.

It’s hard/impossible to answer this question with the example at hand, since it’s only some non-realistic toy demonstration code, but if you have a specific use-case you could try to figure out the ownership story to see is using Cow in the way you tried to is at all reasonable in the first place. Cow is very publicly just an enum between two cases, for typical (i.e. sized) types that’s Cow<T> combining the cases of an owned T or a borrowed &T. (If you want more precision, also paint the lifetime into the picture, and Cow<'a, T> is either T or &'a T.) Reasoning about Cow in general is thus always possible by simply reasoning about both cases separately. If ownership and borrowing works in both cases, then your use-case works, (and could always be implemented e.g. via match if no other convenience methods are fitting) otherwise it doesn’t.


  1. i.e. unless you’ll always have it in the borrowed variant ↩︎

4 Likes

You could deconstruct the vehicle by taking out the motor, then pass that around. Is that the intention?

Yes, that would be my intention. The actual structures that I wanted to Cow-ify were Expr, Stmt, and the like for an AST. I currently have two visitor traits for this AST, one is consuming what it's traversing, and the other is only taking AST nodes by reference. I'd prefer to have a common Visitor trait that could encompass both consumption and taking by reference, and it looks like to me that using Cow could be a solution. But I would need somehow to deconstruct a Cow<MyStruct> into Cow<MyField>.

More generally, if you have functions that take ownership of an object, and other that take only by shared reference, and they proceed recursively on these object by calling other similar functions, it seems to me that you need to separate both worlds: functions that take ownership and those who don't. And I'd prefer to have one common interface.

I didn't think about matching directly, it's true that I could do that indeed, that would help to reach the goal of having a single Visitor trait.

I think I'm still missing something.

Even if I can deconstruct my Cows and pass them around, this is still very, very painful to do. Let's say I have two structures:

pub struct Block {
  stmts: Vec<Stmt>,
  ret: Expr,
}

pub enum Stmt {
  While(Expr, Block),
  Assign(Var, Expr),
}

and a similar enum for expressions. If I want to define a substitution function that operates on the AST, but only clones when necessary, I guess I have to do this using Cows.

What I would like to do intuitively is:

pub fn subst<'a>(self: Cow<'a, Self>, x: Var, y: Expr) -> Cow<'a, Self> {
  match self {
    Stmt::While(cond/*: Cow<'a, Expr>*/, b/*: Cow<'a, Block>*/) => Cow::Owned(cond.subst(x, y)?, b.subst(x, y)?),
    /* */
  }
}

And the same for structures: if self: Cow<'a, Block> then self.ret: Cow<'a, Expr>. I can't see the reason why it wouldn't be possible to have such things. Implementing it myself would probably need some smart use of Try, because in the subst function, we map to the identity if there is no substitution made in any of the lower nodes, otherwise we create a new item. Without Try or proper handling in match, it would be really tedious I think, needing first to match on self to know if I'm using a borrow or have ownership, and then for each branch, compute the subsitutions, and if any return a new owned element, compute a new owned element, otherwise return the reference wrapped in Cow::Borrowed

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.