I I have written a Visitor for crawling over a large data structure. The
whole thing is fairly long (about 700loc).
The problem is that the entities it passes around are currently
references and I now have a use case which needs to mutate them. I am
not terribly keen on duplicating the code for obvious reasons. However allowing the mutability generally breaks other use cases.
I know that I can't make the code generic over the mutability which is
what I would really need. It seems to me the only solution is to macro
out the code. I could generate a version with mutable reference and a
version with immutable versions.
Have I missed another option? Is there macro out there for achieving
this already?
You could make the visitor generic over the reference type and then write code against a trait that may or may not be implemented over a mutable reference. You can also provide default implementations for the trait methods, potentially getting rid of large amounts of duplicated code: Playground
pub trait Visit {
type Ref<'a, T: 'a>: Deref<Target = T>; // the reference type, mutable or immutable
fn visit_string(&mut self, value: Self::Ref<'_, String>) {
dbg!(&*value);
}
fn visit_u32(&mut self, value: Self::Ref<'_, u32>) {
dbg!(&*value);
}
}
struct Visitor;
impl Visit for Visitor {
type Ref<'a, T: 'a> = &'a T;
}
struct VisitorMut;
impl Visit for VisitorMut {
type Ref<'a, T: 'a> = &'a mut T;
}
That's really interesting. I have given this a go with something a bit closer to my code base -- I am using a Visitor will empty implementations, then a Walker which contains the tree traversal logic. In my real code, the Walker was a concrete implementation, so I have tried to move that also to a trait and use the same associated type. But, I can't get Rust to unify the types between the two traits, so it does not compile at the moment. What am I doing wrong?
In a default impl, how could the compiler possibly know the concrete associated type? Generics have to work with every eligible type (including Self) once they pass type checking, so "maybe this associated type will be OK for some concrete impls" isn't something the compiler base type checking on.
V::Ref<'a, T> may be any type; you can't pass a &String or a Self::Ref<'a, T> where a Ref<'a, String> is expected, because there's no guarantee whatsoever they are the same type.
The Ref associated type of the Walk trait is superfluous; the code can only ever work if it's exactly the same as V::Ref. So remove it. Once you remove it, you can see that the following line (for example) is nonsensical:
because 1. you are trying to pass the string by value, which isn't possible directly since it's a non-Copy type behind a reference; and 2. if V::Ref is mutable, you can't get a mutable reference from a place behind an immutable one. So this simply can't work, ever.
So, the mutability of the data structure is specified in two places -- the formal paramters to the method, and in the tree walking invocation.
What I don't see is a solution. The design pattern works perfectly well with both mutable and immutable references, but I cannot see how to make it do both, other than a macro. And having thought about the macro since I posted, I think even that requires a proc macro. Or give up and just duplicate the code.
You want to project a field with mutability based on the mutability of the reference it's behind. You'll have to create additional trait(s) for the projections.
Ah, okay, yes. Factor out the reference call to a third generic. That saves re-writing the visitor and walker but at the cost of still duplicating the call itself in GenRef. Oh dear.
I think that code duplication is probably the way forward at this point.
Thanks for your help! I've learned about the type system.