Implement From for a ref type into another ref type

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.

For From/Info you generally do need to copy the contents. If you can implement a & reference view on a value, in cases where copying is not needed, then the AsRef trait is appropriate.

But in the general case you do need Rc or Arc to share values between different owners.

It is allocating.

If the data is immutable, you could use Arc<str> instead of String, and the clones will be cheap. Or something like Arc<Mutex<String>> if you need mutation.

If we're talking about something with literally one field, you may be able to do the reference conversion using unsafe. But this is only sound if you can guarantee the layouts are the same, and that any invariants of the target type are respected.

1 Like

Thank you for clarifying! I realize based on these answers that since I've mostly been in a single threaded environment, I'm thinking more about allocation and less about threads/reference sharing when approaching this, which helps me align my mental model better.

The unsafe example is also helpful for my mental model. #[repr(transparent)] makes more sense as a result too.

I think probably the right thing to do is to either use traits for shared utility code, or to introduce a wrapper type like Arc to be specific about the expectations here. As usual, there's good reasons for the friction here, I continue to appreciate Rust's design!

Rust references can't exist on their own. They don't hold any data. They are only a temporary permission to view data already stored elsewhere. Since From has no place to store owned data, it has no way to return a temporary reference to already-stored data.

If you want to store or return data "by reference", then Box is the correct reference type for this, not the temporary loan & type.

1 Like