ruststructreferencecloning

Is there a way to wrap an &str to a &T(String) without cloning?


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?


Solution

  • 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.

    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:

    1. 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);
    }
    
    1. 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);

    2. Some sort of string interning system so you only have to pass around integer Ids.

    3. Use Rc / Arc everywhere to avoid deciding on ownership at every function call boundary.

    4. Just clone. Maybe that's fine.

    ... and probably many more. But I'll leave it at that for now.