Reputation: 1070
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?
Upvotes: 0
Views: 236
Reputation: 3714
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.
Upvotes: 1