Hey folks. Curious what this forum's thoughts would be on something that I've run into a few times and have yet to find a clean solution for:
I'll occasionally have a type, say:
struct RichStruct {
private_data: String,
foo: String
}
This type is used internally, and I want to expose a type to an API which is similar:
pub struct APIStruct {
pub public_data: String,
}
Note that while there are shades of inheritance here, that's not really what I'm after. These types are usually convertible but not total overlaps, otherwise I'd just embed the "super" type.
Converting from RichStruct
to APIStruct
is pretty straightforward, as long as I'm good with copying:
impl From<&RichStruct> for APIStruct {
fn from(value: &RichStruct) -> Self {
APIStruct {
public_data: value.public_data.to_owned(),
}
}
}
However often I'd like this to act as a "view", letting me pass around a derived ref without allocating (I guess I'm not sure this is allocating, but I'm assuming to_owned
is going to cost me here). Specifically, I'd love to be able to call .into
on a &RichStruct
, and get an &APIStruct
.
Imagining that the APIStruct
type is useful to some utility methods or other common code, the general case would be something like this:
struct Thing {
data: RichStruct,
}
impl Thing {
fn do_stuff(&self) {
process_data(
// This would require impl From<&RichStruct> for &APIStruct, which doesn't exist
&(self.data).into(),
);
}
}
fn process_data(pub_data: &APIStruct) -> String {
String::default()
}
The main issue here is obviously creating the "temporary" &APIStruct
ref, which won't work. Here's an example of an attempt that will result in cannot return reference to temporary value
, but illustrates the idea.
impl<'a> From<&'a RichStruct> for &'a APIStruct {
fn from(value: &RichStruct) -> Self {
&APIStruct {
public_data: value.private_data,
}
}
}
I tried some creative stuff like boxing the ref, but eventually decided to just clone
everywhere. But it still feels bad to allocate all the data in these structs just so I can do a type conversion, when I technically only need refs to the shared inner data types.
I think the rust-y way to do this might be to derive common traits and change the signature of process_data
to accept impl PublicData
or whatever the trait is, which both types implement. In the trivial case above, this would obviously work great - but I can think of a few reasons (e.g. calling any method that accepts &self
on APIStruct
inside process_data
) that makes this become quite complex quickly, when the underlying idea (I own a struct, I have a different-but-impl From
-able type that I would like a reference of without copying the original struct) seems simple and rust-friendly.
I'm curious if this is far afield of other folks' experiences, or maybe there's a rust pattern I'm unaware of. I feel like lifetimes should give me the ability to do this, since the data is never copied, and the struct is just a type, but I think my understanding of memory is too high level to reason about what I'm missing.