In Rust, I frequently wrap Strings with custom structs for the purpose of disambiguating trait behavior. For example, I might have a data access trait called IGetByField<T,F> implemented like so:
pub struct ParentObjectId(pub String);
impl<'r> IGetByField<Object, ParentObjectId> for ObjectDao<'r> {
fn get_by_field(&self, parent_object_id: &ParentObjectId) -> Vec<Org> {
let connection = &mut unpool!(self.pool.get());
DB::get_object_by_parent_id(connection, &parent_object_id.0)
}
}
My question is, when invoking this method on the ObjectDao and wrapping a string ID, is there a way to go from a &str type to a &ParentObjectId without cloning the id reference?
i.e. my current code:
let some_parent_id: &str = "some_id";
let object = object_dao.get_by_field(&ParentObjectId(some_parent_id.clone()));
Is there a way to do this without cloning?
If there's a will, there's a way:
This is of course pretty horrible, but it illustrates a point.
A
Stringis an owned buffer of bytes with a capacity and a length. It will deallocate this buffer when it is dropped. It's effectively aVec<u8>that enforces itself to beutf-8encoded.A
&stris a borrowed reference to a sequence ofutf-8encoded bytes.These two are not the same, and cannot be used interchangibly. A
Stringcan give you a&str, but not the other way around. Not every&strcomes from aString, so a&strhas no way of giving you a reference to a triple of(pointer, length, capacity)anywhere in memory that would represent itself.Note: please don't use the above code snippet in production. While it will most likely work, it violates the safety invariants of
String::from_raw_partsand therefore might become undefined behavior in future versions of Rust.The problem that you have is very common though. There are a few ways of solving this:
Cow. This type's whole purpose is to encapsulate this kind of dichotomy:Make your id type not require ownership:
struct ParentObjectId<'a>(&'a str);. If you need to storeParentObjectsIds, use a separate type:struct ParentObjectIdStored(String);Some sort of string interning system so you only have to pass around integer Ids.
Use
Rc/Arceverywhere to avoid deciding on ownership at every function call boundary.Just clone. Maybe that's fine.
... and probably many more. But I'll leave it at that for now.