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:
fn foo(id: &str) {
let frankenstring = std::mem::ManuallyDrop::new(unsafe {
String::from_raw_parts(id.as_ptr() as *mut u8, id.len(), id.len())
});
call_whatever_function_wants_the_string(&frankenstring);
}
This is of course pretty horrible, but it illustrates a point.
A String
is an owned buffer of bytes with a capacity and a length.
It will deallocate this buffer when it is dropped.
It's effectively a Vec<u8>
that enforces itself to be utf-8
encoded.
A &str
is a borrowed reference to a sequence of utf-8
encoded bytes.
These two are not the same, and cannot be used interchangibly.
A String
can give you a &str
, but not the other way around.
Not every &str
comes from a String
, so a &str
has 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_parts
and 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:use std::borrow::Cow;
pub struct ParentObjectId<'a>(pub Cow<'a, str>);
pub fn foo(id: &str) {
let po = ParentObjectId(id.into());
call_whatever_function_wants_the_po_id(&po);
}
pub fn bar(id: String) {
let po = ParentObjectId(id.into());
call_whatever_function_wants_the_po_id(&po);
}
Make your id type not require ownership: struct ParentObjectId<'a>(&'a str);
. If you need to store ParentObjectsIds
, use a separate type: struct ParentObjectIdStored(String);
Some sort of string interning system so you only have to pass around integer Ids.
Use Rc
/ Arc
everywhere 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.