So, you could have 3*N methods:
fn into_publish_request(self) -> PublishRequest;
fn as_publish_request(&self) -> &PublishRequest;
fn as_mut_publish_request(&mut self) -> &mut PublishRequest;
fn into_publish_response(self) -> PublishResponse;
fn as_publish_response(&self) -> &PublishResponse;
fn as_mut_publish_response(&mut self) -> &mut PublishResponse;
...
Macros can help generate the implementations, although you will still need to write out the name of every method at least once due to macro hygiene.
...but, my preferred strategy is to try to keep it down to 3 + N methods (give or take a few!).
The goal is to make it possible to write something like the following:
// (note: these are not to be read as sequential lines of code,
// but rather, distinct examples)
let x = msg.invalid().expect("expected invalid!"); // <-- by value
let x = msg.as_ref().publish_response().unwrap(); // <-- by ref
let x = msg.as_mut().publish_request().unwrap(); // <-- by mut ref
Here's a rough draft of what I have in mind. Beware; it introduces a generic type which will be unpleasant to have appear in your public API, so I hope SupportedMessage is internal!
Playground link
First, I make a type for every variant. This isn't strictly necessary, but it helps make things more uniform, which could help if you were to try to generate some of the implementations that follow by wrapping your struct definition in a macro.
#[derive(Clone)] struct A(Vec<i32>);
#[derive(Clone)] struct B;
#[derive(Clone)] struct C(String, String);
Next, we define three types for by-value, borrowed, and mutably borrowed variants of our type. All three can be defined in terms of a single generic type:
#[derive(Copy,Clone)]
enum Thing_<A_,B_,C_> {
A(A_),
B(B_),
C(C_),
}
#[derive(Clone)]
struct Thing(Thing_<A,B,C>);
// (not actually sure if it's better for these lifetimes to be the same
// or different; should hardly matter since it's an enum)
#[derive(Copy,Clone)]
struct ThingRef<'a,'b,'c>(Thing_<&'a A, &'b B, &'c C>);
struct ThingMut<'a,'b,'c>(Thing_<&'a mut A, &'b mut B, &'c mut C>);
Conversions are added from the by-value type to the borrowed ones:
impl Thing {
fn as_ref<'a>(&'a self) -> ThingRef<'a, 'a, 'a> {
ThingRef(match self.0 {
Thing_::A(ref a) => Thing_::A(a),
Thing_::B(ref b) => Thing_::B(b),
Thing_::C(ref c) => Thing_::C(c),
})
}
fn as_mut<'a>(&'a mut self) -> ThingMut<'a, 'a, 'a> {
ThingMut(match self.0 {
Thing_::A(ref mut a) => Thing_::A(a),
Thing_::B(ref mut b) => Thing_::B(b),
Thing_::C(ref mut c) => Thing_::C(c),
})
}
}
And then we can implement a matching function for each enum variant, similar to Result::{ok,err}
. You don't have to make these return Option
but I do because it's more versatile than panicking inside (e.g. you can do .unwrap_or_else(|| panic!())
at the callsite for a more useful line number).
This just begs for macro codegen:
impl<A_,B_,C_> Thing_<A_,B_,C_> {
fn a(self) -> Option<A_> {
match self {
Thing_::A(a) => Some(a),
_ => None,
}
}
fn b(self) -> Option<B_> {
match self {
Thing_::B(b) => Some(b),
_ => None,
}
}
fn c(self) -> Option<C_> {
match self {
Thing_::C(c) => Some(c),
_ => None,
}
}
}
With that, you can use it like this:
fn main() {
let mut x = Thing(Thing_::A(A(vec![])));
x.as_mut().0.a().unwrap().0.push(0);
assert_eq!(0i32, x.as_ref().0.a().unwrap().0[0]);
}
To improve ergonomics, you could also impl Deref{,mut} for Thing{,Ref,Mut}
, which would let you replace e.g. x.0.a()
with just x.a()
. I didn't bother with this.
Of course, this strategy works much, much better for types that are already meant to be generic, so that you don't have to introduce some terrible hack like Thing_
. For instance, this is an entirely reasonable and not-at-all-hacky way for working with a type such as These
:
enum These<A,B> {
JustThis(A),
JustThat(B),
ThemBoth(A, B),
}
impl<A,B> These<A,B> {
// borrowing functions
fn as_ref<'a>(&'a self) -> These<&'a A, &'a B>;
fn as_mut<'a>(&'a mut self) -> These<&'a mut A, &'a mut B>;
// single-variant matchers
fn just_this(self) -> Option<A>;
fn just_that(self) -> Option<B>;
fn them_both(self) -> Option<(A,B)>;
// ...
}